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

AT&T 汇编指令说明

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

一 AT&T的基本语法

W/l4w&Mf*P&e!M,~ nBSD爱好者乐园SS)r0z6pW

语法上主要有以下几个不同.BSD爱好者乐园?S w2}D^ r'w7w

KD5DE2K'YM |"MxS.Ih★ 寄存器命名原则

x&nD eK

.dm#o"RHw K z{AT&T: %eax Intel: eax

'N9xz"G8H,D3h$Rd/H a

/zPU8Yq jV★ 源/目的操作数顺序

]z f-{(k{

^-]7{5Mx1lAT&T: movl %eax,%ebx Intel: mov ebx,eax

7|U0c^0X5D,Y

"Ct9CR)p★ 常数/立即数的格式

*z#|U4UF

1I @s8t;J bU DAT&T: movl $_value,%ebx Intel: mov eax,_valueBSD爱好者乐园 Y6aac0Xc

BSD爱好者乐园Yi/_V$xCl3@0G:\

把_value的地址放入eax寄存器BSD爱好者乐园z6SD a4p)K$_

,NU$F l E|N QAT&T: movl $0xd00d,%ebx Intel: mov ebx,0xd00d

`r7W \ qM+{%oqN]

`'NRs n★ 操作数长度标识

6gcW0L)p _vBSD爱好者乐园%n N(Y_"^KLl

AT&T: movw %ax,%bx Intel: mov bx,ax

x2T0e:JU_[u1n{BSD爱好者乐园m)hU1i#~

★寻址方式

~O0`Du5dcJ_:nQBSD爱好者乐园I VX4{#B(| R*\j1dC,H

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

]5Vk[7u

r+Ce%~Te:Z3}+q1s-EIntel: [basepointer + indexpointer*indexscale + imm32)BSD爱好者乐园p3r'Ru(ssu

BSD爱好者乐园RFt&_"T

Linux工作于保护模式下,用的是32位线性地址,所以在计算地址时

/u![8pI2G kYBSD爱好者乐园-v'?4iP&h}u'`%R

不用考虑segment:offset的问题.上式中的地址应为:BSD爱好者乐园}/WNY1@ P,b L

BSD爱好者乐园jZx$Rq Hi

imm32 + basepointer + indexpointer*indexscale

l6eH5_+Mdw"o_nsxBSD爱好者乐园 t t6q6u |

下面是一些例子:

4DB4R)`2{3v]JyBSD爱好者乐园KGQJI@

★直接寻址

(]:gG%qy)m#A D(HhR

N7v6T$lb'Ml)YAT&T: _booga ; _booga是一个全局的C变量BSD爱好者乐园E%z,NxK)Y3F"Sc _

BSD爱好者乐园0}0WH#_U$\5n)b

注意加上$是表示地址引用,不加是表示值引用.BSD爱好者乐园,b@ie$U1Iu1FbNit

[ c7E B@2W注:对于局部变量,可以通过堆指针引用.

)[)OOl4FX7{

x5kpK$p RIntel: [_booga]

ImrM9JyBSD爱好者乐园,b_$o0b+G?

★寄存器间接寻址

"q(RKKbP

6|;u\;B3[G3SU E$OXAT&T: (%eax)

R+}2j;q0}Ub-j9U(I4y"@

fI.HtbeIntel: [eax]BSD爱好者乐园Y:m,p-lvc {`)GAw

uQ2r$z)uS/t7A★变址寻址BSD爱好者乐园U{M2O`R }U

BSD爱好者乐园Dah#Oc7T T.s$M

AT&T: _variable(%eax)BSD爱好者乐园5yUIYC e

/G_&gJ JR4OIntel: [eax + _variable]

`(`)Q6{5XJ

4NblD2_AT&T: _array(,%eax,4)

H6_ v"_-Uz

-T1V P u$q+Bc6juIntel: [eax*4 + _array]

v-Q lN0p^G#EBSD爱好者乐园Y6j~ Q,fK7M q;Wk

AT&T: _array(%ebx,%eax,8)

8?$bJ'wQMBSD爱好者乐园0xZ3lA;] V"K

Intel: [ebx + eax*8 + _array]BSD爱好者乐园Geh&[&om~"k

@$b)Y0C/vU.SBSD爱好者乐园hF!z.nTJf2H9`Py

二 基本的行内汇编BSD爱好者乐园`9Vr7U\(a#G'}+S8G

BSD爱好者乐园9P,NQq3GZ

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

g_9t9E1R8J

5G [X.Ag(eI'aasm("statements");

9v \%]M3C3O&q

3c(O\!J!iaW例如:asm("nop"); asm("cli");

P'j:TI#]#nZofX

&B?6j@?\,tasm 和 __asm__是完全一样的.BSD爱好者乐园U-f*U3f/AO.E

!Cb$bs0T)H如果有多行汇编,则每一行都要加上 "\n\t"BSD爱好者乐园*A v@^a DXw7MY!i

BSD爱好者乐园]jA;\ l1nKq

例如:

`!nqud$y^ q7?V

M,T\dC.Eh:i |asm( "pushl %eax\n\t"BSD爱好者乐园 L(wGY S l {z

BSD爱好者乐园(@DsP%T#x4z)x}K

"movl $0,%eax\n\t"BSD爱好者乐园?X5V9C@I f k a

4w$K1D![6^Rj~"popl %eax");

*q)c]e1|JBSD爱好者乐园BS/UpQw4_N ~`

实际上gcc在处理汇编时,是要把asm(...)的内容"打印"到汇编

mqh}aPa?9udBSD爱好者乐园 H9w'\4vMuz R

文件中,所以格式控制字符是必要的.BSD爱好者乐园&|tu cZ3@r

BSD爱好者乐园!u | Iqe]f

再例如:BSD爱好者乐园pIbI,Nx{

!PW#il? }asm("movl %eax,%ebx");

p1a CRg NP

fx | k)R m{asm("xorl %ebx,%edx");BSD爱好者乐园1M){!` o1Hm['e H)G w

BSD爱好者乐园?xj PI

asm("movl $0,_booga);

_J2[Dx RbC

3Y/Je}@在上面的例子中,由于我们在行内汇编中改变了edx和ebx的值,但是BSD爱好者乐园 t^v/m2^5E0|

bw/~&ds%})UN由于gcc的特殊的处理方法,即先形成汇编文件,再交给GAS去汇编,BSD爱好者乐园 }V h W INl4HL

BSD爱好者乐园Kb2}!Kz(d*jZ

所以GAS并不知道我们已经改变了edx和ebx的值,如果程序的上下文BSD爱好者乐园`8D Wo \ Dj

9fK jy7h需要edx或ebx作暂存,这样就会引起严重的后果.对于变量_booga也

Q [UU0Uc

&n:z&G4]Qh存在一样的问题.为了解决这个问题,就要用到扩展的行内汇编语法.
([KBc!tQ

Aw:^9gQzmBSD爱好者乐园&E_-Rv6uf,xn[

三 扩展的行内汇编

n[9^m6E$M

6w!dfdI8t%Y扩展的行内汇编类似于Watcom.BSD爱好者乐园_ D\+Cm5T7QJ ]

BSD爱好者乐园_CXY t,Q

基本的格式是:BSD爱好者乐园G8K }bxsD-v~,N

BSD爱好者乐园4S Vn/H7BE:v"fQ

asm ( "statements" : output_regs : input_regs : clobbered_regs);

5U _:ic @'QXBSD爱好者乐园 j nc bc

clobbered_regs指的是被改变的寄存器.

yU'j-RU\

|yA6C4k8G;V ^tp下面是一个例子(为方便起见,我使用全局变量):

ar5CQC m

m(m4fK~n!aC`,@int count=1;

#dU Z(x"]/v

H4h!HJO J/Rint value=1;BSD爱好者乐园ow-U$xF,Mm

k!a6qO$J2qZP-[int buf[10];

h7[Nl N5M

'`Np5fZ3svoid main()

WS)t P,|1bx|)HkBSD爱好者乐园*ZT2N[1L

{BSD爱好者乐园 MlQ%eRQBYm

;Zd6j^e`*bvasm(BSD爱好者乐园:i:r0Sg4r fC

BSD爱好者乐园`Y[^4G&O5s$N|

"cld \n\t"BSD爱好者乐园!A\O{hB

l0p[!J{jK4dZP"rep \n\t"

:M8GHF]3aBSD爱好者乐园,kD"{1C@ e#~r S

"stosl"BSD爱好者乐园:k+v7lS)p:n"v`'c

V!e8['t3L&CZ-C;P*^t:

)?;r+Vp AAn3sBSD爱好者乐园,xo qxpg9A

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

y"N4D;BOXgBSD爱好者乐园SNOg8g4@sAs

: "%ecx","%edi" );

9QPlOK~x5Td mQ

Z c/l B:@i0W%V}

/Yt y#_J!Y!vkp'SDb

)|V0b'@]d2X得到的主要汇编代码为:

/}KM%}0VsU+b,mThBSD爱好者乐园1Bo{(hJn6p3kO

movl count,%ecx

Tc r}2y6y&f3fBSD爱好者乐园O4@Zez5?!A ct

movl value,%eaxBSD爱好者乐园+s+jscN D1hE(e9z

BSD爱好者乐园 }a}(i S'Y!c p(D

movl buf,%ediBSD爱好者乐园-q7xsO StW

BSD爱好者乐园.I? CGC ^+wk

#APPBSD爱好者乐园 ~-y8S8s"gE

BSD爱好者乐园2Eta/?$pl1S+V8Lp.m2C

cld

2jE8v)i4nBSD爱好者乐园7]"]4T.V x%b7aQ O

rep

0poZ2D1KAx tBSD爱好者乐园lU;@7M#aL1~b

stoslBSD爱好者乐园+@0O;|~5v R$Ct

BSD爱好者乐园)E4UX @&];I;m.j

#NO_APPBSD爱好者乐园+B/]LXwll;zt3l~4D

BSD爱好者乐园"d"iR)Z2F.@bKP3t

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

O Yp m0wJ-R/P

)~QO3Ca H1u4B1q这几条语句的功能是向buf中写上count个value值.BSD爱好者乐园p!X"?;Q VM$hP

l5vPKM2X冒号后的语句指明输入,输出和被改变的寄存器.BSD爱好者乐园`v@'ydX)aL h

3j"Y@Di'ZweZ通过冒号以后的语句,编译器就知道你的指令需要和改变哪些寄存器,

)w%B)a`!b]}BSD爱好者乐园'N"b0o.mJN.S m{

从而可以优化寄存器的分配.

x mF+? KHV rJBSD爱好者乐园"l g,GP1Y

其中符号"c"(count)指示要把count的值放入ecx寄存器BSD爱好者乐园Mt Yk*AK3ZVf B6L_

BSD爱好者乐园'E~Ga3}uP+k

类似的还有:

f"?S_AxBSD爱好者乐园$x^$c eR&?U

a eax

6UY3F"} ^v

fM1I8zd?b ebxBSD爱好者乐园t,g,|%F| l

*j&z sL jO \c ecx

v1l;Y!Z V9d$j{Yk

IRw m8k7mxd edxBSD爱好者乐园u y"pf4?@

7y3yL(HF |'P WX.BS esiBSD爱好者乐园$^8Ci J@ \2a*w(S7O

BSD爱好者乐园[ ZsYNW;l

D edi

Z*`cTrBSD爱好者乐园el` GwY G3\"W+A

I 常数值,(0 - 31)BSD爱好者乐园JGN*Xo6`[

BSD爱好者乐园0W6|8sy]#H

q,r 动态分配的寄存器

]N#R1lyR n

%R/Lqf#`&dk3{9B(k[8ovg eax,ebx,ecx,edx或内存变量BSD爱好者乐园]#B)| \lg6FJJyLx

2@})] a i2W$f [A 把eax和edx合成一个64位的寄存器(use long longs)BSD爱好者乐园!Tb4m-i)Qj(mo/z

BSD爱好者乐园 p#dqo_!u!J|z

我们也可以让gcc自己选择合适的寄存器.BSD爱好者乐园DfYWt+Q

h;V1Az%i如下面的例子:BSD爱好者乐园o)O \%n3@K#^)U ^G+~9u

BSD爱好者乐园 a5~c0c,j {9` ]q$s

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

C5t1x2^hbBSD爱好者乐园:N1K;|*q#|l8LI6|R6n

: "=r" (x)BSD爱好者乐园O'DD4O L

!}EMUq"wd B.U: "0" (x) );BSD爱好者乐园?S7sT~;I%]5u

?)\^5K+e r5d`这段代码实现5*x的快速乘法.

-\ R}I IW q

yi%U&Wh得到的主要汇编代码为:

k#~ DnnZ

d e d{Lv*pmovl x,%eaxBSD爱好者乐园(] k v6ZVv,m&ql%u

~%eZ1`&KQ#APPBSD爱好者乐园2E(Pi$S"ae&`,?

BSD爱好者乐园0wC'l'FL"M4}9A(z

leal (%eax,%eax,4),%eaxBSD爱好者乐园w_I?`U^ZC-TV

l%z?]}7Ck?6T#NO_APP

"EO3ft0T n2VNB

/M#U!|7s&C eQmovl %eax,xBSD爱好者乐园l5X$q(l G

'\]`B*hp2G几点说明:BSD爱好者乐园0zY.i(v?

BSD爱好者乐园)Q7h m/IX8yx.pg

1.使用q指示编译器从eax,ebx,ecx,edx分配寄存器.

I6qt a0fcYw

,atu8``!{#a v使用r指示编译器从eax,ebx,ecx,edx,esi,edi分配寄存器.

IC5d]OEkvBSD爱好者乐园m$}'cP$[1H6nY$x

2.我们不必把编译器分配的寄存器放入改变的寄存器列表,因为寄存器BSD爱好者乐园7S:J1W/} y$k)`9Op l7g

D,Yx4g%QJw;y9B已经记住了它们.BSD爱好者乐园 A\*WBCf1X9gY

yZu-B Kt3."="是标示输出寄存器,必须这样用.

N1?\S!e

!Te$pT$QV~4.数字%n的用法:BSD爱好者乐园 m fr f m L"s

BSD爱好者乐园8mhc&i5[ Gd#v W~

数字表示的寄存器是按照出现和从左到右的顺序映射到用"r"或"q"请求BSD爱好者乐园3d#gH._&z i@

BSD爱好者乐园)nH^&EX1Nz!f

的寄存器.如果我们要重用"r"或"q"请求的寄存器的话,就可以使用它们.

|jQ] KBSD爱好者乐园#h5HM Kx-e

5.如果强制使用固定的寄存器的话,如不用%1,而用ebx,则BSD爱好者乐园-dmu6K3\(WTA

vA5w F4i"NRf:Vasm("leal (%%ebx,%%ebx,4),%0"BSD爱好者乐园L?&x1i(~ ?h@

L0Sx-uZ9j&\: "=r" (x)BSD爱好者乐园&VH7h7^-f)R)y/b

MNZ4TwLO*dk p{: "0" (x) );

i0GBj/q_BSD爱好者乐园1DK2nmF

注意要使用两个%,因为一个%的语法已经被%n用掉了.

g#}p {o/DFqBSD爱好者乐园5z.sGD'E

下面可以来解释letter 4854-4855的问题:BSD爱好者乐园,HxkaV7f#R'd

,|H'B/L^'U1、变量加下划线和双下划线有什么特殊含义吗?BSD爱好者乐园S)n}SS{DF

nc!Vn"\8eO1f加下划线是指全局变量,但我的gcc中加不加都无所谓.

4}Ow'pD@BSD爱好者乐园+^6s GR.@8@

2、以上定义用如下调用时展开会是什么意思?BSD爱好者乐园n&X.jL#HA#M

)b6^P7Q4j)] R#define _syscall1(type,name,type1,arg1) \BSD爱好者乐园]nw4k/U0X!Q

2W{9r'H i_ Rtype name(type1 arg1) \BSD爱好者乐园1O7UD+C.f6~BT

BSD爱好者乐园4D3vM+i&wH:H~

{ \

-Tt'@:a)s!@ ]#HBSD爱好者乐园 X6Z-Q1S { YHY

long __res; \BSD爱好者乐园zG7FHx`jR&C

u?*|l5mY2c(K*e#t)Sh/* __res应该是一个全局变量 */

(SPL9i2Z })NBSD爱好者乐园Rv#rp iq q1T6\7PZ

__asm__ volatile ("int $0x80" \

4bO4_;X1cv+c

$u'?VL ~d8S2ia/* volatile 的意思是不允许优化,使编译器严格按照你的汇编代码汇编*/BSD爱好者乐园7E;Q!q A@Y Z6`L&fmz

BSD爱好者乐园o"fs}8u:s

: "=a" (__res) \

;JV3a?E'D8H

eU9J_[ z#?/* 产生代码 movl %eax, __res */

URLbG\*w7R

U9C'L c"AK,H(J5DP9@,V: "0" (__NR_##name),"b" ((long)(arg1))); \BSD爱好者乐园)U8d7^"w#lBm([

BSD爱好者乐园l]'xG0l5{

/* 如果我没记错的话,这里##指的是两次宏展开.BSD爱好者乐园 axv)A9N7x;A9~

BSD爱好者乐园`o5g$W5zF2m

  即用实际的系统调用名字代替"name",然后再把__NR_...展开.

`4O)X'_;}H^X#VxBSD爱好者乐园(|4I7y T)b

  接着把展开的常数放入eax,把arg1放入ebx */BSD爱好者乐园/Nt+_TNW"OV^

BSD爱好者乐园;F"p"Y:C v[7n

if (__res >= 0) \

S.A {+KxFBSD爱好者乐园#DR:hZC7L3G;]

return (type) __res; \BSD爱好者乐园-rp NR.wh ZW*Y

BSD爱好者乐园nf9[k F!w9g

errno = -__res; \BSD爱好者乐园q'q|!R/S WX;F?

w|w!U2m2d~:K;]%s+{return -1; \BSD爱好者乐园JofN'[6r#m

,t9w1s"Mf}BSD爱好者乐园Ot#xkO5a
////////////////////////////////////////////////////////////////////////

4Ak S)Q!F&| ?

]["j/BGS/U/Hq_四.AT&T汇编与Intel汇编的比较BSD爱好者乐园HO5J [)S"w ~'} |6@

BSD爱好者乐园w-W"`*fd"Flz

Intel和AT&T语法的区别
%Ty&?1WR/GIntel和AT&T汇编语言的语法表面上各不相同,这将导致刚刚学会INTEL汇编的人第一次见到AT&T汇编时BSD爱好者乐园+v9O:Ni&bS
会感到困惑,或者反之。因此让我们从基础的东西开始。BSD爱好者乐园#POi;Hyo`^

BSD爱好者乐园l5i+Y"OK;Lb!E

前缀
%G!Ye*vR@i在Intel汇编中没有寄存器前缀或者立即数前缀。而在AT&T汇编中寄存器有一个“%”前缀,立即数有
$Gh.nwn0{w一个“$”前缀。Intel语句中十六进制和二进制数据分别带有“h”和“b”后缀,并且如果十六进制BSD爱好者乐园iG K [&\
数字的第一位是字母的话,那么数值的前面要加一个“0”前缀。BSD爱好者乐园}Va)m!JSp*t
例如,BSD爱好者乐园 `R `7KP$zV?uE
Intex SyntaxBSD爱好者乐园(Af/HUcW&z
mov eax,1
E4T:r!wV!z.I!xmov ebx,0ffhBSD爱好者乐园8Y%j~O7x'pZ\p*a
int 80h

zQR~oo m X,vBSD爱好者乐园,bO CR g;_E*Z

AT&T SyntaxBSD爱好者乐园[V]Lf1ANGEx^
movl $1,%eax
Eu Ix2o#b@movl $0xff,%ebxBSD爱好者乐园 {4v-V g1NJ]r^
int $0x80
r[C3mH*K就像你看到的,AT&T非常难懂。[base+index*scale+disp] 看起来比disp(base,index,scale)更好理解。
c[K;Y9Kl)gIo XBSD爱好者乐园1DthD9F'V6[

u%K K&C+T r1Spn操作数的用法
+^'_#r F#eyintel语句中操作数的用法和AT&T中的用法相反。在Intel语句中,第一个操作数表示目的,第二个BSD爱好者乐园x)t b:bSx9c1I)f
操作数表示源。然而在AT&T语句中第一个操作数表示源而第二个操作数表示目的。在这种情形下AT&T语法
J oW3K8^;Q+U8{9j的好处是显而易见的。我们从左向右读,也从左向右写,这样比较自然。BSD爱好者乐园w.Msdp+yEw
例如,BSD爱好者乐园*l6N `z8d;n`
Intex Syntax
].T7L/L%~%`instr dest,source
yxHF g+t6mX4\hMmov eax,[ecx]BSD爱好者乐园NQw#y'bh:\.l
BSD爱好者乐园q:YA\ G'aSl}1?
AT&T Syntax
Y]pP4N&R7v"];Xinstr source,dest
s L+hx1n;u?`Dmovl (%ecx),%eax

J;X D:k~

nIh"tfnR(Wgm存储器操作数
'X1^ R%Eg'?*`如同上面所看到的,存储器操作数的用法也不相同。在Intel语句中基址寄存器用“[”和“]”括起来
tKl&Nz}而在AT&T语句中是用“(”和“)”括起来的。BSD爱好者乐园PWk4W {6EV%q
例如,BSD爱好者乐园"I2Ctk mB)r1t
Intex SyntaxBSD爱好者乐园 }'OM"R l Y-`
mov eax,[ebx]
1wJ n:~qmov eax,[ebx+3]
/d IR/G6`!t[AT&T SyntaxBSD爱好者乐园kl4UO2f-_eX(}
movl (%ebx),%eaxBSD爱好者乐园%^+lJ.Q4C
movl 3(%ebx),%eax
I E~'k,}*k%ZLAT&T语法中用来处理复杂的操作的指令的形式和Intel语法中的形式比较起来要难懂得多。在Intel语句
!Z W|q)D&\5T中这样的形式是segreg:[base+index*scale+disp]。在AT&T语句中这样的形式是
9Q9a u$Cg3S~%segreg:disp(base,index,scale)。BSD爱好者乐园 |uw%R[,v
Index/scale/disp/segreg 都是可选并且可以去掉的。Scale在本身没有说明而index已指定的情况下BSD爱好者乐园+UbEx)X3K#mb9m
缺省值为1。segreg的确定依赖于指令本身以及程序运行在实模式还是pmode。在实模式下它依赖于BSD爱好者乐园-[uA#i%nf
指令本身而pmode模式下它是不需要的。在AT&T语句中用作scale/disp的立即数不要加“$”前缀。BSD爱好者乐园i3ov8HS
例如BSD爱好者乐园R0a-E/? | LD9\&g
Intel SyntaxBSD爱好者乐园"R1H4o4CS/Ur(s
instr foo,segreg:[base+index*scale+disp]BSD爱好者乐园/|1Q|vP}
mov eax,[ebx+20h]BSD爱好者乐园8a D Uf0]/sGs,X5wwSs
add eax,[ebx+ecx*2h]
0A0d8N+q0L~}lea eax,[ebx+ecx]BSD爱好者乐园 cRm^&X`(N7u
sub eax,[ebx+ecx*4h-20h]
T;w]8o*`dvP&aAT&T SyntaxBSD爱好者乐园.[cnw]
instr %segreg:disp(base,index,scale),fooBSD爱好者乐园"b"u?C~%i
movl 0x20(%ebx),%eax
6eB$g"\ WR,C#eaddl (%ebx,%ecx,0x2),%eaxBSD爱好者乐园#g\(_(a+r"`/I
leal (%ebx,%ecx),%eaxBSD爱好者乐园Db j nd'Cd$|
subl -0x20(%ebx,%ecx,0x4),%eaxBSD爱好者乐园;Z"I7L%c#LD A#V

BSD爱好者乐园3_+{cQP a K

后缀BSD爱好者乐园'nVGR.Hk |U6[
就像你已经注意到的,AT&T语法中有一个后缀,它的意义是表示操作数的大小。“l”代表long,BSD爱好者乐园iG*`9zV3U&Z|~
“w”代表word,“b”代表byte。Intel语法中在处理存储器操作数时也有类似的表示,
`]E{'e如byte ptr, word ptr, dword ptr。"dword" 显然对应于“long”。这有点类似于C语言中定义的BSD爱好者乐园}(c&~dT;v
类型,但是既然使用的寄存器的大小对应着假定的数据类型,这样就显得不必要了。BSD爱好者乐园k+sg ^i`*lb3]
例子:BSD爱好者乐园MC3BomtF@+{ T#@
Intel Syntax
!R8d:L+f4t1vmov al,blBSD爱好者乐园&UT7\$`ENL
mov ax,bxBSD爱好者乐园%FoV5WJ u1r*[2~
mov eax,ebx
C.u2j-U }8p.K z4uymov eax, dword ptr [ebx]
2@\"D'jP9C \AT&T SyntaxBSD爱好者乐园E]!ya6[IV
movb %bl,%alBSD爱好者乐园9Q l!V Z y(Z+J/r
movw %bx,%axBSD爱好者乐园m:BF&RCDj Q)A
movl %ebx,%eaxBSD爱好者乐园a#i$m} ]B0r*_J3X
movl (%ebx),%eaxBSD爱好者乐园zj%W z7c'@"M.Hf

BSD爱好者乐园"P'^U9u3D7h?

注意:从此开始所有的例子都使用AT&T语法
H d F2JY v,G K$b系统调用
@CG~"U,h+I-gUc本节将介绍linux中汇编语言系统调用的用法。系统调用包括位于/usr/man/man2的手册里第二部分所有BSD爱好者乐园#De/j0m8J'vUp"sq
函数。这些函数也在/usr/include/sys/syscall.h中列出来了。一个重要的关于这些函数的列表是
Q VmF!]c1T6fp在http://www.linuxassembly.org/syscall.html里。这些函数通过linux中断服务:int $0x80来被执行
"dr&Va%Es小于六个参数的系统调用
e ?7lx.Z s7CQ对于所有的系统调用,系统调用号在%eax中。对于小于六个参数的系统调用,参数依次存放
W7wR)X1HI;f6^在%ebx,%ecx,%edx,%esi,%edi中,系统调用的返回值保存在%eax中。
+H9Ylt'J%|S(s q/SjZ1\4m系统调用号可以在/usr/include/sys/syscall.h中找到。宏被定义成SYS_的形式,BSD爱好者乐园2aX(oyz9W@9n
如SYS_exit, SYS_close等。
cJ/_3c9rK5}7a#m例子:(hello world 程序)BSD爱好者乐园 TT5h3IKj\0_
参照write(2)的帮助手册,写操作被声明为ssize_t write(int fd, const void *buf, size_t count);
#{,A-?*Ub"|0Qe这样,fd应存放在%ebx中,buf放在 %ecx, count 放在 %edx , SYS_write 放在 %eax中,紧跟着是BSD爱好者乐园JM1YWg#b?vf
int $0x80语句来执行系统调用。系统调用的返回值保存在%eax中。
+AA7TmTL)t0K$ cat write.sBSD爱好者乐园3H"`;NU+D"F
.include "defines.h"
b5yQ"r7iAe.dataBSD爱好者乐园.pzA N(GN)am9T1O
hello:BSD爱好者乐园-kg!\hx1^le
.string "hello world\n"

!~GrA$c0E:[6_Fp

/[!s*Tf%At.globl main
k2h0Ve Y1Jmain:BSD爱好者乐园']8C O:dbec_
movl $SYS_write,%eaxBSD爱好者乐园$jtN,bQ q A f7bB
movl $STDOUT,%ebx
F)ew&K9E]2Ismovl $hello,%ecxBSD爱好者乐园-p6_U#q+p
movl $12,%edx
-dT$vE] x-rint $0x80

l%J N;K}h?$s

5v b k8jf,zretBSD爱好者乐园8PKP#p F.t U
$BSD爱好者乐园3NY5\ e@n;M I'}
少于5个参数的系统调用的处理也是这样的。只是没有用到的寄存器保持不变罢了。象open或者fcntl这样BSD爱好者乐园)_EXD]@DIL
带有一个可选的额外参数的系统调用也就知道怎么用了。
0rl;J;r9a&T9T ^大于5个参数的系统调用
5nD8v5{N&I eP Y5x@参数个数大于五个的系统调用仍然把系统调用号保存在%eax中,但是参数存放在内存中,并且指向第一个
AI1XsH+_参数的指针保存在%ebx中。
$PDhd0{如果你使用栈,参数必须被逆序压进栈里,即按最后一个参数到第一个参数的顺序。然后将栈的指针拷贝
5D#aM(A7]d YD7g"F到%ebx中。或者将参数拷贝到一块分配的内存区域,然后把第一个参数的地址保存在%ebx中。
6n.v@9bI例子:(使用mmap作为系统调用的例子)。在C中使用mmap():BSD爱好者乐园7MZd.Iu/Gb
#includeBSD爱好者乐园k'c0t#m.d2j$O)]
#include
6s$p_ xZ QZ"X%S"b#include
7N0iAm;vU#includeBSD爱好者乐园,r:sQ[v3c
#include

,j/f$E2[)lf*z:f I

r9c/P @R{5^#define STDOUT 1BSD爱好者乐园:v4B,s#g Z

qf0ZnH6Ivoid main(void) {
t,wQ E4`,ochar file[]="mmap.s";BSD爱好者乐园b v1Z0T0ez? g"F|4g
char *mappedptr;
cW.i'eR9?+S9I%?(xint fd,filelen;

/F:{Y uju.n

,B%n-IKPP3cfd=fopen(file, O_RDONLY);
v {1]&Ui"?-hsfilelen=lseek(fd,0,SEEK_END);
5HPZ!T-F t5}Dmappedptr=mmap(NULL,filelen,PROT_READ,MAP_SHARED,fd,0);
]tK Tz kwrite(STDOUT, mappedptr, filelen);
AI5k&iTybmunmap(mappedptr, filelen);
ky&@Q}*l^(]/cclose(fd);
Ay%q3rnX%sd}
O#xP+D p;V!QG0cgcmmap()参数在内存中的排列:
*eA3J7HQW @Q(z#L#m2}%esp %esp+4 %esp+8 %esp+12 %esp+16 %esp+20
g8r9JAU4pt2c#LD00000000 filelen 00000001 00000001 fd 00000000
iPwFF等价的汇编程序:
QeOqHV0[$ cat mmap.sBSD爱好者乐园9uzFOU tK
.include "defines.h"BSD爱好者乐园wg2k9mD0HB

MgE4?0Q']"P.dataBSD爱好者乐园t;fE!M Fwp.}
file:BSD爱好者乐园dS\Dy Mu7yo
.string "mmap.s"BSD爱好者乐园0w~Hdyk3Dkm1G
fd:
'~a8F4V0Jj8V3R.long 0
y-l}#b%H2I` Dfilelen:
dNun8o l.long 0BSD爱好者乐园En1e Y-I Fv/Q
mappedptr:BSD爱好者乐园7fX:z;w%gB2XKi
.long 0BSD爱好者乐园;A[0M3?Dph4Z v

BSD爱好者乐园/]C8S0`XE4c

.globl main
~3oyJ-RV ~main:BSD爱好者乐园 _ u7|$q3D}|/u,j
push %ebpBSD爱好者乐园#V3d4g6jza0|l2|
movl %esp,%ebp
Qb}akaJsubl $24,%espBSD爱好者乐园/x9k Lx8Q\

a H2bo3WN// open($file, $O_RDONLY);BSD爱好者乐园kv r:mBu Z1v

8n-K']g Ogmovl $fd,%ebx // save fdBSD爱好者乐园P,f a/Z#S1z
movl %eax,(%ebx)

h.Ov cv"B Ou+p YUvBSD爱好者乐园y?*Va'q)Hm!X

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

7L@&ChE1SjI_|`BSD爱好者乐园G7XZ*c2V7W~

movl $filelen,%ebx // save file length
@^Ckr}"S[movl %eax,(%ebx)BSD爱好者乐园;B0j"h9SJK'y6e'P v:F2I

BSD爱好者乐园 I)Kn gJG5yl,d't,w

xorl %edx,%edx

rl!D E6pB(X+]iX#r

y.j;MD0GCq// mmap(NULL,$filelen,PROT_READ,MAP_SHARED,$fd,0);BSD爱好者乐园iH"CYB;Ot H
movl %edx,(%esp)
.jsOgkmovl %eax,4(%esp) // file length still in %eax
f:i3|&J;lW5b(\Xmovl $PROT_READ,8(%esp)
zy3m u l'ymovl $MAP_SHARED,12(%esp)
wMU)bO'}%n1s(~movl $fd,%ebx // load file descriptor
J x"o;x&`1n,Y!NX-fmovl (%ebx),%eax
#}+SuI)GF;Asc+zmovl %eax,16(%esp)
rP^%c5M+khmovl %edx,20(%esp)BSD爱好者乐园Q{'|1H'I$x%B&hS\
movl $SYS_mmap,%eax
6d1vk Gp |movl %esp,%ebxBSD爱好者乐园 sal Y{
int $0x80

| Olz6UjxARe%M

T"|wX%N)GFx,O*}movl $mappedptr,%ebx // save ptrBSD爱好者乐园7^;UQd R%tAb$k:F
movl %eax,(%ebx)
}2T+O `"p0deBSD爱好者乐园8b x{3T Lqe/A0F
// write($stdout, $mappedptr, $filelen);
P}\;i D]Uf// munmap($mappedptr, $filelen);
#urK8Z/hA// close($fd);BSD爱好者乐园,X-}gTs
BSD爱好者乐园A&hbi3r$]
movl %ebp,%esp
h.N6E0?ipopl %ebpBSD爱好者乐园8N*j;~ _ Y6_ q|

~o;B\7Nb`!pOret
(R(_$Cd0y7B$BSD爱好者乐园|S Dhv
注意:上面所列出的源代码和本文结束部分的例子的源代码不同。上面列出的代码中没有说明其它的
(eT0x.]gpp系统调用,因为这不是本节的重点,上面列出的源代码仅仅打开mmap.s文件,而例子的源代码要读BSD爱好者乐园)xG4L)uYB
命令行的参数。这个mmap的例子还用到lseek来获取文件大小。
a$i MM*nhQ?[Socket系统调用
)EDuwU3| N;dgSocket系统调用使用唯一的系统调用号:SYS_socketcall,它保存在%eax中。Socket函数是通过位于BSD爱好者乐园1K[n8s?M1J
/usr/include/linux/net.h的一个子函数号来确定的,并且它们被保存在%ebx中。指向系统调用参数BSD爱好者乐园d;I V(?T
的一个指针存放在%ecx中。Socket系统调用也是通过int $0x80来执行的。
3Y6q].l}vv}/@8L$ cat socket.s
4@;pG9KH\x.include "defines.h"

4w3[b,\6VULBSD爱好者乐园Tte |/r[

.globl _start
F/O"`9Us l f t_start:BSD爱好者乐园9R L}f-C
pushl %ebpBSD爱好者乐园 jOh/gw.l8U6tO$tG
movl %esp,%ebpBSD爱好者乐园(R+W-C?$l Qc
sub $12,%esp

n9r9oT}&o

6XkG+^ `-`3wB// socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);BSD爱好者乐园3O` g,u"qzu'u
movl $AF_INET,(%esp)BSD爱好者乐园^4} N"sn$|
movl $SOCK_STREAM,4(%esp)BSD爱好者乐园_(gW'un
movl $IPPROTO_TCP,8(%esp)

4k+o)^Kb\1g_*EBSD爱好者乐园{8O TRd8\ a

movl $SYS_socketcall,%eaxBSD爱好者乐园8c}L@.J
movl $SYS_socketcall_socket,%ebxBSD爱好者乐园-I^#H.m&b?N9o
movl %esp,%ecxBSD爱好者乐园'S,ActyBf
int $0x80

[ GQ pk"HB6E^

M9l@ j%N6e Hmmovl $SYS_exit,%eaxBSD爱好者乐园4No] `MGP
xorl %ebx,%ebx
Ve,QW u7f2cnint $0x80BSD爱好者乐园$v,U1]x eJg1r7KNq&X5p

?sgh C\ _,\,mmovl %ebp,%esp
;f,dr.`+ik epopl %ebp
A sIDn4wFret
\7g Y'i |\~O$BSD爱好者乐园H&B}9Y \K9?/y

s` A0wD命令行参数
NW#S.|})K在linux中执行的时候命令行参数是放在栈上的。先是argc,跟着是一个由指向命令行中各字符串的BSD爱好者乐园j2doS#A
指针组成的数组(**argv)并以空指针结束。接下来是一个由指向环境变量的指针组成的BSD爱好者乐园 MX#X z3@
数组(**envp)。这些东西在asm中都可以很容易的获得,并且在例子代码(args.s)中有示范。

)T X C dT`gw-]

? \%opW4JBSD爱好者乐园Wt]~-f `0mM
GCC内联汇编BSD爱好者乐园_y#ZN(Z*Cpo a`j
本节中GCC内联汇编仅涉及x86的应用程序。操作数约束会和其它处理器上的有所不同。关于这部分
mV9k)F%_Wd(v的说明放在本文的最后。
h*H6D/\]H8{N.@X lgcc中基本的内联汇编非常易懂,如BSD爱好者乐园qw&{ BN$i
__asm__("movl %esp,%eax"); // look familiar ?

e/[B4suP

Hj/A/D1Gd;E或者是
i[d}TZ3]$`U__asm__("
Vn-mx4h0j+e*DWmovl $1,%eax // SYS_exitBSD爱好者乐园y4G%h[q
xor %ebx,%ebxBSD爱好者乐园+U0vPI _ G7uoc0U
int $0x80BSD爱好者乐园I+p1w9a9v ieW+Z
");BSD爱好者乐园 vu7b'i]j
如果指定了用作asm的输入、输出数据并指出哪一个寄存器会被修改,会使程序的执行效率提高。
+[1b'_w5t"b M {input/output/modify都不是必需的。格式如下:
H)[*o6b%]4x:vA%P__asm__("" : output : input : modify);BSD爱好者乐园3nj @ jX
output和input中必须包含一个操作数约束字符串,并紧跟一个用圆括号括起来的C语言表达式。
"KX+m*Vt-v^e!?[1P输出操作数约束的前面必须有一个“=”,表示这是一个输出。可能会有多个输出,多个输入和
W["^1C t2_7X!D多个修改过的寄存器。每个“入口”应该用“,”分隔开,并且入口的总数不多有10个。
j)v U7{c(t$g/N6t操作数约束字符串可以是包含整个寄存器的名称也可以是简写。BSD爱好者乐园!d7v)MvN.JY)_G
Abbrev Table
t*Kydo&z0k}Abbrev RegisterBSD爱好者乐园4m4ooFcx+H9M
a %eax/%ax/%al
b%d ]K(L/iR%y0[-SVb %ebx/%bx/%blBSD爱好者乐园3Z{:y Ocpv*^
c %ecx/%cx/%clBSD爱好者乐园W$wQe0X9L2s
d %edx/%dx/%dlBSD爱好者乐园J B'B[%e;P
S %esi/%siBSD爱好者乐园[ g S-zJN
D %edi/%diBSD爱好者乐园}PL*g2l2H8a
m memory
rd] V!_例如:

+o]O*m-BN {BSD爱好者乐园z7K'L u C/F

__asm__("test %%eax,%%eax", : /* no output */ : "a"(foo));
Hp4v$V%p^"?$f}BSD爱好者乐园4zsbb!Wv tIf

BSD爱好者乐园(h ?!~ z(wVfRX

或者是BSD爱好者乐园 ~8T[3R }#~k

BSD爱好者乐园YaUX$D-dx

__asm__("test %%eax,%%eax", : /* no output */ : "eax"(foo));
z _3LWs#r Z你可以在__asm__后使用关键字__volatile__:“你可以利用在__asm__后使用关键字__volatile__的
Dlkz tf8n$?方法防止一条‘asm’指令被删除、移动或者被重新组合。”(出自gcc的info文件中"Assembler
q,i.u0^7om Q,vInstructions with C Expression Operands" 部分)
5Y0r5A}r0L+\ jQn$ cat inline1.cBSD爱好者乐园-ZG3I;K-K;Y*}
#include

i!QUOanBSD爱好者乐园0ae a_QDc

int main(void) {
.M0H P%C2yb7hint foo=10,bar=15;
A {l(y0M|2f3xBSD爱好者乐园^6djk!n k3fy$S!R
__asm__ __volatile__ ("addl %%ebxx,%%eax"
K4O-t p,K: "=eax"(foo) // ouput
3^+j%N2A,zGZ: "eax"(foo), "ebx"(bar)// input
7aV!v Q9x9dA%xI s%m'I: "eax" // modifyBSD爱好者乐园-U7J"? xGB~+x
);
e6q8t$` {,l e`-~printf("foo+bar=%d\n", foo);
Lx8oa L&areturn 0;BSD爱好者乐园Q'kC)m![H:j*?/\w
}BSD爱好者乐园$z9|Qo"J`2]#n QC?
$
o'l,Z M cJi/{你可能已经注意到现在寄存器使用“%%”前缀而不是“%”。这在使用output/input/modify域时是必要的,
+o(yE i![;b4\_a这是因为此时基于其它域的寄存器的别名的使用。我马上来讨论这个问题。
(Tx`]?E你可以很简单的指定“a”而不是写“eax”或者强制使用一个特殊寄存器如"eax"、"ax"、"al",BSD爱好者乐园frd9\-_*w-X
这同样适用于其它一般用途的寄存器(在Abbrev表中列出的)。当你在当前的代码中使用特殊的寄存器BSD爱好者乐园 n gr:|AZ
时这好像毫无用处,因此gcc提供了寄存器别名。最多有10个别名(%0—%9),这也是为什么只允许10个BSD爱好者乐园"[ZpYU
输入/输出的原因。
u7E YK8y$ cat inline2.cBSD爱好者乐园/h'sdc,P$Q
int main(void) {BSD爱好者乐园8d2uG'DGs9zzSks
long eax;BSD爱好者乐园n7k1Z{0j4cJ c
short bx;BSD爱好者乐园-{l^@wF2k2U
char cl;

7m.JJd1d8]

)@(y$R#b$\.i@__asm__("nop;nop;nop"); // to separate inline asm from the rest of
7H3Nki J'C-c5|,l// the codeBSD爱好者乐园*u{*~?0Ej
__volatile__ __asm__("
Vwe3U:_.vMtest %0,%0BSD爱好者乐园f2VJla*}
test %1,%1BSD爱好者乐园-X!K6mx+p
test %2,%2"BSD爱好者乐园Bs/Jf3hx {L,w+k l
: /* no outputs */BSD爱好者乐园 wLJQm
: "a"((long)eax), "b"((short)bx), "c"((char)cl)
"eD8Z!ZEG);
nq}#Od G__asm__("nop;nop;nop");BSD爱好者乐园#P4m~ `z["M ^b Ew
return 0;BSD爱好者乐园 ejZ+\&sZ,a~b"E
}BSD爱好者乐园F w9s7UKx*o%e
$ gcc -o inline2 inline2.cBSD爱好者乐园7Tn.p8x F
$ gdb ./inline2BSD爱好者乐园7?"bHm p9CYlo
GNU gdb 4.18
i9r3b#cXGoCopyright 1998 Free Software Foundation, Inc.
:o$[ t-l}/H\F3iGDB is free software, covered by the GNU General Public License, and you are
a5O6x$LiF#[n4^o0Swelcome to change it and/or distribute copies of it under certain conditions.BSD爱好者乐园-N_'Z2p? @
Type "show copying" to see the conditions.
.BR ^(l'@x4aThere is absolutely no warranty for GDB. Type "show warranty" for details.
CLXuwr7pm0nThis GDB was configured as "i686-pc-linux-gnulibc1"...
-U~f ^$wyki(no debugging symbols found)...
s5FlD4F%e Hu+m(gdb) disassemble main
B4lIq?iJ9VyDump of assembler code for function main:BSD爱好者乐园 }k0x%F5[I:R.~H
... start: inline asm ...
+gd bt uB0x8048427 : nopBSD爱好者乐园7pw6~uY {8bUb
0x8048428 : nop
/C^j s2~0x8048429 : nop
4zxJQ%X)lub0x804842a : mov 0xfffffffc(%ebp),%eaxBSD爱好者乐园.? goK \&T!AG P
0x804842d : mov 0xfffffffa(%ebp),%bx
]Z*W/h)me@ _(u I0x8048431 : mov 0xfffffff9(%ebp),%cl
+}-JV)Q:v#zbJ,g0x8048434 : test %eax,%eaxBSD爱好者乐园w'F6f HB#u Dr
0x8048436 : test %bx,%bx
hC BJz8Z0x8048439 : test %cl,%cl
5fBc&T!?*uC8Mi0x804843b : nop
1[yh3|G ^$g0x804843c : nopBSD爱好者乐园 s+@A0I5Y+S9F
0x804843d : nopBSD爱好者乐园Qp vZKN b#t;q)lf
... end: inline asm ...
$WTl]lr.PEnd of assembler dump.
r3i I0x_9A$
!nCX0Atc!]JI^就像你看到的,由内联汇编生成的代码将变量的值放入它们在input域中指定的寄存器中,然后继续BSD爱好者乐园h4~[L@5l
执行当前的代码。编译器自动根据变量的大小来侦测操作数的大小,这样相应的寄存器就被BSD爱好者乐园Y({~i`/A Zp8V
别名%0, %1 和 %2代替了(当使用寄存器别名时在存储器里指定操作数的大小回导致编译时发生错误)BSD爱好者乐园Mt:h0B"LG3T&`
在操作数约束里也可以使用别名。这不允许你在输入/输出域中指定多于10个的入口。我能想到的这样
k\,m3^{.@F!JQs做的唯一用法是在你指定操作数约束为“q”以便让编译器在a,b,c,d寄存器之间进行选择的时候。
~ nMed X[当这个寄存器被修改时,我们不会知道选中了那个寄存器,因而不能在modify域中指定它。BSD爱好者乐园,z6fLr+B"dAu
这种情况下你只需指定""。
8]tJgq例子:
/t D1]#aP$ cat inline3.c
&lG2OUa:aoZ;_#include

)q2o+o wfp]lBSD爱好者乐园5q8yT8G$m+]

int main(void) {
G i.Kr H[~5Wlong eax=1,ebx=2;

2k&_3h S6["{z!GBSD爱好者乐园]"{h0qgm'GlHYr

__asm__ __volatile__ ("add %0,%2"
)VPW TCv#z4u: "=b"((long)ebx)
sj s3u$Z6{I!h: "a"((long)eax), "q"(ebx)BSD爱好者乐园0gI#M Z ]
: "2"
6BV1jAh^"T m);BSD爱好者乐园1v Z%q8|]~$w^
printf("ebx=%x\n", ebx);BSD爱好者乐园^'}6lT5p8B)b R
return 0;BSD爱好者乐园!N s[q:_X0f
}BSD爱好者乐园t8I.bwGAd [+eT


[重要提醒]对本篇资料有疑问,请到论坛讨论,尽量使文章准确无误>>>
[版权声明]BSD爱好者乐园站内文章,如来源不是互联网,则均系原创或翻译之作,可随意转载,或以此为基础进行演译,但务必以链接形式注明原始出处和作者信息,否则属于侵权行为。另对本站转载他处文章,俱有说明,如有侵权请联系本人,本人将会在第一时间删除侵权文章。
TAG: gcc GCC 汇编
 

评分:0

我来说两句

seccode