基本内联汇编和扩展内联汇编(《操作系统真象还原》笔记)

什么是内联汇编

  • 内联汇编称为inline assembly,GCC支持在C代码中直接嵌入汇编代码,所以称为GCC inline assembly。C语言不支持寄存器操作,汇编语言可以,所以自然就想到了再C语言中嵌入内联汇编提升“战斗力”的方式,通过内联汇编,C 程序员可以实现 C 语言无法表达的功能,这样使开发能力大为提升。
  • 内联汇编按格式分为两大类,一类是最简单的基本内联汇编,另一类是复杂一些的扩展内联汇编,内联汇编中所用的汇编语言,其语法是 AT&T,并不是咱们熟悉的 Intel 汇编语法,GCC 只支持它,所以咱们还得了解下 AT&T。
    在这里插入图片描述

基本内联汇编

基本内联汇编是最简单的内联形式,其格式为:
asm [volatile] ("assembly code")

  • asm:用于声明内联汇编表达式,这是内联汇编固定部分,不可少。asm和__asm__ 是一样的,是由gcc定义的宏:#define __asm__ asm

  • volatile:是可选项,它告诉gcc:“不要修改我写的汇编代码,请原样保留”。volatile 和__volatile__是一样的,是由gcc定义的宏:#define __volatile__ volatile

  • “assembly code” 是咱们所写的汇编代码,它必须位于圆括号中,而且必须用双引号括起来。规则如下:

    1. 指令必须用双引号引起来,无论双引号中是一条指令或多条指令
    2. 一对双引号不能跨行,如果跨行需要在结尾用反斜杠\转义。
    3. 指令之间用分号; 换行符\n或换行符加制表符\n \t分隔。
    4. 当指令在多个双引号中时,除最后一个双引号外,其余双引号中的代码最后一定要有分隔符,如:
      asm(“movl $9,%eax;””pushl %eax”) 正确
      asm(“movl $9,%eax””pushl %eax”)  错误
      
    5. 寄存器前面加前缀%,立即数前面加前缀$,操作数由左到右的顺序。

扩展内联汇编

格式:
asm [volatile] (“assembly code”:output : input : clobber/modify)

  • output:用来指定汇编代码的数据如何输出给C代码使用。内嵌的汇编指令运行结束后,如果想将运行结果存储到C变量中,就用此项指定输出的位置。output中每个操作数的格式为:“操作数修饰符约束名”(C 变量名),操作数修饰符通常为等号=。多个操作数之间用逗号,分隔。

  • input:用来指定C中数据如何输入给汇编使用。要向让汇编使用C中的变量作为参数,就要在此指定。input中每个操作数的格式为:“[操作数修饰符] 约束名”(C 变量名),操作数修饰符为可选项。多个操作数之间用逗号,分隔。

  • clobber/modify:汇编代码执行后会破坏一些内存和寄存器资源,通过此项通知编译器,可能造成寄存器或内存数据破坏,这样gcc就知道哪些寄存器或内存需要提前保护前来。

    1. 格式:用双引号把寄存器名称引起来,多个寄存器之间用逗号,分隔,寄存器不用再加两个%%,只要写名称即可,如:asm("movl %%eax, %0;movl %%eax,%%ebx":"=m" (ret_value)::"bx")
    2. 如果我们的内联汇编代码修改了标志寄存器 eflags 中的标志位,同样需要在 clobber/modify 中用”cc”声明
    3. 如果我们修改了内存,我们需要在 clobber/modify 中”memory”声明。
    4. 如果我们在 output 中使用了内存约束,gcc 自然会得到哪块内存被修改。但如果被修改的内容并未在output 中,我们就需要用”memory”告诉 gcc 啦。

  • 约束的作用:约束的作用是让C代码的操作数变成汇编代码能使用的操作数,所有的约束形式其实都是给汇编用的。约束是C语言中的操作数与汇编语言中的操作数之间的映射,他告诉gcc,同一个操作数在两种环境下如何变换身份,如何对接沟通。编译过程中C代码是要先变成汇编代码的,内联汇编中的约束就相当于gcc让咱们指定C中数据的编译形式。



以下是各种约束的解释:

  • 寄存器约束:寄存器约束就是要求gcc使用哪个寄存器,将input或output变量约束在某个寄存器中,常见的寄存器约束有:
    a:表示寄存器 eax/ax/al
    b:表示寄存器 ebx/bx/bl
    c:表示寄存器 ecx/cx/cl
    d:表示寄存器 edx/dx/dl
    D:表示寄存器 edi/di
    S:表示寄存器 esi/si
    q:表示任意这 4 个通用寄存器之一:eax/ebx/ecx/edx
    r:表示任意这 6 个通用寄存器之一:eax/ebx/ecx/edx/esi/edi
    g:表示可以存放到任意地点(寄存器和内存)。相当于除了同 q 一样外,还可以让 gcc 安排在内存中
    A:把 eax 和 edx 组合成 64 位整数
    f:表示浮点寄存器
    t:表示第 1 个浮点寄存器
    u:表示第 2 个浮点寄存器
#include<stdio.h> 
void main() 
{ 
	int in_a = 1, in_b = 2, out_sum; 
	asm("addl %%ebx, %%eax":"=a"(out_sum):"a"(in_a),"b"(in_b)); 
	printf("sum is %d\n",out_sum); 
}
  • 内存约束:内存约束是要求gcc直接将位于input和output中的C变量内存地址作为内联汇编代码的操作数,不需要寄存器做中转,直接进行内存读写,也就是汇编代码的操作数是C变量的指针。
    m:表示操作数可以使用任意一种内存形式
    o:操作数为内存变量,但访问它是通过偏移量的形式访问,即包括offset_address的格式。

  • 立即数约束:立即数即常数,此约束要求gcc在传值的时候不通过内存和寄存器,直接作为立即数传给汇编代码。由于立即数不是变量,只能作为右值,所以只能放在input中。
    i:表示操作数为整数立即数
    F:表示操作数为浮点数立即数
    I:表示操作数为 0~31 之间的立即数
    J:表示操作数为 0~63 之间的立即数
    N:表示操作数为 0~255 之间的立即数
    O:表示操作数为 0~32 之间的立即数
    X:表示操作数为任何类型立即数

  • 通用约束:
    0~9:此约束只用在 input 部分,但表示可与 output 和 input 中第 n 个操作数用相同的寄存器或内存。

序号占位符

  • 占位符:为了方便对操作数的引用,扩展内联汇编提供了占位符,他的作用是代表约束指定的操作数(寄存器、内存、立即数),我们更多的是在内联汇编中使用占位符来引用操作数。占位符分为序号占位符和名称占位符:

    1. 序号占位符:序号占位符是对在output和input中的操作数,按照它们从左到右的次序从0开始编号,一直到9,也就是说最多支持10个序号占位符,引用它的格式是%0~9。占位符所表示的操作数默认情况下为 32 位数据,在%和序号之间插入字符’h’来表示操作数为ah(第 8~15 位),或者插入字符’b’来表示操作数为 al(第 0~7 位)。
      h –输出寄存器高位部分中的那一字节对应的寄存器名称,如 ah、bh、ch、dh。
      b –输出寄存器中低部分 1 字节对应的名称,如 al、bl、cl、dl。
      w –输出寄存器中大小为 2 个字节对应的部分,如 ax、bx、cx、dx。
      k –输出寄存器的四字节部分,如 eax、ebx、ecx、edx。
      如:
      asm("addl %%ebx, %%eax":"=a"(out_sum):"a"(in_a),"b"(in_b));
      等价于
      asm("addl %2, %1":"=a"(out_sum):"a"(in_a),"b"(in_b));
      其中:
      “=a”(out_sum)序号为 0,%0 对应的是 eax。
      “a”(in_a)序号为 1,%1 对应的是 eax。
      “b”(in_b)序号为 2,%2 对应的是 ebx。

    2. 名称占位符:名称占位符与序号占位符不同,序号占位符靠本身出现在output和input中的位置就能被编译器识别出来。而名称占位符需要在output和input中把操作数显式地起个名字,它用这样的格式来标识操作数:[名称]”约束名”(C 变量)。这样,该约束对应的汇编操作数便有了名字,在 assembly code 中引用操作数时,采用%[名称]的形式就可以了。

  • 由于扩展内联汇编中的占位符要有前缀%,为了区别占位符和寄存器,只好在寄存器前用两个%做前缀啦,这就是本节前面解释在扩展内联汇编中寄存器前面要有两个%做前缀的原因。

  • 在约束中还有操作数类型修饰符,用来修饰所约束的操作数:内存、寄存器,分别在ouput 和 input中有以下几种。
    在output中:
    =:表示操作数是只写,相当于为 output 括号中的 C 变量赋值,如=a(c_var),此修饰符相当于 c_var=eax。
    +:表示操作数是可读写的,告诉 gcc 所约束的寄存器或内存先被读入,再被写入。
    &:表示此 output 中的操作数要独占所约束(分配)的寄存器,只供 output 使用,任何 input 中所分配的寄存器不能与此相同。注意,当表达式中有多个修饰符时,&要与约束名挨着,不能分隔。
    在input中:
    %:input 中的输入可以和下一个 input 操作数互换

  • 一般情况下,input 中的 C 变量是只读的,output 中的 C 变量是只写的。

  • 修饰符’='只用在 output 中,表示 C 变量是只写的,功能相当于 output 中的 C 变量=约束的汇编操作数,如”=a”(c_var),相当于 c_var=eax 的值。

  • 修饰符’+'也只用在 output 中,但它具备读、写的属性,也就是它既可作为输入,同时也可以作为输出,所以省去了在 input 中声明约束。

#include <stdio.h> 
void main() 
{ 
	int in_a = 1, in_b = 2; 
	asm("addl %%ebx, %%eax;":"+a"(in_a):"b"(in_b)); 
	printf("in_a is %d\n", in_a); 
}

扩展内联汇编之机器模式简介

  • 机器模式用来在机器层面上指定数据的大小及格式。
  • 由于各种约束均不能确切地表达具体的操作数对象,所以引用了机器模式,用来从更细的粒度上描述数据对象的大小及其指定部分

寄存器按是否可单独使用,可分成几个部分,拿 eax 举例:

  • 低部分的一字节:al
  • 高部分的一字节:ah
  • 两字节部分:ax
  • 四字节部分:eax

h :输出寄存器高位部分中的那一字节对应的寄存器名称,如 ah、bh、ch、dh。
b :输出寄存器中低部分 1 字节对应的名称,如 al、bl、cl、dl。
w :输出寄存器中大小为 2 个字节对应的部分,如 ax、bx、cx、dx。
k :输出寄存器的四字节部分,如 eax、ebx、ecx、edx。




举例:

1 #include<stdio.h> 
2 void main() 
3 { 
4 	int in_a = 0x1234, in_b = 0; 
5 	asm("movw %1, %0":"=m"(in_b):"a"(in_a)); 
6 	printf("in_b now is 0x%x\n", in_b);
}

这段代码目的是把in_a的低16位复制到in_b中,第五行中,变量in_a的约束是a,这表示由gcc把in_a的值分配给寄存器al、ax或eax,这很模糊,到底gcc把in_a的值分配给谁了呢?之后的movw指令也很模糊,我们只能这样理解:movw指令将al、ax或eax中的2个字节复制到in_b所在的内存中

修改后:

1 #include<stdio.h> 
2 void main() 
3 { 
4 	int in_a = 0x1234, in_b = 0; 
5 	asm("movw %w1, %0":"=m"(in_b):"a"(in_a));
6 	printf("in_b now is 0x%x\n", in_b);
}

添加w前缀就可以确定源操作数为ax

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CLion 是 JetBrains 公司开发的一款集成开发环境 (IDE),主要用于 C 和 C++ 编程。在 CLion 中,内联汇编(Inline Assembler)允许开发者在编写 C 或 C++ 代码时直接嵌入汇编指令,以利用硬件级别的性能优化或者处理特定平台特有的低级操作。 以下是使用 CLion 进行内联汇编的一些关键步骤和注意事项: 1. **启用内联汇编支持**:确保你的 CLion 配置支持内联汇编。在“Build, Execution, Deployment”(构建、执行和部署)菜单中,选择“CMake”,然后在生成器的配置中启用对 inline assembly 的支持。 2. **插入汇编代码**:在需要使用内联汇编的地方,通常使用 `asm` 关键字,后跟括号括起的汇编指令。例如: ```cpp int x = 0; asm volatile ("movl %1, %0" : "=r"(x) : "r"(42)); ``` 3. **声明和管理内存**:内联汇编需要手动管理内存,使用 `%` 符号指定寄存器或内存地址。例如,`%eax` 表示 EAX 寄存器,`%edi` 表示 EDI 寄存器,`%esp` 则是栈指针。 4. **调试和检查**:由于内联汇编不被编译器完全理解和跟踪,可能需要使用汇编查看器或者断点来检查执行过程。CLion 提供了一些辅助工具,如 Disassembly view(反汇编视图)。 5. **注意兼容性和限制**:不是所有处理器架构都支持内联汇编,且不同编译器对它的处理方式可能不同。务必确保你的目标平台支持,并在编译时正确设置目标架构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值