资讯专栏INFORMATION COLUMN

C++源码调用图生成器实现

oysun / 2316人阅读

摘要:前言之前受知乎用户启发,写了个源码的调用图生成器,可以以图示法显示函数的调用关系,代码放在了仓库里,仅供参考主要思路利用的的注入选项,得到每个函数的调用地址信息,生成一个文件,然后利用和将函数名及其所在源码位置从地址中解析出来,从而得到

前言

之前受知乎用户mailto1587启发,写了个C++源码的调用图生成器,可以以图示法显示C++函数的调用关系,
代码放在了github仓库里,仅供参考:
CodeSnippet/python/SRCGraphviz/c++ at master · Cheukyin/CodeSnippet · GitHub

主要思路

利用gcc/g++-finstrument-functions的注入选项,
得到每个函数的调用地址信息,生成一个trace文件
然后利用addr2linec++filt函数名及其所在源码位置从地址中解析出来,
从而得到程序的Call Stack
然后用pygraphviz画出来

使用示例

比如我现在有A.hppB.hppC.hppABCTest.cpp这几个文件,
我想看他们的Call Graph

源码如下:

然后按下面编译(instrument.c在上面github地址中可以下载,用于注入地址信息):
g++ -g -finstrument-functions -O0 instrument.c ABCTest.cpp -o test
然后运行程序,得到trace.txt
输入shell命令./test
最后
输入shell命令python CallGraph.py trace.txt test
弹出一张Call Graph

图上标注含义:

绿线表示程序启动后的第一次调用

红线表示进入当前上下文的最后一次调用

每一条线表示一次调用,#符号后面的数字是序号,at XXX表示该次调用发生在这个文件(文件路径在框上方)的第几行

在圆圈里,XXX:YYYYYY是调用的函数名,XXX表示这个函数是在该文件的第几行被定义的

获取C/C++调用关系

利用-finstrument-functions编译选项,
可以让编译器在每个函数的开头和结尾注入__cyg_profile_func_enter__cyg_profile_func_exit
这两个函数的实现由用户定义

在本例中,只用到__cyg_profile_func_enter,定义在instrument.c中,
其函数原型如下:
void __cyg_profile_func_enter (void *this_fn, void *call_site);
其中this_fn为 被调用的地址,call_site为 调用方的地址

显然,假如我们把所有的 调用方和被调用方的地址 都打印出来,
就可以得到一张完整的运行时Call Graph

因此,我们的instrument.c实现如下:

/* Function prototypes with attributes */
void main_constructor( void )
    __attribute__ ((no_instrument_function, constructor));

void main_destructor( void )
    __attribute__ ((no_instrument_function, destructor));

void __cyg_profile_func_enter( void *, void * )
    __attribute__ ((no_instrument_function));

void __cyg_profile_func_exit( void *, void * )
    __attribute__ ((no_instrument_function));

static FILE *fp;

void main_constructor( void )
{
  fp = fopen( "trace.txt", "w" );
  if (fp == NULL) exit(-1);
}

void main_deconstructor( void )
{
  fclose( fp );
}

void __cyg_profile_func_enter( void *this_fn, void *call_site )
{
    /* fprintf(fp, "E %p %p
", (int *)this_fn, (int *)call_site); */
    fprintf(fp, "%p %p
", (int *)this_fn, (int *)call_site);
}

其中main_constructor在 调用main 前执行,main_deconstructor在调用main后执行,
以上几个函数的作用就是 将所有的 调用方和被调用方的地址 写入trace.txt

然而,现在有一个问题,就是trace.txt中保存的是地址,我们如何将地址翻译成源码中的符号?
答案就是用addr2line

以上面ABCTest.cpp工程为例,比如我们现在有地址0x400974,输入以下命令
addr2line 0x400aa4 -e a.out -f
结果为

_ZN1A4AOneEv
/home/cheukyin/PersonalProjects/CodeSnippet/python/SRCGraphviz/c++/A.hpp:11

第一行该地址所在的函数名,第二行为函数所在的源码位置

然而,你一定会问,_ZN1A4AOneEv是什么鬼?
为实现重载、命名空间等功能,因此C++name mangling,因此函数名是不可读的

我们需要利用c++filt作进一步解析:
输入shell命令 addr2line 0x400aa4 -e a.out -f | c++filt
结果是不是就清晰很多:

A::AOne()
/home/cheukyin/PersonalProjects/CodeSnippet/python/SRCGraphviz/c++/A.hpp:11

注意这个结果中包含了函数名、函数所在文件和行号

调用图渲染

经过上面的步骤,我们已经可以把所有的(调用方, 被调用方)对分析出来了,相当于获取到调用图所有的节点和边,
最后可以用pygraphviz将 每一条调用关系 画出来即可,代码用python实现在 CallGraph.py 中

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38409.html

相关文章

  • 基于Linux环境搭建Nginx+uWSGI+Python框架方法介绍

    摘要:目前在和平台下使用最广泛的免费服务器有和。涉及到普通用户执行命令时权限不足的问题,可在命令前增加指令解决。是基于二进制的线路协议,与协议作用相同,但属于服务器自有协议是服务器,它实现了协议等协议安装。 前言 浏览器上网的过程简单来说即是浏览器从服务器中获取网站信息,经过渲染后将效果呈现给用户。这里侧重介绍下在幕后默默工作着的服务器。Web服务器是运行在物理服务器上的一个程序,永久地等待...

    spacewander 评论0 收藏0
  • 基于Linux环境搭建Nginx+uWSGI+Python框架方法介绍

    摘要:目前在和平台下使用最广泛的免费服务器有和。涉及到普通用户执行命令时权限不足的问题,可在命令前增加指令解决。是基于二进制的线路协议,与协议作用相同,但属于服务器自有协议是服务器,它实现了协议等协议安装。 前言 浏览器上网的过程简单来说即是浏览器从服务器中获取网站信息,经过渲染后将效果呈现给用户。这里侧重介绍下在幕后默默工作着的服务器。Web服务器是运行在物理服务器上的一个程序,永久地等待...

    whataa 评论0 收藏0
  • javascript引擎——V8

    摘要:类将源代码解释并构建成抽象语法树,使用类来创建它们,并使用类来分配内存。类抽象语法树的访问者类,主要用来遍历抽象语法树。在该函数中,先使用类来生成抽象语法树再使用类来生成本地代码。 通过上一篇文章,我们知道了JavaScript引擎是执行JavaScript代码的程序或解释器,了解了JavaScript引擎的基本工作原理。我们经常听说的JavaScript引擎就是V8引擎,这篇文章我们...

    luoyibu 评论0 收藏0
  • JavaScript 工作原理之六-WebAssembly 对比 JavaScript 及其使用场景

    摘要:现在,我们将会剖析的工作原理,而最重要的是它和在性能方面的比对加载时间,执行速度,垃圾回收,内存使用,平台访问,调试,多线程以及可移植性。目前,是专门围绕和的使用场景设计的。目前不支持多线程。 原文请查阅这里,略有改动,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第六章...

    jay_tian 评论0 收藏0

发表评论

0条评论

oysun

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<