GCC内联汇编

2022/6/6 1:20:18

本文主要是介绍GCC内联汇编,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1. gcc内联汇编格式

__asm_- __volatile__(指令部: 输出部: 输入部: 损坏部)

gcc内联汇编在处理器变量和寄存器上提供了一个模板和一些约束条件:

(1) 在指令部(Assembler Template)中数字前加上%,如%0、%1等,表示需要使用寄存器的样板操作数。若指令部中用到几个不同的操作数,就说明有几个变量需要和寄存器结合。

(2) 输出部(Output Operands) 用于描述在指令部中可以修改的C语言变量以及约束条件。每个输出约束通常以"="或"+"号开头,"="号表示被修饰的操作数只有可写属性,"+"号表示被修饰的操作数具有可读可写属性。然后是一个字母(表示对操作数类型的说明),接着是关于变量结合的约束。输出部可以是空的。

(3) 输入部(Input Operands) 用来描述在指令部只读取的C语言变量及约束条件。输入部描述的参数只有只读属性,不要试图修改输入部参数的内容,因为gcc编译器假定输入部的参数内容在内嵌汇编之前和之后都是一致的。在输入部中不能使用"="号或者"+"号约束条件,否则就会编译报错。输入部可以是空的。

(4) 损坏部(Clobbers) 一般以"memory"结束。"memory"告诉gcc编译器,内联汇编代码改变了内存中的值,强迫编译器在执行该汇编代码前存储所有缓存的值,在执行完汇编代码之后重新加载该值,目的是防止编译乱序。"cc"表示内嵌代码修改了状态寄存器的相关标志位。


2. 例子

(1) 例1:

static inline unsigned long arch_local_irq_save(void)
{
    unsigned long flags;

    asm volatile(
    "mrs    %0, daif", //读取PSTAT寄存器中的DAIF域到flags变量
    "msr    daifset, #2", //关闭IRQ
    : "=r" (flags)
    :
    : "memory");

    return flags;
}

先看输出部,%0 操作数对应 "=r"(flags),即flags变量,其中"="表示被修饰的操作数的属性是只写。"r"表示使用一个通用寄存器。

接着看输入部,上例中输入部为空,没有指定参数。最后看损坏部,以"memory"结束。

该函数主要用于把PSTATE寄存器中的DAIF域保存到临时变量flags中,然后关闭IRQ。

在输出部和输入部使用"%"来表示参数序号,比"%0"表示第一个参数,"%1"表示第2个参数。

(2) 例2:

为了增强代码的可读性,可以使用汇编符号名字来替代%表示的操作数,如下面add函数:

int add(int i, int j)
{
    int ret;

    asm volatile(
    "add    %w[result], %w[input_i], %w[input_j]"
    : [result] "=r" (ret)
    : [input_i] "r" (i), [input_j] "r" (j)
    );

    return ret;
}

书上Demo编译验证不通过,报错:

$ gcc main.c -o pp
main.c: Assembler messages:
main.c:8: Error: number of operands mismatch for `add'

上述是个简单的gcc内联汇编的例子,主要功能是将i和j的值相加返回结果。先看输出部,表示只定义了一个操作数。"[result]"表示定义了一个汇编符号操作数,符号名为result,它对应"=r"(ret),使用函数中定义的ret变量。在汇编代码中对应%w[result],其中w表示ARM64中的32位通用寄存器。再看输入部,定义了两个操作数,同样使用汇编符号操作数的方式来定义。第一个汇编符号操作数是input_i对应形参i,第二个汇编符号操作数是input_j对应形参j。

 

3. gcc内联汇编操作符和修饰符

操作符/修饰符    说明
=                被修饰的操作数只写
+                被修饰的操作数具有可读可写属性
&                被修饰的操作数只能作为输出

 

4. ARM64特有操作符和修饰符

操作符/修饰符    说明
k                SP寄存器
w                浮点寄存器、SIMD、SVE寄存器
Upl                使用P0到P7中任意一个SVE寄存器
Upa                使用P0到P15中任意一个SVE寄存器
Input            整数,常常用于ADD指令
J                整数,常常用于SUB指令
K                整数,常常用于32位逻辑指令
L                整数,常常用于64位逻辑指令
M                整数,常常用于32位的MOV指令
N                整数,常常用于64位的MOV指令
S                绝对符号地址或标签引用
Y                浮点数,其值为0
Z                整数,其值为0
Ush                表示一个符号的PC相对偏移量的高位部分(大于等于12bit的部分),这个PC相对偏移介于0-4GB
Q                表示没有使用偏移量的单一寄存器的内存地址
Ump                一个适用于SI/DI/SF和DF模式下的加载-存储指令的内存地址。

 

参考:《奔跑吧Linux内核》第2版,此书不怎么样。

 



这篇关于GCC内联汇编的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程