AT&T 匯編學習
套用網上看到的一句話開頭吧(這也是我有過的一個過程,基本一樣吧):今天開始學習linux內核編程了,從沒有內核編程基礎開始學起。所以很多相關的知識都要了解。首先就是AT&T匯編語言。因為在linux內核源代碼中,好像除了開始的bootsect.s和head.s是用intel的匯編外,別的匯編代碼都是用的AT&T匯編語言,所以有必要把AT&T匯編語言了解一下。
自己補充一點:我開始沒想過什么linux內核編程哦,只覺得能把內核代碼開懂就是最大的心愿了,不過現在還好,可以開始深入內核編寫一些簡單程序(主要是驅動吧,而且是針對的是嵌入式linux哈)!
1.學習AT&T匯編語言的理由
只有一點就是看懂linux啟動代碼。當然以后可能也會用到。
2.學習的難度
以為本人intel的匯編學的還算過得去,所以在這基礎之上應該不是太難,最多發半天時間久可以熟悉了吧,對比intel匯編學習。
3.和intel的差異
以下的內容都是AT&T匯編的特點:
(1)、寄存器前面要加“%”,如 mov %eax,%ebx
這里要注意的一點是,AT&T匯編中,源寄存器和目的寄存器的順序和intel匯編剛好相反,AT&T匯編中,左邊的是源寄存器,右邊的是目的寄存器,在上邊那個例子中,%eax是源寄存器,%ebx是目的寄存器。
(2)、立即數/常數前面要加$,如 mov $4,%ebx 把4這個數裝入ebx這個寄存器。
符號常數直接用, 如 mov value,%eax 即把value代表的那個值裝入eax寄存器。
mov $value,%eax 即把value的值作為地址,而把對應那個地址中的值裝入eax。
(3)、b(byte):8位, w(word):16位, l(long):32位
如: movb %ax,%bx movw %eax,%ebx
(4)、jum/call的操作數前要加上“*"作為前綴, 遠跳轉ljmp,遠調用lcall
如 ljmp $section,$offset
lcall $section,$offset
這里$section和offset表示的就是,以section為段地址,offset為段內偏移地址。因此,ljmp $section,$offset即跳轉到section:offset地址。
(5)、遠返回lret
如 lret $stack_adjust
(6)、尋址方式
表示方式 section:disp(base,index,scale)
計算方法 base+index*scale+disp
即 section:[base+index*scale+disp]
其中disp是表示偏移地址。
如 movl -4(%ebp),%eax 把[%ebp-4]的內容裝入eax
(7)、C語言中嵌入匯編
格式: _asm_("asm statements":outputs:inputs:registers-modified)
其中,"asm statements"是匯編語句表達式,outputs,inputs,register-modified都是可選參數,以冒號隔開,且一次以0~9編號,如outputs的寄存器是0號,inputs寄存器是1號,往后依次類推。outputs是匯編語句執行完后輸出到的寄存器,inputs是輸入到某個寄存器。
例1:_asm_("pushl %%eax/n/t" "movl $0,%%eax/n/t" "popl %%eax");
在嵌入匯編中,寄存器前面要加兩個%,因為gcc在編譯是,會先去掉一個%再輸出成匯編格式。
例2:{ register char _res;/
asm("push %%fs/n/t"
"movw %%ax,%%fs/n/t"
"movb %%fs:%2,%%al/n/t"
"pop %%fs"
:"=a"(_res):"0"(seg),"m"(*(addr)));/
_res;}
movb %%fs:%2,%%al/n/t一句中是把以fs為段地址,以后面的第二號寄存器即后面的seg中的值為偏移地址所對應的值裝入al。"=a"(_res):"0"(seg),"m"(*(addr)))一句中,"=a"(_res)表示把a寄存器中的內容給_res,"0"(seg)表示把seg中的內容給0所對應的寄存器,而0即表示使用和前一個寄存器相同的寄存器,這里即使用a寄存器,也就是說把seg中的內容個a寄存器。
需要解釋以下的是,a,b,c,d分別表示寄存器eax,ebx,ecx,edx
S,D分別表示寄存器esi,edi
r表示任意寄存器
0(數字0,不是o?。┍硎臼褂蒙弦粋€寄存器
4.下面是找到別人翻譯的文檔:
一 基本語法
語法上主要有以下幾個不同.
★ 寄存器命名原則
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)
浙公網安備 33010602011771號