如何在 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();
}
我正在尝试在 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();
}