部落格: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+0>
0x000000000040045a : mov %rsp,%rbp+1>
0x000000000040045d : sub $0x20,%rsp+4>
0x0000000000400461 : movq $0x1,-0x10(%rbp)+8>
0x0000000000400469 : movq $0x2,-0x18(%rbp)+16>
0x0000000000400471 : movl $0x3,-0x4(%rbp)+24>
0x0000000000400478 : mov -0x4(%rbp),%edx+31>
0x000000000040047b : mov -0x18(%rbp),%rsi+34>
0x000000000040047f : mov -0x10(%rbp),%rdi+38>
0x0000000000400483 : callq 0x400448+42>
0x0000000000400488 : mov $0x0,%eax+47>
0x000000000040048d : leaveq+52>
0x000000000040048e : retq+53>
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的預設行為。