gdb堆栈被破坏时的定位方法
gdb堆栈被破坏时的定位方法
当程序发生崩溃且堆栈信息被破坏时,如何定位问题原因?本文将通过一个具体的实例,详细介绍如何使用gdb调试工具的x命令和栈帧原理,来分析和定位堆栈被破坏情况下的崩溃原因。
gdb的x命令
x
命令是用于按照一定格式打印给定地址的内容,它的命令格式如下:
x [Address expression]
x /[Format] [Address expression]
x /[Length][Format] [Address expression]
[Address expression]
:给定的需要打印内容的地址,这个地址可以是- 指定的地址如
0x7fffffff270
- C/C++中的指针,如下的
ptr
指针
const char *ptr="helloworld";
- 寄存器,如64位系统中的
rip
、rsp
、rbp
等
- 指定的地址如
[Format]
:给定地址打印内容的输出格式,支持的格式包括o
- 八进制x
- 十六进制d
- 十进制u
- 无符号十进制t
- 二进制f
- 浮点数a
- 地址c
- 字符s
- 字符串i
- 指令
然后可以用增加下述修饰,表示以多少个字节作为一个内存单元,默认是4字节
b
- 字节h
- 半字(2字节)w
- 字(4字节)g
- 巨型字(8字节)[Length]
:需要显示的内存单元个数,即从当前地址向后显示Length
个内存单元的内容,一个内存单元的大小由上述的Format
决定
栈帧
栈帧是记录函数调用过程的结构,详细可以阅读之前写的一篇博文什么是栈帧_Respiration-CSDN博客。简单来说,我们这里用到的一个原理是,当前函数调用其他函数时,会把返回后执行的当前函数下一条指令地址压栈,再把当前函数的栈底压栈,然后再开始执行调用函数的逻辑。
实例分析
编写一段无意义的代码,构建堆栈被破坏无法直接用gdb查看
#include <stdint.h>
void dummyFunc(int32_t n)
{
int32_t szArr[10];
szArr[n] = n;
if (n < 20) {
dummyFunc(n + 1);
}
}
int main()
{
dummyFunc(0);
}
运行后当然崩溃了,因为当n
大于10时超出了szArr
的数组大小,但是栈上分配给数组的空间就那么大,只能非预期地修改了a[19]
这样的高地址内存内容,继而破坏了栈帧上记录的栈底地址和函数返回后继续执行的指令地址等,导致函数执行崩溃
然后用gdb
查看是一堆的??
#0 0x0000000f55555186 in ?? ()
#1 0x0000000000000003 in ?? ()
#2 0x0000000e00000000 in ?? ()
#3 0x0000000000000000 in ?? ()
接下来我们利用函数调用时下一条指令和栈底压栈的原理,来还原一下崩溃的过程
- 首先查看寄存器的信息,得到当前栈底寄存器
rbp
的值为0x7fffffffe050
,即指向当前栈底,而$rbp+8
即为函数返回后下一条执行函数地址
(gdb) info reg
rax 0x0 0
rbx 0x5555555551c0 93824992235968
rcx 0x5555555551c0 93824992235968
rdx 0x14 20
rsi 0x7fffffffe5b8 140737488348600
rdi 0x14 20
rbp 0x7fffffffe050 0x7fffffffe050
rsp 0x7fffffffe010 0x7fffffffe010
r8 0x0 0
r9 0x7ffff7fe0d50 140737354009936
r10 0x7 7
r11 0x0 0
r12 0x555555555060 93824992235616
r13 0x7fffffffe5b0 140737488348592
r14 0x0 0
r15 0x0 0
rip 0xf55555186 0xf55555186
eflags 0x10246 [ PF ZF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
我们以8字节为内存单元,跳过被破坏的栈区域,往前查找128个内存单元,以地址格式输出,得到下图
根据图中的信息可以重建栈帧信息
栈底地址 返回后的下一条指令地址
0x7fffffffe140 0x401173
0x7fffffffe190 0x401173
0x7fffffffe1e0 0x401173
0x7fffffffe230 0x401173
0x7fffffffe280 0x401173
0x7fffffffe2d0 0x401173
0x7fffffffe320 0x401173
0x7fffffffe370 0x401173
0x7fffffffe3c0 0x401173
0x7fffffffe410 0x401173
- 用
addr2line
命令去查看指令地址对应的代码文件及行号
root@DESKTOP-QA1H9TD:/mnt/d/Code/os# addr2line -e a.out 0x401173
/mnt/d/Code/os/stack.cpp:10
- 至此我们得到了部分函数的调用关系和大致的行号信息,结合这些信息和代码,我们就可以继续去定位崩溃的原因了
参考资料
- GDB Command Reference - x command (visualgdb.com)
- x86ManualBacktrace - Devpit