AT&T x86 asm 語法 (轉)

worldblog發表於2007-12-14
AT&T x86 asm 語法 (轉)[@more@]AT&T x86 asm 語法
譯:el8,alert7
from m4in security teams()

DJGPP 使用AT&T格式的語法。和一般的格式的語法有點不同。主要不同點如下:

AT&T 語法顛倒了源和目的運算元的位置, 目的運算元在源運算元之後。暫存器運算元要有個%的字首, 立即數運算元要有個$符號的字首。 器運算元的大小取決於操作碼的最後一個字元。 它們是b (8-bit), w (16-bit), 和 l (32-bit).
這裡有一些例子。 左邊部分是intel指令格式,右邊是at&t格式。
movw %bx, %ax // mov ax, bx
xorl %eax, %eax // xor eax, eax
movw $1, %ax // mov ax,1
moX, %ah // mov ah, byte ptr X
movw X, %ax // mov ax, ptr X
movl X, %eax // mov eax, X
大部分操作指令,at%t和intel都是差不多的,除了這些:
movsSD // movsx
movzSD // movz

S和D分辨代表源和目的運算元字尾。
movswl %ax, %ecx // movsx ecx, ax
cbtw // cbw
cwtl // cwde
cwtd // cwd
cltd // cdq
lcall $S,$O // call far S:O
ljmp $S,$O // jump far S:O
lret $V // ret far V
操作嘛字首不能與他們作用的指令寫在同一行。 例如, rep 和stosd應該是兩個相互獨立的指令, 儲存器的情況也有一點不同。通常intel格式的如下:

section:[base + index*scale + disp]

被寫成:

section:disp(base, index, scale)

這裡有些例子:

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])

Jump 指令通常是個短跳轉。 可是, 下面這些指令都是隻能在一個位元組的範圍內跳轉: jcxz, jecxz, l, loopz, loope, loopnz 和loopne。象線上文件所說的那樣,一個jcxz foo可以擴充套件成以下工作:
jcxz cx_zero
jmp cx_nonzero
cx_zero:
jmp foo
cx_nonzero:
文件也注意到了mul和imul指令。 擴充套件的乘法指令只用一個運算元,例如, imul $ebx, $ebx將不會把結果放入edx:eax。使用imul %ebx中的單運算元來獲得擴充套件結果。


--------------------------------------------------------------------------------

Inline Asm
我將首先開始inline asm, 因為似乎關於這方面的疑問非常多。這是最基本的語法了, 就象線上幫助資訊中描述的:
__asm__(asm statements : outputs : inputs : registers-modified);

這四個欄位的含義是:

asm statements - AT&T 的結構, 每新行都是分開的。
outputs - 修飾符一定要用引號引起來, 用逗號分隔
inputs - 修飾符一定要用引號引起來, 用逗號分隔
registers-modified - 名字用逗號分隔
一個小小的例子:
__asm__("
pushl %eax\n
movl $1, %eax\n
popl %eax"
);
假如你不用到特別的輸入輸出變數或者修改任何暫存器的值,一般來說是不會使用到其他的三個欄位的,
讓我們來分析一下輸入變數。

int i = 0;

__asm__("
pushl %%eax\n
movl %0, %%eax\n
addl $1, %%eax\n
movl %%eax, %0\n
popl %%eax"
:
: "g" (i)
); // increment i
不要為上面的程式碼所困擾! 我將盡力來解釋它。我們想讓輸入變數i加1,我們沒有任何輸出變數, 也沒有改變暫存器值(我們儲存了eax值)。 因此,第二個和最後一個欄位是空的。 因為指定了輸入欄位, 我們仍需要保留一個空的輸出欄位, 但是沒有最後一個欄位, 因為它沒被使用。在兩個空冒號之間留下一個新行或者至少一個空格。

下面讓我們來看看輸入欄位。 附加描述符可以修正指令來讓你給定的來正確處理這些變數。他們一般被附上雙引號。 那麼這個"g"是用來做什麼的呢? 只要是合法的彙編指令,"g"就讓編譯器決定該在哪裡載入i的值。一般來說,你的大部分輸入變數都可以被賦予"g", 讓編譯器決定如何去載入它們 (gcc甚至可以它們!)。 其他描述符使用"r" (載入到任何可用的暫存器去), "a" (ax/eax), "b" (bx/ebx), "c" (cx/ecx), "d" (dx/edx), "D" (di/edi), "S" (si/esi), 等等。

我們將要提到一個在asm程式碼裡面的如%0的輸入變數。如果我們有兩個輸入, 他們會一個是%0一個是%1, 在輸入段裡按順序排列 (如下一個例子)。假如N個輸入變數且沒有輸出變數, 從%0 到%N-1將和輸入欄位裡的變數相對應, 按順序排列。

如果任何的輸入, 輸出, 暫存器修改欄位被使用, 彙編程式碼裡的暫存器名必須用兩個%來代替一個%。對應於第一個沒有使用最後三個欄位的例子。

讓我們看看兩個輸入變數且引入了"volatile"的例子:

int i=0, j=1;
__asm__ __volatile__("
pushl %%eax\n
movl %0, %%eax\n
addl %1, %%eax\n
movl %%eax, %0\n
popl %%eax"
:
: "g" (i), "g" (j)
); // increment i by j
Okay, 現在我們已經有了兩個輸入變數了。沒問題了, 我們只需要記住%0對應第一個輸入變數(在這個例子中是i), %1對應在i後面的列出的j。
Oh yeah, 這個volatile到底是什麼意思呢? 它防止你的編譯器修改你的彙編程式碼,就是不進行最佳化(紀錄, 刪除, 結合,等等最佳化手段。), 不改變程式碼原樣來彙編它們。建議一般情況下使用volatile選項。

讓我們來看看輸出欄位:

int i=0;
__asm__ __volatile__("
pushl %%eax\n
movl $1, %%eax\n
movl %%eax, %0\n
popl %%eax"
: "=g" (i)
); // assign 1 to i
這看起來非常象我們前面提到的輸入欄位的例子; 確實也沒有很大的不同。所有的輸出修飾符前面都應該加上=字元,他們同樣在彙編程式碼裡面用%0到%N-1來表示, 在輸出欄位按順序排列。你一定會問如果同時有輸入和輸出欄位會怎麼排序的呢? 好,下面一個例子就是讓大家知道如何同時處理輸入輸出欄位的。
int i=0, j=1, k=0;
__asm__ __volatile__("
pushl %%eax\n
movl %1, %%eax\n
addl %2, %%eax\n
movl %%eax, %0\n
popl %%eax"
: "=g" (k)
: "g" (i), "g" (j)
); // k = i + j
Okay, 唯一個不清楚的地方就是彙編程式碼中的變數的個數。我馬上來解釋一下。
當同時使用輸入欄位和輸出欄位的時候:

%0 ... %K 是輸出變數

%K+1 ... %N 是輸入變數

在我們的例子中, %0 對應k, %1 對應i, %2對應j。很簡單,是吧?

到現在為止我們都沒有使用最後一個欄位(registers-modified)。如果我們要在我們的彙編程式碼裡使用任何暫存器, 我們要明確的用push和pop指令來儲存它們, 或者列到最後一個欄位裡面讓gcc來處理它們。

這是前面的一個例子, 沒有明確的保留和存貯eax。

int i=0, j=1, k=0;
__asm__ __volatile__("
pushl %%eax\n /*譯者注:好像原文說的有點問題,明明是儲存了eax的值,:(*/
movl %1, %%eax\n
addl %2, %%eax\n
movl %%eax, %0\n
popl %%eax"
: "=g" (k)
: "g" (i), "g" (j)
: "ax", "memory"
); // k = i + j
我們讓gcc來儲存和存貯eax, 如果必要的話。一個16-bit暫存器名代表了32-, 16-或8-bit暫存器。 如果我們要改寫 (寫入一個變數等。), 建議在register-modified欄位裡面來指定"memroy"修飾符。這意味著除了第一個例子我們都應該加上這個修飾符, 但是直到現在我才提出來, 是為了更簡單易懂。

在你的內聯彙編裡面定位標號應該使用b或f來作為終止符, 尤其是向後向前的跳轉。(譯者注:b代表向後跳轉,f代表向前跳轉)

For example,

__asm__ __volatile__("
0:\n
...
jmp 0b\n
...
jmp 1f\n
...
1:\n
...
);
這裡有個用c程式碼和內聯彙編程式碼混合寫的跳轉的例子(thanks to Srikanth B.R for this tip).

void MyFunction( int x, int y )
{
__asm__( "Start:" );
__asm__( ...do some comparison... );
__asm__( "jl Label_1" );

CallFunction( &x, &y );
__asm__("jmp Start");

Label_1:
return;
}

--------------------------------------------------------------------------------

External Asm
Blah... Okay fine. Here's a clue: Get some of your C/C++ files, 且用gcc -S file.c來編譯。 然後檢視file.S。基本結構如下:
.file "myasm.S"

.data
somedata: .word 0
...

.text
.globl __myasmfunc
__myasmfunc:
...
ret
Macros, macros! 標頭檔案libc/asmdefs.h便於你寫asm。 在你的彙編程式碼最前面包含此標頭檔案然後就可以使用宏了。一個例子: myasm.S:
#include

.file "myasm.S"

.data
.align 2
somedata: .word 0
...

.text
.align 4
FUNC(__MyExternalAsmFunc)
ENTER
movl ARG1, %eax
...
jmp mylabel
...
mylabel:
...
LEAVE
這是一個好的純粹的彙編程式碼。


--------------------------------------------------------------------------------

Other Res
The best way to learn all these is to look at others' code. There's some inline asm code in the sys/ftr.h. Also, if you run , Free, etc., somewhere in the kernel source tree (i386/ or something), there are plenty of asm sources. Check the djgpp2/ directory at x2.oulu.fi, for graphics and gaming libraries that have sources.

If you have asm code that needs to be converted from Intel to AT&T syntax, or just want to stick (譯者注:其他資源就不翻了吧,西西)

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993593/,如需轉載,請註明出處,否則將追究法律責任。

相關文章