2

在尝试获取 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 上没有问题。

4

4 回答 4

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
于 2011-10-18T18:40:43.040 回答
4

而不是使用register带有约束的变量,您应该只编写一些显式的内联汇编程序来 fetch %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;})
于 2011-10-19T06:31:39.093 回答
3

我最近需要一个多拱版本:

/**
 * helps to check the architecture macros:
 * `echo | gcc -E -dM - | less`
 *
 * this is arm, x64 and i386 (linux | apple) compatible
 * @return address where the stack starts
 */
void *get_sp(void) {
    void *sp;
    __asm__ __volatile__(
#ifdef __x86_64__
        "movq %%rsp,%0"
#elif __i386__
        "movl %%esp,%0"
#elif __arm__
        // sp is an alias for r13
        "mov %%sp,%0"
#endif
        : "=r" (sp)
        : /* no input */
    );
    return sp;
}
于 2021-01-13T09:40:27.083 回答
0

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

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

于 2011-10-18T19:01:09.163 回答