GCC 内联汇编 HOWTO

v0.1, 01 March 2003.


本 HOWTO 文档将讲解 GCC 提供的内联汇编特性的用途和用法。对于阅读这篇文章,这里只有两个前提要求,很明显,就是 x86 汇编语言和 C 语言的基本认识。


原文链接与说明

  1. http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html
  2. 本翻译文档原文选题自 Linux中国 ,翻译文档版权归属 Linux中国 所有

1. 简介

1.1 版权许可

Copyright (C)2003 Sandeep S.

本文档自由共享;你可以重新发布它,并且/或者在遵循自由软件基金会发布的 GNU 通用公共许可证下修改它;或者该许可证的版本 2 ,或者(按照你的需求)更晚的版本。

发布这篇文档是希望它能够帮助别人,但是没有任何保证;甚至不包括可售性和适用于任何特定目的的保证。关于更详细的信息,可以查看 GNU 通用许可证。

1.2 反馈校正

请将反馈和批评一起提交给 Sandeep.S 。我将感谢任何一个指出本文档中错误和不准确之处的人;一被告知,我会马上改正它们。

1.3 致谢

我对提供如此棒的特性的 GNU 人们表示真诚的感谢。感谢 Mr.Pramode C E 所做的所有帮助。感谢在 Govt Engineering College 和 Trichur 的朋友们的精神支持和合作,尤其是 Nisha Kurur 和 Sakeeb S 。 感谢在 Gvot Engineering College 和 Trichur 的老师们的合作。

另外,感谢 Phillip , Brennan Underwood 和 colin@nyx.net ;这里的许多东西都厚颜地直接取自他们的工作成果。


2. 概览

在这里,我们将学习 GCC 内联汇编。这内联表示的是什么呢?

我们可以要求编译器将一个函数的代码插入到调用者代码中函数被实际调用的地方。这样的函数就是内联函数。这听起来和宏差不多?这两者确实有相似之处。

内联函数的优点是什么呢?

这种内联方法可以减少函数调用开销。同时如果所有实参的值为常量,它们的已知值可以在编译期允许简化,因此并非所有的内联函数代码都需要被包含。代码大小的影响是不可预测的,这取决于特定的情况。为了声明一个内联函数,我们必须在函数声明中使用 inline 关键字。

现在我们正处于一个猜测内联汇编到底是什么的点上。它只不过是一些写为内联函数的汇编程序。在系统编程上,它们方便、快速并且极其有用。我们主要集中学习(GCC)内联汇编函数的基本格式和用法。为了声明内联汇编函数,我们使用 asm 关键词。

内联汇编之所以重要,主要是因为它可以操作并且使其输出通过 C 变量显示出来。正是因为此能力, asm 可以用作汇编指令和包含它的 C 程序之间的接口。


3. GCC 汇编语法

GCC , Linux上的 GNU C 编译器,使用 AT&T / UNIX 汇编语法。在这里,我们将使用 AT&T 语法 进行汇编编码。如果你对 AT&T 语法不熟悉的话,请不要紧张,我会教你的。AT&T 语法和 Intel 语法的差别很大。我会给出主要的区别。

  1. 源操作数和目的操作数顺序

    AT&T 语法的操作数方向和 Intel 语法的刚好相反。在Intel 语法中,第一操作数为目的操作数,第二操作数为源操作数,然而在 AT&T 语法中,第一操作数为源操作数,第二操作数为目的操作数。也就是说,

    Intel 语法中的 Op-code dst src 变为

    AT&T 语法中的 Op-code src dst

  2. 寄存器命名

    寄存器名称有 % 前缀,即如果必须使用 eax,它应该用作 %eax。

  3. 立即数

    AT&T 立即数以 $ 为前缀。静态 "C" 变量 也使用 $ 前缀。在 Intel 语法中,十六进制常量以 h 为后缀,然而AT&T不使用这种语法,这里我们给常量添加前缀 0x。所以,对于十六进制,我们首先看到一个 $,然后是 0x,最后才是常量。

  4. 操作数大小

    在 AT&T 语法中,存储器操作数的大小取决于操作码名字的最后一个字符。操作码后缀 bwl 分别指明了字节(byte)(8位)、字(word)(16位)、长型(long)(32位)存储器引用。Intel 语法通过给存储器操作数添加 byte ptrword ptrdword ptr 前缀来实现这一功能。

    因此,Intel的 mov al, byte ptr foo 在 AT&T 语法中为 movb foo, %al

  5. 存储器操作数

    在 Intel 语法中,基址寄存器包含在 [] 中,然而在 AT&T 中,它们变为 ()。另外,在 Intel 语法中, 间接内存引用为

    section:[base + index*scale + disp], 在 AT&T中变为

    section:disp(base, index, scale)。

    需要牢记的一点是,当一个常量用于 disp 或 scale,不能添加 $ 前缀。

现在我们看到了 Intel 语法和 AT&T 语法之间的一些主要差别。我仅仅写了它们差别的一部分而已。关于更完整的信息,请参考 GNU 汇编文档。现在为了更好地理解,我们可以看一些示例。

Intel Code AT&T Code
mov eax,1 movl $1,%eax
mov ebx,0ffh movl $0xff,%ebx
int 80h int $0x80
mov ebx, eax movl %eax, %ebx
mov eax,[ecx] movl (%ecx),%eax
mov eax,[ebx+3] movl 3(%ebx),%eax
mov eax,[ebx+20h] movl 0x20(%ebx),%eax
add eax,[ebx+ecx*2h] addl (%ebx,%ecx,0x2),%eax
lea eax,[ebx+ecx] leal (%ebx,%ecx),%eax
sub eax,[ebx+ecx*4h-20h] subl -0x20(%ebx,%ecx,0x4),%eax

4. 基本内联

基本内联汇编的格式非常直接了当。它的基本格式为

asm("汇编代码");

示例

asm("movl %ecx %eax"); /* 将 ecx 寄存器的内容移至 eax  */
__asm__("movb %bh (%eax)"); /* 将 bh 的一个字节数据 移至 eax 寄存器指向的内存 */

你可能注意到了这里我使用了 asm __asm__。这两者都是有效的。如果关键词 asm 和我们程序的一些标识符冲突了,我们可以使用 __asm__。如果我们的指令多余一条,我们可以写成一行,并用括号括起,也可以为每条指令添加 \n\t 后缀。这是因为gcc将每一条当作字符串发送给 as(GAS)( GAS 即 GNU 汇编器 ——译者注),并且通过使用换行符/制表符发送正确地格式化行给汇编器。

示例

__asm__ ("movl %eax, %ebx\n\t"
"movl $56, %esi\n\t"
"movl %ecx, $label(%edx,%ebx,$4)\n\t"
"movb %ah, (%ebx)");

如果在代码中,我们涉及到一些寄存器(即改变其内容),但在没有固定这些变化的情况下从汇编中返回,这将会导致一些不好的事情。这是因为 GCC 并不知道寄存器内容的变化,这会导致问题,特别是当编译器做了某些优化。在没有告知 GCC 的情况下,它将会假设一些寄存器存储了我们可能已经改变的变量的值,它会像什么事都没发生一样继续运行(什么事都没发生一样是指GCC不会假设寄存器装入的值是有效的,当退出改变了寄存器值的内联汇编后,寄存器的值不会保存到相应的变量或内存空间 ——译者注)。我们所可以做的是使用这些没有副作用的指令,或者当我们退出时固定这些寄存器,或者等待程序崩溃。这是为什么我们需要一些扩展功能。扩展汇编正好给我们提供了那样的功能。


5. 扩展汇编

在基本内联汇编中,我们只有指令。然而在扩展汇编中,我们可以同时指定操作数。它允许我们指定输入寄存器、输出寄存器以及修饰寄存器列表。GCC 不强制用户必须指定使用的寄存器。我们可以把头疼的事留给 GCC ,这可能可以更好地适应 GCC 的优化。不管怎樣,基本格式为:

       asm ( 汇编程序模板
: 输出操作数 /* 可选的 */
: 输入操作数 /* 可选的 */
: 修饰寄存器列表 /* 可选的 */
);

汇编程序模板由汇编指令组成.每一个操作数由一个操作数约束字符串所描述,其后紧接一个括弧括起的 C 表达式。冒号用于将汇编程序模板和第一个输出操作数分开,另一个(冒号)用于将最后一个输出操作数和第一个输入操作数分开,如果存在的话。逗号用于分离每一个组内的操作数。总操作数的数目限制在10个,或者机器描述中的任何指令格式中的最大操作数数目,以较大者为准。

如果没有输出操作数但存在输入操作数,你必须将两个连续的冒号放置于输出操作数原本会放置的地方周围。

示例:

asm ("cld\n\t"
"rep\n\t"
"stosl"
: /* 无输出寄存器 */
: "c" (count), "a" (fill_value), "D" (dest)
: "%ecx", "%edi"
);

现在,这段代码是干什么的?以上的内联汇编是将 fill_value 值 连续 count 次 拷贝到 寄存器 edi 所指位置(每执行stosl一次,寄存器 edi 的值会递增或递减,这取决于是否设置了 direction 标志,因此以上代码实则初始化一个内存块 ——译者注)。 它也告诉 gcc 寄存器 ecxedi 一直无效(原文为 eax ,但代码修饰寄存器列表中为 ecx,因此这可能为作者的纰漏 ——译者注)。为了使扩展汇编更加清晰,让我们再看一个示例。

         int a=10, b;
asm ("movl %1, %%eax;
movl %%eax, %0;"
:"=r"(b) /* 输出 */
:"r"(a) /* 输入 */
:"%eax" /* 修饰寄存器 */
);

这里我们所做的是使用汇编指令使 b 变量的值等于 a 变量的值。一些有意思的地方是:

  • b 为输出操作数,用 %0 引用,并且 a 为输入操作数,用 %1 引用。
  • r 为操作数约束。之后我们会更详细地了解约束(字符串)。目前,r 告诉 GCC 可以使用任一寄存器存储操作数。输出操作数约束应该有一个约束修饰符 = 。这修饰符表明它是一个只读的输出操作数。
  • 寄存器名字以两个%为前缀。这有利于 GCC 区分操作数和寄存器。操作数以一个 % 为前缀。
  • 第三个冒号之后的修饰寄存器 %eax 告诉 GCC %eax的值将会在 asm 内部被修改,所以 GCC 将不会使用此寄存器存储任何其他值。

asm 执行完毕, b 变量会映射到更新的值,因为它被指定为输出操作数。换句话说, asmb 变量的修改 应该会被映射到 asm 外部。

现在,我们可以更详细地看看每一个域。

5.1 汇编程序模板

汇编程序模板包含了被插入到 C 程序的汇编指令集。其格式为:每条指令用双引号圈起,或者整个指令组用双引号圈起。同时每条指令应以分界符结尾。有效的分界符有换行符(\n)和逗号(;)。\n 可以紧随一个制表符(\t)。我们应该都明白使用换行符或制表符的原因了吧?和 C 表达式对应的操作数使用 %0、%1 ... 等等表示。

5.2 操作数

C 表达式用作 asm 内的汇编指令操作数。作为第一双引号内的操作数约束,写下每一操作数。对于输出操作数,在引号内还有一个约束修饰符,其后紧随一个用于表示操作数的 C 表达式。即,

"约束字符串"(C 表达式),它是一个通用格式。对于输出操作数,还有一个额外的修饰符。约束字符串主要用于决定操作数的寻找方式,同时也用于指定使用的寄存器。

如果我们使用的操作数多于一个,那么每一个操作数用逗号隔开。

在汇编程序模板,每个操作数用数字引用。编号方式如下。如果总共有 n 个操作数(包括输入和输出操作数),那么第一个输出操作数编号为 0 ,逐项递增,并且最后一个输入操作数编号为 n - 1 。操作数的最大数目为前一节我们所看到的那样。

输出操作数表达式必须为左值。输入操作数的要求不像这样严格。它们可以为表达式。扩展汇编特性常常用于编译器自己不知道其存在的机器指令

[翻译] GCC 内联汇编 HOWTO的更多相关文章

  1. GCC内联汇编入门

    原文为GCC-Inline-Assembly-HOWTO,在google上可以找到原文,欢迎指出翻译错误. 中文版说明 由于译者水平有限,故译文出错之处,还请见谅.C语言的关键字不译,一些单词或词组( ...

  2. 最牛X的GCC 内联汇编

    导读 正如大家知道的,在C语言中插入汇编语言,其是Linux中使用的基本汇编程序语法.本文将讲解 GCC 提供的内联汇编特性的用途和用法.对于阅读这篇文章,这里只有两个前提要求,很明显,就是 x86 ...

  3. 推荐一篇讲arm架构gcc内联汇编的文章

    这是来自ethernut网站的一篇文章,原文链接: http://www.ethernut.de/en/documents/arm-inline-asm.html 另外,据说nut/os是个不错的开源 ...

  4. ARM体系下的GCC内联汇编

    转:http://andyhuzhill.github.io/arm/gcc/asm/2012/09/25/gcc-inline-assemly/ 在操作系统级的编程中,有时候,C语言并不能完全的使用 ...

  5. 汇编语言---GCC内联汇编

    转:http://www.cnblogs.com/taek/archive/2012/02/05/2338838.html GCC支持在C/C++代码中嵌入汇编代码,这些代码被称作是"GCC ...

  6. ARM嵌入式开发中的GCC内联汇编__asm__

    在针对ARM体系结构的编程中,一般很难直接使用C语言产生操作协处理器的相关代码,因此使用汇编语言来实现就成为了唯一的选择.但如果完全通过汇编代码实现,又会过于复杂.难以调试.因此,C语言内嵌汇编的方式 ...

  7. GCC 内联汇编(GCC内嵌ARM汇编规则)

    转:http://smileleeboo.howbbs.com/posts/list/3127/81062.html 更多文档参见:http://pan.baidu.com/s/1eQ7nd8Q 有时 ...

  8. VC内联汇编和GCC内联汇编的语法区别

    VC: #include <stdio.h> main(){ int a = 1; int b = 2; int c; __asm{ mov eax,a mov ebx,b mov ecx ...

  9. gcc 内联汇编

    http://www.cnblogs.com/zhuyp1015/archive/2012/05/01/2478099.html

随机推荐

  1. 约会 倍增lca

    题意:一棵树,给两个点,求树上有多少点到他俩距离相等 倍增lca,分好多情况讨论.. #include<cstdio> #include<cstring> #include&l ...

  2. 【树形dp】Bzoj3391 [Usaco2004 Dec]Tree Cutting网络破坏

    Description     约翰意识到贝茜建设网络花费了他巨额的经费,就把她解雇了.贝茜很愤怒,打算狠狠报 复.她打算破坏刚建成的约翰的网络.    约翰的网络是树形的,连接着N(1≤N≤1000 ...

  3. BZOJ_2049_[Sdoi2008]Cave 洞穴勘测_LCT

    BZOJ_2049_[Sdoi2008]Cave 洞穴勘测_LCT Description 辉辉热衷于洞穴勘测.某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由 ...

  4. BZOJ_4873_[Shoi2017]寿司餐厅_最大权闭合子图

    BZOJ_4873_[Shoi2017]寿司餐厅_最大权闭合子图 题意:http://www.lydsy.com/JudgeOnline/problem.php?id=4873 分析:我们发现分数正负 ...

  5. 用PCA(主成分分析法)进行信号滤波

    用PCA(主成分分析法)进行信号滤波 此文章从我之前的C博客上导入,代码什么的可以参考matlab官方帮助文档 现在网上大多是通过PCA对数据进行降维,其实PCA还有一个用处就是可以进行信号滤波.网上 ...

  6. css3波纹特效、H5实现动态波浪

    css3实现动态波纹特效,由于css3里面有过渡和动画效果,现在利用css3实现动态波浪效果就很简单了,直接使用transform来实现就ok, 使得translateX 产生偏移就可以不断实现循环动 ...

  7. Hadoop配置第2节-JDK的安装

    Hadoop配置-JDK的安装 总体目标:完成zookeeper+Hadoop+Hbase 整合平台搭建   进度:1:集群网络属性配置2:集群免密码登陆配置3:JDK的安装4:Zookeeper的安 ...

  8. javascript模块化编程 从入门到实战

    <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8& ...

  9. 一行代码实现数组去重(ES6)

    ES6中新增了Set数据结构,类似于数组,但是 它的成员都是唯一的 ,其构造函数可以接受一个数组作为参数,如: let array = [1, 1, 1, 1, 2, 3, 4, 4, 5, 3]; ...

  10. mpvue 小程序开发爬坑汇总

    <!-- 小程序的爬坑记录 --> 1 微信小程序之动态获取元素宽高 var obj=wx.createSelectorQuery(); 2 微信小程序图片自适应 <image cl ...