10

用LLVM/Clang编译器插件实现除法保护(4)[完]

 2 years ago
source link: https://blog.sbw.so/u/use-clang-plugin-modify-ir-instructions.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

用LLVM/Clang编译器插件实现除法保护(4)[完]

来源: 石博文博客 | 浏览: 2296 | 评论: 0 发表时间: 2019-03-29

上一篇已经成功深入到指令层面了,这一篇就开始使用 IRBuilder 对函数流程进行修改,使得“不安全”版本的除法函数也增加对除数的零检查。因为除法指令分为整数除法、浮点数除法等等,而例如整数除法又可以细分为有符号数除法、无符号数除法,同时每种除法又根据类型不同分为32位除法、64位除法....。这里为了演示效果,我们只实现了32位的有符号除法。仅仅满足了测试代码的要求,如果要真的在生产中实现这个功能,细节上的很多问题还是要仔细分类处理的。

IRBuilder

参考 doc: IRBuilder
除法指令处理函数

添加一个processSignedDiv函数用来处理有符号除法:

void DivisionCheckPass::processSignedDiv(Instruction *inst)
{
auto *div = cast<BinaryOperator>(inst);
Value *divisor = div->getOperand(1);
Value *zero = ConstantInt::get(inst->getContext(), APInt(32, 0));
Value *one = ConstantInt::get(inst->getContext(), APInt(32, 1));
IRBuilder<> builder(inst);
Value *cond = builder.CreateICmp(CmpInst::Predicate::ICMP_EQ, divisor, zero, "cond");
Value *select = builder.CreateSelect(cond, one, divisor, "select");
inst->setOperand(1, select);
}

我们先获取到指令并转成一个BinaryOperator,二元指令就是加减乘除这些指令的类型,它包含一个代表操作类型的操作码及两个操作数。这里取出它的第二个操作数即除数,然后我们构造两个常量0和1。

接下来我们构造一个IRBuilder,构造参数中的inst代表代码的插入点是inst这条指令之前。然后使用IRBuilder插入一条比较指令,比较除数与0,将这条指令的结果用作跳转条件。

这里我没有使用CreatePHI来创建PHI节点,而是使用了CreateSelect这条指令。其实它们的作用一样,只不过PHI节点更强大,可以分发处理多个分支的选择,而我们的需求比较简单,仅仅是二选一,所以这里就偷懒使用CreateSelect了。select指令根据cond指令的结果来进行判断,如果cond成立,即除数等于零时,选择1为结果,否则选择除数为结果。这样我们就成功进行了除法检查,然后再把这个“新”的,经过处理后的除数,设置到原来的除法指令中去,这样就把整个流程闭合住了。

一点额外的处理

记录处理指令

由于我们是遍历除法指令,然后添加比较指令,修改原除法指令的操作数,所以我们需要记录一下哪些除法指令是已经被处理过的了,否则就陷入无限循环之中了。添加set procceedInstructions;来记录已经处理完的指令列表,并在调用processSignedDiv之前进行重复检查:

bool DivisionCheckPass::processBasicBlock(BasicBlock *bb)
{
for (BasicBlock::iterator inst(bb->begin()); inst != bb->end(); ++inst)
{
// bypass if already procceed
if (procceedInstructions.find(&*inst) != procceedInstructions.end())
continue;
switch (inst->getOpcode())
{
case Instruction::SDiv:
procceedInstructions.insert(&*inst);
processSignedDiv(&*inst);
return true;
default:;
}
}
return false;
}
检查中间代码结果

编译生成新的插件模块,然后使用系统的clang编译器加载我们的插件编译示例代码为LLVM IR,可以看到我们新添加的cmp指令及select指令已经生效了:

clang++ -O3 -S -emit-llvm -Xclang -load -Xclang build/libdiv-check.so -Xclang -add-plugin -Xclang DivisionCheck -o build/test-fixed.ll main.cpp
cat build/text-fixed.ll
; Function Attrs: norecurse nounwind readnone sspstrong uwtable
define dso_local i32 @_Z26dangerous_divison_functionii(i32, i32) local_unnamed_addr #3 {
%3 = icmp eq i32 %1, 0
%4 = select i1 %3, i32 1, i32 %1
%5 = sdiv i32 %0, %4
ret i32 %5
}
编译并运行测试程序

使用编译器加载插件编译并运行测试程序,可以看到无论是“安全”版本的除法函数,还是“不安全”版本的除法函数,都正确处理了除零检查,没有发生崩溃的问题:

clang++ -O3 -Xclang -load -Xclang build/libdiv-check.so -Xclang -add-plugin -Xclang DivisionCheck -o build/test main.cpp
./build/test
Test for 3 / 5:
Safe Div Result: 0, Dangerous Div Result: 0
Test for 5 / 3:
Safe Div Result: 1, Dangerous Div Result: 1
Test for 5 / -3:
Safe Div Result: -1, Dangerous Div Result: -1
Test for 6 / 3:
Safe Div Result: 2, Dangerous Div Result: 2
Test for -6 / 3:
Safe Div Result: -2, Dangerous Div Result: -2
Test for 6 / 0:
Safe Div Result: 6, Dangerous Div Result: 6
Test for -6 / 0:
Safe Div Result: -6, Dangerous Div Result: -6
Test for 0 / 3:
Safe Div Result: 0, Dangerous Div Result: 0
Test for 0 / 3:
Safe Div Result: 0, Dangerous Div Result: 0
Test for 0 / 0:
Safe Div Result: 0, Dangerous Div Result: 0
Test for 0 / 0:
Safe Div Result: 0, Dangerous Div Result: 0
Test Finished.

这样,我们这个功能就完成了。如果需要对无符号整数或其它位宽的整数,又或者浮点数进行相同的处理,只需要参考文档加更多的条件判断即可,万变不离其宗。

附:完整代码

#include <llvm/Pass.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/Transforms/IPO/PassManagerBuilder.h>
#include <llvm/Transforms/Utils/BasicBlockUtils.h>
#include <iostream>
#include <set>
using namespace llvm;
using namespace std;
// 用于使 cout 能直接输出 llvm 的 StringRef 类型字符串
ostream &operator<<(ostream &out, const StringRef &str_ref)
{
out.write(str_ref.data(), str_ref.size());
return out;
}
class DivisionCheckPass : public FunctionPass
{
public:
DivisionCheckPass();
~DivisionCheckPass() override = default;
virtual bool runOnFunction(Function &f) override;
private:
bool processBasicBlock(BasicBlock *bb);
void processSignedDiv(Instruction *inst);
public:
static char ID;
set<Instruction *> procceedInstructions;
};
char DivisionCheckPass::ID = 0;
DivisionCheckPass::DivisionCheckPass() : FunctionPass(ID)
{
cout << "DivisionCheckPass Initial" << endl;
}
bool DivisionCheckPass::runOnFunction(Function &f)
{
//cout << "Function: " << f.getName() << endl;
procceedInstructions.clear();
bool modified = false;
for (Function::iterator bb(f.begin()); bb != f.end(); ++bb)
modified |= processBasicBlock(&*bb);
return modified;
}
bool DivisionCheckPass::processBasicBlock(BasicBlock *bb)
{
for (BasicBlock::iterator inst(bb->begin()); inst != bb->end(); ++inst)
{
// bypass if already procceed
if (procceedInstructions.find(&*inst) != procceedInstructions.end())
continue;
switch (inst->getOpcode())
{
case Instruction::SDiv:
procceedInstructions.insert(&*inst);
processSignedDiv(&*inst);
return true;
default:;
}
}
return false;
}
void DivisionCheckPass::processSignedDiv(Instruction *inst)
{
auto *div = cast<BinaryOperator>(inst);
Value *divisor = div->getOperand(1);
Value *zero = ConstantInt::get(inst->getContext(), APInt(32, 0));
Value *one = ConstantInt::get(inst->getContext(), APInt(32, 1));
IRBuilder<> builder(inst);
Value *cond = builder.CreateICmp(CmpInst::Predicate::ICMP_EQ, divisor, zero, "cond");
Value *select = builder.CreateSelect(cond, one, divisor, "select");
inst->setOperand(1, select);
}
static void registerPass(const PassManagerBuilder &,
legacy::PassManagerBase &PM) {
PM.add(new DivisionCheckPass());
}
static RegisterStandardPasses
RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible,
registerPass);

引用本文请以超链接形式保留本文地址

没有人评论过此文,还不快抢个沙发!

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK