动态链接器(九):.init和.init_array

news/2025/2/23 21:40:37

ELF文件中的.init和.init_array段是程序初始化阶段的重要组成部分,用于在main函数执行前完成必要的初始化操作。

1 .init段和.init_array 段

1.1 作用

.init段包含编译器生成的初始化代码,通常由运行时环境(如C标准库的启动例程)直接调用。这些代码负责执行基础的初始化任务,例如设置全局异常处理、初始化堆栈或准备程序运行环境。

.init_array是一个函数指针数组,存储了所有需要在main之前执行的初始化函数。这些函数通常由用户通过__attribute__((constructor))显式定义,或由编译器隐式生成(如C++全局对象的构造函数)。它支持多个初始化函数,按优先级顺序执行。GCC允许通过__attribute__((constructor(priority)))指定优先级(数值越小越早执行),链接器会按优先级合并到.init_array的子段(如.init_array.0、.init_array.1)中。

PS:在早期的ELF实现中,用户可以通过_init()函数定义初始化逻辑,但现代工具链更推荐使用.init_array。

1.2 执行

对于可执行程序,在程序入口(如_start)调用main函数之前,.init中的代码会首先执行,在.init段代码执行后,.init_array中的函数会按顺序依次执行。musl中的libc_start_init函数就是负责执行.init和.init_array中的代码的:

static void libc_start_init(void)
{
	_init();
	uintptr_t a = (uintptr_t)&__init_array_start;
	for (; a<(uintptr_t)&__init_array_end; a+=sizeof(void(*)()))
		(*(void (**)(void))a)();
}

注意这个_init()函数就是1.1中所说的用户定义的_init()函数,它是一个弱符号,如果用户没有定义_init()函数,它就会使用musl中的默认实现(一个空函数,什么也不做):

static void dummy(void) {}
weak_alias(dummy, _init);

当然不光可执行程序有这两个段,动态库中也有这两个段:

对于动态库,这两个段中的函数是由动态链接器进行调用的。在动态链接器完成对动态库的加载和重定位后,就会调用这两个段中的函数。


上面都是对单一的elf文件(动态库、可执行文件)来说,但通常来说elf文件都会有自己的依赖库,此时elf文件和其依赖库的初始化(即调用.init和.init_array中的函数,下文会多次使用初始化这个词语)顺序要满足拓扑排序(先调用依赖库的初始化函数,再调用自身的初始化函数),就像下面这个图所示(这是由动态链接器自主完成的,用户不需要操心):

具体细节可以参考:

https://refspecs.linuxfoundation.org/elf/elf.pdf

2 一个由Glibc的独家秘方导致的Bug

按照规范来说.init和.init_array中的函数是不需要传递参数的,但glibc做了扩展,它会向这些函数传递三个参数:argc,argv,envp。

PS:我猜测它这样做的目的是为了让动态库也能够直接使用argc,argv,不这样做的话动态库是没法直接拿到argc和argv的,除非导出一个函数,由可执行程序传递。(envp是可以拿到的,通过environ全局变量)。下面是glibc中执行.init和.init_array段中函数的代码,可以看到它传了这三个参数:

  ElfW(Dyn) *init_array = l->l_info[DT_INIT_ARRAY];
  if (init_array != NULL)
    {
      unsigned int j;
      unsigned int jm;
      ElfW(Addr) *addrs;

      jm = l->l_info[DT_INIT_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr));

      addrs = (ElfW(Addr) *) (init_array->d_un.d_ptr + l->l_addr);
      for (j = 0; j < jm; ++j)
	((dl_init_t) addrs[j]) (argc, argv, env);
    }

在我实现动态链接器时,我主要参考的是musl的代码,我并不知道glibc对初始化函数做了扩展,于是Bug就产生了。在gnu linux环境下Rust std会使用到glibc的这个扩展,这导致我的动态链接器加载的动态库使用std::env::args()函数时会出错。下面就是Rust std中使用到这个特性的地方:

rust">/// glibc passes argc, argv, and envp to functions in .init_array, as a non-standard extension.
/// This allows `std::env::args` to work even in a `cdylib`, as it does on macOS and Windows.
#[cfg(all(target_os = "linux", target_env = "gnu"))]
#[used]
#[link_section = ".init_array.00099"]
static ARGV_INIT_ARRAY: extern "C" fn(
	crate::os::raw::c_int,
	*const *const u8,
	*const *const u8,
) = {
	extern "C" fn init_wrapper(
		argc: crate::os::raw::c_int,
		argv: *const *const u8,
		_envp: *const *const u8,
	) {
		unsafe {
			really_init(argc as isize, argv);
		}
	}
	init_wrapper
};

简单来说,Rust std会使用glibc的这个特性,在动态库初始化时设置几个全局变量,这几个全局变量中保存的就是argc,argv,envp的值。而我实现的动态链接器在调用初始化函数时不会传递这几个值,所以在动态链接器执行上面这段代码中的init_wrapper函数(它在.init_array中,是一个初始化函数)时,argc和argv传进来的都是垃圾值,而std::env::args()函数又会使用init_wrapper函数设置的全局变量,于是在被加载进来的动态库执行std::env::args()函数时,程序就崩溃了。

Bug发现和修复的细节可以看下面这个链接:

std::env::args seems to require billions of bytes · Issue #3 · weizhiao/dlopen-rs · GitHubHi, I'm playing around with your crate and I noticed that if you create a file that looks like this: #[unsafe(no_mangle)] fn test() { let args = std::env::args(); } and a file main.rs that looks like this: use dlopen_rs::{ElfLibrary, Ope...https://github.com/weizhiao/dlopen-rs/issues/3


http://www.niftyadmin.cn/n/5863792.html

相关文章

前端面试-网络协议篇

1.http网络协议中post和get有什么区别 在HTTP网络协议中&#xff0c;POST和GET主要有以下区别&#xff1a; 参数传递方式&#xff1a; GET&#xff1a;参数附加在URL后面&#xff0c;如example.com?key1value1&key2value2 。POST&#xff1a;参数放在HTTP请求体中。 数据…

ragflow-RAPTOR到底是什么?请通俗的解释!

RAPTOR有两种不同的含义&#xff0c;具体取决于上下文&#xff1a; RAPTOR作为一种信息检索技术 RAPTOR是一种基于树状结构的信息检索系统&#xff0c;全称为“Recursive Abstractive Processing for Tree-Organized Retrieval”&#xff08;递归抽象处理树组织检索&#xff09…

【MCU驱动开发概述】

MCU驱动开发概述 目录 MCU驱动开发概述二、驱动开发的目的三、驱动开发的关键组成部分四、示例 - LED 控制驱动 一、引言 MCU&#xff08;Microcontroller Unit&#xff09;&#xff0c;即微控制器单元&#xff0c;是一种集成在单个芯片上的计算机系统&#xff0c;通常用于控制…

1.3 AI量化炒股的基本流程

**定性价值&#xff1a;** AI量化炒股通过系统化流程&#xff08;数据采集→策略建模→回测优化→实盘执行&#xff09;实现投资决策的客观性与一致性&#xff0c;有效规避人为情绪干扰。例如&#xff0c;基于历史数据挖掘市场规律&#xff0c;结合机器学习动态调整参数&#…

KAJIMA CORPORATION CONTEST 2025 (AtCoder Beginner Contest 394)题解 ABCDE

A - 22222 题意&#xff1a;保留2 思路&#xff1a;模拟 // Code Start Here string s;cin >>s;for(auto i : s){if(i 2)cout << i;}cout << endl;return 0; B - cat 题意&#xff1a;根据长度排序 思路&#xff1a;模拟 // Code Start Here int n;…

【论文带读(1)】《End-to-End Object Detection with Transformers》论文超详细带读 + 翻译

目录 前言 Abstract 一、Introduction 二、Related work&#xff08;相关工作&#xff09; 2.1 Set Prediction&#xff08;设置预测&#xff09; 2.2 Transformers and Parallel Decoding&#xff08;Transformer和并行解码&#xff09; 2.3 Object detection&#xf…

【NLP 31、预训练模型的发展过程】

人的行为&#xff0c;究竟是人所带来的思维方式不同还是与机器一样&#xff0c;刻在脑海里的公式呢&#xff1f; 只是因为不同的人公式不同&#xff0c;所以人的行为才不同&#xff0c;可这又真的是人引以为傲的意识吗&#xff1f; 人脑只是相当于一个大型、驳杂的处理器&#…

S8711A UXM5G 测试应用软件

苏/州/新/利/通 S8711A UXM 5G 测试应用软件 简述 Keysight S8711A UXM 5G 测试应用软件是一款交互式实时测试工具&#xff0c;适用于从早期原型测试一直到集成和验证的整个芯片和设备开发工作流程。 它提供了全套网络仿真、射频测试和功能测试工具&#xff0c;能够高度自…