Skip to content

对于 lec 02 的一些补充

链接时将 _start 标号放在 0x1c00_0000

对于裸机程序而言,有一个硬性要求:必须将 _start 所指向的指令放在 0x1c00_0000 处,否则程序是无法正常运行的。然而在工具链中无法手动指定一个标号的地址,因此为了能将 _start 放在代码的最前面,我们需要在链接时显式地将 start.o 写在前面:

$ ld start.o main.o -o main.elf

程序中使用 jirl 与 bl 指令的区别

jirlbl 指令均为“跳转并链接”指令,即跳转到目标地址并将跳转指令下一条指令的地址写入寄存器。但是对于目前这种小型的汇编程序,我们更推荐使用 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 的地址放在数据段,然后通过 pcaddu12ild.w 两条指令将其加载到寄存器 $t0 中,再进行跳转。

再看 bl 指令,在 start.S 中:

start.S
bl main

经过编译后,获取反汇编:

test.S
bl      364(0x16c) # 1c000194 <main>

bl 指令直接通过立即数表示了它和 main 的相对位置,仅通过这一条指令完成了从汇编语言到 C 语言函数的跳转。

对于短距离跳转,使用 bl 指令相对于 jirl 指令的优势为:

  • 指令数少,且没有发生访存
  • 对于分支预测器而言,bl 指令的相对跳转是直接包含在指令立即数中的,获取跳转目标地址很容易,对分支预测器更友好;而 jirl 指令需要等到读出寄存器中的值才能确定跳转目标地址