gcc中的内嵌汇编语言.docx

上传人:b****1 文档编号:13549463 上传时间:2023-06-15 格式:DOCX 页数:22 大小:29.38KB
下载 相关 举报
gcc中的内嵌汇编语言.docx_第1页
第1页 / 共22页
gcc中的内嵌汇编语言.docx_第2页
第2页 / 共22页
gcc中的内嵌汇编语言.docx_第3页
第3页 / 共22页
gcc中的内嵌汇编语言.docx_第4页
第4页 / 共22页
gcc中的内嵌汇编语言.docx_第5页
第5页 / 共22页
gcc中的内嵌汇编语言.docx_第6页
第6页 / 共22页
gcc中的内嵌汇编语言.docx_第7页
第7页 / 共22页
gcc中的内嵌汇编语言.docx_第8页
第8页 / 共22页
gcc中的内嵌汇编语言.docx_第9页
第9页 / 共22页
gcc中的内嵌汇编语言.docx_第10页
第10页 / 共22页
gcc中的内嵌汇编语言.docx_第11页
第11页 / 共22页
gcc中的内嵌汇编语言.docx_第12页
第12页 / 共22页
gcc中的内嵌汇编语言.docx_第13页
第13页 / 共22页
gcc中的内嵌汇编语言.docx_第14页
第14页 / 共22页
gcc中的内嵌汇编语言.docx_第15页
第15页 / 共22页
gcc中的内嵌汇编语言.docx_第16页
第16页 / 共22页
gcc中的内嵌汇编语言.docx_第17页
第17页 / 共22页
gcc中的内嵌汇编语言.docx_第18页
第18页 / 共22页
gcc中的内嵌汇编语言.docx_第19页
第19页 / 共22页
gcc中的内嵌汇编语言.docx_第20页
第20页 / 共22页
亲,该文档总共22页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

gcc中的内嵌汇编语言.docx

《gcc中的内嵌汇编语言.docx》由会员分享,可在线阅读,更多相关《gcc中的内嵌汇编语言.docx(22页珍藏版)》请在冰点文库上搜索。

gcc中的内嵌汇编语言.docx

gcc中的内嵌汇编语言

gcc中的内嵌汇编语言

作者:

欧阳光ouyangguang@

初次接触到AT&T格式的汇编代码,看着那一堆莫名其妙的怪符号,真是有点痛不欲生的感觉,

只好慢慢地去啃gcc文档,在似懂非懂的状态下过了一段时间。

后来又在网上找到了灵溪写的

《gcc中的内嵌汇编语言》一文,读后自感大有裨益。

几个月下来,接触的源代码多了以后,慢慢

有了一些经验。

为了使初次接触AT&T格式的汇编代码的同志不至于遭受我这样的痛苦,就整理出

该文来和大家共享.如有错误之处,欢迎大家指正,共同提高.

本文主要以举例的方式对gcc中的内嵌汇编语言进行进一步的解释。

一、gcc对内嵌汇编语言的处理方式

gcc在编译内嵌汇编语言时,采取的步骤如下

1.变量输入:

根据限定符的内容将输入操作数放入合适的寄存器,如果限定符指定为立即

数("I")或内存变量("m"),则该步被省略,如果限定符没有具体指定输入操作数的类型(如

常用的"g"),gcc会视需要决定是否将该操作数输入到某个寄存器.这样每个占位符都与某

个寄存器,内存变量,或立即数形成了一一对应的关系.这就是对第二个冒号后内容的解释.

如:

:

"a"(foo),"I"(100),"m"(bar)表示%0对应eax寄存器,%1对应100,%2对应内存变量

bar.

2.生成代码:

然后根据这种一一对应的关系(还应包括输出操作符),用这些寄存器,内存变

量,或立即数来取代汇编代码中的占位符(则有点像宏操作),注意,则一步骤并不检查由这

种取代操作所生成的汇编代码是否合法,例如,如果有这样一条指令asm("movl

%0,%1":

:

"m"(foo),"m"(bar));如果你用gcc-c-S选项编译该源文件,那么在生成的汇

编文件中,你将会看到生成了movlfoo,bar这样一条指令,这显然是错误的.这个错误在稍

后的编译检查中会被发现.

3.变量输出:

按照输出限定符的指定将寄存器的内容输出到某个内存变量中,如果输出操

作数的限定符指定为内存变量("m"),则该步骤被省略.这就是对第一个冒号后内容的解

释,如:

asm("mov%0,%1":

"=m"(foo),"=a"(bar):

);编译后为

#APP

movlfoo,eax

#NO_APP

movleax,bar

该语句虽然有点怪怪的,但它很好的体现了gcc的运作方式.          

再以arch/i386/kernel/apm.c中的一段代码为例,我们来比较一下它们编译前后的情况

源程序

编译后的汇编代码

__asm__(

"pushl%%edi\n\t"

"pushl%%ebp\n\t"

"lcall%%cs:

\n\t"

"setc%%al\n\t"

"addl%1,%2\n\t"

"popl%%ebp\n\t"

"popl%%edi\n\t"

:

"=a"(ea),"=b"(eb),

"=c"(ec),"=d"(ed),"=S"(es)

:

"a"(eax_in),"b"(ebx_in),"c"(ecx_in)

:

"memory","cc");

movleax_in,%eax

movlebx_in,%ebx

movlecx_in,%ecx

#APP

pushl%edi

pushl%ebp

lcall%cs:

setc%al

addleb,ec

popl%ebp

popl%edi

#NO_APP

movl%eax,ea

movl%ebx,eb

movl%ecx,ec

movl%edx,ed

movl%esi,es

二.对第三个冒号后面内容的解释

第三个冒号后面内容主要针对gcc优化处理,它告诉gcc在本段汇编代码中对寄存器和内存的

使用情况,以免gcc在优化处理时产生错误.

1.它可以是"eax","ebx","ecx"等寄存器名,表示本段汇编代码对该寄存器进行了显式操作,

如asm("mov%%eax,%0",:

"=r"(foo):

:

"eax");这样gcc在优化时会避免使用eax作临

时变量,或者避免cache到eax的内存变量通过该段汇编码.

下面的代码均用gcc的-O2级优化,它显示了嵌入汇编中第三个冒号后"eax"的作用

 

源程序

编译后的汇编代码

intmain()

{intbar=1;

bar=fun();

bar++;

returnbar;

}

pushl%ebp

movl%esp,%ebp

callfun

incl%eax#显然,bar缺省使用eax寄存器

leave

ret

intmain()

{intbar=1;

bar=fun();

asmvolatile("":

:

:

"eax");

bar++;

returnbar;

}

pushl%ebp

movl%esp,%ebp#建立堆栈框架

callfun

#fun的返回值放入bar中,此时由于嵌入汇编

#指明改变了eax的值,为了避免冲突,

#bar改为使用edx寄存器

movl%eax,%edx

#APP

#NO_APP

incl%edx

movl%edx,%eax#放入main()的返回值

leave

ret

2."merory"是一个常用的限定,它表示汇编代码以不可预知的方式改变了内存,这样gcc在优

化时就不会让cache到寄存器的内存变量使用该寄存器通过汇编代码,否则可能会发生同

步出错.有了上面的例子,这个问题就很好理解了

三.对"&"限定符的解释

这是一个较常见用于输出的限定符.它告诉gcc输出操作数使用的寄存器不可再让输入操作数

使用.

对于"g","r"等限定符,为了有效利用为数不多的几个通用寄存器,gcc一般会让输入操作数

和输出操作数选用同一个寄存器.但如果代码没编好,会引起一些意想不到的错误:

asm("callfun;movebx,%1":

"=a"(foo):

"r"(bar));gcc编译的结果是foo和bar同时使用

eax寄存器:

movlbar,eax

#APP

callfun

movlebx,eax

#NO_APP

movleax,foo

本来这段代码的意图是将fun()函数的返回值放入foo变量,但半路杀出个程咬金,用ebx的值冲

掉了返回值,所以这是一段错误的代码,解决的方法是加上一个给输出操作数加上一个"&"限定

符:

asm("callfun;movebx,%1":

"=&a"(foo):

"r"(bar));这样gcc就会让输入操作数另寻高

就,不再使用eax寄存器了

GCC内嵌汇编简介

发表:

2004-4-30:

16:

23出处:

你的博客网(yourblog.org)

在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可,GCC会自动插入代码完成必要的操作。

1、简单的内嵌汇编

例:

      __asm____volatile__("hlt");“__asm__”表示后面的代码为内嵌汇编,“asm”是“__asm__”的别名。

“__volatile__”表示编译器不要优化代码,后面的指令保留原样,“volatile”是它的别名。

括号里面是汇编指令。

2、内嵌汇编举例

  使用内嵌汇编,要先编写汇编指令模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC对这些操作有哪些限制条件。

例如在下面的汇编语句:

    

__asm____violate__("movl%1,%0":

"=r"(result):

"m"(input));

“movl%1,%0”是指令模板;“%0”和“%1”代表指令的操作数,称为占位符,内嵌汇编靠它们将C语言表达式与指令操作数相对应。

指令模板后面用小括号括起来的是C语言表达式,本例中只有两个:

“result”和“input”,他们按照出现的顺序分别与指令操作数“%0”,“%1”对应;注意对应顺序:

第一个C表达式对应“%0”;第二个表达式对应“%1”,依次类推,操作数至多有10个,分别用“%0”,“%1”….“%9”表示。

在每个操作数前面有一个用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。

“result”前面的限制字符串是“=r”,其中“=”表示“result”是输出操作数,“r”表示需要将“result”与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而不是“result”本身,当然指令执行完后需要将寄存器中的值存入变量“result”,从表面上看好像是指令直接对“result”进行操作,实际上GCC做了隐式处理,这样我们可以少写一些指令。

“input”前面的“r”表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。

  C表达式或者变量与寄存器的关系由GCC自动处理,我们只需使用限制字符串指导GCC如何处理即可。

限制字符必须与指令对操作数的要求相匹配,否则产生的汇编代码将会有错,读者可以将上例中的两个“r”,都改为“m”(m表示操作数放在内存,而不是寄存器中),编译后得到的结果是:

            movlinput,result

很明显这是一条非法指令,因此限制字符串必须与指令对操作数的要求匹配。

例如指令movl允许寄存器到寄存器,立即数到寄存器等,但是不允许内存到内存的操作,因此两个操作数不能同时使用“m”作为限定字符。

  

GCC内嵌汇编2

出处:

http:

//www.yourblog.org/Data/20044/35012.html

内嵌汇编语法如下:

__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(intnr,volatilevoid*addr)

{

__asm__(

"btsl%1,%0"

:

"=m"(ADDR)

:

"Ir"(nr));

}

后例功能是将(*addr)的第nr位设为1。

第一个占位符%0与C语言变量ADDR对应,第二个占位符%1与C语言变量nr对应。

因此上面的汇编语句代码与下面的伪代码等价:

btslnr,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位的寄存器(uselonglongs)

内存“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”等。

AT&T与INTEL的汇编语言语法的区别

出处:

http:

//www.yourblog.org/Data/20044/34973.html

1、大小写

INTEL格式的指令使用大写字母,而AT&T格式的使用小写字母。

例:

INTELAT&T

MOVEAX,EBXmovl%ebx,%eax

2、操作数赋值方向

在INTEL语法中,第一个表示目的操作数,第二个表示源操作数,赋值方向从右向左。

AT&T语法第一个为源操作数,第二个为目的操作数,方向从左到右,合乎自然。

例:

INTELAT&T

MOVEAX,EBXmovl%ebx,%eax

3、前缀

在INTEL语法中寄存器和立即数不需要前缀;AT&T中寄存器需要加前缀“%”;立即数

需要加前缀“$”。

例:

INTELAT&T

MOVEAX,1movl$1,%eax

符号常数直接引用,不需要加前缀,如:

movlvalue,%ebx,value为一常数;在符

号前加前缀$表示引用符号地址,如movl$value,%ebx,是将value的地址放到ebx中。

总线锁定前缀“lock”:

总线锁定操作。

“lock”前缀在Linux核心代码中使用很多,特

别是SMP代码中。

当总线锁定后其它CPU不能存取锁定地址处的内存单元。

远程跳转指令和子过程调用指令的操作码使用前缀“l“,分别为ljmp,lcall,与之

相应的返回指令伪lret。

例:

INTELAT&T

CALLFARSECTION:

OFFSETlcall$secion:

$offset

JMPFARSECTION:

OFFSETljmp$secion:

$offset

RETFARSATCK_ADJUSTlret$stack_adjust

4、间接寻址语法

INTEL中基地址使用“[”、“]”,而在AT&T中使用“(”、“)”;另外处理复杂操作数的

语法也不同,INTEL为Segreg:

[base+index*scale+disp],而在AT&T中为

%segreg:

disp(base,index,sale),其中segreg,index,scale,disp都是可选的,在指定

index而没有显式指定Scale的情况下使用默认值1。

Scale和disp不需要加前缀“&”。

INTELAT&T

Instrinstr

foo,segreg:

[base+index*scale+disp]%segreg:

disp(base,index,scale),foo

5、后缀

AT&T语法中大部分指令操作码的最后一个字母表示操作数大小,“b”表示byte(一个

字节);“w”表示word(2个字节);“l”表示long(4个字节)。

INTEL中处理内存操作数

时也有类似的语法如:

BYTEPTR、WORDPTR、DWORDPTR。

例:

INTELAT&T

moval,blmovb%bl,%al

movax,bxmovw%bx,%ax

moveax,dwordptr[ebx]movl(%ebx),%eax

在AT&T汇编指令中,操作数扩展指令有两个后缀,一个指定源操作数的字长,另一个

指定目标操作数的字长。

AT&T的符号扩展指令的为“movs”,零扩展指令为“movz”(相应

的Intel指令为“movsx”和“movzx”)。

因此,“movsbl%al,%edx”表示对寄存器al中的

字节数据进行字节到长字的符号扩展,计算结果存放在寄存器edx中。

下面是一些允许的操

作数扩展后缀:

bl:

字节->长字

bw:

字节->字

wl:

字->长字

跳转指令标号后的后缀表示跳转方向,“f”表示向前(forward),“b”表示向后(back)。

例:

jmp1f

1:

jmp1f

1:

6、指令

INTEL汇编与AT&T汇编指令基本相同,差别仅在语法上。

关于每条指令的语法可以参考I386Manual。

gcc中的内嵌汇编语言

出处:

gcc采用的是AT&T的汇编格式,MS采用Intel的格式.

一 基本语法

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

★寄存器命名原则

AT&T:

%eaxIntel:

eax

★源/目的操作数顺序

AT&T:

movl%eax,%ebxIntel:

movebx,eax

★常数/立即数的格式

AT&T:

movl$_value,%ebxIntel:

moveax,_value

把_value的地址放入eax寄存器

AT&T:

movl$0xd00d,%ebxIntel:

movebx,0xd00d

★操作数长度标识

AT&T:

movw%ax,%bxIntel:

movbx,ax

★寻址方式

AT&T:

immed32(basepointer,indexpointer,indexscale)

Intel:

[basepointer+indexpointer*indexscale+imm32)

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

不用考虑segment

ffset的问题.上式中的地址应为:

imm32+basepointer+indexpointer*indexscale

下面是一些例子:

★直接寻址

AT&T:

_booga ;_booga是一个全局的C变量

注意加上$是表示地址引用,不加是表示值引用.

注:

对于局部变量,可以通过堆栈指针引用.

Intel:

[_booga]

★寄存器间接寻址

AT&T:

(%eax)

Intel:

[eax]

★变址寻址

AT&T:

_variable(%eax)

Intel:

[eax+_variable]

AT&T:

_array(,%eax,4)

Intel:

[eax*4+_array]

AT&T:

_array(%ebx,%eax,8)

Intel:

[ebx+eax*8+_array]

二 基本的行内汇编

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

asm("statements");

例如:

asm("nop");asm("cli");

asm 和 __asm__是完全一样的.

如果有多行汇编,则每一行都要加上 "nt"

例如:

asm("pushl%eaxnt"

"movl$0,%eaxnt"

"popl%eax");

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

文件中,所以格式控制字符是必要的.

再例如:

asm("movl%eax,%ebx");

asm("xorl%ebx,%edx");

asm("movl$0,_booga);

在上面的例子中,由于我们在行内汇编中改变了edx和ebx的值,但是

由于gcc的特殊的处理方法,即先形成汇

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 临时分类 > 批量上传

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2