2011-10-18 24 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