对于 lec 02 的一些补充
链接时将 _start 标号放在 0x1c00_0000
对于裸机程序而言,有一个硬性要求:必须将 _start
所指向的指令放在 0x1c00_0000
处,否则程序是无法正常运行的。然而在工具链中无法手动指定一个标号的地址,因此为了能将 _start
放在代码的最前面,我们需要在链接时显式地将 start.o
写在前面:
$ ld start.o main.o -o main.elf
程序中使用 jirl 与 bl 指令的区别
jirl
与 bl
指令均为“跳转并链接”指令,即跳转到目标地址并将跳转指令下一条指令的地址写入寄存器。但是对于目前这种小型的汇编程序,我们更推荐使用 bl
指令。
我们先来看对于 jirl
指令,编译器是如何处理的。在源文件 start.S
中:
start.S
la $t0, main
jirl $ra, $t0, 0
经过编译后,获取反汇编:
test.S
pcaddu12i $r12,0
ld.w $r12,$r12,1384(0x568)
jirl $r1,$r12,0
其中寄存器 $r12
在 LoongArch ABI 中即为 $t0
,$r1
则为 $ra
。编译器为了实现伪指令 la
,需要在链接时将 main
的地址放在数据段,然后通过 pcaddu12i
和 ld.w
两条指令将其加载到寄存器 $t0
中,再进行跳转。
再看 bl
指令,在 start.S
中:
start.S
bl main
经过编译后,获取反汇编:
test.S
bl 364(0x16c) # 1c000194 <main>
bl
指令直接通过立即数表示了它和 main
的相对位置,仅通过这一条指令完成了从汇编语言到 C 语言函数的跳转。
对于短距离跳转,使用 bl
指令相对于 jirl
指令的优势为:
- 指令数少,且没有发生访存
- 对于分支预测器而言,
bl
指令的相对跳转是直接包含在指令立即数中的,获取跳转目标地址很容易,对分支预测器更友好;而jirl
指令需要等到读出寄存器中的值才能确定跳转目标地址