使用asm关键字在C代码中嵌入汇编语言。asm是GNU扩展,如果使用-ansi或各种-std选项,使用__asm__代替asm。
GCC提供两种形式的asm语句:basic asm statement和extended asm statement。前者不包含任何操作数。在函数内部,后者是混合C和汇编语言的首选方式。而在顶层(函数外),只能使用basic asm。
语法如下:
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。
使用扩展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标签列表。
对于扩展asm语句,GCC可能会优化它,使用volatile来避免这些优化。即使使用volatile,GCC也可能改变asm的顺序,使用显示的依赖(添加变量到Output)来避免这种移动。
一串汇编指令,但是%是转义符号,%%表示输出单个%。%=输出为一个实例。
逗号分开的c变量,汇编代码会改变这些变量,每个操作数的格式:
[ [asmSymbolicName] ] constraint (cvariablename)
asmSymbolicName
操作数的符号名,在汇编模板中使用方括号引用,如%[value],名字的作用域仅限于asm语句。
如果没有使用asmSymbolicName,则使用操作数的位置(0开始),如在模板中%0,表示第一个操作数。
constraint
字符串常量,用于指定操作数的约束。输出约束必须以=或者+开头,后面还需要有1个或多个额外的约束,如r表示寄存器,m表示内存。
cvariablename
指定一个C语言左值,来存储输出,通常是一个变量名。
输入操作数是汇编代码使用的C变量或表达式,操作数用逗号分开,每个操作数的格式:
[ [asmSymbolicName] ] constraint (cexpression)
asmSymbolicName
和输入参数的含义一样。
constraint
输入约束不能以=或+开头,约束可以使用数字,表示使用输入操作数相同的约束。
expression
是C变量或者表达式。
当编译器选择用来表示输入操作数的寄存器时,它不会选择任何clobbered registers。
汇编指令可能使用一些寄存器,这需要显示地告诉编译器,来避免寄存器使用冲突。clobber list使用逗号分开,每个item表示一个寄存器或者特殊的clobbers。每个item是一个字符串常量,使用双引号括起来。
特殊的clobber arguments:
"memory" 告诉编译器,汇编指令执行输入输出参数之外的内存读写操作,如访问输入参数指向的内存。
约束是asm中最难理解的部分了,约束可以描述:操作数是否可以为寄存器,什么种类的寄存器;操作数是否可以位内存引用,什么类型的地址;操作数是否可以为立即数常量,可能的值是什么。约束还可以要求两个操作数匹配。
'm' 允许内存操作。
'r' 允许寄存器操作数。
'=' 意味着指令会写操作数,之前的值被丢掉,替换位新的值。
'+' 意味着读写。'='或'='应该是约束中的第一个字符。
每个架构都有其约束字符,可参考GCC的手册。
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