使用LLVM分析函数CFG
Posted wuhui_gdnt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用LLVM分析函数CFG相关的知识,希望对你有一定的参考价值。
作者:Eli Bendersky
http://eli.thegreenplace.net/2013/09/16/analyzing-function-cfgs-with-llvm
在Stack Overflow上,关于LLVM一个常见问题是如何构建一个函数的控制流图(CFG),并对它进行拓扑排序,或者拓扑排序的某些变形。为了节省我将来的回答时间,我认为我应该抛出一篇简明的博文,展示LLVM在这个领域的能力。
首先,问这个问题的人通常忘了这个事实:在一个CFG里,基本块(BB)已经组织好了,无需构建一个新的图来运行感兴趣的分析。
每个BB有一组后继者——控制流从这个BB传递到这些BB。很容易通过查看BB的终结符指令获得它(由定义,一个BB有单个终结符):
// BB is a BasicBlock*
// ...
const TerminatorInst *TInst= BB->getTerminator();
for (unsigned I = 0, NSucc =TInst->getNumSuccessors(); I < NSucc; ++I)
BasicBlock *Succ =TInst->getSuccessor(I);
// Do stuff with Succ
BB的这个互联构成了一张图,我们可以任何我们觉得合适的方式遍历之。例如,下面是拓扑排序的一个实现:
classTopoSorter
public:
void runToposort(const Function &F)
outs() << "Topological sort of " << F.getName() << ":\\n";
// Initialize the color map by marking all the vertices white.
for(Function::const_iterator I = F.begin(), IE = F.end(); I != IE; ++I)
ColorMap[I] =TopoSorter::WHITE;
// The BB graph has a single entry vertex from which the otherBBs should
// be discoverable - the function entry block.
bool success =recursiveDFSToposort(&F.getEntryBlock());
if (success)
// Now we have all the BBs inside SortedBBs in reversetopological order.
for(BBVector::const_reverse_iterator RI = SortedBBs.rbegin(),
RE= SortedBBs.rend();
RI!= RE; ++RI)
outs() << " " <<(*RI)->getName() << "\\n";
else
outs() << " Sorting failed\\n";
private:
enum Color WHITE, GREY,BLACK;
// Color marks per vertex (BB).
typedef DenseMap<const BasicBlock *,Color> BBColorMap;
// Collects vertices (BBs) in "finish" order. Thefirst finished vertex is
// first, and so on.
typedefSmallVector<const BasicBlock *, 32> BBVector;
BBColorMap ColorMap;
BBVector SortedBBs;
// Helper function to recursively run topological sort from agiven BB.
// Returns true if the sort succeeded and false otherwise;topological sort
// may fail if, for example, the graph is not a DAG (detected acycle).
bool recursiveDFSToposort(const BasicBlock *BB)
ColorMap[BB] =TopoSorter::GREY;
// For demonstration, using the lowest-level APIs here. A BB'ssuccessors
// are determined by looking at its terminator instruction.
const TerminatorInst *TInst= BB->getTerminator();
for (unsigned I = 0, NSucc =TInst->getNumSuccessors(); I < NSucc; ++I)
BasicBlock *Succ =TInst->getSuccessor(I);
Color SuccColor =ColorMap[Succ];
if (SuccColor ==TopoSorter::WHITE)
if(!recursiveDFSToposort(Succ))
returnfalse;
elseif (SuccColor ==TopoSorter::GREY)
// This detects a cycle because grey vertices are all ancestorsof the
// currently explored vertex (in other words, they're "onthe stack").
outs() << " Detected cycle: edge from " << BB->getName() <<
" to " <<Succ->getName() << "\\n";
returnfalse;
// This BB is finished (fully explored), so we can add it to thevector.
ColorMap[BB] =TopoSorter::BLACK;
SortedBBs.push_back(BB);
returntrue;
;
[本文里还包含其他片段的完整代码在这里]
它使用简单的,在Cormen等著的《Introduction to Algorithms》中给出的递归DFS算法。在递归查找期间,在第一次遭遇时,顶点被标记为“灰”,在处理完成时,标记为“黑”。一个完成的顶点所有的外出边都已经被探查了。拓扑排序是所有顶点按完成时刻排序,从最后到第一(这也称为“反后序”)。在我们特定的情形里,一个BB是一个顶点,到其后继者的连接是边。
对这个CFG:
我们得到:
Topological sort of func:
AA
BB
CC
DD
不过有一个重要的警告。拓扑排序仅对没有环的有向图(DAG)定义了。尽管基本块图是有向的,它不一定是无环的。事实上,代码里的任何循环都翻译为BB图中的一个环。上面的代码检测这并报告一个错误,在找到一个环时拒绝提供排序。例如,考虑这个带有一些环的CFG:
代码将抱怨:
Topological sort of func:
Detected cycle: edgefrom BB4 to BB3
Sorting failed
现在我们知道如何辛苦地实现它,让我们看一下LLVM提供的某些有用的工具。头文件llvm/ADT/PostOrderIterator.h提供了以反后序遍历一个函数BB的迭代器。下面是完整的使用片段:
outs() << "Basicblocks of " << F.getName()<< " in post-order:\\n";
for(po_iterator<BasicBlock *> I = po_begin(&F.getEntryBlock()),
IE = po_end(&F.getEntryBlock());
I != IE; ++I)
outs() << " " << (*I)->getName() << "\\n";
回忆拓扑排序是反后序的。因此这正是你需要的顺序,考察来自同一头文件的类ReversePostOrderTraversal。注意也没检测环。在出现环时,这些迭代器将产生某种遍历,但不是拓扑序,因为在这样的情形里它是未定义的。如果你希望一个检测环的工具,在llvm/Analysis/CFG.h里有FindFunctionBackedges。它本质上运行与我上面展示的相同的DFS,虽然使用一个使用栈而不是递归的迭代算法。
关于po_iterator与其亲属的一件趣事是:它们可用于任何类型的图,不只是基本块图。它们可用于过程间分析的函数图,一个表达式图的节点,等等。这个魔法通过GraphTraits机制(llvm/ADT/GraphTraits.h)实现,这将图表示与工作在所有类型图上的实际算法解耦。使之对基本块工作的模板特化可在llvm/Support/CFG.h中找到——在这个头文件里,你还可以找到遍历BB后继(以及前驱)的迭代器,无需手动查询终结者指令。
回到拓扑排序。因为许多有用的函数有循环,因此包含环,我们怎么对付它们?答案是强连同分量(SCC)。如果我们找出BB图的SCC,我们拓扑排序这些SCC,仍然可以进行感兴趣的分析。比如,一个循环通常会收缩为一个SCC。那么我们怎么实现这个?
很幸运,LLVM已经有一个工具可以帮助我们。头文件llvm/ADT/SCCIterator.h定义了scc_iterator在一个图中以后序遍历SCC。这让我们以类似在无环图中排序BB那样的方式,拓扑排序SCC。事实上。在一个无环图中每个BB自己就是一个SCC,因此SCC的做法是一个泛化。使用scc_iterator是容易的:
// Use LLVM's Strongly Connected Components (SCCs) iterator toproduce
// a reverse topological sort of SCCs.
outs() << "SCCsfor " << F.getName() << " in post-order:\\n";
for(scc_iterator<Function *> I = scc_begin(&F),
IE = scc_end(&F);
I != IE; ++I)
// Obtain the vector of BBs in this SCC and print it out.
conststd::vector<BasicBlock *> &SCCBBs = *I;
outs() << " SCC: ";
for(std::vector<BasicBlock *>::const_iterator BBI = SCCBBs.begin(),
BBIE =SCCBBs.end();
BBI != BBIE; ++BBI)
outs() <<(*BBI)->getName() << " ";
outs() << "\\n";
对上面展示的循环CFG,这个代码将打印:
SCCs for func in post-order:
SCC: DD
SCC: CC2 CC1 CC
SCC: BB4 BB3
SCC: BB2 BB1 BB
SCC: AA
【注意这是后序,不是反后序;因此显示在列表中的拓扑排序是自底向上的。】
我希望这是一次对LLVM的CFG分析能力有益的审视。代码中我已经给出了许多指示,它们可以作为严肃阅读代码的开端。看一下LLVM为此所拥有的许多工具是相当酷的,看到它们中许多适用各种类型的图特别棒,幸亏GraphTraits机制。
以上是关于使用LLVM分析函数CFG的主要内容,如果未能解决你的问题,请参考以下文章