gcc中的内嵌汇编语言(Intel i386平台)
【转】http://bbs.chinaunix.net/thread-2149855-1-1.html
一.声明
虽然Linux的核心代码大部分是用C语言编写的,但是不可避免的其中还是有一部分是用汇编语言写成的。有些汇编语言代码是直接写在汇编源程序中的,特别是Linux的启动代码部分;还有一些则是利用gcc的内嵌汇编语言嵌在C语言程序中的。这篇文章简单介绍了gcc中的内嵌式汇编语言,主要想帮助那些才开始阅读Linux核心代码的朋友们能够更快的入手。
写这篇文章的主要信息来源是GNU的两个info文件:as.info和gcc.info,如果你觉得这篇文章中的介绍还不够详细的话,你可以查阅这两个文件。当然,直接查阅这两个文件可以获得更加权威的信息。如果你不想被这两篇文档中的一大堆信息搞迷糊的话,我建议你先阅读一下这篇文章,然后在必要时再去查阅更权威的信息。
二.简介
在Linux的核心代码中,还是存在相当一部分的汇编语言代码。如果你想顺利阅读Linux代码的话,你不可能绕过这一部分代码。在Linux使用的汇编语言代码中,主要有两种格式:一种是直接写成汇编语言源程序的形式,这一部分主要是一些Linux的启动代码;另一部分则是利用gcc的内嵌式汇编语言语句asm嵌在Linux的C语言代码中的。这篇文章主要是介绍第二种形式的汇编语言代码。
首先,我介绍一下as支持的汇编语言的语法格式。大家知道,我们现在学习的汇编语言的格式主要是Intel风格的,而在Linux的核心代码中使用的则是AT&T格式的汇编语言代码,应该说大部分人对这种格式的汇编语言还不是很了解,所以我觉得有必要介绍一下。
接着,我主要介绍一下gcc的内嵌式汇编语言的格式。gcc的内嵌式汇编语言提供了一种在C语言源程序中直接嵌入汇编指令的很好的办法,既能够直接控制所形成的指令序列,又有着与C语言的良好接口,所以在Linux代码中很多地方都使用了这一语句。
三.AT&T的汇编语言语法格式
我想我们大部分人对Intel格式的汇编语言都很了解了。但是,在Linux核心代码中,所有的汇编语言指令都是用AT&T格式的汇编语言书写的。这两种汇编语言在语法格式上有着很大的不同:
1.在AT&T的汇编语言中,用'$'前缀表示一个立即操作数;而在Intel的格式中,立即操作数的表示不带任何前缀符。例如:下面两个语句是完全相同的:
*AT&T: pushl $4
*Intel: push 4
2.AT&T和Intel的汇编语言格式中,源操作数和目标操作数的位置正好相反。Intel的汇编语言中,目标操作数在源操作数的左边;而在AT&T的汇编语言中,目标操作数则在源操作数的右边。例如:
*AT&T : addl $4,陎
*Intel: add eax,4
3.在AT&T的汇编语言中,操作数的字长是由操作码助记符的最后一个字母决定的,后缀'b'、'w'、'l'分别表示操作数的字长为8比特(字节,byte),16比特(字,word)和32比特(长字,long),而Intel格式中操作数的字长是用“word ptr”或者“byte ptr”等前缀来表示的。例如:
*AT&T: movb FOO,al
*Intel: mov al,byte ptr FOO
4.在AT&T汇编指令中,直接远跳转/调用的指令格式是“lcall/ljmp
$SECTION,$OFFSET”,同样,远程返回的指令是“lret
$STACK-ADJUST”;而在Intel格式中,相应的指令分别为“call/jmp
far SECTION:OFFSET”和“ret far STACK-ADJUST”。
①AT&T汇编指令操作助记符命名规则
AT&T汇编语言中,操作码助记符的后缀字符指定了该指令中操作数的字长。后缀字母'b'、'w'、'l'分别表示字长为8比特(字节,byte),16比特(字,word)和32比特(长字,long)的操作数。如果助记符中没有指定字长后缀并且该指令中没有内存操作数,汇编程序'as'会根据指令中指定的寄存器操作数补上相应的后缀字符。所以,下面的两个指令具有相同的效果(这只是GNU的汇编程序as的一个特性,AT&T的Unix汇编程序将没有字长后缀的指令的操作数字长假设为32比特):
mov ax,bx
movw ax,bx
AT&T中几乎所有的操作助记符与Intel格式中的助记符同名,仅有一小部分例外。操作数扩展指令就是例外之一。在AT&T汇编指令中,操作数扩展指令有两个后缀:一个指定源操作数的字长,另一个指定目标操作数的字长。AT&T的符号扩展指令的基本助记符为'movs',零扩展指令的基本助记符为'movz'(相应的Intel指令为'movsx'和'movzx')。因此,'movsbl al,韝'表示对寄存器al中的字节数据进行字节到长字的符号扩展,计算结果存放在寄存器edx中。下面是一些允许的操作数扩展后缀:
*bl: 字节->长字
*bw: 字节->字
*wl: 字->长字
还有一些其他的类型转换指令的对应关系:
*Intel *AT&T
⑴ cbw cbtw
符号扩展:al->ax
⑵ cwde cwtl
符号扩展:ax->eax
⑶ cwd cwtd
符号扩展:ax->dx:ax
⑷ cdq cltd
符号扩展:eax->edx:eax
还有一个不同名的助记符就是远程跳转/调用指令。在Intel格式中,远程跳转/调用指令的助记符为“call/jmp far”,而在AT&T的汇编语言中,相应的指令为“lcall”和“ljmp”。
②AT&T中寄存器的命名
在AT&T汇编语言中,寄存器操作数总是以''作为前缀。80386芯片的
寄存器包括:
⑴8个32位寄存器:'陎','離','靫','韝','韎','esi',
'雙','esp'
⑵8个16位寄存器:'ax','bx','cx','dx','si','di','bp',
'sp'
⑶8个8位寄存器:'ah','al','bh','bl','ch','cl','dh',
'dl'
⑷6个段寄存器:'cs','ds','es','ss','fs','gs'
⑸3个控制寄存器:'cr0','cr1','cr2'
⑹6个调试寄存器:'?','?','?','?','?','?'
⑺2个测试寄存器:'tr6','tr7'
⑻8个浮点寄存器栈:'st(0)','st(1)','st(2)','st(3)',
'st(4)','st(5)','st(6)','st(7)'
*注:我对这些寄存器并不是都了解,这些资料只是摘自as.info文档。
如果真的需要寄存器命名的资料,我想可以参考一下相应GNU工具的机器描述方面的源文件。
③AT&T中的操作码前缀
⑴段超越前缀'cs','ds','es','ss','fs','gs':当汇编程序中对内存操作数进行SECTION:MEMORY-OPERAND引用时,自动加上相应的段超越前缀。
⑵操作数/地址尺寸前缀'data16','addr16':这些前缀将32位的操作数/地址转化为16位的操作数/地址。
⑶总线锁定前缀'lock':总线锁定操作。'lock'前缀在Linux核心代码中使用很多,特别是SMP代码中。
⑷协处理器等待前缀'wait':等待协处理器完成当前操作。
⑸指令重复前缀'rep','repe','repne':在串操作中重复指令的执行。
④AT&T中的内存操作数
在Intel的汇编语言中,内存操作数引用的格式如下:
SECTION:[BASE INDEX*SCALE DISP]
而在AT&T的汇编语言中,内存操作数的应用格式则是这样的:
SECTIONISP(BASE,INDEX,SCALE)
下面是一些内存操作数的例子:
*AT&T *Intel
⑴ -4(雙) [ebp-4]
⑵ foo(,陎,4) [foo eax*4]
⑶ foo(,1) [foo]
⑷ gs:foo gs:foo
还有,绝对跳转/调用指令中的内存操作数必须以'*'最为前缀,否则as总是假设这是一个相对跳转/调用指令。
⑤AT&T中的跳转指令
as汇编程序自动对跳转指令进行优化,总是使用尽可能小的跳转偏移量。如果8比特的偏移量无法满足要求的话,as会使用一个32位的偏移量,as汇编程序暂时还不支持16位的跳转偏移量,所以对跳转指令使用'addr16'前缀是无效的。
还有一些跳转指令只支持8位的跳转偏移量,这些指令包括:'jcxz', 'jecxz','loop','loopz','loope','loopnz'和'loopne'。所以,在as的汇编源程序中使用这些指令可能会出错。(幸运的是,gcc并不使用这些指令)
对AT&T汇编语言语法的简单介绍差不多了,其中有些特性是as特有的。在Linux核心代码中,并不涉及到所有上面这些提到的语法规则,其中有两点规则特别重要:第一,as中对寄存器引用时使用前缀'';第二,AT&T汇编语言中源操作数和目标操作数的位置与我们熟悉的Intel的语法正好相反。
四.gcc的内嵌汇编语言语句asm
利用gcc的asm语句,你可以在C语言代码中直接嵌入汇编语言指令,同时还可以使用C语言的表达式指定汇编指令所用到的操作数。这一特性提供了很大的方便。
要使用这一特性,首先要写一个汇编指令的模板(这种模板有点类似于机器描述文件中的指令模板),然后要为每一个操作数指定一个限定字符串。例如:
extern __inline__ void change_bit(int nr,volatile void *addr)
{
__asm__ __volatile__( LOCK_PREFIX
"btcl 1,0"
:"=m" (ADDR)
:"ir" (nr));
}
上面的函数中:
LOCK_PREFIX:这是一个宏,如果定义了__SMP__,扩展为"lock;",用于指定总线锁定前缀,否则扩展为""。
ADDR:这也是一个宏,定义为(*(volatile struct __dummy *) addr)
"btcl 1,0":这就是嵌入的汇编语言指令,btcl为指令操作码,1,0是这条指令两个操作数的占位符。后面的两个限定字符串就用于描述这两个操作数。
: "=m" (ADDR):第一个冒号后的限定字符串用于描述指令中的“输出”操作数。刮号中的ADDR将操作数与C语言的变量联系起来。这个限定字符串表示指令中的“0”就是addr指针指向的内存操作数。这是一个“输出”类型的内存操作数。
: "ir" (nr):第二个冒号后的限定字符串用于描述指令中的“输入”操作数。这条限定字符串表示指令中的“1”就是变量nr,这个的操作数可以是一个立即操作数或者是一个寄存器操作数。
*注:限定字符串与操作数占位符之间的对应关系是这样的:在所有限定字符串中(包括第一个冒号后的以及第二个冒号后的所有限定字符串),最先出现的字符串用于描述操作数“0”,第二个出现的字符串描述操作数“1”,以此类推。
①汇编指令模板
asm语句中的汇编指令模板主要由汇编指令序列和限定字符串组成。
在一个asm语句中可以包括多条汇编指令。汇编指令序列中使用操作数占位符引用C语言中的变量。一条asm语句中最多可以包含十个操作数占位符:0,1,...,9。汇编指令序列后面是操作数限定字符串,对指令序列中的占位符进行限定。限定的内容包括:该占位符与哪个C语言变量对应,可以是什么类型的操作数等等。限定字符串可以分为三个部分:输出操作数限定字符串(指令序列后第一个冒号后的限定字符串),输入操作数限定字符串(第一个冒号与第二个冒号之间),还有第三种类型的限定字符串在第二个冒号之后。同一种类型的限定字符串之间用逗号间隔。asm语句中出现的第一个限定字符串用于描述占位符“0”,第二个用于描述占位符“1”,以此类推(不管该限定字符串的类型)。如果指令序列中没有任何输出操作数,那么在语句中出现的第一个限定字符串(该字符串用于描述输入操作数)之前应该有两个冒号(这样,编译器就知道指令中没有输出操作数)。
指令中的输出操作数对应的C语言变量应该具有左值类型,当然对于输出操作数没有这种左值限制。
输出操作数必须是只写的,也就是说,asm对取出某个操作数,执行一定计算以后再将结果存回该操作数这种类型的汇编指令的支持不是直接的,而必须通过特定的格式的说明。如果汇编指令中包含了一个输入-输出类型的操作数,那么在模板中必须用两个占位符对该操作数的不同功能进行引用:一个负责输入,另一个负责输出。
例如:
asm ("addl 2,0":"=r"(foo):"0"(foo),"g"(bar));
在上面这条指令中,“0”是一个输入-输出类型的操作数,"=r"(foo)用于限定其输出功能,该指令的输出结果会存放到C语言变量foo中;指令中没有显式的出现“1”操作数,但是针对它有一个限定字符串"0"(foo),事实上指令中隐式的“1”操作数用于描述“0”操作数的输入功能,它的限定字符串中的"0"限定了“1”操作数与“0”具有相同的地址。可以这样理解上述指令中的模板:该指令将“1”和“2”中的值相加,计算结果存放回“0”中,指令中的“1”与“0”具有相同的地址。注意,用于描述“1”的"0"限定字符足以保证“1”与“0”具有相同的地址。但是,如果用下面的指令完成这种输入-输出操作就不会正常工作:
asm ("addl 2,0":"=r"(foo):"r"(foo),"g"(bar));
虽然该指令中“0”和“1”同样引用了C语言变量foo,但是gcc并不保证在生成的汇编程序中它们具有相同的地址。
还有一些汇编指令可能会改变某些寄存器的值,相应的汇编指令模板中必须将这种情况通知编译器。所以在模板中还有第三种类型的限定字符串,它们跟在输入操作数限定字符串的后面,之间用冒号间隔。这些字符串是某些寄存器的名称,代表该指令会改变这些寄存器中的内容。
在内嵌的汇编指令中可能会直接引用某些硬件寄存器,我们已经知道AT&T格式的汇编语言中,寄存器名以“”作为前缀,为了在生成的汇编程序中保留这个“”号,在asm语句中对硬件寄存器的引用必须用“”作为寄存器名称的前缀。如果汇编指令改变了硬件寄存器的内容,不要忘记通知编译器(在第三种类型的限定串中添加相应的字符串)。还有一些指令可能会改变CPU标志寄存器EFLAG的内容,那么需要在第三种类型的限定字符串中加入"cc"。
为了防止gcc在优化过程中对asm中的汇编指令进行改变,可以在"asm"关键字后加上"volatile"修饰符。
可以在一条asm语句中描述多条汇编语言指令;各条汇编指令之间用“;”或者“\n”隔开。
②操作数限定字符
操作数限定字符串中利用规定的限定字符来描述相应的操作数,一些常用的限定字符有:(还有一些没有涉及的限定字符,参见gcc.info)
1。"m":操作数是内存变量。
2。"o":操作数是内存变量,但它的寻址方式必须是“偏移量”类型的,也就是基址寻址或者基址加变址寻址。
3。"V":操作数是内存变量,其寻址方式非“偏移量”类型。
4。" ":操作数是内存变量,其地址自动增量。
6。"r":操作数是通用寄存器。
7。"i":操作数是立即操作数。(其值可在汇编时确定)
8。"n":操作数是立即操作数。有些系统不支持除字(双字节)以外的立即操作数,这些操作数要用"n"而不是"i"来描述。
9。"g":操作数可以是立即数,内存变量或者寄存器,只要寄存器属于通用寄存器。
10。"X":操作数允许是任何类型。
11。"0","1",...,"9":操作数与某个指定的操作数匹配。也就是说,该操作数就是指定的那个操作数。例如,如果用"0"来描述"1"操作数,那么"1"引用的其实就是"0"操作数。
12。"p":操作数是一个合法的内存地址(指针)。
13。"=":操作数在指令中是只写的(输出操作数)。
14。" ":操作数在指令中是读-写类型的(输入-输出操作数)。
15。"a":寄存器EAX。
16。"b":寄存器EBX。
17。"c":寄存器ECX。
18。"d":寄存器EDX。
19。"q":寄存器"a","b","c"或者"d"。
20。"A":寄存器"a"或者"d"。
21。"a":寄存器EAX。
22。"f":浮点数寄存器。
23。"t":第一个浮点数寄存器。
24。"u":第二个浮点数寄存器。
25。"D":寄存器di。
26。"S":寄存器si。
27。"I":0-31之间的立即数。(用于32位的移位指令)
28。"J":0-63之间的立即数。(用于64位的移位指令)
29。"N":0-255之间的立即数。(用于"out"指令)
30。"G":标准的80387浮点常数。
*注:还有一些不常见的限定字符并没有在此说明,另外有一些限定字符,例如"","&"等由于我缺乏编译器方面的一些知识,所以我也不是很理解它们的含义,如果有高手愿意补充,不慎感激!不过在核心代码中出现的限定字符差不多就是上面这些了。
gcc中的内嵌汇编语言(Intel i386平台)的更多相关文章
- GCC中的内嵌汇编语言
原文可参考:GCC中的内嵌汇编语言 一.声明 虽然Linux的核心代码大部分是用C语言编写的,但是不可避免的其中还是有一部分是用汇编语言写成的.有些汇编语言代码是直接写在汇编源程序中的,特别是Li ...
- Delphi内嵌汇编语言BASM精要(转帖)
1 BASM概念简要 汇编语句由指令和零至三个表达式构成.表达式由常数(立即数).寄存器和标识符构成.例如: movsb // 单指令语句 jmp @Here // 一个表达式: ...
- colormap中的内嵌彩色模块和调用方式
内嵌彩色模块代码: import numpy as npimport matplotlib.pyplot as plt # Have colormaps separated into categori ...
- Android程序中,内嵌ELF可执行文件-- Android开发C语言混合编程总结
前言 都知道的,Android基于Linux系统,然后覆盖了一层由Java虚拟机为核心的壳系统.跟一般常见的Linux+Java系统不同的,是其中有对硬件驱动进行支持,以避开GPL开源协议限制的HAL ...
- PluginOK中间件高级版-支持在Chrome、Edge、Firefox等浏览器网页中真正内嵌ActiveX等控件运行的版本已获多家上市公司采购
PluginOK(牛插)中间件(原名:本网通WebRunLocal)是一个实现WEB浏览器(Web Browser)与本地程序(Local Application)之间进行双向调用的低成本.强兼容.安 ...
- 小程序中webview内嵌h5页面
小程序内嵌h5页面跳转小程序指定页面, 需要引用 JSSDK: <script src="https://res.wx.qq.com/open/js/jweixin-1.3.2 ...
- 【spring实战第五版遇到的坑】4.2.3中LDAP内嵌服务器不启动的问题
按照4.2.3中的指导一步一步的去做,在登录界面进行登录时,报错了,报错信息是LDAP服务器连接不上. 后来查了一些资源发现还需要加入一些其他的依赖,如下: <dependency> &l ...
- Markdown 代码块中再内嵌一个行内代码
在 jQuery 1.9 之前(不含1.9):如果传入一个空字符串. null 或 jQuery.parseJSON( jsonString ) ,该函数将返回,而不是抛出一个错误,即使它不是有效的 ...
- linux内嵌汇编语言
http://blog.chinaunix.net/uid-21254310-id-1828921.html http://www.cnblogs.com/lxgeek/archive/2011/01 ...
随机推荐
- 使用Gulp实现网页自动刷新:gulp-connect
入门指南 1. 全局安装 gulp: npm install --global gulp 2. 作为项目的开发依赖(devDependencies)安装: npm install --save-dev ...
- 第23天:js-数据类型转换
一.padding1.内边距会影响盒子大小2.行内元素,尽量不用上下的padding和margin3.块元素嵌套块元素.子级会继承父级的宽度,高度由内容决定.如果给子级再设置padding,不会影响盒 ...
- 【.Net】在C#中判断某个类是否实现了某个接口
有时我们需要判断某个类是否实现了某个接口(Interface),比如在使用反射机制(Reflection)来查找特定类型的时候. 简单来说,可以使用Type.IsAssignableFrom方法: t ...
- 实用图像处理入门 - 1 - opencv VS2012 环境搭建
标签中的部分 font-family: 华文细黑; font-size: 26px; font-weight: bold; color: #611427; margin-top:40px; } h2 ...
- [HNOI2007][BZOJ1185] 最小矩形覆盖 [凸包+旋转卡壳]
题面 BZOJ题面 前置芝士 建议先学习向量相关的计算几何基础 计算几何基础戳这里 思路 用这道题学习一下凸包和旋转卡壳 首先是凸包部分 凸包 求凸包用的算法是graham算法 算法流程如下: 找到$ ...
- BZOJ2653:middle——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=2653 Description 一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2], ...
- 洛谷 P2024 [NOI2001]食物链 解题报告
P2024 [NOI2001]食物链 题目描述 动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形.A 吃 B,B 吃 C,C 吃 A. 现有 N 个动物,以 1 - N 编号.每个 ...
- 中国MOOC_面向对象程序设计——Java语言_第2周 对象交互_秒计时的数字时钟
第2周编程题 查看帮助 返回 第2周编程题,在课程所给的时钟程序的基础上修改 依照学术诚信条款,我保证此作业是本人独立完成的. 温馨提示: 1.本次作业属于Online Judge题目,提交后由系 ...
- Network LCA修改点权
Problem Description The ALPC company is now working on his own network system, which is connecting a ...
- WPF 与设备无关的单位
WPF从发布之日起,一直将“分辨率无关(resolution independence)”作为其亮点,声称使用WPF制作的用户界面在轻巧的Ultra-Mobile PC的屏幕上和在50英寸的电视机上都 ...