这里填充错了,后面只能改20个字,😓,统称为科学技术,简称科技。实际二者既有密切联系,又有重要区别。科学解决理论问题,技术解决实际问题。
⊙1.修复原因
⊙2.修复过程
⊙3.注意点
1.修复原因
在控制流平坦化结束后,ollvm调用了fixstack, fixstack中对phi-node和demote reg,移入了stack,本文只分析phi-node指令to stack的过程。
先说为啥要处理phi指令,在控制流平坦化后,执行真正逻辑的基本块的前驱块大概率变为分发块,或者其他的前驱块,根本上改变了phi指令能够正常执行的逻辑。
我们先来看一下phi指令:
inttest(int a, int b) {
if (a < b) {
return a + 1;
} else {
return b + 1;
}
}
经过转为ir后为:
define i32 @test(i32 %a, i32 %b) {
entry:
%cmp = icmp slt i32 %a, %b
br i1 %cmp, label %if_true, label %if_false
if_true:
%result1 = add i32 %a, 1
br label %merge
if_false:
%result2 = add i32 %b, 1
br label %merge
merge:
%result = phi i32 [ %result1, %if_true ], [ %result2, %if_false ]
ret i32 %result
}
也就是说第12行的result的值,会根据从%if_true块或%if_false块的不同,从而得到%result1、%result2的返回值,所以对于merge来说,从那个快跳转来的极为重要。
2.修复过程
如果将上方的代码经过了flatting后,那么到merge块的前驱块就不为原始的块了,所以代码出现了混乱,无法正常原有的逻辑被实现。
那么有一个方案(这个是看了ollvm源码才得来的方案):因为本质上phi指令的值是根据前驱块来源然后得来的,可以先在入口块alloca,在原phi的前驱块里面进行store,然后在phi指令用的那个时候,把value从栈中load出来,在这种情况下完美替换了phi指令,这个llvm已经给实现了。
根据llvm DemotePHIStack(参见llvm源码)画出了程序流图如图所示:
其对应的代码如下所示
AllocaInst *llvm::DemotePHIToStack(PHINode *P, Instruction *AllocaPoint){
if (P->use_empty()) {
P->eraseFromParent();
returnnullptr;
}
const DataLayout &DL = P->getModule()->getDataLayout();
// Create a stack slot to hold the value.
AllocaInst *Slot;
if (AllocaPoint) {
Slot = new AllocaInst(P->getType(), DL.getAllocaAddrSpace(), nullptr,
P->getName()+".reg2mem", AllocaPoint);
} else {
Function *F = P->getParent()->getParent();
Slot = new AllocaInst(P->getType(), DL.getAllocaAddrSpace(), nullptr,
P->getName() + ".reg2mem",
&F->getEntryBlock().front());
}
// Iterate over each operand inserting a store in each predecessor.
for (unsigned i = 0, e = P->getNumIncomingValues(); i < e; ++i) {
if (InvokeInst *II = dyn_cast<InvokeInst>(P->getIncomingValue(i))) {
assert(II->getParent() != P->getIncomingBlock(i) &&
"Invoke edge not supported yet"); (void)II;
}
new StoreInst(P->getIncomingValue(i), Slot,
P->getIncomingBlock(i)->getTerminator());
}
// Insert a load in place of the PHI and replace all uses.
BasicBlock::iterator InsertPt = P->getIterator();
for (; isa<PHINode>(InsertPt) || InsertPt->isEHPad(); ++InsertPt)
/* empty */; // Don't insert before PHI nodes or landingpad instrs.
Value *V =
new LoadInst(P->getType(), Slot, P->getName() + ".reload", &*InsertPt);
P->replaceAllUsesWith(V);
// Delete PHI.
P->eraseFromParent();
return Slot;
}
可以几张图来对比下经过phi to stack指令后的对比图:分别为alloca,store,load
3.注意点
在处理store和load指令的时候,要注意不要对于异常处理的指令前进行插入。
学习逆向和爬虫可以关注我朋友:
我是BestToYou,分享工作或日常学习中关于Android、iOS逆向及安全防护的一些思路和一些自己闲暇时刻调试的一些程序,文中若有错误或者不足的地方,恳请大家联系我批评指正。
原文始发于微信公众号(二进制科学):phi指令在控制流平坦化的修复
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论