Linux 内核连接脚本vmlinux.lds.S

编程入门 行业动态 更新时间:2024-10-20 09:27:10

Linux <a href=https://www.elefans.com/category/jswz/34/1769575.html style=内核连接脚本vmlinux.lds.S"/>

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

本文发布于:2023-11-17 03:04:37,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1636442.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:内核   脚本   Linux   vmlinux   lds

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!