ELF文件信息初步探索

准备开个专栏,记录《从零开始实现链接器》的学习过程,先占个坑。

之前一直想把自己的学习过程记录在个人博客网站上,但这个要自己维护,上传图片什么的比较麻烦。关键是没有人互动,自己也没有怎么去看,慢慢的就遗忘了。在优快云上有人点赞的时候我会看一下对应的博客,还能复习一下,索性后面的内容全部记录在优快云上了。
学的过程中有一些零零散散的东西要记录,但可能没时间整理,索性先记一些关键词在这里,有空的时候在扩充和整理。所以我的博客可能经常不完整,像一个草稿,随记随保存。


参考链接:
静态链接和加载;最小动态加载器[南京大学(蒋炎岩)]
深入理解计算机系统——链接
从零开始实现链接器——第二课
https://en.wikipedia.org/wiki/GNU_Binutils

了解elf文件中的调试信息

有时候编译好程序调试的时候,虽然设置了断点,但程序并没有在断点的位置停下来。这可能是由于编译时没有创建调试信息。当使用g++编译程序时,通过-g参数可生成调试信息:

$ g++ -g elfinfo.cpp -o elfinfo
$ file elfinfo
elfinfo: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c814baea7a3040d5463c6a203dfb82c8a7f235b1, for GNU/Linux 3.2.0, with debug_info, not stripped
$ readelf -S elfinfo | grep debug
  [29] .debug_aranges    PROGBITS         0000000000000000  00007043
  [30] .debug_info       PROGBITS         0000000000000000  000075d3
  [31] .debug_abbrev     PROGBITS         0000000000000000  00010cf5
  [32] .debug_line       PROGBITS         0000000000000000  000118bb
  [33] .debug_str        PROGBITS         0000000000000000  00012a7f
  [34] .debug_line_str   PROGBITS         0000000000000000  0001cb79
  [35] .debug_rnglists   PROGBITS         0000000000000000  0001cfa1

上面的命令先带-g去编译程序,通过file命令看到最后的信息为with debug_info, not stripped,说明时存在调试信息的,这类文件可以被调试。如果用readelf -S elfinfo查看Section Header Table里的信息,会发现存在一些debug段,调试器通过这些段来完成调试。如果不带-g进行编译,则没有这些段,因此不能调试。

ELF符号信息及其读取

这部分内容通过debug代码来了解符号的读取过程会更好理解。在github上有相关的项目:https://github.com/finixbit/elf-parser

有时候为了进一步缩小文件尺寸,还会使用strip命令删除文件中不重要的段,在strip后,通过file命令输出的信息中会显示stripped。当对比strip文件和未被strip的文件时,会发现strip后的文件少了两个段.symtab和.strtab。同时nm命令的输出结果为空,如下:

$ g++ elfinfo.cpp  -o elfinfo
$ nm -a ./elfinfo | wc
    186     507   11412
$ strip elfinfo
$ nm -a ./elfinfo | wc
nm: ./elfinfo: no symbols
      0       0       0

实际上符号有静态和动态两类,在strip后,只删除了静态符号(对应.symstr和.strtab),动态符号(对应.dynsym和.dynstr)依然保留,他们可以通过nm -D ./your_elf_file_path来查看。

在elf文件中,符号信息保存在.symstr.dynsym段中,这两个段其实就是两个数组,数组中的每个元素代表一条符号信息,每元素的长度是一样的。 但我们在用readelf工具去显示其中的符号时,会发现每个符号的字符串长度各不相同,有长有短,那么字符串在这两个段中是如何保存的呢?

这就需要用到.strtab.dynstr两个段了。.synstrdynsym本身并不存储符号的字符串信息,而是将其中涉及的字符串分别保存到.strtab.dynstr两个段。

下面是readelf命令从ELF文件中读取静态符号的过程:
在这里插入图片描述

nm命令从符号表.strtab .symtab中读取符号信息。通过readelf -x .strtab your_elf_file_path可以查看elf文件.strtab段的二进制内容:

$ readelf -x .strtab ./examples/sections | less
Hex dump of section '.strtab':
  0x00000000 00637274 73747566 662e6300 64657265 .crtstuff.c.dere
  0x00000010 67697374 65725f74 6d5f636c 6f6e6573 gister_tm_clones
  0x00000020 005f5f64 6f5f676c 6f62616c 5f64746f .__do_global_dto
  0x00000030 72735f61 75780063 6f6d706c 65746564 rs_aux.completed
  0x00000040 2e383036 31005f5f 646f5f67 6c6f6261 .8061.__do_globa
  0x00000050 6c5f6474 6f72735f 6175785f 66696e69 l_dtors_aux_fini
  0x00000060 5f617272 61795f65 6e747279 00667261 _array_entry.fra

第一列表示该行二进制的起始位置,随后四列为二进制数据,最后一列为二进制的字符串表示,这里将字符串的结束标志00表示为.

虽然这些字符是连在一起的,但通过这样的分隔标志即可划分每个字符串,在读取的时候,并不需要知道字符串的长度信息。在C++中,将这样的字符序列传递给std::string,它只会读取到第一个字符串,在00之后的则会被忽略。

结合上面的图示,在每一个Section Header中,都有一个offset用于说明对应节相对elf文件开头位置的偏移量。比如上面图示中,.symstr段的开始位置在离文件开头位置第337384个字节处,可以通过dd命令来验证:

$ dd if=./symbols bs=1 skip=337384 count=300 | xxd
00000000: 0063 7274 7374 7566 662e 6300 6465 7265  .crtstuff.c.dere
00000010: 6769 7374 6572 5f74 6d5f 636c 6f6e 6573  gister_tm_clones
00000020: 005f 5f64 6f5f 676c 6f62 616c 5f64 746f  .__do_global_dto
00000030: 7273 5f61 7578 0063 6f6d 706c 6574 6564  rs_aux.completed
00000040: 2e38 3036 3100 5f5f 646f 5f67 6c6f 6261  .8061.__do_globa
00000050: 6c5f 6474 6f72 735f 6175 785f 6669 6e69  l_dtors_aux_fini
00000060: 5f61 7272 6179 5f65 6e74 7279 0066 7261  _array_entry.fra
00000070: 6d65 5f64 756d 6d79 005f 5f66 7261 6d65  me_dummy.__frame
00000080: 5f64 756d 6d79 5f69 6e69 745f 6172 7261  _dummy_init_arra
00000090: 795f 656e 7472 7900 7379 6d62 6f6c 732e  y_entry.symbols.

这和先前由readelf -x .strtab ./examples/sections读取到的内容一致。

### 关于Raspberry Pi 4B官方启动文件及其汇编说明 #### 官方引导加载程序(U-Boot) 对于Raspberry Pi 4B,其官方引导过程依赖于一系列特定的启动文件以及固件支持。这些文件通常由Broadcom BCM2711芯片组提供硬件支持,并通过树莓派基金会维护的相关资源发布。具体来说,Raspberry Pi 4B 的启动流程涉及以下关键阶段: 1. **第一阶段引导加载程序** 这是由SoC内部ROM执行的第一步操作,负责初始化基本硬件并加载第二阶段引导程序。此部分无需额外配置即可运行。 2. **第二阶段引导加载程序 (bootcode.bin)** `bootcode.bin` 是存储在SD卡上的第一个外部可执行二进制文件[^1]。该文件的主要功能是从FAT32分区读取后续所需的其他文件,例如内核镜像和设备树Blob (DTB) 文件。 3. **第三阶段引导加载程序 (start*.elf 和 fixup*.dat)** - `start.elf`: 负责GPU驱动初始化及内存分配。 - `fixup.dat`: 提供关于如何设置显示控制器的信息。 上述两个文件共同作用来完成整个系统的初步准备环境工作[^2]。 #### U-Boot源码分析 针对更深入的学习需求,比如了解具体的汇编指令实现细节,则需要查阅开源项目中的U-Boot代码库。以下是获取相关资料的方式之一: 访问GitHub上托管的u-boot-for-rpi仓库链接地址(https://github.com/raspberrypi/u-boot),其中包含了适用于不同型号树莓派平台版本定制化修改后的通用引导加载器(U-Boot)源代码集合[^3]。 下面展示了一个简化版进入ARM状态切换至EL1模式的例子片段: ```armasm /* arch/arm/mach-bcm283x/include/asm/arch/sysmap.h */ #define SYS_MAP_BASE 0x3f000000 /* Base address of peripherals */ /* Switch to EL1 */ mov sp, #0 @ Set stack pointer adr x0, reset_handler @ Load function pointer into register X0 br x0 @ Branch instruction jumps unconditionally reset_handler: bl start_kernel @ Call subroutine starting Linux kernel execution flow. ``` #### 总结 综上所述,要研究Raspberry Pi 4B的官方启动文件与对应的汇编指导材料,可以从官方文档、社区贡献的技术博客文章或者直接参与开发者的讨论论坛入手探索更多实践案例和技术交流机会。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值