本文主要是介绍Linux 内核连接脚本vmlinux.lds.S,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
源码基于:Linux5.4
0. 前言
现代软件工程中,一个大的工程通常都会有多个源文件组成,其中包括高级计算机语言编写的源文件,以及汇编语言编写的汇编文件。在编译构建过程中会分别对这些源文件进行汇编、编译生成目标文件,这些目标文件包含:代码段、数据段、符号表等内容。链接器主要任务是将符号引用解析到符号定义上,将多个目标文件和库文件合并成为一个可执行文件或者动态链接库,生成符号表,并对程序代码做最后的检查和优化。
本文主要针对 ARM64架构的连接脚本 vmlinux.lds.S 进行剖析。
因为 vmlinux.lds.S 的内容比较多,本文将其拆分后分析。
1. vmlinux.lds.S 源码
arch/arm64/kernel/vmlinux.lds.S#include <asm-generic/vmlinux.lds.h>
#include <asm/cache.h>
#include <asm/kernel-pgtable.h>
#include <asm/thread_info.h>
#include <asm/memory.h>
#include <asm/page.h>
#include <asm/pgtable.h>#include "image.h"/* .exit.text needed in case of alternative patching */
#define ARM_EXIT_KEEP(x) x
#define ARM_EXIT_DISCARD(x)OUTPUT_ARCH(aarch64)
ENTRY(_text)jiffies = jiffies_64;#define HYPERVISOR_EXTABLE \. = ALIGN(SZ_8); \__start___kvm_ex_table = .; \*(__kvm_ex_table) \__stop___kvm_ex_table = .;#define HYPERVISOR_TEXT \/* \* Align to 4 KB so that \* a) the HYP vector table is at its minimum \* alignment of 2048 bytes \* b) the HYP init code will not cross a page \* boundary if its size does not exceed \* 4 KB (see related ASSERT() below) \*/ \. = ALIGN(SZ_4K); \__hyp_idmap_text_start = .; \*(.hyp.idmap.text) \__hyp_idmap_text_end = .; \__hyp_text_start = .; \*(.hyp.text) \HYPERVISOR_EXTABLE \__hyp_text_end = .;#define IDMAP_TEXT \. = ALIGN(SZ_4K); \__idmap_text_start = .; \*(.idmap.text) \__idmap_text_end = .;#ifdef CONFIG_HIBERNATION
#define HIBERNATE_TEXT \. = ALIGN(SZ_4K); \__hibernate_exit_text_start = .; \*(.hibernate_exit.text) \__hibernate_exit_text_end = .;
#else
#define HIBERNATE_TEXT
#endif#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
#define TRAMP_TEXT \. = ALIGN(PAGE_SIZE); \__entry_tramp_text_start = .; \*(.entry.tramp.text) \. = ALIGN(PAGE_SIZE); \__entry_tramp_text_end = .;
#else
#define TRAMP_TEXT
#endif/** The size of the PE/COFF section that covers the kernel image, which* runs from stext to _edata, must be a round multiple of the PE/COFF* FileAlignment, which we set to its minimum value of 0x200. 'stext'* itself is 4 KB aligned, so padding out _edata to a 0x200 aligned* boundary should be sufficient.*/
PECOFF_FILE_ALIGNMENT = 0x200;#ifdef CONFIG_EFI
#define PECOFF_EDATA_PADDING \.pecoff_edata_padding : { BYTE(0); . = ALIGN(PECOFF_FILE_ALIGNMENT); }
#else
#define PECOFF_EDATA_PADDING
#endifSECTIONS
{/** 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.*//DISCARD/ : {ARM_EXIT_DISCARD(EXIT_TEXT)ARM_EXIT_DISCARD(EXIT_DATA)EXIT_CALL*(.discard)*(.discard.*)*(.interp .dynamic)*(.dynsym .dynstr .hash .gnu.hash)*(.eh_frame)}. = KIMAGE_VADDR + TEXT_OFFSET;.head.text : {_text = .;HEAD_TEXT}.text : { /* Real text segment */_stext = .; /* Text and read-only data */__exception_text_start = .;*(.exception.text)__exception_text_end = .;IRQENTRY_TEXTSOFTIRQENTRY_TEXTENTRY_TEXTTEXT_TEXTSCHED_TEXTCPUIDLE_TEXTLOCK_TEXTKPROBES_TEXTHYPERVISOR_TEXTIDMAP_TEXTHIBERNATE_TEXTTRAMP_TEXT*(.fixup)*(.gnu.warning). = ALIGN(16);*(.got) /* Global offset table */}. = ALIGN(SEGMENT_ALIGN);_etext = .; /* End of text section */RO_DATA(PAGE_SIZE) /* everything from this point to */EXCEPTION_TABLE(8) /* __init_begin will be marked RO NX */NOTES. = ALIGN(PAGE_SIZE);idmap_pg_dir = .;. += IDMAP_DIR_SIZE;idmap_pg_end = .;#ifdef CONFIG_UNMAP_KERNEL_AT_EL0tramp_pg_dir = .;. += PAGE_SIZE;
#endifreserved_pg_dir = .;. += PAGE_SIZE;swapper_pg_dir = .;. += PAGE_SIZE;. = ALIGN(SEGMENT_ALIGN);__init_begin = .;__inittext_begin = .;INIT_TEXT_SECTION(8)__exittext_begin = .;.exit.text : {ARM_EXIT_KEEP(EXIT_TEXT)}__exittext_end = .;. = ALIGN(4);.altinstructions : {__alt_instructions = .;*(.altinstructions)__alt_instructions_end = .;}.altinstr_replacement : {*(.altinstr_replacement)}. = ALIGN(PAGE_SIZE);__inittext_end = .;__initdata_begin = .;.init.data : {INIT_DATAINIT_SETUP(16)INIT_CALLSCON_INITCALLINIT_RAM_FS*(.init.rodata.* .init.bss) /* from the EFI stub */}.exit.data : {ARM_EXIT_KEEP(EXIT_DATA)}PERCPU_SECTION(L1_CACHE_BYTES).rela.dyn : ALIGN(8) {*(.rela .rela*)}__rela_offset = ABSOLUTE(ADDR(.rela.dyn) - KIMAGE_VADDR);__rela_size = SIZEOF(.rela.dyn);#ifdef CONFIG_RELR.relr.dyn : ALIGN(8) {*(.relr.dyn)}__relr_offset = ABSOLUTE(ADDR(.relr.dyn) - KIMAGE_VADDR);__relr_size = SIZEOF(.relr.dyn);
#endif. = ALIGN(SEGMENT_ALIGN);__initdata_end = .;__init_end = .;_data = .;_sdata = .;RW_DATA_SECTION(L1_CACHE_BYTES, PAGE_SIZE, THREAD_ALIGN)/** Data written with the MMU off but read with the MMU on requires* cache lines to be invalidated, discarding up to a Cache Writeback* Granule (CWG) of data from the cache. Keep the section that* requires this type of maintenance to be in its own Cache Writeback* Granule (CWG) area so the cache maintenance operations don't* interfere with adjacent data.*/.mmuoff.data.write : ALIGN(SZ_2K) {__mmuoff_data_start = .;*(.mmuoff.data.write)}. = ALIGN(SZ_2K);.mmuoff.data.read : {*(.mmuoff.data.read)__mmuoff_data_end = .;}PECOFF_EDATA_PADDING__pecoff_data_rawsize = ABSOLUTE(. - __initdata_begin);_edata = .;BSS_SECTION(0, 0, 0). = ALIGN(PAGE_SIZE);init_pg_dir = .;. += INIT_DIR_SIZE;init_pg_end = .;__pecoff_data_size = ABSOLUTE(. - __initdata_begin);_end = .;STABS_DEBUGHEAD_SYMBOLS
}#include "image-vars.h"/** The HYP init code and ID map text can't be longer than a page each,* and should not cross a page boundary.*/
ASSERT(__hyp_idmap_text_end - (__hyp_idmap_text_start & ~(SZ_4K - 1)) <= SZ_4K,"HYP init code too big or misaligned")
ASSERT(__idmap_text_end - (__idmap_text_start & ~(SZ_4K - 1)) <= SZ_4K,"ID map text too big or misaligned")
#ifdef CONFIG_HIBERNATION
ASSERT(__hibernate_exit_text_end - (__hibernate_exit_text_start & ~(SZ_4K - 1))<= SZ_4K, "Hibernate exit text too big or misaligned")
#endif
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
ASSERT((__entry_tramp_text_end - __entry_tramp_text_start) <= 3*PAGE_SIZE,"Entry trampoline text too big")
#endif
/** If padding is applied before .head.text, virt<->phys conversions will fail.*/
ASSERT(_text == (KIMAGE_VADDR + TEXT_OFFSET), "HEAD is misaligned")
1.1 头文件
#include <asm-generic/vmlinux.lds.h>
#include <asm/cache.h>
#include <asm/kernel-pgtable.h>
#include <asm/thread_info.h>
#include <asm/memory.h>
#include <asm/page.h>
#include <asm/pgtable.h>#include "image.h"
上面的头文件,大多会使用宏定义的方式编写特定段的描述内容,用于在 vmlinux.lds.S 文件中引用。
1.2 参数设置
//指定了链接之后输出文件的体系结构是 aarch64
OUTPUT_ARCH(aarch64)//指定程序的入口地址为 _text
ENTRY(_text)//Linux内核中定义了jiffies变量来记录从系统启动到当前时刻系统时钟所产生的tick数
//通常都设置为 jiffies_64
jiffies = jiffies_64;
ENTRY() 用以指定程序的入口地址,这里指定是 _text,其他方式:
- 在GCC 工具链 LD 命令通过 '-e' 参数指定入口点;
- 在连接脚本中通过 ENTRY() 命令设定入口点;
- 通过特定符号(例如 start 符号) 设置入口点;
- 如果存在 .text section , 使用.text section的第一字节的位置值;
- 使用值0;
1.3 宏定义描述
2. SECTIONS
SECTIONS { } 是链接脚本语法中的关键命令,用以描述输出文件的内存布局。
SECTIONS 命令告诉链接文件如何把输入文件的段映射到输出文件的各个段中,如何将输入段整合为输出段,如何把输出段放入程序地址空间和进程地址空间中。
2.1 /DISCARD/ 段
这是一个特殊的输出段,被该段引用的任何输入段,将不会出现在输出文件中。
/DISCARD/ : {ARM_EXIT_DISCARD(EXIT_TEXT)ARM_EXIT_DISCARD(EXIT_DATA)EXIT_CALL*(.discard)*(.discard.*)*(.interp .dynamic)*(.dynsym .dynstr .hash .gnu.hash)*(.eh_frame)}
2.2 _text 段
. = KIMAGE_VADDR + TEXT_OFFSET;.head.text : {_text = .;HEAD_TEXT}
'.' 号是连接脚本中一个特殊的符号,用以表示当前位置计数器。
最开始将 KIMAGE_VADDR + TEXT_OFFSET 赋值给 '.' 意思是把代码段的地址设置给当前位置;
KIMAGE_VADDR 在 memory.h 中定义:
arch/arm64/include/asm/memory.h#define KIMAGE_VADDR (MODULES_END)
TEXT_OFFSET 在Makefile 中定义:
arch/arm64/MakefileTEXT_OFFSET := 0x00080000
.head.text 表示输出段,对应的输入段位 HEAD_TEXT:
include/asm-generic/vmlinux.lds.h#define HEAD_TEXT KEEP(*(.head.text))
意思是将所有目标文件中的 .head.text 都放入 .head.text 输出段中。
其中 _text = .; 用以标识 _text 段的开始就是当前位置;
从《fixmap详解》一文中,我们看到 KIMAGE_VADDR 是在0xFFFF FFC0 0000 0000 基础上偏移 256M,即KIMAGE_VADDR 的地址为 0xFFFF FFC0 1000 0000。如果加上 TEXT_OFFSET 之后就会得到 .head.text 所在段的地址 0xFFFF FFC0 1008 0000,我们通过 readelf -S vmlinux 来看下:
There are 52 section headers, starting at offset 0x1c82de30:Section Headers:[Nr] Name Type Address OffsetSize EntSize Flags Link Info Align[ 0] NULL 0000000000000000 000000000000000000000000 0000000000000000 0 0 0[ 1] .head.text PROGBITS ffffffc0 10080000 000100000000000000001000 0000000000000000 AX 0 0 4096[ 2] .text PROGBITS ffffffc010081000 0001100000000000011ae294 0000000000000000 WAX 0 0 4096[ 3] .rodata PROGBITS ffffffc011230000 011c00000000000000c11d3d 0000000000000000 WAMS 0 0 4096
段 [1] 为 .head.text 段,我们看到其入口地址就是 0xFFFF FFC0 1008 0000
2.3 .text 段
.text : { /* Real text segment */_stext = .; /* Text and read-only data */__exception_text_start = .;*(.exception.text)__exception_text_end = .;IRQENTRY_TEXTSOFTIRQENTRY_TEXTENTRY_TEXTTEXT_TEXTSCHED_TEXTCPUIDLE_TEXTLOCK_TEXTKPROBES_TEXTHYPERVISOR_TEXTIDMAP_TEXTHIBERNATE_TEXTTRAMP_TEXT*(.fixup)*(.gnu.warning). = ALIGN(16);*(.got) /* Global offset table */}. = ALIGN(SEGMENT_ALIGN);_etext = .; /* End of text section */
开始的时候,将当前位置存放在 _stext 和 __exception_text_start 中;
接着加入所有输入目标文件的 .exception.text 段到 .text 段中;
添加完 .exception.text 后,将当前位置存入 __exception_text_end 中;
接着是宏定义 IRQENTRY_TEXT,定义在 vmlinux.lds.h 中:
include/asm-generic/vmlinux.lds.h#define IRQENTRY_TEXT \ALIGN_FUNCTION(); \__irqentry_text_start = .; \*(.irqentry.text) \__irqentry_text_end = .;
以此类推,最终 .text 段中依次输入:
- .exception.text;
- .irqentry.text;
- .softirqentry.text;
- .entry.text;
- ...
- .fixup;
- .gnu.warning;
- .got;
最后将当前位置存入 _etext 中。
2.4 .rodata 段
RO_DATA(PAGE_SIZE) /* everything from this point to */
该宏定义是在 vmlinux.lds.h:
include/asm-generic/vmlinux.lds.h#define RO_DATA(align) RO_DATA_SECTION(align)#define RO_DATA_SECTION(align) \. = ALIGN((align)); \.rodata : AT(ADDR(.rodata) - LOAD_OFFSET) { \__start_rodata = .; \*(.rodata) *(.rodata.*) \RO_AFTER_INIT_DATA /* Read only after init */ \. = ALIGN(8); \__start___tracepoints_ptrs = .; \KEEP(*(__tracepoints_ptrs)) /* Tracepoints: pointer array */ \__stop___tracepoints_ptrs = .; \*(__tracepoints_strings)/* Tracepoints: strings */ \} \\.rodata1 : AT(ADDR(.rodata1) - LOAD_OFFSET) { \*(.rodata1) \} \\/* PCI quirks */ \.pci_fixup : AT(ADDR(.pci_fixup) - LOAD_OFFSET) { \__start_pci_fixups_early = .; \KEEP(*(.pci_fixup_early)) \__end_pci_fixups_early = .; \__start_pci_fixups_header = .; \KEEP(*(.pci_fixup_header)) \__end_pci_fixups_header = .; \__start_pci_fixups_final = .; \KEEP(*(.pci_fixup_final)) \__end_pci_fixups_final = .; \__start_pci_fixups_enable = .; \KEEP(*(.pci_fixup_enable)) \__end_pci_fixups_enable = .; \__start_pci_fixups_resume = .; \KEEP(*(.pci_fixup_resume)) \__end_pci_fixups_resume = .; \__start_pci_fixups_resume_early = .; \KEEP(*(.pci_fixup_resume_early)) \__end_pci_fixups_resume_early = .; \__start_pci_fixups_suspend = .; \KEEP(*(.pci_fixup_suspend)) \__end_pci_fixups_suspend = .; \__start_pci_fixups_suspend_late = .; \KEEP(*(.pci_fixup_suspend_late)) \__end_pci_fixups_suspend_late = .; \} \\/* Built-in firmware blobs */ \.builtin_fw : AT(ADDR(.builtin_fw) - LOAD_OFFSET) ALIGN(8) { \__start_builtin_fw = .; \KEEP(*(.builtin_fw)) \__end_builtin_fw = .; \} \\TRACEDATA \\/* Kernel symbol table: Normal symbols */ \__ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) { \__start___ksymtab = .; \KEEP(*(SORT(___ksymtab+*))) \__stop___ksymtab = .; \} \\/* Kernel symbol table: GPL-only symbols */ \__ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - LOAD_OFFSET) { \__start___ksymtab_gpl = .; \KEEP(*(SORT(___ksymtab_gpl+*))) \__stop___ksymtab_gpl = .; \} \\/* Kernel symbol table: Normal unused symbols */ \__ksymtab_unused : AT(ADDR(__ksymtab_unused) - LOAD_OFFSET) { \__start___ksymtab_unused = .; \KEEP(*(SORT(___ksymtab_unused+*))) \__stop___ksymtab_unused = .; \} \\/* Kernel symbol table: GPL-only unused symbols */ \__ksymtab_unused_gpl : AT(ADDR(__ksymtab_unused_gpl) - LOAD_OFFSET) { \__start___ksymtab_unused_gpl = .; \KEEP(*(SORT(___ksymtab_unused_gpl+*))) \__stop___ksymtab_unused_gpl = .; \} \\/* Kernel symbol table: GPL-future-only symbols */ \__ksymtab_gpl_future : AT(ADDR(__ksymtab_gpl_future) - LOAD_OFFSET) { \__start___ksymtab_gpl_future = .; \KEEP(*(SORT(___ksymtab_gpl_future+*))) \__stop___ksymtab_gpl_future = .; \} \\/* Kernel symbol table: Normal symbols */ \__kcrctab : AT(ADDR(__kcrctab) - LOAD_OFFSET) { \__start___kcrctab = .; \KEEP(*(SORT(___kcrctab+*))) \__stop___kcrctab = .; \} \\/* Kernel symbol table: GPL-only symbols */ \__kcrctab_gpl : AT(ADDR(__kcrctab_gpl) - LOAD_OFFSET) { \__start___kcrctab_gpl = .; \KEEP(*(SORT(___kcrctab_gpl+*))) \__stop___kcrctab_gpl = .; \} \\/* Kernel symbol table: Normal unused symbols */ \__kcrctab_unused : AT(ADDR(__kcrctab_unused) - LOAD_OFFSET) { \__start___kcrctab_unused = .; \KEEP(*(SORT(___kcrctab_unused+*))) \__stop___kcrctab_unused = .; \} \\/* Kernel symbol table: GPL-only unused symbols */ \__kcrctab_unused_gpl : AT(ADDR(__kcrctab_unused_gpl) - LOAD_OFFSET) { \__start___kcrctab_unused_gpl = .; \KEEP(*(SORT(___kcrctab_unused_gpl+*))) \__stop___kcrctab_unused_gpl = .; \} \\/* Kernel symbol table: GPL-future-only symbols */ \__kcrctab_gpl_future : AT(ADDR(__kcrctab_gpl_future) - LOAD_OFFSET) { \__start___kcrctab_gpl_future = .; \KEEP(*(SORT(___kcrctab_gpl_future+*))) \__stop___kcrctab_gpl_future = .; \} \\/* Kernel symbol table: strings */ \__ksymtab_strings : AT(ADDR(__ksymtab_strings) - LOAD_OFFSET) { \*(__ksymtab_strings) \} \\/* __*init sections */ \__init_rodata : AT(ADDR(__init_rodata) - LOAD_OFFSET) { \*(.ref.rodata) \MEM_KEEP(init.rodata) \MEM_KEEP(exit.rodata) \} \\/* Built-in module parameters. */ \__param : AT(ADDR(__param) - LOAD_OFFSET) { \__start___param = .; \KEEP(*(__param)) \__stop___param = .; \} \\/* Built-in module versions. */ \__modver : AT(ADDR(__modver) - LOAD_OFFSET) { \__start___modver = .; \KEEP(*(__modver)) \__stop___modver = .; \} \\BTF \\. = ALIGN((align)); \__end_rodata = .;
2.5 __ex_table 段
EXCEPTION_TABLE(8) /* __init_begin will be marked RO NX */
该宏定义也是在 vmlinux.lds.h:
include/asm-generic/vmlinux.lds.h#define EXCEPTION_TABLE(align) \. = ALIGN(align); \__ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) { \__start___ex_table = .; \KEEP(*(__ex_table)) \__stop___ex_table = .; \}
2.6 .notes 段
NOTES
该宏定义也是在 vmlinux.lds.h:
include/asm-generic/vmlinux.lds.h#define NOTES \/DISCARD/ : { *(.note.GNU-stack) } \.notes : AT(ADDR(.notes) - LOAD_OFFSET) { \__start_notes = .; \KEEP(*(.note.*)) \__stop_notes = .; \}
2.7
这篇关于Linux 内核连接脚本vmlinux.lds.S的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!