本文根据南京大学静态程序分析课程中关于Control Flow Analysis的Basic Blocks的片段,对给定的源代码进行有关BB的实验。
南京大学静态程序分析课程课件,其中第二课 Intermediate Representation有关于Basic Block
与控制流的讲解。
课件中给出了三地址码与BB控制流图,以下为课件中的截图。
LLVM BB与Control Flow Graph
仿照上文中的三地址码构造如下合法的C语言源代码:
int main(){
int x, y, z, p, q, a, b, c;
x = 6;
y = x - 1;
L3: z = x * y;
if(z<x) goto L7;
p = x / y;
q = p + y;
L7: a = q;
b = x + a;
c = 2 * a - b;
if(p==q) goto L12;
goto L3;
L12:return 0;
}
其LLVM-IR的片段为:
define dso_local i32 @main() #0 !dbg !7 {
entry:
%retval = alloca i32, align 4
%x = alloca i32, align 4
%y = alloca i32, align 4
%z = alloca i32, align 4
%p = alloca i32, align 4
%q = alloca i32, align 4
%a = alloca i32, align 4
%b = alloca i32, align 4
%c = alloca i32, align 4
...... ; 此处省略部分
store i32 0, i32* %retval, align 4
store i32 6, i32* %x, align 4, !dbg !27
%0 = load i32, i32* %x, align 4, !dbg !28
%sub = sub nsw i32 %0, 1, !dbg !29
store i32 %sub, i32* %y, align 4, !dbg !30
br label %L3, !dbg !31
L3: ; preds = %if.end6, %entry
call void @llvm.dbg.label(metadata !32), !dbg !33
%1 = load i32, i32* %x, align 4, !dbg !34
%2 = load i32, i32* %y, align 4, !dbg !35
%mul = mul nsw i32 %1, %2, !dbg !36
store i32 %mul, i32* %z, align 4, !dbg !37
%3 = load i32, i32* %z, align 4, !dbg !38
%4 = load i32, i32* %x, align 4, !dbg !40
%cmp = icmp slt i32 %3, %4, !dbg !41
br i1 %cmp, label %if.then, label %if.end, !dbg !42
if.then: ; preds = %L3
br label %L7, !dbg !43
if.end: ; preds = %L3
%5 = load i32, i32* %x, align 4, !dbg !44
%6 = load i32, i32* %y, align 4, !dbg !45
%div = sdiv i32 %5, %6, !dbg !46
store i32 %div, i32* %p, align 4, !dbg !47
%7 = load i32, i32* %p, align 4, !dbg !48
%8 = load i32, i32* %y, align 4, !dbg !49
%add = add nsw i32 %7, %8, !dbg !50
store i32 %add, i32* %q, align 4, !dbg !51
br label %L7, !dbg !52
L7: ; preds = %if.end, %if.then
call void @llvm.dbg.label(metadata !53), !dbg !54
%9 = load i32, i32* %q, align 4, !dbg !55
store i32 %9, i32* %a, align 4, !dbg !56
%10 = load i32, i32* %x, align 4, !dbg !57
%11 = load i32, i32* %a, align 4, !dbg !58
%add1 = add nsw i32 %10, %11, !dbg !59
store i32 %add1, i32* %b, align 4, !dbg !60
%12 = load i32, i32* %a, align 4, !dbg !61
%mul2 = mul nsw i32 2, %12, !dbg !62
%13 = load i32, i32* %b, align 4, !dbg !63
%sub3 = sub nsw i32 %mul2, %13, !dbg !64
store i32 %sub3, i32* %c, align 4, !dbg !65
%14 = load i32, i32* %p, align 4, !dbg !66
%15 = load i32, i32* %q, align 4, !dbg !68
%cmp4 = icmp eq i32 %14, %15, !dbg !69
br i1 %cmp4, label %if.then5, label %if.end6, !dbg !70
if.then5: ; preds = %L7
br label %L12, !dbg !71
if.end6: ; preds = %L7
br label %L3, !dbg !72
L12: ; preds = %if.then5
call void @llvm.dbg.label(metadata !73), !dbg !74
ret i32 0, !dbg !75
}
使用LLVM自带的指令:假设ll文件名为bb.ll
opt -dot-cfg bb.ll # 该指令生成默认名为.main.dot文件
dot -Tpng -o bb_cfg.png .main.dot
可以得到一个使用BB描述的控制流图。
SVFTools生成自定义的BB图
继承自SVFTools的GenericGraph
,定义一个LxBBGraph
,用于展示基于BasicBlock
的控制流图。其中节点、边、图的主要定义如下:
class LxBBNode;
class LxBBEdge: public SVF::GenericEdge<LxBBNode>{ // 边没有添加任何新数据,只需实现构造函数即可
public:
LxBBEdge(LxBBNode *src, LxBBNode *dst):GenericEdge(src,dst,0){}
};
class LxBBNode:public SVF::GenericNode<LxBBNode, LxBBEdge>{
public:
LxBBNode(SVF::NodeID id, llvm::BasicBlock *b):GenericNode(id,0), bb(b){}
llvm::BasicBlock* getBB(){return bb;} // getter
private:
llvm::BasicBlock *bb; //节点多了一个数据成员,即该节点所代表的BB
};
class LxBBGraph: public SVF::GenericGraph<LxBBNode, LxBBEdge>{
public:
LxBBGraph():GenericGraph(){}
/// 加边的函数
bool addEdge(LxBBNode *src, LxBBNode *dst, LxBBEdge *edge){
///不考虑边的类型,直接使用addOutgoingEdge添加即可
src->addOutgoingEdge(edge);
dst->addIncomingEdge(edge);
this->incEdgeNum();
return true;
}
/// 写dot文件所需
void dump(std::string name){
SVF::GraphPrinter::WriteGraphToFile(SVF::SVFUtil::outs(), name, this);
}
/// 写dot文件所需
inline std::string getGraphName() const{
return "LxBBGraph";
}
/**
* 根据SVFModule生成BB控制流图
* 目前只能生成单函数,没有对多函数进行实验
*/
static LxBBGraph* newInstance(const SVF::SVFModule *module){
LxBBGraph *g = new LxBBGraph;
/// 找出所有的BB,并按照名字编号,BB的名字应该是唯一的?
map<string, int> name2Idx;
int k = 1;
for(auto &fun: fanwei(module->llvmFunBegin(), module->llvmFunEnd())){
auto &s1 = fun->getBasicBlockList();
for(auto &bb: s1){
name2Idx.insert(mp((string)bb.getName(), k));
/// 每一个BB添加一个节点
g->addGNode(k, new LxBBNode(k, &bb));
++k;
}
}
/// 找出所有的边
for(auto &fun: fanwei(module->llvmFunBegin(), module->llvmFunEnd())){
auto &s1 = fun->getBasicBlockList();
for(auto &bb: s1){
llvm::BasicBlock *pBB = &bb;
int from = name2Idx[(string)bb.getName()];
for(auto suc: successors(pBB)){
int to = name2Idx[(string)suc->getName()];
LxBBNode *src = g->getGNode(from);
LxBBNode *dst = g->getGNode(to);
LxBBEdge *edge = new LxBBEdge(src, dst);
g->addEdge(src, dst, edge);
}
}
}
return g;
}
};
除此之外,为了写dot
文件以及与其他可能的遍历算法匹配,还需要实现一些偏特化。具体原理可以参照SVFTools图基类的简单使用。
namespace llvm{
/// 以下三个结构体为类型萃取所需
template<> struct GraphTraits<LxBBNode*>:public GraphTraits<SVF::GenericNode<LxBBNode,LxBBEdge>* >{
};
template<> struct GraphTraits<Inverse<LxBBNode*> >
:public GraphTraits<Inverse<SVF::GenericNode<LxBBNode,LxBBEdge>* > >{
};
template<> struct GraphTraits<LxBBGraph*>
: public GraphTraits<SVF::GenericGraph<LxBBNode,LxBBEdge>* >{
typedef LxBBNode *NodeRef;
};
/// 以下为输出dot文件所需
template<>
struct DOTGraphTraits<LxBBGraph*> : public DefaultDOTGraphTraits{
typedef LxBBNode NodeType;
typedef NodeType::iterator ChildIteratorTy;
/// 必须的,不可省略
DOTGraphTraits(bool isSimple = false) :
DefaultDOTGraphTraits(isSimple)
{
}
/// Return name of the graph
static std::string getGraphName(LxBBGraph *graph){
return graph->getGraphName();
}
/**
* 指定节点的标签
* LxBBNode* node: 指定节点
* LxBBGraph*:
* return: string, 该字符串的内容不知道会显示在哪
*/
static std::string getNodeLable(LxBBNode *node, LxBBGraph*){
std::string str;
llvm::raw_string_ostream rawstr(str);
/// 只写节点编号
rawstr<<"NodeID: "<<node->getId();
return rawstr.str();
}
/**
* 指定节点的标签
* LxBBNode* node: 指定节点
* LxBBGraph*:
* return: string, 该字符串的内容会显示在dot上
*/
static std::string getNodeIdentifierLabel(LxBBNode *node, LxBBGraph*){
std::string str;
llvm::raw_string_ostream rawstr(str);
/// 写节点编号,以及该BB的名字
rawstr<<"ID: "<<node->getId();
rawstr<<", BBName: "<<(string)node->getBB()->getName();
return rawstr.str();
}
/**
* 指定节点的属性字符串,例如"shape=circle",则节点呈圆形
*/
static std::string getNodeAttributes(LxBBNode *node, LxBBGraph*){
return "shape=rectangle";
}
};
}//end namespace llvm
最后自定义BB控制流图生成的图片如下
虽然表现形式不同,但是比较每个节点的BB名称可知,这个图与LLVM
生成的控制流图是等价的。其中ID1
是entry
节点,ID8
相当于exit
节点。
比较南大静态程序分析课件中的示意图,由于具体细节不同,两个BB控制流图并不相同。但是两边都是由8个节点构成,都有一个entry节点(即入度为0),都有一个exit节点(即出度为0),剩下的节点中,都有2个节点是2进2出的,最后剩下4个节点全都是1进1出的。