天天看點

64位x86的函數調用棧布局

作者:[email protected]部落格:blog.focus-linux.net    linuxfocus.blog.chinaunix.net

為啥還要就64位的情況單開一篇文章呢,難道64位與32位不一樣嗎?

還是先看測試代碼:

#include stdlib.h>

#include stdio.h>

static void test(void *p1, void *p2, int p3)

{

    p1 = p1;

    p2 = p2;

    p3 = p3;

}

int main()

    void *p1 = (void*)1;

    void *p2 = (void*)2;

    int p3 = 3;

    test(p1, p2, p3);

    return 0;

編譯gcc -g -Wall test.c,調試進入test

(gdb) bt

#0 test (p1=0x1, p2=0x2, p3=3) at test.c:10

#1 0x0000000000400488 in main () at test.c:18

檢視寄存器bp

(gdb) info registers rbp

rbp 0x7fffab620d00 0x7fffab620d00

那麼檢查棧的内容

(gdb) x /16xg 0x7fffab620d00

0x7fffab620d00: 0x00007fffab620d30 0x0000000000400488

0x7fffab620d10: 0x00000000004004a0 0x0000000000000002

0x7fffab620d20: 0x0000000000000001 0x0000000300000000

0x7fffab620d30: 0x0000000000000000 0x00007f93bbaa11c4

0x7fffab620d40: 0x0000000000400390 0x00007fffab620e18

0x7fffab620d50: 0x0000000100000000 0x0000000000400459

0x7fffab620d60: 0x00007f93bc002c00 0x85b4aff07d2e87c7

0x7fffab620d70: 0x0000000000000000 0x00007fffab620e10

開始分析棧的内容:

1. 0x00007fffab620d30:為test調用者main的BP内容,沒有問題;

2. 0x0000000000400488:為test的傳回位址,與前面的bt輸出相符,沒有問題;

3. 0x00000000004004a0:——這個是什麼東東??!!

4. 0x0000000000000002, 0x0000000000000001, 0x0000000300000000:這裡也有不少疑問啊?!

1. 這個0x00000003是第3個參數?因為是整數是以在64位的機器上,隻使用棧的一個單元的一半空間?

2. 參數的順序為什麼是3,1,2呢?難道是因為前兩個參數為指針,第三個參數為int有關?

我在工作中遇到了類似的問題,是以才特意寫了上面的測試代碼,就為了測試相同參數原型的函數調用棧的問題。看到這裡,感覺很奇怪,對于上面兩個問題很困惑啊。上網也沒有找到64位的x86函數調用棧的特别的資料。

難道64位機與32位機有這麼大的不同?!大家先想一下,答案馬上揭曉。

當遇到疑難雜症時,彙編則是王道:

(gdb) disassemble main

Dump of assembler code for function main:

0x0000000000400459 : push %rbp

0x000000000040045a : mov %rsp,%rbp

0x000000000040045d : sub $0x20,%rsp

0x0000000000400461 : movq $0x1,-0x10(%rbp)

0x0000000000400469 : movq $0x2,-0x18(%rbp)

0x0000000000400471 : movl $0x3,-0x4(%rbp)

0x0000000000400478 : mov -0x4(%rbp),%edx

0x000000000040047b : mov -0x18(%rbp),%rsi

0x000000000040047f : mov -0x10(%rbp),%rdi

0x0000000000400483 : callq 0x400448

0x0000000000400488 : mov $0x0,%eax

0x000000000040048d : leaveq

0x000000000040048e : retq

End of assembler dump.

看紅色部分的彙編代碼,為調用test時的處理,原來64位機器上,調用test時,根本沒有對參數進行壓棧,是以上面對于棧内容的分析有誤。後面的記憶體中存放的根本不是test的參數。看到彙編代碼,我突然想起,由于64位cpu的寄存器比32位cpu的寄存器要多,是以gcc會盡量使用寄存器來傳遞參數來提高效率。

讓我們重新運作程式,再次在test下檢視寄存器内容:

(gdb) info registers

rax 0x7f141fea1a60 139724411509344

rbx 0x7f14200c2c00 139724413742080

rcx 0x4004a0 4195488

rdx 0x3 3

rsi 0x2 2

rdi 0x1 1

rbp 0x7fff9c08d380 0x7fff9c08d380

rsp 0x7fff9c08d380 0x7fff9c08d380

這裡rdx,rsi和rdi清晰的顯示了三個參數的值,分别為3,2,1與前面的反彙編代碼相符。

而前面被當做參數的0x0000000000000002, 0x0000000000000001和0x00000003,其實為main中的局部變量p2, p1和p3的定義。如前面反彙編代碼中的藍色代碼,這三個局部變量在棧上的定義順序為p3, p1和p2,與棧的内容相符。

我寫本文的目的,主要是為了與大家分享一下64位機器上調試時需要注意的一個問題:函數調用時,編譯器會盡量使用寄存器來傳遞參數,這點與32位機有很大不同。在我們的調試中,要特别注意這點。

注:關于壓棧順序,參數的傳遞方式等等,都可以通過編譯選項來指定或者禁止的。本文的情況為GCC的預設行為。

繼續閱讀