2012年11月22日 星期四

gcc c call convention

environment: x86 32bit

看過 openwatcom c/pascal call convention, 接著看 linux 上最常用的 gcc, 它是如何生成產生 c call convention 的組合語言。

x86 push 指令會先將 %esp - 4 (32bit 環境) 然後將某個值複製到 %esp - 4 的位址。

ex:
%esp = 0xfff4

pushl $5
%esp - 4
copy $5 %esp (0xfff0)

asm_func.S
 1 .global asm_func
 2 asm_func:
 3    push   %ebp
 4    mov    %esp,%ebp
13    pushl $addr
14    pushl $56
15    pushl $'z'
16    pushl $fmt
18    call printf
19    addl $16, %esp
25    mov %ebp, %esp
26    pop %ebp
27    ret    
28 

37 .data
38 fmt: .asciz "%c %d %p\n"
39 addr: .asciz "str addr"
40 esp_fmt: .asciz "func esp: %x\n"
41 esp_value: .int

asm_func 是一個組合語言 function, 但使用 c 語言 call convention, 所以可以被 c 呼叫用來傳遞 c 參數, 就像這樣:
void asm_func(char c, int i, const char *ptr);
asm_func('a', 9, (char*)(0x1234));
不過這篇的主題不談這個。

 3    push   %ebp
 4    mov    %esp,%ebp
 
25    mov %ebp, %esp
26    pop %ebp
前後這段只是反向操作, 應該沒什麼問題。不過我的程式並沒有用到 %ebp, 為何要這樣做呢?原因是會有用到的時候, 有機會再談。這篇的主題一樣不談這個。

13    pushl $addr
14    pushl $56
15    pushl $'z'
16    pushl $fmt
18    call printf
19    addl $16, %esp

x86 c calling conventions 由右往左把 $addr, $56, $'z', $fmt 複製到 stack 中, 以 c 來看 L13 ~ L18 就像這樣:

printf("%c %d %p\n", 'z', 56, (char*)(0x80497be) );

pushl 4 次, 自然在 printf 執行後, 要把 esp 加回來 4+4+4+4 = 16, 這就是很多書上提到的 c 語言的 call convention。呼叫的一方負責把 stack 復原。但這些書沒提到的是 ...

看看 gcc 反組譯的結果, 令人驚訝, 看不到上述的結果, 而是:

gcc -S ...

 1 .global asm_func
 2 asm_func:
 3    push   %ebp
 4    mov    %esp,%ebp
 5    sub    $0x28,%esp
 6 
 7    movl   $addr,0xb(%esp)
 8    movl   $56,0x8(%esp)
 9    movl   $'z',0x4(%esp)
10    movl   $fmt, (%esp)
18    call printf
 
25    mov %ebp, %esp
26    pop %ebp
27    ret    
28 
37 .data
38 fmt: .asciz "%c %d %p\n"
39 addr: .asciz "str addr"
40 esp_fmt: .asciz "func esp: %x\n"
41 esp_value: .int


 5    sub    $0x28,%esp
 6 
 7    movl   $addr,0xb(%esp)
 8    movl   $56,0x8(%esp)
 9    movl   $'z',0x4(%esp)
10    movl   $fmt, (%esp)
18    call printf

呼叫的一方沒有將 stack 恢復 (沒有這樣的程式碼 addl $16, %esp), 其實不能這麼說, 而是呼叫的一方並沒有更動 stack pointer 自然不需要回復。

L5 先將 esp 留出 0x28 byte 空間, 再使用 L7 - L10 的方式將參數填入 stack, 一樣照右至左的順序, 這是書上沒提的部份, 我被這想法困擾很久了。在用組合語言呼叫 C 的時候, 大部份都是使用 asm_func.S 的作法, 後面的作法總是讓我困惑, 這次終於搞懂了。

回頭提這個:

 3    push   %ebp
 4    mov    %esp,%ebp
 
25    mov %ebp, %esp
26    pop %ebp
 

gcc 也不是生成(我才不寫這術語, 留點台灣味) 產生 L25, L26 的指令, 而是  leave, 因為 leave 就等於是這兩個指令。function return 實在太常使用, intel 創造了這個指令, 也相當程度混淆了初學者理解 c function return 這部份。

like this:

3    push   %ebp
4    mov    %esp,%ebp
25   leave

這就是 gcc 產生的組合語言。asm_func.S 是我從 gcc 反組譯之後修改過來的, 然後加上 printf 這些測試程式碼。和 open watcom 比較起來, open watcom 的組合語言程式碼容易理解, 不過基本原理都是一樣的。

ref:
http://en.wikipedia.org/wiki/X86_calling_conventions
http://stackoverflow.com/questions/672268/fastcall-gcc-example
http://pcmanlin.blogbus.com/logs/57953276.html
http://m.newsmth.net/article/KernelTech/69
http://stackoverflow.com/questions/7760736/gcc-fastcall-function-definition

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。