Files
lifting-bits-remill/tests/Thumb/TestLifting.cpp
T
Alex Cameron fb018c96e9 PowerPC Support (#645)
* Add skeleton for PPC

* Copyright notices

* Fill in some details for the PPC arch

* Start building a (wrong) PPC runtime

* Begin populating state structure

* First pass for EIS state structure

* Map registers to Sleigh register names

* More fixes

* add optional param

* Create handle unsupported and invalid instruction isels

* Correct typo

* Get a basic `remill-lift` invocation running without failure

* Fix capitalisation

* Set vle context reg

* Fix SleighDecoder signatures

* Set VLE context register in the Sleigh engine in addition to our
internal context reg mapping

* Capitalize reg names

* Add the flag registers for XER and CR

* Rename bitflag structures in PPC state

* PPC Sleigh patches (#643)

* Modified sleigh patch script to generate patches for multiple .sinc files

* update README with new examples of sleigh patch script invocation

* add ppc register definition

* add ppc sleigh patches

* fix issue with remill_insn_size definition

* regenerate sleigh patches for PPC

* update CMakeLists.txt to include PPC patches

* Add TEA signal as a register in the PPC state

* Uppercase the stack pointer register name

* Fix PPC instruction sizes

* initial PPC tests

* remove duplicate tests

* fix tests for e_stmvgprw/e_ldmvgprw

* add tests for loading/storing from special registers

* add tests with internal conditionals in pcode

* fix for pc reg and addr width not being the same... I suspect this issue is going to come up elsewhere

* add heuristic for flow from normal intrainstruction flow

* rework tests to allow testing for different sized registers

* add tests for overflow and record add

* fix bug with log printout

* add intrafunction control flow lifting

* handle edge case where there is no pcode op at the zero index

* Fix another inconsistency with mismatching address and PC reg size

* Allocate unique ptrs in the entry block

* Fix `INT_LEFT` and `INT_RIGHT` impl where shift exceeds bit width

* fix supiece lift?

* Add PPC emulate instruction to hyper call

* fix for pc reg and addr width not being the same... I suspect this issue is going to come up elsewhere

* add heuristic for flow from normal intrainstruction flow

* add intrafunction control flow lifting

* handle edge case where there is no pcode op at the zero index

* Fix another inconsistency with mismatching address and PC reg size

* fix supiece lift?

* Allocate unique ptrs in the entry block

* Fix `INT_LEFT` and `INT_RIGHT` impl where shift exceeds bit width

* fix int2float semantics

should use appropriate sized float based on the output size

* add tests for lifting int2float

* fix INT_{LEFT,RIGHT} semantics

should be `ICmpSGE` instead of `ICmpSGT`

* add cr0-7 registers

* fix formatting

* fix conditional branch test

* add test for compare

* re-enable rotate left word immediate and mask test

* genericize TestSpecOutput

* explicit instruction data size

* add test for syscall/callother (disabled)

* add tests for store/load word

* add test to convert from float to int

* specify intrinsic arg type, fixes null deref

* Add PPC emulate instruction to hyper call

* add headers + formatting

* remove old comment

* Map CRALL register

* Add basic LLVM data layout that specifies 32-bit addresses

* Remove unused variables

* convert auto* to auto when possible

* RegisterPrecondition -> RegisterCondition

* fix variable name

* convert any to variant

* use std::move

* bump to c++20, use concepts

* set arch in constructor since class isn't generic anyways

* formatting

* make type aliases

* bump cxx-common

* add comment

* clang format

* throw exception if register not found

* use const ref

* use shorthand for lambda capture values

* Add more detail to data layout to include proper stack alignment

* Compare to the correct size for SUBPIECE impl

* Add Sleigh message to error

* throw exception in else case

* throw runtime error if register value has incorrect type

* use reference instead of value

* get rid of unnecessary type alias

* formatting

* Propagate VLE context reg value into Sleigh

* Remove unnecessary whitespace

* Remove stale TODO and NOTE comments

* add additional parameter to test runner to specify decoding context

* drop llvm 14, bump macos version

* bump cxx-common, fix ci.yml mac build

* add test for unconditional relative negative branch

* add missing space to pcode debug log

* fix bug due to unordered_map, iteration order matters

* add error log in case we aren't able to adjust PC value

* use helper for getting register reference

* Revert "add optional param"

This reverts commit 51ed49f8cf.

* Remove remaining LLVM 14 compatibility code and configuration

* Add padding between CR and XER flags

* Use `enum class`

* Remove void cast

* Remove unnecessary variable

* Use initialiser lists where appropriate

* Remove redundant `else`

* Prefer `CHECK` over `assert`

* Polish PowerPC function initialisation with lambda

* zero out xer_so to fix tests

* log error when we see claim_eq with no usages

* Collapse namespace blocks

* Remove unnecessary `this->`

* Use `auto` where appropriate

* Remove unnecessary `else`

* Use `emplace` over `insert` for `std::map`

Co-authored-by: lkorenc <lukas.korencik@trailofbits.com>

* Use `constexpr` for VLE reg name

* Use module verification util

* Add `VerifyFunction` util and use where applicable

* Extract lambda to improve readability of flow categorisation

* Use type alias for context values

* Introduce type alias for block exit

* Create type alias for optional branch taken

* Refactor `PcodeCFGBuilder`

* Use lambda to avoid conditional mutation

* Extract duplicated bit-shift code generation into helper

* Simplify flow with ternary

* Add `GetBlock` helper

* Rename variables

* Move statement for clarity

* Create helpers for working with Sleigh context register values

* Convert loop to `std::copy`

* Add a comment explaining the use of set to de-duplicate and sort

* Refactor `IntraProcTransferCollector`

* Expose static method to easily use `IntraProcTransferCollector`

* Rename PPC related variables to include address width

* add docs to intrainstructionindex

* remove llvm 14 ifdefs

* don't log error if no claim_eqs were used

* update comments

* Cleanup exit visitors

---------

Co-authored-by: 2over12 <ian.smith@trailofbits.com>
Co-authored-by: William Tan <1284324+Ninja3047@users.noreply.github.com>
Co-authored-by: lkorenc <lukas.korencik@trailofbits.com>
2023-02-02 10:05:28 -05:00

659 lines
20 KiB
C++

/*
* Copyright (c) 2022 Trail of Bits, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/ExecutionEngine/GenericValue.h>
#include <llvm/ExecutionEngine/Interpreter.h>
#include <llvm/ExecutionEngine/MCJIT.h>
#include <llvm/IR/Instructions.h>
#include <llvm/Passes/PassBuilder.h>
#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>
#include <remill/BC/ABI.h>
#include <remill/BC/IntrinsicTable.h>
#include <remill/BC/Optimizer.h>
#include <remill/BC/Util.h>
#include <remill/BC/Version.h>
#include <remill/OS/OS.h>
#include <test_runner/TestRunner.h>
#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 = {
{"r15",
[](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;
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 &)>;
struct RegisterPrecondition {
std::string register_name;
uint32_t enforced_value;
};
class TestOutputSpec {
public:
uint64_t addr;
std::string target_bytes;
private:
remill::Instruction::Category expected_category;
std::vector<RegisterPrecondition> register_preconditions;
std::vector<RegisterPrecondition> register_postconditions;
std::vector<MemoryModifier> initial_memory_conditions;
void ApplyCondition(AArch32State &state, std::string reg,
uint32_t value) const {
auto accessor = reg_to_accessor.find(reg);
if (accessor != reg_to_accessor.end()) {
accessor->second(state) = value;
}
}
void CheckCondition(AArch32State &state, std::string reg,
uint32_t value) const {
auto accessor = reg_to_accessor.find(reg);
if (accessor != reg_to_accessor.end()) {
CHECK_EQ(accessor->second(state), value);
}
}
public:
template <typename T>
void AddPrecWrite(uint64_t addr, T value) {
this->initial_memory_conditions.push_back(
[addr, value](test_runner::MemoryHandler &mem_hand) {
mem_hand.WriteMemory(addr, value);
});
}
const std::vector<MemoryModifier> &GetMemoryPrecs() const {
return this->initial_memory_conditions;
}
TestOutputSpec(uint64_t disas_addr, std::string target_bytes,
remill::Instruction::Category expected_category,
std::vector<RegisterPrecondition> register_preconditions,
std::vector<RegisterPrecondition> register_postconditions)
: addr(disas_addr),
target_bytes(target_bytes),
expected_category(expected_category),
register_preconditions(std::move(register_preconditions)),
register_postconditions(std::move(register_postconditions)) {}
void SetupTestPreconditions(AArch32State &state) const {
for (auto prec : this->register_preconditions) {
this->ApplyCondition(state, prec.register_name, prec.enforced_value);
}
}
void CheckLiftedInstruction(const remill::Instruction &lifted) const {
CHECK_EQ(lifted.category, this->expected_category);
}
void CheckResultingState(AArch32State &state) const {
for (auto post : this->register_postconditions) {
this->CheckCondition(state, post.register_name, post.enforced_value);
}
}
};
class TestSpecRunner {
private:
test_runner::LiftingTester lifter;
uint64_t tst_ctr;
test_runner::random_bytes_engine rbe;
llvm::support::endianness endian;
public:
TestSpecRunner(llvm::LLVMContext &context)
: lifter(test_runner::LiftingTester(
context, remill::OSName::kOSLinux,
remill::ArchName::kArchThumb2LittleEndian)),
tst_ctr(0),
endian(lifter.GetArch()->MemoryAccessIsLittleEndian()
? llvm::support::endianness::little
: llvm::support::endianness::big) {}
void RunTestSpec(const TestOutputSpec &test) {
std::stringstream ss;
ss << "test_disas_func_" << this->tst_ctr++;
auto maybe_func =
lifter.LiftInstructionFunction(ss.str(), test.target_bytes, test.addr);
CHECK(maybe_func.has_value());
auto lifted_func = maybe_func->first;
auto new_mod = llvm::CloneModule(*lifted_func->getParent());
remill::OptimizeBareModule(new_mod.get());
auto just_func_mod =
std::make_unique<llvm::Module>("", new_mod->getContext());
auto new_func = test_runner::CopyFunctionIntoNewModule(
just_func_mod.get(), lifted_func, new_mod);
AArch32State st = {};
test.CheckLiftedInstruction(maybe_func->second);
test_runner::RandomizeState(st, this->rbe);
st.sr.z = test_runner::random_boolean_flag(this->rbe);
st.sr.c = test_runner::random_boolean_flag(this->rbe);
st.sr.v = test_runner::random_boolean_flag(this->rbe);
st.sr.z = test_runner::random_boolean_flag(this->rbe);
st.sr.n = test_runner::random_boolean_flag(this->rbe);
test.SetupTestPreconditions(st);
auto mem_hand = std::make_unique<test_runner::MemoryHandler>(this->endian);
for (const auto &prec : test.GetMemoryPrecs()) {
prec(*mem_hand);
}
test_runner::ExecuteLiftedFunction<AArch32State, uint32_t>(
new_func, test.target_bytes.length(), &st, mem_hand.get(),
[](AArch32State *st) { return st->gpr.r15.dword; });
LOG(INFO) << "Pc after execute " << st.gpr.r15.dword;
test.CheckResultingState(st);
}
};
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
return RUN_ALL_TESTS();
}
TEST(ThumbRandomizedLifts, PopPC) {
llvm::LLVMContext curr_context;
std::string insn_data("\x00\xbd", 2);
TestOutputSpec spec(0x12, insn_data,
remill::Instruction::Category::kCategoryFunctionReturn,
{{"r15", 12}, {"sp", 10}}, {{"r15", 16}});
spec.AddPrecWrite<uint32_t>(10, 16);
llvm::LLVMContext context;
TestSpecRunner runner(context);
runner.RunTestSpec(spec);
}
TEST(ThumbRandomizedLifts, RelPcTest) {
llvm::LLVMContext curr_context;
std::string insn_data("\x03\x49", 2);
TestOutputSpec spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"r15", 0x12}}, {{"r1", 0xdeadc0de}});
// So ok instruction is at 18 which means pc is = 22
spec.AddPrecWrite<uint32_t>(32, 0xdeadc0de);
llvm::LLVMContext context;
TestSpecRunner runner(context);
runner.RunTestSpec(spec);
}
TEST(RegressionTests, AARCH64RegSize) {
llvm::LLVMContext context;
auto arch = remill::Arch::Build(&context, remill::OSName::kOSLinux,
remill::ArchName::kArchAArch64LittleEndian);
auto sems = remill::LoadArchSemantics(arch.get());
remill::IntrinsicTable instrinsics(sems.get());
auto op_lifter = arch->DefaultLifter(instrinsics);
auto target_lift = arch->DefineLiftedFunction("test_lift", sems.get());
auto st_ptr = remill::LoadStatePointer(target_lift);
CHECK_NOTNULL(st_ptr);
auto lifted =
op_lifter->LoadRegValue(&target_lift->getEntryBlock(), st_ptr, "W0");
CHECK_EQ(lifted->getType()->getIntegerBitWidth(), 32);
op_lifter->ClearCache();
auto lifted2 =
op_lifter->LoadRegValue(&target_lift->getEntryBlock(), st_ptr, "W0");
CHECK_EQ(lifted2->getType()->getIntegerBitWidth(), 32);
}
TEST(RegressionTests, Armv8FPSCR) {
llvm::LLVMContext context;
auto arch = remill::Arch::Build(&context, remill::OSName::kOSLinux,
remill::ArchName::kArchAArch32LittleEndian);
CHECK_NOTNULL(arch->RegisterByName("FPSCR"));
}
/* 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);
}