如何在 C 中用 nm 或 readelf 输出区分静态函数

How can I differentiate static functions with nm or readelf output in C

我正在尝试在 executable 上处理 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函数出现在哪个文件中?或者我需要在编译之前做一些魔术才能让它工作吗?

我应该补充一点,对于我的用例,我可以访问最终的二进制文件和它使用的目标文件,但我实际上不能自己构建它以确保它有一个符号 table。

谢谢!

您可能需要阅读 ELF 符号 table 并提取 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 被定义为 "Local symbols are not visible outside the object file containing their definition. Local symbols of the same name may exist in multiple files without interfering with each other.",这似乎与静态 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 进行编译并查看符号 table(删除了很​​多条目):

    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

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

我尝试了以下顺序。

如果您删除了没有调试符号的输出文件,那么使用 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

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

[ 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文件。希望对你有所帮助。

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

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

gcc -o test test.c other.c

但是如果文件是在任何更高的优化级别编译的——因为它们肯定 将用于发布版本 - 然后从对象中省略局部符号 文件符号 table。所以链接器甚至都看不到它们。所以你无法恢复 他们来自 executable 和 nm 或其他任何东西。

编译示例文件:

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

然后再次 nm test,您将观察到:

00000000004004ed t foo
0000000000400500 t foo

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

在那种情况下,如果如您所说您无法控制 executable 的构建方式, 那么你无法确保甚至有可能出现你的问题

如果您可以控制 executable 的构建方式以确保文件是 使用 -O0 编译,那么您可以通过多种方式绑定 源文件的静态函数名称。两个同样简单的是:

readelf -s test

objdump -t test

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

(如果需要说明,@Amol 建议的 gdb 方法并没有逃脱 executable 必须经过优化 -O0 编译的限制)

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

#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();
}