碰到一个dangling pointer导致进程core的问题,此类问题很常见,从上图m_vRelativePosition.yz的异常值可以猜测,m_targetDomArea指向的内存被踩坏了,或是m_targetDomArea指向的对象已被销毁,其内存可能已被重新分配。

这里不去讨论m_targetDomArea野的原因(熟悉这块逻辑的同事已经在上图指出具体原因),只去研究导致core的直接原因是什么。

首先通过gdb查看具体是哪个signal导致的core:

最常见的signal SIGSEGV, Segmentation fault,段错误。

以及core在哪一行:

float fDist = Magnitude(m_targetDomArea->GetPosition() -vPos);

SEGV

This very popular signal is generated when a program makes an invalid memory reference. A memory reference may be invalid because the referenced page doesn’t exist (e.g., it lies in an unmapped area somewhere between the heap and the stack), the process tried to update a location in read-only memory (e.g., the program text segment or a region of mapped memory marked read-only), or the process tried to access a part of kernel memory while running in user mode (Section 2.1). In C, these events often result from dereferencing a pointer containing a bad address (e.g., an uninitialized pointer) or passing an invalid argument in a function call. The name of this signal derives from the term segmentation violation.

——引用自《The Linux Programming Interface》

段错误意味着访问了非法地址,而core位置对应的代码看似只有一次对m_targetDomArea指针的访问,那么m_targetDomArea是导致core的非法地址吗?

可以看到m_targetDomArea的值为0x8fe3498,而导致core的非法地址是0x82b,不是它。

查看源码发现Magnitude()是内联函数,会不会是core在这个函数里?但这个函数只是对函数参数做一些简单的算术运算,并没有指针解引用之类的操作,而传入的参数又是个栈上的临时变量,地址肯定是OK的,所以不是core在这里。

只看源码已经无法断定core的具体原因,进一步查看core在汇编的哪一行:

可以看到最后执行的汇编指令确实有一次内存访问:(%rax),即访问寄存器rax所存储的内存地址对应的内存,将这块内存存储的值放入rax寄存器。那么猜测rax寄存器的值应该就是非法地址0x82b,验证一下:

那么为什么会访问这个地址?仔细观察后面几行汇编,发现有一处函数调用call *%rax

普通的函数调用应该是固定的函数地址,因为在链接期间函数的地址就都已经通过重定位确定好了,类似这样:

但这里却是动态计算出来的一个函数地址(保存在rax寄存器中),想想什么情况下函数地址是需要经过计算得出的?没错,虚函数。根据对象中的虚表指针找到虚表,在加上函数的偏移找到函数真正的地址。

那只能是GetPosition()了,查看源码发现这确实是一个虚函数:

到这里基本可以下结论了,m_targetDomArea指向的内存被踩,或者销毁后被其他对象重新利用,导致虚表指针的值错掉了,进而在通过虚表指针计算虚函数地址的时候得出一个非法的地址0x82b,最终访问这个地址触发段错误,进程core掉。

还可以进一步验证一下,mov (%rax),%rax的下一行mov %rdx,%rdi,应该是为调用GetPosition()准备隐藏的参数this指针(按照函数调用约定,rdi寄存器用于传入第一个参数),即m_targetDomArea

可以看到用于给rdi赋值的rdx的值就是m_targetDomArea

System V Application Binary Interface AMD64 Architecture Processor Supplement (With LP64 and ILP32 Programming Models) Version 1.0 中对寄存器的说明

知识点

1. gdb中通过$_siginfo查看导致core的具体原因以及非法地址。

2. 使用gdb指令disassemble加选项/m可以同时对照查看C++源码及其对应的汇编指令,很方便:

3. 如果调式机器上的源码路径与可执行文件调试信息中的路径不一致,还需要使用set substitute-path from to设置替代路径,以便gdb找到正确的源码。

(gdb) set substitute-path /src/path/in/binary/ /src/path/on/machine/

4. 虚函数。

5. 各寄存器在函数调用中的用途。