简介
内嵌汇编代码是指在C语言中嵌入汇编代码(Inline Assembly Language in C Code),可以在对代码时间要求比较高,及在C语言中访问某些汇编指令来实现特殊功能,内嵌汇编代码主要有两种形式:基础内嵌汇编代码(basic asm):不带任何参数扩展内嵌汇编代码(extended asm):可以带输入/输出参数。
扩展内嵌汇编的模板如下
asm asm-qualifiers ( AssemblerTemplate : OutputOperands [ : InputOperands [ : Clobbers ] ]) asm 修饰词 ( 指令部分 :输出部分 :输入部分 :损害部分 )
常见的约束体条件如下:
= 被修饰的操作数具有只写属性
“+” 被修饰的操作数具有可读可写属性
& 用于输出限定符,这个操作数在输入参数指令执行完成后才能写入。
r 通用寄存器
p 内存地址
m 内存变量
i 立即数
内嵌汇编代码验证1
学习内嵌汇编代码,我们将之前汇编实现的my_memcpy 代码按照函数调用规划a0 传递数据源地址,a1 传递目的地址,a2 传递拷贝长度,返回拷贝长度通过a0 返回,以下是 rars 验证通过的汇编代码。
.global my_memcpy_test .text li t0, 0x10010000 li t1, 0x11111111 sw t1, (t0) li t1, 0x22222222 sw t1, 4(t0) li t1, 0x33333333 sw t1, 8(t0) li t1, 0x44444444 sw t1, 12(t0) li t1, 0x55555555 sw t1, 16(t0) li t1, 0x66666666 sw t1, 20(t0) li t1, 0x77777777 sw t1, 24(t0) li t1, 0x88888888 sw t1, 28(t0) li t1, 0x99999999 sw t1, 32(t0) li t1, 0xaaaaaaaa sw t1, 36(t0) li t1, 0xbbbbbbbb sw t1, 40(t0) li a0, 0x10010000 li a1, 0x100100c0 li a2, 32 my_memcpy_test: mv t0, a0 mv t1, a1 add t2, t0, a2 .loop: lw t3, (t0) sw t3, 0(t1) addi t0, t0, 4 addi t1, t1, 4 blt t0, t2, .loop mv a0,a2 ret
将上述代码按照转化以下内嵌汇编代码:
int my_memcpy(void * src,void * dest ,int len) { asm volatile ( "mv t0, a0 \n" "mv t1, a1 \n" "add t2, t0, a2 \n" ".loop: \n" "lw t3, (t0) \n" "sw t3, 0(t1) \n" "addi t0, t0, 4 \n" "addi t1, t1, 4 \n" "blt t0, t2, .loop\n" "mv a0,a2 \n" "ret \n" ::: ); }
在main函数内添加测试代码调用my_mempy 函数,验证函数工作状态。
int main(void) { int src[8] = {0x11111111,0x22222222,0x33333333,0x44444444, 0x55555555,0x66666666,0x7777777,0x88888888}; int dest[8] = {0x00}; int ret = my_memcpy((void*) src,(void *)dest,32); for(int i = 0;i < 8;i++) printf("dest[%d] = %x,",i,dest[i]); printf("ret = %d\r\n",ret); }
运行后数据已经按照预期的进行了拷贝,函数返回值也已经按照预期的打印输出32.
dest[0] = 11111111,dest[1] = 22222222,dest[2] = 33333333,dest[3] = 44444444,dest[4] = 55555555,dest[5] = 66666666,dest[6] = 7777777,dest[7] = 88888888,ret = 32
反汇编 产看编译器产生的汇编代码如下:
30079462 <my_memcpy>: 30079462: 82aa mv t0,a0 30079464: 832e mv t1,a1 30079466: 00c283b3 add t2,t0,a2 3007946a <.loop>: 3007946a: 0002ae03 lw t3,0(t0) 3007946e: 01c32023 sw t3,0(t1) 30079472: 0291 addi t0,t0,4 30079474: 0311 addi t1,t1,4 30079476: fe72cae3 blt t0,t2,3007946a <.loop> 3007947a: 8532 mv a0,a2 3007947c: 8082 ret 3007947e: 8082 ret 30079480 <main>: 30079480: 1101 addi sp,sp,-32 30079482: 02000613 li a2,32 30079486: 0ff89597 auipc a1,0xff89 3007948a: c3258593 addi a1,a1,-974 # 400020b8 <dest.0> 3007948e: 00043517 auipc a0,0x43 30079492: 4aa50513 addi a0,a0,1194 # 300bc938 <src.1> 30079496: cc22 sw s0,24(sp) 30079498: ca26 sw s1,20(sp) 3007949a: c84a sw s2,16(sp) 3007949c: c64e sw s3,12(sp) 3007949e: c452 sw s4,8(sp) 300794a0: ce06 sw ra,28(sp) 300794a2: fc1ff0ef jal ra,30079462 <my_memcpy> 300794a6: 8a2a mv s4,a0 300794a8: 4401 li s0,0
内嵌汇编代码验证2
上面的内嵌汇编的代码直接使用的汇编代码实现的,并没有使用变量,我们再次基础上修改内嵌汇编的代码使用变量的方式实现上述功能,修改代码如下。
static int my_memcpy1(void * src,void * dst ,int len) { unsigned long tmp = 0; unsigned long end = (char *)src + len; asm volatile ( "1: lw %1, (%2)\n" //tmp = *src "sw %1, (%0)\n" //*dst = tmp "addi %0, %0, 4\n" //dst += 4 "addi %2, %2, 4\n" //src += 4 "blt %2, %3, 1b" //if(src < end) goto 1 : "+r" (dst), "+r" (tmp), "+r" (src) : "r" (end) : "memory"); return len; }
添加测试代码在main 函数中调用my_memcpy1 函数
int main(void) { static int src[8] = {0x11111111,0x22222222,0x33333333,0x44444444, 0x55555555,0x66666666,0x7777777,0x88888888}; static int dest[8] = {0x00}; int ret = my_memcpy1((void*) src,(void *)dest,32); for(int i = 0;i < 8;i++) printf("dest[%d] = %x,",i,dest[i]); printf("ret = %d\r\n",ret); }
上述代码运行结果跟之前也是一致说明功能已经ok,查看反汇编代码如下
40034284 <main>: 40034284: 1101 addi sp,sp,-32 40034286: ca26 sw s1,20(sp) 40034288: 0007c497 auipc s1,0x7c 4003428c: 98048493 addi s1,s1,-1664 # 400afc08 <dest.0> 40034290: ce06 sw ra,28(sp) 40034292: cc22 sw s0,24(sp) 40034294: c84a sw s2,16(sp) 40034296: c64e sw s3,12(sp) 40034298: 87a6 mv a5,s1 4003429a: 4701 li a4,0 4003429c: 00078697 auipc a3,0x78 400342a0: 1c868693 addi a3,a3,456 # 400ac464 <src.1> 400342a4: 00078617 auipc a2,0x78 400342a8: 1e060613 addi a2,a2,480 # 400ac484 <aic_audio_ops> 400342ac: 4298 lw a4,0(a3) 400342ae: c398 sw a4,0(a5) 400342b0: 0791 addi a5,a5,4 400342b2: 0691 addi a3,a3,4 400342b4: fec6cce3 blt a3,a2,400342ac <main+0x28>
发现编译器将 my_memcpy1 函数进行了编译优化,进行了inline 处理,修改my_memcpy1 函数的编译属性__attribute__((noinline))查看反汇编代码如下。
40034264 <my_memcpy1>: 40034264: 00c506b3 add a3,a0,a2 //end = src + len 40034268: 4701 li a4,0 //tmp = 0 4003426a: 87aa mv a5,a0 //a5 = src 4003426c: 4398 lw a4,0(a5) //tmp = *src 4003426e: c198 sw a4,0(a1) //*dst = tmp 40034270: 0591 addi a1,a1,4 //dst += 4 40034272: 0791 addi a5,a5,4 //src += 4 40034274: fed7cce3 blt a5,a3,4003426c <my_memcpy1+0x8>//if(src < end) goto 4003426c 40034278: 8532 mv a0,a2 4003427a: 8082 ret
从上述反汇编代码可以看出编译器对寄存器分区配情况传递情况,也是符合RISC-V ABI 接口调用规范
src 通过 a0/a5 传递 |
dst 通过 a1 传递 |
len 通过 a2 传递 |
将end 变量分配了a3寄存器 |
tmp 变量分配了a4 寄存器 |
返回值通过 a0 返回 |
内嵌汇编代码验证3
在方式2 所使用的内嵌汇编代码中使用 0% 1% 2% 这种方式来表示参数,相对直接使用符号表示变量的方式不够直接,Gcc 内嵌汇编也是支持用符号明来代替%0 %1 %2,以下是Gcc 对符号的使用示例代码。
uint32_t Mask = 1234; uint32_t Index; asm ("bsfl %[aMask], %[aIndex]" : [aIndex] "=r" (Index) : [aMask] "r" (Mask) : "cc"); Here are some more examples of output operands. uint32_t c = 1; uint32_t d; uint32_t *e = &c; asm ("mov %[e], %[d]" : [d] "=rm" (d) : [e] "rm" (*e));
参照上述代码,我们在方式2的基础上继续修改代码。
static __attribute__((noinline)) int my_memcpy2(void * src,void * dst,int len) { unsigned int tmp = 0; unsigned int end = (char *)src + len; asm volatile ( "1: lw %[tmp], (%[src])\n" "sw %[tmp], (%[dst])\n" "addi %[dst], %[dst], 4\n" "addi %[src], %[src], 4\n" "blt %[src], %[end], 1b" : [dst] "+r" (dst),[tmp] "+r" (tmp),[src] "+r" (src) : [end] "r" (end) : "memory"); return len; }
将之前的 %0 使用 [dst] 来代替,其它% 也替换成对应的符号这样相对内嵌汇编代码阅读起来相对更容易些,不过本质还是一样的上述代码对应的反汇编代码和方式2是一致。