如何区分静态函数与 C 中的 nm 或 readelf 输出

Posted

技术标签:

【中文标题】如何区分静态函数与 C 中的 nm 或 readelf 输出【英文标题】:How can I differentiate static functions with nm or readelf output in C 【发布时间】:2015-06-08 16:38:03 【问题描述】:

我正在尝试在可执行文件上处理 nm 或 readelf -s 的输出。但是,我无法在输出中区分静态函数。

这是我正在使用的:

test.c

static int foo() 
    int x = 6;


main() 

other.c

static int foo() 
    int x = 5;

我是这样编译的:

gcc -o test test.c other.c

然后运行 ​​nm 命令获取所有符号:

nm test

其中出现以下两个符号(用于我的静态函数):

00000000004004ed t foo
0000000000400500 t foo

有没有一种方法可以区分特定的 foo 函数来自哪个文件?还是在编译之前我需要做一些魔法才能让它工作?

我应该为我的用例添加一点,我可以访问最终的二进制文件和它使用的目标文件,但我不能自己实际构建它以确保它具有符号表。

谢谢!

【问题讨论】:

据我所知,源文件信息存储在调试信息中。完全剥离的二进制文件不会有这样的信息。要阅读调试信息,请查看readelf --debug-dump 选项。 nm 的类似 --debug-syms -g编译并使用nm -l 我应该补充一点,对于我的用例,我可以访问最终的二进制文件和它使用的目标文件,但我无法在二进制文件中构建或确保调试信息。 这可能需要做很多工作......但也许您可以使用“nm -S”来获取每个目标文件的符号大小,然后与“nm -S”比较链接的可执行文件。当然,这假设不同的大小...... 如果大小不是唯一的,您可以使用objdumpcrc32 或类似的,首先在obj文件上然后在链接文件上。 【参考方案1】:

您的问题假设,给定一个可执行文件,您总能发现 编译到其中的static(本地)函数的名称, 使用nm 或其他工具。因此,您将能够看到两个或 更多这样的名字是相同的,并提出如何发现的问题 它们是从哪些源文件编译而来的。

但是,这个假设是错误的。在 gcc 的情况下,如果文件被编译 通过优化-O0 然后将在对象中发出局部符号 文件符号表。 -O0 是默认值,因此它适用于您的情况:

gcc -o test test.c other.c

但是如果文件是在任何更高的优化级别编译的——当然 将用于发布版本 - 然后从对象中省略本地符号 文件符号表。所以链接器甚至从来没有看到它们。所以你无法恢复 它们来自带有nm 或其他任何东西的可执行文件。

编译您的示例文件:

gcc -O1 -o test test.c other.c

然后再次nm test,您会发现:

00000000004004ed t foo
0000000000400500 t foo

连同所有其他静态函数名称一起消失了。

在这种情况下,如果您说您无法控制可执行文件的构建方式, 那么您就无法确保甚至可能出现您的问题

如果您可以控制如何构建可执行文件以确保文件是 用-O0编译,那么有几种方法可以绑定 源文件的静态函数名称。两个同样简单的方法是:

readelf -s test

objdump -t test

每个都会在每个块的头部列出一个源文件名 来自它的符号。

(如果需要说明的话,@Amol 建议的gdb 方法并没有摆脱可执行文件必须经过优化-O0 编译的限制)

【讨论】:

【参考方案2】:

我尝试了以下顺序。

如果您已经剥离了没有调试符号的输出文件,那么使用gdb 您可以创建目标文件。 请按照以下命令进行操作:

$ gdb a.out

将给出以下输出

Reading symbols from /home/amol/amol/a.out...(no debugging symbols found)...done.

然后 (gdb) 将进入终端

按顺序给出以下命令((gdb) 提示符会在你继续输入命令时出现)

 (gdb) maint print symbols filename
 (gdb) maint print psymbols filename
 (gdb) maint print msymbols filename

现在在您的文件夹中,您可以看到一个名为 filename 的文件。在文本编辑器中打开这个文件,你可以看到如下信息:

[ 8] t 0x80483b4 foo section .text  test.c
[ 9] T 0x80483c3 main section .text  other.c
[10] t 0x80483c8 foo section .text  other.c

在这里你可以清楚地看到哪个foo()函数来自哪个.c文件。希望这对您有所帮助。

【讨论】:

感谢您的回复。我可能应该提到我希望我的方法可以接受任何任意可执行文件,因此甚至可以省略符号表并且该方法应该仍然有效。但是,在查看您的回复和其他几个回复后,我认为这是不可能的。再次感谢!【参考方案3】:

您可能需要读取 ELF 符号表并提取 ELF32_ST_BIND 值。

根据 ELF 规范(参见 http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf),ELF32_ST_BIND 的值可以是:

       Name         Value
     STB_LOCAL      0
     STB_GLOBAL     1
     STB_WEAK       2
     STB_LOPROC    13
     STB_HIPROC    15

其中 STB_LOCAL 被定义为“本地符号在包含其定义的目标文件之外不可见。同名的本地符号可以存在于多个文件中而不会相互干扰。”这似乎与静态 C 函数相当匹配。

比如给你采样,稍微修改一下:

    test.c:

    static int foo() 
        int x = 5;
    

    int bar()
    
         int y = 6;
    

    main() 

    other.c:

    static int foo()
    
        int x = 7;
    

并使用gcc -o test test.c other.c 进行编译并查看符号表(删除了许多条目):

    readelf -s test
    Num:    Value          Size Type    Bind   Vis      Ndx Name
    37: 00000000004004f0    13 FUNC    LOCAL  DEFAULT   13 foo
    39: 0000000000400510    13 FUNC    LOCAL  DEFAULT   13 foo
    52: 00000000004004fd    13 FUNC    GLOBAL DEFAULT   13 bar

我们可以看到两个静态函数显示为 LOCAL,一个“普通”函数显示为 GLOBAL

注意:虽然此方法适用于非调试文件,但如果最终文件被剥离,我们不能使用此方法。

【讨论】:

【参考方案4】:

如果它应该适用于任何剥离的可执行文件,您可以将字符串植入函数并在可执行文件中搜索它们。

#define STR1(x) #x
#define STR(x) STR1(x)
#define FUNCID(funcname) __asm__ __volatile__ (\
    "jmp 1f;"\
    ".string \"" __FILE__ "/" STR(funcname) "()\";"\
    "1:"\
)

static int foo() 
    FUNCID(foo);
    return rand();

【讨论】:

以上是关于如何区分静态函数与 C 中的 nm 或 readelf 输出的主要内容,如果未能解决你的问题,请参考以下文章

Linux的nm查看动态和静态库中的符号

Linux的nm命令查看动态库和静态库中的符号

使用 `nm` 命令列出局部变量

C里面静态动态,生命周期.作用域怎么区分?怎么用

C++类和对象下

c语言中的变量总结