diff --git a/.clang-tidy b/.clang-tidy index 2731957..de7d899 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -12,6 +12,8 @@ Checks: > -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access, -cppcoreguidelines-pro-type-reinterpret-cast, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-owning-memory, diff --git a/.github/scripts/generate-coverage.sh b/.github/scripts/generate-coverage.sh index f249d3c..becddb3 100755 --- a/.github/scripts/generate-coverage.sh +++ b/.github/scripts/generate-coverage.sh @@ -14,16 +14,16 @@ if ! ls caesar-*.profraw 1> /dev/null 2>&1; then fi echo "Merging profile data..." -/usr/bin/llvm-profdata-21 merge -sparse caesar-*.profraw -o caesar.profdata +llvm-profdata merge -sparse caesar-*.profraw -o caesar.profdata echo "Generating coverage report (text summary)..." -/usr/bin/llvm-cov-21 report ./caesar_test \ +llvm-cov report ./caesar_test \ -instr-profile=caesar.profdata \ -ignore-filename-regex='test/.*' \ -ignore-filename-regex='.*/Catch2/.*' echo "Generating detailed HTML report..." -/usr/bin/llvm-cov-21 show ./caesar_test \ +llvm-cov show ./caesar_test \ -instr-profile=caesar.profdata \ -format=html \ -output-dir=coverage-report \ @@ -34,7 +34,7 @@ echo "Generating detailed HTML report..." echo "Generating per-file coverage summary..." echo "### Per-File Coverage Summary" > coverage-summary.txt -/usr/bin/llvm-cov-21 report ./caesar_test \ +llvm-cov report ./caesar_test \ -instr-profile=caesar.profdata \ -ignore-filename-regex='test/.*' \ -ignore-filename-regex='.*/Catch2/.*' \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0e9934..96d9b71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ env: jobs: build: name: Build and test - runs-on: ubuntu-latest + runs-on: macos-26 steps: - uses: actions/checkout@v4 @@ -24,16 +24,8 @@ jobs: - name: Install system dependencies run: | - sudo apt-get update - sudo apt-get install -y cmake ninja-build ccache - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 21 all - sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-21 100 - sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-21 100 - sudo update-alternatives --install /usr/bin/clang-scan-deps clang-scan-deps /usr/bin/clang-scan-deps-21 100 - sudo update-alternatives --install /usr/bin/llvm-profdata llvm-profdata /usr/bin/llvm-profdata-21 100 - sudo update-alternatives --install /usr/bin/llvm-cov llvm-cov /usr/bin/llvm-cov-21 100 + brew install cmake ninja ccache llvm + echo "$(brew --prefix llvm)/bin" >> $GITHUB_PATH - name: Setup ccache uses: hendrikmuhs/ccache-action@v1.2 @@ -51,8 +43,8 @@ jobs: - name: Configure CMake run: cmake -B build --preset debug -DENABLE_COVERAGE=ON env: - CC: /usr/bin/clang-21 - CXX: /usr/bin/clang++-21 + CC: clang + CXX: clang++ CMAKE_CXX_COMPILER_LAUNCHER: ccache - name: Build @@ -81,7 +73,7 @@ jobs: lint: name: Lint - runs-on: ubuntu-latest + runs-on: macos-26 needs: build steps: @@ -91,13 +83,8 @@ jobs: - name: Install system dependencies run: | - sudo apt-get update - sudo apt-get install -y cmake ninja-build - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 21 all - sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-21 100 - sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-21 100 + brew install cmake ninja llvm + echo "$(brew --prefix llvm)/bin" >> $GITHUB_PATH - name: Download build artifacts uses: actions/download-artifact@v4 diff --git a/src/cmd/environment.hpp b/src/cmd/environment.hpp index 4631ed9..e9064d2 100644 --- a/src/cmd/environment.hpp +++ b/src/cmd/environment.hpp @@ -19,6 +19,7 @@ class Environment { this->define("run", std::make_shared(RunFn())); this->define("resume", std::make_shared(ContinueFn())); this->define("target", std::make_shared(TargetFn())); + this->define("register", std::make_shared(RegisterFn())); } public: diff --git a/src/cmd/stdlib.hpp b/src/cmd/stdlib.hpp index 907c579..0b9194f 100644 --- a/src/cmd/stdlib.hpp +++ b/src/cmd/stdlib.hpp @@ -12,6 +12,7 @@ #include "callable.hpp" #include "cmd/object.hpp" #include "cmd/util.hpp" +#include "core/platform.hpp" #include "core/target.hpp" #include "error.hpp" #include "expected.hpp" @@ -52,7 +53,7 @@ class BreakpointFn : public Callable { private: static Object rmOrToggleBreakpoint(const std::vector& args, bool toggle = false) { - Expected addrRes = strToAddr(args[0]); + Expected addrRes = detail::strToAddr(args[0]); if (!addrRes) return addrRes.error(); u64 addr = *addrRes; @@ -81,7 +82,7 @@ class BreakpointFn : public Callable { if (breakpoints.empty()) return "No breakpoints set!"; for (const auto& [k, v] : breakpoints) { - retStr += std::format("Breakpoint @ {} ({})\n", toHex(k), + retStr += std::format("Breakpoint @ {} ({})\n", detail::toHex(k), breakpoints[k].enabled); } @@ -90,7 +91,7 @@ class BreakpointFn : public Callable { }; FnPtr set = [](const std::vector& args) -> Object { - Expected addrRes = strToAddr(args[0]); + Expected addrRes = detail::strToAddr(args[0]); if (!addrRes) return addrRes.error(); const u64 addr = *addrRes; @@ -98,7 +99,7 @@ class BreakpointFn : public Callable { if (bpRes != 0) return "Error setting breakpoint!"; else - return std::format("Breakpoint set at: {}", toHex(addr)); + return std::format("Breakpoint set at: {}", detail::toHex(addr)); }; FnPtr remove = [](const std::vector& args) -> Object { @@ -136,7 +137,7 @@ class BreakpointFn : public Callable { class RunFn : public Callable { private: - static CStringArray concatElems(const std::vector& v) { + static detail::CStringArray concatElems(const std::vector& v) { auto ensureStr = [](const auto& obj) -> std::string { using T = std::decay_t; @@ -150,7 +151,7 @@ class RunFn : public Callable { return ""; }; - CStringArray res{}; + detail::CStringArray res{}; res.storage.reserve(v.size()); res.ptrs.reserve(v.size() + 1); @@ -178,7 +179,7 @@ class RunFn : public Callable { [[nodiscard]] std::string str() const override { return ""; } Object call(std::vector args) override { - CStringArray argList = RunFn::concatElems(args); + detail::CStringArray argList = RunFn::concatElems(args); if (m_target == nullptr) { std::cerr << "Error: Target is not set!\n"; CmdError::getInstance().m_had_error = true; @@ -262,4 +263,33 @@ class TargetFn : public Callable { } }; +class RegisterFn : public Callable { + private: + FnPtr view = [](const std::vector& args) -> Object { + auto& target = Context::getTarget(); + if (!target && !target->m_started) return "Target is not running!"; + if (args.front() == "all") + return target->formatRegisterOutput(&target->getLastKnownThreadState()); + auto res = findRegEntry(args.front()); + if (!res.hasValue()) return res.error(); + return std::format("{}: {}", args.front(), + detail::toHex(readRegValue( + target->getLastKnownThreadState(), *res.value()))); + }; + SubcommandHandler m_subcmds{{{sv("view"), view}}, "register"}; + + public: + [[nodiscard]] int arity() const override { return 2; } + [[nodiscard]] std::string str() const override { + return ""; + } + Object call(std::vector args) override { + std::vector convertedArgs = detail::convertToStr(args); + if (convertedArgs.empty()) return std::monostate{}; + const std::string subcmd = convertedArgs.front(); + convertedArgs.erase(convertedArgs.begin()); + return m_subcmds.exec(subcmd, convertedArgs); + } +}; + #endif diff --git a/src/cmd/token_type.hpp b/src/cmd/token_type.hpp index e376187..4bfa9f7 100644 --- a/src/cmd/token_type.hpp +++ b/src/cmd/token_type.hpp @@ -2,8 +2,10 @@ #define CAESAR_TOKEN_TYPE_H #include +// Needed here separately to not recursively include typedefs.hpp +using u8 = std::uint8_t; -enum class TokenType : std::uint8_t { +enum class TokenType : u8 { LEFT_PAREN, RIGHT_PAREN, MINUS, diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index be57169..c964e71 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -6,6 +6,7 @@ add_library(caesar_core STATIC context.hpp target.hpp target.cpp + platform.hpp ) target_include_directories(caesar_core PUBLIC diff --git a/src/core/macho/CMakeLists.txt b/src/core/macho/CMakeLists.txt index c9790bd..f0bd98b 100644 --- a/src/core/macho/CMakeLists.txt +++ b/src/core/macho/CMakeLists.txt @@ -4,6 +4,7 @@ add_library(caesar_macho STATIC ports.hpp mach_exc.h mach_excServer.h + types.hpp ) target_include_directories(caesar_macho PUBLIC diff --git a/src/core/macho/macho.cpp b/src/core/macho/macho.cpp index 4557ee4..ed59967 100644 --- a/src/core/macho/macho.cpp +++ b/src/core/macho/macho.cpp @@ -48,6 +48,7 @@ #include #include +#include "platform.hpp" #include "target.hpp" extern "C" { #include "mach_exc.h" @@ -80,10 +81,11 @@ void Macho::dumpHeader(int offset) { auto header = loadBytesAndMaybeSwap(offset); ncmds = header.ncmds; loadCmdsOffset += headerSize; - std::cout << Macho::cpuTypeName(header.cputype) << '\n'; } else { - const int headerSize = sizeof(struct mach_header); + constexpr size_t headerSize = sizeof(struct mach_header); auto header = loadBytesAndMaybeSwap(offset); + ncmds = header.ncmds; + loadCmdsOffset += headerSize; } dumpSegmentCommands(loadCmdsOffset, ncmds); @@ -134,7 +136,7 @@ Macho::Macho(std::ifstream f, std::string filePath) } // TODO: Add appropriate error messages -i32 Macho::launch(CStringArray& argList) { +i32 Macho::launch(detail::CStringArray& argList) { pid_t pid = 0; int status = 0; posix_spawnattr_t attr = nullptr; @@ -349,30 +351,35 @@ kern_return_t catch_mach_exception_raise_state_identity( #pragma unused(newStateCnt) auto& target = Context::getTarget(); target->setTargetState(TargetState::STOPPED); + task_suspend(task); + auto* macho = dynamic_cast(Context::getTarget().get()); - auto* oldArmState = reinterpret_cast(oldState); - auto* newArmState = reinterpret_cast(newState); - memcpy(newArmState, oldArmState, sizeof(arm_thread_state64_t)); + auto* oldArmState = reinterpret_cast(oldState); + auto* newArmState = reinterpret_cast(newState); + + memcpy(newArmState, oldArmState, sizeof(ThreadState)); std::cout << Macho::exceptionReason(exc, codeCnt, code); - std::cout << std::format("PC @ {}\n", - toHex(oldArmState->__pc - macho->getAslrSlide())); + std::cout << macho->formatRegisterOutput(oldArmState); if (macho->getThreadPort() == 0) macho->setThreadPort(thread); if (exc == EXC_BAD_INSTRUCTION && *flavour == ARM_THREAD_STATE64) { - std::cout << std::format("Fault @ {}!\n", - toHex(oldArmState->__pc - macho->getAslrSlide())); - newArmState->__pc += 4; - std::cout << std::format("Resuming @ {}\n", - toHex(newArmState->__pc - macho->getAslrSlide())); + std::cout << std::format( + "Fault @ {}!\n", + detail::toHex(oldArmState->pc - macho->getAslrSlide())); + newArmState->pc += 4; + std::cout << std::format( + "Resuming @ {}\n", + detail::toHex(newArmState->pc - macho->getAslrSlide())); } if (exc == EXC_BREAKPOINT) { - macho->restorePrevIns(oldArmState->__pc - macho->getAslrSlide()); + macho->restorePrevIns(oldArmState->pc - macho->getAslrSlide()); } + macho->setThreadState(newArmState); *newStateCnt = oldStateCnt; return KERN_SUCCESS; } @@ -618,3 +625,9 @@ i32 Macho::disableBreakpoint(u64 addr, bool remove) { return 0; } + +void Macho::setThreadState(ThreadState* state) { + memcpy(&m_last_thread_state, state, sizeof(ThreadState)); +} + +ThreadState& Macho::getLastKnownThreadState() { return m_last_thread_state; } diff --git a/src/core/macho/macho.hpp b/src/core/macho/macho.hpp index ef8a3c5..4cf8688 100644 --- a/src/core/macho/macho.hpp +++ b/src/core/macho/macho.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include +#include "core/platform.hpp" #include "core/target.hpp" #include "core/util.hpp" @@ -25,14 +27,28 @@ class Macho final : public Target { public: explicit Macho(std::ifstream f, std::string filePath); +#ifdef __arm64__ + static constexpr thread_state_flavor_t THREAD_FLAVOUR = ARM_THREAD_STATE64; + static constexpr mach_msg_type_number_t THREAD_STATE_COUNT = + ARM_THREAD_STATE64_COUNT; +#endif + +#ifdef __i386__ + static constexpr thread_state_flavor_t THREAD_FLAVOUR = X86_THREAD_STATE64; + static constexpr mach_msg_type_number_t THREAD_STATE_COUNT = + X86_THREAD_STATE64_COUNT; +#endif + void dumpHeader(int offset) override; i32 attach() override; i32 setBreakpoint(u64 addr) override; i32 disableBreakpoint(u64 addr, bool remove) override; - i32 launch(CStringArray& argList) override; + i32 launch(detail::CStringArray& argList) override; void detach() override; void eventLoop() override; void resume(ResumeType cond) override; + void setThreadState(ThreadState* state) override; + ThreadState& getLastKnownThreadState() override; static std::string exceptionReason(exception_type_t exc, mach_msg_type_number_t codeCnt, @@ -50,6 +66,7 @@ class Macho final : public Target { mach_port_t m_exc_port = 0; mach_port_t m_thread_port = 0; bool m_is_swap = false; + ThreadState m_last_thread_state{}; static constexpr std::array CPU_TYPE_NAMES = { {{.cpu_type = CPU_TYPE_I386, .cpu_name = "i386"}, {.cpu_type = CPU_TYPE_X86_64, .cpu_name = "x86_64"}, @@ -66,7 +83,7 @@ class Macho final : public Target { T buf; m_file.seekg(offset, std::ios::beg); m_file.read(std::bit_cast(&buf), sizeof(T)); - if (m_is_swap) SwapDescriptor::swap(&buf); + if (m_is_swap) detail::SwapDescriptor::swap(&buf); return buf; } diff --git a/src/core/macho/types.hpp b/src/core/macho/types.hpp new file mode 100644 index 0000000..f4d4346 --- /dev/null +++ b/src/core/macho/types.hpp @@ -0,0 +1,40 @@ +#ifndef CAESAR_MACHO_TYPES_H +#define CAESAR_MACHO_TYPES_H + +#include "typedefs.hpp" + +struct X86ThreadState64T { + u64 rax; + u64 rbx; + u64 rcx; + u64 rdx; + u64 rdi; + u64 rsi; + u64 rbp; + u64 rsp; + u64 r8; + u64 r9; + u64 r10; + u64 r11; + u64 r12; + u64 r13; + u64 r14; + u64 r15; + u64 rip; + u64 rflags; + u64 cs; + u64 fs; + u64 gs; +}; + +struct ArmThreadState64T { + std::array x; + u64 fp; + u64 lr; + u64 sp; + u64 pc; + u32 cpsr; + u32 pad; +}; + +#endif diff --git a/src/core/platform.hpp b/src/core/platform.hpp new file mode 100644 index 0000000..59b9e3f --- /dev/null +++ b/src/core/platform.hpp @@ -0,0 +1,317 @@ +#ifndef CAESAR_PLATFORM_H +#define CAESAR_PLATFORM_H + +#include +#include +#include +#include +#include + +#include "expected.hpp" + +enum class Platform : u8 { MACH, LINUX, WIN }; +enum class Architecture : u8 { ARM64, X86_64, X86 }; + +enum class Arm64Reg : u8 { + // General purpose + X0, + X1, + X2, + X3, + X4, + X5, + X6, + X7, + X8, + X9, + X10, + X11, + X12, + X13, + X14, + X15, + X16, + X17, + X18, + X19, + X20, + X21, + X22, + X23, + X24, + X25, + X26, + X27, + X28, + // Named aliases + FP, // x29 + LR, // x30 + // Special registers + SP, + PC, + CPSR, + COUNT +}; + +enum class X86Reg : u8 { + // General purpose (64-bit) + RAX, + RBX, + RCX, + RDX, + RSI, + RDI, + RBP, + RSP, + R8, + R9, + R10, + R11, + R12, + R13, + R14, + R15, + // Instruction pointer & flags + RIP, + RFLAGS, + // Segment registers + CS, + SS, + DS, + ES, + FS, + GS, + COUNT +}; + +constexpr static Platform getPlatform() { + Platform t{}; +#ifdef __APPLE__ + t = Platform::MACH; +#elif defined(__linux__) + t = PlatformType::LINUX; +#elif defined(_WIN32) + t = PlatformType::WIN; +#else + static_asssert(false, "Unsupported platform!"); +#endif + return t; +} + +constexpr std::string_view getTargetType() { + constexpr Platform p = getPlatform(); + switch (p) { + case Platform::MACH: + return "Mach-O"; + case Platform::LINUX: + return "ELF"; + case Platform::WIN: + return "PE"; + } +} + +constexpr Architecture getArchitecture() { +#if defined(__arm64__) || defined(__aarch64__) + return Architecture::ARM64; +#elif defined(__x86_64__) + return Architecture::X86_64; +#elif defined(__i386__) + return Architecture::X86; +#else + static_assert(false, "Unsupported architecture"); +#endif +} + +template +struct RegEntry { + RegEnum reg; + std::ptrdiff_t offset; + u8 size; +}; + +template +struct PlatformTraits; + +struct StringHash { + using is_transparent = void; + [[nodiscard]] size_t operator()(std::string_view sv) const { + return std::hash{}(sv); + } +}; + +struct StringEqual { + using is_transparent = void; + [[nodiscard]] bool operator()(std::string_view lhs, + std::string_view rhs) const { + return lhs == rhs; + } +}; + +template +using RegMap = std::unordered_map, + StringHash, StringEqual>; + +template <> +struct PlatformTraits { + using ThreadState = ArmThreadState64T; + using RegEnum = Arm64Reg; + using Entry = RegEntry; + using Map = RegMap; + + static inline const Map REG_MAP = { + {"x0", + {.reg = Arm64Reg::X0, + .offset = offsetof(ThreadState, x) + (0 * sizeof(u64)), + .size = 8}}, + {"x1", + {.reg = Arm64Reg::X1, + .offset = offsetof(ThreadState, x) + (1 * sizeof(u64)), + .size = 8}}, + {"x2", + {.reg = Arm64Reg::X2, + .offset = offsetof(ThreadState, x) + (2 * sizeof(u64)), + .size = 8}}, + {"x3", + {.reg = Arm64Reg::X3, + .offset = offsetof(ThreadState, x) + (3 * sizeof(u64)), + .size = 8}}, + {"x4", + {.reg = Arm64Reg::X4, + .offset = offsetof(ThreadState, x) + (4 * sizeof(u64)), + .size = 8}}, + {"x5", + {.reg = Arm64Reg::X5, + .offset = offsetof(ThreadState, x) + (5 * sizeof(u64)), + .size = 8}}, + {"x6", + {.reg = Arm64Reg::X6, + .offset = offsetof(ThreadState, x) + (6 * sizeof(u64)), + .size = 8}}, + {"x7", + {.reg = Arm64Reg::X7, + .offset = offsetof(ThreadState, x) + (7 * sizeof(u64)), + .size = 8}}, + {"x8", + {.reg = Arm64Reg::X8, + .offset = offsetof(ThreadState, x) + (8 * sizeof(u64)), + .size = 8}}, + {"x9", + {.reg = Arm64Reg::X9, + .offset = offsetof(ThreadState, x) + (9 * sizeof(u64)), + .size = 8}}, + {"x10", + {.reg = Arm64Reg::X10, + .offset = offsetof(ThreadState, x) + (10 * sizeof(u64)), + .size = 8}}, + {"x11", + {.reg = Arm64Reg::X11, + .offset = offsetof(ThreadState, x) + (11 * sizeof(u64)), + .size = 8}}, + {"x12", + {.reg = Arm64Reg::X12, + .offset = offsetof(ThreadState, x) + (12 * sizeof(u64)), + .size = 8}}, + {"x13", + {.reg = Arm64Reg::X13, + .offset = offsetof(ThreadState, x) + (13 * sizeof(u64)), + .size = 8}}, + {"x14", + {.reg = Arm64Reg::X14, + .offset = offsetof(ThreadState, x) + (14 * sizeof(u64)), + .size = 8}}, + {"x15", + {.reg = Arm64Reg::X15, + .offset = offsetof(ThreadState, x) + (15 * sizeof(u64)), + .size = 8}}, + {"x16", + {.reg = Arm64Reg::X16, + .offset = offsetof(ThreadState, x) + (16 * sizeof(u64)), + .size = 8}}, + {"x17", + {.reg = Arm64Reg::X17, + .offset = offsetof(ThreadState, x) + (17 * sizeof(u64)), + .size = 8}}, + {"x18", + {.reg = Arm64Reg::X18, + .offset = offsetof(ThreadState, x) + (18 * sizeof(u64)), + .size = 8}}, + {"x19", + {.reg = Arm64Reg::X19, + .offset = offsetof(ThreadState, x) + (19 * sizeof(u64)), + .size = 8}}, + {"x20", + {.reg = Arm64Reg::X20, + .offset = offsetof(ThreadState, x) + (20 * sizeof(u64)), + .size = 8}}, + {"x21", + {.reg = Arm64Reg::X21, + .offset = offsetof(ThreadState, x) + (21 * sizeof(u64)), + .size = 8}}, + {"x22", + {.reg = Arm64Reg::X22, + .offset = offsetof(ThreadState, x) + (22 * sizeof(u64)), + .size = 8}}, + {"x23", + {.reg = Arm64Reg::X23, + .offset = offsetof(ThreadState, x) + (23 * sizeof(u64)), + .size = 8}}, + {"x24", + {.reg = Arm64Reg::X24, + .offset = offsetof(ThreadState, x) + (24 * sizeof(u64)), + .size = 8}}, + {"x25", + {.reg = Arm64Reg::X25, + .offset = offsetof(ThreadState, x) + (25 * sizeof(u64)), + .size = 8}}, + {"x26", + {.reg = Arm64Reg::X26, + .offset = offsetof(ThreadState, x) + (26 * sizeof(u64)), + .size = 8}}, + {"x27", + {.reg = Arm64Reg::X27, + .offset = offsetof(ThreadState, x) + (27 * sizeof(u64)), + .size = 8}}, + {"x28", + {.reg = Arm64Reg::X28, + .offset = offsetof(ThreadState, x) + (28 * sizeof(u64)), + .size = 8}}, + {"fp", + {.reg = Arm64Reg::FP, .offset = offsetof(ThreadState, fp), .size = 8}}, + {"x29", + {.reg = Arm64Reg::FP, .offset = offsetof(ThreadState, fp), .size = 8}}, + {"lr", + {.reg = Arm64Reg::LR, .offset = offsetof(ThreadState, lr), .size = 8}}, + {"x30", + {.reg = Arm64Reg::LR, .offset = offsetof(ThreadState, lr), .size = 8}}, + {"sp", + {.reg = Arm64Reg::SP, .offset = offsetof(ThreadState, sp), .size = 8}}, + {"pc", + {.reg = Arm64Reg::PC, .offset = offsetof(ThreadState, pc), .size = 8}}, + {"cpsr", + {.reg = Arm64Reg::CPSR, + .offset = offsetof(ThreadState, cpsr), + .size = 4}}, + }; +}; + +using CurrentPlatform = PlatformTraits; +using ThreadState = CurrentPlatform::ThreadState; +using Reg = CurrentPlatform::RegEnum; +using RegEntryT = CurrentPlatform::Entry; +inline constexpr auto& regMap = CurrentPlatform::REG_MAP; + +inline u64 readRegValue(const ThreadState& threadState, + const RegEntryT& regEntry) { + const auto* ptr = reinterpret_cast(&threadState) + regEntry.offset; + return regEntry.size == 8 ? *reinterpret_cast(ptr) + : *reinterpret_cast(ptr); +} + +inline Expected findRegEntry( + std::string_view name) { + auto it = regMap.find(name); + if (it != regMap.end()) return &it->second; + return Unexpected{std::format("Unknown register: {}", name)}; +} + +#endif diff --git a/src/core/target.cpp b/src/core/target.cpp index f4c6691..31cd859 100644 --- a/src/core/target.cpp +++ b/src/core/target.cpp @@ -2,7 +2,8 @@ #include -#include "core/util.hpp" +#include "platform.hpp" + #ifdef __APPLE__ #include "macho/macho.hpp" #endif @@ -22,18 +23,18 @@ std::unique_ptr Target::create(const std::string& path) { } bool Target::isFileValid(const std::string& filePath) { - const std::unordered_map magics{ + const std::unordered_map magics{ {{byteArrayToInt(MagicBytes{std::byte{0xCF}, std::byte{0xFA}, std::byte{0xED}, std::byte{0xFE}}), - PlatformType::MACH}, + Platform::MACH}, {byteArrayToInt(MagicBytes{std::byte{0x7F}, std::byte{'E'}, std::byte{'L'}, std::byte{'F'}}), - PlatformType::LINUX}, + Platform::LINUX}, {byteArrayToInt(MagicBytes{std::byte{'M'}, std::byte{'Z'}, std::byte{}, std::byte{}}), - PlatformType::WIN}}}; + Platform::WIN}}}; const u32 magicRead = 0; std::ifstream f{filePath}; @@ -65,3 +66,26 @@ std::string Target::getInfo() { m_is_64 ? "64-bit" : "32-bit"); return res; } + +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +std::string Target::formatRegisterOutput(ThreadState* threadState) const { + std::string res{}; + constexpr int regsPerRow = 4; + for (int i = 0; i < 29; i++) { + res += std::format( + " x{:<2}: {}", i, + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + detail::toHex(threadState->x[i])); + if ((i + 1) % regsPerRow == 0) + res += '\n'; + else + res += " "; + } + + res += std::format("\n fp: {} lr: {}\n", detail::toHex(threadState->fp), + detail::toHex(threadState->lr)); + res += std::format(" sp: {} pc: {}\n", detail::toHex(threadState->sp), + detail::toHex(threadState->pc)); + res += std::format(" cpsr: {}\n", detail::toHex(threadState->cpsr)); + return res; +} diff --git a/src/core/target.hpp b/src/core/target.hpp index de89dad..cc2489f 100644 --- a/src/core/target.hpp +++ b/src/core/target.hpp @@ -8,13 +8,14 @@ #include #include +#include "core/platform.hpp" #include "typedefs.hpp" #include "util.hpp" -enum class TargetState : std::uint8_t { STOPPED, RUNNING, EXITED }; -enum class BinaryType : std::uint8_t { MACHO, ELF, PE }; -enum class TargetError : std::uint8_t { FORK_FAIL }; -enum class ResumeType : std::uint8_t { RESUME }; +enum class TargetState : u8 { STOPPED, RUNNING, EXITED }; +enum class BinaryType : u8 { MACHO, ELF, PE }; +enum class TargetError : u8 { FORK_FAIL }; +enum class ResumeType : u8 { RESUME }; struct Breakpoint { u32 orig_ins; @@ -50,10 +51,12 @@ class Target { virtual i32 attach() = 0; virtual i32 setBreakpoint(u64 addr) = 0; virtual i32 disableBreakpoint(u64 addr, bool remove) = 0; - virtual i32 launch(CStringArray& argList) = 0; + virtual i32 launch(detail::CStringArray& argList) = 0; virtual void detach() = 0; virtual void eventLoop() = 0; virtual void resume(ResumeType cond) = 0; + virtual void setThreadState(ThreadState* state) = 0; + virtual ThreadState& getLastKnownThreadState() = 0; void setTargetState(TargetState s) { m_state = s; } std::atomic& getTargetState() { return m_state; } @@ -61,6 +64,7 @@ class Target { void startEventLoop(); std::map& getRegisteredBreakpoints(); std::string getInfo(); + std::string formatRegisterOutput(ThreadState* threadState) const; static bool isFileValid(const std::string& filePath); static std::unique_ptr create(const std::string& path); diff --git a/src/core/util.hpp b/src/core/util.hpp index ad7a9d7..71c6773 100644 --- a/src/core/util.hpp +++ b/src/core/util.hpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -17,6 +16,7 @@ #include "expected.hpp" #include "typedefs.hpp" +namespace detail { template struct SwapDescriptor { static void swap(T* ptr) { @@ -128,19 +128,6 @@ struct SwapDescriptor { #endif -enum class PlatformType : std::uint8_t { MACH, LINUX, WIN, UNSUPPORTED }; -constexpr static PlatformType getPlatform() { - PlatformType t = PlatformType::UNSUPPORTED; -#ifdef __APPLE__ - t = PlatformType::MACH; -#elif defined(__linux__) - t = PlatformType::LINUX; -#elif defined(_WIN32) - t = PlatformType::LINUX; -#endif - return t; -} - struct CStringArray { std::vector> storage; std::vector ptrs; @@ -178,17 +165,6 @@ inline Expected strToAddr(const std::string& addr) { return Unexpected{"Address provided is out of range!"}; } } - -constexpr std::string_view getTargetType() { - constexpr PlatformType p = getPlatform(); - switch (p) { - case PlatformType::MACH: - return "Mach-O"; - case PlatformType::LINUX: - return "ELF"; - case PlatformType::WIN: - return "PE"; - } -} +} // namespace detail #endif diff --git a/src/error.hpp b/src/error.hpp index ad90945..72a22bb 100644 --- a/src/error.hpp +++ b/src/error.hpp @@ -1,17 +1,14 @@ #ifndef CMD_ERROR_H #define CMD_ERROR_H -#include #include #include #include -enum class CmdErrorType : std::uint8_t { - PARSE_ERROR, - SCAN_ERROR, - RUNTIME_ERROR -}; -enum class TokenType : std::uint8_t; +#include "cmd/token_type.hpp" + +enum class CmdErrorType : u8 { PARSE_ERROR, SCAN_ERROR, RUNTIME_ERROR }; +enum class TokenType : u8; // NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) class CoreError { diff --git a/src/typedefs.hpp b/src/typedefs.hpp index 0cac917..cd50878 100644 --- a/src/typedefs.hpp +++ b/src/typedefs.hpp @@ -12,5 +12,6 @@ using u64 = std::uint64_t; using MagicBytes = std::array; using FnPtr = Object (*)(const std::vector&); using sv = std::string_view; +using u8 = std::uint8_t; #endif