如何在 Linux(Debian 和 Ubuntu)中打印出 C 中堆栈指针的当前值?
我尝试了谷歌但没有找到任何结果。
一个不可移植甚至不能保证工作的技巧是简单地将本地地址作为指针打印出来。
void print_stack_pointer() {
void* p = NULL;
printf("%p", (void*)&p);
}
这实际上将打印出与p
当前堆栈指针非常接近的地址
没有便携的方法可以做到这一点。
在 GNU C 中,这可能适用于具有名为 SP 的寄存器的目标 ISA,包括 x86,其中 gcc 将“SP”识别为 ESP 或 RSP 的缩写。
// broken with clang, but usually works with GCC
register void *sp asm ("sp");
printf("%p", sp);
GCC 现在不推荐使用本地寄存器变量的这种用法:
此功能唯一受支持的用途是在调用扩展 asm时为输入和输出操作数指定寄存器
定义寄存器变量不会保留寄存器。除了调用扩展 asm 时,不保证指定寄存器的内容。因此,明确不支持以下用途。如果它们似乎起作用,那只是偶然,并且可能由于周围代码的(看似)不相关的更改,甚至是未来版本 gcc 优化中的微小更改而停止按预期工作。...
在实践中它也被 clang 打破,它sp
被视为任何其他未初始化的变量。
除了duedl0r 对 GCC 的回答之外,您还可以使用__builtin_frame_address(0)
GCC 特定的(但不是x86特定的)。
获取本地地址(正如JaredPar 回答的那样)也是一种解决方案。
请注意,AFAIK C 标准理论上不需要任何调用堆栈。
记住 Appel 的论文:垃圾收集可以比堆栈分配更快;一个非常奇怪的 C 实现可以使用这种技术!但是AFAIK它从未用于C。
人们可以梦想另一种技术。并且您可以拆分堆栈(至少在最近的 GCC 上),在这种情况下,堆栈指针的概念意义不大(因为堆栈不是连续的,并且可以由每个调用帧的许多段组成) .
Linux
您可以使用proc
伪文件系统打印堆栈指针。
看看这里,在 /proc/your-pid/stat 伪文件,在字段28
,29
。
startstack %lu 堆栈的开始(即底部)地址。
kstkesp %lu ESP(堆栈指针)的当前值,可在进程的内核堆栈页面中找到。
你只需要解析这两个值!
您还可以使用扩展的汇编指令,例如:
#include <stdint.h>
uint64_t getsp( void )
{
uint64_t sp;
asm( "mov %%rsp, %0" : "=rm" ( sp ));
return sp;
}
对于 32 位系统,必须将 64 替换为 32,并将 rsp 替换为 esp。
您在文件中有该信息,/proc/<your-process-id>/maps
与字符串出现在同一行[stack]
(因此它独立于编译器或机器)。这种方法的唯一缺点是要读取该文件,它需要是 root。
试试 lldb 或 gdb。例如,我们可以在 lldb 中设置回溯格式。
settings set frame-format "frame #${frame.index}: ${ansi.fg.yellow}${frame.pc}: {pc:${frame.pc},fp:${frame.fp},sp:${frame.sp}} ${ansi.normal}{ ${module.file.basename}{\`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}{${function.is-optimized} [opt]}{${frame.is-artificial} [artificial]}\n"
所以我们可以在调试中打印 bp , sp 如
frame #10: 0x208895c4: pc:0x208895c4,fp:0x01f7d458,sp:0x01f7d414 UIKit`-[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 376
您可以使用 setjmp。确切的细节取决于实现,请查看头文件。
#include <setjmp.h>
jmp_buf jmp;
setjmp(jmp);
printf("%08x\n", jmp[0].j_esp);
这在执行未知代码时也很方便。您可以在前后检查 sp 并longjmp
进行清理。