本文對內嵌彙編語法,從基本語法、內嵌彙編的格式介紹、和擴展的內嵌彙編格式進行了詳細說明,需要說明的是gcc採用的是at&t的彙編格式. 一 基本語法 語法上主要有以下幾個不同. ★ 寄存器命名原則 at&t: %eax intel: eax ★ 源/目的操作數順序 at&t: movl %eax,%ebx intel: mov ebx,eax ★常數/立即數的格式 at&t: movl $_value,%ebx intel: mov eax,_value 把_value的地址放入eax寄存器 at&t: movl $0xd00d,%ebx intel: mov ebx,0xd00d ★ 操作數長度標識 at&t: movw %ax,%bx intel: mov bx,ax ★尋址方式 at&t: immed32(basepointer,indexpointer,indexscale) intel: [basepointer indexpointer*indexscale imm32) linux工作於保護模式下,用的是32位線性地址,所以在計算地址時不用考慮segment:offset的問題.上式中的地址應爲: imm32 basepointer indexpointer*indexscale 下面是一些例子: ★直接尋址 at&t: _booga ; _booga是一個全局的c變量注意加上$是表示地址引用,不加是表示值引用. 注:對於局部變量,可以通過堆棧指針引用. intel: [_booga] ★寄存器間接尋址 at&t: (%eax) intel: [eax] ★變址尋址 at&t: _variable(%eax) intel: [eax _variable] at&t: _array(,%eax,4) intel: [eax*4 _array] at&t: _array(%ebx,%eax,8) intel: [ebx eax*8 _array] 二 基本的內嵌彙編 基本的內嵌彙編很簡單,一般是按照下面的格式 asm(statements); 例如:asm(nop); asm(cli); asm 和 __asm__是完全一樣的. 如果有多行彙編,則每一行都要加上 nt例如: asm( pushl %eaxnt movl $0,%eaxnt popl %eax); 實際上gcc在處理彙編時,是要把asm(...)的內容打印到彙編文件中,所以格式控制字符是必要的.再例如: asm(movl %eax,%ebx); asm(xorl %ebx,%edx); asm(movl $0,_booga); 在上面的例子中,由於我們在行內彙編中改變了edx和ebx的值,但是由於gcc的特殊的處理方法,即先形成彙編文件,再交給gas去彙編,所以gas並不知道我們已經改變了edx和ebx的值,如果程序的上下文需要edx或ebx作暫存,這樣就會引起嚴重的後果.對於變量_booga也存在一樣的問題.爲了解決這個問題,就要用到擴展的行內彙編語法. 三 擴展的行內彙編 擴展的行內彙編類似於watcom. 基本的格式是: asm ( statements : output_regs : input_regs : clobbered_regs); clobbered_regs指的是被改變的寄存器. 下面是一個例子(爲方便起見,我使用全局變量): int count=1; int value=1; int buf[10]; void main() { asm( cld nt rep nt stosl : : c (count), a (value) , d (buf[0]) : %ecx,%edi ); } 得到的主要彙編代碼爲: movl count,%ecx movl value,%eax movl buf,%edi #app cld rep stosl #no_app cld,rep,stos就不用多解釋了. 這幾條語句的功能是向buf中寫上count個value值. 冒號後的語句指明輸入,輸出和被改變的寄存器. 通過冒號以後的語句,編譯器就知道你的指令需要和改變哪些寄存器, 從而可以優化寄存器的分配. 其中符號c(count)指示要把count的值放入ecx寄存器 類似的還有: a eax b ebx c ecx d edx s esi d edi i 常數值,(0 - 31) q,r 動態分配的寄存器 g eax,ebx,ecx,edx或內存變量 a 把eax和edx合成一個64位的寄存器(use long longs) 我們也可以讓gcc自己選擇合適的寄存器. 如下面的例子: asm(leal (%1,%1,4),%0 : =r (x) : 0 (x) ); 這段代碼實現5*x的快速乘法. 得到的主要彙編代碼爲: movl x,%eax #app leal (%eax,%eax,4),%eax #no_app movl %eax,x 幾點說明: 1.使用q指示編譯器從eax,ebx,ecx,edx分配寄存器.使用r指示編譯器從eax,ebx,ecx,edx,esi,edi分配寄存器. 2.我們不必把編譯器分配的寄存器放入改變的寄存器列表,因爲寄存器已經記住了它們. 3.=是標示輸出寄存器,必須這樣用. 4.數字%n的用法:數字表示的寄存器是按照出現和從左到右的順序映射到用r或q請求 的寄存器.如果我們要重用r或q請求的寄存器的話,就可以使用它們. 5.如果強制使用固定的寄存器的話,如不用%1,而用ebx,則 asm(leal (%%ebx,%%ebx,4),%0 : =r (x) : 0 (x) ); 注意要使用兩個%,因爲一個%的語法已經被%n用掉了. 下面可以來解釋letter 4854-4855的問題: 1、變量加下劃線和雙下劃線有什麼特殊含義嗎?加下劃線是指全局變量,但我的gcc中加不加都無所謂. 2、以上定義用如下調用時展開會是什麼意思? #define _syscall1(type,name,type1,arg1) type name(type1 arg1) { long __res; /* __res應該是一個全局變量 */ __asm__ volatile (int $0x80 /* volatile 的意思是不允許優化,使編譯器嚴格按照你的彙編代碼彙編*/ : =a (__res) /* 產生代碼 movl %eax, __res */ : 0 (__nr_##name),b ((long)(arg1))); /* 如果我沒記錯的話,這裏##指的是兩次宏展開. 即用實際的系統調用名字代替name,然後再把__nr_...展開. 接着把展開的常數放入eax,把arg1放入ebx */ if (__res >= 0) return (type) __res; errno = -__res; return -1; }
|