c语言分析工具——函数调用关系生成图像、流程图生成器

⌚Time: 2022-10-12 00:06:26

👨‍💻Author: Jack Ge

calltree

test.c


/* file: test.c */

#include <stdio.h>





void func_5(void){

    printf("hello!\n");

}



void func_4(void){

    func_5();

}



void func_3(void){

    func_4();

}



void func_2(void){

    func_3();

}



void func_1(void){

    func_2();

}



int main()

{

    func_1();

    func_4();



    return 0;

}


解压


tar -jxvf calltree-2.3.tar.bz2

cd calltree-2.3

如果系统是x86架构,则需要进行以下规则的配置


cp RULES/i686-linux-cc.rul RULES/x86_64-linux-cc.rul

由于项目中的configure已经弃用,推荐直接使用make,但是make之前先配置项目中函数名与gcc函数名冲突问题


find . -name "*.[c|h]" |xargs sed -i -e "s/fexecve/fexecve_calltree/"

find . -name "*.[c|h]" |xargs sed -i -e "s/getline/getline_calltree/"

make

这里系统是x86架构,所以拷贝的是x86_64-linux-cc目录下的程序(或建立链接文件也可以)


sudo cp calltree/OBJ/x86_64-linux-cc/calltree /usr/bin/calltree

安装graphviz


sudo apt-get install graphviz

通过calltree --help命令查看使用帮助信息,主要有以下几个常用选项:


-b:在每个制表位处打印垂直条;

-g:输出函数所在文件的目录;

-m:只分析main函数调用关系;

-p:使用c预处理(默认),缺点就是容易产生多余的信息;

-np:不使用c预处理;

-xvcg:导出供xvcg使用的格式;

-dot:导出供graphviz使用的格式;

depth=#:设置最大打印深度;

list=name:仅为函数name生成调用图;

listfile=file:只列出在file中找到的函数;

igorefile=file:不列出在file中找到的函数。

test.c生成图像


calltree -np -g -b depth=10 list=main test.c -dot > test.dot

dot -T png test.dot -o test.png

结果

cflow

osx.c


/*

 *      osx.c - this file is part of Geany, a fast and lightweight IDE

 *

 *      Copyright 2015 Jiri Techet <techet(at)gmail(dot)com>

 *

 *      This program is free software; you can redistribute it and/or modify

 *      it under the terms of the GNU General Public License as published by

 *      the Free Software Foundation; either version 2 of the License, or

 *      (at your option) any later version.

 *

 *      This program is distributed in the hope that it will be useful,

 *      but WITHOUT ANY WARRANTY; without even the implied warranty of

 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

 *      GNU General Public License for more details.

 *

 *      You should have received a copy of the GNU General Public License along

 *      with this program; if not, write to the Free Software Foundation, Inc.,

 *      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

 */



#ifdef MAC_INTEGRATION



#include "osx.h"



#include "utils.h"

#include "ui_utils.h"

#include "main.h"





static gboolean app_block_termination_cb(GtkosxApplication *app, gpointer data)

{

    return !main_quit();

}





/* For some reason osx doesn't like when the NSApplicationOpenFile handler blocks for 

 * a long time which may be caused by the project_ask_close() below. Finish the

 * NSApplicationOpenFile handler immediately and perform the potentially blocking

 * code on idle in this function. */

static gboolean open_project_idle(gchar *locale_path)

{

    gchar *utf8_path;



    utf8_path = utils_get_utf8_from_locale(locale_path);

    if (app->project == NULL || 

        (g_strcmp0(utf8_path, app->project->file_name) != 0 && project_ask_close()))

        project_load_file_with_session(locale_path);

    g_free(utf8_path);

    g_free(locale_path);

    return FALSE;

}





static gboolean app_open_file_cb(GtkosxApplication *osx_app, gchar *path, gpointer user_data)

{

    gchar opened = FALSE;

    gchar *locale_path;



    locale_path = utils_get_locale_from_utf8(path);



    if (!g_path_is_absolute(locale_path))

    {

        gchar *cwd = g_get_current_dir();

        SETPTR(locale_path, g_build_filename(cwd, locale_path, NULL));

        g_free(cwd);

    }



    if (g_str_has_suffix(path, ".geany"))

    {

        g_idle_add((GSourceFunc)open_project_idle, locale_path);

        opened = TRUE;

    }

    else

    {

        opened = document_open_file(locale_path, FALSE, NULL, NULL) != NULL;

        g_free(locale_path);

    }



    return opened;

}





static void on_new_window(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)

{

    utils_start_new_geany_instance(NULL);

}





void osx_ui_init(void)

{

    GtkWidget *item, *menu;

    GtkosxApplication *osx_app = gtkosx_application_get();



    item = ui_lookup_widget(main_widgets.window, "menubar1");

    gtk_widget_hide(item);

    gtkosx_application_set_menu_bar(osx_app, GTK_MENU_SHELL(item));



    item = ui_lookup_widget(main_widgets.window, "menu_quit1");

    gtk_widget_hide(item);



    item = ui_lookup_widget(main_widgets.window, "menu_info1");

    gtkosx_application_insert_app_menu_item(osx_app, item, 0);



    item = ui_lookup_widget(main_widgets.window, "menu_help1");

    gtkosx_application_set_help_menu(osx_app, GTK_MENU_ITEM(item));



    gtkosx_application_set_use_quartz_accelerators(osx_app, FALSE);



    g_signal_connect(osx_app, "NSApplicationBlockTermination",

                    G_CALLBACK(app_block_termination_cb), NULL);

    g_signal_connect(osx_app, "NSApplicationOpenFile",

                    G_CALLBACK(app_open_file_cb), NULL);



    menu = gtk_menu_new();

    item = gtk_menu_item_new_with_label("New Window");

    g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(on_new_window), NULL);

    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);

    gtkosx_application_set_dock_menu(osx_app, GTK_MENU_SHELL(menu));

}



#endif /* MAC_INTEGRATION */

安装cflow和graphviz


sudo apt-get install cflow graphviz

将以下脚本保存为tree2dotx


#!/bin/bash

# tree2dot.sh --- transfer a "tree"(such as the result of tree,calltree)

#                 to a p_w_picpath discribed by DOT language(provided by Graphviz)

# author: falcon<zhangjinw@gmail.com>

# update: 2007-11-14

# usage: 

#       tree -L 2 -d /path/to/a/directory | bash tree2dot.sh > tree.dot

#       cd /path/to/a/c/project/; calltree -gb -np -m *.c | bash tree2dot.sh > tree.dot



# indicate the symbols you not concern with space as decollator here

filterstr="";



# transfer the tree result to a file described in DOT language

grep -v ^$ | grep -v "^[0-9]* director" \

| 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]=""; filternode[nodep[-1]]=0;

                # store the symbols to an array farr

                split(fstr,farr," ");

                # print some setting info

                printf("digraph G{\n"); 

                printf("\trankdir=LR;\n");

                printf("\tsize=\"800,600\";\n");

                printf("\tnode [fontsize=10,fontcolor=red,style=filled,fillcolor=lightblue];\n");

        }{

                # get the node, and its depth(nodedepth)

                nodedepth=match($0, "[^| `]");

                node=substr($0,nodedepth); 

                nodedepth=int(nodedepth/4)

                # 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) || filternode[nodep[nodedepth-1]]==1) {

                                filter[node]=1;

            } else if (nodep[nodedepth-1] != "") {

                                printf("\t\"%s\" -> \"%s\";\n", nodep[nodedepth-1], node, nodep[nodedepth-1], 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=nodedepth;

                oldnode=node;

        }END{

                printf("}");

        }'

添加执行权限,放入/usr/bin目录


chmod +x tree2dotx

sudo cp tree2dotx /usr/bin

安装gawk


sudo apt-get install gawk

生成图片


cflow osx.c | tree2dotx  > out.dot

dot -T png out.dot -o out.png

流程图分析工具AutoFlowchart


#include <stdio.h>

#include <assert.h>

//链表结构体

struct SListNode{

    int data;//数据

    struct SListNode *pNext;//下个节点

};

//建立新节点

SListNode* buy_new_node(int data){

    SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));//分配内存空间

    assert(newNode);

    newNode->data = data;//设置数据

    newNode->pNext = NULL;//下一个节点为空

    return newNode;//返回节点

}

//插入新数据

void pushback_node(SListNode *header,SListNode *node){

    assert(header);

    SListNode *pCur = header;//头指针

    while(pCur->pNext){//找到尾部

        pCur = pCur->pNext;

    }

    pCur->pNext = node;//插入数据

}

//数量统计

int get_number_of_list(SListNode *header){

    assert(header);

    int iCount = 0;

    SListNode *pCur = header;

    while(pCur->pNext){//遍历链表

        pCur = pCur->pNext;

        iCount++;//增加计数

    }

    return iCount;

}

int main(){

    SListNode *node;

    int numbers[50] = {0};

    int n;

    puts("输入数字N");

    scanf("%d",&n);//获取数字总数

    puts("依次输入数字");

    for(int i=0;i<n;i++){

        scanf("%d",&numbers[i]);//将数字存入数组

    }

    SListNode *header0 = buy_new_node(0);//链表

    for(int k=0;k<n;k++){

            node = buy_new_node(numbers[k]);

            pushback_node(header0,node);//将数据插入链表

    }

    SListNode * header1 = buy_new_node(0);//余数为0的子链表

    SListNode * header2 = buy_new_node(0);//余数为1的子链表

    SListNode * header3 = buy_new_node(0);//余数为2的子链表

    

    for(int j=0;j<n;j++){

        switch(numbers[j]%3){

        case 0:

            node = buy_new_node(numbers[j]);

            pushback_node(header1,node);//将余数为0的数据插入子链表

            break;

        case 1:

            node = buy_new_node(numbers[j]);

            pushback_node(header2,node);//将余数为1的数据插入子链表

            break;

        case 2:

            node = buy_new_node(numbers[j]);

            pushback_node(header3,node);//将余数为2的数据插入子链表

            break;

        default:

            break;

        }

    }

    printf("余数为0 为1 为2的链表数据个数:%d %d %d\n",get_number_of_list(header1),get_number_of_list(header2),get_number_of_list(header3));

    SListNode *pCur;

    pCur = header1->pNext;

    while(pCur){//打印余数为0的子链表

        printf("%d ",pCur->data);

        pCur = pCur->pNext;

    }

    printf("\n");

    pCur = header2->pNext;

    while(pCur){//打印余数为1的子链表

        printf("%d ",pCur->data);

        pCur = pCur->pNext;

    }

    printf("\n");//打印余数为2的子链表

    pCur = header3->pNext;

    while(pCur){

        printf("%d ",pCur->data);

        pCur = pCur->pNext;

    }

    printf("\n");

    return 0;

}


打开c文件后,点击相应函数,分析流程图

结果