X86 寻址方式、AT&T 汇编语言相关知识、AT&T 与 Intel 汇编语言的比较、gcc 嵌入式汇编

注:本分类下文章大多整理自《深入分析linux内核源代码》一书,另有参考其他一些资料如《linux内核完全剖析》、《linux c 编程一站式学习》等,只是为了更好地理清系统编程和网络编程中的一些概念性问题,并没有深入地阅读分析源码,我也是草草翻过这本书,请有兴趣的朋友自己参考相关资料。此书出版较早,分析的版本为2.4.16,故出现的一些概念可能跟最新版本内核不同。

此书已经开源,阅读地址 http://www.kerneltravel.net



注解:不同平台有不同的instruction set 即指令集,比如x86, PowerPC, ARM等平台的指令集是不同的。而汇编一直存在两种不同的语法,在intel的官方文档中使用intel语法,Windows也使用intel语法,而UNIX 系统的汇编器一直使用AT&T语法,下文会比较两种语法的区别。

一、X86 寻址方式

x86的通用寄存器有8个。这些寄存器在大多数指令中是可以任意选用的,比如movl 指令可以把一个立即数传送到eax 中,也可传送到ebx 中。但也有一些指令规定只能用其中某个寄存器做某种用途,例如除法指令idivl 要求被除数在eax 寄存器中,edx 寄存器必须是0,而除数可以在任意寄存器中,计算结果的商数保存在eax 寄存器中(覆盖原来的被除数),余数保存在edx 寄存器中。也就是说,通用寄存器对于某些特殊指令来说也不是通用的。

介绍x86常用的几种寻址方式(Addressing Mode)。内存寻址在指令中可以表示成如下的通用格式:
ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER)

它所表示的地址可以这样计算出来:

FINAL ADDRESS = ADDRESS_OR_OFFSET + BASE_OR_OFFSET + MULTIPLIER * INDEX

注:实际上 final address 也只是逻辑地址中的32位偏移量部分,需要使用段选择符找到段描述符,进而得到段基地址,两者相加才是线性地址,但在Linux实现中段基地址都为0,故偏移量可以直接当作线性地址,再经过分页转换就是真正的物理地址,也就是说final address 是程序中访问的地址。具体参见 《80386分段分页机制

其中ADDRESS_OR_OFFSET 和 MULTIPLIER 必须是常数, BASE_OR_OFFSET 和 INDEX 必须是寄存器。
在有些寻址方式中会省略这4项中的某些项,相当于这些项是0。

直接寻址(Direct Addressing Mode)。只使用ADDRESS_OR_OFFSET寻址,例如movl ADDRESS, %eax 把ADDRESS地址处的32位数传送到eax 寄存器。

变址寻址(Indexed Addressing Mode) 。movl data_items(,%edi,4), %eax 就属于这种寻址方式,用于访问数组元素比较方便。

间接寻址(Indirect Addressing Mode)。只使用BASE_OR_OFFSET寻址,例如movl (%eax), %ebx ,把eax 寄存器的值看作地址,把内存中这个地址处的32位数传送到ebx 寄存器。注意和movl %eax, %ebx 区分开。

基址寻址(Base Pointer Addressing Mode)。只使用ADDRESS_OR_OFFSET和BASE_OR_OFFSET寻址,例如movl 4(%eax), %ebx ,用于访问结构体成员比较方便,例如一个结构体的基地址保存在eax 寄存器中,其中一个成员在结构体内的偏移量是4字节,要把这个成员读上来就可以用这条指令。

立即数寻址(Immediate Mode)。就是指令中有一个操作数是立即数,例如movl $12,%eax 中的$12, 这其实跟寻址没什么关系,但也算作一种寻址方式。

寄存器寻址(Register Addressing Mode)。就是指令中有一个操作数是寄存器, 例如movl $12, %eax 中的%eax ,这跟内存寻址没什么关系,但也算作一种寻址方式。在汇编程序中寄存器用助记符来表示,在机器指令中则要用几个Bit表示寄存器的编号,这几个Bit也可以看作寄存器的地址,但是和内存地址不在一个地址空间。

二、AT&T 与 Intel 汇编语言的比较

1.前缀

在Intel 的语法中,寄存器和和立即数都没有前缀。但是在AT&T 中,寄存器前冠以“%”, 而立即数前冠以“$”。在Intel 的语法中,十六进制和二进制立即数后缀分别冠以“h”和 “b”,而在AT&T 中,十六进制立即数前冠以“0x”,如表2.2 所示给出几个相应的例子。

2.操作数的方向

Intel 与AT&T 操作数的方向正好相反。在Intel 语法中,第一个操作数是目的操作数, 第二个操作数是源操作数。而在AT&T 中,第一个数是源操作数,第二个数是目的操作数。由 此可以看出,AT&T 的语法符合人们通常的阅读习惯。
例如:在Intel 中,mov eax,[ecx]
在AT&T 中,movl (%ecx),%eax


3.内存单元操作数

从上面的例子可以看出,内存操作数也有所不同。在Intel 的语法中,基寄存器用“[]” 括起来,而在AT&T 中,用“()”括起来。
例如: 在Intel 中,mov eax,[ebx+5]
在AT&T,movl 5(%ebx),%eax

4.间接寻址方式

与Intel 的语法比较,AT&T 间接寻址方式可能更晦涩难懂一些。Intel 的指令格式是 segreg:[base+index*scale+disp],而AT&T 的格式是%segreg:disp(base,index,scale)。 其中index/scale/disp/segreg 全部是可选的,完全可以简化掉。如果没有指定scale 而指 定了index,则scale 的缺省值为1。segreg 段寄存器依赖于指令以及应用程序是运行在实 模式还是保护模式下,在实模式下,它依赖于指令,而在保护模式下,segreg 是多余的。在 AT&T 中,当立即数用在scale/disp 中时,不应当在其前冠以“$”前缀,表2.3 给出其语法 及几个相应的例子。

从表中可以看出,AT&T 的语法比较晦涩难懂,因为[base+index*scale+disp]一眼就可 以看出其含义,而disp(base,index,scale)则不可能做到这点。 这种寻址方式常常用在访问数据结构数组中某个特定元素内的一个字段,其中,base 为 数组的起始地址,scale 为每个数组元素的大小,index 为下标。如果数组元素还是一个结构, 则disp 为具体字段在结构中的位移。

5.操作码的后缀

在上面的例子中你可能已注意到,在AT&T 的操作码后面有一个后缀,其含义就是指出 操作码的大小。“l”表示长整数(32 位),“w”表示字(16 位),“b”表示字节(8 位)。而 在Intel 的语法中,则要在内存单元操作数的前面加上byte ptr、word ptr 和dword ptr, “dword”对应“long”。表2.4 给出了几个相应的例子。


三、AT&T 汇编语言相关知识

在Linux 源代码中,以.S 为扩展名的文件是“纯”汇编语言的文件。这里,我们结合具 体的例子再介绍一些AT&T 汇编语言的相关知识。

1.GNU 汇编程序GAS(GNU Assembly)和连接程序

当你编写了一个程序后,就需要对其进行汇编(assembly)和连接。在Linux 下有两种 方式,一种是使用汇编程序GAS 和连接程序ld,一种是使用gcc。我们先来看一下GAS 和ld:

GAS 把汇编语言源文件(.o)转换为目标文件(.o),其基本语法如下:
as filename.s -o filename.o
一旦创建了一个目标文件,就需要把它连接并执行,连接一个目标文件的基本语法为:
ld filename.o -o filename
这里 filename.o 是目标文件名,而filename 是输出(可执行) 文件。

GAS 使用的是AT&T 的语法而不是Intel 的语法,这就再次说明了AT&T 语法是UNIX 世界 的标准,你必须熟悉它。 如果要使用GNC 的C 编译器gcc,就可以一步完成汇编和连接,例如:
gcc -o example example.S
这里,example.S 是你的汇编程序,输出文件(可执行文件)名为example。其中,扩 展名必须为大写的S,这是因为,大写的S 可以使gcc 自动识别汇编程序中的C 预处理命令,
  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值