- 註冊時間
- 2006-10-8
- 最後登錄
- 2019-6-20
- 主題
- 查看
- 積分
- 1306
- 閱讀權限
- 110
- 文章
- 1393
- 相冊
- 0
- 日誌
- 1
狀態︰
離線
|
有時,為了能增進程式的效率,或者在 C 語言當中加入一些組合語言程式碼,我們會採用內嵌的方式,將組合語言內嵌在C語言當中。針對這點,gcc提供了如圖 4.10的內嵌語法,讓我們在 C 語言中可以直接撰寫組合語言。其中,assembler template 為組合語言程式、output operands 為輸入參數、input operands 為輸入參數,而 list of clobbered registers 則指定了被更改的暫存器參數列表。
asm( ; 內嵌起始符號
assembler template ; 組合語言程式
: output operands ; : 輸出參數列表
: input operands ; : 輸入參數列表
: list of clobbered registers ; : 被更改的暫存器列表
); ; 結束符號
圖 4.10GNU 的內嵌組合語言語法
舉例而言,範例 4.24的C語言程式當中,就內嵌了組合語言。其中,第一欄是內嵌組合語言的C語言程式 inlineAsm1.c,第二欄則是其說明,第三欄是我們利用gcc將該程式轉換成inlineAsm.s 組合語言程式的片段,其中有底線的文字部分對應的是內嵌組合語言的展開部分。
在GNU的內嵌組合語言 (assembler template) 中,由於 % 被用作參數 %1, %2, … 的前置字元,因此當遇到暫存器(像是 %eax) 時,必須使用兩個 % 符號 (%%) 作為暫存器的起始標記,因此,在範例 4.24中,原本的 addl %ebx, %eax 指令,就成了 addl %%ebx, %%eax 這個更冗長的指令。
在範例 4.24的內嵌參數部分,:"a"(foo), "b"(bar) 是輸入參數,代表要將 foo 變數傳遞給限制條件為 "a" 的暫存器,由於在 IA32 中限制條件為"a"的暫存器就是 eax,因此,foo 變數將會傳給 eax 暫存器。同理,"b"(bar) 參數代表將 bar 傳給 ebx 暫存器。而輸出參數 :"=a"(foo) 則是將 eax 的結果傳回到 foo 變數中。
範例 4.24 內嵌組合語言的C程式
int main(void) ; ...
{ ; movl $10, -8(%ebp) ; foo=-8(%ebp)=10
int foo = 10, bar = 15; ; movl $15, -12(%ebp) ; bar=-12(%ebp)=15
; 輸入參數:
asm( ; movl -8(%ebp), %eax ; eax = -8(%ebp)=foo
"addl %%ebx,%%eax" ; movl -12(%ebp), %ebx ; ebx = -12(%ebp)=bar
:"=a"(foo) ; /APP ; 嵌入的程式
:"a"(foo), "b"(bar) ; addl %ebx,%eax ; eax = eax+ebx
); ; /NO_APP ; 傳出參數
; movl %eax, -8(%ebp) ; foo=eax
printf("foo=%d\n", foo); ; movl -8(%ebp), %eax
return 0; ; movl %eax, 4(%esp)
} ; movl $LC0, (%esp)
; call _printf
; ...
請讀者仔細觀察範例 4.24的組合語言程式碼,其中,內嵌指令 asm() 中的輸入參數部分為 :"a"(foo), "b"(bar)。這導致代表foo參數的-8(%ebp)被傳入限制條件 "a" 所對應的 eax暫存器中;而代表bar參數的-12(%ebp)則被傳入限制條件"b"所對應的ebx 暫存器中,然後,才執行內嵌指令 addl %ebx, %eax。在執行完畢之後,輸出參數 :"=a"(foo) 指定將 eax 傳回 foo:-8(%ebp) 變數中,於是最後補上了 movl %eax, -8(%ebp) 指令,完成整個內嵌呼叫的過程。
範例 4.25顯示了該程式的編譯執行過程,首先,我們利用 gcc 加上 –S 參數,將C語言編譯為組合語言檔案。接著,再利用gcc作為組譯連結器,將inlineAsm1.s組譯並連結為執行檔,然後執行之,結果,foo果然變成 25,這代表嵌入的組合語言程式發揮了效用。
範例 4.25 <範例 4.24> 的編譯與執行過程
$ gcc -S inlineAsm1.c -o inlineAsm1.s ; 將C程式編譯為組合語言
$ gcc inlineAsm1.s -o inlineAsm1 ; 組譯 inlineAsm1.s 組合語言檔
$ ./inlineAsm1 ; 執行inlineAsm1 的執行檔
foo=25 ; 結果: foo+bar 的值為25
在嵌入式組合語言 asm() 的語法中,每個參數的語法為 "限制條件"(變數),例如範例 4.24中的 "a"(foo), "b"(bar) 等,其中的 "a", "b" 都是限制條件,而 (foo), (bar) 則是C語言中所傳入傳出的變數。
限制條件可能代表某一個暫存器、一群暫存器、記憶體變數、或者數值等。例如,限制條件m代表『記憶體運算元』、p代表『合法的位址』,而 g 則代表『通用型暫存器、記憶體位址或立即定址的數值』的集合等等。
但是,限制條件的設定與CPU的架構有關,同一個符號在不同的CPU中會有不同的意義。例如,在IA32處理器中,限制條件a代表 eax、b 代表 ebx、c代表 ecx、d代表 edx、S 代表 esi、D代表 edi、r代表『eax ebx ecx edx esi edi』的集合、q代表 『eax ebx ecx edx』的集合、g代表『eax,ebx, ecx, edx 或變數』,而 1,則代表與參數 %1 相同, 2 代表與參數 %2 相同 …。
表格 4.1 GNU嵌入式組合語言的限制條件 (針對 IA32處理器)
限制 說明 限制 說明
A eax I (instant constant) 常數 (0到31 之間)
B ebx m (memory) 記憶體位址
C ecx r (register) 暫存器eax ebx ecx edx esi edi
D edx q eax ebx ecx edx
S esi g (general) eax,ebx, ecx, edx、變數或常數
D edi 0, 1, 2, … 參數 %0 %1 %2 ….
… … … …
利用表格 4.1中的限制條件,我們可以放寬暫存器的限制,改使用像 addl %2, %0 這樣的方式。這種方式可以讓編譯器自行選擇能使用的暫存器,如此,可以增進程式的效率。在範例 4.26中,我們就使用了此種參數指定的方式,利用 addl %2, %0 指令內嵌,然後,在輸出參數0 中指定 :"=r"(foo),這使得 foo 可使用的候選的暫存器放寬為 r 條件的『eax ebx ecx edx esi edi』。接著,在輸入參數 %1, %2中指定 :"0"(foo), "r"(bar),這同樣使得 bar 的候選暫存器放寬為r 條件的『eax ebx ecx edx esi edi』。但是,由於 foo 變數已經在輸出參數 :"=r"(foo)被指定了,因此,必須在第一個輸入參數的 (foo) 前加上 "0" 條件,告訴編譯器該參數所用的暫存器必須與 :"=r"(foo) 所選擇的一致,如此,就能讓編譯器有更多的選擇空間。範例 4.26顯示了我們將前述的內嵌組合語言之條件由 a, b 放寬為 r 之後的結果。
範例 4.26 將<範例 4.24> 的條件放寬為 “r” – (2)
int main(void) ;
{ ; movl $10, -4(%ebp) ; foo=-4(%ebp)=10
int foo = 10, bar = 15; ; movl $15, -8(%ebp) ; bar=-8(%ebp)=15
; ; 輸入參數:
asm( ; movl -4(%ebp), %ecx ; ecx = -4(%ebp)=foo
"addl %2,%0" ; movl -8(%ebp), %edx ; edx = -8(%ebp)=bar
:"=r"(foo) ; /APP ; 嵌入的程式
:"0"(foo), "r"(bar) ; addl %edx,%ecx ; eax = eax+ebx
:"0"
); ; /NO_APP
printf("foo=%d\n", foo); ; movl %ecx, %edx ; 傳出參數
return 0; ; movl %edx, %eax ; foo=eax
} ; movl %eax, -4(%ebp)
; movl -4(%ebp), %eax
; movl %eax, 4(%esp)
; movl $LC0, (%esp)
; call _printf
乍看之下,範例 4.26的輸出程式碼並沒有比範例 4.24更短或更有效率,但是,仔細看範例 4.27中兩個組合語言的對照後會發現,foo 變數從 -8(%ebp) 改變為 -4(%ebp),而 bar 則從 -12(%ebp) 改變為 -8(%ebp),這是因為在範例 4.26中,由於編譯器選擇改用 addl %edx, %ecx 取代範例 4.24中的 addl %ebx, %eax,這使得範例 4.26的組合語言能少掉第4行的push %ebx 的動作,於是增進了效率,並且減少了堆疊的使用空間。
範例 4.27 <範例 4.24>與<範例 4.26>的組合語言對照
... ; ...
_main: ; _main:
pushl %ebp ; pushl %ebp
movl %esp, %ebp ; movl %esp, %ebp
pushl %ebx ; subl $24, %esp
subl $20, %esp ; andl $-16, %esp
andl $-16, %esp ; movl $0, %eax
movl $0, %eax ; addl $15, %eax
addl $15, %eax ; addl $15, %eax
addl $15, %eax ; shrl $4, %eax
shrl $4, %eax ; sall $4, %eax
sall $4, %eax ; movl %eax, -12(%ebp)
movl %eax, -16(%ebp) ; movl -12(%ebp), %eax
movl -16(%ebp), %eax ; call __alloca
call __alloca ; call ___main
call ___main ; movl $10, -4(%ebp)
movl $10, -8(%ebp) ; movl $15, -8(%ebp)
movl $15, -12(%ebp) ; movl -4(%ebp), %ecx
movl -8(%ebp), %eax ; movl -8(%ebp), %edx
movl -12(%ebp), %ebx ; /APP
/APP ; addl %edx,%ecx
addl %ebx,%eax ; /NO_APP
/NO_APP ; movl %ecx, %edx
movl %eax, -8(%ebp) ; movl %edx, %eax
movl -8(%ebp), %eax ; movl %eax, -4(%ebp)
movl %eax, 4(%esp) ; movl -4(%ebp), %eax
movl $LC0, (%esp) ; movl %eax, 4(%esp)
call _printf ; movl $LC0, (%esp)
movl $0, %eax ; call _printf
movl -4(%ebp), %ebx ; movl $0, %eax
leave ; leave
ret ; ret
... ; ...
有關gcc 的內嵌組合語言的語法,您可以參考網路上的『GCC Inline Assembly - HOWTO』一文 。
在本節中,我們已經介紹了如何使用 gcc 進行編譯、組譯與連結的方法,由於gcc是一個編譯、組譯與連結的萬用工具,因此,通常我們不會直接使用 GNU的 as 組譯器,而會改用 gcc 作為組譯器,以便與 C 語言程式進行連結,甚至在C語言當中直接內嵌組合語言,這讓我門不需要完全從組合語言開始撰寫,對系統程式的開發有相當大的用處,特別是嵌入式系統。
|
|