第 五 章 RISC-V 汇编语言编程
RISC-V 汇编语言入门
练习5.1
test.s如下:
# Substract
# Format:
# SUB RD, RS1, RS2
# Description:
# The contents of RS2 is subtracted from the contents of RS1 and the result
# is placed in RD.
.text # Define beginning of text section
.global _start # Define entry _start
_start:
li x6, -1 # x6 = -1
li x7, -2 # x7 = -2
sub x5, x6, x7 # x5 = x6 - x7
stop:
j stop # Infinite loop to stop execution
.end # End of file
反汇编结果如下:sub x5, x6, x7的机器码为407302b3
二进制为:01000000 01110011 00000010 10110011 查表对应最高7位和最低7位可以确定为sub指令。则可以对应找出rs2为x7,rs1为x6,rd为x5 ,则指令为sub x5,x6,x7
同理,指令b3 05 95 00,从左往右为从低地址到⾼地址,则翻译为2进制为:
00000000 10010101 00000101 10110011 查表可以确定为add指令
rs2为x9,rs1为x10,rd为x11 指令为add x11,x10,x9
练习5.2
寄存器对应名称如表所示 通过gdb单步执行可以看到寄存器的值的变化ra,sp,gp,tp,t0
练习5.4
通用寄存器大小也是32位的,因此可以先将一个32位的数存储起来,再分别与0xffff和0xffff0000进行与操作,将低16位和高16位分离。
# int a = 0x87654321;
# int b = a & 0xffff;
# int c = a & 0xffff0000;
.text # Define beginning of text section
.global _start # Define entry _start
_start:
li x5, 0x87654321 # x5 = 0x87654321
li x1, 0xffff # x1 = 0xffff
li x2, 0xffff0000 # x2 = 0xffff0000
and x6, x5, x1 # x6 = x5 & 0xffff
and x7, x5, x2 # x7 = x5 & 0xffff0000
stop:
j stop # Infinite loop to stop execution
.end # End of file
debug结果如下,可以看到ra,sp等寄存器的变化。
练习5.5
汇编代码如下: 注意因为数组里面的数占了32位,因此初始化数组时,前面的提示符要写成.word
不然会有如下报错,原数字被截断了。
.text # Define beginning of text section
.global _start # Define entry _start
_start:
la x5, _array # x5 = &(_array)
lw x6, 0(x5) # x6 = array[0]
lw x7, 4(x5) # x7 = array[1]
stop:
j stop # Infinite loop to stop execution
_array:
.word 0x11111111
.word 0xffffffff
.end # End of file
test.s: Assembler messages:
test.s:24: Warning: value 0x11111111 truncated to 0x11
test.s:25: Warning: value 0xffffffff truncated to 0xff
相应的,使用读内存指令时,也要使用lw,才可以将数组里的数完整的读出来。以及注意lw的立即数的使用,在这里是每隔4字节读取一次(32位)
在调试过程中,可能是由于出现警告等原因,退出qemu没有杀死其进程,采用命令行
找到进程号,杀死进程。
最终调试结果如下:
练习5.6
主要学习了RISCV汇编中宏定义及使用,结构体的定义(这里我仍然将结构体看作里面元素不统一的数组,直接分配内存即可)
汇编代码如下:
.text # Define beginning of text section
.global _start # Define entry _start
.macro set_struct a, b
sw \a, 0(x5)
sw \b, 4(x5)
.endm
.macro get_struct c, d
lw \c, 0(x5)
lw \d, 4(x5)
.endm
_start:
la x5, _struct
sw x0, 0(x5)
li x6, 0x12345678
li x7, 0x87654321
set_struct x6, x7
li x6, 0x0
li x7, 0x0
get_struct x6, x7
stop:
j stop # Infinite loop to stop execution
_struct:
.word
.word
.end # End of file
注意宏定义时,里面的宏展开对参数前面要加转义符号'\'
最终调试结果如下:
练习5.7
汇编代码如下:空字符的ASCII码为0
.data
array:
.byte 'h', 'e', 'l', 'l', 'o', ',', 'w', 'o', 'r', 'l', 'd', '!', 0
.space 13
.text # Define beginning of text section
.global _start # Define entry _start
_start:
# len = 0
# while (array[len] != '\0') len++;
la x5, array
li x6, 0
li x7, 0
loop:
lb x8, 0(x5)
addi x5, x5, 1
addi x6, x6, 1
bne x8, x7, loop
addi x6, x6, -1
stop:
j stop # Infinite loop to stop execution
.end # End of file
调试结果如下:
可以看到数组地址从0x80001208开始,到0x80001035结束。里面每个字节存的是字符对应的ASCII码数值。
最后寄存器X5的值为0x80001035,数组长度存在寄存器X6中,数组长为12
RISC-V 汇编函数调用约定
1、函数调用过程概述
系统在调用一个进程时,会分配一整块栈区给该进程,当进程里面调用函数时,会为调用的函数分配一块函数栈,当函数退出时则退栈,释放空间。函数栈中一般保存的是自动变量,生命周期随函数产生和消失。其它的全局变量等数据是存在与ELF文件中的段里,例如.data等。
2、函数调用过程中的编程约定
首先是要对函数调用过程中使用的一些寄存器进行编程约定,因为往往函数的调用者和被调用者往往不是同一个用户,若不对相应的寄存器进行规定会出现更多的麻烦,例如覆盖寄存器中的正确信息或找不到正确寄存器中的值。
在函数调用过程中,除了函数执行体部分,还会由汇编器自动生成函数起始部分(Prologue)和函数退出部分(Epilogue),分别用来开辟栈空间和释放栈空间。
练习5.8
汇编代码如下:
.text # Define beginning of text section
.global _start # Define entry _start
_start:
la sp, stack_end # prepare stack for calling functions
# sum_squares(3);
li a0, 3
call sum_squares
stop:
j stop # Infinite loop to stop execution
#unsigned int sum_squares(unsigned int i)
#{
# unsigned int sum = 0;
# for (int j = 1; j <= i; j++) {
# sum += square(j);
#}
# return sum;
#}
sum_squares:
# prologue
addi sp, sp, -20
sw s0, 0(sp)
sw s1, 4(sp)
sw s2, 8(sp)
sw s3, 12(sp)
sw ra, 16(sp)
# cp and store the input params
addi s0, a0, 1 #i+1
# sum will be stored in s1 and is initialized as zero
li s1, 0 #sum = 0
li s2, 1 #j = 1
li s3, 0
loop:
addi s3, s2, -1 #0 1 2 3
mv a0, s3
jal square
add s1, s1, a0 #sum += square(j)
addi s2, s2, 1 #j++
ble s2, s0, loop # 2 3 4 5
mv a0, s1
# epilogue
lw s0, 0(sp)
lw s1, 4(sp)
lw s2, 8(sp)
lw s3, 12(sp)
lw ra, 16(sp)
addi sp, sp, 20
ret
# unsigned int square(unsigned int num)
square:
# prologue
addi sp, sp, -8
sw s0, 0(sp)
sw s1, 4(sp)
# `mul a0, a0, a0` should be fine,
# programing as below just to demo we can contine use the stack
mv s0, a0
mul s1, s0, s0
mv a0, s1
# epilogue
lw s0, 0(sp)
lw s1, 4(sp)
addi sp, sp, 8
ret
# add nop here just for demo in gdb
nop
# allocate stack space
stack_start:
.rept 12
.word 0
.endr
stack_end:
.end # End of file
调试过程如下:
执行call sum_squares 后,ra变成call下一条指令的地址,即0x8000000c + 4 = 0x80000010
进入sum_squares函数的Prologue部分,开辟函数栈
依次保存了s0-s3和ra
随后,开始循环调用square函数,相应的用ra保存了jar square的下一条指令的地址,因此如果 sum_squares函数一开始没有将ra的内容保存在函数栈中,则ra原来的值丢失,则sum_squares返回不到主函数了。
每次执行完square函数后,返回回来刚好在jar square的下一条指令的地址
最终结果:a0 = 14
RISC-V 汇编与 C 混合编程
练习5.9
int foo(int a, int b)
{
int c;
c = a * a + b * b;
asm volatile(
"mul a1, %1, %1;\
mul a2, %2, %2;\
add %0, a1, a2"
:"=r" (c)
:"r" (a), "r" (b)
);
return c;
}
反汇编结果如下:
最终结果应该是寄存器a0和a5存储着最终结果,且a1和a2存储着中间结果