轉載 http://www.360doc.com/content/10/0926/12/1317564_56492037.shtml
匯編基本語法簡介
在 AT&T 匯編格式中,寄存器名要加上 '%' 作為前綴;而在 Intel 匯編格式中,寄存器名不需要加前綴。例如:
AT&T 格式
Intel 格式
pushl %eax
push eax
在 AT&T 匯編格式中,用 '$' 前綴表示一個立即操作數;而在 Intel 匯編格式中,立即數的表示不用帶任何前綴。例如:
AT&T 格式
Intel 格式
pushl $1
push 1
AT&T 和 Intel 格式中的源操作數和目標操作數的位置正好相反。在 Intel 匯編格式中,目標操作數在源操作數的左邊;而在 AT&T 匯編格式中,目標操作數在源操作數的右邊。例如:
AT&T 格式
Intel 格式
addl $1, %eax
add eax, 1
在 AT&T 匯編格式中,操作數的字長由操作符的最后一個字母決定,后綴'b'、'w'、'l'分別表示操作數為字節(byte,8 比特)、字(word,16 比特)和長字(long,32比特);而在 Intel 匯編格式中,操作數的字長是用 "byte ptr" 和 "word ptr" 等前綴來表示的。例如:
AT&T 格式
Intel 格式
movb val, %al
mov al, byte ptr val
在 AT&T 匯編格式中,絕對轉移和調用指令(jump/call)的操作數前要加上'*'作為前綴,而在 Intel 格式中則不需要。
遠程轉移指令和遠程子調用指令的操作碼,在 AT&T 匯編格式中為 "ljump" 和 "lcall",而在 Intel 匯編格式中則為 "jmp far" 和 "call far",即:
AT&T 格式 Intel 格式
ljump $section, $offset
jmp far section:offset
lcall $section, $offset
call far section:offset
與之相應的遠程返回指令則為:
AT&T 格式
Intel 格式
lret $stack_adjust
ret far stack_adjust
在 AT&T 匯編格式中,內存操作數的尋址方式是
AT&T 格式
Intel 格式
section:disp(base, index, scale)
section:[base + index*scale + disp]
由於 Linux 工作在保護模式下,用的是 32 位線性地址,所以在計算地址時不用考慮段基址和偏移量,而是采用如下的地址計算方法:disp + base + index * scale
下面是一些內存操作數的例子:
AT&T 格式
Intel 格式
movl -4(%ebp), %eax
mov eax, [ebp - 4]
movl array(, %eax, 4), %eax
mov eax, [eax*4 + array]
movw array(%ebx, %eax, 4), %cx
mov cx, [ebx + 4*eax + array]
movb $4, %fs:(%eax)
mov fs:eax, 4
內嵌匯編格式簡介
內嵌匯編語法如下:
__asm__(匯編語句模板: 輸出部分: 輸入部分: 破壞描述部分)
其中,asm 和 __asm__是完全一樣的。共四個部分:匯編語句模板,輸出部分,輸入部分,破壞描述部分,各部分使用“:”格開,匯編語句模板必不可少,其他三部分可選,如果使用了后面的部分,而前面部分為空,也需要用“:”格開,相應部分內容為空。例如:
__asm__ __volatile__("cli": : :"memory")
1、匯編語句模板
匯編語句模板由匯編語句序列組成,語句之間使用 “;”、“\\n”或“\\n\\t”分開。指令中的操作數可以使用占位符引用C語言變量,操作數占位符最多10個,名稱如下:%0,%1,…,%9。指令中使用占位符表示的操作數,總被視為long型(4個字節),但對其施加的操作根據指令可以是字或者字節,當把操作數當作字或者字節使用時,默認為低字或者低字節。對字節操作可以顯式的指明是低字節還是次字節。方法是在%和序號之間插入一個字母,“b”代表低字節,“h”代表高字節,例如:%h1。
2、輸出部分
輸出部分描述輸出操作數,不同的操作數描述符之間用逗號格開,每個操作數描述符由限定字符串和C 語言變量組成。每個輸出操作數的限定字符串必須包含“=”表示他是一個輸出操作數。
例:
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )
描述符字符串表示對該變量的限制條件,這樣GCC 就可以根據這些條件決定如何分配寄存器,如何產生必要的代碼處理指令操作數與C表達式或C變量之間的聯系。
3、輸入部分
輸入部分描述輸入操作數,不同的操作數描述符之間使用逗號格開,每個操作數描述符由限定字符串和C語言表達式或者C語言變量組成。
例1 :
__asm__ __volatile__ ("lidt %0" : : "m" (real_mode_idt));
例二(bitops.h):
Static __inline__ void __set_bit(int nr, volatile void * addr)
{
__asm__(
"btsl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}
后例功能是將(*addr)的第nr位設為 1。第一個占位符%0與C 語言變量ADDR對應,第二個占位符%1與C語言變量nr對應。因此上面的匯編語句代碼與下面的偽代碼等價:btsl nr, ADDR,該指令的兩個操作數不能全是內存變量,因此將nr的限定字符串指定為“Ir”,將nr 與立即數或者寄存器相關聯,這樣兩個操作數中只有ADDR為內存變量。
4、限制字符
4.1、限制字符列表
限制字符有很多種,有些是與特定體系結構相關,此處僅列出常用的限定字符和i386中可能用到的一些常用的限定符。它們的作用是指示編譯器如何處理其后的C語言變量與指令操作數之間的關系。
分類
限定符
描述
通用寄存器
a
將輸入變量放入eax這里有一個問題:假設eax已經被使用,那怎么辦?其實很簡單:因為GCC 知道eax 已經被使用,它在這段匯編代碼的起始處插入一條語句pushl %eax,將eax 內容保存到堆棧,然后在這段代碼結束處再增加一條語句popl %eax,恢復eax的內容
b
將輸入變量放入ebx
c
將輸入變量放入ecx
d
將輸入變量放入edx
s
將輸入變量放入esi
d
將輸入變量放入edi
q
將輸入變量放入eax,ebx,ecx,edx中的一個
r
將輸入變量放入通用寄存器,也就是eax,ebx,ecx,edx,esi,edi中的一個
A
把eax和edx合成一個64 位的寄存器(use long longs)
內存
m
內存變量
o
操作數為內存變量,但是其尋址方式是偏移量類型,也即是基址尋址,或者是基址加變址尋址
V
操作數為內存變量,但尋址方式不是偏移量類型
“”
操作數為內存變量,但尋址方式為自動增量
p
操作數是一個合法的內存地址(指針)
寄存器或內存
g
將輸入變量放入eax,ebx,ecx,edx中的一個或者作為內存變量
X
操作數可以是任何類型
立即數
I
0-31之間的立即數(用於32位移位指令)
J
0-63之間的立即數(用於64位移位指令)
N
0-255之間的立即數(用於out指令)
i
立即數
n
立即數,有些系統不支持除字以外的立即數,這些系統應該使用“n”而不是“i”
匹配
0
表示用它限制的操作數與某個指定的操作數匹配,
1
也即該操作數就是指定的那個操作數,例如“0”
9
去描述“%1”操作數,那么“%1”引用的其實就是“%0”操作數,注意作為限定符字母的0-9 與指令中的“%0”-“%9”的區別,前者描述操作數,后者代表操作數。
&
該輸出操作數不能使用過和輸入操作數相同的寄存器
操作數類型
=
操作數在指令中是只寫的(輸出操作數)
+
操作數在指令中是讀寫類型的(輸入輸出操作數)
浮點數
f
浮點寄存器
t
第一個浮點寄存器
u
第二個浮點寄存器
G
標准的80387浮點常數
%
該操作數可以和下一個操作數交換位置 例如addl的兩個操作數可以交換順序(當然兩個操作數都不能是立即數)
#
部分注釋,從該字符到其后的逗號之間所有字母被忽略
*
表示如果選用寄存器,則其后的字母被忽略
5、破壞描述部分
破壞描述符用於通知編譯器我們使用了哪些寄存器或內存,由逗號格開的字符串組成,每個字符串描述一種情況,一般是寄存器名;除寄存器外還有“memory”。例如:“%eax”,“%ebx”,“memory”等。
感謝
這篇文章是兩篇文章的綜合體,我只是把這兩篇文章綜合起來了,進行了一下簡單的排版,閱讀起來方便一點,舒服一點。兩位作者分別是肖文鵬和臨江仙,向他們表示感謝。后續我會根據相關資料,繼續改進該文檔,使之更全面。Mail:normalnotebook@126.com,互相學習。
