c++函数调用追踪

用什么方法可以记录各个函数的调用呢?编译器可以帮我们在每个函数的调用开始和结束时,插入钩子函数。

基本用法

添加编译选项-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>这些模板类,又会造成递归调用,大坑。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值