怎么做熊掌号网站,图书租借网站 开发,湖南株洲网,企业策划书ppt前言 本篇继续研究 musl libc ldso 的动态加载过程中遇到的关键性的概念#xff1a;到底要加载ELF 文件的哪些内容到 内存 当前如果遇到 ELF 动态加载#xff0c;当前系统需要有【文件系统】#xff0c;并且有较大的内存#xff0c;因为 ELF 文件是无法直接运行的#xf…前言 本篇继续研究 musl libc ldso 的动态加载过程中遇到的关键性的概念到底要加载ELF 文件的哪些内容到 内存 当前如果遇到 ELF 动态加载当前系统需要有【文件系统】并且有较大的内存因为 ELF 文件是无法直接运行的首先通过解析 ELF 头部 获取入口函数把需要载入到内存中的文件内容复制到指定内存区域然后执行ELF 的入口函数通常不是 ELF的 main 函数而是更早的执行函数如 _start 或者 _dlstart 函数。此时 PC 指针指向 ELF 加载的基地址 ELF 入口函数。
ELF 加载基地址
一个 ELF 文件是否可以随意的加载
当前验证发现 ELF 文件包括我们通常见到的 可以执行的文件以及 共享库如 xx.so。共享库没有连接地址基址是 0但入口函数不一定是 0如果遇到入口函数也是 0 的需要注意这个 偏移地址 为 0 的入口函数是否只是个空的符号无法执行 为何有的 xxx.so 也称作 ELF 文件 比如 musl libc.so本身是个 库但是它 有入口函数并且可以执行。 当前 musl libc.so 确实如此通常我们一般区分 执行文件与 库库不用于执行。但是 musl libc.so 具备执行的功能就像是我们见到的普通的执行文件但是它依旧具备普通库的功能为其他动态编译的应用程序提供共享库。 作为 共享库与可执行 集成在一起的 musl libc.so基地址0入口函数不为0基地址为0 可以重定位加载如手动把 libc.so 加载到 0x200000 地址 那么 libc.so 的入口函数就是 0x200000 libc.so 入口地址 普通静态或者动态链接的ELF 文件由于基地址 不为0就无法手动加载到 随意的地址。 如下 ELF 文件基地址 0x200000这个基地址跟链接脚本中的链接地址有关系可以查看这个 elf 的连接脚本配置 入口点入口地址这个地址 已经是基于基地址 0x200000的所以这个地址就不能随机加载了。如果想改变 这个 elf 的基地址需要更改 相应的 链接脚本 链接地址的设置 动态加载需要加载哪些 ELF 内容到内存 有的 ELF 文件特别的大尤其是开启了 【DEBUG】的比如编译时使用 -O0 -g gdb 的调试信息都加入 ELF 文件了 ELF 文件不同于单片机的烧写文件 bin 文件里面还有一些内容如调试信息是不需要加载到内存的那么到底需要加载什么内容呢 这部分可以查看 Linux 内核代码 elf 加载部分如 linux-6.3.8/fs/binfmt_elf.c 中的 load_elf_binary Linux 系统由于默认支持 mmu执行文件的 mmap 映射所以没有文件没有使用常规的 内存分配不过依旧是先把文件内容映射 到用户地址空间之所以不填充是因为 Linux文件mmap 有缺页异常机制需要访问时才会真正载入文件内容到内存这样有很多好处开始只映射占位子不加载这样节省了加载时间一个 ELF 文件不可能上来全部执行到可能只会执行部分内容这样采用 访问时再加载将会节省数量可观的内存节省大量的加载时间。加上文件 mmap 有 cache 功能如果加载过后缓存暂时不清掉这样下次执行就不再重复加载了。Linux 这个文件mmap 映射加载机制对于 ELF 加载非常的有用。 经过熟悉 Linux 的 load_elf_binary 发现只需要 加载 PT_LOAD 段 那么 ELF 的 PT_LOAD 段真的覆盖 ELF 的所有需要加载到内存中的内容范围吗有没有漏下的或者说 elf 不是还有 重定位、符号、.text 、.data、等等吗这些包含在里面吗 通过 elf 查看工具加上对实际加载到内存的内容进行反向 dump 出来肯定的一点就是 ELF 的 PT_LOAD 段 包含了所有需要加载到内存的文件内容是所有如果在其他的系统上发现动态加载后 内存中的文件内容不正确或者部分内容为0需要查看文件加载部分是否有处理不当的地方。
查看 PT_LOAD 段
可以使用 Die 这个工具查看 ELF文件 这里了解到 PT_LOAD 段 第一个段 文件偏移是 0也就是把 ELF 文件头部也加入了内存 两个 PT_LOAD 段 的大小Program 中的 p_filesz 就是当前的段大小总大小 0x23528 0x9f8 0x23f20之所以这么计算是因为 当前的两个段 是连在一起的。 由于段有多个节section可以查看 节 信息 通过 计算 PT_LOAD 段的总大小知道 这个 elf 文件 前面 0 ~ (0x23f20 -1) 也就是 0x23f20 个字节已经加载到内存剩下 的节.bss 没有实际内容但内存中需要留位置并且清 0。其他的节全部是 调试信息 debug 相关的。 所以通过加载 PT_LOAD 段确实实现了整个 ELF 必需文件内容的全部加载
加载大小 这里需要提一下段的加载大小不是 段的 p_filesz而是 段的 p_memsz p_memsz 一般等于或者大于 p_filesz超出的大小就是 .bss section 的大小这部分大小需要手动清零不清零可能引发程序启动后的异常比如定义了一个变量但是没有初始化就使用而程序员默认没有初始化的变量会被初始化 为 0。 清零 .bss 就是清零 PT_LOAD 段 中 p_memsz - p_filesz 大小的区域这个区域的起始地址应该是 base elf_ppnt-p_vaddr elf_ppnt-p_filesz如果是静态连接编译的 elf 程序 base 是0也就是 elf_ppnt-p_vaddr elf_ppnt-p_filesz。elf_ppnt-p_vaddr 是这个文件段的起始地址。 这里需要提一下 段的 p_offset这个是相对文件本身的偏移通过情况下 p_offset 与 p_vaddr 是相同的但也有不相同的。所以在文件填充时需要把 文件内容 偏移 p_offset 后读取到内存地址 p_vaddr 的位置也就是说 文件内容的存放位置 与 文件映射到内存的地址并非一一对应。 小结 本篇注意讲解一下 ELF文件在 动态加载时需要加载哪些内容到内存注意这里的动态加载是动态加载 ELF 文件这个 ELF文件不单是 动态编译链接的 ELF也包括静态编译链接的 ELF 以及 经常遇到的 动态共享库 (xx.so 需要熟悉 ELF 的 头部、Program Header、了解 各个 Segment 段了解 Section 节信息这样对理解 动态加载程序熟悉 动态加载非常有用。 需要了解操作系统的进程、线程机制文件映射 mmap 机制。注意需要反复确认 内存的文件内容是否正确、完整。可以同 dump 的方式把内存中的文件内容 dump 成一个文件然后与实际的文件进行内容对比。 需要深刻了解 文件段的本身的偏移 p_offset 与 内存地址 p_vaddr 的关系也需要了解 段真实文件大小 p_filesz 与 p_memsz 的关系也就是 .bss 节的存在