从一个ELF程序的加载窥探操作系统内核-(5)

操作系统加载一个ELF程序看似一个EASY的动作,其实下面隐藏了很多很多OS内核的关键实现,让我们一起来解密其中的流程

作者是一个micro kernel的开发者,在设计动态链接器的时候,在此留下一些笔记,重点参考了以下资料文献

  • 《程序员的自我修养》
  • 《深入理解计算机系统》
  • 《现代操作系统-原理与实现》
  • 《深入理解LINUX内核》
  • 《设计模式/JAVA》

LINUX下的ELF加载器究竟是如何完成的

ELF加载器其实和OS的实现是紧密捆绑在一起的,就像glibc捆绑了linux一样,可移植性是很差的

  • LINUX下的ELF加载流程如下

当ELF程序需要解释器(动态链接器)的时候,LINUX内核只完成了初步的解析工作,剩下的工作就转给链接器去完成了,也就是大名鼎鼎的ld.so

Linux下一个最小程序,也必须包括ld.so和libc.so这两个动态库

  • ld.so看起来是个动态库,实际上他是静态的,这里说的静态指的是他的运行不依赖任何外部库,只是被编译成了PIC了,算作披着羊皮的狼吧。但是既然ld被编译成了动态库的形式,那么必然有重定位工作要做,也就是对自己的重定位,重定位后就可以正常使用ld.so内的函数和全局变量了
  • 有同学说既然是这样,为什么不直接编译静态库算了,链接器自举操作纯属脱了裤子放屁,归根结底还是为了节省内存罢了

glibc下的ld.so链接器流程如下

链接器最重要的工作就是映射依赖库以及重定位工作,映射工作主要依赖mmap这个系统调用,重定位是链接器最复杂的,里面的细节可以参考ELF加载器的原理与实现

  • 完成重定位工作后,最后一步将程序转移到crt,crt是一个C运行时环境,CRT有什么用呢?
  • 很多同学有疑问?既然链接器都完成了全部工作,不应该跳转到main去运行程序吗?实际上在执行main前我们还需要为main做一点准备工作,这个工作就是由CRT完成

CRT主要有两个工作

  1. 用户堆管理的初始化

    1. malloc用户进程的堆分配是放在libc里来完成的,但是这个内存管理器的初始化工作是在crt中完成的!
  2. 输入输出设备初始化
    1. 如果要使用printf,实际上最后调用的是write系统调用(fd=STDOUT=1),初始化stdin/stdout/stderr这三个全局变量和相关权限的工作是在crt中完成的

伪代码如下

extern int main(int argc, char *argv[]);
extern int crt_io_initialize(void);
extern int crt_heap_initialize(void);void exit(int code)
{}int crt_main_entry(int argc, char **argv)
{int ret;/* 初始化IO资源管理器 */crt_io_initialize();/* 初始化进程堆管理器 */crt_heap_initialize();/* 跳转到main */ret = main(argc, argv);/* 进程退出 *///exit(ret);return ret;
}

简单看一下IO是如何初始化的

struct streamlist tg_streamlist;#define STDIN_FILENO                       0       /* File number of stdin */
#define STDOUT_FILENO                    1       /* File number of stdout */
#define STDERR_FILENO                     2       /* File number of stderr */#define stdin       (&tg_streamlist.sl_std[STDIN_FILENO])
#define stdout     (&tg_streamlist.sl_std[STDOUT_FILENO])
#define stderr      (&tg_streamlist.sl_std[STDERR_FILENO])tg_streamlist.sl_std[0].fs_fd        = STDIN_FILENO;
tg_streamlist.sl_std[0].fs_oflags   = O_RDONLY;tg_streamlist.sl_std[1].fs_fd        = STDOUT_FILENO;
tg_streamlist.sl_std[1].fs_oflags   = O_WROK | O_CREAT;tg_streamlist.sl_std[2].fs_fd        = STDERR_FILENO;
tg_streamlist.sl_std[2].fs_oflags   = O_WROK | O_CREAT;

简单看一下用户堆是如何初始化的

先使用brk(0)确定heap的起始地址,然后默认分配132KB内存,再初始化具体的内存管理算法,glibc是ptmalloc,后面的malloc和free就由内存管理算法去分配与释放

int crt_heap_initialize(void)
{void *base = NULL;/* 参考linux分配超过128KB使用MMAP* 默认大小为128KB(0x20000)* 内存管理的数据结构本身还需占用空间,则扩容至132KB(0x21000)*/unsigned long heap_size = 132 * 1024;base = (void *)brk(0);if (!base) {return -1;}void *end = (void *)((unsigned long)base + heap_size);end = (void *)brk(end);if (!end) {return -1;}return mm_heap_initialize(base, heap_size);
}

看一下我们的ELF链接脚本该如何写

ENTRY(crt_main_entry);SECTIONS
{. = 0x08040000;.text :{*(.text .text.*)}.rodata :{*(.rodata .rodata.*)}. = ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1));.data :{*(.data .data.*)}.bss :{*(.bss .bss.*)}
}

总结一下crt的流程

最后crt最后以crt.o的方式被链接到elf程序中

从一个ELF程序的加载窥探操作系统内核-(5)相关推荐

  1. ELF文件的加载和动态链接过程

    本文的目的:大家对于Hello World程序应该非常熟悉,随便使用哪一种语言,即使还不熟悉的语言,写出一个Hello World程序应该毫不费力,但是如果让大家详细的说明这个程序加载和链接的过程,以 ...

  2. QT程序启动加载流程简介

    1. QT应用程序启动加载流程简介 1.1      QWS与QPA启动客户端程序区别 1.1.1   QWS(Qt Window System)介绍 QWS(Qt Windows System)是Q ...

  3. 程序的加载和执行(六)——《x86汇编语言:从实模式到保护模式》读书笔记26

    程序的加载和执行(六)--<x86汇编语言:从实模式到保护模式>读书笔记26 通过本文能学到什么? NASM的条件汇编 用NASM编译的时候,通过命令行选项定义宏 Makefile的条件语 ...

  4. 程序的加载和执行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25

    程序的加载和执行(五)--<x86汇编语言:从实模式到保护模式>读书笔记25 前面几篇博文终于把代码分析完了.这篇就来说说代码的编译.运行和调试. 1.代码的编译及写入镜像文件 之前我们都 ...

  5. 程序的加载和执行(四)——《x86汇编语言:从实模式到保护模式》读书笔记24

    程序的加载和执行(四)--<x86汇编语言:从实模式到保护模式>读书笔记24 通过本文能学到什么? 怎样跳转到用户程序 用户程序通过调用内核过程完成自己的功能 怎样从用户程序返回到内核 接 ...

  6. 程序的加载和执行(三)——《x86汇编语言:从实模式到保护模式》读书笔记23

    程序的加载和执行(三)--读书笔记23 接着上次的内容说. 关于过程load_relocate_program的讲解还没有完,还差创建栈段描述符和重定位符号表. 1.分配栈空间与创建栈段描述符 462 ...

  7. 程序的加载和执行(一)——《x86汇编语言:从实模式到保护模式》读书笔记21

    程序的加载和执行(一) 本文及之后的几篇博文是原书第13章的学习笔记. 本章主要是学习一个例子,对应的代码分为3个文件: ;代码清单13-1;文件名:c13_mbr.asm;文件说明:硬盘主引导扇区代 ...

  8. JAVA入门级教学之(JAVA程序的加载和运行)

    JAVA程序的加载和运行 多思考多动脑(边参考文章最后的示意图,边按步骤理解) 1.JAVA程序的加载和运行包括两个非常重要的阶段: 编译阶段 运行阶段 2.我们先来了解一下什么是编译阶段: 首先,我 ...

  9. 微信小程序数据拼接_最佳方式实现微信小程序分页加载数据

    一般小程序做分页加载数据,会做一些下拉加载更多.然后上拉刷新的操作.数据放在一个for循环里去加载,数据源是一个数组对象.在加载下一页数据时,将下一页的数据拼到当前数组后面.这样的确可以实现分页加载数 ...

最新文章

  1. Nginx服务优化——性能与安全
  2. Xamarin设备相关图片尺寸要求
  3. 微服务落地,我们在考虑什么?
  4. 我的 Serverless 实战 — Serverless 架构理念 ( 后端服务器发展 | Serverless 与 ServerFul | Serverless 定义 | 架构优缺点 )
  5. 关于App开发:模拟服务器数据接口 - MockApi
  6. 《天天数学》连载16:一月十六日
  7. VMWare学习总结(2)——VMware中三种网络连接的区别
  8. 拓端tecdat|R语言Bass模型进行销售预测
  9. 【内核】进程切换 switch_to 与 __switch_to
  10. MaxPooling里面的padding
  11. AB触摸屏2711P-T12W22A9P、2711P-T10C4A9,2711P-T10C22D8S-B、2711P-T10C22D9P-B、2711P-T12C10D2
  12. 推荐 | 南方医院历时4年构建新HIS系统
  13. HP34401a实现高精度温度测量
  14. 强化学习入门项目 Spinning up OpenAI (1) installation
  15. 【Oracle】一条SQL的一生
  16. 【Unity】如何删除不要的Tile Palette(瓦片地图)(遇到新的问题就继续更新2021.4.29)
  17. Autojs在线云更新教程
  18. linux 防火墙 超时时间,linux – TCP Keepalive和防火墙杀死空闲会话
  19. 二、生理信号处理 ——1.心电信号(含Matlab代码及数据)
  20. 啊哈C——学习3.6一起来找茬

热门文章

  1. 求派c语言程序,C语言求圆周率π(三种方法)
  2. Android循环滚动广告条的完美实现,封装方便,平滑过渡,从网络加载图片,点击广告进入对应网址
  3. SUMO应用工具:DUAROUTER
  4. oracle层级计算推演,R语言使用层次分析法进行综合指标等级划分
  5. P1199 [NOIP2010 普及组] 三国游戏-博弈论+贪心
  6. 10个PHP开发者最容易犯的错误
  7. wmv文件怎么转化为mp4,分享4个实用方法
  8. 将.html转换成.jsp,如何将html页改成jsp
  9. Kafka工具--Kafka Tool
  10. 十条经典而平凡的语录