疑问所在
在linux 启动时,在如下位置会将bootloader 传入的x0 - x3 参数保存到boot_args[] 变量中。代码如下:
/*
* Preserve the arguments passed by the bootloader in x0 .. x3
*/
preserve_boot_args:
mov x21, x0 // x21=FDT
adr_l x0, boot_args // record the contents of
stp x21, x1, [x0] // x0 .. x3 at kernel entry
stp x2, x3, [x0, #16]
dmb sy // needed before dc ivac with
// MMU off
mov x1, #0x20 // 4 x 8 bytes
b __inval_dcache_area // tail call
ENDPROC(preserve_boot_args)
其中adr_l x0, boot_args 命令就是获取boot_args 的地址并存到x0 寄存器。
adr_l 的定义在arch/arm64/include/asm/assembler.h 中,如下:
.macro adr_l, dst, sym
adrp \dst, \sym
add \dst, \dst, :lo12:\sym
.endm
将adr_l x0, boot_args 展开,如下:
adrp x0, boot_args
add x0, x0, :lo12:boot_args
即先获取boot_args 的页地址,再加上boot_args 的低12位,即得到boot_args 的地址。
看到这里我有如下两个疑问:
- 此时x0 保存的是物理地址还是虚拟地址?
- boot_args 定义在某个.c 中,此时mmu 未打开,页表也未建立,为什么adrp 指令能拿到boot_args 的地址?
adrp arm 官方文档
ARM 官方文档中对adrp 的解释如下
即,adrp 的作用是:
= (当前的 PC 地址所在page 的基地址) + (imm)
其中,imm 为label 所在page 地址 与 当前pc 的page 地址的差。
也就是说,label 在编译时,已经被替换成了一个偏移量。
adrp 实例
将vmlinux 反汇编如下:
ffff800011310020 <preserve_boot_args>:
ffff800011310020: aa0003f5 mov x21, x0
ffff800011310024: d00042a0 adrp x0, ffff800011b66000 <boot_args>
ffff800011310028: 91000000 add x0, x0, #0x0
ffff80001131002c: a9000415 stp x21, x1, [x0]
ffff800011310030: a9010c02 stp x2, x3, [x0,#16]
ffff800011310034: d5033fbf dmb sy
ffff800011310038: d2800401 mov x1, #0x20 // #32
ffff80001131003c: 17b65539 b ffff8000100a5520 <__inval_dcache_area>
...
ffff800011b66000 <boot_args>
d00042a0 这个指令,参考图“adrp 官方文档”,各个字段的值如下:
immhi = 0x215,immlo = 0x2
imm = ( (immhi << 2) | immlo ) << 12 = 0x856000
Xd = x0
adrp 指令所在的pc 为 0xffff800011310024
则指令 d00042a0 等效为:
x0 = pc & 0xfffffffffffff000 + imm
即
x0 = (0xffff800011310024 & 0xfffffffffffff000) + imm = 0xffff800011b66000
即 boot_args 的地址。
其实,preserve_boot_args 这个阶段,mmu 还没有打开,pc 并非链接地址 0xffff800011310024,而是一个物理地址。不过该指令与boot_args 地址的offset 是固定不变的,即imm,所以最终仍能得到正确的boot_args 的物理地址。
总结
adrp 在编译时,会记录label 地址与当前指令地址的offset。在运行时,pc + offset 就能得到label 的地址。
又因为arm64 指令长度的限制,所以adrp 这条指令得到的地址仅仅精确到了label 所在的页的基地址,需要配合ADD 加上label 的lo12 位才能得到准确地址。