【问题标题】:Visualising C struct dependencies可视化 C 结构依赖项
【发布时间】:2020-03-11 16:20:25
【问题描述】:

在大型 C 项目中,有许多 structs 具有其他 structs 或指向它们的指针作为字段。我想创建一个有向图来显示“类型”之间的依赖关系。一个例子是

typedef struct javaStat {
    int idNo;
    struct idIdentList *className;
    struct typeModifiers *thisType;
    struct symbol thisClass;
} ...

由此我想生成一个 DOT 结构,看起来像

digraph {
    javaStat -> idIdentList
    javaStat -> typeModifiers
    javaStat -> symbol
}

或者,使用 DOT 简写:

digraph {
    javaStat -> {idIdentList typeModifiers symbol}
}

当然第一行和最后一行可以手动添加,所以主要问题是将结构引用转换为图形“指针”行。

在这一点上,我对一级解决方案感到满意,这意味着可以忽略更深的嵌套。

我首先尝试了一个简单的grep struct *.h,它产生了一些可行的东西:

typedef struct javaStat {
    struct idIdentList *className;
    struct typeModifiers *thisType;
    struct symbol thisClass;
typedef struct <next struct> {

这是一个简单的问题,几行 Python 就可以解决,但是否还有其他方便的解决方案,可能使用 sedgrepawk 和他们的兄弟?

编辑:我意识到我想这样做的原因是因为我需要找到一个或多个位于“结构树”基础的结构。

【问题讨论】:

  • 这将无法注意到类型为别名 (typedef) 的结构成员。
  • @rici 是的,但我现在不在乎。但是对于工业强度的解决方案,您可能应该去实际解析事物,例如pycparser.
  • 正是我的意思,虽然我倾向于clang python binding,因为从头开始构建一个 C 解析器似乎需要做很多工作。您需要问的问题包括(1)您最终是否需要更精确的信息,以及(2)您应该在非正式解决方案上投入多少精力,而不是在学习如何使用真正的 C 解析库方面投入多少精力。但我想你知道这一点。
  • 我真的怀疑任何涉及 sed、grep 或 awk 的解决方案都可以定义为“方便”。它可能与“方便”完全相反。
  • @rici pycparser is 已经是具有节点访问者的 Python 中的完整 C 解析器。没试过clangs,但pycparser帮了我几次。

标签: c struct dot


【解决方案1】:

Clang 9 允许使用 JSON 表示 c 文件的 AST(在此 question 中找到它)。可以进一步处理 JSON AST 以生成目标输出。

例如这个Python 脚本:

#clang_ast_to_dot.py
from jsonpath_rw_ext import parse;
import sys, json;

def extract_struct_name(fieldDefinition):
  return fieldDefinition["type"]["qualType"].replace("struct", "").replace("*", "").replace(" ","")

def is_struct_field(fieldDefinition, knownStructs):
  return (fieldDefinition["kind"] == "FieldDecl" and 
          ("struct " in fieldDefinition["type"]["qualType"] or 
           extract_struct_name(fieldDefinition) in knownStructs))


data = json.load(sys.stdin)

allStructs = {}

for structDef in parse('$.inner[?(@.kind=="RecordDecl")]').find(data):
    allStructs[structDef.value["name"]]=structDef.value

print("digraph {")
for name, structDescription in allStructs.items():
    print("    %s -> {%s}"
          % (name, ", ".join(extract_struct_name(field) for field in structDescription["inner"] if is_struct_field(field, allStructs))))
print("}")

称为:

clang -Xclang -ast-dump=json MyCFile.c | python clang_ast_to_dot.py

产生:

digraph {
    javaStat -> {idIdentList, typeModifiers, symbol}
}

当然,这是一个玩具示例,我相信它不会适用于所有情况。

【讨论】:

  • 在某处有关于 clang AST 的文档吗?
  • 好的,在这里找到了一些:releases.llvm.org/9.0.0/tools/clang/docs/…
  • 谢谢,虽然这是一个有希望的答案,因为这是我第一次遇到 clang AST,但我很难弄清楚所有需要注意的情况。实际解析的缺点是它为您提供了编译器看到的所有 内容,包括所有系统头文件中的数百万个各种类型和形状的结构。我认为我最初的 Python hack 给了我足够的时间。
  • 感谢您的反馈,很高兴知道这种方法在大规模上失败了。 TBH 我只是猜测了一下,还没有用现实世界的例子来尝试过。
【解决方案2】:

我将首先在代码库上运行 Doxygen。它可以轻松配置为创建结构的点图。正确解析所有这些信息并生成正确的输出涉及到足够多的怪癖和极端情况,使用现有解决方案可以节省大量时间。

【讨论】:

  • 虽然这是一个很好的建议,但它并没有真正给我想要的东西。我意识到我没有说明这一点,所以我会将其编辑到问题中,但我的目标是在结构之间找到一个依赖树,以便我知道哪些 structs 不依赖于其他任何一个。而且我找不到任何显示所有structs 的视图。
【解决方案3】:

尝试了@gavinb 的doxygen 建议,@albert 增强,这需要对源进行一些操作,@Renats 建议使用Clangs python 绑定,这对我来说有点复杂时间,我尝试了pycparser

Here's a link to that script 在我需要的项目中。

这是两个重要部分中的第一个:

ast = parse_file(args[-1], use_cpp=True,
                 cpp_args=cpp_args + args[0:-1])
print("digraph {")
for node in (node for node in ast.ext if isinstance(node, c_ast.Typedef)):
    if isinstance(node.type.type, c_ast.Struct):
        node2dot(node)
print("}")

pycparser 将文件解析为 AST 的主循环,然后过滤该 AST 以仅获取输入到 node2dot 的 typedef,它位于以下部分:

def node2dot(node):
    if isinstance(node.type, c_ast.TypeDecl) and isinstance(node.type.type, c_ast.Struct):
        print("   ", node.type.type.name, "-> {", end="")
        if node.type.type.decls:  # has fields?
            for field in node.type.type.decls:
                if isstruct(field.type):
                    print("", struct_name_of(field.type), end="")
        print(" }")

def struct_name_of(node):
    if isinstance(node, c_ast.Struct):
        return node.name
    elif isinstance(node, c_ast.TypeDecl) or isinstance(node, c_ast.PtrDecl):
        return struct_name_of(node.type)

def isstruct(node):
    if isinstance(node, c_ast.Struct):
        return True
    elif isinstance(node, c_ast.TypeDecl) or isinstance(node, c_ast.PtrDecl):
        return isstruct(node.type)

【讨论】:

    【解决方案4】:

    @gavinb 的答案的扩展并带有一些示例。

    拥有一个带有EXTRACT_ALL = YESHAVE_DOT=YES 的doxygen 配置文件(对于更复杂的情况,将DOT_GRAPH_MAX_NODES = 设置为适当的值并设置DOT_IMAGE_FORMAT = svg 可能很有用;UML_LOOK = YES 可能也很有趣)。

    我用了一个简单的例子:

    typedef  struct idIdentList {
         int member;
    };
    typedef  struct typeModifiers {
         int member;
    };
    typedef  struct symbol {
         int member;
    };
    typedef  struct s1 {
         struct s2 member;
    };
    typedef  struct s2 {
         struct s3 member;
    };
    typedef  struct s3 {
         struct s4 member;
    };
    typedef  struct s4 {
         struct s5 member;
    };
    typedef  struct s5 {
         struct s6 member;
    };
    typedef  struct s6 {
         struct s6 member;
    };
    typedef struct javaStat {
        int idNo;
        struct idIdentList *className;
        struct typeModifiers *thisType;
        struct symbol thisClass;
        struct s1 member;
    };
    

    我由此得到:

    Doxygen 没有完整的概览图,但是通过一些脚本可以创建一个“超级结构”,例如(我还在这里添加了struct not_ref,没有额外的参考):

    typedef struct super_script
    {
      struct idIdentList a1;
      struct typeModifiers a2;
      struct symbol a3;
      struct s1 a4;
      struct s2 a5;
      struct s3 a6;
      struct s4 a7;
      struct s5 a8;
      struct s6 a9;
      struct javaStat a10;
      struct not_ref a11;
    };
    
    typedef struct not_ref
    {
      int member;
    };
    

    导致:

    当您设置DOT_CLEANUP = NO 时,使用的dot 文件将在html 目录中可用

    【讨论】:

    • 有趣。 “一点脚本”是什么意思?至于我何时应用该脚本以及应用于哪些文件?氧气输出?或者通过提取我自己文件中的所有结构来提取/创建那个“超结构”?
    • 在运行 doxygen 之前,我的意思是“通过提取我自己的文件中的所有结构来提取/创建该“超结构””。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-01
    • 2011-06-20
    • 2013-10-12
    • 1970-01-01
    • 2016-06-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多