C 是否可以通过编程方式获取共享库中函数的签名?

C 是否可以通过编程方式获取共享库中函数的签名?,c,shared-libraries,C,Shared Libraries,标题很清楚,我们可以通过dl_open等加载库 但是如何才能获得其中函数的签名呢?不,这是不可能的。函数的签名在运行时并不意味着什么,它是编译时编译器用来验证程序的有用信息。你不能。要么是库在标头中发布了一个公共API,要么需要用其他方法知道签名。 < P>下层函数的参数取决于堆栈框架中有多少堆栈参数以及如何解释它们。因此,一旦函数被编译成目标代码,就不可能得到这样的签名。一个遥远的可能性是反汇编代码,并读取其功能如何工作,以了解参数的数量,但类型仍然很难或不可能确定。总之,这是不可能的。此信息

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


但是如何才能获得其中函数的签名呢?

不,这是不可能的。函数的签名在运行时并不意味着什么,它是编译时编译器用来验证程序的有用信息。

你不能。要么是库在标头中发布了一个公共API,要么需要用其他方法知道签名。

< P>下层函数的参数取决于堆栈框架中有多少堆栈参数以及如何解释它们。因此,一旦函数被编译成目标代码,就不可能得到这样的签名。一个遥远的可能性是反汇编代码,并读取其功能如何工作,以了解参数的数量,但类型仍然很难或不可能确定。总之,这是不可能的。

此信息不可用。即使调试器也不知道:

$ 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) 
$cat foo.c
#包括
#包括
int main(int argc,char*argv[])
{
char foo[10]={0};
字符条[10]={0};
printf(“%s\n”、“foo”);
memcpy(bar、foo、sizeof(foo));
返回0;
}
$gcc-g-o foo foo.c
$gdb foo
从foo读取符号…完成。
(gdb)b干管
0x4005f3处的断点1:文件foo.c,第5行。
(gdb)r
启动程序:foo
foo.c处的主断点1(argc=1,argv=0x7fffffe3e8):5
5   {
(gdb)打印类型
type=int()
(gdb)ptype memcpy
type=int()
(gdb)

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

然而,Windows PEs使用完全不同的格式,因此不幸的是,DWARF不是truley cross plattform(实际上,在我的3D引擎的早期开发阶段,我为Windows实现了一个ELF/DWARF加载程序,因此我可以为引擎的各个模块使用一种通用格式,因此,只要付出一些认真的努力,就可以做到这一点)

如果不想实现自己的加载程序或调试信息访问器,那么可以通过导出的一些额外符号(通过一些标准命名方案)嵌入反射信息这是一个函数名表,映射到它们的签名。在C源文件的情况下,编写解析器从源文件本身提取信息是相当微不足道的。C++ Otoh是非常难于正确解析的,需要一些完全成熟的编译器才能正确。为此,GCXML被开发出来。从技术上讲,GCC以XML形式而不是对象二进制形式发出AST。这样发出的XML就更容易解析了

根据提取的信息创建一个源文件,该文件具有描述每个函数的某种链表/数组等结构。如果您不直接导出每个函数的符号,而是使用函数指针初始化反射结构中的某个字段,则会得到一个非常好且干净的带注释的导出方案我们也可以将此信息放在二进制文件的sperate部分,但将其放在只读数据部分也可以


然而,如果给你一个第三方二进制文件,比如说最坏的情况,它是从C源代码编译的,没有调试信息,没有外部引用的所有符号,那么你就完蛋了。你所能做的最好的事情就是对函数访问不同位置的方式进行一些二进制分析,在这些位置上参数n被通过

这只会告诉您参数的数量和每个参数值的大小,而不会告诉您参数的类型或名称/含义。当对某些程序进行反向工程时(例如恶意软件分析或安全审计)最近,我遇到了一些驱动程序,我不得不为了调试目的而反转,而你无法相信我在Linux内核模块中发现C++符号(在一个明智的方法中,不能在Linux内核中使用C++)这一事实让我吃惊。而且,也因为Pc++名字的修改给了我很多信息。在Linux(或MAC)上,你可以使用“NM”和“C++ FLT”(C++ C++)的组合< < /P> nm mylibrary.so | c++过滤器

nm mylibrary.a | c++过滤器


“nm”将为您提供已损坏的表单,“c++filt”尝试将其设置为更易于阅读的格式。您可能希望使用nm中的一些选项来过滤结果,尤其是当库很大时(或者您可以“grep”最终输出以查找特定项)

你可能会发现维基百科文章中的名字是信息量的,以查看C + C++中的对象代码名所编码的信息以及它是如何由编译器改变的:@敌意叉,在单个<代码>中。因此,<代码>可以有不同的名称吗?如在维基百科文章中所指出的,C中的名字只是支持Windows CONV。在中,所以文件。对于C++,是的,同一个函数名的不同的处理出现在一个代码>中。所以支持超载的语言特性。至少调试器知道它是一个函数,<代码> int()。@Je-Rog,但正如您所看到的
memcpy
它只对返回类型应用默认规则,因此即使这样也没有多大帮助。@Jens Gustedt,您知道调试器是如何首先知道它是一个函数的吗?@Je-Rog可能很大程度上取决于系统,但通常对象文件的符号表会对int的符号进行分类o不同的catego