Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
elf
rt-smart
RT-Smart elf 动态加载技术 : elf 加载原理与流程
发布于 2023-12-23 22:19:36 浏览:1900
订阅该版
[tocm] ## 前言 - RT-Smart 类似于Linux,可以动态的加载与运行应用程序 elf 文件,也就是内核与应用可以分开,一个内核,多个应用,不同的应用可以按需加载执行 - 应用程序 elf 文件,有的是静态链接编译的,有的是动态链接编译的,动态链接编译的,elf 的运行依赖动态共享库 (.so) - 本篇讲解一下 RT-Smart 上 elf 文件的介绍 与 动态加载原理,动态加载属于内核的【基础设施】,对于一般用户来讲其实是【透明】的,不过了解动态加载的原理与流程,可以更好的理解与掌握操作系统 ## 环境搭建 - RT-Thread 版本: 当前 最新 master 主线版本 - BSP : bsp/qemu-virt64-aarch64 - 开启 RT-Smart - dfs : V2 版本,开启 pagecache - 开启 ldso 动态加载 - 使用开启了 ldso 功能的 musl gcc 交叉编译工具链编译内核与应用程序 ## 引言 - 既然 elf 文件是可执行的,但是我在 RT-Smart msh 命令行输入 `xxx.elf` 文件时,执行失败,提示找不到这个命令,这是什么原因? - 显然 elf 文件执行并不像 shell 命令那样执行 - 那么 elf 文件到底是怎么执行的呢? ## elf 文件介绍 - 全称:Executable and Linking Format (ELF),可以翻译成 可执行与可链接的格式(文件格式) - elf 属于一种文件格式,elf 文件就是 elf 格式的文件,是一种二进制文件,对比 `*.txt` 属于 ASCII 文本文件。 - elf 格式的文件常见于 Linux、类Unix 系统,与 windows 上 PE 格式文件 `*.exe` `dll` 不同 - 虽然 elf 文件属于可执行的,但是不是直接执行,而是需要借助操作系统内核进行加载与执行 - 不同的平台,elf 文件需要使用各自平台的 gcc 工具链进行编译链接,比如 ARM 32位平台上的 elf 文件,不能放在 ARM 64位(aarch64)平台上加载执行 ![2023-12-23_211100.png](https://oss-club.rt-thread.org/uploads/20231223/9058aef5ebfae811d71419a5bbebf200.png.webp) ## elf 格式文件分类: - 可能大家觉得只有后缀是 elf 的应用程序才是 elf 格式的文件,其实 elf 文件格式是通过文件的具体内容来解析的,比如Linux 平台下,应用程序 elf 文件是没有后缀的。 - elf 文件有个 ELF 头部,这个头部,有 elf 文件的文件属性,由操作系统内核进行解析。 - elf 文件一般分为三类: ![2023-12-23_211437.png](https://oss-club.rt-thread.org/uploads/20231223/494f531dc0f84b946ddf4aaa0929de38.png.webp) - `executable file` : 可执行应用文件 - `shared object file` :动态共享库,如 `.so` - `relocatable file` : 可重定位文件, 如 `.o`,默认不能直接加载执行,一般需要链接后执行 ## elf 文件组成 - 动态加载首先需要了解需要加载的对象: elf 文件,了解 elf 文件怎么来的,怎么产生的,一般 elf 文件是由 gcc 工具链编译链接生成的,类似于【黑盒子】操作,按照 elf 标准生成的 elf 文件,用户一般不会关心,但对于动态加载,需要有所了解。 - elf 文件格式是一种标准文件格式,需要查看 elf 文件格式的规范,了解 elf 文件的组成,这个规范可以网络搜索一下获取。 - gcc 工具链 链接器 按照 elf 标准打包生成了 elf 文件,如果要操作系统内核 加载与运行 elf 文件,就需要按照 elf 标准 拆包解析 elf 文件。 ## elf 文件查看 - 想学习 动态加载技术与加载原理,熟悉 elf 文件的结构与组成是必要的。 - Linux 平台下 `file` 与 `readelf` 是非常好用的工具,可以查看 elf 文件信息,以及 elf 文件组成 ![2023-12-23_212333.png](https://oss-club.rt-thread.org/uploads/20231223/0c40de2af15aa22ae09591636037a4d7.png) - 工欲善其事必先利其器,使用靠谱的工具,可以让研究更进一步,可以使用 elf 的一些特殊工具去查看 elf 文件的组成,比如 `detect-it-easy` ![2023-12-23_212552.png](https://oss-club.rt-thread.org/uploads/20231223/df53a17fe5e7302824c572b6ed05327a.png) ![2023-12-23_212643.png](https://oss-club.rt-thread.org/uploads/20231223/3c634feaf0f583aea8d1eb494f7cb6eb.png) ![2023-12-23_212759.png](https://oss-club.rt-thread.org/uploads/20231223/5d509bc8060d21ff780a9d69cb02af7b.png) ![2023-12-23_212856.png](https://oss-club.rt-thread.org/uploads/20231223/be630412ff833c4d07685e4c58d1fe19.png) - elf 格式细节,建议去查看 elf 文件规范 ## 动态加载 - dynamic loading,比如 在需要时,把 elf 文件加载到内存去执行,而不是把 elf 固化到内核,随着系统开机运行 - 也就是按需动态加载到内存去执行,当然执行完占用的内存可以回收 ## ldso - load shared object,加载共享目标文件, 这个ld 是 load 加载的意思, so 可以认为是目标文件,包括常见的 *.so 共享库 - ldso 功能一般包含在 动态加载器中,比如 Linux 上的 ld,RT-Smart 上的 musl gcc 工具链,ld 其实就是 libc.so,也就是 musl 中的 ldso 集成到了 libc.so 中。 - 备注:ldso 是动态加载器的可执行的程序,虽然集成在了 libc.so 中,说明 libc.so 是可执行,事实上, libc.so 这种共享库,都属于 elf 文件格式。 ## elf 文件加载 - 这个问题一直困扰了很久,经过不断的尝试与分析验证,发现 elf 文件加载与执行过程反而非常的简单 - 并不是借助文件系统读取 elf 文件全部内容到指定内存并执行,而是加载 elf 的 PT_LOAD 段(segment) - 通过 elf 文件查看工具了解 elf 的组成,比如 段(segment) 与 节 (section)的概念,我们常见的 `.text` `.rodata` 等属于 section(节),而 段 (segment)可以包含多个 节(sectin), elf 加载运行只需要加载 elf 的 PT_LOAD 段(segment),这也算是简化了 elf 文件的内容的加载。 - 先读取 elf 文件的头部,解析 elf 文件,获取 elf 文件的段与节等信息,然后加载 elf 文件的 段表 (segment table),遍历 段表,加载 `PT_LOAD` 类型的段到内存的指定位置,段表中的 `p_vaddr` 是这个段的内存加载地址,`p_filesz` 是这个段的长度(大小) - 【备注】你是怎么知道 elf 加载运行只加载 【PT_LOAD】段(segment)内容,而不是整个 elf 文件内容的? - 答:查看 Linux 代码,并且深入理解 elf 文件的组成,elf 的 `PT_LOAD` 段包含很多的内容,把 elf 有用的内容 `section` 全部包含了,elf 如果带调试 DEBUG 信息,这些与加载执行关系无关,不加载到内存(浪费内存资源) ## 动态加载分工 - 在 RT-Smart 中,ldso 动态加载器功能包含在了 musl gcc 工具链中的 `libc.so` 中,那么 RT-Smart 内核还需要做点什么呢? - RT-Smart shell 命令行输入 elf 的文件名字(包括执行路径),到动态加载 elf 文件,里面是有一些操作流程的 ### RT-Smart 内核动态加载中需要做的工作: - (1)内核需要获取 elf 文件的名字与路径,这部分需要通过 RT-Smart `shell` 命令行等执行路径获取到 - (2)内核需要解析并确认 elf 文件的合法性,是否是 elf 文件格式,是否是【CPU平台】的 elf 文件格式,这里强调一点,可以通过翻阅 elf 文件格式规范,掌握 elf 文件的组成 与 elf 特性,32位平台不能执行64位平台的 elf 文件,64位平台上也不能执行32位平台的 elf 文件。当然不同的平台,内核与应用 app 使用不同的编译工具链。 - (3)内核需要确认 elf 文件是 静态链接的还是动态链接的(依赖共享库 .so),这个可以通过 elf 文件是否包含 `INTERP` 类型的段来判断,如果包含 `INTERP` 类型的段(segment),就是动态链接的应用程序,就需要借助 动态加载器 `ldso` - (4)如果 elf 文件是静态链接的,读取 elf 的 `PT_LOAD` 段,注意一般 elf 文件可能有多个 `PT_LOAD` 段 到指定的内存地址,并解析出 elf 文件的 入口地址 `entry point`,创建进程环境,执行 elf 文件入口地址,切换到用户态,就可以运行 elf 文件了 - (5)如果 elf 文件是动态链接的,除了加载 elf 文件本身的 `PT_LOAD` 段,还需要加载 动态加载器(ld),在 musl gcc 工具链中,动态加载器 集成在 `libc.so` 中,也就是加载 `libc.so`,如 aarch64 平台的 musl gcc `ld-musl-aarch64.so.1` 是个指向 libc.so 的文件软链接,如果文件系统像 elm fat 格式不支持 文件软链接,需要复制 `libc.so` 到 `ld-musl-aarch64.so.1`,并获取 ld 动态加载器的入口地址,也就是 `libc.so` 的入口地址 `entry point`,创建进程环境,应用程序的加载信息通过入参、env、AUX 等需要设置并传递给动态加载器 ld,执行 动态加载器 ld 的入口,动态加载器 ld 会经过一系列的操作,加载 应程序的依赖的共享库,为 elf 执行准备好条件,并最终执行 elf 应用程序的入口地址。 ![ldso_load.png](https://oss-club.rt-thread.org/uploads/20231223/7c4d36220b50f5dd005a4c1e37148eec.png.webp) ### 动态加载器做的工作 - 动态加载器做了很多复杂的工作,动态加载器运行于 用户态,各种操作需要借助操作系统内核的【系统调用】来实现 - 动态加载器首先加载自身,并对自己进行【重定位】等操作,并解析 应用程序,加载应用程序依赖的共享库,并进行重定位操作,初始化好 libc,最后执行应用程序的入口地址。 - 经过动态加载器的一番操作,动态链接的应用程序就像静态链接的应用程序那样,所有依赖的执行操作代码都载入到了内存,可以正常执行了 ![ldso_start.png](https://oss-club.rt-thread.org/uploads/20231223/fd782adaa5d17a6b31424b6195171606.png.webp) ## 动态加载依赖的内核机制 - 文件映射 mmap,内核需要提供文件的映射机制,这是因为 动态加载器是通过 `mmap` 对 elf 文件的 段内容进行内存映射的,通过文件映射 mmap 过的内存,进程退出时,会自动释放映射的内存,回收占用的物理内存。 - 按需加载机制:内核的文件mmap 机制,虽然把文件的段内容的范围映射到了某个【虚拟地址】,但是如果内容没有执行到,不需要把全部的段内容都填充到【物理内存】,也就是映射不分配内存,只有访问或者执行某个页面的代码时再实际分配实际物理内存并加载。这样的好处就是降低对物理内存的占用,并且映射一个大的 elf 文件时,不需要一上来就读取全部文件内容到内存,减少加载时间。也有不足,就是运行过程中需要加载新的文件内容,加载需要时间,影响一点执行的速度。 - pagecache : 页缓存机制,如果系统的内存不够大,一直运行 多个大的的 elf 文件,会造成 物理内存最终被占用完,因此需要一个 页缓存机制,执行 elf 过程中 加载内存的页,加入缓存管理,如果缓存满了,释放一部分 加载过内容的页,降低整个内存的占用,当然页缓存最大设置数量过小,或者实际物理内存过小,可能导致部分 elf 文件内容多次读取、释放,影响执行时间。 ## 小结 - elf 动态加载流程还是有点复杂,但是如果想了解一个应用程序怎么生成 elf 文件,怎么一步步执行的,这也是比较有意思的 - 后面抽时间继续讲一下具体的流程与细节,从而解开 动态加载的内幕 - 动态加载除了参考 Linux 等平台的动态加载代码,可以参考的书籍确实不算多,大部分的书籍上来就是一堆【汇编代码】,讲解细节,但是原理与流程却没有提到,只谈细节不谈原理。 - 本篇的初衷还是先想讲清楚原理,细节与代码实现部分,后面使用 RT-Smart qemu 平台讲解与代码调试
2
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
张世争
学以致用
文章
131
回答
804
被采纳
174
关注TA
发私信
相关文章
1
动态模块加载为何不支持elf可执行文件?
2
如何动态加载用户程序(elf)
3
用户程序如何动态加载
4
如何将编译后用户程序放到开发板上运行
5
龙芯2K1000加载RTThread的elf固件失败
6
RT-Thread Studio For Vscode编译工程报错
7
Scons生成elf报错
8
rt-smart发布时间
9
rt-smart qemu-vexpress-a9 编译报错
10
rt-smart分支编译rasp4-32bsp报错
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
a1012112796
20
个答案
3
次被采纳
张世争
11
个答案
3
次被采纳
踩姑娘的小蘑菇
7
个答案
3
次被采纳
rv666
9
个答案
2
次被采纳
用户名由3_15位
13
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
RTT_逍遥
1
篇文章
6
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部