ARM GCC内联汇编
ARM GCC内联汇编
- 通用的内嵌汇编模板
- Example
- Operand List中修饰符
- Example
- Clobber List
- Example
通用的内嵌汇编模板
__asm volatile (code:output operand list:input operand list:clobber list
);
Example
C内联汇编:
// __attribute__((naked))
__attribute__((noinline))
uint8_t asm_test(uint8_t a, uint8_t b)
{uint8_t c;__asm volatile ("adds %[res], %[src0], %[src1]\n\t":[res] "=r" (c):[src0] "r" (a), [src1] "r" (b):);return c;
}反汇编代码:
0040a350 <asm_test>:40a350: 1840 adds r0, r0, r140a352: b2c0 uxtb r0, r040a354: 4770 bx lr
- 使用
__attribute__((naked)
可以使函数开头和结尾没有保存和恢复的指令(由编译器自动添加),使用__attribute__((noinline))
可以使函数不被优化为嵌入某个函数的汇编指令,而是保持一个函数的形式。 - 使用operand list与C函数的变量和入参进行交互,格式为
[alias] "修饰符" (C variable)
,使用[]和()绑定变量的语法很像超链接,比如[Good](https://google.com)。 - operand list中的各项以逗号
,
分隔 - 对于早期gcc,不支持使用alias标识变量,而是使用%0,%1…这种根据变量出现的顺序编号,这样的缺陷在于一旦中间加一个操作符,所有的编号都变了
- 使用volatile可以禁止编译器优化,用于将咱写的汇编指令都编译出来,比如
mov r0, r0
就不会再被优化
Operand List中修饰符
- 定义数据类型
- 输入输出类型
Modifier | 含义 |
---|---|
= | write-only,仅用于output operand list |
+ | read-write,定义操作数可读可写,仅用于output operand list |
& | 指示编译器将变量绑定到特定的寄存器,避免与其它寄存器冲突,引发输出值被改变的问题,& 只能放在+/= 之后 |
没有=,+
修饰的操作符都是read-only的,input operand list都是read-only,所以想对同一变量又读又写,那只能将它放在output operand list并用+
修饰。
Example
- 立即数搭配宏进行使用,因为内联汇编里的东西没法被预处理
#define A 20void demo(void)
{__asm volatile ("mov r0, %[imm]\n\t"::[imm] "I" (A):);
}/* -------------------对应汇编----------------------- */
0040a364 <demo>:40a364: f04f 000a mov.w r0, #1040a368: 4770 bx lr
&
的作用
/* ---------------------正确写法带&----------------------- */
uint32_t tab[2];
uint8_t asm_test(uint8_t a, uint8_t b)
{uint32_t rdv, wdv=1;__asm volatile ("ldr %[rd], [%[tab]]\n\t""str %[wr], [%[tab], #4]\n\t":[rd] "=&r" (rdv):[tab] "r" (tab),[wr] "r" (wdv):);return 1;
}
/* ---------------------对应汇编----------------------- */
0040a350 <asm_test>:40a350: 2001 movs r0, #140a352: 4b02 ldr r3, [pc, #8] @ (40a35c <asm_test+0xc>)40a354: 681a ldr r2, [r3, #0]40a356: 6058 str r0, [r3, #4]40a358: 4770 bx lr40a35a: bf00 nop40a35c: 200023fc .word 0x200023fc/* ---------------------错误写法无&----------------------- */
uint32_t tab[2];
uint8_t asm_test(uint8_t a, uint8_t b)
{uint32_t rdv, wdv=1;__asm volatile ("ldr %[rd], [%[tab]]\n\t""str %[wr], [%[tab], #4]\n\t":[rd] "=r" (rdv):[tab] "r" (tab),[wr] "r" (wdv):);return 1;
}
/* ---------------------对应汇编----------------------- */
0040a350 <asm_test>:40a350: 2001 movs r0, #140a352: 4b02 ldr r3, [pc, #8] @ (40a35c <asm_test+0xc>)40a354: 681b ldr r3, [r3, #0]40a356: 6058 str r0, [r3, #4]40a358: 4770 bx lr40a35a: bf00 nop40a35c: 200023fc .word 0x200023fc
在错误的写法中,由于不带&,造成rd和tab共用了寄存器r3;加上&后,rd用的r2,没有受到干扰,这些行为和编译器类型及版本有关系,为了避免出错,直接将output operand list都加上&修饰
。
Clobber List
破坏列表,告诉编译器我在这段汇编代码中改变了什么,比如改变了寄存器,内存等等,这样编译器会在执行这段汇编代码之前保存一下将被改变的数据。
类型 | 含义 |
---|---|
r0, r1… | 表示寄存器将被改变,在执行汇编之前将push对应的寄存器,并在执行完汇编之后pop出来 |
memory | 告诉编译器内存将会被改变,在执行汇编之前,将那些还未写入内存的值赶紧写入 |
cc | 告诉编译器condition code状态寄存器将改变 |
Example
对于memory和cc没构建出好的例子,只有关于寄存器的了。
/*--------------------------C--------------------------*/
uint8_t asm_test(uint8_t a, uint8_t b)
{__asm volatile ("mov r0, r0\n\t":::"r4");return 0;
}/*--------------------------汇编--------------------------*/
0040a350 <asm_test>:40a350: b510 push {r4, lr}40a352: 4600 mov r0, r040a354: 2000 movs r0, #040a356: bd10 pop {r4, pc}
果然编译器对咱指定的"r4"做了保存恢复的操作。