ILD

inline assembly language in C code
作者:HerbertYuan 邮箱:yuanjp89@163.com
发布时间:2017-10-6 站点:Inside Linux Development

使用asm关键字在C代码中嵌入汇编语言。asm是GNU扩展,如果使用-ansi或各种-std选项,使用__asm__代替asm。

GCC提供两种形式的asm语句:basic asm statement和extended asm statement。前者不包含任何操作数。在函数内部,后者是混合C和汇编语言的首选方式。而在顶层(函数外),只能使用basic asm。


1 Basic Asm - Assembler Instructions Without Operands

语法如下:

    asm [volatile] (AssemblerInstructions)


可选的volatile修饰符没有影响,所有的basic asm语句隐式地是volatile。


AssemblerInstructions是字符串字面量,指定汇编代码。字符串可以包含任何汇编器识别的语句,包括directives。GCC不解析汇编指令。可以将多条汇编指令一起放到一个asm字符串中,使用汇编代码使用的字符分开它们,这通常是一个"\n\t"。


使用扩展asm通常可以产生更小、更安全、更有效的代码,但是两种情况只能使用basic asm。

- top level (函数外)

- naked function


由于GCC不解析basic asm,所以在写basic asm时,你最好不要影响GCC,例如,不要试图使用通用寄存器,因为GCC也可能使用它们,这可能导致潜在的冲突。编译器直接将basic asm中的汇编指令逐字的拷贝到汇编语言输出文件,不会处理%等。所以在basic asm中,引用寄存器可使用%eax,而在extended asm中,需要使用%%eax。


2 Extended Asm - Assembler Instructions with C Expression Operands

使用扩展asm,可以在汇编代码中,读写C变量,从汇编代码中跳转到C标签。扩展汇编语法使用冒号分割汇编器模板和操作数参数。

asm [volatile] ( AssemblerTemplate

                        : OutputOperands

                        [ : InputOperands

                            [ : Clobbers ] ] )


修饰符:

volatile 扩展asm的典型用途是维护输入值,产生输出值。如果你的asm语句有副作用,需要添加volatile修饰符,来阻止优化。

goto 通知编译器asm可能跳转到GotoLabels指定的一个标签。


参数:

AssemblerTemplate 字符串字面量用于汇编代码的模板,包括固定字符和tokens,token用来引用输入、输出和goto参数。

OutputOperands 汇编指令修改的逗号分隔的C变量列表,允许为空。

InputOperands 汇编指令读取的逗号分开的C表达式列表,允许为空。

Clobbers 汇编指令修改的不在output中的逗号分开的寄存器或者其它值列表,允许为空。

GotoLabels 当使用asm形式的goto时,这个参数包含要跳转的c标签列表。


2.1 Volatile

对于扩展asm语句,GCC可能会优化它,使用volatile来避免这些优化。即使使用volatile,GCC也可能改变asm的顺序,使用显示的依赖(添加变量到Output)来避免这种移动。


2.2 Assembler Template

一串汇编指令,但是%是转义符号,%%表示输出单个%。%=输出为一个实例。


2.3 Output Operands

逗号分开的c变量,汇编代码会改变这些变量,每个操作数的格式:

[ [asmSymbolicName] ] constraint (cvariablename)


asmSymbolicName

操作数的符号名,在汇编模板中使用方括号引用,如%[value],名字的作用域仅限于asm语句。

如果没有使用asmSymbolicName,则使用操作数的位置(0开始),如在模板中%0,表示第一个操作数。


constraint

字符串常量,用于指定操作数的约束。输出约束必须以=或者+开头,后面还需要有1个或多个额外的约束,如r表示寄存器,m表示内存。


cvariablename

指定一个C语言左值,来存储输出,通常是一个变量名。


2.4 Input Operands

输入操作数是汇编代码使用的C变量或表达式,操作数用逗号分开,每个操作数的格式:

[ [asmSymbolicName] ] constraint (cexpression)


asmSymbolicName

和输入参数的含义一样。


constraint

输入约束不能以=或+开头,约束可以使用数字,表示使用输入操作数相同的约束。


expression

是C变量或者表达式。


当编译器选择用来表示输入操作数的寄存器时,它不会选择任何clobbered registers。


2.5 Clobbers

汇编指令可能使用一些寄存器,这需要显示地告诉编译器,来避免寄存器使用冲突。clobber list使用逗号分开,每个item表示一个寄存器或者特殊的clobbers。每个item是一个字符串常量,使用双引号括起来。

特殊的clobber arguments:

"memory" 告诉编译器,汇编指令执行输入输出参数之外的内存读写操作,如访问输入参数指向的内存。


3 Constraints

约束是asm中最难理解的部分了,约束可以描述:操作数是否可以为寄存器,什么种类的寄存器;操作数是否可以位内存引用,什么类型的地址;操作数是否可以为立即数常量,可能的值是什么。约束还可以要求两个操作数匹配。

'm' 允许内存操作。

'r' 允许寄存器操作数。


3.1 Constraint Modifier Characters

'=' 意味着指令会写操作数,之前的值被丢掉,替换位新的值。

'+' 意味着读写。'='或'='应该是约束中的第一个字符。


每个架构都有其约束字符,可参考GCC的手册。


4 一个简单的例子

c代码如下:

1
2
3
4
5
6
7
8
9
int a()
{
    int b = 0;
    __asm__("add %0, %0, #1"
        "=r" (b)
        "r" (b)
        "r3");
    return b;
}


编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
herbert@herbert-pc:/work/code/gcc/asm$ $CC -S asm.c
herbert@herbert-pc:/work/code/gcc/asm$ cat asm.s
    .cpu arm920t
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 2
    .eabi_attribute 30, 6
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .file   "asm.c"
    .text
    .align  2
    .global a
    .syntax unified
    .arm
    .fpu softvfp
    .type   a, %function
a:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 8
    @ frame_needed = 1, uses_anonymous_args = 0
    @ link register save eliminated.
    str fp, [sp, #-4]!
    add fp, sp, #0
    sub sp, sp, #12
    mov r3, #0
    str r3, [fp, #-8]
    ldr r2, [fp, #-8]
    .syntax divided
@ 4 "asm.c" 1
    add r2, r2, #1
@ 0 "" 2
    .arm
    .syntax unified
    str r2, [fp, #-8]
    ldr r3, [fp, #-8]
    mov r0, r3
    add sp, fp, #0
    @ sp needed
    ldr fp, [sp], #4
    bx  lr
    .size   a, .-a
    .ident  "GCC: (crosstool-NG crosstool-ng-1.23.0) 6.3.0"
    .section    .note.GNU-stack,"",%progbits

可以看到gcc使用r2作为寄存器。如果没有指定clobber list为r3,则gcc使用r3,这里仅做测试,r3实际上在asm中未被使用。


参考:

gcc manual. Chapter 6.45, How to Use Inline Assembly Language in C Code

https://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html

http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.