【RISC-V操作系统】从零开始写一个操作系统(六)RISCV汇编语言编程

一、来写第一个算术运算指令

指的就是加减。

ADD:Rtype,寄存器类型, 例子:

add x5,x6,x7 
x5=x6+x7

指令结构之前已经说过:

opcode为op,rs1,rs2为源寄存器地址(例子里为6:00110和7:00111),rd为目标位置寄存器地址(例子里为5:00101),这里可以理解为rd=rs1+rs2。 

funct3取值000,funct7取值0000000,和opcode:0110011一起决定了指令类别。

 可以观察到add和sub的区别就在funct7。

最后例子里的指令在内存中:

add x5,x6,x7 
内容为:
0000000 00111 00110 000 00101 0110011
但还要注意字节序,因为有四个字节。
转成16进制方便阅读:
0x007302B3
高地址 00 73 02 B3 低地址 
如果字节这样排,那就是小端排序。

对照指令结构自己分析一下即可。这就是add指令。

后面可以来练习写一段汇编来模拟运行并调试。

二.汇编和调试的相关文件

makefile定义的make过程,这个文件的作用可以看第五篇文章。

# Add
# Format:
#	ADD RD, RS1, RS2
# Description:
#	The contents of RS1 is added to the contents of RS2 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
	add x5, x6, x7		# x5 = x6 + x7

stop:
	j stop			# Infinite loop to stop execution

	.end			# End of file

 rule.mk:

下面这段读到调试再来看:

include ../../common.mk

.DEFAULT_GOAL := all
all:
	@${CC} ${CFLAGS} ${SRC} -Ttext=0x80000000 -o ${EXEC}.elf
	@${OBJCOPY} -O binary ${EXEC}.elf ${EXEC}.bin

.PHONY : run
run: all
	@echo "Press Ctrl-A and then X to exit QEMU"
	@echo "------------------------------------"
	@echo "No output, please run 'make debug' to see details"
	@${QEMU} ${QFLAGS} -kernel ./${EXEC}.elf

.PHONY : debug
debug: all
	@echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU"
	@echo "-------------------------------------------------------"
	@${QEMU} ${QFLAGS} -kernel ${EXEC}.elf -s -S &
	@${GDB} ${EXEC}.elf -q -x ${GDBINIT}

.PHONY : code
code: all
	@${OBJDUMP} -S ${EXEC}.elf | less

.PHONY : hex
hex: all
	@hexdump -C ${EXEC}.bin

.PHONY : clean
clean:
	rm -rf *.o *.bin *.elf
include …/…/common.mk

这一行告诉make包含一个叫做common.mk的文件,包含一些通用的变量或规则。

.DEFAULT_GOAL := all all: @${CC} ${CFLAGS} ${SRC} -Ttext=0x80000000 -o ${EXEC}.elf @${OBJCOPY} -O binary ${EXEC}.elf ${EXEC}.bin

这一部分定义了一个默认的目标all,它会在没有指定其他目标时运行。all目标使用CC编译器和CFLAGS选项编译SRC源文件,并使用-Ttext=0x80000000指定程序加载地址为0x80000000,生成一个可执行文件EXEC.elf。然后使用OBJCOPY工具将可执行文件转换为二进制文件EXEC.bin。

.PHONY : run run: all @echo “Press Ctrl-A and then X to exit QEMU” @echo “------------------------------------” @echo “No output, please run ‘make debug’ to see details” @${QEMU} ${QFLAGS} -kernel ./${EXEC}.elf

这一部分定义了一个伪目标run,它会在运行all目标后运行。run目标使用echo命令打印一些提示信息,然后使用QEMU模拟器和QFLAGS选项加载并运行可执行文件作为内核。

.PHONY : debug debug: all @echo “Press Ctrl-C and then input ‘quit’ to exit GDB and QEMU” @echo “-------------------------------------------------------” @${QEMU} ${QFLAGS} -kernel ${EXEC}.elf -s -S & @${GDB} ${EXEC}.elf -q -x ${GDBINIT}

这一部分定义了一个伪目标debug,它会在运行all目标后运行。debug目标使用echo命令打印一些提示信息,然后使用QEMU模拟器和QFLAGS选项加载并运行可执行文件作为内核,并开启-s和-S选项以便于调试。同时,在后台启动GDB调试器,并加载可执行文件和GDBINIT脚本。

.PHONY : code code: all @${OBJDUMP} -S ${EXEC}.elf | less

这一部分定义了一个伪目标code,它会在运行all目标后运行。code目标使用OBJDUMP工具反汇编可执行文件,并显示其源代码和汇编代码,并使用less命令进行分页浏览。

.PHONY : hex hex: all @hexdump -C ${EXEC}.bin

这一部分定义了一个伪目标hex,它会在运行all目标后运行。hex目标使用hexdump工具显示二进制文件的十六进制格式。

.PHONY : clean clean: rm -rf *.o *.bin *.elf

这一部分定义了一个伪目标clean,它会在指定为参数时运行。clean目标使用rm命令删除当前目录下所有的对象文件、二进制文件和可执行文件。

.PHONY指令告诉make这些目标不是实际的文件,而只是命令的名称。这样可以防止make跳过它们如果有同名的文件存在。

更细节的:

这里的debug为target,对象为all,下方使用的 :

        @{QEMU} ${QFLAGS} -kernel ${EXEC}.elf -s -S &
        @${GDB} ${EXEC}.elf -q -x ${GDBINIT}

是调试的内容也就是command, -s选项 和-S选项的意思可以了解一下

在 QEMU 中,"-s"选项表示启用一个GDB调试服务器,可以让GDB连接到QEMU模拟的目标机器并对其进行调试。具体来说,该选项会在QEMU中打开一个监听端口(默认是1234),等待来自GDB的连接请求。

一旦GDB连接到了QEMU模拟的目标机器,就可以使用GDB进行源代码级别的单步调试、断点设置、内存查看等操作。

而"-S"选项则表示在启动时暂停目标机器的执行,等待调试器连接。这个选项通常和"-s"一起使用,这样可以让GDB在连接到目标机器之前,先暂停目标机器的执行,等待调试器的指令。

使用"-s"和"-S"选项,可以方便地进行QEMU虚拟机的调试工作,特别是对于操作系统内核或嵌入式系统的开发者而言,这是非常有用的功能。

这就是我们交叉调试的开发环境。

@符号是一个shell命令行中的特殊字符,用于执行命令并将其输出作为另一个命令的输入。
在这个例子中,@符号用于执行QEMU和GDB命令。

&符号是一个shell命令行中的特殊字符,用于将命令放入后台执行,从而使终端不会被阻塞。
在这个例子中,它用于将QEMU放入后台执行,以便可以在GDB中进行调试。

-q选项是GDB的一个选项,它表示安静模式,即在运行GDB时不显示一些冗余的信息。

-x选项是GDB的一个选项,它允许在启动GDB时自动执行一些命令。
在这个例子中,它用于自动加载一个包含GDB命令的文件${GDBINIT},
从而在启动GDB时自动执行这些命令。

common.mk: 

CROSS_COMPILE = riscv64-unknown-elf-
CFLAGS = -nostdlib -fno-builtin -march=rv32ima -mabi=ilp32 -g -Wall

QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 1 -machine virt -bios none

GDB = gdb-multiarch
CC = ${CROSS_COMPILE}gcc
OBJCOPY = ${CROSS_COMPILE}objcopy
OBJDUMP = ${CROSS_COMPILE}objdump

解释:-smp,单核。-nographic,无图像界面,-machine virt,一种机器类型。

QEMU被赋值:qemu-system-riscv32gdbinit文件: 

display/z $x5
display/z $x6
display/z $x7

set disassemble-next-line on
b _start
target remote : 1234
c

具体内容什么含义我也不多说。b,c是断点,用于gdb调试。
set disassemble-next-line on是设置汇编级别的单步调试模式。display就是调试时会把这三个寄存器展示出来。

那么makefile的内容就讲到这,回到汇编。

# Add
# Format:
#	ADD RD, RS1, RS2
# Description:
#	The contents of RS1 is added to the contents of RS2 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
	add x5, x6, x7		# x5 = x6 + x7

stop:
	j stop			# Infinite loop to stop execution

	.end			# End of file

分析注释,对应上一节讲的汇编规则,.text属于指示(directive),.global _start 就是指示一个全局变量,前面在gdbinit文件里也有影子:

_start属于什么,标签,对应的后面三行汇编则是具体汇编指令。stop同理。.end也是指示,给汇编器看的。

j stop则是跳转到执行stop,自己跳转自己然后循环跳转,相当于死循环。

现在准备开始调试可以回去看刚刚的rule.mk文件的介绍了。

三、调试

 在这里我遇到了一个问题,起初是make时出现:

 查看源文件:

 后面解决的方法是使用ubuntu系统重新下载解压得到了和windows下解压不同的文件。

 可以看到这里makefile的实际内容还是指向build.mk但是由于Ubuntu与Windows解压方式可能有区别,导致得到的makefile实际文件不同,另外所谓缺失分割符的问题也可能是windows下空格与tab的混淆导致的。

 最后make生成了两个文件。一个elf文件一个bin文件,16字节的bin文件事实上就是:

这里起到的作用, 把无关的内容剥离了(调试等信息)

 四条指令, ,每条指令32位四字节也就是最后16字节。

 执行make code反汇编:

 那么对这个汇编程序汇编得到的bin(机器指令)就介绍到这。

接下来运行:make debug:

如果是通过命令行安装各个程序并且没有下载工具包的,这里不会出现问题,如果下载了工具包可能会出一些问题,所以我一开始就推荐大家使用apt安装的方式。大家到这里做理论学习,开发环境也可也重新配置一遍,后面我可能会录屏给大家来一遍。总之不要钻牛角尖,学到东西才是最重要的。

可以看到断点在_start处:

 至于x5为什么是0x80000000,也暂且不提,这里我们看到x6是0,断点下一句就是给x6赋值,我们输入si进行单步调试。(single instruction)

 可以看到x6已经变成1了,下一步就是x7赋值了,直接敲回车就会重复si了。后续:

 x5最后为3,并且得到下一步是无限死循环的提示。后面再按回车也无法退出,只能ctrl+C再输入quit。

 调试和运行就写到这。

四、拓展必要概念

无符号数和有符号数

这个是很古老的问题了,计组里应该有讲到,这里给大家再提一下,也就是说,有符号的数字,表达范围绝对值就会缩小。因为要拿一位作为符号位。另外在计算机中有符号数用补码表示,这样才能进行快速的加减,这个也是计组内容,补码是什么,如果不清楚的小伙伴建议恶补计组知识。

符号拓展和零拓展

符号拓展就是比如-4的补码为1111100,而零拓展就是10000100。想知道具体一点可以自行了解。

后面自己尝试一下add加法负数的汇编:

# Add
# Format:
#	ADD RD, RS1, RS2
# Description:
#	The contents of RS1 is added to the contents of RS2 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
	add x5, x6, x7		# x5 = x6 + x7

stop:

	j stop			# Infinite loop to stop execution

	.end			# End of file

这一篇就到这里,下一篇继续。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值