如何从C中的函数指针获取函数名?
编辑:真实情况是:我正在编写一个 linux 内核模块并且我正在调用内核函数。其中一些函数是指针,我想在内核源代码中检查该函数的代码。但我不知道它指向哪个函数。我认为可以这样做,因为当系统失败(内核恐慌)时,它会在屏幕上打印出当前调用堆栈以及函数名称。但是,我想我错了……是吗?
如何从C中的函数指针获取函数名?
编辑:真实情况是:我正在编写一个 linux 内核模块并且我正在调用内核函数。其中一些函数是指针,我想在内核源代码中检查该函数的代码。但我不知道它指向哪个函数。我认为可以这样做,因为当系统失败(内核恐慌)时,它会在屏幕上打印出当前调用堆栈以及函数名称。但是,我想我错了……是吗?
我很惊讶为什么每个人都说这是不可能的。在 Linux 上可以使用非静态函数。
我知道至少有两种方法可以实现这一目标。
有用于回溯打印的 GNU 函数:backtrace()
和backtrace_symbols()
(参见 参考资料man
)。在您的情况下,您不需要backtrace()
,因为您已经有函数指针,您只需将其传递给backtrace_symbols()
.
示例(工作代码):
#include <stdio.h>
#include <execinfo.h>
void foo(void) {
printf("foo\n");
}
int main(int argc, char *argv[]) {
void *funptr = &foo;
backtrace_symbols_fd(&funptr, 1, 1);
return 0;
}
编译gcc test.c -rdynamic
输出:./a.out(foo+0x0)[0x8048634]
它为您提供二进制名称、函数名称、从函数开始的指针偏移量和指针值,以便您可以解析它。
另一种方法是使用dladdr()
(另一个扩展名),我猜print_backtrace()
使用dladdr()
. dladdr()
返回在字段Dl_info
中具有函数名称的结构。dli_sname
我在这里没有提供代码示例,但很明显 - 请参阅man dladdr
详细信息。
注意!这两种方法都要求函数是非静态的!
好吧,还有另一种方法 - 使用调试信息,libdwarf
但它需要未剥离的二进制文件并且不太容易做到,所以我不推荐它。
如果没有额外的帮助,这是不可能直接实现的。
你可以:
在程序中维护一个表映射函数指针到名称
检查可执行文件的符号表(如果有的话)。
然而,后者很难,而且不便携。该方法将取决于操作系统的二进制格式(ELF、a.out、.exe 等),以及链接器完成的任何重定位。
编辑:既然您现在已经解释了您的真实用例是什么,那么答案实际上并不难。内核符号表在 中可用/proc/kallsyms
,并且有一个用于访问它的 API:
#include <linux/kallsyms.h>
const char *kallsyms_lookup(unsigned long addr, unsigned long *symbolsize,
unsigned long *ofset, char **modname, char *namebuf)
void print_symbol(const char *fmt, unsigned long addr)
出于简单的调试目的,后者可能会完全满足您的需求 - 它获取地址、格式化并将其发送到printk
,或者您可以使用printk
格式%pF
说明符。
在Linux内核中,可以直接使用“%pF”格式的printk!
void *func = &foo;
printk("func: %pF at address: %p\n", func, func);
以下内容适用于 Linux:
%p
nm <program_path> | grep <address>
(不带0x
前缀)它仅在所讨论的函数在同一个程序中(而不是在动态链接库或其他东西中)时才有效。
如果能查出加载的共享库的加载地址,可以从打印出来的数字中减去地址,在库上用nm来查出函数名。
你不能直接,但如果你愿意,你可以用不同的方法来解决这个问题。您可以创建一个结构指针,而不是指向一个函数以及一个描述性字符串,您可以将其设置为您想要的任何内容。我还添加了一个调试姿势,因为您可能不希望这些变量永远被打印。
// Define it like this
typedef struct
{
char *dec_text;
#ifdef _DEBUG_FUNC
void (*action)(char);
#endif
} func_Struct;
// Initialize it like this
func_Struct func[3]= {
#ifdef _DEBUG_FUNC
{"my_Set(char input)",&my_Set}};
{"my_Get(char input)",&my_Get}};
{"my_Clr(char input)",&my_Clr}};
#else
{&my_Set}};
{&my_Get}};
{&my_Clr}};
#endif
// And finally you can use it like this
func[0].action( 0x45 );
#ifdef _DEBUG_FUNC
printf("%s",func.dec_text);
#endif
如果可以指向的函数列表不是太大,或者如果您已经怀疑有一小组函数,您可以打印地址并将它们与执行期间使用的地址进行比较。前任:
typedef void (*simpleFP)();
typedef struct functionMETA {
simpleFP funcPtr;
char * funcName;
} functionMETA;
void f1() {/*do something*/}
void f2() {/*do something*/}
void f3() {/*do something*/}
int main()
{
void (*funPointer)() = f2; // you ignore this
funPointer(); // this is all you see
printf("f1 %p\n", f1);
printf("f2 %p\n", f2);
printf("f3 %p\n", f3);
printf("%p\n", funPointer);
// if you want to print the name
struct functionMETA arrFuncPtrs[3] = {{f1, "f1"}, {f2, "f2"} , {f3, "f3"}};
int i;
for(i=0; i<3; i++) {
if( funPointer == arrFuncPtrs[i].funcPtr )
printf("function name: %s\n", arrFuncPtrs[i].funcName);
}
}
输出:
f1 0x40051b
f2 0x400521
f3 0x400527
0x400521
function name: f2
这种方法也适用于静态函数。
一般来说,没有办法做到这一点。
如果您将相应的代码编译到 DLL/共享库中,您应该能够列出所有入口点并与您获得的指针进行比较。还没有尝试过,但我对 DLL/共享库有一些经验,并且希望它能够工作。这甚至可以实现跨平台工作。
其他人已经提到使用调试符号进行编译,然后您可以尝试找到一种方法从正在运行的应用程序中分析这些符号,类似于调试器所做的事情。但这绝对是专有的,不可移植。
用于kallsyms_lookup_name()
查找 的地址kallsyms_lookup
。
使用指向 , 的函数指针kallsyms_lookup
来调用它。
查看Visual Leak Detector以了解他们如何让调用堆栈打印工作。不过,这假设您使用的是 Windows。
不完全是问题的要求,但在阅读了这里的答案后,我想到了这个解决方案来解决我的类似问题:
/**
* search methods */
static int starts(const char *str, const char *c);
static int fuzzy(const char *str, const char *c);
int (*search_method)(const char *, const char *);
/* asign the search_method and do other stuff */
[...]
printf("The search method is %s\n", search_method == starts ? "starts" : "fuzzy")
如果您的程序需要这个很多,您可以在 XMacro 中定义方法名称以及字符串,并#define X(name, str) ... #undef X
在代码中使用从函数名称中获取相应的字符串。
当我正在寻找一种解决方法以在内核模块中打印出函数的名称时,Alnitak 的回答对我非常有帮助。但是我想提供一件事,那就是您可能想使用 %pS 而不是 %pF 来打印函数的名称,因为 %pF 在某些较新版本的内核(例如 5.10.x)中不再起作用。
你不能。函数名在编译和链接时并未附加到函数上。在这一点上,这完全是内存地址,而不是名称。
没有反光镜,你不会知道自己的样子。您必须使用具有反射功能的语言,例如 C#。