Linux下的汇编语言之GCC 内联汇编

运维 系统运维
汇编语言是基本的语言之一,他的明显优势是速度快,可以直接对硬件进行操作。Linux下如何使用汇编语言,语法格式有哪些?需要使用哪些开发工具?本文将会为大家详细道来。

      作为最基本的编程语言之一,汇编语言虽然应用的范围不算很广,但重要性却勿庸置疑,因为它能够完成许多其它语言所无法完成的功能。就拿 Linux 内核来讲,虽然绝大部分代码是用 C 语言编写的,但仍然不可避免地在某些关键地方使用了汇编代码,其中主要是在 Linux 的启动部分。由于这部分代码与硬件的关系非常密切,即使是 C 语言也会有些力不从心,而汇编语言则能够很好扬长避短,最大限度地发挥硬件的性能。

  GCC 内联汇编

  用汇编编写的程序虽然运行速度快,但开发速度非常慢,效率也很低。如果只是想对关键代码段进行优化,或许更好的办法是将汇编指令嵌入到 C 语言程序中,从而充分利用高级语言和汇编语言各自的特点。但一般来讲,在 C 代码中嵌入汇编语句要比"纯粹"的汇编语言代码复杂得多,因为需要解决如何分配寄存器,以及如何与C代码中的变量相结合等问题。

  GCC 提供了很好的内联汇编支持,最基本的格式是:

  1.   __asm__("asm statements");  

  例如:

  1.   __asm__("nop");  

  如果需要同时执行多条汇编语句,则应该用"\\n\\t"将各个语句分隔开,例如:

  1.   __asm__( "pushl %%eax \\n\\t"  
  2.  
  3.   "movl $0, %%eax \\n\\t"  
  4.  
  5.   "popl %eax");  

  通常嵌入到 C 代码中的汇编语句很难做到与其它部分没有任何关系,因此更多时候需要用到完整的内联汇编格式:

  1.   __asm__("asm statements" : outputs : inputs : registers-modified); 

  插入到 C 代码中的汇编语句是以":"分隔的四个部分,其中第一部分就是汇编代码本身,通常称为指令部,其格式和在汇编语言中使用的格式基本相同。指令部分是必须的,而其它部分则可以根据实际情况而省略。

  在将汇编语句嵌入到C代码中时,操作数如何与C代码中的变量相结合是个很大的问题。GCC采用如下方法来解决这个问题:程序员提供具体的指令,而对寄存器的使用则只需给出"样板"和约束条件就可以了,具体如何将寄存器与变量结合起来完全由GCC和GAS来负责。

  在GCC内联汇编语句的指令部中,加上前缀'%'的数字(如%0,%1)表示的就是需要使用寄存器的"样板"操作数。指令部中使用了几个样板操作数,就表明有几个变量需要与寄存器相结合,这样GCC和GAS在编译和汇编时会根据后面给定的约束条件进行恰当的处理。由于样板操作数也使用'%'作为前缀,因此在涉及到具体的寄存器时,寄存器名前面应该加上两个'%',以免产生混淆。

  紧跟在指令部后面的是输出部,是规定输出变量如何与样板操作数进行结合的条件,每个条件称为一个"约束",必要时可以包含多个约束,相互之间用逗号分隔开就可以了。每个输出约束都以'='号开始,然后紧跟一个对操作数类型进行说明的字后,最后是如何与变量相结合的约束。凡是与输出部中说明的操作数相结合的寄存器或操作数本身,在执行完嵌入的汇编代码后均不保留执行之前的内容,这是GCC在调度寄存器时所使用的依据。

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

  有时在进行某些操作时,除了要用到进行数据输入和输出的寄存器外,还要使用多个寄存器来保存中间计算结果,这样就难免会破坏原有寄存器的内容。在GCC内联汇编格式中的最后一个部分中,可以对将产生副作用的寄存器进行说明,以便GCC能够采用相应的措施。

  下面是一个内联汇编的简单例子:

  例4.内联汇编

  1.   /* inline.c */  
  2.  
  3.   int main()  
  4.  
  5.   {  
  6.  
  7.   int a = 10b = 0;  
  8.  
  9.   __asm__ __volatile__("movl %1, %%eax;\\n\\r"  
  10.  
  11.   "movl %%eax, %0;"  
  12.  
  13.   :"=r"(b) /* 输出 */  
  14.  
  15.   :"r"(a) /* 输入 */  
  16.  
  17.   :"%eax"); /* 不受影响的寄存器 */  
  18.  
  19.   printf("Result: %d, %d\\n", a, b);  
  20.  
  21.   } 

  上面的程序完成将变量a的值赋予变量b,有几点需要说明:

  变量b是输出操作数,通过%0来引用,而变量a是输入操作数,通过%1来引用。

  输入操作数和输出操作数都使用r进行约束,表示将变量a和变量b存储在寄存器中。输入约束和输出约束的不同点在于输出约束多一个约束修饰符'='。

  在内联汇编语句中使用寄存器eax时,寄存器名前应该加两个'%',即%%eax。内联汇编中使用%0、%1等来标识变量,任何只带一个'%'的标识符都看成是操作数,而不是寄存器。

  内联汇编语句的最后一个部分告诉GCC它将改变寄存器eax中的值,GCC在处理时不应使用该寄存器来存储任何其它的值。

  由于变量b被指定成输出操作数,当内联汇编语句执行完毕后,它所保存的值将被更新。

  在内联汇编中用到的操作数从输出部的第一个约束开始编号,序号从0开始,每个约束记数一次,指令部要引用这些操作数时,只需在序号前加上'%'作为前缀就可以了。需要注意的是,内联汇编语句的指令部在引用一个操作数时总是将其作为32位的长字使用,但实际情况可能需要的是字或字节,因此应该在约束中指明正确的限定符:

限定符 意义
"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"

【编辑推荐】

  1. 2.3 用汇编语言编写程序
  2. 术语汇编 基本CSS滤镜概述
  3. 琢石成器—Windows环境下32位汇编语言程序设计
  4. 3.1.3 as86汇编语言程序的编译和链接
  5. 3.1.2 as86汇编语言程序
  6. 3.1.1 as86汇编语言语法
  7. 3.2.6 as汇编命令

 

责任编辑:zhaolei 来源: 网络转载
相关推荐

2011-01-14 14:08:17

Linux汇编语言

2011-01-14 14:15:11

Linux汇编语言

2011-01-14 14:22:50

Linux汇编语言

2011-01-14 13:44:45

Linux汇编语言

2016-09-22 09:37:14

GCC内联语法

2012-02-09 09:00:54

汇编语言

2021-03-25 13:05:56

网络安全寄存器汇编语言

2010-11-09 09:51:52

汇编语言

2021-06-11 10:02:39

语言编程开发

2018-01-11 14:58:40

2013-01-08 11:02:26

IBMdW

2011-01-04 17:08:10

汇编语言

2023-11-23 08:25:40

开发人员SmaliAndroid

2022-10-31 14:02:24

汇编语言神经网络

2017-01-12 22:36:30

2011-07-21 09:59:26

JavaScript

2023-06-01 16:27:34

汇编语言函数

2010-06-04 17:56:22

Linux 常用工具

2010-06-13 15:35:01

2022-03-25 21:57:49

汇编Go语言
点赞
收藏

51CTO技术栈公众号