内联汇编简介

February 13, 2021 默认分类

内联汇编用于在C程序中直接嵌入汇编代码,可用于编写使用C/C++语句无法实现的功能(当然,最好先去看一下 GCC 文档,尤其是__builtin类函数)

GCC 默认使用 AT&T 语法。AT&T 语法有以下不同。

  • 立即数:所有立即数前需要加上$,如1h在 AT&T 语法中写作$0x1
  • 寄存器:寄存器名称前需要加上%,如寄存器eax需要写作%eax
  • 操作数顺序:与 Intel 语法相反,AT&T 语法中,源操作数在前,目标操作数在后,与 Intel 语法相反。如MOV EBX,EAX在 AT&T 语法中应为mov %eax,%ebx
  • 内存寻址:AT&T 语法为%seg:disp(base,index,scale),其中,非实模式下不使用seg,并且立即数用于 disp 和 scale 时,不加$号。例:(%eax)表示访问eax寄存器中存储的内存地址。
  • 操作数长度:Intel 语法中,操作数长度在内存操作数中指定。而 AT&T 语法中,操作数长度在指令中指定,并与 Intel 语法使用的符号不同。

基本语法

内联汇编分为基本内联汇编和扩展内联汇编两种。
基本内联汇编:asm [限定符](汇编语句);
扩展内联汇编:`asm 限定符;

限定符常用于内联汇编。volatile指示编译器不要自动优化(经常在内联汇编上出问题)这段汇编。
基本内联汇编将汇编语句直接插入程序中(可能会被自动“优化”掉),而编译器会处理扩展内联汇编,自动分配使用的寄存器,将汇编的输入与输出和C语言变量绑定,避免汇编语句干扰变量和程序正常执行,并且可以生成更短、性能更好的代码。因此,除非编写裸函数(编译器不生成函数对应的调用与返回序列)等特殊用途,大部分情况下建议使用扩展内联汇编。

而扩展内联汇编中,汇编语句变为汇编模板,此时,编译器就需要分配寄存器,并在汇编语句两侧生成可能需要的额外指令,然后填上汇编模板中的格式串。

约束模板

扩展内联汇编可指定输入与输出,而将汇编语句与C变量绑定,就需要使用约束模板。
约束模板格式:"[约束]"(值)
常用约束如下:

  • 内存:m
  • 通用寄存器:r
  • 立即数:i
  • 编号:十进制数字,用于重复约束,即一个操作数需要用于多处(通常为输入和输出),常用于CISC指令集。
  • 赋值:=
  • 读取并赋值:+

此外,在不同架构上有不同的约束符以指定特定寄存器。对于x86与amd64,下列约束可指定特定寄存器:

  • a,b,c,d:相应地指定a,b,c,d寄存器
  • Ssi寄存器
  • Ddi寄存器
  • A: 64位的结果分别储存在ad寄存器中。

在汇编模板中,为了能正确地与C程序互操作,应避免直接使用寄存器名与已定义的符号(如变量名)。

修饰列表(Clobber)

当汇编模板修改了输入输出约束以外的寄存器或内存,则需要在clobbered列表中指定,以告知编译器此段指令完成后,相应寄存器、内存值已被改写,不可使用。

什么时候需要clobber,clobber什么?
凡是被修改(无论显式或隐式)的寄存器,并且未出现于输入输出约束中,则需要添加此寄存器。
如果此段汇编完成后以某种形式修改了输入输出约束以外的内存,则需要添加"memory"至此列表中.

汇编模板

在基本汇编基础上,汇编模板通过将操作数模板化,并在编译时替换,增加了灵活性,有利于C语言与汇编的正确互操作。
常用模板:

  • %[n]:引用第n个约束(C操作数)
  • %%:字面的%,直接指定寄存器时常用。

例子

in指令

asm volatile("inb %1,%0":"=a"(ret):"Nd"(port):)

其中,N指示操作数为无符号8位整数。inout指令要求指定的端口为立即数或dx寄存器。

交换两个值(xchg指令)

asm volatile("xchg %1,%0":
"=r"(a):
"m"(b),"0"(a):
"memory"
);

注:x86与x86-64的所有指令均不支持两个操作数均为内存。
这里使用了引用约束,"0"(a[i])中,寄存器与前面的"=r"(a[i])使用的相同。


相关文章

添加新评论

 我们使用cookie在本地保存您评论时填写的信息,参见隐私条款