我可以强制 ld 解析本地符号吗?
Posted
技术标签:
【中文标题】我可以强制 ld 解析本地符号吗?【英文标题】:Can l force ld to resolve a local symbol? 【发布时间】:2022-01-03 07:35:33 【问题描述】:一个共享对象,例如 glibc,如果编译得当,会定义很多符号,例如 main_arena
,这些符号通常不会被其他程序使用(尽管它们可以在 objdump
和 gcc
中看到),但是用它们的地址定义为本地符号:
$ objdump -t ../.glibc/glibc_2.30_no-tcache/libc.so.6 | grep main_arena
00000000003b4b60 l O .data 0000000000000898 main_arena
然而,当我在 C 中引用其中一个(通过extern
)并尝试链接时,链接器找不到它:
$ gcc -g -Og -no-pie -Wl,-rpath ../.glibc/glibc_2.30_no-tcache/ -Wl,--dynamic-linker=../.glibc/glibc_2.30_no-tcache/ld.so.2 s1.c -o s1
/usr/bin/ld: /tmp/ccjKyCNh.o: in function `printf':
/usr/include/x86_64-linux-gnu/bits/stdio2.h:112: undefined reference to `main_arena'
/usr/bin/ld: /usr/include/x86_64-linux-gnu/bits/stdio2.h:112: undefined reference to `main_arena'
collect2: error: ld returned 1 exit status
注意:我已经通过广泛的研究更新了这个问题:
这是设计使然:
c language, global symbol, local symbol clarification "local (static): 由模块 m 专门定义和引用的本地符号....这些符号在模块 m 内的任何地方都可见,但不能被其他模块引用。"
另请参阅“符号可见性 符号可以分为本地或全局。不能从包含符号定义的对象以外的对象引用局部符号。”https://docs.oracle.com/cd/E26505_01/html/E26506/chapter2-90421.html
和https://reverseengineering.stackexchange.com/questions/14895/why-are-symbols-with-local-binding-present-in-the-symbol-table-of-my-elf-files 和http://web.cse.ohio-state.edu/~reeves.92/CSE2421au12/SlidesDay52.pdf
尽管如此,为了调试、探索和逆向工程,有时需要引用在共享对象中定义的外部本地符号。所有信息都在那里,gdb 的显示能力证明了这一点;它只是一个标志,告诉 ld 不要将符号解析为它。
鉴于此,是否可以告诉 ld 忽略本地标志,并解析为符号?
例如:
$ objdump -t ../.glibc/glibc_2.30_no-tcache/libc.so.6 | grep -E ' malloc$| main_arena$'
00000000003b4b60 l O .data 0000000000000898 main_arena
0000000000083500 g F .text 0000000000000213 malloc
$ man objdump 2>/dev/null | grep -A10 'flag characters'
The flag characters are divided into 7 groups as follows:
"l"
"g"
"u"
"!" The symbol is a local (l), global (g), unique global (u), neither global nor local (a space) or both global and
local (!). ...
我希望能够编写用于调试和逆向工程的代码,无论如何都引用符号main_arena
。我该怎么做?
更新
我已阅读Employed Russian关于相关主题的优秀帖子,并看到他对XY Problem 的引用。考虑到这一点,让我问我的问题 X:
出于探索目的,我希望能够查看 main_arena
和其他 malloc 内部结构的行为,因为我使用 malloc 和 free。我可以用 gdb 做到这一点。但我想在 C 中以编程方式执行此操作。执行此操作的一种方法可能是实际链接到这些符号(question Y),但没有理由认为这是最好的方法,唯一的方法,甚至是可行的方法。鉴于:
如何从不同的程序中检查共享库中本地符号的值,而不必放到 gdb 中?
【问题讨论】:
【参考方案1】:鉴于此,是否可以告诉 ld 忽略本地标志,并解析为符号?
没有。
所有信息都在那里,gdb 的显示能力证明了这一点;它只是一个标志,告诉 ld 不要将符号解析为它。
你错了。虽然符号存在于静态符号表中(.symtab
部分),但它不 存在于动态符号表中(.dynsym
部分)。 不只是一个标志的问题,缺少在运行时执行动态链接所需的基本部分。
-
您可以通过查看
readelf --dyn-syms .../libc.so.6 | grep main_arena
来确认这一点——该符号将不存在。
您可以对“标志”进行二进制修补,将@987654326@ 中符号的STB_LOCAL
绑定更改为STB_GLOBAL
。完成此操作后,符号将在 objdump
输出中显示为 g
,但链接器将仍然无法使用它。
附:你永远不应该使用objdump
来检查 ELF 二进制文件——它对于这个目的非常缺乏。请改用readelf
。
更新:
GDB 如何找到...
通过阅读.symtab
部分。
有没有办法告诉 ld 做类似的事情?
没有。链接器也可以轻松读取.symtab
部分,并且可以链接导入 main_arena
符号的二进制文件,其方式与导入的方式相同,例如stdout
.
但是这样的二进制文件不会运行。
在运行时,一旦二进制文件被加载,加载器 (ld.so
) 将需要解析对main_arena
的引用。由于动态符号表中不存在该符号(这是ld.so
唯一可以使用的符号表),符号解析将失败,ld.so
将退出并出现致命错误。
这与将a.out
与foo.so
链接并定义int foo
,然后针对不同版本的foo.so
运行a.out
完全相同,其中一个没有foo
。
更新 2:
这仅仅是 ld 缺乏的一个功能(因为在逆向工程和其他非标准用例之外不需要它),还是天生不可能?
这是一个ld
(静态链接器)和ld.so
(动态加载器)都缺少的功能。
是可以做到的(毕竟GDB可以解析这些符号),但是工作量很大,收效甚微。
是否可以增加 ld 以使用常规的 .symtab(我知道由于缺少哈希值会变慢)?
就像我说的,您需要同时修改 ld
和 ld.so
。后者是GLIBC的一部分,修改GLIBC有complications。在此过程中犯任何错误很容易导致您的系统无法启动。
如果您仍然要修改 GLIBC,那么公开您想要的所有符号(使它们非本地)可能会简单得多。这样您只需要更改 GLIBC,就可以使用标准的ld
和其他标准符号解析机制。
【讨论】:
谢谢。 gdb 如何找到符号的地址?有没有办法告诉 ld 做类似的事情? @SRobertJames 我已经更新了答案。 “动态符号表中不存在符号(这是 ld.so 唯一可以使用的符号表)” - 请澄清:这只是 ld 缺少的功能(因为不需要在逆向工程和其他非标准用例之外),或者它本质上是不可能的?是否可以增加 ld 以使用常规的.symtab
(我知道由于缺少哈希,它会变慢)?
另外,我已经阅读了您的其他帖子,并相应地更新了问题。
@SRobertJames 我已经更新了答案。以上是关于我可以强制 ld 解析本地符号吗?的主要内容,如果未能解决你的问题,请参考以下文章
如何告诉/强制 GNU ld 将部分/符号放在输出 ELF 文件的特定部分?
如何在运行时解析 dll 中的外部符号,而不是使用 Cygwin 进行链接时
我可以从 ELF 文件的符号表中的符号信息中获取对象名称吗?