对于大多数gdb堆栈破坏的情况,查问题时只能缩小问题代码范围,不断测试复现,找出容易复现的方式,一步步解决。但是有一种情况,如果你的gdb堆栈破坏了,但是有ucontext_t进程上下文信息,那么是可以继续分析的!本文介绍了gdb堆栈破坏但有ucontext_t进程上下文信息的前提下使用gdb调试定位问题的过程。
1 使用带有debug信息的动态库(SDL,X11)
Linux根据xxx.so下载xxx源码,编译和安装_u012906122的专栏-CSDN博客
2 查看log的Stack trace
Segmentation fault(Invalid permisssions for mapped object [0xffff28207220])
访问的内存地址无效!!!
3 gdb coredump
gdb xxx core_xxx_3255
发现gdb堆栈被破坏了
看下sig_handler函数:
handleSignal函数:
从以上信息可以推断出来,void* ctx的实际类型是struct ucontext_t* ctx。
4 查看用户进程上下文信息
p *(struct ucontext_t*) 0xffff6a7f9cf0
目前觉得uc_mcontext里有用的用户进程上下文信息:
(1) fault_address
(2) regs
对于arm64 cpu,有32个regs:
x0~x7:传递程序参数,多余参数采用堆栈传递
x29:FP栈框指针
x30:LR程序连接寄存器,保存函数调用的返回地址.
x31:SP堆栈指针
(3) sp
(4) pc
(5) __reserved
下面逐个分析用户进程上下文信息:
(1)fault_address,pc
pc指针可能访问了一个非法的内存地址导致段错误
(2)通过LR程序连接寄存器,查看函数调用的返回地址
info symbol xxx:用于查看该地址的符号
p/x 281472886753104(x30,LR寄存器)
info symbol 0xffff836dc750
说明是在X11_DispatchFocusOut里出现指针越界的情况
(3)__reserved信息
info types xxx:用于查看该类型符号
info types ucontext_t
官方注释:
vim /usr/include/aarch64-linux-gnu/sys/ucontext.h
__reserved表示出现崩溃前的信息,保存了CPU额外的状态信息如FP/SIMD状态.FP是栈框指针。
这个变量很有用,如果崩溃附近的代码有打印,会保存打印的内容。
__reserved信息:
看下代码里在哪里:
grep "FocusOut2 data->ic" ./ -R -n
结合以上信息可以推断出来,崩溃是在X11_DispatchFocusOut接口里,可能与X11_XUnsetICFocus有关.
5 分析X11_XUnsetICFocus接口
info functions xxx:用于查看该函数符号信息
info functions XUnsetICFocus
(1) 分析XUnsetICFocus代码流程
(2)反汇编XUnsetICFocus,结合ucontext_t分析寄存器信息
disassemble XUnsetICFocus
ldr:LDR R0,[R1] 将存储器地址为R1的字数据读入寄存器R0. //32位cpu是一个字是4个字节,64位cpu一个字是8个字节。
ldr x2,[x0,#8] 将x0偏移8个字节的地址里的字数据读入到寄存器x2. //在64位机上,指针占用内存大小:8个字节
cbz:compare branch zero
br:branch register
以上信息推断出来,只是读了x0寄存器,ucontext_t中的x0是有效的.
(3)gdb跟踪变量
[1] x0:
methods:0xffff282bfab0
崩溃的pc指针地址就是这个地址(0xffff28207220)!
[2] x1:
x1寄存器的值就是非法的内存地址!
即上图位置出错。即下图代码中第359行,函数指针地址错误!即函数指针变为野指针,怀疑可能其他地方(多线程)在修改它。
6 info sharedlibrary确定地址越界
info sharedlibrary
0xffff28207220不在X11.so有效范围内.
7 梳理代码流程,定位问题根因
既然怀疑多线程导致,在所有调用data->ic处加打印线程id,复现:
蓝色框是thread1,红色框是thread2。
结论:验证猜测,是多线程问题,先析构后调用导致访问野指针越界。