CallGraph文件信息重整 前言 在 RTT 系统下我们通常可以动态地查看剩余栈空间去约算最大栈空间,最近了解到可以通过查看 CallGraph 文件从静态角度分析,尝试结合 CallGraph 文件从另一个角度分析栈大小分配是否合理,实现动静结合。在查看CallGraph 文件时发现了一些不便的地方,然后对文件进行了一些重整理。
文件分析 浏览文件 检查Callgraph
是否勾选,勾选后编译一次就可以看到Callgraph的htm文件生成了
MaximumStackUsage 页面最上方给出了计算出最大栈深的一个函数,nrt_rms_entry
函数最大使用2116字节的栈空间,那就先检查nrt_rms_entry
所在线程分配的栈大小。
nrt_rms_entry
是nrt_rms
线程的入口函数,该线程当前分配了2600字节栈空间,仍有20%的冗余。
MutuallyRecursiveFunctions 然后下面是递归函数
SVC_Handler
和 ADC_IRQHandler
,通过全局搜索,这两个函数都没有使用到。
FunctionPoints 往下的内容是函数指针,文件列出了很长的函数列表,但是这里面并没有什么有效信息。
Global Symbols 最后就是Global Symbols
和Undefined Global Symbols
, Undefined Global Symbols
是空的就不需要看了
Global Symbols
中查看函数的指令占用,栈占用,最大栈深、调用链等相关信息。目前需要关注的是MaxDeph
的值
按照之前介绍的方法,在文本中搜索”Max Depth”,我这个工程搜索到了1178个结果,数据十分之多。而且栈深不同,我们不可能每一个都去看,就算只找较大栈深的来看,在数据量较大的情况下也很困难。
刚刚的MaximumStackUsage
一栏里提到线程的入口函数,从函数调用链角度来说,这些入口函数理论都是调用链的链头,即对于该线程是(静态分析下)最大栈深。通过直接搜索各个线程的入口函数,找到线程的Max Depth
,根据Max Depth
查看栈空间分配是否合理。
但是这样还不够,因为这个调用链并不包含函数指针指向的函数栈深。所以我们还需要看下这些函数栈使用情况。
所以问题还是回来了,数据量大,想只看栈较大的部分函数不容易。
对callgraph信息重整 下面写一个Python脚本,重新处理网页内容后可以更好查看函数信息,不安装额外依赖。callgraph文件中函数格式都是有规律的,由一些标签包围,所以可以很容易地取得所需内容。为了不引入外部依赖,主要使用通过正则取得相应字符串。正则难理解、可读性差,而且性能低,一般是不推荐使用的。
主要做如下处理:
将带[called by]
信息和不带此信息的函数分组,然后按最大栈深降序显示到网页上 不带[called by]
函数置于网页左侧,按序列出函数名与最大栈深,且可链接到对应函数,并标记没有出现在函数指针列表的函数 将Call Chain
中的函数添加超链接,并在函数后面添加函数的栈大小 根据[called by]
分组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 N_CALLED_BY_LIST = [] OTHERS_LIST = [] RE_GROUPS = re.compile (r"<P><STRONG>(.*?)(?=<P>)" , re.S) groups = RE_GROUPS.findall(html) for group in groups: if "[Called By]" not in group and "Max Depth" in group: N_CALLED_BY_LIST.append(group) else : OTHERS_LIST.append(group)
获取左侧函数列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 N_CALLED_BY_NAV_LIST = [] RE_MATCH_GROUP = re.compile (r"<a name=\"\[(\w+)]\"></a>(.*?)</STRONG> \(Thumb, \d+ bytes, Stack size (\d+) bytes" ) RE_MAX_DEPTH = re.compile (r"Max Depth = (\w+)" ) def get_max_depth (group ): match = RE_MAX_DEPTH.search(group) return int (match .group(1 )) if match else 0 def get_nav_string (group ): match = RE_MATCH_GROUP.search(group) if match : label = "【==Non Fn Pointer==】</BR>" if match .group(2 ) not in fn_pointers else "" return (f"<LI><a href=\"#[{match .group(1 )} ]\">{label} {match .group(2 )} " f"Max Depth = {get_max_depth(group)} </a>" ) else : return "" N_CALLED_BY_NAV_LIST.append(get_nav_string(group))
Call Chain
中的函数添加超链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 RE_CALL_CHAIN = re.compile (r"Call Chain = (.*)" ) RE_LI = re.compile (r"<a.*?>>.*" ) groups = RE_GROUPS.findall(html) for i, group in enumerate (groups): match = RE_CALL_CHAIN.search(group) if match : call_chain_names = match .group(1 ).split(" ⇒ " ) links = map (call_chain_link, map (str .strip, call_chain_names)) new_call_chain = "Call Chain = " + " ⇒ " .join(links) groups[i] = group.replace(match .group(0 ), new_call_chain)
处理后效果如下图,排序后可以继续根据最大栈深分析了
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 import osimport ioimport reRE_RECURSIVE = re.compile (r"<P>\n<H3>\nMutually(.*?)</UL>" , re.S) RE_GROUPS = re.compile (r"<P><STRONG>(.*?)(?=<P>)" , re.S) RE_MAX_DEPTH = re.compile (r"Max Depth = (\w+)" ) RE_MATCH_GROUP = re.compile (r"<a name=\"\[(\w+)]\"></a>(.*?)</STRONG> \(Thumb, \d+ bytes, Stack size (\d+) bytes" ) RE_CALL_CHAIN = re.compile (r"Call Chain = (.*)" ) RE_LI = re.compile (r"<a.*?>>.*" ) RE_FN_POINTERS = re.compile (r"<H3>\nFunction Pointers\n</H3><UL>\n.*?\n</UL>" , re.S) OTHERS_LIST = [] N_CALLED_BY_LIST = [] N_CALLED_BY_NAV_LIST = [] LINK_DICT = {} def get_max_depth (group ): match = RE_MAX_DEPTH.search(group) return int (match .group(1 )) if match else 0 def get_nav_string (group ): match = RE_MATCH_GROUP.search(group) if match : label = "【==Non Fn Pointer==】</BR>" if match .group(2 ) not in fn_pointers else "" return (f"<LI><a href=\"#[{match .group(1 )} ]\">{label} {match .group(2 )} " f"Max Depth = {get_max_depth(group)} </a>" ) else : return "" def call_chain_link (name ): return (f'<a href="#[{LINK_DICT[name][0 ]} ]">{name} ({LINK_DICT[name][1 ]} )</a>' if name in LINK_DICT else name) htm_file = input ("请输入callgraph文件路径:" ) while not os.path.isfile(htm_file): print ("文件不存在,请重新输入!" ) htm_file = input ("请输入callgraph文件路径:" ) output_file = "(layout)" + os.path.basename(htm_file) output_file = os.path.join(os.path.dirname(htm_file), output_file) with open (htm_file, "r" , encoding="utf-8" ) as f_in: html = f_in.read() recursive = RE_RECURSIVE.search(html)[0 ].replace("H3" , "H1" ) fn_pointers = RE_FN_POINTERS.search(html)[0 ] groups = RE_GROUPS.findall(html) for group in groups: match = RE_MATCH_GROUP.search(group) if match : href, name, stack = match .group(1 , 2 , 3 ) LINK_DICT[name] = href, stack for i, group in enumerate (groups): match = RE_CALL_CHAIN.search(group) if match : call_chain_names = match .group(1 ).split(" ⇒ " ) links = map (call_chain_link, map (str .strip, call_chain_names)) new_call_chain = "Call Chain = " + " ⇒ " .join(links) groups[i] = group.replace(match .group(0 ), new_call_chain) groups[i] = RE_LI.sub(lambda a: a.group(0 ).replace("</a>" , "" ) + "</a>" , groups[i]) for group in groups: if "[Called By]" not in group and "Max Depth" in group: N_CALLED_BY_LIST.append(group) N_CALLED_BY_NAV_LIST.append(get_nav_string(group)) else : OTHERS_LIST.append(group) N_CALLED_BY_LIST.sort(key=get_max_depth, reverse=True ) N_CALLED_BY_NAV_LIST.sort(key=get_max_depth, reverse=True ) N_CALLED_BY_NAV_LIST = [s.replace("Max Depth = " , "</BR>(Max " )[:-4 ] + ")" + s[-4 :] for s in N_CALLED_BY_NAV_LIST] my_css = "<style>#button{display:none;position:fixed;bottom:50px;right:50px;z-index:99;\ border:none;outline:none;color:white;cursor:pointer;padding:15px;border-radius:25px;\ background-image:linear-gradient(to right,rgba(120, 167, 181, 0.732), rgb(120, 157, 180));\ }#button:hover{background-image:linear-gradient(to right,rgba(98, 136, 146, 0.835), rgb(103, 137, 164))}\ li{display:block}.main{margin-left:20%}.frame{border:1px solid#dddddd;padding:10px}\ .frame:hover{border:1px solid#81a79e}li a,h1{color:rgb(2,76,100);text-decoration:none}\ li a:hover{color:rgb(1,24,32);font-weight:700}.nav{list-style-type:none;margin:0;padding:0;\ width:19%;background-color:white;position:fixed;height:99%;overflow-y:auto}.nav li a{\ display:block;color:rgb(2,76,100);padding:5px 1px;margin-bottom:10px}.nav li a:hover{\ background-color:rgb(114,141,150);color:rgb(255,255,255)}</style>" my_button = "<button onclick=\"topFunction()\" id=\"button\">回到顶部</button>" my_js = "<script>var button=document.getElementById(\"button\");window.onscroll=function()\ {scrollFunction()};function scrollFunction(){if(document.body.scrollTop>10||document.documentElement\ .scrollTop>10){button.style.display=\"block\"}else{button.style.display=\"none\"}}\ function topFunction(){document.body.scrollTop=0;document.documentElement.scrollTop=0}</script>" with open (output_file, "w" , encoding="utf-8" ) as f_out: buffer = io.StringIO() buffer.write("<html><head>" + my_css + "<title>Static Call Graph</title></head><body>" ) buffer.write("<div class=\"nav\">" ) buffer.write("" .join(N_CALLED_BY_NAV_LIST)) buffer.write("</div>" ) buffer.write("<div class=\"main\">" ) buffer.write(my_button) buffer.write(recursive + "</BR>" ) buffer.write("<BR><H1><a id=\"top\" href=\"#all\">Extracted and Sorted Data from Global Symbols</a></H1><BR>" ) buffer.write("" .join( [f"<div class=\"frame\"><P><STRONG>{no_called_by} </P></div></BR>" for no_called_by in N_CALLED_BY_LIST])) buffer.write("<BR><BR><BR><BR><BR>" ) buffer.write("<BR><H1><a id=\"all\" href=\"#top\">Others</a></H1><BR>" ) buffer.write("" .join([f"<div class=\"frame\"><P><STRONG>{other} </P></div></BR>" for other in OTHERS_LIST])) buffer.write("<BR><BR><BR><BR><BR>" ) buffer.write("</div></body>" + my_js + "</html>" ) f_out.write(buffer.getvalue()) print ("保存成功!" )
扩展资料 参考:callgraph - Arm Developer Document
callgraph文件默认输出命名为工程名,也可以通过修改参数配置输出文件名
1 --callgraph_file=<filename>
<filename> 参数设置你的文件名
callgraph文件默认输出的是.htm
,也可以通过修改参数配置输出为.txt
1 --callgraph_output=<fmt>
<fmt> 参数可以为以下选项
默认--callgraph_output=html