Linux GCC编程技巧 – Wrap拦截标准库与系统函数
1 Instrument Function
1.1 作用
统计函数调用的进入和退出,以及被谁调用,在分析问题时是非常有用的。例如,内存泄漏时,可以统计 malloc/free
函数的调用,然后分析持续增长的调用函数,一般就是内存泄漏分配点。
使用方法是 finstrument-functions
参数编译,提供两个函数统计调用。这两个函数的编译不能打开 finstrument-functions
(否则循环调用导致堆栈溢出)。使用 __attribute__((no_instrument_function))
关闭这两个函数的注入是有意义的。
void __attribute__((no_instrument_function)) __cyg_profile_func_enter (void *this_fn, void *call_site) { // Do enter stuff } void __attribute__((no_instrument_function)) __cyg_profile_func_exit (void *this_fn, void *call_site) { // Do exit stuff }
1.2 Example
#define _GNU_SOURCE 1 #include <dlfcn.h> #include <stdio.h> #include <stdlib.h> #include <string.h> void* __attribute__((noinline)) my_malloc(size_t size) { return malloc(size); } void __attribute__((noinline)) func() { printf(">>> memory leak function addr=%p !\n", func); void *leak = my_malloc(100); } int main(int argc, char *argv[]) { func(); return 0; } static void __attribute__((no_instrument_function)) print_info(const char *prefix, void *this_fn, void *call_fn) { Dl_info this_info; Dl_info call_info; int this_ret, call_ret; memset(&this_info, 0, sizeof(this_info)); memset(&call_info, 0, sizeof(call_info)); this_ret = dladdr(this_fn, &this_info); call_ret = dladdr(call_fn, &call_info); printf("%s: this_fn: %p(%s in %s), call_fn:%p(%s in %s)\n", prefix, this_fn, this_info.dli_sname, this_info.dli_fname, call_fn, call_info.dli_sname, call_info.dli_fname); } void __attribute__((no_instrument_function)) __cyg_profile_func_enter (void *this_fn, void *call_fn) { print_info("Enter", this_fn, call_fn); } void __attribute__((no_instrument_function)) __cyg_profile_func_exit (void *this_fn, void *call_fn) { print_info("Exit ", this_fn, call_fn); }
1.3 gcc docs
Docs1: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
Docs2: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
Generate instrumentation calls for entry and exit to functions. Just after function entry and just before function exit, the following profiling functions are called with the address of the current function and its call site. (On some platforms, __builtin_return_address does not work beyond the current function, so the call site information may not be available to the profiling functions otherwise.)
void __cyg_profile_func_enter (void *this_fn, void *call_site); void __cyg_profile_func_exit (void *this_fn, void *call_site);
The first argument is the address of the start of the current function, which may be looked up exactly in the symbol table.
This instrumentation is also done for functions expanded inline in other functions. The profiling calls indicate where, conceptually, the inline function is entered and exited. This means that addressable versions of such functions must be available. If all your uses of a function are expanded inline, this may mean an additional expansion of code size. If you use extern inline in your C code, an addressable version of such functions must be provided. (This is normally the case anyway, but if you get lucky and the optimizer always expands the functions inline, you might have gotten away without providing static copies.)
A function may be given the attribute no_instrument_function, in which case this instrumentation is not done. This can be used, for example, for the profiling functions listed above, high-priority interrupt routines, and any functions from which the profiling functions cannot safely be called (perhaps signal handlers, if the profiling routines generate output or allocate memory).
2 使用wrap拦截系统函数
下例使用wrap包装malloc,我们可以添加一些统计,来确认是否有内存泄漏问题。
#include <stdio.h> #include <stdlib.h> #include <string.h> unsigned long mcount = 0; extern void * __real_malloc(size_t size); extern void __real_free(void *ptr); void *__wrap_malloc(size_t size) { void *ptr = __real_malloc(size); if (ptr) ++mcount; printf("wrap malloc ptr=%p, size=%zu, count=%lu\n", ptr, size, mcount); return ptr; } void __wrap_free(void *ptr) { __real_free(ptr); if (ptr) --mcount; printf("wrap free ptr=%p, count=%lu\n", ptr, mcount); } int main(int argc, char *argv[argc]) { int *pi; pi = malloc(sizeof(*pi)*10); memset(pi, 0, sizeof(*pi)*10); free(pi); return 0; }
Compile:
~$ gcc wrap.c -Wl,-wrap,malloc,-wrap,free