【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

前段时间,参与了我司基于dpdk的高速数据包转发模式项目,在开发的过程,我们遇到一个奇怪的coredump事件,考虑到其定位过程比较特殊,又是关于的dpdk定位过程,故记录在此。

背景:

我司的产品是个典型的数据转发为导向的项目,业务逻辑在流量高并发情况下,可能连5%都占不到。所以我们在dpdk的开发套件上,完成一些特性,支持了我们的业务。其系统配置如下:

version: dpdk17.11

system: ubuntu14.04 server

线程:dpdk接管8个超线程

现象:

数据面在做稳定性测试时,使用tc模拟数据报文高并发冲击测试,大约半个小时左右会出现一次coredump,且每次出现的core文件的记录基本都是挂在在rte_pktmbuf_read。如果流量不大的话,则未出现异常现象。如下,是一次coredump的堆栈信息。

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

分析:

查看mbuf的指针在rte_pktmbuf_read时还是有地址的, 在调用内置的封装函数__rte_pktmbuf_read时, 该mbuf的指针地址突然变成了0, 显然程序在访问这个mbuf时产生了异常,造成coredump。

上一步函数入参还是正常,下一步入参直接地址变成了0,为什么会出现这种情况?如果是单线程,绝不可能出现这种情况的(不考虑硬盘,内存等硬件的问题,因为这个一个必现的问题)。考虑到是多线程程序,第一个假设就是多线程引起的野指针问题(后来证明的确是由于多线程的临界区抢占造成的)。

定位过程:

一、验证多线程

为了证明该coredump确实是个多线程引起的,我去验证了在不同的线程数下,coredump出现的频率。如下是验证结果:

线程数

1

2

4

8

1个小时内出现的coredump次数

0

0

1

3

由上面实验的结果,可以很明显的看出,coredump的出现与线程数相关。那方向就可以明确,重点关注多线程下指针的处理问题。

二、代码校验

在不考虑dpdk本身套件的代码,我们本身加入的特性代码还是不多的。所以我在想着最简单的方法就是检视代码。重点关注 mbuf的

clone、free, 看看有没有哪里的处理逻辑,会有可能造成提前free掉mbuf, 而指针却在继续操作?

在代码检视几遍之后,的确找到几处代码处理不规范的问题。将其修改之后,继续验证,发现问题仍然存在。说明问题隐藏的比较深,也可能是dpdk本身有一些特性,我们使用的时候没有完全理解。总之,代码校验的工作到此为止,解决了几处隐患,但是并未完全解决此处coredump的问题根源。

三、从dpdk的内存池出发

我思考着,既然是多线程的mbuf管理问题,那会不会是线程B释放了线程A创建的mbuf, 导致线程A再去操作这个mbuf时就会出现问题。如下图,如果是第八步就会发生如上异常问题。

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

思路:

既然每个线程只能释放自己的申请的mbuf,其他的线程来操作就一定会有问题;那么给每个mbuf一个线程id的身份信息,在每次释放的时候,不允许其他线程对其释放,就能找到错误释放mbuf的指针在哪里了。

实施:

拓展struct rte_mbuf , 在结构体中加入元素,记录申请mbuf是的coreid;

拓展结构体:

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

申请时记录coreid

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

在释放mbuf处增加线程id判断。

注意此处在rte_pktmbuf_free的外面提前进行判断,而不free的函数内部实现,因为我们的业务代码申请的mbuf才有这个test_coreid,而dpdk的内部也有其他的mbuf申请,而他们没有却没有test_coreid。所以不能所有的mbuf free时都进行判断。

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

结果:

我设置的testid,并没有起到作用。也就是说coredump继续发生,但却不是在我加入的代码校验那里。此处的实验虽没有抓到coredump的第一现场,但是却给我提供了一个思路,从内存池的设计出发,思考有没有其他的方法,判断mbuf是否有相应的校验机制。

四、终极大招

由前面的实验提供的思路,我对mbuf 的内存池管理源码仔细研究了一番,深入跟读了rte_pktmbuf_free的逻辑处理过程,竟有意外的发现。直接码上代码:

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

 

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

 

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

由上面的代码逻辑,可以看出当开启RTE_LIBRTE_MEMPOOL_DEBUG时,在mbuf的申请和释放出都会调用rte_mempool_check_cookies这个函数,对mbuf的cookie值进行校验。其校验原理可以理解为,在申请mbuf时,校验是否为cookie2,并对其打上cookie1的标记;对释放mbuf是对其校验是否为cookie1,并打上cookie2; 简单的理解即mbuf的申请和释放处于两种状态,当任何一种动作(申请或释放)做了两次,都会产生panic;

我在dpdk的官方文档,也找到它的定义。

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

 

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

该debug开关的发现,令我十分兴奋。这个开关的存在,正好可以解决我们这个多进程重复free mbuf的问题。之前都是core在mbuf被改变后的另一个指针来访问的第二现场,就好比明明知道有个小偷经常来家里偷酒喝,每次自己想去喝点酒时,才发现酒被人喝过了,留下一个风中凌乱的自己; 而这次开关的打开,可以在小偷在偷酒喝的时候,立即抓住他,可以亲自过去把小偷的面罩打开,看看是哪个。

 

揭开面纱

由前面的分析,我把RTE_LIBRTE_MEMPOOL_DEBUG开关打开,重新编译了一遍dpdk,按照原先的环境进行打流测试。果然很短的时间便出现coredump, 此次core文件记录的便是free的流程。我们仔细分析了过程,发现是一个关于kni的共享临界区的未加锁问题,导致多线程抢占kni发包接口,从而出现两次free的情况。修改后的逻辑如下,tc重新测试,异常再也没有出现。

【DPDK17.11】记录一次由dpdk的野指针造成的coredump过程

后记

写完这篇博客,可能大家只记得一个RTE_LIBRTE_MEMPOOL_DEBUG开关可以解决dpdk Mbuf的野指针问题。与我而言,却是个很珍贵的经历,虽然写这篇博客的时候,解决这个bug已经是过去两个月前的事情了,但是我还是希望把它分享出来。

从遇到这个coredump起,从审视代码到走读dpdk,从猜想到验证,从证明到假设,虽然也走了一点弯路(扩展rte_mbuf), 到最终解决问题,可以说是以前经常用到的野指针的定位手段;在走读dpdk的开关设计中,我们可以看到dpdk内存池设计的人性化,同时我们自己公司的工程中是否也可以加入一点cookie的设计思路呢, 如果有遇到同样的多线程问题,的确是个很不错的想法可以尝试一把。