Files
lifting-bits-remill/tests/PPC/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

775 lines
30 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 <gtest/gtest.h>
#include <llvm/IR/LLVMContext.h>
#include <remill/Arch/Arch.h>
#include <remill/Arch/Instruction.h>
#include <remill/Arch/Name.h>
#include <remill/Arch/PPC/Runtime/State.h>
#include <remill/BC/Optimizer.h>
#include <remill/BC/Version.h>
#include <remill/OS/OS.h>
#include <test_runner/TestOutputSpec.h>
#include <test_runner/TestRunner.h>
#include <unordered_map>
namespace {
const static std::unordered_map<
std::string, std::function<test_runner::RegisterValueRef(PPCState &)>>
reg_to_accessor = {
{"pc",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.pc.qword);
}},
{"r0",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.gpr.r0.qword);
}},
{"r1",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.gpr.r1.qword);
}},
{"r2",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.gpr.r2.qword);
}},
{"r3",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.gpr.r3.qword);
}},
{"r4",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.gpr.r4.qword);
}},
{"r5",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.gpr.r5.qword);
}},
{"r6",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.gpr.r6.qword);
}},
{"r7",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.gpr.r7.qword);
}},
{"r8",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.gpr.r8.qword);
}},
{"r9",
[](PPCState &st) -> test_runner::RegisterValueRef {
return (st.gpr.r9.qword);
}},
{"r10",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.gpr.r10.qword;
}},
{"r11",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.gpr.r11.qword;
}},
{"r12",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.gpr.r12.qword;
}},
{"cr",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.iar.cr.qword;
}},
{"cr0",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.cr_flags.cr0;
}},
{"cr1",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.cr_flags.cr1;
}},
{"cr2",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.cr_flags.cr2;
}},
{"cr3",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.cr_flags.cr3;
}},
{"cr4",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.cr_flags.cr4;
}},
{"cr5",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.cr_flags.cr5;
}},
{"cr6",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.cr_flags.cr6;
}},
{"cr7",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.cr_flags.cr7;
}},
{"lr",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.iar.lr.qword;
}},
{"ctr",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.iar.ctr.qword;
}},
{"xer",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.iar.xer.qword;
}},
{"xer_so",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.xer_flags.so;
}},
{"xer_ov",
[](PPCState &st) -> test_runner::RegisterValueRef {
return st.xer_flags.ov;
}},
};
std::optional<remill::Instruction>
GetFlows(std::string_view bytes, uint64_t address, uint64_t vle_val) {
llvm::LLVMContext context;
auto arch = remill::Arch::Build(&context, remill::OSName::kOSLinux,
remill::ArchName::kArchPPC);
auto sems = remill::LoadArchSemantics(arch.get());
remill::DecodingContext dec_context;
dec_context.UpdateContextReg(std::string("VLEReg"), vle_val);
CHECK(dec_context.HasValueForReg("VLEReg"));
remill::Instruction insn;
if (!arch->DecodeInstruction(address, bytes, insn, dec_context)) {
return std::nullopt;
} else {
return insn;
}
}
} // namespace
using test_runner::TestOutputSpec;
template <test_runner::State S>
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::kArchPPC)),
tst_ctr(0),
endian(lifter.GetArch()->MemoryAccessIsLittleEndian()
? llvm::support::endianness::little
: llvm::support::endianness::big) {}
void RunTestSpec(const TestOutputSpec<S> &test,
const remill::DecodingContext &dec_ctx) {
std::stringstream ss;
ss << "test_disas_func_" << this->tst_ctr++;
auto maybe_func = lifter.LiftInstructionFunction(
ss.str(), test.target_bytes, test.addr, dec_ctx);
CHECK(maybe_func.has_value());
auto lifted_func = maybe_func->first;
// Copy lifted function into new module to optimize out intrinsics that aren't used in the lifted function
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);
S st = {};
test.CheckLiftedInstruction(maybe_func->second);
test_runner::RandomizeState(st, 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<S, uint64_t>(
new_func, test.target_bytes.length(), &st, mem_hand.get(),
[](S *st) { return st->pc.qword; });
LOG(INFO) << "Pc after execute " << st.pc.qword;
test.CheckResultingState(st);
test.CheckResultingMemory(*mem_hand);
}
};
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
return RUN_ALL_TESTS();
}
inline const remill::DecodingContext kVLEContext =
remill::DecodingContext({{std::string("VLEReg"), 1}});
// Add two registers
TEST(PPCVLELifts, PPCVLEAdd) {
llvm::LLVMContext curr_context;
// add r5, r4, r3
std::string insn_data("\x7C\xA4\x1A\x14", 4);
TestOutputSpec<PPCState> spec(
0x12, insn_data, remill::Instruction::Category::kCategoryNormal,
{{"r4", uint64_t(0xcc)}, {"r3", uint64_t(0xdd)}, {"pc", uint64_t(0x12)}},
{{"r5", uint64_t(0x1a9)}, {"pc", uint64_t(0x16)}}, reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// Add two registers and record
TEST(PPCVLELifts, PPCVLEAddRecord) {
llvm::LLVMContext curr_context;
// add. r5, r4, r3
// result is positive so cr0[1] is set which is the third bit in little endian
std::string insn_data("\x7C\xA4\x1A\x15", 4);
TestOutputSpec<PPCState> spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"r4", uint64_t(0xcc)},
{"r3", uint64_t(0xdd)},
{"cr0", uint8_t(0)},
{"xer_so", uint8_t(0x0)},
{"pc", uint64_t(0x12)}},
{{"r5", uint64_t(0x1a9)},
{"cr0", uint8_t(0b100)},
{"xer_so", uint8_t(0x0)},
{"pc", uint64_t(0x16)}},
reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// Add two registers and set overflow
TEST(PPCVLELifts, PPCVLEAddOverflow) {
llvm::LLVMContext curr_context;
// addo r5, r4, r3
std::string insn_data("\x7C\xA4\x1E\x14", 4);
TestOutputSpec<PPCState> spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"r4", uint64_t(5000000000000000000)},
{"r3", uint64_t(5000000000000000000)},
{"xer_ov", uint8_t(0x0)},
{"xer_so", uint8_t(0x0)},
{"pc", uint64_t(0x12)}},
{{"r5", uint64_t(0x8ac7230489e80000)},
{"xer_ov", uint8_t(0x1)},
{"xer_so", uint8_t(0x1)},
{"pc", uint64_t(0x16)}},
reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE short Branch to Link Register
TEST(PPCVLELifts, PPCVLEBranchLinkRegister) {
llvm::LLVMContext curr_context;
// se_blr
std::string insn_data("\x00\x04", 2);
TestOutputSpec<PPCState> spec(
0x12, insn_data, remill::Instruction::Category::kCategoryFunctionReturn,
{{"lr", uint64_t(0x4)}, {"pc", uint64_t(0x12)}},
{{"lr", uint64_t(0x4)}, {"pc", uint64_t(0x4)}}, reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE short Branch to Link Register and Link
TEST(PPCVLELifts, PPCVLEBranchLinkRegisterAndLink) {
llvm::LLVMContext curr_context;
// se_blrl
std::string insn_data("\x00\x05", 2);
TestOutputSpec<PPCState> spec(
0x12, insn_data, remill::Instruction::Category::kCategoryFunctionReturn,
{{"lr", uint64_t(0x4)}, {"pc", uint64_t(0x12)}},
{{"lr", uint64_t(0x14)}, {"pc", uint64_t(0x4)}}, reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE long relative branch that branches to negative relative offset
TEST(PPCVLELifts, PPCVLENegBranch) {
llvm::LLVMContext curr_context;
// e_b 0xfffffffa (-0x6)
std::string insn_data("\x79\xff\xff\xfa", 4);
auto maybe_flow = GetFlows(insn_data, 0xdeadbee0, 1);
ASSERT_TRUE(maybe_flow.has_value());
auto act_insn = *maybe_flow;
remill::Instruction::InstructionFlowCategory expected_condjmp =
remill::Instruction::DirectJump(
remill::Instruction::DirectFlow(0xdeadbee0 - 0x6, kVLEContext));
auto actual_condjmp =
std::get<remill::Instruction::DirectJump>(act_insn.flows);
EXPECT_EQ(expected_condjmp, act_insn.flows);
TestOutputSpec<PPCState> spec(
0x12, insn_data, remill::Instruction::Category::kCategoryDirectJump,
{{"pc", uint64_t(0x10)}}, {{"pc", uint64_t(0xa)}}, reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE long relative conditional branch
TEST(PPCVLELifts, PPCVLECondBranch) {
llvm::LLVMContext curr_context;
// e_beq 0xfffffffa (-0x6)
std::string insn_data("\x7a\x12\xff\xfa", 4);
auto maybe_flow = GetFlows(insn_data, 0xdeadbee0, 1);
ASSERT_TRUE(maybe_flow.has_value());
auto act_insn = *maybe_flow;
remill::Instruction::InstructionFlowCategory expected_condjmp =
remill::Instruction::ConditionalInstruction(
remill::Instruction::DirectJump(
remill::Instruction::DirectFlow(0xdeadbee0 - 0x6, kVLEContext)),
remill::Instruction::FallthroughFlow(kVLEContext));
auto actual_condjmp =
std::get<remill::Instruction::ConditionalInstruction>(act_insn.flows);
EXPECT_EQ(expected_condjmp, act_insn.flows);
TestOutputSpec<PPCState> spec(
0x12, insn_data,
remill::Instruction::Category::kCategoryConditionalBranch,
{{"pc", uint64_t(0x10)}, {"cr0", uint8_t(0b10)}},
{{"pc", uint64_t(0xa)}, {"cr0", uint8_t(0b10)}}, reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE long relative conditional branch
TEST(PPCVLELifts, PPCVLECondBranch2) {
llvm::LLVMContext curr_context;
// e_beq 0xfffffffa (-0x6)
std::string insn_data("\x7a\x12\xff\xfa", 4);
auto maybe_flow = GetFlows(insn_data, 0xdeadbee0, 1);
ASSERT_TRUE(maybe_flow.has_value());
auto act_insn = *maybe_flow;
remill::Instruction::InstructionFlowCategory expected_condjmp =
remill::Instruction::ConditionalInstruction(
remill::Instruction::DirectJump(
remill::Instruction::DirectFlow(0xdeadbee0 - 0x6, kVLEContext)),
remill::Instruction::FallthroughFlow(kVLEContext));
auto actual_condjmp =
std::get<remill::Instruction::ConditionalInstruction>(act_insn.flows);
EXPECT_EQ(expected_condjmp, act_insn.flows);
TestOutputSpec<PPCState> spec(
0x12, insn_data,
remill::Instruction::Category::kCategoryConditionalBranch,
{{"pc", uint64_t(0x10)}, {"cr0", uint8_t(0b0)}},
{{"pc", uint64_t(0x14)}, {"cr0", uint8_t(0b0)}}, reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE long relative branch
TEST(PPCVLELifts, PPCVLEBranch) {
llvm::LLVMContext curr_context;
// e_b 0x5a
std::string insn_data("\x78\x00\x00\x5a", 4);
// offset PC by 0x1000012 to also test that relative PC lifting works correctly
TestOutputSpec<PPCState> spec(
0x12, insn_data, remill::Instruction::Category::kCategoryDirectJump,
{{"pc", uint64_t(0x1000012)}}, {{"pc", uint64_t(0x1000012 + 0x5a)}},
reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE short compare immediate
TEST(PPCVLELifts, PPCVLECompareImmediate) {
llvm::LLVMContext curr_context;
// se_cmpi r7, 0x0
std::string insn_data("\x2a\x07", 2);
// cr1[2], set when result is zero
TestOutputSpec<PPCState> spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)},
{"r7", uint64_t(0x0)},
{"xer_so", uint8_t(0)},
{"cr0", uint8_t(0)}},
{{"pc", uint64_t(0x12 + 2)},
{"xer_so", uint8_t(0)},
{"cr0", uint8_t(0b10)}},
reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE Store word
TEST(PPCVLELifts, PPCVLEStoreWord) {
llvm::LLVMContext curr_context;
// e_stw r5, 0x10(r4)
std::string insn_data("\x54\xa4\x00\x10", 4);
TestOutputSpec<PPCState> spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)},
{"r5", uint64_t(0x13371337)},
{"r4", uint64_t(0xdeadbee0)}},
{{"pc", uint64_t(0x12 + 4)},
{"r5", uint64_t(0x13371337)},
{"r4", uint64_t(0xdeadbee0)}},
reg_to_accessor);
spec.AddPrecWrite<uint32_t>(0xdeadbee0 + 0x10, 0x0);
spec.AddPostRead<uint32_t>(0xdeadbee0 + 0x10, 0x13371337);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// Load word and zero
TEST(PPCVLELifts, PPCVLELoadWordAndZero) {
llvm::LLVMContext curr_context;
// e_lwz r5, 0x10(r4)
std::string insn_data("\x50\xa4\x00\x10", 4);
TestOutputSpec<PPCState> spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)},
{"r5", uint64_t(0x0)},
{"r4", uint64_t(0xdeadbee0)}},
{{"pc", uint64_t(0x12 + 4)},
{"r5", uint64_t(0x13371337)},
{"r4", uint64_t(0xdeadbee0)}},
reg_to_accessor);
spec.AddPrecWrite<uint32_t>(0xdeadbee0 + 0x10, 0x13371337);
spec.AddPostRead<uint32_t>(0xdeadbee0 + 0x10, 0x13371337);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE Load Multiple Volatile General Purpose Registers
// Instruction only operates on the 32bit register sizes
TEST(PPCVLELifts, PPCVLELoadMultipleGeneralPurposeRegisters) {
llvm::LLVMContext curr_context;
// e_ldmvgprw 0x0(r1)
std::string insn_data("\x18\x01\x10\x00", 4);
TestOutputSpec<PPCState> spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)},
{"r1", uint64_t(0x13370)},
{"r0", uint64_t(0x0)},
{"r3", uint64_t(0x0)},
{"r4", uint64_t(0x0)},
{"r5", uint64_t(0x0)},
{"r6", uint64_t(0x0)},
{"r7", uint64_t(0x0)},
{"r8", uint64_t(0x0)},
{"r9", uint64_t(0x0)},
{"r10", uint64_t(0x0)},
{"r11", uint64_t(0x0)},
{"r12", uint64_t(0x0)}},
{{"pc", uint64_t(0x12 + 4)},
{"r1", uint64_t(0x13370)},
{"r0", uint64_t(0x11223344)},
{"r3", uint64_t(0x22114433)},
{"r4", uint64_t(0x99aabbcc)},
{"r5", uint64_t(0xaa99ccbb)},
{"r6", uint64_t(0x88776655)},
{"r7", uint64_t(0x77885566)},
{"r8", uint64_t(0x00ffeedd)},
{"r9", uint64_t(0xff00ddee)},
{"r10", uint64_t(0x44332211)},
{"r11", uint64_t(0xccbbaa99)},
{"r12", uint64_t(0xbbcc99aa)}},
reg_to_accessor);
spec.AddPrecWrite<uint32_t>(0x13370, 0x11223344);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x4, 0x22114433);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x8, 0x99aabbcc);
spec.AddPrecWrite<uint32_t>(0x13370 + 0xc, 0xaa99ccbb);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x10, 0x88776655);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x14, 0x77885566);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x18, 0x00ffeedd);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x1c, 0xff00ddee);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x20, 0x44332211);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x24, 0xccbbaa99);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x28, 0xbbcc99aa);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE Store Multiple Volatile General Purpose Registers
TEST(PPCVLELifts, PPCVLEStoreMultipleGeneralPurposeRegisters) {
llvm::LLVMContext curr_context;
// e_stmvgprw 0x0(r1)
std::string insn_data("\x18\x01\x11\x00", 4);
TestOutputSpec<PPCState> spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)},
{"r1", uint64_t(0x13370)},
{"r0", uint64_t(0x11223344)},
{"r3", uint64_t(0x22114433)},
{"r4", uint64_t(0x99aabbcc)},
{"r5", uint64_t(0xaa99ccbb)},
{"r6", uint64_t(0x88776655)},
{"r7", uint64_t(0x77885566)},
{"r8", uint64_t(0x00ffeedd)},
{"r9", uint64_t(0xff00ddee)},
{"r10", uint64_t(0x44332211)},
{"r11", uint64_t(0xccbbaa99)},
{"r12", uint64_t(0xbbcc99aa)}},
{{"pc", uint64_t(0x12 + 4)},
{"r1", uint64_t(0x13370)},
{"r0", uint64_t(0x11223344)},
{"r3", uint64_t(0x22114433)},
{"r4", uint64_t(0x99aabbcc)},
{"r5", uint64_t(0xaa99ccbb)},
{"r6", uint64_t(0x88776655)},
{"r7", uint64_t(0x77885566)},
{"r8", uint64_t(0x00ffeedd)},
{"r9", uint64_t(0xff00ddee)},
{"r10", uint64_t(0x44332211)},
{"r11", uint64_t(0xccbbaa99)},
{"r12", uint64_t(0xbbcc99aa)}},
reg_to_accessor);
spec.AddPostRead<uint32_t>(0x13370, 0x11223344);
spec.AddPostRead<uint32_t>(0x13370 + 0x4, 0x22114433);
spec.AddPostRead<uint32_t>(0x13370 + 0x8, 0x99aabbcc);
spec.AddPostRead<uint32_t>(0x13370 + 0xc, 0xaa99ccbb);
spec.AddPostRead<uint32_t>(0x13370 + 0x10, 0x88776655);
spec.AddPostRead<uint32_t>(0x13370 + 0x14, 0x77885566);
spec.AddPostRead<uint32_t>(0x13370 + 0x18, 0x00ffeedd);
spec.AddPostRead<uint32_t>(0x13370 + 0x1c, 0xff00ddee);
spec.AddPostRead<uint32_t>(0x13370 + 0x20, 0x44332211);
spec.AddPostRead<uint32_t>(0x13370 + 0x24, 0xccbbaa99);
spec.AddPostRead<uint32_t>(0x13370 + 0x28, 0xbbcc99aa);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE Load Multiple Volatile Special Purpose Registers
TEST(PPCVLELifts, PPCVLELoadMultipleSpecialPurposeRegisters) {
llvm::LLVMContext curr_context;
// e_ldmvsprw 0x0(r1)
std::string insn_data("\x18\x21\x10\x00", 4);
TestOutputSpec<PPCState> spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)},
{"r1", uint64_t(0x13370)},
{"cr", uint64_t(0x0)},
{"cr0", uint8_t(0x0)},
{"cr1", uint8_t(0x0)},
{"cr2", uint8_t(0x0)},
{"cr3", uint8_t(0x0)},
{"cr4", uint8_t(0x0)},
{"cr5", uint8_t(0x0)},
{"cr6", uint8_t(0x0)},
{"cr7", uint8_t(0x0)},
{"lr", uint64_t(0x0)},
{"ctr", uint64_t(0x0)},
{"xer", uint64_t(0x0)}},
{{"pc", uint64_t(0x12 + 4)},
{"r1", uint64_t(0x13370)},
// each crN register is 4-bits
{"cr0", uint8_t(0x1)},
{"cr1", uint8_t(0x2)},
{"cr2", uint8_t(0x3)},
{"cr3", uint8_t(0x4)},
{"cr4", uint8_t(0x5)},
{"cr5", uint8_t(0x6)},
{"cr6", uint8_t(0x7)},
{"cr7", uint8_t(0x8)},
{"lr", uint64_t(0x55667788)},
{"ctr", uint64_t(0x99aabbcc)},
{"xer", uint64_t(0xddeeff00)}},
reg_to_accessor);
spec.AddPrecWrite<uint32_t>(0x13370, 0x87654321);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x4, 0x55667788);
spec.AddPrecWrite<uint32_t>(0x13370 + 0x8, 0x99aabbcc);
spec.AddPrecWrite<uint32_t>(0x13370 + 0xc, 0xddeeff00);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// VLE Store Multiple Volatile Special Purpose Registers
// TODO(wtan): Disabled for now due to bug in Ghidra pcode for this instruction
TEST(PPCVLELifts, DISABLED_PPCVLEStoreMultipleSpecialPurposeRegisters) {
llvm::LLVMContext curr_context;
// e_stmvsprw 0x0(r1)
std::string insn_data("\x18\x21\x11\x00", 4);
TestOutputSpec<PPCState> spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)},
{"r1", uint64_t(0x13370)},
{"cr", uint64_t(0x11223344)},
{"lr", uint64_t(0x55667788)},
{"ctr", uint64_t(0x99aabbcc)},
{"cr0", uint8_t(0x1)},
{"cr1", uint8_t(0x2)},
{"cr2", uint8_t(0x3)},
{"cr3", uint8_t(0x4)},
{"cr4", uint8_t(0x5)},
{"cr5", uint8_t(0x6)},
{"cr6", uint8_t(0x7)},
{"cr7", uint8_t(0x8)},
{"xer", uint64_t(0xddeeff00)}},
{{"pc", uint64_t(0x12 + 4)},
{"r1", uint64_t(0x13370)},
{"cr0", uint8_t(0x1)},
{"cr1", uint8_t(0x2)},
{"cr2", uint8_t(0x3)},
{"cr3", uint8_t(0x4)},
{"cr4", uint8_t(0x5)},
{"cr5", uint8_t(0x6)},
{"cr6", uint8_t(0x7)},
{"cr7", uint8_t(0x8)},
{"lr", uint64_t(0x55667788)},
{"ctr", uint64_t(0x99aabbcc)},
{"xer", uint64_t(0xddeeff00)}},
reg_to_accessor);
spec.AddPostRead<uint32_t>(0x13370, 0x87654321);
spec.AddPostRead<uint32_t>(0x13370 + 0x4, 0x55667788);
spec.AddPostRead<uint32_t>(0x13370 + 0x8, 0x99aabbcc);
spec.AddPostRead<uint32_t>(0x13370 + 0xc, 0xddeeff00);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// Rotate Left Word Immediate then AND with Mask
// Tests internal conditional branches in pcode
TEST(PPCVLELifts, PPCVLERotateLeftWordImmediateAndMask) {
llvm::LLVMContext curr_context;
// e_rlwinm r6, r5, 0x1e, 0x1d, 0x1f
// n >> 2 & 7
// (n & 31) >> 2
std::string insn_data("\x74\xa6\xf7\x7f", 4);
TestOutputSpec<PPCState> spec(
0x12, insn_data, remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)}, {"r5", uint64_t(0x1337)}, {"r6", uint64_t(0x0)}},
{{"pc", uint64_t(0x12 + 4)},
{"r5", uint64_t(0x1337)},
{"r6", uint64_t(0x5)}},
reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// Convert Floating-Point Double-Precision from Signed Integer
TEST(PPCVLELifts, PPCVLEConvertDoubleFromSignedInteger) {
llvm::LLVMContext curr_context;
// efdcfsi r5, r4
std::string insn_data("\x10\xa0\x22\xf1", 4);
TestOutputSpec<PPCState> spec(
0x12, insn_data, remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)}, {"r4", uint64_t(0x1337)}, {"r5", uint64_t(0x0)}},
{{"pc", uint64_t(0x12 + 4)},
{"r4", uint64_t(0x1337)},
{"r5", uint64_t(0x40b3370000000000)}},
reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// Convert Floating-Point Single-Precision from Signed Integer
TEST(PPCVLELifts, PPCVLEConvertFloatFromSignedInteger) {
llvm::LLVMContext curr_context;
// efscfsi r5, r4
std::string insn_data("\x10\xa0\x22\xd1", 4);
TestOutputSpec<PPCState> spec(
0x12, insn_data, remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)}, {"r4", uint64_t(0x1337)}, {"r5", uint64_t(0x0)}},
{{"pc", uint64_t(0x12 + 4)},
{"r4", uint64_t(0x1337)},
{"r5", uint64_t(0x4599b800)}},
reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// Convert Floating-Point Single-Precision to Signed Integer
TEST(PPCVLELifts, PPCVLEConvertFloatToSignedInteger) {
llvm::LLVMContext curr_context;
// efsctsi r5, r4
std::string insn_data("\x10\xa0\x22\xd5", 4);
TestOutputSpec<PPCState> spec(0x12, insn_data,
remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)},
{"r4", uint64_t(0x4599b800)},
{"r5", uint64_t(0x0)}},
{{"pc", uint64_t(0x12 + 4)},
{"r4", uint64_t(0x4599b800)},
{"r5", uint64_t(0x1337)}},
reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}
// Test syscall
// TODO(wtan): Disabled, callother not supported
TEST(PPCVLELifts, DISABLED_PPCVLESyscall) {
llvm::LLVMContext curr_context;
// e_sc
std::string insn_data("\x7c\x00\x00\x48", 4);
TestOutputSpec<PPCState> spec(
0x12, insn_data, remill::Instruction::Category::kCategoryNormal,
{{"pc", uint64_t(0x12)}}, {{"pc", uint64_t(0x12 + 4)}}, reg_to_accessor);
TestSpecRunner<PPCState> runner(curr_context);
runner.RunTestSpec(spec, kVLEContext);
}