From 733e62a8418f0a7a36663c79d950b0c472b49682 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 23 Mar 2026 00:26:46 +0000 Subject: [PATCH 01/10] feat(core/macho): write register values --- src/cmd/stdlib.hpp | 16 +++++++++++++++- src/core/macho/macho.cpp | 35 +++++++++++++++++++++++++---------- src/core/macho/macho.hpp | 1 + src/core/target.hpp | 1 + 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/cmd/stdlib.hpp b/src/cmd/stdlib.hpp index 0b9194f..a60431d 100644 --- a/src/cmd/stdlib.hpp +++ b/src/cmd/stdlib.hpp @@ -276,7 +276,21 @@ class RegisterFn : public Callable { detail::toHex(readRegValue( target->getLastKnownThreadState(), *res.value()))); }; - SubcommandHandler m_subcmds{{{sv("view"), view}}, "register"}; + + FnPtr write = [](const std::vector& args) -> Object { + auto& target = Context::getTarget(); + if (!target && !target->m_started) return "Target is not running!"; + auto res = findRegEntry(args.front()); + if (!res.hasValue()) return res.error(); + auto val = detail::strToAddr(args[1]); + if (!val.hasValue()) return val.error(); + auto regWriteRes = target->writeRegValue(*res.value(), val.value()); + if (regWriteRes != 0) return "Error writing to register!"; + return std::format("{}: {}", args.front(), args[1]); + }; + + SubcommandHandler m_subcmds{{{sv("view"), view}, {sv("write"), write}}, + "register"}; public: [[nodiscard]] int arity() const override { return 2; } diff --git a/src/core/macho/macho.cpp b/src/core/macho/macho.cpp index ed59967..4b349fd 100644 --- a/src/core/macho/macho.cpp +++ b/src/core/macho/macho.cpp @@ -365,16 +365,6 @@ kern_return_t catch_mach_exception_raise_state_identity( if (macho->getThreadPort() == 0) macho->setThreadPort(thread); - if (exc == EXC_BAD_INSTRUCTION && *flavour == ARM_THREAD_STATE64) { - 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()); } @@ -631,3 +621,28 @@ void Macho::setThreadState(ThreadState* state) { } ThreadState& Macho::getLastKnownThreadState() { return m_last_thread_state; } + +u64 Macho::writeRegValue(const RegEntryT& regEntry, u64 val) { + auto* ptr = reinterpret_cast(&m_last_thread_state) + regEntry.offset; + + if (regEntry.size == 8) { + auto* reg = reinterpret_cast(ptr); + *reg = val; + } else { + auto* reg = reinterpret_cast(ptr); + *reg = val; + } + + const kern_return_t kr = + thread_set_state(m_thread_port, Macho::THREAD_FLAVOUR, + reinterpret_cast(&m_last_thread_state), + Macho::THREAD_STATE_COUNT); + + if (kr != KERN_SUCCESS) { + std::cout << "thread_set_state fail!\n"; + CoreError::error(mach_error_string(kr)); + return -1; + } + + return 0; +} diff --git a/src/core/macho/macho.hpp b/src/core/macho/macho.hpp index 4cf8688..415ba25 100644 --- a/src/core/macho/macho.hpp +++ b/src/core/macho/macho.hpp @@ -49,6 +49,7 @@ class Macho final : public Target { void resume(ResumeType cond) override; void setThreadState(ThreadState* state) override; ThreadState& getLastKnownThreadState() override; + u64 writeRegValue(const RegEntryT& regEntry, u64 val) override; static std::string exceptionReason(exception_type_t exc, mach_msg_type_number_t codeCnt, diff --git a/src/core/target.hpp b/src/core/target.hpp index cc2489f..e20ab69 100644 --- a/src/core/target.hpp +++ b/src/core/target.hpp @@ -57,6 +57,7 @@ class Target { virtual void resume(ResumeType cond) = 0; virtual void setThreadState(ThreadState* state) = 0; virtual ThreadState& getLastKnownThreadState() = 0; + virtual u64 writeRegValue(const RegEntryT& regEntry, u64 val) = 0; void setTargetState(TargetState s) { m_state = s; } std::atomic& getTargetState() { return m_state; } From d87826b77d4a54b2afc93fa3c72beb64839473d6 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 23 Mar 2026 01:51:23 +0000 Subject: [PATCH 02/10] refactor(cmd): refactor subcommand target check into lambda wrapper --- src/cmd/callable.hpp | 9 +++++ src/cmd/stdlib.hpp | 86 ++++++++++++++++++++++---------------------- src/typedefs.hpp | 3 +- 3 files changed, 53 insertions(+), 45 deletions(-) diff --git a/src/cmd/callable.hpp b/src/cmd/callable.hpp index e79d25f..e1606ef 100644 --- a/src/cmd/callable.hpp +++ b/src/cmd/callable.hpp @@ -8,6 +8,7 @@ #include "core/context.hpp" #include "core/target.hpp" #include "object.hpp" +#include "typedefs.hpp" class Interpreter; @@ -32,4 +33,12 @@ struct std::formatter { // NOLINT(cert-dcl58-cpp) } }; +inline FnPtr requiresRunningTarget(const FnPtr& fn) { + return [fn = fn](const std::vector& args) -> Object { + auto& target = Context::getTarget(); + if (!target || !target->m_started) return "Target is not running!"; + return fn(args); + }; +} + #endif diff --git a/src/cmd/stdlib.hpp b/src/cmd/stdlib.hpp index a60431d..9f07f05 100644 --- a/src/cmd/stdlib.hpp +++ b/src/cmd/stdlib.hpp @@ -74,7 +74,7 @@ class BreakpointFn : public Callable { return std::format("Removed breakpoint at {}", addr); } - FnPtr list = [](const std::vector& args) -> Object { + FnPtr list = requiresRunningTarget([](const std::vector& args) -> Object { #pragma unused(args) auto& breakpoints = Context::getTarget()->getRegisteredBreakpoints(); std::string retStr{}; @@ -88,9 +88,9 @@ class BreakpointFn : public Callable { retStr.pop_back(); return retStr; - }; + }); - FnPtr set = [](const std::vector& args) -> Object { + FnPtr set = requiresRunningTarget([](const std::vector& args) -> Object { Expected addrRes = detail::strToAddr(args[0]); if (!addrRes) return addrRes.error(); const u64 addr = *addrRes; @@ -100,15 +100,15 @@ class BreakpointFn : public Callable { return "Error setting breakpoint!"; else return std::format("Breakpoint set at: {}", detail::toHex(addr)); - }; + }); - FnPtr remove = [](const std::vector& args) -> Object { + FnPtr remove = requiresRunningTarget([](const std::vector& args) -> Object { return BreakpointFn::rmOrToggleBreakpoint(args, false); - }; + }); - FnPtr toggle = [](const std::vector& args) -> Object { + FnPtr toggle = requiresRunningTarget([](const std::vector& args) -> Object { return BreakpointFn::rmOrToggleBreakpoint(args, true); - }; + }); SubcommandHandler m_subcmds{{{sv("list"), list}, {sv("set"), set}, @@ -230,21 +230,17 @@ class TargetFn : public Callable { return target->getInfo(); }; - FnPtr set = [](const std::vector& args) -> Object { - auto& target = Context::getTarget(); - if (target) { - if (target->getTargetState() == TargetState::RUNNING || - target->getTargetState() == TargetState::STOPPED) - return "Cannot set new target whilst one is running!"; - } + FnPtr set = + requiresRunningTarget([](const std::vector& args) -> Object { + auto& target = Context::getTarget(); - if (Target::isFileValid(args[0])) { - Context::setTarget(Target::create(args[0])); - return std::format("Target: {}", args[0]); - } + if (Target::isFileValid(args[0])) { + Context::setTarget(Target::create(args[0])); + return std::format("Target: {}", args[0]); + } - return std::format("{} is not a valid target path!", args[0]); - }; + return std::format("{} is not a valid target path!", args[0]); + }); SubcommandHandler m_subcmds{{{sv("info"), info}, {sv("set"), set}}, "target"}; @@ -265,29 +261,31 @@ 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()))); - }; - - FnPtr write = [](const std::vector& args) -> Object { - auto& target = Context::getTarget(); - if (!target && !target->m_started) return "Target is not running!"; - auto res = findRegEntry(args.front()); - if (!res.hasValue()) return res.error(); - auto val = detail::strToAddr(args[1]); - if (!val.hasValue()) return val.error(); - auto regWriteRes = target->writeRegValue(*res.value(), val.value()); - if (regWriteRes != 0) return "Error writing to register!"; - return std::format("{}: {}", args.front(), args[1]); - }; + FnPtr view = + requiresRunningTarget([](const std::vector& args) -> Object { + auto& target = Context::getTarget(); + if (args.front() == "all") + return target->formatRegisterOutput( + &target->getLastKnownThreadState()); + auto res = findRegEntry(args.front()); + if (!res) return res.error(); + return std::format( + "{}: {}", args.front(), + detail::toHex( + readRegValue(target->getLastKnownThreadState(), *res.value()))); + }); + + FnPtr write = + requiresRunningTarget([](const std::vector& args) -> Object { + auto& target = Context::getTarget(); + auto res = findRegEntry(args.front()); + if (!res) return res.error(); + auto val = detail::strToAddr(args[1]); + if (!val) return val.error(); + auto regWriteRes = target->writeRegValue(*res.value(), val.value()); + if (regWriteRes != 0) return "Error writing to register!"; + return std::format("{}: {}", args.front(), args[1]); + }); SubcommandHandler m_subcmds{{{sv("view"), view}, {sv("write"), write}}, "register"}; diff --git a/src/typedefs.hpp b/src/typedefs.hpp index cd50878..a6d3386 100644 --- a/src/typedefs.hpp +++ b/src/typedefs.hpp @@ -4,13 +4,14 @@ #include #include #include +#include using u32 = std::uint32_t; using i32 = std::int32_t; using i64 = std::int64_t; using u64 = std::uint64_t; using MagicBytes = std::array; -using FnPtr = Object (*)(const std::vector&); +using FnPtr = std::function&)>; using sv = std::string_view; using u8 = std::uint8_t; From 9f9502bf16b33adb0b76a427e0a5c69f3e6f3126 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 23 Mar 2026 02:08:42 +0000 Subject: [PATCH 03/10] refactor(cmd): refactor subcommand handling into base class --- src/cmd/callable.hpp | 19 +++++++ src/cmd/stdlib.hpp | 123 ++++++++++++++++++------------------------- 2 files changed, 70 insertions(+), 72 deletions(-) diff --git a/src/cmd/callable.hpp b/src/cmd/callable.hpp index e1606ef..5161155 100644 --- a/src/cmd/callable.hpp +++ b/src/cmd/callable.hpp @@ -5,6 +5,8 @@ #include #include +#include "cmd/subcommand.hpp" +#include "cmd/util.hpp" #include "core/context.hpp" #include "core/target.hpp" #include "object.hpp" @@ -33,6 +35,23 @@ struct std::formatter { // NOLINT(cert-dcl58-cpp) } }; +class SubcommandCallable : public Callable { + protected: + SubcommandHandler m_subcmds; + + explicit SubcommandCallable(SubcommandHandler subcmds) + : m_subcmds(std::move(subcmds)) {} + + public: + 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); + } +}; + inline FnPtr requiresRunningTarget(const FnPtr& fn) { return [fn = fn](const std::vector& args) -> Object { auto& target = Context::getTarget(); diff --git a/src/cmd/stdlib.hpp b/src/cmd/stdlib.hpp index 9f07f05..a49106a 100644 --- a/src/cmd/stdlib.hpp +++ b/src/cmd/stdlib.hpp @@ -11,12 +11,10 @@ #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" -#include "subcommand.hpp" #include "token_type.hpp" #include "typedefs.hpp" @@ -49,7 +47,7 @@ class PrintFn : public Callable { Object call(std::vector args) override { return args[0]; } }; -class BreakpointFn : public Callable { +class BreakpointFn : public SubcommandCallable { private: static Object rmOrToggleBreakpoint(const std::vector& args, bool toggle = false) { @@ -74,47 +72,45 @@ class BreakpointFn : public Callable { return std::format("Removed breakpoint at {}", addr); } - FnPtr list = requiresRunningTarget([](const std::vector& args) -> Object { + static inline FnPtr list = + requiresRunningTarget([](const std::vector& args) -> Object { #pragma unused(args) - auto& breakpoints = Context::getTarget()->getRegisteredBreakpoints(); - std::string retStr{}; - - if (breakpoints.empty()) return "No breakpoints set!"; - - for (const auto& [k, v] : breakpoints) { - retStr += std::format("Breakpoint @ {} ({})\n", detail::toHex(k), - breakpoints[k].enabled); - } + auto& breakpoints = Context::getTarget()->getRegisteredBreakpoints(); + std::string retStr{}; - retStr.pop_back(); - return retStr; - }); + if (breakpoints.empty()) return "No breakpoints set!"; - FnPtr set = requiresRunningTarget([](const std::vector& args) -> Object { - Expected addrRes = detail::strToAddr(args[0]); - if (!addrRes) return addrRes.error(); - const u64 addr = *addrRes; + for (const auto& [k, v] : breakpoints) { + retStr += std::format("Breakpoint @ {} ({})\n", detail::toHex(k), + breakpoints[k].enabled); + } - const i32 bpRes = Context::getTarget()->setBreakpoint(addr); - if (bpRes != 0) - return "Error setting breakpoint!"; - else - return std::format("Breakpoint set at: {}", detail::toHex(addr)); - }); + retStr.pop_back(); + return retStr; + }); - FnPtr remove = requiresRunningTarget([](const std::vector& args) -> Object { - return BreakpointFn::rmOrToggleBreakpoint(args, false); - }); + static inline FnPtr set = + requiresRunningTarget([](const std::vector& args) -> Object { + Expected addrRes = detail::strToAddr(args[0]); + if (!addrRes) return addrRes.error(); + const u64 addr = *addrRes; + + const i32 bpRes = Context::getTarget()->setBreakpoint(addr); + if (bpRes != 0) + return "Error setting breakpoint!"; + else + return std::format("Breakpoint set at: {}", detail::toHex(addr)); + }); - FnPtr toggle = requiresRunningTarget([](const std::vector& args) -> Object { - return BreakpointFn::rmOrToggleBreakpoint(args, true); - }); + static inline FnPtr remove = + requiresRunningTarget([](const std::vector& args) -> Object { + return BreakpointFn::rmOrToggleBreakpoint(args, false); + }); - SubcommandHandler m_subcmds{{{sv("list"), list}, - {sv("set"), set}, - {sv("remove"), remove}, - {sv("toggle"), toggle}}, - "breakpoint"}; + static inline FnPtr toggle = + requiresRunningTarget([](const std::vector& args) -> Object { + return BreakpointFn::rmOrToggleBreakpoint(args, true); + }); public: [[nodiscard]] int arity() const override { return 1; } @@ -122,17 +118,12 @@ class BreakpointFn : public Callable { return ""; } - Object call(std::vector args) override { - if (m_target == nullptr) { - CoreError::error("Target is not running!"); - return std::monostate{}; - } - 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); - } + BreakpointFn() + : SubcommandCallable({{{sv("list"), list}, + {sv("set"), set}, + {sv("remove"), remove}, + {sv("toggle"), toggle}}, + "breakpoint"}) {} }; class RunFn : public Callable { @@ -221,16 +212,16 @@ class ContinueFn : public Callable { } }; -class TargetFn : public Callable { +class TargetFn : public SubcommandCallable { private: - FnPtr info = [](const std::vector& args) -> Object { + static inline FnPtr info = [](const std::vector& args) -> Object { #pragma unused(args) auto& target = Context::getTarget(); if (!target) return "Target not set!"; return target->getInfo(); }; - FnPtr set = + static inline FnPtr set = requiresRunningTarget([](const std::vector& args) -> Object { auto& target = Context::getTarget(); @@ -242,26 +233,20 @@ class TargetFn : public Callable { return std::format("{} is not a valid target path!", args[0]); }); - SubcommandHandler m_subcmds{{{sv("info"), info}, {sv("set"), set}}, "target"}; - public: [[nodiscard]] int arity() const override { return 1; } [[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); + TargetFn() + : SubcommandCallable({{{sv("info"), info}, {sv("set"), set}}, "target"}) { } }; -class RegisterFn : public Callable { +class RegisterFn : public SubcommandCallable { private: - FnPtr view = + static inline FnPtr view = requiresRunningTarget([](const std::vector& args) -> Object { auto& target = Context::getTarget(); if (args.front() == "all") @@ -275,7 +260,7 @@ class RegisterFn : public Callable { readRegValue(target->getLastKnownThreadState(), *res.value()))); }); - FnPtr write = + static inline FnPtr write = requiresRunningTarget([](const std::vector& args) -> Object { auto& target = Context::getTarget(); auto res = findRegEntry(args.front()); @@ -287,21 +272,15 @@ class RegisterFn : public Callable { return std::format("{}: {}", args.front(), args[1]); }); - SubcommandHandler m_subcmds{{{sv("view"), view}, {sv("write"), write}}, - "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); - } + + RegisterFn() + : SubcommandCallable( + {{{sv("view"), view}, {sv("write"), write}}, "register"}) {} }; #endif From 12599026b4db0c15ea8a9afff3f3fe09b5f3c8a7 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 23 Mar 2026 03:00:03 +0000 Subject: [PATCH 04/10] fix(cmd/misc): support same type for Result and Error --- src/expected.hpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/expected.hpp b/src/expected.hpp index 7015347..e97f61e 100644 --- a/src/expected.hpp +++ b/src/expected.hpp @@ -18,21 +18,20 @@ class Expected { public: // NOLINTBEGIN(google-explicit-constructor) - Expected(const Result& val) : m_data(val) {} - Expected(Result&& val) : m_data(std::move(val)) {} - Expected(Unexpected err) : m_data(std::move(err.error)) {} + Expected(const Result& val) : m_data(std::in_place_index<0>, val) {} + Expected(Result&& val) : m_data(std::in_place_index<0>, std::move(val)) {} + Expected(Unexpected err) + : m_data(std::in_place_index<1>, std::move(err.error)) {} // NOLINTEND(google-explicit-constructor) - [[nodiscard]] bool hasValue() const { - return std::holds_alternative(m_data); - } + [[nodiscard]] bool hasValue() const { return m_data.index() == 0; } explicit operator bool() const { return hasValue(); } - Result& value() { return std::get(m_data); } - const Result& value() const { return std::get(m_data); } + Result& value() { return std::get<0>(m_data); } + const Result& value() const { return std::get<0>(m_data); } - Error& error() { return std::get(m_data); } - const Error& error() const { return std::get(m_data); } + Error& error() { return std::get<1>(m_data); } + const Error& error() const { return std::get<1>(m_data); } Result& operator*() { return value(); } Result* operator->() { return &value(); } From 31e5e7dd575d958f903c08e1fb627afd549b4f42 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 23 Mar 2026 03:02:21 +0000 Subject: [PATCH 05/10] refactor(cmd): use Object arg instead of std::string in subcommand --- src/cmd/callable.hpp | 22 +++++++---- src/cmd/stdlib.hpp | 89 ++++++++++++++++++++++++------------------ src/cmd/subcommand.hpp | 2 +- src/cmd/util.hpp | 29 ++++++++++++++ src/typedefs.hpp | 2 +- 5 files changed, 97 insertions(+), 47 deletions(-) diff --git a/src/cmd/callable.hpp b/src/cmd/callable.hpp index 5161155..5ce6f14 100644 --- a/src/cmd/callable.hpp +++ b/src/cmd/callable.hpp @@ -9,6 +9,7 @@ #include "cmd/util.hpp" #include "core/context.hpp" #include "core/target.hpp" +#include "expected.hpp" #include "object.hpp" #include "typedefs.hpp" @@ -44,20 +45,27 @@ class SubcommandCallable : public Callable { public: 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); + if (args.empty()) return std::monostate{}; + auto* subcmdStr = std::get_if(&args.front()); + if (!subcmdStr) return "Subcommand must be a string"; + std::string subcmd = *subcmdStr; + args.erase(args.begin()); + return m_subcmds.exec(subcmd, args); } }; -inline FnPtr requiresRunningTarget(const FnPtr& fn) { - return [fn = fn](const std::vector& args) -> Object { +inline FnPtr requiresRunningTarget(FnPtr fn) { + return [fn = std::move(fn)](const std::vector& args) -> Object { auto& target = Context::getTarget(); if (!target || !target->m_started) return "Target is not running!"; return fn(args); }; } +template +auto andThen(Expected& exp, Fn&& fn) -> decltype(fn(*exp)) { + if (!exp) return Unexpected{exp.error()}; + return fn(*exp); +} + #endif diff --git a/src/cmd/stdlib.hpp b/src/cmd/stdlib.hpp index a49106a..137d637 100644 --- a/src/cmd/stdlib.hpp +++ b/src/cmd/stdlib.hpp @@ -11,6 +11,7 @@ #include "callable.hpp" #include "cmd/object.hpp" +#include "cmd/util.hpp" #include "core/platform.hpp" #include "core/target.hpp" #include "error.hpp" @@ -49,12 +50,7 @@ class PrintFn : public Callable { class BreakpointFn : public SubcommandCallable { private: - static Object rmOrToggleBreakpoint(const std::vector& args, - bool toggle = false) { - Expected addrRes = detail::strToAddr(args[0]); - if (!addrRes) return addrRes.error(); - u64 addr = *addrRes; - + static Object rmOrToggleBreakpoint(u64 addr, bool toggle = false) { auto& target = Context::getTarget(); auto& bps = target->getRegisteredBreakpoints(); @@ -72,8 +68,8 @@ class BreakpointFn : public SubcommandCallable { return std::format("Removed breakpoint at {}", addr); } - static inline FnPtr list = - requiresRunningTarget([](const std::vector& args) -> Object { + static inline FnPtr list = + requiresRunningTarget([](const std::vector& args) -> Object { #pragma unused(args) auto& breakpoints = Context::getTarget()->getRegisteredBreakpoints(); std::string retStr{}; @@ -90,26 +86,29 @@ class BreakpointFn : public SubcommandCallable { }); static inline FnPtr set = - requiresRunningTarget([](const std::vector& args) -> Object { - Expected addrRes = detail::strToAddr(args[0]); - if (!addrRes) return addrRes.error(); - const u64 addr = *addrRes; + requiresRunningTarget([](const std::vector& args) -> Object { + auto addr = detail::asU64(args.front()); + if (!addr) return addr.error(); - const i32 bpRes = Context::getTarget()->setBreakpoint(addr); + const i32 bpRes = Context::getTarget()->setBreakpoint(*addr); if (bpRes != 0) return "Error setting breakpoint!"; else - return std::format("Breakpoint set at: {}", detail::toHex(addr)); + return std::format("Breakpoint set at: {}", detail::toHex(*addr)); }); static inline FnPtr remove = - requiresRunningTarget([](const std::vector& args) -> Object { - return BreakpointFn::rmOrToggleBreakpoint(args, false); + requiresRunningTarget([](const std::vector& args) -> Object { + auto addr = detail::asU64(args.front()); + if (!addr) return addr.error(); + return BreakpointFn::rmOrToggleBreakpoint(*addr, false); }); static inline FnPtr toggle = - requiresRunningTarget([](const std::vector& args) -> Object { - return BreakpointFn::rmOrToggleBreakpoint(args, true); + requiresRunningTarget([](const std::vector& args) -> Object { + auto addr = detail::asU64(args.front()); + if (!addr) return addr.error(); + return BreakpointFn::rmOrToggleBreakpoint(*addr, true); }); public: @@ -214,7 +213,7 @@ class ContinueFn : public Callable { class TargetFn : public SubcommandCallable { private: - static inline FnPtr info = [](const std::vector& args) -> Object { + static inline FnPtr info = [](const std::vector& args) -> Object { #pragma unused(args) auto& target = Context::getTarget(); if (!target) return "Target not set!"; @@ -222,15 +221,18 @@ class TargetFn : public SubcommandCallable { }; static inline FnPtr set = - requiresRunningTarget([](const std::vector& args) -> Object { + requiresRunningTarget([](const std::vector& args) -> Object { auto& target = Context::getTarget(); - if (Target::isFileValid(args[0])) { - Context::setTarget(Target::create(args[0])); - return std::format("Target: {}", args[0]); + auto newTarget = detail::asString(args.front()); + if (newTarget) return newTarget.error(); + + if (Target::isFileValid(*newTarget)) { + Context::setTarget(Target::create(*newTarget)); + return std::format("Target: {}", *newTarget); } - return std::format("{} is not a valid target path!", args[0]); + return std::format("{} is not a valid target path!", *newTarget); }); public: @@ -247,29 +249,40 @@ class TargetFn : public SubcommandCallable { class RegisterFn : public SubcommandCallable { private: static inline FnPtr view = - requiresRunningTarget([](const std::vector& args) -> Object { + requiresRunningTarget([](const std::vector& args) -> Object { auto& target = Context::getTarget(); - if (args.front() == "all") + + auto reg = detail::asString(args.front()); + if (*reg == "all") return target->formatRegisterOutput( &target->getLastKnownThreadState()); - auto res = findRegEntry(args.front()); - if (!res) return res.error(); + + auto entry = findRegEntry(*reg); + if (!entry) return entry.error(); + return std::format( - "{}: {}", args.front(), - detail::toHex( - readRegValue(target->getLastKnownThreadState(), *res.value()))); + "{}: {}", *reg, + detail::toHex(readRegValue(target->getLastKnownThreadState(), + *entry.value()))); }); static inline FnPtr write = - requiresRunningTarget([](const std::vector& args) -> Object { + requiresRunningTarget([](const std::vector& args) -> Object { auto& target = Context::getTarget(); - auto res = findRegEntry(args.front()); - if (!res) return res.error(); - auto val = detail::strToAddr(args[1]); + + auto regName = detail::asString(args.front()); + if (!regName) return regName.error(); + + auto entry = findRegEntry(*regName); + if (!entry) return entry.error(); + + auto val = detail::asU64(args[1]); if (!val) return val.error(); - auto regWriteRes = target->writeRegValue(*res.value(), val.value()); - if (regWriteRes != 0) return "Error writing to register!"; - return std::format("{}: {}", args.front(), args[1]); + + if (target->writeRegValue(*entry.value(), *val) != 0) + return "Error writing to register!"; + + return std::format("{}: {}", *regName, *val); }); public: diff --git a/src/cmd/subcommand.hpp b/src/cmd/subcommand.hpp index 16878a7..85b62e8 100644 --- a/src/cmd/subcommand.hpp +++ b/src/cmd/subcommand.hpp @@ -24,7 +24,7 @@ class SubcommandHandler { const std::string_view callee) : m_subcommands(fns), m_callee(callee) {}; - Object exec(const std::string& subcmd, const std::vector& args) { + Object exec(const std::string& subcmd, const std::vector& args) { if (m_subcommands.contains(subcmd)) return std::invoke(m_subcommands[subcmd], args); CmdError::error(TokenType::IDENTIFIER, diff --git a/src/cmd/util.hpp b/src/cmd/util.hpp index 4810fb2..a87fdd7 100644 --- a/src/cmd/util.hpp +++ b/src/cmd/util.hpp @@ -5,13 +5,42 @@ #include #include "cmd/object.hpp" +#include "expected.hpp" #include "token_type.hpp" +#include "typedefs.hpp" namespace detail { bool isop(char c); TokenType ctoop(char c); double parseNumber(const std::string& str); std::vector convertToStr(const std::vector& vec); + +// Object extraction helpers +inline Expected asU64(const Object& obj) { + if (const auto* d = std::get_if(&obj)) { + return static_cast(*d); + } + if (const auto* s = std::get_if(&obj)) { + // Try parsing as hex/decimal + try { + if (s->starts_with("0x") || s->starts_with("0X")) { + return static_cast(std::stoull(*s, nullptr, 16)); + } + return static_cast(std::stoull(*s, nullptr, 10)); + } catch (...) { + return Unexpected{std::format("Cannot parse '{}' as number", *s)}; + } + } + return Unexpected{"Expected numeric value"}; +} + +inline Expected asString(const Object& obj) { + if (const auto* s = std::get_if(&obj)) { + return *s; + } + return Unexpected{"Expected string"}; +} + } // namespace detail #endif diff --git a/src/typedefs.hpp b/src/typedefs.hpp index a6d3386..c3ef851 100644 --- a/src/typedefs.hpp +++ b/src/typedefs.hpp @@ -11,7 +11,7 @@ using i32 = std::int32_t; using i64 = std::int64_t; using u64 = std::uint64_t; using MagicBytes = std::array; -using FnPtr = std::function&)>; +using FnPtr = std::function&)>; using sv = std::string_view; using u8 = std::uint8_t; From 6cd688e828cb15c71ccdb8bd9d87cb1c136c1897 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 23 Mar 2026 03:03:38 +0000 Subject: [PATCH 06/10] test(cmd): update tests for Object-based subcommands --- test/cmd/test_environment.cpp | 4 ++-- test/cmd/test_helpers.hpp | 4 ++-- test/cmd/test_stdlib.cpp | 9 ++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/test/cmd/test_environment.cpp b/test/cmd/test_environment.cpp index 8232f3b..395e71b 100644 --- a/test/cmd/test_environment.cpp +++ b/test/cmd/test_environment.cpp @@ -174,13 +174,13 @@ TEST_CASE("Test SubcommandHandler exec", "[subcommand][exec]") { SubcommandHandler handler({{"test", helpers::testSubcmd}}, "testcmd"); SECTION("Execute valid subcommand") { - Object result = handler.exec("test", {"hello"}); + Object result = handler.exec("test", {std::string("hello")}); REQUIRE(std::get(result) == "hello"); } SECTION("Execute invalid subcommand produces error") { auto captured = helpers::captureStream(std::cerr, [&handler]() { - Object result = handler.exec("invalid", {}); + Object result = handler.exec("invalid", std::vector{}); REQUIRE(std::holds_alternative(result)); }); REQUIRE(captured.find("Subcommand invalid is not valid") != diff --git a/test/cmd/test_helpers.hpp b/test/cmd/test_helpers.hpp index 1bdfdad..6d4fd81 100644 --- a/test/cmd/test_helpers.hpp +++ b/test/cmd/test_helpers.hpp @@ -93,8 +93,8 @@ inline Token makeToken(const std::string& name) { } // Simple subcommand for testing -inline Object testSubcmd(const std::vector& args) { - return args.empty() ? std::string("ok") : args[0]; +inline Object testSubcmd(const std::vector& args) { + return args.empty() ? Object{std::string("ok")} : args[0]; } } // namespace helpers diff --git a/test/cmd/test_stdlib.cpp b/test/cmd/test_stdlib.cpp index 4dddf2f..da88769 100644 --- a/test/cmd/test_stdlib.cpp +++ b/test/cmd/test_stdlib.cpp @@ -35,11 +35,10 @@ TEST_CASE("Test BreakpointFn without target", "[stdlib][breakpoint]") { SECTION("call when target is null produces error") { std::vector args = {std::string("list")}; - auto captured = helpers::captureStream(std::cerr, [&bp, &args]() { - Object result = bp.call(args); - REQUIRE(std::holds_alternative(result)); - }); - REQUIRE(captured.find("Target is not running") != std::string::npos); + Object result = bp.call(args); + REQUIRE(std::holds_alternative(result)); + REQUIRE(std::get(result).find("Target is not running") != + std::string::npos); } } From d4e17e33a38be27c5585ea655610076a2f7920db Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 23 Mar 2026 03:06:20 +0000 Subject: [PATCH 07/10] refactor(cmd): remove unused function `andThen` --- src/cmd/callable.hpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/cmd/callable.hpp b/src/cmd/callable.hpp index 5ce6f14..1f7bc88 100644 --- a/src/cmd/callable.hpp +++ b/src/cmd/callable.hpp @@ -62,10 +62,4 @@ inline FnPtr requiresRunningTarget(FnPtr fn) { }; } -template -auto andThen(Expected& exp, Fn&& fn) -> decltype(fn(*exp)) { - if (!exp) return Unexpected{exp.error()}; - return fn(*exp); -} - #endif From 53ebe02e840ffa88263bb7ef2af96ae62c2c33be Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 23 Mar 2026 03:11:26 +0000 Subject: [PATCH 08/10] style: remove unused includes and fix lint errors --- src/cmd/callable.hpp | 4 +--- src/core/macho/macho.cpp | 1 + src/core/platform.hpp | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cmd/callable.hpp b/src/cmd/callable.hpp index 1f7bc88..cfe50fe 100644 --- a/src/cmd/callable.hpp +++ b/src/cmd/callable.hpp @@ -6,10 +6,8 @@ #include #include "cmd/subcommand.hpp" -#include "cmd/util.hpp" #include "core/context.hpp" #include "core/target.hpp" -#include "expected.hpp" #include "object.hpp" #include "typedefs.hpp" @@ -47,7 +45,7 @@ class SubcommandCallable : public Callable { Object call(std::vector args) override { if (args.empty()) return std::monostate{}; auto* subcmdStr = std::get_if(&args.front()); - if (!subcmdStr) return "Subcommand must be a string"; + if (subcmdStr == nullptr) return "Subcommand must be a string"; std::string subcmd = *subcmdStr; args.erase(args.begin()); return m_subcmds.exec(subcmd, args); diff --git a/src/core/macho/macho.cpp b/src/core/macho/macho.cpp index 4b349fd..818d10c 100644 --- a/src/core/macho/macho.cpp +++ b/src/core/macho/macho.cpp @@ -349,6 +349,7 @@ kern_return_t catch_mach_exception_raise_state_identity( #pragma unused(codeCnt) #pragma unused(newState) #pragma unused(newStateCnt) +#pragma unused(flavour) auto& target = Context::getTarget(); target->setTargetState(TargetState::STOPPED); diff --git a/src/core/platform.hpp b/src/core/platform.hpp index 59b9e3f..1ba3e75 100644 --- a/src/core/platform.hpp +++ b/src/core/platform.hpp @@ -3,7 +3,6 @@ #include #include -#include #include #include From 5a52084af58b58dc0e525f41a70fef6f49e601ff Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 23 Mar 2026 03:18:41 +0000 Subject: [PATCH 09/10] fix(cmd): properly handle hex literals --- src/cmd/scanner.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cmd/scanner.cpp b/src/cmd/scanner.cpp index 74393d2..c03beed 100644 --- a/src/cmd/scanner.cpp +++ b/src/cmd/scanner.cpp @@ -112,6 +112,18 @@ void Scanner::string() { } void Scanner::number() { + // Check for hex prefix + if (peek() == 'x' || peek() == 'X') { + advance(); + while (isxdigit(peek()) != 0) { + advance(); + } + const std::string text = m_source.substr(m_start, m_current - m_start); + auto value = static_cast(std::stoull(text, nullptr, 16)); + addToken(TokenType::NUMBER, value); + return; + } + while (isdigit(peek()) != 0) { advance(); } From a427aaba6af87e2c2e27f45c0c8d5ca360eba15d Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 23 Mar 2026 03:37:45 +0000 Subject: [PATCH 10/10] docs: update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc954d6..ae87892 100644 --- a/README.md +++ b/README.md @@ -69,10 +69,11 @@ This creates a tmux session with editor, compiler, and miscellaneous panes for e ### Core Debugging Engine - **Target**: Process control, breakpoint management, and binary inspection +- **Register Modification**: View and write register contents - **Macho**: Mach-O parser supporting 64-bit architectures and byte swapping -- **Exception Ports**: Mach-based exception handling for traps and signals - **ASLR**: Automatic slide detection for address resolution + ## Project Status This is an early-stage project under active development. The core expression evaluation system is functional, with binary analysis and other features in development.