在Mac OS X Lion上获取C中的堆栈指针

时间:2011-10-18 18:27:41

标签: c macos assembly x86-64

尝试在C中获取当前堆栈指针时(使用内联ASM),我遇到了一些奇怪的行为。代码如下:

#include <stdio.h>
class os {
  public:
    static void* current_stack_pointer();
};

void* os::current_stack_pointer() {
  register void *esp __asm__ ("rsp");
  return esp;
}

int main() {
  printf("%p\n", os::current_stack_pointer());
}

如果我使用标准gcc选项编译代码:

$ g++ test.cc -o test

它生成以下程序集:

__ZN2os21current_stack_pointerEv:
0000000000000000        pushq   %rbp
0000000000000001        movq    %rsp,%rbp
0000000000000004        movq    %rdi,0xf8(%rbp)
0000000000000008        movq    0xe0(%rbp),%rax
000000000000000c        movq    %rax,%rsp
000000000000000f        movq    %rsp,%rax
0000000000000012        movq    %rax,0xe8(%rbp)
0000000000000016        movq    0xe8(%rbp),%rax
000000000000001a        movq    %rax,0xf0(%rbp)
000000000000001e        movq    0xf0(%rbp),%rax
0000000000000022        popq    %rbp

如果我运行生成的二进制文件,它会以SIGILL(非法指令)崩溃。但是,如果我在编译中添加一些优化:

$ g++ -O1 test.cc -o test

生成的程序集更简单:

0000000000000000        pushq   %rbp
0000000000000001        movq    %rsp,%rbp
0000000000000004        movq    %rsp,%rax
0000000000000007        popq    %rbp
0000000000000008        ret

代码运行正常。所以对于这个问题;从Mac OS X上的C代码获取堆栈指针是否更稳定?相同的代码在Linux上没有问题。

3 个答案:

答案 0 :(得分:5)

尝试通过函数调用获取堆栈指针的问题是被调用函数内的堆栈指针指向一个在函数返回后将完全不同的值,因此您正在捕获一个地址。通话后无效的位置。您还假设编译器在该平台上没有添加函数序言(即,您的函数当前都有一个序言,其中编译器在函数的堆栈上设置当前激活记录,这将改变您尝试捕获的RSP值)。至少,如果编译器没有添加函数序言,你需要减去你正在使用的平台上的指针大小,以便实际获得堆栈所在的“真实”地址。从函数调用返回后指向。这是因为汇编命令call将指令指针的返回地址压入堆栈,并且被调用者中的ret将该值从堆栈中弹出。因此,在被调用者内部,至少会有一个返回地址指令,堆栈指针将指向该指令,并且该函数调用后该位置将无效。最后,在某些平台上(遗憾的是不是x86),您可以使用__attributes__((naked))标记在gcc中创建一个没有序言的函数。使用inline关键字来避免序言并不完全可靠,因为它不会强制编译器内联函数...在某些低优化级别下,内联不会发生,并且你最终会得到一个再次进行序言,如果您决定在这些情况下使用它的地址,则堆栈指针将不会指向正确的位置。

如果你必须拥有堆栈指针的值,那么唯一可靠的方法是使用程序集,遵循平台的ABI规则,使用汇编程序编译到目标文件,然后将该目标文件链接到可执行文件中的其余目标文件。然后,您可以通过在头文件中包含函数声明,将汇编程序函数公开给其余代码。所以你的代码看起来像(假设你使用gcc来编译程序集):

//get_stack_pointer.h
extern "C" void* get_stack_ptr();

//get_stack_pointer.S
.section .text
.global get_stack_ptr

get_stack_ptr:
    movq %rsp, %rax
    addq $8, %rax
    ret

答案 1 :(得分:3)

您应该只编写一些显式的内联汇编程序来获取register,而不是使用带有约束的%esp变量:

static void *getsp(void)
{
    void *sp;
    __asm__ __volatile__ ("movq %%rsp,%0"
    : "=r" (sp)
    : /* No input */);
    return sp;
}

您还可以使用gcc语句表达式将其转换为宏:

#define GETSP() ({void *sp;__asm__ __volatile__("movl %%esp,%0":"=r"(sp):);sp;})

答案 2 :(得分:0)

我没有这方面的参考,但是如果编译根本没有优化,GCC会在内联汇编的情况下偶尔(通常)行为不通。所以你应该总是添加-O1标志。

作为旁注,在优化编译器的存在下,您尝试做的事情并不是非常强大,因为编译器可能会内联对current_stack_pointer()的调用,因此返回的值可能是当前堆栈指针值的近似(甚至不是下限)。