作家
登录

用gdb分析coredump的一些技巧

作者: 来源: 2017-05-11 09:05:20 阅读 我要评论

前几天我们正在运营的一款产品产生了崩溃,我花了两天测验测验用 gdb 分析了 coredump ,固然最后照样没能找到 bug ,但照样认为应当做一些总结。

产品是基于 skynet 开辟的,因为汗青原因,它基于的是 skynet 1.0 之前 2015 年中的一个版本,因为这两年一向没出过什愦问题,所以保护人员懈怠而没有更新。

用gdb分析coredump的一些技能

导致代码崩溃的直接原因是 rip 指向了一个数据段的地址,精确的说,跳转到了当前工作线程拥有的 lua 虚拟机的主线程 L 那边。

崩溃的时刻,关于 Lua 部分的代码缺氨慎试符号信息,这加大年夜了分析难度。如今的 skynet 在编译 lua 时,参加了 -g 选项,这应当可以赞助将来竽暌箍现类似问题时更好的定位问题。

发明这条线索很轻易,skynet 的其它部分是有调试符号的,可以在崩溃的调用栈上看到,办事的 callback 函数的 ud 和崩溃地址一致,而 lua 办事的 ud 恰是 L 。用 gdb 的 p ( lua_State *)地址 查看这个构造,也能不雅察到这个数据构造的内容恰是一个 lua_State 。

因为用 bt 查看的调用栈是不正常的,所以可以断定在函数调用链的过程中应当是产生了某种缺点改写了 C 栈的内容。在这种情况下,gdb 多半靠猜测来重建调用链(就是用 bt 看到的那些)。

现代编译器经由优化代码之后, C 栈上已经没有 stack frame 的基地址了,所以如今不克不及简单的看客栈的数据内容来推想 stack frame 。也就是经由优化的代码不必定实用 rbp 来保存 stack frame ,它也不必定入栈。对于 gcc ,这个优化策略是经由过程 -fomit-frame-pointer 开启的,只要用 -O 编译,就必定打开的。在 stack 本身出问题时,gdb 的猜测很可能不精确,人工来猜或手工补全或许更靠谱一些。办法就是先用 x/40xg $rsp 打印出 C stack 的内容,然后不雅察肯定 stack 上的哪些数据落在代码段上。所有有函数调用的处所,必定有处于代码段上的某处返回地址指针。

主法度榜样的代码段一般都地址偏低,动态链入的代码段可以用 info sharedlibrary 来查看。返回地址肯定是落在函数代码的内部,而肯定不会是函数人口,而这些地址除了函数调用外,都弗成能用正常的 C 代码生成出来,所以辨认性很强,不会有歧义。

我在分析此次的问题时,写了脚本查看两个 L 的 lua 调用栈,这些脚本只要对 lstate.h 里的 callinfo 数据构造熟悉就很轻易写出来。lua 的调试信息很丰富,找到源文件名和行号都很轻易。别的,L 栈顶的数据是什么也是重要的线索,可以推导出崩溃产生时 Lua 的状况。

如不雅认为某个指针是函数返回地址,可以用 x/10i 地址 来反汇编确认。

然则须要留意的是,即使在 C stack 上发明一个函数返回地址,并不解释这个函数调用尚未返回。它只能解释这个函数至少被调用过。这是因为,汇编在 call 一个函数时,会把当前调用处的地址压栈。而调用停止后,ret 指令返回只是修改了 rsp 这个栈指针,而数据本身是残留在栈上的。这也是为什么 gdb 有时刻也会猜错。

在此次的案例里,崩溃产生在履行跳转到了数据段,这种情况多半是因为 call 指令调用的是一个借居引用,在 C 层面来看,就是调用了一个函数指针。这种情况下,跳转地址肯定还在存放器里。用 info registers 可以查看。(注:在 64bit 平台下,查看存放器内容异常重要,因为 64bit 下,函数调用的前四个参数是经由过程存放器 rdi rsi 传递而非客栈,往往须要结合 disass 反汇编看代码去推算。)

当然,按 lua 本身的┞俘常逻辑,是弗成能把 L 作为一个函数指帐攀来调用的。按我的猜测,这里掉足比较大年夜的可能是 longjmp 的时刻数据掉足,恢复了缺点的存放器。btw, setjmp 在生成 jmp_buf 时,对于 rsp rbp 这类很可能用于地址的存放器,crt 做了变形(mangling)处理,所以很难简单的靠写越界写出一个偶合的缺点值。

对于调试崩溃在 lua 内部的情况,比较关键的线索平日是 L 本身的状况。因为营业的主流程其实是用 lua vm 驱动的,L 的 callinfo 也就是 lua 的 stack frame 信息更多。

对于 skynet ,在正常运行的时刻平日会有两个晃荡的 L 。一个是主线程,用来分发消息;但消息本身是在一个自力的 coroutine 中进行的。以上可以肯定主线程,而子线程的 L 可以在存放器和 stack frame 里找。因为没有调试符号,所以可以靠猜来寻找,这并不算太麻烦。要肯定一个地址是否是 L ,只须要查看 L->l_G 看是否和前面找到的主线程 L 的对应值是否雷同。

在缺氨慎试符号的情况下,会发明 lua 下的一些内部数据构造 gdb 无法辨认。这个时刻可以用 add-symbol-file 来导入须要的构造信息。办法是加上 -g 从新编译一下 lua ,把一些包含这些构造的文件,例如 ldo.o 加进来。

此次我们崩溃的法度榜样最后停在主线程的 resume 调用子线程上。子线程调用了 skynet.sleep ,也就是最后把 "SLEEP", session 经由过程 yield 传给了主线程。这些要传出的量可以在子线程的 L->top 上查到。固然 lua 本身已经把值 pop 出去了,但 pop 本身是不清空栈的,只是调剂了栈顶指针,所以在 gdb 下依然可见。主线程也接收到了传过来的数据,数据栈上可见。

不过此次的明日诡之处在于,lua 线程间拷贝数据这个过程是在 lcorolib.c 中的 auxresume 函数中履行的,在 luaB_coresume 里还须要在结不雅中插入一个 boolean 。而我在 coredump 数据中发清楚明了拷贝过程已经完成,然则 boolean 却没有压入。那么变乱产生点只可能在两者之间。不过在 auxresume 返回到后续 push boolean 之间只有几屑ゃ编代码,绝对弗成能掉足。

独一能解释的就是在 lua_resume 时代,子线程运行的流程破坏了 C 的 stackframe ,让 auxresume 没能精确的返回到调用它的 luaB_coresume 中。但如何才能制造出这种情况,我临时没有设法主意。


  推荐阅读

  私有云2.0时代来临,OpenStack已上车

OpenStack已经进入第七年了,如今治理着跨越500万个处理器核心。它的增长在很大年夜程度上与私有云安排有关。而如今,OpenStack基金会称,企业须要为私有云2.0做好预备,因为下一代技巧的>>>详细阅读


本文标题:用gdb分析coredump的一些技巧

地址:http://www.17bianji.com/lsqh/35135.html

关键词: 探索发现

乐购科技部分新闻及文章转载自互联网,供读者交流和学习,若有涉及作者版权等问题请及时与我们联系,以便更正、删除或按规定办理。感谢所有提供资讯的网站,欢迎各类媒体与乐购科技进行文章共享合作。

网友点评
自媒体专栏

评论

热度

精彩导读
栏目ID=71的表不存在(操作类型=0)