这篇文章会包含很多项目的源码阅读, 覆盖 elf 的解析, elf 的加载, elf 的链接, 一次说清楚. 并换一种风格, 只在代码关键地方做注释. 并不会直接总结一个 elf 包含了哪些信息, 然后去哪找云云, 只是纯粹的源码阅读的记录, 看看在实际生产中 elf 如何被使用的, 必要的地方做一些总结和提示, 仅仅如此, 这点您要做好心理准备.所有注释, 总结, 补充说明都是我改过, 读过的, 放心食用.编译完能跳转了, 但是还是有红线, 不理会.这个我也写了, 实现的很乱, 各种历史的沉淀, 不好读, 就删了, 感兴趣可以自己看看, glibc/elf/rtld.c 中的 _dl_start 是入口函数.编译也很快, 但是有红线, 推荐看网页版: 1a9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6W2L8r3W2^5K9i4u0Q4x3X3g2T1L8$3!0@1L8r3W2F1i4K6u0W2j5$3!0E0i4K6u0r3k6$3I4A6j5X3y4Q4x3V1k6Y4L8r3W2T1j5#2)9J5k6o6u0Q4x3X3f1K6x3W2)9J5c8Y4y4G2N6i4u0U0k6b7`.`.不想编译 AOSP, 直接看网页版: 5e9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0M7#2)9J5k6h3q4F1k6s2u0G2K9h3c8Q4x3X3g2U0L8$3#2Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0r3M7r3I4S2N6r3k6G2M7X3#2Q4x3V1k6K6N6i4m8W2M7Y4m8J5L8$3A6W2j5%4c8Q4x3V1k6Q4x3V1u0Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0V1L8r3q4@1k6i4y4@1i4K6u0V1M7X3g2D9k6h3q4K6k6g2)9K6b7h3u0A6L8$3&6A6j5#2)9J5c8W2)9J5b7H3`.`. 个人观点" 代码比 glibc 中的好读一万倍, 代码写的非常好, 看着很舒服. elf 的解析 实现, 大而全, 很好读. 作为开胃菜, 让我们从这里开始.找到 main 函数:前边就是在处理输入的参数, 然后调用 process_file 解析, 传入的参数 argv[optind++] 是一个个文件名, 跟进去:主要就是在根据魔数识别文件类型, 分发给对应处理函数, 我们继续跟入 process_object, 看看如何处理可执行文件的:这是是核心"地图", 顺着这个函数我们可以找到任何一个想要的功能, 虽然代码排版很奇怪, 但逻辑很明了, 从上大小一点点解析, 好了, 接下来我们看看这些函数了, 他们会非常多.其中一些工具函数(byte_get_little_endian, byte_put_little_endian 等), 我们不再看, 大致流程就是根据前 16 字节确定大小端, 32 位还是 64 位, 然后用已有结构体读取文件头, 下面贴一下 Elf32_External_Ehdr, Elf64_External_Ehdr, 也就是程序头的结构体:保留了英文注释, 更原汁原味.这个真是再熟悉不过了.接着看看 get_32bit_section_headers, get_64bit_section_headers:这一段就是根据之前读取的 ELF 文件头的记录的节头表偏移(e_shoff), 单个节头项大小(e_shentsize), 节头项总数(e_shnum)填充 filedata 的section_headers 字段, 节头表类似书的目录, 它记录了 ELF 文件中所有节(如代码段, 数据段, 符号表, 字符串表等)的关键信息(位置, 大小, 类型, 属性等), 后续解析文件内容时, 通过节头表就能快速定位到各个节的具体位置.贴一下 Elf32_External_Shdr Elf64_External_Shdr, 节头表项:依旧原汁原味.将文件头的信息进行打印, 值得注意的是: 该来的还是来了, 一个非常长的函数节头表中记录了一个个节以及他们对应的数据, 主要用于链接和调试, 告诉链接器如何把不同文件的节组合起来, 以及告诉调试器代码和数据在文件中的具体位置.整体看下来就是对节头表的校验和打印后, 将信息存入 filedata 中, 值得一说的是, 每一个节头表项都有一个名字, 这个名字记录的是偏移, 在上边说的 e_shstrndx 中对应的 .shstrtab 节的字符串池中做偏移, 查找字符串.其中调用了 get_elf_symbols, 解析符号表, 我们也来看看.get_32bit_elf_symbols get_64bit_elf_symbols:总结一下就是将符号表和扩展表的符号信息提取出来然后返回.值得一提的是, 扩展表(.symtab_shndx 节)与符号表(.symtab 节), .symtab_shndx 节是 .symtab 节的辅助补充表, 核心作用是解决符号表中 节索引字段位数不足 的问题, ELF 符号表中每个符号都有 st_shndx 字段, 用于存储该符号所属的节索引, 当 st_shndx 溢出时就需要 .symtab_shndx 节来补充存储"超长节索引". 符号表与扩展表的关联通过节头的 sh_link 字段绑定, 扩展表的 sh_link 字段会存储其关联的符号表在节头表中的索引.Elf32_External_Sym, Elf64_External_Sym, 符号表项结构体:依旧贴出.程序头表记录了一个个段, 他们描述了如何装载到内存中, 比如可执行代码段, 只读数据段, 可读写数据段等, 主要用于加载和运行, 告诉操作系统的加载器应该把文件的哪些部分映射到内存的哪个地址, 以及这些部分在内存中的访问权限.解析和校验程序头表保存到 filedata 中, 都在代码中了, 我干了(狗头).不管前面的校验直接看 get_32bit_program_headers get_64bit_program_headers:朴实无华, 接着看一下 Elf32_External_Phdr Elf64_External_Phdr:依旧贴出.readelf 就到这吧, 如果全部记笔记要写很长, 删了很多, 整体读代码的方式大概就是这样, readelf.c 是一个非常全的 elf 解析. 总结一下读代码的流程: 首先跟着 process_object 找到你需要的功能对应的函数, 然后跟进去开读, 先把握整体, 在关心局部, 先 ai 注释, 在人工精读, 最后用自己的话总结一下. readelf 的代码整体看下来是非常好读, 质朴的 c 代码, 没有多少让人看不懂的工程化部分, 点赞. 好了开胃菜到此为止.没有时间在为没读完 readelf 哀悼, 接下来映入眼帘的是 elf 的加载, 在 linux 系统中在运行一个 elf 时如何加载可执行文件或者 .so 文件搞清楚这个函数就知道 linux 怎么加载 elf 的了.
总结一下核
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-289299.htm