from: http://hi.baidu.com/bluebanboom/blog/item/381959af65ff36fbfaed5068.html
閑着沒事的時候就拿自己動手寫作業系統裡的例子來編譯,但我裝的是AMD64的ubuntu,而實際上這并沒帶來多少好處,反倒帶來不少麻煩!
在編譯第五章中彙編和C混合程式設計的例子時遇到了麻煩,因為用的nasm不支援64位的elf格式,我就找到了yasm,yasm是nasm的完全重寫,
輸出格式支援64位的elf,但是編譯連結後運作程式始終提示段錯誤。
一開始我以為可能yasm的輸出與gcc的輸出不一樣,為了驗證我的想法,我寫了一個簡單,不需要傳遞參數的函數,結果連結後的
程式居然可以運作。看來可能是參數傳遞上出了問題。
一開始,我用hte來反彙編看函數調用過程,但是沒個比較,也不知道問題出在了哪兒,而且hte的反彙編效果很差。
還好,我還有個32位的debian而且,在debian下,程式順利編譯通過了。
我就在debian下用ida反彙編檢視
兩個程式反彙編的結果一對照,發現
在debian下編譯的程式是通過堆棧來傳遞參數的
而在ubuntu amd64下編譯的程式是通過寄存器來傳遞的參數
而原來的彙編代碼是通過堆棧來處理參數,當然會出問題。
修改後的foo.asm
============================
extern choose ; int choose(int a, int b);
[section .data] ; 資料在此
num1st dd 5
num2nd dd 6
[section .text] ; 代碼在此
global _start ; 我們必須導出 _start 這個入口,以便讓連結器識别。
global myprint ; 導出這個函數為了讓 bar.c 使用
_start:
;mov edi,3 ; ┓
;mov esi,4 ; ┃
mov esi,[num2nd]
mov edi,[num1st]
;push num1st
;push num2nd
call choose ; ┣ choose(num1st, num2nd);
add esp, 4 ; ┛
mov ebx, 0
mov eax, 1 ; sys_exit
int 0x80 ; 系統調用
; void myprint(char* msg, int len)
myprint:
;mov edx, [esp + 8] ; len
;mov ecx, [esp + 4] ; msg
mov edx, esi ; len
mov ecx, edi
mov ebx, 1
mov eax, 4 ; sys_write
int 0x80 ; 系統調用
ret
================================================
然後我想知道gcc的參數傳遞方式,就在網上搜了一哈,找到了下邊的資料了:
==========================================
版權為 win_hate 所有, 轉載請保留作者名字
我這段時間要把以前的一個 x86_32 的 linux 程式移植到 x86_64(AMD) 的 linux 環境裡. 由于寫的是數學算 法, 64 與 32 位有很大不同, 代碼實際上要重寫. 看了點資料後, 覺得 AMD64 的擴充于以前 16 到 32 位的擴充很類 似, e**, 擴充為 r**, 此外還多了8個通用寄存器 r8~r15.指令格式與32位的極為相似. 我覺得比較容易, 是以沒再仔細看, 就開 始動手寫了.
我的程式由若幹個彙編子產品于與若幹個c子產品構成, 很多c子產品要調用彙編子產品. 作為試驗, 我先寫了個簡單的彙編函數, 然後用c來調用. 結果 算出來的值始終是錯誤的. 這令我很惱火, 因為函數很簡單, 沒有多少出錯的餘地. 後來我把程式反彙編出來, 錯誤馬上浮現出來了, 函數的參數居然 是通過寄存器來傳遞的. 我憑以前的經驗, 從堆棧裡取參數, 算出的結果當然不對了. 我以前不是沒碰到過用寄存器傳遞參數的情況, 但所在的環境都不 是 pc. 在 x86_32/linux 中, 即使用 -O3 優化選項, gcc 仍通過棧來傳遞參數的.
是以我們現在知道, 在 x86_64/linux/gcc3.2 中, 即使不打開優化選項, 函數的參數也會通過寄存器來傳遞, 這肯定是闊了的表現(通用寄存器多了).
我試驗了多個參數的情況,發現一般規則為, 當參數少于7個時, 參數從左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。當參數為 7 個以上時, 前 6 個與前面一樣, 但後面的依次從 "右向左" 放入棧中。
例如:
CODE
(1) 參數個數少于7個:
f (a, b, c, d, e, f);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9
g (a, b)
a->%rdi, b->%rsi
有趣的是, 實際上将參數放入寄存器的語句是從右到左處理參數表的, 這點與32位的時候一緻.
CODE
2) 參數個數大于 7 個的時候
H(a, b, c, d, e, f, g);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%rax
g->8(%esp)
f->(%esp)
call H
易失寄存器:
%rax, %rcx, %rdx, %rsi, %rdi, %r8, %r9 為易失寄存器, 被調用者不必恢複它們的值。
顯然,這裡出現的寄存器大多用于參數傳遞了, 值被改掉也無妨。而 %rax, %rdx 常用于
數值計算, %rcx 常用于循環計數,它們的值是經常改變的。其它的寄存器為非易失的,也
就是 rbp, rbx, rsp, r10~r15 的值如果在彙編子產品中被改變了,在退出該子產品時,必須将
其恢複。
教訓:
用彙編寫子產品, 然後與 c 整合, 一定要搞清楚編譯器的行為, 特别是參數傳遞的方式. 此外, 我現在比較擔心的一點是, 将來如果要把程式移植 到 WIN/VC 環境怎麼辦? 以前我用cygwin的gcc來處理彙編子產品, 用vc來處理c子產品, 隻需要很少改動. 現在的問題是, 如果VC用 不同的參數傳遞方式, 那我不就麻煩了?
補充:
前面的參數 a, b, c, d 等, 都是整數, 長整數, 或指針, 也就是說, 能放到寄存器裡頭的. 如果你要傳遞一個很大的結構, 我估計編譯器也隻能通過棧來傳遞了.
環境為 AMD Athlon64, Mandrak linux 9.2, GCC3.3.1