[英]My (AT&T) assembly (x86-x64) code should increment but doesn't
我正在嘗試組裝一個小程序(用於AT&T)。 我正在嘗試以整數形式從用戶那里獲得輸入,在此之后增加它,然后輸出增加的值。 但是,該值不會增加。 我花了最后幾個小時嘗試所有可能的方法,但是仍然無法正常工作,所以我有一個想法,就是我可能對匯編中的一個概念不太了解,導致我沒有發現錯誤。 這是我的代碼:
1 hiString: .asciz "Hi\n"
2 formatstr: .asciz "%ld"
3
4 .global main
5
6 main:
7 movq $0, %rax #no vector registers printf
8 movq $hiString, %rdi #load hiString
9 call printf #printf
10 call inout #inout
11 movq $0, %rdi #loading exit value into register rdi
12 call exit #exit
13
14 inout:
15 pushq %rbp #Pushing bp
16 movq %rsp, %rbp #Moving sp to bp
17 subq $8, %rsp #Space on stack for variable
18 leaq -8(%rbp), %rsi
19 movq $formatstr, %rdi #1st argument scanf
20 movq $0, %rax #no vector for scanf registers
21 call scanf #scanf
22 incq %rsi
23 call printf
從我的一個朋友那里獲得的教程中,我知道第17至19行是必要的,但是,我認為我不使用我在那兒找到的堆棧空間,因此我懷疑該錯誤與之有關。 當然,我不確定。 先感謝您。
編輯,更新代碼(現在仍在子例程中調用printf)
1 hiString: .asciz "hi\n"
2 formatstr: .asciz "%ld"
3
4 .global main
5
6 main:
7 movq $0, %rax
8 movq $hiString, %di
9 call printf
10 call inout
11 movq $0, %rdi
12 call exit
13
14 inout:
15 pushq %rbp
16 movq %rsp, %rbp
17 subq $8, %rsp
18 leaq -8(%rbp), %rsi
19 movq $formatstr, %rdi
20 movq $0, %rax
21 call scanf
22 popq %rax
23 incq %rax
24 movq %rax, %rsi
25 movq $0, %rax
26 call printf
27 addq $8, %rs
它現在運行並遞增,但是,當輸出遞增的值時,該值之后會出現一些奇怪的符號。
編輯:沒關系,上面只發生過一次,現在沒有輸出增量值,只有怪異的跡象。
這是有關如何正確調用scanf
的經典混淆的匯編級版本。
14 inout:
15 pushq %rbp #Pushing bp
16 movq %rsp, %rbp #Moving sp to bp
17 subq $8, %rsp #Space on stack for variable
18 leaq -8(%rbp), %rsi
19 movq $formatstr, %rdi #1st argument scanf
20 movq $0, %rax #no vector for scanf registers
21 call scanf #scanf
(編者注:最好是mov $formatstr, %edi
Linux非PIE可執行文件中的mov $formatstr, %edi
,或者更可移植的與位置無關的lea formatstr(%rip), %rdi
將靜態存儲中的字符串地址放入寄存器中)。
到目前為止,您的代碼是正確的(除非您沒有正確對齊堆棧,但是現在不必擔心, scanf
可能會讓您擺脫它)。 更新:現代版本的glibc確實存在scanf,該錯誤會在未對齊的RSP上發生故障 ,例如從Ubuntu 18.04開始,可能更早。
22 incq %rsi
這是您出問題的地方。 在調用之前,將RSI( scanf
的第二個參數寄存器)設置為指向存儲位置的指針 。 scanf
從stdin讀取一個數字並將其寫入該存儲位置 ,而不是RSI。
從評論的討論中,您的意圖是將一個值添加到scanf
讀取的值中,然后立即將其打印出來。 正如其他一些人指出的那樣,在scanf
返回之后,您不能假定加載到RSI,RDI或RAX中的值是完整的。 ( x86-64 psABI指定通過函數調用保留哪些寄存器:在整數寄存器中,僅保留RBX,RBP和R12至R15。如果您打算進行大量匯編,則應閱讀本文檔的封面,以了解更多信息。在x86-64上進行編程(警告:Windows使用不同的ABI,其調用約定已記錄在MSDN上,請參閱x86標簽wiki中的鏈接。)
因此,您必須將args從頭設置為printf
,因為scanf
破壞了這些寄存器:
movq -8(%rbp), %rsi # load variable as arg 2 of printf
incq %rsi # and add one
movq $formatstr, %rdi # first argument to printf
xorl %rax, %rax # no vector args to printf
call printf
請在此處密切注意scanf
和printf
之間的區別:您可以為兩者使用相同的格式字符串,但是當調用scanf
您傳遞存儲位置的地址 ( leaq -8(%rbp), %rsi
),而當您調用printf
傳遞要打印的值 ( movq -8(%rbp), %rsi; incq %rsi
)。
(實際上,在調用printf
,您應該使用略有不同的格式字符串,因為您需要在數字后打印換行符,因此"%ld\\n"
會更好。)
您當前的代碼幾乎以不同的方式完成了此任務。 我這樣做是因為在函數中間弄亂堆棧指針( popq %rax
)是一種不好的做法。 (還記得我說過的關於未正確對齊堆棧的內容嗎?如果在進入時設置一個完整的“調用框架”,然后不理會堆棧指針直到退出,則使堆棧保持對齊會容易得多。從技術上講,您只需要具有不過,堆棧指針會在每個調用指令的位置對齊。)
您也沒有正確結束該功能:
27 addq $8, %rs
我認為您沒有復制並粘貼整個程序-看起來好像已經在行中間被切斷了。 無論如何,如果您首先要麻煩幀指針(x86-64上不需要幀指針),則應再次使用它退出:
movq %rbp, %rsp
popq %rbp
ret
順便說一句,“ AT&T”匯編語法用於許多不同的CPU體系結構。 在談到匯編語言,我們總是首先要知道CPU的架構; 語法變體(如果有)是次要的。 您應該將問題命名為“我的匯編程序(x86-64,AT&T語法)...”。
作為最后的建議,我建議您編譯此C程序
#include <stdio.h>
static void inout(void)
{
long x;
scanf("%ld", &x);
printf("%ld\n", x+1);
}
int main(void)
{
printf("hi\n");
inout();
return 0;
}
使用您選擇的C編譯器時,請使用與-S -O2 -fno-inline
等效的選項(即:生成文本匯編語言,進行了優化,但不進行任何內聯),然后逐行讀取匯編輸出。 每當C編譯器執行與您不同的操作時,這可能意味着C編譯器知道您不知道的內容,因此您應該了解該內容。
或更簡單地說, 在Godbolt編譯器資源管理器中查看它
回復:更新的代碼:
它現在運行並遞增,但是,當輸出遞增的值時,該值之后會出現一些奇怪的符號。
傳遞arg的寄存器被調用。 您可以在不將格式字符串放入%rdi
情況下調用printf
,而在scanf
返回后必須假定該字符串保留垃圾。
使用調試器單步執行代碼。 使用ni
結束gdb中的call
。 (有關GDB技巧,請參見x86標簽Wiki的底部)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.