【问题标题】:Can l force ld to resolve a local symbol?我可以强制 ld 解析本地符号吗?
【发布时间】: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

注意:我已经通过广泛的研究更新了这个问题:

这是设计使然:

尽管如此,为了调试、探索和逆向工程,有时需要引用在共享对象中定义的外部本地符号。所有信息都在那里,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 中?

【问题讨论】:

    标签: c gcc linker symbols ld


    【解决方案1】:

    鉴于此,是否可以告诉 ld 忽略本地标志,并解析为符号?

    没有。

    所有信息都在那里,gdb 的显示能力证明了这一点;它只是一个标志,告诉 ld 不要将符号解析为它。

    你错了。虽然符号存在于静态符号表中(.symtab 部分),但它 存在于动态符号表中(.dynsym 部分)。 只是一个标志的问题,缺少在运行时执行动态链接所需的基本部分。

    1. 您可以通过查看readelf --dyn-syms .../libc.so.6 | grep main_arena 来确认这一点——该符号将不存在。
    2. 您可以对“标志”进行二进制修补,将@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 我已经更新了答案。
    猜你喜欢
    • 2023-03-03
    • 2015-02-01
    • 2020-04-26
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多