GCC中彙編格式

   gcc採用的是AT&T的彙編格式,MS採用Intel的格式. 

一 基本語法 

    語法上主要有以下幾個不同. 

★ 寄存器命名原則 

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__是完全一樣的. 

如果有多行彙編,則每一行都要加上 "/n/t" 

例如: 

asm( "pushl %eax/n/t" 

"movl $0,%eax/n/t" 

"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 /n/t" 

"rep /n/t" 

"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; / 

}  
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章