循序渐进,学习开发一个RISC-V 上的操作系统

第 五 章 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为x7rs1为x6rd为x5 ,则指令为sub x5,x6,x7

 

 同理,指令b3 05 95 00,从左往右为从低地址到⾼地址,则翻译为2进制为:

00000000 10010101 00000101 10110011 查表可以确定为add指令

rs2为x9rs1为x10rd为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

注意宏定义时,里面的宏展开对参数前面要加转义符号'\'

https://stackoverflow.com/questions/66319166/how-to-write-riscv-csr-in-an-assembly-macro-taking-immediate-values-as-parameter

最终调试结果如下:

练习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存储着中间结果

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值