gcc内嵌汇编语法分析

1. 前置学习

  • 在学习gcc的内嵌汇编语法前,建议先了解C语言函数的调用规范,达到能使用汇编写出可供C语言安全调用的函数 以及 使用汇编安全调用C语言,如下是arm架构的过程调用标准:
    AAPCS(Procedure Call Standard for the Arm® Architecture)
  • 大致了解C语言的编译流程。

2. 官方手册的大致浏览

gcc的内嵌汇编gcc扩展语法的一部分,应阅读gcc官方文档 第6.47章,下面是目录的截图。
在这里插入图片描述
如下是对各个目录的简要说明,建议先看一下官方手册:

6.47.1 基本的内嵌汇编语法,不含操作数(operands),一般用于运行时对上下文没有影响的指令,如nop指令。
6.47.2 扩展的内嵌汇编语法,包含操作数(operands)。
6.47.3 扩展的内嵌汇编中操作数的约束条件。
6.47.4 控制变量/函数编译后(汇编文件中)符号命名的语法。
6.47.5 指定变量与固定寄存器绑定的方法及注意事项。
6.47.6 gcc在编译阶段获得内嵌汇编大小的机制。

3. gcc内嵌汇编语法速览

本章会提供一些内嵌汇编的示例,有兴趣可以先搜一些gcc内嵌汇编的语法学习一下,本文不会详细介绍基础语法。

3.1 Basic Asm - 不带操作数的内嵌汇编语法

  • 语法描述
    在这里插入图片描述
  • 示例
    在这里插入图片描述
    在这里插入图片描述

3.2 Extended Asm - 带有C表达式操作数的内嵌汇编指令

  • 语法描述
    在这里插入图片描述

  • 示例
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.3 控制c语言中的符号编译为汇编代码后的名字

int foo asm ("myfoo") = 2;

int func (int x, int y)    asm ("MYFUNC");
int func (int x, int y)
{
/*    . . .     */
}

3.4 为变量指定寄存器的语法

register int *foo 		asm ("r12");
register int *p1        asm ("r0")    =    ...;
register int *p2        asm ("r1")    =    ...;

4.核心 - gcc是如何处理内嵌汇编代码的?

在编译阶段(将C语言编译成汇编的阶段),编译器是不会解析内嵌汇编指令的,而是将内嵌汇编看做一个字符串直接传递下一个阶段,由汇编器解析。
编译阶段仅仅会解析“输出部”,“输入部”,“破坏部”, “GotoLabels”,程序员也是同步这四个部分告诉编译器执行这段内嵌汇编可能发生的情况,如可能破坏寄存器,或者修改内存。或者输出部输入部需要的参数类型。
基于这些理解,我们可以再次审视gcc内嵌汇编那些奇怪的语法,很多内容就能理解了。

5.volatile的理解

使用volatile的目的是为了防止gcc将内嵌汇编整体优化掉的,gcc在编译阶段并不解析汇编指令,那么再什么情况下,gcc可能会认为内嵌汇编是没有意义的呢?

  • 没有输出部,如果没有输出部,gcc可能会认为内嵌汇编是没有意义的,从而可能会优化掉。
  • 连续两次同一个内嵌汇编代码块,如果输入部的参数一样,gcc可能会认为第二次调用没有执行的必要从而会被优化掉。
    在这里插入图片描述
  • 一个主动让gcc优化的例子
    在这里插入图片描述

注:关于编译器的优化问题,还没有能力分析的面面俱到,本部分也只是一个大致的理解,

6. inline 的理解

  • 前提:gcc将内嵌汇编当做字符串处理直接复制到汇编文件中由汇编器处理。
  • 我们知道,针对汇编器会有一些伪指令不会被翻译为机器码或者一条汇编伪指令会被汇编为多条机器码,内嵌汇编具体有多大在编译阶段是没办法知道的(经过汇编器处理才能最终得出),但编译阶段有时有事需要知道内嵌汇编究竟有多大(在一些相对跳转指令中,在编译阶段需要通过代码量知道能不能跳转过去),这个时候gcc会根据代码的行数和体系架构估算可能的代码量,但gcc的估算是按照最大的可能去估算的,如果这段指令用的内嵌指令过多,可能会导致gcc估算的偏差太大。
  • 使用inline修饰,gcc在估算代码时会将这段内嵌汇编当做最小大小来处理。

注:关于inline的应用场景,我目前还没遇见过,理解的可能也有偏差。

7. goto 的理解

goto比较容易理解,目的是告诉gcc内嵌汇编中有跳转指令,并指示可能的跳转位置,便于gcc对程序进行优化。
使用 goto 修饰应注意以下两点:

  • goto 隐含 volatile 属性; 理解:存在跳转指令,此段汇编必定不会是没有意义的,故gcc不会进行优化
  • 不能有输出部
    内嵌汇编中使用 GotoLabels 使用 %l(小写的L)进行索引,有两种索引方式:
  • 直接通过标签索引,直接看如下示例:
    在这里插入图片描述
  • 通过编号索引,方法是接着输出部输入部的参数继续编号。如下示例:
    在这里插入图片描述

8. 输出部 (OutputOperands)和 输入部(InputOperands)

8.1 输出部 (OutputOperands) 和输入部(InputOperands)的构成:

  • c语言符号
    • C语言中的变量,函数,表达式,常量等一切分配了存储空间的符号。
    • 输出部 (OutputOperands) 的参数使用C语言中的 左值 ,具有可写的属性,使用 “=” 修饰,编译器则认为所修饰的符号是 只写 的,在汇编代码中是不会被的,使用 “+” 修饰,则认为这个符号是可读可写的“+r” 表示为所修饰的符号分配一个寄存器,且这个寄存器的值在汇编指令执行中是会被使用的“=r” 表示为所修饰的符号分配一个寄存器,且这个寄存器的值在汇编指令执行中是不会被使用的“+m” “=m”, 则是要求编译器为所修饰的符号分配内存进行存储。
    • 输入部(InputOperands) 的参数使用C语言中的 右值, 编译器会认为输入部的参数在汇编代码执行前后是不会发生改变的(编译器会依据这一点进行程序优化),程序员应该在编程时保证这一点,
  • 约束(Constraints)

    输出部 (OutputOperands)“+r”“=r”“+m”“=m”, 输入部 (InputOperands)“r”“m”,目的是约束编译器为他所修饰的符号是一个什么类型的存储空间,“寄存器”?, “地址”?,“可读”?, “可写”? 。。。。。。

8.2 一个示例的分析

如下图示例:在下图红框中 “%0” 按汇编指令的要求需要一个普通寄存器,使用 “tmp” 作为输出部的参数,那么这个时候编译器会怎么作呢?

  • 编译器并不会分析 “mrc p15, 0, %0, c15, c0,0” 这段汇编代码到底要干什么,
  • 编译器会分析 :“=r” (tmp), 会知道 tmp 是需要一个寄存器存储的,且编译器会认为这个寄存器在汇编指令中是会被改变的,且汇编指令并不会使用这个寄存器的值。
  • 假设编译器为 tmp分配了 R0, 编译器会将 %0 进行替换,然后将 “mrc p15, 0, r0, c15, c0,0” 作为一个字符串,原封不动的复制到汇编文件中。
    在这里插入图片描述

8.3 输出部为什么需要 “=”,即为什么需要告诉编译器在内嵌汇编中不使用“=”所修饰符号的值。

简单来讲是gcc优化的需要,gcc只要保证一个符号在他使用前将其准备好就可以了,如果使用**“=”**修饰一个符号则是告诉编译器,内嵌汇编中不会用到这个值,所以gcc也不用在这段内嵌汇编前把这个值准备好,又因为gcc在编译阶段并不会分析内嵌汇编程序,所以即使在内嵌汇编中使用了这个值,编译器也不会报错,但这个值可能不是预期的。

8.4 为什么要有输出部 和 输入部,而不是程序员在内嵌汇编中自己分配

编译器会为定义的一些变量参数分配存储空间,如果在汇编程序中程序员在内嵌汇编中自己分配,则可能与编译器分配的产生冲突,当然如果程序员比较了解当前架构的ABI,明确知道编译器使用了哪些寄存器或者内存,在保证不冲突的情况下也可以自己分配。

8.5 其他的一些约束(Constraints)

这部分建议在用时在查手册,下面列出来一些,目的是大致有个印象,一些架构特定的约束可以约束到使用哪些寄存器。

m内存变量
o操作数为内存变量,但是其寻址方式是偏移量类型
V操作数为内存变量,但寻址方式不是偏移量类型
r通用寄存器
i立即数
p指针

在这里插入图片描述

9. 破坏部 (Clobbers)

  • “cc”

    告诉编译器汇编代码会修改标志寄存器,如 N Z C V

  • “memory”

    告诉编译器汇编代码会破坏内存,破坏部使用了“memory”,相当于一道内存屏障,可以用来防止编译时乱序。
    考虑如下代码,他不会执行任何一条汇编指令,他其实也不会破坏内存,但编译器并不知道,编译器在看到"memory"后就认为他是可能破坏内存的,一般用来作为内存屏障,防止编译乱序。

    asm volatile ("":::"memory");
    

10 为C语言中的符号在编译为汇编后重新指定一个名字

此部分语法很简单,但目前我还没理解这种用法的应用场景。
在此不展开了,有兴趣可以看手册6.47.4章。

int foo asm ("myfoo") = 2;
int func (int x, int y) asm ("MYFUNC");
int func (int x, int y)
{
/*    . . .     */
}

11 指定变量与固定寄存器绑定的语法。

语法很简单,有兴趣可以看手册6.47.5章

12 示例 - arm32 系统调用内嵌汇编的实现

如下是linux内核中的示例,在此不展开分析系统调用,在理解了前面的知识后,应该能看懂下面代码。
arm的软中断指令:swi svc
arm系统调用的参数传递:R0-R6
arm系统调用号的传递:R7
返回值:R0
在这里插入图片描述

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GCC内联汇编asm格式是一种将汇编代码嵌入到C或C++源代码中的方法。它允许开发人员直接使用汇编语言来访问底层硬件或执行高性能算法。以下是GCC内联汇编asm格式的详细说明。 基本格式 GCC内联汇编asm格式基本格式如下: ```c asm (assembly code : output operands : input operands : clobbered registers); ``` - assembly code:汇编代码,可以是单行或多行代码。 - output operands:用于存储计算结果的变量,可以有多个,用逗号分隔。输出操作数是可选的,可以省略。 - input operands:用于传递参数的变量,可以有多个,用逗号分隔。输入操作数是必需的。 - clobbered registers:代码执行期间会被修改的寄存器,用于通知编译器。可以有多个,用逗号分隔。clobbered registers是可选的,可以省略。 示例 以下是一个简单的GCC内联汇编asm格式示例,将eax寄存器中的值加1,并将结果存储在eax中。 ```c int value = 10; asm ("addl $1, %%eax" : "=a" (value) : "a" (value)); ``` - "addl $1, %%eax":汇编代码,将eax加1。 - "=a" (value):输出操作数,将eax中的值存储在value变量中。 - "a" (value):输入操作数,将value的值传递给eax。 - 没有clobbered registers。 输出操作数 输出操作数用于将汇编代码的结果存储在变量中。输出操作数有两种类型:普通输出(通道约束)和跨约束输出。 普通输出 普通输出使用“=约束”语法表示,其中约束指定了输出操作数应存储在哪个寄存器或内存位置中。约束可以是以下之一: - "=r"(任意寄存器) - "=m"(任意内存位置) - "=a"(eax寄存器) - "=d"(edx寄存器) - "=q"(eax或edx寄存器) 示例 以下是一个使用普通输出的示例,将eax寄存器中的值加1,并将结果存储在value变量中。 ```c int value; asm ("addl $1, %%eax" : "=a" (value) : "a" (value)); ``` 跨约束输出 跨约束输出是一种将结果存储在多个输出变量中的方法。它使用“+约束”语法表示,其中约束指定了输出操作数应存储在哪个寄存器或内存位置中。多个约束可以用逗号分隔。 示例 以下是一个使用跨约束输出的示例,将eax寄存器中的值加1,并将结果存储在value1和value2变量中。 ```c int value1, value2; asm ("addl $1, %%eax" : "+a" (value1), "=r" (value2)); ``` 输入操作数 输入操作数用于将变量的值传递给汇编代码。输入操作数使用“约束”语法表示,其中约束指定了变量应该存储在哪个寄存器或内存位置中。约束可以是以下之一: - "r"(任意寄存器) - "m"(任意内存位置) - "a"(eax寄存器) - "d"(edx寄存器) - "q"(eax或edx寄存器) 示例 以下是一个使用输入操作数的示例,将value变量的值传递给eax寄存器中。 ```c int value = 10; asm ("movl %0, %%eax" : : "r" (value)); ``` clobbered registers clobbered registers是在汇编代码执行期间会被修改的寄存器列表。它用于通知编译器哪些寄存器应该被保存和恢复。clobbered registers使用“%约束”语法表示,其中约束指定了被修改的寄存器名称。多个寄存器可以用逗号分隔。 示例 以下是一个使用clobbered registers的示例,将eax寄存器中的值加1,并告诉编译器edx寄存器也被修改了。 ```c asm ("addl $1, %%eax" : : "a" (value) : "%edx"); ``` 总结 GCC内联汇编asm格式是一种将汇编代码嵌入到C或C++源代码中的方法。它允许开发人员直接使用汇编语言来访问底层硬件或执行高性能算法。通过输出操作数、输入操作数和clobbered registers,开发人员可以管理汇编代码与C或C++代码之间的数据流和寄存器使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值