amobbs.com 阿莫电子论坛

 找回密码
 注册
搜索
bottom↓
回复: 132

记一次lwip中 遇到 pcb == pcb->next 的pcb死循环debug过程

  [复制链接]
(212723806)

打赏出0元收入0元

发表于 2013-10-11 17:44:06 | 显示全部楼层 |阅读模式
本帖最后由 kayatsl 于 2013-10-11 17:50 编辑



如图中, 在tcp_slowtmr() 中, 历遍 tcp_tw_pcbs 过程中, 其中一个 pcb -> next 指向pcb自身

导致while循环永久死在这里跳不出去, 程序死机..     这种问题, 虽然说必然会发生, 但发生的时间随机性很强, 很难捕捉到什么时候一定会出现, 所以这种bug很难解决

这种情况在国外论坛搜了下, 都是同一个回答: 当跑os的时候,有多个线程同时争抢就会导致这种问题产生

但到底怎么产生的, 和如何避免, 没有作详细介绍,也没有案例分析..

这现象以前也有遇到过, 其实这种情况通过写一个定时检测是否有pcb指向自身 和加入看门狗, 也能保证程序不会一直死在这,但这次一个偶然的机会,决定跟个所以然.



这次由于时间片之间程序运行的先后顺序不同, 会导致hardfault产生, hardfault没有截图, 系统是记录到我读取一个非法地址, 引致hardfault

从堆栈中跟踪得知, hardfault前跑的函数是 memp_malloc 中 memp_tab[type] = memp->next 引起的, 而memp 自身被指向了一个非内存地址, 再读取 ->next 必然导致出错

蓝色圈是我加入的条件断点, 来捕捉异常状态.

这里得知memp_tab[2] 中的地址指向出问题了, 但这和 pcb的死循环有什么关系呢? 到这里还是看不出头绪.. 思路断了, 那就回去继续跟踪 pcb的问题



既然已经知道死循环的 pcb是属于 tcp_tw_pcbs 链表里面的, 那就 ctrl+shift+f 把所有对 tcp_tw_pcbs 有操作的地方都找出来, 统统下条件断点

看执行到哪一句后会出现死循环的现象

程序果然乖乖的死在陷阱里面, 这样看来, 明显是运行了 tcp_reg 后才出现的现象, 但为什么运行这个后会导致死循环呢? 没什么头绪? 那就跟另一条线索




跟踪一下 memp_tab的情况,

图中红字部分已经说得差不多了,  这里给了我们一个很重要的线索, 就是 memp_tab[]->next 和 tcp_tw_pcbs -> local_ip 为什么会重叠了..

试图跟着这个函数返回一下, 看看会有什么情况



没错, 这里又重新给这个pcb赋值了, 然后又重新递回到上层做处理..

也就是说, 系统给我们分配了一个 正在使用的内存区域, 来处理新的东西, 那必然会导致和旧的东西发生冲突.. 要验证这句话? 继续下条件断点



图中可以看到, 进来的pcb, 本来是属于 active_pcbs 链表的, 刚从 active 链表中移除, 准备放到 timewait链表里面去, 但此时发现, 这个pcb的内存空间,正是tw_pcbs的内存空间

其实 TCP_REG 很简单, 就是把指针指一下, 就注册过去了,  但就是因为太简单了, 没有做任何判断, 所以自己指回了自己也不知道.

其实到这里, 可以在这个定义里面写个判断, 遇到指向自己的就不进行操作, 但这样还是没找到最原始导致的原因..  所以必须继续跟踪下去



再截一个图, 就是 死循环的图..  大家可以比较一下这几个图里面红圈圈着的地址,  截图顺序是按照程序执行顺序来的




回想一下, malloc的时候, 分配到同一个地址空间, 那明显就是上一次分配的时候, ->next指针又指回自己了, 验证这个猜想, 继续下条件断点..

果然不出所料, memp == memp->next , 而这个是分配的时候导致的还是释放的时候导致的, 暂时还不确定,

但是这个出错的现场, 可以在堆栈中继续追溯是哪里调用的, 但这里还不是第一现场, 为了还原第一现场, 那就继续跟着 callstack回去找



这图中很有意思..  这里程序开始的时候, 我使用 tcp_new 创建一个pcb, 而 tcp_new 又调用 malloc 来取出一个 pcb的内存空间

而我在 tcp_abort 这里打了个断点, 按道理来说, 我还没有 abort掉这个链接, 空间是不应该被释放掉的, 但奇怪的是 memp_tab[2] 中指向的地址,确实是我正在使用的地址

memp_tab[] 链表是空闲内存的链表, 而这个内存块是我正在使用的, 怎么会出现在空闲链表中??

越来越接近真相了,  就是这块内存申请后, 在使用中, 意外被别人释放了, 那罪灰祸首到底是谁??



那就继续下条件断点去捕捉释放前的现象,.

图中可以看到, memp_Free 地址是跟 memp_alloc一样的 ..  这里的原因是,我链接很频繁,一个pcb还没完全关闭, 又建立另一个链接, 所以会导致刚刚free出来的一个块, 马上又被用来使用了

所以这两个地址一样并不惊讶, 惊讶的是, 同一个地址, 为什么会出现在 memp_tab[2] 中???

这明显是 free完这个块, 然后 malloc到来用, 但用的过程中, 还没等我释放, 就被别的地方释放了, 所以导致这块正在使用的内存块会跑到了空闲内存块中去..

可以断定是free多了一次出问题了, 就好办了..



由上面的现象可以估计 是pcb的内存空间刚刚分配到, 很短时间又被释放了, 既然是这样, 就在 memp_free中下条件断点  当现在free的,刚好是刚刚malloc出来的内存块就断

因为正常情况下, free的肯定是旧的空间, 不可能刚刚malloc就被free掉的.  

等了一段时间, 中陷阱了..  其实看到这个现场没什么作用, 本来就已经猜到的事情,  而在这里下断点的目的, 是要跟踪, 到底是谁把它释放掉的.

看 call stack 就一目了然了..  继续跟回去 tcp_input中



这时候清晰了.. 这个pcb收到了关闭请求, 然后关闭掉pcb链接, 顺便把内存区域也 free掉了..




好了, 看到这里,  其实大家往回读就知道到底 pcb死循环是怎么引起的了...

我正在开启一条到服务器的链接, 但在等待确定链接已经保持的过程中, 意外被服务器关闭了, 而由于时间太短, 这时候, 我应用程序认为 服务器并没有连接上, 然后把这个tcp的控制块 abort掉

而 abort 的时候调用 free 内存的函数,  却不知道这块内存其实已经是free掉的了, 再free一次, 就会出现 memp_tab中 元素的 -next 指回自身.

而这时候再 malloc一个新内存空间的时候, 由于 next永远是指回同一块内存区域, 所以导致下次再分配的时候, 还是分配同一块空间给应用层

但应用层不知道分配的是同一块空间, 而继续使用, 然后就导致两条链接共用同一个内存区域.. 但lwip自己并不知道.

当两条链接最终注册入同一个 pcbs链表去管理的时候, 就会导致自己指回自己, 因为本来就是同一块内存,.. 最后就 pcb死循环了..

跟踪到此结束




跟踪完当然是解决问题了..



在 memp_free中 ,free之前做一个判断 由于type == 2 属于 tcp_pcb, 而 tcp_pcb 的 前四个字节是不可能为0x00的,

所以可以判断为当 memp->next 为 NULL 的时候, 这片内存已经释放过了, 就不再重新释放, 直接跳到程序末尾, 解锁

经验证, 方法正常, 因为 出问题通常是 tcp_pcb 的问题,因为 tcp控制太复杂了, 所以目前只控制 type==2 的地方就可以保证程序正常了.

到此 结贴.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
(212722561)

打赏出0元收入0元

 楼主| 发表于 2013-10-11 18:04:51 | 显示全部楼层
大家有什么好方法避免的..  提一下.. 集思广益啊...
(212722483)

打赏出0元收入0元

发表于 2013-10-11 18:06:09 | 显示全部楼层
我仅顶一下 表示我看不懂……
(212717936)

打赏出0元收入0元

 楼主| 发表于 2013-10-11 19:21:56 | 显示全部楼层
因为内存空间是直接分配出来的..  所以最后解决只能这么判断

不过呵..

/* No sanity checks
* We don't need to preserve the struct memp while not allocated, so we
* can save a little space and set MEMP_SIZE to 0.
*/
#define MEMP_SIZE           0

这个memp_sizze 其实可以定义一下, 这里定义了4的话 , 每个头部都会有4个空余字节.

然后每次 在malloc中 memp = (struct memp*)((u8_t*)memp + MEMP_SIZE); 之前, 都先 memp->next = 赋一个固定的值;

然后 free的时候, 检查下 ->next 指针 如果不是这个固定值的话, 就说明已经被free过了, 如果还是固定值的话, 就free一下..

理论上想了下, 应该可以这么做, 只是每个区间都再加4个字节的话.. 这片内存区域耗用还是挺庞大的..
(212717522)

打赏出0元收入0元

 楼主| 发表于 2013-10-11 19:28:50 | 显示全部楼层
网上都说 多线程访问导致的, 而这次的情况, 确实也是因为两条线程都对一个对象访问导致的..

但由于我调用的是 rawapi , 所以在connect的时候, 必须vtaskdelay 将cpu控制权让出来给 lwip内核来跑, 才能connect上服务器

这个等待时间很难断定 , 网速无法保证, 而就在等待过程中, 刚连上服务器,又被服务器踢掉, 踢掉的时候, 是用lwip的内核线程来处理的.

所以...不从内存分配的方向去解决的话, 这问题基本是无解了...

希望给遇到同样问题的人, 看到之后, 能快速定位到自己程序哪里引起这问题吧..

先写到这了...
(212710514)

打赏出0元收入0元

发表于 2013-10-11 21:25:38 | 显示全部楼层
感谢楼主共享自己的经验, lwip 确实有一些莫名奇妙的 bug.
(212708622)

打赏出0元收入0元

发表于 2013-10-11 21:57:10 | 显示全部楼层
这个分析很到位啊。前面有个帖子似乎也提到了同样的问题。
(212708614)

打赏出0元收入0元

发表于 2013-10-11 21:57:18 | 显示全部楼层
本帖最后由 yiyu 于 2013-10-11 22:09 编辑

lwip是单线程的,所有api都是通过队列通讯的,使用row是在裸奔情况下,应用和lwip在一个线程内,
(212708196)

打赏出0元收入0元

发表于 2013-10-11 22:04:16 | 显示全部楼层
yiyu 发表于 2013-10-11 21:57
lwip是单线程的,所有api都是通过队列通讯的,

lwip是单线程,如何理解?是不能用在使用操作系统的多任务的环境中?还是指?
(212707397)

打赏出0元收入0元

发表于 2013-10-11 22:17:35 | 显示全部楼层
本帖最后由 yiyu 于 2013-10-11 23:19 编辑

lwip在os中作为一个独立线程实现,外部应用只能使用api调用,可以看一下api的实现,很多都是把row函数通过消息队列发送到lwip线程调用
api投递消息
err_t
tcpip_apimsg(struct api_msg *apimsg)
{
  struct tcpip_msg msg;
  
  if (mbox != SYS_MBOX_NULL) {
    msg.type = TCPIP_MSG_API;
    msg.msg.apimsg = apimsg;
    sys_mbox_post(mbox, &msg);
    sys_arch_sem_wait(apimsg->msg.conn->op_completed, 0);
    return ERR_OK;
  }
  return ERR_VAL;
}

lwip线程
static void
tcpip_thread(void *arg)
{
  struct tcpip_msg *msg;
  LWIP_UNUSED_ARG(arg);

#if IP_REASSEMBLY
  sys_timeout(IP_TMR_INTERVAL, ip_reass_timer, NULL);
#endif /* IP_REASSEMBLY */
#if LWIP_ARP
  sys_timeout(ARP_TMR_INTERVAL, arp_timer, NULL);
#endif /* LWIP_ARP */
#if LWIP_DHCP
  sys_timeout(DHCP_COARSE_TIMER_MSECS, dhcp_timer_coarse, NULL);
  sys_timeout(DHCP_FINE_TIMER_MSECS, dhcp_timer_fine, NULL);
#endif /* LWIP_DHCP */
#if LWIP_AUTOIP
  sys_timeout(AUTOIP_TMR_INTERVAL, autoip_timer, NULL);
#endif /* LWIP_AUTOIP */
#if LWIP_IGMP
  sys_timeout(IGMP_TMR_INTERVAL, igmp_timer, NULL);
#endif /* LWIP_IGMP */
#if LWIP_DNS
  sys_timeout(DNS_TMR_INTERVAL, dns_timer, NULL);
#endif /* LWIP_DNS */

  if (tcpip_init_done != NULL) {
    tcpip_init_done(tcpip_init_done_arg);
  }

  LOCK_TCPIP_CORE();
  while (1) {                          /* MAIN Loop */
    sys_mbox_fetch(mbox, (void *)&msg);
    switch (msg->type) {
#if LWIP_NETCONN
    case TCPIP_MSG_API:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
      msg->msg.apimsg->function(&(msg->msg.apimsg->msg));
      break;
#endif /* LWIP_NETCONN */

    case TCPIP_MSG_INPKT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
#if LWIP_ARP
      if (msg->msg.inp.netif->flags & NETIF_FLAG_ETHARP) {
        ethernet_input(msg->msg.inp.p, msg->msg.inp.netif);
      } else
#endif /* LWIP_ARP */
      { ip_input(msg->msg.inp.p, msg->msg.inp.netif);
      }
      memp_free(MEMP_TCPIP_MSG_INPKT, msg);
      break;

#if LWIP_NETIF_API
    case TCPIP_MSG_NETIFAPI:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: Netif API message %p\n", (void *)msg));
      msg->msg.netifapimsg->function(&(msg->msg.netifapimsg->msg));
      break;
#endif /* LWIP_NETIF_API */

    case TCPIP_MSG_CALLBACK:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK %p\n", (void *)msg));
      msg->msg.cb.f(msg->msg.cb.ctx);
      memp_free(MEMP_TCPIP_MSG_API, msg);
      break;

    case TCPIP_MSG_TIMEOUT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: TIMEOUT %p\n", (void *)msg));
      sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg);
      memp_free(MEMP_TCPIP_MSG_API, msg);
      break;
    case TCPIP_MSG_UNTIMEOUT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: UNTIMEOUT %p\n", (void *)msg));
      sys_untimeout(msg->msg.tmo.h, msg->msg.tmo.arg);
      memp_free(MEMP_TCPIP_MSG_API, msg);
      break;

    default:
      break;
    }
  }
}

(212703679)

打赏出0元收入0元

 楼主| 发表于 2013-10-11 23:19:33 | 显示全部楼层
如果是用 netconn 来做的话, 确实是通过调用 tcpip_apimsg 来传递信息

但也可以直接调用raw_api 和设置自己的回调函数来做处理   lwip本身也是提供 raw_api 来给应用做更快速的处理的..  处理得到位就不会出现争抢的现象了.
(212661333)

打赏出0元收入0元

发表于 2013-10-12 11:05:19 | 显示全部楼层
kayatsl 发表于 2013-10-11 23:19
如果是用 netconn 来做的话, 确实是通过调用 tcpip_apimsg 来传递信息

但也可以直接调用raw_api 和设置自 ...

lwip到底能否商用还值得商榷。附网上看到一篇文章。


成功实现了LWIP的keepalive功能

原来的lwip没有启动keepalive功能,导致tcp客户端工作不可靠,主要就是无法处理服务器的断线、断网、down机等异常。表现是服务器故障后,tcp客户端总是等待无法返回,造成锁死。

处理方法:

1,使用TCPkeepalive功能,定时探测连接的状态,当发生掉线时,自动关闭连接,正常返回。

2,检测网线状态PHY寄存器中有标准位。(没有上一种方法好。)

下面详细介绍如何启动keepalive

1,打开keepalive的标志使能。

2,修改keepalive各个计数值,主要是改小,方便测试。

3,在pcb中需要置位keepalive的一个选项。pcb->so_options |= SOF_KEEPALIVE;

4,修改pcb的一处bug,该bug可以通过给我汇款获得。

启动了keepalive才是真正的tcp连接,用起来稳定可靠,异常爽快。


by 天远易 发表于:2012/7/13 10:04:43
(212659654)

打赏出0元收入0元

 楼主| 发表于 2013-10-12 11:33:18 | 显示全部楼层
fengyunyu 发表于 2013-10-12 11:05
lwip到底能否商用还值得商榷。附网上看到一篇文章。

keepalive 只是一个心跳包的作用..

本身应用层有设计心跳包机制, 就不需要底层再做了..

(212659512)

打赏出0元收入0元

发表于 2013-10-12 11:35:40 | 显示全部楼层
kayatsl 发表于 2013-10-12 11:33
keepalive 只是一个心跳包的作用..

本身应用层有设计心跳包机制, 就不需要底层再做了..

原文中的“4,修改pcb的一处bug,该bug可以通过给我汇款获得。"
(212650954)

打赏出0元收入0元

 楼主| 发表于 2013-10-12 13:58:18 | 显示全部楼层
fengyunyu 发表于 2013-10-12 11:35
原文中的“4,修改pcb的一处bug,该bug可以通过给我汇款获得。"

哈哈哈... 肯定不是这个bug... 严格来说 这个bug并不属于pcb自身的..
(212626194)

打赏出0元收入0元

发表于 2013-10-12 20:50:58 | 显示全部楼层
密切关注中......
(212621443)

打赏出0元收入0元

发表于 2013-10-12 22:10:09 | 显示全部楼层
高级技术帖,顶起
(212488192)

打赏出0元收入0元

发表于 2013-10-14 11:11:00 | 显示全部楼层
我最近出现了一个lwip的问题,也是连接上的。。。还在查
(212488131)

打赏出0元收入0元

发表于 2013-10-14 11:12:01 | 显示全部楼层
keepalive并不一定需要,网上那个说开启了keepalive才是真的tcp我认为是错误的,完全可以通过自定义心跳包解决
(212487863)

打赏出0元收入0元

 楼主| 发表于 2013-10-14 11:16:29 | 显示全部楼层
farmerzhangdl 发表于 2013-10-14 11:12
keepalive并不一定需要,网上那个说开启了keepalive才是真的tcp我认为是错误的,完全可以通过自定义心跳包 ...

哎.. 现在还是会遇到 指向自己, 只不过导致的原因不是原来的地方, 跑到新的地方了.. 而且跑大半天甚至几天才能触发一次.. 更不好跟了
(212485639)

打赏出0元收入0元

发表于 2013-10-14 11:53:33 | 显示全部楼层
本帖最后由 fengyunyu 于 2013-10-14 17:38 编辑
kayatsl 发表于 2013-10-14 11:16
哎.. 现在还是会遇到 指向自己, 只不过导致的原因不是原来的地方, 跑到新的地方了.. 而且跑大半天甚至几 ...


LZ是做Server还是做Client?据说做Server比较稳定,做Client则问题较多。
(212485032)

打赏出0元收入0元

 楼主| 发表于 2013-10-14 12:03:40 | 显示全部楼层
同时做 TCP的Client   UDP的Server 和 Client
(212484980)

打赏出0元收入0元

 楼主| 发表于 2013-10-14 12:04:32 | 显示全部楼层
fengyunyu 发表于 2013-10-14 11:53
LZ是做Server还是做Client?据说做Server比较稳定,做Client则问题较多。另外,裸奔似乎更稳定些。公司有 ...

做Server比较稳定,做Client则问题较多


这个说法我也看到过..  后来经过一段时间后判断是他不会用..
(212474164)

打赏出0元收入0元

发表于 2013-10-14 15:04:48 | 显示全部楼层
    npcb = tcp_alloc(pcb->prio);
(212474144)

打赏出0元收入0元

发表于 2013-10-14 15:05:08 | 显示全部楼层
    npcb = tcp_alloc(pcb->prio);  后面加个         if(npcb == tcp_active_pcbs ){
                tcp_close(npcb)        ;
                return ERR_OK;
        }
(212474108)

打赏出0元收入0元

发表于 2013-10-14 15:05:44 | 显示全部楼层
判一下这个npcb 是不是在tcp_active_pcbs 里面
(212472382)

打赏出0元收入0元

 楼主| 发表于 2013-10-14 15:34:30 | 显示全部楼层
inurl 发表于 2013-10-14 15:05
npcb = tcp_alloc(pcb->prio);  后面加个         if(npcb == tcp_active_pcbs ){
                tcp_close(npcb)        ;
                retu ...


这个需要历遍一下 active的链表,  不一定就是第一个pcb

同时, 不一定在 active链表, 也可能在 tw链表..

但是我感觉, 避免发生, 总比发生了之后解决要好..

得找到发生的原因
(212445602)

打赏出0元收入0元

发表于 2013-10-14 23:00:50 | 显示全部楼层
我是好多设备只有一台发生过这个问题,但是没有查到原因,加了个狗,但是狗也没起作用。。。
(212405546)

打赏出0元收入0元

发表于 2013-10-15 10:08:26 | 显示全部楼层
mark               
(212324381)

打赏出0元收入0元

发表于 2013-10-16 08:41:11 | 显示全部楼层
mark
(212314893)

打赏出0元收入0元

发表于 2013-10-16 11:19:19 | 显示全部楼层
你先把hard_fault解决了吧, 应该跟lwip没关系, 除非是lwip主任务堆栈溢出或者是任务退出了
(212313153)

打赏出0元收入0元

 楼主| 发表于 2013-10-16 11:48:19 | 显示全部楼层
inurl 发表于 2013-10-16 11:19
你先把hard_fault解决了吧, 应该跟lwip没关系, 除非是lwip主任务堆栈溢出或者是任务退出了  ...

hardfault 就是因为memp指针指出了内存区域..

解决hardfault就是解决lwip的问题
(212312002)

打赏出0元收入0元

发表于 2013-10-16 12:07:30 | 显示全部楼层
kayatsl 发表于 2013-10-16 11:48
hardfault 就是因为memp指针指出了内存区域..

解决hardfault就是解决lwip的问题 ...

看来内存管理的水很深。
(212286882)

打赏出0元收入0元

发表于 2013-10-16 19:06:10 | 显示全部楼层
我发现tcp_tw_pcbs在tcp_close或者tcp_abort的时候不会释放, 只能等到tcp_alloc失败的时候才去释放
(212286656)

打赏出0元收入0元

发表于 2013-10-16 19:09:56 | 显示全部楼层
  我觉得有可能是一个代码重入的问题。
(212286579)

打赏出0元收入0元

发表于 2013-10-16 19:11:13 | 显示全部楼层
  我觉得有可能是一个代码重入的问题。导致了重复释放
(212235864)

打赏出0元收入0元

 楼主| 发表于 2013-10-17 09:16:28 | 显示全部楼层
inurl 发表于 2013-10-16 19:06
我发现tcp_tw_pcbs在tcp_close或者tcp_abort的时候不会释放, 只能等到tcp_alloc失败的时候才去释放 ...

tcp_close 是要等待服务器回复才会释放   abort 调用 abanden, 是马上释放的.

是不是重入问题还需要跟踪下, 现在这问题比之前更加不定时了,, 之前起码说一个小时内还能出错, 现在几天都没死一次, 真还原不了现场
(212235387)

打赏出0元收入0元

发表于 2013-10-17 09:24:25 | 显示全部楼层
kayatsl 发表于 2013-10-17 09:16
tcp_close 是要等待服务器回复才会释放   abort 调用 abanden, 是马上释放的.

是不是重入问题还需要跟踪 ...


说lwip稳定的,一般是做server。做client,LZ算是吃螃蟹的。
(212234607)

打赏出0元收入0元

发表于 2013-10-17 09:37:25 | 显示全部楼层
LZ好有耐心啊,这么一步一步跟进分析,收藏一下以后备用,我的LwIP没用在OS下面,跑得很正常
(212233436)

打赏出0元收入0元

 楼主| 发表于 2013-10-17 09:56:56 | 显示全部楼层
fengyunyu 发表于 2013-10-17 09:24
说lwip稳定的,一般是做server。做client,LZ算是吃螃蟹的。

应该不会吧... 做Client的人也不少的其实..

其实做Server和做Client, 感觉做起来差别倒不是很大..
(212193264)

打赏出0元收入0元

发表于 2013-10-17 21:06:28 来自手机 | 显示全部楼层
kayatsl 发表于 2013-10-17 09:16
tcp_close 是要等待服务器回复才会释放   abort 调用 abanden, 是马上释放的.

是不是重入问题还需要跟踪 ...

abort只在tcp_malloc的时候调用。你狂点tcp连接试试
(212191908)

打赏出0元收入0元

发表于 2013-10-17 21:29:04 | 显示全部楼层
inurl 发表于 2013-10-17 21:06
abort只在tcp_malloc的时候调用。你狂点tcp连接试试

那就相当于DoS攻击了。LWIP不管是做server还是client,这种情况都会崩溃
(212180034)

打赏出0元收入0元

 楼主| 发表于 2013-10-18 00:46:58 | 显示全部楼层
inurl 发表于 2013-10-17 21:06
abort只在tcp_malloc的时候调用。你狂点tcp连接试试

因为第一次malloc的时候找不到空的内存块的话, 会将一个现有但已经关闭的pcb释放掉来用, 释放就调用abort了.
(211853113)

打赏出0元收入0元

发表于 2013-10-21 19:35:39 来自手机 | 显示全部楼层
有啥发现没?
(211853081)

打赏出0元收入0元

发表于 2013-10-21 19:36:11 来自手机 | 显示全部楼层
inurl 发表于 2013-10-17 21:06
abort只在tcp_malloc的时候调用。你狂点tcp连接试试

不会的,限制tcp就不会崩溃
(211835165)

打赏出0元收入0元

 楼主| 发表于 2013-10-22 00:34:47 | 显示全部楼层
inurl 发表于 2013-10-21 19:35
有啥发现没?

这几天在忙别的东西..  明天再写个陷阱给他吧..
(211805117)

打赏出0元收入0元

发表于 2013-10-22 08:55:35 | 显示全部楼层
inurl 发表于 2013-10-21 19:36
不会的,限制tcp就不会崩溃

限制tcp,如何限制?
(211719574)

打赏出0元收入0元

发表于 2013-10-23 08:41:18 | 显示全部楼层
fengyunyu 发表于 2013-10-22 08:55
限制tcp,如何限制?

计数 and tcp_accept()拒绝
(211314866)

打赏出0元收入0元

发表于 2013-10-28 01:06:26 | 显示全部楼层
LZ,有新进展了么?
(211285650)

打赏出0元收入0元

发表于 2013-10-28 09:13:22 | 显示全部楼层
技术贴,mark一下。
(211285381)

打赏出0元收入0元

 楼主| 发表于 2013-10-28 09:17:51 | 显示全部楼层
fengyunyu 发表于 2013-10-28 01:06
LZ,有新进展了么?

哎.. 没连调试器的机子久不久都出问题.. 连着调试器的. 跑了一个星期都没出问题.. 郁闷...

偶尔有一次进去了. 结果忘了开中断, 截不到原始记录
(211074032)

打赏出0元收入0元

发表于 2013-10-30 20:00:20 | 显示全部楼层
kayatsl 发表于 2013-10-28 09:17
哎.. 没连调试器的机子久不久都出问题.. 连着调试器的. 跑了一个星期都没出问题.. 郁闷...

偶尔有一次进 ...

估计不用操作系统,裸奔能解决问题。最近也碰到过类似问题,最后改成RAW模式未再测试到问题。
(211058469)

打赏出0元收入0元

 楼主| 发表于 2013-10-31 00:19:43 | 显示全部楼层
fengyunyu 发表于 2013-10-30 20:00
估计不用操作系统,裸奔能解决问题。最近也碰到过类似问题,最后改成RAW模式未再测试到问题。 ...

整个系统都搭好了, 不能说不用操作系统

前两天找到解决方案了, 复现了两三次, 能够解决了.

再测试多几天, 确定没问题就可以结贴了...
(210917129)

打赏出0元收入0元

 楼主| 发表于 2013-11-1 15:35:23 | 显示全部楼层
kayatsl 发表于 2013-10-31 00:19
整个系统都搭好了, 不能说不用操作系统

前两天找到解决方案了, 复现了两三次, 能够解决了.



问题解决方案如上..  至今测试正常..

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
(210916906)

打赏出0元收入0元

 楼主| 发表于 2013-11-1 15:39:06 | 显示全部楼层
fengyunyu 发表于 2013-10-30 20:00
估计不用操作系统,裸奔能解决问题。最近也碰到过类似问题,最后改成RAW模式未再测试到问题。 ...

54楼 解决方案
(210916844)

打赏出0元收入0元

 楼主| 发表于 2013-11-1 15:40:08 | 显示全部楼层
inurl 发表于 2013-10-21 19:35
有啥发现没?

54楼 解决方案
(209898862)

打赏出0元收入0元

发表于 2013-11-13 10:26:30 | 显示全部楼层
mark 一下 新人学习了
(209707928)

打赏出0元收入0元

发表于 2013-11-15 15:28:44 | 显示全部楼层
kayatsl 发表于 2013-11-1 15:35
问题解决方案如上..  至今测试正常..

正在做的项目也用到lwip协议栈,也遇到你贴中死机的问题,能否发一下解决的相关代码,非常感谢!
(209707865)

打赏出0元收入0元

发表于 2013-11-15 15:29:47 | 显示全部楼层
(209704627)

打赏出0元收入0元

发表于 2013-11-15 16:23:45 | 显示全部楼层
kayatsl 发表于 2013-11-1 15:40
54楼 解决方案

我顶。。。。。真不错啊。。。。你算是第一个把LWIP进行商用了吗,真正的用于产品上了的吧。
(209464707)

打赏出0元收入0元

 楼主| 发表于 2013-11-18 11:02:25 | 显示全部楼层
kinsno 发表于 2013-11-15 16:23
我顶。。。。。真不错啊。。。。你算是第一个把LWIP进行商用了吗,真正的用于产品上了的吧。 ...

我肯定不是第一个..   很早就有人商用了其实.. 只不过国内拿出来开帖探讨的不是很多...
(209464463)

打赏出0元收入0元

 楼主| 发表于 2013-11-18 11:06:29 | 显示全部楼层
gotearcom 发表于 2013-11-15 15:28
正在做的项目也用到lwip协议栈,也遇到你贴中死机的问题,能否发一下解决的相关代码,非常感谢! ...

死机方式像, 不代表就是这种死法... 你得设陷阱跟踪...

我改栈加入的代码基本都贴出来了.. 其余代码都是产品上的了, 需保密..
(209446147)

打赏出0元收入0元

发表于 2013-11-18 16:11:45 | 显示全部楼层
学习了 !    谢谢楼主!
(209422109)

打赏出0元收入0元

发表于 2013-11-18 22:52:23 | 显示全部楼层
kayatsl 发表于 2013-11-18 11:02
我肯定不是第一个..   很早就有人商用了其实.. 只不过国内拿出来开帖探讨的不是很多... ...

你们的项目是做设备端的吗?
我们最近也要上一从机设备端,LWIP源码里需要改动的多吗?需要全部啃过去吗?还是可以直接DEBUG仿到哪改哪!当然改前,肯定要了解前生今世才可以改的啊。之所以这样问,是因为啃过一次UCOS的代码,就不愿意再肯其它代码了,实在是太伤神了。
(209383068)

打赏出0元收入0元

 楼主| 发表于 2013-11-19 09:43:04 | 显示全部楼层
kinsno 发表于 2013-11-18 22:52
你们的项目是做设备端的吗?
我们最近也要上一从机设备端,LWIP源码里需要改动的多吗?需要全部啃过去吗 ...

是设备端..

其实很多问题在国外论坛上一般能找到答案的..

只是这个问题我搜了好久都没找到问题最根源的引起原因, 所以才写个帖, 记录一下跟踪过程

基本上你遇到问题,只要找准问题的关键字, 一搜就很多了.. 必要时,有些国外论坛要出国加速才能打开那些页面..
(209382825)

打赏出0元收入0元

发表于 2013-11-19 09:47:07 | 显示全部楼层
kayatsl 发表于 2013-11-19 09:43
是设备端..

其实很多问题在国外论坛上一般能找到答案的..

3KS, 好久不番墙了.
(207309939)

打赏出0元收入0元

发表于 2013-12-13 09:35:13 | 显示全部楼层
楼主牛B!!!!
(207308095)

打赏出0元收入0元

发表于 2013-12-13 10:05:57 | 显示全部楼层
查错精神不错,学习学习
(207307529)

打赏出0元收入0元

发表于 2013-12-13 10:15:23 | 显示全部楼层
你的err_handle应该能收到这个错误啊,怎么没有处理?
(201388785)

打赏出0元收入0元

发表于 2014-2-19 22:21:07 | 显示全部楼层
这个帖子 很高级
(201218112)

打赏出0元收入0元

发表于 2014-2-21 21:45:40 来自手机 | 显示全部楼层
这个难度很大啊谢谢楼主分享经验之谈
(199775093)

打赏出0元收入0元

发表于 2014-3-10 14:35:59 | 显示全部楼层
表示讲的比较详细深入,值得学习
(198907951)

打赏出0元收入0元

发表于 2014-3-20 15:28:21 | 显示全部楼层
根据楼主的分析是pcb被释放了2次,而多出来的那一次释放是因为lz在应用层调用abort导致了,那么为什么不在应用层避免使用abort来解决这个问题。而要在free函数中来避免呢?
(198891837)

打赏出0元收入0元

 楼主| 发表于 2014-3-20 19:56:55 | 显示全部楼层
有点嚣张 发表于 2014-3-20 15:28
根据楼主的分析是pcb被释放了2次,而多出来的那一次释放是因为lz在应用层调用abort导致了,那么为什么不在 ...

确实, 应用层能解决这问题

如果仅仅是个人项目的话, 完全可以在应用层解决,

但如果是开放性项目, 不排除别人以后用你的栈也会出现同样情况

从底层出发保证稳定是绝对的稳定可靠

就像stm32的编程, 出现任何意外地址读取写入, 直接给你抛个hardfault, 而不是等你盲匆匆地去找.
(198639104)

打赏出0元收入0元

发表于 2014-3-23 18:09:08 | 显示全部楼层
kayatsl 发表于 2014-3-20 19:56
确实, 应用层能解决这问题

如果仅仅是个人项目的话, 完全可以在应用层解决,

第一种方案,判断memp->next == NULL 不成功的原因应该是,释放了这个memp并不是把各个成员变量赋值为NULL,而是将这个memp放回到对应的空闲链表中,第二次释放的时候memp-> next的值应该是memp_tab的地址,而不为NULL,所以还是会进行第二次释放。
第二种方案,预留了MEMP_SIZE,这种方案可行,但是也要保证flag的值与memp_tab的值不相等;
(198581777)

打赏出0元收入0元

 楼主| 发表于 2014-3-24 10:04:35 | 显示全部楼层
有点嚣张 发表于 2014-3-23 18:09
第一种方案,判断memp->next == NULL 不成功的原因应该是,释放了这个memp并不是把各个成员变量赋值为NUL ...

这分析对,

第二个, 内存地址范围已知的, 所以只要flag随便赋一个不挨边的地址就可以了.
(197695503)

打赏出0元收入0元

发表于 2014-4-3 16:15:49 | 显示全部楼层
本帖最后由 fengyunyu 于 2014-4-3 16:17 编辑

今天,认真看了lz的分析过程,貌似没有说出现问题的原因,只是在底层加了些容错代码。另请教lz,if (type == 2)中,2代表什么?还有就是,lz应用层代码是不是写法有问题,才导致出现该问题?
(197691127)

打赏出0元收入0元

 楼主| 发表于 2014-4-3 17:28:45 | 显示全部楼层
fengyunyu 发表于 2014-4-3 16:15
今天,认真看了lz的分析过程,貌似没有说出现问题的原因,只是在底层加了些容错代码。另请教lz,if (type = ...

原因总结就是一句话.  abort 被调用了2次. 一次是我调的, 一次是内核调的
(197680814)

打赏出0元收入0元

发表于 2014-4-3 20:20:38 | 显示全部楼层
kayatsl 发表于 2014-4-3 17:28
原因总结就是一句话.  abort 被调用了2次. 一次是我调的, 一次是内核调的

要是内核在进行abort的时候,有一个回调函数通知应用层就好了。
(195366465)

打赏出0元收入0元

发表于 2014-4-30 15:13:07 | 显示全部楼层
http://www.amobbs.com/thread-4369748-1-1.html

”我移植的是裸机版的lwip,在程序跑的过程中发现有时候会产生死机的情况
检查发现停在了类似下面的地方
此处是void tcp_fasttmr(void)中的一句代码
/* send delayed ACKs */
for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next)
在这里tcp_active_pcbs和tcp_active_pcbs->next会出现相等的情况,然后for循环就回移植循环下去。抓狂中……
经过测试可能和tcp连接建立和断开有关系,但是找不到问题的根源。请问有谁碰到过类似的情况?是怎么解决的呀。“

这个帖子中的问题,LZ遇到过么?
(195365930)

打赏出0元收入0元

发表于 2014-4-30 15:22:02 | 显示全部楼层
http://blog.csdn.net/hecong_kit/article/details/24415693

这里貌似有一个新的类似问题的解决方案。
(195365529)

打赏出0元收入0元

发表于 2014-4-30 15:28:43 | 显示全部楼层
用两句话总结下?
(195365072)

打赏出0元收入0元

 楼主| 发表于 2014-4-30 15:36:20 | 显示全部楼层
fengyunyu 发表于 2014-4-30 15:13
http://www.amobbs.com/thread-4369748-1-1.html

”我移植的是裸机版的lwip,在程序跑的过程中发现有时候 ...


lwip 里面有很多处 for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next)  的地方

void tcp_fasttmr(void) 只是其中一处

但不管死在哪处,  只要链表链回自身 基本都可以归为同一个问题.


你下面贴的那个, 粗略看了下, 跟我这个问题貌似关系不大
(195362378)

打赏出0元收入0元

发表于 2014-4-30 16:21:14 | 显示全部楼层
kayatsl 发表于 2014-4-30 15:36
lwip 里面有很多处 for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next)  的地方

void tcp_fast ...

请问54楼图中memp_use_flag是自定义的一个常量么?另外,memp_free中,跳到Free_Already后,如何处理的,直接return么?
(195359319)

打赏出0元收入0元

发表于 2014-4-30 17:12:13 | 显示全部楼层
另外,LZ把tcp_abort(clippcb)改成tcp_close(clippcb)估计就对了。
(195330922)

打赏出0元收入0元

发表于 2014-5-1 01:05:30 | 显示全部楼层
http://blog.sina.com.cn/s/blog_5d18cf7f010100nj.html

这里貌似有一个正确的答案。
(195122376)

打赏出0元收入0元

 楼主| 发表于 2014-5-3 11:01:16 | 显示全部楼层
fengyunyu 发表于 2014-5-1 01:05
http://blog.sina.com.cn/s/blog_5d18cf7f010100nj.html

这里貌似有一个正确的答案。

你这个贴就是我为什么用 abort 而不用close的原因, 但不是我这个问题的正解
(195111607)

打赏出0元收入0元

发表于 2014-5-3 14:00:45 | 显示全部楼层
kayatsl 发表于 2014-5-3 11:01
你这个贴就是我为什么用 abort 而不用close的原因, 但不是我这个问题的正解

可能,用close更合适些。close后,pcb的释放是由lwip自己进行。应用层,重新进行tcp_new应该就行了。
(195041857)

打赏出0元收入0元

 楼主| 发表于 2014-5-4 09:23:15 | 显示全部楼层
close 和 abort 最大的区别是 close会发送关闭请求给对方, 然后等到对方关闭的ack后才会真正释放掉pcb

但我的函数是用在连接的时候的, 连接还没真正建立, 是不需要发送关闭请求的, 可以直接abort
(195041819)

打赏出0元收入0元

 楼主| 发表于 2014-5-4 09:23:53 | 显示全部楼层
fengyunyu 发表于 2014-5-3 14:00
可能,用close更合适些。close后,pcb的释放是由lwip自己进行。应用层,重新进行tcp_new应该就行了。 ...

close 和 abort 最大的区别是 close会发送关闭请求给对方, 然后等到对方关闭的ack后才会真正释放掉pcb

但我的函数是用在连接的时候的, 连接还没真正建立, 是不需要发送关闭请求的, 可以直接abort


喔.. 忘了按回复来发.. 重发一条吧...
(195038636)

打赏出0元收入0元

发表于 2014-5-4 10:16:56 | 显示全部楼层
kayatsl 发表于 2014-5-4 09:23
close 和 abort 最大的区别是 close会发送关闭请求给对方, 然后等到对方关闭的ack后才会真正释放掉pcb

...

直接用tcp_abort可能有些问题,以下是tcp_abort()的部分代码:
...

  if (pcb->state == TIME_WAIT) {
    tcp_pcb_remove(&tcp_tw_pcbs, pcb);
    memp_free(MEMP_TCP_PCB, pcb);
  } else {
    seqno = pcb->snd_nxt;
    ackno = pcb->rcv_nxt;
    ip_addr_copy(local_ip, pcb->local_ip);
    ip_addr_copy(remote_ip, pcb->remote_ip);
    local_port = pcb->local_port;
    remote_port = pcb->remote_port;
#if LWIP_CALLBACK_API
    errf = pcb->errf;
#endif /* LWIP_CALLBACK_API */
    errf_arg = pcb->callback_arg;
    tcp_pcb_remove(&tcp_active_pcbs, pcb);
    if (pcb->unacked != NULL) {
      tcp_segs_free(pcb->unacked);
    }
    if (pcb->unsent != NULL) {
      tcp_segs_free(pcb->unsent);
    }
#if TCP_QUEUE_OOSEQ   
    if (pcb->ooseq != NULL) {
      tcp_segs_free(pcb->ooseq);
    }
#endif /* TCP_QUEUE_OOSEQ */
    memp_free(MEMP_TCP_PCB, pcb);
    TCP_EVENT_ERR(errf, errf_arg, ERR_ABRT);
    if (reset) {
      LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_abandon: sending RST\n"));
      tcp_rst(seqno, ackno, &local_ip, &remote_ip, local_port, remote_port);
    }

代码中的 tcp_pcb_remove(&tcp_active_pcbs, pcb);可能会出现问题。
(195038465)

打赏出0元收入0元

发表于 2014-5-4 10:19:47 | 显示全部楼层
kayatsl 发表于 2014-5-4 09:23
close 和 abort 最大的区别是 close会发送关闭请求给对方, 然后等到对方关闭的ack后才会真正释放掉pcb

...

tcp_close则处理了连接还没有真正建立的情况,tcp_close的部分代码如下:
...

  switch (pcb->state) {
  case CLOSED:
    /* Closing a pcb in the CLOSED state might seem erroneous,
     * however, it is in this state once allocated and as yet unused
     * and the user needs some way to free it should the need arise.
     * Calling tcp_close() with a pcb that has already been closed, (i.e. twice)
     * or for a pcb that has been used and then entered the CLOSED state
     * is erroneous, but this should never happen as the pcb has in those cases
     * been freed, and so any remaining handles are bogus. */
    err = ERR_OK;
    if (pcb->local_port != 0) {
      TCP_RMV(&tcp_bound_pcbs, pcb);
    }
    memp_free(MEMP_TCP_PCB, pcb);
    pcb = NULL;
    break;

其中TCP_RMV(&tcp_bound_pcbs, pcb)处理了连接还没有真正建立的情况。
(195014902)

打赏出0元收入0元

 楼主| 发表于 2014-5-4 16:52:30 | 显示全部楼层
fengyunyu 发表于 2014-5-4 10:19
tcp_close则处理了连接还没有真正建立的情况,tcp_close的部分代码如下:
...

其实你是没有搞懂我上面描述的问题在哪.

其实问题在于储存pcb的地方, 系统自身释放了一次, 而我不知道, 变量存在我身上, 而我又再释放一次.. 所以就卡擦了..

这跟调哪个函数是没关系的, 最终都是mempfree
(195014089)

打赏出0元收入0元

发表于 2014-5-4 17:06:03 | 显示全部楼层
kayatsl 发表于 2014-5-4 16:52
其实你是没有搞懂我上面描述的问题在哪.

其实问题在于储存pcb的地方, 系统自身释放了一次, 而我不知道,  ...

你在mem_free中加了补丁,这个是看到了。我是觉得,你在应用层对raw_api的使用“方法”可能有问题,引起了这个“问题”。
(195013614)

打赏出0元收入0元

 楼主| 发表于 2014-5-4 17:13:58 | 显示全部楼层
fengyunyu 发表于 2014-5-4 17:06
你在mem_free中加了补丁,这个是看到了。我是觉得,你在应用层对raw_api的使用“方法”可能有问题,引起 ...

上面回帖已经说了,  问题就在于 "变量存在我身上"

而不是调了哪个函数
(194949279)

打赏出0元收入0元

发表于 2014-5-5 11:06:13 | 显示全部楼层
貌似老外使用lwip 1.4.1,raw模式也碰到类似问题。

以下转自网络:

lwip tcp_tw_pcbs list problem in tcp_slowtmr()

I have been having a problem in the tcp_slowtmr() function in tcp.c.  I have been using the raw api for a quite a while to implement TCP servers listening on several different ports.  I have not really had any problems so far.  Recently I have also implemented TCP client connection which quickly open up a TCP client connection read/write some data and then close the connection.  This sequence repeats itself over and over connecting to several different remote TCP servers.  Things seem to work fairly well until I start introducing some error conditions like extending remote server resonse times or reducing my timeouts waiting for data to be received.  This causes my code to timeout and close the TCP connection (probably while some responses may come back in later).  I have also tried disconnecting some of my remote servers network connections so that the initial client connection attempts will fail.  I am just mainly trying to do some general stress testing with normal conditions that may occur when deploying the application.



The problem that I am having occurs when adding these additional stress tests, and possibly with normal conditions after an extended period of time.  I have not nailed down the exact cause as of yet.  I finally get into a lock up condition when calling the tcp_slowtmr() function.  The lockup occurs cycling through the code lines highlighted below from the tcp_slowtmr() function:



  /* Steps through all of the TIME-WAIT PCBs. */

prev = NULL;

  pcb = tcp_tw_pcbs;

  while (pcb != NULL) {

    LWIP_ASSERT("tcp_slowtmr: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT);

    pcb_remove = 0;



    /* Check if this PCB has stayed long enough in TIME-WAIT */

    if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {

      ++pcb_remove;

    }

   





    /* If the PCB should be removed, do it. */

    if (pcb_remove) {

      struct tcp_pcb *pcb2;

      tcp_pcb_purge(pcb);

      /* Remove PCB from tcp_tw_pcbs list. */

      if (prev != NULL) {

        LWIP_ASSERT("tcp_slowtmr: middle tcp != tcp_tw_pcbs", pcb != tcp_tw_pcbs);

        prev->next = pcb->next;

      } else {

        /* This PCB was the first. */

        LWIP_ASSERT("tcp_slowtmr: first pcb == tcp_tw_pcbs", tcp_tw_pcbs == pcb);

        tcp_tw_pcbs = pcb->next;

      }

      pcb2 = pcb;

      pcb = pcb->next;

      memp_free(MEMP_TCP_PCB, pcb2);

    } else {

      prev = pcb;

      pcb = pcb->next;

    }

  }



The problem occurs when the first item on the tcp_tw_pcbs list points back to itself:  pcb->next == pcb, so the code never exits the while loop.  The tcp_ticks value never changes in this part of the code so pcb_remove is never set > 0 either.



This is a single threaded application and only the standard interrupt handling function are being used.  This is an application running on an LPC4350 using the LPCOpen library from NXP with lwip v1.4.1.



It seems like the problem is created if I start calling tcp_close() to close my client connections.  If I use tcp_abort() instead then I don’t seem to have to problem – It does however cause undesirable sequences in wireshark.



What is the recommended sequence to close a tcp client session using the raw api?



Any suggestions as to what I may be doing wrong here or could this possibly be a bug that has been seen before in lwip?



Thanks,
Greg Dunn
(192247839)

打赏出0元收入0元

发表于 2014-6-5 17:30:13 | 显示全部楼层
收藏,顶起。
(183246936)

打赏出0元收入0元

发表于 2014-9-17 21:45:16 | 显示全部楼层
楼主的分析实在精辟,佩服。
(182817685)

打赏出0元收入0元

发表于 2014-9-22 20:59:27 | 显示全部楼层
mark!!!
(182816625)

打赏出0元收入0元

发表于 2014-9-22 21:17:07 | 显示全部楼层
好详细
回帖提示: 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子论坛 ( 公安交互式论坛备案:44190002001997 粤ICP备09047143号-1 )

GMT+8, 2020-7-8 19:40

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表