mirror of
https://github.com/lifting-bits/remill
synced 2026-06-21 13:56:07 +00:00
677 lines
24 KiB
C++
677 lines
24 KiB
C++
/*
|
|
* Copyright (c) 2020 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 <glog/logging.h>
|
|
#include <llvm/IR/Instructions.h>
|
|
#include <remill/Arch/Instruction.h>
|
|
#include <remill/BC/IntrinsicTable.h>
|
|
#include <remill/BC/TraceLifter.h>
|
|
#include <remill/BC/Util.h>
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include <sstream>
|
|
|
|
#include "InstructionLifter.h"
|
|
|
|
namespace remill {
|
|
|
|
TraceManager::~TraceManager(void) {}
|
|
|
|
// Return an already lifted trace starting with the code at address
|
|
// `addr`.
|
|
llvm::Function *TraceManager::GetLiftedTraceDeclaration(uint64_t) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Return an already lifted trace starting with the code at address
|
|
// `addr`.
|
|
llvm::Function *TraceManager::GetLiftedTraceDefinition(uint64_t) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Apply a callback that gives the decoder access to multiple virtual
|
|
// targets of this instruction (indirect call or jump).
|
|
void TraceManager::ForEachDevirtualizedTarget(
|
|
const Instruction &,
|
|
std::function<void(uint64_t, DevirtualizedTargetKind)>) {
|
|
|
|
// Must be extended.
|
|
}
|
|
|
|
// Figure out the name for the trace starting at address `addr`.
|
|
std::string TraceManager::TraceName(uint64_t addr) {
|
|
std::stringstream ss;
|
|
ss << "sub_" << std::hex << addr;
|
|
return ss.str();
|
|
}
|
|
|
|
namespace {
|
|
|
|
using DecoderWorkList = std::set<uint64_t>; // For ordering.
|
|
|
|
} // namespace
|
|
|
|
class TraceLifter::Impl {
|
|
public:
|
|
Impl(const Arch *arch_, TraceManager *manager_);
|
|
|
|
// Lift one or more traces starting from `addr`. Calls `callback` with each
|
|
// lifted trace.
|
|
bool Lift(uint64_t addr,
|
|
std::function<void(uint64_t, llvm::Function *)> callback);
|
|
|
|
// Reads the bytes of an instruction at `addr` into `state.inst_bytes`.
|
|
bool ReadInstructionBytes(uint64_t addr);
|
|
|
|
// Return an already lifted trace starting with the code at address
|
|
// `addr`.
|
|
//
|
|
// NOTE: This is guaranteed to return either `nullptr`, or a function
|
|
// within `module`.
|
|
llvm::Function *GetLiftedTraceDeclaration(uint64_t addr);
|
|
|
|
// Return an already lifted trace starting with the code at address
|
|
// `addr`.
|
|
//
|
|
// NOTE: This is guaranteed to return either `nullptr`, or a function
|
|
// within `module`.
|
|
llvm::Function *GetLiftedTraceDefinition(uint64_t addr);
|
|
|
|
llvm::BasicBlock *GetOrCreateBlock(uint64_t block_pc) {
|
|
auto &block = blocks[block_pc];
|
|
if (!block) {
|
|
block = llvm::BasicBlock::Create(context, "", func);
|
|
}
|
|
return block;
|
|
}
|
|
|
|
llvm::BasicBlock *GetOrCreateBranchTakenBlock(void) {
|
|
inst_work_list.insert(inst.branch_taken_pc);
|
|
return GetOrCreateBlock(inst.branch_taken_pc);
|
|
}
|
|
|
|
llvm::BasicBlock *GetOrCreateBranchNotTakenBlock(void) {
|
|
CHECK(inst.branch_not_taken_pc != 0);
|
|
inst_work_list.insert(inst.branch_not_taken_pc);
|
|
return GetOrCreateBlock(inst.branch_not_taken_pc);
|
|
}
|
|
|
|
llvm::BasicBlock *GetOrCreateNextBlock(void) {
|
|
inst_work_list.insert(inst.next_pc);
|
|
return GetOrCreateBlock(inst.next_pc);
|
|
}
|
|
|
|
uint64_t PopTraceAddress(void) {
|
|
auto trace_it = trace_work_list.begin();
|
|
const auto trace_addr = *trace_it;
|
|
trace_work_list.erase(trace_it);
|
|
return trace_addr;
|
|
}
|
|
|
|
uint64_t PopInstructionAddress(void) {
|
|
auto inst_it = inst_work_list.begin();
|
|
const auto inst_addr = *inst_it;
|
|
inst_work_list.erase(inst_it);
|
|
return inst_addr;
|
|
}
|
|
|
|
const Arch *const arch;
|
|
const remill::IntrinsicTable *intrinsics;
|
|
llvm::Type *word_type;
|
|
llvm::LLVMContext &context;
|
|
llvm::Module *const module;
|
|
const uint64_t addr_mask;
|
|
TraceManager &manager;
|
|
|
|
llvm::Function *func;
|
|
llvm::BasicBlock *block;
|
|
llvm::SwitchInst *switch_inst;
|
|
const size_t max_inst_bytes;
|
|
std::string inst_bytes;
|
|
Instruction inst;
|
|
Instruction delayed_inst;
|
|
DecoderWorkList trace_work_list;
|
|
DecoderWorkList inst_work_list;
|
|
std::map<uint64_t, llvm::BasicBlock *> blocks;
|
|
};
|
|
|
|
TraceLifter::Impl::Impl(const Arch *arch_, TraceManager *manager_)
|
|
: arch(arch_),
|
|
intrinsics(arch->GetInstrinsicTable()),
|
|
word_type(arch->AddressType()),
|
|
context(word_type->getContext()),
|
|
module(intrinsics->async_hyper_call->getParent()),
|
|
addr_mask(arch->address_size >= 64 ? ~0ULL
|
|
: (~0ULL >> arch->address_size)),
|
|
manager(*manager_),
|
|
func(nullptr),
|
|
block(nullptr),
|
|
switch_inst(nullptr),
|
|
// TODO(Ian): The trace lfiter is not supporting contexts
|
|
max_inst_bytes(arch->MaxInstructionSize(arch->CreateInitialContext())) {
|
|
|
|
inst_bytes.reserve(max_inst_bytes);
|
|
}
|
|
|
|
// Return an already lifted trace starting with the code at address
|
|
// `addr`.
|
|
llvm::Function *TraceLifter::Impl::GetLiftedTraceDeclaration(uint64_t addr) {
|
|
auto func = manager.GetLiftedTraceDeclaration(addr);
|
|
if (!func || func->getParent() == module) {
|
|
return func;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Return an already lifted trace starting with the code at address
|
|
// `addr`.
|
|
llvm::Function *TraceLifter::Impl::GetLiftedTraceDefinition(uint64_t addr) {
|
|
auto func = manager.GetLiftedTraceDefinition(addr);
|
|
if (!func || func->getParent() == module) {
|
|
return func;
|
|
}
|
|
|
|
CHECK_EQ(&(func->getContext()), &context);
|
|
|
|
auto func_type = llvm::dyn_cast<llvm::FunctionType>(
|
|
RecontextualizeType(func->getFunctionType(), context));
|
|
|
|
// Handle the different module situation by declaring the trace in
|
|
// this module to be external, with the idea that it will link to
|
|
// another module.
|
|
auto extern_func = module->getFunction(func->getName());
|
|
if (!extern_func || extern_func->getFunctionType() != func_type) {
|
|
extern_func = llvm::Function::Create(
|
|
func_type, llvm::GlobalValue::ExternalLinkage, func->getName(), module);
|
|
|
|
} else if (extern_func->isDeclaration()) {
|
|
extern_func->setLinkage(llvm::GlobalValue::ExternalLinkage);
|
|
}
|
|
|
|
return extern_func;
|
|
}
|
|
|
|
TraceLifter::~TraceLifter(void) {}
|
|
|
|
TraceLifter::TraceLifter(const Arch *arch_, TraceManager *manager_)
|
|
: impl(new Impl(arch_, manager_)) {}
|
|
|
|
void TraceLifter::NullCallback(uint64_t, llvm::Function *) {}
|
|
|
|
// Reads the bytes of an instruction at `addr` into `inst_bytes`.
|
|
bool TraceLifter::Impl::ReadInstructionBytes(uint64_t addr) {
|
|
inst_bytes.clear();
|
|
for (size_t i = 0; i < max_inst_bytes; ++i) {
|
|
const auto byte_addr = (addr + i) & addr_mask;
|
|
if (byte_addr < addr) {
|
|
break; // 32- or 64-bit address overflow.
|
|
}
|
|
uint8_t byte = 0;
|
|
if (!manager.TryReadExecutableByte(byte_addr, &byte)) {
|
|
DLOG(WARNING) << "Couldn't read executable byte at " << std::hex
|
|
<< byte_addr << std::dec;
|
|
break;
|
|
}
|
|
inst_bytes.push_back(static_cast<char>(byte));
|
|
}
|
|
return !inst_bytes.empty();
|
|
}
|
|
|
|
// Lift one or more traces starting from `addr`.
|
|
bool TraceLifter::Lift(
|
|
uint64_t addr, std::function<void(uint64_t, llvm::Function *)> callback) {
|
|
return impl->Lift(addr, callback);
|
|
}
|
|
|
|
// Lift one or more traces starting from `addr`.
|
|
bool TraceLifter::Impl::Lift(
|
|
uint64_t addr, std::function<void(uint64_t, llvm::Function *)> callback) {
|
|
// Reset the lifting state.
|
|
trace_work_list.clear();
|
|
inst_work_list.clear();
|
|
blocks.clear();
|
|
inst_bytes.clear();
|
|
func = nullptr;
|
|
switch_inst = nullptr;
|
|
block = nullptr;
|
|
inst.Reset();
|
|
delayed_inst.Reset();
|
|
|
|
// Get a trace head that the manager knows about, or that we
|
|
// will eventually tell the trace manager about.
|
|
auto get_trace_decl = [this](uint64_t trace_addr) -> llvm::Function * {
|
|
if (auto trace = GetLiftedTraceDeclaration(trace_addr)) {
|
|
return trace;
|
|
}
|
|
|
|
auto trace_name = manager.TraceName(trace_addr);
|
|
if (auto trace = module->getFunction(trace_name)) {
|
|
return trace;
|
|
}
|
|
|
|
if (trace_work_list.count(trace_addr)) {
|
|
return arch->DeclareLiftedFunction(trace_name, module);
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
trace_work_list.insert(addr);
|
|
while (!trace_work_list.empty()) {
|
|
const auto trace_addr = PopTraceAddress();
|
|
|
|
// Already lifted.
|
|
func = GetLiftedTraceDefinition(trace_addr);
|
|
if (func) {
|
|
continue;
|
|
}
|
|
|
|
DLOG(INFO) << "Lifting trace at address " << std::hex << trace_addr
|
|
<< std::dec;
|
|
|
|
func = get_trace_decl(trace_addr);
|
|
blocks.clear();
|
|
|
|
if (!func || !func->isDeclaration()) {
|
|
func = arch->DeclareLiftedFunction(manager.TraceName(trace_addr), module);
|
|
}
|
|
|
|
CHECK(func->isDeclaration());
|
|
|
|
// Fill in the function, and make sure the block with all register
|
|
// variables jumps to the block that will contain the first instruction
|
|
// of the trace.
|
|
arch->InitializeEmptyLiftedFunction(func);
|
|
|
|
auto state_ptr = NthArgument(func, kStatePointerArgNum);
|
|
|
|
if (auto entry_block = &(func->front())) {
|
|
auto pc = LoadProgramCounterArg(func);
|
|
auto [next_pc_ref, next_pc_ref_type] =
|
|
this->arch->DefaultLifter(*this->intrinsics)
|
|
->LoadRegAddress(entry_block, state_ptr, kNextPCVariableName);
|
|
|
|
// Initialize `NEXT_PC`.
|
|
(void) new llvm::StoreInst(pc, next_pc_ref, entry_block);
|
|
|
|
// Branch to the first basic block.
|
|
llvm::BranchInst::Create(GetOrCreateBlock(trace_addr), entry_block);
|
|
}
|
|
|
|
CHECK(inst_work_list.empty());
|
|
inst_work_list.insert(trace_addr);
|
|
|
|
// Decode instructions.
|
|
while (!inst_work_list.empty()) {
|
|
const auto inst_addr = PopInstructionAddress();
|
|
|
|
block = GetOrCreateBlock(inst_addr);
|
|
switch_inst = nullptr;
|
|
|
|
// We have already lifted this instruction block.
|
|
if (!block->empty()) {
|
|
continue;
|
|
}
|
|
|
|
// Check to see if this instruction corresponds with an existing
|
|
// trace head, and if so, tail-call into that trace directly without
|
|
// decoding or lifting the instruction.
|
|
if (inst_addr != trace_addr) {
|
|
if (auto inst_as_trace = get_trace_decl(inst_addr)) {
|
|
AddTerminatingTailCall(block, inst_as_trace, *intrinsics);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// No executable bytes here.
|
|
if (!ReadInstructionBytes(inst_addr)) {
|
|
AddTerminatingTailCall(block, intrinsics->missing_block, *intrinsics);
|
|
continue;
|
|
}
|
|
|
|
inst.Reset();
|
|
|
|
// TODO(Ian): not passing context around in trace lifter
|
|
std::ignore = arch->DecodeInstruction(inst_addr, inst_bytes, inst,
|
|
this->arch->CreateInitialContext());
|
|
|
|
auto lift_status =
|
|
inst.GetLifter()->LiftIntoBlock(inst, block, state_ptr);
|
|
if (kLiftedInstruction != lift_status) {
|
|
AddTerminatingTailCall(block, intrinsics->error, *intrinsics);
|
|
continue;
|
|
}
|
|
|
|
// Handle lifting a delayed instruction.
|
|
auto try_delay = arch->MayHaveDelaySlot(inst);
|
|
if (try_delay) {
|
|
delayed_inst.Reset();
|
|
if (!ReadInstructionBytes(inst.delayed_pc) ||
|
|
!arch->DecodeDelayedInstruction(
|
|
inst.delayed_pc, inst_bytes, delayed_inst,
|
|
this->arch->CreateInitialContext())) {
|
|
LOG(ERROR) << "Couldn't read delayed inst "
|
|
<< delayed_inst.Serialize();
|
|
AddTerminatingTailCall(block, intrinsics->error, *intrinsics);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Functor used to add in a delayed instruction.
|
|
auto try_add_delay_slot = [&](bool on_branch_taken_path,
|
|
llvm::BasicBlock *into_block) -> void {
|
|
if (!try_delay) {
|
|
return;
|
|
}
|
|
if (!arch->NextInstructionIsDelayed(inst, delayed_inst,
|
|
on_branch_taken_path)) {
|
|
return;
|
|
}
|
|
lift_status = delayed_inst.GetLifter()->LiftIntoBlock(
|
|
delayed_inst, into_block, state_ptr, true /* is_delayed */);
|
|
if (kLiftedInstruction != lift_status) {
|
|
AddTerminatingTailCall(block, intrinsics->error, *intrinsics);
|
|
}
|
|
};
|
|
|
|
// Connect together the basic blocks.
|
|
switch (inst.category) {
|
|
case Instruction::kCategoryInvalid:
|
|
case Instruction::kCategoryError:
|
|
AddTerminatingTailCall(block, intrinsics->error, *intrinsics);
|
|
break;
|
|
|
|
case Instruction::kCategoryNormal:
|
|
case Instruction::kCategoryNoOp:
|
|
llvm::BranchInst::Create(GetOrCreateNextBlock(), block);
|
|
break;
|
|
|
|
// Direct jumps could either be local or could be tail-calls. In the
|
|
// case of a tail call, we'll assume that the trace manager contains
|
|
// advanced knowledge of this, and so when we go to make a block for
|
|
// the targeted instruction, we'll either tail call to the target
|
|
// trace, or we'll just extend out the current trace. Either way, no
|
|
// sacrifice in correctness is made.
|
|
case Instruction::kCategoryDirectJump:
|
|
try_add_delay_slot(true, block);
|
|
llvm::BranchInst::Create(GetOrCreateBranchTakenBlock(), block);
|
|
break;
|
|
|
|
case Instruction::kCategoryIndirectJump: {
|
|
try_add_delay_slot(true, block);
|
|
AddTerminatingTailCall(block, intrinsics->jump, *intrinsics);
|
|
break;
|
|
}
|
|
|
|
case Instruction::kCategoryAsyncHyperCall:
|
|
AddCall(block, intrinsics->async_hyper_call, *intrinsics);
|
|
goto check_call_return;
|
|
|
|
case Instruction::kCategoryIndirectFunctionCall: {
|
|
try_add_delay_slot(true, block);
|
|
const auto fall_through_block =
|
|
llvm::BasicBlock::Create(context, "", func);
|
|
|
|
const auto ret_pc_ref =
|
|
LoadReturnProgramCounterRef(fall_through_block);
|
|
const auto next_pc_ref =
|
|
LoadNextProgramCounterRef(fall_through_block);
|
|
llvm::IRBuilder<> ir(fall_through_block);
|
|
ir.CreateStore(ir.CreateLoad(word_type, ret_pc_ref), next_pc_ref);
|
|
ir.CreateBr(GetOrCreateBranchNotTakenBlock());
|
|
|
|
AddCall(block, intrinsics->function_call, *intrinsics);
|
|
llvm::BranchInst::Create(fall_through_block, block);
|
|
block = fall_through_block;
|
|
continue;
|
|
}
|
|
|
|
case Instruction::kCategoryConditionalIndirectFunctionCall: {
|
|
auto taken_block = llvm::BasicBlock::Create(context, "", func);
|
|
auto not_taken_block = GetOrCreateBranchNotTakenBlock();
|
|
const auto orig_not_taken_block = not_taken_block;
|
|
|
|
// If we might need to add delay slots, then try to lift the delayed
|
|
// instruction on each side of the conditional branch, injecting in
|
|
// new blocks (for the delayed instruction) between the branch
|
|
// and its original targets.
|
|
if (try_delay) {
|
|
not_taken_block = llvm::BasicBlock::Create(context, "", func);
|
|
|
|
try_add_delay_slot(true, taken_block);
|
|
try_add_delay_slot(false, not_taken_block);
|
|
|
|
llvm::BranchInst::Create(orig_not_taken_block, not_taken_block);
|
|
}
|
|
|
|
llvm::BranchInst::Create(taken_block, not_taken_block,
|
|
LoadBranchTaken(block), block);
|
|
|
|
AddCall(taken_block, intrinsics->function_call, *intrinsics);
|
|
|
|
const auto ret_pc_ref = LoadReturnProgramCounterRef(taken_block);
|
|
const auto next_pc_ref = LoadNextProgramCounterRef(taken_block);
|
|
llvm::IRBuilder<> ir(taken_block);
|
|
ir.CreateStore(ir.CreateLoad(word_type, ret_pc_ref), next_pc_ref);
|
|
ir.CreateBr(orig_not_taken_block);
|
|
block = orig_not_taken_block;
|
|
continue;
|
|
}
|
|
|
|
// In the case of a direct function call, we try to handle the
|
|
// pattern of a call to the next PC as a way of getting access to
|
|
// an instruction pointer. It is the case where a call to the next
|
|
// PC could also be something more like a call to a `noreturn` function
|
|
// and that is OK, because either a user of the trace manager has
|
|
// already told us that the next PC is a trace head (and we'll pick
|
|
// that up when trying to lift it), or we'll just have a really big
|
|
// trace for this function without sacrificing correctness.
|
|
case Instruction::kCategoryDirectFunctionCall: {
|
|
direct_func_call:
|
|
try_add_delay_slot(true, block);
|
|
if (inst.branch_not_taken_pc != inst.branch_taken_pc) {
|
|
trace_work_list.insert(inst.branch_taken_pc);
|
|
auto target_trace = get_trace_decl(inst.branch_taken_pc);
|
|
AddCall(block, target_trace, *intrinsics);
|
|
}
|
|
|
|
const auto ret_pc_ref = LoadReturnProgramCounterRef(block);
|
|
const auto next_pc_ref = LoadNextProgramCounterRef(block);
|
|
llvm::IRBuilder<> ir(block);
|
|
ir.CreateStore(ir.CreateLoad(word_type, ret_pc_ref), next_pc_ref);
|
|
ir.CreateBr(GetOrCreateBranchNotTakenBlock());
|
|
|
|
continue;
|
|
}
|
|
|
|
case Instruction::kCategoryConditionalDirectFunctionCall: {
|
|
if (inst.branch_not_taken_pc == inst.branch_taken_pc) {
|
|
goto direct_func_call;
|
|
}
|
|
|
|
auto taken_block = llvm::BasicBlock::Create(context, "", func);
|
|
auto not_taken_block = GetOrCreateBranchNotTakenBlock();
|
|
const auto orig_not_taken_block = not_taken_block;
|
|
|
|
// If we might need to add delay slots, then try to lift the delayed
|
|
// instruction on each side of the conditional branch, injecting in
|
|
// new blocks (for the delayed instruction) between the branch
|
|
// and its original targets.
|
|
if (try_delay) {
|
|
not_taken_block = llvm::BasicBlock::Create(context, "", func);
|
|
|
|
try_add_delay_slot(true, taken_block);
|
|
try_add_delay_slot(false, not_taken_block);
|
|
|
|
llvm::BranchInst::Create(orig_not_taken_block, not_taken_block);
|
|
}
|
|
|
|
llvm::BranchInst::Create(taken_block, not_taken_block,
|
|
LoadBranchTaken(block), block);
|
|
|
|
trace_work_list.insert(inst.branch_taken_pc);
|
|
auto target_trace = get_trace_decl(inst.branch_taken_pc);
|
|
|
|
AddCall(taken_block, intrinsics->function_call, *intrinsics);
|
|
AddCall(taken_block, target_trace, *intrinsics);
|
|
|
|
const auto ret_pc_ref = LoadReturnProgramCounterRef(taken_block);
|
|
const auto next_pc_ref = LoadNextProgramCounterRef(taken_block);
|
|
llvm::IRBuilder<> ir(taken_block);
|
|
ir.CreateStore(ir.CreateLoad(word_type, ret_pc_ref), next_pc_ref);
|
|
ir.CreateBr(orig_not_taken_block);
|
|
block = orig_not_taken_block;
|
|
continue;
|
|
}
|
|
|
|
// Lift an async hyper call to check if it should do the hypercall.
|
|
// If so, it will jump to the `do_hyper_call` block, otherwise it will
|
|
// jump to the block associated with the next PC. In the case of the
|
|
// `do_hyper_call` block, we assign it to `state.block`, then go
|
|
// to `check_call_return` to add the hyper call into that block,
|
|
// checking if the hyper call returns to the next PC or not.
|
|
//
|
|
// TODO(pag): Delay slots?
|
|
case Instruction::kCategoryConditionalAsyncHyperCall: {
|
|
auto do_hyper_call = llvm::BasicBlock::Create(context, "", func);
|
|
llvm::BranchInst::Create(do_hyper_call, GetOrCreateNextBlock(),
|
|
LoadBranchTaken(block), block);
|
|
block = do_hyper_call;
|
|
AddCall(block, intrinsics->async_hyper_call, *intrinsics);
|
|
goto check_call_return;
|
|
}
|
|
|
|
check_call_return:
|
|
do {
|
|
auto pc = LoadProgramCounter(block, *intrinsics);
|
|
auto ret_pc =
|
|
llvm::ConstantInt::get(intrinsics->pc_type, inst.next_pc);
|
|
|
|
llvm::IRBuilder<> ir(block);
|
|
auto eq = ir.CreateICmpEQ(pc, ret_pc);
|
|
auto unexpected_ret_pc =
|
|
llvm::BasicBlock::Create(context, "", func);
|
|
ir.CreateCondBr(eq, GetOrCreateNextBlock(), unexpected_ret_pc);
|
|
AddTerminatingTailCall(unexpected_ret_pc, intrinsics->missing_block,
|
|
*intrinsics);
|
|
} while (false);
|
|
break;
|
|
|
|
case Instruction::kCategoryFunctionReturn:
|
|
try_add_delay_slot(true, block);
|
|
AddTerminatingTailCall(block, intrinsics->function_return,
|
|
*intrinsics);
|
|
break;
|
|
|
|
case Instruction::kCategoryConditionalFunctionReturn: {
|
|
auto taken_block = llvm::BasicBlock::Create(context, "", func);
|
|
auto not_taken_block = GetOrCreateBranchNotTakenBlock();
|
|
const auto orig_not_taken_block = not_taken_block;
|
|
|
|
// If we might need to add delay slots, then try to lift the delayed
|
|
// instruction on each side of the conditional branch, injecting in
|
|
// new blocks (for the delayed instruction) between the branch
|
|
// and its original targets.
|
|
if (try_delay) {
|
|
not_taken_block = llvm::BasicBlock::Create(context, "", func);
|
|
|
|
try_add_delay_slot(true, taken_block);
|
|
try_add_delay_slot(false, not_taken_block);
|
|
|
|
llvm::BranchInst::Create(orig_not_taken_block, not_taken_block);
|
|
}
|
|
|
|
llvm::BranchInst::Create(taken_block, not_taken_block,
|
|
LoadBranchTaken(block), block);
|
|
|
|
AddTerminatingTailCall(taken_block, intrinsics->function_return,
|
|
*intrinsics);
|
|
block = orig_not_taken_block;
|
|
continue;
|
|
}
|
|
|
|
case Instruction::kCategoryConditionalBranch: {
|
|
auto taken_block = GetOrCreateBranchTakenBlock();
|
|
auto not_taken_block = GetOrCreateBranchNotTakenBlock();
|
|
|
|
// If we might need to add delay slots, then try to lift the delayed
|
|
// instruction on each side of the conditional branch, injecting in
|
|
// new blocks (for the delayed instruction) between the branch
|
|
// and its original targets.
|
|
if (try_delay) {
|
|
auto new_taken_block = llvm::BasicBlock::Create(context, "", func);
|
|
auto new_not_taken_block =
|
|
llvm::BasicBlock::Create(context, "", func);
|
|
|
|
try_add_delay_slot(true, new_taken_block);
|
|
try_add_delay_slot(false, new_not_taken_block);
|
|
|
|
llvm::BranchInst::Create(taken_block, new_taken_block);
|
|
llvm::BranchInst::Create(not_taken_block, new_not_taken_block);
|
|
|
|
taken_block = new_taken_block;
|
|
not_taken_block = new_not_taken_block;
|
|
}
|
|
|
|
llvm::BranchInst::Create(taken_block, not_taken_block,
|
|
LoadBranchTaken(block), block);
|
|
break;
|
|
}
|
|
case Instruction::kCategoryConditionalIndirectJump: {
|
|
auto taken_block = llvm::BasicBlock::Create(context, "", func);
|
|
auto not_taken_block = GetOrCreateBranchNotTakenBlock();
|
|
const auto orig_not_taken_block = not_taken_block;
|
|
|
|
// If we might need to add delay slots, then try to lift the delayed
|
|
// instruction on each side of the conditional branch, injecting in
|
|
// new blocks (for the delayed instruction) between the branch
|
|
// and its original targets.
|
|
if (try_delay) {
|
|
not_taken_block = llvm::BasicBlock::Create(context, "", func);
|
|
|
|
try_add_delay_slot(true, taken_block);
|
|
try_add_delay_slot(false, not_taken_block);
|
|
|
|
llvm::BranchInst::Create(orig_not_taken_block, not_taken_block);
|
|
}
|
|
|
|
llvm::BranchInst::Create(taken_block, not_taken_block,
|
|
LoadBranchTaken(block), block);
|
|
|
|
AddTerminatingTailCall(taken_block, intrinsics->jump, *intrinsics);
|
|
block = orig_not_taken_block;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto &block : *func) {
|
|
if (!block.getTerminator()) {
|
|
AddTerminatingTailCall(&block, intrinsics->missing_block, *intrinsics);
|
|
}
|
|
}
|
|
|
|
callback(trace_addr, func);
|
|
manager.SetLiftedTraceDefinition(trace_addr, func);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace remill
|