2011-10-18 25 views
2

我试图获得在C当前堆栈指针时(使用内联ASM)碰到一些奇怪的行为。代码如下:获取堆栈指针用C在Mac OS X Lion的

$ g++ test.cc -o test 

它产生下面的汇编:

#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选项编译代码

__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

是不是一个_function call_设计得到的堆栈地址基本上注定了设计? – zneak

+0

此外,为什么该函数是一个非静态类方法*?你可能想要一个'this'指针? –

+0

检查了原始代码,并将其声明为静态,并且更新了该问题。我只是在做一些现有的代码。大多数用例使用不等式,所以如果该值稍微偏离,那么它不是太多问题。对我来说关键的问题是非法指令的例外。 –

回答

5

与试图通过一个函数调用来获取堆栈指针的问题是,被调用函数内部的堆栈指针指向,这将是该函数返回后完全不同的值,因此,您采集的地址通话后无效的位置。你也使得假设得到了由编译器平台上不加函数序言(即既您的功能目前有一个序幕编译器在其中设置了当前活动记录栈的功能上,这将改变您试图捕获的RSP的价值)。至少,如果编译器没有添加函数序言,则需要减去所用平台上指针的大小,以便实际将“真实”地址获取到堆栈所在的位置在函数调用返回后指向。这是因为组件命令call推动的返回地址中的指令指针压入堆栈,和在ret被叫会弹出堆栈该值。因此,在被调用者内部,至少会有一个栈指针指向的返回地址指令,并且该函数调用后该位置将无效。最后,在某些平台上(可惜没有X86),则可以使用__attributes__((naked))标签来创建一个功能方面与gcc没有序幕。使用inline关键字,以避免开场白并不完全可靠,因为它不强制编译器内联函数...在某些低优化级别,不会出现内联,你会再次结束了一个序幕,并如果您决定在这些情况下使用地址,堆栈指针将不会指向正确的位置。

如果您必须具有堆栈指针的值,那么唯一可靠的方法是使用assembly,遵循平台的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 
+0

它看起来像__attribute __((裸))仅在某些平台上受支持,并被x86上的gcc忽略。 –

+0

感谢您的信息......我将修改我的帖子。 – Jason

0

我没有为参考,但GCC是众所周知的偶尔(经常)在直列存在行为不当如果编译没有进行优化,则为汇编。所以你应该总是加上-O1的标志。

作为一个侧面说明,你正在试图做的是不是一个优化编译器的存在非常强大的,因为编译器可以内联调用current_stack_pointer(),因此返回值可能是由一个近似当前的堆栈指针值(甚至不是下限)。

3

而不是使用register变量和约束,你应该写一些明确的内联汇编程序来获取%esp

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

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

你也可以这样使用gcc语句表达式转换为宏

+0

要调用一个函数需要一个'call'命令(按下堆栈中的返回指令地址),因此'ESP'的值将指向堆栈中被调用者堆栈帧内的一个值,而不是呼叫者,召集者 。这使得返回的堆栈指针值在调用者中不可用,因为一旦被调用者完成,它不会反映堆栈指针的当前地址。我发现你已经在'assembly'标记下获得了很多声望点,那么是否有一些我错过了这段代码将返回一个有效的栈指针值给调用者的地方? – Jason

+1

@Jason:当然可以将它转换成一个可以避免这个问题的宏(我已经更新了包含这个的答案),但实际上大多数情况下当你想知道堆栈指针时,它的下限实际上就足够了(即“当前的筹码不比这更深”)。在C代码中请求确切的堆栈指针是无论如何都是愚蠢的,因为C编译器会很高兴地将堆栈指针转换为它认为合适的地方 - 不能保证它在读取它和当您使用该值时不会改变。 – caf

+0

谢谢,这是一个很好的解释。 – Jason