引言
前面的文章《Windows上获取当前调用堆栈信息,StackWalker的C语言实现》实现了如何通过编程的方式获取调用堆栈的详细信息。本文接下来说明如何将分析得到的结果用图形化的方式展现出来。为此选用了功能强大的文本处理工具php与编程式绘图工具graphviz。
实现步骤
具体的实现步骤如下:
1.用StackWalker生成调用堆栈的信息,并保存到文本文件callStackResult.txt
2.用php分析第1步得到的中间结果文件callStackResult.txt,将文本转换为php的数组对象
3.用php动态替换dot模板文件,得到表示图形的dot文件
4.用dot命令渲染dot格式的源文件,得到svg图形,并在浏览器上显示。
图形效果
下面是调用堆栈的图形化显示效果
具体实例
为了后续更好的分析,对《Windows上获取当前调用堆栈信息,StackWalker的C语言实现》一文中的程序做了少许改进。
其中调用堆栈的形成过程如下所示
void Func5()
{
showCurrentCallstack();
}
void Func4()
{
Func5();
}
void Func3()
{
Func4();
}
void Func2()
{
Func3();
}
void Func1()
{
Func2();
}
void StackWalkTest()
{
Func1();
}
int main(int argc, char* argv[])
{
printf("\n\n\nShow a simple callstack of the current thread:\n\n\n");
StackWalkTest();
return 0;
}
从以上源码可以很容易地看出调用过程
main->StackWalkTest->Func1->Func2->Func3->Func4->Func5。可见前述的图形效果展示中的调用过程与这里是完全一致的。
由showCurrentCallstack 生成的调用堆栈的输出结果文件为:
callStackResult.txt
*********call stack begin***********:
StackEntry->d:\编程学习\c语言相关\编程查看调用堆栈\stackwalker_c\stackwalker_simple\stackwalker.c (801): StackWalker_ShowCallstack
StackEntry->d:\编程学习\c语言相关\编程查看调用堆栈\stackwalker_c\stackwalker_simple\stackwalker.c (753): StackWalker_ShowCurrentCallstack
StackEntry->d:\编程学习\c语言相关\编程查看调用堆栈\stackwalker_c\stackwalker_simple\stackwalker.c (1228): showCurrentCallstack
StackEntry->d:\编程学习\c语言相关\编程查看调用堆栈\stackwalker_c\stackwalker_simple\main.c (33): Func5
StackEntry->d:\编程学习\c语言相关\编程查看调用堆栈\stackwalker_c\stackwalker_simple\main.c (37): Func4
StackEntry->d:\编程学习\c语言相关\编程查看调用堆栈\stackwalker_c\stackwalker_simple\main.c (41): Func3
StackEntry->d:\编程学习\c语言相关\编程查看调用堆栈\stackwalker_c\stackwalker_simple\main.c (45): Func2
StackEntry->d:\编程学习\c语言相关\编程查看调用堆栈\stackwalker_c\stackwalker_simple\main.c (49): Func1
StackEntry->d:\编程学习\c语言相关\编程查看调用堆栈\stackwalker_c\stackwalker_simple\main.c (54): StackWalkTest
StackEntry->d:\编程学习\c语言相关\编程查看调用堆栈\stackwalker_c\stackwalker_simple\main.c (60): main
StackEntry->f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c (278): __tmainCRTStartup
StackEntry->f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c (189): mainCRTStartup
DbgHelpErr-> SymGetLineFromAddr64, GetLastError: 487 (Address: 77608674)
InvalidName->77608674 (KERNEL32): (filename not available): BaseThreadInitThunk
DbgHelpErr-> SymGetLineFromAddr64, GetLastError: 487 (Address: 77975E17)
InvalidName->77975E17 (ntdll): (filename not available): RtlGetAppContainerNamedObjectPath
DbgHelpErr-> SymGetLineFromAddr64, GetLastError: 487 (Address: 77975DE7)
InvalidName->77975DE7 (ntdll): (filename not available): RtlGetAppContainerNamedObjectPath
从上面的例子可以看出,除了main->StackWalkTest->Func1->Func2->Func3->Func4->Func5 这一个主要调用过程之外,还有一些我们并不太关心的内部函数的调用。在用php进行处理时,会将这一些内部函数过滤掉。
下面是相应的php处理函数,将以上的文本文件转换为一个php的数组,存放调用堆栈中的源文件名与函数名
//分析调用堆栈的输出文本,返回保存有文件名与函数名的数组
function parseCallStackResult($fileName){
$resultArray=[];
//逐行读取文本文件
$fin = fopen($fileName, "r");
if ($fin) {
$preFuncName = '';
//是否进入了有效区间
$validRange = false;
while (!feof($fin)) {
$line = fgets($fin, 4096);
//只处理堆栈入口
if(substr($line, 0,10)=='StackEntry'){
$tempLine = substr($line, 12);
//echo $tempLine;
//用":"分隔开的部分切分为数组
$tempArray = explode(':', $tempLine);
//提取文件名
$fileName = basename($tempArray[1]);
//提取函数名
$funcName = trim($tempArray[2]);
//echo 'test:',$preFuncName,',',$funcName,"\n";
if($preFuncName =='showCurrentCallstack'){
$validRange = true;
}
if($funcName =='__tmainCRTStartup'){
$validRange = false;
}
if($validRange){
//echo "t:",$fileName,',',$funcName,"\n";
$resultArray[]=['fileName'=>$fileName,'funcName'=>$funcName];
}
$preFuncName = $funcName;
}
}
fclose($fin);
}
return $resultArray;
}
下面是 为了生成相应的dot文件而准备的模板文件
//函数调用栈
digraph G
{
rankdir = TB;
node [shape=record, width=.1, height=.1];
node[ height=.1 ];
<?php
for ($i = 0;$i<count($callStackArray);$i++){
?>
node<?=$i?> [label = "{<e> <?=$callStackArray[$i]['funcName']?> | <p> <?=$callStackArray[$i]['fileName']?> }" ];
<?php
}
?>
<?php
for ($i = 0;$i<count($callStackArray)-1;$i++){
?>
node<?=$i?>:ps-> node<?=$i+1?>:en;
<?php
}
?>
}
下面是用php动态替换模板,得到dot文件,并渲染为svg图形的功能代码
//分析调用堆栈的输出文本,生成相应的.dot文件,并由graphviz渲染得到svg图形
$fileName = "callStackResult.txt";
$callStackArray=array_reverse(parseCallStackResult($fileName));
//得到由模板生成的dot文件
ob_start();
require_once('callStackTemplate.dot');
$content = ob_get_contents();
ob_end_clean();
file_put_contents('callStack.dot', $content) ;
//执行dot命令,生成svg图形
exec('dot -Tsvg callStack.dot -o callStack.html');
//输出图形内容
echo file_get_contents("callStack.html");
下面是php动态替换模板后得到dot图形文件
//函数调用栈
digraph G
{
rankdir = TB;
node [shape=record, width=.1, height=.1];
node[ height=.1 ];
node0 [label = "{<e> main | <p> main.c (60) }" ];
node1 [label = "{<e> StackWalkTest | <p> main.c (54) }" ];
node2 [label = "{<e> Func1 | <p> main.c (49) }" ];
node3 [label = "{<e> Func2 | <p> main.c (45) }" ];
node4 [label = "{<e> Func3 | <p> main.c (41) }" ];
node5 [label = "{<e> Func4 | <p> main.c (37) }" ];
node6 [label = "{<e> Func5 | <p> main.c (33) }" ];
node0:ps-> node1:en;
node1:ps-> node2:en;
node2:ps-> node3:en;
node3:ps-> node4:en;
node4:ps-> node5:en;
node5:ps-> node6:en;
}
感兴趣的朋友可以与模板文件对比一下。
最后执行dot -Tsvg callStack.dot -o callStack.html 就可以得到最终的svg图形文件 callStack.htm
浏览器上的显示效果已经在前面展示过,此处看一下生成的svg的源文件内容:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: G Pages: 1 -->
<svg width="110pt" height="553pt"
viewBox="0.00 0.00 110.00 553.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 549)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-549 106,-549 106,4 -4,4"/>
<!-- node0 -->
<g id="node1" class="node"><title>node0</title>
<polygon fill="none" stroke="black" points="11,-498.5 11,-544.5 91,-544.5 91,-498.5 11,-498.5"/>
<text text-anchor="middle" x="51" y="-529.3" font-family="Times New Roman,serif" font-size="14.00">main</text>
<polyline fill="none" stroke="black" points="11,-521.5 91,-521.5 "/>
<text text-anchor="middle" x="51" y="-506.3" font-family="Times New Roman,serif" font-size="14.00">main.c (60)</text>
</g>
<!-- node1 -->
<g id="node2" class="node"><title>node1</title>
<polygon fill="none" stroke="black" points="0,-415.5 0,-461.5 102,-461.5 102,-415.5 0,-415.5"/>
<text text-anchor="middle" x="51" y="-446.3" font-family="Times New Roman,serif" font-size="14.00">StackWalkTest</text>
<polyline fill="none" stroke="black" points="0,-438.5 102,-438.5 "/>
<text text-anchor="middle" x="51" y="-423.3" font-family="Times New Roman,serif" font-size="14.00">main.c (54)</text>
</g>
<!-- node0->node1 -->
<g id="edge1" class="edge"><title>node0:ps->node1:en</title>
<path fill="none" stroke="black" d="M51,-498.38C51,-490.175 51,-480.768 51,-471.879"/>
<polygon fill="black" stroke="black" points="54.5001,-471.784 51,-461.784 47.5001,-471.784 54.5001,-471.784"/>
</g>
<!-- node2 -->
<g id="node3" class="node"><title>node2</title>
<polygon fill="none" stroke="black" points="11,-332.5 11,-378.5 91,-378.5 91,-332.5 11,-332.5"/>
<text text-anchor="middle" x="51" y="-363.3" font-family="Times New Roman,serif" font-size="14.00">Func1</text>
<polyline fill="none" stroke="black" points="11,-355.5 91,-355.5 "/>
<text text-anchor="middle" x="51" y="-340.3" font-family="Times New Roman,serif" font-size="14.00">main.c (49)</text>
</g>
<!-- node1->node2 -->
<g id="edge2" class="edge"><title>node1:ps->node2:en</title>
<path fill="none" stroke="black" d="M51,-415.38C51,-407.175 51,-397.768 51,-388.879"/>
<polygon fill="black" stroke="black" points="54.5001,-388.784 51,-378.784 47.5001,-388.784 54.5001,-388.784"/>
</g>
<!-- node3 -->
<g id="node4" class="node"><title>node3</title>
<polygon fill="none" stroke="black" points="11,-249.5 11,-295.5 91,-295.5 91,-249.5 11,-249.5"/>
<text text-anchor="middle" x="51" y="-280.3" font-family="Times New Roman,serif" font-size="14.00">Func2</text>
<polyline fill="none" stroke="black" points="11,-272.5 91,-272.5 "/>
<text text-anchor="middle" x="51" y="-257.3" font-family="Times New Roman,serif" font-size="14.00">main.c (45)</text>
</g>
<!-- node2->node3 -->
<g id="edge3" class="edge"><title>node2:ps->node3:en</title>
<path fill="none" stroke="black" d="M51,-332.38C51,-324.175 51,-314.768 51,-305.879"/>
<polygon fill="black" stroke="black" points="54.5001,-305.784 51,-295.784 47.5001,-305.784 54.5001,-305.784"/>
</g>
<!-- node4 -->
<g id="node5" class="node"><title>node4</title>
<polygon fill="none" stroke="black" points="11,-166.5 11,-212.5 91,-212.5 91,-166.5 11,-166.5"/>
<text text-anchor="middle" x="51" y="-197.3" font-family="Times New Roman,serif" font-size="14.00">Func3</text>
<polyline fill="none" stroke="black" points="11,-189.5 91,-189.5 "/>
<text text-anchor="middle" x="51" y="-174.3" font-family="Times New Roman,serif" font-size="14.00">main.c (41)</text>
</g>
<!-- node3->node4 -->
<g id="edge4" class="edge"><title>node3:ps->node4:en</title>
<path fill="none" stroke="black" d="M51,-249.38C51,-241.175 51,-231.768 51,-222.879"/>
<polygon fill="black" stroke="black" points="54.5001,-222.784 51,-212.784 47.5001,-222.784 54.5001,-222.784"/>
</g>
<!-- node5 -->
<g id="node6" class="node"><title>node5</title>
<polygon fill="none" stroke="black" points="11,-83.5 11,-129.5 91,-129.5 91,-83.5 11,-83.5"/>
<text text-anchor="middle" x="51" y="-114.3" font-family="Times New Roman,serif" font-size="14.00">Func4</text>
<polyline fill="none" stroke="black" points="11,-106.5 91,-106.5 "/>
<text text-anchor="middle" x="51" y="-91.3" font-family="Times New Roman,serif" font-size="14.00">main.c (37)</text>
</g>
<!-- node4->node5 -->
<g id="edge5" class="edge"><title>node4:ps->node5:en</title>
<path fill="none" stroke="black" d="M51,-166.38C51,-158.175 51,-148.768 51,-139.879"/>
<polygon fill="black" stroke="black" points="54.5001,-139.784 51,-129.784 47.5001,-139.784 54.5001,-139.784"/>
</g>
<!-- node6 -->
<g id="node7" class="node"><title>node6</title>
<polygon fill="none" stroke="black" points="11,-0.5 11,-46.5 91,-46.5 91,-0.5 11,-0.5"/>
<text text-anchor="middle" x="51" y="-31.3" font-family="Times New Roman,serif" font-size="14.00">Func5</text>
<polyline fill="none" stroke="black" points="11,-23.5 91,-23.5 "/>
<text text-anchor="middle" x="51" y="-8.3" font-family="Times New Roman,serif" font-size="14.00">main.c (33)</text>
</g>
<!-- node5->node6 -->
<g id="edge6" class="edge"><title>node5:ps->node6:en</title>
<path fill="none" stroke="black" d="M51,-83.3799C51,-75.1745 51,-65.7679 51,-56.8786"/>
<polygon fill="black" stroke="black" points="54.5001,-56.784 51,-46.784 47.5001,-56.784 54.5001,-56.784"/>
</g>
</g>
</svg>