Windows上获取当前调用堆栈信息,并用graphviz图形化显示调用堆栈

引言

前面的文章《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&#45;&gt;node1 -->
<g id="edge1" class="edge"><title>node0:ps&#45;&gt;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&#45;&gt;node2 -->
<g id="edge2" class="edge"><title>node1:ps&#45;&gt;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&#45;&gt;node3 -->
<g id="edge3" class="edge"><title>node2:ps&#45;&gt;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&#45;&gt;node4 -->
<g id="edge4" class="edge"><title>node3:ps&#45;&gt;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&#45;&gt;node5 -->
<g id="edge5" class="edge"><title>node4:ps&#45;&gt;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&#45;&gt;node6 -->
<g id="edge6" class="edge"><title>node5:ps&#45;&gt;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>


版权声明:本文为littlezhuhui原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>