Files
lifting-bits-mcsema/mcsema/BC/Optimize.cpp
T
Peter Goodman 8a9856ada3 Klee maze example (#369)
* In progress. Working on an example of using KLEE on a Maze, but with the maze program being compiled to x86, amd64, and aarch64.

* Making lots of progress on getting lifting and runnning an aarch64 maze program on amd64, but using --explicit_args. The key thing I'm working through right now is a jump offset table, but where the offset is a block pc, rather than a table base. Also adding various bits of code here and there to making runnning with klee more directly doable, and working on a debugging facility to track down when the emulated program counter gets out of sync with the original program.

* Fixed a subtle @PAGE and @PAGEOFF-related reference bug on AArch64. Partially disabled the special jump offset table handling I had in table.py, as it doesn't (yet) handle the shifted table values. However, I still have the code there, so that it can recognize that a basic block address is used as a possible offset, so that I can remove the block address as a reference, which permits a new heuristic on the C++ side to work. On the C++ side, when there's a jump instruction that isn't associated with a cross-reference flow, I try to auto-augment it with addition switch cases, targeting blocks with no predecessors (as present in the CFG). This seems to work reasonably well.

* Improved the scripts and updated the READMEs.

* Minor rephrase

* Minor rephrase
2018-01-13 23:47:55 -05:00

371 lines
11 KiB
C++

/*
* Copyright (c) 2017 Trail of Bits, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <algorithm>
#include <limits>
#include <set>
#include <unordered_set>
#include <utility>
#include <vector>
#include <llvm/ADT/Triple.h>
#include <llvm/IR/Constants.h>
#include <llvm/IR/DataLayout.h>
#include <llvm/IR/DebugInfo.h>
#include <llvm/IR/InstIterator.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Metadata.h>
#include <llvm/IR/MDBuilder.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/Type.h>
#include <llvm/Transforms/IPO.h>
#include <llvm/Transforms/IPO/PassManagerBuilder.h>
#include <llvm/Transforms/Scalar.h>
#include <llvm/Transforms/Utils/Local.h>
#include <llvm/Transforms/Utils/Cloning.h>
#include <llvm/Transforms/Utils/ValueMapper.h>
#include "remill/Arch/Arch.h"
#include "remill/BC/ABI.h"
#include "remill/BC/Compat/TargetLibraryInfo.h"
#include "remill/BC/Util.h"
#include "mcsema/Arch/Arch.h"
#include "mcsema/BC/Optimize.h"
#include "mcsema/BC/Util.h"
DEFINE_bool(disable_optimizer, false,
"Disable interprocedural optimizations?");
DEFINE_bool(keep_memops, false,
"Should the memory intrinsics be replaced or not?");
namespace mcsema {
namespace {
// Replace all uses of a specific intrinsic with an undefined value. We actually
// don't use LLVM's `undef` values because those can behave unpredictably
// across different LLVM versions with different optimization levels. Instead,
// we use a null value (zero, really).
static void ReplaceUndefIntrinsic(llvm::Function *function) {
auto call_insts = remill::CallersOf(function);
auto undef_val = llvm::Constant::getNullValue(function->getReturnType());
for (auto call_inst : call_insts) {
call_inst->replaceAllUsesWith(undef_val);
call_inst->removeFromParent();
delete call_inst;
}
}
static void RemoveFunction(llvm::Function *func) {
if (!func->hasNUsesOrMore(1)) {
func->eraseFromParent();
}
}
static void RemoveFunction(const char *name) {
if (auto func = gModule->getFunction(name)) {
RemoveFunction(func);
}
}
// Remove calls to the various undefined value intrinsics.
static void RemoveUndefFuncCalls(void) {
llvm::Function *undef_funcs[] = {
gModule->getFunction("__remill_undefined_8"),
gModule->getFunction("__remill_undefined_16"),
gModule->getFunction("__remill_undefined_32"),
gModule->getFunction("__remill_undefined_64"),
gModule->getFunction("__remill_undefined_f32"),
gModule->getFunction("__remill_undefined_f64"),
};
for (auto undef_func : undef_funcs) {
if (undef_func) {
ReplaceUndefIntrinsic(undef_func);
RemoveFunction(undef_func);
}
}
}
static void RunO3(void) {
llvm::legacy::FunctionPassManager func_manager(gModule);
llvm::legacy::PassManager module_manager;
auto TLI = new llvm::TargetLibraryInfoImpl(
llvm::Triple(gModule->getTargetTriple()));
TLI->disableAllFunctions(); // `-fno-builtin`.
llvm::PassManagerBuilder builder;
builder.OptLevel = 3;
builder.SizeLevel = 2;
builder.Inliner = llvm::createFunctionInliningPass(
std::numeric_limits<int>::max());
builder.LibraryInfo = TLI; // Deleted by `llvm::~PassManagerBuilder`.
builder.DisableUnrollLoops = false; // Unroll loops!
builder.DisableUnitAtATime = false;
builder.SLPVectorize = false;
builder.LoopVectorize = false;
// TODO(pag): Not sure when these became available.
// builder.MergeFunctions = false; // Try to deduplicate functions.
// builder.VerifyInput = false;
// builder.VerifyOutput = false;
builder.populateFunctionPassManager(func_manager);
builder.populateModulePassManager(module_manager);
func_manager.doInitialization();
for (auto &func : *gModule) {
func_manager.run(func);
}
func_manager.doFinalization();
module_manager.run(*gModule);
}
// Get a list of all ISELs.
static std::vector<llvm::GlobalVariable *> FindISELs(void) {
std::vector<llvm::GlobalVariable *> isels;
remill::ForEachISel(
gModule, [&](llvm::GlobalVariable *isel, llvm::Function *) {
isels.push_back(isel);
});
return isels;
}
// Remove the ISEL variables used for finding the instruction semantics.
static void PrivatizeISELs(std::vector<llvm::GlobalVariable *> &isels) {
for (auto isel : isels) {
isel->setInitializer(nullptr);
isel->setExternallyInitialized(false);
isel->setLinkage(llvm::GlobalValue::PrivateLinkage);
if (!isel->hasNUsesOrMore(2)) {
isel->eraseFromParent();
}
}
}
// Remove some of the remill intrinsics.
static void RemoveIntrinsics(void) {
if (auto llvm_used = gModule->getGlobalVariable("llvm.used")) {
llvm_used->eraseFromParent();
}
// This function makes removing intrinsics tricky, so if it's there, then
// we'll try to get the optimizer to inline it on our behalf, which should
// drop some references :-D
if (auto remill_used = gModule->getFunction("__remill_mark_as_used")) {
std::vector<llvm::CallInst *> uses;
for (auto use : remill_used->users()) {
if (auto call = llvm::dyn_cast<llvm::CallInst>(use)) {
uses.push_back(call);
}
}
for (auto call : uses) {
call->eraseFromParent();
}
if (remill_used->hasNUsesOrMore(1)) {
if (remill_used->isDeclaration()) {
remill_used->setLinkage(llvm::GlobalValue::InternalLinkage);
remill_used->removeFnAttr(llvm::Attribute::NoInline);
remill_used->addFnAttr(llvm::Attribute::InlineHint);
remill_used->addFnAttr(llvm::Attribute::AlwaysInline);
auto block = llvm::BasicBlock::Create(*gContext, "", remill_used);
(void) llvm::ReturnInst::Create(*gContext, block);
}
}
RemoveFunction(remill_used);
}
// if (auto intrinsics = gModule->getFunction("__remill_intrinsics")) {
// intrinsics->eraseFromParent();
// }
RemoveFunction("__remill_basic_block");
RemoveFunction("__remill_defer_inlining");
RemoveFunction("__remill_intrinsics");
}
static void ReplaceBarrier(const char *name) {
auto func = gModule->getFunction(name);
if (!func) {
return;
}
CHECK(func->isDeclaration())
<< "Cannot lower already implemented memory intrinsic " << name;
auto callers = remill::CallersOf(func);
for (auto call_inst : callers) {
auto mem_ptr = call_inst->getArgOperand(0);
call_inst->replaceAllUsesWith(mem_ptr);
call_inst->eraseFromParent();
}
}
// Lower a memory read intrinsic into a `load` instruction.
static void ReplaceMemReadOp(const char *name, llvm::Type *val_type) {
auto func = gModule->getFunction(name);
if (!func) {
return;
}
CHECK(func->isDeclaration())
<< "Cannot lower already implemented memory intrinsic " << name;
auto callers = remill::CallersOf(func);
for (auto call_inst : callers) {
auto addr = call_inst->getArgOperand(1);
llvm::IRBuilder<> ir(call_inst);
llvm::Value *ptr = nullptr;
if (auto as_int = llvm::dyn_cast<llvm::PtrToIntInst>(addr)) {
ptr = ir.CreateBitCast(
as_int->getPointerOperand(),
llvm::PointerType::get(val_type, as_int->getPointerAddressSpace()));
} else {
ptr = ir.CreateIntToPtr(addr, llvm::PointerType::get(val_type, 0));
}
llvm::Value *val = ir.CreateLoad(ptr);
if (val_type->isX86_FP80Ty()) {
val = ir.CreateFPTrunc(val, func->getReturnType());
}
call_inst->replaceAllUsesWith(val);
}
for (auto call_inst : callers) {
call_inst->eraseFromParent();
}
RemoveFunction(func);
}
// Lower a memory write intrinsic into a `store` instruction.
static void ReplaceMemWriteOp(const char *name, llvm::Type *val_type) {
auto func = gModule->getFunction(name);
if (!func) {
return;
}
CHECK(func->isDeclaration())
<< "Cannot lower already implemented memory intrinsic " << name;
auto callers = remill::CallersOf(func);
for (auto call_inst : callers) {
auto mem_ptr = call_inst->getArgOperand(0);
auto addr = call_inst->getArgOperand(1);
auto val = call_inst->getArgOperand(2);
llvm::IRBuilder<> ir(call_inst);
llvm::Value *ptr = nullptr;
if (auto as_int = llvm::dyn_cast<llvm::PtrToIntInst>(addr)) {
ptr = ir.CreateBitCast(
as_int->getPointerOperand(),
llvm::PointerType::get(val_type, as_int->getPointerAddressSpace()));
} else {
ptr = ir.CreateIntToPtr(addr, llvm::PointerType::get(val_type, 0));
}
if (val_type->isX86_FP80Ty()) {
val = ir.CreateFPExt(val, val_type);
}
ir.CreateStore(val, ptr);
call_inst->replaceAllUsesWith(mem_ptr);
}
for (auto call_inst : callers) {
call_inst->eraseFromParent();
}
RemoveFunction(func);
}
static void LowerMemOps(void) {
ReplaceMemReadOp("__remill_read_memory_8",
llvm::Type::getInt8Ty(*gContext));
ReplaceMemReadOp("__remill_read_memory_16",
llvm::Type::getInt16Ty(*gContext));
ReplaceMemReadOp("__remill_read_memory_32",
llvm::Type::getInt32Ty(*gContext));
ReplaceMemReadOp("__remill_read_memory_64",
llvm::Type::getInt64Ty(*gContext));
ReplaceMemReadOp("__remill_read_memory_f32",
llvm::Type::getFloatTy(*gContext));
ReplaceMemReadOp("__remill_read_memory_f64",
llvm::Type::getDoubleTy(*gContext));
ReplaceMemWriteOp("__remill_write_memory_8",
llvm::Type::getInt8Ty(*gContext));
ReplaceMemWriteOp("__remill_write_memory_16",
llvm::Type::getInt16Ty(*gContext));
ReplaceMemWriteOp("__remill_write_memory_32",
llvm::Type::getInt32Ty(*gContext));
ReplaceMemWriteOp("__remill_write_memory_64",
llvm::Type::getInt64Ty(*gContext));
ReplaceMemWriteOp("__remill_write_memory_f32",
llvm::Type::getFloatTy(*gContext));
ReplaceMemWriteOp("__remill_write_memory_f64",
llvm::Type::getDoubleTy(*gContext));
ReplaceMemReadOp("__remill_read_memory_f80",
llvm::Type::getX86_FP80Ty(*gContext));
ReplaceMemWriteOp("__remill_write_memory_f80",
llvm::Type::getX86_FP80Ty(*gContext));
}
} // namespace
void OptimizeModule(void) {
auto isels = FindISELs();
RemoveIntrinsics();
LOG(INFO)
<< "Optimizing module.";
PrivatizeISELs(isels);
if (!FLAGS_disable_optimizer) {
RunO3();
}
RemoveIntrinsics();
if (!FLAGS_keep_memops) {
LowerMemOps();
ReplaceBarrier("__remill_barrier_load_load");
ReplaceBarrier("__remill_barrier_load_store");
ReplaceBarrier("__remill_barrier_store_load");
ReplaceBarrier("__remill_barrier_store_store");
ReplaceBarrier("__remill_barrier_atomic_begin");
ReplaceBarrier("__remill_barrier_atomic_end");
}
RemoveUndefFuncCalls();
}
} // namespace mcsema