[GDB] 使用 GDB 找尋造成 strlen() 當掉的原因
最近專案的 python 程式常常出現當掉的狀況,
用 gdb debug 了一下,看來是 python 用到我們的 C++ 函式庫的問題~
來記錄一下 debug 的過程吧~
首先用 gdb /usr/bin/python coredump 指令載入 gdb,
可以看到程式死在一個很怪的 __strlen_sse2_pminub(),
看起來像是 strlen() 的 SSE 版本:
Core was generated by `python -u -m /opt/testd'. Program terminated with signal 11, Segmentation fault. #0 0x00007ff5d2da2811 in __strlen_sse2_pminub () from /lib64/libc.so.6
先用 py-bt 看一下 python 部分的 call stack,列出目前是在 scan() 這個函式:
(gdb) py-bt #22 Frame 0x7ff5a806dea0, for file ./wrapper.py, line 422, in scan
再用 bt 看一下 call stack,python 程式後來會呼叫到我們寫的 C++ 函式庫
的 Scanner::Callback() 函式,而這函式後來又呼叫到 __strlen_sse2_pminub() 後掛了:
(gdb) bt #0 0x00007ff5d2da2811 in __strlen_sse2_pminub () from /lib64/libc.so.6 #1 0x00007ff5c0ce9843 in Scanner::Callback(CONNECTION*, unsigned int, void*, void*) () from /opt/libscan.so
想知道 __strlen_sse2_pminub() 傳入的參數是什麼的話,
就要先了解一下 calling convention…
從 wikipedia: x86 calling conventions: System V AMD64 ABI 這邊可以知道,
x64 的函式呼叫,參數會依序放在 RDI, RSI, RDX, RCX,
而 strlen() 只有一個參數,所以應該是放在 RDI 這個暫存器上~
用 info reg 看一下目前暫存器的值,RDI 的內容是 0x7ff5aa402008,
另外也可以注意到程式最終的 IP 值是指向 __strlen_sse2_pminub+17 的位置:
(gdb) info reg rax 0x0 0 rbx 0x7ff5b168d700 140693220153088 rcx 0x8 8 rdx 0x7ff590005fb0 140692659658672 rsi 0x7ff5a8061d60 140693062688096 rdi 0x7ff5aa402008 140693100044296 rbp 0x7ff5c0eee3b0 0x7ff5c0eee3b0 rsp 0x7ff5b168a628 0x7ff5b168a628 r8 0x0 0 r9 0x0 0 r10 0xffffff00 4294967040 r11 0x7ff5d2dc17b0 140693781354416 r12 0x3e323 254755 r13 0x136faf0 20380400 r14 0x7ff5b168a6a0 140693220140704 r15 0x7ff590005fb0 140692659658672 rip 0x7ff5d2da2811 0x7ff5d2da2811 <__strlen_sse2_pminub+17> eflags 0x10287 [ CF PF SF IF RF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
用 disas 看一下 __strlen_sse2_pminub() 的組合語言碼,
+17 的位置的指令是用 movdqu 將 RDI 暫存器指到的記憶體內容,
複製到 XMM1 暫存器上,而 RDI 又正好是 __strlen_sse2_pminub() 的參數,
所以應該是想要把輸入的字串複製到 XMM1 暫存器上時出了問題:
(gdb) disas __strlen_sse2_pminub Dump of assembler code for function __strlen_sse2_pminub: 0x00007ff5d2da2800 <+0>: xor %rax,%rax 0x00007ff5d2da2803 <+3>: mov %edi,%ecx 0x00007ff5d2da2805 <+5>: and $0x3f,%ecx 0x00007ff5d2da2808 <+8>: pxor %xmm0,%xmm0 0x00007ff5d2da280c <+12>: cmp $0x30,%ecx 0x00007ff5d2da280f <+15>: ja 0x7ff5d2da282e <__strlen_sse2_pminub+46> => 0x00007ff5d2da2811 <+17>: movdqu (%rdi),%xmm1 0x00007ff5d2da2815 <+21>: pcmpeqb %xmm1,%xmm0 0x00007ff5d2da2819 <+25>: pmovmskb %xmm0,%edx 0x00007ff5d2da281d <+29>: test %edx,%edx 0x00007ff5d2da281f <+31>: jne 0x7ff5d2da2a73 <__strlen_sse2_pminub+627>
試著瞄一下 RDI 指到的字串內容… 這邊寫 out of bounds,看來不是個有效的位址:
(gdb) x/s $rdi 0x7ff5aa402008: <Address 0x7ff5aa402008 out of bounds>
再用 info proc mappings 查查看… 不過這指令似乎只能列出 DLL 的載入位址,
沒辦法顯示出 stack/heap 的區段:
(gdb) info proc mappings Mapped address spaces: Start Addr End Addr Size Offset objfile ................... 0x7ff5a1e67000 0x7ff5a1e69000 0x2000 0x12000 /usr/lib64/python2.7/lib-dynload/_curses.so 0x7ff5b0114000 0x7ff5b0264000 0x150000 0x0 /opt/worker.so ...................
改用 maintenance info sections 來看,可以看到 0x7ff5aa402008 這位址,
正好是夾在下面兩個區段的中間,應該蠻有可能是被釋放掉的記憶體:
(gdb) maintenance info sections Core file: ..................... 0x7ff5aa402000->0x7ff5aa402000 at 0x1aa1f000: load110 ALLOC READONLY 0x7ff5ac000000->0x7ff5ac422000 at 0x1aa1f000: load111 ALLOC LOAD HAS_CONTENTS .....................
回到 C++ 程式庫去查 Scanner 相關的程式,
後來找到了一個地方,使用了 STL vector,
但可能索引到了一個之前被釋放掉的元素,因而導致了 crash~
這次雖然不是直接指出造成程式當掉的元兇,但 gdb 提供的資訊給了我們一些線索,
讓我們知道要去查程式裡是否有用到被釋放掉記憶體的部分,
算是蠻有收穫的囉~^^
參考資料:stackoverflow: can I find via GDB if a variable belong to heap or stack?