I am using gnu tool chain. How can I, at run time, find caller of a function? i.e for example function B() gets called by many functions using function pointers. Now, whenever B gets called, I want to print the callers name. I need this for debugging a certain issue.
我正在使用gnu工具链。如何在运行时找到函数的调用者?我。例如,函数B()通过函数指针被许多函数调用。现在,无论何时调用B,我都要打印调用者的名字。我需要这个来调试某个问题。
3 个解决方案
#1
18
If you're using GNU, you can use the backtrace functions. There's an example of the use on that man page.
如果您正在使用GNU,您可以使用回溯函数。在手册页上有一个使用的例子。
#2
9
The code location of the call to your function is kept by gcc in the __builtin_return_address()
intrinsic. To retrieve the name for that, you have to parse the program's symbol table; while that is possible to do, via dladdr()
, there are limits to this:
调用函数的代码位置由gcc保存在__builtin_return_address()内部。要检索它的名称,您必须解析程序的符号表;虽然可以通过dladdr()实现这一点,但是有一些限制:
- it might not be safe in all contexts to call
backtrace()
/dladdr()
(like, from signal handlers, or concurrently in a multithreaded program, or from contexts where you can't callmalloc()
.). - 在所有上下文中调用backtrace()/dladdr()可能都不安全(比如,从信号处理程序中调用,或者在多线程程序中并发调用,或者从不能调用malloc()的上下文中调用)。
- the operation is not instant but might require putting the calling thread to sleep; this is a bad thing if at the point of the call any locks are held.
- 操作不是即时的,但可能需要将调用线程放入休眠状态;如果在调用时持有任何锁,这是一件坏事。
- there's limits to how well "resolvable" a return address is into a name; e.g. if there are inlined functions calling your function then the caller of your caller would show up (the
backtrace()
manpage states this as well, as does the one fordladdr()
in the "BUGS" section). - 返回地址在名称中“可分解”的程度是有限的;例如,如果有内联函数调用您的函数,那么调用者的调用者就会出现(backtrace() manpage也会显示这一点,就像“BUGS”一节中的dladdr()一样。
- In C++, there's the additional challenge of calls through con/destructors/initializers, where the "caller" of your function may end up being some compiler-generated metacode, and the actual place you're interested in could be elsewhere (caller of caller, or even deeper in the call stack).
- 在c++中,还存在通过con/析构函数/初始化器进行调用的额外挑战,在这种情况下,函数的“调用者”最终可能是一些编译器生成的元ode,而您感兴趣的实际位置可能在其他地方(调用者的调用者,或者在调用堆栈中更深入的地方)。
It's often a better way to decouple tracing and function name resolving; i.e. just output the return addresses (as hex / binary) and then postprocess the resulting log against a symbol table retrieved when the program was running.
它通常是一种更好的解耦跟踪和函数名解析的方法;例如,只需输出返回地址(如十六进制/二进制),然后针对程序运行时检索到的符号表对结果日志进行后处理。
#3
5
Another method, pointed out by Vasil Dimov in answer to a similar question, is to replace the function call with a wrapper macro that reports or passes in the calling function name. This will work with inline functions, where backtrace won't. On the other hand it won't work if you call the function by reference, or otherwise take its address.
Vasil Dimov在回答类似问题时指出的另一种方法是用一个包装器宏代替函数调用,该宏报告或传递调用函数名。这将与内联函数一起工作,而回溯不会。另一方面,如果您通过引用调用函数,或者以其他方式获取它的地址,它将无法工作。
For example this:
例如:
int B(int x){
...
}
could become:
可能成为:
int _B(int x, char *caller){
printf("caller is %s\n", caller);
...
}
#define B(x) _B((x), __func__)
and every call to B() will print the callers name. Vasil Dimov constructs it differently, printing the name directly in the macro and leaving the function unchanged.
每次调用B()都会打印调用者的名字。Vasil Dimov以不同的方式构造它,直接在宏中打印名称并保持函数不变。
#1
18
If you're using GNU, you can use the backtrace functions. There's an example of the use on that man page.
如果您正在使用GNU,您可以使用回溯函数。在手册页上有一个使用的例子。
#2
9
The code location of the call to your function is kept by gcc in the __builtin_return_address()
intrinsic. To retrieve the name for that, you have to parse the program's symbol table; while that is possible to do, via dladdr()
, there are limits to this:
调用函数的代码位置由gcc保存在__builtin_return_address()内部。要检索它的名称,您必须解析程序的符号表;虽然可以通过dladdr()实现这一点,但是有一些限制:
- it might not be safe in all contexts to call
backtrace()
/dladdr()
(like, from signal handlers, or concurrently in a multithreaded program, or from contexts where you can't callmalloc()
.). - 在所有上下文中调用backtrace()/dladdr()可能都不安全(比如,从信号处理程序中调用,或者在多线程程序中并发调用,或者从不能调用malloc()的上下文中调用)。
- the operation is not instant but might require putting the calling thread to sleep; this is a bad thing if at the point of the call any locks are held.
- 操作不是即时的,但可能需要将调用线程放入休眠状态;如果在调用时持有任何锁,这是一件坏事。
- there's limits to how well "resolvable" a return address is into a name; e.g. if there are inlined functions calling your function then the caller of your caller would show up (the
backtrace()
manpage states this as well, as does the one fordladdr()
in the "BUGS" section). - 返回地址在名称中“可分解”的程度是有限的;例如,如果有内联函数调用您的函数,那么调用者的调用者就会出现(backtrace() manpage也会显示这一点,就像“BUGS”一节中的dladdr()一样。
- In C++, there's the additional challenge of calls through con/destructors/initializers, where the "caller" of your function may end up being some compiler-generated metacode, and the actual place you're interested in could be elsewhere (caller of caller, or even deeper in the call stack).
- 在c++中,还存在通过con/析构函数/初始化器进行调用的额外挑战,在这种情况下,函数的“调用者”最终可能是一些编译器生成的元ode,而您感兴趣的实际位置可能在其他地方(调用者的调用者,或者在调用堆栈中更深入的地方)。
It's often a better way to decouple tracing and function name resolving; i.e. just output the return addresses (as hex / binary) and then postprocess the resulting log against a symbol table retrieved when the program was running.
它通常是一种更好的解耦跟踪和函数名解析的方法;例如,只需输出返回地址(如十六进制/二进制),然后针对程序运行时检索到的符号表对结果日志进行后处理。
#3
5
Another method, pointed out by Vasil Dimov in answer to a similar question, is to replace the function call with a wrapper macro that reports or passes in the calling function name. This will work with inline functions, where backtrace won't. On the other hand it won't work if you call the function by reference, or otherwise take its address.
Vasil Dimov在回答类似问题时指出的另一种方法是用一个包装器宏代替函数调用,该宏报告或传递调用函数名。这将与内联函数一起工作,而回溯不会。另一方面,如果您通过引用调用函数,或者以其他方式获取它的地址,它将无法工作。
For example this:
例如:
int B(int x){
...
}
could become:
可能成为:
int _B(int x, char *caller){
printf("caller is %s\n", caller);
...
}
#define B(x) _B((x), __func__)
and every call to B() will print the callers name. Vasil Dimov constructs it differently, printing the name directly in the macro and leaving the function unchanged.
每次调用B()都会打印调用者的名字。Vasil Dimov以不同的方式构造它,直接在宏中打印名称并保持函数不变。