/* * Copyright (c) 2020 Trail of Bits, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "mcsema/BC/Optimize.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wsign-conversion" #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Wold-style-cast" #pragma clang diagnostic ignored "-Wdocumentation" #pragma clang diagnostic ignored "-Wswitch-enum" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma clang diagnostic pop #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mcsema/Arch/Arch.h" #include "mcsema/BC/Optimize.h" #include "mcsema/BC/Util.h" #include "mcsema/CFG/CFG.h" DEFINE_bool(keep_memops, false, "Should the memory intrinsics be replaced or not?"); DEFINE_bool(check_for_lowmem_xrefs, false, "Check every constant, even those less than 4096, to see " "if they might be cross-reference targets. This might be " "reasonable to enable for PIC code, .o files, etc."); DEFINE_bool(volatile_memops, false, "Mark all lowered loads/stores as volatile"); DEFINE_bool(local_state_pointer, false, "Use the state pointer passed by argument to all lifted functions." "Set local_state_pointer to false to disable it."); DEFINE_bool(restore_all_on_unreachable, false, "Ensure that functions containing unreachable code end up " "restoring all saved registers on returning paths."); DECLARE_bool(disable_aliases); 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(); } else { auto ret_type = func->getReturnType(); if (!ret_type->isVoidTy()) { func->replaceAllUsesWith(llvm::UndefValue::get(func->getType())); 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); } } } // Get a list of all ISELs. static std::vector FindISELs(void) { std::vector isels; remill::ForEachISel(gModule.get(), [&](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 &isels) { for (auto isel : isels) { isel->setInitializer(nullptr); isel->setExternallyInitialized(false); isel->setLinkage(llvm::GlobalValue::PrivateLinkage); if (!isel->hasNUsesOrMore(2)) { isel->eraseFromParent(); } } } 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(); } } static llvm::Value *FindPointer(llvm::IRBuilder<> &ir, llvm::Value *addr, llvm::Type *elem_type, unsigned addr_space) { if (auto as_ptr_to_int = llvm::dyn_cast(addr)) { if (!addr_space) { addr_space = as_ptr_to_int->getPointerAddressSpace(); } auto curr = as_ptr_to_int->getPointerOperand(); auto possible = FindPointer(ir, curr, elem_type, addr_space); return possible ? possible : curr; } else { return nullptr; } } unsigned GetPointerAddressSpace(llvm::Value *val, unsigned addr_space) { if (addr_space || !val) { return addr_space; } if (auto source_type = llvm::dyn_cast(val->getType())) { addr_space = source_type->getPointerAddressSpace(); if (addr_space) { return addr_space; } } if (auto as_bc = llvm::dyn_cast(val)) { return GetPointerAddressSpace(as_bc->getOperand(0), addr_space); } else if (auto as_pti = llvm::dyn_cast(val)) { return GetPointerAddressSpace(as_pti->getOperand(0), addr_space); } else if (auto as_itp = llvm::dyn_cast(val)) { return GetPointerAddressSpace(as_itp->getOperand(0), addr_space); } else if (auto as_addr = llvm::dyn_cast(val)) { return GetPointerAddressSpace(as_addr->getOperand(0), addr_space); } else { return addr_space; } } // Try to get an Value representing the address of `ea` as an entity, or return // `nullptr`. static llvm::Constant *GetAddress(const NativeModule *cfg_module, uint64_t ea) { if (auto cfg_var = cfg_module->TryGetVariable(ea); cfg_var) { return cfg_var->Address(); } else if (auto cfg_func = cfg_module->TryGetFunction(ea); cfg_func) { return cfg_func->Address(); } else if (auto cfg_seg = cfg_module->TryGetSegment(ea); cfg_seg) { return LiftXrefInData(cfg_seg, ea); // Look to see if `ea - 1` is the last byte of an existing segment or // variable, and if so, create a GEP that gets the address immediately // following the segment. // // NOTE(pag): In practice, we don't need to worry about segment padding // here, as we expect that this pointer is used as an upper bound // for some computation, and the referenced address is not // actually mapped, so it's OK that it points into "the next // element's" padding. } else { if (auto cfg_var = cfg_module->TryGetVariable(ea - 1); cfg_var) { auto i32_type = llvm::Type::getInt32Ty(*gContext); auto var = cfg_var->Pointer(); auto var_type = var->getType()->getPointerElementType(); auto ptr = llvm::ConstantExpr::getGetElementPtr( var_type, var, llvm::ConstantInt::get(i32_type, 1, false)); return llvm::ConstantExpr::getPtrToInt(ptr, gWordType); } else if (auto cfg_seg = cfg_module->TryGetSegment(ea - 1); cfg_seg) { auto i32_type = llvm::Type::getInt32Ty(*gContext); auto seg_var = cfg_seg->Pointer(); auto seg_type = seg_var->getType()->getPointerElementType(); const auto ptr = llvm::ConstantExpr::getGetElementPtr( seg_type, seg_var, llvm::ConstantInt::get(i32_type, 1, false)); return llvm::ConstantExpr::getPtrToInt(ptr, gWordType); } } return nullptr; } static llvm::Value *GetPointer(const NativeModule *cfg_module, llvm::IRBuilder<> &ir, llvm::Value *addr, llvm::Type *elem_type, unsigned addr_space); static llvm::Value *GetIndexedPointer(const NativeModule *cfg_module, llvm::IRBuilder<> &ir, llvm::Value *lhs, llvm::Value *rhs, llvm::Type *dest_type, unsigned addr_space) { auto i32_ty = llvm::Type::getInt32Ty(*gContext); auto i8_ty = llvm::Type::getInt8Ty(*gContext); auto i8_ptr_ty = llvm::PointerType::get(i8_ty, addr_space); if (auto rhs_const = llvm::dyn_cast(rhs)) { const auto rhs_index = static_cast(rhs_const->getSExtValue()); const auto &dl = gModule->getDataLayout(); const auto [new_lhs, index] = remill::StripAndAccumulateConstantOffsets(dl, lhs); // It's possible that we will index into, but beyond, one global variable, // intending to get to another global. // // NOTE(pag): We use `getTypeStoreSize` of `global_type` so that we deal // with the defined bytes, and not any implied alignment of // globals lifted due to things like section alignment. if (auto lhs_global = llvm::dyn_cast(new_lhs)) { if (cfg_module) { if (auto cfg_seg = cfg_module->TryGetSegment(lhs_global->getName())) { const auto real_ea = static_cast(static_cast(cfg_seg->ea) + index); if (auto addr = GetAddress(cfg_module, real_ea)) { LOG_IF(WARNING, cfg_module->TryGetSegment(real_ea) != cfg_seg) << "Fixing cross-reference to " << std::hex << real_ea << std::dec << " that was misplaced into segment " << cfg_seg->name; return GetPointer(nullptr, ir, addr, dest_type->getPointerElementType(), addr_space); } else { LOG(ERROR) << "Out-of-bounds reference to " << std::hex << real_ea << std::dec << " tied to segment " << cfg_seg->name; } } } if (!index) { return ir.CreateBitCast(lhs_global, dest_type); } // It's a global variable not associated with a native segment, try to // index into it in a natural-ish way. We only apply this when the index // is positive. if (0 < index) { auto offset = static_cast(index); return remill::BuildPointerToOffset(ir, lhs_global, offset, dest_type); } } auto lhs_elem_type = lhs->getType()->getPointerElementType(); auto dest_elem_type = dest_type->getPointerElementType(); const auto lhs_el_size = dl.getTypeAllocSize(lhs_elem_type); const auto dest_el_size = dl.getTypeAllocSize(dest_elem_type); llvm::Value *ptr = nullptr; // If either the source or destination element size is divisible by the // other then we might get lucky and be able to compute a pointer to the // destination with a single GEP. if (!(lhs_el_size % dest_el_size) || !(dest_el_size % lhs_el_size)) { if (0 > rhs_index) { const auto pos_rhs_index = static_cast(-rhs_index); if (!(pos_rhs_index % lhs_el_size)) { const auto scaled_index = static_cast( rhs_index / static_cast(lhs_el_size)); llvm::Value *indices[1] = { llvm::ConstantInt::get(i32_ty, scaled_index, true)}; ptr = ir.CreateGEP(lhs_elem_type, lhs, indices); } } else { const auto pos_rhs_index = static_cast(rhs_index); if (!(pos_rhs_index % lhs_el_size)) { const auto scaled_index = static_cast( rhs_index / static_cast(lhs_el_size)); llvm::Value *indices[1] = { llvm::ConstantInt::get(i32_ty, scaled_index, false)}; ptr = ir.CreateGEP(lhs_elem_type, lhs, indices); } } } // We got a GEP for the dest, now make sure it's the right type. if (ptr) { if (lhs->getType() == dest_type) { return ptr; } else { return ir.CreateBitCast(ptr, dest_type); } } } auto base = ir.CreateBitCast(lhs, i8_ptr_ty); llvm::Value *indices[1] = {ir.CreateTrunc(rhs, i32_ty)}; auto gep = ir.CreateGEP(i8_ty, base, indices); return ir.CreateBitCast(gep, dest_type); } // Try to get a pointer for the address operand of a remill memory access // intrinsic. static llvm::Value *GetPointerFromInt(llvm::IRBuilder<> &ir, llvm::Value *addr, llvm::Type *elem_type, unsigned addr_space) { auto dest_type = llvm::PointerType::get(elem_type, addr_space); if (auto phi = llvm::dyn_cast(addr)) { const auto old_ipoint = &*(ir.GetInsertPoint()); ir.SetInsertPoint(phi); const auto max = phi->getNumIncomingValues(); const auto new_phi = ir.CreatePHI(dest_type, max); for (auto i = 0u; i < max; ++i) { auto val = phi->getIncomingValue(i); auto block = phi->getIncomingBlock(i); llvm::IRBuilder<> sub_ir(block->getTerminator()); auto ptr = FindPointer(sub_ir, val, elem_type, addr_space); if (ptr) { if (ptr->getType() != dest_type) { ptr = sub_ir.CreateBitCast(ptr, dest_type); } } else { ptr = sub_ir.CreateIntToPtr(val, dest_type); } new_phi->addIncoming(ptr, block); } ir.SetInsertPoint(old_ipoint); return new_phi; } else { return ir.CreateIntToPtr(addr, dest_type); } } // Try to get a pointer for the address operand of a remill memory access // intrinsic. llvm::Value *GetPointer(const NativeModule *cfg_module, llvm::IRBuilder<> &ir, llvm::Value *addr, llvm::Type *elem_type, unsigned addr_space) { addr_space = GetPointerAddressSpace(addr, addr_space); const auto addr_type = addr->getType(); auto dest_type = llvm::PointerType::get(elem_type, addr_space); // Handle this case first so that we don't return early on the `ptrtoint` that // may directly reach into the address parameter of the memory access // intrinsics. if (auto as_itp = llvm::dyn_cast(addr); as_itp) { llvm::IRBuilder<> sub_ir(as_itp); return GetPointer(cfg_module, sub_ir, as_itp->getOperand(0), elem_type, addr_space); // It's a `ptrtoint`, but of the wrong type; lets go back and try to use // that pointer. } else if (auto as_pti = llvm::dyn_cast(addr); as_pti) { return GetPointer(cfg_module, ir, as_pti->getPointerOperand(), elem_type, addr_space); // We've found a pointer of the desired type; return :-D } else if (addr_type == dest_type) { return addr; // A missed cross-reference! } else if (auto ci = llvm::dyn_cast(addr); ci) { const auto ea = ci->getZExtValue(); if (auto addr = GetAddress(cfg_module, ea); addr) { return GetPointer(cfg_module, ir, addr, elem_type, addr_space); } else { LOG(ERROR) << "Missed cross-reference target " << std::hex << ea << " to pointer"; return llvm::ConstantExpr::getIntToPtr(ci, dest_type); } // It's a constant expression, the one we're interested in is `inttoptr` // as we've already handled `ptrtoint` above. } else if (auto ce = llvm::dyn_cast(addr); ce) { if (ce->getOpcode() == llvm::Instruction::IntToPtr) { return GetPointer(cfg_module, ir, ce->getOperand(0), elem_type, addr_space); } else if (addr_type->isIntegerTy()) { return llvm::ConstantExpr::getIntToPtr(ce, dest_type); } else { CHECK(addr_type->isPointerTy()); return llvm::ConstantExpr::getBitCast(ce, dest_type); } } else if (llvm::isa(addr)) { return ir.CreateBitCast(addr, dest_type); } else if (auto as_add = llvm::dyn_cast(addr); as_add) { const auto lhs_op = as_add->getOperand(0); const auto rhs_op = as_add->getOperand(1); auto lhs = FindPointer(ir, lhs_op, elem_type, addr_space); auto rhs = FindPointer(ir, rhs_op, elem_type, addr_space); if (!lhs && !rhs) { auto lhs_inst = llvm::dyn_cast(lhs_op); auto lhs_const = llvm::dyn_cast(lhs_op); auto rhs_inst = llvm::dyn_cast(rhs_op); auto rhs_const = llvm::dyn_cast(rhs_op); // If we see something like the following: // // %res = add %lhs_inst, // %ptr = inttoptr %res // // Then go find/create a pointer for `%lhs_inst`, then generate a GEP // based off of that. This is to address a common pattern that we observe // with things like accesses through the stack pointer. if (lhs_inst && rhs_const && lhs_inst->hasNUsesOrMore(2)) { auto ipoint = lhs_inst->getNextNode(); while (llvm::isa(ipoint)) { ipoint = ipoint->getNextNode(); } llvm::IRBuilder<> sub_ir(ipoint); lhs = GetPointer(cfg_module, sub_ir, lhs_inst, elem_type, addr_space); } else if (lhs_const && rhs_inst && rhs_inst->hasNUsesOrMore(2)) { auto ipoint = rhs_inst->getNextNode(); while (llvm::isa(ipoint)) { ipoint = ipoint->getNextNode(); } llvm::IRBuilder<> sub_ir(ipoint); rhs = GetPointer(cfg_module, sub_ir, rhs_inst, elem_type, addr_space); } else { return GetPointerFromInt(ir, addr, elem_type, addr_space); return ir.CreateIntToPtr(addr, dest_type); } } addr_space = GetPointerAddressSpace(lhs, addr_space); addr_space = GetPointerAddressSpace(rhs, addr_space); dest_type = llvm::PointerType::get(elem_type, addr_space); if (lhs && rhs) { const auto bb = ir.GetInsertBlock(); LOG(ERROR) << "Two pointers " << remill::LLVMThingToString(lhs) << " and " << remill::LLVMThingToString(rhs) << " are added together " << remill::LLVMThingToString(addr) << " in block " << bb->getName().str() << " in function " << bb->getParent()->getName().str(); return ir.CreateIntToPtr(addr, dest_type); } if (rhs) { return GetIndexedPointer(cfg_module, ir, rhs, lhs_op, dest_type, addr_space); } else { return GetIndexedPointer(cfg_module, ir, lhs, rhs_op, dest_type, addr_space); } } else if (auto as_sub = llvm::dyn_cast(addr); as_sub) { const auto lhs_op = as_sub->getOperand(0); const auto rhs_op = as_sub->getOperand(1); const auto rhs = llvm::dyn_cast(rhs_op); const auto lhs = FindPointer(ir, lhs_op, elem_type, addr_space); if (!lhs || !rhs) { return ir.CreateIntToPtr(addr, dest_type); } else { auto i32_ty = llvm::Type::getInt32Ty(*gContext); auto neg_index = static_cast(-static_cast(rhs->getZExtValue())); auto const_index = llvm::ConstantInt::get( i32_ty, static_cast(neg_index), true); addr_space = GetPointerAddressSpace(lhs, addr_space); dest_type = llvm::PointerType::get(elem_type, addr_space); return GetIndexedPointer(cfg_module, ir, lhs, const_index, dest_type, addr_space); } } else if (auto as_bc = llvm::dyn_cast(addr); as_bc) { return GetPointer(cfg_module, ir, as_bc->getOperand(0), elem_type, addr_space); // E.g. loading an address-sized integer register. } else if (addr_type->isIntegerTy()) { const auto bb = ir.GetInsertBlock(); const auto addr_inst = &*ir.GetInsertPoint(); // Go see if we can find multiple uses of `addr` in the same block, such // that each use converts `addr` to a pointer. If so, go and re-use those // `inttoptr` conversions instead of adding new ones. for (auto user : addr->users()) { const auto inst_user = llvm::dyn_cast(user); if (!inst_user || inst_user == addr_inst || inst_user->getParent() != bb) { continue; } for (auto next_inst = inst_user->getNextNode(); next_inst; next_inst = next_inst->getNextNode()) { DCHECK_EQ(next_inst->getParent(), bb); // We've found `addr_inst`, i.e. the address we're pointer that we're // try to compute follows a previous equivalent computation in the same // block, so we'll go take that one. if (next_inst == addr_inst) { return ir.CreateBitCast(inst_user, dest_type); } } // We found another computation of this pointer, but it follows // `addr_inst` in the block, so we'll move it to where we need it. inst_user->removeFromParent(); inst_user->insertBefore(addr_inst); return ir.CreateBitCast(inst_user, dest_type); } return GetPointerFromInt(ir, addr, elem_type, addr_space); } else { CHECK(addr_type->isPointerTy()); return ir.CreateBitCast(addr, dest_type); } } // Lower a memory read intrinsic into a `load` instruction. static void ReplaceMemReadOp(const NativeModule *cfg_module, 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 = GetPointer(cfg_module, ir, addr, val_type, 0); llvm::Value *val = ir.CreateLoad(ptr); if (auto load_inst = llvm::dyn_cast(val); FLAGS_volatile_memops && load_inst) { load_inst->setVolatile(true); } if (val_type->isX86_FP80Ty() || val_type->isFP128Ty()) { 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 NativeModule *cfg_module, 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 = GetPointer(cfg_module, ir, addr, val_type, 0); if (val_type->isX86_FP80Ty() || val_type->isFP128Ty()) { val = ir.CreateFPExt(val, val_type); } auto store_inst = ir.CreateStore(val, ptr); if (FLAGS_volatile_memops) { store_inst->setVolatile(true); } call_inst->replaceAllUsesWith(mem_ptr); } for (auto call_inst : callers) { call_inst->eraseFromParent(); } RemoveFunction(func); } static void LowerMemOps(const NativeModule *cfg_module) { ReplaceMemReadOp(cfg_module, "__remill_read_memory_8", llvm::Type::getInt8Ty(*gContext)); ReplaceMemReadOp(cfg_module, "__remill_read_memory_16", llvm::Type::getInt16Ty(*gContext)); ReplaceMemReadOp(cfg_module, "__remill_read_memory_32", llvm::Type::getInt32Ty(*gContext)); ReplaceMemReadOp(cfg_module, "__remill_read_memory_64", llvm::Type::getInt64Ty(*gContext)); ReplaceMemReadOp(cfg_module, "__remill_read_memory_f32", llvm::Type::getFloatTy(*gContext)); ReplaceMemReadOp(cfg_module, "__remill_read_memory_f64", llvm::Type::getDoubleTy(*gContext)); ReplaceMemWriteOp(cfg_module, "__remill_write_memory_8", llvm::Type::getInt8Ty(*gContext)); ReplaceMemWriteOp(cfg_module, "__remill_write_memory_16", llvm::Type::getInt16Ty(*gContext)); ReplaceMemWriteOp(cfg_module, "__remill_write_memory_32", llvm::Type::getInt32Ty(*gContext)); ReplaceMemWriteOp(cfg_module, "__remill_write_memory_64", llvm::Type::getInt64Ty(*gContext)); ReplaceMemWriteOp(cfg_module, "__remill_write_memory_f32", llvm::Type::getFloatTy(*gContext)); ReplaceMemWriteOp(cfg_module, "__remill_write_memory_f64", llvm::Type::getDoubleTy(*gContext)); ReplaceMemReadOp(cfg_module, "__remill_read_memory_f80", llvm::Type::getX86_FP80Ty(*gContext)); ReplaceMemReadOp(cfg_module, "__remill_write_memory_f128", llvm::Type::getFP128Ty(*gContext)); ReplaceMemWriteOp(cfg_module, "__remill_write_memory_f80", llvm::Type::getX86_FP80Ty(*gContext)); ReplaceMemWriteOp(cfg_module, "__remill_write_memory_f128", llvm::Type::getFP128Ty(*gContext)); } static bool RemoveDeadRestores(llvm::Function *restorer) { std::vector> to_replace; std::vector to_remove; std::vector try_to_remove; std::vector> to_fixup; std::unordered_set functions_with_noreturn; if (FLAGS_restore_all_on_unreachable) { for (auto &func : *gModule) { if (func.doesNotReturn()) { for (auto user : func.users()) { if (auto call_inst = llvm::dyn_cast(user); call_inst && call_inst->getCalledFunction() == &func) { functions_with_noreturn.insert(call_inst->getParent()->getParent()); } else if (auto invoke_inst = llvm::dyn_cast(user); invoke_inst && invoke_inst->getCalledFunction() == &func) { functions_with_noreturn.insert( invoke_inst->getParent()->getParent()); } } } for (auto &block : func) { if (llvm::isa(block.getTerminator())) { functions_with_noreturn.insert(&func); } } } } auto needs_restores = false; do { for (auto [use, new_val] : to_fixup) { use->set(new_val); } to_replace.clear(); to_remove.clear(); try_to_remove.clear(); to_fixup.clear(); for (auto user : restorer->users()) { const auto call = llvm::dyn_cast(user); if (!call) { continue; } auto args = call->arg_begin(); auto first_arg = args->get(); auto second_arg = (++args)->get(); auto func = call->getParent()->getParent(); // If the two arguments match, then it means that the register being // restored didn't change over the course of this function. // // NOTE(pag): If a function has an unreachable instruction, then it might // miss out on some optimizations, and so we want to always // force some restores. if (first_arg == second_arg && (!FLAGS_restore_all_on_unreachable || !functions_with_noreturn.count(func))) { auto all_users_are_stores = true; // Go find the store of the return value to the state structure, and // schedule it for removal. for (auto &call_use : call->uses()) { const auto call_user = call_use.getUser(); if (auto store_inst = llvm::dyn_cast(call_user); store_inst) { to_remove.emplace_back(store_inst); // Check to see if one `__remill_restore` function's output leads to // another one's, and then queue up a replacement that will send us // back around the main loop. } else if (auto call_inst = llvm::dyn_cast(call_user); call_inst) { if (call_inst == call) { continue; // Weird? } else if (call_inst->getCalledFunction() == restorer) { to_fixup.emplace_back(&call_use, call_inst->arg_begin()->get()); } else { all_users_are_stores = false; } } else { all_users_are_stores = false; } } // Go look for any stores of the first argument into the state structure, // and remove them. If we find these, then they are likely leftovers from // after function calls to other lifted functions. if (auto reg_load = llvm::dyn_cast(first_arg); reg_load) { for (auto reg_user : first_arg->users()) { if (auto reg_restore = llvm::dyn_cast(reg_user); reg_restore) { // Found a save of the register back into itself. if (reg_load->getPointerOperand() == reg_restore->getPointerOperand()) { to_remove.push_back(reg_restore); } } } } if (!all_users_are_stores) { to_replace.emplace_back(call, first_arg); } } else { needs_restores = true; for (auto call_user : call->users()) { if (auto store_inst = llvm::dyn_cast(call_user)) { store_inst->setVolatile(false); } } to_replace.emplace_back(call, first_arg); // The `load` of the most recent value in that register. if (auto inst = llvm::dyn_cast(second_arg)) { try_to_remove.push_back(inst); } } to_remove.push_back(call); } } while (!to_fixup.empty()); std::unordered_set removed; for (auto [call_inst, orig_val] : to_replace) { call_inst->replaceAllUsesWith(orig_val); } removed.reserve(to_remove.size() + try_to_remove.size()); for (auto inst : to_remove) { if (!removed.count(inst)) { inst->eraseFromParent(); removed.insert(inst); } } for (auto inst : try_to_remove) { if (!removed.count(inst) && !inst->hasNUsesOrMore(1)) { inst->eraseFromParent(); removed.insert(inst); } } return needs_restores; } static bool RemoveDeadRestores(void) { auto needs_restores = false; std::vector to_remove; for (auto &func : *gModule) { if (func.getName().startswith("__remill_restore.")) { needs_restores = RemoveDeadRestores(&func) || needs_restores; to_remove.push_back(&func); } } for (auto func : to_remove) { func->eraseFromParent(); } return needs_restores; } static void RemoveKilledStores(void) { if (auto killer = gModule->getGlobalVariable("__remill_kill"); killer) { std::vector> to_replace; std::vector to_remove; std::vector work_list; std::vector next_work_list; next_work_list.push_back(killer); while (!next_work_list.empty()) { work_list.swap(next_work_list); next_work_list.clear(); for (auto val : work_list) { for (auto user : val->users()) { if (auto si = llvm::dyn_cast(user); si) { to_remove.emplace_back(si); } else if (auto ce = llvm::dyn_cast(user); ce) { next_work_list.push_back(ce); } } } } std::sort(to_remove.begin(), to_remove.end()); auto it = std::unique(to_remove.begin(), to_remove.end()); to_remove.erase(it, to_remove.end()); for (auto inst : to_remove) { inst->eraseFromParent(); } killer->replaceAllUsesWith(llvm::UndefValue::get(killer->getType())); killer->eraseFromParent(); } } // When implementing the save/restore optimization, we can sometimes have an // annoying interaction with no-return functions. Here's an example: // // lifted_foo { // orig_rbp = state->rbp // ... // state->rbp = state->rsp // From instruction semantics // ... // // saved_rbp = state->rbp // state->rbp = __remill_saving_kill_XXX(saved_rbp) // To kill `state->rbp` // lifted_bar() // May be noreturn, and if so, the rest gets eliminated // state->rbp = saved_rbp // To propagate `saved_rbp` around // // ... // // state->rbp = orig_rbp // ret // } // // So the issue that can happen is that if `lifted_bar` is `noreturn`, then a // bunch of stores into the state struct can get eliminated. If those stores // get eliminated, then it's possible that `state->rbp` is treated as being // live when `state->rbp = state->rsp` happens, and so that store will happen // and a caller might see the callee's `rbp` instead of `orig_rbp`. By // introducing the call and store `state->rbp = __remill_saving_kill_XXX...`, // we make sure that there is always a store to a saved/restored reg, even // before noreturn functions, and so the store `state->rbp = state->rsp` is // marked as dead, and so the calle sees the right `rbp` upon return. static void RemoveSavingKilledStores(void) { std::vector> to_replace; std::vector to_remove; for (auto &func : *gModule) { if (func.getName().startswith("__remill_saving_kill_")) { for (auto user : func.users()) { if (auto call_inst = llvm::dyn_cast(user); call_inst && call_inst->getCalledFunction() == &func) { to_replace.emplace_back(call_inst, call_inst->getArgOperand(0)); } } } } for (auto [kill_val, reg_val] : to_replace) { if (auto store_inst = llvm::dyn_cast(kill_val->getNextNode()); store_inst && store_inst->getValueOperand() == kill_val) { to_remove.push_back(store_inst); } kill_val->replaceAllUsesWith(reg_val); kill_val->eraseFromParent(); } for (auto store_inst : to_remove) { store_inst->eraseFromParent(); } } // Adapt a constant (possibly expression) of integral type in `src` to another // integer type (likely `gWordType`) that is `dest_type`. static llvm::Constant *AdaptToType(llvm::Constant *src, llvm::Type *dest_type) { const auto src_type = src->getType(); if (src_type == dest_type) { return src; } CHECK(src_type->isIntegerTy()); if (dest_type->isIntegerTy()) { auto src_size = src_type->getPrimitiveSizeInBits(); auto dest_size = dest_type->getPrimitiveSizeInBits(); if (src_size < dest_size) { return llvm::ConstantExpr::getZExt(src, dest_type); } else { return llvm::ConstantExpr::getTrunc(src, dest_type); } } else if (dest_type->isPointerTy()) { if (auto pti = llvm::dyn_cast(src); pti) { src = llvm::cast(pti->getOperand(0)); if (src->getType() == dest_type) { return src; } else { return llvm::ConstantExpr::getBitCast(src, dest_type); } } else { return llvm::ConstantExpr::getIntToPtr(src, dest_type); } } else { LOG(FATAL) << "Unsupported destination type: " << remill::LLVMThingToString(dest_type); return nullptr; } } // The add-on pass to merge the `GEP` instructions if they are operating // on the same instruction operand static void MergeGEPInstructions(llvm::Function &func) { std::map, std::vector> gep_map; for (auto &block : func) { for (auto &inst : block) { if ((inst.getNumOperands() != 2)) continue; if ((inst.getOpcode() == llvm::Instruction::GetElementPtr) && (llvm::isa(inst.getOperand(0))) && (llvm::isa(inst.getOperand(1)))) { gep_map[std::pair(inst.getOperand(0), inst.getOperand(1))] .push_back(&inst); } } } for (auto it = gep_map.begin(); it != gep_map.end(); ++it) { auto &inst_vec = it->second; if (inst_vec.size() == 1) { continue; } auto opnd0 = llvm::dyn_cast(inst_vec[0]->getOperand(0)); if ((opnd0->getOpcode() == llvm::Instruction::PHI)) continue; inst_vec[0]->moveAfter(opnd0); for (auto i = 1u; i < inst_vec.size(); ++i) { auto gep_to_merge = inst_vec[i]; gep_to_merge->replaceAllUsesWith(inst_vec[0]); if (!gep_to_merge->hasNUsesOrMore(1)) { gep_to_merge->eraseFromParent(); } } } } // Lower cross-references, and try to fixup pointers. static void LowerXrefs(const NativeModule *cfg_module) { std::vector work_list; std::vector next_work_list; std::vector> fixups; std::unordered_map done_fixups; anvill::XrefExprFolder folder(*cfg_module, *gModule); auto get_fixup = [=, &folder, &done_fixups]( llvm::User *user, llvm::Constant *ce) -> llvm::Constant * { bool is_bitwise = false; if (auto op = llvm::dyn_cast(user); op) { switch (op->getOpcode()) { case llvm::Instruction::AShr: case llvm::Instruction::LShr: case llvm::Instruction::Shl: case llvm::Instruction::And: case llvm::Instruction::Xor: case llvm::Instruction::Or: //is_bitwise = true; break; default: break; } } const auto ce_type = ce->getType(); auto &fixup = done_fixups[ce]; if (!is_bitwise && fixup) { return fixup; } folder.Reset(); const auto ea = folder.VisitConst(ce); if (remill::IsError(folder.error)) { LOG(ERROR) << remill::GetErrorString(folder.error); } // Common to have a shift left by one, two, or three for common // optimizations and address calculations (e.g. multiply by pointer or // offset size). //if (is_bitwise && folder.left_shift_amount <= 3) { // is_bitwise = false; //} // It's a small number, just treat it like a constant. if (llvm::isa(ce_type) && (ea < 128 || (ea < 4096 && !FLAGS_check_for_lowmem_xrefs))) { fixup = llvm::ConstantInt::get(ce_type, ea); // Try to map it to a segment, external variable, or function. } else if (auto addr = GetAddress(cfg_module, ea)) { fixup = AdaptToType(addr, ce_type); // It doesn't reference anything known; treat it as a constant. } else { if (auto inst = llvm::dyn_cast(user); inst) { LOG(WARNING) << "Treating " << std::hex << ea << std::dec << " as a constant" << " in " << inst->getParent()->getName().str(); } else { LOG(WARNING) << "Treating " << std::hex << ea << std::dec << " as a constant"; } fixup = llvm::ConstantInt::get(ce_type, ea); } if (is_bitwise) { if (fixup) { if (llvm::isa(fixup)) { return fixup; } else { LOG(ERROR) << "Previously lifted cross-reference to " << std::hex << ea << std::dec << " is used in subsequent bitwise operations; " << "assuming it is actually a constant in this instance"; return llvm::ConstantInt::get(ce_type, ea); } } else { LOG(ERROR) << "Cross-reference to " << std::hex << ea << std::dec << " is used in bitwise operations; assuming it is a constant"; fixup = llvm::ConstantInt::get(ce_type, ea); } } else if (folder.bits_xor && !llvm::isa(fixup)) { fixup = llvm::ConstantInt::get(ce_type, ea); if (auto inst = llvm::dyn_cast(user); inst) { LOG(ERROR) << "Cross-reference to " << std::hex << ea << std::dec << " in " << inst->getParent()->getName().str() << " is computed via a XOR; assuming it is a constant: " << remill::LLVMThingToString(ce); } else { LOG(ERROR) << "Cross-reference to " << std::hex << ea << std::dec << " is computed via a XOR; assuming it is a constant: " << remill::LLVMThingToString(ce); } } return fixup; }; if (auto pc = gModule->getGlobalVariable("__anvill_pc"); pc) { next_work_list.push_back(pc); } // Sometimes, after optimizations, we'll end up seeing expressions operating // on our callback function pointers. for (auto [func_ea, cfg_func] : cfg_module->ea_to_func) { (void) func_ea; if (cfg_func->function) { if (auto func = gModule->getFunction(cfg_func->lifted_name); func) { cfg_func->function = func; for (auto &use : func->uses()) { const auto user = use.getUser(); if (auto user_ce = llvm::dyn_cast(user)) { next_work_list.push_back(user_ce); } } } else { cfg_func->function = nullptr; } } } while (!next_work_list.empty()) { next_work_list.swap(work_list); next_work_list.clear(); for (auto ce : work_list) { for (auto &use : ce->uses()) { const auto user = use.getUser(); if (auto inst = llvm::dyn_cast(user); inst) { fixups.emplace_back(&use, get_fixup(inst, ce)); } else if (auto user_ce = llvm::dyn_cast(user)) { next_work_list.push_back(user_ce); } else if (llvm::isa(user) || llvm::isa(user) || llvm::isa(user)) { continue; } else { LOG(ERROR) << "Unexpected user of cross-reference: " << remill::LLVMThingToString(user); } } } } for (auto [use, replacement] : fixups) { use->set(replacement); } auto find_missed_fixup = [&](const char *func_name, llvm::Type *val_type) { const auto func = gModule->getFunction(func_name); if (!func) { return; } for (auto user : func->users()) { const auto ci = llvm::dyn_cast(user); if (!ci) { continue; } auto &addr_use = ci->getArgOperandUse(1); auto addr_val = addr_use.get(); // If it's a constant integer, it means it was "missed" by the frontend // and thus by constant folding on `__mcsema_zero`. if (auto addr_int = llvm::dyn_cast(addr_val)) { const auto new_addr_val = get_fixup(user, addr_int); if (llvm::isa(new_addr_val)) { LOG(ERROR) << "Missed cross-reference to absolute address " << std::hex << addr_int->getZExtValue() << std::dec << " in block " << ci->getParent()->getName().str() << " in function " << ci->getParent()->getParent()->getName().str(); } else { LOG(WARNING) << "Fixing absolute address " << std::hex << addr_int->getZExtValue() << std::dec << " to be reference " << remill::LLVMThingToString(new_addr_val); } fixups.emplace_back(&addr_use, new_addr_val); // At this point, it should be an `ptrtoint` on a global, or an // instruction that computes an integer. } else { llvm::IRBuilder<> ir(ci); llvm::Value *ptr = GetPointer(cfg_module, ir, addr_use.get(), val_type, 0); fixups.emplace_back(&addr_use, ir.CreatePtrToInt(ptr, gWordType)); } } }; // Clear out all prior fixups. fixups.clear(); folder.Reset(); const auto int8_ty = llvm::Type::getInt8Ty(*gContext); const auto int16_ty = llvm::Type::getInt16Ty(*gContext); const auto int32_ty = llvm::Type::getInt32Ty(*gContext); const auto int64_ty = llvm::Type::getInt64Ty(*gContext); const auto float_ty = llvm::Type::getFloatTy(*gContext); const auto double_ty = llvm::Type::getDoubleTy(*gContext); const auto fp80_ty = llvm::Type::getX86_FP80Ty(*gContext); const auto fp128_ty = llvm::Type::getFP128Ty(*gContext); find_missed_fixup("__remill_read_memory_8", int8_ty); find_missed_fixup("__remill_read_memory_16", int16_ty); find_missed_fixup("__remill_read_memory_32", int32_ty); find_missed_fixup("__remill_read_memory_64", int64_ty); find_missed_fixup("__remill_read_memory_f32", float_ty); find_missed_fixup("__remill_read_memory_f64", double_ty); find_missed_fixup("__remill_read_memory_f80", fp80_ty); find_missed_fixup("__remill_read_memory_f128", fp128_ty); find_missed_fixup("__remill_compare_exchange_memory_8", int8_ty); find_missed_fixup("__remill_fetch_and_add_8", int8_ty); find_missed_fixup("__remill_fetch_and_sub_8", int8_ty); find_missed_fixup("__remill_fetch_and_or_8", int8_ty); find_missed_fixup("__remill_fetch_and_and_8", int8_ty); find_missed_fixup("__remill_fetch_and_xor_8", int8_ty); find_missed_fixup("__remill_compare_exchange_memory_16", int16_ty); find_missed_fixup("__remill_fetch_and_add_16", int16_ty); find_missed_fixup("__remill_fetch_and_sub_16", int16_ty); find_missed_fixup("__remill_fetch_and_or_16", int16_ty); find_missed_fixup("__remill_fetch_and_and_16", int16_ty); find_missed_fixup("__remill_fetch_and_xor_16", int16_ty); find_missed_fixup("__remill_compare_exchange_memory_32", int32_ty); find_missed_fixup("__remill_fetch_and_add_32", int32_ty); find_missed_fixup("__remill_fetch_and_sub_32", int32_ty); find_missed_fixup("__remill_fetch_and_or_32", int32_ty); find_missed_fixup("__remill_fetch_and_and_32", int32_ty); find_missed_fixup("__remill_fetch_and_xor_32", int32_ty); find_missed_fixup("__remill_compare_exchange_memory_64", int64_ty); find_missed_fixup("__remill_fetch_and_add_64", int64_ty); find_missed_fixup("__remill_fetch_and_sub_64", int64_ty); find_missed_fixup("__remill_fetch_and_or_64", int64_ty); find_missed_fixup("__remill_fetch_and_and_64", int64_ty); find_missed_fixup("__remill_fetch_and_xor_64", int64_ty); find_missed_fixup("__remill_write_memory_8", int8_ty); find_missed_fixup("__remill_write_memory_16", int16_ty); find_missed_fixup("__remill_write_memory_32", int32_ty); find_missed_fixup("__remill_write_memory_64", int64_ty); find_missed_fixup("__remill_write_memory_f32", float_ty); find_missed_fixup("__remill_write_memory_f64", double_ty); find_missed_fixup("__remill_write_memory_f80", fp80_ty); find_missed_fixup("__remill_write_memory_f128", fp128_ty); for (auto [use, replacement] : fixups) { use->set(replacement); } gZero = nullptr; if (auto zero = gModule->getNamedGlobal("__anvill_pc")) { zero->eraseFromParent(); } } // Looks for calls to a function like `__remill_function_return`, and // replace its state pointer with a null pointer so that the state // pointer never escapes. static void MuteStateEscape(const char *func_name) { auto func = gModule->getFunction(func_name); if (!func) { return; } const auto state_ptr = GetStatePointer(); for (auto user : func->users()) { if (auto call_inst = llvm::dyn_cast(user)) { call_inst->setArgOperand(remill::kStatePointerArgNum, state_ptr); } } } static void SanitizeNameForLinking(std::string &name) { for (auto &c : name) { if (!std::isalnum(c)) { c = '_'; } } } // Try to get `ptr` as an alias to a register in the thread-local state // structure. static llvm::Value *TryGetRegAlias(llvm::Value *ptr, unsigned offset) { if (FLAGS_disable_aliases) { return ptr; } auto reg = gArch->RegisterAtStateOffset(offset); if (!reg) { return ptr; } reg = reg->EnclosingRegister(); auto ptr_const = llvm::dyn_cast(ptr); if (!ptr_const) { return ptr; } const auto ptr_type = ptr_const->getType(); const auto elem_type = ptr_type->getPointerElementType(); std::stringstream ss; ss << reg->name << '_' << offset << '_' << std::hex << reinterpret_cast(elem_type); auto alias_name = ss.str(); SanitizeNameForLinking(alias_name); auto alias = gModule->getNamedAlias(alias_name); if (alias) { return alias; } alias = llvm::GlobalAlias::create( elem_type, ptr_type->getPointerAddressSpace(), llvm::GlobalValue::PrivateLinkage, alias_name, ptr_const, gModule.get()); alias->setThreadLocalMode(llvm::GlobalValue::InitialExecTLSModel); return alias; } // Go replace all uses of the state pointer argument with the global state // pointer, and try to look for the following patterns and also fold them // into constant expressions: // // %blah = ptrtoint %val load %val, %reg_ptr // store %blah, %reg_ptr %blah = intoptr %val // // into // // store %val, (bitcast CE) %blah = load (bitcast CE) // // So as to reduce the total number of GEPs and inttoptr instructions. static void GlobalizeStateStructures(void) { const auto state_ptr = ::mcsema::GetStatePointer(); const auto state_ptr_type = state_ptr->getType(); //const auto undef_state = llvm::UndefValue::get(state_ptr_type); std::vector> work_list; std::vector> next_work_list; std::vector> to_replace; std::vector to_remove; const auto &dl = gModule->getDataLayout(); for (auto &func : *gModule) { if (func.isDeclaration()) { continue; } if (func.getFunctionType()->getNumParams() <= remill::kStatePointerArgNum) { continue; } auto func_state_ptr = remill::NthArgument(&func, remill::kStatePointerArgNum); if (func_state_ptr->getType() != state_ptr_type) { continue; } #if 0 // Replace all state pointer args with `undef`. for (auto user : func.users()) { if (auto call_inst = llvm::dyn_cast(user); call_inst && call_inst->getCalledFunction() == &func) { call_inst->setArgOperand(remill::kStatePointerArgNum, undef_state); } } #endif to_replace.clear(); to_remove.clear(); next_work_list.clear(); next_work_list.emplace_back(func_state_ptr, 0); while (!next_work_list.empty()) { work_list.clear(); next_work_list.swap(work_list); for (auto [ptr_, offset] : work_list) { llvm::Value *ptr = ptr_; for (auto &use : ptr->uses()) { auto user = use.getUser(); if (auto bc = llvm::dyn_cast(user); bc) { next_work_list.emplace_back(bc, offset); to_remove.push_back(bc); } else if (auto gep = llvm::dyn_cast(user); gep) { llvm::APInt sub_offset(gArch->address_size, 0); if (gep->accumulateConstantOffset(dl, sub_offset)) { next_work_list.emplace_back(gep, offset + sub_offset.getZExtValue()); } else { to_replace.emplace_back(&use, offset); } to_remove.push_back(gep); } else if (llvm::isa(user) || llvm::isa(user) || llvm::isa(user)) { to_replace.emplace_back(&use, offset); } else if (auto inst = llvm::dyn_cast(user); inst) { to_replace.emplace_back(&use, offset); to_remove.push_back(inst); } else { to_replace.emplace_back(&use, offset); } } } } llvm::IRBuilder<> ir(&(func.getEntryBlock()), func.getEntryBlock().getFirstInsertionPt()); for (auto [use, offset] : to_replace) { auto user = use->getUser(); // Match on the store pattern that we want to simplify, if possible. if (auto store = llvm::dyn_cast(user); store) { auto base = store->getValueOperand(); if (auto pti = llvm::dyn_cast(base); pti) { auto ptr = remill::BuildPointerToOffset( ir, state_ptr, offset, llvm::PointerType::get(pti->getPointerOperandType(), 0)); ptr = TryGetRegAlias(ptr, offset); if (auto pti_inst = llvm::dyn_cast(pti); pti_inst) { to_remove.push_back(pti_inst); } (void) new llvm::StoreInst(pti->getPointerOperand(), ptr, store); to_remove.push_back(store); continue; } // Match on the load pattern that we want to simplify, if possible. } else if (auto load = llvm::dyn_cast(user); load) { for (auto &load_use : load->uses()) { auto user = load_use.getUser(); if (auto itp = llvm::dyn_cast(user)) { auto ptr = remill::BuildPointerToOffset( ir, state_ptr, offset, llvm::PointerType::get(itp->getType(), 0)); ptr = TryGetRegAlias(ptr, offset); itp->replaceAllUsesWith( #if LLVM_VERSION_NUMBER < LLVM_VERSION(11, 0) new llvm::LoadInst(ptr, load->getName(), load) #else new llvm::LoadInst(ptr->getType()->getPointerElementType(), ptr, load->getName(), load) #endif ); to_remove.push_back(itp); } } } auto ptr = remill::BuildPointerToOffset(ir, state_ptr, offset, use->get()->getType()); ptr = TryGetRegAlias(ptr, offset); use->set(ptr); } std::sort(to_remove.begin(), to_remove.end()); auto remove_it = std::unique(to_remove.begin(), to_remove.end()); to_remove.erase(remove_it, to_remove.end()); while (!to_remove.empty()) { auto inst = to_remove.back(); to_remove.pop_back(); if (!inst->hasNUsesOrMore(1)) { inst->eraseFromParent(); } } } } static void MuteLinkerSymbol(const char *sym_name) { if (auto gv = gModule->getGlobalVariable(sym_name); gv) { gv->setLinkage(llvm::GlobalValue::PrivateLinkage); } } } // namespace void OptimizeModule(const NativeModule *cfg_module) { if (auto llvm_used = gModule->getGlobalVariable("llvm.used")) { llvm_used->eraseFromParent(); } MuteStateEscape("__remill_function_return"); MuteStateEscape("__remill_jump"); MuteStateEscape("__remill_error"); MuteStateEscape("__remill_missing_block"); MuteStateEscape("__remill_async_hyper_call"); auto isels = FindISELs(); LOG(INFO) << "Optimizing module."; PrivatizeISELs(isels); auto bb_func = remill::BasicBlockFunction(gModule.get()); auto slots = remill::StateSlots(gArch.get(), gModule.get()); if (auto llvm_used = gModule->getGlobalVariable("llvm.used")) { llvm_used->eraseFromParent(); } llvm::legacy::PassManager mod_pm; mod_pm.add(llvm::createFunctionInliningPass(250)); mod_pm.run(*gModule); llvm::legacy::FunctionPassManager pm(gModule.get()); // pm.add(llvm::createGVNHoistPass()); // pm.add(llvm::createGVNSinkPass()); // pm.add(llvm::createMergedLoadStoreMotionPass()); pm.add(llvm::createEarlyCSEPass(true)); pm.add(llvm::createDeadCodeEliminationPass()); pm.add(llvm::createConstantPropagationPass()); pm.add(llvm::createSinkingPass()); pm.add(llvm::createNewGVNPass()); pm.add(llvm::createSCCPPass()); pm.add(llvm::createDeadStoreEliminationPass()); pm.add(llvm::createSROAPass()); pm.add(llvm::createPromoteMemoryToRegisterPass()); pm.add(llvm::createBitTrackingDCEPass()); pm.add(llvm::createCFGSimplificationPass()); pm.add(llvm::createSinkingPass()); pm.add(llvm::createCFGSimplificationPass()); pm.doInitialization(); for (auto &func : *gModule) { pm.run(func); } pm.doFinalization(); remill::RemoveDeadStores(gArch.get(), gModule.get(), bb_func, slots); // If some of the restores are *not* dead, then we will have eliminated // some loads and subsequent uses (in the `__remill_restore.*` argument lists) // that made those registers look live. The addition of restoring stores thus // means that there may be new DSE opportunities. if (RemoveDeadRestores()) { remill::RemoveDeadStores(gArch.get(), gModule.get(), bb_func, slots); pm.doInitialization(); for (auto &func : *gModule) { pm.run(func); } pm.doFinalization(); } RemoveKilledStores(); // NOTE(pag): These are not done in Function.cpp atm. if (false) { RemoveSavingKilledStores(); } // anvill::RecoverMemoryAccesses(*cfg_module, *gModule); LowerXrefs(cfg_module); for (auto &[ea, cfg_func] : cfg_module->ea_to_func) { (void) ea; // Make sure things like `main` show up. if (cfg_func->is_exported) { (void) cfg_func->Pointer(); } cfg_func->lifted_function = gModule->getFunction(cfg_func->lifted_name); // if (!cfg_func->is_exported && cfg_func->function) { // if (cfg_func->function->hasNUsesOrMore(1)) { // continue; // } // } } pm.doInitialization(); for (auto &func : *gModule) { pm.run(func); } pm.doFinalization(); for (auto &func : *gModule) { MergeGEPInstructions(func); } pm.doInitialization(); for (auto &func : *gModule) { pm.run(func); } pm.doFinalization(); } // Remove some of the Remill intrinsics. void CleanUpModule(const NativeModule *cfg_module) { RemoveUndefFuncCalls(); if (auto llvm_used = gModule->getGlobalVariable("llvm.used")) { llvm_used->eraseFromParent(); } if (!FLAGS_keep_memops) { LowerMemOps(cfg_module); 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"); ReplaceBarrier("__remill_delay_slot_begin"); ReplaceBarrier("__remill_delay_slot_end"); ReplaceBarrier("__remill_atomic_begin"); ReplaceBarrier("__remill_atomic_end"); llvm::legacy::FunctionPassManager pm(gModule.get()); pm.add(llvm::createEarlyCSEPass(true)); pm.add(llvm::createDeadCodeEliminationPass()); pm.add(llvm::createCFGSimplificationPass()); pm.doInitialization(); for (auto &func : *gModule) { pm.run(func); } pm.doFinalization(); } LowerXrefs(cfg_module); for (auto &[ea, cfg_func] : cfg_module->ea_to_func) { (void) ea; cfg_func->lifted_function = gModule->getFunction(cfg_func->lifted_name); if (cfg_func->lifted_function) { cfg_func->lifted_function->setLinkage(llvm::GlobalValue::InternalLinkage); } } // // Go try to inline // for (const auto &func : cfg_module->functions) { // if (func->lifted_function) { // func->lifted_function = gModule->getFunction(func->lifted_name); // } // // if (func->lifted_function) // // if (func->function) { // func->function = gModule->getFunction(func->name); // } // // if (func->function && // func->lifted_function && // func->lifted_function->hasNUses(1)) { // // } // } std::vector to_remove; do { to_remove.clear(); for (auto &func : *gModule) { if (!func.isDeclaration()) { continue; } if (!func.hasNUsesOrMore(1)) { to_remove.push_back(&func); // E.g. `__libc_init`. } else if (func.hasInternalLinkage()) { func.setLinkage(llvm::GlobalValue::ExternalLinkage); } } for (auto func : to_remove) { func->eraseFromParent(); } } while (!to_remove.empty()); // 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 uses; std::vector to_remove; for (auto use : remill_used->users()) { if (auto call = llvm::dyn_cast(use)) { uses.push_back(call); } } for (auto call : uses) { for (const auto &arg : call->arg_operands()) { to_remove.push_back(llvm::dyn_cast(arg.get())); } call->eraseFromParent(); } for (auto inst : to_remove) { if (inst && !inst->hasNUsesOrMore(1)) { inst->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_intrinsics"); RemoveFunction("__remill_basic_block"); RemoveFunction("__remill_defer_inlining"); RemoveFunction("__remill_undefined_8"); RemoveFunction("__remill_undefined_16"); RemoveFunction("__remill_undefined_32"); RemoveFunction("__remill_undefined_64"); RemoveFunction("__remill_undefined_f32"); RemoveFunction("__remill_undefined_f64"); RemoveFunction("__remill_undefined_f80"); RemoveFunction("__remill_undefined_f128"); if (!FLAGS_keep_memops) { RemoveFunction("__remill_read_memory_8"); RemoveFunction("__remill_read_memory_16"); RemoveFunction("__remill_read_memory_32"); RemoveFunction("__remill_read_memory_64"); RemoveFunction("__remill_read_memory_f32"); RemoveFunction("__remill_read_memory_f64"); RemoveFunction("__remill_read_memory_f80"); RemoveFunction("__remill_read_memory_f128"); RemoveFunction("__remill_write_memory_8"); RemoveFunction("__remill_write_memory_16"); RemoveFunction("__remill_write_memory_32"); RemoveFunction("__remill_write_memory_64"); RemoveFunction("__remill_write_memory_f32"); RemoveFunction("__remill_write_memory_f64"); RemoveFunction("__remill_write_memory_f80"); RemoveFunction("__remill_write_memory_f128"); RemoveFunction("__remill_compare_exchange_memory_8"); RemoveFunction("__remill_fetch_and_add_8"); RemoveFunction("__remill_fetch_and_sub_8"); RemoveFunction("__remill_fetch_and_or_8"); RemoveFunction("__remill_fetch_and_and_8"); RemoveFunction("__remill_fetch_and_xor_8"); RemoveFunction("__remill_compare_exchange_memory_16"); RemoveFunction("__remill_fetch_and_add_16"); RemoveFunction("__remill_fetch_and_sub_16"); RemoveFunction("__remill_fetch_and_or_16"); RemoveFunction("__remill_fetch_and_and_16"); RemoveFunction("__remill_fetch_and_xor_16"); RemoveFunction("__remill_compare_exchange_memory_32"); RemoveFunction("__remill_fetch_and_add_32"); RemoveFunction("__remill_fetch_and_sub_32"); RemoveFunction("__remill_fetch_and_or_32"); RemoveFunction("__remill_fetch_and_and_32"); RemoveFunction("__remill_fetch_and_xor_32"); RemoveFunction("__remill_compare_exchange_memory_64"); RemoveFunction("__remill_fetch_and_add_64"); RemoveFunction("__remill_fetch_and_sub_64"); RemoveFunction("__remill_fetch_and_or_64"); RemoveFunction("__remill_fetch_and_and_64"); RemoveFunction("__remill_fetch_and_xor_64"); } if (!FLAGS_local_state_pointer) { GlobalizeStateStructures(); llvm::legacy::FunctionPassManager pm(gModule.get()); pm.add(llvm::createEarlyCSEPass(true)); pm.add(llvm::createDeadCodeEliminationPass()); pm.add(llvm::createCFGSimplificationPass()); pm.doInitialization(); for (auto &func : *gModule) { pm.run(func); } pm.doFinalization(); } MuteLinkerSymbol("__TMC_END__"); MuteLinkerSymbol("__TMC_LIST__"); } } // namespace mcsema