diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2a5cb4f..75ecbb33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: matrix: image: - { name: 'ubuntu', tag: '20.04' } - llvm: ['14', '15'] + llvm: ['15'] runs-on: ubuntu-20.04 container: @@ -103,9 +103,9 @@ jobs: fail-fast: false matrix: os: ['macos-11'] - llvm: ['14', '15'] + llvm: ['15'] - runs-on: ${{ matrix.os }} + runs-on: macos-12 steps: - uses: actions/checkout@v2 @@ -235,7 +235,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - llvm: ["14", "15"] + llvm: ["15"] ubuntu: ["20.04"] steps: - uses: actions/checkout@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index a4ff6cf5..6f07e41e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,11 +44,11 @@ llvm_map_components_to_libnames(llvm_libs support core irreader bitreader bitwriter passes asmprinter - aarch64codegen aarch64asmparser + aarch64info aarch64desc aarch64codegen aarch64asmparser armcodegen armasmparser interpreter mcjit nvptxdesc - x86codegen x86asmparser + x86info x86codegen x86asmparser sparccodegen sparcasmparser webassemblydesc) message(STATUS "LLVM Libraries: ${llvm_libs}") @@ -72,6 +72,7 @@ set(REMILL_BUILD_SEMANTICS_DIR_AARCH32 "${CMAKE_CURRENT_BINARY_DIR}/lib/Arch/AAr set(REMILL_BUILD_SEMANTICS_DIR_AARCH64 "${CMAKE_CURRENT_BINARY_DIR}/lib/Arch/AArch64/Runtime") set(REMILL_BUILD_SEMANTICS_DIR_SPARC32 "${CMAKE_CURRENT_BINARY_DIR}/lib/Arch/SPARC32/Runtime") set(REMILL_BUILD_SEMANTICS_DIR_SPARC64 "${CMAKE_CURRENT_BINARY_DIR}/lib/Arch/SPARC64/Runtime") +set(REMILL_BUILD_SEMANTICS_DIR_PPC64_32ADDR "${CMAKE_CURRENT_BINARY_DIR}/lib/Arch/PPC/Runtime") set(REMILL_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") set(REMILL_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/lib") @@ -87,7 +88,8 @@ find_package(glog CONFIG REQUIRED) find_package(gflags CONFIG REQUIRED) set(sleigh_ENABLE_TESTS OFF) -set(sleigh_ADDITIONAL_PATCHES "${CMAKE_CURRENT_SOURCE_DIR}/patches/sleigh/x86-ia.patch;${CMAKE_CURRENT_SOURCE_DIR}/patches/sleigh/arm-thumb.patch" CACHE STRING "" FORCE) +file(GLOB sleigh_patches "${CMAKE_CURRENT_SOURCE_DIR}/patches/sleigh/*.patch") +set(sleigh_ADDITIONAL_PATCHES "${sleigh_patches}" CACHE STRING "" FORCE) # GHIDRA SLEIGH FetchContent_Declare(sleigh @@ -202,6 +204,7 @@ target_compile_definitions(remill_settings INTERFACE "REMILL_BUILD_SEMANTICS_DIR_AARCH64=\"${REMILL_BUILD_SEMANTICS_DIR_AARCH64}\"" "REMILL_BUILD_SEMANTICS_DIR_SPARC32=\"${REMILL_BUILD_SEMANTICS_DIR_SPARC32}\"" "REMILL_BUILD_SEMANTICS_DIR_SPARC64=\"${REMILL_BUILD_SEMANTICS_DIR_SPARC64}\"" + "REMILL_BUILD_SEMANTICS_DIR_PPC64_32ADDR=\"${REMILL_BUILD_SEMANTICS_DIR_PPC64_32ADDR}\"" ) target_link_libraries(remill_settings INTERFACE @@ -295,6 +298,11 @@ if(REMILL_ENABLE_TESTING) add_subdirectory(tests/Thumb) endif() + if(REMILL_ENABLE_TESTING_SLEIGH_PPC) + message(STATUS "ppc tests enabled") + add_subdirectory(tests/PPC) + endif() + if(REMILL_ENABLE_TESTING_X86) message(STATUS "X86 tests enabled") add_subdirectory(tests/X86) diff --git a/Dockerfile b/Dockerfile index 295a8f06..5dc90585 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Choose your LLVM version -ARG LLVM_VERSION=13 +ARG LLVM_VERSION=15 ARG ARCH=amd64 ARG UBUNTU_VERSION=20.04 ARG DISTRO_BASE=ubuntu${UBUNTU_VERSION} diff --git a/README.md b/README.md index 6140f688..8ad498df 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@ Most of Remill's dependencies can be provided by the [cxx-common](https://github | [Google Flags](https://github.com/google/glog) | Latest | | [Google Log](https://github.com/google/glog) | Latest | | [Google Test](https://github.com/google/googletest) | Latest | -| [LLVM](http://llvm.org/) | 14 | -| [Clang](http://clang.llvm.org/) | 14 | +| [LLVM](http://llvm.org/) | 15 | +| [Clang](http://clang.llvm.org/) | 15 | | [Intel XED](https://software.intel.com/en-us/articles/xed-x86-encoder-decoder-software-library) | Latest | | [Python](https://www.python.org/) | 2.7 | | Unzip | Latest | @@ -55,7 +55,7 @@ Remill now comes with a Dockerfile for easier testing. This Dockerfile reference The Dockerfile allows for quick builds of multiple supported LLVM, architecture, and Linux configurations. -Quickstart (builds Remill against LLVM 14 on Ubuntu 20.04 for AMD64): +Quickstart (builds Remill against LLVM 15 on Ubuntu 20.04 for AMD64): Clone Remill: ```shell @@ -71,7 +71,7 @@ docker build . -t remill \ -f Dockerfile \ --build-arg UBUNTU_VERSION=20.04 \ --build-arg ARCH=amd64 \ - --build-arg LLVM_VERSION=14 + --build-arg LLVM_VERSION=15 ``` Ensure remill works: diff --git a/bin/differential_tester_x86/LiftAndCompare.cpp b/bin/differential_tester_x86/LiftAndCompare.cpp index 6ec9732d..35e55662 100644 --- a/bin/differential_tester_x86/LiftAndCompare.cpp +++ b/bin/differential_tester_x86/LiftAndCompare.cpp @@ -107,9 +107,6 @@ class DifferentialModuleBuilder { // it is expected that compatible arches share a semantics module. std::unique_ptr context = std::make_unique(); -#if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) - context->enableOpaquePointers(); -#endif auto tmp_arch = remill::Arch::Build(context.get(), os_name_1, arch_name_1); std::shared_ptr semantics_module = remill::LoadArchSemantics(tmp_arch.get()); @@ -142,7 +139,7 @@ class DifferentialModuleBuilder { auto f2 = f2_and_name.first; for (auto x : {f1, f2}) { - llvm::verifyFunction(*x, &llvm::errs()); + CHECK(remill::VerifyFunction(x)); } diff --git a/bin/lift/Lift.cpp b/bin/lift/Lift.cpp index 3e51b23d..967f51a1 100644 --- a/bin/lift/Lift.cpp +++ b/bin/lift/Lift.cpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include #include @@ -23,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -241,9 +244,6 @@ int main(int argc, char *argv[]) { // Make sure `--address` and `--entry_address` are in-bounds for the target // architecture's address size. llvm::LLVMContext context; -#if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) - context.enableOpaquePointers(); -#endif auto arch = remill::Arch::Get(context, FLAGS_os, FLAGS_arch); const uint64_t addr_mask = ~0ULL >> (64UL - arch->address_size); if (FLAGS_address != (FLAGS_address & addr_mask)) { @@ -395,7 +395,9 @@ int main(int argc, char *argv[]) { llvm::Value *trace_args[remill::kNumBlockArgs] = {}; trace_args[remill::kStatePointerArgNum] = state_ptr; trace_args[remill::kMemoryPointerArgNum] = mem_ptr; - trace_args[remill::kPCArgNum] = trace_pc; + trace_args[remill::kPCArgNum] = llvm::ConstantInt::get( + llvm::IntegerType::get(context, arch->address_size), + FLAGS_entry_address, false); mem_ptr = ir.CreateCall(entry_trace, trace_args); @@ -432,6 +434,8 @@ int main(int argc, char *argv[]) { guide.slp_vectorize = true; guide.loop_vectorize = true; + + CHECK(remill::VerifyModule(&dest_module)); remill::OptimizeBareModule(&dest_module, guide); } diff --git a/cmake/options.cmake b/cmake/options.cmake index 39a6d51d..77528625 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -28,5 +28,6 @@ option(REMILL_ENABLE_INSTALL_TARGET "Should Remill be installed?" TRUE) cmake_dependent_option(REMILL_ENABLE_TESTING "Build your tests" ON "can_enable_testing" OFF) cmake_dependent_option(REMILL_ENABLE_TESTING_X86 "Build your tests" ON "REMILL_ENABLE_TESTING;can_enable_testing_x86" OFF) cmake_dependent_option(REMILL_ENABLE_TESTING_AARCH64 "Build your tests" ON "REMILL_ENABLE_TESTING;can_enable_testing_aarch64" OFF) -cmake_dependent_option(REMILL_ENABLE_TESTING_SLEIGH_THUMB "Build cross platform sliegh tests" ON "REMILL_ENABLE_TESTING" OFF) -cmake_dependent_option(REMILL_ENABLE_DIFFERENTIAL_TESTING "Build cross platform differential testing of sleigh x86" ON "REMILL_ENABLE_TESTING" OFF) \ No newline at end of file +cmake_dependent_option(REMILL_ENABLE_TESTING_SLEIGH_THUMB "Build cross platform sleigh tests thumb" ON "REMILL_ENABLE_TESTING" OFF) +cmake_dependent_option(REMILL_ENABLE_TESTING_SLEIGH_PPC "Build cross platform sliegh tests for ppc" ON "REMILL_ENABLE_TESTING" OFF) +cmake_dependent_option(REMILL_ENABLE_DIFFERENTIAL_TESTING "Build cross platform differential testing of sleigh x86" ON "REMILL_ENABLE_TESTING" OFF) diff --git a/cmake/settings.cmake b/cmake/settings.cmake index c031379c..774cd591 100644 --- a/cmake/settings.cmake +++ b/cmake/settings.cmake @@ -29,7 +29,7 @@ macro(main) # # Globally set the required C++ standard - set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_EXTENSIONS OFF) if(UNIX) diff --git a/include/remill/Arch/Arch.h b/include/remill/Arch/Arch.h index f2943e47..56870e2a 100644 --- a/include/remill/Arch/Arch.h +++ b/include/remill/Arch/Arch.h @@ -356,6 +356,7 @@ class Arch { bool IsAArch64(void) const; bool IsSPARC32(void) const; bool IsSPARC64(void) const; + bool IsPPC(void) const; bool IsWindows(void) const; bool IsLinux(void) const; @@ -420,14 +421,18 @@ class Arch { static ArchPtr GetAArch64(llvm::LLVMContext *context, OSName os, ArchName arch_name); - // Defined in `lib/Arch/SleighX86/Arch.cpp` + // Defined in `lib/Arch/Sleigh/X86Arch.cpp` static ArchPtr GetSleighX86(llvm::LLVMContext *context, OSName os, ArchName arch_name); - // Defined in `lib/Arch/SleighThumb2/Arch.cpp` + // Defined in `lib/Arch/Sleigh/Thumb2Arch.cpp` static ArchPtr GetSleighThumb2(llvm::LLVMContext *context, OSName os, ArchName arch_name); + // Defined in `lib/Arch/Sleigh/PPCArch.cpp` + static ArchPtr GetSleighPPC(llvm::LLVMContext *context, OSName os, + ArchName arch_name); + // Defined in `lib/Arch/SPARC32/Arch.cpp`. static ArchPtr GetSPARC(llvm::LLVMContext *context, OSName os, ArchName arch_name); diff --git a/include/remill/Arch/Context.h b/include/remill/Arch/Context.h index e06d6b85..67e2f992 100644 --- a/include/remill/Arch/Context.h +++ b/include/remill/Arch/Context.h @@ -26,6 +26,8 @@ namespace remill { +using ContextValues = std::map; + /// A decoding context is contextual information about the state of the program that affects decoding, ie. the thumb mode register on ARM /// We allow clients to interpose on a context for resolution @@ -36,14 +38,14 @@ namespace remill { class DecodingContext { private: - std::map context_value; + ContextValues context_value; public: bool operator==(const DecodingContext &rhs) const; DecodingContext() = default; - DecodingContext(std::map context_value); + DecodingContext(ContextValues context_value); void UpdateContextReg(std::string creg, uint64_t value); @@ -55,6 +57,8 @@ class DecodingContext { uint64_t GetContextValue(const std::string &context_reg) const; DecodingContext PutContextReg(std::string creg, uint64_t value) const; DecodingContext ContextWithoutRegister(const std::string &creg) const; + + const ContextValues &GetContextValues() const; }; -} // namespace remill \ No newline at end of file +} // namespace remill diff --git a/include/remill/Arch/Name.h b/include/remill/Arch/Name.h index 774f758f..c9de645d 100644 --- a/include/remill/Arch/Name.h +++ b/include/remill/Arch/Name.h @@ -25,6 +25,7 @@ # define REMILL_ON_AARCH32 0 # define REMILL_ON_SPARC64 0 # define REMILL_ON_SPARC32 0 +# define REMILL_ON_PPC 0 # elif defined(__i386__) || defined(_M_X86) # define REMILL_ARCH "x86" # define REMILL_ON_AMD64 0 @@ -33,6 +34,7 @@ # define REMILL_ON_AARCH32 0 # define REMILL_ON_SPARC64 0 # define REMILL_ON_SPARC32 0 +# define REMILL_ON_PPC 0 # elif defined(__aarch64__) # define REMILL_ARCH "aarch64" # define REMILL_ON_AMD64 0 @@ -41,6 +43,7 @@ # define REMILL_ON_AARCH32 0 # define REMILL_ON_SPARC64 0 # define REMILL_ON_SPARC32 0 +# define REMILL_ON_PPC 0 # elif defined(__arm__) # define REMILL_ARCH "aarch32" # define REMILL_ON_AMD64 0 @@ -49,12 +52,14 @@ # define REMILL_ON_AARCH32 1 # define REMILL_ON_SPARC64 0 # define REMILL_ON_SPARC32 0 +# define REMILL_ON_PPC 0 # elif defined(__sparc__) || defined(__sparc) || defined(__sparc_v8__) || \ defined(__sparc_v9__) || defined(__sparcv8) || defined(__sparcv9) # define REMILL_ON_AMD64 0 # define REMILL_ON_X86 0 # define REMILL_ON_AARCH64 0 # define REMILL_ON_AARCH32 0 +# define REMILL_ON_PPC 0 # if (defined(__LP64__) && __LP64__) || (defined(_LP64) && _LP64) # define REMILL_ARCH "sparc64" # define REMILL_ON_SPARC64 1 @@ -64,6 +69,15 @@ # define REMILL_ON_SPARC64 0 # define REMILL_ON_SPARC32 1 # endif +# elif defined(__PPC__) +# define REMILL_ARCH "ppc" +# define REMILL_ON_AMD64 0 +# define REMILL_ON_X86 0 +# define REMILL_ON_AARCH64 0 +# define REMILL_ON_AARCH32 0 +# define REMILL_ON_SPARC64 0 +# define REMILL_ON_SPARC32 0 +# define REMILL_ON_PPC 1 # else # error "Cannot infer current architecture." # define REMILL_ARCH "invalid" @@ -72,6 +86,7 @@ # define REMILL_ON_AARCH64 0 # define REMILL_ON_SPARC64 0 # define REMILL_ON_SPARC32 0 +# define REMILL_ON_PPC 0 # endif #endif @@ -102,6 +117,8 @@ enum ArchName : uint32_t { kArchSparc64, kArchThumb2LittleEndian, + + kArchPPC, }; ArchName GetArchName(const llvm::Triple &triple); diff --git a/include/remill/Arch/PPC/Runtime/State.h b/include/remill/Arch/PPC/Runtime/State.h new file mode 100644 index 00000000..b12245dc --- /dev/null +++ b/include/remill/Arch/PPC/Runtime/State.h @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2022-present 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. + */ + +#pragma once + +#pragma clang diagnostic push +#pragma clang diagnostic fatal "-Wpadded" + +#include "remill/Arch/Runtime/State.h" + +#if !defined(INCLUDED_FROM_REMILL) +# include "remill/Arch/Runtime/Types.h" +#endif + +struct Reg final { + union { + alignas(4) uint32_t dword; + alignas(8) uint64_t qword; + } __attribute__((packed)); +} __attribute__((packed)); + +static_assert(sizeof(uint64_t) == sizeof(Reg), "Invalid packing of `Reg`."); +static_assert(0 == __builtin_offsetof(Reg, dword), + "Invalid packing of `Reg::dword`."); + +static_assert(0 == __builtin_offsetof(Reg, qword), + "Invalid packing of `Reg::qword`."); + +// General Purpose Registers +struct alignas(8) GPR final { + + // Prevents LLVM from casting a `GPR` into an `i64` to access `r0`. + volatile uint64_t _0; + Reg r0; + volatile uint64_t _1; + Reg r1; + volatile uint64_t _2; + Reg r2; + volatile uint64_t _3; + Reg r3; + volatile uint64_t _4; + Reg r4; + volatile uint64_t _5; + Reg r5; + volatile uint64_t _6; + Reg r6; + volatile uint64_t _7; + Reg r7; + volatile uint64_t _8; + Reg r8; + volatile uint64_t _9; + Reg r9; + volatile uint64_t _10; + Reg r10; + volatile uint64_t _11; + Reg r11; + volatile uint64_t _12; + Reg r12; + volatile uint64_t _13; + Reg r13; + volatile uint64_t _14; + Reg r14; + volatile uint64_t _15; + Reg r15; + volatile uint64_t _16; + Reg r16; + volatile uint64_t _17; + Reg r17; + volatile uint64_t _18; + Reg r18; + volatile uint64_t _19; + Reg r19; + volatile uint64_t _20; + Reg r20; + volatile uint64_t _21; + Reg r21; + volatile uint64_t _22; + Reg r22; + volatile uint64_t _23; + Reg r23; + volatile uint64_t _24; + Reg r24; + volatile uint64_t _25; + Reg r25; + volatile uint64_t _26; + Reg r26; + volatile uint64_t _27; + Reg r27; + volatile uint64_t _28; + Reg r28; + volatile uint64_t _29; + Reg r29; + volatile uint64_t _30; + Reg r30; + volatile uint64_t _31; + Reg r31; + +} __attribute__((packed)); + +static_assert(512 == sizeof(GPR), "Invalid structure packing of `GPR`."); + +// Floating Pointer Registers +struct alignas(8) FPR final { + + // Prevents LLVM from casting an `FPR` into an `i64` to access `f0`. + volatile uint64_t _0; + Reg f0; + volatile uint64_t _1; + Reg f1; + volatile uint64_t _2; + Reg f2; + volatile uint64_t _3; + Reg f3; + volatile uint64_t _4; + Reg f4; + volatile uint64_t _5; + Reg f5; + volatile uint64_t _6; + Reg f6; + volatile uint64_t _7; + Reg f7; + volatile uint64_t _8; + Reg f8; + volatile uint64_t _9; + Reg f9; + volatile uint64_t _10; + Reg f10; + volatile uint64_t _11; + Reg f11; + volatile uint64_t _12; + Reg f12; + volatile uint64_t _13; + Reg f13; + volatile uint64_t _14; + Reg f14; + volatile uint64_t _15; + Reg f15; + volatile uint64_t _16; + Reg f16; + volatile uint64_t _17; + Reg f17; + volatile uint64_t _18; + Reg f18; + volatile uint64_t _19; + Reg f19; + volatile uint64_t _20; + Reg f20; + volatile uint64_t _21; + Reg f21; + volatile uint64_t _22; + Reg f22; + volatile uint64_t _23; + Reg f23; + volatile uint64_t _24; + Reg f24; + volatile uint64_t _25; + Reg f25; + volatile uint64_t _26; + Reg f26; + volatile uint64_t _27; + Reg f27; + volatile uint64_t _28; + Reg f28; + volatile uint64_t _29; + Reg f29; + volatile uint64_t _30; + Reg f30; + volatile uint64_t _31; + Reg f31; + +} __attribute__((packed)); + +static_assert(512 == sizeof(FPR), "Invalid structure packing of `FPR`."); + +// These are actually bitflags. +// +// Remill's notion of registers operates at a byte granularity so these need to +// take a full byte. +struct alignas(8) CRFlags final { + + volatile uint8_t _0; + uint8_t cr0; + volatile uint8_t _1; + uint8_t cr1; + volatile uint8_t _2; + uint8_t cr2; + volatile uint8_t _3; + uint8_t cr3; + volatile uint8_t _4; + uint8_t cr4; + volatile uint8_t _5; + uint8_t cr5; + volatile uint8_t _6; + uint8_t cr6; + volatile uint8_t _7; + uint8_t cr7; + +} __attribute__((packed)); + +struct alignas(8) XERFlags final { + + volatile uint8_t _0; + uint8_t so; + volatile uint8_t _1; + uint8_t ov; + volatile uint8_t _2; + uint8_t ca; + volatile uint8_t _3; + uint8_t sl; + // uint8_t _padding[2]; + +} __attribute__((packed)); + +// Instruction-Accessible Registers +struct alignas(8) IAR final { + + // Prevents LLVM from casting an `IAR` into an `i64` to access `cr`. + volatile uint64_t _1; + Reg cr; + volatile uint64_t _2; + Reg ctr; + volatile uint64_t _3; + Reg lr; + volatile uint64_t _4; + Reg xer; + volatile uint64_t _5; + Reg spefscr; + volatile uint64_t _6; + Reg acc; + +} __attribute__((packed)); + +// Read-Only Performance Monitor Registers +struct alignas(8) UPM final { + + volatile uint64_t _0; + Reg gc; + + // Counter registers + volatile uint64_t _1; + Reg c0; + volatile uint64_t _2; + Reg c1; + volatile uint64_t _3; + Reg c2; + volatile uint64_t _4; + Reg c3; + + // Local control registers + volatile uint64_t _5; + Reg lca0; + volatile uint64_t _6; + Reg lca1; + volatile uint64_t _7; + Reg lca2; + volatile uint64_t _8; + Reg lca3; + volatile uint64_t _9; + Reg lcb0; + volatile uint64_t _10; + Reg lcb1; + volatile uint64_t _11; + Reg lcb2; + volatile uint64_t _12; + Reg lcb3; + +} __attribute__((packed)); + +// Time-Based Registers +struct alignas(8) TBR final { + + volatile uint64_t _0; + Reg tbl; + volatile uint64_t _1; + Reg tbu; + volatile uint64_t _2; + Reg atbl; + volatile uint64_t _3; + Reg atbu; + +} __attribute__((packed)); + +// General Special-Purpose Registers +struct alignas(8) SPRG final { + + volatile uint64_t _0; + Reg r3; + volatile uint64_t _1; + Reg r4; + volatile uint64_t _2; + Reg r5; + volatile uint64_t _3; + Reg r6; + volatile uint64_t _4; + Reg r7; + +} __attribute__((packed)); + +// L1 Cache Configuration +struct alignas(8) L1CFG final { + + volatile uint64_t _0; + Reg r0; + volatile uint64_t _1; + Reg r1; + +} __attribute__((packed)); + +// Signals +// +// These are signals that are commonly found on PPC devices +// They are implemented as registers here as Sleigh treats them that way +struct alignas(8) Signals final { + volatile uint64_t _0; + Reg tea; // Transfer Error Acknowledge + uint8_t _padding[8]; +}; + +struct alignas(8) PPCState : public ArchState { + + GPR gpr; // 528 bytes. + + uint64_t _0; + + FPR fpr; + + uint64_t _1; + + IAR iar; + + uint64_t _2; + + UPM upm; + + uint64_t _3; + + TBR tbr; + + uint64_t _4; + + Reg uspr; // User Special-Purpose Register + + uint64_t _5; + + SPRG sprg; + + uint64_t _6; + + L1CFG l1cfg; + + uint64_t _7; + + Reg pc; // This isn't exposed via PPC's API however Sleigh uses a "fake" register to maintain the program counter + + uint64_t _8; + + XERFlags xer_flags; + + uint64_t _9; + + CRFlags cr_flags; + + uint64_t _10; + + Signals signals; + +} __attribute__((packed)); + +// static_assert((1152 + 16) == sizeof(PPCState), +// "Invalid packing of `struct State`"); + +struct State : public PPCState {}; + +#pragma clang diagnostic pop diff --git a/include/remill/Arch/Runtime/HyperCall.h b/include/remill/Arch/Runtime/HyperCall.h index 69043614..4c529c5b 100644 --- a/include/remill/Arch/Runtime/HyperCall.h +++ b/include/remill/Arch/Runtime/HyperCall.h @@ -93,6 +93,8 @@ class SyncHyperCall { kSPARCTrapCondNEG, kSPARCTrapCondVC, kSPARCTrapCondVS, + + kPPCEmulateInstruction, }; } __attribute__((packed)); diff --git a/include/remill/Arch/Runtime/Int.h b/include/remill/Arch/Runtime/Int.h index b2fccd70..1894d594 100644 --- a/include/remill/Arch/Runtime/Int.h +++ b/include/remill/Arch/Runtime/Int.h @@ -20,6 +20,7 @@ #if __has_include() # include +# include #elif __has_include() # include #else @@ -64,7 +65,7 @@ using uint64_t = TypeSelector<8, unsigned, unsigned long, unsigned long long>::T #endif // cstint, cinttypes #if !defined(REMILL_DISABLE_INT128) -#if defined(__x86_64__) || defined(__i386__) || defined(_M_X86) || defined (__arm__) +#if defined(__x86_64__) || defined(__i386__) || defined(_M_X86) || defined (__arm__) || defined(__PPC__) typedef unsigned uint128_t __attribute__((mode(TI))); typedef int int128_t __attribute__((mode(TI))); #elif defined(__aarch64__) diff --git a/include/remill/Arch/Runtime/Intrinsics.h b/include/remill/Arch/Runtime/Intrinsics.h index c1cc4da8..1cc244d7 100644 --- a/include/remill/Arch/Runtime/Intrinsics.h +++ b/include/remill/Arch/Runtime/Intrinsics.h @@ -420,4 +420,7 @@ __remill_sparc32_emulate_instruction(Memory *); [[gnu::used, gnu::const]] extern Memory * __remill_sparc64_emulate_instruction(Memory *); +[[gnu::used, gnu::const]] extern Memory * +__remill_ppc_emulate_instruction(Memory *); + } // extern C diff --git a/include/remill/BC/PCodeCFG.h b/include/remill/BC/PCodeCFG.h new file mode 100644 index 00000000..76a7d87e --- /dev/null +++ b/include/remill/BC/PCodeCFG.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022-present 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. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "lib/Arch/Sleigh/ControlFlowStructuring.h" + + +namespace remill::sleigh { + +// A zero-indexed pointer to a PcodeOp within an instruction, relative +// to the first PcodeOp of the instruction. +struct IntrainstructionIndex { + size_t target_block_index; +}; + +struct InstrExit {}; + +using Exit = std::variant; + +struct ConditionalExit { + Exit true_branch; + Exit false_branch; +}; + +using BlockExit = std::variant; + +class PcodeBlock { + public: + size_t base_index; + std::vector ops; + BlockExit block_exit; + + // Default block with an instruction exit and no ops. + PcodeBlock(size_t base_index); + + PcodeBlock(size_t base_index, std::vector ops, + BlockExit block_exit); +}; + +class PcodeCFG { + public: + std::map blocks; + + PcodeCFG(std::map blocks); +}; + +PcodeCFG CreateCFG(const std::vector &linear_ops); + +class PcodeCFGBuilder { + public: + explicit PcodeCFGBuilder(const std::vector &linear_ops); + PcodeCFG Build() const; + + private: + PcodeBlock BuildBlock(size_t start_ind, size_t next_start) const; + BlockExit GetBlockExitsForIndex(size_t index) const; + std::vector GetIntraProcTargets(size_t index) const; + std::vector GetBlockStarts() const; + + const std::vector &linear_ops; +}; + + +} // namespace remill::sleigh diff --git a/include/remill/BC/SleighLifter.h b/include/remill/BC/SleighLifter.h index 3e70252b..ebda426d 100644 --- a/include/remill/BC/SleighLifter.h +++ b/include/remill/BC/SleighLifter.h @@ -35,6 +35,7 @@ struct BranchTakenVar { VarnodeData target_vnode; size_t index; }; +using MaybeBranchTakenVar = std::optional; class SleighDecoder; class SingleInstructionSleighContext; @@ -60,9 +61,11 @@ class SleighLifter : public InstructionLifter { virtual ~SleighLifter(void) = default; - LiftStatus LiftIntoBlockWithSleighState( - Instruction &inst, llvm::BasicBlock *block, llvm::Value *state_ptr, - bool is_delayed, const std::optional &btaken); + LiftStatus + LiftIntoBlockWithSleighState(Instruction &inst, llvm::BasicBlock *block, + llvm::Value *state_ptr, bool is_delayed, + const sleigh::MaybeBranchTakenVar &btaken, + const ContextValues &context_values); private: static void SetISelAttributes(llvm::Function *); @@ -74,7 +77,8 @@ class SleighLifter : public InstructionLifter { std::pair> LiftIntoInternalBlockWithSleighState( Instruction &inst, llvm::Module *target_mod, bool is_delayed, - const std::optional &btaken); + const sleigh::MaybeBranchTakenVar &btaken, + const ContextValues &context_values); ::Sleigh &GetEngine(void) const; }; @@ -83,11 +87,13 @@ class SleighLifter : public InstructionLifter { // 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 btaken; + sleigh::MaybeBranchTakenVar btaken; + ContextValues context_values; std::shared_ptr lifter; public: - SleighLifterWithState(std::optional btaken, + SleighLifterWithState(sleigh::MaybeBranchTakenVar btaken, + ContextValues context_values, std::shared_ptr lifter_); // Lift a single instruction into a basic block. `is_delayed` signifies that @@ -110,6 +116,10 @@ class SleighLifterWithState final : public InstructionLifterIntf { virtual llvm::Type *GetMemoryType() override; virtual void ClearCache(void) const override; + + const ContextValues &GetContextValues() const { + return context_values; + } }; } // namespace remill diff --git a/include/remill/BC/Util.h b/include/remill/BC/Util.h index 8f36bbff..2a3688f9 100644 --- a/include/remill/BC/Util.h +++ b/include/remill/BC/Util.h @@ -165,6 +165,11 @@ bool VerifyModule(llvm::Module *module); // Returns diagnostic message if verify failed. std::optional VerifyModuleMsg(llvm::Module *module); +// Try to verify a function. +bool VerifyFunction(llvm::Function *func); +// Returns diagnostic message if verify failed. +std::optional VerifyFunctionMsg(llvm::Function *func); + std::unique_ptr LoadModuleFromFile(llvm::LLVMContext *context, std::filesystem::path file_name); diff --git a/include/remill/BC/Version.h b/include/remill/BC/Version.h index e25982c6..4acb3b28 100644 --- a/include/remill/BC/Version.h +++ b/include/remill/BC/Version.h @@ -29,25 +29,16 @@ #define LLVM_VERSION_NUMBER LLVM_VERSION(LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR) -#if LLVM_VERSION_NUMBER < LLVM_VERSION(14, 0) -# error "Minimum supported LLVM version is 14.0" +#if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) +# error "Minimum supported LLVM version is 15.0" #endif -#if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) -# define IF_LLVM_LT_1500(...) __VA_ARGS__ -# define IF_LLVM_LT_1500_(...) __VA_ARGS__, -# define _IF_LLVM_LT_1500(...) , __VA_ARGS__ -# define IF_LLVM_GTE_1500(...) -# define IF_LLVM_GTE_1500_(...) -# define _IF_LLVM_GTE_1500(...) -#else -# define IF_LLVM_LT_1500(...) -# define IF_LLVM_LT_1500_(...) -# define _IF_LLVM_LT_1500(...) -# define IF_LLVM_GTE_1500(...) __VA_ARGS__ -# define IF_LLVM_GTE_1500_(...) __VA_ARGS__, -# define _IF_LLVM_GTE_1500(...) , __VA_ARGS__ -#endif +#define IF_LLVM_LT_1500(...) +#define IF_LLVM_LT_1500_(...) +#define _IF_LLVM_LT_1500(...) +#define IF_LLVM_GTE_1500(...) __VA_ARGS__ +#define IF_LLVM_GTE_1500_(...) __VA_ARGS__, +#define _IF_LLVM_GTE_1500(...) , __VA_ARGS__ #define IF_LLVM_LT(major, minor, ...) IF_LLVM_LT_##major##minor##0(__VA_ARGS__) diff --git a/lib/Arch/Arch.cpp b/lib/Arch/Arch.cpp index b3c5940b..c6370138 100644 --- a/lib/Arch/Arch.cpp +++ b/lib/Arch/Arch.cpp @@ -53,7 +53,8 @@ static unsigned AddressSize(ArchName arch_name) { case kArchX86_SLEIGH: case kArchAArch32LittleEndian: case kArchThumb2LittleEndian: - case kArchSparc32: return 32; + case kArchSparc32: + case kArchPPC: return 32; case kArchAMD64: case kArchAMD64_AVX: case kArchAMD64_AVX512: @@ -107,7 +108,8 @@ ArchLocker Arch::Lock(ArchName arch_name_) { case ArchName::kArchAArch32LittleEndian: case ArchName::kArchThumb2LittleEndian: case ArchName::kArchAMD64_SLEIGH: - case ArchName::kArchX86_SLEIGH: return &gSleighArchLock; + case ArchName::kArchX86_SLEIGH: + case ArchName::kArchPPC: return &gSleighArchLock; default: return ArchLocker(); } } @@ -221,6 +223,12 @@ auto Arch::GetArchByName(llvm::LLVMContext *context_, OSName os_name_, DLOG(INFO) << "Using architecture: 64-bit SPARC"; return GetSPARC64(context_, os_name_, arch_name_); } + + case kArchPPC: { + DLOG(INFO) << "Using architecture: PowerPC"; + return GetSleighPPC(context_, os_name_, arch_name_); + } + default: { return nullptr; } @@ -398,6 +406,10 @@ bool Arch::IsSPARC64(void) const { return remill::kArchSparc64 == arch_name; } +bool Arch::IsPPC(void) const { + return remill::kArchPPC == arch_name; +} + bool Arch::IsWindows(void) const { return remill::kOSWindows == os_name; } diff --git a/lib/Arch/CMakeLists.txt b/lib/Arch/CMakeLists.txt index 89cd741f..e0f50b1c 100644 --- a/lib/Arch/CMakeLists.txt +++ b/lib/Arch/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(remill_arch STATIC add_subdirectory(AArch32) add_subdirectory(AArch64) +add_subdirectory(PPC) add_subdirectory(SPARC32) add_subdirectory(SPARC64) add_subdirectory(Sleigh) diff --git a/lib/Arch/Context.cpp b/lib/Arch/Context.cpp index ff6058aa..409e38c9 100644 --- a/lib/Arch/Context.cpp +++ b/lib/Arch/Context.cpp @@ -9,7 +9,7 @@ bool DecodingContext::operator==(remill::DecodingContext const &rhs) const { return this->context_value == rhs.context_value; } -DecodingContext::DecodingContext(std::map context_value) +DecodingContext::DecodingContext(ContextValues context_value) : context_value(std::move(context_value)) {} @@ -25,7 +25,7 @@ DecodingContext::GetContextValue(const std::string &context_reg) const { DecodingContext DecodingContext::PutContextReg(std::string creg, uint64_t value) const { - std::map new_value(this->context_value); + auto new_value = context_value; new_value.emplace(creg, value); return DecodingContext(std::move(new_value)); } @@ -50,4 +50,8 @@ DecodingContext::ContextWithoutRegister(const std::string &creg) const { return cpy; } -} // namespace remill \ No newline at end of file +const ContextValues &DecodingContext::GetContextValues() const { + return this->context_value; +} + +} // namespace remill diff --git a/lib/Arch/Instruction.cpp b/lib/Arch/Instruction.cpp index f6453744..af82c9d7 100644 --- a/lib/Arch/Instruction.cpp +++ b/lib/Arch/Instruction.cpp @@ -670,6 +670,7 @@ std::string Instruction::Serialize(void) const { case kArchAArch64LittleEndian: ss << "AArch64"; break; case kArchSparc32: ss << "SPARC32"; break; case kArchSparc64: ss << "SPARC64"; break; + case kArchPPC: ss << "PowerPC"; break; } }; diff --git a/lib/Arch/Name.cpp b/lib/Arch/Name.cpp index 8d8d7a64..5d5d6ee0 100644 --- a/lib/Arch/Name.cpp +++ b/lib/Arch/Name.cpp @@ -29,6 +29,7 @@ ArchName GetArchName(const llvm::Triple &triple) { case llvm::Triple::ArchType::thumb: return kArchThumb2LittleEndian; case llvm::Triple::sparc: return kArchSparc32; case llvm::Triple::sparcv9: return kArchSparc64; + case llvm::Triple::ppc: return kArchPPC; default: return kArchInvalid; } } @@ -73,6 +74,9 @@ ArchName GetArchName(std::string_view arch_name) { } else if (arch_name == "sparc64") { return kArchSparc64; + } else if (arch_name == "ppc") { + return kArchPPC; + } else { return kArchInvalid; } @@ -95,6 +99,7 @@ static const std::string_view kArchNames[] = { [kArchAArch64LittleEndian] = "aarch64", [kArchSparc32] = "sparc32", [kArchSparc64] = "sparc64", + [kArchPPC] = "ppc", }; } // namespace diff --git a/lib/Arch/PPC/CMakeLists.txt b/lib/Arch/PPC/CMakeLists.txt new file mode 100644 index 00000000..588d9c73 --- /dev/null +++ b/lib/Arch/PPC/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Runtime) diff --git a/lib/Arch/PPC/Runtime/CMakeLists.txt b/lib/Arch/PPC/Runtime/CMakeLists.txt new file mode 100644 index 00000000..50c9e1b6 --- /dev/null +++ b/lib/Arch/PPC/Runtime/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (c) 2022-present 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. + +cmake_minimum_required(VERSION 3.2) +project(ppc_runtime) + +set(PPCRUNTIME_SOURCEFILES + Instructions.cpp + + "${REMILL_LIB_DIR}/Arch/Runtime/Intrinsics.cpp" +) + +set_source_files_properties(BasicBlock.cpp PROPERTIES COMPILE_FLAGS "-O3 -g0") + +# Visual C++ requires C++14 +if(WIN32) + set(required_cpp_standard "c++14") +else() + set(required_cpp_standard "c++17") +endif() + +add_runtime(ppc + SOURCES ${PPCRUNTIME_SOURCEFILES} + ADDRESS_SIZE 32 + DEFINITIONS "LITTLE_ENDIAN=${little_endian}" + BCFLAGS "-std=${required_cpp_standard}" + INCLUDEDIRECTORIES "${REMILL_INCLUDE_DIR}" "${REMILL_SOURCE_DIR}" + INSTALLDESTINATION "${REMILL_INSTALL_SEMANTICS_DIR}" + ARCH ppc + + DEPENDENCIES + "${REMILL_INCLUDE_DIR}/remill/Arch/PPC/Runtime/State.h" +) diff --git a/lib/Arch/PPC/Runtime/Instructions.cpp b/lib/Arch/PPC/Runtime/Instructions.cpp new file mode 100644 index 00000000..9495fc65 --- /dev/null +++ b/lib/Arch/PPC/Runtime/Instructions.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022-present 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 "remill/Arch/PPC/Runtime/State.h" +#include "remill/Arch/Runtime/Float.h" +#include "remill/Arch/Runtime/Intrinsics.h" +#include "remill/Arch/Runtime/Operators.h" + +// A definition is required to ensure that LLVM doesn't optimize the `State` type out of the bytecode +// See https://github.com/lifting-bits/remill/pull/631#issuecomment-1279989004f +State __remill_state; + +#define HYPER_CALL state.hyper_call + +namespace { + +DEF_SEM(HandleUnsupported) { + return __remill_sync_hyper_call(state, memory, + SyncHyperCall::kPPCEmulateInstruction); +} + +DEF_SEM(HandleInvalidInstruction) { + HYPER_CALL = AsyncHyperCall::kInvalidInstruction; + return memory; +} + +} // namespace + +DEF_ISEL(UNSUPPORTED_INSTRUCTION) = HandleUnsupported; +DEF_ISEL(INVALID_INSTRUCTION) = HandleInvalidInstruction; diff --git a/lib/Arch/Runtime/HyperCall.cpp b/lib/Arch/Runtime/HyperCall.cpp index 62f5ee25..51f54eca 100644 --- a/lib/Arch/Runtime/HyperCall.cpp +++ b/lib/Arch/Runtime/HyperCall.cpp @@ -36,6 +36,9 @@ # else # error "Cannot deduce hyper call SPARC variant" # endif +#elif defined(__PPC__) +# include "remill/Arch/PPC/Runtime/State.h" +# define REMILL_HYPERCALL_PPC 1 #else # error "Cannot deduce hyper call architecture" #endif @@ -377,6 +380,12 @@ Memory *__remill_sync_hyper_call(State &state, Memory *mem, # endif +#elif defined(REMILL_HYPERCALL_PPC) + + case SyncHyperCall::kPPCEmulateInstruction: + mem = __remill_ppc_emulate_instruction(mem); + break; + #endif default: __builtin_unreachable(); break; diff --git a/lib/Arch/Sleigh/Arch.cpp b/lib/Arch/Sleigh/Arch.cpp index 01462a3b..2ea9afd7 100644 --- a/lib/Arch/Sleigh/Arch.cpp +++ b/lib/Arch/Sleigh/Arch.cpp @@ -99,7 +99,7 @@ SingleInstructionSleighContext::SingleInstructionSleighContext( auto pspec_path = ::sleigh::FindSpecFile(pspec_name.c_str()); if (!pspec_path) { - LOG(FATAL) << "Couldn't find required spec file: " << sla_name << '\n'; + LOG(FATAL) << "Couldn't find required pspec file: " << pspec_name << '\n'; } LOG(INFO) << "Using pspec at: " << pspec_path->string(); @@ -191,6 +191,7 @@ bool SleighDecoder::DecodeInstruction(uint64_t address, DecodingContext context) const { + auto context_values = context.GetContextValues(); auto res_cat = const_cast(this)->DecodeInstructionImpl( address, instr_bytes, inst, std::move(context)); @@ -201,8 +202,8 @@ bool SleighDecoder::DecodeInstruction(uint64_t address, << "Should always emit branch taken var for conditional instruction"; } - inst.SetLifter(std::make_shared(res_cat->second, - this->GetLifter())); + inst.SetLifter(std::make_shared( + res_cat->second, std::move(context_values), this->GetLifter())); CHECK(inst.GetLifter() != nullptr); return res_cat.has_value(); } @@ -213,8 +214,8 @@ SleighDecoder::SleighDecoder( std::unordered_map context_reg_map_, std::unordered_map state_reg_map_) : sleigh_ctx(sla_name, pspec_name), - sla_name(sla_name), - pspec_name(pspec_name), + sla_name(std::move(sla_name)), + pspec_name(std::move(pspec_name)), lifter(nullptr), arch(arch_), context_reg_mapping(std::move(context_reg_map_)), @@ -231,8 +232,8 @@ SleighDecoder::GetStateRegRemappings() const { return this->state_reg_remappings; } -std::optional>> +std::optional< + std::pair> SleighDecoder::DecodeInstructionImpl(uint64_t address, std::string_view instr_bytes, Instruction &inst, @@ -243,7 +244,8 @@ SleighDecoder::DecodeInstructionImpl(uint64_t address, // Now decode the instruction. this->sleigh_ctx.resetContext(); - this->InitializeSleighContext(this->sleigh_ctx); + this->InitializeSleighContext(this->sleigh_ctx, + curr_context.GetContextValues()); PcodeDecoder pcode_handler(this->sleigh_ctx.GetEngine()); @@ -296,12 +298,12 @@ SleighDecoder::DecodeInstructionImpl(uint64_t address, } -std::string SleighDecoder::GetSLAName() const { +const std::string &SleighDecoder::GetSLAName() const { return this->sla_name; } -std::string SleighDecoder::GetPSpec() const { +const std::string &SleighDecoder::GetPSpec() const { return this->pspec_name; } @@ -334,7 +336,7 @@ std::optional SingleInstructionSleighContext::oneInstruction( return std::nullopt; } } catch (BadDataError e) { - LOG(ERROR) << "Bad data error"; + LOG(ERROR) << "Bad data error: " << e.explain; // NOTE (Ian): if sleigh cant find a constructor it throws an exception... yay for unrolling. return std::nullopt; } catch (UnimplError e) { @@ -465,9 +467,29 @@ void SleighDecoder::ApplyFlowToInstruction(remill::Instruction &inst) const { std::visit(conditional_applyer, cat.taken_branch); }, - }; // namespace remill::sleigh + }; std::visit(applyer, inst.flows); } +uint64_t GetContextRegisterValue(const char *remill_reg_name, + uint64_t default_value, + const ContextValues &context_values) { + const auto iter = context_values.find(remill_reg_name); + if (iter != context_values.end()) { + return iter->second; + } + return default_value; +} + + +void SetContextRegisterValueInSleigh( + const char *remill_reg_name, const char *sleigh_reg_name, + uint64_t default_value, sleigh::SingleInstructionSleighContext &ctxt, + const ContextValues &context_values) { + auto value = + GetContextRegisterValue(remill_reg_name, default_value, context_values); + ctxt.GetContext().setVariableDefault(sleigh_reg_name, value); +} + } // namespace remill::sleigh diff --git a/lib/Arch/Sleigh/Arch.h b/lib/Arch/Sleigh/Arch.h index 164edab5..006ddf77 100644 --- a/lib/Arch/Sleigh/Arch.h +++ b/lib/Arch/Sleigh/Arch.h @@ -103,12 +103,12 @@ class SleighDecoder { const remill::Arch &arch, std::string sla_name, std::string pspec_name, std::unordered_map context_reg_mapping, std::unordered_map state_reg_remappings); - std::string GetSLAName() const; + const std::string &GetSLAName() const; - std::string GetPSpec() const; + const std::string &GetPSpec() const; // Decoder specific prep - virtual void - InitializeSleighContext(SingleInstructionSleighContext &) const = 0; + virtual void InitializeSleighContext(SingleInstructionSleighContext &, + const ContextValues &) const = 0; virtual llvm::Value *LiftPcFromCurrPc(llvm::IRBuilder<> &bldr, @@ -150,4 +150,14 @@ class SleighDecoder { std::unordered_map context_reg_mapping; std::unordered_map state_reg_remappings; }; + +uint64_t GetContextRegisterValue(const char *remill_reg_name, + uint64_t default_value, + const ContextValues &context_values); + +void SetContextRegisterValueInSleigh( + const char *remill_reg_name, const char *sleigh_reg_name, + uint64_t default_value, sleigh::SingleInstructionSleighContext &ctxt, + const ContextValues &context_values); + } // namespace remill::sleigh diff --git a/lib/Arch/Sleigh/CMakeLists.txt b/lib/Arch/Sleigh/CMakeLists.txt index c42e116a..8824f5ec 100644 --- a/lib/Arch/Sleigh/CMakeLists.txt +++ b/lib/Arch/Sleigh/CMakeLists.txt @@ -30,11 +30,15 @@ add_library(remill_arch_sleigh STATIC "${REMILL_INCLUDE_DIR}/remill/Arch/AArch32/Runtime/State.h" "${REMILL_INCLUDE_DIR}/remill/Arch/AArch32/Runtime/Types.h" + "${REMILL_INCLUDE_DIR}/remill/Arch/PPC/Runtime/State.h" + Arch.h Thumb.h + PPC.h Arch.cpp X86Arch.cpp Thumb2Arch.cpp + PPCArch.cpp ControlFlowStructuring.cpp ControlFlowStructuring.h ) diff --git a/lib/Arch/Sleigh/ControlFlowStructuring.cpp b/lib/Arch/Sleigh/ControlFlowStructuring.cpp index e057aff9..c22c812f 100644 --- a/lib/Arch/Sleigh/ControlFlowStructuring.cpp +++ b/lib/Arch/Sleigh/ControlFlowStructuring.cpp @@ -1,6 +1,9 @@ #include #include +#include +#include + namespace remill::sleigh { bool isVarnodeInConstantSpace(VarnodeData vnode) { @@ -29,14 +32,19 @@ auto variant_cast(const std::variant &v) return {v}; } -enum CoarseEffect { kAbnormal, kNormal }; +enum class CoarseEffect { kAbnormal, kNormal, kIntraInstruction }; struct CoarseFlow { CoarseEffect eff; bool is_conditional; }; -enum CoarseCategory { kCatNormal, kCatAbnormal, kCatConditionalAbnormal }; +enum class CoarseCategory { + kCatNormal, + kCatNormalWithIntraInstructionFlow, + kCatAbnormal, + kCatConditionalAbnormal +}; static CoarseEffect EffectFromDirectControlFlowOp(const RemillPcodeOp &op, @@ -59,14 +67,13 @@ CoarseFlowFromControlFlowOp(const RemillPcodeOp &op, uint64_t next_pc) { 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 {{CoarseEffect::kIntraInstruction, is_conditional}}; } return {{EffectFromDirectControlFlowOp(op, next_pc), is_conditional}}; } -// gets a list of indeces and coarse categories in this pcodeop block +// gets a list of indices and coarse categories in this pcodeop block static std::optional> CoarseFlows(const std::vector &ops, uint64_t next_pc) { std::map res; @@ -118,7 +125,6 @@ static bool isUnconditionalNormal(CoarseFlow flow) { static std::optional CoarseCategoryFromFlows(const std::map &ops) { - auto all_normal_effects = std::all_of( ops.begin(), ops.end(), [](const std::pair &op) { return op.second.eff == CoarseEffect::kNormal; @@ -127,6 +133,17 @@ CoarseCategoryFromFlows(const std::map &ops) { return CoarseCategory::kCatNormal; } + auto is_normal_or_intra = [](const std::pair &op) { + return op.second.eff == CoarseEffect::kNormal || + op.second.eff == CoarseEffect::kIntraInstruction; + }; + auto all_normal_or_intra_effects = + std::all_of(ops.begin(), ops.end(), is_normal_or_intra); + + if (all_normal_or_intra_effects) { + return CoarseCategory::kCatNormalWithIntraInstructionFlow; + } + auto all_abnormal_effects = std::all_of( ops.begin(), ops.end(), [](const std::pair &op) { return op.second.eff == CoarseEffect::kAbnormal; @@ -258,7 +275,7 @@ ExtractNonConditionalCategory( return std::nullopt; } - Instruction::InstructionFlowCategory fst = cats[0]; + auto fst = cats[0]; auto all_flows_equal = [&fst](Instruction::InstructionFlowCategory curr_cat) { return fst == curr_cat; }; @@ -380,14 +397,14 @@ So in a coarse grained way we can just treat indirect/direct/interprocedural flo 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 +- 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 +- 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. +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 */ @@ -410,10 +427,33 @@ ControlFlowStructureAnalysis::ComputeCategory( DLOG(ERROR) << "No coarse category found"; return std::nullopt; } - auto context_updater = this->BuildContextUpdater(std::move(entry_context)); + auto context_updater = BuildContextUpdater(entry_context); + + if (*maybe_ccategory == CoarseCategory::kCatNormalWithIntraInstructionFlow) { + // our control flow analysis for decoding contexts doesnt handle intraprocedural control flow, + // so if we have a normal instruction with no decoding context updates then we are fine otherwise bail + auto no_context_updates = std::all_of( + ops.begin(), ops.end(), [&context_updater](const RemillPcodeOp &op) { + return !op.outvar.has_value() || + !context_updater.GetRemillReg(*op.outvar).has_value(); + }); + + if (no_context_updates) { + Instruction::NormalInsn norm_insn( + (Instruction::FallthroughFlow(entry_context))); + Instruction::InstructionFlowCategory ifc = norm_insn; + return std::make_pair(ifc, std::nullopt); + } + DLOG(ERROR) + << "Had an instructon with intrainstruction flow, but also decoding context updates"; + return std::nullopt; + } + auto flows = GetBoundContextsForFlows(ops, cc, context_updater); switch (*maybe_ccategory) { + case CoarseCategory::kCatNormalWithIntraInstructionFlow: + return std::nullopt; case CoarseCategory::kCatAbnormal: return ExtractAbnormal(flows, ops); case CoarseCategory::kCatConditionalAbnormal: return ExtractConditionalAbnormal(flows, ops); @@ -421,6 +461,18 @@ ControlFlowStructureAnalysis::ComputeCategory( } } +std::optional +ContextUpdater::GetRemillReg(const VarnodeData &outvar) { + auto reg_name = + engine.getRegisterName(outvar.space, outvar.offset, outvar.size); + auto it = context_reg_mapping.find(reg_name); + if (it != context_reg_mapping.end()) { + return it->second; + } + return std::nullopt; +} + + // Applies a pcode op to the held context, this may produce a complete context void ContextUpdater::ApplyPcodeOp(const RemillPcodeOp &op) { if (!op.outvar) { @@ -428,36 +480,35 @@ void ContextUpdater::ApplyPcodeOp(const RemillPcodeOp &op) { } 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()) { + auto maybe_remill_reg_name = GetRemillReg(out); + if (!maybe_remill_reg_name) { return; } - auto remill_reg_name = maybe_remill_reg_name->second; + auto remill_reg_name = *maybe_remill_reg_name; if (op.op == CPUI_COPY && isVarnodeInConstantSpace(op.vars[0])) { - this->curr_context.UpdateContextReg(remill_reg_name, op.vars[0].offset); + curr_context.UpdateContextReg(remill_reg_name, op.vars[0].offset); } else { - this->curr_context.DropReg(remill_reg_name); + curr_context.DropReg(remill_reg_name); } } // May have a complete context std::optional ContextUpdater::GetContext() const { for (const auto &[_, remill_reg] : this->context_reg_mapping) { - if (!this->curr_context.HasValueForReg(remill_reg)) { + if (!curr_context.HasValueForReg(remill_reg)) { return std::nullopt; } } - return this->curr_context; + return curr_context; } ContextUpdater ControlFlowStructureAnalysis::BuildContextUpdater( DecodingContext initial_context) { - return ContextUpdater(this->context_reg_mapping, std::move(initial_context), - this->engine); + return ContextUpdater(context_reg_mapping, std::move(initial_context), + engine); } ContextUpdater::ContextUpdater( @@ -475,4 +526,4 @@ ControlFlowStructureAnalysis::ControlFlowStructureAnalysis( engine(engine_) {} -} // namespace remill::sleigh \ No newline at end of file +} // namespace remill::sleigh diff --git a/lib/Arch/Sleigh/ControlFlowStructuring.h b/lib/Arch/Sleigh/ControlFlowStructuring.h index 52c4dec0..75c88ae2 100644 --- a/lib/Arch/Sleigh/ControlFlowStructuring.h +++ b/lib/Arch/Sleigh/ControlFlowStructuring.h @@ -5,6 +5,7 @@ #include #include +#include #include namespace remill::sleigh { @@ -33,6 +34,9 @@ class ContextUpdater { // Applies a pcode op to the held context, this may produce a complete context void ApplyPcodeOp(const RemillPcodeOp &op); + std::optional GetRemillReg(const VarnodeData &); + + // May have a complete context std::optional GetContext() const; }; @@ -47,9 +51,8 @@ class ControlFlowStructureAnalysis { ContextUpdater BuildContextUpdater(DecodingContext initial_context); public: - using SleighDecodingResult = - std::optional>>; + using SleighDecodingResult = std::optional< + std::pair>; ControlFlowStructureAnalysis( const std::unordered_map &context_reg_mapping, Sleigh &engine); @@ -60,4 +63,4 @@ class ControlFlowStructureAnalysis { uint64_t fallthrough_addr, DecodingContext entry_context); }; -} // namespace remill::sleigh \ No newline at end of file +} // namespace remill::sleigh diff --git a/lib/Arch/Sleigh/PPC.h b/lib/Arch/Sleigh/PPC.h new file mode 100644 index 00000000..dc8c2ae0 --- /dev/null +++ b/lib/Arch/Sleigh/PPC.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022-present 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "Arch.h" + +namespace remill::sleighppc { + +class SleighPPCDecoder final : public remill::sleigh::SleighDecoder { + public: + SleighPPCDecoder(const remill::Arch &); + + llvm::Value *LiftPcFromCurrPc(llvm::IRBuilder<> &, llvm::Value *, + size_t) const override; + + void InitializeSleighContext(remill::sleigh::SingleInstructionSleighContext &, + const ContextValues &) const override; +}; + +} // namespace remill::sleighppc diff --git a/lib/Arch/Sleigh/PPCArch.cpp b/lib/Arch/Sleigh/PPCArch.cpp new file mode 100644 index 00000000..65a8e4cd --- /dev/null +++ b/lib/Arch/Sleigh/PPCArch.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2022-present 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 "PPC.h" + +#define INCLUDED_FROM_REMILL +#include + +namespace remill { + +namespace sleighppc { + +static constexpr auto kPPCVLERegName = "VLEReg"; + +SleighPPCDecoder::SleighPPCDecoder(const remill::Arch &arch) + : SleighDecoder(arch, "ppc_64_isa_vle_be.sla", "ppc_64.pspec", + {{"vle", kPPCVLERegName}}, {}) {} + +llvm::Value *SleighPPCDecoder::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(), curr_insn_size)); +} + +void SleighPPCDecoder::InitializeSleighContext( + remill::sleigh::SingleInstructionSleighContext &ctxt, + const ContextValues &context_values) const { + // If the context value mappings specify a value for the VLE register, let's pass that into + // Sleigh. + // + // Otherwise, default to VLE off. + sleigh::SetContextRegisterValueInSleigh(kPPCVLERegName, "vle", 0, ctxt, + context_values); +} + +class SleighPPCArch : public ArchBase { + public: + SleighPPCArch(llvm::LLVMContext *context_, OSName os_name_, + ArchName arch_name_) + : ArchBase(context_, os_name_, arch_name_), + decoder(*this) {} + virtual ~SleighPPCArch() = default; + + DecodingContext CreateInitialContext(void) const override { + return DecodingContext().PutContextReg(kPPCVLERegName, 0); + } + + std::string_view StackPointerRegisterName(void) const override { + return "R1"; + } + + std::string_view ProgramCounterRegisterName(void) const override { + return "PC"; + } + + OperandLifter::OpLifterPtr + DefaultLifter(const remill::IntrinsicTable &intrinsics) const override { + return decoder.GetOpLifter(); + } + + 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 MinInstructionAlign(const DecodingContext &) const override { + return 2; + } + + uint64_t MinInstructionSize(const DecodingContext &) const override { + return 2; + } + + uint64_t MaxInstructionSize(const DecodingContext &, bool) const override { + return 4; + } + + llvm::CallingConv::ID DefaultCallingConv(void) const override { + return llvm::CallingConv::C; + } + + llvm::Triple Triple(void) const override { + auto triple = BasicTriple(); + triple.setArch(llvm::Triple::ppc); + return triple; + } + + llvm::DataLayout DataLayout(void) const override { + return llvm::DataLayout("e-m:e-p:32:32-i32:32-i64:64-f64:64-n32:64-S128"); + } + + void PopulateRegisterTable(void) const override { + CHECK_NOTNULL(context); + + reg_by_offset.resize(sizeof(PPCState)); + + auto u8 = llvm::Type::getInt8Ty(*context); + auto u32 = llvm::Type::getInt32Ty(*context); + auto u64 = llvm::Type::getInt64Ty(*context); + + auto f64 = llvm::Type::getDoubleTy(*context); + +#define OFFSET_OF(type, access) \ + (reinterpret_cast(&reinterpret_cast( \ + static_cast(nullptr)->access))) + +#define REG(name, access, type) \ + AddRegister(#name, type, OFFSET_OF(PPCState, access), nullptr) + +#define SUB_REG(name, access, type, parent_reg_name) \ + AddRegister(#name, type, OFFSET_OF(PPCState, access), #parent_reg_name) + + REG(R0, gpr.r0.qword, u64); + REG(R1, gpr.r1.qword, u64); + REG(R2, gpr.r2.qword, u64); + REG(R3, gpr.r3.qword, u64); + REG(R4, gpr.r4.qword, u64); + REG(R5, gpr.r5.qword, u64); + REG(R6, gpr.r6.qword, u64); + REG(R7, gpr.r7.qword, u64); + REG(R8, gpr.r8.qword, u64); + REG(R9, gpr.r9.qword, u64); + REG(R10, gpr.r10.qword, u64); + REG(R11, gpr.r11.qword, u64); + REG(R12, gpr.r12.qword, u64); + REG(R13, gpr.r13.qword, u64); + REG(R14, gpr.r14.qword, u64); + REG(R15, gpr.r15.qword, u64); + REG(R16, gpr.r16.qword, u64); + REG(R17, gpr.r17.qword, u64); + REG(R18, gpr.r18.qword, u64); + REG(R19, gpr.r19.qword, u64); + REG(R20, gpr.r20.qword, u64); + REG(R21, gpr.r21.qword, u64); + REG(R22, gpr.r22.qword, u64); + REG(R23, gpr.r23.qword, u64); + REG(R24, gpr.r24.qword, u64); + REG(R25, gpr.r25.qword, u64); + REG(R26, gpr.r26.qword, u64); + REG(R27, gpr.r27.qword, u64); + REG(R28, gpr.r28.qword, u64); + REG(R29, gpr.r29.qword, u64); + REG(R30, gpr.r30.qword, u64); + REG(R31, gpr.r31.qword, u64); + + REG(F0, fpr.f0.qword, f64); + REG(F1, fpr.f1.qword, f64); + REG(F2, fpr.f2.qword, f64); + REG(F3, fpr.f3.qword, f64); + REG(F4, fpr.f4.qword, f64); + REG(F5, fpr.f5.qword, f64); + REG(F6, fpr.f6.qword, f64); + REG(F7, fpr.f7.qword, f64); + REG(F8, fpr.f8.qword, f64); + REG(F9, fpr.f9.qword, f64); + REG(F10, fpr.f10.qword, f64); + REG(F11, fpr.f11.qword, f64); + REG(F12, fpr.f12.qword, f64); + REG(F13, fpr.f13.qword, f64); + REG(F14, fpr.f14.qword, f64); + REG(F15, fpr.f15.qword, f64); + REG(F16, fpr.f16.qword, f64); + REG(F17, fpr.f17.qword, f64); + REG(F18, fpr.f18.qword, f64); + REG(F19, fpr.f19.qword, f64); + REG(F20, fpr.f20.qword, f64); + REG(F21, fpr.f21.qword, f64); + REG(F22, fpr.f22.qword, f64); + REG(F23, fpr.f23.qword, f64); + REG(F24, fpr.f24.qword, f64); + REG(F25, fpr.f25.qword, f64); + REG(F26, fpr.f26.qword, f64); + REG(F27, fpr.f27.qword, f64); + REG(F28, fpr.f28.qword, f64); + REG(F29, fpr.f29.qword, f64); + REG(F30, fpr.f30.qword, f64); + + REG(CRALL, iar.cr.qword, u64); + REG(CTR, iar.ctr.qword, u64); + REG(LR, iar.lr.qword, u64); + REG(XER, iar.xer.dword, u32); + REG(SPEFCR, iar.spefscr.qword, u64); + REG(ACC, iar.acc.qword, u64); + + // These are actually bitflags within XER and CR respectively. These would + // normally be subregisters however, Sleigh treats these as entirely + // separate registers of size 1. + REG(XER_SO, xer_flags.so, u8); + REG(XER_OV, xer_flags.ov, u8); + REG(XER_CA, xer_flags.ca, u8); + REG(XER_COUNT, xer_flags.sl, u8); + + REG(CR0, cr_flags.cr0, u8); + REG(CR1, cr_flags.cr1, u8); + REG(CR2, cr_flags.cr2, u8); + REG(CR3, cr_flags.cr3, u8); + REG(CR4, cr_flags.cr4, u8); + REG(CR5, cr_flags.cr5, u8); + REG(CR6, cr_flags.cr6, u8); + REG(CR7, cr_flags.cr7, u8); + + REG(TBLR, tbr.tbl.qword, u64); + REG(TBUR, tbr.tbu.qword, u64); + + REG(SPR103, sprg.r3.qword, u64); + REG(SPR104, sprg.r4.qword, u64); + REG(SPR105, sprg.r5.qword, u64); + REG(SPR106, sprg.r6.qword, u64); + REG(SPR107, sprg.r7.qword, u64); + + REG(SPR203, l1cfg.r0.qword, u64); + REG(SPR204, l1cfg.r1.qword, u64); + + REG(PC, pc, u64); + + REG(TEA, signals.tea.qword, u64); + } + + void + FinishLiftedFunctionInitialization(llvm::Module *module, + llvm::Function *bb_func) const override { + auto &context = module->getContext(); + const auto addr = llvm::Type::getInt64Ty(context); + + auto &entry_block = bb_func->getEntryBlock(); + llvm::IRBuilder<> ir(&entry_block); + + const auto pc_arg = NthArgument(bb_func, kPCArgNum); + const auto state_ptr_arg = NthArgument(bb_func, kStatePointerArgNum); + + auto mk_alloca = [&](auto &from) { + return ir.CreateAlloca(addr, nullptr, from.data()); + }; + ir.CreateStore(pc_arg, mk_alloca(kNextPCVariableName)); + ir.CreateStore(pc_arg, mk_alloca(kIgnoreNextPCVariableName)); + + std::ignore = RegisterByName(kPCVariableName)->AddressOf(state_ptr_arg, ir); + } + + private: + SleighPPCDecoder decoder; +}; + +} // namespace sleighppc + +Arch::ArchPtr Arch::GetSleighPPC(llvm::LLVMContext *context_, + remill::OSName os_name_, + remill::ArchName arch_name_) { + return std::make_unique(context_, os_name_, + arch_name_); +} + +} // namespace remill diff --git a/lib/Arch/Sleigh/Thumb.h b/lib/Arch/Sleigh/Thumb.h index 027d573b..a4b1a90c 100644 --- a/lib/Arch/Sleigh/Thumb.h +++ b/lib/Arch/Sleigh/Thumb.h @@ -22,8 +22,9 @@ class SleighThumb2Decoder final : public remill::sleigh::SleighDecoder { virtual llvm::Value *LiftPcFromCurrPc(llvm::IRBuilder<> &bldr, llvm::Value *, size_t curr_insn_size) const final; - void InitializeSleighContext( - remill::sleigh::SingleInstructionSleighContext &ctxt) const final; + void + InitializeSleighContext(remill::sleigh::SingleInstructionSleighContext &ctxt, + const ContextValues &context_values) const final; }; } // namespace sleighthumb2 -} // namespace remill \ No newline at end of file +} // namespace remill diff --git a/lib/Arch/Sleigh/Thumb2Arch.cpp b/lib/Arch/Sleigh/Thumb2Arch.cpp index 86ffe0f5..5b142f9d 100644 --- a/lib/Arch/Sleigh/Thumb2Arch.cpp +++ b/lib/Arch/Sleigh/Thumb2Arch.cpp @@ -41,7 +41,8 @@ SleighThumb2Decoder::SleighThumb2Decoder(const remill::Arch &arch) void SleighThumb2Decoder::InitializeSleighContext( - remill::sleigh::SingleInstructionSleighContext &ctxt) const { + remill::sleigh::SingleInstructionSleighContext &ctxt, + const ContextValues &) const { ctxt.GetContext().setVariableDefault("TMode", 1); } diff --git a/lib/Arch/Sleigh/X86Arch.cpp b/lib/Arch/Sleigh/X86Arch.cpp index a6e9e1d6..a1519399 100644 --- a/lib/Arch/Sleigh/X86Arch.cpp +++ b/lib/Arch/Sleigh/X86Arch.cpp @@ -42,8 +42,9 @@ class SleighX86Decoder final : public SleighDecoder { {}, {}) {} // The x86 default context is sufficient. No context register assignments are required. - void InitializeSleighContext( - remill::sleigh::SingleInstructionSleighContext &ctxt) const override {} + void + InitializeSleighContext(remill::sleigh::SingleInstructionSleighContext &ctxt, + const ContextValues &) const override {} llvm::Value *LiftPcFromCurrPc(llvm::IRBuilder<> &bldr, llvm::Value *curr_pc, size_t curr_insn_size) const final { diff --git a/lib/BC/CMakeLists.txt b/lib/BC/CMakeLists.txt index 6defe7aa..cf49b961 100644 --- a/lib/BC/CMakeLists.txt +++ b/lib/BC/CMakeLists.txt @@ -31,6 +31,7 @@ add_library(remill_bc STATIC Optimizer.cpp TraceLifter.cpp SleighLifter.cpp + PcodeCFG.cpp Util.cpp ) diff --git a/lib/BC/PcodeCFG.cpp b/lib/BC/PcodeCFG.cpp new file mode 100644 index 00000000..0cfbc171 --- /dev/null +++ b/lib/BC/PcodeCFG.cpp @@ -0,0 +1,163 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/Arch/Sleigh/ControlFlowStructuring.h" + +namespace remill { +namespace sleigh { + +PcodeCFG CreateCFG(const std::vector &linear_ops) { + return PcodeCFGBuilder(linear_ops).Build(); +} + + +std::vector PcodeCFGBuilder::GetBlockStarts() const { + std::vector res = {0}; + + for (size_t curr_index = 0; curr_index < linear_ops.size(); curr_index += 1) { + auto interproc = GetIntraProcTargets(curr_index); + res.insert(res.end(), interproc.begin(), interproc.end()); + } + return res; +} + +PcodeBlock PcodeCFGBuilder::BuildBlock(size_t start_ind, + size_t next_start) const { + std::vector ops; + + std::copy(linear_ops.begin() + start_ind, linear_ops.begin() + next_start, + std::back_inserter(ops)); + + return PcodeBlock(start_ind, std::move(ops), + GetBlockExitsForIndex(next_start - 1)); +} + + +namespace { +struct IntraProcTransferCollector { + + static std::vector CollectIntraProcTransfers(const BlockExit &exit) { + IntraProcTransferCollector collector; + std::visit(collector, exit); + return collector.targets; + } + + std::vector targets; + + void operator()(const IntrainstructionIndex &ex) { + targets.push_back(ex.target_block_index); + } + + void operator()(const Exit &ex) { + std::visit(*this, ex); + } + + void operator()(const ConditionalExit &ex) { + std::visit(*this, ex.true_branch); + std::visit(*this, ex.false_branch); + } +}; +} // namespace + +std::vector PcodeCFGBuilder::GetIntraProcTargets(size_t index) const { + auto ex = GetBlockExitsForIndex(index); + return IntraProcTransferCollector::CollectIntraProcTransfers(ex); +} + +BlockExit PcodeCFGBuilder::GetBlockExitsForIndex(size_t index) const { + CHECK(index < linear_ops.size()); + const auto &curr_op = linear_ops[index]; + + auto build_direct_target_exit = [](VarnodeData target, + size_t curr_ind) -> Exit { + if (isVarnodeInConstantSpace(target)) { + // need to treat as signed? + return IntrainstructionIndex{curr_ind + target.offset}; + } + return InstrExit{}; + }; + switch (curr_op.op) { + case CPUI_BRANCH: + case CPUI_CALL: { + return Exit{build_direct_target_exit(curr_op.vars[0], index)}; + } + case CPUI_CBRANCH: { + + auto fallthrough_exit = [this, index]() -> Exit { + // if we are not the last pcodeop then we have an intraproc fallthrough + if (index < linear_ops.size() - 1) { + return IntrainstructionIndex{index + 1}; + } + return InstrExit{}; + }(); + + auto taken_exit = build_direct_target_exit(curr_op.vars[0], index); + + return ConditionalExit{taken_exit, fallthrough_exit}; + } + case CPUI_CALLIND: + case CPUI_BRANCHIND: { + return Exit{InstrExit{}}; + } + + default: { + return Exit{InstrExit{}}; + } + } +} + + +PcodeBlock::PcodeBlock(size_t base_index) + : base_index(base_index), + ops(), + block_exit(Exit{InstrExit{}}) {} + +PcodeCFG PcodeCFGBuilder::Build() const { + + auto starts = GetBlockStarts(); + + // De-duplicate and sort the block starts. We want to iterate in order. + std::set s(starts.begin(), starts.end()); + starts.assign(s.begin(), s.end()); + + std::map blocks; + + if (linear_ops.empty()) { + // There is no insturction at 0 to build a block at + // build an empty block so we transfer through to exit by terminating the block + blocks.emplace(0, PcodeBlock(0)); + return blocks; + } + + for (size_t i = 0; i < starts.size(); i++) { + auto next_start = + (i + 1) < starts.size() ? starts[i + 1] : linear_ops.size(); + blocks.emplace(starts[i], BuildBlock(starts[i], next_start)); + } + + return PcodeCFG(blocks); +} + + +PcodeCFGBuilder::PcodeCFGBuilder(const std::vector &linear_ops) + : linear_ops(linear_ops) {} + +PcodeCFG::PcodeCFG(std::map blocks) + : blocks(std::move(blocks)) {} + +PcodeBlock::PcodeBlock(size_t base_index, std::vector ops, + BlockExit block_exit) + : base_index(base_index), + ops(std::move(ops)), + block_exit(std::move(block_exit)) {} + +} // namespace sleigh +} // namespace remill diff --git a/lib/BC/SleighLifter.cpp b/lib/BC/SleighLifter.cpp index d1e745de..294a53bd 100644 --- a/lib/BC/SleighLifter.cpp +++ b/lib/BC/SleighLifter.cpp @@ -18,10 +18,13 @@ #include #include #include +#include #include #include +#include #include #include +#include #include #include @@ -30,9 +33,11 @@ #include #include #include +#include #include "remill/BC/InstructionLifter.h" + namespace remill { @@ -57,6 +62,7 @@ std::string DumpPcode(Sleigh &engine, const remill::sleigh::RemillPcodeOp &op) { ss << " = "; } for (size_t i = 0; i < op.vars.size(); ++i) { + ss << ' '; auto iv = op.vars[i]; print_vardata(engine, ss, iv); } @@ -85,9 +91,28 @@ static llvm::Value *ExtractOverflowBitFromCallToIntrinsic( // The value at index 1 is the overflow bit. return bldr.CreateExtractValue(res_val, {1}); } + +using BitShiftFunction = std::function &)>; + +llvm::Value *CreatePcodeBitShift(llvm::Value *lhs, llvm::Value *rhs, + llvm::IRBuilder<> &bldr, + const BitShiftFunction &bitshift_func) { + if (lhs->getType() != rhs->getType()) { + rhs = bldr.CreateZExtOrTrunc(rhs, lhs->getType()); + } + // If the number of bits we're shifting exceeds the bit width of the + // other operand, the result should be zero. + auto max_shift = llvm::ConstantInt::get(lhs->getType(), + lhs->getType()->getIntegerBitWidth()); + return bldr.CreateSelect(bldr.CreateICmpSGE(rhs, max_shift), + llvm::ConstantInt::get(lhs->getType(), 0), + bitshift_func(lhs, rhs, bldr)); +} + } // namespace -class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { +class SleighLifter::PcodeToLLVMEmitIntoBlock { private: class Parameter { public: @@ -212,6 +237,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { llvm::BasicBlock *target_block; + const sleigh::PcodeBlock *pcode_block{nullptr}; llvm::Value *state_pointer; llvm::LLVMContext &context; const Instruction &insn; @@ -254,7 +280,8 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { SleighLifter::PcodeToLLVMEmitIntoBlock &lifter, VarnodeData lhs_constant, VarnodeData rhs_unfolded_value) { - assert(isVarnodeInConstantSpace(lhs_constant)); + CHECK(isVarnodeInConstantSpace(lhs_constant)); + DLOG(INFO) << "Adding (" << lhs_constant.offset << ") to map"; this->current_replacements.insert( {lhs_constant.offset, lifter.LiftParamPtr(bldr, rhs_unfolded_value)}); } @@ -265,9 +292,14 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { this->used_values.clear(); } + // NOTE(wtan): this may end up replacing constants that shouldn't be replaced + // if the claim_eq happens to be used on constant values that are used elsewhere + // In practice, we don't expect this to happen since the program will be mapped + // to a higher address space so collisions should be rare llvm::Value *LiftOffsetOrReplace(llvm::IRBuilder<> &bldr, VarnodeData target, llvm::Type *target_type) { + DLOG(INFO) << "Fetching (" << target.offset << ") from map"; if (this->current_replacements.find(target.offset) != this->current_replacements.end()) { @@ -288,6 +320,13 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { return llvm::ConstantInt::get(target_type, target.offset); } + + // Returns true if the equality claim was used or if no equality claims were declared + // if the equality claim is not used at all when lifting an instruction, + // this can indicate that there is a bug + bool IsEqualityUsed() const { + return !used_values.empty() || current_replacements.empty(); + } }; UniqueRegSpace uniques; @@ -297,11 +336,15 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { // Generic sleigh arch std::vector user_op_names; + llvm::BasicBlock *entry_block; llvm::BasicBlock *exit_block; size_t curr_id; - const std::optional &to_lift_btaken; + const sleigh::MaybeBranchTakenVar &to_lift_btaken; + + std::unordered_map start_index_to_block; + void UpdateStatus(LiftStatus new_status, OpCode opc) { if (new_status != LiftStatus::kLiftedInstruction) { @@ -312,12 +355,33 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } } + llvm::BasicBlock *GetBlock(size_t target) const { + auto blk = start_index_to_block.find(target); + if (blk != start_index_to_block.end()) { + return blk->second; + } + return nullptr; + } + public: - PcodeToLLVMEmitIntoBlock( - llvm::BasicBlock *target_block, llvm::Value *state_pointer, - const Instruction &insn, SleighLifter &insn_lifter_parent, - std::vector user_op_names_, llvm::BasicBlock *exit_block_, - const std::optional &to_lift_btaken_) + llvm::BasicBlock *GetOrCreateBlock(size_t target) { + if (auto blk = GetBlock(target)) { + return blk; + } + + auto newblk = llvm::BasicBlock::Create(this->exit_block->getContext(), "", + this->exit_block->getParent()); + + this->start_index_to_block[target] = newblk; + return newblk; + } + + PcodeToLLVMEmitIntoBlock(llvm::BasicBlock *target_block, + llvm::Value *state_pointer, const Instruction &insn, + SleighLifter &insn_lifter_parent, + std::vector user_op_names_, + llvm::BasicBlock *exit_block_, + const sleigh::MaybeBranchTakenVar &to_lift_btaken_) : target_block(target_block), state_pointer(state_pointer), context(target_block->getContext()), @@ -327,6 +391,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { uniques(target_block->getContext()), unknown_regs(target_block->getContext()), user_op_names(user_op_names_), + entry_block(target_block), exit_block(exit_block_), curr_id(0), to_lift_btaken(to_lift_btaken_) {} @@ -373,12 +438,15 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { return *res; } + // Uniques must be allocated in the entry block + llvm::IRBuilder<> entry_bldr(entry_block); + std::stringstream ss; - auto reg_ptr = this->unknown_regs.GetUniquePtr(target_vnode.offset, - target_vnode.size, bldr); + auto reg_ptr = this->unknown_regs.GetUniquePtr( + target_vnode.offset, target_vnode.size, entry_bldr); print_vardata(this->insn_lifter_parent.GetEngine(), ss, target_vnode); - DLOG(ERROR) << "Creating unique for unkown register: " << ss.str() << " " + DLOG(ERROR) << "Creating unique for unknown register: " << ss.str() << " " << reg_ptr->getName().str(); return RegisterValue::CreateRegister(reg_ptr); @@ -412,7 +480,11 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { return ConstantValue::CreatConstant(cst_v); } else if (space_name == "unique") { - auto reg_ptr = this->uniques.GetUniquePtr(vnode.offset, vnode.size, bldr); + // Uniques must be allocated in the entry block + llvm::IRBuilder<> entry_bldr(entry_block); + + auto reg_ptr = + this->uniques.GetUniquePtr(vnode.offset, vnode.size, entry_bldr); return RegisterValue::CreateRegister(reg_ptr); } else { LOG(FATAL) << "Unhandled memory space: " << space_name; @@ -446,7 +518,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { LiftStatus LiftRequireOutParam(std::function inner_lift, - VarnodeData *outvar) { + std::optional outvar) { if (outvar) { return inner_lift(*outvar); } else { @@ -457,7 +529,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { LiftStatus LiftStoreIntoOutParam(llvm::IRBuilder<> &bldr, llvm::Value *inner_lifted, - VarnodeData *outvar) { + std::optional outvar) { return this->LiftRequireOutParam( [&bldr, this, inner_lifted](VarnodeData out_param_data) { auto ptr = this->LiftParamPtr(bldr, out_param_data); @@ -468,17 +540,18 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { LiftStatus LiftUnaryOpWithFloatIntrinsic( llvm::IRBuilder<> &bldr, - llvm::Intrinsic::IndependentIntrinsics intrinsic_id, VarnodeData *outvar, - VarnodeData input_var) { + llvm::Intrinsic::IndependentIntrinsics intrinsic_id, + std::optional outvar, VarnodeData input_var) { auto inval = this->LiftFloatInParam(bldr, input_var); if (!inval) { return LiftStatus::kLiftedUnsupportedInstruction; } - llvm::Function *intrinsic = llvm::Intrinsic::getDeclaration( - bldr.GetInsertBlock()->getModule(), intrinsic_id); llvm::Value *intrinsic_args[] = {*inval}; + llvm::Function *intrinsic = + llvm::Intrinsic::getDeclaration(bldr.GetInsertBlock()->getModule(), + intrinsic_id, {(*inval)->getType()}); return this->LiftStoreIntoOutParam( bldr, this->CastFloatResult(bldr, *outvar, @@ -496,7 +569,8 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } LiftStatus LiftFloatUnop(llvm::IRBuilder<> &bldr, OpCode opc, - VarnodeData *outvar, VarnodeData input_var) { + std::optional outvar, + VarnodeData input_var) { switch (opc) { case OpCode::CPUI_FLOAT_NEG: { auto negate_inval = this->LiftFloatInParam(bldr, input_var); @@ -530,7 +604,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { auto nan_inval = this->LiftFloatInParam(bldr, input_var); if (nan_inval.has_value()) { // LLVM trunk has an `isnan` intrinsic but to support older versions, I think we need to do this. - auto *isnan_check = bldr.CreateZExt( + auto isnan_check = bldr.CreateZExt( bldr.CreateNot(bldr.CreateFCmpORD(*nan_inval, *nan_inval)), llvm::IntegerType::get(this->context, outvar->size * 8)); return this->LiftStoreIntoOutParam(bldr, isnan_check, outvar); @@ -539,9 +613,9 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } case OpCode::CPUI_FLOAT_INT2FLOAT: { auto int2float_inval = this->LiftIntegerInParam(bldr, input_var); - if (int2float_inval.has_value()) { - auto *converted = bldr.CreateSIToFP( - *int2float_inval, llvm::Type::getFloatTy(this->context)); + auto new_float_type = this->GetFloatTypeOfByteSize(outvar->size); + if (int2float_inval.has_value() && new_float_type) { + auto converted = bldr.CreateSIToFP(*int2float_inval, *new_float_type); return this->LiftStoreIntoOutParam( bldr, this->CastFloatResult(bldr, *outvar, converted), outvar); } @@ -565,7 +639,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { auto trunc_inval = this->LiftFloatInParam(bldr, input_var); if (trunc_inval.has_value()) { // Should this be UI? - auto *converted = bldr.CreateFPToSI( + auto converted = bldr.CreateFPToSI( *trunc_inval, llvm::IntegerType::get(this->context, outvar->size * 8)); return this->LiftStoreIntoOutParam(bldr, converted, outvar); @@ -580,7 +654,8 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } LiftStatus LiftUnaryOp(llvm::IRBuilder<> &bldr, OpCode opc, - VarnodeData *outvar, VarnodeData input_var) { + std::optional outvar, + VarnodeData input_var) { auto res = this->LiftFloatUnop(bldr, opc, outvar, input_var); if (res != LiftStatus::kLiftedUnsupportedInstruction) { @@ -620,8 +695,8 @@ 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)) { - DLOG(ERROR) << "Internal control flow not supported"; - return LiftStatus::kLiftedUnsupportedInstruction; + this->TerminateBlock(); + return LiftStatus::kLiftedInstruction; } auto input_val = this->replacement_cont.LiftOffsetOrReplace( @@ -647,11 +722,11 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { case OpCode::CPUI_INT_SEXT: { auto zext_inval = this->LiftIntegerInParam(bldr, input_var); if (zext_inval.has_value()) { - auto *zext_type = + auto zext_type = llvm::IntegerType::get(this->context, outvar->size * 8); - auto *zext_op = (opc == OpCode::CPUI_INT_ZEXT) - ? bldr.CreateZExt(*zext_inval, zext_type) - : bldr.CreateSExt(*zext_inval, zext_type); + auto zext_op = (opc == OpCode::CPUI_INT_ZEXT) + ? bldr.CreateZExt(*zext_inval, zext_type) + : bldr.CreateSExt(*zext_inval, zext_type); return this->LiftStoreIntoOutParam(bldr, zext_op, outvar); } break; @@ -700,24 +775,66 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { static std::unordered_set INTEGER_COMP_OPS; + struct VisitExit { + PcodeToLLVMEmitIntoBlock &lifter; + + llvm::BasicBlock *operator()(const sleigh::Exit &exit) { + return std::visit(*this, exit); + } + + llvm::BasicBlock *operator()(const sleigh::InstrExit &exit) { + return lifter.exit_block; + }; + + llvm::BasicBlock *operator()(const sleigh::IntrainstructionIndex &exit) { + return lifter.GetOrCreateBlock(exit.target_block_index); + }; + }; + struct VisitBlockExitTrue : public VisitExit { + using VisitExit::operator(); + + llvm::BasicBlock *operator()(const sleigh::ConditionalExit &exit) { + return std::visit(*this, exit.true_branch); + } + }; + + struct VisitBlockExitFalse : public VisitExit { + using VisitExit::operator(); + + llvm::BasicBlock *operator()(const sleigh::ConditionalExit &exit) { + return std::visit(*this, exit.false_branch); + } + }; + + llvm::BasicBlock *GetTrueOut() { + return std::visit(VisitBlockExitTrue{*this}, this->pcode_block->block_exit); + } + + llvm::BasicBlock *GetFalseOut() { + return std::visit(VisitBlockExitFalse{*this}, + this->pcode_block->block_exit); + } + LiftStatus TerminateBlockWithCondition(llvm::Value *condition) { - llvm::IRBuilder<> ir(this->target_block); - this->target_block = llvm::BasicBlock::Create( - this->context, "continuation", this->target_block->getParent()); - ir.CreateCondBr(condition, this->exit_block, this->target_block); + if (this->target_block->getTerminator() == nullptr) { + llvm::IRBuilder<> ir(this->target_block); + + ir.CreateCondBr(condition, GetTrueOut(), GetFalseOut()); + } return LiftStatus::kLiftedInstruction; } void TerminateBlock() { if (this->target_block->getTerminator() == nullptr) { llvm::IRBuilder ir(this->target_block); - ir.CreateBr(this->exit_block); + ir.CreateBr(GetTrueOut()); } } - LiftStatus LiftCBranch(llvm::IRBuilder<> &bldr, VarnodeData *outvar, - VarnodeData lhs, VarnodeData rhs) { + LiftStatus LiftCBranch(llvm::IRBuilder<> &bldr, + std::optional outvar, VarnodeData lhs, + VarnodeData rhs) { auto should_branch = this->LiftInParam( bldr, rhs, llvm::IntegerType::get(this->context, rhs.size * 8)); @@ -726,40 +843,36 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { return LiftStatus::kLiftedUnsupportedInstruction; } - if (isVarnodeInConstantSpace(lhs)) { - DLOG(ERROR) << "Internal control flow not supported"; - return LiftStatus::kLiftedUnsupportedInstruction; - } - - // directs dont read the address of the variable, the offset is the jump - // TODO(Ian): handle other address spaces - 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, 1)); + if (!isVarnodeInConstantSpace(lhs)) { + // directs dont read the address of the variable, the offset is the jump + // TODO(Ian): handle other address spaces + auto jump_addr = this->replacement_cont.LiftOffsetOrReplace( + bldr, lhs, this->insn_lifter_parent.GetWordType()); + auto pc_reg_param = this->LiftNormalRegister(bldr, "PC"); + CHECK(pc_reg_param.has_value()); + auto pc_reg_ptr = *pc_reg_param; + auto orig_pc_value = pc_reg_ptr->LiftAsInParam( + bldr, this->insn_lifter_parent.GetWordType()); - auto pc_reg_param = this->LiftNormalRegister(bldr, "PC"); - assert(pc_reg_param.has_value()); - auto pc_reg_ptr = *pc_reg_param; - auto orig_pc_value = - pc_reg_ptr->LiftAsInParam(bldr, this->insn_lifter_parent.GetWordType()); + if (orig_pc_value.has_value()) { + auto next_pc_value = + bldr.CreateSelect(trunc_should_branch, jump_addr, *orig_pc_value); - if (orig_pc_value.has_value()) { - auto next_pc_value = - bldr.CreateSelect(trunc_should_branch, jump_addr, *orig_pc_value); - - bldr.CreateStore(next_pc_value, this->GetNextPcRef()); + bldr.CreateStore(next_pc_value, this->GetNextPcRef()); + } } return this->TerminateBlockWithCondition(trunc_should_branch); } LiftStatus LiftIntegerBinOp(llvm::IRBuilder<> &bldr, OpCode opc, - VarnodeData *outvar, VarnodeData lhs, - VarnodeData rhs) { + std::optional outvar, + VarnodeData lhs, VarnodeData rhs) { if (INTEGER_BINARY_OPS.find(opc) != INTEGER_BINARY_OPS.end()) { @@ -767,6 +880,7 @@ 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()) { + DLOG(INFO) << "Binop op: " << get_opname(opc); DLOG(INFO) << "Binop with lhs: " << remill::LLVMThingToString(*lifted_lhs); DLOG(INFO) << "Binop with rhs: " @@ -790,7 +904,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { LiftStatus LiftBoolBinOp(llvm::IRBuilder<> &bldr, OpCode opc, - VarnodeData *outvar, VarnodeData lhs, + std::optional outvar, VarnodeData lhs, VarnodeData rhs) { // We make sure to only attempt to lift params for operands where we know they are booleans @@ -910,7 +1024,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } LiftStatus LiftFloatBinOp(llvm::IRBuilder<> &bldr, OpCode opc, - VarnodeData *outvar, VarnodeData lhs, + std::optional outvar, VarnodeData lhs, VarnodeData rhs) { std::optional op_func = this->FindFloatBinOpFunc(opc); if (!op_func) { @@ -936,8 +1050,9 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } - LiftStatus LiftBinOp(llvm::IRBuilder<> &bldr, OpCode opc, VarnodeData *outvar, - VarnodeData lhs, VarnodeData rhs) { + LiftStatus LiftBinOp(llvm::IRBuilder<> &bldr, OpCode opc, + std::optional outvar, VarnodeData lhs, + VarnodeData rhs) { if (opc == OpCode::CPUI_CBRANCH) { return this->LiftCBranch(bldr, outvar, lhs, rhs); @@ -982,7 +1097,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } if (opc == OpCode::CPUI_PIECE && outvar) { - assert(rhs.size + lhs.size == outvar->size); + CHECK(rhs.size + lhs.size == outvar->size); // Treat them as integers auto lifted_lhs = this->LiftInParam( @@ -992,14 +1107,14 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { if (lifted_lhs.has_value() && lifted_rhs.has_value()) { // Widen the most significant operand and then left shift it to make room for the least significant operand. - auto *ms_operand = bldr.CreateZExt( + auto ms_operand = bldr.CreateZExt( *lifted_lhs, llvm::IntegerType::get(this->context, outvar->size)); - auto *shifted_ms_operand = bldr.CreateShl( + auto shifted_ms_operand = bldr.CreateShl( ms_operand, llvm::ConstantInt::get( llvm::Type::getInt8Ty(this->context), rhs.size)); // Now concatenate them with an OR. - auto *concat = bldr.CreateOr(shifted_ms_operand, *lifted_rhs); + auto concat = bldr.CreateOr(shifted_ms_operand, *lifted_rhs); return this->LiftStoreIntoOutParam(bldr, concat, outvar); } } @@ -1010,15 +1125,13 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { 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)); + auto subpiece_lhs = bldr.CreateLShr(*lifted_lhs, rhs.offset * 8); - if (new_size < outvar->size) { + if (lhs.size < outvar->size) { subpiece_lhs = bldr.CreateZExt( subpiece_lhs, llvm::IntegerType::get(this->context, 8 * outvar->size)); - } else if (new_size > outvar->size) { + } else if (lhs.size > outvar->size) { subpiece_lhs = bldr.CreateTrunc( subpiece_lhs, llvm::IntegerType::get(this->context, 8 * outvar->size)); @@ -1044,8 +1157,9 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } LiftStatus LiftThreeOperandOp(llvm::IRBuilder<> &bldr, OpCode opc, - VarnodeData *outvar, VarnodeData param0, - VarnodeData param1, VarnodeData param2) { + std::optional outvar, + VarnodeData param0, VarnodeData param1, + VarnodeData param2) { switch (opc) { case OpCode::CPUI_STORE: { auto addr_operand = param1; @@ -1068,7 +1182,7 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { auto lifted_addr = this->LiftInParam( bldr, param0, this->insn_lifter_parent.GetWordType()), lifted_index = this->LiftIntegerInParam(bldr, param1); - auto *elem_size = llvm::ConstantInt::get( + auto elem_size = llvm::ConstantInt::get( llvm::IntegerType::get(this->context, param2.size * 8), param2.offset); if (lifted_addr.has_value() && lifted_index.has_value()) { @@ -1095,8 +1209,8 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } LiftStatus LiftVariadicOp(llvm::IRBuilder<> &bldr, OpCode opc, - VarnodeData *outvar, VarnodeData *vars, - int4 isize) { + std::optional outvar, + VarnodeData *vars, int4 isize) { switch (opc) { // We shouldnt encounter this afaik MULTIEQUAL is a decompiler concept? case OpCode::CPUI_MULTIEQUAL: { @@ -1137,7 +1251,8 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } static const size_t kEqualityClaimArity = 3; - LiftStatus HandleCallOther(llvm::IRBuilder<> &bldr, VarnodeData *outvar, + LiftStatus HandleCallOther(llvm::IRBuilder<> &bldr, + std::optional outvar, VarnodeData *vars, int4 isize) { auto other_func_name = this->GetOtherFuncName(vars, isize); if (other_func_name.has_value()) { @@ -1188,8 +1303,9 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } } - void LiftPcodeOp(llvm::IRBuilder<> &bldr, OpCode opc, VarnodeData *outvar, - VarnodeData *vars, int4 isize) { + void LiftPcodeOp(llvm::IRBuilder<> &bldr, OpCode opc, + std::optional 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), @@ -1225,12 +1341,24 @@ class SleighLifter::PcodeToLLVMEmitIntoBlock : public PcodeEmit { } } - virtual void dump(const Address &addr, OpCode opc, VarnodeData *outvar, - VarnodeData *vars, int4 isize) final override { + + void VisitBlock(const sleigh::PcodeBlock &blk) { + this->target_block = GetOrCreateBlock(blk.base_index); + this->pcode_block = &blk; llvm::IRBuilder bldr(this->target_block); - this->LiftBtakenIfReached(bldr, opc); - this->LiftPcodeOp(bldr, opc, outvar, vars, isize); - this->curr_id++; + + // we have a problem with block terminators where a cbranch -> fallthrough, need to either exit to the exit block + // or transfer to a block. So really our cfg needs to tell us how to terminate a block + // either exit (means real control flow), to block (fake control flow) + for (auto pc : blk.ops) { + this->LiftPcodeOp(bldr, pc.op, pc.outvar, pc.vars.data(), pc.vars.size()); + } + + this->TerminateBlock(); + } + + bool ClaimEqualityUsed() const { + return this->replacement_cont.IsEqualityUsed(); } LiftStatus GetStatus() { @@ -1276,17 +1404,19 @@ std::map }}, {OpCode::CPUI_INT_LEFT, [](llvm::Value *lhs, llvm::Value *rhs, llvm::IRBuilder<> &bldr) { - if (lhs->getType() != rhs->getType()) { - rhs = bldr.CreateZExtOrTrunc(rhs, lhs->getType()); - } - return bldr.CreateShl(lhs, rhs); + return CreatePcodeBitShift( + lhs, rhs, bldr, + [](llvm::Value *lhs, llvm::Value *rhs, llvm::IRBuilder<> &bldr) { + return bldr.CreateShl(lhs, rhs); + }); }}, {OpCode::CPUI_INT_RIGHT, [](llvm::Value *lhs, llvm::Value *rhs, llvm::IRBuilder<> &bldr) { - if (lhs->getType() != rhs->getType()) { - rhs = bldr.CreateZExtOrTrunc(rhs, lhs->getType()); - } - return bldr.CreateLShr(lhs, rhs); + return CreatePcodeBitShift( + lhs, rhs, bldr, + [](llvm::Value *lhs, llvm::Value *rhs, llvm::IRBuilder<> &bldr) { + return bldr.CreateLShr(lhs, rhs); + }); }}, {OpCode::CPUI_INT_SRIGHT, [](llvm::Value *lhs, llvm::Value *rhs, llvm::IRBuilder<> &bldr) { @@ -1378,9 +1508,7 @@ SleighLifter::SleighLifter(const remill::Arch &arch_, : InstructionLifter(&arch_, intrinsics_), sleigh_context(new sleigh::SingleInstructionSleighContext( dec_.GetSLAName(), dec_.GetPSpec())), - decoder(dec_) { - this->decoder.InitializeSleighContext(*sleigh_context); -} + decoder(dec_) {} const std::string_view SleighLifter::kInstructionFunctionPrefix = @@ -1427,10 +1555,11 @@ SleighLifter::DefineInstructionFunction(Instruction &inst, std::pair> SleighLifter::LiftIntoInternalBlockWithSleighState( Instruction &inst, llvm::Module *target_mod, bool is_delayed, - const std::optional &btaken) { + const sleigh::MaybeBranchTakenVar &btaken, + const ContextValues &context_values) { this->sleigh_context->resetContext(); - this->decoder.InitializeSleighContext(*this->sleigh_context); + this->decoder.InitializeSleighContext(*this->sleigh_context, context_values); sleigh::PcodeDecoder pcode_record(this->GetEngine()); sleigh_context->oneInstruction(inst.pc, pcode_record, inst.bytes); @@ -1441,7 +1570,6 @@ SleighLifter::LiftIntoInternalBlockWithSleighState( 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 = @@ -1461,25 +1589,36 @@ SleighLifter::LiftIntoInternalBlockWithSleighState( //TODO(Ian): make a safe to use sleighinstruction context that wraps a context with an arch to preform reset reinits + auto cfg = sleigh::CreateCFG(pcode_record.ops); + 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); + for (auto blk : cfg.blocks) { + lifter.VisitBlock(blk.second); + } + // Log error if claim_eq values that were declared saw no uses + if (lifter.ClaimEqualityUsed()) { + LOG(ERROR) << "claim_eq value not used when lifting " << inst.Serialize(); + } + + ir.CreateBr(lifter.GetOrCreateBlock(0)); - lifter.TerminateBlock(); // Setup like an ISEL SleighLifter::SetISelAttributes(target_func); remill::InitFunctionAttributes(target_func); + CHECK(remill::VerifyFunction(target_func)); return {lifter.GetStatus(), target_func}; } LiftStatus SleighLifter::LiftIntoBlockWithSleighState( Instruction &inst, llvm::BasicBlock *block, llvm::Value *state_ptr, - bool is_delayed, const std::optional &btaken) { + bool is_delayed, const sleigh::MaybeBranchTakenVar &btaken, + const ContextValues &context_values) { if (!inst.IsValid()) { DLOG(ERROR) << "Invalid function" << inst.Serialize(); return kLiftedInvalidInstruction; @@ -1488,7 +1627,7 @@ LiftStatus SleighLifter::LiftIntoBlockWithSleighState( // Call the instruction function auto res = this->LiftIntoInternalBlockWithSleighState( - inst, block->getModule(), is_delayed, btaken); + inst, block->getModule(), is_delayed, btaken, context_values); if (res.first != LiftStatus::kLiftedInstruction || !res.second.has_value()) { return res.first; @@ -1546,9 +1685,10 @@ Sleigh &SleighLifter::GetEngine(void) const { } SleighLifterWithState::SleighLifterWithState( - std::optional btaken_, + sleigh::MaybeBranchTakenVar btaken_, ContextValues context_values_, std::shared_ptr lifter_) : btaken(btaken_), + context_values(std::move(context_values_)), lifter(std::move(lifter_)) {} // Lift a single instruction into a basic block. `is_delayed` signifies that @@ -1556,8 +1696,8 @@ SleighLifterWithState::SleighLifterWithState( 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); + return this->lifter->LiftIntoBlockWithSleighState( + inst, block, state_ptr, is_delayed, this->btaken, this->context_values); } diff --git a/lib/BC/Util.cpp b/lib/BC/Util.cpp index 63415465..468f64c8 100644 --- a/lib/BC/Util.cpp +++ b/lib/BC/Util.cpp @@ -443,6 +443,27 @@ bool VerifyModule(llvm::Module *module) { return true; } +std::optional VerifyFunctionMsg(llvm::Function *func) { + std::string error; + llvm::raw_string_ostream error_stream(error); + if (llvm::verifyFunction(*func, &error_stream)) { + error_stream.flush(); + return error; + } + + return {}; +} + +// Try to verify a function. +bool VerifyFunction(llvm::Function *func) { + if (auto error = VerifyFunctionMsg(func)) { + DLOG(ERROR) << "Error verifying function: " << *error; + return false; + } + + return true; +} + std::unique_ptr LoadModuleFromFile(llvm::LLVMContext *context, std::filesystem::path file_name) { @@ -556,6 +577,12 @@ namespace { # define REMILL_BUILD_SEMANTICS_DIR_SPARC64 #endif // REMILL_BUILD_SEMANTICS_DIR_SPARC64 +#ifndef REMILL_BUILD_SEMANTICS_DIR_PPC64_32ADDR +# error \ + "Macro `REMILL_BUILD_SEMANTICS_DIR_PPC64_32ADDR` must be defined to support the PPC64_32ADDR architectures." +# define REMILL_BUILD_SEMANTICS_DIR_PPC64_32ADDR +#endif // REMILL_BUILD_SEMANTICS_DIR_PPC64_32ADDR + #ifndef REMILL_INSTALL_SEMANTICS_DIR # error "Macro `REMILL_INSTALL_SEMANTICS_DIR` must be defined." # define REMILL_INSTALL_SEMANTICS_DIR @@ -574,6 +601,7 @@ const paths_t &DefaultSemanticsSearchPaths() { REMILL_BUILD_SEMANTICS_DIR_AARCH64, REMILL_BUILD_SEMANTICS_DIR_SPARC32, REMILL_BUILD_SEMANTICS_DIR_SPARC64, + REMILL_BUILD_SEMANTICS_DIR_PPC64_32ADDR, REMILL_INSTALL_SEMANTICS_DIR, "/usr/local/share/remill/" MAJOR_MINOR "/semantics", "/usr/share/remill/" MAJOR_MINOR "/semantics", diff --git a/patches/sleigh/0001-ppc_common.patch b/patches/sleigh/0001-ppc_common.patch new file mode 100644 index 00000000..2cf9e9d4 --- /dev/null +++ b/patches/sleigh/0001-ppc_common.patch @@ -0,0 +1,59 @@ +From bd02af97c29961dc6544cccdee57526c3812dc15 Mon Sep 17 00:00:00 2001 +From: William Tan <1284324+Ninja3047@users.noreply.github.com> +Date: Mon, 19 Dec 2022 16:29:09 -0500 +Subject: [PATCH] ppc_common + +--- + .../PowerPC/data/languages/ppc_common.sinc | 21 +++++++++++++++++-- + 1 file changed, 19 insertions(+), 2 deletions(-) + +diff --git a/Ghidra/Processors/PowerPC/data/languages/ppc_common.sinc b/Ghidra/Processors/PowerPC/data/languages/ppc_common.sinc +index 8498e4bdc..761a2e9a0 100644 +--- a/Ghidra/Processors/PowerPC/data/languages/ppc_common.sinc ++++ b/Ghidra/Processors/PowerPC/data/languages/ppc_common.sinc +@@ -3,6 +3,10 @@ + # version 1.0 + + define endian=$(ENDIAN); ++@define INST_NEXT_PTR "pc" ++ ++define pcodeop claim_eq; ++ + + define alignment=2; + +@@ -1200,6 +1204,9 @@ attach names [ vrDD vrAD vrBD vrSD vrCD] + [ v0 v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 + v16 v17 v18 v19 v20 v21 v22 v23 v24 v25 v26 v27 v28 v29 v30 v31 ]; + ++ ++remill_insn_size: calculated_size is epsilon [calculated_size= inst_next-inst_start; ] { local insn_size_hinted:$(REGISTER_SIZE)=calculated_size; ++ export insn_size_hinted; } + vrD: vrDD is vrDD & vrDR { export vrDR; } + vrA: vrAD is vrAD & vrAR { export vrAR; } + vrB: vrBD is vrBD & vrBR { export vrBR; } +@@ -1825,9 +1832,19 @@ macro unpackFPSCR(tmp) { + REL_ABS: "a" is AA = 1 {} + REL_ABS: is AA = 0 {} + +-addressLI: reloc is LI & AA=0 [ reloc = inst_start + LI*4;] { export *[ram]:4 reloc; } ++addressLI: reloc is LI & AA=0 ; remill_insn_size [ reloc = inst_start + LI*4;] { ++remill_please_dont_use_this_temp_named:$(REGISTER_SIZE)=reloc; ++claim_eq(remill_please_dont_use_this_temp_named, ($(INST_NEXT_PTR)-remill_insn_size) + LI*4); ++ export *[ram]:4 reloc; ++ } ++ + addressLI: reloc is LI & AA=1 [ reloc = LI*4; ] { export *[ram]:4 reloc; } +-addressBD: reloc is BD & AA=0 [ reloc = inst_start + BD*4; ] { export *[ram]:4 reloc; } ++addressBD: reloc is BD & AA=0 ; remill_insn_size [ reloc = inst_start + BD*4; ] { ++remill_please_dont_use_this_temp_name12:$(REGISTER_SIZE)=reloc; ++claim_eq(remill_please_dont_use_this_temp_name12, ($(INST_NEXT_PTR)-remill_insn_size) + BD*4); ++ export *[ram]:4 reloc; ++ } ++ + addressBD: reloc is BD & AA=1 [ reloc = BD*4; ] { export *[ram]:4 reloc; } + + OFF16SH: val is D0 & D1 & D2 [ val = ((D0 << 6) | (D1 << 1) | D2) << 16; ] { export *[const]:4 val;} +-- +2.37.1 (Apple Git-137.1) + diff --git a/patches/sleigh/0001-ppc_instructions.patch b/patches/sleigh/0001-ppc_instructions.patch new file mode 100644 index 00000000..fe411527 --- /dev/null +++ b/patches/sleigh/0001-ppc_instructions.patch @@ -0,0 +1,196 @@ +From e6fbe2b39e45a78af3031a0357eef199dedc342c Mon Sep 17 00:00:00 2001 +From: William Tan <1284324+Ninja3047@users.noreply.github.com> +Date: Mon, 19 Dec 2022 16:29:10 -0500 +Subject: [PATCH] ppc_instructions + +--- + .../data/languages/ppc_instructions.sinc | 102 ++++++++++++++---- + 1 file changed, 80 insertions(+), 22 deletions(-) + +diff --git a/Ghidra/Processors/PowerPC/data/languages/ppc_instructions.sinc b/Ghidra/Processors/PowerPC/data/languages/ppc_instructions.sinc +index a3fa7a8f7..75aa75f58 100644 +--- a/Ghidra/Processors/PowerPC/data/languages/ppc_instructions.sinc ++++ b/Ghidra/Processors/PowerPC/data/languages/ppc_instructions.sinc +@@ -368,10 +368,15 @@ + # special case when branch is to fall-through instruction, just loading the link register + #bl (Load LR) + :bl addressBD is $(NOTVLE) & OP=16 & addressBD & REL_ABS & BO_0=1 & BO_2=1 & BD=1 & LK=1 +-{ ++ { ++remill_please_dont_use_this_temp_name45:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name45, $(INST_NEXT_PTR)); + LR = inst_next; ++ + goto addressBD; +-} ++ ++ } ++ + + + +@@ -493,15 +498,25 @@ + + #bgectr 0x4c 80 04 20 + :b^CC^"ctr" is $(NOTVLE) & OP=19 & CC & BO_0=0 & BO_2=1 & BI_CR= 0 & BH=0 & LK=0 & BITS_13_15=0 & XOP_1_10=528 +-{ +- if (!CC) goto inst_next; ++ { ++remill_please_dont_use_this_temp_name4f:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name4f, $(INST_NEXT_PTR)); ++ if (!CC) goto inst_next; ++ + goto [CTR]; +-} ++ ++ } ++ + :b^CC^"ctr" BH is $(NOTVLE) & OP=19 & CC & BO_0=0 & BO_2=1 & BI_CR= 0 & BH & BH_BITS!=0 & LK=0 & BITS_13_15=0 & XOP_1_10=528 +-{ +- if (!CC) goto inst_next; ++ { ++remill_please_dont_use_this_temp_name51:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name51, $(INST_NEXT_PTR)); ++ if (!CC) goto inst_next; ++ + goto [CTR]; +-} ++ ++ } ++ + + #bgectrl 0x4c 80 04 21 + :b^CC^"ctrl" is $(NOTVLE) & OP=19 & CC & BO_0=0 & BO_2=1 & BI_CR= 0 & BH=0 & LK=1 & BITS_13_15=0 & XOP_1_10=528 +@@ -521,17 +536,27 @@ + + #bgectr cr3 0x4c 8c 04 20 + :b^CC^"ctr" BI_CR is $(NOTVLE) & OP=19 & CC & BI_CR & BO_0=0 & BO_2=1 & BH=0 & LK=0 & BITS_13_15=0 & XOP_1_10=528 +-{ +- if (!CC) goto inst_next; ++ { ++remill_please_dont_use_this_temp_name53:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name53, $(INST_NEXT_PTR)); ++ if (!CC) goto inst_next; ++ + goto [CTR]; +-} ++ ++ } ++ + + #bnectr cr2,#0x3 0x4c 8c 1c 20 + :b^CC^"ctr" BI_CR,BH is $(NOTVLE) & OP=19 & CC & BI_CR & BO_0=0 & BO_2=1 & BH & LK=0 & BITS_13_15=0 & XOP_1_10=528 +-{ +- if (!CC) goto inst_next; ++ { ++remill_please_dont_use_this_temp_name55:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name55, $(INST_NEXT_PTR)); ++ if (!CC) goto inst_next; ++ + goto [CTR]; +-} ++ ++ } ++ + + #bgectrl cr2,LAB_xxxx 0x4c 8c 04 21 + :b^CC^"ctrl" BI_CR is $(NOTVLE) & OP=19 & CC & BI_CR & BO_0=0 & BO_2=1 & BH=0 & LK=1 & BITS_13_15=0 & XOP_1_10=528 +@@ -1913,26 +1938,45 @@ + + #fsel f0r,fr0,fr0,fr0 0xfc 00 00 2e + :fsel fD,fA,fC,fB is $(NOTVLE) & OP=63 & fD & fA & fB & fC & XOP_1_5=23 & Rc=0 +-{ ++ { + local tmpfA = fA; ++ + local tmpfB = fB; ++ + zero:4=0; ++ + fD=fC; ++remill_please_dont_use_this_temp_nameef:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_nameef, $(INST_NEXT_PTR)); ++ + if (tmpfA f> int2float(zero)) goto inst_next; ++ + fD=tmpfB; +-} ++ ++ } ++ + + #fsel. fr0,fr0,fr0,fr0 0xfc 00 00 2f + :fsel. fD,fA,fC,fB is $(NOTVLE) & OP=63 & fD & fA & fB & fC & XOP_1_5=23 & Rc=1 +-{ ++ { + local tmpfA = fA; ++ + local tmpfB = fB; ++ + zero:4=0; ++ + fD=fC; ++ + cr1flags(); ++remill_please_dont_use_this_temp_namef1:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_namef1, $(INST_NEXT_PTR)); ++ + if (tmpfA f> int2float(zero)) goto inst_next; ++ + fD=tmpfB; +-} ++ ++ } ++ + + #fsqrt f0r,fr0 0xfc 00 00 2c + :fsqrt fD,fB is $(NOTVLE) & OP=63 & fD & BITS_16_20=0 & fB & BITS_6_10=0 & XOP_1_5=22 & Rc=0 +@@ -3960,13 +4004,20 @@ CRM_val: crmval is CRM [crmval = CRM+0;] {export *[const]:1 crmval;} + + #stdcx. r0,8(0) 0x7c 00 01 AD + :stdcx. S,RA_OR_ZERO,B is OP=31 & S & RA_OR_ZERO & B & XOP_1_10=214 & BIT_0=1 +-{ ++ { + EA:$(REGISTER_SIZE) = RA_OR_ZERO + B; ++remill_please_dont_use_this_temp_name267:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name267, $(INST_NEXT_PTR)); ++ + if (RESERVE == 0) goto inst_next; ++ + *[ram]:8 EA = storeDoubleWordConditionalIndexed(S,RA_OR_ZERO,B); ++ + # set when a stwcx. or stdcx. successfully completes + cr0flags(0:$(REGISTER_SIZE)); +-} ++ ++ } ++ + + #stdu r0,8(0) 0xf8 00 00 01 + #stdu r0,8(r2) 0xf8 02 00 01 +@@ -4150,13 +4201,20 @@ define pcodeop stswxOp; + + #stwcx. r0,8(0) 0x7c 00 01 2D + :stwcx. S,RA_OR_ZERO,B is OP=31 & S & RA_OR_ZERO & B & XOP_1_10=150 & BIT_0=1 +-{ ++ { + EA:$(REGISTER_SIZE) = RA_OR_ZERO + B; ++remill_please_dont_use_this_temp_name28d:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name28d, $(INST_NEXT_PTR)); ++ + if (RESERVE == 0) goto inst_next; ++ + *[ram]:4 EA = storeWordConditionalIndexed(S,RA_OR_ZERO,B); ++ + # set when a stwcx. or stdcx. successfully completes + cr0flags(0:$(REGISTER_SIZE)); +-} ++ ++ } ++ + + #stwu r0,r0 0x94 00 00 00 + :stwu S,dPlusRaAddress is $(NOTVLE) & OP=37 & S & A & dPlusRaAddress +-- +2.37.1 (Apple Git-137.1) + diff --git a/patches/sleigh/0001-ppc_isa.patch b/patches/sleigh/0001-ppc_isa.patch new file mode 100644 index 00000000..82c29c93 --- /dev/null +++ b/patches/sleigh/0001-ppc_isa.patch @@ -0,0 +1,97 @@ +From 9bf1ba10175be998e5406fbcde7faec0fddf11f9 Mon Sep 17 00:00:00 2001 +From: William Tan <1284324+Ninja3047@users.noreply.github.com> +Date: Mon, 19 Dec 2022 16:29:10 -0500 +Subject: [PATCH] ppc_isa + +--- + .../PowerPC/data/languages/ppc_isa.sinc | 52 ++++++++++++++----- + 1 file changed, 38 insertions(+), 14 deletions(-) + +diff --git a/Ghidra/Processors/PowerPC/data/languages/ppc_isa.sinc b/Ghidra/Processors/PowerPC/data/languages/ppc_isa.sinc +index 10f0cc691..ffc603048 100644 +--- a/Ghidra/Processors/PowerPC/data/languages/ppc_isa.sinc ++++ b/Ghidra/Processors/PowerPC/data/languages/ppc_isa.sinc +@@ -2377,15 +2377,25 @@ define pcodeop InstructionCacheBlockLockSetX; + } + + :b^CC^"ctar" is $(NOTVLE) & OP=19 & CC & BO_0=0 & BO_2=1 & BI_CR= 0 & BH=0 & LK=0 & BITS_13_15=0 & XOP_1_10=560 +-{ +- if (!CC) goto inst_next; ++ { ++remill_please_dont_use_this_temp_name191:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name191, $(INST_NEXT_PTR)); ++ if (!CC) goto inst_next; ++ + goto [TAR]; +-} ++ ++ } ++ + :b^CC^"ctar" BH is $(NOTVLE) & OP=19 & CC & BO_0=0 & BO_2=1 & BI_CR= 0 & BH & BH_BITS!=0 & LK=0 & BITS_13_15=0 & XOP_1_10=560 +-{ +- if (!CC) goto inst_next; ++ { ++remill_please_dont_use_this_temp_name193:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name193, $(INST_NEXT_PTR)); ++ if (!CC) goto inst_next; ++ + goto [TAR]; +-} ++ ++ } ++ + + :b^CC^"ctarl" is $(NOTVLE) & OP=19 & CC & BO_0=0 & BO_2=1 & BI_CR= 0 & BH=0 & LK=1 & BITS_13_15=0 & XOP_1_10=560 + [ linkreg=0; globalset(inst_start,linkreg); ] +@@ -2403,16 +2413,26 @@ define pcodeop InstructionCacheBlockLockSetX; + } + + :b^CC^"ctar" BI_CR is $(NOTVLE) & OP=19 & CC & BI_CR & BO_0=0 & BO_2=1 & BH=0 & LK=0 & BITS_13_15=0 & XOP_1_10=560 +-{ +- if (!CC) goto inst_next; ++ { ++remill_please_dont_use_this_temp_name195:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name195, $(INST_NEXT_PTR)); ++ if (!CC) goto inst_next; ++ + goto [TAR]; +-} ++ ++ } ++ + + :b^CC^"ctar" BI_CR,BH is $(NOTVLE) & OP=19 & CC & BI_CR & BO_0=0 & BO_2=1 & BH & LK=0 & BITS_13_15=0 & XOP_1_10=560 +-{ +- if (!CC) goto inst_next; ++ { ++remill_please_dont_use_this_temp_name197:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name197, $(INST_NEXT_PTR)); ++ if (!CC) goto inst_next; ++ + goto [TAR]; +-} ++ ++ } ++ + + :b^CC^"ctarl" BI_CR is $(NOTVLE) & OP=19 & CC & BI_CR & BO_0=0 & BO_2=1 & BH=0 & LK=1 & BITS_13_15=0 & XOP_1_10=560 + [ linkreg=0; globalset(inst_start,linkreg); ] +@@ -2530,9 +2550,13 @@ define pcodeop InstructionCacheBlockLockSetX; + ####################### + # v3.0 + +-:addpcis D,OFF16SH is $(NOTVLE) & OP=19 & XOP_1_5=2 & D & OFF16SH { ++:addpcis D,OFF16SH is $(NOTVLE) & OP=19 & XOP_1_5=2 & D & OFF16SH { ++remill_please_dont_use_this_temp_name1bd:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name1bd, $(INST_NEXT_PTR)); + D = inst_next + sext(OFF16SH); +-} ++ ++ } ++ + + :cmpeqb CRFD,A,B is $(NOTVLE) & OP=31 & BITS_21_22=0 & BIT_0=0 & XOP_1_10=224 & A & B & CRFD { + tmpa:1 = A:1; +-- +2.37.1 (Apple Git-137.1) + diff --git a/patches/sleigh/0001-ppc_vle.patch b/patches/sleigh/0001-ppc_vle.patch new file mode 100644 index 00000000..54ce4b79 --- /dev/null +++ b/patches/sleigh/0001-ppc_vle.patch @@ -0,0 +1,120 @@ +From f84f07be17cd7594d32f49f2c3095e65d6f3c393 Mon Sep 17 00:00:00 2001 +From: William Tan <1284324+Ninja3047@users.noreply.github.com> +Date: Mon, 19 Dec 2022 16:29:10 -0500 +Subject: [PATCH] ppc_vle + +--- + .../PowerPC/data/languages/ppc_vle.sinc | 68 +++++++++++++++---- + 1 file changed, 55 insertions(+), 13 deletions(-) + +diff --git a/Ghidra/Processors/PowerPC/data/languages/ppc_vle.sinc b/Ghidra/Processors/PowerPC/data/languages/ppc_vle.sinc +index 3b6283470..b6d763bd0 100644 +--- a/Ghidra/Processors/PowerPC/data/languages/ppc_vle.sinc ++++ b/Ghidra/Processors/PowerPC/data/languages/ppc_vle.sinc +@@ -20,9 +20,24 @@ CC32: "ns" is BI_CC_VLE=3 & BO_VLE=0 & BI_CR_VLE & BI_CC_VLE { tmp:1 = 0; getCr + CC32: "dnz" is BO_VLE=2 {CTR = CTR-1; tmp:1 = (CTR != 0); export tmp; } + CC32: "dz" is BO_VLE=3 {CTR = CTR-1; tmp:1 = (CTR == 0); export tmp; } + +-addrBD8: reloc is BD8_VLE [ reloc = inst_start + (BD8_VLE << 1);] { export *[ram]:4 reloc; } +-addrBD15: reloc is BD15_VLE [ reloc = inst_start + (BD15_VLE << 1);] { export *[ram]:4 reloc; } +-addrBD24: reloc is BD24_VLE [ reloc = inst_start + (BD24_VLE << 1);] { export *[ram]:4 reloc; } ++addrBD8: reloc is BD8_VLE ; remill_insn_size [ reloc = inst_start + (BD8_VLE << 1);] { ++remill_please_dont_use_this_temp_name23:$(REGISTER_SIZE)=reloc; ++claim_eq(remill_please_dont_use_this_temp_name23, ($(INST_NEXT_PTR)-remill_insn_size) + (BD8_VLE << 1)); ++ export *[ram]:4 reloc; ++ } ++ ++addrBD15: reloc is BD15_VLE ; remill_insn_size [ reloc = inst_start + (BD15_VLE << 1);] { ++remill_please_dont_use_this_temp_name26:$(REGISTER_SIZE)=reloc; ++claim_eq(remill_please_dont_use_this_temp_name26, ($(INST_NEXT_PTR)-remill_insn_size) + (BD15_VLE << 1)); ++ export *[ram]:4 reloc; ++ } ++ ++addrBD24: reloc is BD24_VLE ; remill_insn_size [ reloc = inst_start + (BD24_VLE << 1);] { ++remill_please_dont_use_this_temp_name29:$(REGISTER_SIZE)=reloc; ++claim_eq(remill_please_dont_use_this_temp_name29, ($(INST_NEXT_PTR)-remill_insn_size) + (BD24_VLE << 1)); ++ export *[ram]:4 reloc; ++ } ++ + + d8PlusRaAddress: S8IMM(A) is S8IMM & A {tmp:$(REGISTER_SIZE) = A+S8IMM; export tmp; } + d8PlusRaOrZeroAddress: S8IMM(RA_OR_ZERO) is S8IMM & RA_OR_ZERO {tmp:$(REGISTER_SIZE) = RA_OR_ZERO+S8IMM; export tmp; } +@@ -53,38 +68,65 @@ IMM16B: val is IMM_0_10_VLE & IMM_16_20_VLE [ val = (IMM_16_20_VLE << 11) | + goto addrBD24; + } + +-:e_bl addrBD24 is $(ISVLE) & OP=30 & BIT_25=0 & LK=1 & addrBD24 { ++:e_bl addrBD24 is $(ISVLE) & OP=30 & BIT_25=0 & LK=1 & addrBD24 { ++remill_please_dont_use_this_temp_name3a:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name3a, $(INST_NEXT_PTR)); + LR = inst_next; ++ + call addrBD24; +-} ++ ++ } ++ + + :se_b addrBD8 is $(ISVLE) & OP6_VLE=58 & BIT9_VLE=0 & LK8_VLE=0 & addrBD8 { + goto addrBD8; + } + +-:se_bl addrBD8 is $(ISVLE) & OP6_VLE=58 & BIT9_VLE=0 & LK8_VLE=1 & addrBD8 { ++:se_bl addrBD8 is $(ISVLE) & OP6_VLE=58 & BIT9_VLE=0 & LK8_VLE=1 & addrBD8 { ++remill_please_dont_use_this_temp_name3e:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name3e, $(INST_NEXT_PTR)); + LR = inst_next; ++ + call addrBD8; +-} ++ ++ } ++ + + # NOTE: For the conditional branches, the "official" mnemonics have just bc and bcl. + # We use extended mnemonics so the display is understandable without having to cross- + # reference multiple tables. +-:e_b^CC32 addrBD15 is $(ISVLE) & OP=30 & XOP_VLE=8 & LK=0 & addrBD15 & CC32 { ++:e_b^CC32 addrBD15 is $(ISVLE) & OP=30 & XOP_VLE=8 & LK=0 & addrBD15 & CC32 { ++remill_please_dont_use_this_temp_name40:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name40, $(INST_NEXT_PTR)); + if (CC32 == 0) goto inst_next; ++ + goto addrBD15; +-} + +-:e_b^CC32^"l" addrBD15 is $(ISVLE) & OP=30 & XOP_VLE=8 & LK=1 & addrBD15 & CC32 { ++ } ++ ++ ++:e_b^CC32^"l" addrBD15 is $(ISVLE) & OP=30 & XOP_VLE=8 & LK=1 & addrBD15 & CC32 { ++remill_please_dont_use_this_temp_name42:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name42, $(INST_NEXT_PTR)); + if (CC32 == 0) goto inst_next; ++claim_eq(remill_please_dont_use_this_temp_name42, $(INST_NEXT_PTR)); ++ + LR= inst_next; ++ + call [addrBD15]; +-} + +-:se_b^CC16 addrBD8 is $(ISVLE) & OP5_VLE=28 & addrBD8 & CC16 { ++ } ++ ++ ++:se_b^CC16 addrBD8 is $(ISVLE) & OP5_VLE=28 & addrBD8 & CC16 { ++remill_please_dont_use_this_temp_name44:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_name44, $(INST_NEXT_PTR)); + if (CC16 == 0) goto inst_next; ++ + goto addrBD8; +-} ++ ++ } ++ + ####### + + :se_bctr is $(ISVLE) & OP15_VLE=3 & LK0_VLE=0 { +-- +2.37.1 (Apple Git-137.1) + diff --git a/patches/sleigh/0001-quicciii.patch b/patches/sleigh/0001-quicciii.patch new file mode 100644 index 00000000..957b1f9e --- /dev/null +++ b/patches/sleigh/0001-quicciii.patch @@ -0,0 +1,40 @@ +From aa64d2d91c4d8cba234533e8ef619d01cbc81ef2 Mon Sep 17 00:00:00 2001 +From: William Tan <1284324+Ninja3047@users.noreply.github.com> +Date: Mon, 19 Dec 2022 16:29:11 -0500 +Subject: [PATCH] quicciii + +--- + .../Processors/PowerPC/data/languages/quicciii.sinc | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/Ghidra/Processors/PowerPC/data/languages/quicciii.sinc b/Ghidra/Processors/PowerPC/data/languages/quicciii.sinc +index 8b7977bce..13b381559 100644 +--- a/Ghidra/Processors/PowerPC/data/languages/quicciii.sinc ++++ b/Ghidra/Processors/PowerPC/data/languages/quicciii.sinc +@@ -62,13 +62,21 @@ define pcodeop invalidateTLB; + } + + :isel^CC_X_OPm D,RA_OR_ZERO,B,CC_X_OP is OP=31 & D & RA_OR_ZERO & B & CC_X_OP & CC_X_OPm & XOP_1_5=15 +-{ ++ { + local tmp:$(REGISTER_SIZE) = RA_OR_ZERO; ++ + D = B; ++remill_please_dont_use_this_temp_namef:$(REGISTER_SIZE)=inst_next; ++claim_eq(remill_please_dont_use_this_temp_namef, $(INST_NEXT_PTR)); ++ + if (!CC_X_OP) goto inst_next; ++ + D = tmp; ++ + # D = (zext(CC_X_OP) * RA_OR_ZERO) + (zext(!CC_X_OP) * B); +-} ++ ++ } ++ + + @ifndef IS_ISA + #mbar 0 #FIXME +-- +2.37.1 (Apple Git-137.1) + diff --git a/scripts/build.sh b/scripts/build.sh index d525b5ea..6736233c 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -27,7 +27,7 @@ LLVM_VERSION=llvm-15 OS_VERSION= ARCH_VERSION= BUILD_FLAGS= -CXX_COMMON_VERSION="0.2.12" +CXX_COMMON_VERSION="0.2.22" # There are pre-build versions of various libraries for specific # Ubuntu releases. @@ -280,10 +280,6 @@ function Package function GetLLVMVersion { case ${1} in - 14) - LLVM_VERSION=llvm-14 - return 0 - ;; 15) LLVM_VERSION=llvm-15 return 0 diff --git a/scripts/docker-lifter-entrypoint.sh b/scripts/docker-lifter-entrypoint.sh index 9f751af4..cd54deae 100755 --- a/scripts/docker-lifter-entrypoint.sh +++ b/scripts/docker-lifter-entrypoint.sh @@ -4,9 +4,6 @@ V="" case ${LLVM_VERSION} in - llvm14*) - V=14 - ;; llvm15*) V=15 ;; diff --git a/scripts/replace_pc_relative_disactions/README.md b/scripts/replace_pc_relative_disactions/README.md index ef4c3e15..4f0fa5db 100644 --- a/scripts/replace_pc_relative_disactions/README.md +++ b/scripts/replace_pc_relative_disactions/README.md @@ -1 +1,2 @@ -Example invocation for x86 `python replace_pc_relative_disactions/main.py --out --inst_next_size_hint "\$(SIZE)" --pc_def register_definitions/x86.ia` \ No newline at end of file +Example invocation for x86 `python replace_pc_relative_disactions/main.py --out_dir --inst_next_size_hint "\$(SIZE)" --pc_def register_definitions/x86.ia` +Example invocation for PowerPC using `python replace_pc_relative_disactions/main.py /Ghidra/Processors/PowerPC/data/languages/{ppc_common,ppc_embedded,ppc_instructions,ppc_isa,ppc_vle,quicciii}.sinc --pc_def ./register_definitions/powerpc.ia --inst_next_size_hint '$(REGISTER_SIZE)' --base_path --out_dir $PWD/../../patches/sleigh` diff --git a/scripts/replace_pc_relative_disactions/register_definitions/powerpc.ia b/scripts/replace_pc_relative_disactions/register_definitions/powerpc.ia new file mode 100644 index 00000000..6be24e02 --- /dev/null +++ b/scripts/replace_pc_relative_disactions/register_definitions/powerpc.ia @@ -0,0 +1 @@ +@define INST_NEXT_PTR "pc" diff --git a/scripts/replace_pc_relative_disactions/replace_pc_relative_disactions/main.py b/scripts/replace_pc_relative_disactions/replace_pc_relative_disactions/main.py index 16a9e63e..99e0aa76 100644 --- a/scripts/replace_pc_relative_disactions/replace_pc_relative_disactions/main.py +++ b/scripts/replace_pc_relative_disactions/replace_pc_relative_disactions/main.py @@ -1,6 +1,7 @@ import argparse import re from typing import Dict, List, Match, Optional, Set, Tuple +from pathlib import Path import os import tempfile import subprocess @@ -198,23 +199,15 @@ def build_constructor(env: Environment, constructor: Match[str]) -> Optional[str ENDIAN_DEF_REGEX = "define\s*endian\s*=[\w()$]+;" - -def main(): - prsr = argparse.ArgumentParser("Disassembly action replacer") - prsr.add_argument("target_file") - prsr.add_argument("--pc_def", required=True) - prsr.add_argument("--inst_next_size_hint", required=True) - prsr.add_argument("--base_path", required=True) - prsr.add_argument("--commit_message", required=True) - prsr.add_argument("--out", required=True) - - args = prsr.parse_args() +def generate_patch(target_file, pc_def_path, inst_next_size_hint, base_path, out_dir): print(CONSTRUCTOR_BASE_REGEX) construct_pat = re.compile(CONSTRUCTOR_BASE_REGEX) + commit_message = f"{Path(target_file).stem}" + total_output = "" - with open(args.target_file, 'r') as target_f: - with open(args.pc_def) as pc_def_file: + with open(target_file, 'r') as target_f: + with open(pc_def_path) as pc_def_file: pc_def = pc_def_file.read() target = target_f.read() @@ -225,23 +218,22 @@ def main(): if endian_def is not None: total_output += target[0:endian_def.end()] - # can insert our defs here - total_output += "\n" + pc_def - total_output += "\ndefine pcodeop claim_eq;\n" + # can insert our defs here + # we only want to define this once in the file that also defines the endian-ness + total_output += "\n" + pc_def + total_output += "\ndefine pcodeop claim_eq;\n" first_constructor_offset = next( construct_pat.finditer(target)).start() print("first constructor at: " + str(first_constructor_offset)) - target_insert_match = max(filter(lambda k: k.end() < first_constructor_offset, - re.finditer("@endif", target)), key=lambda elem: elem.end(), default=None) - - target_insert_loc = target_insert_match.end( - ) if target_insert_match else (first_constructor_offset) + target_insert_loc = first_constructor_offset total_output += target[endian_def.end() if endian_def is not None else 0: target_insert_loc] - total_output += f"\n{REMILL_INSN_SIZE_NAME}: calculated_size is epsilon [calculated_size= inst_next-inst_start; ] {{ local insn_size_hinted:{args.inst_next_size_hint}=calculated_size; \n export insn_size_hinted; }}\n" + if endian_def is not None: + # This should be defined once and it MUST be defined after all context definitions + total_output += f"\n{REMILL_INSN_SIZE_NAME}: calculated_size is epsilon [calculated_size= inst_next-inst_start; ] {{ local insn_size_hinted:{inst_next_size_hint}=calculated_size; \n export insn_size_hinted; }}\n" last_offset = target_insert_loc cont = Context() @@ -249,7 +241,7 @@ def main(): total_output += constructor.string[last_offset:constructor.start()] last_offset = constructor.end() env = Environment(cont, - args.inst_next_size_hint, [InstStartReplacer(), InstNextReplacer()]) + inst_next_size_hint, [InstStartReplacer(), InstNextReplacer()]) act_section = constructor.group("action_section") if act_section: statements = act_section[ @@ -267,22 +259,37 @@ def main(): total_output += target[last_offset:] # compute the patch header - src_and_dst = os.path.relpath(args.target_file, args.base_path) + src_and_dst = os.path.relpath(target_file, base_path) # compute the patch - with open(args.target_file, 'w') as target_f: + with open(target_file, 'w') as target_f: target_f.write(total_output) - res = subprocess.run(["git", "commit", "-a", "-m", args.commit_message], - cwd=args.base_path, capture_output=True) + res = subprocess.run(["git", "commit", "-a", "-m", commit_message], + cwd=base_path, capture_output=True) print(res) print(subprocess.run( - ["git", "format-patch", "-1", "HEAD", "-o", args.out], cwd=args.base_path, capture_output=True)) + ["git", "format-patch", "-1", "HEAD", "-o", out_dir], cwd=base_path, capture_output=True)) subprocess.run( - ["git", "reset", "--hard", "HEAD~1"], cwd=args.base_path, capture_output=True) + ["git", "reset", "--hard", "HEAD~1"], cwd=base_path, capture_output=True) + + +def main(): + prsr = argparse.ArgumentParser("Disassembly action replacer") + prsr.add_argument("target_files", nargs="+", help="List of files to patch") + prsr.add_argument("--pc_def", required=True, help="Path to file containing definition of INST_NEXT_PTR") + prsr.add_argument("--inst_next_size_hint", required=True, help="Number of bytes of the PC register") + prsr.add_argument("--base_path", required=True, help="Path to Ghidra git repo") + prsr.add_argument("--out_dir", required=True) + + args = prsr.parse_args() + + for target in args.target_files: + print(f"Processing {target}") + generate_patch(target, args.pc_def, args.inst_next_size_hint, args.base_path, args.out_dir) if __name__ == "__main__": diff --git a/test_runner_lib/CMakeLists.txt b/test_runner_lib/CMakeLists.txt index f8cd5d55..d6085b96 100644 --- a/test_runner_lib/CMakeLists.txt +++ b/test_runner_lib/CMakeLists.txt @@ -19,6 +19,7 @@ add_library( STATIC TestRunner.cpp "${TEST_RUNNER_INCLUDE_DIR}/test_runner/TestRunner.h" + "${TEST_RUNNER_INCLUDE_DIR}/test_runner/TestOutputSpec.h" ) target_link_libraries( diff --git a/test_runner_lib/TestRunner.cpp b/test_runner_lib/TestRunner.cpp index 822deea7..d060ccb0 100644 --- a/test_runner_lib/TestRunner.cpp +++ b/test_runner_lib/TestRunner.cpp @@ -121,22 +121,6 @@ std::string MemoryHandler::DumpState() const { return ss.str(); } -template -T MemoryHandler::ReadMemory(uint64_t addr) { - auto buff = this->readSize(addr, sizeof(T)); - return llvm::support::endian::read(buff.data(), this->endian); -} - - -template -void MemoryHandler::WriteMemory(uint64_t addr, T value) { - std::vector buff(sizeof(T)); - llvm::support::endian::write(buff.data(), value, this->endian); - for (size_t i = 0; i < sizeof(T); i++) { - this->state[addr + i] = buff[i]; - } -} - std::unordered_map MemoryHandler::GetUninitializedReads() { return this->uninitialized_reads; } @@ -237,12 +221,11 @@ std::unordered_map LiftingTester::GetTypeMapping() { std::optional> LiftingTester::LiftInstructionFunction(std::string_view fname, - std::string_view bytes, - uint64_t address) { + std::string_view bytes, uint64_t address, + const remill::DecodingContext &ctx) { remill::Instruction insn; // This works for now since each arch has an initial context that represents the arch correctly. - if (!this->arch->DecodeInstruction(address, bytes, insn, - this->arch->CreateInitialContext())) { + if (!this->arch->DecodeInstruction(address, bytes, insn, ctx)) { LOG(ERROR) << "Failed decode"; return std::nullopt; } @@ -278,6 +261,14 @@ LiftingTester::LiftInstructionFunction(std::string_view fname, return std::make_pair(target_func, insn); } +std::optional> +LiftingTester::LiftInstructionFunction(std::string_view fname, + std::string_view bytes, + uint64_t address) { + return LiftInstructionFunction(fname, bytes, address, + this->arch->CreateInitialContext()); +} + const remill::Arch::ArchPtr &LiftingTester::GetArch() const { return this->arch; } @@ -320,4 +311,4 @@ void StubOutFlagComputationInstrinsics(llvm::Module *mod, } } } -} // namespace test_runner \ No newline at end of file +} // namespace test_runner diff --git a/test_runner_lib/include/test_runner/TestOutputSpec.h b/test_runner_lib/include/test_runner/TestOutputSpec.h new file mode 100644 index 00000000..b2457ccf --- /dev/null +++ b/test_runner_lib/include/test_runner/TestOutputSpec.h @@ -0,0 +1,160 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + + +namespace test_runner { + +using MemoryModifier = std::function; +using RegisterValue = std::variant; +using RegisterValueRef = std::variant, + std::reference_wrapper>; + +struct RegisterCondition { + std::string register_name; + RegisterValue enforced_value; +}; + +template +concept State = std::is_base_of_v; + +template +class TestOutputSpec { + public: + uint64_t addr; + std::string target_bytes; + + private: + using RegisterAccessorMap = + std::unordered_map>; + using RegisterConditionList = std::vector; + using MemoryConditionList = std::vector; + + remill::Instruction::Category expected_category; + RegisterConditionList register_preconditions; + RegisterConditionList register_postconditions; + MemoryConditionList initial_memory_conditions; + MemoryConditionList expected_memory_conditions; + RegisterAccessorMap reg_to_accessor; + + template + T &GetRegister(S &state, const std::string ®_name) const { + auto accessor = reg_to_accessor.find(reg_name); + if (accessor == reg_to_accessor.end()) { + throw std::runtime_error(std::string("Unknown reg: ") + reg_name); + } + auto wrapper = accessor->second(state); + if (auto underlying = std::get_if>(&wrapper)) { + return underlying->get(); + } + throw std::runtime_error( + std::string("Reg value " + reg_name + " has incorrect type")); + } + + template + void ApplyCondition(S &state, const std::string ®_name, T value) const { + auto ® = this->GetRegister(state, reg_name); + reg = value; + } + + template + void CheckCondition(S &state, const std::string ®_name, T value) const { + auto actual = this->GetRegister(state, reg_name); + LOG(INFO) << "Reg: " << reg_name << " Actual: " << std::hex + << static_cast(actual) << " Expected: " << std::hex + << static_cast(value); + CHECK_EQ(actual, value); + } + + public: + template + void AddPrecWrite(uint64_t addr, T value) { + this->initial_memory_conditions.push_back( + [=](MemoryHandler &mem_hand) { mem_hand.WriteMemory(addr, value); }); + } + + template + void AddPostRead(uint64_t addr, T value) { + this->expected_memory_conditions.push_back([=](MemoryHandler &mem_hand) { + LOG(INFO) << "Mem: " << std::hex << addr << " Actual: " << std::hex + << mem_hand.ReadMemory(addr) << " Expected: " << std::hex + << value; + CHECK_EQ(mem_hand.ReadMemory(addr), value); + }); + } + + const std::vector &GetMemoryPrecs() const { + return this->initial_memory_conditions; + } + + const std::vector &GetMemoryPosts() const { + return this->expected_memory_conditions; + } + + TestOutputSpec(uint64_t disas_addr, std::string target_bytes, + remill::Instruction::Category expected_category, + RegisterConditionList register_preconditions, + RegisterConditionList register_postconditions, + RegisterAccessorMap reg_to_accessor) + : addr(disas_addr), + target_bytes(std::move(target_bytes)), + expected_category(expected_category), + register_preconditions(std::move(register_preconditions)), + register_postconditions(std::move(register_postconditions)), + reg_to_accessor(std::move(reg_to_accessor)) {} + + + void SetupTestPreconditions(S &state) const { + for (auto &prec : this->register_preconditions) { + std::visit( + [&](auto &arg) { + this->ApplyCondition(state, prec.register_name, arg); + }, + prec.enforced_value); + } + } + + void CheckLiftedInstruction(const remill::Instruction &lifted) const { + CHECK_EQ(lifted.category, this->expected_category); + } + + void CheckResultingState(S &state) const { + for (auto &post : this->register_postconditions) { + std::visit( + [&](auto &arg) { + this->CheckCondition(state, post.register_name, arg); + }, + post.enforced_value); + } + } + + void CheckResultingMemory(MemoryHandler &mem_hand) const { + for (const auto &post : this->GetMemoryPosts()) { + post(mem_hand); + } + } +}; +} // namespace test_runner diff --git a/test_runner_lib/include/test_runner/TestRunner.h b/test_runner_lib/include/test_runner/TestRunner.h index a27b5a25..85d23755 100644 --- a/test_runner_lib/include/test_runner/TestRunner.h +++ b/test_runner_lib/include/test_runner/TestRunner.h @@ -75,6 +75,23 @@ class MemoryHandler { std::unordered_map GetUninitializedReads(); }; +template +T MemoryHandler::ReadMemory(uint64_t addr) { + auto buff = this->readSize(addr, sizeof(T)); + return llvm::support::endian::read(buff.data(), this->endian); +} + + +template +void MemoryHandler::WriteMemory(uint64_t addr, T value) { + std::vector buff(sizeof(T)); + llvm::support::endian::write(buff.data(), value, this->endian); + for (size_t i = 0; i < sizeof(T); i++) { + this->state[addr + i] = buff[i]; + } +} + + void StubOutFlagComputationInstrinsics(llvm::Module *mod, llvm::ExecutionEngine &exec_engine); llvm::Function * @@ -173,11 +190,14 @@ class LiftingTester { remill::ArchName arch_name); std::unordered_map GetTypeMapping(); - std::optional> LiftInstructionFunction(std::string_view fname, std::string_view bytes, uint64_t address); + std::optional> + LiftInstructionFunction(std::string_view fname, std::string_view bytes, + uint64_t address, const remill::DecodingContext &ctx); + const remill::Arch::ArchPtr &GetArch() const; }; -} // namespace test_runner \ No newline at end of file +} // namespace test_runner diff --git a/tests/AArch64/Lift.cpp b/tests/AArch64/Lift.cpp index 1acb4b60..837eaec3 100644 --- a/tests/AArch64/Lift.cpp +++ b/tests/AArch64/Lift.cpp @@ -123,9 +123,6 @@ extern "C" int main(int argc, char *argv[]) { } llvm::LLVMContext context; -#if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) - context.enableOpaquePointers(); -#endif auto os_name = remill::GetOSName(REMILL_OS); auto arch_name = remill::GetArchName(FLAGS_arch); auto arch = remill::Arch::Build(&context, os_name, arch_name); diff --git a/tests/PPC/CMakeLists.txt b/tests/PPC/CMakeLists.txt new file mode 100644 index 00000000..013f6d8c --- /dev/null +++ b/tests/PPC/CMakeLists.txt @@ -0,0 +1,36 @@ +# 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. + +find_package(GTest CONFIG REQUIRED) +list(APPEND PROJECT_LIBRARIES GTest::gtest) + +enable_testing() + +add_executable( + run-ppc-tests + TestLifting.cpp +) + +add_test(NAME "ppc-tests" COMMAND "run-ppc-tests") +target_link_libraries( + run-ppc-tests + PRIVATE + GTest::gtest + remill + test-runner + glog::glog +) + +set_property(TARGET run-ppc-tests PROPERTY ENABLE_EXPORTS ON) +set_property(TARGET run-ppc-tests PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/tests/PPC/TestLifting.cpp b/tests/PPC/TestLifting.cpp new file mode 100644 index 00000000..93e99502 --- /dev/null +++ b/tests/PPC/TestLifting.cpp @@ -0,0 +1,774 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +const static std::unordered_map< + std::string, std::function> + 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 +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 +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 &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("", 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(this->endian); + + for (const auto &prec : test.GetMemoryPrecs()) { + prec(*mem_hand); + } + + test_runner::ExecuteLiftedFunction( + 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 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 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 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 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 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 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 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 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 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 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(act_insn.flows); + + EXPECT_EQ(expected_condjmp, act_insn.flows); + + TestOutputSpec spec( + 0x12, insn_data, remill::Instruction::Category::kCategoryDirectJump, + {{"pc", uint64_t(0x10)}}, {{"pc", uint64_t(0xa)}}, reg_to_accessor); + + TestSpecRunner 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(act_insn.flows); + + EXPECT_EQ(expected_condjmp, act_insn.flows); + + TestOutputSpec 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 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(act_insn.flows); + + EXPECT_EQ(expected_condjmp, act_insn.flows); + + TestOutputSpec 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 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 spec( + 0x12, insn_data, remill::Instruction::Category::kCategoryDirectJump, + {{"pc", uint64_t(0x1000012)}}, {{"pc", uint64_t(0x1000012 + 0x5a)}}, + reg_to_accessor); + + TestSpecRunner 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 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 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 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(0xdeadbee0 + 0x10, 0x0); + spec.AddPostRead(0xdeadbee0 + 0x10, 0x13371337); + + TestSpecRunner 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 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(0xdeadbee0 + 0x10, 0x13371337); + spec.AddPostRead(0xdeadbee0 + 0x10, 0x13371337); + + TestSpecRunner 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 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(0x13370, 0x11223344); + spec.AddPrecWrite(0x13370 + 0x4, 0x22114433); + spec.AddPrecWrite(0x13370 + 0x8, 0x99aabbcc); + spec.AddPrecWrite(0x13370 + 0xc, 0xaa99ccbb); + spec.AddPrecWrite(0x13370 + 0x10, 0x88776655); + spec.AddPrecWrite(0x13370 + 0x14, 0x77885566); + spec.AddPrecWrite(0x13370 + 0x18, 0x00ffeedd); + spec.AddPrecWrite(0x13370 + 0x1c, 0xff00ddee); + spec.AddPrecWrite(0x13370 + 0x20, 0x44332211); + spec.AddPrecWrite(0x13370 + 0x24, 0xccbbaa99); + spec.AddPrecWrite(0x13370 + 0x28, 0xbbcc99aa); + + TestSpecRunner 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 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(0x13370, 0x11223344); + spec.AddPostRead(0x13370 + 0x4, 0x22114433); + spec.AddPostRead(0x13370 + 0x8, 0x99aabbcc); + spec.AddPostRead(0x13370 + 0xc, 0xaa99ccbb); + spec.AddPostRead(0x13370 + 0x10, 0x88776655); + spec.AddPostRead(0x13370 + 0x14, 0x77885566); + spec.AddPostRead(0x13370 + 0x18, 0x00ffeedd); + spec.AddPostRead(0x13370 + 0x1c, 0xff00ddee); + spec.AddPostRead(0x13370 + 0x20, 0x44332211); + spec.AddPostRead(0x13370 + 0x24, 0xccbbaa99); + spec.AddPostRead(0x13370 + 0x28, 0xbbcc99aa); + + TestSpecRunner 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 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(0x13370, 0x87654321); + spec.AddPrecWrite(0x13370 + 0x4, 0x55667788); + spec.AddPrecWrite(0x13370 + 0x8, 0x99aabbcc); + spec.AddPrecWrite(0x13370 + 0xc, 0xddeeff00); + + TestSpecRunner 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 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(0x13370, 0x87654321); + spec.AddPostRead(0x13370 + 0x4, 0x55667788); + spec.AddPostRead(0x13370 + 0x8, 0x99aabbcc); + spec.AddPostRead(0x13370 + 0xc, 0xddeeff00); + + TestSpecRunner 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 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 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 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 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 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 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 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 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 spec( + 0x12, insn_data, remill::Instruction::Category::kCategoryNormal, + {{"pc", uint64_t(0x12)}}, {{"pc", uint64_t(0x12 + 4)}}, reg_to_accessor); + + TestSpecRunner runner(curr_context); + runner.RunTestSpec(spec, kVLEContext); +} diff --git a/tests/Thumb/CMakeLists.txt b/tests/Thumb/CMakeLists.txt index e08de207..91120a0e 100644 --- a/tests/Thumb/CMakeLists.txt +++ b/tests/Thumb/CMakeLists.txt @@ -34,6 +34,3 @@ target_link_libraries( set_property(TARGET run-thumb-tests PROPERTY ENABLE_EXPORTS ON) set_property(TARGET run-thumb-tests PROPERTY POSITION_INDEPENDENT_CODE ON) - -message(STATUS "Adding test: thumb as run-thumb-tests") -add_test(NAME "thumb" COMMAND "run-thumb-tests") diff --git a/tests/Thumb/TestLifting.cpp b/tests/Thumb/TestLifting.cpp index 655f7ec0..7b22d323 100644 --- a/tests/Thumb/TestLifting.cpp +++ b/tests/Thumb/TestLifting.cpp @@ -60,9 +60,6 @@ std::optional GetFlows(std::string_view bytes, uint64_t address, uint64_t tm_val) { llvm::LLVMContext context; -#if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) - context.enableOpaquePointers(); -#endif auto arch = remill::Arch::Build(&context, remill::OSName::kOSLinux, remill::ArchName::kArchAArch32LittleEndian); auto sems = remill::LoadArchSemantics(arch.get()); @@ -188,11 +185,11 @@ class TestSpecRunner { auto new_mod = llvm::CloneModule(*lifted_func->getParent()); remill::OptimizeBareModule(new_mod.get()); - auto justFuncMod = + auto just_func_mod = std::make_unique("", new_mod->getContext()); auto new_func = test_runner::CopyFunctionIntoNewModule( - justFuncMod.get(), lifted_func, new_mod); + just_func_mod.get(), lifted_func, new_mod); AArch32State st = {}; @@ -240,9 +237,6 @@ TEST(ThumbRandomizedLifts, PopPC) { spec.AddPrecWrite(10, 16); llvm::LLVMContext context; -#if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) - context.enableOpaquePointers(); -#endif TestSpecRunner runner(context); runner.RunTestSpec(spec); } @@ -259,18 +253,12 @@ TEST(ThumbRandomizedLifts, RelPcTest) { spec.AddPrecWrite(32, 0xdeadc0de); llvm::LLVMContext context; -#if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) - context.enableOpaquePointers(); -#endif TestSpecRunner runner(context); runner.RunTestSpec(spec); } TEST(RegressionTests, AARCH64RegSize) { llvm::LLVMContext context; -#if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) - context.enableOpaquePointers(); -#endif auto arch = remill::Arch::Build(&context, remill::OSName::kOSLinux, remill::ArchName::kArchAArch64LittleEndian); auto sems = remill::LoadArchSemantics(arch.get()); @@ -291,9 +279,6 @@ TEST(RegressionTests, AARCH64RegSize) { } TEST(RegressionTests, Armv8FPSCR) { llvm::LLVMContext context; - #if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) - context.enableOpaquePointers(); - #endif auto arch = remill::Arch::Build(&context, remill::OSName::kOSLinux, remill::ArchName::kArchAArch32LittleEndian); CHECK_NOTNULL(arch->RegisterByName("FPSCR")); diff --git a/tests/X86/Lift.cpp b/tests/X86/Lift.cpp index b313f008..876ad0c3 100644 --- a/tests/X86/Lift.cpp +++ b/tests/X86/Lift.cpp @@ -124,9 +124,6 @@ extern "C" int main(int argc, char *argv[]) { } llvm::LLVMContext context; -#if LLVM_VERSION_NUMBER < LLVM_VERSION(15, 0) - context.enableOpaquePointers(); -#endif auto os_name = remill::GetOSName(REMILL_OS); auto arch_name = remill::GetArchName(FLAGS_arch); auto arch = remill::Arch::Build(&context, os_name, arch_name);