[英]Tracing program in assembly.
我試圖了解C程序在匯編級別的外觀,因此我運行gdb並在main和get_input上使用了反匯編。 該程序很短,所以我可以更好地遵循它。 我不明白有2行。 首先在main()中是:
0x00000000004005a3 <+4>: mov $0x0,%eax
我們保存舊的rbp值,並將rsp的當前值保存到rbp。 該指令的目的是什么?
get_input()中的另一個是:
000000000400581 <+4>: sub $0x10,%rsp
在這里,我們也從保存舊值rbp開始,將其壓入堆棧。 然后為rbp給出rsp的當前值。 然后從rsp中減去16個字節。 我知道這是分配的空間,但是為什么它是16個字節而不是8個字節? 我只將緩沖區設置為8個字節,其他8個字節的用途是什么?
#include <stdio.h>
void get_input()
{
char buffer[8];
gets(buffer);
puts(buffer);
}
int main()
{
get_input();
return 0;
}
函數main的匯編代碼轉儲:
0x000000000040059f <+0>: push %rbp
0x00000000004005a0 <+1>: mov %rsp,%rbp
0x00000000004005a3 <+4>: mov $0x0,%eax
0x00000000004005a8 <+9>: callq 0x40057d <get_input>
0x00000000004005ad <+14>: mov $0x0,%eax
0x00000000004005b2 <+19>: pop %rbp
0x00000000004005b3 <+20>: retq
End of assembler dump.
函數get_input的匯編代碼轉儲:
0x000000000040057d <+0>: push %rbp
0x000000000040057e <+1>: mov %rsp,%rbp
0x0000000000400581 <+4>: sub $0x10,%rsp
0x0000000000400585 <+8>: lea -0x10(%rbp),%rax
0x0000000000400589 <+12>: mov %rax,%rdi
0x000000000040058c <+15>: callq 0x400480 <gets@plt>
0x0000000000400591 <+20>: lea -0x10(%rbp),%rax
0x0000000000400595 <+24>: mov %rax,%rdi
0x0000000000400598 <+27>: callq 0x400450 <puts@plt>
0x000000000040059d <+32>: leaveq
0x000000000040059e <+33>: retq
對於main()
...
0x000000000040059f <+0>: push %rbp
將%RBP
的值壓入堆棧。
0x00000000004005a0 <+1>: mov %rsp,%rbp
將%RSP
的值復制到%RBP
(創建一個新的堆棧框架)。
0x00000000004005a3 <+4>: mov $0x0,%eax
將立即值0x0
移動到%EAX
。 也就是說,它將%EAX
。 當您處於64位模式時,這還將清除所有%RAX
。
0x00000000004005a8 <+9>: callq 0x40057d <get_input>
推送%RIP
的值(直接get_input()
撤消),然后跳轉到label / function get_input()
。
0x00000000004005ad <+14>: mov $0x0,%eax
根據AMD64 System V ABI ,函數的返回值存儲在%RAX
(不考慮浮點和大型結構)。 它還說有兩組寄存器:保存呼叫者和保存被呼叫者。 調用函數時,不能期望保存調用者的寄存器保持不變,必須在必要時將它們自己保存在堆棧中。 同樣,被調用的函數必須使用被調用方保存的寄存器。 調用方保存的寄存器為%RAX
, %RDI
, %RSI
, %RDX
, %RCX
, %R8
, %R9
, %R10
和%R11
。 被調用者保存的寄存器為%RBX
, %RSP
, %RBP
, %R12
, %R13
, %R14
和%R15
。
現在,由於main()
顯然執行return 0
,所以它必須在%RAX
返回0
,對吧? 但是,應考慮兩點。 首先,在AMD64系統V ABI中, sizeof(int) == 4
。 %RAX
寬度為8個字節,但%EAX
寬度為4個字節,因此應使用%EAX
來處理int
范圍的東西,例如main()
的返回值。 其次, %EAX
是%RAX
一部分,而%RAX
是保存呼叫者的,因此在調用后我們不能依靠它的值。 因此,我們執行MOV $0x0, %EAX
,以將函數的返回值設置為零。
0x00000000004005b2 <+19>: pop %rbp
恢復main()
的調用者的%RBP
,即銷毀main()
的堆棧幀。
0x00000000004005b3 <+20>: retq
從main()
返回,返回值為0
。
然后,我們有get_input()
...
0x000000000040057d <+0>: push %rbp
將%RBP
的值壓入堆棧。
0x000000000040057e <+1>: mov %rsp,%rbp
將%RSP
的值復制到%RBP
(創建一個新的堆棧框架)。
0x0000000000400581 <+4>: sub $0x10,%rsp
從%RSP
減去16(為當前幀保留16個字節的臨時存儲空間)。
0x0000000000400585 <+8>: lea -0x10(%rbp),%rax
將有效地址-0x10(%RBP)
加載到%RAX
。 也就是說,它將%RBP
的值減去16的結果加載到%RAX
。 這意味着%RAX
現在指向本地臨時存儲的第一個字節。
0x0000000000400589 <+12>: mov %rax,%rdi
根據ABI的說法,函數的第一個參數在%RDI
上給出,第二個參數在%RSI
等等,在這種情況下, %RAX
的值作為要調用的函數的第一個參數給出。
0x000000000040058c <+15>: callq 0x400480 <gets@plt>
調用函數gets()
。
0x0000000000400591 <+20>: lea -0x10(%rbp),%rax
與上述相同。
0x0000000000400595 <+24>: mov %rax,%rdi
將%RAX
作為第一個參數傳遞。
0x0000000000400598 <+27>: callq 0x400450 <puts@plt>
調用函數puts()
。
0x000000000040059d <+32>: leaveq
與MOV %RBP, %RSP
等效MOV %RBP, %RSP
然后POP %RBP
,即破壞堆棧幀。
0x000000000040059e <+33>: retq
從函數get_input()
返回而沒有正確的返回值。
現在...
MOV $0x0, %EAX
該指令的目的是什么?
該指令的第二個實例非常重要,因為它設置了main()
的返回值。 但是,第一個實際上是多余的。 您可能在編譯器上禁用了優化。
然后從rsp中減去16個字節。 我知道這是分配的空間,但是為什么它是16個字節而不是8個字節? 我只將緩沖區設置為8個字節,其他8個字節的用途是什么?
ABI要求將%RSP
定位在每個函數調用之前的16字節邊界上。 順便說一句,您應該遠離靜態大小的緩沖區和gets()
。
第一條指令mov $0x0, %eax
將零移動到EAX中以設置返回碼。
第二條指令sub $0x10,%rsp
分配內存並為系統調用對齊堆棧。 調用標准要求16字節對齊,而不是8。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.