首页
社区
课程
招聘
[原创]一个 ELF 文件的运行
发表于: 2025-12-2 13:58 9026

[原创]一个 ELF 文件的运行

2025-12-2 13:58
9026

这篇文章会包含很多项目的源码阅读, 覆盖 elf 的解析, elf 的加载, elf 的链接, 一次说清楚. 并换一种风格, 只在代码关键地方做注释. 并不会直接总结一个 elf 包含了哪些信息, 然后去哪找云云, 只是纯粹的源码阅读的记录, 看看在实际生产中 elf 如何被使用的, 必要的地方做一些总结和提示, 仅仅如此, 这点您要做好心理准备.

所有注释, 总结, 补充说明都是我改过, 读过的, 放心食用.

编译完能跳转了, 但是还是有红线, 不理会.

这个我也写了, 实现的很乱, 各种历史的沉淀, 不好读, 就删了, 感兴趣可以自己看看, glibc/elf/rtld.c 中的 _dl_start 是入口函数.

编译也很快, 但是有红线, 推荐看网页版: 5faK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6W2L8r3W2^5K9i4u0Q4x3X3g2T1L8$3!0@1L8r3W2F1i4K6u0W2j5$3!0E0i4K6u0r3k6$3I4A6j5X3y4Q4x3V1k6Y4L8r3W2T1j5#2)9J5k6o6u0Q4x3X3f1K6x3W2)9J5c8Y4y4G2N6i4u0U0k6b7`.`.

不想编译 AOSP, 直接看网页版: c9dK9s2c8@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)填充 filedatasection_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 的了.

总结一下核心就是 检查和加载, 只用到 ELF 文件头, 程序头表的 PT_LOAD 段, 读取要执行的 ELF 文件, 然后通过 ELF 头找到程序头表, 然后将 PT_LOAD 段加载到内存, 再移交控制权到其入口(有动态链接器, 会先进入动态链接器代码). 核心思想就这么点, 不过细节却有很多, 下面通过几个点讲一下:

首先看一下入参 struct linux_binprm:

可以看到这个结构体中包含了很多结构体, 不再一一贴出, 总结一下就是记录新程序的种种信息, 内存, 文件对象, 路径, 参数...

这段代码要摘出来单独说一下:

如果是动态链接 ELF, 这段的计算流程大概是这样(一个例子):

BSS 段专门用来存放未初始化的全局变量和静态变量, 它在程序加载时由操作系统自动清零. 他们存在于"缝隙"之中:

举个例子:

再来看一下加载动态链接器这个函数

可以看到这个就像是简化版的 load_elf_binary, 没有那么多需要处理的额外情况.

elf 的加载 大致就这样, 可以看到没什么神秘的地方, 尽管在读这段代码之前, 我一直觉得它很神秘, 还记得在 load_elf_binary 中我们将程序入口设置在了哪吗, 没错, 动态链接器, 如果一个 ELF 有动态链接器的话, 会先执行动态链接器的代码, 接下来我们看看动态链接器的实现. let's go.


不同的架构有不同的入口, 对应一段简短的汇编

bionic/linker/arch/arm64/begin.S:

搜索 __linker_init 找到 bionic/linker/linker_main.cpp:

代码虽然简短, 但事却不少, 初始化 TCB, 计算基地址, 重定位自身...我们调重点看.

通过程序头表中的 PT_PHDR 段计算链接器基地址(linker_addr)和加载偏移(load_bias):

获取程序头表中的动态段, 解析重定位符号, 我们分别看看三个处理函数.

RELR 格式的重定位有两种模式: 单一项 / 位图项, 单一项不用说, 对单个地址重定位, 位图项是用来将相近的一段基址重定位, 以上一个单一项的偏移为基地址, 然后用当前取出来的值作为位图, 比特位为 0 位代表不需要重定位, 为 1 代表需要重定位.

可以看到, 逻辑写的非常清晰, 好代码, 我们在看看 apply_relr_reloc:

还记得 load_bias, 在上边计算的: load_bias = 程序头表实际内存地址 - 程序头表的虚拟地址(p_vaddr), 也就是地址的内存偏移, 在 RELR 重定位中得到的值 offset 是预计的虚拟地址, 就像 p_vaddr, 需要加上 load_bias 后才是真实内存地址, 然后将这个地址存的值在加上 load_bias 完成重定位.

这里要讲一下 IFUNC 机制, Indirect Function, 即间接函数. 运行时动态选择函数实现, 也就是添加了一个中间层, 为兼容性和优化创造了条件.

回到 __linker_init 接着往下看, 来到了 prelink_image 函数

整体看下来, 主要是就是在遍历解析动态段的信息, 先找到动态段位置, 然后遍历, 保存信息, 然后在做一些校验.

额外说明 ARM64 MTE: MTE 通过给内存地址和指针添加 "标签", 并在内存访问时验证标签匹配性, 实现对非法内存访问的实时检测, 将物理内存按 16 字节划分, 每个 16 字节块分配一个 5 位的 "内存标签", 64 位虚拟地址的高 8 位(bit 56-63)中预留 5 位作为 "指针标签", 用于存储对应内存块的标签值.

还有哈希表, 这个也要介绍一下, 传统哈希表(DT_HASH)和 GNU 哈希表(DT_GNU_HASH)都是用于"根据符号名快速找到对应的符号地址"这一问题, 当程序调用外部函数或者访问全局变量时, 需要通过符号名在符号表中找到对应的内存地址, 哈希表的作用就是将符号名映射为哈希值, 通过哈希表快速定位符号在符号表中的位置, 避免线性遍历符号表.

回到 __linker_init 接着往下看, 来到了 link_image 函数

这个只是一个包装方法, 实际是调用 relocate 方法处理, 跟入 relocate:

总结一下就是根据重定位项不同的类型做重定位处理, 计算重定位值写会目标地址, 与上边的 apply_relr_reloc 中的处理逻辑其实很类似.

到此完成了链接器的重定位.

回到主线, 再看 __linker_init_post_relocation

可以看到做了很多的初始化, 感兴趣可以自己看一下, 我们跟入 linker_main, 看后续如何处理目标程序,

加载并链接目标可执行文件, 算是最核心的部分, 也是最后一部分, 坐稳发车.

读下来和动态链接器的最大的区别就是是否加载依赖库是吧, 剩下的重定位逻辑基本一致, 初始化也差不多, 我们重点分析一下加载依赖库部分.

把大象装进冰箱分为哪几步?

前边的几步好说, 但是 Step 5-7 工作机制要举个例子讲一下.

这就是库重定位, 链接的逻辑.

回过头, 我们在看一下是如何加载库到内存的.

通过名称判断当前命名空间及其链接的命名空间中是否已加载该库, 如果未加载就调用 load_library 加载, 如果已经加载就复用, 总结来说就是这样.

再看 load_library

解析要加载库的 ELF 文件, 做一些校验, 得到它的 soinfo, 然后将它依赖库加入加载任务列表. 至于怎么解析的 ELF, 这里就不再看了, 已经读过太多遍了.

到此为止, 旅途结束.

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.301.tar.xz
tar xvf linux-5.9.6.tar.xz
 
sudo apt install -y llvm clang clang++ lld git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison
 
bear -- make CC=clang CXX=clang++ LLVM=1 defconfig -j$(nproc)
bear -- make CC=clang CXX=clang++ LLVM=1 -j$(nproc)
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.301.tar.xz
tar xvf linux-5.9.6.tar.xz
 
sudo apt install -y llvm clang clang++ lld git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison
 
bear -- make CC=clang CXX=clang++ LLVM=1 defconfig -j$(nproc)
bear -- make CC=clang CXX=clang++ LLVM=1 -j$(nproc)
git clone git://sourceware.org/git/binutils-gdb.git
cd ~/binutils-gdb
sudo apt install -y build-essential flex bison texinfo libncurses5-dev python3-dev
 
mkdir build && cd build 
../configure CC=clang CXX=clang++ --disable-gdb --disable-werror
 
bear -- make -j$(nproc)
git clone git://sourceware.org/git/binutils-gdb.git
cd ~/binutils-gdb
sudo apt install -y build-essential flex bison texinfo libncurses5-dev python3-dev
 
mkdir build && cd build 
../configure CC=clang CXX=clang++ --disable-gdb --disable-werror
 
bear -- make -j$(nproc)
注释
int
main (int argc, char ** argv)
{
  int err;
 
#ifdef HAVE_LC_MESSAGES
  setlocale (LC_MESSAGES, "");
#endif
  setlocale (LC_CTYPE, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);
 
  expandargv (&argc, &argv);
 
  parse_args (& cmdline, argc, argv);
 
  if (optind < (argc - 1))
    show_name = true;
  else if (optind >= argc)
    {
      do_checks = true;
 
      warn (_("Nothing to do.\n"));
      usage (stderr);
    }
 
  err = false;
  while (optind < argc)
    if (! process_file (argv[optind++]))
      err = true;
 
  free (cmdline.dump_sects);
 
  free (dump_ctf_symtab_name);
  free (dump_ctf_strtab_name);
  free (dump_ctf_parent_name);
 
  return err ? EXIT_FAILURE : EXIT_SUCCESS;
}
注释
int
main (int argc, char ** argv)
{
  int err;
 
#ifdef HAVE_LC_MESSAGES
  setlocale (LC_MESSAGES, "");
#endif
  setlocale (LC_CTYPE, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);
 
  expandargv (&argc, &argv);
 
  parse_args (& cmdline, argc, argv);
 
  if (optind < (argc - 1))
    show_name = true;
  else if (optind >= argc)
    {
      do_checks = true;
 
      warn (_("Nothing to do.\n"));
      usage (stderr);
    }
 
  err = false;
  while (optind < argc)
    if (! process_file (argv[optind++]))
      err = true;
 
  free (cmdline.dump_sects);
 
  free (dump_ctf_symtab_name);
  free (dump_ctf_strtab_name);
  free (dump_ctf_parent_name);
 
  return err ? EXIT_FAILURE : EXIT_SUCCESS;
}
static bool
process_file (char * file_name)
{
  Filedata * filedata = NULL;
  struct stat statbuf;
  char armag[SARMAG];
  bool ret = true;
 
  // 调用 stat 系统调用获取文件元数据
  if (stat (file_name, &statbuf) < 0)
    {
      if (errno == ENOENT)
    error (_("'%s': No such file\n"), file_name);
      else
    error (_("Could not locate '%s'.  System error message: %s\n"),
           file_name, strerror (errno));
      return false;
    }
 
  // 验证文件类型是否为普通文件, 程序仅处理普通文件, 非普通文件直接返回错误
  if (! S_ISREG (statbuf.st_mode))
    {
      error (_("'%s' is not an ordinary file\n"), file_name);
      return false;
    }
 
  filedata = calloc (1, sizeof * filedata);
  if (filedata == NULL)
    {
      error (_("Out of memory allocating file data structure\n"));
      return false;
    }
 
  // 给 Filedata 结构体赋值, 保存文件名和文件句柄
  filedata->file_name = file_name;
  filedata->handle = fopen (file_name, "rb");
  if (filedata->handle == NULL)
    {
      error (_("Input file '%s' is not readable.\n"), file_name);
      free (filedata);
      return false;
    }
 
  // 读取文件头部魔数
 
  // fread(缓冲区, 每次读取大小, 读取次数, 文件句柄)
  if (fread (armag, SARMAG, 1, filedata->handle) != 1)
    {
      error (_("%s: Failed to read file's magic number\n"), file_name);
      fclose (filedata->handle);
      free (filedata);
      return false;
    }
 
  // // 填充 Filedata 结构体的文件大小
  filedata->file_size = statbuf.st_size;
  filedata->is_separate = false;
 
 
  // 根据魔数识别文件类型, 分发给对应处理函数
 
  // 标准归档文件, 标准 .a: 「装满 .o 的压缩包」, 链接时直接用
  if (memcmp (armag, ARMAG, SARMAG) == 0)
    {
      if (! process_archive (filedata, false))
    ret = false;
    }
  // 精简归档文件, 精简 .a: 「记录 .o 地址的清单」, 链接时要找原始 .o
  else if (memcmp (armag, ARMAGT, SARMAG) == 0)
    {
      if ( ! process_archive (filedata, true))
    ret = false;
    }
  // 非归档文件, 视为普通目标文件, 如 .o 文件, 可执行文件, .so 文件等, 也就是咱们的目标
  else
    {
      if (do_archive_index && !check_all)
    error (_("File %s is not an archive so its index cannot be displayed.\n"),
           file_name);
 
      // 回退文件指针到开头
      rewind (filedata->handle);
      filedata->archive_file_size = filedata->archive_file_offset = 0;
       
      // 调用 process_object 解析
      if (! process_object (filedata))
    ret = false;
    }
 
  // 释放资源, 清理状态
 
  // 若打开了调试信息文件, 此处释放调试相关文件
  close_debug_file (filedata);
 
  free (ba_cache.strtab);
  ba_cache.strtab = NULL;
  free (ba_cache.symtab);
  ba_cache.symtab = NULL;
  ba_cache.filedata = NULL;
 
  return ret;
}
static bool
process_file (char * file_name)
{
  Filedata * filedata = NULL;
  struct stat statbuf;
  char armag[SARMAG];
  bool ret = true;
 
  // 调用 stat 系统调用获取文件元数据
  if (stat (file_name, &statbuf) < 0)
    {
      if (errno == ENOENT)
    error (_("'%s': No such file\n"), file_name);
      else
    error (_("Could not locate '%s'.  System error message: %s\n"),
           file_name, strerror (errno));
      return false;
    }
 
  // 验证文件类型是否为普通文件, 程序仅处理普通文件, 非普通文件直接返回错误
  if (! S_ISREG (statbuf.st_mode))
    {
      error (_("'%s' is not an ordinary file\n"), file_name);
      return false;
    }
 
  filedata = calloc (1, sizeof * filedata);
  if (filedata == NULL)
    {
      error (_("Out of memory allocating file data structure\n"));
      return false;
    }
 
  // 给 Filedata 结构体赋值, 保存文件名和文件句柄
  filedata->file_name = file_name;
  filedata->handle = fopen (file_name, "rb");
  if (filedata->handle == NULL)
    {
      error (_("Input file '%s' is not readable.\n"), file_name);
      free (filedata);
      return false;
    }
 
  // 读取文件头部魔数
 
  // fread(缓冲区, 每次读取大小, 读取次数, 文件句柄)
  if (fread (armag, SARMAG, 1, filedata->handle) != 1)
    {
      error (_("%s: Failed to read file's magic number\n"), file_name);
      fclose (filedata->handle);
      free (filedata);
      return false;
    }
 
  // // 填充 Filedata 结构体的文件大小
  filedata->file_size = statbuf.st_size;
  filedata->is_separate = false;
 
 
  // 根据魔数识别文件类型, 分发给对应处理函数
 
  // 标准归档文件, 标准 .a: 「装满 .o 的压缩包」, 链接时直接用
  if (memcmp (armag, ARMAG, SARMAG) == 0)
    {
      if (! process_archive (filedata, false))
    ret = false;
    }
  // 精简归档文件, 精简 .a: 「记录 .o 地址的清单」, 链接时要找原始 .o
  else if (memcmp (armag, ARMAGT, SARMAG) == 0)
    {
      if ( ! process_archive (filedata, true))
    ret = false;
    }
  // 非归档文件, 视为普通目标文件, 如 .o 文件, 可执行文件, .so 文件等, 也就是咱们的目标
  else
    {
      if (do_archive_index && !check_all)
    error (_("File %s is not an archive so its index cannot be displayed.\n"),
           file_name);
 
      // 回退文件指针到开头
      rewind (filedata->handle);
      filedata->archive_file_size = filedata->archive_file_offset = 0;
       
      // 调用 process_object 解析
      if (! process_object (filedata))
    ret = false;
    }
 
  // 释放资源, 清理状态
 
  // 若打开了调试信息文件, 此处释放调试相关文件
  close_debug_file (filedata);
 
  free (ba_cache.strtab);
  ba_cache.strtab = NULL;
  free (ba_cache.symtab);
  ba_cache.symtab = NULL;
  ba_cache.filedata = NULL;
 
  return ret;
}
static bool
process_object (Filedata * filedata)
{
  bool have_separate_files;
  unsigned int i;
  bool res;
 
  // 读取 ELF 文件头部
  if (! get_file_header (filedata))
    {
      error (_("%s: Failed to read file header\n"), filedata->file_name);
      return false;
    }
 
  // 初始化
 
  // 初始化版本信息数组
  for (i = ARRAY_SIZE (filedata->version_info); i--;)
    filedata->version_info[i] = 0;
 
  // 初始化动态段信息数组
  for (i = ARRAY_SIZE (filedata->dynamic_info); i--;)
    filedata->dynamic_info[i] = 0;
  filedata->dynamic_info_DT_GNU_HASH = 0;
  filedata->dynamic_info_DT_MIPS_XHASH = 0;
 
  if (show_name)
    printf (_("\nFile: %s\n"), filedata->file_name);
 
  // 初始化待 dump 的节列表
  initialise_dump_sects (filedata);
 
  // 预读取第一个节头
  get_section_headers (filedata, true);
 
  // 处理 ELF 文件头部信息
  if (! process_file_header (filedata))
    {
      res = false;
      goto out;
    }
 
  // 释放之前预读取的单个节头内存, 因为后续会完整读取所有节头, 避免重复内存占用, 此处释放并置空指针
  free (filedata->section_headers);
  filedata->section_headers = NULL;
 
  // 处理所有节头
  if (! process_section_headers (filedata))
    {
      do_unwind = do_version = do_dump = do_arch = false;
 
      if (! do_using_dynamic)
    do_syms = do_dyn_syms = do_reloc = false;
    }
 
  // 处理节组
  if (! process_section_groups (filedata))
    do_unwind = false;
 
  // 处理程序头
  process_program_headers (filedata);
 
  // 处理动态段
  res = process_dynamic_section (filedata);
 
  // 处理重定位表
  if (! process_relocs (filedata))
    res = false;
 
  // 处理 unwind 信息, 用于异常处理和调试, 记录函数调用栈的展开规则
  if (! process_unwind (filedata))
    res = false;
 
  // 处理符号表, 包含函数名, 变量名, 符号类型, 地址等信息
  if (! process_symbol_table (filedata))
    res = false;
 
  // 处理 LTO 符号表, LTO 是编译优化技术, 会生成特殊的符号表
  if (! process_lto_symbol_tables (filedata))
    res = false;
 
  // 处理符号信息扩展, 部分 ELF 文件包含 .syminfo 节, 存储符号的额外信息
  if (! process_syminfo (filedata))
    res = false;
 
  // 处理版本节, 解析版本相关节, 记录符号版本和依赖库版本
  if (! process_version_sections (filedata))
    res = false;
 
  // 加载调试信息文件
  if (might_need_separate_debug_info (filedata))
    have_separate_files = load_separate_debug_files (filedata, filedata->file_name);
  else
    have_separate_files = false;
 
  // 处理节内容
  if (! process_section_contents (filedata))
    res = false;
 
  // 处理 GOT 节内容, Global Offset Table, 全局偏移表, 是动态链接中用于存储全局符号地址的表, 解析其内容可查看动态符号的地址映射
  if (! process_got_section_contents (filedata))
    res = false;
 
  // 处理已加载的分离调试信息文件
  if (have_separate_files)
    {
      separate_info * d;
 
      for (d = first_separate_info; d != NULL; d = d->next)
    {
      initialise_dump_sects (d->handle);
 
      if (process_links && ! process_file_header (d->handle))
        res = false;
      else if (! process_section_headers (d->handle))
        res = false;
      else if (! process_section_contents (d->handle))
        res = false;
      else if (process_links)
        {
          if (! process_section_groups (d->handle))
        res = false;
          process_program_headers (d->handle);
          if (! process_dynamic_section (d->handle))
        res = false;
          if (! process_relocs (d->handle))
        res = false;
          if (! process_unwind (d->handle))
        res = false;
          if (! process_symbol_table (d->handle))
        res = false;
          if (! process_lto_symbol_tables (d->handle))
        res = false;
          if (! process_syminfo (d->handle))
        res = false;
          if (! process_version_sections (d->handle))
        res = false;
          if (! process_notes (d->handle))
        res = false;
        }
    }
 
    }
 
  // 处理注释节, 注释节存储额外的文件信息, 如编译器版本, 调试器信息, 架构特定备注等
  if (! process_notes (filedata))
    res = false;
 
  // 处理 GNU 库列表, 存储程序依赖的 GNU 库信息, 如库名称, 版本, 路径等
  if (! process_gnu_liblist (filedata))
    res = false;
 
  // 处理架构特定信息, 针对特定 CPU 架构的专有节, 如 ARM 的 .ARM.exidx, x86 的 .eh_frame_hdr 等
  if (! process_arch_specific (filedata))
    res = false;
 
  // 资源释放
 out:
  free_filedata (filedata);
 
  free_debug_memory ();
 
  return res;
}
static bool
process_object (Filedata * filedata)
{
  bool have_separate_files;
  unsigned int i;
  bool res;
 
  // 读取 ELF 文件头部
  if (! get_file_header (filedata))
    {
      error (_("%s: Failed to read file header\n"), filedata->file_name);
      return false;
    }
 
  // 初始化
 
  // 初始化版本信息数组
  for (i = ARRAY_SIZE (filedata->version_info); i--;)
    filedata->version_info[i] = 0;
 
  // 初始化动态段信息数组
  for (i = ARRAY_SIZE (filedata->dynamic_info); i--;)
    filedata->dynamic_info[i] = 0;
  filedata->dynamic_info_DT_GNU_HASH = 0;
  filedata->dynamic_info_DT_MIPS_XHASH = 0;
 
  if (show_name)
    printf (_("\nFile: %s\n"), filedata->file_name);
 
  // 初始化待 dump 的节列表
  initialise_dump_sects (filedata);
 
  // 预读取第一个节头
  get_section_headers (filedata, true);
 
  // 处理 ELF 文件头部信息
  if (! process_file_header (filedata))
    {
      res = false;
      goto out;
    }
 
  // 释放之前预读取的单个节头内存, 因为后续会完整读取所有节头, 避免重复内存占用, 此处释放并置空指针
  free (filedata->section_headers);
  filedata->section_headers = NULL;
 
  // 处理所有节头
  if (! process_section_headers (filedata))
    {
      do_unwind = do_version = do_dump = do_arch = false;
 
      if (! do_using_dynamic)
    do_syms = do_dyn_syms = do_reloc = false;
    }
 
  // 处理节组
  if (! process_section_groups (filedata))
    do_unwind = false;
 
  // 处理程序头
  process_program_headers (filedata);
 
  // 处理动态段
  res = process_dynamic_section (filedata);
 
  // 处理重定位表
  if (! process_relocs (filedata))
    res = false;
 
  // 处理 unwind 信息, 用于异常处理和调试, 记录函数调用栈的展开规则
  if (! process_unwind (filedata))
    res = false;
 
  // 处理符号表, 包含函数名, 变量名, 符号类型, 地址等信息
  if (! process_symbol_table (filedata))
    res = false;
 
  // 处理 LTO 符号表, LTO 是编译优化技术, 会生成特殊的符号表
  if (! process_lto_symbol_tables (filedata))
    res = false;
 
  // 处理符号信息扩展, 部分 ELF 文件包含 .syminfo 节, 存储符号的额外信息
  if (! process_syminfo (filedata))
    res = false;
 
  // 处理版本节, 解析版本相关节, 记录符号版本和依赖库版本
  if (! process_version_sections (filedata))
    res = false;
 
  // 加载调试信息文件
  if (might_need_separate_debug_info (filedata))
    have_separate_files = load_separate_debug_files (filedata, filedata->file_name);
  else
    have_separate_files = false;
 
  // 处理节内容
  if (! process_section_contents (filedata))
    res = false;
 
  // 处理 GOT 节内容, Global Offset Table, 全局偏移表, 是动态链接中用于存储全局符号地址的表, 解析其内容可查看动态符号的地址映射
  if (! process_got_section_contents (filedata))
    res = false;
 
  // 处理已加载的分离调试信息文件
  if (have_separate_files)
    {
      separate_info * d;
 
      for (d = first_separate_info; d != NULL; d = d->next)
    {
      initialise_dump_sects (d->handle);
 
      if (process_links && ! process_file_header (d->handle))
        res = false;
      else if (! process_section_headers (d->handle))
        res = false;
      else if (! process_section_contents (d->handle))
        res = false;
      else if (process_links)
        {
          if (! process_section_groups (d->handle))
        res = false;
          process_program_headers (d->handle);
          if (! process_dynamic_section (d->handle))
        res = false;
          if (! process_relocs (d->handle))
        res = false;
          if (! process_unwind (d->handle))
        res = false;
          if (! process_symbol_table (d->handle))
        res = false;
          if (! process_lto_symbol_tables (d->handle))
        res = false;
          if (! process_syminfo (d->handle))
        res = false;
          if (! process_version_sections (d->handle))
        res = false;
          if (! process_notes (d->handle))
        res = false;
        }
    }
 
    }
 
  // 处理注释节, 注释节存储额外的文件信息, 如编译器版本, 调试器信息, 架构特定备注等
  if (! process_notes (filedata))
    res = false;
 
  // 处理 GNU 库列表, 存储程序依赖的 GNU 库信息, 如库名称, 版本, 路径等
  if (! process_gnu_liblist (filedata))
    res = false;
 
  // 处理架构特定信息, 针对特定 CPU 架构的专有节, 如 ARM 的 .ARM.exidx, x86 的 .eh_frame_hdr 等
  if (! process_arch_specific (filedata))
    res = false;
 
  // 资源释放
 out:
  free_filedata (filedata);
 
  free_debug_memory ();
 
  return res;
}
static bool
get_file_header (Filedata * filedata)
{
  // 读取 ELF 文件标识数组, 也就是前 EI_NIDENT(16) 字节
  if (fread (filedata->file_header.e_ident, EI_NIDENT, 1, filedata->handle) != 1)
    return false;
 
  // 根据 ELF 文件标识数组中的第 EI_DATA 字节判断大小端
  switch (filedata->file_header.e_ident[EI_DATA])
    {
    default:
    case ELFDATANONE:
    case ELFDATA2LSB:
      byte_get = byte_get_little_endian;
      byte_put = byte_put_little_endian;
      break;
    case ELFDATA2MSB:
      byte_get = byte_get_big_endian;
      byte_put = byte_put_big_endian;
      break;
    }
 
  // 根据 ELF 文件标识数组中的第 EI_CLASS 字节判断是 32 位还是 64 位
  is_32bit_elf = (filedata->file_header.e_ident[EI_CLASS] != ELFCLASS64);
 
  // 读取文件头剩余字段, 填充到 filedata 的文件头中
  if (is_32bit_elf)
    {
      Elf32_External_Ehdr ehdr32;
 
      if (fread (ehdr32.e_type, sizeof (ehdr32) - EI_NIDENT, 1, filedata->handle) != 1)
    return false;
 
      filedata->file_header.e_type      = BYTE_GET (ehdr32.e_type);
      filedata->file_header.e_machine   = BYTE_GET (ehdr32.e_machine);
      filedata->file_header.e_version   = BYTE_GET (ehdr32.e_version);
      filedata->file_header.e_entry     = BYTE_GET (ehdr32.e_entry);
      filedata->file_header.e_phoff     = BYTE_GET (ehdr32.e_phoff);
      filedata->file_header.e_shoff     = BYTE_GET (ehdr32.e_shoff);
      filedata->file_header.e_flags     = BYTE_GET (ehdr32.e_flags);
      filedata->file_header.e_ehsize    = BYTE_GET (ehdr32.e_ehsize);
      filedata->file_header.e_phentsize = BYTE_GET (ehdr32.e_phentsize);
      filedata->file_header.e_phnum     = BYTE_GET (ehdr32.e_phnum);
      filedata->file_header.e_shentsize = BYTE_GET (ehdr32.e_shentsize);
      filedata->file_header.e_shnum     = BYTE_GET (ehdr32.e_shnum);
      filedata->file_header.e_shstrndx  = BYTE_GET (ehdr32.e_shstrndx);
    }
  else
    {
      Elf64_External_Ehdr ehdr64;
 
      if (fread (ehdr64.e_type, sizeof (ehdr64) - EI_NIDENT, 1, filedata->handle) != 1)
    return false;
 
      filedata->file_header.e_type      = BYTE_GET (ehdr64.e_type);
      filedata->file_header.e_machine   = BYTE_GET (ehdr64.e_machine);
      filedata->file_header.e_version   = BYTE_GET (ehdr64.e_version);
      filedata->file_header.e_entry     = BYTE_GET (ehdr64.e_entry);
      filedata->file_header.e_phoff     = BYTE_GET (ehdr64.e_phoff);
      filedata->file_header.e_shoff     = BYTE_GET (ehdr64.e_shoff);
      filedata->file_header.e_flags     = BYTE_GET (ehdr64.e_flags);
      filedata->file_header.e_ehsize    = BYTE_GET (ehdr64.e_ehsize);
      filedata->file_header.e_phentsize = BYTE_GET (ehdr64.e_phentsize);
      filedata->file_header.e_phnum     = BYTE_GET (ehdr64.e_phnum);
      filedata->file_header.e_shentsize = BYTE_GET (ehdr64.e_shentsize);
      filedata->file_header.e_shnum     = BYTE_GET (ehdr64.e_shnum);
      filedata->file_header.e_shstrndx  = BYTE_GET (ehdr64.e_shstrndx);
    }
 
  return true;
}
static bool
get_file_header (Filedata * filedata)
{
  // 读取 ELF 文件标识数组, 也就是前 EI_NIDENT(16) 字节
  if (fread (filedata->file_header.e_ident, EI_NIDENT, 1, filedata->handle) != 1)
    return false;
 
  // 根据 ELF 文件标识数组中的第 EI_DATA 字节判断大小端
  switch (filedata->file_header.e_ident[EI_DATA])
    {
    default:
    case ELFDATANONE:
    case ELFDATA2LSB:
      byte_get = byte_get_little_endian;
      byte_put = byte_put_little_endian;
      break;
    case ELFDATA2MSB:
      byte_get = byte_get_big_endian;
      byte_put = byte_put_big_endian;
      break;
    }
 
  // 根据 ELF 文件标识数组中的第 EI_CLASS 字节判断是 32 位还是 64 位
  is_32bit_elf = (filedata->file_header.e_ident[EI_CLASS] != ELFCLASS64);
 
  // 读取文件头剩余字段, 填充到 filedata 的文件头中
  if (is_32bit_elf)
    {
      Elf32_External_Ehdr ehdr32;
 
      if (fread (ehdr32.e_type, sizeof (ehdr32) - EI_NIDENT, 1, filedata->handle) != 1)
    return false;
 
      filedata->file_header.e_type      = BYTE_GET (ehdr32.e_type);
      filedata->file_header.e_machine   = BYTE_GET (ehdr32.e_machine);
      filedata->file_header.e_version   = BYTE_GET (ehdr32.e_version);
      filedata->file_header.e_entry     = BYTE_GET (ehdr32.e_entry);
      filedata->file_header.e_phoff     = BYTE_GET (ehdr32.e_phoff);
      filedata->file_header.e_shoff     = BYTE_GET (ehdr32.e_shoff);
      filedata->file_header.e_flags     = BYTE_GET (ehdr32.e_flags);
      filedata->file_header.e_ehsize    = BYTE_GET (ehdr32.e_ehsize);
      filedata->file_header.e_phentsize = BYTE_GET (ehdr32.e_phentsize);
      filedata->file_header.e_phnum     = BYTE_GET (ehdr32.e_phnum);
      filedata->file_header.e_shentsize = BYTE_GET (ehdr32.e_shentsize);
      filedata->file_header.e_shnum     = BYTE_GET (ehdr32.e_shnum);
      filedata->file_header.e_shstrndx  = BYTE_GET (ehdr32.e_shstrndx);
    }
  else
    {
      Elf64_External_Ehdr ehdr64;
 
      if (fread (ehdr64.e_type, sizeof (ehdr64) - EI_NIDENT, 1, filedata->handle) != 1)
    return false;
 
      filedata->file_header.e_type      = BYTE_GET (ehdr64.e_type);
      filedata->file_header.e_machine   = BYTE_GET (ehdr64.e_machine);
      filedata->file_header.e_version   = BYTE_GET (ehdr64.e_version);
      filedata->file_header.e_entry     = BYTE_GET (ehdr64.e_entry);
      filedata->file_header.e_phoff     = BYTE_GET (ehdr64.e_phoff);
      filedata->file_header.e_shoff     = BYTE_GET (ehdr64.e_shoff);
      filedata->file_header.e_flags     = BYTE_GET (ehdr64.e_flags);
      filedata->file_header.e_ehsize    = BYTE_GET (ehdr64.e_ehsize);
      filedata->file_header.e_phentsize = BYTE_GET (ehdr64.e_phentsize);
      filedata->file_header.e_phnum     = BYTE_GET (ehdr64.e_phnum);
      filedata->file_header.e_shentsize = BYTE_GET (ehdr64.e_shentsize);
      filedata->file_header.e_shnum     = BYTE_GET (ehdr64.e_shnum);
      filedata->file_header.e_shstrndx  = BYTE_GET (ehdr64.e_shstrndx);
    }
 
  return true;
}
typedef struct {
  unsigned char e_ident[16];        /* ELF 魔数 */
  unsigned char e_type[2];      /* 标识目标文件类型 */
  unsigned char e_machine[2];       /* 指定所需的架构 */
  unsigned char e_version[4];       /* 标识目标文件版本 */
  unsigned char e_entry[4];     /* 程序入口点的虚拟地址 */
  unsigned char e_phoff[4];     /* 程序头表在文件中的偏移量 */
  unsigned char e_shoff[4];     /* 节头表在文件中的偏移量 */
  unsigned char e_flags[4];     /* 处理器相关标志 */
  unsigned char e_ehsize[2];        /* ELF 文件头本身的大小 */
  unsigned char e_phentsize[2];     /* 程序头表中每个表项的大小 */
  unsigned char e_phnum[2];     /* 程序头表的表项数量 */
  unsigned char e_shentsize[2];     /* 节头表中每个表项的大小 */
  unsigned char e_shnum[2];     /* 节头表的表项数量 */
  unsigned char e_shstrndx[2];      /* 节头字符串表的索引 */
} Elf32_External_Ehdr;
 
typedef struct {
  unsigned char e_ident[16];        /* ELF "magic number" */
  unsigned char e_type[2];      /* Identifies object file type */
  unsigned char e_machine[2];       /* Specifies required architecture */
  unsigned char e_version[4];       /* Identifies object file version */
  unsigned char e_entry[8];     /* Entry point virtual address */
  unsigned char e_phoff[8];     /* Program header table file offset */
  unsigned char e_shoff[8];     /* Section header table file offset */
  unsigned char e_flags[4];     /* Processor-specific flags */
  unsigned char e_ehsize[2];        /* ELF header size in bytes */
  unsigned char e_phentsize[2];     /* Program header table entry size */
  unsigned char e_phnum[2];     /* Program header table entry count */
  unsigned char e_shentsize[2];     /* Section header table entry size */
  unsigned char e_shnum[2];     /* Section header table entry count */
  unsigned char e_shstrndx[2];      /* Section header string table index */
} Elf64_External_Ehdr;
typedef struct {
  unsigned char e_ident[16];        /* ELF 魔数 */
  unsigned char e_type[2];      /* 标识目标文件类型 */
  unsigned char e_machine[2];       /* 指定所需的架构 */
  unsigned char e_version[4];       /* 标识目标文件版本 */
  unsigned char e_entry[4];     /* 程序入口点的虚拟地址 */
  unsigned char e_phoff[4];     /* 程序头表在文件中的偏移量 */
  unsigned char e_shoff[4];     /* 节头表在文件中的偏移量 */
  unsigned char e_flags[4];     /* 处理器相关标志 */
  unsigned char e_ehsize[2];        /* ELF 文件头本身的大小 */
  unsigned char e_phentsize[2];     /* 程序头表中每个表项的大小 */
  unsigned char e_phnum[2];     /* 程序头表的表项数量 */
  unsigned char e_shentsize[2];     /* 节头表中每个表项的大小 */
  unsigned char e_shnum[2];     /* 节头表的表项数量 */
  unsigned char e_shstrndx[2];      /* 节头字符串表的索引 */
} Elf32_External_Ehdr;
 
typedef struct {
  unsigned char e_ident[16];        /* ELF "magic number" */
  unsigned char e_type[2];      /* Identifies object file type */
  unsigned char e_machine[2];       /* Specifies required architecture */
  unsigned char e_version[4];       /* Identifies object file version */
  unsigned char e_entry[8];     /* Entry point virtual address */
  unsigned char e_phoff[8];     /* Program header table file offset */
  unsigned char e_shoff[8];     /* Section header table file offset */
  unsigned char e_flags[4];     /* Processor-specific flags */
  unsigned char e_ehsize[2];        /* ELF header size in bytes */
  unsigned char e_phentsize[2];     /* Program header table entry size */
  unsigned char e_phnum[2];     /* Program header table entry count */
  unsigned char e_shentsize[2];     /* Section header table entry size */
  unsigned char e_shnum[2];     /* Section header table entry count */
  unsigned char e_shstrndx[2];      /* Section header string table index */
} Elf64_External_Ehdr;
static bool
get_section_headers (Filedata *filedata, bool probe)
{
  if (filedata->section_headers != NULL)
    return true;
 
  if (is_32bit_elf)
    return get_32bit_section_headers (filedata, probe);
  else
    return get_64bit_section_headers (filedata, probe);
}
static bool
get_section_headers (Filedata *filedata, bool probe)
{
  if (filedata->section_headers != NULL)
    return true;
 
  if (is_32bit_elf)
    return get_32bit_section_headers (filedata, probe);
  else
    return get_64bit_section_headers (filedata, probe);
}
static bool
get_32bit_section_headers (Filedata * filedata, bool probe)
{
  Elf32_External_Shdr * shdrs;
  Elf_Internal_Shdr *   internal;
  unsigned int          i;
  // 节头项大小
  unsigned int          size = filedata->file_header.e_shentsize;
  // probe 为 true 仅仅读取一个, false 读取全部
  unsigned int          num = probe ? 1 : filedata->file_header.e_shnum;
 
    // 一些检查
  if (size == 0 || num == 0)
    return false;
 
  if (filedata->file_header.e_shoff == 0)
    return false;
 
  if (size < sizeof * shdrs)
    {
      if (! probe)
    error (_("The e_shentsize field in the ELF header is less than the size of an ELF section header\n"));
      return false;
    }
  if (!probe && size > sizeof * shdrs)
    warn (_("The e_shentsize field in the ELF header is larger than the size of an ELF section header\n"));
 
  // 从文件读取节头数据
  shdrs = (Elf32_External_Shdr *) get_data (NULL, filedata, filedata->file_header.e_shoff,
                                            size, num,
                        probe ? NULL : _("section headers"));
  if (shdrs == NULL)
    return false;
  
    // 为 filedata->section_headers 申请内存
  filedata->section_headers = (Elf_Internal_Shdr *)
    cmalloc (num, sizeof (Elf_Internal_Shdr));
  if (filedata->section_headers == NULL)
    {
      if (!probe)
    error (_("Out of memory reading %u section headers\n"), num);
      free (shdrs);
      return false;
    }
  // 遍历所有节头项, 填充到 filedata 节头表
  for (i = 0, internal = filedata->section_headers;
       i < num;
       i++, internal++)
    {
      internal->sh_name      = BYTE_GET (shdrs[i].sh_name);
      internal->sh_type      = BYTE_GET (shdrs[i].sh_type);
      internal->sh_flags     = BYTE_GET (shdrs[i].sh_flags);
      internal->sh_addr      = BYTE_GET (shdrs[i].sh_addr);
      internal->sh_offset    = BYTE_GET (shdrs[i].sh_offset);
      internal->sh_size      = BYTE_GET (shdrs[i].sh_size);
      internal->sh_link      = BYTE_GET (shdrs[i].sh_link);
      internal->sh_info      = BYTE_GET (shdrs[i].sh_info);
      internal->sh_addralign = BYTE_GET (shdrs[i].sh_addralign);
      internal->sh_entsize   = BYTE_GET (shdrs[i].sh_entsize);
      if (!probe && internal->sh_link > num)
    warn (_("Section %u has an out of range sh_link value of %u\n"), i, internal->sh_link);
      if (!probe && internal->sh_flags & SHF_INFO_LINK && internal->sh_info > num)
    warn (_("Section %u has an out of range sh_info value of %u\n"), i, internal->sh_info);
    }
 
  free (shdrs);
  return true;
}
 
// 64 位与 32 位逻辑类似, 结构体不一样
static bool
get_64bit_section_headers (Filedata * filedata, bool probe)
{
  Elf64_External_Shdr *  shdrs;
  Elf_Internal_Shdr *    internal;
  unsigned int           i;
  unsigned int           size = filedata->file_header.e_shentsize;
  unsigned int           num = probe ? 1 : filedata->file_header.e_shnum;
 
  if (size == 0 || num == 0)
    return false;
 
  if (filedata->file_header.e_shoff == 0)
    return false;
 
  if (size < sizeof * shdrs)
    {
      if (! probe)
    error (_("The e_shentsize field in the ELF header is less than the size of an ELF section header\n"));
      return false;
    }
 
  if (! probe && size > sizeof * shdrs)
    warn (_("The e_shentsize field in the ELF header is larger than the size of an ELF section header\n"));
 
  shdrs = (Elf64_External_Shdr *) get_data (NULL, filedata,
                        filedata->file_header.e_shoff,
                                            size, num,
                        probe ? NULL : _("section headers"));
  if (shdrs == NULL)
    return false;
 
  filedata->section_headers = (Elf_Internal_Shdr *)
    cmalloc (num, sizeof (Elf_Internal_Shdr));
  if (filedata->section_headers == NULL)
    {
      if (! probe)
    error (_("Out of memory reading %u section headers\n"), num);
      free (shdrs);
      return false;
    }
 
  for (i = 0, internal = filedata->section_headers;
       i < num;
       i++, internal++)
    {
      internal->sh_name      = BYTE_GET (shdrs[i].sh_name);
      internal->sh_type      = BYTE_GET (shdrs[i].sh_type);
      internal->sh_flags     = BYTE_GET (shdrs[i].sh_flags);
      internal->sh_addr      = BYTE_GET (shdrs[i].sh_addr);
      internal->sh_size      = BYTE_GET (shdrs[i].sh_size);
      internal->sh_entsize   = BYTE_GET (shdrs[i].sh_entsize);
      internal->sh_link      = BYTE_GET (shdrs[i].sh_link);
      internal->sh_info      = BYTE_GET (shdrs[i].sh_info);
      internal->sh_offset    = BYTE_GET (shdrs[i].sh_offset);
      internal->sh_addralign = BYTE_GET (shdrs[i].sh_addralign);
      if (!probe && internal->sh_link > num)
    warn (_("Section %u has an out of range sh_link value of %u\n"), i, internal->sh_link);
      if (!probe && internal->sh_flags & SHF_INFO_LINK && internal->sh_info > num)
    warn (_("Section %u has an out of range sh_info value of %u\n"), i, internal->sh_info);
    }
 
  free (shdrs);
  return true;
}
static bool
get_32bit_section_headers (Filedata * filedata, bool probe)
{
  Elf32_External_Shdr * shdrs;
  Elf_Internal_Shdr *   internal;
  unsigned int          i;
  // 节头项大小
  unsigned int          size = filedata->file_header.e_shentsize;
  // probe 为 true 仅仅读取一个, false 读取全部
  unsigned int          num = probe ? 1 : filedata->file_header.e_shnum;
 
    // 一些检查
  if (size == 0 || num == 0)
    return false;
 
  if (filedata->file_header.e_shoff == 0)
    return false;
 
  if (size < sizeof * shdrs)
    {
      if (! probe)
    error (_("The e_shentsize field in the ELF header is less than the size of an ELF section header\n"));
      return false;
    }
  if (!probe && size > sizeof * shdrs)
    warn (_("The e_shentsize field in the ELF header is larger than the size of an ELF section header\n"));
 
  // 从文件读取节头数据
  shdrs = (Elf32_External_Shdr *) get_data (NULL, filedata, filedata->file_header.e_shoff,
                                            size, num,
                        probe ? NULL : _("section headers"));
  if (shdrs == NULL)
    return false;
  
    // 为 filedata->section_headers 申请内存
  filedata->section_headers = (Elf_Internal_Shdr *)
    cmalloc (num, sizeof (Elf_Internal_Shdr));
  if (filedata->section_headers == NULL)
    {
      if (!probe)
    error (_("Out of memory reading %u section headers\n"), num);
      free (shdrs);
      return false;
    }
  // 遍历所有节头项, 填充到 filedata 节头表
  for (i = 0, internal = filedata->section_headers;
       i < num;
       i++, internal++)
    {
      internal->sh_name      = BYTE_GET (shdrs[i].sh_name);
      internal->sh_type      = BYTE_GET (shdrs[i].sh_type);
      internal->sh_flags     = BYTE_GET (shdrs[i].sh_flags);
      internal->sh_addr      = BYTE_GET (shdrs[i].sh_addr);
      internal->sh_offset    = BYTE_GET (shdrs[i].sh_offset);
      internal->sh_size      = BYTE_GET (shdrs[i].sh_size);
      internal->sh_link      = BYTE_GET (shdrs[i].sh_link);
      internal->sh_info      = BYTE_GET (shdrs[i].sh_info);
      internal->sh_addralign = BYTE_GET (shdrs[i].sh_addralign);
      internal->sh_entsize   = BYTE_GET (shdrs[i].sh_entsize);
      if (!probe && internal->sh_link > num)
    warn (_("Section %u has an out of range sh_link value of %u\n"), i, internal->sh_link);
      if (!probe && internal->sh_flags & SHF_INFO_LINK && internal->sh_info > num)
    warn (_("Section %u has an out of range sh_info value of %u\n"), i, internal->sh_info);
    }
 
  free (shdrs);
  return true;
}
 
// 64 位与 32 位逻辑类似, 结构体不一样
static bool
get_64bit_section_headers (Filedata * filedata, bool probe)
{
  Elf64_External_Shdr *  shdrs;
  Elf_Internal_Shdr *    internal;
  unsigned int           i;
  unsigned int           size = filedata->file_header.e_shentsize;
  unsigned int           num = probe ? 1 : filedata->file_header.e_shnum;
 
  if (size == 0 || num == 0)
    return false;
 
  if (filedata->file_header.e_shoff == 0)
    return false;
 
  if (size < sizeof * shdrs)
    {
      if (! probe)
    error (_("The e_shentsize field in the ELF header is less than the size of an ELF section header\n"));
      return false;
    }
 
  if (! probe && size > sizeof * shdrs)
    warn (_("The e_shentsize field in the ELF header is larger than the size of an ELF section header\n"));
 
  shdrs = (Elf64_External_Shdr *) get_data (NULL, filedata,
                        filedata->file_header.e_shoff,
                                            size, num,
                        probe ? NULL : _("section headers"));
  if (shdrs == NULL)
    return false;
 
  filedata->section_headers = (Elf_Internal_Shdr *)
    cmalloc (num, sizeof (Elf_Internal_Shdr));
  if (filedata->section_headers == NULL)
    {
      if (! probe)
    error (_("Out of memory reading %u section headers\n"), num);
      free (shdrs);
      return false;
    }
 
  for (i = 0, internal = filedata->section_headers;
       i < num;
       i++, internal++)
    {
      internal->sh_name      = BYTE_GET (shdrs[i].sh_name);
      internal->sh_type      = BYTE_GET (shdrs[i].sh_type);
      internal->sh_flags     = BYTE_GET (shdrs[i].sh_flags);
      internal->sh_addr      = BYTE_GET (shdrs[i].sh_addr);
      internal->sh_size      = BYTE_GET (shdrs[i].sh_size);
      internal->sh_entsize   = BYTE_GET (shdrs[i].sh_entsize);
      internal->sh_link      = BYTE_GET (shdrs[i].sh_link);
      internal->sh_info      = BYTE_GET (shdrs[i].sh_info);
      internal->sh_offset    = BYTE_GET (shdrs[i].sh_offset);
      internal->sh_addralign = BYTE_GET (shdrs[i].sh_addralign);
      if (!probe && internal->sh_link > num)
    warn (_("Section %u has an out of range sh_link value of %u\n"), i, internal->sh_link);
      if (!probe && internal->sh_flags & SHF_INFO_LINK && internal->sh_info > num)
    warn (_("Section %u has an out of range sh_info value of %u\n"), i, internal->sh_info);
    }
 
  free (shdrs);
  return true;
}
typedef struct {
  unsigned char sh_name[4];     /* 名称, 对应字符串表中的索引 */
  unsigned char sh_type[4];     /* 类型 */
  unsigned char sh_flags[4];        /* 各类属性 */
  unsigned char sh_addr[4];     /* 执行时节的虚拟地址 */
  unsigned char sh_offset[4];       /* 在文件中的偏移量 */
  unsigned char sh_size[4];     /* 总大小 */
  unsigned char sh_link[4];     /* 关联节的索引 */
  unsigned char sh_info[4];     /* 补充信息 */
  unsigned char sh_addralign[4];    /* 内存对齐要求 */
  unsigned char sh_entsize[4];      /* 若节为条目数组类型, 指单个条目的字节数, 非数组类节为 0 */
} Elf32_External_Shdr;
 
typedef struct {
  unsigned char sh_name[4];     /* Section name, index in string tbl */
  unsigned char sh_type[4];     /* Type of section */
  unsigned char sh_flags[8];        /* Miscellaneous section attributes */
  unsigned char sh_addr[8];     /* Section virtual addr at execution */
  unsigned char sh_offset[8];       /* Section file offset */
  unsigned char sh_size[8];     /* Size of section in bytes */
  unsigned char sh_link[4];     /* Index of another section */
  unsigned char sh_info[4];     /* Additional section information */
  unsigned char sh_addralign[8];    /* Section alignment */
  unsigned char sh_entsize[8];      /* Entry size if section holds table */
} Elf64_External_Shdr;
typedef struct {
  unsigned char sh_name[4];     /* 名称, 对应字符串表中的索引 */
  unsigned char sh_type[4];     /* 类型 */
  unsigned char sh_flags[4];        /* 各类属性 */
  unsigned char sh_addr[4];     /* 执行时节的虚拟地址 */
  unsigned char sh_offset[4];       /* 在文件中的偏移量 */
  unsigned char sh_size[4];     /* 总大小 */
  unsigned char sh_link[4];     /* 关联节的索引 */
  unsigned char sh_info[4];     /* 补充信息 */
  unsigned char sh_addralign[4];    /* 内存对齐要求 */
  unsigned char sh_entsize[4];      /* 若节为条目数组类型, 指单个条目的字节数, 非数组类节为 0 */
} Elf32_External_Shdr;
 
typedef struct {
  unsigned char sh_name[4];     /* Section name, index in string tbl */
  unsigned char sh_type[4];     /* Type of section */
  unsigned char sh_flags[8];        /* Miscellaneous section attributes */
  unsigned char sh_addr[8];     /* Section virtual addr at execution */
  unsigned char sh_offset[8];       /* Section file offset */
  unsigned char sh_size[8];     /* Size of section in bytes */
  unsigned char sh_link[4];     /* Index of another section */
  unsigned char sh_info[4];     /* Additional section information */
  unsigned char sh_addralign[8];    /* Section alignment */
  unsigned char sh_entsize[8];      /* Entry size if section holds table */
} Elf64_External_Shdr;
static bool
process_file_header (Filedata * filedata)
{
  Elf_Internal_Ehdr * header = & filedata->file_header;
 
  // 验证 ELF 魔数, 4 字节(0x7f, 'E', 'L', 'F')
  if (! check_magic_number (filedata, header))
    return false;
 
  if (! filedata->is_separate)
    init_dwarf_by_elf_machine_code (header->e_machine);
 
  // 当启用 header 打印选项时, 打印 ELF 文件头信息
  if (do_header)
    {
      unsigned i;
 
      if (filedata->is_separate)
    printf (_("ELF Header in linked file '%s':\n"),
        printable_string (filedata->file_name, 0));         // 打印文件名
      else
    printf (_("ELF Header:\n"));
      printf (_("  Magic:   "));
      for (i = 0; i < EI_NIDENT; i++)
    printf ("%2.2x ", header->e_ident[i]);                // 打印魔数字段, EI_NIDENT=16, ELF 标识字段总长度
      printf ("\n");
      printf (_("  Class:                             %s\n"),
          get_elf_class (header->e_ident[EI_CLASS]));     // 32 位/ 64 位
      printf (_("  Data:                              %s\n"),
          get_data_encoding (header->e_ident[EI_DATA]));  // 小端/大端
      printf (_("  Version:                           %d%s\n"),
          header->e_ident[EI_VERSION],                    // 区分当前版本(EV_CURRENT), 无版本(EV_NONE)和未知版本
          (header->e_ident[EI_VERSION] == EV_CURRENT
           ? _(" (current)")
           : (header->e_ident[EI_VERSION] != EV_NONE
          ? _(" <unknown>")
          : "")));
      printf (_("  OS/ABI:                            %s\n"),
          get_osabi_name (filedata, header->e_ident[EI_OSABI]));    // 打印 OS/ABI 标识
      printf (_("  ABI Version:                       %d\n"),
          header->e_ident[EI_ABIVERSION]);                          // 打印 ABI 版本号
      printf (_("  Type:                              %s\n"),    
          get_file_type (filedata));                                // 打印文件类型, 可执行文件、库文件、目标文件等
      printf (_("  Machine:                           %s\n"),
          get_machine_name (header->e_machine));                    // 打印机器架构, x86、ARM、x86_64 等
      printf (_("  Version:                           0x%lx\n"),
          header->e_version);                                       // 依旧版本
 
      printf (_("  Entry point address:               "));
      print_vma (header->e_entry, PREFIX_HEX);                    // 打印入口点地址
      printf (_("\n  Start of program headers:          "));
      print_vma (header->e_phoff, DEC);                           // 打印程序头表在文件中的偏移量
      printf (_(" (bytes into file)\n  Start of section headers:          "));
      print_vma (header->e_shoff, DEC);                           // 打印节头表在文件中的偏移量
      printf (_(" (bytes into file)\n"));
 
      printf (_("  Flags:                             0x%lx%s\n"),
          header->e_flags,                                          // 打印处理器特定标志
          get_machine_flags (filedata, header->e_flags, header->e_machine));
      printf (_("  Size of this header:               %u (bytes)\n"),
          header->e_ehsize);                                        // 打印 ELF 文件头自身的大小
      printf (_("  Size of program headers:           %u (bytes)\n"),
          header->e_phentsize);                                     // 打印每个程序头表项的大小
      printf (_("  Number of program headers:         %u"),
          header->e_phnum);                                         // 打印程序头表项数量
      if (filedata->section_headers != NULL
      && header->e_phnum == PN_XNUM       // 特殊处理: 当 e_phnum=PN_XNUM 时, 实际数量存储在节头表第 0 项的 sh_info 中
      && filedata->section_headers[0].sh_info != 0)
    printf (" (%u)", filedata->section_headers[0].sh_info);
      putc ('\n', stdout);
      printf (_("  Size of section headers:           %u (bytes)\n"),   // 打印每个节头表项的大小
          header->e_shentsize);
      printf (_("  Number of section headers:         %u"),             // 打印节头表项数量
          header->e_shnum);
      if (filedata->section_headers != NULL && header->e_shnum == SHN_UNDEF) 
    { // 特殊处理: 当 e_shnum=SHN_UNDEF 时, 实际数量存储在节头表第 0 项的 sh_size 中
      header->e_shnum = filedata->section_headers[0].sh_size;
      printf (" (%u)", header->e_shnum);
    }
      putc ('\n', stdout);
      printf (_("  Section header string table index: %u"),
          header->e_shstrndx);                                      // 打印节名字符串表的节头索引
      if (filedata->section_headers != NULL
      && header->e_shstrndx == (SHN_XINDEX & 0xffff))
    { // 特殊处理: 当 e_shstrndx=SHN_XINDEX 时, 实际索引存储在节头表第 0 项的 sh_link 中
      header->e_shstrndx = filedata->section_headers[0].sh_link;
      printf (" (%u)", header->e_shstrndx);
    }
      if (header->e_shstrndx != SHN_UNDEF
      && header->e_shstrndx >= header->e_shnum)
    {
      header->e_shstrndx = SHN_UNDEF; // 若索引超出节头数量范围, 则标记为无效
      printf (_(" <corrupt: out of range>"));
    }
      putc ('\n', stdout);
    }
 
  // 修正文件头字段
  if (filedata->section_headers != NULL)
    {
      if (header->e_phnum == PN_XNUM
      && filedata->section_headers[0].sh_info != 0)
    {
      free (filedata->program_headers);
      filedata->program_headers = NULL;
      header->e_phnum = filedata->section_headers[0].sh_info;
    }
      if (header->e_shnum == SHN_UNDEF)
    header->e_shnum = filedata->section_headers[0].sh_size;
      if (header->e_shstrndx == (SHN_XINDEX & 0xffff))
    header->e_shstrndx = filedata->section_headers[0].sh_link;
      if (header->e_shstrndx >= header->e_shnum)
    header->e_shstrndx = SHN_UNDEF;
    }
 
  return true;
}
static bool
process_file_header (Filedata * filedata)
{
  Elf_Internal_Ehdr * header = & filedata->file_header;
 
  // 验证 ELF 魔数, 4 字节(0x7f, 'E', 'L', 'F')
  if (! check_magic_number (filedata, header))
    return false;
 
  if (! filedata->is_separate)
    init_dwarf_by_elf_machine_code (header->e_machine);
 
  // 当启用 header 打印选项时, 打印 ELF 文件头信息
  if (do_header)
    {
      unsigned i;
 
      if (filedata->is_separate)
    printf (_("ELF Header in linked file '%s':\n"),
        printable_string (filedata->file_name, 0));         // 打印文件名
      else
    printf (_("ELF Header:\n"));
      printf (_("  Magic:   "));
      for (i = 0; i < EI_NIDENT; i++)
    printf ("%2.2x ", header->e_ident[i]);                // 打印魔数字段, EI_NIDENT=16, ELF 标识字段总长度
      printf ("\n");
      printf (_("  Class:                             %s\n"),
          get_elf_class (header->e_ident[EI_CLASS]));     // 32 位/ 64 位
      printf (_("  Data:                              %s\n"),
          get_data_encoding (header->e_ident[EI_DATA]));  // 小端/大端
      printf (_("  Version:                           %d%s\n"),
          header->e_ident[EI_VERSION],                    // 区分当前版本(EV_CURRENT), 无版本(EV_NONE)和未知版本
          (header->e_ident[EI_VERSION] == EV_CURRENT
           ? _(" (current)")
           : (header->e_ident[EI_VERSION] != EV_NONE
          ? _(" <unknown>")
          : "")));
      printf (_("  OS/ABI:                            %s\n"),
          get_osabi_name (filedata, header->e_ident[EI_OSABI]));    // 打印 OS/ABI 标识
      printf (_("  ABI Version:                       %d\n"),
          header->e_ident[EI_ABIVERSION]);                          // 打印 ABI 版本号
      printf (_("  Type:                              %s\n"),    
          get_file_type (filedata));                                // 打印文件类型, 可执行文件、库文件、目标文件等
      printf (_("  Machine:                           %s\n"),
          get_machine_name (header->e_machine));                    // 打印机器架构, x86、ARM、x86_64 等
      printf (_("  Version:                           0x%lx\n"),
          header->e_version);                                       // 依旧版本
 
      printf (_("  Entry point address:               "));
      print_vma (header->e_entry, PREFIX_HEX);                    // 打印入口点地址
      printf (_("\n  Start of program headers:          "));
      print_vma (header->e_phoff, DEC);                           // 打印程序头表在文件中的偏移量
      printf (_(" (bytes into file)\n  Start of section headers:          "));
      print_vma (header->e_shoff, DEC);                           // 打印节头表在文件中的偏移量
      printf (_(" (bytes into file)\n"));
 
      printf (_("  Flags:                             0x%lx%s\n"),
          header->e_flags,                                          // 打印处理器特定标志
          get_machine_flags (filedata, header->e_flags, header->e_machine));
      printf (_("  Size of this header:               %u (bytes)\n"),
          header->e_ehsize);                                        // 打印 ELF 文件头自身的大小
      printf (_("  Size of program headers:           %u (bytes)\n"),
          header->e_phentsize);                                     // 打印每个程序头表项的大小
      printf (_("  Number of program headers:         %u"),
          header->e_phnum);                                         // 打印程序头表项数量
      if (filedata->section_headers != NULL
      && header->e_phnum == PN_XNUM       // 特殊处理: 当 e_phnum=PN_XNUM 时, 实际数量存储在节头表第 0 项的 sh_info 中
      && filedata->section_headers[0].sh_info != 0)
    printf (" (%u)", filedata->section_headers[0].sh_info);
      putc ('\n', stdout);
      printf (_("  Size of section headers:           %u (bytes)\n"),   // 打印每个节头表项的大小
          header->e_shentsize);
      printf (_("  Number of section headers:         %u"),             // 打印节头表项数量
          header->e_shnum);
      if (filedata->section_headers != NULL && header->e_shnum == SHN_UNDEF) 
    { // 特殊处理: 当 e_shnum=SHN_UNDEF 时, 实际数量存储在节头表第 0 项的 sh_size 中
      header->e_shnum = filedata->section_headers[0].sh_size;
      printf (" (%u)", header->e_shnum);
    }
      putc ('\n', stdout);
      printf (_("  Section header string table index: %u"),
          header->e_shstrndx);                                      // 打印节名字符串表的节头索引
      if (filedata->section_headers != NULL
      && header->e_shstrndx == (SHN_XINDEX & 0xffff))
    { // 特殊处理: 当 e_shstrndx=SHN_XINDEX 时, 实际索引存储在节头表第 0 项的 sh_link 中
      header->e_shstrndx = filedata->section_headers[0].sh_link;
      printf (" (%u)", header->e_shstrndx);
    }
      if (header->e_shstrndx != SHN_UNDEF
      && header->e_shstrndx >= header->e_shnum)
    {
      header->e_shstrndx = SHN_UNDEF; // 若索引超出节头数量范围, 则标记为无效
      printf (_(" <corrupt: out of range>"));
    }
      putc ('\n', stdout);
    }
 
  // 修正文件头字段
  if (filedata->section_headers != NULL)
    {
      if (header->e_phnum == PN_XNUM
      && filedata->section_headers[0].sh_info != 0)
    {
      free (filedata->program_headers);
      filedata->program_headers = NULL;
      header->e_phnum = filedata->section_headers[0].sh_info;
    }
      if (header->e_shnum == SHN_UNDEF)
    header->e_shnum = filedata->section_headers[0].sh_size;
      if (header->e_shstrndx == (SHN_XINDEX & 0xffff))
    header->e_shstrndx = filedata->section_headers[0].sh_link;
      if (header->e_shstrndx >= header->e_shnum)
    header->e_shstrndx = SHN_UNDEF;
    }
 
  return true;
}
static bool
process_section_headers (Filedata * filedata)
{
    // ... 省略一些与主逻辑无关的代码
 
  // get_section_headers 在上面看过了, 第二个 false 参数代表读取全部节
  if (!get_section_headers (filedata, false))
    return false;
 
  // 加载节头字符串表(.shstrtab), 存到 filedata->string_table 中, 用于解析节名称
  if (filedata->string_table == NULL
      && filedata->file_header.e_shstrndx != SHN_UNDEF
      && filedata->file_header.e_shstrndx < filedata->file_header.e_shnum)
    {
            // 根据 e_shstrndx 算出 .shstrtab 所在节
      section = filedata->section_headers + filedata->file_header.e_shstrndx;
 
      if (section->sh_size != 0)
    {
        // 写入 filedata->string_table
      filedata->string_table = (char *) get_data (NULL, filedata, section->sh_offset,
                              1, section->sh_size,
                              _("string table"));
 
      filedata->string_table_length = filedata->string_table != NULL ? section->sh_size : 0;
    }
    }
 
  // 根据不同架构, 初始化异常处理的地址长度
  eh_addr_size = is_32bit_elf ? 4 : 8;
  switch (filedata->file_header.e_machine)
    {
    case EM_MIPS:
    case EM_MIPS_RS3_LE:
      if ((filedata->file_header.e_flags & EF_MIPS_ABI) == EF_MIPS_ABI_EABI64
      && find_section (filedata, ".gcc_compiled_long32") == NULL)
    eh_addr_size = 8;
      break;
 
    case EM_H8_300:
    case EM_H8_300H:
      switch (filedata->file_header.e_flags & EF_H8_MACH)
    {
    case E_H8_MACH_H8300:
    case E_H8_MACH_H8300HN:
    case E_H8_MACH_H8300SN:
    case E_H8_MACH_H8300SXN:
      eh_addr_size = 2;
      break;
    case E_H8_MACH_H8300H:
    case E_H8_MACH_H8300S:
    case E_H8_MACH_H8300SX:
      eh_addr_size = 4;
      break;
    }
      break;
 
    case EM_M32C_OLD:
    case EM_M32C:
      switch (filedata->file_header.e_flags & EF_M32C_CPU_MASK)
    {
    case EF_M32C_CPU_M16C:
      eh_addr_size = 2;
      break;
    }
      break;
    }
 
// 校验节表项大小(sh_entsize)的合法性
#define CHECK_ENTSIZE_VALUES(section, i, size32, size64)        \
  do                                    \
    {                                   \
      uint64_t expected_entsize = is_32bit_elf ? size32 : size64;   \
      if (section->sh_entsize != expected_entsize)           \
    {                               \
      error (_("Section %d has invalid sh_entsize of %" PRIx64 "\n"), \
         i, section->sh_entsize);                \
      error (_("(Using the expected size of %" PRIx64 " for the rest of this dump)\n"), \
         expected_entsize);                 \
      section->sh_entsize = expected_entsize;            \
    }                               \
    }                                   \
  while (0)
 
#define CHECK_ENTSIZE(section, i, type)                 \
  CHECK_ENTSIZE_VALUES (section, i, sizeof (Elf32_External_##type), \
            sizeof (Elf64_External_##type))
 
  // 遍历所有节头
  for (i = 0, section = filedata->section_headers;
       i < filedata->file_header.e_shnum;
       i++, section++)
    {
      // 获取当前节的可读名称, 从前面 filedata->string_table 中取, section->sh_name 为偏移
      const char *name = printable_section_name (filedata, section);
 
      // 据节类型(sh_type)解析关键节 + 校验表项大小
      switch (section->sh_type)
    {
    case SHT_DYNSYM:  // 动态符号表节
      if (filedata->dynamic_symbols != NULL)
        {
          error (_("File contains multiple dynamic symbol tables\n"));
          continue;
        }
 
      CHECK_ENTSIZE (section, i, Sym);
 
        // 记录到 filedata->dynamic_symbols
      filedata->dynamic_symbols
        = get_elf_symbols (filedata, section, &filedata->num_dynamic_syms);
      filedata->dynamic_symtab_section = section;
      break;
 
    case SHT_STRTAB:  // 字符串表节
      if (streq (name, ".dynstr"))
        {
          if (filedata->dynamic_strings != NULL)
        {
          error (_("File contains multiple dynamic string tables\n"));
          continue;
        }
 
                // 记录到 filedata->dynamic_strings
          filedata->dynamic_strings
        = (char *) get_data (NULL, filedata, section->sh_offset,
                     1, section->sh_size, _("dynamic strings"));
          filedata->dynamic_strings_length
        = filedata->dynamic_strings == NULL ? 0 : section->sh_size;
          filedata->dynamic_strtab_section = section;
        }
      break;
 
    case SHT_SYMTAB_SHNDX:  // 符号表节索引扩展节, 处理符号节索引溢出
      {
        elf_section_list * entry = xmalloc (sizeof * entry);
 
        entry->hdr = section;
        entry->next = filedata->symtab_shndx_list;
        filedata->symtab_shndx_list = entry;
      }
      break;
 
    case SHT_SYMTAB:    // 静态符号表节
      CHECK_ENTSIZE (section, i, Sym);
      break;
 
    case SHT_GROUP:     // 节组节, 用于分组关联的节
      CHECK_ENTSIZE_VALUES (section, i, GRP_ENTRY_SIZE, GRP_ENTRY_SIZE);
      break;
 
    case SHT_REL:       // 重定位表节
      CHECK_ENTSIZE (section, i, Rel);
      if (do_checks && section->sh_size == 0)
        warn (_("Section '%s': zero-sized relocation section\n"), name);
      break;
 
    case SHT_RELA:      // 重定位表节
      CHECK_ENTSIZE (section, i, Rela);
      if (do_checks && section->sh_size == 0)
        warn (_("Section '%s': zero-sized relocation section\n"), name);
      break;
 
    case SHT_RELR:      // 简化重定位表节
      CHECK_ENTSIZE (section, i, Relr);
      break;
 
    case SHT_NOTE:        // 备注信息节
    case SHT_PROGBITS:    // 程序数据节
    case SHT_GNU_SFRAME:  // GNU 栈帧节
      if (do_checks && section->sh_size == 0)
        warn (_("Section '%s': has a size of zero - is this intended ?\n"), name);
      break;
 
    default:
      break;
    }
 
  // 以下根据用户指定的调试选项, 标记需要 dump 的调试相关节
 
      if ((do_debugging || do_debug_info || do_debug_abbrevs
       || do_debug_lines || do_debug_pubnames || do_debug_pubtypes
       || do_debug_aranges || do_debug_frames || do_debug_macinfo
       || do_debug_str || do_debug_str_offsets || do_debug_loc
       || do_debug_ranges
       || do_debug_addr || do_debug_cu_index || do_debug_links)
      && (startswith (name, ".debug_")
          || startswith (name, ".zdebug_")))
    {       // 跳过前缀统一匹配后续名称
          if (name[1] == 'z')
            name += sizeof (".zdebug_") - 1;
          else
            name += sizeof (".debug_") - 1;
 
    // 根据用户启用的调试选项, 标记对应的节为需要dump
      if (do_debugging
          || (do_debug_info     && startswith (name, "info"))
          || (do_debug_info     && startswith (name, "types"))
          || (do_debug_abbrevs  && startswith (name, "abbrev"))
          || (do_debug_lines    && strcmp (name, "line") == 0)
          || (do_debug_lines    && startswith (name, "line."))
          || (do_debug_pubnames && startswith (name, "pubnames"))
          || (do_debug_pubtypes && startswith (name, "pubtypes"))
          || (do_debug_pubnames && startswith (name, "gnu_pubnames"))
          || (do_debug_pubtypes && startswith (name, "gnu_pubtypes"))
          || (do_debug_aranges  && startswith (name, "aranges"))
          || (do_debug_ranges   && startswith (name, "ranges"))
          || (do_debug_ranges   && startswith (name, "rnglists"))
          || (do_debug_frames   && startswith (name, "frame"))
          || (do_debug_macinfo  && startswith (name, "macinfo"))
          || (do_debug_macinfo  && startswith (name, "macro"))
          || (do_debug_str      && startswith (name, "str"))
          || (do_debug_links    && startswith (name, "sup"))
          || (do_debug_str_offsets && startswith (name, "str_offsets"))
          || (do_debug_loc      && startswith (name, "loc"))
          || (do_debug_loc      && startswith (name, "loclists"))
          || (do_debug_addr     && startswith (name, "addr"))
          || (do_debug_cu_index && startswith (name, "cu_index"))
          || (do_debug_cu_index && startswith (name, "tu_index"))
          )
        request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
    }
      else if ((do_debugging || do_debug_info)
           && startswith (name, ".gnu.linkonce.wi."))
    request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
      else if (do_debug_frames && streq (name, ".eh_frame"))
    request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
      else if (do_debug_frames && streq (name, ".eh_frame_hdr"))
    request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
      else if (do_gdb_index && (streq (name, ".gdb_index")
                || streq (name, ".debug_names")))
    request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
      else if ((do_debugging || do_trace_info || do_trace_abbrevs
                || do_trace_aranges)
           && startswith (name, ".trace_"))
    {
          name += sizeof (".trace_") - 1;
 
      if (do_debugging
          || (do_trace_info     && streq (name, "info"))
          || (do_trace_abbrevs  && streq (name, "abbrev"))
          || (do_trace_aranges  && streq (name, "aranges"))
          )
        request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
    }
      else if ((do_debugging || do_debug_links)
           && (startswith (name, ".gnu_debuglink")
           || startswith (name, ".gnu_debugaltlink")))
    request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
    }
 
  if (! do_sections)
    return true;
 
  if (filedata->is_separate && ! process_links)
    return true;
 
  // 打印节头表标题
  if (filedata->is_separate)
    printf (_("\nSection Headers in linked file '%s':\n"),
        printable_string (filedata->file_name, 0));
  else if (filedata->file_header.e_shnum > 1)
    printf (_("\nSection Headers:\n"));
  else
    printf (_("\nSection Header:\n"));
 
  // 打印节头表列标题
  if (is_32bit_elf)
    {
      if (do_section_details)
    {
      printf (_("  [Nr] Name\n"));
      printf (_("       Type            Addr     Off    Size   ES   Lk Inf Al\n"));
    }
      else
    printf
      (_("  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al\n"));
    }
  else if (do_wide)
    {
      if (do_section_details)
    {
      printf (_("  [Nr] Name\n"));
      printf (_("       Type            Address          Off    Size   ES   Lk Inf Al\n"));
    }
      else
    printf
      (_("  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al\n"));
    }
  else
    {
      if (do_section_details)
    {
      printf (_("  [Nr] Name\n"));
      printf (_("       Type              Address          Offset            Link\n"));
      printf (_("       Size              EntSize          Info              Align\n"));
    }
      else
    {
      printf (_("  [Nr] Name              Type             Address           Offset\n"));
      printf (_("       Size              EntSize          Flags  Link  Info  Align\n"));
    }
    }
 
  if (do_section_details)
    printf (_("       Flags\n"));
 
  // 遍历所有节头, 打印每个节的详细信息
  for (i = 0, section = filedata->section_headers;
       i < filedata->file_header.e_shnum;
       i++, section++)
    {
 
      // 校验节头的 sh_link 字段, 关联节索引
      switch (section->sh_type)
    {
    case SHT_REL:
    case SHT_RELR:
    case SHT_RELA:
    // 动态重定位节(可执行/共享库)允许 sh_link=0
      if (section->sh_link == 0
          && (filedata->file_header.e_type == ET_EXEC
          || filedata->file_header.e_type == ET_DYN))
        break;
    case SHT_SYMTAB_SHNDX:
    case SHT_GROUP:
    case SHT_HASH:
    case SHT_GNU_HASH:
    case SHT_GNU_versym:
    // 校验: sh_link 必须是有效的符号表节索引(SHT_SYMTAB/SHT_DYNSYM)
      if (section->sh_link == 0
          || section->sh_link >= filedata->file_header.e_shnum
          || (filedata->section_headers[section->sh_link].sh_type != SHT_SYMTAB
          && filedata->section_headers[section->sh_link].sh_type != SHT_DYNSYM))
        warn (_("[%2u]: Link field (%u) should index a symtab section.\n"),
          i, section->sh_link);
      break;
 
    case SHT_DYNAMIC:
    case SHT_SYMTAB:
    case SHT_DYNSYM:
    case SHT_GNU_verneed:
    case SHT_GNU_verdef:
    case SHT_GNU_LIBLIST:
    // 校验: sh_link 必须是有效的字符串表节索引(SHT_STRTAB)
      if (section->sh_link == 0
          || section->sh_link >= filedata->file_header.e_shnum
          || filedata->section_headers[section->sh_link].sh_type != SHT_STRTAB)
        warn (_("[%2u]: Link field (%u) should index a string section.\n"),
          i, section->sh_link);
      break;
 
    case SHT_INIT_ARRAY:
    case SHT_FINI_ARRAY:
    case SHT_PREINIT_ARRAY:
    // 校验: 非 OS 特定节的 sh_link 应为 0
      if (section->sh_type < SHT_LOOS && section->sh_link != 0)
        warn (_("[%2u]: Unexpected value (%u) in link field.\n"),
          i, section->sh_link);
      break;
 
    default:
#if 0    
#endif
      break;
    }
 
      // 校验节头的 sh_info 字段
      switch (section->sh_type)
    {
    case SHT_REL:
    case SHT_RELA:
    // 动态重定位节允许 sh_info=0
      if (section->sh_info == 0
          && (filedata->file_header.e_type == ET_EXEC
          || filedata->file_header.e_type == ET_DYN))
        break;
    // // 校验: sh_info 必须是有效的可重定位节索引
      if (section->sh_info == 0
          || section->sh_info >= filedata->file_header.e_shnum
          || (filedata->section_headers[section->sh_info].sh_type != SHT_PROGBITS
          && filedata->section_headers[section->sh_info].sh_type != SHT_NOBITS
          && filedata->section_headers[section->sh_info].sh_type != SHT_NOTE
          && filedata->section_headers[section->sh_info].sh_type != SHT_INIT_ARRAY
          && filedata->section_headers[section->sh_info].sh_type != SHT_FINI_ARRAY
          && filedata->section_headers[section->sh_info].sh_type != SHT_PREINIT_ARRAY
          /* FIXME: Are other section types valid ?  */
          && filedata->section_headers[section->sh_info].sh_type < SHT_LOOS))
        warn (_("[%2u]: Info field (%u) should index a relocatable section.\n"),
          i, section->sh_info);
      break;
 
    case SHT_DYNAMIC:
    case SHT_HASH:
    case SHT_SYMTAB_SHNDX:
    case SHT_INIT_ARRAY:
    case SHT_FINI_ARRAY:
    case SHT_PREINIT_ARRAY:
    // 校验: sh_info 应为 0
      if (section->sh_info != 0)
        warn (_("[%2u]: Unexpected value (%u) in info field.\n"),
          i, section->sh_info);
      break;
 
    case SHT_GROUP:
    case SHT_SYMTAB:
    case SHT_DYNSYM:
      break;
 
    default:
      if (section->sh_type == SHT_NOBITS)
        ;
      else if (section->sh_flags & SHF_INFO_LINK)
        {
          if (section->sh_info < 1 || section->sh_info >= filedata->file_header.e_shnum)
        warn (_("[%2u]: Expected link to another section in info field"), i);
        }
      else if (section->sh_type < SHT_LOOS
           && (section->sh_flags & SHF_GNU_MBIND) == 0
           && section->sh_info != 0)
        warn (_("[%2u]: Unexpected value (%u) in info field.\n"),
          i, section->sh_info);
      break;
    }
      // 校验节大小
      if (section->sh_size > filedata->file_size
      && section->sh_type != SHT_NOBITS
      && section->sh_type != SHT_NULL
      && section->sh_type < SHT_LOOS)
    warn (_("Size of section %u is larger than the entire file!\n"), i);
 
      // 打印节头信息
      printf ("  [%2u] ", i);
      if (do_section_details)
    printf ("%s\n      ", printable_section_name (filedata, section));
      else
    print_symbol_name (-17, printable_section_name (filedata, section));
 
      // 打印节类型
      printf (do_wide ? " %-15s " : " %-15.15s ",
          get_section_type_name (filedata, section->sh_type));
 
      // 32位 ELF 显示逻辑
      if (is_32bit_elf)
    {
      const char * link_too_big = NULL;
 
    // 节的内存地址
      print_vma (section->sh_addr, LONG_HEX);
 
    // 打印文件偏移, 节大小, 表项大小
      printf ( " %6.6lx %6.6lx %2.2lx",
           (unsigned long) section->sh_offset,
           (unsigned long) section->sh_size,
           (unsigned long) section->sh_entsize);
 
      if (do_section_details)
        fputs ("  ", stdout);
      else
        printf (" %3s ", get_elf_section_flags (filedata, section->sh_flags));  // 节标志
 
      if (section->sh_link >= filedata->file_header.e_shnum)
        {
          link_too_big = "";
          switch (filedata->file_header.e_machine)
        {
        case EM_386:
        case EM_IAMCU:
        case EM_X86_64:
        case EM_L1OM:
        case EM_K1OM:
        case EM_OLD_SPARCV9:
        case EM_SPARC32PLUS:
        case EM_SPARCV9:
        case EM_SPARC:
          if (section->sh_link == (SHN_BEFORE & 0xffff))
            link_too_big = "BEFORE";
          else if (section->sh_link == (SHN_AFTER & 0xffff))
            link_too_big = "AFTER";
          break;
        default:
          break;
        }
        }
 
    // 打印链接索引, 信息字段, 对齐要求
      if (do_section_details)
        {
          if (link_too_big != NULL && * link_too_big)
        printf ("<%s> ", link_too_big);
          else
        printf ("%2u ", section->sh_link);
          printf ("%3u %2lu\n", section->sh_info,
              (unsigned long) section->sh_addralign);
        }
      else
        printf ("%2u %3u %2lu\n",
            section->sh_link,
            section->sh_info,
            (unsigned long) section->sh_addralign);
 
      if (link_too_big && ! * link_too_big)
        warn (_("section %u: sh_link value of %u is larger than the number of sections\n"),
          i, section->sh_link);
    }
      // 64位 ELF 宽显示模式
      else if (do_wide)
    {
      print_vma (section->sh_addr, LONG_HEX);
 
      if ((long) section->sh_offset == section->sh_offset)
        printf (" %6.6lx", (unsigned long) section->sh_offset);
      else
        {
          putchar (' ');
          print_vma (section->sh_offset, LONG_HEX);
        }
 
      if ((unsigned long) section->sh_size == section->sh_size)
        printf (" %6.6lx", (unsigned long) section->sh_size);
      else
        {
          putchar (' ');
          print_vma (section->sh_size, LONG_HEX);
        }
 
      if ((unsigned long) section->sh_entsize == section->sh_entsize)
        printf (" %2.2lx", (unsigned long) section->sh_entsize);
      else
        {
          putchar (' ');
          print_vma (section->sh_entsize, LONG_HEX);
        }
 
      if (do_section_details)
        fputs ("  ", stdout);
      else
        printf (" %3s ", get_elf_section_flags (filedata, section->sh_flags));
 
      printf ("%2u %3u ", section->sh_link, section->sh_info);
 
      if ((unsigned long) section->sh_addralign == section->sh_addralign)
        printf ("%2lu\n", (unsigned long) section->sh_addralign);
      else
        {
          print_vma (section->sh_addralign, DEC);
          putchar ('\n');
        }
    }
      // 64位 ELF 普通详细显示模式
      else if (do_section_details)
    {
      putchar (' ');
      print_vma (section->sh_addr, LONG_HEX);
      if ((long) section->sh_offset == section->sh_offset)
        printf ("  %16.16lx", (unsigned long) section->sh_offset);
      else
        {
          printf ("  ");
          print_vma (section->sh_offset, LONG_HEX);
        }
      printf ("  %u\n       ", section->sh_link);
      print_vma (section->sh_size, LONG_HEX);
      putchar (' ');
      print_vma (section->sh_entsize, LONG_HEX);
 
      printf ("  %-16u  %lu\n",
          section->sh_info,
          (unsigned long) section->sh_addralign);
    }
      else
    {
      putchar (' ');
      print_vma (section->sh_addr, LONG_HEX);
      if ((long) section->sh_offset == section->sh_offset)
        printf ("  %8.8lx", (unsigned long) section->sh_offset);
    // 64位 ELF 普通简洁显示模式
      else
        {
          printf ("  ");
          print_vma (section->sh_offset, LONG_HEX);
        }
      printf ("\n       ");
      print_vma (section->sh_size, LONG_HEX);
      printf ("  ");
      print_vma (section->sh_entsize, LONG_HEX);
 
      printf (" %3s ", get_elf_section_flags (filedata, section->sh_flags));
 
      printf ("     %2u   %3u     %lu\n",
          section->sh_link,
          section->sh_info,
          (unsigned long) section->sh_addralign);
    }
 
      // 若为压缩节, 详细模式下补充打印节标志和压缩信息
      if (do_section_details)
    {
      printf ("       %s\n", get_elf_section_flags (filedata, section->sh_flags));
 
    // 处理压缩节
      if ((section->sh_flags & SHF_COMPRESSED) != 0)
        {
          unsigned char buf[24];
 
          assert (sizeof (buf) >= sizeof (Elf64_External_Chdr));
          if (get_data (&buf, filedata, section->sh_offset, 1,
                sizeof (buf), _("compression header")))
        {
          Elf_Internal_Chdr chdr;
 
      // 解析压缩头部
          if (get_compression_header (&chdr, buf, sizeof (buf)) == 0)
            printf (_("       [<corrupt>]\n"));
          else
            {
              if (chdr.ch_type == ch_compress_zlib)
            printf ("       ZLIB, ");
              else if (chdr.ch_type == ch_compress_zstd)
            printf ("       ZSTD, ");
              else
            printf (_("       [<unknown>: 0x%x], "),
                chdr.ch_type);
              print_vma (chdr.ch_size, LONG_HEX);
              printf (", %lu\n", (unsigned long) chdr.ch_addralign);
            }
        }
        }
    }
    }
 
    // ... 省略一些与主逻辑无关的代码
  return true;
}
static bool
process_section_headers (Filedata * filedata)
{
    // ... 省略一些与主逻辑无关的代码
 
  // get_section_headers 在上面看过了, 第二个 false 参数代表读取全部节
  if (!get_section_headers (filedata, false))
    return false;
 
  // 加载节头字符串表(.shstrtab), 存到 filedata->string_table 中, 用于解析节名称
  if (filedata->string_table == NULL
      && filedata->file_header.e_shstrndx != SHN_UNDEF
      && filedata->file_header.e_shstrndx < filedata->file_header.e_shnum)
    {
            // 根据 e_shstrndx 算出 .shstrtab 所在节
      section = filedata->section_headers + filedata->file_header.e_shstrndx;
 
      if (section->sh_size != 0)
    {
        // 写入 filedata->string_table
      filedata->string_table = (char *) get_data (NULL, filedata, section->sh_offset,
                              1, section->sh_size,
                              _("string table"));
 
      filedata->string_table_length = filedata->string_table != NULL ? section->sh_size : 0;
    }
    }
 
  // 根据不同架构, 初始化异常处理的地址长度
  eh_addr_size = is_32bit_elf ? 4 : 8;
  switch (filedata->file_header.e_machine)
    {
    case EM_MIPS:
    case EM_MIPS_RS3_LE:
      if ((filedata->file_header.e_flags & EF_MIPS_ABI) == EF_MIPS_ABI_EABI64
      && find_section (filedata, ".gcc_compiled_long32") == NULL)
    eh_addr_size = 8;
      break;
 
    case EM_H8_300:
    case EM_H8_300H:
      switch (filedata->file_header.e_flags & EF_H8_MACH)
    {
    case E_H8_MACH_H8300:
    case E_H8_MACH_H8300HN:
    case E_H8_MACH_H8300SN:
    case E_H8_MACH_H8300SXN:
      eh_addr_size = 2;
      break;
    case E_H8_MACH_H8300H:
    case E_H8_MACH_H8300S:
    case E_H8_MACH_H8300SX:
      eh_addr_size = 4;
      break;
    }
      break;
 
    case EM_M32C_OLD:
    case EM_M32C:
      switch (filedata->file_header.e_flags & EF_M32C_CPU_MASK)
    {
    case EF_M32C_CPU_M16C:
      eh_addr_size = 2;
      break;
    }
      break;
    }
 
// 校验节表项大小(sh_entsize)的合法性
#define CHECK_ENTSIZE_VALUES(section, i, size32, size64)        \
  do                                    \
    {                                   \
      uint64_t expected_entsize = is_32bit_elf ? size32 : size64;   \
      if (section->sh_entsize != expected_entsize)           \
    {                               \
      error (_("Section %d has invalid sh_entsize of %" PRIx64 "\n"), \
         i, section->sh_entsize);                \
      error (_("(Using the expected size of %" PRIx64 " for the rest of this dump)\n"), \
         expected_entsize);                 \
      section->sh_entsize = expected_entsize;            \
    }                               \
    }                                   \
  while (0)
 
#define CHECK_ENTSIZE(section, i, type)                 \
  CHECK_ENTSIZE_VALUES (section, i, sizeof (Elf32_External_##type), \
            sizeof (Elf64_External_##type))
 
  // 遍历所有节头
  for (i = 0, section = filedata->section_headers;
       i < filedata->file_header.e_shnum;
       i++, section++)
    {
      // 获取当前节的可读名称, 从前面 filedata->string_table 中取, section->sh_name 为偏移
      const char *name = printable_section_name (filedata, section);
 
      // 据节类型(sh_type)解析关键节 + 校验表项大小
      switch (section->sh_type)
    {
    case SHT_DYNSYM:  // 动态符号表节
      if (filedata->dynamic_symbols != NULL)
        {
          error (_("File contains multiple dynamic symbol tables\n"));
          continue;
        }
 
      CHECK_ENTSIZE (section, i, Sym);
 
        // 记录到 filedata->dynamic_symbols
      filedata->dynamic_symbols
        = get_elf_symbols (filedata, section, &filedata->num_dynamic_syms);
      filedata->dynamic_symtab_section = section;
      break;
 
    case SHT_STRTAB:  // 字符串表节
      if (streq (name, ".dynstr"))
        {
          if (filedata->dynamic_strings != NULL)
        {
          error (_("File contains multiple dynamic string tables\n"));
          continue;
        }
 
                // 记录到 filedata->dynamic_strings
          filedata->dynamic_strings
        = (char *) get_data (NULL, filedata, section->sh_offset,
                     1, section->sh_size, _("dynamic strings"));
          filedata->dynamic_strings_length
        = filedata->dynamic_strings == NULL ? 0 : section->sh_size;
          filedata->dynamic_strtab_section = section;
        }
      break;
 
    case SHT_SYMTAB_SHNDX:  // 符号表节索引扩展节, 处理符号节索引溢出
      {
        elf_section_list * entry = xmalloc (sizeof * entry);
 
        entry->hdr = section;
        entry->next = filedata->symtab_shndx_list;
        filedata->symtab_shndx_list = entry;
      }
      break;
 
    case SHT_SYMTAB:    // 静态符号表节
      CHECK_ENTSIZE (section, i, Sym);
      break;
 
    case SHT_GROUP:     // 节组节, 用于分组关联的节
      CHECK_ENTSIZE_VALUES (section, i, GRP_ENTRY_SIZE, GRP_ENTRY_SIZE);
      break;
 
    case SHT_REL:       // 重定位表节
      CHECK_ENTSIZE (section, i, Rel);
      if (do_checks && section->sh_size == 0)
        warn (_("Section '%s': zero-sized relocation section\n"), name);
      break;
 
    case SHT_RELA:      // 重定位表节
      CHECK_ENTSIZE (section, i, Rela);
      if (do_checks && section->sh_size == 0)
        warn (_("Section '%s': zero-sized relocation section\n"), name);
      break;
 
    case SHT_RELR:      // 简化重定位表节
      CHECK_ENTSIZE (section, i, Relr);
      break;
 
    case SHT_NOTE:        // 备注信息节
    case SHT_PROGBITS:    // 程序数据节
    case SHT_GNU_SFRAME:  // GNU 栈帧节
      if (do_checks && section->sh_size == 0)
        warn (_("Section '%s': has a size of zero - is this intended ?\n"), name);
      break;
 
    default:
      break;
    }
 
  // 以下根据用户指定的调试选项, 标记需要 dump 的调试相关节
 
      if ((do_debugging || do_debug_info || do_debug_abbrevs
       || do_debug_lines || do_debug_pubnames || do_debug_pubtypes
       || do_debug_aranges || do_debug_frames || do_debug_macinfo
       || do_debug_str || do_debug_str_offsets || do_debug_loc
       || do_debug_ranges
       || do_debug_addr || do_debug_cu_index || do_debug_links)
      && (startswith (name, ".debug_")
          || startswith (name, ".zdebug_")))
    {       // 跳过前缀统一匹配后续名称
          if (name[1] == 'z')
            name += sizeof (".zdebug_") - 1;
          else
            name += sizeof (".debug_") - 1;
 
    // 根据用户启用的调试选项, 标记对应的节为需要dump
      if (do_debugging
          || (do_debug_info     && startswith (name, "info"))
          || (do_debug_info     && startswith (name, "types"))
          || (do_debug_abbrevs  && startswith (name, "abbrev"))
          || (do_debug_lines    && strcmp (name, "line") == 0)
          || (do_debug_lines    && startswith (name, "line."))
          || (do_debug_pubnames && startswith (name, "pubnames"))
          || (do_debug_pubtypes && startswith (name, "pubtypes"))
          || (do_debug_pubnames && startswith (name, "gnu_pubnames"))
          || (do_debug_pubtypes && startswith (name, "gnu_pubtypes"))
          || (do_debug_aranges  && startswith (name, "aranges"))
          || (do_debug_ranges   && startswith (name, "ranges"))
          || (do_debug_ranges   && startswith (name, "rnglists"))
          || (do_debug_frames   && startswith (name, "frame"))
          || (do_debug_macinfo  && startswith (name, "macinfo"))
          || (do_debug_macinfo  && startswith (name, "macro"))
          || (do_debug_str      && startswith (name, "str"))
          || (do_debug_links    && startswith (name, "sup"))
          || (do_debug_str_offsets && startswith (name, "str_offsets"))
          || (do_debug_loc      && startswith (name, "loc"))
          || (do_debug_loc      && startswith (name, "loclists"))
          || (do_debug_addr     && startswith (name, "addr"))
          || (do_debug_cu_index && startswith (name, "cu_index"))
          || (do_debug_cu_index && startswith (name, "tu_index"))
          )
        request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
    }
      else if ((do_debugging || do_debug_info)
           && startswith (name, ".gnu.linkonce.wi."))
    request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
      else if (do_debug_frames && streq (name, ".eh_frame"))
    request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
      else if (do_debug_frames && streq (name, ".eh_frame_hdr"))
    request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
      else if (do_gdb_index && (streq (name, ".gdb_index")
                || streq (name, ".debug_names")))
    request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
      else if ((do_debugging || do_trace_info || do_trace_abbrevs
                || do_trace_aranges)
           && startswith (name, ".trace_"))
    {
          name += sizeof (".trace_") - 1;
 
      if (do_debugging
          || (do_trace_info     && streq (name, "info"))
          || (do_trace_abbrevs  && streq (name, "abbrev"))
          || (do_trace_aranges  && streq (name, "aranges"))
          )
        request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
    }
      else if ((do_debugging || do_debug_links)
           && (startswith (name, ".gnu_debuglink")
           || startswith (name, ".gnu_debugaltlink")))
    request_dump_bynumber (&filedata->dump, i, DEBUG_DUMP);
    }
 
  if (! do_sections)
    return true;
 
  if (filedata->is_separate && ! process_links)
    return true;
 
  // 打印节头表标题
  if (filedata->is_separate)
    printf (_("\nSection Headers in linked file '%s':\n"),
        printable_string (filedata->file_name, 0));
  else if (filedata->file_header.e_shnum > 1)
    printf (_("\nSection Headers:\n"));
  else
    printf (_("\nSection Header:\n"));
 
  // 打印节头表列标题
  if (is_32bit_elf)
    {
      if (do_section_details)
    {
      printf (_("  [Nr] Name\n"));
      printf (_("       Type            Addr     Off    Size   ES   Lk Inf Al\n"));
    }
      else
    printf
      (_("  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al\n"));
    }
  else if (do_wide)
    {
      if (do_section_details)
    {
      printf (_("  [Nr] Name\n"));
      printf (_("       Type            Address          Off    Size   ES   Lk Inf Al\n"));
    }
      else
    printf
      (_("  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al\n"));
    }
  else
    {
      if (do_section_details)
    {
      printf (_("  [Nr] Name\n"));
      printf (_("       Type              Address          Offset            Link\n"));
      printf (_("       Size              EntSize          Info              Align\n"));
    }
      else
    {
      printf (_("  [Nr] Name              Type             Address           Offset\n"));
      printf (_("       Size              EntSize          Flags  Link  Info  Align\n"));
    }
    }
 
  if (do_section_details)
    printf (_("       Flags\n"));
 
  // 遍历所有节头, 打印每个节的详细信息
  for (i = 0, section = filedata->section_headers;
       i < filedata->file_header.e_shnum;
       i++, section++)
    {
 
      // 校验节头的 sh_link 字段, 关联节索引
      switch (section->sh_type)
    {
    case SHT_REL:
    case SHT_RELR:
    case SHT_RELA:
    // 动态重定位节(可执行/共享库)允许 sh_link=0
      if (section->sh_link == 0
          && (filedata->file_header.e_type == ET_EXEC
          || filedata->file_header.e_type == ET_DYN))
        break;
    case SHT_SYMTAB_SHNDX:
    case SHT_GROUP:
    case SHT_HASH:
    case SHT_GNU_HASH:
    case SHT_GNU_versym:
    // 校验: sh_link 必须是有效的符号表节索引(SHT_SYMTAB/SHT_DYNSYM)
      if (section->sh_link == 0
          || section->sh_link >= filedata->file_header.e_shnum
          || (filedata->section_headers[section->sh_link].sh_type != SHT_SYMTAB
          && filedata->section_headers[section->sh_link].sh_type != SHT_DYNSYM))
        warn (_("[%2u]: Link field (%u) should index a symtab section.\n"),
          i, section->sh_link);
      break;
 
    case SHT_DYNAMIC:
    case SHT_SYMTAB:
    case SHT_DYNSYM:
    case SHT_GNU_verneed:
    case SHT_GNU_verdef:
    case SHT_GNU_LIBLIST:
    // 校验: sh_link 必须是有效的字符串表节索引(SHT_STRTAB)
      if (section->sh_link == 0
          || section->sh_link >= filedata->file_header.e_shnum
          || filedata->section_headers[section->sh_link].sh_type != SHT_STRTAB)
        warn (_("[%2u]: Link field (%u) should index a string section.\n"),
          i, section->sh_link);
      break;
 
    case SHT_INIT_ARRAY:
    case SHT_FINI_ARRAY:
    case SHT_PREINIT_ARRAY:
    // 校验: 非 OS 特定节的 sh_link 应为 0
      if (section->sh_type < SHT_LOOS && section->sh_link != 0)
        warn (_("[%2u]: Unexpected value (%u) in link field.\n"),
          i, section->sh_link);
      break;
 
    default:
#if 0    
#endif
      break;
    }
 
      // 校验节头的 sh_info 字段
      switch (section->sh_type)
    {
    case SHT_REL:
    case SHT_RELA:
    // 动态重定位节允许 sh_info=0
      if (section->sh_info == 0
          && (filedata->file_header.e_type == ET_EXEC
          || filedata->file_header.e_type == ET_DYN))
        break;
    // // 校验: sh_info 必须是有效的可重定位节索引
      if (section->sh_info == 0
          || section->sh_info >= filedata->file_header.e_shnum
          || (filedata->section_headers[section->sh_info].sh_type != SHT_PROGBITS
          && filedata->section_headers[section->sh_info].sh_type != SHT_NOBITS
          && filedata->section_headers[section->sh_info].sh_type != SHT_NOTE
          && filedata->section_headers[section->sh_info].sh_type != SHT_INIT_ARRAY
          && filedata->section_headers[section->sh_info].sh_type != SHT_FINI_ARRAY
          && filedata->section_headers[section->sh_info].sh_type != SHT_PREINIT_ARRAY
          /* FIXME: Are other section types valid ?  */
          && filedata->section_headers[section->sh_info].sh_type < SHT_LOOS))
        warn (_("[%2u]: Info field (%u) should index a relocatable section.\n"),
          i, section->sh_info);
      break;
 
    case SHT_DYNAMIC:
    case SHT_HASH:
    case SHT_SYMTAB_SHNDX:
    case SHT_INIT_ARRAY:
    case SHT_FINI_ARRAY:
    case SHT_PREINIT_ARRAY:
    // 校验: sh_info 应为 0
      if (section->sh_info != 0)
        warn (_("[%2u]: Unexpected value (%u) in info field.\n"),
          i, section->sh_info);
      break;
 
    case SHT_GROUP:
    case SHT_SYMTAB:
    case SHT_DYNSYM:
      break;
 
    default:
      if (section->sh_type == SHT_NOBITS)
        ;
      else if (section->sh_flags & SHF_INFO_LINK)
        {
          if (section->sh_info < 1 || section->sh_info >= filedata->file_header.e_shnum)
        warn (_("[%2u]: Expected link to another section in info field"), i);
        }
      else if (section->sh_type < SHT_LOOS
           && (section->sh_flags & SHF_GNU_MBIND) == 0
           && section->sh_info != 0)
        warn (_("[%2u]: Unexpected value (%u) in info field.\n"),
          i, section->sh_info);
      break;
    }
      // 校验节大小
      if (section->sh_size > filedata->file_size
      && section->sh_type != SHT_NOBITS
      && section->sh_type != SHT_NULL
      && section->sh_type < SHT_LOOS)
    warn (_("Size of section %u is larger than the entire file!\n"), i);
 
      // 打印节头信息
      printf ("  [%2u] ", i);
      if (do_section_details)
    printf ("%s\n      ", printable_section_name (filedata, section));
      else
    print_symbol_name (-17, printable_section_name (filedata, section));
 
      // 打印节类型
      printf (do_wide ? " %-15s " : " %-15.15s ",
          get_section_type_name (filedata, section->sh_type));
 
      // 32位 ELF 显示逻辑
      if (is_32bit_elf)
    {
      const char * link_too_big = NULL;
 
    // 节的内存地址
      print_vma (section->sh_addr, LONG_HEX);
 
    // 打印文件偏移, 节大小, 表项大小
      printf ( " %6.6lx %6.6lx %2.2lx",
           (unsigned long) section->sh_offset,
           (unsigned long) section->sh_size,
           (unsigned long) section->sh_entsize);
 
      if (do_section_details)
        fputs ("  ", stdout);
      else
        printf (" %3s ", get_elf_section_flags (filedata, section->sh_flags));  // 节标志
 
      if (section->sh_link >= filedata->file_header.e_shnum)
        {
          link_too_big = "";
          switch (filedata->file_header.e_machine)
        {
        case EM_386:
        case EM_IAMCU:
        case EM_X86_64:
        case EM_L1OM:
        case EM_K1OM:
        case EM_OLD_SPARCV9:
        case EM_SPARC32PLUS:
        case EM_SPARCV9:
        case EM_SPARC:
          if (section->sh_link == (SHN_BEFORE & 0xffff))
            link_too_big = "BEFORE";
          else if (section->sh_link == (SHN_AFTER & 0xffff))
            link_too_big = "AFTER";
          break;
        default:
          break;
        }
        }
 
    // 打印链接索引, 信息字段, 对齐要求
      if (do_section_details)
        {
          if (link_too_big != NULL && * link_too_big)
        printf ("<%s> ", link_too_big);
          else
        printf ("%2u ", section->sh_link);
          printf ("%3u %2lu\n", section->sh_info,
              (unsigned long) section->sh_addralign);
        }
      else
        printf ("%2u %3u %2lu\n",
            section->sh_link,
            section->sh_info,
            (unsigned long) section->sh_addralign);
 
      if (link_too_big && ! * link_too_big)
        warn (_("section %u: sh_link value of %u is larger than the number of sections\n"),
          i, section->sh_link);
    }
      // 64位 ELF 宽显示模式
      else if (do_wide)
    {
      print_vma (section->sh_addr, LONG_HEX);
 
      if ((long) section->sh_offset == section->sh_offset)
        printf (" %6.6lx", (unsigned long) section->sh_offset);
      else
        {
          putchar (' ');
          print_vma (section->sh_offset, LONG_HEX);
        }
 
      if ((unsigned long) section->sh_size == section->sh_size)
        printf (" %6.6lx", (unsigned long) section->sh_size);
      else
        {
          putchar (' ');
          print_vma (section->sh_size, LONG_HEX);
        }
 
      if ((unsigned long) section->sh_entsize == section->sh_entsize)
        printf (" %2.2lx", (unsigned long) section->sh_entsize);
      else
        {
          putchar (' ');
          print_vma (section->sh_entsize, LONG_HEX);
        }
 
      if (do_section_details)
        fputs ("  ", stdout);
      else
        printf (" %3s ", get_elf_section_flags (filedata, section->sh_flags));
 
      printf ("%2u %3u ", section->sh_link, section->sh_info);
 
      if ((unsigned long) section->sh_addralign == section->sh_addralign)
        printf ("%2lu\n", (unsigned long) section->sh_addralign);
      else
        {
          print_vma (section->sh_addralign, DEC);
          putchar ('\n');
        }
    }
      // 64位 ELF 普通详细显示模式
      else if (do_section_details)
    {
      putchar (' ');
      print_vma (section->sh_addr, LONG_HEX);
      if ((long) section->sh_offset == section->sh_offset)
        printf ("  %16.16lx", (unsigned long) section->sh_offset);
      else
        {
          printf ("  ");
          print_vma (section->sh_offset, LONG_HEX);
        }
      printf ("  %u\n       ", section->sh_link);
      print_vma (section->sh_size, LONG_HEX);
      putchar (' ');
      print_vma (section->sh_entsize, LONG_HEX);
 
      printf ("  %-16u  %lu\n",
          section->sh_info,
          (unsigned long) section->sh_addralign);
    }
      else
    {
      putchar (' ');
      print_vma (section->sh_addr, LONG_HEX);
      if ((long) section->sh_offset == section->sh_offset)
        printf ("  %8.8lx", (unsigned long) section->sh_offset);
    // 64位 ELF 普通简洁显示模式
      else
        {
          printf ("  ");
          print_vma (section->sh_offset, LONG_HEX);
        }
      printf ("\n       ");
      print_vma (section->sh_size, LONG_HEX);
      printf ("  ");
      print_vma (section->sh_entsize, LONG_HEX);
 
      printf (" %3s ", get_elf_section_flags (filedata, section->sh_flags));
 
      printf ("     %2u   %3u     %lu\n",
          section->sh_link,
          section->sh_info,
          (unsigned long) section->sh_addralign);
    }
 
      // 若为压缩节, 详细模式下补充打印节标志和压缩信息
      if (do_section_details)
    {
      printf ("       %s\n", get_elf_section_flags (filedata, section->sh_flags));
 
    // 处理压缩节
      if ((section->sh_flags & SHF_COMPRESSED) != 0)
        {
          unsigned char buf[24];
 
          assert (sizeof (buf) >= sizeof (Elf64_External_Chdr));
          if (get_data (&buf, filedata, section->sh_offset, 1,
                sizeof (buf), _("compression header")))
        {
          Elf_Internal_Chdr chdr;
 
      // 解析压缩头部
          if (get_compression_header (&chdr, buf, sizeof (buf)) == 0)
            printf (_("       [<corrupt>]\n"));
          else
            {
              if (chdr.ch_type == ch_compress_zlib)
            printf ("       ZLIB, ");
              else if (chdr.ch_type == ch_compress_zstd)
            printf ("       ZSTD, ");
              else
            printf (_("       [<unknown>: 0x%x], "),
                chdr.ch_type);
              print_vma (chdr.ch_size, LONG_HEX);
              printf (", %lu\n", (unsigned long) chdr.ch_addralign);
            }
        }
        }
    }
    }
 
    // ... 省略一些与主逻辑无关的代码
  return true;
}
static Elf_Internal_Sym *
get_elf_symbols (Filedata *filedata,
         Elf_Internal_Shdr *section,
         uint64_t *num_syms_return)
{
  if (is_32bit_elf)
    return get_32bit_elf_symbols (filedata, section, num_syms_return);
  else
    return get_64bit_elf_symbols (filedata, section, num_syms_return);
}
static Elf_Internal_Sym *
get_elf_symbols (Filedata *filedata,
         Elf_Internal_Shdr *section,
         uint64_t *num_syms_return)
{
  if (is_32bit_elf)
    return get_32bit_elf_symbols (filedata, section, num_syms_return);
  else
    return get_64bit_elf_symbols (filedata, section, num_syms_return);
}
static Elf_Internal_Sym *
get_32bit_elf_symbols (Filedata *filedata,
               Elf_Internal_Shdr *section,
               uint64_t *num_syms_return)
{
  uint64_t number = 0;
  Elf32_External_Sym * esyms = NULL;
  Elf_External_Sym_Shndx * shndx = NULL;
  Elf_Internal_Sym * isyms = NULL;
  Elf_Internal_Sym * psym;
  unsigned int j;
  elf_section_list * entry;
 
  if (section->sh_size == 0)
    {
      if (num_syms_return != NULL)
    * num_syms_return = 0;
      return NULL;
    }
 
  // 符号表有效性校验
 
    // 项大小(sh_entsize)无效: 为 0 或大于节总大小
  if (section->sh_entsize == 0 || section->sh_entsize > section->sh_size)
    {
      error (_("Section %s has an invalid sh_entsize of %#" PRIx64 "\n"),
         printable_section_name (filedata, section),
         section->sh_entsize);
      goto exit_point;
    }
 
    // 符号表节大小超过文件总大小
  if (section->sh_size > filedata->file_size)
    {
      error (_("Section %s has an invalid sh_size of %#" PRIx64 "\n"),
         printable_section_name (filedata, section),
         section->sh_size);
      goto exit_point;
    }
 
    // 符号总数: 节总大小 / 每个符号的大小
  number = section->sh_size / section->sh_entsize;
 
    // 节大小不是项大小的整数倍, 符号表不完整, 文件损坏
  if (number * sizeof (Elf32_External_Sym) > section->sh_size + 1)
    {
      error (_("Size (%#" PRIx64 ") of section %s "
           "is not a multiple of its sh_entsize (%#" PRIx64 ")\n"),
         section->sh_size,
         printable_section_name (filedata, section),
         section->sh_entsize);
      goto exit_point;
    }
 
    // 读取原始符号表数据
  esyms = (Elf32_External_Sym *) get_data (NULL, filedata, section->sh_offset, 1,
                                           section->sh_size, _("symbols"));
  if (esyms == NULL)
    goto exit_point;
 
    // 查找并读取关联的符号节索引扩展表(.shndx)
    // 当一个符号的节索引值非常大, 会使用 SHN_XINDEX 标记, 其真实的节索引值存储在一个单独的, 与符号表平行的 .shndx 节中
  shndx = NULL;
  for (entry = filedata->symtab_shndx_list; entry != NULL; entry = entry->next)
    {
            // 找到与当前处理的符号表节相匹配的 .shndx 节, sh_link 字段指向关联的符号节索引扩展表
      if (entry->hdr->sh_link != (size_t) (section - filedata->section_headers))
    continue;
 
            // 一个符号表只能关联一个 .shndx 节, 发现多个则报错
      if (shndx != NULL)
    {
      error (_("Multiple symbol table index sections associated with the same symbol section\n"));
      free (shndx);
    }
 
            // 读取 .shndx 节的数据
      shndx = (Elf_External_Sym_Shndx *) get_data (NULL, filedata,
                           entry->hdr->sh_offset,
                           1, entry->hdr->sh_size,
                           _("symbol table section indices"));
      if (shndx == NULL)
    goto exit_point;
 
            // 校验 .shndx 节的大小是否足够容纳所有符号的扩展索引, 它的条目数量应该至少与符号表的符号数量相等
      if (entry->hdr->sh_size / sizeof (Elf_External_Sym_Shndx) < number)
    {
      error (_("Index section %s has an sh_size of %#" PRIx64 " - expected %#" PRIx64 "\n"),
         printable_section_name (filedata, entry->hdr),
         entry->hdr->sh_size,
         section->sh_size);
      goto exit_point;
    }
    }
 
    // 分配内存用于存储转换后的内部符号
  isyms = (Elf_Internal_Sym *) cmalloc (number, sizeof (Elf_Internal_Sym));
 
  if (isyms == NULL)
    {
      error (_("Out of memory reading %" PRIu64 " symbols\n"), number);
      goto exit_point;
    }
 
    // 将原始的, 字节序无关的外部符号转换为内部使用的符号格式
  for (j = 0, psym = isyms; j < number; j++, psym++)
    {
      psym->st_name  = BYTE_GET (esyms[j].st_name);
      psym->st_value = BYTE_GET (esyms[j].st_value);
      psym->st_size  = BYTE_GET (esyms[j].st_size);
      psym->st_shndx = BYTE_GET (esyms[j].st_shndx);
            // 当 st_shndx 为 SHN_XINDEX 时, 从扩展表中读取真实索引
      if (psym->st_shndx == (SHN_XINDEX & 0xffff) && shndx != NULL)
    psym->st_shndx
      = byte_get ((unsigned char *) &shndx[j], sizeof (shndx[j]));
            // SHN_LORESERVE 及以上是保留值, 需要调整偏移
      else if (psym->st_shndx >= (SHN_LORESERVE & 0xffff))
    psym->st_shndx += SHN_LORESERVE - (SHN_LORESERVE & 0xffff);
      psym->st_info  = BYTE_GET (esyms[j].st_info);
      psym->st_other = BYTE_GET (esyms[j].st_other);
    }
 
 exit_point:
  free (shndx);
  free (esyms);
 
  if (num_syms_return != NULL)
    * num_syms_return = isyms == NULL ? 0 : number;
 
  return isyms;
}
 
// 64 位逻辑相同, 结构体不一样
static Elf_Internal_Sym *
get_64bit_elf_symbols (Filedata *filedata,
               Elf_Internal_Shdr *section,
               uint64_t *num_syms_return)
{
  uint64_t number = 0;
  Elf64_External_Sym * esyms = NULL;
  Elf_External_Sym_Shndx * shndx = NULL;
  Elf_Internal_Sym * isyms = NULL;
  Elf_Internal_Sym * psym;
  unsigned int j;
  elf_section_list * entry;
 
  if (section->sh_size == 0)
    {
      if (num_syms_return != NULL)
    * num_syms_return = 0;
      return NULL;
    }
 
  if (section->sh_entsize == 0 || section->sh_entsize > section->sh_size)
    {
      error (_("Section %s has an invalid sh_entsize of %#" PRIx64 "\n"),
         printable_section_name (filedata, section),
         section->sh_entsize);
      goto exit_point;
    }
 
  if (section->sh_size > filedata->file_size)
    {
      error (_("Section %s has an invalid sh_size of %#" PRIx64 "\n"),
         printable_section_name (filedata, section),
         section->sh_size);
      goto exit_point;
    }
 
  number = section->sh_size / section->sh_entsize;
 
  if (number * sizeof (Elf64_External_Sym) > section->sh_size + 1)
    {
      error (_("Size (%#" PRIx64 ") of section %s "
           "is not a multiple of its sh_entsize (%#" PRIx64 ")\n"),
         section->sh_size,
         printable_section_name (filedata, section),
         section->sh_entsize);
      goto exit_point;
    }
 
  esyms = (Elf64_External_Sym *) get_data (NULL, filedata, section->sh_offset, 1,
                                           section->sh_size, _("symbols"));
  if (!esyms)
    goto exit_point;
 
  shndx = NULL;
  for (entry = filedata->symtab_shndx_list; entry != NULL; entry = entry->next)
    {
      if (entry->hdr->sh_link != (size_t) (section - filedata->section_headers))
    continue;
 
      if (shndx != NULL)
    {
      error (_("Multiple symbol table index sections associated with the same symbol section\n"));
      free (shndx);
    }
 
      shndx = (Elf_External_Sym_Shndx *) get_data (NULL, filedata,
                           entry->hdr->sh_offset,
                           1, entry->hdr->sh_size,
                           _("symbol table section indices"));
      if (shndx == NULL)
    goto exit_point;
 
      if (entry->hdr->sh_size / sizeof (Elf_External_Sym_Shndx) < number)
    {
      error (_("Index section %s has an sh_size of %#" PRIx64 " - expected %#" PRIx64 "\n"),
         printable_section_name (filedata, entry->hdr),
         entry->hdr->sh_size,
         section->sh_size);
      goto exit_point;
    }
    }
 
  isyms = (Elf_Internal_Sym *) cmalloc (number, sizeof (Elf_Internal_Sym));
 
  if (isyms == NULL)
    {
      error (_("Out of memory reading %" PRIu64 " symbols\n"), number);
      goto exit_point;
    }
 
  for (j = 0, psym = isyms; j < number; j++, psym++)
    {
      psym->st_name  = BYTE_GET (esyms[j].st_name);
      psym->st_info  = BYTE_GET (esyms[j].st_info);
      psym->st_other = BYTE_GET (esyms[j].st_other);
      psym->st_shndx = BYTE_GET (esyms[j].st_shndx);
 
      if (psym->st_shndx == (SHN_XINDEX & 0xffff) && shndx != NULL)
    psym->st_shndx
      = byte_get ((unsigned char *) &shndx[j], sizeof (shndx[j]));
      else if (psym->st_shndx >= (SHN_LORESERVE & 0xffff))
    psym->st_shndx += SHN_LORESERVE - (SHN_LORESERVE & 0xffff);
 
      psym->st_value = BYTE_GET (esyms[j].st_value);
      psym->st_size  = BYTE_GET (esyms[j].st_size);
    }
 
 exit_point:
  free (shndx);
  free (esyms);
 
  if (num_syms_return != NULL)
    * num_syms_return = isyms == NULL ? 0 : number;
 
  return isyms;
}
static Elf_Internal_Sym *
get_32bit_elf_symbols (Filedata *filedata,
               Elf_Internal_Shdr *section,
               uint64_t *num_syms_return)
{
  uint64_t number = 0;
  Elf32_External_Sym * esyms = NULL;
  Elf_External_Sym_Shndx * shndx = NULL;
  Elf_Internal_Sym * isyms = NULL;
  Elf_Internal_Sym * psym;
  unsigned int j;
  elf_section_list * entry;
 
  if (section->sh_size == 0)
    {
      if (num_syms_return != NULL)
    * num_syms_return = 0;
      return NULL;
    }
 
  // 符号表有效性校验
 
    // 项大小(sh_entsize)无效: 为 0 或大于节总大小
  if (section->sh_entsize == 0 || section->sh_entsize > section->sh_size)
    {
      error (_("Section %s has an invalid sh_entsize of %#" PRIx64 "\n"),
         printable_section_name (filedata, section),
         section->sh_entsize);
      goto exit_point;
    }
 
    // 符号表节大小超过文件总大小
  if (section->sh_size > filedata->file_size)
    {
      error (_("Section %s has an invalid sh_size of %#" PRIx64 "\n"),
         printable_section_name (filedata, section),
         section->sh_size);
      goto exit_point;
    }
 
    // 符号总数: 节总大小 / 每个符号的大小
  number = section->sh_size / section->sh_entsize;
 
    // 节大小不是项大小的整数倍, 符号表不完整, 文件损坏
  if (number * sizeof (Elf32_External_Sym) > section->sh_size + 1)
    {
      error (_("Size (%#" PRIx64 ") of section %s "
           "is not a multiple of its sh_entsize (%#" PRIx64 ")\n"),
         section->sh_size,
         printable_section_name (filedata, section),
         section->sh_entsize);
      goto exit_point;
    }
 
    // 读取原始符号表数据
  esyms = (Elf32_External_Sym *) get_data (NULL, filedata, section->sh_offset, 1,
                                           section->sh_size, _("symbols"));
  if (esyms == NULL)
    goto exit_point;
 
    // 查找并读取关联的符号节索引扩展表(.shndx)
    // 当一个符号的节索引值非常大, 会使用 SHN_XINDEX 标记, 其真实的节索引值存储在一个单独的, 与符号表平行的 .shndx 节中
  shndx = NULL;
  for (entry = filedata->symtab_shndx_list; entry != NULL; entry = entry->next)
    {
            // 找到与当前处理的符号表节相匹配的 .shndx 节, sh_link 字段指向关联的符号节索引扩展表
      if (entry->hdr->sh_link != (size_t) (section - filedata->section_headers))
    continue;
 
            // 一个符号表只能关联一个 .shndx 节, 发现多个则报错
      if (shndx != NULL)
    {
      error (_("Multiple symbol table index sections associated with the same symbol section\n"));
      free (shndx);
    }
 
            // 读取 .shndx 节的数据
      shndx = (Elf_External_Sym_Shndx *) get_data (NULL, filedata,
                           entry->hdr->sh_offset,
                           1, entry->hdr->sh_size,
                           _("symbol table section indices"));
      if (shndx == NULL)
    goto exit_point;
 
            // 校验 .shndx 节的大小是否足够容纳所有符号的扩展索引, 它的条目数量应该至少与符号表的符号数量相等
      if (entry->hdr->sh_size / sizeof (Elf_External_Sym_Shndx) < number)
    {
      error (_("Index section %s has an sh_size of %#" PRIx64 " - expected %#" PRIx64 "\n"),
         printable_section_name (filedata, entry->hdr),
         entry->hdr->sh_size,
         section->sh_size);
      goto exit_point;
    }
    }
 
    // 分配内存用于存储转换后的内部符号
  isyms = (Elf_Internal_Sym *) cmalloc (number, sizeof (Elf_Internal_Sym));
 
  if (isyms == NULL)
    {
      error (_("Out of memory reading %" PRIu64 " symbols\n"), number);
      goto exit_point;
    }
 
    // 将原始的, 字节序无关的外部符号转换为内部使用的符号格式
  for (j = 0, psym = isyms; j < number; j++, psym++)
    {
      psym->st_name  = BYTE_GET (esyms[j].st_name);
      psym->st_value = BYTE_GET (esyms[j].st_value);
      psym->st_size  = BYTE_GET (esyms[j].st_size);
      psym->st_shndx = BYTE_GET (esyms[j].st_shndx);
            // 当 st_shndx 为 SHN_XINDEX 时, 从扩展表中读取真实索引
      if (psym->st_shndx == (SHN_XINDEX & 0xffff) && shndx != NULL)
    psym->st_shndx
      = byte_get ((unsigned char *) &shndx[j], sizeof (shndx[j]));
            // SHN_LORESERVE 及以上是保留值, 需要调整偏移
      else if (psym->st_shndx >= (SHN_LORESERVE & 0xffff))
    psym->st_shndx += SHN_LORESERVE - (SHN_LORESERVE & 0xffff);
      psym->st_info  = BYTE_GET (esyms[j].st_info);
      psym->st_other = BYTE_GET (esyms[j].st_other);
    }
 
 exit_point:
  free (shndx);
  free (esyms);
 
  if (num_syms_return != NULL)
    * num_syms_return = isyms == NULL ? 0 : number;
 
  return isyms;
}
 
// 64 位逻辑相同, 结构体不一样
static Elf_Internal_Sym *
get_64bit_elf_symbols (Filedata *filedata,
               Elf_Internal_Shdr *section,
               uint64_t *num_syms_return)
{
  uint64_t number = 0;
  Elf64_External_Sym * esyms = NULL;
  Elf_External_Sym_Shndx * shndx = NULL;
  Elf_Internal_Sym * isyms = NULL;
  Elf_Internal_Sym * psym;
  unsigned int j;
  elf_section_list * entry;
 
  if (section->sh_size == 0)
    {
      if (num_syms_return != NULL)
    * num_syms_return = 0;
      return NULL;
    }
 
  if (section->sh_entsize == 0 || section->sh_entsize > section->sh_size)
    {
      error (_("Section %s has an invalid sh_entsize of %#" PRIx64 "\n"),
         printable_section_name (filedata, section),
         section->sh_entsize);
      goto exit_point;
    }
 
  if (section->sh_size > filedata->file_size)
    {
      error (_("Section %s has an invalid sh_size of %#" PRIx64 "\n"),
         printable_section_name (filedata, section),
         section->sh_size);
      goto exit_point;
    }
 
  number = section->sh_size / section->sh_entsize;
 
  if (number * sizeof (Elf64_External_Sym) > section->sh_size + 1)
    {
      error (_("Size (%#" PRIx64 ") of section %s "
           "is not a multiple of its sh_entsize (%#" PRIx64 ")\n"),
         section->sh_size,
         printable_section_name (filedata, section),
         section->sh_entsize);
      goto exit_point;
    }
 
  esyms = (Elf64_External_Sym *) get_data (NULL, filedata, section->sh_offset, 1,
                                           section->sh_size, _("symbols"));
  if (!esyms)
    goto exit_point;
 
  shndx = NULL;
  for (entry = filedata->symtab_shndx_list; entry != NULL; entry = entry->next)
    {
      if (entry->hdr->sh_link != (size_t) (section - filedata->section_headers))
    continue;
 
      if (shndx != NULL)
    {
      error (_("Multiple symbol table index sections associated with the same symbol section\n"));
      free (shndx);
    }
 
      shndx = (Elf_External_Sym_Shndx *) get_data (NULL, filedata,
                           entry->hdr->sh_offset,
                           1, entry->hdr->sh_size,
                           _("symbol table section indices"));
      if (shndx == NULL)
    goto exit_point;
 
      if (entry->hdr->sh_size / sizeof (Elf_External_Sym_Shndx) < number)
    {
      error (_("Index section %s has an sh_size of %#" PRIx64 " - expected %#" PRIx64 "\n"),
         printable_section_name (filedata, entry->hdr),
         entry->hdr->sh_size,
         section->sh_size);
      goto exit_point;
    }
    }
 
  isyms = (Elf_Internal_Sym *) cmalloc (number, sizeof (Elf_Internal_Sym));
 
  if (isyms == NULL)
    {
      error (_("Out of memory reading %" PRIu64 " symbols\n"), number);
      goto exit_point;
    }
 
  for (j = 0, psym = isyms; j < number; j++, psym++)
    {
      psym->st_name  = BYTE_GET (esyms[j].st_name);
      psym->st_info  = BYTE_GET (esyms[j].st_info);
      psym->st_other = BYTE_GET (esyms[j].st_other);
      psym->st_shndx = BYTE_GET (esyms[j].st_shndx);
 
      if (psym->st_shndx == (SHN_XINDEX & 0xffff) && shndx != NULL)
    psym->st_shndx
      = byte_get ((unsigned char *) &shndx[j], sizeof (shndx[j]));
      else if (psym->st_shndx >= (SHN_LORESERVE & 0xffff))
    psym->st_shndx += SHN_LORESERVE - (SHN_LORESERVE & 0xffff);
 
      psym->st_value = BYTE_GET (esyms[j].st_value);
      psym->st_size  = BYTE_GET (esyms[j].st_size);
    }
 
 exit_point:
  free (shndx);
  free (esyms);
 
  if (num_syms_return != NULL)
    * num_syms_return = isyms == NULL ? 0 : number;
 
  return isyms;
}
typedef struct {
  unsigned char st_name[4];     /* 符号名称, 字符串表中的索引 */
  unsigned char st_value[4];        /* 符号的值 */
  unsigned char st_size[4];     /* 符号大小, 函数符号为代码长度, 变量符号为占用字节数 */
  unsigned char st_info[1];     /* 符号类型与绑定属性 */
  unsigned char st_other[1];        /* 符号其他属性 */
  unsigned char st_shndx[2];        /* 关联节索引 */
} Elf32_External_Sym;
 
typedef struct {
  unsigned char st_name[4];     /* Symbol name, index in string tbl */
  unsigned char st_info[1];     /* Type and binding attributes */
  unsigned char st_other[1];        /* No defined meaning, 0 */
  unsigned char st_shndx[2];        /* Associated section index */
  unsigned char st_value[8];        /* Value of the symbol */
  unsigned char st_size[8];     /* Associated symbol size */
} Elf64_External_Sym;
typedef struct {
  unsigned char st_name[4];     /* 符号名称, 字符串表中的索引 */
  unsigned char st_value[4];        /* 符号的值 */
  unsigned char st_size[4];     /* 符号大小, 函数符号为代码长度, 变量符号为占用字节数 */
  unsigned char st_info[1];     /* 符号类型与绑定属性 */
  unsigned char st_other[1];        /* 符号其他属性 */
  unsigned char st_shndx[2];        /* 关联节索引 */
} Elf32_External_Sym;
 
typedef struct {
  unsigned char st_name[4];     /* Symbol name, index in string tbl */
  unsigned char st_info[1];     /* Type and binding attributes */
  unsigned char st_other[1];        /* No defined meaning, 0 */
  unsigned char st_shndx[2];        /* Associated section index */
  unsigned char st_value[8];        /* Value of the symbol */
  unsigned char st_size[8];     /* Associated symbol size */
} Elf64_External_Sym;
static void
process_program_headers (Filedata * filedata)
{
  Elf_Internal_Phdr * segment;
  unsigned int i;
  Elf_Internal_Phdr * previous_load = NULL;
 
    // ... 省略一些代码
 
    // 需要打印段信息, 且不需要打印 ELF 文件头, 避免重复输出
  if (do_segments && !do_header)
    {
      if (filedata->is_separate)
    printf ("\nIn linked file '%s' the ELF file type is %s\n",
        printable_string (filedata->file_name, 0),
        get_file_type (filedata));
      else
    printf (_("\nElf file type is %s\n"), get_file_type (filedata));
      printf (_("Entry point 0x%" PRIx64 "\n"),
          filedata->file_header.e_entry);                                // 打印程序入口地址
      printf (ngettext ("There is %d program header,"
            " starting at offset %" PRIu64 "\n",
            "There are %d program headers,"
            " starting at offset %" PRIu64 "\n",
            filedata->file_header.e_phnum),              // 打印程序头数量和起始偏移量
          filedata->file_header.e_phnum,
          filedata->file_header.e_phoff);
    }
 
    // 读取程序头表到 filedata->program_headers
  if (! get_program_headers (filedata))
    goto no_headers;
 
  if (do_segments)
    {
      if (filedata->file_header.e_phnum > 1)
    printf (_("\nProgram Headers:\n"));
      else
    printf (_("\nProgram Headers:\n"));
 
        // 打印表头
      if (is_32bit_elf)
    printf
      (_("  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align\n"));
      else if (do_wide)
    printf
      (_("  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align\n"));
      else
    {
      printf
        (_("  Type           Offset             VirtAddr           PhysAddr\n"));
      printf
        (_("                 FileSiz            MemSiz              Flags  Align\n"));
    }
    }
 
  uint64_t dynamic_addr = 0;
  uint64_t dynamic_size = 0;
 
    // 遍历所有程序头
  for (i = 0, segment = filedata->program_headers;
       i < filedata->file_header.e_phnum;
       i++, segment++)
    {
      if (do_segments)
    {
        // 打印段类型名称
      printf ("  %-14.14s ", get_segment_type (filedata, segment->p_type));
 
        // 32 位
      if (is_32bit_elf)
        {
          printf ("0x%6.6lx ", (unsigned long) segment->p_offset);   // 段在文件中的偏移量
          printf ("0x%8.8lx ", (unsigned long) segment->p_vaddr);        // 段的虚拟地址
          printf ("0x%8.8lx ", (unsigned long) segment->p_paddr);        // 段的物理地址
          printf ("0x%5.5lx ", (unsigned long) segment->p_filesz);   // 段在文件中的大小
          printf ("0x%5.5lx ", (unsigned long) segment->p_memsz);        // 段在内存中的大小
          printf ("%c%c%c ",                                                                                // 打印段权限标志
              (segment->p_flags & PF_R ? 'R' : ' '),
              (segment->p_flags & PF_W ? 'W' : ' '),
              (segment->p_flags & PF_X ? 'E' : ' '));
          printf ("%#lx", (unsigned long) segment->p_align);             // 段的对齐要求
        }
        // 64 位宽格式打印
      else if (do_wide)
        {
          if ((unsigned long) segment->p_offset == segment->p_offset)
        printf ("0x%6.6lx ", (unsigned long) segment->p_offset);
          else
        {
          print_vma (segment->p_offset, FULL_HEX);
          putchar (' ');
        }
 
          print_vma (segment->p_vaddr, FULL_HEX);
          putchar (' ');
          print_vma (segment->p_paddr, FULL_HEX);
          putchar (' ');
 
          if ((unsigned long) segment->p_filesz == segment->p_filesz)
        printf ("0x%6.6lx ", (unsigned long) segment->p_filesz);
          else
        {
          print_vma (segment->p_filesz, FULL_HEX);
          putchar (' ');
        }
 
          if ((unsigned long) segment->p_memsz == segment->p_memsz)
        printf ("0x%6.6lx", (unsigned long) segment->p_memsz);
                // 64 位标准格式打印
          else
        {
          print_vma (segment->p_memsz, FULL_HEX);
        }
 
          printf (" %c%c%c ",
              (segment->p_flags & PF_R ? 'R' : ' '),
              (segment->p_flags & PF_W ? 'W' : ' '),
              (segment->p_flags & PF_X ? 'E' : ' '));
 
          if ((unsigned long) segment->p_align == segment->p_align)
        printf ("%#lx", (unsigned long) segment->p_align);
          else
        {
          print_vma (segment->p_align, PREFIX_HEX);
        }
        }
      else
        {
          print_vma (segment->p_offset, FULL_HEX);
          putchar (' ');
          print_vma (segment->p_vaddr, FULL_HEX);
          putchar (' ');
          print_vma (segment->p_paddr, FULL_HEX);
          printf ("\n                 ");
          print_vma (segment->p_filesz, FULL_HEX);
          putchar (' ');
          print_vma (segment->p_memsz, FULL_HEX);
          printf ("  %c%c%c    ",
              (segment->p_flags & PF_R ? 'R' : ' '),
              (segment->p_flags & PF_W ? 'W' : ' '),
              (segment->p_flags & PF_X ? 'E' : ' '));
          print_vma (segment->p_align, PREFIX_HEX);
        }
 
      putc ('\n', stdout);
    }
            // 根据段类型(p_type)执行特定处理和合法性校验
      switch (segment->p_type)
    {
    // 可加载段: 程序运行时需要加载到内存的段
    case PT_LOAD:
#if 0 /* 不校验 PT_LOAD 段的顺序
        虽然 ELF 标准要求 PT_LOAD 段按虚拟地址递增排序, 但部分程序
        会使用非有序段, 因此禁用该校验以兼容实际场景 */
      if (previous_load
          && previous_load->p_vaddr > segment->p_vaddr)
        error (_("LOAD segments must be sorted in order of increasing VirtAddr\n"));
#endif
        // 段的文件大小不能大于内存大小
      if (segment->p_memsz < segment->p_filesz)
        error (_("the segment's file size is larger than its memory size\n"));
      previous_load = segment;
      break;
 
    // 程序头表段: 存储程序头表本身的段
    case PT_PHDR:
      if (i > 0 && previous_load != NULL)
        error (_("the PHDR segment must occur before any LOAD segment\n"));
 
        // 除 PARISC 架构外, PT_PHDR 段必须被某个 PT_LOAD 段覆盖, 以确保加载到内存
      if (filedata->file_header.e_machine != EM_PARISC)
        {
          unsigned int j;
 
          for (j = 1; j < filedata->file_header.e_phnum; j++)
        {
          Elf_Internal_Phdr *load = filedata->program_headers + j;
          if (load->p_type == PT_LOAD
              && load->p_offset <= segment->p_offset
              && (load->p_offset + load->p_filesz
              >= segment->p_offset + segment->p_filesz)
              && load->p_vaddr <= segment->p_vaddr
              && (load->p_vaddr + load->p_filesz
              >= segment->p_vaddr + segment->p_filesz))
            break;
        }
          if (j == filedata->file_header.e_phnum)
        error (_("the PHDR segment is not covered by a LOAD segment\n"));
        }
      break;
 
    // 动态段: 存储动态链接相关信息
    case PT_DYNAMIC:
        // 校验: 一个 ELF 文件只能有一个动态段
      if (dynamic_addr)
        error (_("more than one dynamic segment\n"));
 
      dynamic_addr = segment->p_offset;
      dynamic_size = segment->p_filesz;
 
        // 如果有节头表, 通过节头表精确查找 .dynamic 节
      if (filedata->section_headers != NULL)
        {
          Elf_Internal_Shdr * sec;
 
          sec = find_section (filedata, ".dynamic");
          if (sec == NULL || sec->sh_size == 0)
        {
          if (!is_ia64_vms (filedata))
            error (_("no .dynamic section in the dynamic segment\n"));
          break;
        }
                // 如果 .dynamic 节是 NOBITS 类型, 则其仅占内存, 无文件存储, 重置动态段信息
          if (sec->sh_type == SHT_NOBITS)
        {
          dynamic_addr = 0;
          dynamic_size = 0;
          break;
        }
 
          dynamic_addr = sec->sh_offset;
          dynamic_size = sec->sh_size;
 
                // 校验: 动态段应与 .dynamic 节完全一致
          if (do_checks
          && (dynamic_addr != segment->p_offset
              || dynamic_size != segment->p_filesz))
        warn (_("\
the .dynamic section is not the same as the dynamic segment\n"));
        }
 
      if (dynamic_addr > filedata->file_size
          || (dynamic_size > filedata->file_size - dynamic_addr))
        {
          error (_("the dynamic segment offset + size exceeds the size of the file\n"));
          dynamic_addr = 0;
          dynamic_size = 0;
        }
      break;
 
    // 解释器段: 存储动态链接器路径
    case PT_INTERP:
        // 校验: 解释器段的偏移量和大小是否在文件范围内, 且文件指针跳转成功
      if (segment->p_offset >= filedata->file_size
          || segment->p_filesz > filedata->file_size - segment->p_offset
          || segment->p_filesz - 1 >= (size_t) -2
          || fseek64 (filedata->handle,
              filedata->archive_file_offset + segment->p_offset,
              SEEK_SET))
        error (_("Unable to find program interpreter name\n"));
      else
        {
          size_t len = segment->p_filesz;
          free (filedata->program_interpreter);
          filedata->program_interpreter = xmalloc (len + 1);
 
                // 从文件中读取解释器路径
          len = fread (filedata->program_interpreter, 1, len,
               filedata->handle);
          filedata->program_interpreter[len] = 0;
 
                // 如果需要打印段信息, 输出解释器路径
          if (do_segments)
        printf (_("      [Requesting program interpreter: %s]\n"),
            printable_string (filedata->program_interpreter, 0));
        }
      break;
    }
    }
 
  if (do_segments
      && filedata->section_headers != NULL
      && filedata->string_table != NULL)
    {
      printf (_("\n Section to Segment mapping:\n"));
      printf (_("  Segment Sections...\n"));
 
            // 遍历每个段, 查找属于该段的所有节
      for (i = 0; i < filedata->file_header.e_phnum; i++)
    {
      unsigned int j;
      Elf_Internal_Shdr * section;
 
      segment = filedata->program_headers + i;
      section = filedata->section_headers + 1;   // 跳过第 0 个节, 通常是无效节
 
      printf ("   %2.2d     ", i);
 
        // 遍历所有节, 判断节是否属于当前段
      for (j = 1; j < filedata->file_header.e_shnum; j++, section++)
        {
          if (!ELF_TBSS_SPECIAL (section, segment)
          && ELF_SECTION_IN_SEGMENT_STRICT (section, segment))
        printf ("%s ", printable_section_name (filedata, section));
        }
 
      putc ('\n',stdout);
    }
    }
 
    // 保存动态段信息到文件数据结构
  filedata->dynamic_addr = dynamic_addr;
  filedata->dynamic_size = dynamic_size ? dynamic_size : 1;
  return;
 
 no_headers:
  filedata->dynamic_addr = 0;
  filedata->dynamic_size = 1;
}
static void
process_program_headers (Filedata * filedata)
{
  Elf_Internal_Phdr * segment;
  unsigned int i;
  Elf_Internal_Phdr * previous_load = NULL;
 
    // ... 省略一些代码
 
    // 需要打印段信息, 且不需要打印 ELF 文件头, 避免重复输出
  if (do_segments && !do_header)
    {
      if (filedata->is_separate)
    printf ("\nIn linked file '%s' the ELF file type is %s\n",
        printable_string (filedata->file_name, 0),
        get_file_type (filedata));
      else
    printf (_("\nElf file type is %s\n"), get_file_type (filedata));
      printf (_("Entry point 0x%" PRIx64 "\n"),
          filedata->file_header.e_entry);                                // 打印程序入口地址
      printf (ngettext ("There is %d program header,"
            " starting at offset %" PRIu64 "\n",
            "There are %d program headers,"
            " starting at offset %" PRIu64 "\n",
            filedata->file_header.e_phnum),              // 打印程序头数量和起始偏移量
          filedata->file_header.e_phnum,
          filedata->file_header.e_phoff);
    }
 
    // 读取程序头表到 filedata->program_headers
  if (! get_program_headers (filedata))
    goto no_headers;
 
  if (do_segments)
    {
      if (filedata->file_header.e_phnum > 1)
    printf (_("\nProgram Headers:\n"));
      else
    printf (_("\nProgram Headers:\n"));
 
        // 打印表头
      if (is_32bit_elf)
    printf
      (_("  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align\n"));
      else if (do_wide)
    printf
      (_("  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align\n"));
      else
    {
      printf
        (_("  Type           Offset             VirtAddr           PhysAddr\n"));
      printf
        (_("                 FileSiz            MemSiz              Flags  Align\n"));
    }
    }
 
  uint64_t dynamic_addr = 0;
  uint64_t dynamic_size = 0;
 
    // 遍历所有程序头
  for (i = 0, segment = filedata->program_headers;
       i < filedata->file_header.e_phnum;
       i++, segment++)
    {
      if (do_segments)
    {
        // 打印段类型名称
      printf ("  %-14.14s ", get_segment_type (filedata, segment->p_type));
 
        // 32 位
      if (is_32bit_elf)
        {
          printf ("0x%6.6lx ", (unsigned long) segment->p_offset);   // 段在文件中的偏移量
          printf ("0x%8.8lx ", (unsigned long) segment->p_vaddr);        // 段的虚拟地址
          printf ("0x%8.8lx ", (unsigned long) segment->p_paddr);        // 段的物理地址
          printf ("0x%5.5lx ", (unsigned long) segment->p_filesz);   // 段在文件中的大小
          printf ("0x%5.5lx ", (unsigned long) segment->p_memsz);        // 段在内存中的大小
          printf ("%c%c%c ",                                                                                // 打印段权限标志
              (segment->p_flags & PF_R ? 'R' : ' '),
              (segment->p_flags & PF_W ? 'W' : ' '),
              (segment->p_flags & PF_X ? 'E' : ' '));
          printf ("%#lx", (unsigned long) segment->p_align);             // 段的对齐要求
        }
        // 64 位宽格式打印
      else if (do_wide)
        {
          if ((unsigned long) segment->p_offset == segment->p_offset)
        printf ("0x%6.6lx ", (unsigned long) segment->p_offset);
          else
        {
          print_vma (segment->p_offset, FULL_HEX);
          putchar (' ');
        }
 
          print_vma (segment->p_vaddr, FULL_HEX);
          putchar (' ');
          print_vma (segment->p_paddr, FULL_HEX);
          putchar (' ');
 
          if ((unsigned long) segment->p_filesz == segment->p_filesz)
        printf ("0x%6.6lx ", (unsigned long) segment->p_filesz);
          else
        {
          print_vma (segment->p_filesz, FULL_HEX);
          putchar (' ');
        }
 
          if ((unsigned long) segment->p_memsz == segment->p_memsz)
        printf ("0x%6.6lx", (unsigned long) segment->p_memsz);
                // 64 位标准格式打印
          else
        {
          print_vma (segment->p_memsz, FULL_HEX);
        }
 
          printf (" %c%c%c ",
              (segment->p_flags & PF_R ? 'R' : ' '),
              (segment->p_flags & PF_W ? 'W' : ' '),
              (segment->p_flags & PF_X ? 'E' : ' '));
 
          if ((unsigned long) segment->p_align == segment->p_align)
        printf ("%#lx", (unsigned long) segment->p_align);
          else
        {
          print_vma (segment->p_align, PREFIX_HEX);
        }
        }
      else
        {
          print_vma (segment->p_offset, FULL_HEX);
          putchar (' ');
          print_vma (segment->p_vaddr, FULL_HEX);
          putchar (' ');
          print_vma (segment->p_paddr, FULL_HEX);
          printf ("\n                 ");
          print_vma (segment->p_filesz, FULL_HEX);
          putchar (' ');
          print_vma (segment->p_memsz, FULL_HEX);
          printf ("  %c%c%c    ",
              (segment->p_flags & PF_R ? 'R' : ' '),
              (segment->p_flags & PF_W ? 'W' : ' '),
              (segment->p_flags & PF_X ? 'E' : ' '));
          print_vma (segment->p_align, PREFIX_HEX);
        }
 
      putc ('\n', stdout);
    }
            // 根据段类型(p_type)执行特定处理和合法性校验
      switch (segment->p_type)
    {
    // 可加载段: 程序运行时需要加载到内存的段
    case PT_LOAD:
#if 0 /* 不校验 PT_LOAD 段的顺序
        虽然 ELF 标准要求 PT_LOAD 段按虚拟地址递增排序, 但部分程序
        会使用非有序段, 因此禁用该校验以兼容实际场景 */
      if (previous_load
          && previous_load->p_vaddr > segment->p_vaddr)
        error (_("LOAD segments must be sorted in order of increasing VirtAddr\n"));
#endif
        // 段的文件大小不能大于内存大小
      if (segment->p_memsz < segment->p_filesz)
        error (_("the segment's file size is larger than its memory size\n"));
      previous_load = segment;
      break;
 
    // 程序头表段: 存储程序头表本身的段
    case PT_PHDR:
      if (i > 0 && previous_load != NULL)
        error (_("the PHDR segment must occur before any LOAD segment\n"));
 
        // 除 PARISC 架构外, PT_PHDR 段必须被某个 PT_LOAD 段覆盖, 以确保加载到内存
      if (filedata->file_header.e_machine != EM_PARISC)
        {
          unsigned int j;
 
          for (j = 1; j < filedata->file_header.e_phnum; j++)
        {
          Elf_Internal_Phdr *load = filedata->program_headers + j;
          if (load->p_type == PT_LOAD
              && load->p_offset <= segment->p_offset
              && (load->p_offset + load->p_filesz
              >= segment->p_offset + segment->p_filesz)
              && load->p_vaddr <= segment->p_vaddr
              && (load->p_vaddr + load->p_filesz
              >= segment->p_vaddr + segment->p_filesz))
            break;
        }
          if (j == filedata->file_header.e_phnum)
        error (_("the PHDR segment is not covered by a LOAD segment\n"));
        }
      break;
 
    // 动态段: 存储动态链接相关信息
    case PT_DYNAMIC:
        // 校验: 一个 ELF 文件只能有一个动态段
      if (dynamic_addr)
        error (_("more than one dynamic segment\n"));
 
      dynamic_addr = segment->p_offset;
      dynamic_size = segment->p_filesz;
 
        // 如果有节头表, 通过节头表精确查找 .dynamic 节
      if (filedata->section_headers != NULL)
        {
          Elf_Internal_Shdr * sec;
 
          sec = find_section (filedata, ".dynamic");
          if (sec == NULL || sec->sh_size == 0)
        {
          if (!is_ia64_vms (filedata))
            error (_("no .dynamic section in the dynamic segment\n"));
          break;
        }
                // 如果 .dynamic 节是 NOBITS 类型, 则其仅占内存, 无文件存储, 重置动态段信息
          if (sec->sh_type == SHT_NOBITS)
        {
          dynamic_addr = 0;
          dynamic_size = 0;
          break;
        }
 
          dynamic_addr = sec->sh_offset;
          dynamic_size = sec->sh_size;
 
                // 校验: 动态段应与 .dynamic 节完全一致
          if (do_checks
          && (dynamic_addr != segment->p_offset
              || dynamic_size != segment->p_filesz))
        warn (_("\
the .dynamic section is not the same as the dynamic segment\n"));
        }
 
      if (dynamic_addr > filedata->file_size
          || (dynamic_size > filedata->file_size - dynamic_addr))
        {
          error (_("the dynamic segment offset + size exceeds the size of the file\n"));
          dynamic_addr = 0;
          dynamic_size = 0;
        }
      break;
 
    // 解释器段: 存储动态链接器路径
    case PT_INTERP:
        // 校验: 解释器段的偏移量和大小是否在文件范围内, 且文件指针跳转成功
      if (segment->p_offset >= filedata->file_size
          || segment->p_filesz > filedata->file_size - segment->p_offset
          || segment->p_filesz - 1 >= (size_t) -2
          || fseek64 (filedata->handle,
              filedata->archive_file_offset + segment->p_offset,
              SEEK_SET))
        error (_("Unable to find program interpreter name\n"));
      else
        {
          size_t len = segment->p_filesz;
          free (filedata->program_interpreter);
          filedata->program_interpreter = xmalloc (len + 1);
 
                // 从文件中读取解释器路径
          len = fread (filedata->program_interpreter, 1, len,
               filedata->handle);
          filedata->program_interpreter[len] = 0;
 
                // 如果需要打印段信息, 输出解释器路径
          if (do_segments)
        printf (_("      [Requesting program interpreter: %s]\n"),
            printable_string (filedata->program_interpreter, 0));
        }
      break;
    }
    }
 
  if (do_segments
      && filedata->section_headers != NULL
      && filedata->string_table != NULL)
    {
      printf (_("\n Section to Segment mapping:\n"));
      printf (_("  Segment Sections...\n"));
 
            // 遍历每个段, 查找属于该段的所有节
      for (i = 0; i < filedata->file_header.e_phnum; i++)
    {
      unsigned int j;
      Elf_Internal_Shdr * section;
 
      segment = filedata->program_headers + i;
      section = filedata->section_headers + 1;   // 跳过第 0 个节, 通常是无效节
 
      printf ("   %2.2d     ", i);
 
        // 遍历所有节, 判断节是否属于当前段
      for (j = 1; j < filedata->file_header.e_shnum; j++, section++)
        {
          if (!ELF_TBSS_SPECIAL (section, segment)
          && ELF_SECTION_IN_SEGMENT_STRICT (section, segment))
        printf ("%s ", printable_section_name (filedata, section));
        }
 
      putc ('\n',stdout);
    }
    }
 
    // 保存动态段信息到文件数据结构
  filedata->dynamic_addr = dynamic_addr;
  filedata->dynamic_size = dynamic_size ? dynamic_size : 1;
  return;
 
 no_headers:
  filedata->dynamic_addr = 0;
  filedata->dynamic_size = 1;
}
static bool
get_program_headers (Filedata * filedata)
{
  Elf_Internal_Phdr * phdrs;
 
  if (filedata->program_headers != NULL)
    return true;
 
  if (filedata->file_header.e_phnum
      * (is_32bit_elf ? sizeof (Elf32_External_Phdr) : sizeof (Elf64_External_Phdr))
      >= filedata->file_size)
    {
      error (_("Too many program headers - %#x - the file is not that big\n"),
         filedata->file_header.e_phnum);
      return false;
    }
 
  phdrs = (Elf_Internal_Phdr *) cmalloc (filedata->file_header.e_phnum,
                     sizeof (Elf_Internal_Phdr));
  if (phdrs == NULL)
    {
      error (_("Out of memory reading %u program headers\n"),
         filedata->file_header.e_phnum);
      return false;
    }
 
  if (is_32bit_elf
      ? get_32bit_program_headers (filedata, phdrs)
      : get_64bit_program_headers (filedata, phdrs))
    {
      filedata->program_headers = phdrs;
      return true;
    }
 
  free (phdrs);
  return false;
}
static bool
get_program_headers (Filedata * filedata)
{
  Elf_Internal_Phdr * phdrs;
 
  if (filedata->program_headers != NULL)
    return true;
 
  if (filedata->file_header.e_phnum
      * (is_32bit_elf ? sizeof (Elf32_External_Phdr) : sizeof (Elf64_External_Phdr))
      >= filedata->file_size)
    {
      error (_("Too many program headers - %#x - the file is not that big\n"),
         filedata->file_header.e_phnum);
      return false;
    }
 
  phdrs = (Elf_Internal_Phdr *) cmalloc (filedata->file_header.e_phnum,
                     sizeof (Elf_Internal_Phdr));
  if (phdrs == NULL)
    {
      error (_("Out of memory reading %u program headers\n"),
         filedata->file_header.e_phnum);
      return false;
    }
 
  if (is_32bit_elf
      ? get_32bit_program_headers (filedata, phdrs)
      : get_64bit_program_headers (filedata, phdrs))
    {
      filedata->program_headers = phdrs;
      return true;
    }
 
  free (phdrs);
  return false;
}
static bool
get_32bit_program_headers (Filedata * filedata, Elf_Internal_Phdr * pheaders)
{
  Elf32_External_Phdr * phdrs;
  Elf32_External_Phdr * external;
  Elf_Internal_Phdr *   internal;
  unsigned int i;
  unsigned int size = filedata->file_header.e_phentsize;
  unsigned int num  = filedata->file_header.e_phnum;
 
  if (size == 0 || num == 0)
    return false;
  if (size < sizeof * phdrs)
    {
      error (_("The e_phentsize field in the ELF header is less than the size of an ELF program header\n"));
      return false;
    }
  if (size > sizeof * phdrs)
    warn (_("The e_phentsize field in the ELF header is larger than the size of an ELF program header\n"));
 
    // 根据文件头记录的偏移和大小, 读取程序头表数据
  phdrs = (Elf32_External_Phdr *) get_data (NULL, filedata, filedata->file_header.e_phoff,
                                            size, num, _("program headers"));
  if (phdrs == NULL)
    return false;
 
    // 填充结构体
  for (i = 0, internal = pheaders, external = phdrs;
       i < filedata->file_header.e_phnum;
       i++, internal++, external++)
    {
      internal->p_type   = BYTE_GET (external->p_type);
      internal->p_offset = BYTE_GET (external->p_offset);
      internal->p_vaddr  = BYTE_GET (external->p_vaddr);
      internal->p_paddr  = BYTE_GET (external->p_paddr);
      internal->p_filesz = BYTE_GET (external->p_filesz);
      internal->p_memsz  = BYTE_GET (external->p_memsz);
      internal->p_flags  = BYTE_GET (external->p_flags);
      internal->p_align  = BYTE_GET (external->p_align);
    }
 
  free (phdrs);
  return true;
}
 
// 同 32 位类似
static bool
get_64bit_program_headers (Filedata * filedata, Elf_Internal_Phdr * pheaders)
{
  Elf64_External_Phdr * phdrs;
  Elf64_External_Phdr * external;
  Elf_Internal_Phdr *   internal;
  unsigned int i;
  unsigned int size = filedata->file_header.e_phentsize;
  unsigned int num  = filedata->file_header.e_phnum;
 
  if (size == 0 || num == 0)
    return false;
  if (size < sizeof * phdrs)
    {
      error (_("The e_phentsize field in the ELF header is less than the size of an ELF program header\n"));
      return false;
    }
  if (size > sizeof * phdrs)
    warn (_("The e_phentsize field in the ELF header is larger than the size of an ELF program header\n"));
 
  phdrs = (Elf64_External_Phdr *) get_data (NULL, filedata, filedata->file_header.e_phoff,
                                            size, num, _("program headers"));
  if (!phdrs)
    return false;
 
  for (i = 0, internal = pheaders, external = phdrs;
       i < filedata->file_header.e_phnum;
       i++, internal++, external++)
    {
      internal->p_type   = BYTE_GET (external->p_type);
      internal->p_flags  = BYTE_GET (external->p_flags);
      internal->p_offset = BYTE_GET (external->p_offset);
      internal->p_vaddr  = BYTE_GET (external->p_vaddr);
      internal->p_paddr  = BYTE_GET (external->p_paddr);
      internal->p_filesz = BYTE_GET (external->p_filesz);
      internal->p_memsz  = BYTE_GET (external->p_memsz);
      internal->p_align  = BYTE_GET (external->p_align);
    }
 
  free (phdrs);
  return true;
}
static bool
get_32bit_program_headers (Filedata * filedata, Elf_Internal_Phdr * pheaders)
{
  Elf32_External_Phdr * phdrs;
  Elf32_External_Phdr * external;
  Elf_Internal_Phdr *   internal;
  unsigned int i;
  unsigned int size = filedata->file_header.e_phentsize;
  unsigned int num  = filedata->file_header.e_phnum;
 
  if (size == 0 || num == 0)
    return false;
  if (size < sizeof * phdrs)
    {
      error (_("The e_phentsize field in the ELF header is less than the size of an ELF program header\n"));
      return false;
    }
  if (size > sizeof * phdrs)
    warn (_("The e_phentsize field in the ELF header is larger than the size of an ELF program header\n"));
 
    // 根据文件头记录的偏移和大小, 读取程序头表数据
  phdrs = (Elf32_External_Phdr *) get_data (NULL, filedata, filedata->file_header.e_phoff,
                                            size, num, _("program headers"));
  if (phdrs == NULL)
    return false;
 
    // 填充结构体
  for (i = 0, internal = pheaders, external = phdrs;
       i < filedata->file_header.e_phnum;
       i++, internal++, external++)
    {
      internal->p_type   = BYTE_GET (external->p_type);
      internal->p_offset = BYTE_GET (external->p_offset);
      internal->p_vaddr  = BYTE_GET (external->p_vaddr);
      internal->p_paddr  = BYTE_GET (external->p_paddr);
      internal->p_filesz = BYTE_GET (external->p_filesz);
      internal->p_memsz  = BYTE_GET (external->p_memsz);
      internal->p_flags  = BYTE_GET (external->p_flags);
      internal->p_align  = BYTE_GET (external->p_align);
    }
 
  free (phdrs);
  return true;
}
 
// 同 32 位类似
static bool
get_64bit_program_headers (Filedata * filedata, Elf_Internal_Phdr * pheaders)
{
  Elf64_External_Phdr * phdrs;
  Elf64_External_Phdr * external;
  Elf_Internal_Phdr *   internal;
  unsigned int i;
  unsigned int size = filedata->file_header.e_phentsize;
  unsigned int num  = filedata->file_header.e_phnum;
 
  if (size == 0 || num == 0)
    return false;
  if (size < sizeof * phdrs)
    {
      error (_("The e_phentsize field in the ELF header is less than the size of an ELF program header\n"));
      return false;
    }
  if (size > sizeof * phdrs)
    warn (_("The e_phentsize field in the ELF header is larger than the size of an ELF program header\n"));
 
  phdrs = (Elf64_External_Phdr *) get_data (NULL, filedata, filedata->file_header.e_phoff,
                                            size, num, _("program headers"));
  if (!phdrs)
    return false;
 
  for (i = 0, internal = pheaders, external = phdrs;
       i < filedata->file_header.e_phnum;
       i++, internal++, external++)
    {
      internal->p_type   = BYTE_GET (external->p_type);
      internal->p_flags  = BYTE_GET (external->p_flags);
      internal->p_offset = BYTE_GET (external->p_offset);
      internal->p_vaddr  = BYTE_GET (external->p_vaddr);
      internal->p_paddr  = BYTE_GET (external->p_paddr);
      internal->p_filesz = BYTE_GET (external->p_filesz);
      internal->p_memsz  = BYTE_GET (external->p_memsz);
      internal->p_align  = BYTE_GET (external->p_align);
    }
 
  free (phdrs);
  return true;
}
typedef struct {
  unsigned char p_type[4];      /* 程序段类型 */
  unsigned char p_offset[4];        /* 段在文件中的偏移量 */
  unsigned char p_vaddr[4];     /* 段的虚拟地址 */
  unsigned char p_paddr[4];     /* 段的物理地址 */
  unsigned char p_filesz[4];        /* 段在文件中的大小 */
  unsigned char p_memsz[4];     /* 段在内存中的大小 */
  unsigned char p_flags[4];     /* 段的权限标志 */
  unsigned char p_align[4];     /* 段的对齐要求 */
} Elf32_External_Phdr;
 
typedef struct {
  unsigned char p_type[4];      /* Identifies program segment type */
  unsigned char p_flags[4];     /* Segment flags */
  unsigned char p_offset[8];        /* Segment file offset */
  unsigned char p_vaddr[8];     /* Segment virtual address */
  unsigned char p_paddr[8];     /* Segment physical address */
  unsigned char p_filesz[8];        /* Segment size in file */
  unsigned char p_memsz[8];     /* Segment size in memory */
  unsigned char p_align[8];     /* Segment alignment, file & memory */
} Elf64_External_Phdr;
typedef struct {
  unsigned char p_type[4];      /* 程序段类型 */
  unsigned char p_offset[4];        /* 段在文件中的偏移量 */
  unsigned char p_vaddr[4];     /* 段的虚拟地址 */
  unsigned char p_paddr[4];     /* 段的物理地址 */
  unsigned char p_filesz[4];        /* 段在文件中的大小 */
  unsigned char p_memsz[4];     /* 段在内存中的大小 */
  unsigned char p_flags[4];     /* 段的权限标志 */
  unsigned char p_align[4];     /* 段的对齐要求 */
} Elf32_External_Phdr;
 
typedef struct {
  unsigned char p_type[4];      /* Identifies program segment type */
  unsigned char p_flags[4];     /* Segment flags */
  unsigned char p_offset[8];        /* Segment file offset */
  unsigned char p_vaddr[8];     /* Segment virtual address */
  unsigned char p_paddr[8];     /* Segment physical address */
  unsigned char p_filesz[8];        /* Segment size in file */
  unsigned char p_memsz[8];     /* Segment size in memory */
  unsigned char p_align[8];     /* Segment alignment, file & memory */
} Elf64_External_Phdr;
static int load_elf_binary(struct linux_binprm *bprm)
{
    // 动态链接器
    struct file *interpreter = NULL;
     unsigned long load_addr = 0, load_bias = 0;
    int load_addr_set = 0;
    unsigned long error;
    struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
    unsigned long elf_bss, elf_brk;
    int bss_prot = 0;
    int retval, i;
    unsigned long elf_entry;
    unsigned long interp_load_addr = 0;
    unsigned long start_code, end_code, start_data, end_data;
    unsigned long reloc_func_desc __maybe_unused = 0;
    int executable_stack = EXSTACK_DEFAULT;
 
    // 存储 ELF 文件头部和动态链接器的 ELF 文件头部
    struct {
        struct elfhdr elf_ex;                   // 主程序的 ELF 文件头部
        struct elfhdr interp_elf_ex;    // 动态链接器的 ELF 文件头部
    } *loc;
    struct arch_elf_state arch_state = INIT_ARCH_ELF_STATE;
    struct pt_regs *regs;
 
    // 配内存存储 ELF 文件头部信息
    loc = kmalloc(sizeof(*loc), GFP_KERNEL);
    if (!loc) {
        retval = -ENOMEM;
        goto out_ret;
    }
     
    // 从文件缓冲区读取 ELF 文件头部, bprm->buf 已由内核预读,
    loc->elf_ex = *((struct elfhdr *)bprm->buf);
 
    retval = -ENOEXEC;
    // ELF 文件合法性检查
 
    // 检查 ELF 魔数, 0x7f454c46
    if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
        goto out;
 
    // 检查 ELF 文件类型, 必须是可执行文件(ET_EXEC)或动态链接库(ET_DYN)
    if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)
        goto out;
 
    // 检查 ELF 架构是否与当前内核匹配
    if (!elf_check_arch(&loc->elf_ex))
        goto out;
 
    // 检查是否是 FDPIC 格式, 用于嵌入式系统的位置无关代码
    if (elf_check_fdpic(&loc->elf_ex))
        goto out;
 
    // 检查文件系统是否支持内存映射, ELF 必须通过 mmap 加载
    if (!bprm->file->f_op->mmap)
        goto out;
 
    // 加载 ELF 程序头表
    elf_phdata = load_elf_phdrs(&loc->elf_ex, bprm->file);
    if (!elf_phdata)
        goto out;
 
    elf_ppnt = elf_phdata;
    // 遍历程序头表, 查找动态链接器
    for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
        char *elf_interpreter;
        loff_t pos;
 
        if (elf_ppnt->p_type != PT_INTERP)
            continue;
 
        // 检测动态链接器路径长度
        retval = -ENOEXEC;
        if (elf_ppnt->p_filesz > PATH_MAX || elf_ppnt->p_filesz < 2)
            goto out_free_ph;
 
        // 申请内存用来存储动态链接器路径
        retval = -ENOMEM;
        elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);
        if (!elf_interpreter)
            goto out_free_ph;
 
        // 根据对应程序头表的文件偏移读取动态链接器路径
        pos = elf_ppnt->p_offset;
        retval = kernel_read(bprm->file, elf_interpreter,
                     elf_ppnt->p_filesz, &pos);
        if (retval != elf_ppnt->p_filesz) {
            if (retval >= 0)
                retval = -EIO;
            goto out_free_interp;
        }
 
        // 确保路径是以 '\0' 结尾
        retval = -ENOEXEC;
        if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')
            goto out_free_interp;
 
        // 打开动态链接器文件, open_exec 专门用于打开可执行文件
        interpreter = open_exec(elf_interpreter);
        kfree(elf_interpreter);
 
        // 检查打开结果
        retval = PTR_ERR(interpreter);
        if (IS_ERR(interpreter))
            goto out_free_ph;
 
        // 如果 interpreter 文件是不可读的, 强制设置 bprm->mm->dumpable = 0
        would_dump(bprm, interpreter);
 
        // 读取动态链接器的 ELF 文件头部
        pos = 0;
        retval = kernel_read(interpreter, &loc->interp_elf_ex,
                     sizeof(loc->interp_elf_ex), &pos);
        if (retval != sizeof(loc->interp_elf_ex)) {
            if (retval >= 0)
                retval = -EIO;
            goto out_free_dentry;
        }
 
        break;
 
out_free_interp:
        kfree(elf_interpreter);
        goto out_free_ph;
    }
 
    // 处理特殊程序头部
    elf_ppnt = elf_phdata;
    for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++)
        switch (elf_ppnt->p_type) {
        case PT_GNU_STACK:
            if (elf_ppnt->p_flags & PF_X)
                executable_stack = EXSTACK_ENABLE_X;
            else
                executable_stack = EXSTACK_DISABLE_X;
            break;
 
        case PT_LOPROC ... PT_HIPROC:
            retval = arch_elf_pt_proc(&loc->elf_ex, elf_ppnt,
                          bprm->file, false,
                          &arch_state);
            if (retval)
                goto out_free_dentry;
            break;
        }
 
    // 验证动态链接器合法性并加载其程序头部
    if (interpreter) {
        retval = -ELIBBAD;
        // 检查魔数
        if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
            goto out_free_dentry;
        // 检查动态链接器的架构
        if (!elf_check_arch(&loc->interp_elf_ex) ||
            elf_check_fdpic(&loc->interp_elf_ex))
            goto out_free_dentry;
 
        // 加载动态链接器的程序头部
        interp_elf_phdata = load_elf_phdrs(&loc->interp_elf_ex,
                           interpreter);
        if (!interp_elf_phdata)
            goto out_free_dentry;
 
        // 处理动态链接器特殊程序头部
        elf_ppnt = interp_elf_phdata;
        for (i = 0; i < loc->interp_elf_ex.e_phnum; i++, elf_ppnt++)
            switch (elf_ppnt->p_type) {
            case PT_LOPROC ... PT_HIPROC:
                retval = arch_elf_pt_proc(&loc->interp_elf_ex,
                              elf_ppnt, interpreter,
                              true, &arch_state);
                if (retval)
                    goto out_free_dentry;
                break;
            }
    }
 
    // 架构特定的 ELF 验证
    retval = arch_check_elf(&loc->elf_ex,
                !!interpreter, &loc->interp_elf_ex,
                &arch_state);
    if (retval)
        goto out_free_dentry;
 
    // 清除当前进程的旧执行上下文
    retval = flush_old_exec(bprm);
    if (retval)
        goto out_free_dentry;
 
    // 设置进程的 personality, 用于兼容不同系统的行为, 如 SVr4, BSD
    SET_PERSONALITY2(loc->elf_ex, &arch_state);
    if (elf_read_implies_exec(loc->elf_ex, executable_stack))
        current->personality |= READ_IMPLIES_EXEC;
 
    // 启用地址空间随机化
    if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
        current->flags |= PF_RANDOMIZE;
 
    // 初始化新执行上下文
    setup_new_exec(bprm);
    install_exec_creds(bprm);
 
    // 初始化参数和环境变量页面
    retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
                 executable_stack);
    if (retval < 0)
        goto out_free_dentry;
     
    elf_bss = 0;
    elf_brk = 0;
 
    start_code = ~0UL;
    end_code = 0;
    start_data = 0;
    end_data = 0;
 
    // 加载 ELF 程序段到内存, 重点部分
    for(i = 0, elf_ppnt = elf_phdata;
        i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
        int elf_prot, elf_flags;            // 内存权限, mmap 标志
        unsigned long k, vaddr;            
        unsigned long total_size = 0;
 
        // 只处理 PT_LOAD 类型的程序段
        if (elf_ppnt->p_type != PT_LOAD)
            continue;
 
        // 如果 elf_brk > elf_bss, 说明上一个 PT_LOAD 段包含未初始化数据, 即 BSS, 清理 BSS 数据, 并确保内存权限正确
        if (unlikely (elf_brk > elf_bss)) {
            unsigned long nbyte;
         
            retval = set_brk(elf_bss + load_bias,
                     elf_brk + load_bias,
                     bss_prot);
            if (retval)
                goto out_free_dentry;
 
            nbyte = ELF_PAGEOFFSET(elf_bss);
            if (nbyte) {
                nbyte = ELF_MIN_ALIGN - nbyte;
                if (nbyte > elf_brk - elf_bss)
                    nbyte = elf_brk - elf_bss;
                if (clear_user((void __user *)elf_bss +
                            load_bias, nbyte)) {
                }
            }
        }
 
        // 从段的 p_flags 得到内存保护权限
        elf_prot = make_prot(elf_ppnt->p_flags);
 
        // 设置 mmap 标志, 私有映射, 禁止写入, 可执行
        elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;
 
        // 段虚拟地址
        vaddr = elf_ppnt->p_vaddr;
 
        if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
            // 静态链接或已确定加载地址, 使用固定地址映射
            elf_flags |= MAP_FIXED;
        } else if (loc->elf_ex.e_type == ET_DYN) {
            // 动态链接的 ELF, 计算加载偏移
 
            if (interpreter) {
                // 设置默认起始地址
                load_bias = ELF_ET_DYN_BASE;
                if (current->flags & PF_RANDOMIZE)
                    // 随机基址
                    load_bias += arch_mmap_rnd();
                elf_flags |= MAP_FIXED;
            } else
                // 无动态链接器, 使用随机地址映射
                load_bias = 0;
 
            // 进行页边界对齐
            load_bias = ELF_PAGESTART(load_bias - vaddr);
 
            // 计算 ELF 所有 PT_LOAD 段的总映射大小
            total_size = total_mapping_size(elf_phdata,
                            loc->elf_ex.e_phnum);
            if (!total_size) {
                retval = -EINVAL;
                goto out_free_dentry;
            }
        }
 
        // 将程序段映射到内存
        error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
                elf_prot, elf_flags, total_size);
        if (BAD_ADDR(error)) {
            retval = IS_ERR((void *)error) ?
                PTR_ERR((void*)error) : -EINVAL;
            goto out_free_dentry;
        }
 
        // 如果是第一个 PT_LOAD 段, 确定加载基地址
        if (!load_addr_set) {
            load_addr_set = 1;
            // load_addr = p_vaddr - p_offset 就是段起始虚拟地址
            load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);
            if (loc->elf_ex.e_type == ET_DYN) {
                // 对于动态链接的 ELF 还要考虑前面的 load_bias 与 mmap 的实际返回地址, 更新加载基地址
                load_bias += error -
                             ELF_PAGESTART(load_bias + vaddr);
                load_addr += load_bias;
                reloc_func_desc = load_bias;
            }
        }
        k = elf_ppnt->p_vaddr;
        // 代码段起始地址, 取最小值
        if (k < start_code)
            start_code = k;
        // 数据段起始地址, 数据段通常在代码段之后, 取最大值
        if (start_data < k)
            start_data = k;
 
        // 检查地址是否合理
        if (BAD_ADDR(k) || elf_ppnt->p_filesz > elf_ppnt->p_memsz ||
            elf_ppnt->p_memsz > TASK_SIZE ||
            TASK_SIZE - elf_ppnt->p_memsz < k) {
            retval = -EINVAL;
            goto out_free_dentry;
        }
 
        k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;
 
        // 更新 BSS 段起始地址
        if (k > elf_bss)
            elf_bss = k;
        // 更新代码段结束地址
        if ((elf_ppnt->p_flags & PF_X) && end_code < k)
            end_code = k;
        // 更新数据段结束地址
        if (end_data < k)
            end_data = k;
        k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;
        // 更新 BSS 段结束地址
        if (k > elf_brk) {
            bss_prot = elf_prot;
            elf_brk = k;
        }
    }
 
    // 调整所有地址到内存中的实际地址, 即加上偏移
    loc->elf_ex.e_entry += load_bias;
    elf_bss += load_bias;
    elf_brk += load_bias;
    start_code += load_bias;
    end_code += load_bias;
    start_data += load_bias;
    end_data += load_bias;
 
    // 映射 BSS 段的剩余部分
    retval = set_brk(elf_bss, elf_brk, bss_prot);
    if (retval)
        goto out_free_dentry;
    if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) {
        retval = -EFAULT;
        goto out_free_dentry;
    }
 
    // 如果存在动态链接器, 加载并初始化它
    if (interpreter) {
        // 动态链接器的映射地址
        unsigned long interp_map_addr = 0;
 
        // 加载动态链接器并返回其重定位偏移
        elf_entry = load_elf_interp(&loc->interp_elf_ex,
                        interpreter,
                        &interp_map_addr,
                        load_bias, interp_elf_phdata);
        if (!IS_ERR((void *)elf_entry)) {
            // 动态链接器的加载地址
            interp_load_addr = elf_entry;
            // 动态链接器的入口地址
            elf_entry += loc->interp_elf_ex.e_entry;
        }
        if (BAD_ADDR(elf_entry)) {
            retval = IS_ERR((void *)elf_entry) ?
                    (int)elf_entry : -EINVAL;
            goto out_free_dentry;
        }
        reloc_func_desc = interp_load_addr;
 
        allow_write_access(interpreter);
        fput(interpreter);
    } else {
        // 静态链接程序, 口地址就是主程序的入口
        elf_entry = loc->elf_ex.e_entry;
        if (BAD_ADDR(elf_entry)) {
            retval = -EINVAL;
            goto out_free_dentry;
        }
    }
 
    kfree(interp_elf_phdata);
    kfree(elf_phdata);
 
    set_binfmt(&elf_format);
 
#ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES
    retval = arch_setup_additional_pages(bprm, !!interpreter);
    if (retval < 0)
        goto out;
#endif
 
    // 创建 ELF 辅助向量(auxv)并传递给用户空间(如 AT_PHDR, AT_PHENT 等)
    retval = create_elf_tables(bprm, &loc->elf_ex,
              load_addr, interp_load_addr);
    if (retval < 0)
        goto out;
 
    // 初始化进程地址空间的代码段, 数据段信息
    current->mm->end_code = end_code;
    current->mm->start_code = start_code;
    current->mm->start_data = start_data;
    current->mm->end_data = end_data;
    current->mm->start_stack = bprm->p;
 
    // 如果配置了 CONFIG_RANDOMIZE_BRK, 随机化 brk 地址
    if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
 
        // 对于动态链接器本身, 将 brk 移到 ELF_ET_DYN_BASE 附近, 避免与主程序冲突
        if (IS_ENABLED(CONFIG_ARCH_HAS_ELF_RANDOMIZE) &&
            loc->elf_ex.e_type == ET_DYN && !interpreter)
            current->mm->brk = current->mm->start_brk =
                ELF_ET_DYN_BASE;
 
        current->mm->brk = current->mm->start_brk =
            arch_randomize_brk(current->mm);
#ifdef compat_brk_randomized
        current->brk_randomized = 1;
#endif
    }
 
    if (current->personality & MMAP_PAGE_ZERO) {
        error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,
                MAP_FIXED | MAP_PRIVATE, 0);
    }
 
    regs = current_pt_regs();
#ifdef ELF_PLAT_INIT
    ELF_PLAT_INIT(regs, reloc_func_desc);
#endif
 
    // 将控制权移交给程序入口点
    finalize_exec(bprm);
    start_thread(regs, elf_entry, bprm->p);
    retval = 0;
out:
    kfree(loc);
out_ret:
    return retval;
 
out_free_dentry:
    kfree(interp_elf_phdata);
    allow_write_access(interpreter);
    if (interpreter)
        fput(interpreter);
out_free_ph:
    kfree(elf_phdata);
    goto out;
}
static int load_elf_binary(struct linux_binprm *bprm)
{
    // 动态链接器
    struct file *interpreter = NULL;
     unsigned long load_addr = 0, load_bias = 0;
    int load_addr_set = 0;
    unsigned long error;
    struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
    unsigned long elf_bss, elf_brk;
    int bss_prot = 0;
    int retval, i;
    unsigned long elf_entry;
    unsigned long interp_load_addr = 0;
    unsigned long start_code, end_code, start_data, end_data;
    unsigned long reloc_func_desc __maybe_unused = 0;
    int executable_stack = EXSTACK_DEFAULT;
 
    // 存储 ELF 文件头部和动态链接器的 ELF 文件头部
    struct {
        struct elfhdr elf_ex;                   // 主程序的 ELF 文件头部
        struct elfhdr interp_elf_ex;    // 动态链接器的 ELF 文件头部
    } *loc;
    struct arch_elf_state arch_state = INIT_ARCH_ELF_STATE;
    struct pt_regs *regs;
 
    // 配内存存储 ELF 文件头部信息
    loc = kmalloc(sizeof(*loc), GFP_KERNEL);
    if (!loc) {
        retval = -ENOMEM;
        goto out_ret;
    }
     
    // 从文件缓冲区读取 ELF 文件头部, bprm->buf 已由内核预读,
    loc->elf_ex = *((struct elfhdr *)bprm->buf);
 
    retval = -ENOEXEC;
    // ELF 文件合法性检查
 
    // 检查 ELF 魔数, 0x7f454c46
    if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
        goto out;
 
    // 检查 ELF 文件类型, 必须是可执行文件(ET_EXEC)或动态链接库(ET_DYN)
    if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)
        goto out;
 
    // 检查 ELF 架构是否与当前内核匹配
    if (!elf_check_arch(&loc->elf_ex))
        goto out;
 
    // 检查是否是 FDPIC 格式, 用于嵌入式系统的位置无关代码
    if (elf_check_fdpic(&loc->elf_ex))
        goto out;
 
    // 检查文件系统是否支持内存映射, ELF 必须通过 mmap 加载
    if (!bprm->file->f_op->mmap)
        goto out;
 
    // 加载 ELF 程序头表
    elf_phdata = load_elf_phdrs(&loc->elf_ex, bprm->file);
    if (!elf_phdata)
        goto out;
 
    elf_ppnt = elf_phdata;
    // 遍历程序头表, 查找动态链接器
    for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
        char *elf_interpreter;
        loff_t pos;
 
        if (elf_ppnt->p_type != PT_INTERP)
            continue;
 
        // 检测动态链接器路径长度
        retval = -ENOEXEC;
        if (elf_ppnt->p_filesz > PATH_MAX || elf_ppnt->p_filesz < 2)
            goto out_free_ph;
 
        // 申请内存用来存储动态链接器路径
        retval = -ENOMEM;
        elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);
        if (!elf_interpreter)
            goto out_free_ph;
 
        // 根据对应程序头表的文件偏移读取动态链接器路径
        pos = elf_ppnt->p_offset;
        retval = kernel_read(bprm->file, elf_interpreter,
                     elf_ppnt->p_filesz, &pos);
        if (retval != elf_ppnt->p_filesz) {
            if (retval >= 0)
                retval = -EIO;
            goto out_free_interp;
        }
 
        // 确保路径是以 '\0' 结尾
        retval = -ENOEXEC;
        if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')
            goto out_free_interp;
 
        // 打开动态链接器文件, open_exec 专门用于打开可执行文件
        interpreter = open_exec(elf_interpreter);
        kfree(elf_interpreter);
 
        // 检查打开结果
        retval = PTR_ERR(interpreter);
        if (IS_ERR(interpreter))
            goto out_free_ph;
 
        // 如果 interpreter 文件是不可读的, 强制设置 bprm->mm->dumpable = 0
        would_dump(bprm, interpreter);
 
        // 读取动态链接器的 ELF 文件头部
        pos = 0;
        retval = kernel_read(interpreter, &loc->interp_elf_ex,
                     sizeof(loc->interp_elf_ex), &pos);
        if (retval != sizeof(loc->interp_elf_ex)) {
            if (retval >= 0)
                retval = -EIO;
            goto out_free_dentry;
        }
 
        break;
 
out_free_interp:
        kfree(elf_interpreter);
        goto out_free_ph;
    }
 
    // 处理特殊程序头部
    elf_ppnt = elf_phdata;
    for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++)
        switch (elf_ppnt->p_type) {
        case PT_GNU_STACK:
            if (elf_ppnt->p_flags & PF_X)
                executable_stack = EXSTACK_ENABLE_X;
            else
                executable_stack = EXSTACK_DISABLE_X;
            break;
 
        case PT_LOPROC ... PT_HIPROC:
            retval = arch_elf_pt_proc(&loc->elf_ex, elf_ppnt,
                          bprm->file, false,
                          &arch_state);
            if (retval)
                goto out_free_dentry;
            break;
        }
 
    // 验证动态链接器合法性并加载其程序头部
    if (interpreter) {
        retval = -ELIBBAD;
        // 检查魔数
        if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
            goto out_free_dentry;
        // 检查动态链接器的架构
        if (!elf_check_arch(&loc->interp_elf_ex) ||
            elf_check_fdpic(&loc->interp_elf_ex))
            goto out_free_dentry;
 
        // 加载动态链接器的程序头部
        interp_elf_phdata = load_elf_phdrs(&loc->interp_elf_ex,
                           interpreter);
        if (!interp_elf_phdata)
            goto out_free_dentry;
 
        // 处理动态链接器特殊程序头部
        elf_ppnt = interp_elf_phdata;
        for (i = 0; i < loc->interp_elf_ex.e_phnum; i++, elf_ppnt++)
            switch (elf_ppnt->p_type) {
            case PT_LOPROC ... PT_HIPROC:
                retval = arch_elf_pt_proc(&loc->interp_elf_ex,
                              elf_ppnt, interpreter,
                              true, &arch_state);
                if (retval)
                    goto out_free_dentry;
                break;
            }
    }
 
    // 架构特定的 ELF 验证
    retval = arch_check_elf(&loc->elf_ex,
                !!interpreter, &loc->interp_elf_ex,
                &arch_state);
    if (retval)
        goto out_free_dentry;
 
    // 清除当前进程的旧执行上下文
    retval = flush_old_exec(bprm);
    if (retval)
        goto out_free_dentry;
 
    // 设置进程的 personality, 用于兼容不同系统的行为, 如 SVr4, BSD
    SET_PERSONALITY2(loc->elf_ex, &arch_state);
    if (elf_read_implies_exec(loc->elf_ex, executable_stack))
        current->personality |= READ_IMPLIES_EXEC;
 
    // 启用地址空间随机化
    if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
        current->flags |= PF_RANDOMIZE;
 
    // 初始化新执行上下文
    setup_new_exec(bprm);
    install_exec_creds(bprm);
 
    // 初始化参数和环境变量页面
    retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
                 executable_stack);
    if (retval < 0)
        goto out_free_dentry;
     
    elf_bss = 0;
    elf_brk = 0;
 
    start_code = ~0UL;
    end_code = 0;
    start_data = 0;
    end_data = 0;
 
    // 加载 ELF 程序段到内存, 重点部分
    for(i = 0, elf_ppnt = elf_phdata;
        i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
        int elf_prot, elf_flags;            // 内存权限, mmap 标志
        unsigned long k, vaddr;            
        unsigned long total_size = 0;
 
        // 只处理 PT_LOAD 类型的程序段
        if (elf_ppnt->p_type != PT_LOAD)
            continue;
 
        // 如果 elf_brk > elf_bss, 说明上一个 PT_LOAD 段包含未初始化数据, 即 BSS, 清理 BSS 数据, 并确保内存权限正确
        if (unlikely (elf_brk > elf_bss)) {
            unsigned long nbyte;
         
            retval = set_brk(elf_bss + load_bias,
                     elf_brk + load_bias,
                     bss_prot);
            if (retval)
                goto out_free_dentry;
 
            nbyte = ELF_PAGEOFFSET(elf_bss);
            if (nbyte) {
                nbyte = ELF_MIN_ALIGN - nbyte;
                if (nbyte > elf_brk - elf_bss)
                    nbyte = elf_brk - elf_bss;
                if (clear_user((void __user *)elf_bss +
                            load_bias, nbyte)) {
                }
            }
        }
 
        // 从段的 p_flags 得到内存保护权限
        elf_prot = make_prot(elf_ppnt->p_flags);
 
        // 设置 mmap 标志, 私有映射, 禁止写入, 可执行
        elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;
 
        // 段虚拟地址
        vaddr = elf_ppnt->p_vaddr;
 
        if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
            // 静态链接或已确定加载地址, 使用固定地址映射
            elf_flags |= MAP_FIXED;
        } else if (loc->elf_ex.e_type == ET_DYN) {
            // 动态链接的 ELF, 计算加载偏移
 
            if (interpreter) {
                // 设置默认起始地址
                load_bias = ELF_ET_DYN_BASE;
                if (current->flags & PF_RANDOMIZE)
                    // 随机基址
                    load_bias += arch_mmap_rnd();
                elf_flags |= MAP_FIXED;
            } else
                // 无动态链接器, 使用随机地址映射
                load_bias = 0;
 
            // 进行页边界对齐
            load_bias = ELF_PAGESTART(load_bias - vaddr);
 
            // 计算 ELF 所有 PT_LOAD 段的总映射大小
            total_size = total_mapping_size(elf_phdata,
                            loc->elf_ex.e_phnum);
            if (!total_size) {
                retval = -EINVAL;
                goto out_free_dentry;
            }
        }
 
        // 将程序段映射到内存
        error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
                elf_prot, elf_flags, total_size);
        if (BAD_ADDR(error)) {
            retval = IS_ERR((void *)error) ?
                PTR_ERR((void*)error) : -EINVAL;
            goto out_free_dentry;
        }
 
        // 如果是第一个 PT_LOAD 段, 确定加载基地址
        if (!load_addr_set) {
            load_addr_set = 1;
            // load_addr = p_vaddr - p_offset 就是段起始虚拟地址
            load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);
            if (loc->elf_ex.e_type == ET_DYN) {
                // 对于动态链接的 ELF 还要考虑前面的 load_bias 与 mmap 的实际返回地址, 更新加载基地址
                load_bias += error -
                             ELF_PAGESTART(load_bias + vaddr);
                load_addr += load_bias;
                reloc_func_desc = load_bias;
            }
        }
        k = elf_ppnt->p_vaddr;
        // 代码段起始地址, 取最小值
        if (k < start_code)
            start_code = k;
        // 数据段起始地址, 数据段通常在代码段之后, 取最大值
        if (start_data < k)
            start_data = k;
 
        // 检查地址是否合理
        if (BAD_ADDR(k) || elf_ppnt->p_filesz > elf_ppnt->p_memsz ||
            elf_ppnt->p_memsz > TASK_SIZE ||
            TASK_SIZE - elf_ppnt->p_memsz < k) {
            retval = -EINVAL;
            goto out_free_dentry;
        }
 
        k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;
 
        // 更新 BSS 段起始地址
        if (k > elf_bss)
            elf_bss = k;
        // 更新代码段结束地址
        if ((elf_ppnt->p_flags & PF_X) && end_code < k)
            end_code = k;
        // 更新数据段结束地址
        if (end_data < k)
            end_data = k;
        k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;
        // 更新 BSS 段结束地址

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2025-12-2 18:34 被nothing233编辑 ,原因: 增加流程图
收藏
免费 71
支持
分享
最新回复 (41)
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2025-12-2 14:00
0
雪    币: 1495
活跃值: (3698)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
111
2025-12-2 14:04
0
雪    币: 2365
活跃值: (2926)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
大佬太牛了
2025-12-2 14:05
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
大佬太强了
2025-12-2 14:39
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
111
2025-12-2 14:40
0
雪    币: 3255
活跃值: (3253)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
7
不错,感谢楼主的精彩文章。
2025-12-2 14:53
1
雪    币: 227
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
6666
2025-12-2 16:00
0
雪    币: 105
活跃值: (2272)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
666666
2025-12-2 16:05
0
雪    币: 3673
活跃值: (5807)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
感谢分享
2025-12-2 16:11
0
雪    币: 3781
活跃值: (4075)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
6
2025-12-2 16:27
0
雪    币: 43
活跃值: (2579)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12

这个讨论对我很有帮助,谢谢!
2025-12-2 16:50
0
雪    币: 2308
活跃值: (1597)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
13
金罡 不错,感谢楼主的精彩文章。
诚惶诚恐
2025-12-2 17:47
0
雪    币: 3262
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
学习
2025-12-2 19:10
0
雪    币: 263
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
学习
2025-12-2 21:12
0
雪    币: 368
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
学习学习
2025-12-2 21:28
0
雪    币: 442
活跃值: (90)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
学习了????
2025-12-2 22:27
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
666
2025-12-3 09:52
0
雪    币: 104
活跃值: (7159)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
TQL
2025-12-3 14:23
0
雪    币: 1929
活跃值: (2080)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
学习一下
2025-12-3 14:31
0
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21
11
2025-12-3 17:39
0
雪    币: 23
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22
6666
2025-12-4 09:16
0
雪    币: 5627
活跃值: (9427)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
很ok啊
2025-12-4 09:31
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24
111
2025-12-6 20:45
0
雪    币: 102
活跃值: (3305)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
25
文章非常nice,写得非常细。感谢楼主
2025-12-6 21:01
0
游客
登录 | 注册 方可回帖
返回