gdb堆栈被破坏时的定位方法

前几天碰到一个崩溃问题,利用coredump查看崩溃堆栈信息时,却发现堆栈被破坏了

(gdb) bt
#0  0x0000000f55555186 in ?? ()
#1  0x0000000000000003 in ?? ()
#2  0x0000000e00000000 in ?? ()
#3  0x0000000000000000 in ?? ()

不过正好还可以利用栈帧和寄存器的信息继续去定位崩溃原因,这里简单做下分享用到的一些命令和知识

gdb的x命令

x命令是用于按照一定格式打印给定地址的内容,它的命令格式如下:

x [Address expression]
x /[Format] [Address expression]
x /[Length][Format] [Address expression]
  • [Address expression]

    给定的需要打印内容的地址,这个地址可以是

    1. 指定的地址如`0x7fffffff270``

    2. ``C\C++中的指针,如下的ptr`指针

      const char *ptr="helloworld";
      
    3. 寄存器,如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 ?? ()

接下来我们利用函数调用时下一条指令和栈底压栈的原理,来还原一下崩溃的过程

  1. 首先查看寄存器的信息,得到当前栈底寄存器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
    
  2. 我们以8字节为内存单元,跳过被破坏的栈区域,往前查找128个内存单元,以地址格式输出,得到下图
    在这里插入图片描述

  3. 根据图中的信息可以重建栈帧信息

    栈底地址返回后的下一条指令地址
    0x7fffffffe1400x401173
    0x7fffffffe1900x401173
    0x7fffffffe1e00x401173
    0x7fffffffe2300x401173
    0x7fffffffe2800x401173
    0x7fffffffe2d00x401173
    0x7fffffffe3200x401173
    0x7fffffffe3700x401173
    0x7fffffffe3c00x401173
    0x7fffffffe4100x401173
  4. addr2line命令去查看指令地址对应的代码文件及行号

    root@DESKTOP-QA1H9TD:/mnt/d/Code/os# addr2line -e a.out 0x401173
    /mnt/d/Code/os/stack.cpp:10
    
  5. 至此我们得到了部分函数的调用关系和大致的行号信息,结合这些信息和代码,我们就可以继续去定位崩溃的原因了

参考资料

GDB Command Reference - x command (visualgdb.com)
x86ManualBacktrace - Devpit

  • 5
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值