高级CGNU C/C++ 内联汇编——补充介绍
Posted 从善若水
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高级CGNU C/C++ 内联汇编——补充介绍相关的知识,希望对你有一定的参考价值。
本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。
博客内容主要围绕:
5G协议讲解
算力网络讲解(云计算,边缘计算,端计算)
高级C语言讲解
Rust语言讲解
文章目录
GNU C/C++ 内联汇编——补充介绍
为C symbol 声明一个可用于汇编程序的name
你可以通过将asm(或__asm__)关键字写在声明符(declarator)后面,为C函数或变量指定一个在汇编代码中使用的名称。你需要确定选择的汇编程序名称不会与任何其它汇编程序符号或 reference registers 冲突。
C 变量命名汇编名
下面的例子展示了如何为变量指定汇编程序名称:
int foo asm ("myfoo") = 2;
这指定了在汇编代码中用于变量foo的名称应该是 “myfoo” 而不是通常的 “foo” 。
GCC不支持对非静态局部变量使用此特性,因为这些变量没有对应的汇编名称(都存在栈中,通过偏移访问)。
C 函数命名汇编名
要指定函数的汇编器名称,请在函数定义之前写一个函数声明,并在其后添加 asm,如下所示:
int func (int x, int y) asm ("MYFUNC");
int func (int x, int y)
/* … */
这表示在汇编代码中用于函数 “func” 的名称应为 “MYFUNC”。
定义一个驻留在指定寄存器中的变量
GNU C允许你将特定的硬件寄存器与C变量关联起来。在几乎所有的情况下,都是让编译器来分配寄存器以生成最好的代码。然而,在某些特殊情况下,需要对变量存储进行更精确的控制。
全局变量和局部变量都可以与寄存器相关联。执行这种关联的结果在两者之间是非常不同的,如下面的解释。
全局变量
定义一个全局变量
你可以像这样定义一个全局寄存器变量,并将它与指定的寄存器关联起来:
register int *foo asm ("r12");
这里 “r12” 是关联起来的寄存器名称。注意,这与定义局部寄存器变量的语法相同,但对于全局变量,声明出现在函数之外。register关键字是必需的,不能与static结合使用。寄存器名称必须在目标平台有效。
不要使用类型限定符,如 const 和 volatile ,因为结果可能与预期相反。特别是,使用volatile限定符并不能完全阻止编译器优化对寄存器的访问。
寄存器在大多数系统上都是一种稀缺资源,允许编译器管理它们的使用通常会产生最好的代码。然而,在特殊情况下,在全局范围内保留一些寄存器是有意义的。例如,这可能在程序中是有用的。例如,有一对需要频繁访问的全局变量的编程语言解释器。
定义一个全局寄存器变量后,对于当前编译单元:
- 如果变量在内联汇编中引用,则必须通过约束向编译器提供访问类型。不支持从 basic asm 访问;
- 对变量的访问可以像往常一样优化,并且寄存器仍然可以在任何计算中分配和使用,前提是变量的可观察值不受影响;
- 如果寄存器是 call-saved 寄存器,那么调用ABI将受到影响:在变量被赋值之后,寄存器的值并没有在函数的 epilogue sequences 恢复。因此,函数不能安全地返回到采用标准ABI的调用方。
- 如果寄存器是一个 call-clobbered 寄存器,调用使用标准ABI的函数可能会丢失变量的内容。这样的调用可能由编译器创建,即使在原始程序中没有这样写,例如当使用 libgcc 函数来弥补不可用的指令时。
可以通过指定编译器选项 ‘-fixed-reg’ 来保留指定的寄存器。
声明一个全局变量
全局寄存器变量没有初始值,因为可执行文件没有办法为寄存器提供初始值。
选择寄存器时,通常选择在函数调用前会被保存现场,调用之后恢复现场的寄存器。这将确保那些不知道有保留寄存器的 code (例如,库函数) 在函数调用返回之前能够恢复它的值。
全局变量的使用
当调用不知道保留寄存器存在的函数时,如果在这些函数中又调用了使用这些保留寄存器的函数,则要小心。例如,如果调用 qsort 的系统库版本,它可能会在执行期间破坏你的保留寄存器,但是(如果你选择了适当的寄存器)它会在返回之前恢复它们。但是,在调用 qsort 的比较函数之前,是不会恢复它们,如果在 qsort 的比较函数中使用了这个变量的值,那这个值可能是错误的。
同样,从信号处理程序或多个控制线程访问全局寄存器变量也不安全。系统库函数可能会暂时将寄存器用于其它事情。此外,由于寄存器不是专门为变量保留的(也就是说其它函数也可以使用),因此从异步信号处理程序访问它时,有时会观察到一些不相关的临时值驻留在寄存器中。
在大多数机器上,longjmp 会将它在 setjmp 时保存的所有全局寄存器变量的值恢复。然而,在某些机器上,longjmp不会恢复全局寄存器变量的值。为了便于移植,调用 setjmp 函数时应该手动保存全局寄存器变量的值,并在 longjmp 中手动恢复它们。
局部变量(在函数中声明的变量)
你可以像这样定义一个局部寄存器变量,并将它与指定的寄存器关联起来:
register int *foo asm ("r12");
这里 “r12” 是寄存器的名称。注意,这与定义全局寄存器变量的语法相同,但对于局部变量,声明出现在函数中。register关键字是必需的,不能与static结合使用。寄存器名称必须是目标平台的有效名称。
不要使用类型限定符,如const和volatile,因为结果可能与预期相反。特别是,当使用 const 限定符时,编译器可能会用 asm 语句中的初始值替换变量的当前值,这可能会导致相应的操作数出现在不同的寄存器中,而非指定的寄存器。
与全局寄存器变量一样,建议选择通常在函数调用中会被保存和恢复的寄存器,这样库函数的调用就不会破坏它。
该特性唯一有用途的是在调用扩展 asm 时为输入和输出操作数指定寄存器。如果特定机器的约束不能提供足够的控制来选择所需的寄存器,那么这个功能就有用了。要将操作数强制放入寄存器,先创建一个局部变量,并在变量声明之后指定寄存器名称。然后在asm操作数中使用这个局部变量,并指定任何匹配这个寄存器的约束:
register int *p1 asm ("r0") = …;
register int *p2 asm ("r1") = …;
register int *result asm ("r0");
asm ("sysint" : "=r" (result) : "0" (p1), "r" (p2));
警告:在上面的例子中,要注意寄存器(例如 “r0” )可能会被后面的代码调用破坏(例如 “p2” 的初始化),也可能会被一些为其它变量进行算术运算的函数(包括库函数)破坏。在这种情况下,可以使用临时变量解决:
int t1 = …;
register int *p1 asm ("r0") = …;
register int *p2 asm ("r1") = t1;
register int *result asm ("r0");
asm ("sysint" : "=r" (result) : "0" (p1), "r" (p2));
定义一个寄存器变量并不会保留这个寄存器。而且在调用扩展 asm 时,指定寄存器的内容不会得到保证。由于这个原因,下面的用法是不受支持的。如果它们看起来能工作,那只是偶然的,可能会因为(看起来)周围代码的不相关的变化而停止工作,或者甚至因为gcc未来版本的优化而产生一些微小的变化:
- 向 basic asm 传递或者接收参数;
- 在不使用输入或输出操作数的情况下,向扩展asm传递或接收参数;
- 使用非标准调用方法,向用汇编语言(或其他语言)编写的函数传递或接收参数。
一些开发人员使用本地寄存器变量是为了改善 gcc 的寄存器分配,特别是在大型函数中。在这种情况下,寄存器名实际上是对寄存器分配器的一个提示。虽然在某些情况下,这可以生成更好的代码,但这种提升可能不稳定,主要取决于分配器/优化器。由于不能保证你的优化一定有效,因此不鼓励使用本地寄存器变量。
GCC如何计算一个asm块的大小
有些目标机器要求GCC能够跟踪正在使用的每条指令的大小,以便生成正确的代码。因为由 asm 语句生成的代码的最终长度只有汇编器知道,GCC必须估计它的长度。它通过计算 asm template 中的指令数,并乘以处理器支持的最长指令的长度来实现这一点(在计算指令数时,任何出现的换行符或语句分隔符(例如,’ ; ’ )都表示指令的结束)。
通常,GCC作出的评估是足以确保生成正确的代码。但是,如果你使用伪指令或汇编宏,或者如果你使用汇编指令在对象文件中扩展更多的空间(比单条指令所需的空间多),这时可能会影响编译器的评估结果。如果发生这种情况,则汇编器可能产生一个诊断,说明标签不可达(label is unreachable)。
这里是从善若水的博客,感谢您的阅读⌨🖥🖱
文章链接
《GNU C/C++ 内联汇编编程指南全集》
《GNU C/C++ 内联汇编——入门级》
《GNU C/C++ 内联汇编——进阶——语法详解》
《GNU C/C++ 内联汇编——进阶——约束详解》
《GNU C/C++ 内联汇编——补充介绍》
《GNU C/C++ 内联汇编——实例参考》
《GNU C/C++ 内联汇编——Intel与ATT汇编语法对比》
以上是关于高级CGNU C/C++ 内联汇编——补充介绍的主要内容,如果未能解决你的问题,请参考以下文章