用什么方法可以记录各个函数的调用呢?编译器可以帮我们在每个函数的调用开始和结束时,插入钩子函数。
基本用法
添加编译选项-finstrument-functions
,同时实现两个函数。
void __cyg_profile_func_enter(void *this_func, void *call_site);
void __cyg_profile_func_exit(void *this_func, void *call_site);
那么每个函数在开始时和结束时,都会分别调用__cyg_profile_func_enter和__cyg_profile_func_exit。
如果这两个函数在编译时,也添加了-finstrument-functions
,那么添加__attribute__((__no_instrument_function__))
,如下
void __attribute__((__no_instrument_function__))
__cyg_profile_func_enter(void *this_func, void *call_site);
void __attribute__((__no_instrument_function__))
__cyg_profile_func_exit(void *this_func, void *call_site);
- g++编译器
注意添加extern “C”,否则提示链接不到。
cmake工程
- 新建子目录func_tracker
CMakeLists.txt如下
cmake_minimum_required(VERSION 3.6.0)
#函数追踪模块
# 配置源码和编译目标
file(GLOB_RECURSE func_tracker_src
FuncTracker.cpp
)
set(TARGET func_tracker)
add_library(${TARGET} STATIC ${func_tracker_src})
add_definitions(-DLOG_TAG="func_tracker")
target_include_directories(
${TARGET} PRIVATE
)
target_link_libraries(
${TARGET}
)
FuncTracker.cpp
#include <android/log.h>
#define log_track(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#ifdef __cplusplus
extern "C"{
#endif
void __cyg_profile_func_enter(void *this_func, void *call_site)
{
log_track("[func-tracker] %p\n",this_func);
}
void __cyg_profile_func_exit(void *this_func, void *call_site)
{
}
#ifdef __cplusplus
}
#endif
- 需要跟踪的模块
在需要跟踪的模块的CMakeLists.txt中,添加
add_definitions(-finstrument-functions) #函数追踪
target_link_libraries(
${TARGET}
func_tracker
)
扩展
void *this_func
只是函数在这次运行中的虚拟内存地址,怎样由地址得到函数具体信息呢。
有如下几个方法
- dladdr
dladdr函数可以由地址获取具体的函数信息,但这个函数的内部工作原理,是根据地址找动态库,再在动态库中找距离最近的地址对应的符号,在稍微大的工程中,造成的卡顿明显。
#include <dlfcn.h>
void __cyg_profile_func_enter(void *this_func, void *call_site)
{
Dl_info info;
dladdr(this_func, &info);
}
- dl_iterate_phdr
在程序刚开始运行时,用dl_iterate_phdr获取每个动态库在内存映射的基地址,对于后续的分析,可以交给第三方程序。
#define _GNU_SOURCE
#include <link.h>
#include <stdlib.h>
#include <stdio.h>
static int
callback(struct dl_phdr_info *info, size_t size, void *data)
{
//info->dlpi_name 动态库路径
//info->dlpi_addr 动态库在内存中的基地址
log_track("[func-tracker-lib] %s, %p\n",info->dlpi_name,reinterpret_cast<void*>(info->dlpi_addr));
return 0;
}
int
main(int argc, char *argv[])
{
dl_iterate_phdr(callback, NULL);
exit(EXIT_SUCCESS);
}
弊端
由于被追踪的模块,经常有模板的使用,比如最基本的vector<int>
,编译时一旦加入追踪选项,vector的构造函数也会添加追踪代码,导致追踪了很多底层代码。而且在__cyg_profile_func_enter中使用了vector<int>
这些模板类,又会造成递归调用,大坑。