无法完全理解有关重定位在 u-boot 中如何工作的汇编代码

Can't exactly understand the assembly code about how relocation works in u-boot

在 u-boot link 脚本中我看到了这些行(https://source.denx.de/u-boot/u-boot/-/blob/v2021.10/arch/arm/cpu/armv8/u-boot.lds 靠近第 134 行)。

.rel_dyn_start :
{
    *(.__rel_dyn_start)
}

.rela.dyn : {
    *(.rela*)
}

.rel_dyn_end :
{
    *(.__rel_dyn_end)
}

这是一个做搬迁的代码。 (https://source.denx.de/u-boot/u-boot/-/blob/v2021.10/arch/arm/cpu/armv8/start.S 靠近第 88 行)
但我不能完全理解以下几行。

    adrp    x2, __rel_dyn_start     /* x2 <- Runtime &__rel_dyn_start */
    add     x2, x2, #:lo12:__rel_dyn_start
    adrp    x3, __rel_dyn_end       /* x3 <- Runtime &__rel_dyn_end */
    add     x3, x3, #:lo12:__rel_dyn_end
pie_fix_loop:
    ldp x0, x1, [x2], #16   /* (x0, x1) <- (Link location, fixup) */
    ldr x4, [x2], #8        /* x4 <- addend */
    cmp w1, #1027       /* relative fixup? */                         // <==== from here??
    bne pie_skip_reloc
    /* relative fix: store addend plus offset at dest location */
    add x0, x0, x9
    add x4, x4, x9
    str x4, [x0]
pie_skip_reloc:
    cmp x2, x3
    b.lo    pie_fix_loop

我的第一个问题是关于 adrp x2, __rel_dyn_start 说明。
从 linker 脚本中我看到 .__rel_dyn_start 是一个部分的名称。但是我找不到名为 __rel_dyn_start(没有点)的变量。它来自哪里?

我的第二个问题是关于 cmp w1, #1027 中的代码(上面标记为 <==== from here??)。
我想此时,x0x1x4 包含 .__rel_dyn_start 部分中的前三个 8 字节值(假设 __rel_dyn_start 是第一个组合 .rel_dyn_start 部分中的变量)。 x9 似乎包含重定位的偏移量。
那么在cmp w1, #1027中,为什么要比较w1(重定位段中第二个8字节值的低位部分(?))和#1027?如果相等,为什么要将重定位偏移量添加到x0x4并将复制的值x4存储到新地址x0? (也许 x0x4 包含某种地址)。如果不理解这一点,我将无法遵循下一个代码。

感谢您的阅读,如果有人能向我解释这背后的主要逻辑以及所有这些代码的作用,我将不胜感激。

  1. 我认为您正在寻找“u-boot-spl.lds”(https://source.denx.de/u-boot/u-boot/-/blob/v2021.10/arch/arm/cpu/u-boot-spl.lds)。
    至少该文件包含命名变量。
    .rel.dyn : {
        __rel_dyn_start = .;  // <<== assigned with starting address of section
        *(.rel*)
        __rel_dyn_end = .;
    }

2.我不知道,只能推测。
整个循环看起来像是将数据从原始位置复制到 'relocated' 区域,其他人会在那里寻找它。分别由于位置不同地址分别调整。
`#1027` 可以随意选择。或者这可能是可以编码到指令中并根据该限制进行选择的最大相对偏移量。
不过,用一勺盐服用。

我想现在我可以理解代码的作用了(对于缺少的 __rel_dyn_start 和 __rel_dyn_end 变量,请参阅 user3124812 的回答和我对它的评论)。
我在https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/elf.h#L182中找到了这个Elf64_Rela结构

typedef struct elf64_rela {
  Elf64_Addr r_offset;  /* Location at which to apply the action */
  Elf64_Xword r_info;   /* index and type of relocation */
  Elf64_Sxword r_addend;    /* Constant addend used to compute value */
} Elf64_Rela;

r_offset是需要重定位的地址(地址应该改为指向内存中的实际位置,它们是代码中的引用),r_info是重定位类型(doc说许多类型,arch specific..)并且 r_addend 是添加到地址 r_offset 中的值(符号引用)的值。 linker 只知道段中的相对地址,但动态加载器应该在程序加载到内存后为其添加更多值,因为加载的地址并不总是与 link 相同-时间地址。
Elf64_structure 似乎在 __rel_dyn 部分重复了所有需要重定位的符号。代码将此结构读取到 x0、x1 和 x4 寄存器,分别用于 r_offset、r_info 和 r_addened。如果 rinfo 的低 16 位(w1,即 x1 的低 16 位)是#1027(我猜它表示相对修复类型,如评论所述),则符号引用位置将被覆盖(link时间偏移量 r_addend,这是在构建程序时确定的,相对于段开始 0) +(link 时间地址和实际加载地址之间的偏移量)。代码对 rel_dyn 部分中的所有 Elf64_Rela 数据重复此操作(x2 从 __rel_dyn_start 开始直到到达 x3,__rel_dyn_end)。
所以程序实际上并没有被复制到新位置,只是所有的引用都被覆盖以匹配 link 时间地址与实际执行时间地址。
上面的代码是针对 PIE(位置独立可执行文件)的,因此只有引用被覆盖以匹配加载后的加载地址,代码位于加载的位置。但是在 arch/arm/lib/relocate_64.S 中,还有另一个代码真正将代码从 SRAM(=onchip ram)复制(重新定位)到 SDRAM(=DDR)中的新位置,这是在 SDRAM 初始化之后完成的。这里的代码实际上是被复制的,.rel.dyn 数据(关于引用的信息)被调整得和重定位一样多。(将{existing added + added offset}写入每个相对引用的新位置)