如何解读ELF文件中ABS符号的值及链接相关技术问题
我在查看libc.a文件中的符号时,发现了一些ABS符号,例如_nl_current_LC_COLLATE_used。以下是readelf对libc.a文件的输出:
File: libc.a(setlocale.o) Symbol table '.symtab' contains 77 entries: Num: Value Size Type Bind Vis Ndx Name ... 39: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _nl_current_LC_COLLATE_used ... File: libc.a(uselocale.o) Symbol table '.symtab' contains 34 entries: Num: Value Size Type Bind Vis Ndx Name ... 6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _nl_current_LC_COLLATE_used ... File: libc.a(lc-collate.o) Symbol table '.symtab' contains 5 entries: Num: Value Size Type Bind Vis Ndx Name ... 1: 0000000000000002 0 NOTYPE GLOBAL DEFAULT ABS _nl_current_LC_COLLATE_used ...
重定位信息如下:
File: libc.a(setlocale.o) Relocation section '.rela.text' at offset 0x1b98 contains 124 entries: Offset Info Type Symbol's Value Symbol's Name + Addend ... 00000000000009a3 0000002700000009 R_X86_64_GOTPCREL 0000000000000000 _nl_current_LC_COLLATE_used - 5 ... Relocation section '.rela.data.rel.ro' at offset 0x2738 contains 13 entries: Offset Info Type Symbol's Value Symbol's Name + Addend ... 0000000000000098 0000002700000001 R_X86_64_64 0000000000000000 _nl_current_LC_COLLATE_used + 0 ... File: libc.a(uselocale.o) Relocation section '.rela.text' at offset 0x838 contains 29 entries: Offset Info Type Symbol's Value Symbol's Name + Addend ... 0000000000000029 0000000600000009 R_X86_64_GOTPCREL 0000000000000000 _nl_current_LC_COLLATE_used - 5 ...
该符号是两次R_X86_64_GOTPCREL重定位和一次R_X86_64_64重定位的目标,这意味着需要该符号的地址,因此它必须在进程内存中定义。
该符号在3个文件中出现:两次作为WEAK UNDEFINED符号,一次作为值为2的ABS符号,且无其他定义。当编译简单的helloworld.c并链接libc.a时,必须使用该符号的定义生成最终二进制,但该符号无关联节,仅值为2。
根据ELF规范,SHN_ABS表示对应引用的绝对值,即引用该节的符号已有绝对值,不受重定位影响。但2并非该符号的绝对地址,查看libc源码可知,_nl_current_LC_COLLATE_used通过宏和asm指令定义为值为2的const整数。
我存在以下疑问:
- 最终二进制中,值2存储在哪里?是否由链接器创建新数据节存储所有ABS符号的值?
- 若如此,链接器如何确定每个ABS符号值的大小?此处源码显示值2应存储为64位整数,但.symtab的size字段为0。
- 链接器如何判断该符号应存储在只读还是可读写数据节?
解答
1. 值2的存储位置
链接器不会为这类ABS符号创建新的数据节。因为这个符号本质是一个编译期确定的常量值,而非需要分配内存的变量。处理重定位时,链接器会直接将符号的绝对值(也就是2)代入到对应的引用位置:
- 对于
R_X86_64_64这类直接寻址的重定位,会直接把2写入到目标地址; - 对于
R_X86_64_GOTPCREL这类需要地址的重定位,这里的逻辑是源码将该常量当作“伪地址”使用,链接器会把2作为该符号的"地址"值完成计算,不需要额外存储,直接替换重定位后的结果。
2. 符号大小的确定
.symtab里的size字段为0,是因为这类ABS符号没有关联到实际的节数据,只是一个纯符号值,所以不需要记录大小。链接器会通过两个上下文信息推断大小:
- 重定位类型:比如
R_X86_64_64是64位重定位,说明需要处理的是64位数据; - 源码定义:从libc源码的宏和汇编定义可以明确这是一个64位整数,链接器在处理时会遵循这个定义。
3. 只读/可读写节的判断
这类ABS符号本身不是内存中的变量,而是编译时固定的常量,所以不会被放到任何数据节里。因为_nl_current_LC_COLLATE_used是const整数,所有引用它的地方都会被直接替换成固定值2,运行时不存在可修改的存储。如果是可修改的ABS符号(非常见场景),链接器才会将其分配到可读写数据节,但这里显然不属于这种情况。
内容的提问来源于stack exchange,提问作者Orel

