以5.10内核为例
链接脚本分析
KIMAGE_VADDR
KIMAGE_VADDR表示虚拟内核的虚拟起始地址,其值计算比较复杂,是linux kernel一堆宏定义的运算,得出的结果是 0xFFFF 8000 1000 0000
这值在后续如下章节有详细的推导
慢慢欣赏arm64内核启动18 primary_entry之__create_page_tables代码第五部分-CSDN博客
我们还可以查看构建vmlinux.lds.S生成的文件vmlinux.lds,里面的定义如下
. = (((((((-(((1)) << ((((48))) - 1))))) + (0x08000000))) + (0x08000000)));
.head.text : {
_text = .;
KEEP(*(.head.text))
}
.text : {
_stext = .;
我们尝试计算KIMAGE_VADDR的数值
(((((((-(((1)) << ((((48))) - 1))))) + (0x08000000))) + (0x08000000)))
1 << 47 == 0x8000 0000 0000
-(1 << 47) = 0xFFFF 7FFF FFFF FFFF + 1 == 0xFFFF 8000 0000 0000
-(1 << 47) + + (0x0800 0000))) + (0x0800 0000))) = 0xFFFF 8000 1000 0000
SECTIONS
{
/*
* XXX: The linker does not define how output sections are
* assigned to input sections when there are multiple statements
* matching the same input section name. There is no documented
* order of matching.
*/
DISCARDS
/DISCARD/ : {
*(.interp .dynamic)
*(.dynsym .dynstr .hash .gnu.hash)
}
. = KIMAGE_VADDR;
.head.text : {
_text = .;
HEAD_TEXT
}
镜像的第一个段是.head.test段,起始地址也就是上文计算的0xFFFF 8000 1000 0000,再接着看,_text的地址也是0xFFFF 8000 1000 0000
查看Sysmap也确实如此
$ cat System.map | grep ffff800010000000
ffff800010000000 t __efistub__text
ffff800010000000 t _head
ffff800010000000 T _text
HEAD_TEXT
HEAD_TEXT是指什么呢?我们搜索一下内核源码
$ grep -rn HEAD_TEXT ./
./include/asm-generic/vmlinux.lds.h:661:#define HEAD_TEXT KEEP(*(.head.text))
$ grep -rn .head.text ./
./include/linux/init.h:95:#define __HEAD .section ".head.text","ax"
我们再接着查.head.text
$ grep -rnw .head.text ./
./include/linux/init.h:95:#define __HEAD .section ".head.text","ax"
$ grep -rnw __HEAD ./arch/arm64/
./arch/arm64/kernel/head.S:60: __HEAD
也就是说,HEAD_TEXT对应的代码段就在arm64/kernel/head.S里面,这也应该是bootloader把控制权交给内核后的启动地址。为了验证我们的猜想,我们解析一下内核ELF文件
$ readelf -e vmlinux
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: AArch64
Version: 0x1
Entry point address: 0xffff800010000000
Start of program headers: 64 (bytes into file)
Start of section headers: 368544920 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 5
Size of section headers: 64 (bytes)
Number of section headers: 38
Section header string table index: 37
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .head.text PROGBITS ffff800010000000 00010000
0000000000010000 0000000000000000 AX 0 0 65536
[ 2] .text PROGBITS ffff800010010000 00020000
0000000000db24b8 0000000000000008 AX 0 0 2048
[ 3] .got.plt PROGBITS ffff800010dc24b8 00dd24b8
0000000000000018 0000000000000008 WA 0 0 8
[ 4] .rodata PROGBITS ffff800010dd0000 00de0000
00000000004a09c0 0000000000000000 WA 0 0 4096
[ 5] .pci_fixup PROGBITS ffff8000112709c0 012809c0
00000000000026b0 0000000000000000 A 0 0 16
[ 6] __ksymtab PROGBITS ffff800011273070 01283070
0000000000011dcc 0000000000000000 A 0 0 4
[ 7] __ksymtab_gpl PROGBITS ffff800011284e3c 01294e3c
0000000000014490 0000000000000000 A 0 0 4
[ 8] __ksymtab_strings PROGBITS ffff8000112992cc 012a92cc
000000000003ee20 0000000000000001 AMS 0 0 1
[ 9] __param PROGBITS ffff8000112d80f0 012e80f0
00000000000046a0 0000000000000000 A 0 0 8
[10] __modver PROGBITS ffff8000112dc790 012ec790
0000000000000100 0000000000000000 A 0 0 8
[11] __ex_table PROGBITS ffff8000112dc890 012ec890
0000000000002300 0000000000000000 A 0 0 8
[12] .notes NOTE ffff8000112deb90 012eeb90
000000000000003c 0000000000000000 A 0 0 4
[13] .init.text PROGBITS ffff8000112f0000 012f0000
0000000000057da0 0000000000000000 AX 0 0 4
[14] .exit.text PROGBITS ffff800011347da0 01347da0
0000000000005da0 0000000000000000 AX 0 0 4
[15] .altinstructions PROGBITS ffff80001134db40 0134db40
0000000000028044 0000000000000000 A 0 0 1
[16] .init.data PROGBITS ffff800011380000 01380000
000000000001ea16 0000000000000000 WA 0 0 256
[17] .data..percpu PROGBITS ffff80001139f000 0139f000
000000000000dcd8 0000000000000000 WA 0 0 64
[18] .hyp.data..percpu PROGBITS ffff8000113ad000 013ad000
0000000000000e20 0000000000000000 WA 0 0 16
[19] .rela.dyn RELA ffff8000113ade20 013ade20
0000000000183ea0 0000000000000018 A 0 0 8
[20] .data PROGBITS ffff800011540000 01540000
000000000011d260 0000000000000000 WA 0 0 4096
[21] __bug_table PROGBITS ffff80001165d260 0165d260
0000000000019434 0000000000000000 WA 0 0 4
[22] .mmuoff.data[...] PROGBITS ffff800011676800 01676800
0000000000000018 0000000000000000 WA 0 0 2048
[23] .mmuoff.data.read PROGBITS ffff800011677000 01677000
0000000000000008 0000000000000000 WA 0 0 8
[24] .pecoff_edat[...] PROGBITS ffff800011677008 01677008
00000000000001f8 0000000000000000 WA 0 0 1
[25] .bss NOBITS ffff800011678000 01677200
000000000007955c 0000000000000000 WA 0 0 4096
[26] .debug_aranges PROGBITS 0000000000000000 01677200
000000000002daa0 0000000000000000 0 0 16
[27] .debug_info PROGBITS 0000000000000000 016a4ca0
000000000c9b6c4c 0000000000000000 0 0 1
[28] .debug_abbrev PROGBITS 0000000000000000 0e05b8ec
00000000005bf625 0000000000000000 0 0 1
[29] .debug_line PROGBITS 0000000000000000 0e61af11
00000000019046b9 0000000000000000 0 0 1
[30] .debug_frame PROGBITS 0000000000000000 0ff1f5d0
0000000000345f90 0000000000000000 0 0 8
[31] .debug_str PROGBITS 0000000000000000 10265560
00000000003d0adf 0000000000000001 MS 0 0 1
[32] .debug_loc PROGBITS 0000000000000000 1063603f
0000000004060187 0000000000000000 0 0 1
[33] .debug_ranges PROGBITS 0000000000000000 146961d0
000000000134f6e0 0000000000000000 0 0 16
[34] .comment PROGBITS 0000000000000000 159e58b0
000000000000005d 0000000000000001 MS 0 0 1
[35] .symtab SYMTAB 0000000000000000 159e5910
00000000003536d0 0000000000000018 36 118265 8
[36] .strtab STRTAB 0000000000000000 15d38fe0
000000000023fb1e 0000000000000000 0 0 1
[37] .shstrtab STRTAB 0000000000000000 15f78afe
0000000000000198 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), p (processor specific)
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000010000 0xffff800010000000 0xffff800010000000
0x00000000012debcc 0x00000000012debcc RWE 0x10000
LOAD 0x00000000012f0000 0xffff8000112f0000 0xffff8000112f0000
0x0000000000085b84 0x0000000000085b84 R E 0x10000
LOAD 0x0000000001380000 0xffff800011380000 0xffff800011380000
0x00000000002f7200 0x000000000037155c RW 0x10000
NOTE 0x00000000012eeb90 0xffff8000112deb90 0xffff8000112deb90
0x000000000000003c 0x000000000000003c R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
Section to Segment mapping:
Segment Sections...
00 .head.text .text .got.plt .rodata .pci_fixup __ksymtab __ksymtab_gpl __ksymtab_strings __param __modver __ex_table .notes
01 .init.text .exit.text .altinstructions
02 .init.data .data..percpu .hyp.data..percpu .rela.dyn .data __bug_table .mmuoff.data.write .mmuoff.data.read .pecoff_edata_padding .bss
03 .notes
04
与我们猜想的一样,内核镜像ELF的入口地址果然就是0xFFFF 8000 1000 0000,这样bootloader先加载vmlinux到物理内存之后,然后根据ELF的头部信息解析ELF文件,找到入口地址和所属的.head.test代码段,跳入该地址所在的代码段开始执行,实际上是从head.S的__HEAD位置开始执行。
但是有一个问题,各种Linux硬件的内存大小不一样,很大的概率没有该物理地址,bootloader很可能把镜像加载到一个空闲的合适大小的物理地址上,所以内核一开始执行的时候,虚拟地址和物理地址不同,这就导致了.head.text这个段是地址无关的代码段,这也就是内核把这个段单独划分出来的原因。
参考
参考1
第二章 ARM64在Linux内核中的实现
https://blog.csdn.net/weixin_39247141/article/details/125514337
参考2
kernel启动流程-head.S的执行_1.概述
https://blog.csdn.net/jasonactions/article/details/111190923