Linux平台下生成C语言数据结构关系图

原创 Linux二进制 2023-07-13 18:35

作为一名Linux系统下的C语言开发,经常需要阅读源码,但是有些源码实在是难以阅读,各种庞大的结构体交杂,分分钟把你绕晕,让你头昏眼花,迟迟无法梳理清楚。这时候,一个能够帮你梳理数据结构的工具就显得极其重要,让你能够很清晰的看出各个数据结构之间的关系。

本文我们主要介绍CentOS平台下通过python和graphviz生成数据结构关系图。

一、前置条件

为使用python和graphviz生成C语言的数据结构关系图,需提前安装好python3,这里不做介绍。这里介绍一下绘图工具graphviz和Linux命令行打开图片的工具eog等。

1、安装绘图工具graphviz

Graphviz(Graph Visualization Software)是一个由AT&T实验室启动的开源工具包,能够支持基于 DOT 脚本,文件扩展名通常是 .gv 或 .dot 的描述绘制图形。DOT 是一种文本图形描述语言,将生成的图形转换成多种输出格式的命令行工具,其输出格式包括PostScript,PDF,SVG,PNG,含注解的文本等。DOT 本身非常原始,提供了一种非常简单的描述图形的方法,同时意味着可以在命令行终端使用,或者被其它编程语言调用(Graphviz 就可以作为一个库使用)。这一点非常关键,基于 Graphviz 应用开发者不必掌握布局的复杂算法,而是可以把精力放在业务方面,将最后的图对象交给绘图引擎来处理即可。

yum install graphviz -y

2、安装命令行图片查看工具

GNOME之眼,图像查看器(eog)是GNOME桌面的官方图像查看器,可以用来在服务器端查看图片。它可以查看各种格式的单个图像文件,以及大型图像集合。eog可以通过插件系统进行扩展。

yum install eog -y

二、生成工具及使用方法

绘制关键数据结构的关联关系图,可以协助我们快速理解组织架构,加速理解代码逻辑;Linux平台下生成C语言数据结构关系图主要基于python+graphvizpythongraphviz工具是基础,需要辅助以python脚本,才能实现分析数据结构并生成用于绘图的dot语言;之后利用graphviz根据上一步中的临时生成文件的dot语言描述绘图。图形保存到xxx.svg文件中,.svg可以使用eog或者浏览器打开。

1、python脚本分析工具

用于分析结构体关联关系的python脚本(analysis_dt.py),如下:

#!/usr/bin/python3
import os,re
prefix = '''digraph spdk {
    graph [
        rankdir = "LR"
        //splines=polyline
        //overlap=false
    ];

    node [
        fontsize = "16"
        shape = "ellipse"\r
    ];

    edge [
    ];
'''


middle_str = ''
edge_list = []
edge_string = ''
cur_indentation_level = 0
space4 = '    '
space8 = space4 + space4
space12 = space4 + space8
space16 = space4 + space12
node_database = {}
node_database['created'] = []
color_arrary = ['red''green''blue''black','blueviolet','brown''cadetblue','chocolate','crimson','cyan','darkgrey','deeppink',data_struct
with open(r'/tmp/713/data_struct''r'as file_input:
    tmpline = file_input.readline()
    while(tmpline):
        tmpline = re.sub(r'([^a-zA-Z0-9]const )'' ', tmpline)
        #for match :struct device {
        if re.search(r'struct\s*([0-9a-zA-Z_\-]+)\s*\{', tmpline):
            m = re.search(r'struct\s*([0-9a-zA-Z_\-]+)\s*\{', tmpline)
            cur_indentation_level += 1
            if (cur_indentation_level == 1):
                node_name =  m.group(1)
                node_str = space4 + '\"' + node_name + '\" [\n' + space8 + 'label = \" '+ node_name +'\l|\n' + space12 + '{|{\n'
                node_database['created'].append(node_name)
                try:
                    node_database[node_name]['node_str'] = node_str
                except:
                    node_database[node_name] = {}
                    node_database[node_name]['node_str'] = node_str
        #for match :struct device        *parent;
        elif re.search(r'struct\s*([0-9a-zA-Z_\-]+)\s*(\**)(\s*)([0-9a-zA-Z_\-]+)\s*;', tmpline) and cur_indentation_level > 0:
            m = re.search(r'struct\s*([0-9a-zA-Z_\-]+)\s*(\**)(\s*)([0-9a-zA-Z_\-]+)\s*;', tmpline)
            member_type = m.group(1)
            node_database[node_name]['node_str'] += space16 + '<'+ member_type + '> ' + m.group(2) +  m.group(3) +  m.group(4) + '\l|\n'
            try:
                node_database[member_type]['included_by'].append(node_name)
            except:
                try:
                    node_database[member_type]['included_by'] = []
                    node_database[member_type]['included_by'].append(node_name)
                except:
                    node_database[member_type] = {}
                    node_database[member_type]['included_by'] = []
                    node_database[member_type]['included_by'].append(node_name)
            #print('%s included by %s'%(member_type, node_database[member_type]['included_by']))
            if(member_type in node_database['created']):
                tmp_edge_str = space4 + node_name + ':' + member_type + ' -> ' + member_type + ':' + 'head'
                if not tmp_edge_str in edge_list:
                    edge_list.append(tmp_edge_str)
        #for match : void *driver_data;
        elif re.search(r'\s*[0-9a-zA-Z_\-]+\s*(\**[0-9a-zA-Z_\-]+)\s*;', tmpline) and cur_indentation_level > 0:
            m = re.search(r'\s*[0-9a-zA-Z_\-]+\s*(\**[0-9a-zA-Z_\-]+)\s*;', tmpline)
            node_database[node_name]['node_str'] += space16 + '<'+ m.group(1) + '> ' + m.group(1) +  '\l|\n'
        #for match:const char        *init_name;
        elif re.search(r'(.*)\s+(\**)(\s*)([0-9a-zA-Z_\-]+\s*);', tmpline) and cur_indentation_level > 0:
            m = re.search(r'(.*)\s+(\**)(\s*)([0-9a-zA-Z_\-]+\s*);', tmpline)
            node_database[node_name]['node_str'] += space16 + '<'+ m.group(2) + '> ' + m.group(2) + m.group(3) + m.group(4) +  '\l|\n'
        #for match:int *(*runtime_idle)(struct device *dev);
        elif re.search(r'\s*[0-9a-zA-Z_\-]+\s*\**\s*\(\s*(\**\s*[0-9a-zA-Z_\-]+)\s*\)\s*\([^\)]*\)\s*;', tmpline) and cur_indentation_level > 0:
            m = re.search(r'\s*[0-9a-zA-Z_\-]+\s*\**\s*\(\s*(\**\s*[0-9a-zA-Z_\-]+)\s*\)\s*\([^\)]*\)\s*;', tmpline)
            node_database[node_name]['node_str'] += space16 + '<'+ m.group(1) + '> (' + m.group(1) + ')\l|\n'
        #for match: };
        elif re.search(r'\s*\}\s*;', tmpline):
            if(cur_indentation_level >= 1):
                cur_indentation_level -= 1
                if (cur_indentation_level == 0):
                    node_database[node_name]['node_str'] += space12 + '}}\"\n'
                    node_database[node_name]['node_str'] += space8 + 'shape = \"record\"\n' + space4 + '];\n'
                    if 'included_by' in node_database[node_name]:
                        for parent_node in node_database[node_name]['included_by']:
                            if parent_node in node_database['created']:
                                tmp_edge_str = space4 + parent_node + ':' + node_name + ' -> ' + node_name + ':' + 'head'
                                if not tmp_edge_str in edge_list:
                                    edge_list.append(tmp_edge_str)
        tmpline = file_input.readline()

for tmpnode in node_database['created']:
    middle_str = middle_str + node_database[tmpnode]['node_str']
for i, tmpstr in enumerate(edge_list):
    edge_string += tmpstr + '[color="' + color_arrary[i%len(color_arrary)] + '"]\n'

print(prefix + middle_str + '\n' + edge_string + '}')


2、使用方法

绘制C语言结构体关系图方法和流程如下:

(1)把需要绘制关系图的关键数据结构复制粘贴到一个文本文件data_struct中;

(2)把python脚本中保存数据结构的文件路径(/tmp/713/data_struct )替换为自己的保存数据结构的文件路径(可自行修改脚本,通过参数传入文件路径);

(3)执行命令,自动生成关系图,命令如下:

python3 analysis_dt.py > tmpfile
dot -Tsvg tmpfile -o xxx.svg

其中第一条命令使用python分析数据结构并生成用于绘图的dot语言,第二条命令利用graphviz根据tmpfile中的dot语言描述绘图。图形保存到xxx.svg文件中,xxx可以自行命名;生成的xxx.svg文件可以在服务器的命令行使用eog打开,也可以下载到windows上使用浏览器打开,且可以实现缩放。

注意:这里也可以通过dot命令直接生成图片格式,如下:

dot -Tsvg tmpfile -o xxx.png 即可生成xxx.png图片。

这里以一个简单的结构体文本文件data_struct为Demo,查看其内结构体之间的关系。data_struct文本文件内容如下:

struct ovsdb {
char *name;
struct ovsdb_schema *schema;
struct ovsdb_storage *storage; /* If nonnull, log for transactions. */
struct uuid prereq;
struct ovs_list monitors; /* Contains "struct ovsdb_monitor"s. */
struct shash tables; /* Contains "struct ovsdb_table *"s. */

/* Triggers. */
struct ovs_list triggers; /* Contains "struct ovsdb_trigger"s. */
bool run_triggers;
bool run_triggers_now;

struct ovsdb_table *rbac_role;

/* History trasanctions for incremental monitor transfer. */
bool need_txn_history; /* Need to maintain history of transactions. */
unsigned int n_txn_history; /* Current number of history transactions. */
unsigned int n_txn_history_atoms; /* Total number of atoms in history. */
struct ovs_list txn_history; /* Contains "struct ovsdb_txn_history_node. */

size_t n_atoms; /* Total number of ovsdb atoms in the database. */

/* Relay mode. */
bool is_relay; /* True, if database is in relay mode. */
/* List that holds transactions waiting to be forwarded to the server. */
struct ovs_list txn_forward_new;
/* Hash map for transactions that are already sent and waits for reply. */
struct hmap txn_forward_sent;

/* Database compaction. */
struct ovsdb_compaction_state *snap_state;
};

struct ovsdb_storage {
/* There are three kinds of storage:
*
* - Standalone, backed by a disk file. 'log' is nonnull, 'raft' is
* null.
*
* - Clustered, backed by a Raft cluster. 'log' is null, 'raft' is
* nonnull.
*
* - Memory only, unbacked. 'log' and 'raft' are null. */
struct ovsdb_log *log;
struct raft *raft;

char *unbacked_name; /* Name of the unbacked storage. */

/* All kinds of storage. */
struct ovsdb_error *error; /* If nonnull, a permanent error. */
long long next_snapshot_min; /* Earliest time to take next snapshot. */
long long next_snapshot_max; /* Latest time to take next snapshot. */

/* Standalone only. */
unsigned int n_read;
unsigned int n_written;
};

struct ovsdb_table {
struct ovsdb_table_schema *schema;
struct ovsdb_txn_table *txn_table; /* Only if table is in a transaction. */
struct hmap rows; /* Contains "struct ovsdb_row"s. */

/* An array of schema->n_indexes hmaps, each of which contains "struct
* ovsdb_row"s. Each of the hmap_nodes in indexes[i] are at index 'i' at
* the end of struct ovsdb_row, following the 'fields' member. */
struct hmap *indexes;

bool log; /* True if logging is enabled for this table. */
};

struct ovsdb_compaction_state {
pthread_t thread; /* Thread handle. */

struct ovsdb *db; /* Copy of a database data to compact. */

struct json *data; /* 'db' as a serialized json. */
struct json *schema; /* 'db' schema json. */
uint64_t applied_index; /* Last applied index reported by the storage
* at the moment of a database copy. */

/* Completion signaling. */
struct seq *done;
uint64_t seqno;

uint64_t init_time; /* Time spent by the main thread preparing. */
uint64_t thread_time; /* Time spent for compaction by the thread. */
};

执行结果如下:

[root@localhost 713]# vi analysis_dt.py
[root@localhost 713]# python3 analysis_dt.py > tmpfile
[root@localhost 713]# ls
analysis_dt.py  data_struct  tmpfile
[root@localhost 713]# vi tmpfile
[root@localhost 713]# dot -Tsvg tmpfile -o my.svg
[root@localhost 713]# ls
analysis_dt.py  data_struct  my.svg  tmpfile
[root@localhost 713]# eog my.svg

eog打开my.svg文件,结果如下:

根据图片可以看出,成功展现了结构体之间的关系。

三、总结

graphviz很强大,可以用来画各种各样的图,本文只是简单总结一下用于画C语言结构体关系的方法,并做一个记录。小编在看代码的时候,一直想着有什么工具可以实现这个功能,检索了一圈,没找到什么很方便的工具。也有人推荐用draw.io,processon等,但是小编觉得这两个工具画结构体关系图终究是没有这种通过脚本工具的方法方便。因此总结成文,希望对有需要的朋友能够有帮助,也希望有更方便的解决方案的朋友可以反馈给小编,大家一起互帮互助,一起成长。


Linux二进制 Linux编程、内核模块、网络原创文章分享,欢迎关注"Linux二进制"微信公众号
评论
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 164浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 122浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 202浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 116浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 61浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 141浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 106浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 92浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 70浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 58浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦