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:
2over12
2022-09-28 13:38:36 -04:00
committed by GitHub
parent 166553c8e2
commit 65817f05b8
36 changed files with 2399 additions and 685 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ CMakeLists.txt.user
cmake-build-debug
cmake-build-release
compile_commands.json
.cache
third_party/*
build/*
+1 -1
View File
@@ -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)
+10 -13
View File
@@ -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
+15
View File
@@ -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
View File
@@ -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;
+13 -3
View File
@@ -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
+14 -6
View File
@@ -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
+143 -1
View File
@@ -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;
+15 -11
View File
@@ -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
+18 -8
View File
@@ -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 *>
+57 -8
View File
@@ -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
+29 -3
View File
@@ -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
View File
@@ -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;
};
+1 -16
View File
@@ -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);
+3
View File
@@ -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
View File
@@ -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
+7 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+4 -3
View File
@@ -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.
}
+4 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
+5
View File
@@ -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
+478
View File
@@ -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> &register_mapping_,
Sleigh &engine_)
: context_reg_mapping(register_mapping_),
engine(engine_) {}
} // namespace remill::sleigh
+63
View File
@@ -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
+29
View File
@@ -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
+71 -32
View File
@@ -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
+43 -9
View File
@@ -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_) {
+3 -3
View File
@@ -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;
}
+3 -3
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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);
}
+2 -2
View File
@@ -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
View File
@@ -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);
}