【问题标题】:Is it possible to get the signature of a function in a shared library programmatically?是否可以通过编程方式获取共享库中函数的签名?
【发布时间】:2011-10-16 09:59:46
【问题描述】:

标题很清楚,我们可以通过dl_open等加载一个库。

但是我怎样才能得到其中的函数签名呢?

【问题讨论】:

  • 您可能会发现关于名称修饰信息的 Wikipedia 文章,以查看 C 与 C++ 的目标代码名称中编码的信息以及编译器如何变化:en.wikipedia.org/wiki/Name_mangling
  • @Hostile Fork ,在一个单个 .so,是否可以有不同的名称修改?
  • 如 Wikipedia 文章中所述,C 中的名称修饰只是为了支持 Windows 约定,不会出现在 .so 文件中。对于C++,是的,相同函数名的不同修饰出现在一个.so 中,以支持重载的语言特性。 en.wikipedia.org/wiki/Function_overloading

标签: c shared-libraries


【解决方案1】:

这个答案一般无法回答。从技术上讲,如果您使用详尽的调试信息编译可执行文件(代码可能仍然是优化的发布版本),那么可执行文件将包含额外的部分,从而提供二进制文件的某种反射率。在 *nix 系统上(您提到了dl_open),这是通过DWARFELF 二进制文件的额外部分中调试数据来实现的。类似的,它适用于 MacOS X 上的 Mach Universal Binaries

然而,Windows PE 使用完全不同的格式,所以不幸的是 DWARF 不是真正的跨平台(实际上在我的 3D 引擎的早期开发阶段,我为 Windows 实现了一个 ELF/DWARF 加载器,以便我可以使用通用格式引擎的各种模块,因此可以通过一些认真的努力来完成)。

如果您不想实现自己的加载器或调试信息访问器,那么您可以通过一些引用函数名称表的导出的额外符号(通过某些标准命名方案)嵌入反射信息,映射到他们的签名。在 C 源文件的情况下,编写解析器从源文件本身提取信息是相当简单的。众所周知,C++ OTOH 很难正确解析,因此您需要一些成熟的编译器才能正确解析。为此目的,开发了 GCCXML,从技术上讲,它是一种 GCC,它以 XML 形式而不是对象二进制文件发出 AST。发出的 XML 更容易解析。

从提取的信息中创建一个包含某种链表/数组/等的源文件。描述每个功能的结构。如果您不直接导出每个函数的符号,而是使用函数指针初始化反射结构中的某些字段,那么您将获得一个非常漂亮且干净的带注释的导出方案。从技术上讲,您也可以将此信息放在二进制文件的单独部分中,但将其放入只读数据部分也可以完成这项工作。


但是,如果给您一个第 3 方二进制文件——比如说最坏的情况,它是从 C 源代码编译的,没有调试信息,并且所有没有外部引用的符号都被剥离了——你就大错特错了。您能做的最好的事情就是对函数访问可以传递参数的各个位置的方式进行一些二进制分析。

这只会告诉您参数的数量和每个参数值的大小,而不是类型或名称/含义。在对某些程序进行逆向工程(例如恶意软件分析或安全审计)时,识别传递给函数的参数的类型和含义是主要工作之一。最近我遇到了一些我为了调试目的而不得不反转的驱动程序,你无法相信我在 Linux 内核模块中发现 C++ 符号这一事实让我感到多么震惊(你不能以理智的方式在 Linux 内核中使用 C++ ),但也松了一口气,因为 C++ 名称修改为我提供了很多信息。

【讨论】:

    【解决方案2】:

    在 Linux(或 Mac)上,您可以结合使用“nm”和“c++filt”(用于 C++ 库)

    nm mylibrary.so | c++filt

    nm mylibrary.a | c++filt

    "nm" 会为您提供损坏的形式,而"c++filt" 会尝试将它们置于更易于阅读的格式中。您可能希望使用 nm 中的一些选项来过滤结果,尤其是在库很大的情况下(或者您可以“grep”最终输出以查找特定项目)

    【讨论】:

    • 在我的 Mac 上我必须这样做 nm -D -Ca mylibrary.so | c++filt
    【解决方案3】:

    不,这是不可能的。函数的签名在运行时没有任何意义,它是在编译时对编译器验证程序很有用的一条信息。

    【讨论】:

    • 我不认为这种说法是准确的,你基本上只是断然地说这是不可能的。可以做很多事情来恢复签名。您可以查看调用约定,如果您可以使用污点分析,则可以跟踪来自其他已知签名的参数并将类型返回给所需的函数。
    【解决方案4】:

    你不能。库要么在标头中发布公共 API,要么您需要通过其他方式知道签名。

    【讨论】:

      【解决方案5】:

      低层函数的参数取决于您考虑的堆栈帧中有多少堆栈参数以及您如何解释它们。因此,一旦函数被编译成目标代码,就不可能得到这样的签名。一种遥远的可能性是反汇编代码并阅读它的功能如何知道参数的数量,但仍然很难或无法确定类型。总之,这是不可能的。

      【讨论】:

        【解决方案6】:

        此信息不可用。连调试器都不知道:

        $ cat foo.c
        #include <stdio.h>
        #include <string.h>
        
        int main(int argc, char* argv[])
        {
            char foo[10] = { 0 };
            char bar[10] = { 0 };
            printf("%s\n", "foo");
            memcpy(bar, foo, sizeof(foo));
            return 0;
        }
        
        $ gcc -g -o foo foo.c
        $ gdb foo
        Reading symbols from foo...done.
        (gdb) b main
        Breakpoint 1 at 0x4005f3: file foo.c, line 5.
        (gdb) r
        Starting program: foo 
        
        Breakpoint 1, main (argc=1, argv=0x7fffffffe3e8) at foo.c:5
        5   {
        (gdb) ptype printf
        type = int ()
        (gdb) ptype memcpy
        type = int ()
        (gdb) 
        

        【讨论】:

        • 至少调试器知道它是一个函数,int ()
        • @Je Rog,但正如您在 memcpy 中看到的那样,它只是应用了返回类型的默认规则,所以即使这样也不是很有帮助。
        • @Jens Gustedt,你知道调试器是如何知道它是一个函数的吗?
        • @Je Rog,可能很大程度上取决于系统,但通常目标文件的符号表会将符号分类为不同的类别。例如,在 Linux 上,您可以使用 nm 实用程序检查这些。
        • @Je Rog,您可以通过查看符号表来判断两种方法。一是检查标志,二是检查节名。如果函数在该二进制文件中定义,则函数将位于 .text 段中,并且将具有 F 标志以指示它们是函数而不是其他函数。 (l 用于本地,g 用于全球等 - 请参阅 man objdump
        猜你喜欢
        • 2014-10-05
        • 2021-09-24
        • 2019-05-31
        • 2018-03-12
        • 2022-01-14
        • 1970-01-01
        • 2017-06-29
        • 2020-04-21
        • 1970-01-01
        相关资源
        最近更新 更多