mirror of
https://github.com/lifting-bits/remill
synced 2026-06-21 13:56:07 +00:00
Mixed Mode AArch32 (#622)
* stuff * make decoders standalone from arch * compiles * fix lazy initialization of intrinsic table * add back sleigh arch * added switching between arches... need to update contexts * add instructin sizes * thumb triplet * back out of lambda approach * updating context in thumb case * fix differential tester to initialize lifter * add back lambda approach * aarch32 context updates * add tests * fix assetions in tests * fix address alignment for tests * add better decode function * fix tests * add conditional tests * fix uninitialized context in contextupdater * restore old contexts on call... * null out branch taken arch on indirects * fix fallthrough for conditional where neither src evaluates properly * initialize is interproc * fix initial context arm * fix boolop bug where params would be lifted when not bool op * fix comparison * remove debug * make missing context non fatal * bump sleigh * add insn flow variant * stub out computing categories * add control flow structuring * adding unconditoinal abnormal * start refactoring flows to share more structure * rework conditionals to express a combination of a condition with an abnormal flow * finish basic flow analysis * add context updater * implement eq * stop passing back contexts through return value * add tests * rework constructors, make flow usage consistent * implement flows in aarch32 * add implementation of flows for old cateogries and no context * make sleigh lifter backwards compatible by applying flows to instructions * fix bug in generating coarse flows * refactor to allow lifter to bring along decoding state * rework insertion of branch taken vats * fix off by one * fix size * dont reset bytes before use * disable broken float ops * back of ambiguity to non fatal error, also add logging of callother's encountered * update names * remove debug logging * add comment about x86 context * move to headers * move eqs * order of ctors, fields, methods * noop subtype of normal * remove direct constructors * remove more duplication * more duplication in noop * remove unused field * add utility for is thumb * absolute path * remove duplication * absolute paths * review fixes * make const refs * make enums match style guide * add constructor to flow * non ref * make refs * move map out of optional * refer to enum * move curr id out * fix BOOL_NEGATE for non 1 bit * type alias for decoding result of branch taken and flow * more delog * this language makes me incredibly happy * remap flags and treat metavars more like how the instructionlifter does it * more fixes to metavars * fix pc reg definition so consistent with value during isntruction execution * fix alignment of test * add todo * extract condition * add comment
This commit is contained in:
+1
-1
@@ -25,7 +25,7 @@ CMakeLists.txt.user
|
||||
cmake-build-debug
|
||||
cmake-build-release
|
||||
compile_commands.json
|
||||
|
||||
.cache
|
||||
|
||||
third_party/*
|
||||
build/*
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ set(sleigh_ADDITIONAL_PATCHES "${CMAKE_CURRENT_SOURCE_DIR}/patches/sleigh/x86-ia
|
||||
# GHIDRA SLEIGH
|
||||
FetchContent_Declare(sleigh
|
||||
GIT_REPOSITORY https://github.com/lifting-bits/sleigh.git
|
||||
GIT_TAG 74838a39ba7ddd91becb1d7f59e6cc3d7e3956de
|
||||
GIT_TAG 9966017ca00fd910f7a27e62c1024c768fad5d51
|
||||
)
|
||||
|
||||
set(sleigh_BUILD_SUPPORT ON CACHE BOOL "" FORCE)
|
||||
|
||||
@@ -11,34 +11,31 @@
|
||||
|
||||
#include <string>
|
||||
namespace remill {
|
||||
/// Class to derive from to handle x86 addregs
|
||||
|
||||
class AArch32ArchBase : public virtual ArchBase {
|
||||
public:
|
||||
AArch32ArchBase(llvm::LLVMContext *context_, OSName os_name_,
|
||||
ArchName arch_name_)
|
||||
: ArchBase(context_, os_name_, arch_name_) {}
|
||||
|
||||
virtual std::string_view StackPointerRegisterName(void) const;
|
||||
virtual std::string_view StackPointerRegisterName(void) const override;
|
||||
|
||||
std::string_view ProgramCounterRegisterName(void) const;
|
||||
uint64_t MinInstructionAlign(void) const;
|
||||
std::string_view ProgramCounterRegisterName(void) const override;
|
||||
|
||||
|
||||
uint64_t MinInstructionSize(void) const;
|
||||
llvm::CallingConv::ID DefaultCallingConv(void) const override;
|
||||
|
||||
uint64_t MaxInstructionSize(bool) const;
|
||||
llvm::CallingConv::ID DefaultCallingConv(void) const;
|
||||
llvm::DataLayout DataLayout(void) const override;
|
||||
|
||||
llvm::DataLayout DataLayout(void) const;
|
||||
|
||||
llvm::Triple Triple(void) const;
|
||||
llvm::Triple Triple(void) const override;
|
||||
|
||||
|
||||
void PopulateRegisterTable(void) const;
|
||||
void PopulateRegisterTable(void) const override;
|
||||
// Populate a just-initialized lifted function function with architecture-
|
||||
// specific variables.
|
||||
void FinishLiftedFunctionInitialization(llvm::Module *module,
|
||||
llvm::Function *bb_func) const;
|
||||
void
|
||||
FinishLiftedFunctionInitialization(llvm::Module *module,
|
||||
llvm::Function *bb_func) const override;
|
||||
virtual ~AArch32ArchBase(void) = default;
|
||||
};
|
||||
} // namespace remill
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <remill/Arch/Context.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace remill {
|
||||
inline const std::string_view kThumbModeRegName = "TMReg";
|
||||
|
||||
inline const remill::DecodingContext kThumbContext =
|
||||
remill::DecodingContext({{std::string(remill::kThumbModeRegName), 1}});
|
||||
inline const remill::DecodingContext kARMContext =
|
||||
remill::DecodingContext({{std::string(remill::kThumbModeRegName), 0}});
|
||||
|
||||
} // namespace remill
|
||||
+11
-13
@@ -287,36 +287,34 @@ class Arch {
|
||||
// bytes being passed to the decoder, until you successfully decode
|
||||
// or ultimately fail.
|
||||
|
||||
// The decoder takes contextual information in the form of a DecodingContext, making a copy to produce a ContextMap which is a function that maps
|
||||
// a successor to a new context that updates the old context.
|
||||
|
||||
using DecodingResult = std::optional<DecodingContext::ContextMap>;
|
||||
|
||||
virtual DecodingResult
|
||||
DecodeInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst, DecodingContext context) const = 0;
|
||||
virtual bool DecodeInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const = 0;
|
||||
|
||||
// Decode an instruction that is within a delay slot.
|
||||
DecodingResult
|
||||
DecodeDelayedInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst, DecodingContext context) const {
|
||||
bool DecodeDelayedInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const {
|
||||
inst.in_delay_slot = true;
|
||||
return this->DecodeInstruction(address, instr_bytes, inst,
|
||||
std::move(context));
|
||||
}
|
||||
|
||||
// Minimum alignment of an instruction for this particular architecture.
|
||||
virtual uint64_t MinInstructionAlign(void) const = 0;
|
||||
virtual uint64_t
|
||||
MinInstructionAlign(const DecodingContext &context) const = 0;
|
||||
|
||||
// Minimum number of bytes in an instruction for this particular architecture.
|
||||
virtual uint64_t MinInstructionSize(void) const = 0;
|
||||
virtual uint64_t MinInstructionSize(const DecodingContext &context) const = 0;
|
||||
|
||||
// Maximum number of bytes in an instruction for this particular architecture.
|
||||
//
|
||||
// `permit_fuse_idioms` is `true` if Remill is allowed to decode multiple
|
||||
// instructions at a time and look for instruction fusing idioms that are
|
||||
// common to this architecture.
|
||||
virtual uint64_t MaxInstructionSize(bool permit_fuse_idioms = true) const = 0;
|
||||
virtual uint64_t MaxInstructionSize(const DecodingContext &context,
|
||||
bool permit_fuse_idioms = true) const = 0;
|
||||
|
||||
// Default calling convention for this architecture.
|
||||
virtual llvm::CallingConv::ID DefaultCallingConv(void) const = 0;
|
||||
|
||||
@@ -106,12 +106,22 @@ class ArchBase : public remill::Arch {
|
||||
};
|
||||
|
||||
class DefaultContextAndLifter : virtual public remill::ArchBase {
|
||||
private:
|
||||
Instruction::FallthroughFlow GetFallthrough() const;
|
||||
|
||||
Instruction::DirectFlow GetDirectFlow(uint64_t target) const;
|
||||
|
||||
Instruction::IndirectFlow GetIndirectFlow() const;
|
||||
|
||||
Instruction::InstructionFlowCategory FillInFlowFromCategoryAndDefaultContext(
|
||||
const remill::Instruction &inst) const;
|
||||
|
||||
public:
|
||||
virtual DecodingContext CreateInitialContext(void) const override;
|
||||
|
||||
virtual std::optional<DecodingContext::ContextMap>
|
||||
DecodeInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst, DecodingContext context) const override;
|
||||
virtual bool DecodeInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const override;
|
||||
|
||||
|
||||
OperandLifter::OpLifterPtr
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace remill {
|
||||
|
||||
@@ -30,23 +32,29 @@ namespace remill {
|
||||
/// We return a function of successor -> DecodingContext. The decoder defines a relation on the
|
||||
/// previous context and the successor address that produces a new decoding.
|
||||
/// This definition of returned contexts allows us to cleanly handle situations like indirect jumps in arm
|
||||
///
|
||||
class DecodingContext {
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, uint64_t> context_value;
|
||||
std::map<std::string, uint64_t> context_value;
|
||||
|
||||
public:
|
||||
using ContextMap = std::function<DecodingContext(uint64_t)>;
|
||||
bool operator==(const DecodingContext &rhs) const;
|
||||
|
||||
DecodingContext() = default;
|
||||
|
||||
DecodingContext(std::unordered_map<std::string, uint64_t> context_value);
|
||||
DecodingContext(std::map<std::string, uint64_t> context_value);
|
||||
|
||||
|
||||
void UpdateContextReg(std::string creg, uint64_t value);
|
||||
void DropReg(const std::string &creg);
|
||||
|
||||
bool HasValueForReg(const std::string &creg) const;
|
||||
|
||||
|
||||
uint64_t GetContextValue(const std::string &context_reg) const;
|
||||
DecodingContext PutContextReg(std::string creg, uint64_t value) const;
|
||||
|
||||
static ContextMap UniformContextMapping(DecodingContext cst);
|
||||
DecodingContext ContextWithoutRegister(const std::string &creg) const;
|
||||
};
|
||||
|
||||
} // namespace remill
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <remill/Arch/Context.h>
|
||||
#include <remill/BC/InstructionLifter.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
@@ -212,7 +214,8 @@ class Instruction {
|
||||
ArchName sub_arch_name;
|
||||
|
||||
// Name of the architecture of the branch taken target.
|
||||
ArchName branch_taken_arch_name;
|
||||
/// We may not know the arch name if it is an indirect jump
|
||||
std::optional<ArchName> branch_taken_arch_name;
|
||||
|
||||
// Pointer to the `remill::Arch` used to complete the decoding of this
|
||||
// instruction.
|
||||
@@ -254,6 +257,145 @@ class Instruction {
|
||||
kCategoryConditionalAsyncHyperCall,
|
||||
} category;
|
||||
|
||||
|
||||
struct Flow {};
|
||||
|
||||
struct DirectFlow : Flow {
|
||||
public:
|
||||
DirectFlow() = delete;
|
||||
DirectFlow(uint64_t known_target, DecodingContext static_context);
|
||||
|
||||
uint64_t known_target;
|
||||
DecodingContext static_context;
|
||||
|
||||
bool operator==(const DirectFlow &rhs) const;
|
||||
};
|
||||
|
||||
struct IndirectFlow : Flow {
|
||||
public:
|
||||
IndirectFlow() = delete;
|
||||
IndirectFlow(std::optional<DecodingContext> maybe_context);
|
||||
|
||||
// We may have info in the decoder that tells us a context value
|
||||
std::optional<DecodingContext> maybe_context;
|
||||
|
||||
bool operator==(const IndirectFlow &rhs) const;
|
||||
};
|
||||
|
||||
struct FallthroughFlow : Flow {
|
||||
public:
|
||||
FallthroughFlow(DecodingContext fallthrough_context);
|
||||
FallthroughFlow() = delete;
|
||||
|
||||
DecodingContext fallthrough_context;
|
||||
|
||||
bool operator==(const FallthroughFlow &rhs) const;
|
||||
};
|
||||
|
||||
struct NormalInsn {
|
||||
public:
|
||||
NormalInsn() = delete;
|
||||
NormalInsn(FallthroughFlow fallthrough);
|
||||
|
||||
FallthroughFlow fallthrough;
|
||||
|
||||
bool operator==(const NormalInsn &rhs) const;
|
||||
};
|
||||
|
||||
struct NoOp : public NormalInsn {
|
||||
public:
|
||||
using NormalInsn::NormalInsn;
|
||||
|
||||
bool operator==(const NoOp &rhs) const;
|
||||
};
|
||||
|
||||
struct InvalidInsn {
|
||||
public:
|
||||
InvalidInsn() = default;
|
||||
|
||||
bool operator==(const InvalidInsn &rhs) const;
|
||||
};
|
||||
|
||||
struct ErrorInsn {
|
||||
public:
|
||||
ErrorInsn() = default;
|
||||
|
||||
bool operator==(const ErrorInsn &rhs) const;
|
||||
};
|
||||
|
||||
struct DirectJump {
|
||||
public:
|
||||
DirectJump() = delete;
|
||||
DirectJump(DirectFlow taken_flow);
|
||||
|
||||
DirectFlow taken_flow;
|
||||
|
||||
bool operator==(const DirectJump &rhs) const;
|
||||
};
|
||||
|
||||
struct IndirectJump {
|
||||
public:
|
||||
IndirectJump() = delete;
|
||||
IndirectJump(IndirectFlow taken_flow);
|
||||
|
||||
IndirectFlow taken_flow;
|
||||
|
||||
bool operator==(const IndirectJump &rhs) const;
|
||||
};
|
||||
|
||||
class DirectFunctionCall : public DirectJump {
|
||||
public:
|
||||
using DirectJump::DirectJump;
|
||||
|
||||
bool operator==(const DirectFunctionCall &rhs) const;
|
||||
};
|
||||
|
||||
|
||||
struct IndirectFunctionCall : public IndirectJump {
|
||||
public:
|
||||
using IndirectJump::IndirectJump;
|
||||
|
||||
bool operator==(const IndirectFunctionCall &rhs) const;
|
||||
};
|
||||
|
||||
struct FunctionReturn : public IndirectJump {
|
||||
public:
|
||||
using IndirectJump::IndirectJump;
|
||||
|
||||
bool operator==(const FunctionReturn &rhs) const;
|
||||
};
|
||||
|
||||
struct AsyncHyperCall {
|
||||
public:
|
||||
AsyncHyperCall() = default;
|
||||
|
||||
bool operator==(const AsyncHyperCall &rhs) const;
|
||||
};
|
||||
|
||||
using AbnormalFlow =
|
||||
std::variant<DirectFunctionCall, IndirectFunctionCall, FunctionReturn,
|
||||
AsyncHyperCall, IndirectJump, DirectJump>;
|
||||
|
||||
struct ConditionalInstruction {
|
||||
public:
|
||||
ConditionalInstruction() = delete;
|
||||
ConditionalInstruction(AbnormalFlow taken_branch,
|
||||
FallthroughFlow fall_through);
|
||||
|
||||
AbnormalFlow taken_branch;
|
||||
FallthroughFlow fall_through;
|
||||
|
||||
bool operator==(const ConditionalInstruction &rhs) const;
|
||||
};
|
||||
|
||||
|
||||
using InstructionFlowCategory =
|
||||
std::variant<NormalInsn, NoOp, InvalidInsn, ErrorInsn, DirectJump,
|
||||
IndirectJump, IndirectFunctionCall, DirectFunctionCall,
|
||||
FunctionReturn, AsyncHyperCall, ConditionalInstruction>;
|
||||
|
||||
InstructionFlowCategory flows;
|
||||
|
||||
std::vector<Operand> operands;
|
||||
|
||||
std::string Serialize(void) const;
|
||||
|
||||
@@ -18,27 +18,31 @@ class X86ArchBase : public virtual ArchBase {
|
||||
X86ArchBase(llvm::LLVMContext *context_, OSName os_name_, ArchName arch_name_)
|
||||
: ArchBase(context_, os_name_, arch_name_) {}
|
||||
|
||||
virtual std::string_view StackPointerRegisterName(void) const;
|
||||
virtual std::string_view StackPointerRegisterName(void) const override;
|
||||
|
||||
std::string_view ProgramCounterRegisterName(void) const;
|
||||
uint64_t MinInstructionAlign(void) const;
|
||||
std::string_view ProgramCounterRegisterName(void) const override;
|
||||
|
||||
uint64_t MinInstructionAlign(const DecodingContext &context) const override;
|
||||
|
||||
|
||||
uint64_t MinInstructionSize(void) const;
|
||||
uint64_t MinInstructionSize(const DecodingContext &context) const override;
|
||||
|
||||
uint64_t MaxInstructionSize(bool) const;
|
||||
llvm::CallingConv::ID DefaultCallingConv(void) const;
|
||||
uint64_t MaxInstructionSize(const DecodingContext &, bool) const override;
|
||||
|
||||
llvm::DataLayout DataLayout(void) const;
|
||||
llvm::CallingConv::ID DefaultCallingConv(void) const override;
|
||||
|
||||
llvm::Triple Triple(void) const;
|
||||
llvm::DataLayout DataLayout(void) const override;
|
||||
|
||||
llvm::Triple Triple(void) const override;
|
||||
|
||||
|
||||
void PopulateRegisterTable(void) const;
|
||||
void PopulateRegisterTable(void) const override;
|
||||
|
||||
// Populate a just-initialized lifted function function with architecture-
|
||||
// specific variables.
|
||||
void FinishLiftedFunctionInitialization(llvm::Module *module,
|
||||
llvm::Function *bb_func) const;
|
||||
void
|
||||
FinishLiftedFunctionInitialization(llvm::Module *module,
|
||||
llvm::Function *bb_func) const override;
|
||||
virtual ~X86ArchBase(void) = default;
|
||||
};
|
||||
} // namespace remill
|
||||
@@ -71,13 +71,27 @@ class OperandLifter {
|
||||
virtual void ClearCache(void) const = 0;
|
||||
};
|
||||
|
||||
class InstructionLifterIntf : public OperandLifter {
|
||||
public:
|
||||
using LifterPtr = std::shared_ptr<InstructionLifterIntf>;
|
||||
|
||||
// Lift a single instruction into a basic block. `is_delayed` signifies that
|
||||
// this instruction will execute within the delay slot of another instruction.
|
||||
virtual LiftStatus LiftIntoBlock(Instruction &inst, llvm::BasicBlock *block,
|
||||
llvm::Value *state_ptr,
|
||||
bool is_delayed = false) = 0;
|
||||
|
||||
// Lift a single instruction into a basic block. `is_delayed` signifies that
|
||||
// this instruction will execute within the delay slot of another instruction.
|
||||
LiftStatus LiftIntoBlock(Instruction &inst, llvm::BasicBlock *block,
|
||||
bool is_delayed = false);
|
||||
};
|
||||
|
||||
// Wraps the process of lifting an instruction into a block. This resolves
|
||||
// the intended instruction target to a function, and ensures that the function
|
||||
// is called with the appropriate arguments.
|
||||
class InstructionLifter : public OperandLifter {
|
||||
class InstructionLifter : public InstructionLifterIntf {
|
||||
public:
|
||||
using LifterPtr = std::shared_ptr<InstructionLifter>;
|
||||
|
||||
virtual ~InstructionLifter(void);
|
||||
|
||||
inline InstructionLifter(const std::unique_ptr<const Arch> &arch_,
|
||||
@@ -93,12 +107,8 @@ class InstructionLifter : public OperandLifter {
|
||||
// this instruction will execute within the delay slot of another instruction.
|
||||
virtual LiftStatus LiftIntoBlock(Instruction &inst, llvm::BasicBlock *block,
|
||||
llvm::Value *state_ptr,
|
||||
bool is_delayed = false);
|
||||
bool is_delayed = false) override;
|
||||
|
||||
// Lift a single instruction into a basic block. `is_delayed` signifies that
|
||||
// this instruction will execute within the delay slot of another instruction.
|
||||
LiftStatus LiftIntoBlock(Instruction &inst, llvm::BasicBlock *block,
|
||||
bool is_delayed = false);
|
||||
|
||||
// Load the address of a register.
|
||||
std::pair<llvm::Value *, llvm::Type *>
|
||||
|
||||
@@ -29,38 +29,87 @@ class Sleigh;
|
||||
|
||||
namespace remill {
|
||||
namespace sleigh {
|
||||
class SleighArch;
|
||||
// If you lift a varnode before the given pcode index, then you have a branch taken metavar
|
||||
struct BranchTakenVar {
|
||||
bool invert;
|
||||
VarnodeData target_vnode;
|
||||
size_t index;
|
||||
};
|
||||
|
||||
class SleighDecoder;
|
||||
class SingleInstructionSleighContext;
|
||||
} // namespace sleigh
|
||||
|
||||
|
||||
class SleighLifter : public InstructionLifter {
|
||||
private:
|
||||
class PcodeToLLVMEmitIntoBlock;
|
||||
|
||||
std::unique_ptr<sleigh::SingleInstructionSleighContext> sleigh_context;
|
||||
// Architecture being used for lifting.
|
||||
const sleigh::SleighArch *const arch;
|
||||
// Decoder being used for disassembly
|
||||
|
||||
const sleigh::SleighDecoder &decoder;
|
||||
|
||||
public:
|
||||
static const std::string_view kInstructionFunctionPrefix;
|
||||
|
||||
SleighLifter(const sleigh::SleighArch *arch_,
|
||||
SleighLifter(const remill::Arch &arch_,
|
||||
const remill::sleigh::SleighDecoder &dec_,
|
||||
const IntrinsicTable &intrinsics_);
|
||||
|
||||
virtual ~SleighLifter(void) = default;
|
||||
|
||||
LiftStatus LiftIntoBlock(Instruction &inst, llvm::BasicBlock *block,
|
||||
llvm::Value *state_ptr, bool is_delayed) override;
|
||||
LiftStatus LiftIntoBlockWithSleighState(
|
||||
Instruction &inst, llvm::BasicBlock *block, llvm::Value *state_ptr,
|
||||
bool is_delayed, const std::optional<sleigh::BranchTakenVar> &btaken);
|
||||
|
||||
private:
|
||||
static void SetISelAttributes(llvm::Function *);
|
||||
|
||||
std::pair<LiftStatus, llvm::Function *>
|
||||
LiftIntoInternalBlock(Instruction &inst, llvm::Module *target_mod,
|
||||
bool is_delayed);
|
||||
|
||||
llvm::Function *DefineInstructionFunction(Instruction &inst,
|
||||
llvm::Module *target_mod);
|
||||
|
||||
std::pair<LiftStatus, std::optional<llvm::Function *>>
|
||||
LiftIntoInternalBlockWithSleighState(
|
||||
Instruction &inst, llvm::Module *target_mod, bool is_delayed,
|
||||
const std::optional<sleigh::BranchTakenVar> &btaken);
|
||||
|
||||
::Sleigh &GetEngine(void) const;
|
||||
};
|
||||
|
||||
|
||||
// lets us attach state to a lifter that we need to carry on from when we decoded the instruction
|
||||
class SleighLifterWithState final : public InstructionLifterIntf {
|
||||
private:
|
||||
std::optional<sleigh::BranchTakenVar> btaken;
|
||||
std::shared_ptr<SleighLifter> lifter;
|
||||
|
||||
public:
|
||||
SleighLifterWithState(std::optional<sleigh::BranchTakenVar> btaken,
|
||||
std::shared_ptr<SleighLifter> lifter_);
|
||||
|
||||
// Lift a single instruction into a basic block. `is_delayed` signifies that
|
||||
// this instruction will execute within the delay slot of another instruction.
|
||||
virtual LiftStatus LiftIntoBlock(Instruction &inst, llvm::BasicBlock *block,
|
||||
llvm::Value *state_ptr,
|
||||
bool is_delayed = false) override;
|
||||
|
||||
|
||||
// Load the address of a register.
|
||||
virtual std::pair<llvm::Value *, llvm::Type *>
|
||||
LoadRegAddress(llvm::BasicBlock *block, llvm::Value *state_ptr,
|
||||
std::string_view reg_name) const override;
|
||||
|
||||
// Load the value of a register.
|
||||
virtual llvm::Value *LoadRegValue(llvm::BasicBlock *block,
|
||||
llvm::Value *state_ptr,
|
||||
std::string_view reg_name) const override;
|
||||
|
||||
virtual llvm::Type *GetMemoryType() override;
|
||||
|
||||
virtual void ClearCache(void) const override;
|
||||
};
|
||||
|
||||
} // namespace remill
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <llvm/IR/Function.h>
|
||||
#include <llvm/IR/IRBuilder.h>
|
||||
#include <llvm/IR/Module.h>
|
||||
#include <remill/Arch/AArch32/ArchContext.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
@@ -36,7 +37,6 @@
|
||||
#include "remill/BC/Util.h"
|
||||
#include "remill/BC/Version.h"
|
||||
#include "remill/OS/OS.h"
|
||||
|
||||
// clang-format off
|
||||
#define ADDRESS_SIZE 32
|
||||
#include "remill/Arch/AArch32/Runtime/State.h"
|
||||
@@ -51,15 +51,41 @@ AArch32Arch::AArch32Arch(llvm::LLVMContext *context_, OSName os_name_,
|
||||
ArchName arch_name_)
|
||||
: ArchBase(context_, os_name_, arch_name_),
|
||||
AArch32ArchBase(context_, os_name_, arch_name_),
|
||||
DefaultContextAndLifter(context_, os_name_, arch_name_) {}
|
||||
thumb_decoder(*this) {}
|
||||
|
||||
AArch32Arch::~AArch32Arch(void) {}
|
||||
|
||||
|
||||
// TODO(pag): We pretend that these are singletons, but they aren't really!
|
||||
Arch::ArchPtr Arch::GetAArch32(llvm::LLVMContext *context_, OSName os_name_,
|
||||
ArchName arch_name_) {
|
||||
return std::make_unique<AArch32Arch>(context_, os_name_, arch_name_);
|
||||
}
|
||||
|
||||
|
||||
// TODO(pag): Eventually handle Thumb2 and unaligned addresses.
|
||||
uint64_t AArch32Arch::MinInstructionAlign(const DecodingContext &cont) const {
|
||||
return IsThumb(cont) ? 2 : 4;
|
||||
}
|
||||
|
||||
uint64_t AArch32Arch::MinInstructionSize(const DecodingContext &cont) const {
|
||||
return IsThumb(cont) ? 2 : 4;
|
||||
}
|
||||
|
||||
// Maximum number of bytes in an instruction for this particular architecture.
|
||||
uint64_t AArch32Arch::MaxInstructionSize(const DecodingContext &, bool) const {
|
||||
return 4;
|
||||
}
|
||||
|
||||
|
||||
bool AArch32Arch::DecodeThumb(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const {
|
||||
return this->thumb_decoder.DecodeInstruction(address, instr_bytes, inst,
|
||||
context);
|
||||
}
|
||||
|
||||
bool AArch32Arch::IsThumb(const DecodingContext &context) {
|
||||
return context.GetContextValue(std::string(kThumbModeRegName));
|
||||
}
|
||||
|
||||
} // namespace remill
|
||||
|
||||
+32
-4
@@ -16,21 +16,49 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <lib/Arch/Sleigh/Thumb.h>
|
||||
#include <remill/Arch/AArch32/AArch32Base.h>
|
||||
|
||||
namespace remill {
|
||||
class AArch32Arch final : public AArch32ArchBase,
|
||||
public DefaultContextAndLifter {
|
||||
class AArch32Arch final : public AArch32ArchBase {
|
||||
public:
|
||||
AArch32Arch(llvm::LLVMContext *context_, OSName os_name_,
|
||||
ArchName arch_name_);
|
||||
|
||||
virtual ~AArch32Arch(void);
|
||||
|
||||
bool ArchDecodeInstruction(uint64_t address, std::string_view inst_bytes,
|
||||
Instruction &inst) const override;
|
||||
|
||||
virtual DecodingContext CreateInitialContext(void) const override;
|
||||
|
||||
bool DecodeInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const override;
|
||||
|
||||
|
||||
OperandLifter::OpLifterPtr
|
||||
DefaultLifter(const remill::IntrinsicTable &intrinsics) const override;
|
||||
|
||||
// TODO(pag): Eventually handle Thumb2 and unaligned addresses.
|
||||
uint64_t MinInstructionAlign(const DecodingContext &) const override;
|
||||
|
||||
uint64_t MinInstructionSize(const DecodingContext &) const override;
|
||||
|
||||
// Maximum number of bytes in an instruction for this particular architecture.
|
||||
uint64_t MaxInstructionSize(const DecodingContext &, bool) const override;
|
||||
|
||||
|
||||
private:
|
||||
static bool IsThumb(const DecodingContext &context);
|
||||
|
||||
sleighthumb2::SleighThumb2Decoder thumb_decoder;
|
||||
bool DecodeAArch32(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst, DecodingContext context) const;
|
||||
|
||||
|
||||
bool DecodeThumb(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst, DecodingContext context) const;
|
||||
|
||||
|
||||
AArch32Arch(void) = delete;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,22 +12,6 @@
|
||||
#include <remill/OS/OS.h>
|
||||
|
||||
namespace remill {
|
||||
|
||||
|
||||
// TODO(pag): Eventually handle Thumb2 and unaligned addresses.
|
||||
uint64_t AArch32ArchBase::MinInstructionAlign(void) const {
|
||||
return 4;
|
||||
}
|
||||
|
||||
uint64_t AArch32ArchBase::MinInstructionSize(void) const {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Maximum number of bytes in an instruction for this particular architecture.
|
||||
uint64_t AArch32ArchBase::MaxInstructionSize(bool) const {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Default calling convention for this architecture.
|
||||
llvm::CallingConv::ID AArch32ArchBase::DefaultCallingConv(void) const {
|
||||
return llvm::CallingConv::C; // cdecl.
|
||||
@@ -38,6 +22,7 @@ llvm::Triple AArch32ArchBase::Triple(void) const {
|
||||
auto triple = BasicTriple();
|
||||
switch (arch_name) {
|
||||
case kArchAArch32LittleEndian: triple.setArch(llvm::Triple::arm); break;
|
||||
case kArchThumb2LittleEndian: triple.setArch(llvm::Triple::thumb); break;
|
||||
default:
|
||||
LOG(FATAL) << "Cannot get triple for non-aarch32 architecture "
|
||||
<< GetArchName(arch_name);
|
||||
|
||||
@@ -24,6 +24,7 @@ add_library(remill_arch_aarch32 STATIC
|
||||
"${REMILL_INCLUDE_DIR}/remill/Arch/AArch32/Runtime/Operators.h"
|
||||
"${REMILL_INCLUDE_DIR}/remill/Arch/AArch32/Runtime/State.h"
|
||||
"${REMILL_INCLUDE_DIR}/remill/Arch/AArch32/Runtime/Types.h"
|
||||
"${REMILL_INCLUDE_DIR}/remill/Arch/AArch32/ArchContext.h"
|
||||
|
||||
Arch.cpp
|
||||
Decode.cpp
|
||||
@@ -41,6 +42,8 @@ target_link_libraries(remill_arch_aarch32 LINK_PUBLIC
|
||||
remill_settings
|
||||
)
|
||||
|
||||
target_include_directories(remill_arch_aarch32 AFTER PRIVATE "${REMILL_SOURCE_DIR}")
|
||||
|
||||
if(REMILL_ENABLE_INSTALL_TARGET)
|
||||
install(
|
||||
TARGETS remill_arch_aarch32
|
||||
|
||||
+146
-8
@@ -15,18 +15,31 @@
|
||||
*/
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include <remill/Arch/AArch32/ArchContext.h>
|
||||
#include <remill/Arch/Name.h>
|
||||
#include <remill/BC/ABI.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "../BitManipulation.h"
|
||||
#include "../Sleigh/Thumb.h"
|
||||
#include "Arch.h"
|
||||
|
||||
namespace remill {
|
||||
|
||||
namespace {
|
||||
|
||||
Instruction::DirectJump GetDirectJumpFromInst(const remill::Instruction &insn) {
|
||||
CHECK(insn.category == Instruction::Category::kCategoryDirectJump ||
|
||||
insn.category == Instruction::Category::kCategoryConditionalBranch);
|
||||
|
||||
auto taken_context = insn.branch_taken_pc % 2 ? kThumbContext : kARMContext;
|
||||
auto taken_addr = insn.branch_taken_pc % 2 ? insn.branch_taken_pc - 1
|
||||
: insn.branch_taken_pc;
|
||||
return Instruction::DirectJump(
|
||||
Instruction::DirectFlow(taken_addr, std::move(taken_context)));
|
||||
}
|
||||
|
||||
// Integer Data Processing (three register, register shift)
|
||||
union IntDataProcessingRRRR {
|
||||
uint32_t flat;
|
||||
@@ -1207,7 +1220,10 @@ static bool EvalPCDest(Instruction &inst, const bool s, const unsigned int rd,
|
||||
inst.category = Instruction::kCategoryFunctionReturn;
|
||||
}
|
||||
} else if (!src1 || !src2) {
|
||||
inst.category = Instruction::kCategoryIndirectJump;
|
||||
inst.branch_taken_arch_name = std::nullopt;
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
inst.category = is_cond ? Instruction::kCategoryConditionalIndirectJump
|
||||
: Instruction::kCategoryIndirectJump;
|
||||
} else {
|
||||
auto res = evaluator(*src1, *src2);
|
||||
if (!res) {
|
||||
@@ -1215,22 +1231,34 @@ static bool EvalPCDest(Instruction &inst, const bool s, const unsigned int rd,
|
||||
if (is_cond) {
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
inst.category = Instruction::kCategoryConditionalIndirectJump;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::IndirectJump(
|
||||
Instruction::IndirectFlow(std::nullopt)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else {
|
||||
inst.branch_not_taken_pc = 0;
|
||||
inst.branch_taken_arch_name = std::nullopt;
|
||||
inst.category = Instruction::kCategoryIndirectJump;
|
||||
inst.flows = Instruction::IndirectJump(
|
||||
Instruction::IndirectFlow(std::nullopt));
|
||||
}
|
||||
} else if (is_cond) {
|
||||
inst.branch_taken_pc = static_cast<uint64_t>(*res);
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
inst.category = Instruction::kCategoryConditionalBranch;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
GetDirectJumpFromInst(inst),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else {
|
||||
inst.branch_taken_pc = static_cast<uint64_t>(*res);
|
||||
inst.branch_not_taken_pc = 0;
|
||||
inst.category = Instruction::kCategoryDirectJump;
|
||||
inst.flows = GetDirectJumpFromInst(inst);
|
||||
}
|
||||
if (inst.branch_taken_pc % 2u) {
|
||||
inst.branch_taken_arch_name = ArchName::kArchThumb2LittleEndian;
|
||||
inst.branch_taken_pc -= 1u;
|
||||
|
||||
} else {
|
||||
inst.branch_taken_arch_name = inst.arch_name;
|
||||
}
|
||||
@@ -1779,9 +1807,15 @@ static bool TryDecodeLoadStoreWordUBIL(Instruction &inst, uint32_t bits) {
|
||||
if (is_cond) {
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
inst.category = Instruction::kCategoryConditionalIndirectJump;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else {
|
||||
inst.category = Instruction::kCategoryIndirectJump;
|
||||
inst.flows =
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt));
|
||||
}
|
||||
inst.branch_taken_arch_name = std::nullopt;
|
||||
} else {
|
||||
|
||||
// Add operand to ignore any updates of the next pc if done by semantic
|
||||
@@ -1889,8 +1923,13 @@ static bool TryDecodeLoadStoreWordUBReg(Instruction &inst, uint32_t bits) {
|
||||
if (is_cond) {
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
inst.category = Instruction::kCategoryConditionalIndirectJump;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else {
|
||||
inst.category = Instruction::kCategoryIndirectJump;
|
||||
inst.flows =
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2065,8 +2104,13 @@ static bool TryDecodeLoadStoreDualHalfSignedBIL(Instruction &inst,
|
||||
if (is_cond) {
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
inst.category = Instruction::kCategoryConditionalIndirectJump;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else {
|
||||
inst.category = Instruction::kCategoryIndirectJump;
|
||||
inst.flows =
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2224,8 +2268,13 @@ static bool TryDecodeLoadStoreDualHalfSignedBReg(Instruction &inst,
|
||||
if (is_cond) {
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
inst.category = Instruction::kCategoryConditionalIndirectJump;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else {
|
||||
inst.category = Instruction::kCategoryIndirectJump;
|
||||
inst.flows =
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2366,8 +2415,13 @@ static bool TryDecodeLoadStoreMultiple(Instruction &inst, uint32_t bits) {
|
||||
if (is_cond) {
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
inst.category = Instruction::kCategoryConditionalIndirectJump;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else {
|
||||
inst.category = Instruction::kCategoryIndirectJump;
|
||||
inst.flows =
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2724,6 +2778,8 @@ static bool TryBranchImm(Instruction &inst, uint32_t bits) {
|
||||
// PC used by the branch instruction is actually the address of the next instruction
|
||||
auto target_pc = static_cast<uint32_t>(inst.pc + 8 +
|
||||
static_cast<uint32_t>(enc.imm24 << 2));
|
||||
|
||||
DecodingContext taken_context = kARMContext;
|
||||
if (enc.cond != 0b1111) {
|
||||
if (!enc.H) {
|
||||
target_pc = target_pc & ~0b11u;
|
||||
@@ -2743,6 +2799,7 @@ static bool TryBranchImm(Instruction &inst, uint32_t bits) {
|
||||
is_func = true;
|
||||
|
||||
inst.branch_taken_arch_name = remill::ArchName::kArchThumb2LittleEndian;
|
||||
taken_context = kThumbContext;
|
||||
}
|
||||
if (is_cond) {
|
||||
inst.function += "COND";
|
||||
@@ -2754,14 +2811,28 @@ static bool TryBranchImm(Instruction &inst, uint32_t bits) {
|
||||
|
||||
inst.branch_taken_pc = target_pc;
|
||||
inst.branch_not_taken_pc = inst.pc + 4;
|
||||
|
||||
|
||||
auto btaken_flow =
|
||||
Instruction::DirectFlow(inst.branch_taken_pc, taken_context);
|
||||
|
||||
if (is_cond && is_func) {
|
||||
inst.category = Instruction::kCategoryConditionalDirectFunctionCall;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::DirectFunctionCall(std::move(btaken_flow)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else if (is_cond) {
|
||||
inst.category = Instruction::kCategoryConditionalBranch;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::DirectJump(std::move(btaken_flow)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else if (is_func) {
|
||||
inst.category = Instruction::kCategoryDirectFunctionCall;
|
||||
inst.flows = Instruction::DirectFunctionCall(std::move(btaken_flow));
|
||||
} else {
|
||||
inst.category = Instruction::kCategoryDirectJump;
|
||||
inst.flows = Instruction::DirectJump(
|
||||
Instruction::DirectFlow(std::move(btaken_flow)));
|
||||
}
|
||||
AddAddrRegOp(inst, kNextPCVariableName.data(), kAddressSize,
|
||||
Operand::kActionRead, 0);
|
||||
@@ -2812,25 +2883,41 @@ static bool TryDecodeBX(Instruction &inst, uint32_t bits) {
|
||||
AddAddrRegOp(inst, kIntRegName[enc.Rm], kAddressSize, Operand::kActionRead,
|
||||
0);
|
||||
|
||||
inst.branch_taken_arch_name = inst.arch_name;
|
||||
inst.branch_taken_arch_name = std::nullopt;
|
||||
inst.branch_not_taken_pc = inst.pc + 4;
|
||||
if (enc.op1 == 0b01) {
|
||||
if (is_cond && (enc.Rm == kLRRegNum)) {
|
||||
inst.category = Instruction::kCategoryConditionalFunctionReturn;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::FunctionReturn(Instruction::IndirectFlow(std::nullopt)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else if (enc.Rm == kLRRegNum) {
|
||||
inst.category = Instruction::kCategoryFunctionReturn;
|
||||
inst.flows =
|
||||
Instruction::FunctionReturn(Instruction::IndirectFlow(std::nullopt));
|
||||
} else if (is_cond) {
|
||||
inst.category = Instruction::kCategoryConditionalIndirectJump;
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else {
|
||||
inst.category = Instruction::kCategoryIndirectJump;
|
||||
inst.flows =
|
||||
Instruction::IndirectJump(Instruction::IndirectFlow(std::nullopt));
|
||||
}
|
||||
// BX destination is allowed to be the PC
|
||||
if (enc.Rm == kPCRegNum) {
|
||||
inst.branch_taken_pc = inst.pc + 4;
|
||||
}
|
||||
} else if (is_cond) {
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::IndirectFunctionCall(
|
||||
Instruction::IndirectFlow(std::nullopt)),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
inst.category = Instruction::kCategoryConditionalIndirectFunctionCall;
|
||||
} else {
|
||||
inst.flows = Instruction::IndirectFunctionCall(
|
||||
Instruction::IndirectFlow(std::nullopt));
|
||||
inst.category = Instruction::kCategoryIndirectFunctionCall;
|
||||
}
|
||||
|
||||
@@ -3653,11 +3740,15 @@ static uint32_t BytesToBits(const uint8_t *bytes) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Decode an instruction
|
||||
bool AArch32Arch::ArchDecodeInstruction(uint64_t address,
|
||||
std::string_view inst_bytes,
|
||||
Instruction &inst) const {
|
||||
|
||||
DecodingContext AArch32Arch::CreateInitialContext(void) const {
|
||||
return DecodingContext().PutContextReg(std::string(kThumbModeRegName), 0);
|
||||
}
|
||||
|
||||
bool AArch32Arch::DecodeInstruction(uint64_t address,
|
||||
std::string_view inst_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const {
|
||||
inst.pc = address;
|
||||
inst.next_pc = address + inst_bytes.size(); // Default fall-through.
|
||||
inst.branch_taken_pc = 0;
|
||||
@@ -3665,11 +3756,37 @@ bool AArch32Arch::ArchDecodeInstruction(uint64_t address,
|
||||
inst.has_branch_taken_delay_slot = false;
|
||||
inst.has_branch_not_taken_delay_slot = false;
|
||||
inst.arch_name = arch_name;
|
||||
inst.sub_arch_name = arch_name; // TODO(pag): Thumb.
|
||||
inst.sub_arch_name = arch_name;
|
||||
inst.branch_taken_arch_name = arch_name;
|
||||
inst.arch = this;
|
||||
inst.category = Instruction::kCategoryInvalid;
|
||||
inst.operands.clear();
|
||||
inst.flows = Instruction::InvalidInsn();
|
||||
|
||||
if (!context.HasValueForReg(std::string(kThumbModeRegName))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (AArch32Arch::IsThumb(context)) {
|
||||
return this->DecodeThumb(address, inst_bytes, inst, std::move(context));
|
||||
} else {
|
||||
return this->DecodeAArch32(address, inst_bytes, inst, std::move(context));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OperandLifter::OpLifterPtr AArch32Arch::DefaultLifter(
|
||||
const remill::IntrinsicTable &intrinsics_table) const {
|
||||
return std::make_shared<InstructionLifter>(this, intrinsics_table);
|
||||
}
|
||||
|
||||
bool AArch32Arch::DecodeAArch32(uint64_t address, std::string_view inst_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const {
|
||||
|
||||
inst.SetLifter(
|
||||
std::make_shared<InstructionLifter>(this, this->instrinsics.get()));
|
||||
|
||||
if (4ull > inst_bytes.size()) {
|
||||
return false;
|
||||
@@ -3697,7 +3814,28 @@ bool AArch32Arch::ArchDecodeInstruction(uint64_t address,
|
||||
auto ret = decoder(inst, bits);
|
||||
|
||||
// LOG(ERROR) << inst.Serialize();
|
||||
return ret;
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (inst.category == Instruction::Category::kCategoryNormal) {
|
||||
inst.flows =
|
||||
Instruction::NormalInsn(Instruction::FallthroughFlow(kARMContext));
|
||||
} else if (inst.category == Instruction::Category::kCategoryNoOp) {
|
||||
inst.flows = Instruction::NoOp(Instruction::FallthroughFlow(kARMContext));
|
||||
} else if (inst.category == Instruction::Category::kCategoryAsyncHyperCall) {
|
||||
inst.flows = Instruction::AsyncHyperCall();
|
||||
} else if (inst.category ==
|
||||
Instruction::Category::kCategoryConditionalAsyncHyperCall) {
|
||||
inst.flows = Instruction::ConditionalInstruction(
|
||||
Instruction::AsyncHyperCall(),
|
||||
Instruction::FallthroughFlow(kARMContext));
|
||||
} else if (inst.category == Instruction::Category::kCategoryError) {
|
||||
inst.flows = Instruction::ErrorInsn();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace remill
|
||||
|
||||
@@ -124,9 +124,10 @@ class AArch64Arch final : public DefaultContextAndLifter {
|
||||
Instruction &inst) const final;
|
||||
|
||||
// Align/Minimum/Maximum number of bytes in an instruction.
|
||||
uint64_t MinInstructionAlign(void) const final;
|
||||
uint64_t MinInstructionSize(void) const final;
|
||||
uint64_t MaxInstructionSize(bool permit_fuse_idioms) const final;
|
||||
uint64_t MinInstructionAlign(const DecodingContext &) const final;
|
||||
uint64_t MinInstructionSize(const DecodingContext &) const final;
|
||||
uint64_t MaxInstructionSize(const DecodingContext &,
|
||||
bool permit_fuse_idioms) const final;
|
||||
|
||||
llvm::Triple Triple(void) const final;
|
||||
llvm::DataLayout DataLayout(void) const final;
|
||||
@@ -492,16 +493,16 @@ void AArch64Arch::FinishLiftedFunctionInitialization(
|
||||
}
|
||||
|
||||
// TODO(pag): Eventually handle Thumb2 and unaligned addresses.
|
||||
uint64_t AArch64Arch::MinInstructionAlign(void) const {
|
||||
uint64_t AArch64Arch::MinInstructionAlign(const DecodingContext &) const {
|
||||
return 4;
|
||||
}
|
||||
|
||||
uint64_t AArch64Arch::MinInstructionSize(void) const {
|
||||
uint64_t AArch64Arch::MinInstructionSize(const DecodingContext &) const {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Maximum number of bytes in an instruction for this particular architecture.
|
||||
uint64_t AArch64Arch::MaxInstructionSize(bool) const {
|
||||
uint64_t AArch64Arch::MaxInstructionSize(const DecodingContext &, bool) const {
|
||||
return 4;
|
||||
}
|
||||
|
||||
|
||||
+74
-6
@@ -856,16 +856,84 @@ DecodingContext DefaultContextAndLifter::CreateInitialContext(void) const {
|
||||
return DecodingContext();
|
||||
}
|
||||
|
||||
Arch::DecodingResult DefaultContextAndLifter::DecodeInstruction(
|
||||
uint64_t address, std::string_view instr_bytes, Instruction &inst,
|
||||
DecodingContext context) const {
|
||||
|
||||
Instruction::FallthroughFlow DefaultContextAndLifter::GetFallthrough() const {
|
||||
return Instruction::FallthroughFlow(this->CreateInitialContext());
|
||||
}
|
||||
|
||||
Instruction::DirectFlow
|
||||
DefaultContextAndLifter::GetDirectFlow(uint64_t target) const {
|
||||
return Instruction::DirectFlow(target, this->CreateInitialContext());
|
||||
}
|
||||
|
||||
Instruction::IndirectFlow DefaultContextAndLifter::GetIndirectFlow() const {
|
||||
return Instruction::IndirectFlow(this->CreateInitialContext());
|
||||
}
|
||||
|
||||
Instruction::InstructionFlowCategory
|
||||
DefaultContextAndLifter::FillInFlowFromCategoryAndDefaultContext(
|
||||
const remill::Instruction &inst) const {
|
||||
switch (inst.category) {
|
||||
case Instruction::Category::kCategoryNormal:
|
||||
return Instruction::NormalInsn(this->GetFallthrough());
|
||||
case Instruction::Category::kCategoryAsyncHyperCall:
|
||||
return Instruction::AsyncHyperCall();
|
||||
case Instruction::Category::kCategoryConditionalAsyncHyperCall:
|
||||
return Instruction::ConditionalInstruction(Instruction::AsyncHyperCall(),
|
||||
this->GetFallthrough());
|
||||
case Instruction::Category::kCategoryConditionalBranch:
|
||||
return Instruction::ConditionalInstruction(
|
||||
Instruction::DirectJump(this->GetDirectFlow(inst.branch_taken_pc)),
|
||||
this->GetFallthrough());
|
||||
case Instruction::Category::kCategoryDirectJump:
|
||||
return Instruction::DirectJump(this->GetDirectFlow(inst.branch_taken_pc));
|
||||
case Instruction::Category::kCategoryIndirectJump:
|
||||
return Instruction::IndirectJump(this->GetIndirectFlow());
|
||||
case Instruction::Category::kCategoryConditionalIndirectJump:
|
||||
return Instruction::ConditionalInstruction(
|
||||
Instruction::IndirectJump(this->GetIndirectFlow()),
|
||||
this->GetFallthrough());
|
||||
case Instruction::Category::kCategoryError: return Instruction::ErrorInsn();
|
||||
case Instruction::Category::kCategoryFunctionReturn:
|
||||
return Instruction::FunctionReturn(this->GetIndirectFlow());
|
||||
case Instruction::Category::kCategoryConditionalFunctionReturn:
|
||||
return Instruction::ConditionalInstruction(
|
||||
Instruction::FunctionReturn(this->GetIndirectFlow()),
|
||||
this->GetFallthrough());
|
||||
case Instruction::Category::kCategoryConditionalDirectFunctionCall:
|
||||
return Instruction::ConditionalInstruction(
|
||||
Instruction::DirectFunctionCall(
|
||||
this->GetDirectFlow(inst.branch_taken_pc)),
|
||||
this->GetFallthrough());
|
||||
case Instruction::Category::kCategoryDirectFunctionCall:
|
||||
return Instruction::DirectFunctionCall(
|
||||
this->GetDirectFlow(inst.branch_taken_pc));
|
||||
case Instruction::Category::kCategoryIndirectFunctionCall:
|
||||
return Instruction::IndirectFunctionCall(this->GetIndirectFlow());
|
||||
case Instruction::Category::kCategoryConditionalIndirectFunctionCall:
|
||||
return Instruction::ConditionalInstruction(
|
||||
Instruction::IndirectFunctionCall(this->GetIndirectFlow()),
|
||||
this->GetFallthrough());
|
||||
case Instruction::Category::kCategoryInvalid:
|
||||
return Instruction::InvalidInsn();
|
||||
case Instruction::Category::kCategoryNoOp:
|
||||
return Instruction::NoOp(this->GetFallthrough());
|
||||
}
|
||||
}
|
||||
|
||||
bool DefaultContextAndLifter::DecodeInstruction(uint64_t address,
|
||||
std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const {
|
||||
inst.SetLifter(std::make_unique<remill::InstructionLifter>(
|
||||
this, this->GetInstrinsicTable()));
|
||||
if (this->ArchDecodeInstruction(address, instr_bytes, inst)) {
|
||||
return [](uint64_t) -> DecodingContext { return DecodingContext(); };
|
||||
|
||||
auto res = this->ArchDecodeInstruction(address, instr_bytes, inst);
|
||||
if (res) {
|
||||
inst.flows = this->FillInFlowFromCategoryAndDefaultContext(inst);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+26
-9
@@ -4,33 +4,50 @@
|
||||
|
||||
namespace remill {
|
||||
|
||||
DecodingContext::DecodingContext(
|
||||
std::unordered_map<std::string, uint64_t> context_value)
|
||||
|
||||
bool DecodingContext::operator==(remill::DecodingContext const &rhs) const {
|
||||
return this->context_value == rhs.context_value;
|
||||
}
|
||||
|
||||
DecodingContext::DecodingContext(std::map<std::string, uint64_t> context_value)
|
||||
: context_value(std::move(context_value)) {}
|
||||
|
||||
|
||||
uint64_t
|
||||
DecodingContext::GetContextValue(const std::string &context_reg) const {
|
||||
|
||||
if (auto res = this->context_value.find(context_reg);
|
||||
res != this->context_value.end()) {
|
||||
return res->second;
|
||||
}
|
||||
|
||||
LOG(FATAL) << "No context value for " << context_reg
|
||||
<< " but it is required for decoding";
|
||||
LOG(FATAL) << "Required context reg value for: " << context_reg;
|
||||
}
|
||||
|
||||
DecodingContext DecodingContext::PutContextReg(std::string creg,
|
||||
uint64_t value) const {
|
||||
std::unordered_map<std::string, uint64_t> new_value(this->context_value);
|
||||
std::map<std::string, uint64_t> new_value(this->context_value);
|
||||
new_value.emplace(creg, value);
|
||||
return DecodingContext(std::move(new_value));
|
||||
}
|
||||
|
||||
DecodingContext::ContextMap
|
||||
DecodingContext::UniformContextMapping(DecodingContext cst) {
|
||||
return [cst = std::move(cst)](uint64_t) -> DecodingContext { return cst; };
|
||||
void DecodingContext::UpdateContextReg(std::string creg, uint64_t value) {
|
||||
this->context_value[creg] = value;
|
||||
}
|
||||
|
||||
void DecodingContext::DropReg(const std::string &creg) {
|
||||
this->context_value.erase(creg);
|
||||
}
|
||||
|
||||
bool DecodingContext::HasValueForReg(const std::string &creg) const {
|
||||
return this->context_value.find(creg) != this->context_value.end();
|
||||
}
|
||||
|
||||
|
||||
DecodingContext
|
||||
DecodingContext::ContextWithoutRegister(const std::string &creg) const {
|
||||
DecodingContext cpy = *this;
|
||||
cpy.DropReg(creg);
|
||||
return cpy;
|
||||
}
|
||||
|
||||
} // namespace remill
|
||||
+105
-3
@@ -316,7 +316,8 @@ Instruction::Instruction(void)
|
||||
has_branch_taken_delay_slot(false),
|
||||
has_branch_not_taken_delay_slot(false),
|
||||
in_delay_slot(false),
|
||||
category(Instruction::kCategoryInvalid) {}
|
||||
category(Instruction::kCategoryInvalid),
|
||||
flows(Instruction::InvalidInsn()) {}
|
||||
|
||||
void Instruction::Reset(void) {
|
||||
pc = 0;
|
||||
@@ -673,9 +674,9 @@ std::string Instruction::Serialize(void) const {
|
||||
};
|
||||
|
||||
auto maybe_stream_branch_taken_arch = [this, &ss, &stream_arch]() {
|
||||
if (branch_taken_arch_name != arch_name) {
|
||||
if (branch_taken_arch_name && *branch_taken_arch_name != arch_name) {
|
||||
ss << ':';
|
||||
stream_arch(branch_taken_arch_name);
|
||||
stream_arch(*branch_taken_arch_name);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -801,4 +802,105 @@ void Instruction::SetLifter(InstructionLifter::LifterPtr lifter_) {
|
||||
lifter.swap(lifter_);
|
||||
}
|
||||
|
||||
Instruction::DirectFlow::DirectFlow(uint64_t known_target_,
|
||||
DecodingContext static_context_)
|
||||
: known_target(known_target_),
|
||||
static_context(std::move(static_context_)) {}
|
||||
|
||||
Instruction::IndirectFlow::IndirectFlow(
|
||||
std::optional<DecodingContext> maybe_context_)
|
||||
: maybe_context(std::move(maybe_context_)) {}
|
||||
|
||||
|
||||
Instruction::FallthroughFlow::FallthroughFlow(
|
||||
DecodingContext fallthrough_context_)
|
||||
: fallthrough_context(std::move(fallthrough_context_)) {}
|
||||
|
||||
|
||||
Instruction::NormalInsn::NormalInsn(FallthroughFlow fallthrough_)
|
||||
: fallthrough(std::move(fallthrough_)) {}
|
||||
|
||||
Instruction::DirectJump::DirectJump(DirectFlow taken_flow_)
|
||||
: taken_flow(std::move(taken_flow_)) {}
|
||||
|
||||
Instruction::IndirectJump::IndirectJump(IndirectFlow taken_flow_)
|
||||
: taken_flow(std::move(taken_flow_)) {}
|
||||
|
||||
Instruction::ConditionalInstruction::ConditionalInstruction(
|
||||
AbnormalFlow taken_branch_, FallthroughFlow fall_through_)
|
||||
: taken_branch(std::move(taken_branch_)),
|
||||
fall_through(std::move(fall_through_)) {}
|
||||
|
||||
// TODO(Ian): When we bump remill to C++20 we can replace all of these comparisons with =default.
|
||||
bool Instruction::DirectJump::operator==(const DirectJump &rhs) const {
|
||||
return this->taken_flow == rhs.taken_flow;
|
||||
}
|
||||
|
||||
bool Instruction::DirectFlow::operator==(
|
||||
remill::Instruction::DirectFlow const &rhs) const {
|
||||
return this->known_target == rhs.known_target &&
|
||||
this->static_context == rhs.static_context;
|
||||
}
|
||||
|
||||
bool Instruction::NormalInsn::operator==(
|
||||
remill::Instruction::NormalInsn const &rhs) const {
|
||||
return this->fallthrough == rhs.fallthrough;
|
||||
}
|
||||
|
||||
bool Instruction::InvalidInsn::operator==(
|
||||
remill::Instruction::InvalidInsn const &invalid) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Instruction::IndirectJump::operator==(
|
||||
remill::Instruction::IndirectJump const &rhs) const {
|
||||
return this->taken_flow == rhs.taken_flow;
|
||||
}
|
||||
|
||||
bool Instruction::AsyncHyperCall::operator==(
|
||||
remill::Instruction::AsyncHyperCall const &rhs) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Instruction::FunctionReturn::operator==(
|
||||
remill::Instruction::FunctionReturn const &rhs) const {
|
||||
return Instruction::IndirectJump::operator==(rhs);
|
||||
}
|
||||
|
||||
bool Instruction::FallthroughFlow::operator==(
|
||||
remill::Instruction::FallthroughFlow const &rhs) const {
|
||||
return this->fallthrough_context == rhs.fallthrough_context;
|
||||
}
|
||||
|
||||
bool Instruction::DirectFunctionCall::operator==(
|
||||
remill::Instruction::DirectFunctionCall const &rhs) const {
|
||||
return Instruction::DirectJump::operator==(rhs);
|
||||
}
|
||||
|
||||
bool Instruction::ConditionalInstruction::operator==(
|
||||
remill::Instruction::ConditionalInstruction const &rhs) const {
|
||||
return this->fall_through == rhs.fall_through &&
|
||||
this->taken_branch == rhs.taken_branch;
|
||||
}
|
||||
|
||||
bool Instruction::IndirectFlow::operator==(
|
||||
remill::Instruction::IndirectFlow const &rhs) const {
|
||||
return this->maybe_context == rhs.maybe_context;
|
||||
}
|
||||
|
||||
bool Instruction::IndirectFunctionCall::operator==(
|
||||
remill::Instruction::IndirectFunctionCall const &rhs) const {
|
||||
return Instruction::IndirectJump::operator==(rhs);
|
||||
}
|
||||
|
||||
bool Instruction::ErrorInsn::operator==(
|
||||
remill::Instruction::ErrorInsn const &) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Instruction::NoOp::operator==(const NoOp &rhs) const {
|
||||
return this->fallthrough == rhs.fallthrough;
|
||||
}
|
||||
|
||||
} // namespace remill
|
||||
|
||||
@@ -148,16 +148,17 @@ class SPARC32Arch final : public DefaultContextAndLifter {
|
||||
return kPCRegName;
|
||||
}
|
||||
|
||||
uint64_t MinInstructionAlign(void) const final {
|
||||
uint64_t MinInstructionAlign(const DecodingContext &) const final {
|
||||
return 4;
|
||||
}
|
||||
|
||||
uint64_t MinInstructionSize(void) const final {
|
||||
uint64_t MinInstructionSize(const DecodingContext &) const final {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Maximum number of bytes in an instruction.
|
||||
uint64_t MaxInstructionSize(bool permit_fuse_idioms) const final {
|
||||
uint64_t MaxInstructionSize(const DecodingContext &,
|
||||
bool permit_fuse_idioms) const final {
|
||||
return permit_fuse_idioms ? 8 : 4; // To handle `SET` idioms.
|
||||
}
|
||||
|
||||
|
||||
@@ -56,16 +56,17 @@ class SPARC64Arch final : public DefaultContextAndLifter {
|
||||
return kPCRegName;
|
||||
}
|
||||
|
||||
uint64_t MinInstructionAlign(void) const final {
|
||||
uint64_t MinInstructionAlign(const DecodingContext &) const final {
|
||||
return 4;
|
||||
}
|
||||
|
||||
uint64_t MinInstructionSize(void) const final {
|
||||
uint64_t MinInstructionSize(const DecodingContext &) const final {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Maximum number of bytes in an instruction.
|
||||
uint64_t MaxInstructionSize(bool permit_fuse_idioms) const final {
|
||||
uint64_t MaxInstructionSize(const DecodingContext &,
|
||||
bool permit_fuse_idioms) const final {
|
||||
return permit_fuse_idioms ? 8 : 4; // To handle `SET` idioms.
|
||||
}
|
||||
|
||||
|
||||
+196
-258
@@ -45,9 +45,7 @@ class AssemblyLogger : public AssemblyEmit {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
PcodeDecoder::PcodeDecoder(::Sleigh &engine_, Instruction &inst_)
|
||||
: engine(engine_),
|
||||
inst(inst_) {}
|
||||
PcodeDecoder::PcodeDecoder(::Sleigh &engine_) : engine(engine_) {}
|
||||
|
||||
|
||||
void PcodeDecoder::print_vardata(std::stringstream &s, VarnodeData &data) {
|
||||
@@ -64,220 +62,20 @@ void PcodeDecoder::print_vardata(std::stringstream &s, VarnodeData &data) {
|
||||
|
||||
void PcodeDecoder::dump(const Address &, OpCode op, VarnodeData *outvar,
|
||||
VarnodeData *vars, int32_t isize) {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << get_opname(op);
|
||||
RemillPcodeOp new_op;
|
||||
new_op.op = op;
|
||||
if (outvar) {
|
||||
print_vardata(ss, *outvar);
|
||||
ss << " = ";
|
||||
DecodeOperand(*outvar);
|
||||
}
|
||||
for (int i = 0; i < isize; ++i) {
|
||||
print_vardata(ss, vars[i]);
|
||||
DecodeOperand(vars[i]);
|
||||
}
|
||||
|
||||
DecodeCategory(op, vars, isize);
|
||||
LOG(INFO) << ss.str();
|
||||
}
|
||||
|
||||
void PcodeDecoder::DecodeOperand(VarnodeData &var) {
|
||||
const auto loc_name = var.space->getName();
|
||||
if (loc_name == "register") {
|
||||
DecodeRegister(var);
|
||||
} else if (loc_name == "unique") {
|
||||
DecodeMemory(var);
|
||||
} else if (loc_name == "ram") {
|
||||
DecodeMemory(var);
|
||||
} else if (loc_name == "const") {
|
||||
DecodeConstant(var);
|
||||
new_op.outvar = *outvar;
|
||||
} else {
|
||||
LOG(FATAL) << "Instruction location " << loc_name << " not supported";
|
||||
}
|
||||
}
|
||||
|
||||
void PcodeDecoder::DecodeRegister(const VarnodeData &var) {
|
||||
const auto reg_name = engine.getRegisterName(var.space, var.offset, var.size);
|
||||
Operand op;
|
||||
op.type = Operand::kTypeRegister;
|
||||
Operand::Register reg;
|
||||
reg.name = reg_name;
|
||||
reg.size =
|
||||
var.size; // I don't think this is correct. Need to distinguish between the register width vs the read/write size.
|
||||
op.reg = reg;
|
||||
op.size = var.size;
|
||||
// TODO(alex): Pass information about whether its an outvar or not
|
||||
op.action = true ? Operand::kActionRead : Operand::kActionWrite;
|
||||
inst.operands.push_back(op);
|
||||
}
|
||||
|
||||
void PcodeDecoder::DecodeMemory(const VarnodeData &var) {
|
||||
Operand op;
|
||||
op.size = var.size * 8;
|
||||
op.type = Operand::kTypeAddress;
|
||||
op.addr.address_size = 64; // Not sure
|
||||
op.addr.kind =
|
||||
true ? Operand::Address::kMemoryRead : Operand::Address::kMemoryWrite;
|
||||
inst.operands.push_back(op);
|
||||
}
|
||||
|
||||
void PcodeDecoder::DecodeConstant(const VarnodeData &var) {
|
||||
Operand op;
|
||||
op.type = Operand::kTypeImmediate;
|
||||
op.action = Operand::kActionRead;
|
||||
op.imm.is_signed = false; // Not sure
|
||||
op.imm.val = var.offset;
|
||||
inst.operands.push_back(op);
|
||||
}
|
||||
|
||||
/*
|
||||
CPUI_BRANCH = 4, ///< Always branch
|
||||
CPUI_CBRANCH = 5, ///< Conditional branch
|
||||
CPUI_BRANCHIND = 6, ///< Indirect branch (jumptable)
|
||||
|
||||
CPUI_CALL = 7, ///< Call to an absolute address
|
||||
CPUI_CALLIND = 8, ///< Call through an indirect address
|
||||
CPUI_CALLOTHER = 9, ///< User-defined operation
|
||||
CPUI_RETURN = 10, ///< Return from subroutine
|
||||
*/
|
||||
|
||||
|
||||
std::optional<InstructionFlowResolver::IFRPtr>
|
||||
PcodeDecoder::GetFlowResolverForOp(OpCode op, VarnodeData *vars,
|
||||
int32_t isize) {
|
||||
// TODO(Ian): we should check if we know about this address space and do something if not
|
||||
switch (op) {
|
||||
case CPUI_BRANCH:
|
||||
return InstructionFlowResolver::CreateDirectBranch(vars[0].offset);
|
||||
|
||||
case CPUI_CALL:
|
||||
return InstructionFlowResolver::CreateDirectCall(vars[0].offset);
|
||||
break;
|
||||
case CPUI_CBRANCH:
|
||||
return InstructionFlowResolver::CreateDirectCBranchResolver(
|
||||
vars[0].offset);
|
||||
break;
|
||||
case CPUI_BRANCHIND: return InstructionFlowResolver::CreateIndirectBranch();
|
||||
case CPUI_CALLIND: return InstructionFlowResolver::CreateIndirectCall();
|
||||
|
||||
case CPUI_RETURN: return InstructionFlowResolver::CreateIndirectRet();
|
||||
|
||||
default: return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PcodeDecoder::DecodeCategory(OpCode op, VarnodeData *vars, int32_t isize) {
|
||||
if (auto resolver = PcodeDecoder::GetFlowResolverForOp(op, vars, isize)) {
|
||||
if (this->current_resolver) {
|
||||
LOG(ERROR)
|
||||
<< "Demoting instruction to indirect branch, already guessed category";
|
||||
// ok we've already seen a control flow instruction so call it an indirect branch
|
||||
this->current_resolver = InstructionFlowResolver::CreateIndirectBranch();
|
||||
return;
|
||||
}
|
||||
|
||||
this->current_resolver = resolver;
|
||||
}
|
||||
}
|
||||
|
||||
InstructionFlowResolver::IFRPtr PcodeDecoder::GetResolver() {
|
||||
if (!this->current_resolver.has_value()) {
|
||||
LOG(INFO) << "resolver doesnt have a value";
|
||||
return InstructionFlowResolver::CreateNormal();
|
||||
} else {
|
||||
LOG(INFO) << "resolver does have a value";
|
||||
return *this->current_resolver;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InstructionFlowResolver::IFRPtr
|
||||
InstructionFlowResolver::CreateDirectCBranchResolver(uint64_t target) {
|
||||
return std::make_shared<DirectCBranchResolver>(target);
|
||||
}
|
||||
InstructionFlowResolver::IFRPtr InstructionFlowResolver::CreateIndirectCall() {
|
||||
return std::make_shared<IndirectBranch>(
|
||||
remill::Instruction::Category::kCategoryIndirectFunctionCall);
|
||||
}
|
||||
InstructionFlowResolver::IFRPtr InstructionFlowResolver::CreateIndirectRet() {
|
||||
return std::make_shared<IndirectBranch>(
|
||||
remill::Instruction::Category::kCategoryFunctionReturn);
|
||||
}
|
||||
InstructionFlowResolver::IFRPtr
|
||||
InstructionFlowResolver::CreateIndirectBranch() {
|
||||
return std::make_shared<IndirectBranch>(
|
||||
remill::Instruction::Category::kCategoryIndirectJump);
|
||||
}
|
||||
|
||||
InstructionFlowResolver::IFRPtr
|
||||
InstructionFlowResolver::CreateDirectBranch(uint64_t target) {
|
||||
return std::make_shared<DirectBranchResolver>(
|
||||
target, remill::Instruction::Category::kCategoryDirectJump);
|
||||
}
|
||||
InstructionFlowResolver::IFRPtr
|
||||
InstructionFlowResolver::CreateDirectCall(uint64_t target) {
|
||||
return std::make_shared<DirectBranchResolver>(
|
||||
target, remill::Instruction::Category::kCategoryDirectFunctionCall);
|
||||
}
|
||||
|
||||
InstructionFlowResolver::IFRPtr InstructionFlowResolver::CreateNormal() {
|
||||
return std::make_shared<NormalResolver>();
|
||||
}
|
||||
|
||||
IndirectBranch::IndirectBranch(remill::Instruction::Category category)
|
||||
: category(category) {}
|
||||
|
||||
DirectBranchResolver::DirectBranchResolver(
|
||||
uint64_t target_address, remill::Instruction::Category category)
|
||||
: target_address(target_address),
|
||||
category(category) {}
|
||||
|
||||
|
||||
DirectCBranchResolver::DirectCBranchResolver(uint64_t target_address)
|
||||
: target_address(target_address) {}
|
||||
|
||||
|
||||
void IndirectBranch::ResolveControlFlow(uint64_t fall_through,
|
||||
remill::Instruction &insn) {
|
||||
insn.next_pc = 0;
|
||||
insn.category = this->category;
|
||||
insn.branch_not_taken_pc = fall_through;
|
||||
}
|
||||
|
||||
|
||||
void DirectBranchResolver::ResolveControlFlow(uint64_t fall_through,
|
||||
remill::Instruction &insn) {
|
||||
insn.next_pc = this->target_address;
|
||||
insn.branch_taken_pc = this->target_address;
|
||||
insn.branch_not_taken_pc = fall_through;
|
||||
insn.category = this->category;
|
||||
insn.branch_taken_arch_name = insn.arch_name;
|
||||
}
|
||||
|
||||
void NormalResolver::ResolveControlFlow(uint64_t fall_through,
|
||||
remill::Instruction &insn) {
|
||||
insn.next_pc = fall_through;
|
||||
insn.category = remill::Instruction::Category::kCategoryNormal;
|
||||
}
|
||||
|
||||
void DirectCBranchResolver::ResolveControlFlow(uint64_t fall_through,
|
||||
remill::Instruction &insn) {
|
||||
LOG(INFO) << "resolving direct cbranch" << fall_through;
|
||||
|
||||
if (this->target_address == fall_through) {
|
||||
insn.next_pc = fall_through;
|
||||
insn.category = remill::Instruction::Category::kCategoryNormal;
|
||||
return;
|
||||
new_op.outvar = std::nullopt;
|
||||
}
|
||||
|
||||
insn.next_pc = 0;
|
||||
insn.branch_taken_pc = this->target_address;
|
||||
insn.branch_not_taken_pc = fall_through;
|
||||
insn.category = remill::Instruction::Category::kCategoryConditionalBranch;
|
||||
insn.branch_taken_arch_name = insn.arch_name;
|
||||
}
|
||||
for (int i = 0; i < isize; i++) {
|
||||
new_op.vars.push_back(vars[i]);
|
||||
}
|
||||
|
||||
this->ops.push_back(std::move(new_op));
|
||||
}
|
||||
|
||||
std::vector<std::string> SingleInstructionSleighContext::getUserOpNames() {
|
||||
std::vector<std::string> res;
|
||||
@@ -365,42 +163,80 @@ std::string CustomLoadImage::getArchType(void) const {
|
||||
|
||||
void CustomLoadImage::adjustVma(long) {}
|
||||
|
||||
std::shared_ptr<remill::OperandLifter> SleighDecoder::GetOpLifter() const {
|
||||
return this->GetLifter();
|
||||
}
|
||||
|
||||
SleighArch::DecodingResult
|
||||
SleighArch::DecodeInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const {
|
||||
inst.SetLifter(
|
||||
std::make_shared<SleighLifter>(this, *this->GetInstrinsicTable()));
|
||||
assert(inst.GetLifter() != nullptr);
|
||||
|
||||
if (const_cast<SleighArch *>(this)->DecodeInstructionImpl(
|
||||
address, instr_bytes, inst)) {
|
||||
return [this](uint64_t) -> DecodingContext {
|
||||
return this->CreateInitialContext();
|
||||
};
|
||||
std::shared_ptr<remill::SleighLifter> SleighDecoder::GetLifter() const {
|
||||
if (this->lifter) {
|
||||
return this->lifter;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
if (!this->arch.GetInstrinsicTable()) {
|
||||
LOG(FATAL)
|
||||
<< "Architecture was not initialized before asking for a lifting";
|
||||
}
|
||||
|
||||
auto tab = this->arch.GetInstrinsicTable();
|
||||
|
||||
this->lifter =
|
||||
std::make_shared<remill::SleighLifter>(this->arch, *this, *tab);
|
||||
|
||||
return this->lifter;
|
||||
}
|
||||
|
||||
bool SleighDecoder::DecodeInstruction(uint64_t address,
|
||||
std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const {
|
||||
|
||||
|
||||
auto res_cat = const_cast<SleighDecoder *>(this)->DecodeInstructionImpl(
|
||||
address, instr_bytes, inst, std::move(context));
|
||||
|
||||
if (!res_cat->second &&
|
||||
std::holds_alternative<remill::Instruction::ConditionalInstruction>(
|
||||
res_cat->first)) {
|
||||
LOG(FATAL)
|
||||
<< "Should always emit branch taken var for conditional instruction";
|
||||
}
|
||||
|
||||
inst.SetLifter(std::make_shared<SleighLifterWithState>(res_cat->second,
|
||||
this->GetLifter()));
|
||||
CHECK(inst.GetLifter() != nullptr);
|
||||
return res_cat.has_value();
|
||||
}
|
||||
|
||||
|
||||
DecodingContext SleighArch::CreateInitialContext(void) const {
|
||||
return DecodingContext();
|
||||
}
|
||||
|
||||
|
||||
SleighArch::SleighArch(llvm::LLVMContext *context_, OSName os_name_,
|
||||
ArchName arch_name_, std::string sla_name,
|
||||
std::string pspec_name)
|
||||
: ArchBase(context_, os_name_, arch_name_),
|
||||
sleigh_ctx(sla_name, pspec_name),
|
||||
SleighDecoder::SleighDecoder(
|
||||
const remill::Arch &arch_, std::string sla_name, std::string pspec_name,
|
||||
std::unordered_map<std::string, std::string> context_reg_map_,
|
||||
std::unordered_map<std::string, std::string> state_reg_map_)
|
||||
: sleigh_ctx(sla_name, pspec_name),
|
||||
sla_name(sla_name),
|
||||
pspec_name(pspec_name) {}
|
||||
pspec_name(pspec_name),
|
||||
lifter(nullptr),
|
||||
arch(arch_),
|
||||
context_reg_mapping(std::move(context_reg_map_)),
|
||||
state_reg_remappings(std::move(state_reg_map_)) {}
|
||||
|
||||
bool SleighArch::DecodeInstructionImpl(uint64_t address,
|
||||
std::string_view instr_bytes,
|
||||
Instruction &inst) {
|
||||
|
||||
const std::unordered_map<std::string, std::string> &
|
||||
SleighDecoder::GetContextRegisterMapping() const {
|
||||
return this->context_reg_mapping;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, std::string> &
|
||||
SleighDecoder::GetStateRegRemappings() const {
|
||||
return this->state_reg_remappings;
|
||||
}
|
||||
|
||||
std::optional<std::pair<Instruction::InstructionFlowCategory,
|
||||
std::optional<BranchTakenVar>>>
|
||||
SleighDecoder::DecodeInstructionImpl(uint64_t address,
|
||||
std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext curr_context) {
|
||||
|
||||
// The SLEIGH engine will query this image when we try to decode an instruction. Append the bytes so SLEIGH has data to read.
|
||||
|
||||
@@ -408,47 +244,64 @@ bool SleighArch::DecodeInstructionImpl(uint64_t address,
|
||||
// Now decode the instruction.
|
||||
this->sleigh_ctx.resetContext();
|
||||
this->InitializeSleighContext(this->sleigh_ctx);
|
||||
PcodeDecoder pcode_handler(this->sleigh_ctx.GetEngine(), inst);
|
||||
PcodeDecoder pcode_handler(this->sleigh_ctx.GetEngine());
|
||||
|
||||
LOG(INFO) << "Provided insn size: " << instr_bytes.size();
|
||||
|
||||
inst.Reset();
|
||||
inst.arch = this;
|
||||
inst.arch = &this->arch;
|
||||
inst.bytes = instr_bytes;
|
||||
inst.arch_name = arch_name;
|
||||
inst.sub_arch_name = arch_name;
|
||||
inst.arch_name = this->arch.arch_name;
|
||||
inst.sub_arch_name = this->arch.arch_name;
|
||||
inst.branch_taken_arch_name = ArchName::kArchInvalid;
|
||||
inst.pc = address;
|
||||
inst.category = Instruction::kCategoryInvalid;
|
||||
|
||||
auto instr_len =
|
||||
this->sleigh_ctx.oneInstruction(address, pcode_handler, instr_bytes);
|
||||
this->sleigh_ctx.oneInstruction(address, pcode_handler, inst.bytes);
|
||||
|
||||
if (!instr_len || instr_len > instr_bytes.size()) {
|
||||
return false;
|
||||
return std::nullopt;
|
||||
}
|
||||
// communicate the size back to the caller
|
||||
inst.bytes = instr_bytes.substr(0, *instr_len);
|
||||
assert(inst.bytes.size() == instr_len);
|
||||
|
||||
InstructionFunctionSetter setter(inst);
|
||||
|
||||
this->sleigh_ctx.oneInstruction(address, setter, inst.bytes);
|
||||
LOG(INFO) << "Instr len:" << *instr_len;
|
||||
LOG(INFO) << "Addr: " << address;
|
||||
auto fallthrough = address + *instr_len;
|
||||
uint64_t fallthrough = address + *instr_len;
|
||||
inst.next_pc = fallthrough;
|
||||
|
||||
ControlFlowStructureAnalysis analysis(this->context_reg_mapping,
|
||||
this->sleigh_ctx.GetEngine());
|
||||
|
||||
|
||||
auto cat =
|
||||
analysis.ComputeCategory(pcode_handler.ops, fallthrough, curr_context);
|
||||
if (!cat) {
|
||||
LOG(ERROR) << "Failed to compute category for inst at " << std::hex
|
||||
<< inst.pc;
|
||||
inst.flows = Instruction::InvalidInsn();
|
||||
inst.category = Instruction::Category::kCategoryInvalid;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
inst.flows = cat->first;
|
||||
|
||||
this->ApplyFlowToInstruction(inst);
|
||||
|
||||
LOG(INFO) << "Fallthrough: " << fallthrough;
|
||||
pcode_handler.GetResolver()->ResolveControlFlow(fallthrough, inst);
|
||||
LOG(INFO) << "Decoded as " << inst.Serialize();
|
||||
return true;
|
||||
|
||||
return cat;
|
||||
}
|
||||
|
||||
|
||||
std::string SleighArch::GetSLAName() const {
|
||||
std::string SleighDecoder::GetSLAName() const {
|
||||
return this->sla_name;
|
||||
}
|
||||
|
||||
|
||||
std::string SleighArch::GetPSpec() const {
|
||||
std::string SleighDecoder::GetPSpec() const {
|
||||
return this->pspec_name;
|
||||
}
|
||||
|
||||
@@ -519,9 +372,94 @@ std::optional<int32_t> SingleInstructionSleighContext::oneInstruction(
|
||||
}
|
||||
|
||||
|
||||
OperandLifter::OpLifterPtr
|
||||
SleighArch::DefaultLifter(const remill::IntrinsicTable &intrinsics) const {
|
||||
return std::make_unique<SleighLifter>(this, intrinsics);
|
||||
namespace {
|
||||
|
||||
template <typename... Ts> // (7)
|
||||
struct Overload : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
template <class... Ts>
|
||||
Overload(Ts...) -> Overload<Ts...>;
|
||||
|
||||
} // namespace
|
||||
|
||||
void SleighDecoder::ApplyFlowToInstruction(remill::Instruction &inst) const {
|
||||
|
||||
|
||||
auto applyer = Overload{
|
||||
[&inst](const remill::Instruction::NormalInsn &cat) -> void {
|
||||
inst.category = remill::Instruction::Category::kCategoryNormal;
|
||||
},
|
||||
[&inst](const remill::Instruction::NoOp &cat) {
|
||||
inst.category = remill::Instruction::Category::kCategoryNoOp;
|
||||
},
|
||||
[&inst](const remill::Instruction::InvalidInsn &cat) {
|
||||
inst.category = remill::Instruction::Category::kCategoryInvalid;
|
||||
},
|
||||
[&inst](const remill::Instruction::ErrorInsn &cat) {
|
||||
inst.category = remill::Instruction::Category::kCategoryError;
|
||||
},
|
||||
[&inst](const remill::Instruction::DirectJump &cat) {
|
||||
inst.category = remill::Instruction::Category::kCategoryDirectJump;
|
||||
inst.branch_taken_pc = cat.taken_flow.known_target;
|
||||
},
|
||||
[&inst](const remill::Instruction::IndirectJump &cat) {
|
||||
inst.category = remill::Instruction::Category::kCategoryIndirectJump;
|
||||
},
|
||||
[&inst](const remill::Instruction::DirectFunctionCall &cat) {
|
||||
// TODO(Ian) maybe add a return_to_flow for function call flows
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
inst.category =
|
||||
remill::Instruction::Category::kCategoryDirectFunctionCall;
|
||||
},
|
||||
[&inst](const remill::Instruction::IndirectFunctionCall &cat) {
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
inst.category =
|
||||
remill::Instruction::Category::kCategoryIndirectFunctionCall;
|
||||
},
|
||||
[&inst](const remill::Instruction::FunctionReturn &cat) {
|
||||
inst.category = remill::Instruction::Category::kCategoryFunctionReturn;
|
||||
},
|
||||
[&inst](const remill::Instruction::AsyncHyperCall &cat) {
|
||||
inst.category = remill::Instruction::Category::kCategoryAsyncHyperCall;
|
||||
},
|
||||
[&inst](const remill::Instruction::ConditionalInstruction &cat) {
|
||||
// TODO(Ian)
|
||||
inst.branch_not_taken_pc = inst.next_pc;
|
||||
|
||||
auto conditional_applyer = Overload{
|
||||
[&inst](
|
||||
const remill::Instruction::DirectFunctionCall &cat) -> void {
|
||||
inst.category = remill::Instruction::Category::
|
||||
kCategoryConditionalDirectFunctionCall;
|
||||
},
|
||||
[&inst](const remill::Instruction::IndirectFunctionCall &cat) {
|
||||
inst.category = remill::Instruction::Category::
|
||||
kCategoryConditionalIndirectFunctionCall;
|
||||
},
|
||||
[&inst](const remill::Instruction::FunctionReturn &cat) {
|
||||
inst.category = remill::Instruction::Category::
|
||||
kCategoryConditionalFunctionReturn;
|
||||
},
|
||||
[&inst](const remill::Instruction::AsyncHyperCall &cat) {
|
||||
inst.category = remill::Instruction::Category::
|
||||
kCategoryConditionalAsyncHyperCall;
|
||||
},
|
||||
[&inst](const remill::Instruction::IndirectJump &cat) {
|
||||
inst.category = remill::Instruction::Category::
|
||||
kCategoryConditionalIndirectJump;
|
||||
},
|
||||
[&inst](const remill::Instruction::DirectJump &cat) {
|
||||
inst.category =
|
||||
remill::Instruction::Category::kCategoryConditionalBranch;
|
||||
inst.branch_taken_pc = cat.taken_flow.known_target;
|
||||
}};
|
||||
|
||||
std::visit(conditional_applyer, cat.taken_branch);
|
||||
},
|
||||
}; // namespace remill::sleigh
|
||||
|
||||
std::visit(applyer, inst.flows);
|
||||
}
|
||||
|
||||
} // namespace remill::sleigh
|
||||
|
||||
+51
-124
@@ -15,122 +15,30 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <lib/Arch/Sleigh/ControlFlowStructuring.h>
|
||||
#include <remill/Arch/ArchBase.h>
|
||||
#include <remill/BC/SleighLifter.h>
|
||||
|
||||
#include <sleigh/libsleigh.hh>
|
||||
#include <unordered_set>
|
||||
|
||||
// Unifies shared functionality between sleigh architectures
|
||||
|
||||
namespace remill::sleigh {
|
||||
|
||||
// NOTE(Ian): Ok so there is some horrible collaboration with the lifter.
|
||||
// The lifter has to add metavars. So the lifter is responsible for working
|
||||
// out if a branch was taken
|
||||
class InstructionFlowResolver {
|
||||
public:
|
||||
virtual ~InstructionFlowResolver(void) = default;
|
||||
|
||||
using IFRPtr = std::shared_ptr<InstructionFlowResolver>;
|
||||
|
||||
virtual void ResolveControlFlow(uint64_t fall_through,
|
||||
remill::Instruction &insn) = 0;
|
||||
|
||||
|
||||
static IFRPtr CreateDirectCBranchResolver(uint64_t target);
|
||||
static IFRPtr CreateIndirectCall();
|
||||
static IFRPtr CreateIndirectRet();
|
||||
static IFRPtr CreateIndirectBranch();
|
||||
|
||||
static IFRPtr CreateDirectBranch(uint64_t target);
|
||||
static IFRPtr CreateDirectCall(uint64_t target);
|
||||
|
||||
static IFRPtr CreateNormal();
|
||||
};
|
||||
|
||||
|
||||
class NormalResolver : public InstructionFlowResolver {
|
||||
public:
|
||||
NormalResolver() = default;
|
||||
virtual ~NormalResolver() = default;
|
||||
|
||||
void ResolveControlFlow(uint64_t fall_through,
|
||||
remill::Instruction &insn) override;
|
||||
};
|
||||
|
||||
// Direct Branch
|
||||
class DirectBranchResolver : public InstructionFlowResolver {
|
||||
private:
|
||||
uint64_t target_address;
|
||||
|
||||
// Can be a call or branch.
|
||||
remill::Instruction::Category category;
|
||||
|
||||
public:
|
||||
DirectBranchResolver(uint64_t target_address,
|
||||
remill::Instruction::Category category);
|
||||
virtual ~DirectBranchResolver() = default;
|
||||
|
||||
void ResolveControlFlow(uint64_t fall_through,
|
||||
remill::Instruction &insn) override;
|
||||
};
|
||||
|
||||
// Cbranch(NOTE): this may be normal if the cbranch target is the same as the fallthrough
|
||||
class DirectCBranchResolver : public InstructionFlowResolver {
|
||||
private:
|
||||
uint64_t target_address;
|
||||
|
||||
public:
|
||||
DirectCBranchResolver(uint64_t target_address);
|
||||
virtual ~DirectCBranchResolver() = default;
|
||||
|
||||
void ResolveControlFlow(uint64_t fall_through,
|
||||
remill::Instruction &insn) override;
|
||||
};
|
||||
|
||||
|
||||
class IndirectBranch : public InstructionFlowResolver {
|
||||
// can be a return, callind, or branchind
|
||||
remill::Instruction::Category category;
|
||||
|
||||
public:
|
||||
IndirectBranch(remill::Instruction::Category category);
|
||||
|
||||
virtual ~IndirectBranch() = default;
|
||||
|
||||
void ResolveControlFlow(uint64_t fall_through,
|
||||
remill::Instruction &insn) override;
|
||||
};
|
||||
|
||||
class PcodeDecoder final : public PcodeEmit {
|
||||
public:
|
||||
PcodeDecoder(::Sleigh &engine_, Instruction &inst_);
|
||||
PcodeDecoder(::Sleigh &engine_);
|
||||
|
||||
void dump(const Address &, OpCode op, VarnodeData *outvar, VarnodeData *vars,
|
||||
int32_t isize) override;
|
||||
|
||||
InstructionFlowResolver::IFRPtr GetResolver();
|
||||
|
||||
std::vector<RemillPcodeOp> ops;
|
||||
|
||||
private:
|
||||
::Sleigh &engine;
|
||||
void print_vardata(std::stringstream &s, VarnodeData &data);
|
||||
|
||||
void DecodeOperand(VarnodeData &var);
|
||||
|
||||
void DecodeRegister(const VarnodeData &var);
|
||||
|
||||
void DecodeMemory(const VarnodeData &var);
|
||||
|
||||
void DecodeConstant(const VarnodeData &var);
|
||||
|
||||
void DecodeCategory(OpCode op, VarnodeData *vars, int32_t isize);
|
||||
|
||||
static std::optional<InstructionFlowResolver::IFRPtr>
|
||||
GetFlowResolverForOp(OpCode op, VarnodeData *vars, int32_t isize);
|
||||
|
||||
Sleigh &engine;
|
||||
Instruction &inst;
|
||||
|
||||
std::optional<InstructionFlowResolver::IFRPtr> current_resolver;
|
||||
};
|
||||
|
||||
class CustomLoadImage final : public LoadImage {
|
||||
@@ -150,12 +58,10 @@ class CustomLoadImage final : public LoadImage {
|
||||
uint64_t current_offset{0};
|
||||
};
|
||||
|
||||
class SleighArch;
|
||||
// Holds onto contextual sleigh information in order to provide an interface with which you can decode single instructions
|
||||
// Give me bytes and i give you pcode (maybe)
|
||||
class SingleInstructionSleighContext {
|
||||
private:
|
||||
friend class SleighArch;
|
||||
CustomLoadImage image;
|
||||
ContextInternal ctx;
|
||||
::Sleigh engine;
|
||||
@@ -189,38 +95,59 @@ class SingleInstructionSleighContext {
|
||||
std::vector<std::string> getUserOpNames();
|
||||
};
|
||||
|
||||
class SleighArch : virtual public ArchBase {
|
||||
|
||||
class SleighDecoder {
|
||||
public:
|
||||
SleighArch(llvm::LLVMContext *context_, OSName os_name_, ArchName arch_name_,
|
||||
std::string sla_name, std::string pspec_name);
|
||||
|
||||
|
||||
public:
|
||||
OperandLifter::OpLifterPtr
|
||||
DefaultLifter(const remill::IntrinsicTable &intrinsics) const override;
|
||||
|
||||
|
||||
virtual DecodingContext CreateInitialContext(void) const override;
|
||||
|
||||
virtual std::optional<DecodingContext::ContextMap>
|
||||
DecodeInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst, DecodingContext context) const override;
|
||||
|
||||
|
||||
// Arch specific preperation
|
||||
virtual void
|
||||
InitializeSleighContext(SingleInstructionSleighContext &) const = 0;
|
||||
|
||||
SleighDecoder() = delete;
|
||||
SleighDecoder(
|
||||
const remill::Arch &arch, std::string sla_name, std::string pspec_name,
|
||||
std::unordered_map<std::string, std::string> context_reg_mapping,
|
||||
std::unordered_map<std::string, std::string> state_reg_remappings);
|
||||
std::string GetSLAName() const;
|
||||
|
||||
std::string GetPSpec() const;
|
||||
// Decoder specific prep
|
||||
virtual void
|
||||
InitializeSleighContext(SingleInstructionSleighContext &) const = 0;
|
||||
|
||||
|
||||
virtual llvm::Value *LiftPcFromCurrPc(llvm::IRBuilder<> &bldr,
|
||||
llvm::Value *curr_pc,
|
||||
size_t curr_insn_size) const = 0;
|
||||
|
||||
|
||||
bool DecodeInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst, DecodingContext context) const;
|
||||
|
||||
// Gets the context registers that are required to be set in order to decode, maps pcode context reg names to remill context reg names.
|
||||
const std::unordered_map<std::string, std::string> &
|
||||
GetContextRegisterMapping() const;
|
||||
|
||||
// Maps pcode registers to their names in the remill state structure for the given arch (if a renaming is required.)
|
||||
const std::unordered_map<std::string, std::string> &
|
||||
GetStateRegRemappings() const;
|
||||
|
||||
std::shared_ptr<remill::OperandLifter> GetOpLifter() const;
|
||||
|
||||
protected:
|
||||
bool DecodeInstructionImpl(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst);
|
||||
ControlFlowStructureAnalysis::SleighDecodingResult
|
||||
DecodeInstructionImpl(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst, DecodingContext context);
|
||||
|
||||
|
||||
SingleInstructionSleighContext sleigh_ctx;
|
||||
std::string sla_name;
|
||||
std::string pspec_name;
|
||||
|
||||
private:
|
||||
std::shared_ptr<remill::SleighLifter> GetLifter() const;
|
||||
// Compatibility that applies old categories from constructed flows
|
||||
void ApplyFlowToInstruction(remill::Instruction &) const;
|
||||
|
||||
|
||||
mutable std::shared_ptr<remill::SleighLifter> lifter;
|
||||
const remill::Arch &arch;
|
||||
std::unordered_map<std::string, std::string> context_reg_mapping;
|
||||
std::unordered_map<std::string, std::string> state_reg_remappings;
|
||||
};
|
||||
} // namespace remill::sleigh
|
||||
|
||||
@@ -31,9 +31,12 @@ add_library(remill_arch_sleigh STATIC
|
||||
"${REMILL_INCLUDE_DIR}/remill/Arch/AArch32/Runtime/Types.h"
|
||||
|
||||
Arch.h
|
||||
Thumb.h
|
||||
Arch.cpp
|
||||
X86Arch.cpp
|
||||
Thumb2Arch.cpp
|
||||
ControlFlowStructuring.cpp
|
||||
ControlFlowStructuring.h
|
||||
)
|
||||
|
||||
set_property(TARGET remill_arch_sleigh PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
@@ -42,6 +45,8 @@ target_link_libraries(remill_arch_sleigh LINK_PUBLIC
|
||||
remill_settings
|
||||
)
|
||||
|
||||
target_include_directories(remill_arch_sleigh AFTER PRIVATE "${REMILL_SOURCE_DIR}")
|
||||
|
||||
if(REMILL_ENABLE_INSTALL_TARGET)
|
||||
install(
|
||||
TARGETS remill_arch_sleigh
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
#include <glog/logging.h>
|
||||
#include <lib/Arch/Sleigh/ControlFlowStructuring.h>
|
||||
|
||||
namespace remill::sleigh {
|
||||
|
||||
bool isVarnodeInConstantSpace(VarnodeData vnode) {
|
||||
auto spc = vnode.getAddr().getSpace();
|
||||
return spc->constant_space_index == spc->getIndex();
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
// variant casting taken c&ped.
|
||||
template <class... Args>
|
||||
struct variant_cast_proxy {
|
||||
std::variant<Args...> v;
|
||||
|
||||
template <class... ToArgs>
|
||||
operator std::variant<ToArgs...>() const {
|
||||
return std::visit([](auto &&arg) -> std::variant<ToArgs...> { return arg; },
|
||||
v);
|
||||
}
|
||||
};
|
||||
|
||||
template <class... Args>
|
||||
auto variant_cast(const std::variant<Args...> &v)
|
||||
-> variant_cast_proxy<Args...> {
|
||||
return {v};
|
||||
}
|
||||
|
||||
enum CoarseEffect { kAbnormal, kNormal };
|
||||
|
||||
struct CoarseFlow {
|
||||
CoarseEffect eff;
|
||||
bool is_conditional;
|
||||
};
|
||||
|
||||
enum CoarseCategory { kCatNormal, kCatAbnormal, kCatConditionalAbnormal };
|
||||
|
||||
|
||||
static CoarseEffect EffectFromDirectControlFlowOp(const RemillPcodeOp &op,
|
||||
uint64_t next_pc) {
|
||||
CHECK(op.op == CPUI_BRANCH || op.op == CPUI_CBRANCH);
|
||||
return op.vars[0].offset == next_pc ? CoarseEffect::kNormal
|
||||
: CoarseEffect::kAbnormal;
|
||||
}
|
||||
|
||||
static std::optional<CoarseFlow>
|
||||
CoarseFlowFromControlFlowOp(const RemillPcodeOp &op, uint64_t next_pc) {
|
||||
if (op.op == CPUI_CALL || op.op == CPUI_CALLIND || op.op == CPUI_BRANCHIND ||
|
||||
op.op == CPUI_RETURN) {
|
||||
return {{CoarseEffect::kAbnormal, false}};
|
||||
}
|
||||
|
||||
// either a branch or a cbranch
|
||||
|
||||
// figure out if this is a fallthrough, input 0 is the next target
|
||||
|
||||
auto is_conditional = op.op == CPUI_CBRANCH;
|
||||
if (isVarnodeInConstantSpace(op.vars[0])) {
|
||||
// this is an internal branch.. we cant handle that right now
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return {{EffectFromDirectControlFlowOp(op, next_pc), is_conditional}};
|
||||
}
|
||||
|
||||
// gets a list of indeces and coarse categories in this pcodeop block
|
||||
static std::optional<std::map<size_t, CoarseFlow>>
|
||||
CoarseFlows(const std::vector<RemillPcodeOp> &ops, uint64_t next_pc) {
|
||||
std::map<size_t, CoarseFlow> res;
|
||||
size_t ind = 0;
|
||||
for (auto op : ops) {
|
||||
if (ControlFlowStructureAnalysis::isControlFlowPcodeOp(op.op)) {
|
||||
auto cc = CoarseFlowFromControlFlowOp(op, next_pc);
|
||||
if (!cc) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
res.emplace(ind, *cc);
|
||||
}
|
||||
|
||||
ind++;
|
||||
}
|
||||
|
||||
// insert a pseudo control flow op at the end
|
||||
// add a fallthrough insn at +1 to represent a last fallthrough if there is a chance we fallthrough at the end
|
||||
auto insn_may_fallthrough_at_end =
|
||||
ops.empty() ||
|
||||
!ControlFlowStructureAnalysis::isControlFlowPcodeOp(
|
||||
ops[ops.size() - 1].op) ||
|
||||
ops[ops.size() - 1].op == CPUI_CBRANCH;
|
||||
if (insn_may_fallthrough_at_end) {
|
||||
CoarseFlow cat = {CoarseEffect::kNormal, false};
|
||||
res.emplace(ops.size(), cat);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool isConditionalAbnormal(CoarseFlow flow) {
|
||||
return flow.eff == CoarseEffect::kAbnormal && flow.is_conditional;
|
||||
}
|
||||
|
||||
static bool isUnconditionalAbnormal(CoarseFlow flow) {
|
||||
return flow.eff == CoarseEffect::kAbnormal && !flow.is_conditional;
|
||||
}
|
||||
|
||||
static bool isConditionalNormal(CoarseFlow flow) {
|
||||
return flow.eff == CoarseEffect::kNormal && flow.is_conditional;
|
||||
}
|
||||
|
||||
static bool isUnconditionalNormal(CoarseFlow flow) {
|
||||
return flow.eff == CoarseEffect::kNormal && !flow.is_conditional;
|
||||
}
|
||||
|
||||
static std::optional<CoarseCategory>
|
||||
CoarseCategoryFromFlows(const std::map<size_t, CoarseFlow> &ops) {
|
||||
|
||||
|
||||
auto all_normal_effects = std::all_of(
|
||||
ops.begin(), ops.end(), [](const std::pair<size_t, CoarseFlow> &op) {
|
||||
return op.second.eff == CoarseEffect::kNormal;
|
||||
});
|
||||
if (all_normal_effects) {
|
||||
return CoarseCategory::kCatNormal;
|
||||
}
|
||||
|
||||
auto all_abnormal_effects = std::all_of(
|
||||
ops.begin(), ops.end(), [](const std::pair<size_t, CoarseFlow> &op) {
|
||||
return op.second.eff == CoarseEffect::kAbnormal;
|
||||
});
|
||||
if (all_abnormal_effects) {
|
||||
return CoarseCategory::kCatAbnormal;
|
||||
}
|
||||
|
||||
if (ops.size() == 2) {
|
||||
auto fst = ops.begin()->second;
|
||||
auto snd = ops.rbegin()->second;
|
||||
if (((isConditionalAbnormal(fst) && isUnconditionalNormal(snd)) ||
|
||||
(isConditionalNormal(fst) && isUnconditionalAbnormal(snd)))) {
|
||||
return CoarseCategory::kCatConditionalAbnormal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
struct Flow {
|
||||
size_t pcode_index;
|
||||
CoarseFlow flow;
|
||||
std::optional<DecodingContext> context;
|
||||
|
||||
Flow(size_t pcode_index, CoarseFlow flow,
|
||||
std::optional<DecodingContext> context)
|
||||
: pcode_index(pcode_index),
|
||||
flow(std::move(flow)),
|
||||
context(std::move(context)) {}
|
||||
};
|
||||
|
||||
std::vector<Flow>
|
||||
GetBoundContextsForFlows(const std::vector<RemillPcodeOp> &ops,
|
||||
const std::map<size_t, CoarseFlow> &cc,
|
||||
ContextUpdater &updater) {
|
||||
|
||||
std::vector<Flow> res;
|
||||
CHECK(cc.size() >= 1);
|
||||
CHECK(cc.crbegin()->first <= ops.size());
|
||||
for (size_t curr_ind = 0; curr_ind <= ops.size(); curr_ind++) {
|
||||
if (auto curr = cc.find(curr_ind); curr != cc.end()) {
|
||||
auto cont = updater.GetContext();
|
||||
res.emplace_back(curr_ind, curr->second, cont);
|
||||
}
|
||||
|
||||
if (curr_ind < ops.size()) {
|
||||
updater.ApplyPcodeOp(ops[curr_ind]);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
// DirectJump, IndirectJump, FunctionReturn
|
||||
static std::optional<Instruction::AbnormalFlow>
|
||||
AbnormalCategoryOfFlow(const Flow &flow, const RemillPcodeOp &op) {
|
||||
if (op.op == CPUI_RETURN) {
|
||||
Instruction::IndirectFlow id_flow(flow.context);
|
||||
Instruction::FunctionReturn ret(id_flow);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (op.op == CPUI_BRANCHIND) {
|
||||
Instruction::IndirectFlow id_flow(flow.context);
|
||||
Instruction::IndirectJump id_jump(id_flow);
|
||||
return id_jump;
|
||||
}
|
||||
|
||||
if (op.op == CPUI_BRANCH && !isVarnodeInConstantSpace(op.vars[0]) &&
|
||||
flow.context) {
|
||||
auto target = op.vars[0].offset;
|
||||
Instruction::DirectFlow dflow(target, *flow.context);
|
||||
Instruction::DirectJump djump(dflow);
|
||||
return djump;
|
||||
}
|
||||
|
||||
if (op.op == CPUI_CALL) {
|
||||
auto target = op.vars[0].offset;
|
||||
Instruction::DirectFlow dflow(target, *flow.context);
|
||||
Instruction::DirectFunctionCall call(dflow);
|
||||
return call;
|
||||
}
|
||||
|
||||
if (op.op == CPUI_CALLIND) {
|
||||
Instruction::IndirectFlow id_flow(flow.context);
|
||||
Instruction::IndirectFunctionCall call(id_flow);
|
||||
return call;
|
||||
}
|
||||
|
||||
|
||||
// still need to pick up the flow for the actual abnormal transition
|
||||
if (op.op == CPUI_CBRANCH) {
|
||||
auto target = op.vars[0].offset;
|
||||
Instruction::DirectFlow dflow(target, *flow.context);
|
||||
Instruction::DirectJump djump(dflow);
|
||||
return djump;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
static ControlFlowStructureAnalysis::SleighDecodingResult
|
||||
ExtractNonConditionalCategory(
|
||||
const std::vector<Flow> &flows, const std::vector<RemillPcodeOp> &ops,
|
||||
std::function<std::optional<Instruction::InstructionFlowCategory>(
|
||||
const Flow &, const RemillPcodeOp &)>
|
||||
compute_single_flow_category) {
|
||||
|
||||
// So here the requirement to make this cateogry work is that all flows target the same abnormal (or are all returns), and all decoding contexts are equal
|
||||
std::vector<Instruction::InstructionFlowCategory> cats;
|
||||
for (auto flow : flows) {
|
||||
if (auto cat = compute_single_flow_category(flow, ops[flow.pcode_index])) {
|
||||
cats.push_back(*cat);
|
||||
} else {
|
||||
DLOG(ERROR) << "Missing flow cat";
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// if all cats are equal then we have our result
|
||||
|
||||
if (cats.size() < 1) {
|
||||
DLOG(ERROR) << "No extracted cats";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Instruction::InstructionFlowCategory fst = cats[0];
|
||||
auto all_flows_equal = [&fst](Instruction::InstructionFlowCategory curr_cat) {
|
||||
return fst == curr_cat;
|
||||
};
|
||||
if (std::all_of(cats.begin(), cats.end(), std::move(all_flows_equal))) {
|
||||
return std::make_pair(fst, std::nullopt);
|
||||
}
|
||||
DLOG(ERROR) << "Not equal flows";
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static ControlFlowStructureAnalysis::SleighDecodingResult
|
||||
ExtractNormal(const std::vector<Flow> &flows,
|
||||
const std::vector<RemillPcodeOp> &ops) {
|
||||
// So we already know the op fallsthrough
|
||||
return ExtractNonConditionalCategory(
|
||||
flows, ops,
|
||||
[](const Flow &flow, const RemillPcodeOp &op)
|
||||
-> std::optional<Instruction::InstructionFlowCategory> {
|
||||
if (flow.context) {
|
||||
Instruction::NormalInsn norm(
|
||||
Instruction::FallthroughFlow(*flow.context));
|
||||
return norm;
|
||||
}
|
||||
DLOG(ERROR) << "Normal does not have context";
|
||||
return std::nullopt;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static ControlFlowStructureAnalysis::SleighDecodingResult
|
||||
ExtractAbnormal(const std::vector<Flow> &flows,
|
||||
const std::vector<RemillPcodeOp> &ops) {
|
||||
return ExtractNonConditionalCategory(
|
||||
flows, ops,
|
||||
[](const Flow &flow, const RemillPcodeOp &op)
|
||||
-> std::optional<Instruction::InstructionFlowCategory> {
|
||||
auto res = AbnormalCategoryOfFlow(flow, op);
|
||||
if (res) {
|
||||
return variant_cast(*res);
|
||||
}
|
||||
return std::nullopt;
|
||||
});
|
||||
}
|
||||
|
||||
static ControlFlowStructureAnalysis::SleighDecodingResult
|
||||
ExtractConditionalAbnormal(const std::vector<Flow> &flows,
|
||||
const std::vector<RemillPcodeOp> &ops) {
|
||||
if (flows.size() != 2) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto &first_flow = flows[0];
|
||||
const auto &snd_flow = flows[1];
|
||||
|
||||
// Two case sto handle here either conditional_fallthrough->abnormal
|
||||
// Or conditional_abnormal -> fallthrough
|
||||
|
||||
|
||||
if (!isConditionalNormal(first_flow.flow) &&
|
||||
!isConditionalAbnormal(first_flow.flow)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto flip_cond = isConditionalNormal(first_flow.flow);
|
||||
const auto &abnormal_flow =
|
||||
isConditionalNormal(first_flow.flow) ? snd_flow : first_flow;
|
||||
const auto &normal_flow =
|
||||
isConditionalNormal(first_flow.flow) ? first_flow : snd_flow;
|
||||
// so here we know the first flow is conditional of some sort and it should be followed by some unconditonal flow
|
||||
CHECK(isUnconditionalAbnormal(snd_flow.flow) ||
|
||||
isUnconditionalNormal(snd_flow.flow));
|
||||
|
||||
const auto &cond_insn = ops[first_flow.pcode_index];
|
||||
|
||||
CHECK(cond_insn.op == CPUI_CBRANCH);
|
||||
|
||||
BranchTakenVar taken_var = {
|
||||
flip_cond,
|
||||
cond_insn.vars[1],
|
||||
first_flow.pcode_index,
|
||||
};
|
||||
|
||||
|
||||
if (!normal_flow.context) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto normal_context = *normal_flow.context;
|
||||
|
||||
auto abnormal_part =
|
||||
AbnormalCategoryOfFlow(abnormal_flow, ops[abnormal_flow.pcode_index]);
|
||||
|
||||
|
||||
if (!abnormal_part) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Instruction::ConditionalInstruction cond(
|
||||
*abnormal_part, Instruction::FallthroughFlow(normal_context));
|
||||
|
||||
return {{cond, taken_var}};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool ControlFlowStructureAnalysis::isControlFlowPcodeOp(OpCode opc) {
|
||||
return opc == OpCode::CPUI_BRANCH || opc == OpCode::CPUI_CBRANCH ||
|
||||
opc == OpCode::CPUI_CALL || opc == OpCode::CPUI_BRANCHIND ||
|
||||
opc == OpCode::CPUI_CALLIND || opc == OpCode::CPUI_RETURN;
|
||||
}
|
||||
|
||||
|
||||
// Since wre aren't supporting internal control flow right now we can categorize based on the first outgoing flow
|
||||
// The only subtlety here really is allowing for conditional normals where we cbranch [fallthrough_addr] and then potentially fallthrough
|
||||
|
||||
/*
|
||||
So in a coarse grained way we can just treat indirect/direct/interprocedural flows as the same thing and losely classify these as "abnormal" or non-fallthrough.
|
||||
Either a fallthrough or an abnormal flow can be conditional
|
||||
|
||||
So the first step is to categorize a coarse grained control flow category which is one of:
|
||||
- Normal, in this case there are only fallthroughs, conditional or otherwise
|
||||
- Conditional Abnormal: either we have a CONDITIONAL_FALLTHROUGH followed by an kAbnormal
|
||||
- or we have a CONDITIONAL_ABNORMAL followed by a FALLTHROUGH
|
||||
- Abnormal: we have many ABNORMALs conditional or otherwise
|
||||
|
||||
We forbid multiple conditionals in a flow because then we'd need to join conditions
|
||||
|
||||
After we find coarse categories and the flows follow these patterns, we determine if there is a constant context for each relevant flow.
|
||||
|
||||
Finally we pass these coarse flows to a final categorizer to attempt to print these into a flow type
|
||||
*/
|
||||
|
||||
ControlFlowStructureAnalysis::SleighDecodingResult
|
||||
ControlFlowStructureAnalysis::ComputeCategory(
|
||||
const std::vector<RemillPcodeOp> &ops, uint64_t fallthrough_addr,
|
||||
DecodingContext entry_context) {
|
||||
|
||||
auto maybe_cc = CoarseFlows(ops, fallthrough_addr);
|
||||
if (!maybe_cc) {
|
||||
DLOG(ERROR) << "No coarse flow found";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto cc = std::move(*maybe_cc);
|
||||
|
||||
auto maybe_ccategory = CoarseCategoryFromFlows(cc);
|
||||
if (!maybe_ccategory) {
|
||||
DLOG(ERROR) << "No coarse category found";
|
||||
return std::nullopt;
|
||||
}
|
||||
auto context_updater = this->BuildContextUpdater(std::move(entry_context));
|
||||
auto flows = GetBoundContextsForFlows(ops, cc, context_updater);
|
||||
|
||||
switch (*maybe_ccategory) {
|
||||
case CoarseCategory::kCatAbnormal: return ExtractAbnormal(flows, ops);
|
||||
case CoarseCategory::kCatConditionalAbnormal:
|
||||
return ExtractConditionalAbnormal(flows, ops);
|
||||
case CoarseCategory::kCatNormal: return ExtractNormal(flows, ops);
|
||||
}
|
||||
}
|
||||
|
||||
// Applies a pcode op to the held context, this may produce a complete context
|
||||
void ContextUpdater::ApplyPcodeOp(const RemillPcodeOp &op) {
|
||||
if (!op.outvar) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto out = *op.outvar;
|
||||
auto reg_name = this->engine.getRegisterName(out.space, out.offset, out.size);
|
||||
auto maybe_remill_reg_name = this->context_reg_mapping.find(reg_name);
|
||||
if (maybe_remill_reg_name == this->context_reg_mapping.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto remill_reg_name = maybe_remill_reg_name->second;
|
||||
|
||||
if (op.op == CPUI_COPY && isVarnodeInConstantSpace(op.vars[0])) {
|
||||
this->curr_context.UpdateContextReg(remill_reg_name, op.vars[0].offset);
|
||||
} else {
|
||||
this->curr_context.DropReg(remill_reg_name);
|
||||
}
|
||||
}
|
||||
|
||||
// May have a complete context
|
||||
std::optional<DecodingContext> ContextUpdater::GetContext() const {
|
||||
for (const auto &[_, remill_reg] : this->context_reg_mapping) {
|
||||
if (!this->curr_context.HasValueForReg(remill_reg)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return this->curr_context;
|
||||
}
|
||||
|
||||
ContextUpdater ControlFlowStructureAnalysis::BuildContextUpdater(
|
||||
DecodingContext initial_context) {
|
||||
return ContextUpdater(this->context_reg_mapping, std::move(initial_context),
|
||||
this->engine);
|
||||
}
|
||||
|
||||
ContextUpdater::ContextUpdater(
|
||||
const std::unordered_map<std::string, std::string> &context_reg_mapping,
|
||||
DecodingContext initial_context, Sleigh &engine_)
|
||||
: context_reg_mapping(context_reg_mapping),
|
||||
curr_context(std::move(initial_context)),
|
||||
engine(engine_) {}
|
||||
|
||||
|
||||
ControlFlowStructureAnalysis::ControlFlowStructureAnalysis(
|
||||
const std::unordered_map<std::string, std::string> ®ister_mapping_,
|
||||
Sleigh &engine_)
|
||||
: context_reg_mapping(register_mapping_),
|
||||
engine(engine_) {}
|
||||
|
||||
|
||||
} // namespace remill::sleigh
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <remill/Arch/Instruction.h>
|
||||
#include <remill/BC/SleighLifter.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sleigh/libsleigh.hh>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace remill::sleigh {
|
||||
|
||||
bool isVarnodeInConstantSpace(VarnodeData vnode);
|
||||
|
||||
|
||||
struct RemillPcodeOp {
|
||||
OpCode op;
|
||||
std::optional<VarnodeData> outvar;
|
||||
std::vector<VarnodeData> vars;
|
||||
};
|
||||
|
||||
/// A context updates a context if the target PcodeOp updates the context. if it is non constant it drops the context
|
||||
class ContextUpdater {
|
||||
private:
|
||||
const std::unordered_map<std::string, std::string> &context_reg_mapping;
|
||||
DecodingContext curr_context;
|
||||
Sleigh &engine;
|
||||
|
||||
public:
|
||||
ContextUpdater(
|
||||
const std::unordered_map<std::string, std::string> &context_reg_mapping,
|
||||
DecodingContext initial_context, Sleigh &engine_);
|
||||
|
||||
// Applies a pcode op to the held context, this may produce a complete context
|
||||
void ApplyPcodeOp(const RemillPcodeOp &op);
|
||||
|
||||
// May have a complete context
|
||||
std::optional<DecodingContext> GetContext() const;
|
||||
};
|
||||
|
||||
class ControlFlowStructureAnalysis {
|
||||
|
||||
private:
|
||||
const std::unordered_map<std::string, std::string> &context_reg_mapping;
|
||||
Sleigh &engine;
|
||||
|
||||
|
||||
ContextUpdater BuildContextUpdater(DecodingContext initial_context);
|
||||
|
||||
public:
|
||||
using SleighDecodingResult =
|
||||
std::optional<std::pair<Instruction::InstructionFlowCategory,
|
||||
std::optional<BranchTakenVar>>>;
|
||||
ControlFlowStructureAnalysis(
|
||||
const std::unordered_map<std::string, std::string> &context_reg_mapping,
|
||||
Sleigh &engine);
|
||||
|
||||
static bool isControlFlowPcodeOp(OpCode opc);
|
||||
|
||||
SleighDecodingResult ComputeCategory(const std::vector<RemillPcodeOp> &ops,
|
||||
uint64_t fallthrough_addr,
|
||||
DecodingContext entry_context);
|
||||
};
|
||||
} // namespace remill::sleigh
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <remill/Arch/AArch32/AArch32Base.h>
|
||||
#include <remill/Arch/AArch32/Runtime/State.h>
|
||||
#include <remill/Arch/Name.h>
|
||||
#include <remill/BC/ABI.h>
|
||||
#include <remill/BC/Util.h>
|
||||
#include <remill/BC/Version.h>
|
||||
#include <remill/OS/OS.h>
|
||||
|
||||
#include "Arch.h"
|
||||
|
||||
|
||||
namespace remill {
|
||||
|
||||
namespace sleighthumb2 {
|
||||
class SleighThumb2Decoder final : public remill::sleigh::SleighDecoder {
|
||||
public:
|
||||
SleighThumb2Decoder(const remill::Arch &arch);
|
||||
|
||||
|
||||
virtual llvm::Value *LiftPcFromCurrPc(llvm::IRBuilder<> &bldr, llvm::Value *,
|
||||
size_t curr_insn_size) const final;
|
||||
|
||||
void InitializeSleighContext(
|
||||
remill::sleigh::SingleInstructionSleighContext &ctxt) const final;
|
||||
};
|
||||
} // namespace sleighthumb2
|
||||
} // namespace remill
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include <remill/Arch/AArch32/AArch32Base.h>
|
||||
#include <remill/Arch/AArch32/ArchContext.h>
|
||||
#include <remill/Arch/AArch32/Runtime/State.h>
|
||||
#include <remill/Arch/Name.h>
|
||||
#include <remill/BC/ABI.h>
|
||||
@@ -24,54 +25,92 @@
|
||||
#include <remill/OS/OS.h>
|
||||
|
||||
#include "Arch.h"
|
||||
#include "Thumb.h"
|
||||
|
||||
namespace remill {
|
||||
namespace sleighthumb2 {
|
||||
namespace {
|
||||
const size_t kThumbInstructionSize = 2;
|
||||
}
|
||||
|
||||
//ARM7_le.sla"
|
||||
class SleighThumb2Arch final : public remill::sleigh::SleighArch,
|
||||
public remill::AArch32ArchBase {
|
||||
SleighThumb2Decoder::SleighThumb2Decoder(const remill::Arch &arch)
|
||||
: SleighDecoder(arch, "ARM7_le.sla", "ARMtTHUMB.pspec",
|
||||
{{"ISAModeSwitch", std::string(kThumbModeRegName)}},
|
||||
{{"CY", "C"}, {"NG", "N"}, {"ZR", "Z"}, {"OV", "V"}}) {}
|
||||
|
||||
|
||||
void SleighThumb2Decoder::InitializeSleighContext(
|
||||
remill::sleigh::SingleInstructionSleighContext &ctxt) const {
|
||||
ctxt.GetContext().setVariableDefault("TMode", 1);
|
||||
}
|
||||
|
||||
llvm::Value *
|
||||
SleighThumb2Decoder::LiftPcFromCurrPc(llvm::IRBuilder<> &bldr,
|
||||
llvm::Value *curr_pc,
|
||||
size_t curr_insn_size) const {
|
||||
|
||||
// PC on thumb points to the next instructions next.
|
||||
return bldr.CreateAdd(
|
||||
curr_pc,
|
||||
llvm::ConstantInt::get(curr_pc->getType(), kThumbInstructionSize * 2));
|
||||
}
|
||||
|
||||
//TODO(Ian): this has code duplication with SleighX86Arch couldnt come up with a way to share implementation and not run into more
|
||||
// annoying virtual inheretance from remill Arch. If we go back to virtual Arch then maybe we could just add another virtual inheratance of
|
||||
// Arch. All of these are bad tho.
|
||||
class SleighThumbArch : public AArch32ArchBase {
|
||||
public:
|
||||
SleighThumb2Arch(llvm::LLVMContext *context_, OSName os_name_,
|
||||
ArchName arch_name_)
|
||||
SleighThumbArch(llvm::LLVMContext *context_, OSName os_name_,
|
||||
ArchName arch_name_)
|
||||
: ArchBase(context_, os_name_, arch_name_),
|
||||
SleighArch(context_, os_name_, arch_name_, "ARM7_le.sla",
|
||||
"ARMtTHUMB.pspec"),
|
||||
AArch32ArchBase(context_, os_name_, arch_name_) {}
|
||||
AArch32ArchBase(context_, os_name_, arch_name_),
|
||||
|
||||
decoder(*this) {}
|
||||
|
||||
virtual DecodingContext CreateInitialContext(void) const override {
|
||||
return DecodingContext().PutContextReg(std::string(kThumbModeRegName), 1);
|
||||
}
|
||||
|
||||
virtual OperandLifter::OpLifterPtr
|
||||
DefaultLifter(const remill::IntrinsicTable &intrinsics) const override {
|
||||
return this->decoder.GetOpLifter();
|
||||
}
|
||||
|
||||
virtual bool DecodeInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const override {
|
||||
return decoder.DecodeInstruction(address, instr_bytes, inst, context);
|
||||
}
|
||||
|
||||
|
||||
uint64_t MaxInstructionSize(bool permit_fuse_idioms) const final {
|
||||
// TODO(pag): Eventually handle Thumb2 and unaligned addresses.
|
||||
uint64_t MinInstructionAlign(const DecodingContext &) const override {
|
||||
return 2;
|
||||
}
|
||||
|
||||
uint64_t MinInstructionSize(const DecodingContext &) const override {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Maximum number of bytes in an instruction for this particular architecture.
|
||||
uint64_t MaxInstructionSize(const DecodingContext &, bool) const override {
|
||||
return 4;
|
||||
}
|
||||
|
||||
uint64_t MinInstructionSize(void) const final {
|
||||
return 2;
|
||||
}
|
||||
|
||||
uint64_t MinInstructionAlign(void) const final {
|
||||
return 2;
|
||||
}
|
||||
|
||||
void InitializeSleighContext(
|
||||
remill::sleigh::SingleInstructionSleighContext &ctxt) const final {
|
||||
ctxt.GetContext().setVariableDefault("TMode", 1);
|
||||
}
|
||||
|
||||
llvm::Triple Triple(void) const final {
|
||||
auto triple = BasicTriple();
|
||||
triple.setArch(llvm::Triple::thumb);
|
||||
triple.setOS(llvm::Triple::OSType::Linux);
|
||||
triple.setVendor(llvm::Triple::VendorType::UnknownVendor);
|
||||
return triple;
|
||||
}
|
||||
private:
|
||||
SleighThumb2Decoder decoder;
|
||||
};
|
||||
|
||||
|
||||
} // namespace sleighthumb2
|
||||
|
||||
Arch::ArchPtr Arch::GetSleighThumb2(llvm::LLVMContext *context_,
|
||||
OSName os_name_, ArchName arch_name_) {
|
||||
return std::make_unique<sleighthumb2::SleighThumb2Arch>(context_, os_name_,
|
||||
arch_name_);
|
||||
remill::OSName os_name_,
|
||||
remill::ArchName arch_name_) {
|
||||
return std::make_unique<sleighthumb2::SleighThumbArch>(context_, os_name_,
|
||||
arch_name_);
|
||||
}
|
||||
|
||||
// this->sleigh_ctx.GetEngine().setContextDefault("TMode", 1);
|
||||
|
||||
} // namespace remill
|
||||
|
||||
@@ -32,22 +32,56 @@
|
||||
namespace remill {
|
||||
namespace sleigh::x86 {
|
||||
|
||||
class SleighX86Arch final : public SleighArch, public remill::X86ArchBase {
|
||||
class SleighX86Decoder final : public SleighDecoder {
|
||||
public:
|
||||
SleighX86Decoder() = delete;
|
||||
SleighX86Decoder(const remill::Arch &arch)
|
||||
: SleighDecoder(
|
||||
arch, kArchX86_SLEIGH == arch.arch_name ? "x86.sla" : "x86-64.sla",
|
||||
kArchX86_SLEIGH == arch.arch_name ? "x86.pspec" : "x86-64.pspec",
|
||||
{}, {}) {}
|
||||
|
||||
// The x86 default context is sufficient. No context register assignments are required.
|
||||
void InitializeSleighContext(
|
||||
remill::sleigh::SingleInstructionSleighContext &ctxt) const override {}
|
||||
|
||||
llvm::Value *LiftPcFromCurrPc(llvm::IRBuilder<> &bldr, llvm::Value *curr_pc,
|
||||
size_t curr_insn_size) const final {
|
||||
|
||||
// PC on thumb points to the next instructions next.
|
||||
return bldr.CreateAdd(
|
||||
curr_pc, llvm::ConstantInt::get(curr_pc->getType(), curr_insn_size));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class SleighX86Arch : public X86ArchBase {
|
||||
public:
|
||||
SleighX86Arch(llvm::LLVMContext *context_, OSName os_name_,
|
||||
ArchName arch_name_)
|
||||
: ArchBase(context_, os_name_, arch_name_),
|
||||
SleighArch(
|
||||
context_, os_name_, arch_name_,
|
||||
kArchX86_SLEIGH == arch_name_ ? "x86.sla" : "x86-64.sla",
|
||||
kArchX86_SLEIGH == arch_name_ ? "x86.pspec" : "x86-64.pspec"),
|
||||
X86ArchBase(context_, os_name_, arch_name_) {}
|
||||
X86ArchBase(context_, os_name_, arch_name_),
|
||||
decoder(*this) {}
|
||||
|
||||
virtual ~SleighX86Arch(void) = default;
|
||||
virtual DecodingContext CreateInitialContext(void) const override {
|
||||
return DecodingContext();
|
||||
}
|
||||
|
||||
void InitializeSleighContext(
|
||||
remill::sleigh::SingleInstructionSleighContext &ctxt) const override {}
|
||||
virtual OperandLifter::OpLifterPtr
|
||||
DefaultLifter(const remill::IntrinsicTable &intrinsics) const override {
|
||||
return this->decoder.GetOpLifter();
|
||||
}
|
||||
|
||||
virtual bool DecodeInstruction(uint64_t address, std::string_view instr_bytes,
|
||||
Instruction &inst,
|
||||
DecodingContext context) const override {
|
||||
return decoder.DecodeInstruction(address, instr_bytes, inst, context);
|
||||
}
|
||||
|
||||
private:
|
||||
SleighX86Decoder decoder;
|
||||
};
|
||||
|
||||
} // namespace sleigh::x86
|
||||
Arch::ArchPtr Arch::GetSleighX86(llvm::LLVMContext *context_, OSName os_name_,
|
||||
ArchName arch_name_) {
|
||||
|
||||
@@ -22,15 +22,15 @@ std::string_view X86ArchBase::ProgramCounterRegisterName(void) const {
|
||||
}
|
||||
|
||||
|
||||
uint64_t X86ArchBase::MinInstructionAlign(void) const {
|
||||
uint64_t X86ArchBase::MinInstructionAlign(const DecodingContext &) const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint64_t X86ArchBase::MinInstructionSize(void) const {
|
||||
uint64_t X86ArchBase::MinInstructionSize(const DecodingContext &) const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint64_t X86ArchBase::MaxInstructionSize(bool) const {
|
||||
uint64_t X86ArchBase::MaxInstructionSize(const DecodingContext &, bool) const {
|
||||
return 15;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,9 +74,9 @@ InstructionLifter::InstructionLifter(const Arch *arch_,
|
||||
|
||||
// Lift a single instruction into a basic block. `is_delayed` signifies that
|
||||
// this instruction will execute within the delay slot of another instruction.
|
||||
LiftStatus InstructionLifter::LiftIntoBlock(Instruction &inst,
|
||||
llvm::BasicBlock *block,
|
||||
bool is_delayed) {
|
||||
LiftStatus InstructionLifterIntf::LiftIntoBlock(Instruction &inst,
|
||||
llvm::BasicBlock *block,
|
||||
bool is_delayed) {
|
||||
return LiftIntoBlock(inst, block,
|
||||
NthArgument(block->getParent(), kStatePointerArgNum),
|
||||
is_delayed);
|
||||
|
||||
+324
-126
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include <lib/Arch/Sleigh/Arch.h>
|
||||
#include <lib/Arch/Sleigh/ControlFlowStructuring.h>
|
||||
#include <remill/BC/ABI.h>
|
||||
#include <remill/BC/IntrinsicTable.h>
|
||||
#include <remill/BC/SleighLifter.h>
|
||||
@@ -27,8 +28,41 @@
|
||||
|
||||
namespace remill {
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
void print_vardata(Sleigh &engine, std::stringstream &s, VarnodeData &data) {
|
||||
s << '(' << data.space->getName() << ',';
|
||||
data.space->printOffset(s, data.offset);
|
||||
s << ',' << dec << data.size << ')';
|
||||
auto maybe_name = engine.getRegisterName(data.space, data.offset, data.size);
|
||||
if (!maybe_name.empty()) {
|
||||
s << ":" << maybe_name;
|
||||
}
|
||||
}
|
||||
std::string DumpPcode(Sleigh &engine, const remill::sleigh::RemillPcodeOp &op) {
|
||||
std::stringstream ss;
|
||||
ss << get_opname(op.op);
|
||||
if (op.outvar) {
|
||||
auto ov = *op.outvar;
|
||||
print_vardata(engine, ss, ov);
|
||||
ss << " = ";
|
||||
}
|
||||
for (size_t i = 0; i < op.vars.size(); ++i) {
|
||||
auto iv = op.vars[i];
|
||||
print_vardata(engine, ss, iv);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static size_t kBranchTakenArgNum = 2;
|
||||
static size_t kNextPcArgNum = 3;
|
||||
|
||||
static bool isFloatOp(OpCode opc) {
|
||||
return opc >= OpCode::CPUI_FLOAT_EQUAL && opc <= OpCode::CPUI_FLOAT_ROUND;
|
||||
}
|
||||
|
||||
static const std::string kEqualityClaimName = "claim_eq";
|
||||
|
||||
static bool isVarnodeInConstantSpace(VarnodeData vnode) {
|
||||
@@ -195,8 +229,12 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
this->cached_unique_ptrs.end()) {
|
||||
return this->cached_unique_ptrs.find(offset)->second;
|
||||
}
|
||||
auto ptr = bldr.CreateAlloca(
|
||||
llvm::IntegerType::get(this->context, 8 * size), 0, nullptr);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "unique_" << std::hex << offset << ":" << std::dec << size;
|
||||
auto ptr =
|
||||
bldr.CreateAlloca(llvm::IntegerType::get(this->context, 8 * size), 0,
|
||||
nullptr, ss.str());
|
||||
this->cached_unique_ptrs.insert({offset, ptr});
|
||||
return ptr;
|
||||
}
|
||||
@@ -230,8 +268,8 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
this->current_replacements.end()) {
|
||||
|
||||
if (this->used_values.find(target.offset) != this->used_values.end()) {
|
||||
LOG(FATAL) << "Ambigous value substitution via claim eq: "
|
||||
<< target.offset;
|
||||
DLOG(ERROR) << "Ambigous value substitution via claim eq: "
|
||||
<< target.offset;
|
||||
}
|
||||
auto replacement = this->current_replacements.find(target.offset)
|
||||
->second->LiftAsInParam(bldr, target_type);
|
||||
@@ -257,19 +295,23 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
|
||||
llvm::BasicBlock *exit_block;
|
||||
|
||||
size_t curr_id;
|
||||
|
||||
const std::optional<remill::sleigh::BranchTakenVar> &to_lift_btaken;
|
||||
|
||||
void UpdateStatus(LiftStatus new_status, OpCode opc) {
|
||||
if (new_status != LiftStatus::kLiftedInstruction) {
|
||||
this->status = new_status;
|
||||
LOG(ERROR) << "Failed to lift insn with opcode: " << get_opname(opc);
|
||||
DLOG(ERROR) << "Failed to lift insn with opcode: " << get_opname(opc);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
PcodeToLLVMEmitIntoBlock(llvm::BasicBlock *target_block,
|
||||
llvm::Value *state_pointer, const Instruction &insn,
|
||||
SleighLifter &insn_lifter_parent,
|
||||
std::vector<std::string> user_op_names_,
|
||||
llvm::BasicBlock *exit_block_)
|
||||
PcodeToLLVMEmitIntoBlock(
|
||||
llvm::BasicBlock *target_block, llvm::Value *state_pointer,
|
||||
const Instruction &insn, SleighLifter &insn_lifter_parent,
|
||||
std::vector<std::string> user_op_names_, llvm::BasicBlock *exit_block_,
|
||||
const std::optional<remill::sleigh::BranchTakenVar> &to_lift_btaken_)
|
||||
: target_block(target_block),
|
||||
state_pointer(state_pointer),
|
||||
context(target_block->getContext()),
|
||||
@@ -279,7 +321,9 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
uniques(target_block->getContext()),
|
||||
unknown_regs(target_block->getContext()),
|
||||
user_op_names(user_op_names_),
|
||||
exit_block(exit_block_) {}
|
||||
exit_block(exit_block_),
|
||||
curr_id(0),
|
||||
to_lift_btaken(to_lift_btaken_) {}
|
||||
|
||||
|
||||
ParamPtr CreateMemoryAddress(llvm::Value *offset) {
|
||||
@@ -298,7 +342,13 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
for (auto &c : reg_name) {
|
||||
c = toupper(c);
|
||||
}
|
||||
const auto &remappings =
|
||||
this->insn_lifter_parent.decoder.GetStateRegRemappings();
|
||||
|
||||
if (auto el = remappings.find(reg_name); el != remappings.end()) {
|
||||
DLOG(INFO) << "Remapping to " << el->second;
|
||||
reg_name = el->second;
|
||||
}
|
||||
|
||||
if (this->insn_lifter_parent.ArchHasRegByName(reg_name)) {
|
||||
// TODO(Ian): will probably need to adjust the pointer here in certain circumstances
|
||||
@@ -317,6 +367,10 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
return *res;
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
print_vardata(this->insn_lifter_parent.GetEngine(), ss, target_vnode);
|
||||
DLOG(ERROR) << "Creating unique for unkown register: " << ss.str();
|
||||
|
||||
return RegisterValue::CreatRegister(this->unknown_regs.GetUniquePtr(
|
||||
target_vnode.offset, target_vnode.size, bldr));
|
||||
}
|
||||
@@ -339,8 +393,8 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
auto reg_name = this->insn_lifter_parent.GetEngine().getRegisterName(
|
||||
vnode.space, vnode.offset, vnode.size);
|
||||
|
||||
LOG(INFO) << "Looking for reg name " << reg_name << " from offset "
|
||||
<< vnode.offset;
|
||||
DLOG(INFO) << "Looking for reg name " << reg_name << " from offset "
|
||||
<< vnode.offset;
|
||||
return this->LiftNormalRegisterOrCreateUnique(bldr, reg_name, vnode);
|
||||
} else if (space_name == "const") {
|
||||
|
||||
@@ -359,7 +413,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
|
||||
llvm::Value *FixResultForOutVarnode(llvm::IRBuilder<> &bldr,
|
||||
llvm::Value *orig, VarnodeData outvnode) {
|
||||
assert(orig->getType()->isIntegerTy());
|
||||
CHECK(orig->getType()->isIntegerTy());
|
||||
auto out_bits = outvnode.size * 8;
|
||||
if (out_bits == orig->getType()->getIntegerBitWidth()) {
|
||||
return orig;
|
||||
@@ -424,11 +478,10 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
|
||||
LiftStatus RedirectControlFlow(llvm::IRBuilder<> &bldr,
|
||||
llvm::Value *target_addr) {
|
||||
auto pc_reg = this->LiftNormalRegister(bldr, "PC");
|
||||
CHECK(pc_reg.has_value());
|
||||
auto res = (*pc_reg)->StoreIntoParam(bldr, target_addr);
|
||||
|
||||
bldr.CreateStore(target_addr, this->GetNextPcRef());
|
||||
this->TerminateBlock();
|
||||
return res;
|
||||
return LiftStatus::kLiftedInstruction;
|
||||
}
|
||||
|
||||
LiftStatus LiftUnaryOp(llvm::IRBuilder<> &bldr, OpCode opc,
|
||||
@@ -438,13 +491,17 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
|
||||
switch (opc) {
|
||||
case OpCode::CPUI_BOOL_NEGATE: {
|
||||
auto bneg_inval = this->LiftInParam(
|
||||
bldr, input_var, llvm::IntegerType::get(this->context, 1));
|
||||
auto byte_type = llvm::IntegerType::get(this->context, 8);
|
||||
auto bneg_inval = this->LiftInParam(bldr, input_var, byte_type);
|
||||
;
|
||||
if (bneg_inval.has_value()) {
|
||||
// TODO(Ian): is there a more optimization friendly way to get logical not on a byte?
|
||||
return this->LiftStoreIntoOutParam(
|
||||
bldr,
|
||||
bldr.CreateZExt(bldr.CreateNot(*bneg_inval),
|
||||
llvm::IntegerType::get(this->context, 8)),
|
||||
bldr.CreateZExt(
|
||||
bldr.CreateICmpEQ(*bneg_inval,
|
||||
llvm::ConstantInt::get(byte_type, 0)),
|
||||
byte_type),
|
||||
outvar);
|
||||
}
|
||||
break;
|
||||
@@ -465,7 +522,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
// directs dont read the address of the variable, the offset is the jump
|
||||
// TODO(Ian): handle other address spaces
|
||||
if (isVarnodeInConstantSpace(input_var)) {
|
||||
LOG(ERROR) << "Internal control flow not supported";
|
||||
DLOG(ERROR) << "Internal control flow not supported";
|
||||
return LiftStatus::kLiftedUnsupportedInstruction;
|
||||
}
|
||||
|
||||
@@ -615,6 +672,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
using BinaryOperator = std::function<llvm::Value *(
|
||||
llvm::Value *, llvm::Value *, llvm::IRBuilder<> &)>;
|
||||
static std::map<OpCode, BinaryOperator> INTEGER_BINARY_OPS;
|
||||
static std::map<OpCode, BinaryOperator> BOOL_BINARY_OPS;
|
||||
static std::unordered_set<OpCode> INTEGER_COMP_OPS;
|
||||
|
||||
|
||||
@@ -645,7 +703,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
}
|
||||
|
||||
if (isVarnodeInConstantSpace(lhs)) {
|
||||
LOG(ERROR) << "Internal control flow not supported";
|
||||
DLOG(ERROR) << "Internal control flow not supported";
|
||||
return LiftStatus::kLiftedUnsupportedInstruction;
|
||||
}
|
||||
|
||||
@@ -654,8 +712,9 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
auto jump_addr = this->replacement_cont.LiftOffsetOrReplace(
|
||||
bldr, lhs, llvm::IntegerType::get(this->context, lhs.size * 8));
|
||||
|
||||
// TODO(Ian): this should probably technically be != 0
|
||||
auto trunc_should_branch = bldr.CreateTrunc(
|
||||
*should_branch, llvm::IntegerType::get(this->context, rhs.size * 1));
|
||||
*should_branch, llvm::IntegerType::get(this->context, 1));
|
||||
|
||||
|
||||
auto pc_reg_param = this->LiftNormalRegister(bldr, "PC");
|
||||
@@ -664,19 +723,11 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
auto orig_pc_value =
|
||||
pc_reg_ptr->LiftAsInParam(bldr, this->insn_lifter_parent.GetWordType());
|
||||
|
||||
|
||||
if (this->insn.category ==
|
||||
remill::Instruction::Category::kCategoryConditionalBranch) {
|
||||
auto branch_taken_ref = LoadBranchTakenRef(bldr.GetInsertBlock());
|
||||
bldr.CreateStore(*should_branch, branch_taken_ref);
|
||||
}
|
||||
if (orig_pc_value.has_value()) {
|
||||
auto next_pc_value =
|
||||
bldr.CreateSelect(trunc_should_branch, jump_addr, *orig_pc_value);
|
||||
auto res = pc_reg_ptr->StoreIntoParam(bldr, next_pc_value);
|
||||
if (LiftStatus::kLiftedInstruction != res) {
|
||||
return res;
|
||||
}
|
||||
|
||||
bldr.CreateStore(next_pc_value, this->GetNextPcRef());
|
||||
}
|
||||
|
||||
return this->TerminateBlockWithCondition(trunc_should_branch);
|
||||
@@ -692,10 +743,10 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
auto lifted_lhs = this->LiftIntegerInParam(bldr, lhs);
|
||||
auto lifted_rhs = this->LiftIntegerInParam(bldr, rhs);
|
||||
if (lifted_lhs.has_value() && lifted_rhs.has_value()) {
|
||||
LOG(INFO) << "Binop with lhs: "
|
||||
<< remill::LLVMThingToString(*lifted_lhs);
|
||||
LOG(INFO) << "Binop with rhs: "
|
||||
<< remill::LLVMThingToString(*lifted_rhs);
|
||||
DLOG(INFO) << "Binop with lhs: "
|
||||
<< remill::LLVMThingToString(*lifted_lhs);
|
||||
DLOG(INFO) << "Binop with rhs: "
|
||||
<< remill::LLVMThingToString(*lifted_rhs);
|
||||
auto orig_res = op_func(*lifted_lhs, *lifted_rhs, bldr);
|
||||
if (INTEGER_COMP_OPS.find(opc) != INTEGER_COMP_OPS.end()) {
|
||||
// Comparison operators always return a byte
|
||||
@@ -704,9 +755,9 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
orig_res, llvm::IntegerType::get(bldr.getContext(), 8));
|
||||
}
|
||||
}
|
||||
LOG(INFO) << "Res: " << remill::LLVMThingToString(orig_res);
|
||||
LOG(INFO) << "Res ty: "
|
||||
<< remill::LLVMThingToString(orig_res->getType());
|
||||
DLOG(INFO) << "Res: " << remill::LLVMThingToString(orig_res);
|
||||
DLOG(INFO) << "Res ty: "
|
||||
<< remill::LLVMThingToString(orig_res->getType());
|
||||
return this->LiftStoreIntoOutParam(bldr, orig_res, outvar);
|
||||
}
|
||||
}
|
||||
@@ -714,27 +765,15 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
}
|
||||
|
||||
|
||||
std::optional<llvm::Value *>
|
||||
GetResultOfBoolOpFunc(llvm::IRBuilder<> &bldr, OpCode opc,
|
||||
llvm::Value *in_lhs, llvm::Value *in_rhs) {
|
||||
switch (opc) {
|
||||
case CPUI_BOOL_AND: {
|
||||
return bldr.CreateAnd(in_lhs, in_rhs);
|
||||
}
|
||||
case CPUI_BOOL_OR: {
|
||||
|
||||
return bldr.CreateOr(in_lhs, in_rhs);
|
||||
}
|
||||
case CPUI_BOOL_XOR: {
|
||||
return bldr.CreateXor(in_lhs, in_rhs);
|
||||
}
|
||||
default: return std::nullopt;
|
||||
}
|
||||
}
|
||||
LiftStatus LiftBoolBinOp(llvm::IRBuilder<> &bldr, OpCode opc,
|
||||
VarnodeData *outvar, VarnodeData lhs,
|
||||
VarnodeData rhs) {
|
||||
|
||||
// We make sure to only attempt to lift params for operands where we know they are booleans
|
||||
// Otherwise lifting a value as a byte could be an incorrect size for something like a unique.
|
||||
if (this->BOOL_BINARY_OPS.find(opc) == this->BOOL_BINARY_OPS.end()) {
|
||||
return LiftStatus::kLiftedUnsupportedInstruction;
|
||||
}
|
||||
|
||||
auto lifted_lhs =
|
||||
this->LiftInParam(bldr, lhs, llvm::IntegerType::get(this->context, 8));
|
||||
@@ -745,13 +784,9 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
}
|
||||
|
||||
auto computed_value =
|
||||
this->GetResultOfBoolOpFunc(bldr, opc, *lifted_lhs, *lifted_rhs);
|
||||
if (!computed_value) {
|
||||
return LiftStatus::kLiftedUnsupportedInstruction;
|
||||
}
|
||||
this->BOOL_BINARY_OPS.find(opc)->second(*lifted_lhs, *lifted_rhs, bldr);
|
||||
|
||||
|
||||
return this->LiftStoreIntoOutParam(bldr, *computed_value, outvar);
|
||||
return this->LiftStoreIntoOutParam(bldr, computed_value, outvar);
|
||||
}
|
||||
|
||||
std::optional<BinaryOperator> FindFloatBinOpFunc(OpCode opc) {
|
||||
@@ -909,6 +944,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
bldr, lhs, llvm::IntegerType::get(this->context, lhs.size * 8));
|
||||
|
||||
if (lifted_lhs.has_value()) {
|
||||
DLOG(INFO) << "SUBPIECE: " << remill::LLVMThingToString(*lifted_lhs);
|
||||
auto new_size = lhs.size - rhs.offset;
|
||||
auto *subpiece_lhs = bldr.CreateTrunc(
|
||||
*lifted_lhs, llvm::IntegerType::get(this->context, new_size * 8));
|
||||
@@ -1039,20 +1075,56 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
LiftStatus HandleCallOther(llvm::IRBuilder<> &bldr, VarnodeData *outvar,
|
||||
VarnodeData *vars, int4 isize) {
|
||||
auto other_func_name = this->GetOtherFuncName(vars, isize);
|
||||
|
||||
if (other_func_name == kEqualityClaimName && isize == kEqualityClaimArity) {
|
||||
LOG(INFO) << "Applying eq claim";
|
||||
this->replacement_cont.ApplyEqualityClaim(bldr, *this, vars[1], vars[2]);
|
||||
return kLiftedInstruction;
|
||||
if (other_func_name.has_value()) {
|
||||
if (other_func_name == kEqualityClaimName &&
|
||||
isize == kEqualityClaimArity) {
|
||||
DLOG(INFO) << "Applying eq claim";
|
||||
this->replacement_cont.ApplyEqualityClaim(bldr, *this, vars[1],
|
||||
vars[2]);
|
||||
return kLiftedInstruction;
|
||||
}
|
||||
DLOG(ERROR) << "Unsupported pcode intrinsic: " << *other_func_name;
|
||||
}
|
||||
|
||||
return kLiftedUnsupportedInstruction;
|
||||
}
|
||||
virtual void dump(const Address &addr, OpCode opc, VarnodeData *outvar,
|
||||
VarnodeData *vars, int4 isize) final override {
|
||||
LOG(INFO) << "inner handle" << std::endl;
|
||||
llvm::IRBuilder bldr(this->target_block);
|
||||
|
||||
llvm::Argument *GetBranchTakenRef() {
|
||||
return this->exit_block->getParent()->getArg(kBranchTakenArgNum);
|
||||
}
|
||||
|
||||
llvm::Argument *GetNextPcRef() {
|
||||
return this->exit_block->getParent()->getArg(kNextPcArgNum);
|
||||
}
|
||||
|
||||
LiftStatus LiftBranchTaken(llvm::IRBuilder<> &bldr,
|
||||
const sleigh::BranchTakenVar &btaken_var) {
|
||||
|
||||
|
||||
auto maybe_should_branch =
|
||||
this->LiftIntegerInParam(bldr, btaken_var.target_vnode);
|
||||
if (!maybe_should_branch) {
|
||||
DLOG(ERROR) << "Failed to lift iparam branch taken var";
|
||||
return LiftStatus::kLiftedLifterError;
|
||||
}
|
||||
|
||||
auto should_branch = bldr.CreateZExtOrTrunc(
|
||||
*maybe_should_branch, llvm::IntegerType::get(this->context, 8));
|
||||
auto branch_taken_ref = this->GetBranchTakenRef();
|
||||
bldr.CreateStore(should_branch, branch_taken_ref);
|
||||
return LiftStatus::kLiftedInstruction;
|
||||
}
|
||||
|
||||
|
||||
void LiftBtakenIfReached(llvm::IRBuilder<> &bldr, OpCode opc) {
|
||||
|
||||
if (this->to_lift_btaken && curr_id == this->to_lift_btaken->index) {
|
||||
this->UpdateStatus(this->LiftBranchTaken(bldr, *this->to_lift_btaken),
|
||||
opc);
|
||||
}
|
||||
}
|
||||
|
||||
void LiftPcodeOp(llvm::IRBuilder<> &bldr, OpCode opc, VarnodeData *outvar,
|
||||
VarnodeData *vars, int4 isize) {
|
||||
// The MULTIEQUAL op has variadic operands
|
||||
if (opc == OpCode::CPUI_MULTIEQUAL || opc == OpCode::CPUI_CPOOLREF) {
|
||||
this->UpdateStatus(this->LiftVariadicOp(bldr, opc, outvar, vars, isize),
|
||||
@@ -1079,15 +1151,21 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit {
|
||||
this->UpdateStatus(this->LiftThreeOperandOp(bldr, opc, outvar, vars[0],
|
||||
vars[1], vars[2]),
|
||||
opc);
|
||||
break;
|
||||
|
||||
return;
|
||||
}
|
||||
default:
|
||||
//this->replacement_cont.ApplyNonEqualityClaim();
|
||||
this->UpdateStatus(LiftStatus::kLiftedUnsupportedInstruction, opc);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
//this->replacement_cont.ApplyNonEqualityClaim();
|
||||
}
|
||||
|
||||
virtual void dump(const Address &addr, OpCode opc, VarnodeData *outvar,
|
||||
VarnodeData *vars, int4 isize) final override {
|
||||
llvm::IRBuilder bldr(this->target_block);
|
||||
this->LiftBtakenIfReached(bldr, opc);
|
||||
this->LiftPcodeOp(bldr, opc, outvar, vars, isize);
|
||||
this->curr_id++;
|
||||
}
|
||||
|
||||
LiftStatus GetStatus() {
|
||||
@@ -1101,6 +1179,22 @@ std::unordered_set<OpCode>
|
||||
CPUI_INT_SLESS, CPUI_INT_LESSEQUAL, CPUI_INT_SLESSEQUAL,
|
||||
CPUI_INT_SBORROW, CPUI_INT_SCARRY, CPUI_INT_CARRY};
|
||||
|
||||
// NOTE(Ian): we store a mapping from pcode op to supported boolean operation so that we can easily check if
|
||||
// we want to lift the operands to this op as a boolean and also find the right post lifting operation to apply.
|
||||
std::map<OpCode, SleighLifter::PcodeToLLVMEmitIntoBlock::BinaryOperator>
|
||||
SleighLifter::PcodeToLLVMEmitIntoBlock::BOOL_BINARY_OPS = {
|
||||
{OpCode::CPUI_BOOL_AND,
|
||||
[](llvm::Value *lhs, llvm::Value *rhs, llvm::IRBuilder<> &bldr) {
|
||||
return bldr.CreateAnd(lhs, rhs);
|
||||
}},
|
||||
{OpCode::CPUI_BOOL_OR,
|
||||
[](llvm::Value *lhs, llvm::Value *rhs, llvm::IRBuilder<> &bldr) {
|
||||
return bldr.CreateOr(lhs, rhs);
|
||||
}},
|
||||
{OpCode::CPUI_BOOL_XOR,
|
||||
[](llvm::Value *lhs, llvm::Value *rhs, llvm::IRBuilder<> &bldr) {
|
||||
return bldr.CreateXor(lhs, rhs);
|
||||
}}};
|
||||
std::map<OpCode, SleighLifter::PcodeToLLVMEmitIntoBlock::BinaryOperator>
|
||||
SleighLifter::PcodeToLLVMEmitIntoBlock::INTEGER_BINARY_OPS = {
|
||||
{OpCode::CPUI_INT_AND,
|
||||
@@ -1131,6 +1225,9 @@ std::map<OpCode, SleighLifter::PcodeToLLVMEmitIntoBlock::BinaryOperator>
|
||||
}},
|
||||
{OpCode::CPUI_INT_SRIGHT,
|
||||
[](llvm::Value *lhs, llvm::Value *rhs, llvm::IRBuilder<> &bldr) {
|
||||
if (lhs->getType() != rhs->getType()) {
|
||||
rhs = bldr.CreateZExtOrTrunc(rhs, lhs->getType());
|
||||
}
|
||||
return bldr.CreateAShr(lhs, rhs);
|
||||
}},
|
||||
{OpCode::CPUI_INT_ADD,
|
||||
@@ -1210,13 +1307,14 @@ std::map<OpCode, SleighLifter::PcodeToLLVMEmitIntoBlock::BinaryOperator>
|
||||
}},
|
||||
};
|
||||
|
||||
SleighLifter::SleighLifter(const sleigh::SleighArch *arch_,
|
||||
SleighLifter::SleighLifter(const remill::Arch &arch_,
|
||||
const remill::sleigh::SleighDecoder &dec_,
|
||||
const IntrinsicTable &intrinsics_)
|
||||
: InstructionLifter(arch_, intrinsics_),
|
||||
: InstructionLifter(&arch_, intrinsics_),
|
||||
sleigh_context(new sleigh::SingleInstructionSleighContext(
|
||||
arch_->GetSLAName(), arch_->GetPSpec())),
|
||||
arch(arch_) {
|
||||
arch_->InitializeSleighContext(*sleigh_context);
|
||||
dec_.GetSLAName(), dec_.GetPSpec())),
|
||||
decoder(dec_) {
|
||||
this->decoder.InitializeSleighContext(*sleigh_context);
|
||||
}
|
||||
|
||||
|
||||
@@ -1230,48 +1328,82 @@ void SleighLifter::SetISelAttributes(llvm::Function *target_func) {
|
||||
target_func->addFnAttr(llvm::Attribute::AlwaysInline);
|
||||
}
|
||||
|
||||
std::pair<LiftStatus, llvm::Function *>
|
||||
SleighLifter::LiftIntoInternalBlock(Instruction &inst, llvm::Module *target_mod,
|
||||
bool is_delayed) {
|
||||
|
||||
LOG(INFO) << "Secondary lift of bytes: " << llvm::toHex(inst.bytes);
|
||||
auto target_func = inst.arch->DefineLiftedFunction(
|
||||
SleighLifter::kInstructionFunctionPrefix, target_mod);
|
||||
llvm::Function *
|
||||
SleighLifter::DefineInstructionFunction(Instruction &inst,
|
||||
llvm::Module *target_mod) {
|
||||
|
||||
std::stringstream nm;
|
||||
nm << SleighLifter::kInstructionFunctionPrefix << "_" << std::hex << inst.pc;
|
||||
auto &context = target_mod->getContext();
|
||||
auto ptr_ty = llvm::PointerType::get(context, 0);
|
||||
std::array<llvm::Type *, 4> params = {inst.arch->StatePointerType(),
|
||||
inst.arch->MemoryPointerType(), ptr_ty,
|
||||
ptr_ty};
|
||||
auto ty =
|
||||
llvm::FunctionType::get(inst.arch->MemoryPointerType(), params, false);
|
||||
auto func = llvm::Function::Create(ty, llvm::GlobalValue::ExternalLinkage, 0,
|
||||
nm.str(), target_mod);
|
||||
|
||||
auto memory = remill::NthArgument(func, 1);
|
||||
auto state = remill::NthArgument(func, 0);
|
||||
memory->setName("memory");
|
||||
state->setName("state");
|
||||
func->getArg(kBranchTakenArgNum)->setName("btaken");
|
||||
func->getArg(kNextPcArgNum)->setName("npc");
|
||||
auto block = llvm::BasicBlock::Create(context, "entry_block", func);
|
||||
llvm::IRBuilder<> ir(block);
|
||||
|
||||
ir.CreateStore(memory, ir.CreateAlloca(memory->getType(), nullptr, "MEMORY"));
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
std::pair<LiftStatus, std::optional<llvm::Function *>>
|
||||
SleighLifter::LiftIntoInternalBlockWithSleighState(
|
||||
Instruction &inst, llvm::Module *target_mod, bool is_delayed,
|
||||
const std::optional<sleigh::BranchTakenVar> &btaken) {
|
||||
|
||||
this->sleigh_context->resetContext();
|
||||
this->decoder.InitializeSleighContext(*this->sleigh_context);
|
||||
|
||||
sleigh::PcodeDecoder pcode_record(this->GetEngine());
|
||||
sleigh_context->oneInstruction(inst.pc, pcode_record, inst.bytes);
|
||||
for (const auto &op : pcode_record.ops) {
|
||||
DLOG(INFO) << "Pcodeop: " << DumpPcode(this->GetEngine(), op);
|
||||
|
||||
if (isFloatOp(op.op)) {
|
||||
return {LiftStatus::kLiftedUnsupportedInstruction, std::nullopt};
|
||||
}
|
||||
}
|
||||
|
||||
DLOG(INFO) << "Secondary lift of bytes: " << llvm::toHex(inst.bytes);
|
||||
auto target_func = this->DefineInstructionFunction(inst, target_mod);
|
||||
|
||||
|
||||
llvm::BasicBlock *target_block = &target_func->getEntryBlock();
|
||||
llvm::IRBuilder<> ir(target_block);
|
||||
auto internal_state_pointer = remill::LoadStatePointer(target_block);
|
||||
const auto next_pc_ref =
|
||||
LoadRegAddress(target_block, internal_state_pointer, kNextPCVariableName);
|
||||
const auto next_pc = ir.CreateLoad(this->GetWordType(), next_pc_ref.first);
|
||||
auto pc_ref =
|
||||
this->LoadRegAddress(target_block, internal_state_pointer, "PC");
|
||||
|
||||
auto curr_eip = ir.CreateAdd(
|
||||
next_pc, llvm::ConstantInt::get(this->GetWordType(), inst.bytes.size()));
|
||||
ir.CreateStore(curr_eip, next_pc_ref.first);
|
||||
ir.CreateStore(curr_eip, pc_ref.first);
|
||||
auto internal_state_pointer =
|
||||
remill::NthArgument(target_func, kStatePointerArgNum);
|
||||
|
||||
|
||||
auto exit_block = llvm::BasicBlock::Create(target_mod->getContext(),
|
||||
"exit_block", target_func);
|
||||
|
||||
llvm::IRBuilder<> exit_builder(exit_block);
|
||||
exit_builder.CreateStore(
|
||||
exit_builder.CreateLoad(this->GetWordType(), pc_ref.first),
|
||||
next_pc_ref.first);
|
||||
|
||||
|
||||
exit_builder.CreateRet(remill::LoadMemoryPointer(
|
||||
exit_builder.GetInsertBlock(), *this->GetIntrinsicTable()));
|
||||
|
||||
|
||||
SleighLifter::PcodeToLLVMEmitIntoBlock lifter(
|
||||
target_block, internal_state_pointer, inst, *this,
|
||||
this->sleigh_context->getUserOpNames(), exit_block);
|
||||
//TODO(Ian): make a safe to use sleighinstruction context that wraps a context with an arch to preform reset reinits
|
||||
|
||||
this->sleigh_context->resetContext();
|
||||
this->arch->InitializeSleighContext(*this->sleigh_context);
|
||||
|
||||
SleighLifter::PcodeToLLVMEmitIntoBlock lifter(
|
||||
target_block, internal_state_pointer, inst, *this,
|
||||
this->sleigh_context->getUserOpNames(), exit_block, btaken);
|
||||
|
||||
sleigh_context->oneInstruction(inst.pc, lifter, inst.bytes);
|
||||
|
||||
|
||||
@@ -1279,39 +1411,64 @@ SleighLifter::LiftIntoInternalBlock(Instruction &inst, llvm::Module *target_mod,
|
||||
|
||||
// Setup like an ISEL
|
||||
SleighLifter::SetISelAttributes(target_func);
|
||||
remill::InitFunctionAttributes(target_func);
|
||||
|
||||
return {lifter.GetStatus(), target_func};
|
||||
}
|
||||
|
||||
LiftStatus
|
||||
SleighLifter::LiftIntoBlock(Instruction &inst, llvm::BasicBlock *block,
|
||||
llvm::Value *state_ptr, bool is_delayed) {
|
||||
LiftStatus SleighLifter::LiftIntoBlockWithSleighState(
|
||||
Instruction &inst, llvm::BasicBlock *block, llvm::Value *state_ptr,
|
||||
bool is_delayed, const std::optional<sleigh::BranchTakenVar> &btaken) {
|
||||
if (!inst.IsValid()) {
|
||||
LOG(ERROR) << "Invalid function" << inst.Serialize();
|
||||
DLOG(ERROR) << "Invalid function" << inst.Serialize();
|
||||
return kLiftedInvalidInstruction;
|
||||
}
|
||||
|
||||
// Call the instruction function
|
||||
auto res = this->LiftIntoInternalBlock(inst, block->getModule(), is_delayed);
|
||||
|
||||
auto target_func = res.second;
|
||||
// Call the instruction function
|
||||
auto res = this->LiftIntoInternalBlockWithSleighState(
|
||||
inst, block->getModule(), is_delayed, btaken);
|
||||
|
||||
if (res.first != LiftStatus::kLiftedInstruction || !res.second.has_value()) {
|
||||
return res.first;
|
||||
}
|
||||
|
||||
auto target_func = *res.second;
|
||||
|
||||
|
||||
// Setup PC and NEXT_PC
|
||||
const auto [pc_ref, pc_ref_type] =
|
||||
LoadRegAddress(block, state_ptr, kPCVariableName);
|
||||
const auto [next_pc_ref, next_pc_ref_type] =
|
||||
LoadRegAddress(block, state_ptr, kNextPCVariableName);
|
||||
|
||||
llvm::IRBuilder<> intoblock_builer(block);
|
||||
std::array<llvm::Value *, 3> args = {
|
||||
remill::LoadStatePointer(block),
|
||||
remill::LoadProgramCounter(block, *this->GetIntrinsicTable()),
|
||||
remill::LoadMemoryPointer(block, *this->GetIntrinsicTable())};
|
||||
const auto next_pc =
|
||||
intoblock_builer.CreateLoad(this->GetWordType(), next_pc_ref);
|
||||
|
||||
;
|
||||
|
||||
intoblock_builer.CreateStore(
|
||||
this->decoder.LiftPcFromCurrPc(intoblock_builer, next_pc,
|
||||
inst.bytes.size()),
|
||||
pc_ref);
|
||||
intoblock_builer.CreateStore(
|
||||
intoblock_builer.CreateAdd(
|
||||
next_pc,
|
||||
llvm::ConstantInt::get(this->GetWordType(), inst.bytes.size())),
|
||||
next_pc_ref);
|
||||
|
||||
|
||||
std::array<llvm::Value *, 4> args = {
|
||||
state_ptr, remill::LoadMemoryPointer(block, *this->GetIntrinsicTable()),
|
||||
remill::LoadBranchTakenRef(block),
|
||||
remill::LoadNextProgramCounterRef(block)};
|
||||
|
||||
intoblock_builer.CreateStore(intoblock_builer.CreateCall(target_func, args),
|
||||
remill::LoadMemoryPointerRef(block));
|
||||
|
||||
// also store off the potentially updated pc into NEXT_PC to keep with traditional lifters
|
||||
intoblock_builer.CreateStore(
|
||||
remill::LoadProgramCounter(block, *this->GetIntrinsicTable()),
|
||||
remill::LoadNextProgramCounterRef(block));
|
||||
|
||||
//NOTE(Ian): If we made it past decoding we should be able to decode the bytes again
|
||||
LOG(INFO) << res.first;
|
||||
DLOG(INFO) << res.first;
|
||||
|
||||
return res.first;
|
||||
}
|
||||
@@ -1319,4 +1476,45 @@ SleighLifter::LiftIntoBlock(Instruction &inst, llvm::BasicBlock *block,
|
||||
Sleigh &SleighLifter::GetEngine(void) const {
|
||||
return this->sleigh_context->GetEngine();
|
||||
}
|
||||
|
||||
SleighLifterWithState::SleighLifterWithState(
|
||||
std::optional<sleigh::BranchTakenVar> btaken_,
|
||||
std::shared_ptr<SleighLifter> lifter_)
|
||||
: btaken(btaken_),
|
||||
lifter(std::move(lifter_)) {}
|
||||
|
||||
// Lift a single instruction into a basic block. `is_delayed` signifies that
|
||||
// this instruction will execute within the delay slot of another instruction.
|
||||
LiftStatus
|
||||
SleighLifterWithState::LiftIntoBlock(Instruction &inst, llvm::BasicBlock *block,
|
||||
llvm::Value *state_ptr, bool is_delayed) {
|
||||
return this->lifter->LiftIntoBlockWithSleighState(inst, block, state_ptr,
|
||||
is_delayed, this->btaken);
|
||||
}
|
||||
|
||||
|
||||
// Load the address of a register.
|
||||
std::pair<llvm::Value *, llvm::Type *>
|
||||
SleighLifterWithState::LoadRegAddress(llvm::BasicBlock *block,
|
||||
llvm::Value *state_ptr,
|
||||
std::string_view reg_name) const {
|
||||
return this->lifter->LoadRegAddress(block, state_ptr, reg_name);
|
||||
}
|
||||
|
||||
// Load the value of a register.
|
||||
llvm::Value *
|
||||
SleighLifterWithState::LoadRegValue(llvm::BasicBlock *block,
|
||||
llvm::Value *state_ptr,
|
||||
std::string_view reg_name) const {
|
||||
return this->lifter->LoadRegValue(block, state_ptr, reg_name);
|
||||
}
|
||||
|
||||
llvm::Type *SleighLifterWithState::GetMemoryType() {
|
||||
return this->lifter->GetMemoryType();
|
||||
}
|
||||
|
||||
void SleighLifterWithState::ClearCache(void) const {
|
||||
this->lifter->ClearCache();
|
||||
}
|
||||
|
||||
} // namespace remill
|
||||
|
||||
@@ -161,7 +161,8 @@ TraceLifter::Impl::Impl(const Arch *arch_, TraceManager *manager_)
|
||||
func(nullptr),
|
||||
block(nullptr),
|
||||
switch_inst(nullptr),
|
||||
max_inst_bytes(arch->MaxInstructionSize()) {
|
||||
// TODO(Ian): The trace lfiter is not supporting contexts
|
||||
max_inst_bytes(arch->MaxInstructionSize(arch->CreateInitialContext())) {
|
||||
|
||||
inst_bytes.reserve(max_inst_bytes);
|
||||
}
|
||||
|
||||
@@ -210,9 +210,9 @@ LiftingTester::LiftingTester(std::shared_ptr<llvm::Module> semantics_module_,
|
||||
arch(remill::Arch::Build(&this->semantics_module->getContext(), os_name,
|
||||
arch_name)),
|
||||
table(std::make_unique<remill::IntrinsicTable>(
|
||||
this->semantics_module.get())),
|
||||
lifter(this->arch->DefaultLifter(*this->table.get())) {
|
||||
this->semantics_module.get())) {
|
||||
this->arch->InitFromSemanticsModule(this->semantics_module.get());
|
||||
this->lifter = this->arch->DefaultLifter(*this->table.get());
|
||||
}
|
||||
|
||||
LiftingTester::LiftingTester(llvm::LLVMContext &context, remill::OSName os_name,
|
||||
|
||||
+400
-1
@@ -25,6 +25,7 @@
|
||||
#include <llvm/Support/DynamicLibrary.h>
|
||||
#include <llvm/Support/TargetSelect.h>
|
||||
#include <llvm/Transforms/Utils/Cloning.h>
|
||||
#include <remill/Arch/AArch32/ArchContext.h>
|
||||
#include <remill/Arch/AArch32/Runtime/State.h>
|
||||
#include <remill/Arch/Arch.h>
|
||||
#include <remill/Arch/Name.h>
|
||||
@@ -38,11 +39,13 @@
|
||||
#include <functional>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <variant>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
const static std::unordered_map<std::string,
|
||||
std::function<uint32_t &(AArch32State &)>>
|
||||
reg_to_accessor = {
|
||||
@@ -50,7 +53,30 @@ const static std::unordered_map<std::string,
|
||||
[](AArch32State &st) -> uint32_t & { return st.gpr.r15.dword; }},
|
||||
{"sp", [](AArch32State &st) -> uint32_t & { return st.gpr.r13.dword; }},
|
||||
{"r1", [](AArch32State &st) -> uint32_t & { return st.gpr.r1.dword; }}};
|
||||
|
||||
|
||||
std::optional<remill::Instruction> GetFlows(std::string_view bytes,
|
||||
uint64_t address, uint64_t tm_val) {
|
||||
|
||||
llvm::LLVMContext context;
|
||||
context.enableOpaquePointers();
|
||||
auto arch = remill::Arch::Build(&context, remill::OSName::kOSLinux,
|
||||
remill::ArchName::kArchAArch32LittleEndian);
|
||||
auto sems = remill::LoadArchSemantics(arch.get());
|
||||
|
||||
|
||||
remill::DecodingContext dec_context;
|
||||
dec_context.UpdateContextReg(std::string(remill::kThumbModeRegName), tm_val);
|
||||
CHECK(dec_context.HasValueForReg("TMReg"));
|
||||
remill::Instruction insn;
|
||||
|
||||
if (!arch->DecodeInstruction(address, bytes, insn, dec_context)) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return insn;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
using MemoryModifier = std::function<void(test_runner::MemoryHandler &)>;
|
||||
@@ -225,7 +251,7 @@ TEST(ThumbRandomizedLifts, RelPcTest) {
|
||||
remill::Instruction::Category::kCategoryNormal,
|
||||
{{"r15", 11}}, {{"r1", 0xdeadc0de}});
|
||||
// The bit from 11+12 gets masked off
|
||||
spec.AddPrecWrite<uint32_t>(24, 0xdeadc0de);
|
||||
spec.AddPrecWrite<uint32_t>(28, 0xdeadc0de);
|
||||
llvm::LLVMContext context;
|
||||
|
||||
context.enableOpaquePointers();
|
||||
@@ -254,3 +280,376 @@ TEST(RegressionTests, AARCH64RegSize) {
|
||||
|
||||
CHECK_EQ(lifted2->getType()->getIntegerBitWidth(), 32);
|
||||
}
|
||||
|
||||
|
||||
/* These tests are transcribed from the behaviors described in: A2.3.1
|
||||
|
||||
MOV(reg, thumb) ignores last bit, but does not mode-switch
|
||||
|
||||
Thumb -> Thumb (1, mov pc, r1) -> {true: 1}
|
||||
|
||||
B always remains in the same state:
|
||||
Arm -> Arm (0, b 4) -> {true: 0}
|
||||
Arm -> Arm (0, b 0) -> {true: 0}
|
||||
|
||||
Thumb -> Thumb (1, b 4) -> {true: 1}
|
||||
Thumb -> Thumb (1, b 0) -> {true: 1}
|
||||
|
||||
BLX immediate always changes, but is interprocedural so we keep the state the same
|
||||
Arm -> Arm (0, blx 1) -> {true: 0}
|
||||
Thumb -> Thumb (1, blx 1) -> {true: 1}
|
||||
|
||||
Indirects (LDR, MOV(reg,ARM), BX):
|
||||
|
||||
Arm -> (Thumb/Arm) (0, LDR PC, [r0]) -> {true: non_constant}
|
||||
Arm -> (Thumb/Arm) (0, BX r1) -> {true: non_constant}
|
||||
Thumb -> (Thumb/Arm) (1, LDR PC, [0]) -> {true: non_constant}
|
||||
Thumb -> (Thumb/Arm) (1, BX r1) -> {true: non_constant}
|
||||
|
||||
Arm only
|
||||
Arm -> (Thumb/Arm) (0, mov pc, r1) -> {true: non_constant}
|
||||
|
||||
|
||||
|
||||
Same but with conditionals:
|
||||
|
||||
|
||||
B always remains in the same state:
|
||||
Arm -> Arm (0, bne 4) -> {true: 0}
|
||||
Arm -> Arm (0, bne 0) -> {true: 0}
|
||||
\
|
||||
|
||||
BLX immediate always changes, but is interprocedural so we keep the state the same
|
||||
Arm -> Arm (0, blxne 1) -> {true: 0}
|
||||
|
||||
Indirects (LDR, MOV(reg,ARM), BX):
|
||||
|
||||
Arm -> (Thumb/Arm) (0, LDRNE PC, [r0]) -> {!=branch_not_taken_pc: non_constant, ==branch_not_taken_pc: 0}
|
||||
Arm -> (Thumb/Arm) (0, BXNE r1) -> {!=branch_not_taken_pc: non_constant, ==branch_not_taken_pc: 0}
|
||||
|
||||
Arm only
|
||||
Arm -> (Thumb/Arm) (0, movne pc, r1) -> {!=branch_not_taken_pc: non_constant, ==branch_not_taken_pc: 0}
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
TEST(ArmContextTests, ThumbMovIgnoresAnyStateChange) {
|
||||
//mov pc, r1
|
||||
std::string insn_data("\x8f\x46", 2);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_TRUE(map(0xdeadbee2).GetContextValue(remill::kThumbModeRegName));
|
||||
EXPECT_TRUE(map(0x100).GetContextValue(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
|
||||
TEST(ArmContextTests, ArmBStaysInArmAligned1) {
|
||||
// b 0
|
||||
std::string insn_data("\xfe\xff\xff\xea", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0x0).GetContextValue(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ArmBStaysInArmAligned2) {
|
||||
// b 4
|
||||
std::string insn_data("\xff\xff\xff\xea", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0x4).GetContextValue(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
|
||||
TEST(ArmContextTests, ThumbBStaysInThumbAligned1) {
|
||||
// b 0
|
||||
std::string insn_data("\xfe\xe7", 2);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_TRUE(map(0x0).GetContextValue(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ThumbBStaysInThumbAligned2) {
|
||||
// b 4
|
||||
std::string insn_data("\x00\xe0", 2);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_TRUE(map(0x4).GetContextValue(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ArmBLXInterProcStaysInSameMode) {
|
||||
// blx 4
|
||||
std::string insn_data("\xff\xff\xff\xfa", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ThumbBLXInterProcStaysInSameMode) {
|
||||
// blx 4
|
||||
std::string insn_data("\x00\xf0\x00\xe8", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_TRUE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ArmLDRIndirect) {
|
||||
// ldr pc, [r0]
|
||||
std::string insn_data("\x00\xf0\x90\xe5", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0xdeadbee4).HasValueForReg(remill::kThumbModeRegName));
|
||||
EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ArmBXIndirect) {
|
||||
// bx r1
|
||||
std::string insn_data("\x11\xff\x2f\xe1", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0xdeadbee4).HasValueForReg(remill::kThumbModeRegName));
|
||||
EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
|
||||
TEST(ArmContextTests, ThumbLDRIndirect) {
|
||||
// ldr pc, [r0]
|
||||
std::string insn_data("\xd0\xf8\x00\xf0", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0xdeadbee4).HasValueForReg(remill::kThumbModeRegName));
|
||||
EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ThumbBXIndirect) {
|
||||
// bx r1
|
||||
std::string insn_data("\x08\x47", 2);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0xdeadbee2).HasValueForReg(remill::kThumbModeRegName));
|
||||
EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ArmMovPCIndirectDoesAllowModeSwitch) {
|
||||
// mov pc, r1
|
||||
std::string insn_data("\x01\xf0\xa0\xe1", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0xdeadbee4).HasValueForReg(remill::kThumbModeRegName));
|
||||
EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
|
||||
// Conditionals
|
||||
|
||||
|
||||
TEST(ArmContextTests, ArmBStaysInArmConditionalAligned1) {
|
||||
// bne 0
|
||||
std::string insn_data("\xfe\xff\xff\x1a", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0x0).GetContextValue(remill::kThumbModeRegName));
|
||||
EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ArmBStaysInArmConditionalAligned2) {
|
||||
// bne 4
|
||||
std::string insn_data("\xff\xff\xff\x1a", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0x0).GetContextValue(remill::kThumbModeRegName));
|
||||
EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ArmLDRIndirectConditional) {
|
||||
// ldrne pc, [r0]
|
||||
std::string insn_data("\x00\xf0\x90\x15", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName));
|
||||
EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ArmBXIndirectConditional) {
|
||||
// bxne r1
|
||||
std::string insn_data("\x11\xff\x2f\x11", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName));
|
||||
EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName));
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ArmMovPCIndirectDoesAllowModeSwitchConditional) {
|
||||
// movne pc, r1
|
||||
std::string insn_data("\x01\xf0\xa0\x11", 4);
|
||||
|
||||
auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0);
|
||||
ASSERT_TRUE(maybe_map.has_value());
|
||||
auto map = *maybe_map;
|
||||
|
||||
EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName));
|
||||
EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName));
|
||||
}
|
||||
*/
|
||||
|
||||
// Two things we need to test: correct categories and when we lift, the branch taken var is set appropriately
|
||||
|
||||
|
||||
TEST(ArmContextTests, ThumbBXIndirect) {
|
||||
// bx r1
|
||||
std::string insn_data("\x08\x47", 2);
|
||||
|
||||
auto maybe_flow = GetFlows(insn_data, 0xdeadbee0, 1);
|
||||
ASSERT_TRUE(maybe_flow.has_value());
|
||||
auto flow = *maybe_flow;
|
||||
|
||||
remill::Instruction::InstructionFlowCategory jmp =
|
||||
remill::Instruction::IndirectJump(
|
||||
remill::Instruction::IndirectFlow(std::nullopt));
|
||||
|
||||
|
||||
remill::Instruction::IndirectJump str =
|
||||
std::get<remill::Instruction::IndirectJump>(flow.flows);
|
||||
|
||||
EXPECT_FALSE(str.taken_flow.maybe_context.has_value());
|
||||
|
||||
|
||||
EXPECT_EQ(flow.flows, jmp);
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ThumbMovIgnoresAnyStateChange) {
|
||||
//mov pc, r1
|
||||
std::string insn_data("\x8f\x46", 2);
|
||||
|
||||
auto maybe_flow = GetFlows(insn_data, 0xdeadbee0, 1);
|
||||
ASSERT_TRUE(maybe_flow.has_value());
|
||||
auto flow = *maybe_flow;
|
||||
|
||||
|
||||
remill::Instruction::InstructionFlowCategory jmp =
|
||||
remill::Instruction::IndirectJump(
|
||||
remill::Instruction::IndirectFlow(remill::kThumbContext));
|
||||
|
||||
|
||||
remill::Instruction::IndirectJump str =
|
||||
std::get<remill::Instruction::IndirectJump>(flow.flows);
|
||||
|
||||
EXPECT_TRUE(str.taken_flow.maybe_context.has_value());
|
||||
|
||||
|
||||
remill::Instruction::IndirectJump str2 =
|
||||
std::get<remill::Instruction::IndirectJump>(jmp);
|
||||
|
||||
EXPECT_TRUE(str2.taken_flow.maybe_context.has_value());
|
||||
|
||||
EXPECT_EQ(flow.flows, jmp);
|
||||
}
|
||||
|
||||
TEST(ArmContextTests, ThumbBLXInterProcStaysInSameMode) {
|
||||
// blx 4
|
||||
std::string insn_data("\x00\xf0\x00\xe8", 4);
|
||||
|
||||
auto maybe_flow = GetFlows(insn_data, 0xdeadbee0, 1);
|
||||
ASSERT_TRUE(maybe_flow.has_value());
|
||||
auto flow = *maybe_flow;
|
||||
|
||||
remill::Instruction::InstructionFlowCategory jmp =
|
||||
remill::Instruction::DirectFunctionCall(
|
||||
remill::Instruction::DirectFlow(0xdeadbee0, remill::kARMContext));
|
||||
}
|
||||
|
||||
|
||||
TEST(ArmContextTests, ThumbBLStaysInSameContext) {
|
||||
// bl 1b528
|
||||
std::string insn_data("\x05\xf0\x74\xfc", 4);
|
||||
|
||||
auto maybe_flow = GetFlows(insn_data, 0x1596c, 1);
|
||||
ASSERT_TRUE(maybe_flow.has_value());
|
||||
auto act_insn = *maybe_flow;
|
||||
|
||||
|
||||
remill::Instruction::InstructionFlowCategory jmp =
|
||||
remill::Instruction::DirectFunctionCall(
|
||||
remill::Instruction::DirectFlow(0x1b258, remill::kThumbContext));
|
||||
|
||||
auto dfcall =
|
||||
std::get<remill::Instruction::DirectFunctionCall>(act_insn.flows);
|
||||
|
||||
|
||||
EXPECT_EQ(0x0001b258, dfcall.taken_flow.known_target);
|
||||
|
||||
EXPECT_EQ(remill::kThumbContext, dfcall.taken_flow.static_context);
|
||||
|
||||
EXPECT_EQ(jmp, act_insn.flows);
|
||||
|
||||
EXPECT_EQ(0x00015970, act_insn.next_pc);
|
||||
}
|
||||
|
||||
|
||||
TEST(ArmContextTests, ThumbBPLRegressionTest) {
|
||||
// bpl #0x135e0
|
||||
std::string insn_data("\x7f\xf5\x70\xae", 4);
|
||||
|
||||
auto maybe_flow = GetFlows(insn_data, 0x000138fc, 1);
|
||||
ASSERT_TRUE(maybe_flow.has_value());
|
||||
auto act_insn = *maybe_flow;
|
||||
|
||||
|
||||
remill::Instruction::InstructionFlowCategory expect_cond_flow =
|
||||
remill::Instruction::ConditionalInstruction(
|
||||
remill::Instruction::DirectJump(
|
||||
remill::Instruction::DirectFlow(0x135e0, remill::kThumbContext)),
|
||||
remill::Instruction::FallthroughFlow(remill::kThumbContext));
|
||||
|
||||
auto act_cond_insn_flow =
|
||||
std::get<remill::Instruction::ConditionalInstruction>(act_insn.flows);
|
||||
|
||||
EXPECT_EQ(expect_cond_flow, act_insn.flows);
|
||||
}
|
||||
Reference in New Issue
Block a user