我可以强制 ld 解析本地符号吗?

Posted

技术标签:

【中文标题】我可以强制 ld 解析本地符号吗?【英文标题】:Can l force ld to resolve a local symbol? 【发布时间】:2022-01-03 07:35:33 【问题描述】:

一个共享对象,例如 glibc,如果编译得当,会定义很多符号,例如 main_arena,这些符号通常不会被其他程序使用(尽管它们可以在 objdumpgcc 中看到),但是用它们的地址定义为本地符号

 $ 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 来确认这一点——该符号将不存在。 您可以对“标志”进行二进制修补,将@9​​87654326@ 中符号的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.outfoo.so 链接并定义int foo,然后针对不同版本的foo.so 运行a.out 完全相同,其中一个没有foo

更新 2:

这仅仅是 ld 缺乏的一个功能(因为在逆向工程和其他非标准用例之外不需要它),还是天生不可能?

这是一个ld(静态链接器)和ld.so(动态加载器)都缺少的功能。

是可以做到的(毕竟GDB可以解析这些符号),但是工作量很大,收效甚微。

是否可以增加 ld 以使用常规的 .symtab(我知道由于缺少哈希值会变慢)?

就像我说的,您需要同时修改 ldld.so。后者是GLIBC的一部分,修改GLIBC有complications。在此过程中犯任何错误很容易导致您的系统无法启动。

如果您仍然要修改 GLIBC,那么公开您想要的所有符号(使它们非本地)可能会简单得多。这样您只需要更改 GLIBC,就可以使用标准的ld 和其他标准符号解析机制。

【讨论】:

谢谢。 gdb 如何找到符号的地址?有没有办法告诉 ld 做类似的事情? @SRobertJames 我已经更新了答案。 “动态符号表中不存在符号(这是 ld.so 唯一可以使用的符号表)” - 请澄清:这只是 ld 缺少的功能(因为不需要在逆向工程和其他非标准用例之外),或者它本质上是不可能的?是否可以增加 ld 以使用常规的 .symtab (我知道由于缺少哈希,它会变慢)? 另外,我已经阅读了您的其他帖子,并相应地更新了问题。 @SRobertJames 我已经更新了答案。

以上是关于我可以强制 ld 解析本地符号吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何告诉/强制 GNU ld 将部分/符号放在输出 ELF 文件的特定部分?

如何在运行时解析 dll 中的外部符号,而不是使用 Cygwin 进行链接时

我可以从 ELF 文件的符号表中的符号信息中获取对象名称吗?

maven可以解析文件夹结构发生变化的本地资源吗?

如何使用 GetFinalPathNameByHandle 来解析指向本地目录的符号链接?

无法解析符号'NotBlank'?