python环境做C语言分析-pycparser的使用方法(1)

2022年底的更新

推荐用tree-sitter做语法解析,语言多,调试容易。现在许多基于深度学习的科研项目都在使用,还不试试看?
python环境解析任意编程语言 tree-sitter使用方法(1)
python环境解析任意编程语言 tree-sitter使用方法(2)

如果要解析C语言,并且要深度分析抽象节点,希望本文能有帮助。🤗

原正文

在网上转了一圈,好像对此包的使用分析文章太少,所以引出此文做一个介绍。

安装需要环境

在python3环境中安装pycparser。

如果要处理C语言代码中的#include或者#define语句,需要gcc或者clang的支持(当然可以忽略掉,不用纠结编译上的问题啦)。

个人使用的是MinGW,印象中LLVM也可以,但会出一些问题,所以我选择前者。

我只推荐用来处理#define语句,编译器会根据#define的内容替换掉代码 (处理#include语句,会引入include文件中很多不相关的抽象语法树信息)

基本的使用方法

导入pycparser

# parser_file 用于处理c语言文件
from pycparser import parse_file
# c语言有错误时,会引出此错误
from pycparser.plyparser import ParseError
# c_ast.py 文件下包含了抽象语法树的节点类
from pycparser.c_ast import *

有细心的朋友会发现pycparser的_c_ast.cfg 文件有关文件的详细描述,对每种节点类做了分析,个人对节点属性做了总结:

''' pycparser定义的一些规则:
dim: the dimension
dim_quals: list of dimension qualifiers
op: =, +=, /= etc.
type: the typename
init: InitList for the initializer list
type: int, char, float, etc. see CLexer for constant token types
name: the variable being declared
quals: list of qualifiers (const, volatile)
funcspec: list function specifiers (i.e. inline in C99)
storage: list of storage specifiers (extern, register, etc.)
type: declaration type (probably nested with all the modifiers)
init: initialization value, or None
bitsize: bit field size, or None'''

这些属性信息方便我们对节点做操作。

获取c语言文件的抽象语法树ast,如果要处理#include语句,需要下载fake_libc_include文件夹,让编译器预处理常用的方法(添加其到代码的抽象语法树中)

# fake_libc_include文件夹放在处理的c语言目录下
ast = parse_file(filename, use_cpp = True, cpp_path=r'C:\MinGW\bin\gcc.exe', cpp_args=['-E', r'-Iutils/fake_libc_include'])
# 展示ast结构(官方的方法)
ast.show()
# 打印ast节点及其属性
print(ast)

parser_file() 方法也可以设置use_cpp=False,不用本地的c语言编译器预处理代码,就能输出抽象语法树。

with open(filename, encoding='utf-8') as f:
	txt = f.read()
ast = c_parser.CParser().parse(txt)

分析代码的时候,可以对每种节点单独做分析,也可以用统一的方法直接处理,接下来举个例子

def get_tree_node(node):
    nodeType = type(node)
    
    if nodeType is ArrayDecl:
        # ArrayDecl 是给定类型数组的嵌套声明(int a[1][2] 外层是1维度的ArrayDecl,嵌套2维的ArrayDecl)
        # type = Node (数组节点的类型,int a[1][2] 外层type = ArrayDecl,内部type = int)
        # dim = Node (数组维度的表示,Constant/ ID)
        # dim_quals = [str] (数组维度的限定符号,C99允许在方法声明中加上: static, const;void f(int a[const]){}//成立)
        pass

    elif nodeType is ArrayRef:
        # ArrayRef 是对以声明的数组中某一个位置的值做操作(a[0][0] = 1;)
        # name = Node (操作的数组名称)
        # subscript = Node (数组的位置,通常是 Constant)
        pass

    elif nodeType is Assignment:
        # Assignment 赋值语句
        # op = str (操作符: =, +=, -=,...)
        # lvalue = Node (被赋予的对象)
        # rvalue = Node (赋予的值)
        pass

    elif nodeType is BinaryOp:
        # BinaryOp 二元操作符
        # op = str (操作符)
        # left = Node (表达式左侧)
        # right = Node (表达式右侧)
        pass

    elif nodeType is Break:
        # Break 语句,节点无内容
        pass

    elif nodeType is Case:
        # Case 条件判断语句
        # expr = Node (条件表达式)
        # stmts = [Node] (语句块)
        pass

    elif nodeType is Cast:
        # Cast 数据类型转换
        # to_type = Node (转换后的类型)
        # expr = Node (需要转换数据类型的表达式)
        pass

    elif nodeType is Compound:
        # Compound 复合语句,在C99中表示块项的列表(包含decls或者stmts)
        # block_items = [Node] (语句列表)
        pass

    elif nodeType is CompoundLiteral:
        # CompundLiteral 复合字面量,C99中构造指定类型的匿名对象 ( type ) { initializer-list }
        # https://zh.cppreference.com/w/c/language/compound_literal
        # type = Node (复合字面量的类型)
        # init = Node (复合字面量的初始化列表)
        pass

    elif nodeType is Constant:
        # Constant 常量,类型: int, char, float, string, ...
        # type = str (值类型)
        # value = str (具体值)
        pass

    elif nodeType is Continue:
        # Continue 语句,无节点内容
        pass

    elif nodeType is Decl:
        # declaration 声明节点
        # name = str (被声明的变量)
        # quals = [str] (限定符号列表: const, volatile)
        # storage = [str] (存储说明符列表: extern, register, etc.)
        # funcspec = [str] (函数说明符列表: C99的inline)
        # type = Node (声明节点的类型,可能与修饰符嵌套)
        # init = Node (初始化值,或者为None)
        # bitsize = Node (位域bit field大小,或者为None)
        pass

    elif nodeType is DeclList:
        # DeclList for循环的第一个表达式用此节点表示(for(int i,j;;){},decls会有两个Decl)
        # decls = [Node] (声明表达式列表)
        pass

    elif nodeType is Default:
        # Default switch对应的语句
        # stmts = [Node] (语句列表)
        pass

    elif nodeType is DoWhile:
        # DoWhile dowhile条件代码块
        # cond = Node (条件语句)
        # stmt = Node (代码块中的语句,一般是Compound节点)
        pass

    elif nodeType is EllipsisParam:
        # EllipsisParam C语言函数中的可变参数声明, int printf(char *input,...)
        # 无节点内容
        pass

    elif nodeType is EmptyStatement:
        # EmptyStatement 空语句,单独一个;符号
        # 无节点内容
        pass

    elif nodeType is Enum:
        # Enum 枚举类型说明符(enum e{a = 1},Enum节点代表'enum e')
        # name = str (枚举的名称)
        # values = Node (枚举中列举的值,通常是EnumeratorList)
        pass

    elif nodeType is Enumerator:
        # Enumerator 具体的枚举值(enum e{a = 1,b},Enumerator节点代表'a = 1','b')
        # name = str (枚举中具体的名称)
        # value = Node (枚举具体值,可以为None)
        pass

    elif nodeType is EnumeratorList:
        # EnumeratorList 是Enum的子节点,Enumerator的父节点,用于列举多个枚举值
        # enumerators = [Node] (列出多个Enumerator节点)
        pass

    elif nodeType is ExprList:
        # ExprList 逗号分隔符的表达式列表
        # exprs = [Node] (逗号分隔的多个表达式,比如for(;;ExprList))
        pass

    elif nodeType is FileAST:
        # 读取文件的第一个节点
        # ext = [Node] (列表有三种类型: Decl, Typedef, FuncDef)
        pass

    elif nodeType is For:
        # For 循环 for (init; cond; next) stmt
        # init = Node
        # cond = Node
        # next = Node
        # stmt = Node (这里不是语句列表,是节点)
        pass

    elif nodeType is FuncCall:
        # FuncCall 方法调用
        # name = Node (ID)
        # args = Node (ExprList)
        pass

    elif nodeType is FuncDecl:
        # FuncDecl 方法声明type <decl>(args) 有两种:一种在方法定义FuncDef节点后出现此节点;
        # 另一种是在当前方法中调用调用另外一种的、可能出现在任何位置的方法,调用前声明它的存在
        # 例子:第三个代码段 -> https://zh.cppreference.com/w/c/language/functions
        # type = Node (一般是TypeDecl,不仅有type,还有<decl>名称)
        # args = Node (方法的参数)
        pass

    elif nodeType is FuncDef:
        # FuncDef 方法定义,不同于第二种FuncDecl,有具体的函数实现过程
        # decl = Node (一般是包含FuncDecl的Decl节点,方法的声明)
        # param_decls = [Node]或None (一般为None,除非是K&R风格的C语言声明样式,https://stackoverflow.com/questions/3092006/function-declaration-kr-vs-ansi)
        # body = Node (函数实现的代码块)
        pass

    elif nodeType is Goto:
        # Goto 跳转语句,只有一个名称
        # name = str
        pass

    elif nodeType is ID:
        # ID 名称节点
        # name = str (变量之类的节点会用ID的name表示出具体名称)
        pass

    elif nodeType is IdentifierType:
        # IdentifierType 简单标识符,比如void, char, typedef定义之类
        # name = [str] (标识符字符串列表)
        pass

    elif nodeType is If:
        # If if条件语句,包含了条件成立和条件不成立时执行的内容
        # cond = Node (条件语句)
        # iftrue = Node (条件成立的代码块,一般是Compound)
        # iffalse = Node (条件不成立)
        pass

    elif nodeType is InitList:
        # InitList 用于复合字面值Compound Literal的初始化列表 ( type ) { initializer-list }
        # exprs = [Node] (初始化列表的具体数值,一般是Constant)
        pass

    elif nodeType is Label:
        # Label Goto对应的标签
        # name = str (标签的名称)
        # stmt = Node (标签当前语句)
        pass

    elif nodeType is NamedInitializer:
        # NamedInitializer C99的初始化 指派符列表 = 初始化器 -> https://zh.cppreference.com/w/c/language/initialization
        # 例如:struct {int a[3], b;} w[] = {[0].a = {1}, [1].a[0] = 2}; 中,[0].a = {1} 和 [1].a[2] = 2
        # name = [Node] (上述初始化器,数组索引是Constant,结构体变量索引是ID)
        # expr = Node (初始化器 ‘=’符号右侧表达式)
        pass

    elif nodeType is ParamList:
        # ParamList 逗号分隔符的函数参数声明
        # params = [Node] (参数列表)
        pass

    elif nodeType is PtrDecl:
        # PtrDecl 指针
        # quals = [str] (限定符)
        # type = Node (指针的名称以及类型信息)
        pass

    elif nodeType is Return:
        # Return 返回语句
        # expr = Node (表达式)
        pass

    elif nodeType is Struct:
        # Struct 结构体语句
        # name = str (结构体标识名称)
        # decls = [Node] (声明的内容)
        pass

    elif nodeType is StructRef:
        # StructRef 结构体引用节点 struct a{int b}; a->b = 0;
        # name = Node (名称,如a)
        # type = str (结构体引用方式,如-> 还有.)
        # field = Node (变量b)
        pass

    elif nodeType is Switch:
        # Switch 条件判断语句
        # cond = Node (条件)
        # stmt = Node (语句块节点,通常是Compound)
        pass

    elif nodeType is TernaryOp:
        # TernaryOp 三元表达式 cond ? iftrue : iffalse
        # cond = Node (条件)
        # iftrue = Node (条件正确的语句)
        # iffalse = Node
        pass

    elif nodeType is TypeDecl:
        # TypeDecl 类型声明 quals type declname
        # quals = [str] (限定符)
        # type = Node (类型)
        # declname = str (名称)
        pass

    elif nodeType is Typedef:
        # Typedef 类型定义,和Decl类似,但没有其节点的部分属性
        # storage = [str] (存储说明符号列表)
        # quals = [str] (限定符)
        # name = str (typedef内容的名称)
        # type = Node (typedef其中的具体语句)
        pass

    elif nodeType is Typename:
        # 未知何时出现
        # Typename 在官方代码中解释是说明符号列表。
        # name = str
        # quals = [str]
        # type = Node
        pass

    elif nodeType is UnaryOp:
        # UnaryOp 一元操作符
        # op = str (操作符)
        # expr = Node (表达式)
        pass

    elif nodeType is Union:
        # Union 共用体
        # name = str (名称)
        # decls = [Node] (共用体成员)
        pass

    elif nodeType is While:
        # While 循环语句
        # cond = Node (条件语句)
        # stmt = Node (表达式)
        pass

    elif nodeType is Pragma:
        # Pragma C语言预处理 #pragma string -> https://zh.cppreference.com/w/cpp/preprocessor/impl
        # string = str (定义的行为控制)
        pass

python环境做C语言分析-pycparser的使用方法(2) 后来发现还可以根据节点内部的child属性做分析,但是该方法不算太好,属性为None的节点会自动过滤,所以需要适当做选择。

应用

可以根据AST节点,还原代码控制流图,写出变量的define和use图像:
CFG-dupath-of-C

// 归并排序代码
#include <stdio.h>
#define MAXSIZE 10
void merging(int *list1, int list1_size, int *list2, int list2_size)
{
    int i,j,k, m;
    int temp[MAXSIZE];
    i = j = k = 0;
    while(i < list1_size && j < list2_size)
    {
        if(list1[i] < list2[j])
        {
            temp[k] = list1[i];
            k++;
            i++;
        }
        else
        {
            temp[k++] = list2[j++];
        }
    }
    while(i < list1_size)
    {
        temp[k++] = list1[i++];
    }
    while(j < list2_size)
    {
        temp[k++] = list2[j++];
    }
    for(m = 0;m < (list1_size + list2_size);m++)
    {
        list1[m] = temp[m];
    }
}

void MergeSort(int k[], int n)
{
    if(n > 1)
    {
        int *list1 = k;
        int list1_size = n/2;
        int *list2 = k + list1_size;
        int list2_size = n - list1_size;
        MergeSort(list1, list1_size);
        MergeSort(list2, list2_size);
        merging(list1, list1_size, list2, list2_size);
    }

}

int main()
{
    int i, a[10] = {5, 2, 6, 0, 3, 9, 1, 7, 4, 8};
    MergeSort(a, 10);
    for(i = 0;i < 10;i++)
    {
        printf("%d", a[i]);
    }
    printf("\n\n");
    return 0;
}

归并排序控制流图control flow graph cfg和dupath

更新

小更新,python环境做C语言分析-pycparser的使用方法(2),采用更简单的方法对ast节点做遍历操作,也可以统一处理节点属性,相比此方法更方便(缺点是某些属性为None时,不会显示出该属性名称,会麻烦很多)

  • 6
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
C语言的词法分析程序是用来对源程序进行词法分析的工具。词法分析的目的是将源程序按照语法规则划分成一个个单词,包括关键字、自定义标识符、整数、界符和运算符。关键字是预定义的特殊单词,如main、if、else、for、while和int。自定义标识符是除关键字外的其他标识符。整数是无符号的数字。界符是一些特殊符号,如{ } ( ) , ;。运算符则包括= - * / < <= > >= == !=。为了实现词法分析,可以使用NFA(非确定有穷自动机)和DFA(确定有穷自动机)的方法构造词法分析程序。这个程序可以对输入的字符串流进行词法分析,识别出关键字、运算符和定界符,并识别其他单词为标识符(id)和整型常数(num)。词法分析程序的输出形式是按照二元组单词串的形式输出。构造一个小C语言的词法分析程序可以使用正规式来定义词法规则。例如,标识符可以由字母开头,后面可以是字母或数字,整数由数字组成。词法分析程序将按照这些规则来识别不同的单词类型。在词法分析程序中,还可以使用产生式来定义语法规则,如因子可以是标识符、无符号整数或算术表达式等。如果您想使用Python编写一个小C语言的词法分析程序,您可以根据上述的词法规则和语法规则来设计程序的逻辑,使用正则表达式来匹配不同的单词类型,并将其输出为二元组单词串的形式。在程序中,您可以使用字符串处理和正则表达式库来辅助实现词法分析的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [A - 小C语言--词法分析程序](https://blog.csdn.net/qq_46452300/article/details/123394158)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [【编译原理】使用python构造高级语言c语言的词法分析程序,模拟词法分析过程](https://blog.csdn.net/The_Handsome_Sir/article/details/124392414)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值