简介
内嵌汇编代码是指在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是一致。
以下是lib.a 中的memcpy 函数的obj 代码,可以用来学习 RISC-V 汇编指令
lib_a-memcpy.o: file format elf32-littleriscv Disassembly of section .text.memcpy: 00000000 <memcpy>: 0: 00a5c7b3 xor a5,a1,a0 4: 8b8d andi a5,a5,3 6: 00c50733 add a4,a0,a2 a: e781 bnez a5,12 <.L2> c: 478d li a5,3 e: 00c7ee63 bltu a5,a2,2a <.L3> 00000012 <.L2>: 12: 87aa mv a5,a0 14: 0ae57063 bgeu a0,a4,b4 <.L19> 00000018 <.L5>: 18: 0005c683 lbu a3,0(a1) 0000001c <.LVL3>: 1c: 0785 addi a5,a5,1 1e: 0585 addi a1,a1,1 20: fed78fa3 sb a3,-1(a5) 00000024 <.LBE2>: 24: fee7eae3 bltu a5,a4,18 <.L5> 28: 8082 ret 0000002a <.L3>: 2a: 00357693 andi a3,a0,3 2e: 87aa mv a5,a0 00000030 <.L20>: 30: ca91 beqz a3,44 <.L7> 00000032 <.LBB3>: 32: 0005c683 lbu a3,0(a1) 00000036 <.LVL8>: 36: 0785 addi a5,a5,1 38: 0585 addi a1,a1,1 3a: fed78fa3 sb a3,-1(a5) 0000003e <.LBE3>: 3e: 0037f693 andi a3,a5,3 00000042 <.LVL11>: 42: b7fd j 30 <.L20> 00000044 <.L7>: 44: ffc77693 andi a3,a4,-4 00000048 <.LVL13>: 48: fe068613 addi a2,a3,-32 0000004c <.L21>: 4c: 06c7f063 bgeu a5,a2,ac <.L11> 00000050 <.LBB4>: 50: 0005a383 lw t2,0(a1) 00000054 <.LVL16>: 54: 0045a283 lw t0,4(a1) 00000058 <.LVL17>: 58: 0085af83 lw t6,8(a1) 0000005c <.LVL18>: 5c: 00c5af03 lw t5,12(a1) 00000060 <.LVL19>: 60: 0105ae83 lw t4,16(a1) 00000064 <.LVL20>: 64: 0145ae03 lw t3,20(a1) 00000068 <.LVL21>: 68: 0185a303 lw t1,24(a1) 0000006c <.LVL22>: 6c: 01c5a883 lw a7,28(a1) 00000070 <.LVL23>: 70: 02458593 addi a1,a1,36 00000074 <.LVL24>: 74: 0077a023 sw t2,0(a5) 78: ffc5a803 lw a6,-4(a1) 0000007c <.LVL25>: 7c: 0057a223 sw t0,4(a5) 00000080 <.LVL26>: 80: 01f7a423 sw t6,8(a5) 00000084 <.LVL27>: 84: 01e7a623 sw t5,12(a5) 00000088 <.LVL28>: 88: 01d7a823 sw t4,16(a5) 0000008c <.LVL29>: 8c: 01c7aa23 sw t3,20(a5) 00000090 <.LVL30>: 90: 0067ac23 sw t1,24(a5) 00000094 <.LVL31>: 94: 0117ae23 sw a7,28(a5) 98: 02478793 addi a5,a5,36 0000009c <.LVL32>: 9c: ff07ae23 sw a6,-4(a5) a0: b775 j 4c <.L21> 000000a2 <.L12>: a2: 4190 lw a2,0(a1) 000000a4 <.LVL34>: a4: 0791 addi a5,a5,4 a6: 0591 addi a1,a1,4 a8: fec7ae23 sw a2,-4(a5) 000000ac <.L11>: ac: fed7ebe3 bltu a5,a3,a2 <.L12> 000000b0 <.LVL38>: b0: f6e7e4e3 bltu a5,a4,18 <.L5> 000000b4 <.L19>: b4: 8082 ret