前言
拿到一份陌生的 C 工程,想要了解整個工程的脈絡,你會使用什么方法呢?
函式呼叫
一般都是通過函式呼叫關系來理清整個工程的運作流程,通常使用的軟體是 source insight,從 main 函式開始,查看 main 函式呼叫了哪些函式,然后進入各個子函式,以此類推,逐漸展開整個脈絡,我之前也是一直使用這種方法,我稱之為“人肉整理”,這種方法是很耗時的,一個不大的工程就可能要整理大半天,從那時起,我就時常想,有沒有自動化的軟體來干這個事情?后來找到了一款:calltree,聽名字就知道它的功能了,“呼叫樹”,用來理清函式呼叫關系的,不過,該款軟體早已停更,版本較老,用起來不太順手,
cflow
后來又發現了一款:cflow,在 ubuntu 下直接使用命令就可以安裝該軟體,如下
sudo apt install cflow
cflow 能夠分析 C 檔案中的控制流,看個例子
$ cflow -T log.c
+-log_init() <void log_init (void) at log.c:193>
+-InitializeCriticalSection()
+-wget_console_init()
+-wget_logger_set_func()
+-wget_get_logger()
+-write_debug_stderr() <void write_debug_stderr (const char *data, size_t len) at log.c:157>
| \-write_debug() <void write_debug (FILE *fp, const char *data, size_t len) at log.c:138>
| \-write_out() <void write_out (FILE *default_fp, const char *data, size_t len, int with_timestamp, const char *colorstring, wget_console_color color_id) at log.c:55>
| +-strcmp()
| +-open()
| +-wget_buffer_init()
| +-isatty()
| +-fileno()
| +-wget_buffer_strcpy()
| +-gettime()
| +-localtime_r()
| +-wget_buffer_printf_append()
| +-wget_buffer_memcat()
| +-wget_buffer_strcat()
| +-fwrite()
| +-EnterCriticalSection()
| +-wget_console_set_fg_color()
| +-fflush()
| +-wget_console_reset_fg_color()
| +-LeaveCriticalSection()
| +-write()
| +-close()
| \-wget_buffer_deinit()
...
tree2dotx
為了更直觀地展示函式呼叫關系,我們可以使用 xdot 工具,不過使用 xdot 工具地前提是我們必須要有一份用 xdot 格式表示節點地檔案,這就需要使用另外一個工具 tree2dotx,可以從這里獲取,將其存為 tree2dotx 檔案,然后放入系統路徑,
運行一下看看效果
$ cflow log.c | tree2dotx
digraph G{
ranksep = 1;
rankdir=LR;
size="1920,1080";
node [fontsize=16,fontcolor=blue,style=filled,fillcolor=Wheat,shape=box];
"log_init" -> "InitializeCriticalSection";
"log_init" -> "wget_console_init";
"log_init" -> "wget_logger_set_func";
"log_init" -> "wget_get_logger";
"log_init" -> "write_debug_stderr";
"write_debug_stderr" -> "write_debug";
"write_debug" -> "write_out";
"write_out" -> "strcmp";
"write_out" -> "open";
"write_out" -> "wget_buffer_init";
"write_out" -> "isatty";
"write_out" -> "fileno";
"write_out" -> "wget_buffer_strcpy";
"write_out" -> "gettime";
"write_out" -> "localtime_r";
...
}
xdot
xdot 功能能夠以圖形的方式展示節點之間的關系,在 ubuntu 下使用 apt 命令就可以安裝
sudo apt install xdot
運行看下效果
$ cflow log.c | tree2dotx > out.dot
$ xdot out.dot

可以非常直觀的看到 log.c 檔案中的函式呼叫關系,滑鼠放在某個函式上,前后的箭頭還會變成紅色,用來指示被呼叫和呼叫關系,
優化
上述 tree2dotx 腳本其實是有點問題的,我對它做了一些優化,如下
- 去重,經過 tree2dotx 處理過的節點,有重復的,導致連線翻倍,去重后效果如下,和上面對比是不是簡潔多了

命令如下
cflow log.c | tree2dotx | awk '!a[$0]++' > out.dot
- 去除多余空格
原始 tree2dotx 腳本在將函式呼叫關系轉成節點時,有些函式后面多出了一個空格,將腳本中的sed -e "s/<.*>.*//g" | tr -d '\(' | tr -d '\)' | tr '|' ' '改成sed -e "s/ <.*>.*//g" | tr -d '\(' | tr -d '\)' | tr '|' ' ',就可以了, - 增加子節點
這樣就可以顯示當前函式屬于哪個 C 檔案了
附件
優化后的 tree2dotx 完整代碼
$ cat /usr/local/bin/tree2dotx
#!/bin/bash
#
# tree2dotx --- transfer a "tree"(such as the result of tree,calltree,cflow -b)
# to a picture discribed by DOT language(provided by Graphviz)
#
# Author: falcon <wuzhangjin@gmail.com>
# Update: 2007-11-14, 2015-3-19
# Usage:
#
# tree -L 2 -d /path/to/a/directory | bash tree2dotx | dot -Tsvg -o tree.svg
# cd /path/to/a/c/project/; calltree -gb -np -m *.c | bash tree2dotx | dot -Tsvg -o calltree.svg
# cd /path/to/a/c/project/; cflow -b -m setup_rw_floppy kernel/blk_drv/floppy.c | bash tree2dotx | dot -Tsvg -o cflow.svg
#
# Set the picture size, direction(LR=Left2Right,TB=Top2Bottom) and shape(diamond, circle, box)
size="1920,1080"
direction="LR"
shape="box"
# color, X11 color name: http://en.wikipedia.org/wiki/X11_color_names
fontcolor="blue"
fillcolor="Wheat"
# fontsize
fontsize=16
# Specify the symbols you not concern with space as decollator here
filterstr="";
input=`cat`
# output: dot, flame
output="dot"
has_subgraph="0"
ordering="0"
# Usage
#grep -v ^$ | cat
function usage
{
echo ""
echo " $0 "
echo ""
echo " [ -f \"filter1 filter2 ...\" ]"
echo " [ -s size, ex: 1080,760; 1920,1080 ]"
echo " [ -d direction, ex: LR; TB ]"
echo " -h get help"
echo ""
}
function subgraph() {
echo "$input" \
| grep -e " at " \
| sed 's/).* at /)/g;s/:.*//g;s/ //g' \
| sed -r 's/^(.*)\(\)(.*)$/\tsubgraph "cluster_\2" { label="\2";\1;}/' \
| sort -u
}
while getopts "f:s:S:d:e:h:o:r:" opt;
do
case $opt in
f)
filterstr=$OPTARG
;;
s)
size=$OPTARG
;;
S)
shape=$OPTARG
;;
d)
direction=$OPTARG
;;
e)
has_subgraph=$OPTARG
;;
o)
output=$OPTARG
;;
r)
ordering=$OPTARG
;;
h|?)
usage $0;
exit 1;
;;
esac
done
# Transfer the tree result to a file described in DOT language
echo "$input" | \
grep -v ^$ | grep -v "^[0-9]* director" \
| sed -e "s/ <.*>.*//g" | tr -d '\(' | tr -d '\)' | tr '|' ' ' \
| sed -e "s/ \[.*\].*//g" \
| awk '{if(NR==1) system("basename "$0); else printf("%s\n", $0);}' \
| awk -v fstr="$filterstr" '# function for filter the symbols you not concern
function need_filter(node) {
for ( i in farr ) {
if (match(node,farr[i]" ") == 1 || match(node,"^"farr[i]"$") == 1) {
return 1;
}
}
return 0;
}
BEGIN{
# Filternode array are used to record the symbols who have been filtered.
oldnodedepth = -1; oldnode = ""; nodep[-1] = ""; filter[nodep[-1]] = 0;
oldnodedepth_orig = -1; nodepre = 0; nodebase = 0; nodefirst = 0;
output = "'$output'";
#printf("output = %s\n", output);
# Store the symbols to an array farr
split(fstr,farr," ");
# print some setting info
if (output == "dot") {
printf("digraph G{\n");
if(ordering == "1") {
printf("ordering=out;\n");
}
printf("ranksep = 1;\n");
printf("\trankdir='$direction';\n");
printf("\tsize=\"'$size'\";\n");
printf("\tnode [fontsize='$fontsize',fontcolor='$fontcolor',style=filled,fillcolor='$fillcolor',shape='$shape'];\n");
}
}{
# Get the node, and its depth(nodedepth)
# nodedepth = match($0, "[^| `]");
nodedepth = match($0, "[[:digit:]|[:alpha:]]|[[:alnum:]]");
node = substr($0,nodedepth);
# printf("%d %d %s \n", nodedepth, oldnodedepth_orig, node);
if (nodefirst == 1 && oldnodedepth_orig > 0) {
nodefirst = 0;
nodebase = nodedepth-oldnodedepth_orig;
}
if (nodedepth == 0)
nodedepth=1;
tmp = nodedepth;
# printf("pre=%d base=%d np=%d oldnp=%d node=%s \n", nodepre, nodebase, tmp, oldnodedepth_orig, node);
if (nodedepth != 0 && oldnodedepth_orig == -1) {
nodepre = nodedepth-1;
nodefirst = 1;
nodedepth = 0;
} else if (nodebase != 0) {
nodedepth = int((nodedepth-nodepre)/nodebase);
}
# if whose depth is 1 less than him, who is his parent
if (nodedepth-oldnodedepth == 1) {
nodep[nodedepth-1] = oldnode;
}
# for debugging
# printf("%d %s\n", nodedepth, node);
# printf("\t\"%s\";\n",node);
# print the vectors
if (oldnodedepth != -1) {
# if need filter or whose parent have been filter, not print it, and set the flat of filter to 1
if (need_filter(node) || filter[nodep[nodedepth-1]] == 1) {
filter[node] = 1;
# printf("node = %s, filter[node] = %d\n", node, filter[node]);
} else if (nodep[nodedepth-1] != "") {
if (output == "dot") {
printf("\t\"%s\" -> \"%s\";\n", nodep[nodedepth-1], node, nodep[nodedepth-1], node);
} else {
for (i = 0; i < nodedepth; i++)
printf("%s;", nodep[i]);
printf("%s 1\n", node);
}
# printf("\t\"%s\" -> \"%s\"[label=\"%s>%s\"];\n", nodep[nodedepth-1], node, nodep[nodedepth-1], node);
}
}
# save the old depth and the old node
oldnodedepth_orig = tmp;
oldnodedepth = nodedepth;
oldnode = node;
} END {
# if (output == "dot")
# printf("}");
}'
echo ""
if [ $has_subgraph == "1" ]
then
subgraph
fi
echo "}"
cflow -d 3 wget.c | tree2dotx -e 1 -r 1 | awk '!a[$0]++' > out.dot && cat out.dot
tree2dotx -e 0/1 指定是否展示子圖(函式所在檔案)
tree2dotx -r 0/1 指定是否按照函式出現順序展示影像
影像
還可以將 xdot 顯示的影像輸出為圖片
dot -Tgif out.dot -o out.gif

參考
https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/395119.html
標籤:其他
上一篇:微燈手握寸筆,細談記憶體管理
