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

2023-09-24 18 0

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]){}//成立)passelif nodeType is ArrayRef:# ArrayRef 是对以声明的数组中某一个位置的值做操作(a[0][0] = 1;)# name = Node (操作的数组名称)# subscript = Node (数组的位置,通常是 Constant)passelif nodeType is Assignment:# Assignment 赋值语句# op = str (操作符: =, +=, -=,...)# lvalue = Node (被赋予的对象)# rvalue = Node (赋予的值)passelif nodeType is BinaryOp:# BinaryOp 二元操作符# op = str (操作符)# left = Node (表达式左侧)# right = Node (表达式右侧)passelif nodeType is Break:# Break 语句,节点无内容passelif nodeType is Case:# Case 条件判断语句# expr = Node (条件表达式)# stmts = [Node] (语句块)passelif nodeType is Cast:# Cast 数据类型转换# to_type = Node (转换后的类型)# expr = Node (需要转换数据类型的表达式)passelif nodeType is Compound:# Compound 复合语句,在C99中表示块项的列表(包含decls或者stmts)# block_items = [Node] (语句列表)passelif nodeType is CompoundLiteral:# CompundLiteral 复合字面量,C99中构造指定类型的匿名对象 ( type ) { initializer-list }# https://zh.cppreference.com/w/c/language/compound_literal# type = Node (复合字面量的类型)# init = Node (复合字面量的初始化列表)passelif nodeType is Constant:# Constant 常量,类型: int, char, float, string, ...# type = str (值类型)# value = str (具体值)passelif nodeType is Continue:# Continue 语句,无节点内容passelif 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)passelif nodeType is DeclList:# DeclList for循环的第一个表达式用此节点表示(for(int i,j;;){},decls会有两个Decl)# decls = [Node] (声明表达式列表)passelif nodeType is Default:# Default switch对应的语句# stmts = [Node] (语句列表)passelif nodeType is DoWhile:# DoWhile dowhile条件代码块# cond = Node (条件语句)# stmt = Node (代码块中的语句,一般是Compound节点)passelif nodeType is EllipsisParam:# EllipsisParam C语言函数中的可变参数声明, int printf(char *input,...)# 无节点内容passelif nodeType is EmptyStatement:# EmptyStatement 空语句,单独一个;符号# 无节点内容passelif nodeType is Enum:# Enum 枚举类型说明符(enum e{a = 1},Enum节点代表'enum e')# name = str (枚举的名称)# values = Node (枚举中列举的值,通常是EnumeratorList)passelif nodeType is Enumerator:# Enumerator 具体的枚举值(enum e{a = 1,b},Enumerator节点代表'a = 1','b')# name = str (枚举中具体的名称)# value = Node (枚举具体值,可以为None)passelif nodeType is EnumeratorList:# EnumeratorList 是Enum的子节点,Enumerator的父节点,用于列举多个枚举值# enumerators = [Node] (列出多个Enumerator节点)passelif nodeType is ExprList:# ExprList 逗号分隔符的表达式列表# exprs = [Node] (逗号分隔的多个表达式,比如for(;;ExprList))passelif nodeType is FileAST:# 读取文件的第一个节点# ext = [Node] (列表有三种类型: Decl, Typedef, FuncDef)passelif nodeType is For:# For 循环 for (init; cond; next) stmt# init = Node# cond = Node# next = Node# stmt = Node (这里不是语句列表,是节点)passelif nodeType is FuncCall:# FuncCall 方法调用# name = Node (ID)# args = Node (ExprList)passelif nodeType is FuncDecl:# FuncDecl 方法声明type <decl>(args) 有两种:一种在方法定义FuncDef节点后出现此节点;# 另一种是在当前方法中调用调用另外一种的、可能出现在任何位置的方法,调用前声明它的存在# 例子:第三个代码段 -> https://zh.cppreference.com/w/c/language/functions# type = Node (一般是TypeDecl,不仅有type,还有<decl>名称)# args = Node (方法的参数)passelif 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 (函数实现的代码块)passelif nodeType is Goto:# Goto 跳转语句,只有一个名称# name = strpasselif nodeType is ID:# ID 名称节点# name = str (变量之类的节点会用ID的name表示出具体名称)passelif nodeType is IdentifierType:# IdentifierType 简单标识符,比如void, char, typedef定义之类# name = [str] (标识符字符串列表)passelif nodeType is If:# If if条件语句,包含了条件成立和条件不成立时执行的内容# cond = Node (条件语句)# iftrue = Node (条件成立的代码块,一般是Compound)# iffalse = Node (条件不成立)passelif nodeType is InitList:# InitList 用于复合字面值Compound Literal的初始化列表 ( type ) { initializer-list }# exprs = [Node] (初始化列表的具体数值,一般是Constant)passelif nodeType is Label:# Label Goto对应的标签# name = str (标签的名称)# stmt = Node (标签当前语句)passelif 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 (初始化器 ‘=’符号右侧表达式)passelif nodeType is ParamList:# ParamList 逗号分隔符的函数参数声明# params = [Node] (参数列表)passelif nodeType is PtrDecl:# PtrDecl 指针# quals = [str] (限定符)# type = Node (指针的名称以及类型信息)passelif nodeType is Return:# Return 返回语句# expr = Node (表达式)passelif nodeType is Struct:# Struct 结构体语句# name = str (结构体标识名称)# decls = [Node] (声明的内容)passelif nodeType is StructRef:# StructRef 结构体引用节点 struct a{int b}; a->b = 0;# name = Node (名称,如a)# type = str (结构体引用方式,如-> 还有.)# field = Node (变量b)passelif nodeType is Switch:# Switch 条件判断语句# cond = Node (条件)# stmt = Node (语句块节点,通常是Compound)passelif nodeType is TernaryOp:# TernaryOp 三元表达式 cond ? iftrue : iffalse# cond = Node (条件)# iftrue = Node (条件正确的语句)# iffalse = Nodepasselif nodeType is TypeDecl:# TypeDecl 类型声明 quals type declname# quals = [str] (限定符)# type = Node (类型)# declname = str (名称)passelif nodeType is Typedef:# Typedef 类型定义,和Decl类似,但没有其节点的部分属性# storage = [str] (存储说明符号列表)# quals = [str] (限定符)# name = str (typedef内容的名称)# type = Node (typedef其中的具体语句)passelif nodeType is Typename:# 未知何时出现# Typename 在官方代码中解释是说明符号列表。# name = str# quals = [str]# type = Nodepasselif nodeType is UnaryOp:# UnaryOp 一元操作符# op = str (操作符)# expr = Node (表达式)passelif nodeType is Union:# Union 共用体# name = str (名称)# decls = [Node] (共用体成员)passelif nodeType is While:# While 循环语句# cond = Node (条件语句)# stmt = Node (表达式)passelif 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时,不会显示出该属性名称,会麻烦很多)

代码编程
赞赏

相关文章

动态改变shape color
一张图看懂阿里云网络产品[一]网络产品概览
bug人生–CF的那段时光
地图常见操作总结
西门子医疗创新产品与解决方案亮相第31届国际医疗仪器设备展览会
WebView详解:Android和Js交互