table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
pre {
background-color: #f8f8f8;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}
1、如何设置 RISC – V 开发环境,包括在像 Starfive Visionfive 2 这样的 Linux 单板计算机(SBC)上、在 QEMU 模拟器中运行以及使用像 Espressif ESP32 – C3 这样的微控制器的情况?
设置 RISC-V 开发环境的方式
有以下几种设置 RISC-V 开发环境的方式:
1. 在基于 RISC-V 的 Linux 计算机(如 Starfive Visionfive 2)上
特点
:这是使用 RISC-V 汇编语言最简单的方式,所有操作都在同一台计算机上完成。
硬件支持
:Visionfive 2 板载 CPU 支持 RV64GC 系列 RISC-V 扩展。
所需准备
:
Starfive Visionfive 2 板
USB 键盘
USB 鼠标
支持 HDMI 的显示器
microSD 卡
烧录程序(如 Balena Etcher)
安全外壳(SSH)程序
网络连接
启动方式
:Visionfive 2 可从 microSD 卡、M.2 SSD 驱动器或其内部固件运行 Linux。
2. 在基于 Intel/AMD 的 Windows 或 Linux 计算机上运行 RISC-V 模拟器
特点
:可以模拟运行完整 Linux 的 RISC-V 环境。
示例工具
:使用 QEMU 模拟器。
操作步骤
:在 Windows 上安装 QEMU 并启动 RISC-V 版本的 Linux 到首次登录提示。
所需准备
:
QEMU 软件
能处理 xz 格式的解压缩程序(如 7-Zip)
3. 使用 RISC-V 微控制器(如 Espressif ESP32-C3 DevKit)
操作流程
:
1. 先编写程序
2. 然后在另一台计算机(如 Linux 笔记本电脑)上编译
3. 最后将编译好的程序下载到微控制器上运行
限制
:会局限于几个基本指令模块(即 RV32IMC)。
2、将 “Hello RISC-V World!” 字符串修改为不同的字符串。若使用的是 Linux 版本,记得同时修改字符串长度。
对于 Linux 版本,修改代码中 `.data` 段里 `helloworld` 的字符串内容,如将 `.ascii "Hello RISC-V World!
"` 改为 `.ascii "New String Here!
"`,同时修改 `addi a2, x0, 20` 中的长度值,这里新字符串长度是 16,则改为 `addi a2, x0, 16`;对于 Espressif 版本,修改代码中 `.data` 段里 `helloworld` 的字符串内容,如将 `.asciz "Hello RISC-V World!"` 改为 `.asciz "New String Here!"`。
3、计算 -79 和 -23 的 8 位二进制补码。
先将 79 转换为二进制为
01001111
,取反得到
10110000
,再加 1 得到
10110001
,所以 -79 的 8 位二进制补码是
10110001
;将 23 转换为二进制为
00010111
,取反得到
11101000
,再加 1 得到
11101001
,所以 -23 的 8 位二进制补码是
11101001
。
4、写出将十六进制数 0x23 左移三位后的字节表示。
将十六进制数
0x23
转换为二进制是
0010 0011
,左移三位后得到
1000 11000
,由于一个字节是 8 位,舍去溢出部分后为
0001 1000
,转换为十六进制是
0x18
。所以答案是
0x18
。
5、一个数左移4位得到0x60,求这个数是多少
要得到左移4位后是
0x60
的原始值,可将
0x60
右移4位。
0x60
转换为二进制是
01100000
右移4位后是
00000110
即
0x06
所以原始值是
0x06
。
6、说明从一种文件构建另一种文件的规则,以从 HelloWorld.S 文件构建 HelloWorld.o 文件,再从 HelloWorld.o 文件构建 HelloWorld 可执行文件为例,并给出通用的从 .S 文件构建 .o 文件的规则。
从
HelloWorld.S
构建
HelloWorld.o
:
使用命令
as -o HelloWorld.o HelloWorld.S
。
在 makefile 中规则为:
makefile
HelloWorld.o: HelloWorld.S
as -o HelloWorld.o HelloWorld.S
从
HelloWorld.o
构建
HelloWorld
可执行文件:
使用命令
ld -o HelloWorld HelloWorld.o
。
在 makefile 中规则为:
makefile
HelloWorld: HelloWorld.o
ld -o HelloWorld HelloWorld.o
通用规则:
定义从
.S
文件构建
.o
文件的规则,在 makefile 中为:
makefile
%.o : %.S
as $< -o $@
其中:
–
%.S
是通配符,表示任何
.S
文件;
–
$<
代表源文件;
–
$@
代表输出文件。
7、列出你要构建的目标以及它们所依赖的文件。
要构建的目标及依赖文件如下:
目标:
HelloWorld.o
,依赖文件:
%.S
(任意
.S
文件)
目标:
HelloWorld
,依赖文件:
HelloWorld.o
8、GNU Make examines the file date/times to determine what needs to be built.
GNU Make会检查文件的日期和时间,以确定需要构建哪些内容。
9、GNU Make issues the commands to build the components.
GNU Make 会发出命令来构建各个组件。
10、工具objdump可以显示目标文件或可执行文件中的内容,它可用于反汇编程序。假设存在一个可执行文件CodeSnippets,运行命令行:objdump -d CodeSnippets,该命令会使用伪指令和ABI寄存器名称输出汇编语言。要获取原始代码,请使用以下命令:objdump -d -M no-aliases,numeric CodeSnippets。请描述这两个命令的作用,并说明在示例可执行文件上使用这两个命令的目的。
以下是调整为 Markdown 格式的文本内容:
objdump -d CodeSnippets
命令的作用是使用伪指令和 ABI 寄存器名称输出
CodeSnippets
可执行文件的汇编语言。
objdump -d -M no-aliases,numeric CodeSnippets
命令的作用是获取
CodeSnippets
可执行文件的原始代码。
在示例可执行文件上使用这两个命令的目的是查看这两个命令输出结果的差异。
11、解释auipc/addi指令如何仅用两条32位指令构建任意32位地址。
以下是调整为 Markdown 格式的文本内容:
auipc
指令在其立即数参数中提供高 20 位,
addi
指令可从其立即数参数中添加剩余的 12 位,二者结合即可构建 32 位地址。例如使用 PC 相对寻址时:
auipc x5, %pcrel_hi(msg)
addi x5, x5, %pcrel_lo(label)
最终
x5
包含
msg
的地址。
12、为什么函数调用协议中有些寄存器需要调用者保存,有些需要被调用者保存?为什么不全部由一方保存呢?
若每次调用函数都重新加载所有寄存器,效率极低。因此制定规则,明确函数可使用的寄存器及保存责任。
若全部由调用者保存
会增加调用者负担
且每次调用都保存所有寄存器会浪费大量执行周期
若全部由被调用者保存
被调用者可能不清楚调用者后续是否还会使用某些寄存器
且也会增加被调用者的负担
结论:
所以根据寄存器的用途和使用场景,区分调用者和被调用者的保存责任,能在效率和功能实现上达到较好平衡。
13、另一种会在ebreak指令处引发异常的I型指令。如果在GDB中运行,执行这条指令就相当于设置一个断点。添加ebreak指令是在代码中设置断点的一种简单方法。请描述在main.S中添加ebreak语句进行调试的步骤,并说明为什么在GDB之外运行程序之前要移除ebreak指令。
在
main.S
中添加
ebreak
语句进行调试的步骤:
在
main.S
文件中合适的位置添加
ebreak
语句。
然后对代码进行编译。
接着在GDB下运行程序查看效果。
注意:在GDB之外运行程序之前要移除
ebreak
指令,是因为
ebreak
指令会引发异常,在GDB之外运行包含该指令的程序会导致程序崩溃。
14、并非所有设备交互都能通过读写文件来抽象。Linux 允许使用通用函数 ioctl 来定义特殊操作。对于网络接口,需要用 ioctl 控制的一些功能有哪些?
获取/设置 IP 地址,配置各种 TCP/IP 网络选项,如是否接收广播数据包。
15、为什么 Linux 认为访问 GPIO 控制器有危险,并将其使用权限限制为 root 用户?
访问
/dev/mem
功能强大,能访问所有内存和硬件设备,这是受限操作。病毒或其他恶意软件可能会利用此权限访问所有物理内存,所以 Linux 认为访问 GPIO 控制器危险,将使用权限限制为 root 用户。
16、要将两个64位数字相乘得到一个128位的乘积,对于有符号和无符号整数情况,都使用
mul
指令来获取乘积的低64位。为了证明这是可行的,举一个小例子:将两个4位数字相乘得到一个8位乘积。计算0xf乘以2。在有符号的情况下,0xf是 -1,乘积是 -2;在无符号的情况下,0xf是15,乘积是30。手动进行计算,以确保在两种情况下都能得到正确的结果。
mul
有符号情况:
0xf
表示 -1,-1 × 2 = -2;
无符号情况:
0xf
表示 15,15 × 2 = 30。
手动计算结果与题目描述一致,证明该方法可行。
17、创建一个用于矩阵乘法的 ESP32 – C3 项目。由于矩阵已经是 32 位整数,大部分代码无需更改。需要调整压入/弹出栈的寄存器大小,并将 main 函数改为 app_main。
创建该项目时,因矩阵为 32 位整数,多数代码可保持不变,不过要调整栈操作时寄存器的大小,同时把主函数名从
main
改为
app_main
。
18、在讨论启用栈金丝雀时的尾声代码时,指出指令:xor a5,a5,a4 若 a4 和 a5 相等,则会将 a5 置为零。查阅异或指令的逻辑规则,并说明其原理。
异或(XOR)指令对两个操作数的每一位进行按位异或运算,结果存于目标操作数。异或逻辑是:若两个参数中恰好有一个为真(1),则结果为真(1);否则为假(0)。当 `a4` 和 `a5` 相等时,它们对应位的值都相同,每一位进行异或运算时都不会出现恰好一个为 1 的情况,所以每一位运算结果都为 0,最终 `a5` 被置为零。
19、为名为 upper.c 的程序启用栈金丝雀。运行程序,观察它是否能正常工作以及是否能捕获栈溢出。
可以使用
gcc
命令行选项
-fstack-protector-all
来启用栈金丝雀功能。示例命令如下:
gcc -o uppercanary -fstack-protector-all -O3 upper.c
运行编译后的程序,输入较长的字符串来触发栈溢出。若程序输出:
*** stack smashing detected ***: terminated
则表明栈溢出被成功捕获。
20、在一些现有的示例程序中启用位置无关可执行文件(PIE)功能,以确保它们能正常运行。
要在现有示例程序中启用 PIE,需在
ld
命令的选项列表中添加
-pie
。例如,若原本的
ld
命令是:
ld -o creditcard creditcard.o upper.o
启用 PIE 后应变为:
ld -pie -o creditcard creditcard.o upper.o
另外,在 RISC-V 上安装 GNU 工具链时,需先运行以下命令:
cd /lib
sudo cp ld-linux-riscv64-lp64d.so.1 ld.so.1
之后再运行
make -B
等命令来编译程序。
21、始终开启最大保护并接受性能损失是最安全的方法吗?
不一定。开启所有缓冲区溢出保护技术,代码运行速度可能慢达50%,在某些应用或部分应用中可接受,但有些应用部分需要高性能才有竞争力或可用。
对于需大量优化的代码段,可在代码外设置一层或模块,对传入优化例程的数据进行清理和确保正确性,并通过代码和安全审查保障。
22、请编写一条指令,将寄存器t1的值加上立即数15后存储到寄存器t2中。
addi t2, t1, 15
23、关于栈操作代码编写,给出先将x5压入栈再从栈中弹出x5的示例代码
先压入再弹出
x5
的操作如下:
# 将x5压入栈
addi sp, sp, -16 # 栈指针下移,为x5分配空间,通常栈是向下生长的,所以是减16
sd x5, 0(sp) # 将x5的值保存到栈顶
# 从栈中弹出x5
ld x5, 0(sp) # 从栈顶加载x5的值到x5寄存器
addi sp, sp, 16 # 恢复栈指针
24、关于内存和硬件寄存器访问的描述是怎样的?
任何对物理内存和硬件寄存器的访问都是危险的,不建议这样做。安全的访问始终要通过强制执行 Linux 安全机制的设备驱动程序来进行。
25、使用RISC – V架构新版本中新增指令会有什么影响?
如果使用 RISC-V 架构新版本中新增的指令,当程序在使用该架构早期版本的任何 RISC-V 处理器上运行时,会导致
非法指令异常
。使用更新、更高级的指令可以提高性能并减小代码大小。
26、写出将0x4300右移五位后的字节。
下面是给定的【文本内容】:
0x4300
转换为二进制是
0100 0011 0000 0000
,右移五位后是
0000 0010 0001 1000
,转换为十六进制是
0x0218
。