当前位置: 首页 > news >正文

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

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中使用到这个特性的地方:

/// 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

相关文章:

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

ELF文件中的.init和.init_array段是程序初始化阶段的重要组成部分&#xff0c;用于在main函数执行前完成必要的初始化操作。 1 .init段和.init_array 段 1.1 作用 .init段包含编译器生成的初始化代码&#xff0c;通常由运行时环境&#xff08;如C标准库的启动例程&#xff0…...

Elasticsearch:使用经过训练的 ML 模型理解稀疏向量嵌入

作者&#xff1a;来自 Elastic Dai Sugimori 了解稀疏向量嵌入&#xff0c;理解它们的作用/含义&#xff0c;以及如何使用它们实现语义搜索。 Elasticsearch 提供语义搜索功能&#xff0c;允许用户使用自然语言进行查询并检索相关信息。为此&#xff0c;目标文档和查询必须首先…...

安宝特方案 | 电力行业的“智能之眼”,AR重新定义高效运维!

引言&#xff1a; 电力行业正经历智能化变革&#xff0c;安宝特AR数字化工作流以四大核心优势&#xff0c;为电力企业打造全场景智慧运维方案&#xff01; 四大颠覆性功能&#xff0c;直击行业痛点 1、高度自定义作业流程 支持图文指引、语音播报、AI实时识别&#xff08;如…...

【落羽的落羽 数据结构篇】树、二叉树

文章目录 一、树1. 树的概念和结构2. 树的相关术语 二、二叉树1. 概念与结构2. 满二叉树3. 完全二叉树4. 二叉树的性质5. 二叉树的存储结构 一、树 1. 树的概念和结构 之前我们学习了线性表&#xff0c;今天我们再来接触一种全新的数据结构——树。 树是一种非线性的数据结构…...

[回顾]从原型链视角解读Vue底层实现Vue VueCompoent VM VC关系

从原型链视角解读VueComponent与Vue关系 原型链 根据,原型链涉及三个关键属性:__proto__是所有对象的私有属性,指向原型链的第一个元素;prototype是函数的属性,实例对象不拥有它;constructor指向构造函数。提到原型链是JS中实现继承的机制,通过属性链式查找属性,直到…...

springcloud nacos 整合seata解决分布式事务

文章目录 nacos安装Mysql5.7安装及表初始化seata server安装下载并解压seata安装包在conf文件夹修改file.conf文件向本地数据库导入seata需要的表修改registry.conf文件将seata配置信息添加到nacos配置中心启动seata server springcloud整合seata测试流程正常下单流程扣减库存失…...

【算法系列】快速排序详解

文章目录 快速排序的多种实现方式1. 基本快速排序&#xff08;Lomuto 分区方案&#xff09;1.1 基本原理1.2 步骤1.3 Java 实现示例 2. Hoare 分区方案2.1 基本原理2.2 步骤2.3 Java 实现示例 3. 三数取中法3.1 基本原理3.2 步骤3.3 Java 实现示例 4. 尾递归优化4.1 基本原理4.…...

神经网络发展简史:从感知机到通用智能的进化之路

引言 神经网络作为人工智能的核心技术&#xff0c;其发展历程堪称一场人类对生物大脑的致敬与超越。本文将用"模型进化"的视角&#xff0c;梳理神经网络发展的五大关键阶段&#xff0c;结合具象化比喻和经典案例&#xff0c;为读者呈现一幅清晰的AI算法发展图谱。 一…...

C语言番外篇(4)--------->goto语句

在C语言中&#xff0c;有一个很特殊的语法&#xff0c;这就是goto语句。goto用于实现同一函数的跳转&#xff0c;goto后面会有一个标志&#xff0c;执行goto语句时&#xff0c;就会跳转到标志的位置。 一、goto语句的语法 &#xff08;1&#xff09;goto在前&#xff0c;标志…...

AI 编码 2.0 分析、思考与探索实践:从 Cursor Composer 到 AutoDev Sketch

在周末的公司【AI4SE 效能革命与实践&#xff1a;软件研发的未来已来】直播里&#xff0c;我分享了《AI编码工具 2.0 从 Cursor 到 AutoDev Composer》主题演讲&#xff0c;分享了 AI 编码工具 2.0 的核心、我们的思考、以及我们的 AI 编码工具 2.0 探索实践。 在这篇文章中&am…...

Linux与自动化的基础

Linux简介 Linux是一种开源的类Unix操作系统&#xff0c;广泛应用于服务器、桌面和嵌入式设备。常见的Linux发行版包括 Ubuntu、CentOS 和 Debian&#xff0c;它们各有特色&#xff0c;但都以稳定性和安全性著称。 与图形界面相比&#xff0c;Linux的**命令行界面&#xff08…...

安全开发-环境选择

文章目录 个人心得虚拟机选择ubuntu 22.04python环境选择conda下载使用&#xff1a; 个人心得 在做开发时配置一个专门的环境可以使我们在开发中的效率显著提升&#xff0c;可以避免掉很多环境冲突的报错。尤其是python各种版本冲突&#xff0c;还有做渗透工具不要选择windows…...

【算法设计与分析】(一)介绍算法与复杂度分析

【算法设计与分析】&#xff08;一&#xff09;介绍算法与复杂度分析 前言一、什么是算法&#xff1f;二、算法的抽象机制三、描述算法四、复杂度分析4.1 时间复杂度4.2 空间复杂度 前言 从搜索引擎的高效检索&#xff0c;到推荐系统的个性化推荐&#xff0c;再到人工智能领域…...

SurfaceFlinger代码笔记

drawLayers是做client合成&#xff0c;合成完以后的buffer会放在RenderSurface里 FrameBufferSurface里的buffer是通过setClientTarget给到HWC的&#xff08;HWC应该给client合成的buffer留了一个slot) Output.cpp这个文件非常关键&#xff0c;代表着具体一个Display的操作 d…...

2025 PHP授权系统网站源码

2025 PHP授权系统网站源码 安装教程&#xff1a; PHP7.0以上 先上传源码到服务器&#xff0c;然后再配置伪静态&#xff0c; 访问域名根据操作完成安装&#xff0c; 然后配置伪静态规则。 Ngix伪静态规则&#xff1a; location / { if (!-e $request_filename) { rewrite …...

Fisher散度:从信息几何到机器学习的隐藏利器

Fisher散度&#xff1a;从信息几何到机器学习的隐藏利器 在机器学习和统计学中&#xff0c;比较两个概率分布的差异是常见任务&#xff0c;比如评估真实分布与模型预测分布的差距。KL散度&#xff08;Kullback-Leibler Divergence&#xff09;可能是大家熟悉的选择&#xff0c…...

深度学习每周学习总结Y1(Yolov5 调用官方权重进行检测 )

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客Y1中的内容 &#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 ** 注意该训练营出现故意不退押金&#xff0c;恶意揣测偷懒用假的结果冒充真实打卡记录&#xff0c;在提出能够拿到视频录像…...

实体机器人在gazebo中的映射

这一部分目的是将真实的机器人映射到gazebo中&#xff0c;使得gazebo中的其他虚拟机器人能识别到真实世界的wheeltec机器人。 真实机器人的型号的wheeltec旗下的mini_mec。 一、在wheeltec官方百度云文档中找到URDF原始导出功能包.zip 找到对应的包 拷贝到工作空间下 在原有…...

【学习笔记】Kubernetes

一、 概览 Kubernetes 提供了一个抽象层&#xff0c;是用户可以在屋里或虚拟环境中部署容器化应用&#xff0c;提供以容器为中心的基础架构。 Kubernetes的控制平面和工作节点都有什么组建&#xff1f; 分别有什么作用&#xff1f; 1.1 Kubernetes控制平面和工作节点的组件及…...

【网络编程】几个常用命令:ping / netstat / xargs / pidof / watch

ping&#xff1a;检测网络联通 1. ping 的基本功能2. ping 的工作原理3. ping 的常见用法4. ping 的输出解释5. ping 的应用场景6. 注意事项 netstat&#xff1a;查看网络状态 1. netstat 的基本功能2. 常见用法3. 示例4. 输出字段解释5. netstat 的替代工具6. 注意事项 xargs&…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

计算机基础知识解析:从应用到架构的全面拆解

目录 前言 1、 计算机的应用领域&#xff1a;无处不在的数字助手 2、 计算机的进化史&#xff1a;从算盘到量子计算 3、计算机的分类&#xff1a;不止 “台式机和笔记本” 4、计算机的组件&#xff1a;硬件与软件的协同 4.1 硬件&#xff1a;五大核心部件 4.2 软件&#…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...

Python网页自动化Selenium中文文档

1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API&#xff0c;让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API&#xff0c;你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...

【UE5 C++】通过文件对话框获取选择文件的路径

目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 &#xff0c;这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器&#xff0c;右键点击 .uproject 文件&#xff0c;选择 "Generate Visual Studio project files"&#xff0c;重…...

rknn toolkit2搭建和推理

安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 &#xff0c;不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源&#xff08;最常用&#xff09; conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...