ARM GCC 内嵌(inline)汇编手册
2017-06-08 15:05
441 查看
关于这篇文档
这篇文章是本人为方便各位业界同仁而翻译,方便大家开发底层代码使用,转载请注明出处,谢谢。要是你E文功底好,本人还是建议阅读E文版的。http://www.ethernut.de/en/documents/arm-inline-asm.html
对于基于ARM的RISC处理器,GNU C编译器提供了在C代码中内嵌汇编的功能。这种非常酷的特性提供了C代码没有的功能,比如手动优化软件关键部分的代码、使用相关的处理器指令。
这里设想了读者是熟练编写ARM汇编程序读者,因为该片文档不是ARM汇编手册。同样也不是C语言手册。
这篇文档假设使用的是GCC 4 的版本,但是对于早期的版本也有效。
GCC asm
声明
让我们以一个简单的例子开始。就像C中的声明一样,下面的声明代码可能出现在你的代码中。/* NOP 例子 */ asm("mov r0,r0"); |
请接着阅读和学习这篇文档,因为该声明并不像你想象的和其他的C语句一样。内嵌汇编使用汇编指令就像在纯汇编程序中使用的方法一样。可以在一个asm声明中写多个汇编指令。但是为了增加程序的可读性,最好将每一个汇编指令单独放一行。
asm( "mov r0, r0\n\t" "mov r0, r0\n\t" "mov r0, r0\n\t" "mov r0, r0" ); |
asm(code: output operand list: input operand list: clobber list); |
operand list。Clobber list后面再讲。
下面是将C语言的一个整型变量传递给汇编,逻辑左移一位后在传递给C语言的另外一个整型变量。
/* Rotating bits example */ asm("mov %[result], %[value], ror #1": [result]"=r" (y): [value]"r" (x)); |
l 汇编指令放在第一部分中的“”中间。
"mov %[result], %[value], ror #1" |
[result]"=r" (y) |
[value]"r" (x) |
就像上面的NOP例子,asm声明的4个部分中,只要最尾部没有使用的部分都可以省略。但是有有一点要注意的是,上面的4个部分中只要后面的还要使用,前面的部分没有使用也不能省略,必须空但是保留冒号。下面的一个例子就是设置ARM
Soc的CPSR寄存器,它有input但是没有output operand。
asm("msr cpsr,%[ps]": : [ps]"r"(status)) |
asm("":::"memory"); |
asm("mov %[result], %[value], ror #1" : [result]"=r"(y) /* Rotation result.*/ : [value]"r"(x) /* Rotated value.*/ : /* No clobbers*/ ); |
%[result]表示第二部分的C变量y,%[value]表示三部分的C变量x;
符号操作符的名字使用了独立的命名空间。这就意味着它使用的是其他的符号表。简单一点就是说你不必关心使用的符号名在C代码中已经使用了。在早期的C代码中,循环移位的例子必须要这么写:
asm("mov %0, %1, ror #1": "=r" (result) :"r" (value)) |
优化C代码
有两种情况决定了你必须使用汇编。1st,C限制了你更加贴近底层操作硬件,比如,C中没有直接修改程序状态寄存器(PSR)的声明。2nd就是要写出更加优化的代码。毫无疑问GNUC代码优化器做的很好,但是他的结果和我们手工写的汇编代码相差很远。
这一部分有一点很重要,也是被别人忽视最多的就是:我们在C代码中通过内嵌汇编指令添加的汇编代码,也是要被C编译器的优化器处理的。让我们下面做个试验来看看吧。
下面是代码实例。
bigtree@just:~/embedded/basic-C$ arm-Linux-gcc -c test.c
bigtree@just:~/embedded/basic-C$ arm-linux-objdump -D test.o
编译器选择r3作为循环移位使用。它也完全可以选择为每一个C变量分配寄存器。Load或者store一个值并不显式的进行。下面是其它编译器的编译结果。
E420A0E1 mov r2, r4,ror #1 @ y, x |
有的时候这个过程变得更加糟糕。有时候编译器甚至完全抛弃你嵌入的汇编代码。C编译器的这种行为,取决于代码优化器的策略和嵌入汇编所处的上下文。如果在内嵌汇编语句中不使用任何输出部分,那么C代码优化器很有可能将该内嵌语句完全删除。比如NOP例子,我们可以使用它作为延时操作,但是对于编译器认为这影响了程序的执行速速,认为它是没有任何意义的。
上面的解决方法还是有的。那就是使用volatile关键字。它的作用就是禁止优化器优化。将NOP例子修改过后如下:
/*NOP example, revised*/ asm volatile("mov r0, r0"); |
i++; if (j == 1) x += 3; i++; |
if(j == 1) x += 3; i += 2; |
这些将对你的代码产生很到的影响,这将在下面介绍。下面的代码是c乘b,其中c和b中的一个或者两个可能会被中断处理程序修改。进入该代码前先禁止中断,执行完该代码后再开启中断。
asm volatile("mrs r12, cpsr\n\t" "orr r12, r12, #0xC0\n\t" "msr cpsr_c, r12\n\t" ::: "r12", "cc"); c *= b; /*This may fail.*/ asm volatile("mrs r12, cpsr\n" "bic r12, r12, #0xC0\n" "msr cpsr_c, r12" ::: "r12", "cc"); |
我们可以使用clobber list帮忙。上面例子中的clobber list如下:
"r12","cc" |
registor 状态寄存器标志位),memory都是在clobber list上有效的关键字。它用来向编译器指明,内嵌汇编指令改变了内存中的值。这将强迫编译器在执行汇编代码前存储所有缓存的值,然后在执行完汇编代码后重新加载该值。这将保留程序的执行顺序,因为在使用了带有memoryclobber的asm声明后,所有变量的内容都是不可预测的。
asm volatile("mrs r12, cpsr\n\t" "orr r12, r12, #0xC0\n\t" "msr cpsr_c, r12\n\t" :: :"r12", "cc", "memory"); c *= b; /*This is safe.*/ asm volatile("mrs r12, cpsr\n" "bic r12, r12, #0xC0\n" "msr cpsr_c, r12" ::: "r12", "cc","memory"); |
asm volatile("mrs r12, cpsr\n\t" "orr r12, r12, #0xC0\n\t" "msr cpsr_c, r12\n\t" : "=X" (b):: "r12", "cc"); c *= b; /*This is safe.*/ asm volatile("mrs r12 |
理解优化器对内嵌汇编的影响很重要。如果你读到这里还是云里雾里,最好是在看下个主题之前再把这段文章读几遍^_^。
Input and output operands
前面我们学到,每一个input和output operand,由被方括号[]中的符号名,限制字符串,圆括号中的C表达式构成。这些限制性字符串有哪些,为什么我们需要他们?你应该知道每一条汇编指令只接受特定类型的操作数。例如:跳转指令期望的跳转目标地址。不是所有的内存地址都是有效的。因为最后的opcode只接受24位偏移。但矛盾的是跳转指令和数据交换指令都希望寄存器中存储的是32位的目标地址。在所有的例子中,C传给operand的可能是函数指针。所以面对传给内嵌汇编的常量、指针、变量,编译器必须要知道怎样组织到汇编代码中。
对于ARM核的处理器,GCC 4提供了一下的限制。
Constraint | Usage in ARM state | Usage in Thumb state |
f | Floating point registers f0 .. f7 | Not available |
h | Not available | Registers r8..r15 |
G | Immediate floating point constant | Not available |
H | Same a G, but negated | Not available |
I | Immediate value in data processing instructions e.g. ORR R0, R0, #operand | Constant in the range 0 .. 255 e.g. SWI operand |
J | Indexing constants -4095 .. 4095 e.g. LDR R1, [PC, #operand] | Constant in the range -255 .. -1 e.g. SUB R0, R0, #operand |
K | Same as I, but inverted | Same as I, but shifted |
L | Same as I, but negated | Constant in the range -7 .. 7 e.g. SUB R0, R1, #operand |
l | Same as r | Registers r0..r7 e.g. PUSH operand |
M | Constant in the range of 0 .. 32 or a power of 2 e.g. MOV R2, R1, ROR #operand | Constant that is a multiple of 4 in the range of 0 .. 1020 e.g. ADD R0, SP, #operand |
m | Any valid memory address | |
N | Not available | Constant in the range of 0 .. 31 e.g. LSL R0, R1, #operand |
O | Not available | Constant that is a multiple of 4 in the range of -508 .. 508 e.g. ADD SP, #operand |
r | General register r0 .. r15 e.g. SUB operand1, operand2, operand3 | Not available |
w | Vector floating point registers s0 .. s31 | Not available |
X | Any operand |
Modifier | Specifies |
= | Write-only operand, usually used for all output operands |
+ | Read-write operand, must be listed as an output operand |
& | A register that should be used for output only |
operands必须为read-only。C编译器是没有能力做这个检查。
比较严格的规则是:不要试图向input operand写。但是如果你想要使用相同的operand作为input和output。限制性modifier(+)可以达到效果。例子如下:
asm("mov %[value], %[value], ror #1": [value]"+r" (y)) |
可能modifier +不支持早期的编译器版本。庆幸的是这里提供了其他解决办法,该方法在最新的编译器中依然有效。对于input operators有可能使用单一的数字n在限制字符串中。使用数字n可以告诉编译器使用的第n个operand,operand都是以0开始计数。下面是例子:
asm("mov %0, %0, ror #1": "=r" (value) :"0" (value)) |
请注意,在相反的情况下不会自动实现。如果我没告诉编译器那样做,编译器也有可能为input和output选择相同的寄存器。第一个例子中就为input和output选择了r3。
在多数情况下这没有什么,但是如果在input使用前output已经被修改过了,这将是致命的。在input和output使用不同寄存器的情况下,你必须使用&modifier来限制output
operand。下面是代码示例:
asm volatile("ldr %0, [%1]""\n\t" "str %2, [%1, #4]" "\n\t" : "=&r"(rdv) : "r"(&table),"r" (wdv) : "memory"); |
其他
内嵌汇编作为预处理宏
要是经常使用使用部分汇编,最好的方法是将它以宏的形式定义在头文件中。使用该头文件在严格的ANSI模式下会出现警告。为了避免该类问题,可以使用__asm__代替asm,__volatile__代替volatile。这可以等同于别名。下面就是个例程:#define BYTESWAP(val) \ __asm__ __volatile__ ( \ "eor r3, %1, %1, ror #16\n\t" \ "bic r3, r3, #0x00FF0000\n\t" \ "mov %0, %1, ror #8\n\t" \ "eor %0, %0, r3, lsr #8" \ : "=r"(val) \ : "0"(val) \ : "r3","cc" \ ); |
C
桩函数
宏定义包含的是相同的代码。这在大型routine中是不可以接受的。这种情况下最好定义个桩函数。unsignedlong ByteSwap(unsignedlong val) { asm volatile( "eor r3, %1, %1, ror #16\n\t" "bic r3, r3, #0x00FF0000\n\t" "mov %0, %1, ror #8\n\t" "eor %0, %0, r3, lsr #8" : "=r"(val) : "0"(val) : "r3" ); return val; } |
替换C变量的符号名
默认的情况下,GCC使用同函数或者变量相同的符号名。你可以使用asm声明,为汇编代码指定一个不同的符号名unsigned long value asm("clock")= 3686400 |
替换C函数的符号名
为了改变函数名,你需要一个原型声明,因为编译器不接受在函数定义中出现asm关键字。extern long Calc(void) asm("CALCULATE") |
强制使用特定的寄存器
局部变量可能存储在一个寄存器中。你可以利用内嵌汇编为该变量指定一个特定的寄存器。void Count(void) { register unsigned char counter asm("r3"); ... some code... asm volatile("eor r3, r3, r3"); ... more code... } |
临时使用寄存器
如果你使用了寄存器,而你没有在input或output operand传递,那么你就必须向编译器指明这些。下面的例子中使用r3作为scratch寄存器,通过在clobberlist中写r3,来让编译器得知使用该寄存器。由于ands指令跟新了状态寄存器的标志位,使用cc在clobber list中指明。
asm volatile( "ands r3, %1, #3" "\n\t" "eor %0, %0, r3" "\n\t" "addne %0, #4" : "=r" (len) : "0" (len) : "cc","r3" ); |
寄存器的用途
比较好的方法是分析编译后的汇编列表,并且学习C编译器生成的代码。下面的列表是编译器将ARM核寄存器的典型用途,知道这些将有助于理解代码。Register | Alt. Name | Usage |
r0 | a1 | First function argument Integer function result Scratch register |
r1 | a2 | Second function argument Scratch register |
r2 | a3 | Third function argument Scratch register |
r3 | a4 | Fourth function argument Scratch register |
r4 | v1 | Register variable |
r5 | v2 | Register variable |
r6 | v3 | Register variable |
r7 | v4 | Register variable |
r8 | v5 | Register variable |
r9 | v6 rfp | Register variable Real frame pointer |
r10 | sl | Stack limit |
r11 | fp | Argument pointer |
r12 | ip | Temporary workspace |
r13 | sp | Stack pointer |
r14 | lr | Link register Workspace |
r15 | pc | Program counter |
相关文章推荐
- ARM GCC 内嵌(inline)汇编手册
- ARM GCC 内嵌(inline)汇编手册
- ARM GCC 内嵌(inline)汇编手册
- ARM GCC 内嵌(inline)汇编手册
- ARM GCC 内嵌(inline)汇编手册
- ARM GCC 内嵌(inline)汇编手册
- ARM GCC 内嵌汇编手册
- ARM GCC 内嵌汇编手册
- ARM GCC 内嵌汇编手册
- ARM GCC 内嵌汇编手册
- ARM_GCC内嵌汇编
- arm gcc 内嵌汇编
- GCC中内嵌arm汇编
- XCODE(IOS)下内嵌ARM汇编(ARM嵌入式开发中的GCC内联汇编)
- GCC 内联汇编(GCC内嵌ARM汇编规则)
- ARM-elf-gcc的C语言内嵌汇编语言
- GCC 内联汇编(GCC内嵌ARM汇编规则)
- GCC&&G++ C && C++ 内嵌汇编和调用汇编函数的方法(x86,ARM自己对照改)
- ARM GCC 内嵌(inline)汇编手册
- ARM GCC 内嵌(inline)汇编手册