用LLVM/Clang编译器插件实现除法保护(4)[完]
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.
用LLVM/Clang编译器插件实现除法保护(4)[完]
上一篇已经成功深入到指令层面了,这一篇就开始使用 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);
引用本文请以超链接形式保留本文地址
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK