推荐给好友 上一篇 | 下一篇

AT&T 汇编指令说明

在阅读linux/unix内核源代码的时候,必须先掌握汇编,大家都知道,内核代码用的编译器是gcc,而gcc采用的是AT&T的汇编格式,与MS的intel有些区别。BSD爱好者乐园!a-MC2_'~q3x$S

一 AT&T的基本语法BSD爱好者乐园!uX|\d7gsp1v1|

BSD爱好者乐园s`ZJ+W `

语法上主要有以下几个不同.

\,|7cK#w3Xyx0lG8HBSD爱好者乐园$b(x0ur+@nI0P

★ 寄存器命名原则BSD爱好者乐园CcT$U-xq.?

BSD爱好者乐园 c$[(]URG(\

AT&T: %eax Intel: eaxBSD爱好者乐园f+z_8fsRu

'd'O |Y6l★ 源/目的操作数顺序

T d!og*n#vBSD爱好者乐园5}i#VgDmEnE:t

AT&T: movl %eax,%ebx Intel: mov ebx,eaxBSD爱好者乐园M7K} Or9z

`q!k+@#B1l★ 常数/立即数的格式

P&l%e l/m7@k"G{iBSD爱好者乐园G:CRz"]8gRGb

AT&T: movl $_value,%ebx Intel: mov eax,_value

mG#]K2{s}jd`

)s[&jHwy"I把_value的地址放入eax寄存器BSD爱好者乐园3cS;cx$l*P|*~

lGmF$tc(VWAT&T: movl $0xd00d,%ebx Intel: mov ebx,0xd00d

Y`2PS/pS!Y}

Z](b`(E★ 操作数长度标识

9L-{g5M]CBSD爱好者乐园 v/R*u7o p x L;`

AT&T: movw %ax,%bx Intel: mov bx,axBSD爱好者乐园Q,oqFW

BSD爱好者乐园 u-d5lw$Y.lL

★寻址方式

%p'Ous%T6d#|+F aBSD爱好者乐园$^yg-v4gR

AT&T: immed32(basepointer,indexpointer,indexscale)

DVRa+kRBSD爱好者乐园oku7F3_5^hM

Intel: [basepointer + indexpointer*indexscale + imm32)BSD爱好者乐园 d-vCiBp6m

3I JJ R)| H9vmLinux工作于保护模式下,用的是32位线性地址,所以在计算地址时BSD爱好者乐园!M0D3C0^1Jr;_:`1N8r-H

BSD爱好者乐园p9S"S7G'~I

不用考虑segment:offset的问题.上式中的地址应为:BSD爱好者乐园;Z^x;r;?

A?eFMJ0Wimm32 + basepointer + indexpointer*indexscaleBSD爱好者乐园)\8A)i\["M!V3|I

BSD爱好者乐园S#}M#S.z3Sa,OB J0{

下面是一些例子:

0FNX6A]5IkeBSD爱好者乐园'rdxl3s*uR

★直接寻址BSD爱好者乐园 _lr%QZ~wz

0^m8z`,P9Ij&P!}AT&T: _booga ; _booga是一个全局的C变量BSD爱好者乐园-])B([_X x9@

BSD爱好者乐园d"l I4k/XHxK

注意加上$是表示地址引用,不加是表示值引用.BSD爱好者乐园.y@^&OgH[

BSD爱好者乐园D`R$c/Q&X8k(mY

注:对于局部变量,可以通过堆指针引用.BSD爱好者乐园/Z9e,v"H%QsvEg!H

kv l5gR CKIntel: [_booga]

e7YF*r}.\-MBSD爱好者乐园?&[cf)rk6X

★寄存器间接寻址BSD爱好者乐园H%wW.w*n

BSD爱好者乐园tM t5`:P:P O;H

AT&T: (%eax)

u.TeMA$F0Y%nlBSD爱好者乐园 jZ-Sa%F3AK

Intel: [eax]

1{V0gJP6J9Z

'k6h{/oCz ITQ*N★变址寻址BSD爱好者乐园eYyI zGrV

,~&|8z6q^3lAT&T: _variable(%eax)BSD爱好者乐园o T,_3H"@i

z q%?)a I x0~dIntel: [eax + _variable]BSD爱好者乐园7mP X3b+V_+l[

BSD爱好者乐园B$`.G4nksz.xM4Gl

AT&T: _array(,%eax,4)BSD爱好者乐园6J"\G8fV `.I

|.z%R&puIntel: [eax*4 + _array]

~Q-Q#{K*U1jBSD爱好者乐园4k-vf z^s

AT&T: _array(%ebx,%eax,8)BSD爱好者乐园 u/_uVS

M$PvH}(@'K)^.EhV7AIntel: [ebx + eax*8 + _array]
"@:yPNY0}n,cuz%CBSD爱好者乐园 I1xM S"}A T(FC

BSD爱好者乐园.gyI1o;d8A;M#[W^

二 基本的行内汇编

?4i RK,B3|BSD爱好者乐园!VO/Kw-|2UI!K5z

基本的行内汇编很简单,一般是按照下面的格式

W_ S otBSD爱好者乐园`` J-s5@b5y4k

asm("statements");BSD爱好者乐园k*b9ar5nm7oSG/{+`

BSD爱好者乐园X2kA g4\_j7c

例如:asm("nop"); asm("cli");

T*W`$N wKOG)QBSD爱好者乐园@YEaV%v6]

asm 和 __asm__是完全一样的.

X/swb%v7k

-zgH5\;f如果有多行汇编,则每一行都要加上 "\n\t"BSD爱好者乐园n&~+g-\ ~1M!^T s4{},f

BSD爱好者乐园WR|4i8Vf!M

例如:

amG%m rcd4TE5B

fz9UDa5V:J,_7Tasm( "pushl %eax\n\t"

f5e{%z(k0_]!j)s0OBSD爱好者乐园o4oU*PXG k

"movl $0,%eax\n\t"

a.X4^M:IsE.p

2PJM[ |`9\TGw"popl %eax");

tO8{ \b1k,E1CBSD爱好者乐园?9u W)e%w6Ki

实际上gcc在处理汇编时,是要把asm(...)的内容"打印"到汇编BSD爱好者乐园X.u2H z'e~ I

BSD爱好者乐园bQ |2v1mnE W

文件中,所以格式控制字符是必要的.BSD爱好者乐园#gQ)j-dC3r)AGe8G

BSD爱好者乐园w1VV3n8{&D LKc-nh

再例如:

bG2mdd5HgABSD爱好者乐园2d {&Do6t$h9T5f3d

asm("movl %eax,%ebx");BSD爱好者乐园&}g^] R`

al%H.P;nxlS*Easm("xorl %ebx,%edx");BSD爱好者乐园iWIs%R

BSD爱好者乐园cB0bBXPb K

asm("movl $0,_booga);BSD爱好者乐园8S _$U)jf'm8j prw E

;a'Z{&i7W(X在上面的例子中,由于我们在行内汇编中改变了edx和ebx的值,但是BSD爱好者乐园p @_)^S;C#Pss

BSD爱好者乐园.i p[{!`2XVT XF

由于gcc的特殊的处理方法,即先形成汇编文件,再交给GAS去汇编,

mbt3{y{QBSD爱好者乐园,_h)g'p1P!W-a[

所以GAS并不知道我们已经改变了edx和ebx的值,如果程序的上下文

;m.?`,i'`%mD&Y(t M

&['OW"J7o7M需要edx或ebx作暂存,这样就会引起严重的后果.对于变量_booga也

D.n1Z*Y"n-MaBSD爱好者乐园KL/zuh

存在一样的问题.为了解决这个问题,就要用到扩展的行内汇编语法.
,A)D(A!wsR+q[QBSD爱好者乐园 H(s#f`$Yx R`7o

{0X:H@HnR7V三 扩展的行内汇编

syt:W9I7w[CBSD爱好者乐园 r9twc7nX

扩展的行内汇编类似于Watcom.

)]#nh%^TV te/L9F

nBW'U6^t基本的格式是:

VJ4{.mAoRt

[1JH? uFl7z]/E:kasm ( "statements" : output_regs : input_regs : clobbered_regs);

}9s2Ys:@;p

Z0fE)c{clobbered_regs指的是被改变的寄存器.

zkz(C\:k

:m ]0\)i"E(d5R&fU下面是一个例子(为方便起见,我使用全局变量):BSD爱好者乐园H:r.C/X6F C

BSD爱好者乐园 au%QSaJ.NZ)[e-`;J

int count=1;BSD爱好者乐园,f5K.oX7m,ZW

BSD爱好者乐园7\bok?!T WT-L

int value=1;

Es{QUE Y#|,i{!|g

5y.UF.\x.nk,Qint buf[10];BSD爱好者乐园 ZF"kXfq2wsl

[3Mz.~pn[void main()BSD爱好者乐园3mV1JxWF"U

m8akmgA(H V{BSD爱好者乐园-FezfW+vi

BSD爱好者乐园.^8dx q-E;y5O }

asm(

S[qcM

#s&EN6L8pUx cw;I"cld \n\t"

8g1xX i }I.K

T}vV @V5U0k"T;C A"rep \n\t"

0C:P0T |-}&CL:m

HDal,f s aBO,I4H"stosl"

OS#A-@ j+\o-T!c

OO'Tu^:

/G)zQ:D(oE x

C&D)] xS0E3a: "c" (count), "a" (value) , "D" (buf[0])

%W~epdG#iR

&{/YctJP4D9T: "%ecx","%edi" );BSD爱好者乐园z!P}6H,q^0Aw:x

x K6{4Tl E}

&H o6|)t"i!da${GLBSD爱好者乐园0YfiiS,Q)Z4[#cg

得到的主要汇编代码为:

/N w#yV(Q`;G,`GK|

h2oaQk(}1G_movl count,%ecx

]G'P*e5~!v

;P MAX(I8|-jmovl value,%eax

2M A3i4B g%^JBSD爱好者乐园_H(A;JT{&\"ZEe

movl buf,%edi

'r~-O/s8T5La_BSD爱好者乐园9P y8x5QO

#APP

Ir5]dYt3Y6{8bBSD爱好者乐园 B&e1pF7J

cldBSD爱好者乐园!EzP(IaF w9{;j

dJ3`n!J{(]/v2K%^&J6drep

8`,U6Ztw.YNBSD爱好者乐园Kt`!\Rf)O+}

stosl

#i(Q k-}P [@8qJ

1H'Q3}^GXM$_#NO_APPBSD爱好者乐园}\e(B V

BSD爱好者乐园 y+y ?W ]^A,a6n

cld,rep,stos就不用多解释了.

I*P{!^v8`MV$`:K

g"[U)r-Y@这几条语句的功能是向buf中写上count个value值.

5w+A~-J5US

L0dO8V1DU&g冒号后的语句指明输入,输出和被改变的寄存器.BSD爱好者乐园j h]#W/u\D-j

BSD爱好者乐园 i-sX0KP

通过冒号以后的语句,编译器就知道你的指令需要和改变哪些寄存器,

%K \+I7} d#^e

!}5t$Z5L ]EK-j~从而可以优化寄存器的分配.

f"TZ p)?*TBSD爱好者乐园*hS?;{U&Q Q!Ge

其中符号"c"(count)指示要把count的值放入ecx寄存器

!k%s!~a~,`:N8f

[4a&{CV;vVa4pY类似的还有:BSD爱好者乐园(Ker/Uw#z7U+M

BSD爱好者乐园+FK B ] Dy

a eaxBSD爱好者乐园N;J3x/_8p

VJ8c#}cX&fEZgvb ebxBSD爱好者乐园2[^1v8m;J ag

BSD爱好者乐园(`b/T8M7d9S8K/~:z

c ecxBSD爱好者乐园'd_.@A9F,rX E\

mi;SLU8Tuo Ab ^ kd edx

0[3@ pj g2ys-H

]a ~OE-u%qS esiBSD爱好者乐园C ZDRF|QSX$qY!~

,kv_R-Jj bK)V E4ED edi

|&?S8ow:Q8_3kw-~BSD爱好者乐园r*_I-OJ.t

I 常数值,(0 - 31)

Fu4pz];Gs$m

3J!}uc:wq,r 动态分配的寄存器BSD爱好者乐园$Zn]y2\}h

B,m tstSj.vg eax,ebx,ecx,edx或内存变量

Z)m*Q4FW

8?ms U ~A 把eax和edx合成一个64位的寄存器(use long longs)BSD爱好者乐园y5~ I7n;E2A5k(Y;gM

BSD爱好者乐园$iT+gW+j:~

我们也可以让gcc自己选择合适的寄存器.BSD爱好者乐园'f1_/gDVw"Q8RM

BSD爱好者乐园#e3L o7} C$i_f6Fc*u

如下面的例子:BSD爱好者乐园 C h:]#h*f'@

BSD爱好者乐园#O-p)^2I5t;r5D [

asm("leal (%1,%1,4),%0"

.H'HuO(a

qS:@0Tb w/xz$])np: "=r" (x)BSD爱好者乐园8n+^$f"kh X

(af)}LeFO t/?: "0" (x) );

"|P5a_0qMG4SkBSD爱好者乐园0T4d V!|BI H3K

这段代码实现5*x的快速乘法.BSD爱好者乐园PrFxe/?$`8Yv M

xr[(K(A A{得到的主要汇编代码为:

#fy$[)S9xXF/]OTn

V"rO%Bx0Rmovl x,%eaxBSD爱好者乐园y^}V8q

d#wya!d,` F#APP

9K/J*m\ Q8G6~9n;W4V S

Yp V.WC&Vleal (%eax,%eax,4),%eax

*Ea"`]%stEy

pN[4HY pT#NO_APPBSD爱好者乐园N1b#k]joy{ L

b8^NqL(Omovl %eax,xBSD爱好者乐园E6VY,j}h[~ _

"U%Sk H~!o几点说明:

n9E*t W"H)f

H l3il1Y.VK2q@1.使用q指示编译器从eax,ebx,ecx,edx分配寄存器.BSD爱好者乐园g\}&h |2x

/F)fI*KT C8@ T使用r指示编译器从eax,ebx,ecx,edx,esi,edi分配寄存器.BSD爱好者乐园:J gKO$n

BSD爱好者乐园#\P4m [ T

2.我们不必把编译器分配的寄存器放入改变的寄存器列表,因为寄存器BSD爱好者乐园'V)o)C^\;lI+N

BSD爱好者乐园[-g6p$n%C.U"q%{K

已经记住了它们.

P B:QZWBSD爱好者乐园$X8S!G Y*v!k[S

3."="是标示输出寄存器,必须这样用.

a?f0@)vBSD爱好者乐园"_}Z+e.a8C ~

4.数字%n的用法:BSD爱好者乐园)F1\{_ Y

%Xk G&y7x9cW&U['k+@数字表示的寄存器是按照出现和从左到右的顺序映射到用"r"或"q"请求

f`3f ]a A;[

%^D S"J/I/D7ep2F_ W的寄存器.如果我们要重用"r"或"q"请求的寄存器的话,就可以使用它们.BSD爱好者乐园v-A c,Y8G7D!IzK

BSD爱好者乐园ai{:j&E([ G|,T

5.如果强制使用固定的寄存器的话,如不用%1,而用ebx,则BSD爱好者乐园Vna!tb&k IC4^

2AHnx3@asm("leal (%%ebx,%%ebx,4),%0"BSD爱好者乐园+i Fm;}#j)b+K

fY n&T SCn `B: "=r" (x)

5i#\(j }9vADm?&KBSD爱好者乐园0~#fG$fW

: "0" (x) );

8@%n)}Q5^IBSD爱好者乐园Phz'a Z[

注意要使用两个%,因为一个%的语法已经被%n用掉了.BSD爱好者乐园'W|`V(n#`7]2fhD

Z] ?5H*T6y#_A下面可以来解释letter 4854-4855的问题:BSD爱好者乐园5N0IcyW0aO

BSD爱好者乐园FyQK~D+[ A"k

1、变量加下划线和双下划线有什么特殊含义吗?BSD爱好者乐园(z-Ie uWu@({

BSD爱好者乐园 N8mh3}"?X3U

加下划线是指全局变量,但我的gcc中加不加都无所谓.

,Y9E"Z:c(D@ j1G%R

PT.y^u\(N2、以上定义用如下调用时展开会是什么意思?BSD爱好者乐园0F!\ z,L$UV j3n&w

BSD爱好者乐园4h&p0D8Vw Oi+B{4w-O

#define _syscall1(type,name,type1,arg1) \BSD爱好者乐园2x{Sc5I

BSD爱好者乐园)X%V[`"U@!A5O

type name(type1 arg1) \BSD爱好者乐园4e/]i;Z%G6SF2[4Tid

BSD爱好者乐园"DO#FX \

{ \

)@3c#S*lPDvy

UHFHbl2H^@long __res; \BSD爱好者乐园"_WLB3m&e

BSD爱好者乐园V,k ~2jVriG3[8?Td

/* __res应该是一个全局变量 */

P8Z m?![ TU'iar4?!S4r$`

2@{QZ;J{:G7mY__asm__ volatile ("int $0x80" \

*FsR1C8}9H ao8y

"{C](C3_/* volatile 的意思是不允许优化,使编译器严格按照你的汇编代码汇编*/

$t.O_sG

,h%q(\['~|K\: "=a" (__res) \BSD爱好者乐园:\x[$J2W

BSD爱好者乐园(P\*pngVZ

/* 产生代码 movl %eax, __res */

2A^m"l+McBSD爱好者乐园4as6rx{ m2O#r

: "0" (__NR_##name),"b" ((long)(arg1))); \BSD爱好者乐园H|.T)f&xI

7m!?\.T8o0pJA E/* 如果我没记错的话,这里##指的是两次宏展开.BSD爱好者乐园0a:~ W}&V z&E(C%l

"NQ'J(t.FD]#x  即用实际的系统调用名字代替"name",然后再把__NR_...展开.

/E#_:X$^2VE

7eh)\0w {G-mt  接着把展开的常数放入eax,把arg1放入ebx */BSD爱好者乐园q h;s3Rji

BSD爱好者乐园(h/\6sd.p-qYR-ntN

if (__res >= 0) \BSD爱好者乐园6jJOog8\"T

(F Pi~Cb5wP:B2Areturn (type) __res; \

"Y;N(| D G5A9FBSD爱好者乐园,[&k3? h2]T8zh/A

errno = -__res; \

L;dsDG!e\R

1A V!s Y.D$RzTpreturn -1; \

g2?!f KA+CY+J

Ddb._:igZ}
2Zo$p3|L////////////////////////////////////////////////////////////////////////BSD爱好者乐园0Y&[Rr r9W b1J#x

B+Ydd]\四.AT&T汇编与Intel汇编的比较

"SyI)]!VR5oqBSD爱好者乐园1z#M @1fS

Intel和AT&T语法的区别
$HY6^#e kpm(tIntel和AT&T汇编语言的语法表面上各不相同,这将导致刚刚学会INTEL汇编的人第一次见到AT&T汇编时
2D"ZG,UU会感到困惑,或者反之。因此让我们从基础的东西开始。BSD爱好者乐园xA K^F0B

t*AFdE+q _W前缀BSD爱好者乐园 UFcZ-~ scx pq
在Intel汇编中没有寄存器前缀或者立即数前缀。而在AT&T汇编中寄存器有一个“%”前缀,立即数有BSD爱好者乐园(O|v7BR!Bh_%E
一个“$”前缀。Intel语句中十六进制和二进制数据分别带有“h”和“b”后缀,并且如果十六进制
\;U^%niE)q.JA)SZ数字的第一位是字母的话,那么数值的前面要加一个“0”前缀。
!sWq*sAZ"U例如,
4K-VbH(oN FQLIntex SyntaxBSD爱好者乐园 cv*bdO Mk
mov eax,1BSD爱好者乐园(z7QPP,\ ]k[`
mov ebx,0ffhBSD爱好者乐园qw:E7OY G
int 80h

%`b7H,p0fq&_'Im;S

0@^"X6R*u'siF:_;IeAT&T Syntax
+VzIYqqymovl $1,%eaxBSD爱好者乐园nq:rZy|
movl $0xff,%ebx
V5W2KG K'@int $0x80
`/{ R(^B[~就像你看到的,AT&T非常难懂。[base+index*scale+disp] 看起来比disp(base,index,scale)更好理解。BSD爱好者乐园P7S`b#~ sEB

@b` j"r!i-c[BSD爱好者乐园s Fve.N+X U

操作数的用法BSD爱好者乐园*|SB"ITc}r
intel语句中操作数的用法和AT&T中的用法相反。在Intel语句中,第一个操作数表示目的,第二个
6AX3l7~(W#O操作数表示源。然而在AT&T语句中第一个操作数表示源而第二个操作数表示目的。在这种情形下AT&T语法BSD爱好者乐园$ugwF1D8FIC&{A
的好处是显而易见的。我们从左向右读,也从左向右写,这样比较自然。
:J r E0pq例如,BSD爱好者乐园.z7wilc{
Intex Syntax
)D3Z)Zr-Winstr dest,source
(fbo3ELmov eax,[ecx]
'Q8MiT'RT8z!O
H6L%Z Jq;C'oAT&T Syntax
T2\qkz,z$O]instr source,dest
;Wnm:}N&m/fK*CB7dmovl (%ecx),%eaxBSD爱好者乐园 m8{xB#SF ?O*? U

_|,r6m0s'j,N|"T!NA存储器操作数BSD爱好者乐园/tFY,m*k_
如同上面所看到的,存储器操作数的用法也不相同。在Intel语句中基址寄存器用“[”和“]”括起来
F y n?Y-Bl而在AT&T语句中是用“(”和“)”括起来的。
kV6q b;\F/K例如,
pPKR |-XIntex Syntax
WjlB*yimov eax,[ebx]BSD爱好者乐园8W@_dU
mov eax,[ebx+3]
@4~${KuAAT&T Syntax
h @~p(^VLmovl (%ebx),%eax
{,LJm,fmovl 3(%ebx),%eax
_ \Wq8L0?AT&T语法中用来处理复杂的操作的指令的形式和Intel语法中的形式比较起来要难懂得多。在Intel语句BSD爱好者乐园uD}D+y\Dx)s
中这样的形式是segreg:[base+index*scale+disp]。在AT&T语句中这样的形式是BSD爱好者乐园7Wr$kx2H
%segreg:disp(base,index,scale)。BSD爱好者乐园,ush1Zlj
Index/scale/disp/segreg 都是可选并且可以去掉的。Scale在本身没有说明而index已指定的情况下
YdSWg9c'Q缺省值为1。segreg的确定依赖于指令本身以及程序运行在实模式还是pmode。在实模式下它依赖于BSD爱好者乐园.q Gi,E | xG"M
指令本身而pmode模式下它是不需要的。在AT&T语句中用作scale/disp的立即数不要加“$”前缀。
g)g:R i6n/H:Z例如BSD爱好者乐园m VwJ~a%B
Intel Syntax
+X~Ir5I\/z$e%s6\instr foo,segreg:[base+index*scale+disp]BSD爱好者乐园.Vt'xm2{I
mov eax,[ebx+20h]
3J1VecmHQ5Fadd eax,[ebx+ecx*2h]BSD爱好者乐园"XVz5S,vFPA
lea eax,[ebx+ecx]
%GKA)OF0O H0_$Gsub eax,[ebx+ecx*4h-20h]
${9e3|:M#r6P$nAT&T Syntax
4d]$tFHinstr %segreg:disp(base,index,scale),fooBSD爱好者乐园M Z5? zu8__
movl 0x20(%ebx),%eax
,t8M*j1ar4sQ4Faddl (%ebx,%ecx,0x2),%eax
Xu2g_(Dcleal (%ebx,%ecx),%eaxBSD爱好者乐园Wqz6?*l"EXgs2mZ
subl -0x20(%ebx,%ecx,0x4),%eaxBSD爱好者乐园%f)F|)@ ozQ4G

BSD爱好者乐园'J E0TyNt2i

后缀
"D"LX Qx/r就像你已经注意到的,AT&T语法中有一个后缀,它的意义是表示操作数的大小。“l”代表long,
7T p]q}k7n$E“w”代表word,“b”代表byte。Intel语法中在处理存储器操作数时也有类似的表示,
'H+i$K;d t q如byte ptr, word ptr, dword ptr。"dword" 显然对应于“long”。这有点类似于C语言中定义的
m.qR(h9F^3Grh V类型,但是既然使用的寄存器的大小对应着假定的数据类型,这样就显得不必要了。
!];].p!S A%Z#Xw例子:BSD爱好者乐园 Q"B{]A3j0S |nW
Intel Syntax
P{8q)g+g-F`2Ymov al,bl
`O-y2E.?6d)i X&C3Zmov ax,bxBSD爱好者乐园`Q'i;E7p+kD,Y
mov eax,ebx
M/Ssj-f|Hm'kmov eax, dword ptr [ebx]
Z/wd2kzBAT&T SyntaxBSD爱好者乐园 Z P2IZ7qlb
movb %bl,%al
!x]!C3Y6F)x4V*Omovw %bx,%ax
s.a6ee9^9yTimovl %ebx,%eax
c.py m7r}movl (%ebx),%eaxBSD爱好者乐园Z!y c+uHwVzd

BSD爱好者乐园)]p#w!o9Q*d

注意:从此开始所有的例子都使用AT&T语法BSD爱好者乐园$dXi0VZ E]'P
系统调用BSD爱好者乐园 @J'uz5}0\*SY4M
本节将介绍linux中汇编语言系统调用的用法。系统调用包括位于/usr/man/man2的手册里第二部分所有
:LJHE4b'h)oa函数。这些函数也在/usr/include/sys/syscall.h中列出来了。一个重要的关于这些函数的列表是
'h(z[&fWXf在http://www.linuxassembly.org/syscall.html里。这些函数通过linux中断服务:int $0x80来被执行BSD爱好者乐园'U,|/^"N/t_F)_
小于六个参数的系统调用BSD爱好者乐园Gx8KgEK
对于所有的系统调用,系统调用号在%eax中。对于小于六个参数的系统调用,参数依次存放BSD爱好者乐园9_L*Q$J]C*}f
在%ebx,%ecx,%edx,%esi,%edi中,系统调用的返回值保存在%eax中。
5];M#Ky AC.c系统调用号可以在/usr/include/sys/syscall.h中找到。宏被定义成SYS_的形式,BSD爱好者乐园4s/_ [q'Mb:F1a1P I
如SYS_exit, SYS_close等。BSD爱好者乐园L5}W%^ w eO
例子:(hello world 程序)BSD爱好者乐园8H1l4c?HI5Q
参照write(2)的帮助手册,写操作被声明为ssize_t write(int fd, const void *buf, size_t count);
.e8C:x:?Um5Dm这样,fd应存放在%ebx中,buf放在 %ecx, count 放在 %edx , SYS_write 放在 %eax中,紧跟着是BSD爱好者乐园7Z7? T1s;`~$a5Lc
int $0x80语句来执行系统调用。系统调用的返回值保存在%eax中。BSD爱好者乐园!U yJ3}EH1_9JJ
$ cat write.s
0?l DNCz#s:U Fv1k.include "defines.h"BSD爱好者乐园1_LWg.[4|n"r!n)k,NN
.data
8Bh.G0BP)E)P!Rhello:BSD爱好者乐园 }ab\'v{
.string "hello world\n"BSD爱好者乐园 L.i9c0Mk$}%R

.c1l;l |Q,F d6u.globl main
3v3pVUl)B-ouNmain:
eP5ai } V*LZmovl $SYS_write,%eaxBSD爱好者乐园,h+J5c!ZS)v;HD
movl $STDOUT,%ebxBSD爱好者乐园-`!RU9s4{*i:q|
movl $hello,%ecxBSD爱好者乐园w(S;\k'^V(l
movl $12,%edx
$Q$] Q-S/O"~1Hint $0x80BSD爱好者乐园*|0Ot(|"}^ o

&Ro,n;^/^3`dIret
0XoUy1x.h t"R F {s$BSD爱好者乐园 n O#M,I L,h.~
少于5个参数的系统调用的处理也是这样的。只是没有用到的寄存器保持不变罢了。象open或者fcntl这样
'X{sR([BzD!\ Gb;@c带有一个可选的额外参数的系统调用也就知道怎么用了。BSD爱好者乐园(V7k%`2d0y+@(W
大于5个参数的系统调用BSD爱好者乐园H4U6E8E.h6i j8m
参数个数大于五个的系统调用仍然把系统调用号保存在%eax中,但是参数存放在内存中,并且指向第一个
w6e_2_vaT参数的指针保存在%ebx中。
\/TZj4Ma如果你使用栈,参数必须被逆序压进栈里,即按最后一个参数到第一个参数的顺序。然后将栈的指针拷贝
@)vd0v Y到%ebx中。或者将参数拷贝到一块分配的内存区域,然后把第一个参数的地址保存在%ebx中。BSD爱好者乐园-rJ$j0M G]_ zL
例子:(使用mmap作为系统调用的例子)。在C中使用mmap():BSD爱好者乐园_ i6{ kyz{
#include
3znPSl\%W9E#include
o C9P+Z QwHa#includeBSD爱好者乐园2bm7WG.Cr`g"Lh
#includeBSD爱好者乐园 Cm1d8JOb-Q
#includeBSD爱好者乐园,J g/YeY9f

BSD爱好者乐园x:LF)s4})[`$Ww

#define STDOUT 1

R:W)_q3} bt\

FI5o9S6B a!Gvoid main(void) {
4Kx-@2f7?9H-}char file[]="mmap.s";
"e$\7f0Q a#W.I+Pchar *mappedptr;BSD爱好者乐园:w Q!}Lv5s0T
int fd,filelen;BSD爱好者乐园ss\9ylE

BSD爱好者乐园k'_tz%ymD

fd=fopen(file, O_RDONLY);BSD爱好者乐园p^Z%jN;FhZ
filelen=lseek(fd,0,SEEK_END);BSD爱好者乐园h*d"a4K:O|dK
mappedptr=mmap(NULL,filelen,PROT_READ,MAP_SHARED,fd,0);BSD爱好者乐园:U cdmo c_V
write(STDOUT, mappedptr, filelen);
0p8r/eaY6vmunmap(mappedptr, filelen);BSD爱好者乐园#og;I3~(v*oQ8a)s
close(fd);
yX"k^ yTy{}BSD爱好者乐园#~d!tf{+a
mmap()参数在内存中的排列:BSD爱好者乐园S~}6^_ht B7so
%esp %esp+4 %esp+8 %esp+12 %esp+16 %esp+20
&V H3j db3W00000000 filelen 00000001 00000001 fd 00000000
4{!Fn ](iY#M等价的汇编程序:BSD爱好者乐园7V+DI6qgK!t
$ cat mmap.sBSD爱好者乐园2]@*B t` ^0Q
.include "defines.h"

W:?5EB |-o8w(A&G

/GW8H c.Y2_&y2[UVf'`.data
!Z^ e7O@P*hfile:BSD爱好者乐园*O!P#z Kf,L#Ceb
.string "mmap.s"BSD爱好者乐园ZX$rC!CY0w F
fd:
!Cs)ve zY i\[.long 0
*P C#cKGjpfilelen:BSD爱好者乐园6M:x,Ng3J
.long 0
'neYT ?8p| Cb'Z2k2Dmappedptr:BSD爱好者乐园] A zc4yS3V
.long 0BSD爱好者乐园:x~x#L(P#Y| k

-jl5u9n w4GD.globl main
:b-^_5d{B7a0x9omain:BSD爱好者乐园'X&f!E B{
push %ebp
&y1[1n7?5s LDmovl %esp,%ebp
7uz v6lN XXsubl $24,%espBSD爱好者乐园@2M{Cw/w%D+R$A\

BSD爱好者乐园 c'C"nt;b _6F0f

// open($file, $O_RDONLY);

7a9R&a/Y.f%U:qa5P

%}NIn7Iv&w^+Smovl $fd,%ebx // save fd
!t/~^!fL:J5]h^movl %eax,(%ebx)

eWRw#QaEGuBSD爱好者乐园'}3av%os

// lseek($fd,0,$SEEK_END);

M+P5x.zG(H JsW6NBSD爱好者乐园s.r l r9~dL)B

movl $filelen,%ebx // save file lengthBSD爱好者乐园 `K};Ch]%o;z
movl %eax,(%ebx)

'Qb4s C;?T*V/fBSD爱好者乐园D g@+E2qi

xorl %edx,%edxBSD爱好者乐园eH$Vm.mfK/K;_J

BSD爱好者乐园!w#Lygd;k$T]H

// mmap(NULL,$filelen,PROT_READ,MAP_SHARED,$fd,0);BSD爱好者乐园+E#[2Kq0\)V
movl %edx,(%esp)BSD爱好者乐园 s)HF1r]k;]
movl %eax,4(%esp) // file length still in %eax
*A]2{M*G2{w Emovl $PROT_READ,8(%esp)BSD爱好者乐园] xO9MdE n7r
movl $MAP_SHARED,12(%esp)BSD爱好者乐园[4Tg9Cf
movl $fd,%ebx // load file descriptor
$G5qj rF9wmovl (%ebx),%eax
-z fX'conXmovl %eax,16(%esp)
*H'F;u.Ci8xA/YLmovl %edx,20(%esp)
G4g&i$\1AB]lmovl $SYS_mmap,%eaxBSD爱好者乐园w1C+f5Wd'BtU
movl %esp,%ebxBSD爱好者乐园"W d&r:Y`f
int $0x80

siG J `-JinO;~BSD爱好者乐园zp0y@8j|F

movl $mappedptr,%ebx // save ptr
8AB/J;B8Y-X({movl %eax,(%ebx)BSD爱好者乐园2wc[ w&F1m

Y a.p_M |d3yl q// write($stdout, $mappedptr, $filelen);
,M$X.~7m6bN// munmap($mappedptr, $filelen);BSD爱好者乐园(x2p*q|:_.Q%L
// close($fd);
3Il{9dN|1k%u
D c+I:L7KW;} qymovl %ebp,%espBSD爱好者乐园6m)@zqL V Ww5~#]
popl %ebpBSD爱好者乐园it)lo0C6w}\H^

R+Y fY sY5B*n iret
9u+d'` [PvO$
w4?,YD1H+W注意:上面所列出的源代码和本文结束部分的例子的源代码不同。上面列出的代码中没有说明其它的
(YOv @4u系统调用,因为这不是本节的重点,上面列出的源代码仅仅打开mmap.s文件,而例子的源代码要读BSD爱好者乐园2_d8Q5~ TJL
命令行的参数。这个mmap的例子还用到lseek来获取文件大小。BSD爱好者乐园0y+nh b*\ G SEj
Socket系统调用
0H~0ac!l+nSocket系统调用使用唯一的系统调用号:SYS_socketcall,它保存在%eax中。Socket函数是通过位于
P(R(N~y L4l D!l)bS6Bt/usr/include/linux/net.h的一个子函数号来确定的,并且它们被保存在%ebx中。指向系统调用参数
Z)d(D,{8["eZp的一个指针存放在%ecx中。Socket系统调用也是通过int $0x80来执行的。
sj%BK Jc:r@"Q Fv;I$ cat socket.sBSD爱好者乐园#p&[6~({4Y2g*[D,x
.include "defines.h"BSD爱好者乐园AI%Jrv[F

d6my$?@+b.globl _startBSD爱好者乐园/w@;G6_KH0c6b
_start:
4J{?U0Gs&Xpushl %ebpBSD爱好者乐园/o{l"y9n/i!LS
movl %esp,%ebp
7k.E h7T(ul"]:h lsub $12,%espBSD爱好者乐园#B5h9\#S l8o F

1l*D/^ Wq/e// socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);BSD爱好者乐园t&v6Uu_
movl $AF_INET,(%esp)
f W0?(g3T ?8Hmovl $SOCK_STREAM,4(%esp)BSD爱好者乐园cw'A!H ^.j
movl $IPPROTO_TCP,8(%esp)BSD爱好者乐园k`HC P*r7u ]f

BSD爱好者乐园*jWT R8OtV#C _

movl $SYS_socketcall,%eax
'J(z8m'SIS-{"|movl $SYS_socketcall_socket,%ebx
e xX K:C8E.~Bmovl %esp,%ecxBSD爱好者乐园"Y,R&[-EPzj l
int $0x80

XM7lj7U*Fr }[

X)cl-f7}F*d~4fmovl $SYS_exit,%eax
/wX7H5X B,q3jZ+pxorl %ebx,%ebxBSD爱好者乐园 ^ DeSqkT%^
int $0x80

f#}` ~-P4V7@BSD爱好者乐园&ia-k+N7Z/IS D

movl %ebp,%esp
_(T`$Q|)qZ6Dpopl %ebp
w&k2lbMlret
Ay3a,vcQ$BSD爱好者乐园VCNI0Y#o(Q

BSD爱好者乐园#|mUT A1[#V

命令行参数BSD爱好者乐园efr1\ R4I
在linux中执行的时候命令行参数是放在栈上的。先是argc,跟着是一个由指向命令行中各字符串的
wx6bNp?Q [D指针组成的数组(**argv)并以空指针结束。接下来是一个由指向环境变量的指针组成的BSD爱好者乐园\dw^-Q!?#n R4}!m'KV L
数组(**envp)。这些东西在asm中都可以很容易的获得,并且在例子代码(args.s)中有示范。BSD爱好者乐园4d/yo#Hj{J0j#?L

BSD爱好者乐园&L%U R$W(F3I7Z}


Te6HH9? ^GCC内联汇编
b%|ch"U[本节中GCC内联汇编仅涉及x86的应用程序。操作数约束会和其它处理器上的有所不同。关于这部分
9L&n[t r1a的说明放在本文的最后。BSD爱好者乐园 }u*F!uS5YS
gcc中基本的内联汇编非常易懂,如
qd,oVZC~'L__asm__("movl %esp,%eax"); // look familiar ?BSD爱好者乐园3c;};o[] |$Cs

:~)m] wMga或者是BSD爱好者乐园-C;fyR;~
__asm__("BSD爱好者乐园;r uh$th+M
movl $1,%eax // SYS_exitBSD爱好者乐园$u S*p0Q E*Cp-X6Z!}#F wOA
xor %ebx,%ebx
.w8d[M:vki6@!dint $0x80BSD爱好者乐园4vU9p8H'U%e
");BSD爱好者乐园Mml:V$WLJ h%h{
如果指定了用作asm的输入、输出数据并指出哪一个寄存器会被修改,会使程序的执行效率提高。BSD爱好者乐园2V4f b#bG%V,cg(t
input/output/modify都不是必需的。格式如下:
D-Q:H Q-Z%H__asm__("" : output : input : modify);BSD爱好者乐园I+c q8i2D9X
output和input中必须包含一个操作数约束字符串,并紧跟一个用圆括号括起来的C语言表达式。BSD爱好者乐园^FzTj,Q@X h?
输出操作数约束的前面必须有一个“=”,表示这是一个输出。可能会有多个输出,多个输入和
6U9UN:aG0N5K多个修改过的寄存器。每个“入口”应该用“,”分隔开,并且入口的总数不多有10个。BSD爱好者乐园\2u#H6N@ E3c
操作数约束字符串可以是包含整个寄存器的名称也可以是简写。
8U9JLzjL0OAbbrev Table
0A V3`1]t3e"DAbbrev RegisterBSD爱好者乐园;`?#W](?-QS
a %eax/%ax/%al
8`6R+i+YxJb %ebx/%bx/%blBSD爱好者乐园7ex8J;QS
c %ecx/%cx/%cl
3nMXJ;N;Gk'H py&wd %edx/%dx/%dlBSD爱好者乐园9U Oi2h1N I#I
S %esi/%siBSD爱好者乐园+H*A @ J6G7f8{'\
D %edi/%diBSD爱好者乐园U'k}fA7X`5G
m memoryBSD爱好者乐园.S}?I7wL6?db5}
例如:BSD爱好者乐园 D-W$f;W;bH`)Zo

W7`4b}*dmvk3~4R__asm__("test %%eax,%%eax", : /* no output */ : "a"(foo));
:tU6^[[,C

z;t:z/X8x(sF+?;sz#])^BSD爱好者乐园r&uk4BG

或者是

Q N$@Q&w*F*G-BBg

R0@#g h|w'R^__asm__("test %%eax,%%eax", : /* no output */ : "eax"(foo));BSD爱好者乐园U~5g3R+| `$Q6C
你可以在__asm__后使用关键字__volatile__:“你可以利用在__asm__后使用关键字__volatile__的BSD爱好者乐园o)q+o"zJ1Q?9K0{
方法防止一条‘asm’指令被删除、移动或者被重新组合。”(出自gcc的info文件中"AssemblerBSD爱好者乐园7H Gl`:S3|
Instructions with C Expression Operands" 部分)BSD爱好者乐园)k8X'zy$xMF'k
$ cat inline1.c
zbW#r*GL:La#includeBSD爱好者乐园?-L%cGZJ

!Ah,@ T9}2^xM | e3[oint main(void) {BSD爱好者乐园?ntY~aRS0K$n]
int foo=10,bar=15;
'a|`?"@.~"n j
7oA4o3aNR{ ju__asm__ __volatile__ ("addl %%ebxx,%%eax"
'u z5P.OK [N5mq:}: "=eax"(foo) // ouputBSD爱好者乐园o7Zz"B go(O{
: "eax"(foo), "ebx"(bar)// inputBSD爱好者乐园~P|F0jQF
: "eax" // modify
juSFqmqV);BSD爱好者乐园5y/K5x#HNj$k
printf("foo+bar=%d\n", foo);BSD爱好者乐园fHaPT
return 0;
[:np4~,qe[SC}BSD爱好者乐园"cf$KVH2s'O
$
Tn s_/a y'M9y你可能已经注意到现在寄存器使用“%%”前缀而不是“%”。这在使用output/input/modify域时是必要的,BSD爱好者乐园t3F1\ mWn4C
这是因为此时基于其它域的寄存器的别名的使用。我马上来讨论这个问题。
2l _.[,[F t你可以很简单的指定“a”而不是写“eax”或者强制使用一个特殊寄存器如"eax"、"ax"、"al",
;CU+\uq这同样适用于其它一般用途的寄存器(在Abbrev表中列出的)。当你在当前的代码中使用特殊的寄存器
q4_GfUjzr时这好像毫无用处,因此gcc提供了寄存器别名。最多有10个别名(%0—%9),这也是为什么只允许10个BSD爱好者乐园#X_Q%a3Mu
输入/输出的原因。
T'IW4kozH|pIs2v2{$ cat inline2.c
uI:v$m:B#uA t aWint main(void) {BSD爱好者乐园-g#^iCH1UtN1I0U g
long eax;
P(^d K1D~C GbWshort bx;BSD爱好者乐园w:b_NK1Cx/J
char cl;

h(P)m0O7T%c-}

Gq2|:_"t t"a__asm__("nop;nop;nop"); // to separate inline asm from the rest of
l5RU;B'rh@v.^// the code
,h ecJ&QY"k__volatile__ __asm__("BSD爱好者乐园:Z0Al Nv9B ]q1b
test %0,%0BSD爱好者乐园@%V2vug~g9W U G
test %1,%1
6R&P[8t vd~6T atest %2,%2"
,RVY%f7dr TM: /* no outputs */BSD爱好者乐园cq,G!gGK@ }|h
: "a"((long)eax), "b"((short)bx), "c"((char)cl)BSD爱好者乐园:K+n!Fd.RJ'|
);BSD爱好者乐园"o)Kx/RoD]
__asm__("nop;nop;nop");
Cb WKABL$Oyreturn 0;BSD爱好者乐园2{cuzWJ
}
!P.M%L+}7fF{2?{o$ gcc -o inline2 inline2.c
+l'W,{9Y9z8U(u `6Vd R)O$ gdb ./inline2BSD爱好者乐园"x9w ]/e/i2z+y
GNU gdb 4.18
G+A;EWQ EpCopyright 1998 Free Software Foundation, Inc.
ay'tV;P\GDB is free software, covered by the GNU General Public License, and you areBSD爱好者乐园2^{ax r/j1b
welcome to change it and/or distribute copies of it under certain conditions.BSD爱好者乐园8M'~;EaI{d e1Gh
Type "show copying" to see the conditions.
8F"~&v2v,Lr,XThere is absolutely no warranty for GDB. Type "show warranty" for details.BSD爱好者乐园^&V8Q:g G p*?+_
This GDB was configured as "i686-pc-linux-gnulibc1"...
#|fs\0bTw#Bz(no debugging symbols found)...
'I!do*c Bm8o(gdb) disassemble main
+zaK {E ^%^ I5S TDump of assembler code for function main:
f8j X;w&D;a!].e... start: inline asm ...BSD爱好者乐园QQj!FS9mu
0x8048427 : nop
wI(|0]Qn%f c1FS0x8048428 : nop
#O%{4vD,L!e r0x8048429 : nopBSD爱好者乐园#s7]KAOZ:qp[_6Y
0x804842a : mov 0xfffffffc(%ebp),%eax
W2u(^dMalMNZ0x804842d : mov 0xfffffffa(%ebp),%bx
,^ m8v0C9m{0x8048431 : mov 0xfffffff9(%ebp),%cl
y7h.||7q N`/[.~+DR:U0x8048434 : test %eax,%eax
^W;I,R]4{)h.z9`0x8048436 : test %bx,%bxBSD爱好者乐园 H6A d7n:G(z,_
0x8048439 : test %cl,%cl
b;`!iWt&w)u R8D0x804843b : nopBSD爱好者乐园5KO` B o,DD{
0x804843c : nop
`&QTB R7v0x804843d : nop
R#@F}g,n2t.].e... end: inline asm ...
1ubBe,?#EJ*yEnd of assembler dump.BSD爱好者乐园 Cq}!M9z"l3K0d-U1v
$
Bu&u|.yL4E*e {!W就像你看到的,由内联汇编生成的代码将变量的值放入它们在input域中指定的寄存器中,然后继续BSD爱好者乐园M h N]%Cf8xM
执行当前的代码。编译器自动根据变量的大小来侦测操作数的大小,这样相应的寄存器就被BSD爱好者乐园%h,BKh'u t J!I
别名%0, %1 和 %2代替了(当使用寄存器别名时在存储器里指定操作数的大小回导致编译时发生错误)BSD爱好者乐园 q7s_ h4]&Q
在操作数约束里也可以使用别名。这不允许你在输入/输出域中指定多于10个的入口。我能想到的这样BSD爱好者乐园,]G!pP~1nxc:y T0D&@!r
做的唯一用法是在你指定操作数约束为“q”以便让编译器在a,b,c,d寄存器之间进行选择的时候。
a _GU)P当这个寄存器被修改时,我们不会知道选中了那个寄存器,因而不能在modify域中指定它。
1g9{x'cG这种情况下你只需指定""。BSD爱好者乐园K1? mb)v?]*K7h F
例子:
6d(m1GMN$ cat inline3.c
'l nL(I`3ED ~#includeBSD爱好者乐园oXag!~T M

BSD爱好者乐园5Y!g{3K1KZJe

int main(void) {
fvnz zl0YQlong eax=1,ebx=2;

3hlX%X MWlBSD爱好者乐园%t\2{ rQia

__asm__ __volatile__ ("add %0,%2"
3v&Nx#b;V e,B Ep7Z: "=b"((long)ebx)BSD爱好者乐园ZE({U5^I:x
: "a"((long)eax), "q"(ebx)
}9m ff0p~.~m/v6qr |U: "2"BSD爱好者乐园Uj+Pd!^G$r
);
"tZ}$v2y l7gprintf("ebx=%x\n", ebx);BSD爱好者乐园 y;u \ e%w|
return 0;BSD爱好者乐园V:M rDu
}

*f9bt`8|;]X因为使用范围的限制,BSD方面文章更新速度不快,站长会坚持每天更新博客,欢迎访问!
[版权声明]BSD爱好者乐园站内文章,如来源不是互联网,则均系原创或翻译之作,可随意转载,或以此为基础进行演译,但务必以链接形式注明原始出处和作者信息,否则属于侵权行为。另对本站转载他处文章,俱有说明,如有侵权请联系本人,本人将会在第一时间删除侵权文章。
TAG: gcc GCC 汇编
 

评分:0

我来说两句

seccode