前言

Joern在解析php源码时,会先生成Control Flow Graph,然后生成UDG(一个过渡Graph),接着生成Data Dependency Graph,最后处理Call Graph。现在就先来学习下Jeorn是怎么生成Control Flow Graph的。

Switch Demo

官方手册:https://www.php.net/manual/zh/control-structures.switch.php

利用Joern生成下面switch语句的CPG代码属性图:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
switch ($i) {
case "apple":
echo "i is apple";
break;
case "bar":
echo "i is bar";
break;
case "cake":
echo "i is cake";
break;
}
?>

对应的CPG图,下图中蓝色AST结点中的数字是lineno:

1

而下图蓝色AST结点中的数字代表的则是id:

0

其中不存在PDG图,也就是数据流图,没有REACHES关系边。从中抽出CFG图为:

2

nodes.csv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ cat nodes.csv 
id:int labels:label type flags:string_array lineno:int code childnum:int funcid:int classname namespace endlineno:int name doccomment
0 Filesystem File "a.php"
1 AST AST_TOPLEVEL TOPLEVEL_FILE 1 "" 13 "a.php"
2 Artificial CFG_FUNC_ENTRY 1 "a.php"
3 Artificial CFG_FUNC_EXIT 1 "a.php"
4 AST AST_STMT_LIST 1 0 1 ""
5 AST AST_SWITCH 2 0 1 ""
6 AST AST_VAR 2 0 1 ""
7 AST string 2 "i" 0 1 ""
8 AST AST_SWITCH_LIST 3 1 1 ""
9 AST AST_SWITCH_CASE 3 0 1 ""
10 AST string 3 "apple" 0 1 ""
11 AST AST_STMT_LIST 3 1 1 ""
12 AST AST_ECHO 4 0 1 ""
13 AST string 4 "i is apple" 0 1 ""
14 AST AST_BREAK 5 1 1 ""
15 AST NULL 5 0 1 ""
16 AST AST_SWITCH_CASE 6 1 1 ""
17 AST string 6 "bar" 0 1 ""
18 AST AST_STMT_LIST 6 1 1 ""
19 AST AST_ECHO 7 0 1 ""
20 AST string 7 "i is bar" 0 1 ""
21 AST AST_BREAK 8 1 1 ""
22 AST NULL 8 0 1 ""
23 AST AST_SWITCH_CASE 9 2 1 ""
24 AST string 9 "cake" 0 1 ""
25 AST AST_STMT_LIST 9 1 1 ""
26 AST AST_ECHO 10 0 1 ""
27 AST string 10 "i is cake" 0 1 ""
28 AST AST_BREAK 11 1 1 ""
29 AST NULL 11 0 1 ""
30 AST NULL 1 1 1 ""

附上更清晰的图片:

3

rels.csv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ cat rels.csv 
start end type
1 2 ENTRY
1 3 EXIT
6 7 PARENT_OF
5 6 PARENT_OF
9 10 PARENT_OF
12 13 PARENT_OF
11 12 PARENT_OF
14 15 PARENT_OF
11 14 PARENT_OF
9 11 PARENT_OF
8 9 PARENT_OF
16 17 PARENT_OF
19 20 PARENT_OF
18 19 PARENT_OF
21 22 PARENT_OF
18 21 PARENT_OF
16 18 PARENT_OF
8 16 PARENT_OF
23 24 PARENT_OF
26 27 PARENT_OF
25 26 PARENT_OF
28 29 PARENT_OF
25 28 PARENT_OF
23 25 PARENT_OF
8 23 PARENT_OF
5 8 PARENT_OF
4 5 PARENT_OF
4 30 PARENT_OF
1 4 PARENT_OF
0 1 FILE_OF

cpg_edges.csv

输入nodes.csv文件和rel.csv文件,最后输出的文件cpg_edges.csv内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
start	end	type	var
2 6 FLOWS_TO
6 12 FLOWS_TO
6 19 FLOWS_TO
6 26 FLOWS_TO
6 30 FLOWS_TO
12 14 FLOWS_TO
14 30 FLOWS_TO
19 21 FLOWS_TO
21 30 FLOWS_TO
26 28 FLOWS_TO
28 30 FLOWS_TO
30 3 FLOWS_TO

生成的edges.csv中只有控制流上的关系。

Joern源码分析

CFG生成入口

下面是Joern在生成PHP代码属性图时的入口函数Main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package tools.php.ast2cpg;

import java.io.FileReader;
import java.io.IOException;

import org.apache.commons.cli.ParseException;

import ast.php.functionDef.FunctionDef;
import cfg.ASTToCFGConverter;
import cfg.CFG;
import cfg.PHPCFGFactory;
import cg.CG;
import cg.PHPCGFactory;
import ddg.CFGAndUDGToDefUseCFG;
import ddg.DDGCreator;
import ddg.DataDependenceGraph.DDG;
import ddg.DefUseCFG.DefUseCFG;
import inputModules.csv.KeyedCSV.exceptions.InvalidCSVFile;
import inputModules.csv.csvFuncExtractor.CSVFunctionExtractor;
import outputModules.common.Writer;
import outputModules.csv.MultiPairCSVWriterImpl;
import outputModules.csv.exporters.CSVCFGExporter;
import outputModules.csv.exporters.CSVCGExporter;
import outputModules.csv.exporters.CSVDDGExporter;
import udg.CFGToUDGConverter;
import udg.php.useDefAnalysis.PHPASTDefUseAnalyzer;
import udg.useDefGraph.UseDefGraph;

public class Main {

// command line interface
static CommandLineInterface cmdLine = new CommandLineInterface();

// converters
static CSVFunctionExtractor extractor = new CSVFunctionExtractor();
//static PHPCFGFactory cfgFactory = new PHPCFGFactory();
static ASTToCFGConverter ast2cfgConverter = new ASTToCFGConverter();
static CFGToUDGConverter cfgToUDG = new CFGToUDGConverter();
static CFGAndUDGToDefUseCFG udgAndCfgToDefUseCFG = new CFGAndUDGToDefUseCFG();
static DDGCreator ddgCreator = new DDGCreator();

// exporters
static CSVCFGExporter csvCFGExporter = new CSVCFGExporter();
static CSVDDGExporter csvDDGExporter = new CSVDDGExporter();
static CSVCGExporter csvCGExporter = new CSVCGExporter();

public static void main(String[] args) throws InvalidCSVFile, IOException {

// parse command line
// [My Annotation]: 读取命令行参数
parseCommandLine(args);

// initialize readers
String nodeFilename = cmdLine.getNodeFile();
String edgeFilename = cmdLine.getEdgeFile();
FileReader nodeFileReader = new FileReader(nodeFilename);
FileReader edgeFileReader = new FileReader(edgeFilename);

// initialize converters

extractor.setInterpreters(new PHPCSVNodeInterpreter(), new PHPCSVEdgeInterpreter());

extractor.initialize(nodeFileReader, edgeFileReader);
ast2cfgConverter.setFactory(new PHPCFGFactory());
cfgToUDG.setASTDefUseAnalyzer(new PHPASTDefUseAnalyzer());

// initialize writers
MultiPairCSVWriterImpl csvWriter = new MultiPairCSVWriterImpl();
csvWriter.openEdgeFile( ".", "cpg_edges.csv");
Writer.setWriterImpl( csvWriter);

// let's go...
FunctionDef rootnode;
while ((rootnode = (FunctionDef)extractor.getNextFunction()) != null) {

PHPCGFactory.addFunctionDef( rootnode);

CFG cfg = ast2cfgConverter.convert(rootnode);
csvCFGExporter.writeCFGEdges(cfg);

UseDefGraph udg = cfgToUDG.convert(cfg);
DefUseCFG defUseCFG = udgAndCfgToDefUseCFG.convert(cfg, udg);
DDG ddg = ddgCreator.createForDefUseCFG(defUseCFG);
csvDDGExporter.writeDDGEdges(ddg);
}

// now that we wrapped up all functions, let's finish off with the call graph
CG cg = PHPCGFactory.newInstance();
csvCGExporter.writeCGEdges(cg);

csvWriter.closeEdgeFile();
}

private static void parseCommandLine(String[] args) {

try {
cmdLine.parseCommandLine(args);
}
catch (RuntimeException | ParseException e) {
printHelpAndTerminate(e);
}
}

private static void printHelpAndTerminate(Exception e) {

System.err.println(e.getMessage());
cmdLine.printHelp();
System.exit(0);
}

}

其中第37行的:

1
static ASTToCFGConverter ast2cfgConverter = new ASTToCFGConverter();

和第64行的:

1
ast2cfgConverter.setFactory(new PHPCFGFactory());

以及76行至79行的CFG处理流程:

1
2
3
4
PHPCGFactory.addFunctionDef( rootnode);

CFG cfg = ast2cfgConverter.convert(rootnode);
csvCFGExporter.writeCFGEdges(cfg);

若上面代码没有行号,可以看下图:

4

CFGFactory初始化

在整个CFG解析过程中,从ASTToCFGConverter类开始,将AST对象转成CFG对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package cfg;

import ast.functionDef.FunctionDefBase;

public class ASTToCFGConverter
{

private CFGFactory factory;

public void setFactory(CFGFactory factory)
{
// prior to converting a function node,
// this method should be used to set the
// CFG factory for a particular language,
// such as a C CFG factory or a PHP CFG factory.
// [My Annotation]: 为某种语言设置特定的factory,比如php就设置为PHPCFGFactory
this.factory = factory;
}


public CFG convert(FunctionDefBase node)
{
// [My Annotation]: 调用PHPCFGFactory.newInstance来生成一个CFG对象
return factory.newInstance(node);
}
}

6

在从根节点构建CFG图之前,首先确定使用的CFGFactory为PHPCFGFactoryPHPCFGFactory类继承自jpanlib中的父类CFGFactory,它是joern-phpPHPCFGFactory的基类,同时也是joern-fuzzycCCFGFactory的基类。前者是生成php源码CFG的,后者是用来生成fuzzy-c源码CFG的。

CFGFactory:joern/projects/extensions/joern-fuzzyc/src/main/java/cfg/CCFGFactory.java

PHPCFGFactory:joern/projects/extensions/joern-php/src/main/java/cfg/PHPCFGFactory.java

CCFGFactory:joern/projects/extensions/joern-fuzzyc/src/main/java/cfg/CCFGFactory.java

它们之间的继承关系如下图所示:

7

CFG根节点

结点类型为TOPLevelFUnctionDef,是ASTNode类型的结点中特殊的结点之一。整个过程是比较比复杂的,所以我打算在另一篇文章中再阐述。

8

跟进PHPCFGFactory.addFunctionDef()

9

直接返回null。

CFG对象实例化

Control Flow Graph显式地描述了执行代码语句的顺序,以及执行特定执行路径所需要满足的条件。因此,statements和predicates都应该用结点来表示,并且这些结点需要由有向边连接来表示控制流的传递。CFG可以由抽象语法树转化而来。首先,考虑结构化控制语句(constructed control statements),例如,ifwhilefor等等来初步构建控制流图。然后,再考虑像gotobreakcontinue之类的非结构化控制语句(unstructured control statements)来修正初步控制流程图。(来自S&P’14, Modeling and Discovering Vulnerabilities with Code Property Graphs,本文的研究成果就是joern工具)

这里用一张思维导图稍微补充一下流程控制语句中的结构化语句和非结构化语句

13

参考C语言5种程序语句(1)——流程控制语句中的结构化语句(条件语句和循环语句)

在第77行,使用ASTToCFGConverter类的实例化对象ast2cfgConverter,从每个function definition node开始,一步一步生成CFG图:

10

跟进ASTToCFGConverter.convert()(注意区分和后面CFGFactory.convert()函数之间的区别),调用factory对象的newInstance()方法,该factory已经被初始化为PHPCFGFactory

11

Class:FunctionDefBase

FunctionDefBaseFunctionDef的父类,但是无论是父类还是子类,它们都是一种ASTNode,继承关系为:

59

FunctionDefBase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package ast.functionDef;

import ast.ASTNode;
import ast.logical.statements.CompoundStatement;

public abstract class FunctionDefBase extends ASTNode
{
protected ParameterList parameterList = null;
protected CompoundStatement content = null;

public abstract String getName();

public abstract String getFunctionSignature();

public ParameterList getParameterList()
{
return this.parameterList;
}

public void setParameterList(ParameterList parameterList)
{
this.parameterList = parameterList;
super.addChild(parameterList);
}

public CompoundStatement getContent()
{
return this.content;
}

@Override
public String getEscapedCodeStr()
{
setCodeStr(getFunctionSignature());
return getCodeStr();
}

public void setContent(CompoundStatement content)
{
this.content = content;
super.addChild(content);
}
}

FunctionDefBase有两个protected成员变量,ParameterListCompoundStatementCompoundStatement是什么呢?它定义在:

joern/projects/extensions/jpanlib/src/main/java/ast/logical/statements/CompoundStatement.java

继承自类Statement,也是一种ASTNode结点。继承关系如下图所示:

26

CompoundStatement是一种比较特殊的Statement类型,是一种复合类型的statement,比如一个for循环,其内部所有的语句,我们都可以将其认为是CompoundStatement

至于ParameterList,我猜测是这样的,比如有一个函数,它有一些参数,ParameterList中就会维护这些参数。当然这个case中没有体现出这些,是我的猜测,比如一个函数为:

1
2
3
4
5
6
<?php

function foo ($a, $b)
{
$c = $a + $b;
}

那该FunctionDefBase对象对应的parameterList为:

60

PHPCFGFactory.newInstace

回到前面的执行路径,继续跟踪,又了调用PHPCFGFactory.newInstance(node)。我们可以看到在PHPCFGFactory中,存在很多newInstance()同名函数,因为不同语句的结构存在差别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package cfg;

import ...

public class PHPCFGFactory extends CFGFactory {

// node id offsets for entry and exit nodes of function definitions
private static final int CFG_ENTRY_OFFSET = 1;
private static final int CFG_EXIT_OFFSET = 2;

static {
structuredFlowVisitior = new PHPStructuredFlowVisitor();
}

public PHPCFGFactory() {
structuredFlowVisitior = new PHPStructuredFlowVisitor();
}

// 处理FunctionDef node结点
@Override
public CFG newInstance(FunctionDefBase functionDefinition) { ... }

// 处理if条件语句
public static CFG newInstance(IfStatementBase ifStatement) { ... }

// 处理if条件句内的元素结点
private static CFG convertIfElem(Iterator<IfElement> iterator) { ... }

// 处理switch语句
public static CFG newInstance(SwitchStatement switchStatement) { ... }

// 处理foreach语句
public static CFG newInstance(ForEachStatement forEachStatement) { ... }

// 处理try...catch语句
public static CFG newInstance(TryStatement tryStatement) { ... }
}

但是我们很容易发现,还有一些语句,比如whileforbreak语句都没有出现,它们则是在PHPCFGFactory的父类CFGFactory中被定义,像源码中的break语句就是交由newInstance(BreakStatement breakStatement)来处理的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package cfg;

import ...

public class CFGFactory
{
protected static StructuredFlowVisitor structuredFlowVisitior;

public CFG newInstance(FunctionDefBase functionDefinition) { ... }

public static CFG newInstance(ASTNode... nodes) { ... }

public static CFG newErrorInstance() n{ ... }


public static CFG newInstance(WhileStatement whileStatement) { ... }

public static CFG newInstance(ForStatement forStatement) { ... }

public static CFG newInstance(DoStatement doStatement) { ... }

public static CFG newInstance(TryStatement tryStatement) { ... }

public static CFG newInstance(SwitchStatement switchStatement) { ... }

public static CFG newInstance(ParameterList paramList) { ... }

public static CFG newInstance(CompoundStatement content) { ... }

public static CFG newInstance(ReturnStatement returnStatement) { ... }

public static CFG newInstance(GotoStatement gotoStatement) { ... }

public static CFG newInstance(Label labelStatement) { ... }

public static CFG newInstance(ContinueStatement continueStatement) { ... }

public static CFG newInstance(BreakStatement breakStatement) { ... }

public static CFG newInstance(ThrowStatement throwStatement) { ... }

public static CFG convert(ASTNode node) { ... }

public static void fixBreakStatements(CFG thisCFG, CFGNode target) { ... }

public static void fixContinueStatement(CFG thisCFG, CFGNode target) { ... }

private static void fixBreakOrContinueStatements(CFG thisCFG, CFGNode target, Iterator<CFGNode> it) { ... }

public static void fixGotoStatements(CFG thisCFG) { ... }

public static void fixReturnStatements(CFG thisCFG) { ... }
}

继续跟进该函数CFGFactory.newInstance(FunctionDefBase functionDefinition)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package cfg;

import ...

public class PHPCFGFactory extends CFGFactory {

// node id offsets for entry and exit nodes of function definitions
private static final int CFG_ENTRY_OFFSET = 1;
private static final int CFG_EXIT_OFFSET = 2;

static {
structuredFlowVisitior = new PHPStructuredFlowVisitor();
}

public PHPCFGFactory() {
structuredFlowVisitior = new PHPStructuredFlowVisitor();
}

@Override
public CFG newInstance(FunctionDefBase functionDefinition)
{
CFG cfg = super.newInstance(functionDefinition);

// for PHP, we additionally set the node ids for the function entry and exit nodes:
// their ids match the ids of the entry and exit nodes output by the PHP AST parser
Long id = functionDefinition.getNodeId(); // 一般是1
// 分别为entry node和exit node设置id,并且将类型分别转为特殊的CFGEntryNode和CFGExitNode
((CFGEntryNode)cfg.getEntryNode()).setNodeId( id + CFG_ENTRY_OFFSET);
((CFGExitNode)cfg.getExitNode()).setNodeId( id + CFG_EXIT_OFFSET);

return cfg;
}
......
}

PHPCFGFactory.newInstance(FUnctionDefBase functionDefinition)重写了父类CFGFactorynewInstance(FunctionDefBase functionDefinition)方法。先调用父类的方法,即super.newInstance()来生成初步的CFG。

12

CFGFactory.newInstance

看下CFGFactory.newInstance(FunctionDefBase functionDefinition)是如何初始化并且构建一个CFG图的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package cfg;

import ...

public class CFGFactory
{
protected static StructuredFlowVisitor structuredFlowVisitior;

public CFG newInstance(FunctionDefBase functionDefinition)
{
try
{
CFG function = newInstance();
CFG parameterBlock = convert(
functionDefinition.getParameterList());
CFG functionBody = convert(functionDefinition.getContent());
parameterBlock.appendCFG(functionBody);
function.appendCFG(parameterBlock);
fixGotoStatements(function);
fixReturnStatements(function);
if (!function.getBreakStatements().isEmpty())
{
System.err.println("warning: unresolved break statement");
fixBreakStatements(function, function.getErrorNode());
}
if (!function.getContinueStatements().isEmpty())
{
System.err.println("warning: unresolved continue statement");
fixContinueStatement(function, function.getErrorNode());
}
if (function.hasExceptionNode())
{
function.addEdge(function.getExceptionNode(),
function.getExitNode(), CFGEdge.UNHANDLED_EXCEPT_LABEL);
}

return function;
}
catch (Exception e)
{
// e.printStackTrace();
return newErrorInstance();
}
}
}

首先,先创建一个CFG块,将其赋给function变量,它是一个最小单位的CFG图,仅仅由entry node和exit node组成,一般表示为:

1
2
[ENTRY] { [ENTRY] ==[]==> [EXIT] , }
[EXIT] { }

其中这里的function最后会变成cpg图中类型为AST_FUNC_DECL或是AST_TOPLEVEL之类的结点,如果它的类型是AST_TOPLEVEL,那么它的id是1,它的类型在PHPCSVNodeTypes类中有定义,有4种:

1
2
3
4
5
6
7
8
public class PHPCSVNodeTypes
{
// declaration nodes
public static final String TYPE_TOPLEVEL = "AST_TOPLEVEL"; // artificial
public static final String TYPE_FUNC_DECL = "AST_FUNC_DECL";
public static final String TYPE_METHOD = "AST_METHOD";
public static final String TYPE_CLOSURE = "AST_CLOSURE";
}

14

跟入无参的newInstance()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CFGFactory
{
// ASTNode... nodes是自从Java5之后新增的对方法参数支持的一种新写法,叫可变长度参数列标,类型后面跟...表示此处接受的参数为0到n个Object类型的对象,或者是一个Object[]
public static CFG newInstance(ASTNode... nodes)
{
try
{
CFG block = new CFG();
CFGNode last = block.getEntryNode();
for (ASTNode node : nodes)
{
CFGNode container = new ASTNodeContainer(node);
block.addVertex(container);
block.addEdge(last, container);
last = container;
}
block.addEdge(last, block.getExitNode());
return block;
}
catch (Exception e)
{
// e.printStackTrace();
return newErrorInstance();
}
}
}

首先初始化一个CFG block,然后获取该控制流块的entrynode和exitnode,形成一个仅有[Entry] ==[]==> [Exit]的初始CFG block:

16

Class:CFG

跟入CFG类的构造函数CFG()
17

先稍微介绍下CFG类,位于基础类库jpanlib/目录下,具体位置为joern/projects/extensions/jpanlib/src/main/java/cfg/CFG.java

文件中存在注释:

1
2
3
4
/**
* Control Flow Graph. Consider this to be the target format of CFGFactories.
* Please place language specific attributes of the CFG into a sub-class.
*/

说明CFG图就是CFGFactory的目标格式。

CFG类继承自joern/projects/extensions/jpanlib/src/main/java/graphutils/IncidenceListGraph.java中的IncidenceListGraph类,源码中存在注释:

1
2
3
4
5
6
7
8
/**
* An graph implementation based on two incidence lists.
*
* @param <V>
* The vertex type.
* @param <E>
* The edge type.
*/

是基于两个关联列表的图implementation。在IncidenceListGraph中定义了很多的图操作。

5

继续回到源码,执行到第36行,它这里用了this关键字来调用本类的重载构造器(因为构造方法在类里不能像其他方法一样调用,只能通过this(参数列表)调用)。18

补充1:重写Override和重载Overload的区别

https://www.runoob.com/java/java-override-overload.html

重写:是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。

重载:重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载

补充2:Java使用this关键字调用本类重载构造器

https://www.cnblogs.com/wanghongyun/p/6132083.html

在构造器中是可以直接调用本类的其他重载构造器的,但是不能直接使用构造器名称来调用另一个构造器,而是需要使用this关键字。

this(...)方法必须出现在构造器的第一行,用来调用其他重载构造器,调用时参数必须严格匹配。这种调用方式的有点在于一个构造器可以不必重复编写其他构造器中已有的代码,而是通过调用其他构造函数以实现复用,从而提供良好的类代码结构。

在上图public CFG(CFGNode entry, CFGNode exit)函数中,接受的两个参数类型都是CFGNode类型的,不过通过this关键字传入的两个参数分别是new CFGEntryNode()new CFGExitNode(),我们知道CFGNodeCFGEntryNodeCFGExitNode之间存在某种继承关系。在IDEA中查看继承关系,CFGNode是一个接口,AbstractCFGNode是实现了该接口的抽象类(抽象类是只能被用于继承的),然后CFGEntryNodeCFGExitNode分别继承自该抽象类:

19

public CFG(CFGNode entry, CFGNode exit)中是最最初始的CFG图,目前的CFG图中存在两个结点,就是CFG的entry结点和exit结点,但是它们的id都为-1,因为这并不代表任何具象的AST结点,只是标记某个CFG块的entry和exit位置:

20

然后初始化存储非结构化控制语句的List:

21

CFG对象实例化结束后, 程序返回到CFGFactory.newInstance(),并将该CFG对象赋给block参数。接着读取block.entryblock.exit

22

但是nodes是空的list,所以后面的for循环会被跳过,直接进入第89行调用CFG.addEdge()

23

该函数会设置outNeighborhoodinNeiborhood。这样就合成了一个最基本的CFG图,由一个entry结点和exit结点组成。接着开始处理AST树的叶子结点,将其一点点加入到CFG图中。继续跟入:

24

对于CFG图上的起始结点(即flags: TOPLEVEL_FILE的结点),它的content内容就是它的子结点:

25

functionDef.getContent()返回的是一个类型为CompoundStatement的AST结点。接着调用CFGFactory.convert函数,该函数的作用就是将ASTNode转化成CFG:

1
2
3
4
5
6
7
8
public static CFG convert(ASTNode node)
{
if (node == null)
return newInstance();

node.accept(structuredFlowVisitior);
return structuredFlowVisitior.getCFG();
}

61

首先newInstance()会初始化一个最小的CFG块,接着处理后面的CompoundStatement。

27

通过content.getStatements()返回子结点之后,先将statement转化为子控制流图(对应代码是convert(statement),convert函数的返回类型是CFG),为了方便起见,我称其为child-CFG,将其都加入CFG中。可以看到两个子结点的类型分别是:

  • NullNode:一个空结点node类型,该类型结点往往是CFG中EXIT结点的parent结点

  • SwitchStatementPHP:继承自SwitchStatement

    29

    SwitchStatementPHP有两个成员变量:

    1
    2
    3
    4
    5
    public class SwitchStatementPHP extends SwitchStatement
    {
    private Expression expression = null;
    protected SwitchList switchList = null;
    }

继续跟入CFGFactory.convert

28

因为node结点的类型是SwitchStatementPHP,所以调用相应的visitor:PHPStructureFlowVisitor,然后调用PHPCFGFactory.newInstance(node)创建child-CFG,跟入该函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public static CFG newInstance(SwitchStatement switchStatement)
{
try
{
// [My Annotation]: 将传入参数switchStatement类型转为专门处理PHP语言的SwitchStatementPHP
// 因为SwitchStatementPHP是SwitchStatement的子类
SwitchStatementPHP phpSwitchStatement = (SwitchStatementPHP)switchStatement;

// create new CFG block
// [My Annotation]: 实例化一个child-CFG,switchBlock: "[ENTRY] { }\n[EXIT] { }\n"
CFG switchBlock = new CFG();

// "head" vertex: the expression to be compared to the individual cases
// [My Annotation]: ASTNodeContainer其实可以理解为控制流图上普通的结点
// phpSwitchStatement是针对switch语句的一种特殊的ASTNode结点,将其通过new ASTNodeContainer转为CFGNode结点
CFGNode conditionContainer = new ASTNodeContainer(
phpSwitchStatement.getExpression());
// 然后将其加入到child-CFG的vertex-list中
switchBlock.addVertex(conditionContainer);
// 然后把边edge连起来
switchBlock.addEdge(switchBlock.getEntryNode(), conditionContainer);

// create a CFG block for each case and connect them
// [My Annotation]: 将每一个switch case块加入到switchblock中
boolean defaultExists = false; // 判断是否存在一个default块
for( SwitchCase switchCase : phpSwitchStatement.getSwitchList()) {

// convert case block and add to main switch block
// [My Annotation]: 为每一个switch case块实例化一个caseBody CFG
// switchCase.getStatement()得到的statement类型是一个CompoundStatement
// 然后调用CFGFactory.convert函数将其转为child CFG
CFG caseBody = convert(switchCase.getStatement());
// 调用CFG.appendCFG将caseBody添加到main switch block中
switchBlock.appendCFG(caseBody);

// determine label
// [My Annotation]: 判断该switchCase(一种statement)的label是什么,label指case "apple"中的"apple"
String label = switchCase.getValue() != null
? switchCase.getValue().getEscapedCodeStr()
: "default";
// 如果default标签存在,将defaultExists标记为true
if( label.equals("default")) defaultExists = true;

// connect condition to first statement of case
// if case is empty, simply connect to current exit node
if( !caseBody.isEmpty()) {
for( CFGEdge caseEntryEdge : caseBody.outgoingEdges( caseBody.getEntryNode())) {
switchBlock.addEdge( conditionContainer, caseEntryEdge.getDestination(), label);
}
}
else {
switchBlock.addEdge( conditionContainer, switchBlock.getExitNode(), label);
}
}
// default edge to exit node (if no default case is found)
// 如果没有default case,那么需要手动加上
if( !defaultExists) {
switchBlock.addEdge( conditionContainer, switchBlock.getExitNode(), "default");
}

// finally, fix break statements and rejoice, for we are done
fixBreakStatements( switchBlock, switchBlock.getExitNode());

return switchBlock;
}
catch (Exception e)
{
//e.printStackTrace();
return newErrorInstance();
}
}

接下来利用SwitchStatementPHP.getExpression()的结果合成CFGNode:

31

返回一个后继结点,它的类型是Variable,也是ASTNode的子类类型之一:

32

然后将其实例化为一个ASTNodeContainer对象,它是一种CFG的一种Node类型:

30

ASTNodeContainer的作用主要是保存一个CFG结点的ASTNode属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package cfg.nodes;

import ast.ASTNode;

public class ASTNodeContainer extends AbstractCFGNode
{
// 成员变量是ASTNode
private ASTNode astNode;

public ASTNodeContainer(ASTNode node)
{
if (node == null)
{
throw new IllegalArgumentException("node must not be null");
}
setASTNode(node);
}

private void setASTNode(ASTNode astNode)
{
this.astNode = astNode;
// 将该结点标记为CFG上的结点
this.astNode.markAsCFGNode();
}
......
}

因为是CFGNode,所以它的isInCFG属性应为true,调用ASTNode.markAsCFGNode将其标记为true:

33

然后将新的CFGNode保存到CFG图的vertex-list中:

34

CFG.addEdge

接着调用CFG.addEdge()为entry结点和该CFGNode结点添加一条CFGEdge,即[ENTRY] ==[]==> [6 (Variable)]

35

这里会维护outNeighborhoodinNeighborhood

36

接着为每一个switch case块实例化一个caseBody CFG,switchCase.getStatement()得到的statement类型是一个CompoundStatement,它们的结构如下图所示:

37

然后调用CFGFactory.convert函数将switch.getStatement得到的CompoundStatement转为child CFG,执行完的结果:

38

这里可以看到的是这里的CompoundStatement其实就对应我们源码中的echo和break语句,因此是复合类型的:

1
2
echo "i is apple";
break;

然后调用CFG.appendCFG()caseBody添加进main switchblock switchBlock中。

CFG.appendCFG

CFG.appendCFG已经在之前将基本CFG块合成较大的CFG块时出现好几次了,现在来看下它到底做了什么事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public class CFG extends IncidenceListGraph<CFGNode, CFGEdge>
{
private CFGNode entry;
private CFGNode exit;
private CFGNode error;
private List<CFGNode> parameters;

private List<CFGNode> breakStatements;
private List<CFGNode> continueStatements;
private List<CFGNode> returnStatements;
private HashMap<CFGNode, String> gotoStatements;
private HashMap<String, CFGNode> labels;
private CFGExceptionNode exceptionNode;

public void addCFG(CFG otherCFG)
{
// [My Annotation]: 将otherCFG中的vertex加入到当前的main CFG的vertex list中
addVertices(otherCFG);
// [My Annotation]: 将otherCFG中的edge加入当前的main CFG的edge list中,但未进行拼接
addEdges(otherCFG);

// [My Annotation]: 将parameters,breakStatements等必要信息加入到main CFG中
// 后面同下就不再赘述了
getParameters().addAll(otherCFG.getParameters());
getBreakStatements().addAll(otherCFG.getBreakStatements());
getContinueStatements().addAll(otherCFG.getContinueStatements());
getReturnStatements().addAll(otherCFG.getReturnStatements());
getGotoStatements().putAll(otherCFG.getGotoStatements());
getLabels().putAll(otherCFG.getLabels());
if (this.hasExceptionNode() && otherCFG.hasExceptionNode())
{
CFGExceptionNode oldExceptionNode = getExceptionNode();
CFGExceptionNode newExceptionNode = new CFGExceptionNode();
setExceptionNode(newExceptionNode);
addEdge(oldExceptionNode, newExceptionNode,
CFGEdge.UNHANDLED_EXCEPT_LABEL);
addEdge(otherCFG.getExceptionNode(), newExceptionNode,
CFGEdge.UNHANDLED_EXCEPT_LABEL);
} else if (otherCFG.hasExceptionNode())
{
setExceptionNode(otherCFG.getExceptionNode());
}

}

public void appendCFG(CFG otherCFG)
{
// addCFG函数会将otherCFG中vertex和edge加入到this指向的main CFG中,但是还没有进行拼接
addCFG(otherCFG);
if (!otherCFG.isEmpty())
{
for (CFGEdge edge1 : incomingEdges(getExitNode()))
{
for (CFGEdge edge2 : otherCFG
.outgoingEdges(otherCFG.getEntryNode()))
{
addEdge(edge1.getSource(), edge2.getDestination(),
edge1.getLabel());
}
}
// 断开main CFG中某个结点和exit结点之间的连接,等待重新连接
removeEdgesTo(getExitNode());
for (CFGEdge edge : otherCFG.incomingEdges(otherCFG.getExitNode()))
{
addEdge(edge.getSource(), getExitNode(), edge.getLabel());
}
}
}

// [My Annotation]: 将subordinate CFG(即传入的参数)添加到main CFG中,但是不需要加入每个CFG块都存在的entry结点和exit结点
private void addVertices(CFG cfg)
{
for (CFGNode vertex : cfg.getVertices())
{
// do not add entry and exit node
if (!(vertex.equals(cfg.getEntryNode())
|| vertex.equals(cfg.getExitNode())))
{
addVertex(vertex);
}
}
}

private void addEdges(CFG cfg)
{
for (CFGNode vertex : cfg.getVertices())
{
// 给定一个vertex,返回从vertex是出点的edge
for (CFGEdge edge : cfg.outgoingEdges(vertex))
{
// 对于[ENTRY] ==[]==> [(12) EchoStatement]这种情况的边就不要加入到main CFG中了
// 否则会出现一个CFG块里有多条从Entry出发的关系边和到Exit结束的关系边
if (!(edge.getSource().equals(cfg.getEntryNode())
|| edge.getDestination().equals(cfg.getExitNode())))
{
addEdge(edge);
}
}
}
}
}

当程序执行到下图时:

39

先记录下switchBlockcaseBody的状态:

1
2
3
4
5
6
7
8
9
10
// switchBlock
[ENTRY] { [ENTRY] ==[]==> [(6) Variable] , }
[EXIT] { }
[(6) Variable] { }

// caseBody
[ENTRY] { [ENTRY] ==[]==> [(12) EchoStatement] , }
[EXIT] { }
[(12) EchoStatement] { [(12) EchoStatement] ==[]==> [(14) BreakStatement] , }
[(14) BreakStatement] { [(14) BreakStatement] ==[]==> [EXIT] , }

对照neo4j中的可视化图形,switchBlock为:

40

caseBody为:

41

经过CFG.appendCFG合成的new switchBlock为:

42

首先将otherCFG的vertex都加入到main CFG中,但是每个CFG块都有的entry结点和exit结点就不必重复加入了:

43

然后将otherCFG的edges也都加入到main CFG中,这个main CFG就是调用appendCFG的对象。其中IncidenceListGraph.outgoingEdges(vertex)会返回从该vertex出发的关系边,并且一条关系边中的两个结点不能存在entry结点或是exit结点:

44

返回后:

45

得到的CFG图目前是这样的:

1
2
3
4
5
[ENTRY] { [ENTRY] ==[]==> [(6) Variable] , }
[EXIT] { }
[(6) Variable] { }
[(12) EchoStatement] { [(12) EchoStatement] ==[]==> [(14) BreakStatement] , }
[(14) BreakStatement] { }

[(6) Variable][(12) EchoStatement][(14) BreakStatement]是没有什么关联的,但是[(12) EchoStatement] ==[]==> [(14) BreakStatement]是存在关系边的,所以后面需要将id为6的结点和这两个结点连接起来。

然后将otherCFG中一些必要的Statement也加入到main CFG中,比如加入了一个BreakStatement

46

然后重新组合otherCFG和main CFG:

47

可以看到其对main CFG造成的改变就是多了一条[(14) BreakStatement] ==[]==> [EXIT]的边。

但是目前[(6) Variable]并没有与[(12) EchoStatement]连接起来,所以接下去需要将caseBody的每个CFG头结点与switchBlock连接:

49

如此得到的switchBlock就变成了:

1
2
3
4
5
[ENTRY] { [ENTRY] ==[]==> [(6) Variable] , }
[EXIT] { }
[(6) Variable] { [(6) Variable] ==[apple]==> [(12) EchoStatement] , }
[(12) EchoStatement] { [(12) EchoStatement] ==[]==> [(14) BreakStatement] , }
[(14) BreakStatement] { [(14) BreakStatement] ==[]==> [EXIT] , }

现在的CFG就是下面的标红部分:

42

其中下面几个id的结点都是break结点:

  1. id = 14
  2. id = 21
  3. id = 28

default case

接着需要确定switch语句中是否存在default标签,如果没有后面需要特殊处理下:

48

fixBreakStatements

最后,还需要对breakStatements进行最后的处理,调用CFGFactory.fixBreakStatements()处理:

50

传入的参数thisCFG则是:

1
2
3
4
5
6
7
8
9
[ENTRY] { [ENTRY] ==[]==> [(6) Variable] , }
[EXIT] { }
[(6) Variable] { [(6) Variable] ==[apple]==> [(12) EchoStatement] ,[(6) Variable] ==[bar]==> [(19) EchoStatement] ,[(6) Variable] ==[cake]==> [(26) EchoStatement] ,[(6) Variable] ==[default]==> [EXIT] , }
[(12) EchoStatement] { [(12) EchoStatement] ==[]==> [(14) BreakStatement] , }
[(14) BreakStatement] { [(14) BreakStatement] ==[]==> [(19) EchoStatement] , }
[(19) EchoStatement] { [(19) EchoStatement] ==[]==> [(21) BreakStatement] , }
[(21) BreakStatement] { [(21) BreakStatement] ==[]==> [(26) EchoStatement] , }
[(26) EchoStatement] { [(26) EchoStatement] ==[]==> [(28) BreakStatement] , }
[(28) BreakStatement] { [(28) BreakStatement] ==[]==> [EXIT] , }

用一张更清晰的图来表示为:

51

从上图很容易发现多了两条边:

1
2
[(14) BreakStatement] { [(14) BreakStatement] ==[]==> [(19) EchoStatement] , }
[(21) BreakStatement] { [(21) BreakStatement] ==[]==> [(26) EchoStatement] , }

按照上图的语义,对照源码有:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
switch ($i) {
case "apple":
echo "i is apple";
break;
case "bar":
echo "i is bar";
break;
case "cake":
echo "i is cake";
break;
}
?>

当第5行break之后,还会继续执行到case "bar"中的内容。

来分析下fixBreakOrContinueStatements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static void fixBreakOrContinueStatements(CFG thisCFG, CFGNode target, Iterator<CFGNode> it)
{
while(it.hasNext())
{
// 从数组中取出每一个CFGNode结点
CFGNode breakOrContinueNode = it.next();

ASTNodeContainer nodeContainer = (ASTNodeContainer) breakOrContinueNode;
BreakOrContinueStatement statement = (BreakOrContinueStatement) nodeContainer.getASTNode();

// 可能与多层嵌套的break有关,可以后面再看
Integer depth = statement.getDepthAsInteger();
if(depth != 0 && depth != 1){
statement.decrementDepth();
continue;
}

// 除去CFG中多余的边,break语句后面不应继续执行了
thisCFG.removeEdgesFrom(breakOrContinueNode);
thisCFG.addEdge(breakOrContinueNode, target);
it.remove();
}
}

除去CFG中break结点后多余的edge:

52

最后将该break结点连接到exit结点上:

53

最后返回得到一个switchBlock:

54

得到的CFG结构为:

1
2
3
4
5
6
7
8
9
[ENTRY] { [ENTRY] ==[]==> [(6) Variable] , }
[EXIT] { }
[(6) Variable] { [(6) Variable] ==[apple]==> [(12) EchoStatement] ,[(6) Variable] ==[bar]==> [(19) EchoStatement] ,[(6) Variable] ==[cake]==> [(26) EchoStatement] ,[(6) Variable] ==[default]==> [EXIT] , }
[(12) EchoStatement] { [(12) EchoStatement] ==[]==> [(14) BreakStatement] , }
[(14) BreakStatement] { [(14) BreakStatement] ==[]==> [EXIT] , }
[(19) EchoStatement] { [(19) EchoStatement] ==[]==> [(21) BreakStatement] , }
[(21) BreakStatement] { [(21) BreakStatement] ==[]==> [EXIT] , }
[(26) EchoStatement] { [(26) EchoStatement] ==[]==> [(28) BreakStatement] , }
[(28) BreakStatement] { [(28) BreakStatement] ==[]==> [EXIT] , }

返回CFGFactory.newInstance

接着层层返回到CFGFactory.newInstance

55

其中functionparameterBlockfunctionBody分别为:

56

返回PHPCFGFactory.newInstance

最后返回到PHPCFGFactory.newInstance()ENTRY结点和EXIT结点的id进行分配,分别为2和3:

57

最后51行返回的CFG就是最终的CFG。

1
2
3
4
5
6
7
8
9
10
[(2) ENTRY] { [(2) ENTRY] ==[]==> [(6) Variable] , }
[(3) EXIT] { }
[(6) Variable] { [(6) Variable] ==[apple]==> [(12) EchoStatement] ,[(6) Variable] ==[bar]==> [(19) EchoStatement] ,[(6) Variable] ==[cake]==> [(26) EchoStatement] ,[(6) Variable] ==[default]==> [(30) NullNode] , }
[(12) EchoStatement] { [(12) EchoStatement] ==[]==> [(14) BreakStatement] , }
[(14) BreakStatement] { [(14) BreakStatement] ==[]==> [(30) NullNode] , }
[(19) EchoStatement] { [(19) EchoStatement] ==[]==> [(21) BreakStatement] , }
[(21) BreakStatement] { [(21) BreakStatement] ==[]==> [(30) NullNode] , }
[(26) EchoStatement] { [(26) EchoStatement] ==[]==> [(28) BreakStatement] , }
[(28) BreakStatement] { [(28) BreakStatement] ==[]==> [(30) NullNode] , }
[(30) NullNode] { [(30) NullNode] ==[]==> [(3) EXIT] , }

可以看到和此前不一样的地方在于,[ENTRY][EXIT]变成了[(2) ENTRY][(3) EXIT],它们有了自己的id,但注意它们的类型和其他结点不一样,它们是CFGEntryNodeCFGExitNode,其他的结点类型是ASTNodeContainer,不过都是一种CFGNode

CSVCFGExpoter.writeCFGEdges

该函数的作用就是就是将CFGEdges写入到cpg_edges.csv文件中。

58

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package outputModules.csv.exporters;

import java.util.Map;

import cfg.CFG;
import cfg.CFGEdge;
import cfg.nodes.ASTNodeContainer;
import cfg.nodes.AbstractCFGNode;
import cfg.nodes.CFGEntryNode;
import cfg.nodes.CFGExitNode;
import cfg.nodes.CFGNode;
import databaseNodes.EdgeTypes;
import databaseNodes.NodeKeys;
import outputModules.common.CFGExporter;
import outputModules.common.Writer;

public class CSVCFGExporter extends CFGExporter
{

@Override
protected void writeCFGNode(CFGNode statement,
Map<String, Object> properties)
{
properties.put(NodeKeys.FUNCTION_ID,
String.format("%d", Writer.getIdForObject(currentFunction)));
Writer.addNode(statement, properties);
}

@Override
protected void addFlowToLink(Object srcBlock, Object dstBlock,
Map<String, Object> properties)
{
long srcId = Writer.getIdForObject(srcBlock);
long dstId = Writer.getIdForObject(dstBlock);
// [My Annotation]: 写入edge的类型为FLOWS_TO
Writer.addEdge(srcId, dstId, properties, EdgeTypes.FLOWS_TO);
}

/**
* Simple method that takes a CFG and writes out the edges.
*/
public void writeCFGEdges(CFG cfg) {
// cfg.getEdges会获取cfg图中的outgoingEdges
for( CFGEdge cfgEdge : cfg.getEdges()) {

CFGNode src = cfgEdge.getSource();
CFGNode dst = cfgEdge.getDestination();

// skip blocks that are neither entry, exit nor AST nodes
// (e.g., artificial [ERROR] blocks and such generated by the CFG factory)
// CFG图中有三种结点类型,ASTNodeContainer, CFGEntryNode和CFGExitNode
if( (src instanceof ASTNodeContainer || src instanceof CFGEntryNode || src instanceof CFGExitNode)
&& (dst instanceof ASTNodeContainer || dst instanceof CFGEntryNode || dst instanceof CFGExitNode)) {

// CFG nodes that are AST node containers have their ids stored in their AST node;
// abstract nodes such as entry or exit nodes have their id set internally.
// 获取各自node的id,因为写入cpg_edges.csv文件中的就是node id
Long srcId = (src instanceof ASTNodeContainer) ? ((ASTNodeContainer)src).getASTNode().getNodeId()
: ((AbstractCFGNode)src).getNodeId();
Long dstId = (dst instanceof ASTNodeContainer) ? ((ASTNodeContainer)dst).getASTNode().getNodeId()
: ((AbstractCFGNode)dst).getNodeId();

Writer.setIdForObject(src, srcId);
Writer.setIdForObject(dst, dstId);
addFlowToLink( src, dst, null);
}
}
// clean up
Writer.reset();
}

}

小结

本小节仅仅涉及的是switch源码的CFG生成,还有一些其他的可继续挖掘的信息我都是没有跟下去的。因为这些打算放在其他的文章里,进行专项学习。

之前在phith0n大大开设的知识星球里看到过,说现在的漏洞分析文章大多都是一步一步跟踪流程,贴了一张张截图,但是这样的文章大多是”没有灵魂的“,因为缺少了我们从中学习到的更加深层,更加通用的,我们在调试漏洞时思考的内容。这些内容只能对这一个漏洞适用,缺少了”可迁移性“,我们应该在文章体现的是更加本质内容,我深以为然。但是我离这样的水平还有很长的距离,所以现在我还是将我跟踪调试的步骤一步一步记录下来。期望在结束的时候,我能学习到更加深层次的东西。