【linux kernel】基于ARM64分析linux内核的链接脚本vmlinux.lds.S

news2025/7/20 9:50:48

文章目录

    • 一、导读
    • 二、链接器是什么
    • 三、链接脚本
    • 四、linux内核的链接脚本
      • 4-1 头文件包含描述
      • 4-2 参数设置和宏定义描述
      • 4-3 SECTIONS内容分析
    • 五、linux内核的“头”
    • 六、总结

一、导读

在linux内核中,arch目录下放置的是关于linux内核所支持的具体架构相关的代码描述文件。其中在具体架构目录下的kernel目录中都会有一个链接脚本文件。

二、链接器是什么

现代软件工程中,一个大的程序通常都由多个源文件组成,其中包含以高级计算机语言编写的源文件以及汇编语言编写的汇编文件。在编译构建过程中会分别对这些源文件进行汇编或者编译并生成目标文件,这些目录文件包含代码段、数据段、符号表等内容。而链接则是把这些目标文件的代码段、数据段以及符号表等内容收集起来并按照某种格式(例如ELF)组合成一个可执行二进制文件的过程。而这个过程是使用链接器来完成的。

链接器在链接过程中会使用到一个链接脚本文件,该文件用于描述链接的过程,当没有通过“-T”参数指定链接脚本时,链接器会使用内置的链接脚本。

三、链接脚本

链接脚本控制着如何把输入文件中的段合并到输出文件的段中,以及这些段的地址空间布局等。本质上则是把在编译构建过程中大量的二进制文件(.o文件)合并成一个可执行的二进制文件。

四、linux内核的链接脚本

本文以ARM64架构为例,首先贴上链接脚本的完整内容,后面会详细描述。linux内核针对ARM64架构的链接脚本放置于/arch/arm64/kernel/vmlinux.lds.S文件中:

/*
 * ld script to make ARM Linux kernel
 * taken from the i386 version by Russell King
 * Written by Martin Mares <mj@atrey.karlin.mff.cuni.cz>
 */

#include <asm-generic/vmlinux.lds.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_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);				\
	VMLINUX_SYMBOL(__hyp_idmap_text_start) = .;	\
	*(.hyp.idmap.text)				\
	VMLINUX_SYMBOL(__hyp_idmap_text_end) = .;	\
	VMLINUX_SYMBOL(__hyp_text_start) = .;		\
	*(.hyp.text)					\
	VMLINUX_SYMBOL(__hyp_text_end) = .;

/*
 * 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
#endif

#if defined(CONFIG_DEBUG_ALIGN_RODATA)
#define ALIGN_DEBUG_RO			. = ALIGN(1<<SECTION_SHIFT);
#define ALIGN_DEBUG_RO_MIN(min)		ALIGN_DEBUG_RO
#elif defined(CONFIG_DEBUG_RODATA)
#define ALIGN_DEBUG_RO			. = ALIGN(1<<PAGE_SHIFT);
#define ALIGN_DEBUG_RO_MIN(min)		ALIGN_DEBUG_RO
#else
#define ALIGN_DEBUG_RO
#define ALIGN_DEBUG_RO_MIN(min)		. = ALIGN(min);
#endif

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.
	 */
	/DISCARD/ : {
		ARM_EXIT_DISCARD(EXIT_TEXT)
		ARM_EXIT_DISCARD(EXIT_DATA)
		EXIT_CALL
		*(.discard)
		*(.discard.*)
	}

	. = PAGE_OFFSET + TEXT_OFFSET;

	.head.text : {
		_text = .;
		HEAD_TEXT
	}
	ALIGN_DEBUG_RO
	.text : {			/* Real text segment		*/
		_stext = .;		/* Text and read-only data	*/
			__exception_text_start = .;
			*(.exception.text)
			__exception_text_end = .;
			IRQENTRY_TEXT
			TEXT_TEXT
			SCHED_TEXT
			LOCK_TEXT
			HYPERVISOR_TEXT
			*(.fixup)
			*(.gnu.warning)
		. = ALIGN(16);
		*(.got)			/* Global offset table		*/
	}

	ALIGN_DEBUG_RO
	RO_DATA(PAGE_SIZE)
	EXCEPTION_TABLE(8)
	NOTES
	ALIGN_DEBUG_RO
	_etext = .;			/* End of text and rodata section */

	ALIGN_DEBUG_RO_MIN(PAGE_SIZE)
	__init_begin = .;

	INIT_TEXT_SECTION(8)
	.exit.text : {
		ARM_EXIT_KEEP(EXIT_TEXT)
	}

	ALIGN_DEBUG_RO_MIN(16)
	.init.data : {
		INIT_DATA
		INIT_SETUP(16)
		INIT_CALLS
		CON_INITCALL
		SECURITY_INITCALL
		INIT_RAM_FS
	}
	.exit.data : {
		ARM_EXIT_KEEP(EXIT_DATA)
	}

	PERCPU_SECTION(64)

	. = ALIGN(PAGE_SIZE);
	__init_end = .;

	. = ALIGN(4);
	.altinstructions : {
		__alt_instructions = .;
		*(.altinstructions)
		__alt_instructions_end = .;
	}
	.altinstr_replacement : {
		*(.altinstr_replacement)
	}

	. = ALIGN(PAGE_SIZE);
	_data = .;
	_sdata = .;
	RW_DATA_SECTION(64, PAGE_SIZE, THREAD_SIZE)
	PECOFF_EDATA_PADDING
	_edata = .;

	BSS_SECTION(0, 0, 0)

	. = ALIGN(PAGE_SIZE);
	idmap_pg_dir = .;
	. += IDMAP_DIR_SIZE;
	swapper_pg_dir = .;
	. += SWAPPER_DIR_SIZE;

	_end = .;

	STABS_DEBUG

	HEAD_SYMBOLS
}

/*
 * The HYP init code can't be more than a page long,
 * 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")

/*
 * If padding is applied before .head.text, virt<->phys conversions will fail.
 */
ASSERT(_text == (PAGE_OFFSET + TEXT_OFFSET), "HEAD is misaligned")

4-1 头文件包含描述

vmlinux.lds.S文件的开始处,会使用#include包含头文件,这一点与C语言类似:

#include <asm-generic/vmlinux.lds.h>
#include <asm/thread_info.h>
#include <asm/memory.h>
#include <asm/page.h>
#include <asm/pgtable.h>

#include "image.h"

在以上列出的头文件中,大多会使用宏定义方式编写特定段的描述内容,用于在vmlinux.lds.S文件中引用。

4-2 参数设置和宏定义描述

OUTPUT_ARCH(aarch64)语句用于

ENYRY(_text)语言用于设置程序的入口为_text,程序执行的第一条指令称为入口点(entry point)。除了这种方式,还有其他的方式:

(1)在GCC工具链的LD命令通过“-e”参数指定入口点。

(2)在链接脚本中通过ENTRY命令设置入口点。

(3)通过特定符号(例如start符号)设置入口点。

(4)使用代码段的起始地址。

(5)使用地址0。

在上述五种方式中,链接器会依次尝试来设置入口点,直到成功为止。

接下来设置jiffies参数值:jiffies = jiffies_64;jiffies_64定义在/kernel/time/timer.c文件中:

接着定义HYPERVISOR_TEXT代码段:

#define HYPERVISOR_TEXT					\
	. = ALIGN(SZ_4K);				\
	VMLINUX_SYMBOL(__hyp_idmap_text_start) = .;	\
	*(.hyp.idmap.text)				\
	VMLINUX_SYMBOL(__hyp_idmap_text_end) = .;	\
	VMLINUX_SYMBOL(__hyp_text_start) = .;		\
	*(.hyp.text)					\
	VMLINUX_SYMBOL(__hyp_text_end) = .;

4-3 SECTIONS内容分析

SECTIONS{}是链接脚本语法中的关键命令,用于描述输出文件的内存布局。SECTIONS命令告诉链接文件如何把输入文件的段映射到输出文件的各个段中,如何将输入端整合为输出段,如何把输出段放入程序地址空间和进程地址空间中。

在开始之前,先描述两个linux内核中重要的知识点:

(1)在链接脚本中,有一个特殊的符号:“.”,用于表示当前位置计数器。在vmlinux.lds.S文件中很多地方都会使用到。

(2)在链接脚本中有一个常用的编程技巧:为每个段(或者多个段)设置一些符号,用于标识内存位置的开始和结束,这样便可以在C语言代码中访问每个段(或者多个段)的起始地址结束地址

SECTIONS{}中最先开始的是:

	/DISCARD/ : {
		ARM_EXIT_DISCARD(EXIT_TEXT)
		ARM_EXIT_DISCARD(EXIT_DATA)
		EXIT_CALL
		*(.discard)
		*(.discard.*)
	}

/DISCARD/ 是一个特殊的输出段,被该段引用的任何输入段将不会出现在输出文件中。

接着是_text段:

	. = PAGE_OFFSET + TEXT_OFFSET;

	.head.text : {
		_text = .;
		HEAD_TEXT
	}

上述. = PAGE_OFFSET + TEXT_OFFSET;意思是把代码段的链接地址设置为PAGE_OFFSET + TEXT_OFFSET的计算值。PAGE_OFFSET表示内核空间和用户空间对虚拟地址空间的划分,TEXT_OFFSET表示代码段的偏移地址。

.head.text表示输出段,对应的输入段为HEAD_TEXT,本质为*(.head.text)。意思是将所有目标文件中的.head.text段放入.head.text输出段中。其中_text = .;用于标识_text段的开始。

接下来是.text输出段,本质上是代码段:

	.text : {			/* Real text segment		*/
		_stext = .;		/* Text and read-only data	*/
			__exception_text_start = .;
			*(.exception.text)
			__exception_text_end = .;
			IRQENTRY_TEXT
			TEXT_TEXT
			SCHED_TEXT
			LOCK_TEXT
			HYPERVISOR_TEXT
			*(.fixup)
			*(.gnu.warning)
		. = ALIGN(16);
		*(.got)			/* Global offset table		*/
	}

上述代码会汇集目标文件中的多个输入段到.text中。例如:.exception.text.irqentry.text.sched.text.spinlock.text.hyp.idmap.text等。

接下来则是RO_DATA(PAGE_SIZE)宏代表的只读数据段,该宏定义非常长(此处不展开)。紧随其后的是异常表段:

#define EXCEPTION_TABLE(align)						\
	. = ALIGN(align);						\
	__ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {		\
		VMLINUX_SYMBOL(__start___ex_table) = .;			\
		*(__ex_table)						\
		VMLINUX_SYMBOL(__stop___ex_table) = .;			\
	}

接下来放置.notes段:

#define NOTES								\
	.notes : AT(ADDR(.notes) - LOAD_OFFSET) {			\
		VMLINUX_SYMBOL(__start_notes) = .;			\
		*(.note.*)						\
		VMLINUX_SYMBOL(__stop_notes) = .;			\
	}

上述内容就是textrodata段的定义了,最后以_etext = .位置计数器结束。

接下来是与初始化相关的段,由[__init_begin , __init_end]符号标识:

	__init_begin = .;

	INIT_TEXT_SECTION(8)
	.exit.text : {
		ARM_EXIT_KEEP(EXIT_TEXT)
	}

	ALIGN_DEBUG_RO_MIN(16)
	.init.data : {
		INIT_DATA
		INIT_SETUP(16)
		INIT_CALLS
		CON_INITCALL
		SECURITY_INITCALL
		INIT_RAM_FS
	}
	.exit.data : {
		ARM_EXIT_KEEP(EXIT_DATA)
	}

	PERCPU_SECTION(64)

	. = ALIGN(PAGE_SIZE);
	__init_end = .;

接着是.altinstructions.altinstr_replacement两个输出段。

后面是[_data , _edata]符号代表的数据相关段:

	_data = .;
	_sdata = .;
	RW_DATA_SECTION(64, PAGE_SIZE, THREAD_SIZE)
	PECOFF_EDATA_PADDING
	_edata = .;

然后是BSS相关段:BSS_SECTION(0, 0, 0)

最后以_end = .;标识linux内核的结束。然后还放置了与stab相关的调试段:

#define STABS_DEBUG							\
		.stab 0 : { *(.stab) }					\
		.stabstr 0 : { *(.stabstr) }				\
		.stab.excl 0 : { *(.stab.excl) }			\
		.stab.exclstr 0 : { *(.stab.exclstr) }			\
		.stab.index 0 : { *(.stab.index) }			\
		.stab.indexstr 0 : { *(.stab.indexstr) }		\
		.comment 0 : { *(.comment) }

在内存布局的最后会放置HEAD_SYMBOLS代表的三个符号标志:

#define HEAD_SYMBOLS						\
	_kernel_size_le		= DATA_LE64(_end - _text);	\
	_kernel_offset_le	= DATA_LE64(TEXT_OFFSET);	\
	_kernel_flags_le	= DATA_LE64(__HEAD_FLAGS);
  • _kernel_offset_le是镜像从RAM开始加载的偏移量(小端序)。

  • _kernel_flags_le是信息标志(小端序)。

  • _kernel_offset_le表示linux内核镜像的有效大小(小端序)。

在内核镜像生成过程中,上述三个符号标志代表的值会作为镜像头的一部分输出。

五、linux内核的“头”

上述内容对linux内核的vmlinux.lds.S进行了描述,已经知道在内存布局的开始处放置的是.head.text输出段,这正是linux内核的“头”,对应的输入段为*(.head.text)。在linux内核源码中,在arch/arm64/kernel/head.S文件中则描述了.head.text段:

六、总结

本文主要描述了linux内核针对ARM64的链接脚本文件vmlinux.lds.S,寻找linux内核镜像的入口点。不同架构下的vmlinux.lds.S文件内容大多不同,需要具体查看。

总而言之,linux内核镜像中的组成内容由链接脚本控制,从链接脚本和head.S启动汇编代码中可以寻找到linux内核镜像的入口点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/7993.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EFK部署centos7.9(一)ES单节点部署

Elasticsearch部署 系统类型&#xff1a;Centos7.9 节点IP&#xff1a;192.168.11.139 软件版本&#xff1a;jdk-8u121-linux-x64.tar、elasticsearch-6.5.4.tar. 1.首先上传jdk的包 tar xzf jdk-8u121-linux-x64.tar.gz -C /usr/local/ 解压jdk的包 cd /usr/local/ 切…

逻辑回归预测瘀血阻络证||LogRegression 二分类 python3

要求 把数据集分为训练集和测试集使用逻辑回归训练、预测&#xff0c;得出相应的分类指标准确率accuracy&#xff0c;精确率precision&#xff0c;召回率recall&#xff0c;F1-score&#xff0c;并画出最终的ROC曲线&#xff0c;得出AUC值。 数据格式 664条样本 每条103个属性…

列的类型定义——整形类型

文章目录 前言一、整数类型的附带属性 类型名称后面的小括号unsignedauto_increment总结前言 1&#xff09;采用26字母和0-9的自然数加上下互相 ‘_’ 组成&#xff0c;命名简洁明确&#xff0c;多个单词用下划线 ‘_’ 隔开 2&#xff09;全部小写命名&#xff0c;尽量避免出…

猿创征文|计算机专业硕博研究生提高效率的10款科研工具

前言 大家好&#xff0c;我是帝都某高校的一名在读研究生&#xff0c;研究方向为人工智能安全、强化学习、漏洞挖掘。今天想跟各位计算机相关专业的硕士生、博士生们分享几款超级实用并且能够提高科研效率的工具&#xff01;&#xff01;&#xff01;希望能够得到大家的一键四…

【角点检测】 基于各向异性高斯方向导数滤波器实现图像角点检测附matlab代码

1 内容介绍 为了改进噪声鲁棒性和定位准确性,利用各向异性高斯方向导数滤波器,提出多方向角点检测算法.该算法利用一组各向异性高斯方向导数滤波器对输入图像进行卷积处理得到各个方向的滤波器响应.对于每个像素点,利用它与周围邻近像素点的滤波器响应的相关信息构造局部自相关…

Revit导入Cad图元丢失不正确解决和链接CAD功能

一、导入Cad图元丢失或者图元不正确解决&#xff1a; 导入Cad的时候我们会遇到导入图元丢失或者图元不正确等情况&#xff0c;具体解决如下 01.天正画图时一定要导出t3格式&#xff0c;因为Revit只识别t3版本 02.Cad画图时&#xff0c;最后一定要将图元炸开&#xff0c;然后在框…

NVIDIA NCCL 源码学习(五)- 路径计算

上节NCCL完成了对机器PCI系统拓扑的建图&#xff0c;其中建好的图如下所示&#xff0c;其中GPU之间是通过NVLink连接起来的 为了方便之后的搜索channel&#xff0c;接下来NCCL会先计算GPU和NIC节点到其他任意节点之间的最优路径&#xff0c;以及对应的带宽&#xff0c;即最优路…

Vue3基础——Composition API初体验、合成API详解、setup、Provide 和 inject

文章目录p19 组合式 API (Composition API)初体验p20 Vue3合成API详解p21 setup中使用生命周期函数p22 Provide 和 injectp19 组合式 API (Composition API)初体验 <template><h1 click"add">计数count: {{ count }}</h1><h1 click"incre…

51单片机笔记:定时器/计数器

单片机笔记 定时器/计数器 定时器/计数器的结构 AT89S51内部两个16位定时器/计数器&#xff1a;T0(P3.4)&#xff0c;T1(P3.5)&#xff0c;定时器/计数器T0由特殊寄存器TH0,TL0构成&#xff0c;T1由特殊功能寄存器TH1&#xff0c;TL1构成 T0,T1都有定时器和计数器两种工作模…

DuckDB学习-初识tpcds

DuckDB学习-1 文章目录DuckDB学习-1跑TPC-DS编译DuckDB支持TPC-DS扩展执行数据生成及查询TPC-DS简单分析&#xff08;v3.2.0&#xff09;事实表维度表跑TPC-DS 编译DuckDB支持TPC-DS扩展 下载代码&#xff0c;然后进源码目录&#xff0c;执行下面两个步骤。 export BUILD_TPC…

Word控件Spire.Doc 【文本】教程(19) ;如何在 C#、VB.NET 中通过 Word 中的正则表达式查找和替换文本

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

【附源码】计算机毕业设计JAVA车辆调度管理系统

【附源码】计算机毕业设计JAVA车辆调度管理系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA my…

计算机毕业设计ssm+vue基本微信小程序的健康食谱交流共享平台

项目介绍 当今社会健康食谱交流共享买卖是必不可少的,人们不管走到哪里都需要有一个健康的身体,有一个强迫的体质,所以健康食谱交流共享市场也是非常火爆&#xff01;不管是健康食谱交流共享公司或者是个人都需要一套完整的管理系统来掌握整个市场信息。针对这一需求,本文设计并…

一步一步教你安装部署 Jenkins,不信你安不上

1、可以去官网 Jenkins 下载jenkins.war包 这里有个坑&#xff1a;jenkins下载的版本需要和jdk版本匹配 我的jdk是1.8,这里选择jenkins版本为2.332.4&#xff0c;下面的链接是jenkins.war安装包直接用 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;8bxy 2、使…

【C++】模板template

前言&#xff1a;本教程使用到的工具是vs2010&#xff1b; 目录 为什么要使用模板&#xff1f; template模板 函数模板 类的模板 template模板的本质 总结 为什么要使用模板&#xff1f; 我们先来大概了解一下模板的概念&#xff0c;下面是菜鸟教程对于模板给出的解释&am…

每天5分钟快速玩转机器学习算法:带有核函数的支持向量机模型

本文重点 硬间隔和软间隔都是在说样本的完全线性可分或者大部分样本点的线性可分。但我们可能会碰到的一种情况是样本点不是线性可分的,比如: 那么这个时候支持向量机软硬不吃,此时要想解决这个问题,我们可以使用一种魔法,我们将他们映射到一个高维空间中,然后再高维空…

链表相关OJ及方法总结

目录​​​​​​​ 第一类&#xff1a;改变链接关系 第二类&#xff1a;快慢指针 第一类&#xff1a;改变链接关系 1. 删除链表中等于给定值 val 的所有结点。 (1)原地删除 struct ListNode* removeElements(struct ListNode* head, int val){if(headNULL){return head…

一般人我劝你还是要不自学软件测试.....

软件测试基础真的很简单&#xff0c;是个人稍微认真点都能懂&#xff0c;这就是好多人说软件测试简单、易懂、好学&#xff0c;然后就是一顿浮夸的言论&#xff0c;误导那些小白&#xff0c;这里我就给那些轻浮的人泼一桶冷水&#xff0c;懂和学会是一码事吗&#xff1f; 这里…

如何高效填写软件测试缺陷报告?

软件缺陷的描述是软件缺陷报告的基础部分&#xff0c;需要使用简单、准确、专业的术语来描述缺陷。否则&#xff0c;它就会含糊不清&#xff0c;可能会误导开发人员&#xff0c;影响开发人员的效率&#xff0c;也会影响测试人员自身的声誉&#xff0c;准确报告缺陷是非常重要的…

[附源码]java毕业设计基于SSM的酒店管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…