Lomo's BLOG

行之而不著焉 习矣而不察焉 终身由之而不知其道也 众也 ~
  首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Linux中的汇编(转载)

Posted on 2005-02-26 15:46  Lomo  阅读(663)  评论(0)    收藏  举报

        在linux内核的源代码中,以汇编语言编写的程序或程序段,有两种不同的形式。

第一种事完全的汇编代码,这样的代码采用.s作为文件的后缀。事实上,尽管是完全的汇编代码,现代的汇编工具也吸收了C语言的长处,也在汇编之前加上了一趟预处理,而预处理之前的文件则以.s为后缀。此类(.s)文件也和C程序一样,可以使用#include、#ifdef等等成分,而数据结构也一样可以在.h的文件中加以定义。

第二种是嵌在C程序中的汇编语言片断。虽然在ANSI的C语言标准中并没有关于汇编片段的规定,事实上各种实际使用的C编译中都作了这方面的扩充,而GNU的C编译gcc也在这方面作了很强的扩充。

在DOS/windows领域中,386汇编语言都采用Intel定义的语句格式。可是,在Unix领域中,采用的却是由AT&T定义的格式。

AT&T的汇编与Intel的汇编主要有以下的区别:

  1. 在Intel格式中大多使用大写字母,而在AT&T格式中都使用小写字母。

  2. 在AT&T格式中,寄存器名要加上"%"作为前缀,而在Intel格式中不带前缀。

  3. 在AT&T的386汇编语言中,指令的源操作数的顺序与在Intel的386汇编语言中正好相反。

  4. 在AT&T格式中,访问指令的操作数的宽度有操作码名称的最后一个字母(操作码的后缀决定)。用作操作码后缀的字母有b(8位)。 w(16位)和1(32位)。而在Intel格式中,则是在表示内存单元的操作数前面加上"BYTE PTR""WORD PTR","DWORD PTR"来表示。

  5. 在AT$T格式中,直接操作数要加上"$"作为前缀,而在Intel格式中则不带前缀。

  6. 在AT$T格式中,绝对转移和调用指令jump/call的操作数要加上"*"作为前缀,而在intel格式则不带。

  7. 远程的转移指令和子程序调用指令的操作码名称,在AT$T格式中为"ljump"和"lcall",而在intel格式中,则为"JMP FAR"和"CALL FAR"


  8. 间接寻址的一般格式

一般而言,往C代码中插入汇编语言的代码片要比"纯粹"的汇编语言代码复杂的多,因为这里有个怎样分配使用寄存器,怎样与C代码中的变量结合的问题。为了这个目的,必须对所用的汇编语言作更多的扩充,增加对汇编工具的指导作用。其结果是其语法实际上变成了既不同于汇编语言,也不同于C语言的某种中间语言。

插入C代码的一个汇编语言代码片段可以分为四个部分,以":"号加以分隔,其一般形式为:


  指令部: 输出部:输入部:损坏部



第一部分就是汇编代码本身,其格式和在汇编语言中使用基本相同。这一部分称为"指令部",是必须有的,而其他部分可视具体情况而省略。

当将汇编语言代码片段嵌入到C代码中时,操作数与C代码中的变量如何结合显然是个问题。Gcc采用的策略是:程序员提供具体的指令,而对寄存器的使用则一般只提供一个"样板"和一些约束条件,而把到底如何与变量结合的问题留给了gcc和gas处理。

在指令部中,数字加上前缀%,如%0、%1等等,表示需要使用的寄存器的样板操作数。可以使用的此类操作数的总数取决于具体CPU中通用寄存器的数量。这样,指令部中用到了几个不同的这种操作数,就说明有几个变量需要与寄存器结合,由gcc和gas在编译和汇编时根据后面的约束条件自行变通处理。由于这些样板操作数也使用"%"前缀,在涉及到具体的寄存器时就要在寄存器名前面加上两个"%"符,以免混淆。

紧接在指令部后面的是"输出部",用以规定对输出变量如何结合的约束条件。每个这样的条件称为一个"约束"。必要是输出部可以有多个约束,互相以逗号分隔。每个输出约束以"="号开始,然后是一个字母表示对操作数类型的说明,然后是关于变量结合的约束。凡是与输出部中说明的操作数相结合的寄存器或操作数本身,在执行嵌入的汇编代码后均不保留执行之前的内容,这就给gcc提供了调度这些寄存器的依据。

输出部后面是"输入部"。输入约束的格式和输出约束相似,但不带"="号。如果一个输入约束要求使用寄存器,则在预处理时gcc会为之分配一个寄存器,并自动插入必要的指将操作数即变量的值装入该寄存器。与输入部中说明的操作数结合的寄存器或操作数本身,在执行嵌入汇编代码后也不保留执行之前的内容。

在有些操作中,除用于输入数据操作和输出数的寄存器以外,还要将若干个寄存器用于计算或操作的中间结果。这样,这些寄存器原有的内容就损坏了,所以要在损坏部队操作的副作用加以说明,让gcc采取相应的措施。

操作数的编号从输出部的第一个约束(序号为0)开始,顺序数下来,每个约束记数一次。在指令部中引用这些操作或分配用于这些操作数的寄存器时,就在序号前面加上一个"%"号。在指令部中引用一个操作数时总是把它当作一个32位的"长字",但是对其实施的操作,则根据需要也可以是字节操作或是字操作。对操作数进行的字节操作时也允许明确指出是对哪一个字节的操作,此时在%与序号之间插入一个"b"表示最低字节,插入一个"h"表示次低字节。

表示约束调节的字母有很多:


"m""v""o" 表示内存单元
"r" 表示任何寄存器
"q" 表示寄存器eax,ebx,ecx,edx之一
"i"和"h" 表示直接操作数
"E"和"F" 表示浮点数
"g"表示任意
"a","b","c","d" 分别表示寄存器eax,ebx,ecx,edx
"S"和"D" 分别表示寄存器esi,edi
"I"  表示常数(0至31)



此外,如果一个操作数要求使用与前某个约束中所要求的是同一个寄存器,那就把与那个约束相对应的操作数标号放在约束条件中。在损坏部常常会以"memory"为约束条件,表示操作完成后内存中的内容已有改变,如果原来某个寄存器(也许在本次操作汇总并未使用到)的内容来自内存,则现在科能已经不一致。

还要注意,当输出部为空,即没有输出约束时,如果有输入约束存在,则必须保留分隔标记":"号。

文章来自:http://tech.163.com/tm/030401/030401_88428.html