[英]Compiling C to 32-bit assembly with GCC doesn't match a book
我一直在嘗試將此 C 程序編譯為匯編程序,但一直無法正常工作。
我正在為初學者閱讀 Dennis Yurichev 逆向工程,但我沒有得到相同的輸出。 它是一個簡單的 hello world 語句。 我正在嘗試獲取 32 位輸出
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
這就是這本書所說的輸出應該是什么
main proc near
var_10 = dword ptr -10h
push ebp
mov ebp, esp
and esp, 0FFFFFFF0h
sub esp, 10h
mov eax, offset aHelloWorld ; "hello, world\n"
mov [esp+10h+var_10], eax
call _printf
mov eax, 0
leave
retn
main endp
以下是步驟;
將打印語句編譯為 32 位(我目前正在運行 64 位電腦)
gcc -m32 hello_world.c -o hello_world
使用gdb反匯編
我明白了;
lea ecx,[esp+0x4]
and esp,0xfffffff0
push DWORD PTR [ecx-0x4]
push ebp
mov ebp,esp
push ebx
push ecx
call 0x565561d5 <__x86.get_pc_thunk.ax>
add eax,0x2e53
sub esp,0xc
lea edx,[eax-0x1ff8]
push edx
mov ebx,eax
call 0x56556030 <puts@plt>
add esp,0x10
mov eax,0x0
lea esp,[ebp-0x8]
pop ecx
pop ebx
pop ebp
lea esp,[ecx-0x4]
ret
我也用過
objdump -D -M i386,intel hello_world> hello_world.txt
ndisasm -b32 hello_world > hello_world.txt
但這些都不起作用。 我只是無法弄清楚出了什么問題。 我需要幫助。 看着你 Peter Cordes ^^
這本書的輸出看起來像 MSVC,而不是 GCC。 GCC 絕對不會發出main proc
因為那是 MASM 語法,而不是有效的 GAS 語法。 它不會做像var_10 = dword ptr -10h
這樣的事情。
(即使這樣做,您也不會在反匯編中看到匯編時常量定義,只會在編譯器的 asm 輸出中看到這本書建議您查看的內容gcc -S -masm=intel
輸出。 如何消除“噪音” “來自 GCC/clang 程序集輸出? )
因此存在很多差異,因為您使用的是不同的編譯器。 即使是現代版本的 MSVC(在 Godbolt 編譯器資源管理器上)也會使 asm 有所不同,例如不必費心將 ESP 對齊 16,也許是因為更現代的 Windows 版本或 CRT 啟動代碼已經這樣做了?
此外,默認情況下,您的 GCC 正在制作 PIE 可執行文件,因此請使用-fno-pie -no-pie
。 32 位 PIE 在效率和易於理解方面很糟糕。 請參閱如何擺脫呼叫 __x86.get_pc_thunk.ax 。 ( 在 x86-64 Linux 中也不再允許 32 位絕對地址?有關 PIE 可執行文件的更多信息,主要集中在 64 位代碼上)
main 的序言中額外笨重的堆棧對齊是 GCC8 為不需要alloca
函數優化的東西。 但是,當您不啟用優化時,即使當前的 GCC10 似乎也會發出完整的未優化版本 :(。 為什么 gcc 生成額外的返回地址?並試圖了解 gcc 在復制返回的 main 頂部的復雜堆棧對齊地址
將 printf 優化為 puts:請參閱如何讓 gcc 編譯器不優化像 printf 這樣的標准庫函數調用? 並且-O2 將 printf("%s\\n", str) 優化為 puts(str) 。 gcc -fno-builtin-printf
將是避免這種情況發生的一種方法,或者只是習慣它。 GCC 甚至在-O0
處也進行了一些優化,而其他編譯器僅在更高的優化級別進行。
MSVC 19.10 像這樣編譯您的函數(在 Godbolt 編譯器資源管理器上)並禁用優化(默認情況下,沒有編譯器選項)。
_main PROC
push ebp
mov ebp, esp
push OFFSET $SG4501
call _printf
add esp, 4
xor eax, eax
pop ebp
ret 0
_main ENDP
_DATA SEGMENT
$SG4501 DB 'hello, world', 0aH, 00H
GCC10.2 在序言中仍然使用過於復雜的堆棧對齊方式。
.LC0:
.string "hello, world"
main:
lea ecx, [esp+4]
and esp, -16
push DWORD PTR [ecx-4]
push ebp
mov ebp, esp
push ecx
sub esp, 4
# end of function prologue, I think.
sub esp, 12 # make sure arg will be 16-byte aligned
push OFFSET FLAT:.LC0 # push a pointer
call puts
add esp, 16 # pop the arg-passing space
mov eax, 0 # return 0
mov ecx, DWORD PTR [ebp-4] # undo stack alignment.
leave
lea esp, [ecx-4]
ret
是的,這是非常低效的。 如果你調用你的函數除了main
之外的任何東西,它已經假設 ESP 在函數入口對齊 16:
# GCC10.2 -m32 -O0
.LC0:
.string "hello, world"
foo:
push ebp
mov ebp, esp
sub esp, 8 # reach a 16-byte boundary, assuming ESP%16 = 12 on entry
#
sub esp, 12
push OFFSET FLAT:.LC0
call puts
add esp, 16
mov eax, 0
leave
ret
所以它仍然沒有結合這兩sub
指令,但你確實告訴它不要優化,所以需要腦殘代碼。 請參閱為什么 clang 使用 -O0 產生效率低下的 asm(對於這個簡單的浮點和)? 例如。
我的 GCC 會非常熱切地將對printf
的調用交換為puts
! 我沒有設法找到使編譯器不執行此操作的命令行選項。 即程序具有相同的外部行為,但機器代碼是
#include <stdio.h>
int main(void)
{
puts("hello, world");
}
因此,您將很難嘗試獲得與書中完全相同的程序集,因為該書中的程序集調用printf
而不是puts
!
首先你編譯而不是反編譯。
在沒有優化的情況下編譯時會產生很多噪音。 如果您使用優化進行編譯,您將獲得與您擁有的代碼幾乎相同的更小代碼(為了防止從 printf 更改為 puts,您需要刪除'\\n'
https://godbolt.org/z/cs4qe9 ):
.LC0:
.string "hello, world"
main:
lea ecx, [esp+4]
and esp, -16
push DWORD PTR [ecx-4]
push ebp
mov ebp, esp
push ecx
sub esp, 16
push OFFSET FLAT:.LC0
call puts
mov ecx, DWORD PTR [ebp-4]
add esp, 16
xor eax, eax
leave
lea esp, [ecx-4]
ret
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.