at&a語法和intel語法

學習linux內核少不了要讀at&t的彙編,這個對大多數使用intel彙編的人是一個不幸的消息,要另起爐竈。什麼?你不是學得intel彙編?你是不是中國學生?記得我們有門課是學<intel 8086/8088彙編語言>,學得挺感興趣,竊以爲這個就是學電腦的最高境界。當時怎麼也沒人說這個是intel彙編語言格式,還有個什麼at&t格式..
不扯遠了,這兩者之間還是有章可循的,我覺得要注意的是轉移指令,這個要仔細琢磨一下才看的明白。內存間接尋址的格式看起來也比較費勁,其他的東西是到是可以看一次就明白的。如果您有耐心,希望查看更權威的資料建議您查閱as.info和gcc.info,實際上,嚴格是說,gas只是衆多遵循at&t格式彙編程序中的一個實現特例,而你尋找的彙編語言規則,只與你使用的彙編程序和編譯器有關。
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會
根據指令中指定的寄存器操作數補上相應的後綴字符。所以,下面兩個指令是等價的(這是Gas的特性,而真正的AT&T的Unix彙編程序會將將沒有字長後綴的指令的操作數字長全部當作32位):

在linux中,因爲是使用gas進行彙編的,故此

           mov %ax,%bx 

等價於

           movw %ax,%bx

 


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:

絕對跳轉/調用指令中的內存操作數必須以'*'爲前綴,否則gas總是認爲是相對跳轉/調用指令,而且gas彙編程序自動對跳轉指令進行優化,總是使用儘可能小的跳轉偏移量。如果8比特的偏移量無法滿足要求的話,as會使用一個32位的偏移量,as彙編程序暫時還不支持16位的跳轉偏移量,所以對跳轉指令使用'addr16'前綴是無效的。還有一些跳轉指令只支持8位的跳轉偏移量,這些指令是:

'jcxz','jecxz','loop','loopz','loope','loopnz''loopne'

如果你在彙編中使用了這些指令,用gas的彙編可能會出錯,因爲gcc在編譯過程中不產生這些指令,所以在c語言中不必擔心這些問題。


9、 實模式下的語法與Intel指令語法基本相同;可以用上述格式的彙編單獨寫程序(有許多宏定義和它特有的文件格式),而後用gcc/gas將其彙編成目標代碼。在linux中,這種形式的代碼主要集中在啓動部分。

 

10、還有一個比較特別的,就是gcc可以編譯嵌入在c語言中的彙編段落。在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開始。程序中用編號代表輸入參數和輸出變量(加%做前綴)。
輸入、輸出、寄存器部分都可有可無。如有,順序不能變;如無,應保留“:”,除非不引起二意性。

 

看一個在C語言中使用at&t的嵌入彙編程序的例子,c語言中的3個int變量,一般會是三個內存地址。每個操作數的長度則要根據操作系統和編譯器來決定,一般32位操作系統爲32位,則每個操作數佔用4個字節:
 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",所以我們不必關心這些參數究竟是使用了寄存器還是內存操作數,編譯器自己會決定。

發佈了53 篇原創文章 · 獲贊 1 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章