此 GNU C/C++ 代码(GNU 因为它用于检索当前堆栈指针值的内联汇编风格)将打印堆栈指针的当前值(以十六进制表示)
static __inline__ void printESP(void) {
static const hexdigit[] = "0123456789ABCDEF\n";
uintptr_t SP;
__asm__ __volatile__("mov %%esp, %0\n\t" : "=r"(SP));
write(1, &hexdigit[SP >> 28], 1);
write(1, &hexdigit[0xf & SP >> 24], 1);
write(1, &hexdigit[0xf & SP >> 20], 1);
write(1, &hexdigit[0xf & SP >> 16], 1);
write(1, &hexdigit[0xf & SP >> 12], 1);
write(1, &hexdigit[0xf & SP >> 8], 1);
write(1, &hexdigit[0xf & SP >> 4], 1);
write(1, &hexdigit[0xf & SP], 1);
write(1, &hexdigit[16], 1);
}
由于static __inline__
,gcc 在大多数情况下(实际上,如果您使用函数指针除外)实际上不会创建此函数,而是将代码直接嵌入到调用点。
您可以构建一个更复杂的部分,它会执行以下操作:
static __inline__ printESP(void)
{
static const char hexdigit[] = "0123456789ABCDEF\n";
uintptr_t SP;
char buf[9];
__asm__ __volatile__("mov %%esp, %0\n\t" : "=r"(SP));
buf[0] = hexdigit[SP >> 28];
buf[1] = hexdigit[0xf & SP >> 24];
buf[2] = hexdigit[0xf & SP >> 20];
buf[3] = hexdigit[0xf & SP >> 16];
buf[4] = hexdigit[0xf & SP >> 12];
buf[5] = hexdigit[0xf & SP >> 8];
buf[6] = hexdigit[0xf & SP >> 4];
buf[7] = hexdigit[0xf & SP];
buf[8] = hexdigit[16];
write(1, buf, 9);
}
这会在堆栈上构造一个临时缓冲区并write()
仅调用一次。
第二段代码的缺点是它实际上消耗了 stackspace,这意味着如果你调用它,比如说,从一个溢出它的堆栈和/或损坏它的堆栈指针的函数中调用它,它就会崩溃。逐个字符发出地址的第一个微不足道可能仍然有效。
请注意您的初始汇编代码:这是使用全局缓冲区,因此不是线程安全的;如果你同时从多个线程调用它,它们会破坏变量并且你会得到垃圾。所以不,我不会帮助“修复”那个。
关于该方法的一般说明:就个人而言,我认为这就是调试器的用途。这种特定类型的运行时检测(以这种方式 - 通过直接打印值)相当无用;如果是运行时检测,那么最好记录到内存中的跟踪缓冲区,您可以在其中记录扩展信息,例如(部分)堆栈跟踪、线程 ID、时间戳、其他寄存器值等,以及堆栈指针。使用内存(环)缓冲区不依赖于文件描述符 1 的特殊含义,并且允许“上下文”信息比仅堆栈指针值更能描述正在发生的事情。
此外,如果该值不必 100% 准确,则可以使用gcc builtinsuintptr_t SP = (uintptr_t)__builtin_frame_address(0)
代替。您根本不需要内联汇编。不同之处在于它会在函数入口处为您提供堆栈指针,而不是调用站点的当前堆栈指针,即它不考虑局部变量和其他“临时”的堆栈使用情况。