利用堆栈回溯、addr2line和Graphviz生成运行时函数调用图

本文介绍了一种自动生成函数调用图的方法,通过堆栈回溯获取关键函数的调用关系,并使用addr2line工具将内存地址转换为函数名,最终生成可视化的函数调用图。

现在的软件源代码动则千万行,初学者常常感到迷惘,如果能自动生成关键函数的调用关系图,则思路可以清晰许多。如下面这幅图展示了WebKit网页渲染的部分函数执行过程,比单纯地看代码直观多了。


代码下载点这里,包括三个文件backtrace.h、backtrace.c和callgraph.py。

1. 堆栈回溯

比如要分析libwebcore.so里面的函数调用,首先要知道这个库文件在内存中的映射位置。程序启动时调用backtrace_init('libwebcore.so', 10, 65535)(第二个参数表示最大回溯层数,第三个参数表示最大栈帧大小)。函数读取“/proc/self/maps”得到:

  1. 48c00000-49751000 r-xp 00000000 1f:00 607 /system/lib/libwebcore.so  
48c00000-49751000 r-xp 00000000 1f:00 607 /system/lib/libwebcore.so

这一行表示libwebcore.so被映射到内存中48c00000-49751000的位置。为了用addr2line从内存地址得到函数名,对动态库要减去起始位置得到偏移量,对可执行文件不需要减去起始位置。

一般来说,函数栈帧的范围保存在基址寄存器(X86为BP寄存器,ARM为FP寄存器)和栈指针寄存器SP。在调用一个函数时,当前函数的基地址会被保存到栈上。为了让编译器生成标准的堆栈结构,GCC编译X86程序时需加上-fno-omit-frame-pointer参数,编译ARM程序要加上-fno-omit-frame-pointer -mapcs两个参数。

在关键函数开始处调用 backtrace()函数实现堆栈回溯(以下代码只测试了ARM和64位X86,没有测试X84):
  1. void backtrace()  
  2. {  
  3.   if (addr_end <= addr_start)  
  4.     return;  
  5.   void *bp = 0, *ip = 0, *sp = 0, *prev_bp = 0, *prev_ip = 0;  
  6.   
  7.   #if CPU_ARCH == CPU_ARCH_X86  
  8.   __asm__("mov %%ebp, %0;" : "=r"(bp));  
  9.   __asm__("mov %%esp, %0;" : "=r"(sp));  
  10.   #elif CPU_ARCH == CPU_ARCH_X86_64  
  11.   __asm__("movq %%rbp, %0;" : "=r"(bp));  
  12.   __asm__("movq %%rsp, %0;" : "=r"(sp));  
  13.   #elif CPU_ARCH == CPU_ARCH_ARM  
  14.   __asm__("mov %0, fp" : "=r"(bp));  
  15.   __asm__("mov %0, sp" : "=r"(sp));  
  16.   __asm__("mov %0, lr" : "=r"(ip));  
  17.   #else  
  18.   return;  
  19.   #endif  
  20.   int i = 0;  
  21.   while (bp >= sp) {  
  22.     #if CPU_ARCH == CPU_ARCH_X86 || CPU_ARCH == CPU_ARCH_X86_64  
  23.     prev_bp = *((void**)bp);  
  24.     prev_ip = *((void**)bp + 1);  
  25.     #else  
  26.     prev_bp = *((void**)bp - 3);  
  27.     prev_ip = *((void**)bp - 1);  
  28.     #endif  
  29.     if (prev_ip >= addr_start && prev_ip < addr_end  
  30.         && ip >= addr_start && ip < addr_end) {  
  31.       call_table_set((unsigned long)prev_ip - addr_start, (unsigned long)ip - addr_start);  
  32.     }  
  33.     if (abs(bp - prev_bp) > max_frame_size) //函数栈帧太大就认为出错  
  34.       break;  
  35.     i ++;  
  36.     if (i > max_frame_depth)  
  37.       break;  
  38.     bp = prev_bp;  
  39.     ip = prev_ip;  
  40.   }  
  41. }  
void backtrace()
{
  if (addr_end <= addr_start)
    return;
  void *bp = 0, *ip = 0, *sp = 0, *prev_bp = 0, *prev_ip = 0;

  #if CPU_ARCH == CPU_ARCH_X86
  __asm__("mov %%ebp, %0;" : "=r"(bp));
  __asm__("mov %%esp, %0;" : "=r"(sp));
  #elif CPU_ARCH == CPU_ARCH_X86_64
  __asm__("movq %%rbp, %0;" : "=r"(bp));
  __asm__("movq %%rsp, %0;" : "=r"(sp));
  #elif CPU_ARCH == CPU_ARCH_ARM
  __asm__("mov %0, fp" : "=r"(bp));
  __asm__("mov %0, sp" : "=r"(sp));
  __asm__("mov %0, lr" : "=r"(ip));
  #else
  return;
  #endif
  int i = 0;
  while (bp >= sp) {
    #if CPU_ARCH == CPU_ARCH_X86 || CPU_ARCH == CPU_ARCH_X86_64
    prev_bp = *((void**)bp);
    prev_ip = *((void**)bp + 1);
    #else
    prev_bp = *((void**)bp - 3);
    prev_ip = *((void**)bp - 1);
    #endif
    if (prev_ip >= addr_start && prev_ip < addr_end
        && ip >= addr_start && ip < addr_end) {
      call_table_set((unsigned long)prev_ip - addr_start, (unsigned long)ip - addr_start);
    }
    if (abs(bp - prev_bp) > max_frame_size) //函数栈帧太大就认为出错
      break;
    i ++;
    if (i > max_frame_depth)
      break;
    bp = prev_bp;
    ip = prev_ip;
  }
}

call_table_set((unsigned long)prev_ip - addr_start, (unsigned long)ip - addr_start) 是把这个函数调用(prev_ip调用ip)保存到一个哈希表,addr_start和addr_end是libwebcore.so在内存中的映射地址范围。

在程序退出时调用 backtrace_dump('backtrace.out')把这个哈希表的内容保存到文件backtrace.out,文件内容如:
  1. 13b864 1fdee8  
  2. 13b864 1fe000  
  3. 13b864 1ea30c  
  4. 169750 1be190  
  5. 19f13c 66ba78  
  6. 后面省略......  
13b864 1fdee8
13b864 1fe000
13b864 1ea30c
169750 1be190
19f13c 66ba78
后面省略......

2. 生成函数调用图
调用脚本 callgraph.py arm-eabi-addr2line ./out/....../lib/libwebcore.so backtrace.out callgraph.png
脚本处理流程如下:

对backtrace.out文件的每一个偏移量调用addr2line得到函数名:

  1. arm-eabi-addr2line -f -C -e ./out/....../lib/libwebcore.so 13b864  
  2. WebCore::Timer<WebCore:PluginStream>::fired()  
  3. diy-fp.cc:0  
arm-eabi-addr2line -f -C -e ./out/....../lib/libwebcore.so 13b864
WebCore::Timer<WebCore:PluginStream>::fired()
diy-fp.cc:0

根据函数名和其调用关系生成dot脚本文件:

  1. digraph G {  
  2. node0 [ label="android::RecordContent" ];  
  3. node1 [ label="GraphicsLayerAndroid::repaint" ];  
  4. node2 [ label="RenderLayer::paint" ];  
  5. node3 [ label="android::CreateFrame" ];  
  6. node4 [ label="PicturePile::updatePicturesIfNeeded" ];  
  7. 省略......  
  8. node0 -> node30  
  9. node26 -> node39  
  10. node22 -> node7  
  11. node8 -> node8  
  12. node14 -> node4  
  13. 省略......  
  14. }  
digraph G {
node0 [ label="android::RecordContent" ];
node1 [ label="GraphicsLayerAndroid::repaint" ];
node2 [ label="RenderLayer::paint" ];
node3 [ label="android::CreateFrame" ];
node4 [ label="PicturePile::updatePicturesIfNeeded" ];
省略......
node0 -> node30
node26 -> node39
node22 -> node7
node8 -> node8
node14 -> node4
省略......
}

转换dot文件生成函数调用图:
  1. dot -Tpng -Nshape=box -Nfontsize=10 callgraph.dot -o callgraph.png  
dot -Tpng -Nshape=box -Nfontsize=10 callgraph.dot -o callgraph.png


内容概要:本文聚焦于“基于改进电流解耦与电位平衡的T型三电平逆变器低电压穿越研究”,通过Simulink仿真实现,系统探讨了T型三电平逆变器在电网电压跌落情况下的低电压穿越(LVRT)能力。研究提出了一种改进的电流解耦控制策略与中点电位平衡控制方法,旨在提升逆变器在故障工况下的动态响应性能、系统稳定性及功率调节能力。通过构建详细的Simulink电磁暂态模型,验证了所提控制策略在抑制负序电流、维持直流侧中点电压稳定、实现快速有功与无功功率协调控制方面的有效性,为新能源发电系统并网的可靠运行提供了理论支持技术路径。; 适合人群:具备电力电子、自动控制及电力系统相关专业知识,熟悉MATLAB/Simulink仿真环境,从事新能源并网、逆变器控制策略研究、电能质量分析或电力系统仿真的研究生、科研人员及工程技术人员。; 使用场景及目标:① 深入理解T型三电平逆变器在低电压穿越过程中的控制难点,掌握改进电流解耦与中点电位平衡的核心控制原理;② 学习并实践在Simulink中搭建高保真度逆变器仿真模型的方法,掌握双环控制、负序分量处理、中点电位反馈调节等关键技术环节;③ 为开展相关课题研究、撰写学术论文或进行工程化设计提供可复现的仿真案例与理论依据。; 阅读建议:建议结合文中所述控制算法,动手搭建并调试Simulink仿真模型,重点关注电流内环与电压外环的参数整定、中点电位波动的抑制效果以及不同电网故障条件下系统的响应特性,从而深入理解控制策略与系统动态性能之间的内在联系。
内容概要:本文研究了基于阶跃响应的V-Tiger自动增益调整PID控制器优化方法,提出了一种结合阶跃响应特征采集与多目标协同优化的增益整定策略,并引入自适应迭代校正机制以提升控制精度与动态响应性能。通过Matlab代码实现了该算法,系统阐述了PID控制的关键性能指标与V-Tiger控制器的技术特点,详细设计了从响应数据提取、参数初设、优化迭代到性能评估的全流程方案。仿真实验结果表明,该方法在调节间、超调量、稳态误差及抗干扰能力方面均优于传统整定方式,显著提升了PID控制器在复杂工况下的适应性与鲁棒性。; 适合人群:具备自动控制理论基础Matlab编程能力,从事控制工程、自动化、电气工程及相关领域的科研人员、高校研究生及工业界研发工程师。; 使用场景及目标:①应用于工业过程控制中PID参数的自动化整定,减少人工调试成本;②提升控制系统对非线性、变对象的适应能力;③为先进控制算法的教学演示与科研验证提供可复现的仿真平台技术范例。; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,重点关注阶跃响应特征提取方法、多目标优化函数的设计逻辑以及自适应校正环节的实现机制,深入理解各模块之间的协同关系,并可通过修改被控对象模型进一步拓展算法适用范围。
内容概要:本文聚焦于风电出力不确定性带来的挑战,研究了基于分布鲁棒优化的机组组合问题,旨在提升电力系统在高比例风电接入背景下的调度可靠性与经济性。通过构建分布鲁棒优化模型,有效处理风电预测误差与波动性,在缺乏精确概率分布的前提下,仅依靠有限的统计信息(如均值、方差)构建不确定性集合,增强了调度方案的鲁棒性。文中系统阐述了模型的数学形式化过程,包括目标函数的设计、系统运行约束的建模以及不确定性集的构造,并采用对偶转化等方法将复杂的鲁棒模型转化为可求解的确定性优化问题,最终利用Matlab进行仿真验证,证明了该方法在多种运行场景下均能提供优于传统确定性或随机优化方法的调度决策。; 适合人群:具备电力系统分析、运筹学基础及Matlab编程能力,从事新能源并网调度、电力系统优化、鲁棒优化理论研究的研究生、高校科研人员及电力行业相关工程技术开发者。; 使用场景及目标:①应用于含大规模风电的电力系统日前机组组合制定,提升调度方案对不确定性的适应能力;②为电力市场环境下应对可再生能源波动提供科学决策工具;③作为分布鲁棒优化理论在能源系统中应用的教学案例,支撑相关课程设计与科研项目开发。; 阅读建议:建议结合提供的Matlab代码逐模块分析模型实现逻辑,重点理解不确定性集建模与对偶转化技巧,宜在掌握线性规划与凸优化基础后深入研读,并可通过调整风电不确定性参数或测试不同系统规模来进一步探究模型性能边界。
内容概要:本文提出了一种面向综合能源系统的算力-电力-热力联合优化调度策略,旨在实现多能源耦合系统中计算资源、电能与热能的协同高效利用。通过构建联合优化模型,综合考虑数据中心的算力需求与能耗特性,将其作为柔性负荷纳入综合能源系统调度框架,结合电力与热力系统的运行约束,利用Matlab进行仿真求解,实现了系统运行成本最小化与能源利用效率最大化的双重目标。研究涵盖了模型构建、算法求解及仿真验证全过程,体现了多能流协同优化的先进理念,并为数据中心参与电网互动提供了理论支撑与技术路径。; 适合人群:具备一定电力系统、能源工程或自动化背景,熟悉Matlab编程,从事综合能源系统、数据中心节能或优化调度相关研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①用于科研学习,理解算力-电力-热力耦合系统的建模方法与优化思路;②为数据中心参与电网互动、提升综合能源系统灵活性提供技术参考与仿真工具支持;③拓展至“东数西算”等国家重大工程中的能源协同调度研究。; 阅读建议:读者应结合文中模型公式与Matlab代码对照学习,重点关注目标函数构建、约束条件设定及求解流程,建议自行复现仿真结果以加深理解,并可进一步探索引入更多不确定性因素或采用智能优化算法进行改进。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值