目的是 是解决 堆栈信息缺失的情况
本文是以gdb 的内存分布以x86 elf 的格式为例
通常
当前堆栈的输入 参数 为 rdi rsi , (从第一个参数开始) 在x86中,函数的所有参数都是一个个通过push指令压至栈中。
而在x64中,通常函数的前6个参数会优先存放在寄存器中,分别是rdi, rsi, rdx, rcx, r8d和r9d,超过6个的则放在栈中。
x86 常用的汇编寄存器和指令在以下链接 :
x86和x64通用寄存器在汇编中的特殊默认用法
https://zhuanlan.zhihu.com/p/575201221?utm_id=0
https://github.com/GreyZhang/g_unixC
堆栈的内存分布图
一个完整的堆栈 应该包括 old esp ,old rip(返回地址) ,输入参数,localvar其实堆栈被破坏是分几种情况的。
1. 当前的堆栈不完整, bt 出来的符合都是 ???
2. 堆栈被破坏了,内存越界了,后面的出现随机乱地出现崩溃的情况。
3. 崩溃,但是属于存在release版本 找到对应的符合 和 无法进行 i locals和 i args ,无法定位到具体的行号
4. 内存越界,数组越界
https://zhuanlan.zhihu.com/p/575201221?utm_id=0
通俗的崩溃情况:
当内存出现了覆盖,例如写一个内存区域时没控制好长度,越界了,把其他字段的值破坏了,这个时候再使用这个被破坏的字段就会出现崩溃;
当内存被重复释放,一块内存已经被释放了,但是因为逻辑问题,再次尝试释放这块内存,这个时候也会出现崩溃,再次尝试释放不一定是用户主动行为,可能是编译器偷偷安排的工作,例如析构函数的调用。
个人的应对方案
-
- 当前的堆栈不完整, bt 出来的符合都是 ??? 编译的时候添加编译选项:-fstack-protector 和 -fstack-protector-all 这两个选项指示编译器开启栈保护,再在崩溃的位置 反汇编 ,根据栈搜索找到有符号的指令地址,从而跟踪到是具体哪个函数。接着可以采用方案3进行进一步定位
-
- 可先采用在编译器先添加内存 编译的时候添加编译选项:-fstack-protector 和 -fstack-protector-all 这两个选项指示编译器开启栈保护,这样在栈乱序的第一时间可以dump出来现场。
可加在Makefile里面。
1. 可以通过在反汇编里面查找 寄存器的参数,和 已了解到的断点反汇编的函数符合。从而判断该函数是否被正常使用。
2. acan ,采用 AddressSanitizer
从上述图可见,栈底的地址 保存的是返回指令地址
实例 从x86_64 位的数据来看,这除了86 32位 的架构外,64 位系统是带有其他的保留预留地址存在
—> ebp 在未受改变之前始终指向栈帧的开始,也就是栈底,所以ebp的用途是在堆栈中寻址用的。
—> esp是会随着数据的入栈和出栈移动的,也就是说,esp始终指向栈顶。
见下图,假设函数A调用函数B,我们称A函数为"调用者",B函数为“被调用者”则函数调用过程可以这么描述:
(1)先将调用者(A)的堆栈的基址(ebp)入栈,以保存之前任务的信息。
(2)然后将调用者(A)的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者B的栈底)。
(3)然后在这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间。
(4)函数B返回后,从当前栈帧的ebp即恢复为调用者A的栈顶(esp),使栈顶恢复函数B被调用前的位置;然后调用者A再从恢复后的栈顶可弹出之前的ebp值(可以这么做是因为这个值在函数调用前一步被压入堆栈)。这样,ebp和esp就都恢复了调用函数B前的位置,也就是栈恢复函数B调用前的状态