Linux 主要可执行文件类型为 ELF参考文章:本文主要还是方便理解 Linux 在启动程序的时候大致发生了些什么,对 interpreter 加载所处的位置等有一些概念,受限于本人的水平,可能会有一些错误,主要还是以代码为主。Linux中所有支持的可执行文件类型在内核中都有一个对应的linux_binfmt类型的对象。所有的Linux_binfmt对象都保存在一个双向链表中,此链表第一个元素的地址保存在formats内核全局变量中一般程序启动的流程就是调用fork() 复制一个当前进程的副本为新的进程,子进程fock()返回后会继续调用exec()进入到内核中.Linux 6.12.32版本下总的来说exec()作用就是清空新创建的进程的.text,.data,.bss段等,然后装载新进程并运行。刷新原有的环境,设置一些标识位当是ELF的第一个段加载的时候,会根据类型完成一次地址绑定这个部分的代码主要是确定 load_bias ,后续的地址加载就是通过 load_bias + offset 就是没开 PIE 的可执行程序,直接根据 load_bias 为0计算即可ET_DYN 类型这样就会走 PIE 处理机制,实际上,ET_DYN 类型的 ELF 二进制文件分为两类:加载器必须与程序分开加载,否则程序可能会与加载器发生地址冲突(尤其是对于没有随机化加载位置的 ET_EXEC 类型)。例如,为了测试新版本的加载器而执行 "./ld.so someprog" 时,加载器后续加载的程序必须避开加载器自身的地址范围,因此它们不能共享相同的加载区间。同时,必须为加载器分配足够的 brk 空间,因为加载器需要使用 brk 功能。因此,当 ELF 为可执行程序而非 interpreter 的时候会从 ELF_ET_DYN_BASE 偏移处加载而加载器则加载到独立随机化的 mmap 区域(加载偏移量为 0,不使用 MAP_FIXED 或 MAP_FIXED_NOREPLACE 标志)。关于 "brk" 处理的详细信息见下文,其行为同样受程序 / 加载器类型及地址空间布局随机化(ASLR)的影响。elf_ex->e_entry:ELF 文件中记录的程序入口点虚拟地址(即程序开始执行的第一条指令在文件中的相对地址)。转换后:e_entry 变为程序入口点在内存中的实际地址(进程执行时,CPU 会从这个地址开始运行指令)。phdr_addr += load_bias;phdr_addr:之前计算的程序头表(PHDR)在 ELF 虚拟地址空间中的地址(见前文 “程序头表位置记录”)。转换后:变为程序头表在内存中的实际地址,供动态链接器(如ld.so)实际访问时使用(动态链接器需要通过 PHDR 解析程序依赖)。elf_brk += load_bias;elf_brk:ELF 文件中记录的堆(heap)起始虚拟地址(所有段的结束地址,堆从这里开始向上增长)。转换后:变为堆在内存中的实际起始地址,进程运行时通过brk/sbrk系统调用管理堆内存时,会基于这个地址操作。start_code += load_bias;start_code:ELF 文件中记录的代码段(可执行段)起始虚拟地址(所有可执行段的最小起始地址)。转换后:变为代码段在内存中的实际起始地址,内核通过这个地址管理代码段的内存页(如设置可执行权限)。end_code += load_bias;end_code:ELF 文件中记录的代码段结束虚拟地址(所有可执行段的最大结束地址)。转换后:变为代码段在内存中的实际结束地址,用于界定代码段的范围(与数据段区分)。start_data += load_bias;start_data:ELF 文件中记录的数据段起始虚拟地址(所有段的最大起始地址,通常在代码段之后)。转换后:变为数据段在内存中的实际起始地址,内核通过这个地址管理数据段的内存页(如设置读写权限)。end_data += load_bias;end_data:ELF 文件中记录的数据段结束虚拟地址(所有段已初始化部分的最大结束地址)。转换后:变为数据段在内存中的实际结束地址,用于界定数据段的范围。主要是通过 create_elf_tables 将 ELF 和 Interpreter 可能需要的信息注入到栈中将架构相关信息拷贝到 ELF 的栈上执行完这段代码后,进程就完全变成了新程序,当从内核返回到用户态时,将直接从新程序的入口点开始执行。这是 execve() 系统调用中从旧进程到新进程的质变点。/*
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-288885.htm
[原创]基于Linux6.12.32的程序启动流程分析
335 浏览
2 回复
感谢分享,如果能画一个流程图就更加清晰了~
讲得很详细,谢谢分享。