如何区分静态函数与 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”比较链接的可执行文件。当然,这假设不同的大小......
如果大小不是唯一的,您可以使用objdump
和crc32
或类似的,首先在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 输出的主要内容,如果未能解决你的问题,请参考以下文章