学习栈回溯之前要先知道栈运行栈帧的原理下面有介绍不管栈运行还是栈回溯都依赖两个关键寄存器%rsp栈指针始终指向栈顶最新压入数据的位置。%rbp栈帧基址指向当前函数栈帧的起始位置用于定位该函数的参数和局部变量。1、函数栈帧的建立与销毁当函数 A 调用函数 B 时CPU 和编译器会协同完成以下步骤压入返回地址CPU 执行 call 指令。这个指令会自动将下一条指令的地址即函数 A 中调用 B的那行代码的下一行压入当前栈也就是函数 A 的栈帧末端。跳转到函数 BCPU 的指令指针RIP跳转到函数 B 的入口地址。保存调用者的栈基址函数 B 开始执行它的第一件事通常是执行 push %rbp。这会将函数 A 的 RBP 值即调用者函数的栈基址压入栈中。建立函数 B 的栈帧执行 mov %rsp, %rbp。让 RBP 指向当前栈顶的位置从而确立函数 B 自己的栈基址。此后RSP 会随着局部变量的分配而继续向下移动变化但 RBP 在函数 B 执行期间通常保持不变作为访问参数和局部变量的“锚点”。执行函数 B 的代码为局部变量分配栈空间sub $0x10, %rsp执行函数体内的逻辑。函数返回函数 B 执行完毕准备返回。先执行popq %rbp恢复RBP到 函数 A 的 RBP 值。然后执行ret指令。ret 指令会弹出栈顶的返回地址即步骤 1 压入的地址并将 RIP 设置为该地址从而回到函数 A 的 call 指令下一行。2、栈回溯找到存调用者的RBP寄存器上层函数的就可以找到返回地址确定返回地址在哪个函数的范围查找符号表确定这个函数符号以此继续的向上找RBP、返回地址确定全部的调用关系。终止条件2选1main 函数的 RBP 通常指向 0栈底全部都找完。限制回溯栈层级比如30层满足就结束。2.1、举例说明假设初始 RSP 32字节CPU 的通用寄存器如 RIP, RBP, RSP是 64 位宽的。能直接处理 64 位的整数和内存地址。1 个地址 8 字节 64 位。栈向下生长入栈操作rsp的数值要减少出栈rsp的数值要增加。call 指令会自动把【下一条指令地址】压入栈作为返回地址push 返回地址rsp 32 - 8 24。此时地址24 ~ 32 存储的就是 返回地址。进入函数执行 push % rbp把调用者的旧 rbp 压栈。rsp 24 - 8 16。此时地址 16 ~ 24存储的就是 旧 RBP上层函数的RBP。执行 mov % rsp, % rbp 指令。rsp值不变化把rsp值给rbp。rsp rbp 16。执行完上面的步骤最终栈布局如下栈地址32 -----------------------rsp32|返回地址|调用方 call 压入rsp32-824地址24(rbp8)-----------------------|旧 RBP|push rbp 压入rsp24-816地址16(rbp)-----------------------rbprsp 指向这里|||函数局部变量|||地址... -----------------------当前rbp的值是16rbp824这个24是头指针* (24)也就是*(rbp8) 是从头指针8字节取数据取的数据就是24到32之间的返回地址。使用*(rbp)取的就是从16到24的数据就是旧 RBP上层函数的RBP。因此栈回溯原理可以简述如下当前 RBP 指向栈帧基地址获取当前RBP。去栈上取 *(RBP 0) → 得到 旧 RBP。旧 RBP 就是上一层栈帧的基地址。再取 *(旧RBP 8) → 得到上一层的返回地址确定返回地址在哪个函数的范围查找符号表确定这个函数符号。循环直到 RBP 0。