目录
1. 前言
kernel版本:5.10
平台:arm64
本专题主要基于《arm64_linux head.S的执行流程》系列文章,前者是基于3.18,本专题针对的是内核5.10。主要分析head.S的执行过程。本文主要记录head.S的__primary_switch执行过程。
2. __primary_switch
2.1 保存设定值SCTLR_EL1_SET
#ifdef CONFIG_RANDOMIZE_BASE
mov x19, x0 // preserve new SCTLR_EL1 value
mrs x20, sctlr_el1 // preserve old SCTLR_EL1 value
#endif
上一步从__cpu_setup返回前,x0保存了SCTLR_EL1_SET,此处将x0保存到x19中,并将原来的sctlr_el1保存到x20中
2.2 __enable_mmu
adrp x1, init_pg_dir
bl __enable_mmu
__enable_mmu定义如下:
/*
* Enable the MMU.
*
* x0 = SCTLR_EL1 value for turning on the MMU.
* x1 = TTBR1_EL1 value
*
* Returns to the caller via x30/lr. This requires the caller to be covered
* by the .idmap.text section.
*
* Checks if the selected granule size is supported by the CPU.
* If it isn't, park the CPU
*/
SYM_FUNC_START(__enable_mmu)
mrs x2, ID_AA64MMFR0_EL1
ubfx x2, x2, #ID_AA64MMFR0_TGRAN_SHIFT, 4
cmp x2, #ID_AA64MMFR0_TGRAN_SUPPORTED
b.ne __no_granule_support
update_early_cpu_boot_status 0, x2, x3
adrp x2, idmap_pg_dir
phys_to_ttbr x1, x1
phys_to_ttbr x2, x2
msr ttbr0_el1, x2 // load TTBR0
offset_ttbr1 x1, x3
msr ttbr1_el1, x1 // load TTBR1
isb
msr sctlr_el1, x0
isb
/*
* Invalidate the local I-cache so that any instructions fetched
* speculatively from the PoC are discarded, since they may have
* been dynamically patched at the PoU.
*/
ic iallu
dsb nsh
isb
ret
SYM_FUNC_END(__enable_mmu)
__enable_mmu主要完成如下的工作:
- 将idmap_pg_dir的物理地址设置到TTBR0;idmap_pg_dir为kernel image一致性页表的起始虚拟地址;
- 将init_pg_dir的物理地址设置到TTBR1;init_pg_dir为kernel image页表的起始虚拟地址
- 用SCTLR_EL1_SET设置sctlr_el1来开启MMU
Documentation/arm64/memory.rst:
User addresses have bits 63:48 set to 0 while the kernel addresses have the same bits set to 1. TTBRx selection is given by bit 63 of the virtual address. The swapper_pg_dir contains only kernel (global) mappings while the user pgd contains only user (non-global) mappings.The swapper_pg_dir address is written to TTBR1 and never written to TTBR0.
如上由于idmap_pg页表中虚拟地址与物理地址相同,自然bit63为0,因此CPU访存时会使用TTBR0中保存的PGD页表基址;而init_pg页表中使用的虚拟地址是链接地址,自然bit63为1,因此CPU访存时会使用TTBR1中保存的PGD页表基址
2.3 __relocate_kernel
#ifdef CONFIG_RELOCATABLE
bl __relocate_kernel
#endif
__relocate_kernel定义如下(红色代码):
__relocate_kernel片段1
/*
* Iterate over each entry in the relocation table, and apply the
* relocations in place.
*/
ldr w9, =__rela_offset // offset to reloc table
ldr w10, =__rela_size // size of reloc table
__rela_offset和__rela_size在vmlinux.lds.S中定义,代表重定位段的起始地址和大小
#arch/arm64/kernel/vmlinux.lds.S
.rela.dyn : ALIGN(8) {
*(.rela .rela*)
}
__rela_offset = ABSOLUTE(ADDR(.rela.dyn) - KIMAGE_VADDR);
__rela_size = SIZEOF(.rela.dyn);
.rela.dyn区域的起始和结束的链接地址(相对于kernel image的偏移)分别保存在w9和w10
__relocate_kernel片段2
mov_q x11, KIMAGE_VADDR // default virtual offset
add x11, x11, x23 // actual virtual offset
add x9, x9, x11 // __va(.rela)
add x10, x9, x10 // __va(.rela) + sizeof(.rela)
x11保存了KIMAGE_VADDR地址,KIMAGE_VADDR为_text的链接地址,为kernel image的起始虚拟地址,通过与之间计算的x23(保存了2M对齐的偏移)相加,进行2M对齐;
x9保存了.rela.dyn区域的链接地址
x10保存了.rela.dyn区域的结束地址
__relocate_kernel片段3
0: cmp x9, x10
b.hs 1f
ldp x12, x13, [x9], #24 //获取entry的link addr和link flag
ldr x14, [x9, #-8] //获取entry的link value
cmp w13, #R_AARCH64_RELATIVE //判断link flag, 获取重定位类型
b.ne 0b
add x14, x14, x23 // relocate
str x14, [x12, x23] //执行重定位,用link value 修改link addr
b 0b //通过循环将所有的动态链接符号执行重定位
1:
#endif
ret
通过循环将可所有的动态链接符号执行重定位
引自:https://www.entry0.cn/2020/05/09/771.html
什么是重定位?
重定位是连接符号引用与符号定义的过程。例如,程序调用函数时,关联的调用指令必须在执行时将控制权转移到正确的目标地址。可重定位文件必须包含说明如何修改其节内容的信息。通过此信息,可执行文件和共享目标文件可包含进程的程序映像的正确信息。重定位项即是这些数据。
什么是.rela.dyn段?
该节保存的是重定位信息,数据内容是包含带有显式加数的重定位条目,每个条目固定大小(24个字节)。.rela.dyn在动态链接的目标文件中保存的是需要被重定位的变量数据。
通过反编译vmlinux,可以看到.rela.dyn段的内容如下:
4485578 ffff800011280e20 <.rela.dyn>:
4485579 ffff800011280e20: 10044768 .word 0x10044768
4485580 ffff800011280e24: ffff8000 .word 0xffff8000
4485581 ffff800011280e28: 00000403 .word 0x00000403
4485582 ffff800011280e2c: 00000000 .word 0x00000000
4485583 ffff800011280e30: 10047dd8 .word 0x10047dd8
4485584 ffff800011280e34: ffff8000 .word 0xffff8000
4485585 ffff800011280e38: 10044770 .word 0x10044770
4485586 ffff800011280e3c: ffff8000 .word 0xffff8000
4485587 ffff800011280e40: 00000403 .word 0x00000403
.......
可以看出.rela.dyn段包含很多的entry,每个entry有24个字节构成,用于描述一个可重定位符号:
| 64 bit | 64 bit | 64 bit | ...
+----------------+-----------------------------------+-----------------
| sym0 link addr | sym0 reloc flag | sym0 link value | sym1 link ...
+----------------+-----------------------------------+-----------------
如上例中:
4485585 ffff800011280e38: 10044770 .word 0x10044770
4485586 ffff800011280e3c: ffff8000 .word 0xffff8000
4485587 ffff800011280e40: 00000403 .word 0x00000403
组成了一个entry,sym link addr, sym reloc flag, sym link value分别为:
0x10044770, 0xffff8000, 0x00000403
__relocate_kernel片段4
ldr x8, =__primary_switched
adrp x0, __PHYS_OFFSET
br x8
__primary_switch的最后将跳转到__primary_switched执行,首先它会通过adrp将__PHYS_OFFSET的地址(kernel image物理地址)保存到x0作为__primary_switched的参数
3. 总结
__primary_switch主要完成了如下的工作:
- 使能MMU,使能之前会分别用idmap_pg_dir和init_pag_dir设置TTBR0和TTBR1;
- 将kernel image的.rela.dyn段实现重定位
- 跳转到__primary_switched执行
参考文档
- ELF for the ARM® 64-bit Architecture (AArch64)
- https://www.entry0.cn/2020/05/09/771.html
ELF的.RELA.DYN节 - https://amir.rachum.com/blog/2016/09/17/shared-libraries/
Shared Libraries: Understanding Dynamic Loading - arm64_linux启动流程分析07_开启MMU切换到虚拟地址
- http://sp1.wikidot.com/elfobjfile
目的档格式(ELF) - Documentation/arm64/memory.rst