1、 指令操作數的順序是先源後目的,與Intel指令的先目的後源的順序相反;
2、 寄存器操作數總是以'%'作爲前綴。
3、 立即數前加前綴$;
4、 操作碼加後綴以指明操作數的長度,這些後綴有b(8位)、w(16位)、l(32位);
例:
AT&T Intel
moww %bx, %ax // mov ax, bx
xorl %eax, %eax // xor eax, eax
movw $1, %ax // mov ax,1
movb X, %ah // mov ah, byte ptr X
movw X, %ax // mov ax, word ptr X
movl X, %eax // mov eax, X
根據指令中指定的寄存器操作數補上相應的後綴字符。所以,下面兩個指令是等價的(這是Gas的特性,而真正的AT&T的Unix彙編程序會將將沒有字長後綴的指令的操作數字長全部當作32位):
5、 大部分指令的操作碼都與Intel指令相同,只有下面幾個是例外:
movsSD // movsx ,短到長,高位補符號位
movzSD // movzx ,短到長,高位補0
這裏,S 是代表源操作數長度的後綴,D 是代表目的操作數長度的後綴。如:
movswl %ax, %ecx // movsx ecx, ax
cbtw // cbw ,將AL中的一個字節擴充爲一個字(AX),
// AH是AL的符號位
cwtl // cwde ,將AX中的一個字擴充爲一個雙字(EAX),
// EAX的高位是AX的符號位
cwtd // cwd ,將AX中的一個字擴充爲兩個字(DX:AX)
// DX是AX的符號位
cltd // cdq ,將EAX中的一個雙字擴充爲兩個雙字
// (EDX:EAX),EDX是EAX的符號位
lcall $S,$O // call far S:O
ljmp $S,$O // jmp far S:O
lret $V // ret far V
6、 操作碼的前綴(如rep)要單獨寫一行,不要和它修飾的指令(如movsb、stosb)寫在同一行上;
7、 內存間接尋址的格式不同
AT&T Intel
disp(base, index, scale) [base + index*scale + disp]
例:
movl 4(%ebp), %eax // mov eax, [ebp+4]
addl (%eax,%eax,4), %ecx // add ecx, [eax + eax*4]
movb $4, %fs:(%eax) // mov fs:eax, 4
movl _array(,%eax,4), %eax // mov eax, [4*eax + array]
movw _array(%ebx,%eax,4), %cx // mov cx, [ebx + 4*eax + array])
8、 局部標號可以用數字,而且可以重複。在以這些標號爲目的的轉移指令上,標號要帶上後綴,b表示向前,f表示向後。
例:
orw %bx,%bx
jz 1f
1:
movl $0x101000,%eax
movl %eax,%cr3 /* set the page table pointer.. */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */
jmp 1f /* flush the prefetch-queue */
1:
movl $1f,%eax
jmp *%eax /* make sure eip is relocated */
1:
9、 實模式下的語法與Intel指令語法基本相同;可以用上述格式的彙編單獨寫程序(有許多宏定義和它特有的文件格式),而後用gcc/gas將其彙編成目標代碼。在linux中,這種形式的代碼主要集中在啓動部分。
__asm__ __volatile__ (
asm statements
: outputs
: inputs
: registers-modified
);
asm statements是一組AT&T格式的彙編語言語句,每個語句一行,由/n分隔各行。所有的語句都被包裹在一對雙引號內。其中使用的寄存器前面要加兩個%%做前綴;轉移指令多是局部轉移,因此多使用數字標號。
inputs指明程序的輸入參數,每個輸入參數都括在一對圓括號內,各參數用逗號分開。每個參數前加一個用雙引號括起來的標誌,告訴編譯器把該參數裝入到何處。可用的標誌有:“g”:讓編譯器決定如何裝入它;“a”:裝入到ax/eax;“b”:裝入到bx/ebx;“c”:裝入到cx/ecx;“d”:裝入到dx/edx;“D”:裝入到di/edi;“S”:裝入到si/esi;“q”:a、b、c、d寄存器等;“r”:任一通用寄存器;“i”:整立即數;“p”:有效內存地址;“=”:輸出;“+”:既是輸入又是輸出;“&”:改變其值之前寫;“%”:與下一個操作數之間可互換;“#”:忽略其後的字符,直到逗號;“*”:當優先選擇寄存器時,忽略下面的字符;“0~9”:指定一個操作數,它既做輸入又做輸出。通常用“g”。
outputs指明程序的輸出位置,通常是變量。每個輸出變量都括在一對圓括號內,各個輸出變量間用逗號隔開。每個輸出變量前加一個標誌,告訴編譯器從何處輸出。可用的標誌與輸入參數用的標誌相同,只是前面加“=”。如“=g”。輸出操作數必須是左值,而且必須是隻寫的。如果一個操作數即做輸出又做輸入,那麼必須將它們分開:一個只寫操作數,一個輸入操作數。輸入操作數前加一個數字限制(0~9),指出輸出操作數的序號,告訴編譯器它們必須在同一個物理位置。兩個操作數可以是同一個表達式,也可以是不同的表達式。
registers-modified告訴編譯器程序中將要修改的寄存器。每個寄存器都用雙引號括起來,並用逗號隔開。如“ax”。如果彙編程序中引用了某個特定的硬件寄存器,就應該在此處列出這些寄存器,以告訴編譯器這些寄存器的值被改變了。如果彙編程序中用某種不可預測的方式修改了內存,應該在此處加上“memory”。這樣以來,在整個彙編程序中,編譯器就不會把它的值緩存在寄存器中了。
__volatile__是可選的,它防止編譯器修改該段彙編語句(重排序、重組、刪除等)。
輸入參數和輸出變量按順序編號,先輸出後輸入,編號從0開始。程序中用編號代表輸入參數和輸出變量(加%做前綴)。
輸入、輸出、寄存器部分都可有可無。如有,順序不能變;如無,應保留“:”,除非不引起二意性。
int i=0, j=1, k=0;
__asm__ __volatile__("
pushl %%eax/n //asm statement
movl %1, %%eax/n //asm statement
addl %2, %%eax/n //asm statement
movl %%eax, %0/n //...
popl %%eax" //...
: "=g" (k) //outputs
: "g" (i), "g" (j) //inputs
: "ax", "memory" //registers modified
);
按照參數編號原則輸出參數參數k爲%0,輸入參數i和j依次爲%1和%2。值得注意的是輸出和輸入標誌都使用了"g",所以我們不必關心這些參數究竟是使用了寄存器還是內存操作數,編譯器自己會決定。