diff --git a/.clang-tidy b/.clang-tidy index d7f8eb9..d21c33a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -34,7 +34,8 @@ Checks: > -readability-else-after-return, -readability-braces-around-statements, -google-readability-braces-around-statements, - -misc-include-cleaner + -misc-include-cleaner, + -cppcoreguidelines-non-private-member-variables-in-classes WarningsAsErrors: '' @@ -86,7 +87,7 @@ CheckOptions: value: true # Exclude third-party code and build artifacts -HeaderFilterRegex: '^.*/(src|test)/.*\.(h|hpp)$' +HeaderFilterRegex: '^.*/(src|test)/(?!.*mach_exc).*\.(h|hpp)$' # Suppress warnings from system headers SystemHeaders: false diff --git a/CMakeLists.txt b/CMakeLists.txt index e37928e..29cc91d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,44 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# OPTIONAL: Use ccache if present +find_program(CCACHE_PROGRAM ccache) +if(CCACHE_PROGRAM) + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") + set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") + message(STATUS "ccache found: ${CCACHE_PROGRAM}") +else() + message(WARNING "ccache not found - builds will be slower. Install with: brew install ccache") +endif() + +# Only brew LLVM is supported on OSX at the moment +if(CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin") + # Find brew + find_program(BREW_PROGRAM brew) + if(NOT BREW_PROGRAM) + message(FATAL_ERROR "Homebrew not found! Install from https://brew.sh") + endif() + + # Find LLVM + execute_process(COMMAND brew --prefix llvm OUTPUT_VARIABLE LLVM_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE BREW_LLVM_RESULT ERROR_QUIET) + if(NOT BREW_LLVM_RESULT EQUAL 0) + message(FATAL_ERROR "Brew LLVM not found! Install with: brew install llvm") + endif() + + # Use brew LLVM clang + set(CMAKE_C_COMPILER "${LLVM_PREFIX}/bin/clang") + set(CMAKE_CXX_COMPILER "${LLVM_PREFIX}/bin/clang++") + + # Link with brew LLVM libc++ + execute_process(COMMAND brew --prefix llvm OUTPUT_VARIABLE LLVM_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE) + add_compile_options(-stdlib=libc++) + add_link_options(-stdlib=libc++ -L${LLVM_PREFIX}/lib/c++ -Wl,-rpath,${LLVM_PREFIX}/lib/c++) + + # Set OSX SDK + execute_process(COMMAND xcrun --sdk macosx --show-sdk-path OUTPUT_VARIABLE OSX_SYSROOT OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CMAKE_OSX_SYSROOT "${OSX_SYSROOT}") +endif() + project(caesar VERSION 0.1.0) # Optional: Enable code coverage @@ -33,9 +71,13 @@ if(ENABLE_COVERAGE) message(STATUS "Code coverage enabled") endif() -add_executable(caesar src/main.cpp) +add_executable(caesar src/main.cpp src/error.cpp src/error.hpp src/formatter.hpp) target_link_libraries(caesar PRIVATE caesar_cmd caesar_core) +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(caesar PRIVATE caesar_macho) +endif() + # Optional: Build tests if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR OR CAESAR_BUILD_TESTS) option(CAESAR_BUILD_TESTS "Build tests" ON) @@ -52,10 +94,16 @@ if(CAESAR_BUILD_TESTS) test/cmd/test_object.cpp test/cmd/test_parser.cpp test/cmd/test_scanner.cpp + test/cmd/test_stdlib.cpp + src/error.cpp ) target_link_libraries(caesar_test PRIVATE caesar_cmd caesar_core Catch2::Catch2WithMain) + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(caesar_test PRIVATE caesar_macho) + endif() + # Apply coverage flags to test executable if coverage is enabled if(ENABLE_COVERAGE) target_compile_options(caesar_test PUBLIC ${COVERAGE_COMPILE_FLAGS}) diff --git a/README.md b/README.md index 8080015..dc954d6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A modern C++20 cross-platform debugger. The debugger is built with a modular architecture: -- **Core Engine** (`src/core/`): Core debugger engine functionality +- **Core Engine** (`src/core/`): Core debugger engine functionality. Split into separate libraries per platform - **Command System** (`src/cmd/`): Expression parser, interpreter, and AST components ## Building @@ -28,7 +28,7 @@ The debugger is built with a modular architecture: ```bash mkdir build cd build -cmake .. +cmake .. --preset (debug|release) make ``` @@ -42,6 +42,13 @@ Run the debugger without arguments to enter interactive mode: This starts a REPL where you can enter expressions and commands. +Alternatively, run with a file to automatically set a target upon startup: + +```bash +./caesar (file) +``` + + ### Development Environment The project includes a development setup script: @@ -50,7 +57,7 @@ The project includes a development setup script: ./start.sh ``` -This creates a tmux session with editor, compiler, and miscellaneous panes for efficient development. This is my preffered environment, feel free to use or adapt it to your own needs. +This creates a tmux session with editor, compiler, and miscellaneous panes for efficient development. This is my preferred environment, feel free to use or adapt it to your own needs. ## Components @@ -58,7 +65,13 @@ This creates a tmux session with editor, compiler, and miscellaneous panes for e - **Scanner**: Tokenizes input expressions - **Parser**: Builds abstract syntax trees from tokens - **Interpreter**: Evaluates expressions with support for literals, grouping, unary and binary operations -- **AST Printer**: Debug utility for visualizing abstract syntax trees +- **Built-in Functions**: Native commands for debugging (breakpoint, run, resume, etc.) + +### Core Debugging Engine +- **Target**: Process control, breakpoint management, and binary inspection +- **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 diff --git a/src/Entitlements.plist b/src/Entitlements.plist new file mode 100644 index 0000000..3d60e8b --- /dev/null +++ b/src/Entitlements.plist @@ -0,0 +1,8 @@ + + + + + com.apple.security.cs.debugger + + + diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt index 6a9093e..0f58517 100644 --- a/src/cmd/CMakeLists.txt +++ b/src/cmd/CMakeLists.txt @@ -1,6 +1,4 @@ add_library(caesar_cmd STATIC - error.cpp - error.hpp scanner.cpp scanner.hpp token.hpp @@ -17,9 +15,8 @@ add_library(caesar_cmd STATIC callable.hpp stdlib.hpp object.hpp - formatter.hpp ) target_include_directories(caesar_cmd PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. ) diff --git a/src/cmd/callable.hpp b/src/cmd/callable.hpp index 081b40d..e79d25f 100644 --- a/src/cmd/callable.hpp +++ b/src/cmd/callable.hpp @@ -5,11 +5,16 @@ #include #include +#include "core/context.hpp" +#include "core/target.hpp" #include "object.hpp" class Interpreter; class Callable { + protected: + std::unique_ptr& m_target = Context::getTarget(); + public: virtual ~Callable() = default; virtual Object call(std::vector args) = 0; diff --git a/src/cmd/environment.cpp b/src/cmd/environment.cpp index 6f4915f..4d0e304 100644 --- a/src/cmd/environment.cpp +++ b/src/cmd/environment.cpp @@ -11,9 +11,10 @@ void Environment::define(const std::string& name, Object value) { if (m_values.contains(name)) if (auto* ptr = std::get_if>(&m_values[name])) { - Error::error(TokenType::IDENTIFIER, - std::format("Cannot assign to {} as it is a function", name), - ErrorType::RUNTIME_ERROR); + CmdError::error( + TokenType::IDENTIFIER, + std::format("Cannot assign to {} as it is a function", name), + CmdErrorType::RUNTIME_ERROR); return; } @@ -22,9 +23,9 @@ void Environment::define(const std::string& name, Object value) { Object Environment::get(const Token& name) { if (m_values.contains(name.m_lexeme)) return m_values[name.m_lexeme]; - Error::error(name.m_type, - std::format("Undefined variable '{}'.", name.m_lexeme), - ErrorType::RUNTIME_ERROR); + CmdError::error(name.m_type, + std::format("Undefined variable '{}'.", name.m_lexeme), + CmdErrorType::RUNTIME_ERROR); return std::monostate{}; } @@ -33,22 +34,24 @@ void Environment::assign(const Token& name, Object value) { if (auto* val = std::get_if>(&m_values[name.m_lexeme]); val != nullptr) { - Error::error( + CmdError::error( name.m_type, std::format("Cannot reassign {} as it is a function", name.m_lexeme), - ErrorType::RUNTIME_ERROR); + CmdErrorType::RUNTIME_ERROR); return; } m_values[name.m_lexeme] = std::move(value); return; } - Error::error(name.m_type, - std::format("Undefined variable '{}'", name.m_lexeme), - ErrorType::RUNTIME_ERROR); + CmdError::error(name.m_type, + std::format("Undefined variable '{}'", name.m_lexeme), + CmdErrorType::RUNTIME_ERROR); } Environment& Environment::getInstance() { static Environment instance; return instance; } + +std::map Environment::getAll() { return m_values; } diff --git a/src/cmd/environment.hpp b/src/cmd/environment.hpp index e27fd29..5cd8d1c 100644 --- a/src/cmd/environment.hpp +++ b/src/cmd/environment.hpp @@ -15,11 +15,15 @@ class Environment { Environment() : m_values({}) { this->define("print", std::make_shared(PrintFn())); this->define("len", std::make_shared(LenFn())); + this->define("breakpoint", std::make_shared(BreakpointFn())); + this->define("run", std::make_shared(RunFn())); + this->define("resume", std::make_shared(ContinueFn())); } public: void define(const std::string& name, Object value); Object get(const Token& name); + std::map getAll(); void assign(const Token& name, Object value); Environment(Environment& other) = delete; diff --git a/src/cmd/error.cpp b/src/cmd/error.cpp deleted file mode 100644 index 3c070d5..0000000 --- a/src/cmd/error.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "error.hpp" - -#include - -#include "formatter.hpp" - -void Error::_error(TokenType where, const std::string& msg, ErrorType type) { - std::cerr << std::format("{} error at {} : {}\n", type, where, msg); - had_error = true; -} - -void Error::error(TokenType where, const std::string& msg, ErrorType type) { - Error& e = Error::getInstance(); - e._error(where, msg, type); -} - -Error::Error() = default; - -Error& Error::getInstance() { - static Error instance; - return instance; -} diff --git a/src/cmd/error.hpp b/src/cmd/error.hpp deleted file mode 100644 index 7b79871..0000000 --- a/src/cmd/error.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef ERROR_H -#define ERROR_H -#include -#include -#include -#include - -enum class ErrorType : std::uint8_t { PARSE_ERROR, SCAN_ERROR, RUNTIME_ERROR }; -enum class TokenType : std::uint8_t; - -// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) -class Error { - private: - Error(); - // NOLINTNEXTLINE(readability-identifier-naming) - void _error(TokenType where, const std::string& msg, ErrorType type); - - public: - Error(Error& other) = delete; - Error& operator=(const Error& other) = delete; - Error(Error&& other) = delete; - Error& operator=(Error&& other) = delete; - - bool had_error{false}; - static void error(TokenType where, const std::string& msg, ErrorType type); - static Error& getInstance(); -}; - -template <> -struct std::formatter : std::formatter { - constexpr auto format(ErrorType type, auto& ctx) const { - const auto str = [] { - std::map res; -// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define INSERT_ELEM(p) res.emplace(p, #p); - INSERT_ELEM(ErrorType::PARSE_ERROR); - INSERT_ELEM(ErrorType::SCAN_ERROR); - INSERT_ELEM(ErrorType::RUNTIME_ERROR); -#undef INSERT_ELEM - - return res; - }; - - return std::formatter::format(str()[type], ctx); - }; -}; - -#endif diff --git a/src/cmd/interpreter.cpp b/src/cmd/interpreter.cpp index fc95101..f5313b7 100644 --- a/src/cmd/interpreter.cpp +++ b/src/cmd/interpreter.cpp @@ -32,17 +32,18 @@ bool Interpreter::isEqual(const Object& lhs, const Object& rhs) { return lhs == rhs; } -void Interpreter::checkNumberOperand(const Token& op, const Object& operand) { - auto visitor = [&op](const auto& value) -> void { +bool Interpreter::checkNumberOperand(const Token& op, const Object& operand) { + auto visitor = [&op](const auto& value) -> bool { using T = std::decay_t; - if constexpr (std::is_same_v) return; + if constexpr (std::is_same_v) return true; - Error::error(op.m_type, "Operand must be a number", - ErrorType::RUNTIME_ERROR); + CmdError::error(op.m_type, "Operand must be a number", + CmdErrorType::RUNTIME_ERROR); + return false; }; - std::visit(visitor, operand); + return std::visit(visitor, operand); } std::string Interpreter::stringify(const Object& object) { @@ -70,7 +71,7 @@ Object Interpreter::visitUnaryExpr(const Unary& expr) { case TokenType::BANG: return !isTruthy(right); case TokenType::MINUS: - checkNumberOperand(expr.m_op, right); + if (!checkNumberOperand(expr.m_op, right)) return std::monostate{}; return -(std::get(right)); } #pragma clang diagnostic pop @@ -121,22 +122,36 @@ Object Interpreter::visitCallStmnt(const CallStmnt& stmnt) { argList.reserve(stmnt.m_args.size()); for (const auto& x : stmnt.m_args) { + if (auto* id = dynamic_cast(x.get()); + id != nullptr && !(m_env.getAll().contains(id->m_name.m_lexeme))) { + argList.emplace_back(id->m_name.m_lexeme); + continue; + } + argList.emplace_back(std::move(evaluate(x))); } Object fnObj = m_env.get(stmnt.m_fn); auto* fn = std::get_if>(&fnObj); - if (fn != nullptr) { - if (argList.size() != fn->get()->arity()) { - Error::error( - stmnt.m_fn.m_type, - std::format("Function requires {} arguments but {} were provided", - fn->get()->arity(), argList.size()), - ErrorType::RUNTIME_ERROR); - return std::monostate{}; - } + if (fn == nullptr) { + // Not a callable - if no args, just return the value + if (argList.empty()) return fnObj; + CmdError::error(stmnt.m_fn.m_type, + std::format("'{}' is not callable", stmnt.m_fn.m_lexeme), + CmdErrorType::RUNTIME_ERROR); + return std::monostate{}; } + + if (argList.size() < fn->get()->arity()) { + CmdError::error(stmnt.m_fn.m_type, + std::format("Function requires at least {} arguments but " + "only {} were provided", + fn->get()->arity(), argList.size()), + CmdErrorType::RUNTIME_ERROR); + return std::monostate{}; + } + return fn->get()->call(std::move(argList)); } diff --git a/src/cmd/interpreter.hpp b/src/cmd/interpreter.hpp index dfd35ba..d6ae621 100644 --- a/src/cmd/interpreter.hpp +++ b/src/cmd/interpreter.hpp @@ -10,12 +10,12 @@ class Interpreter : public IExprVisitor, IStmntVisitor { private: Environment& m_env = Environment::getInstance(); - Error& err = Error::getInstance(); + CmdError& err = CmdError::getInstance(); Object evaluate(const std::unique_ptr& expr); static bool isTruthy(const Object& object); static bool isEqual(const Object& lhs, const Object& rhs); - static void checkNumberOperand(const Token& op, const Object& operand); + static bool checkNumberOperand(const Token& op, const Object& operand); Object execute(const std::unique_ptr& stmnt); public: diff --git a/src/cmd/object.hpp b/src/cmd/object.hpp index 2e43d63..87397e0 100644 --- a/src/cmd/object.hpp +++ b/src/cmd/object.hpp @@ -31,15 +31,16 @@ inline Object binaryOperation(const Object& lhs, const Object& rhs, Op op, if constexpr (std::is_same_v>) { return left + right; } else { - Error::error(TokenType::STRING, - std::format("Cannot apply operator {} to strings", opName), - ErrorType::RUNTIME_ERROR); + CmdError::error( + TokenType::STRING, + std::format("Cannot apply operator {} to strings", opName), + CmdErrorType::RUNTIME_ERROR); return std::monostate{}; } } else { - Error::error(TokenType::STRING, - std::format("Unsupported operand types for {}", opName), - ErrorType::RUNTIME_ERROR); + CmdError::error(TokenType::STRING, + std::format("Unsupported operand types for {}", opName), + CmdErrorType::RUNTIME_ERROR); return std::monostate{}; } }; @@ -57,11 +58,11 @@ inline bool comparisonOperation(const Object& lhs, const Object& rhs, Op op, if constexpr (std::is_arithmetic_v && std::is_arithmetic_v) { return op(static_cast(left), static_cast(right)); } else { - Error::error( + CmdError::error( TokenType::STRING, std::format("Cannot apply operator {} to non-arithmetic types", opName), - ErrorType::RUNTIME_ERROR); + CmdErrorType::RUNTIME_ERROR); return false; } }; diff --git a/src/cmd/parser.cpp b/src/cmd/parser.cpp index 22c090a..c2671f5 100644 --- a/src/cmd/parser.cpp +++ b/src/cmd/parser.cpp @@ -3,6 +3,7 @@ #include #include +#include "cmd/token_type.hpp" #include "error.hpp" #include "expr.hpp" #include "stmnt.hpp" @@ -71,8 +72,8 @@ std::unique_ptr Parser::unary() { } std::unique_ptr Parser::primary() { - if (match(TokenType::FALSE)) return std::make_unique(false); - if (match(TokenType::TRUE)) return std::make_unique(true); + if (match(TokenType::BOOL_FALSE)) return std::make_unique(false); + if (match(TokenType::BOOL_TRUE)) return std::make_unique(true); if (match(TokenType::NIL)) return std::make_unique(std::monostate{}); if (match(TokenType::NUMBER, TokenType::STRING)) { @@ -89,7 +90,8 @@ std::unique_ptr Parser::primary() { return std::make_unique(std::move(e)); } - Error::error(peek().m_type, "Expected expression.", ErrorType::PARSE_ERROR); + CmdError::error(peek().m_type, "Expected expression.", + CmdErrorType::PARSE_ERROR); return nullptr; } @@ -109,7 +111,7 @@ std::unique_ptr Parser::comparison() { Token Parser::consume(TokenType type, const std::string& msg) { if (check(type)) return advance(); - Error::error(peek().m_type, msg, ErrorType::PARSE_ERROR); + CmdError::error(peek().m_type, msg, CmdErrorType::PARSE_ERROR); return peek(); // Return current token on error } @@ -127,8 +129,8 @@ std::unique_ptr Parser::statement() { // Function call if (check(TokenType::IDENTIFIER) || check(TokenType::NUMBER) || check(TokenType::STRING) || check(TokenType::LEFT_PAREN) || - check(TokenType::TRUE) || check(TokenType::FALSE) || - check(TokenType::NIL)) { + check(TokenType::BOOL_TRUE) || check(TokenType::BOOL_FALSE) || + check(TokenType::NIL) || check(TokenType::END)) { m_current = saved; return funStmnt(); } @@ -177,8 +179,8 @@ std::unique_ptr Parser::assignment() { return std::make_unique(name, std::move(value)); } - Error::error(equals.m_type, "Invalid assignment target.", - ErrorType::PARSE_ERROR); + CmdError::error(equals.m_type, "Invalid assignment target.", + CmdErrorType::PARSE_ERROR); } return expr; diff --git a/src/cmd/scanner.cpp b/src/cmd/scanner.cpp index b9edbdf..74393d2 100644 --- a/src/cmd/scanner.cpp +++ b/src/cmd/scanner.cpp @@ -68,8 +68,8 @@ void Scanner::scanToken() { } else if (isalpha(c) != 0) { identifier(); } else { - Error::error(TokenType::END, "Unexpected character", - ErrorType::SCAN_ERROR); + CmdError::error(TokenType::END, "Unexpected character", + CmdErrorType::SCAN_ERROR); } break; } @@ -100,8 +100,8 @@ void Scanner::string() { } if (isAtEnd()) { - Error::error(TokenType::STRING, "Unterminated string", - ErrorType::SCAN_ERROR); + CmdError::error(TokenType::STRING, "Unterminated string", + CmdErrorType::SCAN_ERROR); return; } diff --git a/src/cmd/scanner.hpp b/src/cmd/scanner.hpp index d85d580..e91b653 100644 --- a/src/cmd/scanner.hpp +++ b/src/cmd/scanner.hpp @@ -11,12 +11,13 @@ class Scanner { private: const std::string m_source; std::vector m_tokens; + std::map m_keywords = { + {"false", TokenType::BOOL_FALSE}, + {"nil", TokenType::NIL}, + {"true", TokenType::BOOL_TRUE}, + {"var", TokenType::VAR}}; int m_start = 0; int m_current = 0; - std::map m_keywords = {{"false", TokenType::FALSE}, - {"nil", TokenType::NIL}, - {"true", TokenType::TRUE}, - {"var", TokenType::VAR}}; [[nodiscard]] bool isAtEnd() const; void scanToken(); diff --git a/src/cmd/stdlib.hpp b/src/cmd/stdlib.hpp index 96e4779..5a66565 100644 --- a/src/cmd/stdlib.hpp +++ b/src/cmd/stdlib.hpp @@ -1,10 +1,21 @@ #ifndef STDLIB_H #define STDLIB_H +#include + +#include +#include +#include +#include #include +#include "callable.hpp" +#include "cmd/object.hpp" +#include "core/target.hpp" #include "error.hpp" -#include "formatter.hpp" +#include "expected.hpp" +#include "subcommand.hpp" +#include "token_type.hpp" class LenFn : public Callable { public: @@ -15,8 +26,9 @@ class LenFn : public Callable { auto* val = std::get_if(args.data()); if (val == nullptr) { - Error::error(TokenType::IDENTIFIER, "len can only be called on a string", - ErrorType::RUNTIME_ERROR); + CmdError::error(TokenType::IDENTIFIER, + "len can only be called on a string", + CmdErrorType::RUNTIME_ERROR); return std::monostate{}; } @@ -31,8 +43,198 @@ class PrintFn : public Callable { return ""; } + Object call(std::vector args) override { return args[0]; } +}; + +class BreakpointFn : public Callable { + private: + using sv = std::string_view; + + static Object rmOrToggleBreakpoint(const std::vector& args, + bool toggle = false) { + Expected addrRes = strToAddr(args[0]); + if (!addrRes) return addrRes.error(); + u64 addr = *addrRes; + + auto& target = Context::getTarget(); + auto& bps = target->getRegisteredBreakpoints(); + + if (!bps.contains(addr)) return std::format("No breakpoint at {}", addr); + + if (toggle) { + i32 res = target->disableBreakpoint(addr, true); + if (res != 0) return "Error toggling breakpoint"; + return std::format("Toggled breakpoint at {}, now {}", addr, + bps[addr].enabled); + } + + i32 res = target->disableBreakpoint(addr, false); + if (res != 0) return "Error disabling breakpoint"; + return std::format("Removed breakpoint at {}", addr); + } + + FnPtr list = [](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", toHex(k), + breakpoints[k].enabled); + } + + retStr.pop_back(); + return retStr; + }; + + FnPtr set = [](const std::vector& args) -> Object { + Expected addrRes = strToAddr(args[0]); + if (!addrRes) return addrRes.error(); + u64 addr = *addrRes; + + i32 bpRes = Context::getTarget()->setBreakpoint(addr); + if (bpRes != 0) + return "Error setting breakpoint!"; + else + return std::format("Breakpoint set at: {}", toHex(addr)); + }; + + FnPtr remove = [](const std::vector& args) -> Object { + return BreakpointFn::rmOrToggleBreakpoint(args, false); + }; + + FnPtr toggle = [](const std::vector& args) -> Object { + return BreakpointFn::rmOrToggleBreakpoint(args, true); + }; + + SubcommandHandler m_subcmds{{{sv("list"), list}, + {sv("set"), set}, + {sv("remove"), remove}, + {sv("toggle"), toggle}}, + "breakpoint"}; + + static std::vector convertToStr(const std::vector& vec) { + std::vector res{}; + res.reserve(vec.size()); + + for (const auto& x : vec) { + const auto* const str = std::get_if(&x); + if (str == nullptr) { + CmdError::error(TokenType::IDENTIFIER, + "Please provide all arguments as strings", + CmdErrorType::RUNTIME_ERROR); + return {}; // Return empty on error, not partial + } + res.emplace_back(*str); + } + + return res; + } + + public: + [[nodiscard]] int arity() const override { return 1; } + [[nodiscard]] std::string str() const override { + return ""; + } + + Object call(std::vector args) override { + if (m_target == nullptr) { + CoreError::error("Target is not running!"); + return std::monostate{}; + } + std::vector convertedArgs = BreakpointFn::convertToStr(args); + if (convertedArgs.size() < 1) return std::monostate{}; + const std::string subcmd = convertedArgs.front(); + convertedArgs.erase(convertedArgs.begin()); + return m_subcmds.exec(subcmd, convertedArgs); + } +}; + +class RunFn : public Callable { + private: + static CStringArray concatElems(const std::vector& v) { + auto ensureStr = [](const auto& obj) -> std::string { + using T = std::decay_t; + + if constexpr (std::is_same_v) + return std::to_string(obj); + else if constexpr (std::is_same_v) + return obj; + else if constexpr (std::is_same_v) + return obj ? "true" : "false"; + else + return ""; + }; + + CStringArray res{}; + res.storage.reserve(v.size()); + res.ptrs.reserve(v.size() + 1); + + for (const auto& x : v) { + std::string temp = std::visit(ensureStr, x); + if (temp.empty()) { + CmdError::error( + TokenType::IDENTIFIER, + "Only strings, bools and numbers are supported as arguments", + CmdErrorType::RUNTIME_ERROR); + break; + } + + res.storage.emplace_back(temp.begin(), temp.end()); + res.storage.back().push_back('\0'); + res.ptrs.push_back(res.storage.back().data()); + } + res.ptrs.push_back(nullptr); + return res; + }; + + public: + // TODO: Allow for inline target args (just one string containing all args) + [[nodiscard]] int arity() const override { return 0; } + [[nodiscard]] std::string str() const override { return ""; } + Object call(std::vector args) override { - return std::format("{}", args[0]); + CStringArray argList = RunFn::concatElems(args); + if (m_target == nullptr) { + std::cerr << "Error: Target is not set!\n"; + CmdError::getInstance().m_had_error = true; + return std::monostate{}; + } + + i32 pid = m_target->launch(argList); + if (pid >= 0) + std::cout << std::format("Target started with pid {}\n", pid); + else + return "Unable to start target (are you running with sudo?)\n"; + + i32 res = m_target->attach(); + if (res != 0) return "Could not attach to target!\n"; + m_target->setTargetState(TargetState::RUNNING); + // TODO: Reset this when target exits + m_target->m_started = true; + m_target->startEventLoop(); + + return std::monostate{}; + } +}; + +class ContinueFn : public Callable { + public: + [[nodiscard]] int arity() const override { return 0; } + [[nodiscard]] std::string str() const override { + return ""; + } + Object call(std::vector args) override { +#pragma unused(args) + if (m_target->getTargetState() != TargetState::STOPPED) { + std::cout << "Target still seems to think its running?\n"; + return std::monostate{}; + } + m_target->resume(ResumeType::RESUME); + m_target->startEventLoop(); + return std::monostate{}; } }; diff --git a/src/cmd/subcommand.hpp b/src/cmd/subcommand.hpp new file mode 100644 index 0000000..16878a7 --- /dev/null +++ b/src/cmd/subcommand.hpp @@ -0,0 +1,38 @@ +#ifndef CAESAR_SUBCOMMAND_H +#define CAESAR_SUBCOMMAND_H + +#include +#include +#include +#include +#include +#include + +#include "error.hpp" +#include "object.hpp" +#include "token_type.hpp" +#include "typedefs.hpp" + +class SubcommandHandler { + private: + std::string_view m_callee; + std::unordered_map m_subcommands; + + public: + SubcommandHandler( + std::initializer_list> fns, + const std::string_view callee) + : m_subcommands(fns), m_callee(callee) {}; + + 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, + std::format("Subcommand {} is not valid for {} command", + subcmd, m_callee), + CmdErrorType::RUNTIME_ERROR); + return std::monostate{}; + } +}; + +#endif diff --git a/src/cmd/token_type.hpp b/src/cmd/token_type.hpp index be1cad2..e376187 100644 --- a/src/cmd/token_type.hpp +++ b/src/cmd/token_type.hpp @@ -22,8 +22,8 @@ enum class TokenType : std::uint8_t { STRING, NUMBER, NIL, - TRUE, - FALSE, + BOOL_TRUE, + BOOL_FALSE, VAR, END, }; diff --git a/src/cmd/util.cpp b/src/cmd/util.cpp index 27f4aae..1616c5a 100644 --- a/src/cmd/util.cpp +++ b/src/cmd/util.cpp @@ -1,5 +1,6 @@ #include "util.hpp" +#include #include bool detail::isop(const char c) { diff --git a/src/cmd/util.hpp b/src/cmd/util.hpp index eb84093..57c9e1d 100644 --- a/src/cmd/util.hpp +++ b/src/cmd/util.hpp @@ -1,7 +1,9 @@ #ifndef UTIL_H #define UTIL_H -#include "token.hpp" +#include + +#include "token_type.hpp" namespace detail { bool isop(char c); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 005d90f..be57169 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -3,18 +3,18 @@ add_library(caesar_core STATIC dwarf/context.cpp dwarf/context.hpp dwarf/alloc.hpp + context.hpp + target.hpp + target.cpp ) -if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - target_sources(caesar_core PRIVATE - macho.hpp - macho.cpp - ) -endif() - target_include_directories(caesar_core PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. ) find_package(libdwarf CONFIG REQUIRED) target_link_libraries(caesar_core PUBLIC libdwarf::dwarf) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + add_subdirectory(macho) +endif() diff --git a/src/core/context.hpp b/src/core/context.hpp new file mode 100644 index 0000000..2205af6 --- /dev/null +++ b/src/core/context.hpp @@ -0,0 +1,32 @@ +#ifndef CAESAR_CONTEXT_H +#define CAESAR_CONTEXT_H + +#include +#include + +#include "target.hpp" + +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) +class Context { + private: + Context() = default; + inline static std::unique_ptr mTarget; + + public: + Context(const Context&) = delete; + Context(Context&&) = delete; + Context& operator=(const Context&) = delete; + Context& operator=(Context&&) = delete; + + static Context& getInstance() { + static Context instance; + return instance; + } + + static std::unique_ptr& getTarget() { return mTarget; } + static void setTarget(std::unique_ptr ptr) { + mTarget = std::move(ptr); + } +}; + +#endif diff --git a/src/core/macho.cpp b/src/core/macho.cpp deleted file mode 100644 index da07b56..0000000 --- a/src/core/macho.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "macho.hpp" - -#include -#include -#include -#include - -#include -#include - -// Adapted from https://lowlevelbits.org/parsing-mach-o-files/ - -void Macho::dump() { dump_mach_header(0); } - -void Macho::read_magic(int offset) { - uint32_t magic; - this->file.seekg(offset, std::ios::beg); - this->file.read(std::bit_cast(&magic), sizeof(uint32_t)); - this->magic = magic; -} - -void Macho::is_magic_64() { - if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) this->is_64 = true; -} - -void Macho::maybe_swap_bytes() { - if (magic == MH_CIGAM || magic == MH_CIGAM_64) this->is_swap = true; -} - -void Macho::dump_mach_header(int offset) { - uint32_t ncmds; - int load_cmds_offset = offset; - - if (this->is_64) { - constexpr size_t header_size = sizeof(struct mach_header_64); - auto header = this->load_bytes_and_maybe_swap(offset); - ncmds = header->ncmds; - load_cmds_offset += header_size; - std::cout << this->cpu_type_name(header->cputype) << std::endl; - } else { - int header_size = sizeof(struct mach_header); - auto header = this->load_bytes_and_maybe_swap(offset); - } - - dump_segment_commands(load_cmds_offset, ncmds); -} - -void Macho::dump_segment_commands(int offset, uint32_t ncmds) { - int actual_offset = offset; - for (int i = 0; i < ncmds; i++) { - auto cmd = this->load_bytes_and_maybe_swap(actual_offset); - if (cmd->cmd == LC_SEGMENT_64) { - auto segment = - this->load_bytes_and_maybe_swap(actual_offset); - std::cout << std::format( - "segname: {:<25} offset: 0x{:<12x} vmaddr: 0x{:<18x} " - "vmsize: 0x{:x}", - segment->segname, segment->fileoff, segment->vmaddr, - segment->vmsize) - << std::endl; - dump_sections(actual_offset + sizeof(segment_command_64), - actual_offset + cmd->cmdsize); - } - actual_offset += cmd->cmdsize; - } -} - -std::string Macho::cpu_type_name(cpu_type_t cpu_type) { - for (const auto x : cpu_type_names) { - if (cpu_type == x.cpu_type) return x.cpu_name; - } - return "unknown"; -} - -void Macho::dump_sections(int offset, int end) { - int actual_offset = offset; - while (actual_offset != end) { - auto section = this->load_bytes_and_maybe_swap(actual_offset); - std::cout << std::format("Section: {}; Address: 0x{:x}", section->sectname, - section->addr) - << std::endl; - actual_offset += sizeof(section_64); - } -} - -Macho::Macho(std::ifstream& f) : file(f) { - read_magic(0); - is_magic_64(); - maybe_swap_bytes(); -} diff --git a/src/core/macho.hpp b/src/core/macho.hpp deleted file mode 100644 index b7d7d92..0000000 --- a/src/core/macho.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef CAESAR_MACHO_HPP -#define CAESAR_MACHO_HPP - -#include -#include -#include - -#include -#include - -#include "util.hpp" - -struct _cpu_type_names { - cpu_type_t cpu_type; - std::string cpu_name; -}; - -class Macho { - public: - explicit Macho(std::ifstream& f); - void dump(); - - private: - uint32_t magic; - bool is_64; - bool is_swap; - std::ifstream& file; - - void read_magic(int offset); - void is_magic_64(); - void maybe_swap_bytes(); - - template - std::unique_ptr load_bytes_and_maybe_swap(int offset) { - auto buf = std::make_unique(); - this->file.seekg(offset, std::ios::beg); - this->file.read(std::bit_cast(buf.get()), sizeof(T)); - if (this->is_swap) SwapDescriptor::swap(buf.get()); - return buf; - } - - void dump_mach_header(int offset); - void dump_segment_commands(int offset, uint32_t ncmds); - static constexpr _cpu_type_names cpu_type_names[] = { - {CPU_TYPE_I386, "i386"}, - {CPU_TYPE_X86_64, "x86_64"}, - {CPU_TYPE_ARM, "arm"}, - {CPU_TYPE_ARM64, "arm64"}}; - std::string cpu_type_name(cpu_type_t cpu_type); - void dump_sections(int offset, int end); -}; - -#endif // CAESAR_MACHO_HPP diff --git a/src/core/macho/CMakeLists.txt b/src/core/macho/CMakeLists.txt new file mode 100644 index 0000000..c9790bd --- /dev/null +++ b/src/core/macho/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(caesar_macho STATIC + macho.cpp + macho.hpp + ports.hpp + mach_exc.h + mach_excServer.h +) + +target_include_directories(caesar_macho PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/../.. +) + +target_include_directories(caesar_macho PRIVATE + $ +) diff --git a/src/core/macho/mach_exc.h b/src/core/macho/mach_exc.h new file mode 100644 index 0000000..a687789 --- /dev/null +++ b/src/core/macho/mach_exc.h @@ -0,0 +1,296 @@ +#ifndef _mach_exc_user_ +#define _mach_exc_user_ + +/* Module mach_exc */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* BEGIN VOUCHER CODE */ + +#ifndef KERNEL +#if defined(__has_include) +#if __has_include() +#ifndef USING_VOUCHERS +#define USING_VOUCHERS +#endif +#ifndef __VOUCHER_FORWARD_TYPE_DECLS__ +#define __VOUCHER_FORWARD_TYPE_DECLS__ +#ifdef __cplusplus +extern "C" { +#endif +#ifndef __VOUCHER_FOWARD_TYPE_DECLS_SINGLE_ATTR +#define __VOUCHER_FOWARD_TYPE_DECLS_SINGLE_ATTR __unsafe_indexable +#endif +extern boolean_t voucher_mach_msg_set(mach_msg_header_t* msg) + __attribute__((weak_import)); +#ifdef __cplusplus +} +#endif +#endif // __VOUCHER_FORWARD_TYPE_DECLS__ +#endif // __has_include() +#endif // __has_include +#endif // !KERNEL + +/* END VOUCHER CODE */ + +/* BEGIN MIG_STRNCPY_ZEROFILL CODE */ + +#if defined(__has_include) +#if __has_include() +#ifndef USING_MIG_STRNCPY_ZEROFILL +#define USING_MIG_STRNCPY_ZEROFILL +#endif +#ifndef __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ +#define __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ +#ifdef __cplusplus +extern "C" { +#endif +#ifndef __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS_CSTRING_ATTR +#define __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS_CSTRING_COUNTEDBY_ATTR(C) \ + __unsafe_indexable +#endif +extern int mig_strncpy_zerofill(char* dest, const char* src, int len) + __attribute__((weak_import)); +#ifdef __cplusplus +} +#endif +#endif /* __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ */ +#endif /* __has_include() */ +#endif /* __has_include */ + +/* END MIG_STRNCPY_ZEROFILL CODE */ + +#ifdef AUTOTEST +#ifndef FUNCTION_PTR_T +#define FUNCTION_PTR_T +typedef void (*function_ptr_t)(mach_port_t, char*, mach_msg_type_number_t); +typedef struct { + char* name; + function_ptr_t function; +} function_table_entry; +typedef function_table_entry* function_table_t; +#endif /* FUNCTION_PTR_T */ +#endif /* AUTOTEST */ + +#ifndef mach_exc_MSG_COUNT +#define mach_exc_MSG_COUNT 6 +#endif /* mach_exc_MSG_COUNT */ + +#include +#include +#include +#include + +#ifdef __BeforeMigUserHeader +__BeforeMigUserHeader +#endif /* __BeforeMigUserHeader */ + +#include + __BEGIN_DECLS + +/* Routine mach_exception_raise */ +#ifdef mig_external + mig_external +#else +extern +#endif /* mig_external */ + kern_return_t mach_exception_raise(mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt); + +/* Routine mach_exception_raise_state */ +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ + kern_return_t mach_exception_raise_state( + mach_port_t exception_port, exception_type_t exception, + const mach_exception_data_t code, mach_msg_type_number_t codeCnt, + int* flavor, const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, thread_state_t new_state, + mach_msg_type_number_t* new_stateCnt); + +/* Routine mach_exception_raise_state_identity */ +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ + kern_return_t mach_exception_raise_state_identity( + mach_port_t exception_port, mach_port_t thread, mach_port_t task, + exception_type_t exception, mach_exception_data_t code, + mach_msg_type_number_t codeCnt, int* flavor, thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, thread_state_t new_state, + mach_msg_type_number_t* new_stateCnt); + +__END_DECLS + +/********************** Caution **************************/ +/* The following data types should be used to calculate */ +/* maximum message sizes only. The actual message may be */ +/* smaller, and the position of the arguments within the */ +/* message layout may vary from what is presented here. */ +/* For example, if any of the arguments are variable- */ +/* sized, and less than the maximum is sent, the data */ +/* will be packed tight in the actual message to reduce */ +/* the presence of holes. */ +/********************** Caution **************************/ + +/* typedefs for all requests */ + +#ifndef __Request__mach_exc_subsystem__defined +#define __Request__mach_exc_subsystem__defined + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; +} __Request__mach_exception_raise_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + int flavor; + mach_msg_type_number_t old_stateCnt; + natural_t old_state[1296]; +} __Request__mach_exception_raise_state_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + int flavor; + mach_msg_type_number_t old_stateCnt; + natural_t old_state[1296]; +} __Request__mach_exception_raise_state_identity_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif +#endif /* !__Request__mach_exc_subsystem__defined */ + +/* union of all requests */ + +#ifndef __RequestUnion__mach_exc_subsystem__defined +#define __RequestUnion__mach_exc_subsystem__defined +union __RequestUnion__mach_exc_subsystem { + __Request__mach_exception_raise_t Request_mach_exception_raise; + __Request__mach_exception_raise_state_t Request_mach_exception_raise_state; + __Request__mach_exception_raise_state_identity_t + Request_mach_exception_raise_state_identity; +}; +#endif /* !__RequestUnion__mach_exc_subsystem__defined */ +/* typedefs for all replies */ + +#ifndef __Reply__mach_exc_subsystem__defined +#define __Reply__mach_exc_subsystem__defined + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + kern_return_t RetCode; +} __Reply__mach_exception_raise_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + kern_return_t RetCode; + int flavor; + mach_msg_type_number_t new_stateCnt; + natural_t new_state[1296]; +} __Reply__mach_exception_raise_state_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + kern_return_t RetCode; + int flavor; + mach_msg_type_number_t new_stateCnt; + natural_t new_state[1296]; +} __Reply__mach_exception_raise_state_identity_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif +#endif /* !__Reply__mach_exc_subsystem__defined */ + +/* union of all replies */ + +#ifndef __ReplyUnion__mach_exc_subsystem__defined +#define __ReplyUnion__mach_exc_subsystem__defined +union __ReplyUnion__mach_exc_subsystem { + __Reply__mach_exception_raise_t Reply_mach_exception_raise; + __Reply__mach_exception_raise_state_t Reply_mach_exception_raise_state; + __Reply__mach_exception_raise_state_identity_t + Reply_mach_exception_raise_state_identity; +}; +#endif /* !__RequestUnion__mach_exc_subsystem__defined */ + +#ifndef subsystem_to_name_map_mach_exc +#define subsystem_to_name_map_mach_exc \ + {"mach_exception_raise", 2405}, {"mach_exception_raise_state", 2406}, \ + {"mach_exception_raise_state_identity", 2407} +#endif + +#ifdef __AfterMigUserHeader +__AfterMigUserHeader +#endif /* __AfterMigUserHeader */ + +#endif /* _mach_exc_user_ */ diff --git a/src/core/macho/mach_excServer.h b/src/core/macho/mach_excServer.h new file mode 100644 index 0000000..8598dbc --- /dev/null +++ b/src/core/macho/mach_excServer.h @@ -0,0 +1,834 @@ +/* + * IDENTIFICATION: + * stub generated by bootstrap_cmds-138 + * OPTIONS: + */ + +/* Module mach_exc */ + +#define __MIG_check__Request__mach_exc_subsystem__ 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* BEGIN VOUCHER CODE */ + +#ifndef KERNEL +#if defined(__has_include) +#if __has_include() +#ifndef USING_VOUCHERS +#define USING_VOUCHERS +#endif +#ifndef __VOUCHER_FORWARD_TYPE_DECLS__ +#define __VOUCHER_FORWARD_TYPE_DECLS__ +#ifdef __cplusplus +extern "C" { +#endif +#ifndef __VOUCHER_FOWARD_TYPE_DECLS_SINGLE_ATTR +#define __VOUCHER_FOWARD_TYPE_DECLS_SINGLE_ATTR __unsafe_indexable +#endif +extern boolean_t voucher_mach_msg_set(mach_msg_header_t* msg) + __attribute__((weak_import)); +#ifdef __cplusplus +} +#endif +#endif // __VOUCHER_FORWARD_TYPE_DECLS__ +#endif // __has_include() +#endif // __has_include +#endif // !KERNEL + +/* END VOUCHER CODE */ + +/* BEGIN MIG_STRNCPY_ZEROFILL CODE */ + +#if defined(__has_include) +#if __has_include() +#ifndef USING_MIG_STRNCPY_ZEROFILL +#define USING_MIG_STRNCPY_ZEROFILL +#endif +#ifndef __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ +#define __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ +#ifdef __cplusplus +extern "C" { +#endif +#ifndef __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS_CSTRING_ATTR +#define __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS_CSTRING_COUNTEDBY_ATTR(C) \ + __unsafe_indexable +#endif +extern int mig_strncpy_zerofill(char* dest, const char* src, int len) + __attribute__((weak_import)); +#ifdef __cplusplus +} +#endif +#endif /* __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ */ +#endif /* __has_include() */ +#endif /* __has_include */ + +/* END MIG_STRNCPY_ZEROFILL CODE */ + +#include +#include +#include +#include + +#ifndef mig_internal +#define mig_internal static __inline__ +#endif /* mig_internal */ + +#ifndef mig_external +#define mig_external +#endif /* mig_external */ + +#if !defined(__MigTypeCheck) && defined(TypeCheck) +#define __MigTypeCheck TypeCheck /* Legacy setting */ +#endif /* !defined(__MigTypeCheck) */ + +#if !defined(__MigKernelSpecificCode) && defined(_MIG_KERNEL_SPECIFIC_CODE_) +#define __MigKernelSpecificCode \ + _MIG_KERNEL_SPECIFIC_CODE_ /* Legacy setting \ + */ +#endif /* !defined(__MigKernelSpecificCode) */ + +#ifndef LimitCheck +#define LimitCheck 0 +#endif /* LimitCheck */ + +#ifndef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* min */ + +#if !defined(_WALIGN_) +#define _WALIGN_(x) (((x) + 3) & ~3) +#endif /* !defined(_WALIGN_) */ + +#if !defined(_WALIGNSZ_) +#define _WALIGNSZ_(x) _WALIGN_(sizeof(x)) +#endif /* !defined(_WALIGNSZ_) */ + +#ifndef UseStaticTemplates +#define UseStaticTemplates 0 +#endif /* UseStaticTemplates */ + +#ifndef MIG_SERVER_ROUTINE +#define MIG_SERVER_ROUTINE +#endif + +#ifndef __DeclareRcvRpc +#define __DeclareRcvRpc(_NUM_, _NAME_) +#endif /* __DeclareRcvRpc */ + +#ifndef __BeforeRcvRpc +#define __BeforeRcvRpc(_NUM_, _NAME_) +#endif /* __BeforeRcvRpc */ + +#ifndef __AfterRcvRpc +#define __AfterRcvRpc(_NUM_, _NAME_) +#endif /* __AfterRcvRpc */ + +#ifndef __DeclareRcvSimple +#define __DeclareRcvSimple(_NUM_, _NAME_) +#endif /* __DeclareRcvSimple */ + +#ifndef __BeforeRcvSimple +#define __BeforeRcvSimple(_NUM_, _NAME_) +#endif /* __BeforeRcvSimple */ + +#ifndef __AfterRcvSimple +#define __AfterRcvSimple(_NUM_, _NAME_) +#endif /* __AfterRcvSimple */ + +#define novalue void + +#define msgh_request_port msgh_local_port +#define MACH_MSGH_BITS_REQUEST(bits) MACH_MSGH_BITS_LOCAL(bits) +#define msgh_reply_port msgh_remote_port +#define MACH_MSGH_BITS_REPLY(bits) MACH_MSGH_BITS_REMOTE(bits) + +#define MIG_RETURN_ERROR(X, code) \ + { \ + ((mig_reply_error_t*)X)->RetCode = code; \ + ((mig_reply_error_t*)X)->NDR = NDR_record; \ + return; \ + } + +/* typedefs for all requests */ + +#ifndef __Request__mach_exc_subsystem__defined +#define __Request__mach_exc_subsystem__defined + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; +} __Request__mach_exception_raise_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + int flavor; + mach_msg_type_number_t old_stateCnt; + natural_t old_state[1296]; +} __Request__mach_exception_raise_state_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + int flavor; + mach_msg_type_number_t old_stateCnt; + natural_t old_state[1296]; +} __Request__mach_exception_raise_state_identity_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif +#endif /* !__Request__mach_exc_subsystem__defined */ + +/* typedefs for all replies */ + +#ifndef __Reply__mach_exc_subsystem__defined +#define __Reply__mach_exc_subsystem__defined + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + kern_return_t RetCode; +} __Reply__mach_exception_raise_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + kern_return_t RetCode; + int flavor; + mach_msg_type_number_t new_stateCnt; + natural_t new_state[1296]; +} __Reply__mach_exception_raise_state_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif +typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + kern_return_t RetCode; + int flavor; + mach_msg_type_number_t new_stateCnt; + natural_t new_state[1296]; +} __Reply__mach_exception_raise_state_identity_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif +#endif /* !__Reply__mach_exc_subsystem__defined */ + +/* union of all replies */ + +#ifndef __ReplyUnion__catch_mach_exc_subsystem__defined +#define __ReplyUnion__catch_mach_exc_subsystem__defined +union __ReplyUnion__catch_mach_exc_subsystem { + __Reply__mach_exception_raise_t Reply_mach_exception_raise; + __Reply__mach_exception_raise_state_t Reply_mach_exception_raise_state; + __Reply__mach_exception_raise_state_identity_t + Reply_mach_exception_raise_state_identity; +}; +#endif /* __ReplyUnion__catch_mach_exc_subsystem__defined */ +/* Forward Declarations */ + +mig_internal novalue _Xmach_exception_raise(mach_msg_header_t* InHeadP, + mach_msg_header_t* OutHeadP); + +mig_internal novalue _Xmach_exception_raise_state(mach_msg_header_t* InHeadP, + mach_msg_header_t* OutHeadP); + +mig_internal novalue _Xmach_exception_raise_state_identity( + mach_msg_header_t* InHeadP, mach_msg_header_t* OutHeadP); + +#if (__MigTypeCheck) +#if __MIG_check__Request__mach_exc_subsystem__ +#if !defined(__MIG_check__Request__mach_exception_raise_t__defined) +#define __MIG_check__Request__mach_exception_raise_t__defined + +mig_internal kern_return_t __MIG_check__Request__mach_exception_raise_t( + __attribute__((__unused__)) __Request__mach_exception_raise_t* In0P) { + typedef __Request__mach_exception_raise_t __Request; +#if __MigTypeCheck + unsigned int msgh_size; +#endif /* __MigTypeCheck */ + +#if __MigTypeCheck + msgh_size = In0P->Head.msgh_size; + if (!(In0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) || + (In0P->msgh_body.msgh_descriptor_count != 2) || + (msgh_size < (mach_msg_size_t)(sizeof(__Request) - 16)) || + (msgh_size > (mach_msg_size_t)sizeof(__Request))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + +#if __MigTypeCheck + if (In0P->thread.type != MACH_MSG_PORT_DESCRIPTOR || + In0P->thread.disposition != 17) + return MIG_TYPE_ERROR; +#endif /* __MigTypeCheck */ + +#if __MigTypeCheck + if (In0P->task.type != MACH_MSG_PORT_DESCRIPTOR || + In0P->task.disposition != 17) + return MIG_TYPE_ERROR; +#endif /* __MigTypeCheck */ + +#if defined( \ + __NDR_convert__int_rep__Request__mach_exception_raise_t__codeCnt__defined) + if (In0P->NDR.int_rep != NDR_record.int_rep) + __NDR_convert__int_rep__Request__mach_exception_raise_t__codeCnt( + &In0P->codeCnt, In0P->NDR.int_rep); +#endif /* __NDR_convert__int_rep__Request__mach_exception_raise_t__codeCnt__defined \ + */ +#if __MigTypeCheck + if (In0P->codeCnt > 2) return MIG_BAD_ARGUMENTS; + if (((msgh_size - (mach_msg_size_t)(sizeof(__Request) - 16)) / 8 < + In0P->codeCnt) || + (msgh_size != + (mach_msg_size_t)(sizeof(__Request) - 16) + (8 * In0P->codeCnt))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + + return MACH_MSG_SUCCESS; +} +#endif /* !defined(__MIG_check__Request__mach_exception_raise_t__defined) */ +#endif /* __MIG_check__Request__mach_exc_subsystem__ */ +#endif /* ( __MigTypeCheck ) */ + +/* Routine mach_exception_raise */ +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ + MIG_SERVER_ROUTINE kern_return_t catch_mach_exception_raise( + mach_port_t exception_port, mach_port_t thread, mach_port_t task, + exception_type_t exception, mach_exception_data_t code, + mach_msg_type_number_t codeCnt); + +/* Routine mach_exception_raise */ +mig_internal novalue _Xmach_exception_raise(mach_msg_header_t* InHeadP, + mach_msg_header_t* OutHeadP) { +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + mach_msg_trailer_t trailer; + } Request __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + typedef __Reply__mach_exception_raise_t Reply __attribute__((unused)); + typedef __Request__mach_exception_raise_t __Request __attribute__((unused)); + + /* + * typedef struct { + * mach_msg_header_t Head; + * NDR_record_t NDR; + * kern_return_t RetCode; + * } mig_reply_error_t; + */ + + Request* In0P = (Request*)InHeadP; + Reply* OutP = (Reply*)OutHeadP; +#ifdef __MIG_check__Request__mach_exception_raise_t__defined + kern_return_t check_result; +#endif /* __MIG_check__Request__mach_exception_raise_t__defined */ + + __DeclareRcvRpc(2405, "mach_exception_raise") + __BeforeRcvRpc(2405, "mach_exception_raise") + +#if defined(__MIG_check__Request__mach_exception_raise_t__defined) + check_result = + __MIG_check__Request__mach_exception_raise_t((__Request*)In0P); + if (check_result != MACH_MSG_SUCCESS) { + MIG_RETURN_ERROR(OutP, check_result); + } +#endif /* defined(__MIG_check__Request__mach_exception_raise_t__defined) */ + + OutP->RetCode = catch_mach_exception_raise( + In0P->Head.msgh_request_port, In0P->thread.name, In0P->task.name, + In0P->exception, In0P->code, In0P->codeCnt); + + OutP->NDR = NDR_record; + + __AfterRcvRpc(2405, "mach_exception_raise") +} + +#if (__MigTypeCheck) +#if __MIG_check__Request__mach_exc_subsystem__ +#if !defined(__MIG_check__Request__mach_exception_raise_state_t__defined) +#define __MIG_check__Request__mach_exception_raise_state_t__defined + +mig_internal kern_return_t __MIG_check__Request__mach_exception_raise_state_t( + __attribute__((__unused__)) __Request__mach_exception_raise_state_t* In0P, + __attribute__((__unused__)) + __Request__mach_exception_raise_state_t** In1PP) { + typedef __Request__mach_exception_raise_state_t __Request; + __Request* In1P; +#if __MigTypeCheck + unsigned int msgh_size; +#endif /* __MigTypeCheck */ + unsigned int msgh_size_delta; + +#if __MigTypeCheck + msgh_size = In0P->Head.msgh_size; + if ((In0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) || + (msgh_size < (mach_msg_size_t)(sizeof(__Request) - 5200)) || + (msgh_size > (mach_msg_size_t)sizeof(__Request))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + +#if defined( \ + __NDR_convert__int_rep__Request__mach_exception_raise_state_t__codeCnt__defined) + if (In0P->NDR.int_rep != NDR_record.int_rep) + __NDR_convert__int_rep__Request__mach_exception_raise_state_t__codeCnt( + &In0P->codeCnt, In0P->NDR.int_rep); +#endif /* __NDR_convert__int_rep__Request__mach_exception_raise_state_t__codeCnt__defined \ + */ + msgh_size_delta = (8 * In0P->codeCnt); +#if __MigTypeCheck + if (In0P->codeCnt > 2) return MIG_BAD_ARGUMENTS; + if (((msgh_size - (mach_msg_size_t)(sizeof(__Request) - 5200)) / 8 < + In0P->codeCnt) || + (msgh_size < + (mach_msg_size_t)(sizeof(__Request) - 5200) + (8 * In0P->codeCnt))) + return MIG_BAD_ARGUMENTS; + msgh_size -= msgh_size_delta; +#endif /* __MigTypeCheck */ + + *In1PP = In1P = (__Request*)((pointer_t)In0P + msgh_size_delta - 16); + +#if defined( \ + __NDR_convert__int_rep__Request__mach_exception_raise_state_t__old_stateCnt__defined) + if (In0P->NDR.int_rep != NDR_record.int_rep) + __NDR_convert__int_rep__Request__mach_exception_raise_state_t__old_stateCnt( + &In1P->old_stateCnt, In1P->NDR.int_rep); +#endif /* __NDR_convert__int_rep__Request__mach_exception_raise_state_t__old_stateCnt__defined \ + */ +#if __MigTypeCheck + if (In1P->old_stateCnt > 1296) return MIG_BAD_ARGUMENTS; + if (((msgh_size - (mach_msg_size_t)(sizeof(__Request) - 5200)) / 4 < + In1P->old_stateCnt) || + (msgh_size != + (mach_msg_size_t)(sizeof(__Request) - 5200) + (4 * In1P->old_stateCnt))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + + return MACH_MSG_SUCCESS; +} +#endif /* !defined(__MIG_check__Request__mach_exception_raise_state_t__defined) \ + */ +#endif /* __MIG_check__Request__mach_exc_subsystem__ */ +#endif /* ( __MigTypeCheck ) */ + +/* Routine mach_exception_raise_state */ +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ + MIG_SERVER_ROUTINE kern_return_t catch_mach_exception_raise_state( + mach_port_t exception_port, exception_type_t exception, + const mach_exception_data_t code, mach_msg_type_number_t codeCnt, + int* flavor, const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, thread_state_t new_state, + mach_msg_type_number_t* new_stateCnt); + +/* Routine mach_exception_raise_state */ +mig_internal novalue _Xmach_exception_raise_state(mach_msg_header_t* InHeadP, + mach_msg_header_t* OutHeadP) { +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + int flavor; + mach_msg_type_number_t old_stateCnt; + natural_t old_state[1296]; + mach_msg_trailer_t trailer; + } Request __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + typedef __Reply__mach_exception_raise_state_t Reply __attribute__((unused)); + typedef __Request__mach_exception_raise_state_t __Request + __attribute__((unused)); + + /* + * typedef struct { + * mach_msg_header_t Head; + * NDR_record_t NDR; + * kern_return_t RetCode; + * } mig_reply_error_t; + */ + + Request* In0P = (Request*)InHeadP; + Request* In1P; + Reply* OutP = (Reply*)OutHeadP; +#ifdef __MIG_check__Request__mach_exception_raise_state_t__defined + kern_return_t check_result; +#endif /* __MIG_check__Request__mach_exception_raise_state_t__defined */ + + __DeclareRcvRpc(2406, "mach_exception_raise_state") + __BeforeRcvRpc(2406, "mach_exception_raise_state") + +#if defined(__MIG_check__Request__mach_exception_raise_state_t__defined) + check_result = __MIG_check__Request__mach_exception_raise_state_t( + (__Request*)In0P, (__Request**)&In1P); + if (check_result != MACH_MSG_SUCCESS) { + MIG_RETURN_ERROR(OutP, check_result); + } +#endif /* defined(__MIG_check__Request__mach_exception_raise_state_t__defined) \ + */ + + OutP->new_stateCnt = 1296; + + OutP->RetCode = catch_mach_exception_raise_state( + In0P->Head.msgh_request_port, In0P->exception, In0P->code, In0P->codeCnt, + &In1P->flavor, In1P->old_state, In1P->old_stateCnt, OutP->new_state, + &OutP->new_stateCnt); + if (OutP->RetCode != KERN_SUCCESS) { + MIG_RETURN_ERROR(OutP, OutP->RetCode); + } + + OutP->NDR = NDR_record; + + OutP->flavor = In1P->flavor; + OutP->Head.msgh_size = + (mach_msg_size_t)(sizeof(Reply) - 5184) + (((4 * OutP->new_stateCnt))); + + __AfterRcvRpc(2406, "mach_exception_raise_state") +} + +#if (__MigTypeCheck) +#if __MIG_check__Request__mach_exc_subsystem__ +#if !defined( \ + __MIG_check__Request__mach_exception_raise_state_identity_t__defined) +#define __MIG_check__Request__mach_exception_raise_state_identity_t__defined + +mig_internal kern_return_t +__MIG_check__Request__mach_exception_raise_state_identity_t( + __attribute__((__unused__)) + __Request__mach_exception_raise_state_identity_t* In0P, + __attribute__((__unused__)) + __Request__mach_exception_raise_state_identity_t** In1PP) { + typedef __Request__mach_exception_raise_state_identity_t __Request; + __Request* In1P; +#if __MigTypeCheck + unsigned int msgh_size; +#endif /* __MigTypeCheck */ + unsigned int msgh_size_delta; + +#if __MigTypeCheck + msgh_size = In0P->Head.msgh_size; + if (!(In0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) || + (In0P->msgh_body.msgh_descriptor_count != 2) || + (msgh_size < (mach_msg_size_t)(sizeof(__Request) - 5200)) || + (msgh_size > (mach_msg_size_t)sizeof(__Request))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + +#if __MigTypeCheck + if (In0P->thread.type != MACH_MSG_PORT_DESCRIPTOR || + In0P->thread.disposition != 17) + return MIG_TYPE_ERROR; +#endif /* __MigTypeCheck */ + +#if __MigTypeCheck + if (In0P->task.type != MACH_MSG_PORT_DESCRIPTOR || + In0P->task.disposition != 17) + return MIG_TYPE_ERROR; +#endif /* __MigTypeCheck */ + +#if defined( \ + __NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__codeCnt__defined) + if (In0P->NDR.int_rep != NDR_record.int_rep) + __NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__codeCnt( + &In0P->codeCnt, In0P->NDR.int_rep); +#endif /* __NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__codeCnt__defined \ + */ + msgh_size_delta = (8 * In0P->codeCnt); +#if __MigTypeCheck + if (In0P->codeCnt > 2) return MIG_BAD_ARGUMENTS; + if (((msgh_size - (mach_msg_size_t)(sizeof(__Request) - 5200)) / 8 < + In0P->codeCnt) || + (msgh_size < + (mach_msg_size_t)(sizeof(__Request) - 5200) + (8 * In0P->codeCnt))) + return MIG_BAD_ARGUMENTS; + msgh_size -= msgh_size_delta; +#endif /* __MigTypeCheck */ + + *In1PP = In1P = (__Request*)((pointer_t)In0P + msgh_size_delta - 16); + +#if defined( \ + __NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__old_stateCnt__defined) + if (In0P->NDR.int_rep != NDR_record.int_rep) + __NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__old_stateCnt( + &In1P->old_stateCnt, In1P->NDR.int_rep); +#endif /* __NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__old_stateCnt__defined \ + */ +#if __MigTypeCheck + if (In1P->old_stateCnt > 1296) return MIG_BAD_ARGUMENTS; + if (((msgh_size - (mach_msg_size_t)(sizeof(__Request) - 5200)) / 4 < + In1P->old_stateCnt) || + (msgh_size != + (mach_msg_size_t)(sizeof(__Request) - 5200) + (4 * In1P->old_stateCnt))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + + return MACH_MSG_SUCCESS; +} +#endif /* !defined(__MIG_check__Request__mach_exception_raise_state_identity_t__defined) \ + */ +#endif /* __MIG_check__Request__mach_exc_subsystem__ */ +#endif /* ( __MigTypeCheck ) */ + +/* Routine mach_exception_raise_state_identity */ +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ + MIG_SERVER_ROUTINE kern_return_t catch_mach_exception_raise_state_identity( + mach_port_t exception_port, mach_port_t thread, mach_port_t task, + exception_type_t exception, mach_exception_data_t code, + mach_msg_type_number_t codeCnt, int* flavor, thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, thread_state_t new_state, + mach_msg_type_number_t* new_stateCnt); + +/* Routine mach_exception_raise_state_identity */ +mig_internal novalue _Xmach_exception_raise_state_identity( + mach_msg_header_t* InHeadP, mach_msg_header_t* OutHeadP) { +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + int flavor; + mach_msg_type_number_t old_stateCnt; + natural_t old_state[1296]; + mach_msg_trailer_t trailer; + } Request __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + typedef __Reply__mach_exception_raise_state_identity_t Reply + __attribute__((unused)); + typedef __Request__mach_exception_raise_state_identity_t __Request + __attribute__((unused)); + + /* + * typedef struct { + * mach_msg_header_t Head; + * NDR_record_t NDR; + * kern_return_t RetCode; + * } mig_reply_error_t; + */ + + Request* In0P = (Request*)InHeadP; + Request* In1P; + Reply* OutP = (Reply*)OutHeadP; +#ifdef __MIG_check__Request__mach_exception_raise_state_identity_t__defined + kern_return_t check_result; +#endif /* __MIG_check__Request__mach_exception_raise_state_identity_t__defined \ + */ + + __DeclareRcvRpc(2407, "mach_exception_raise_state_identity") + __BeforeRcvRpc(2407, "mach_exception_raise_state_identity") + +#if defined( \ + __MIG_check__Request__mach_exception_raise_state_identity_t__defined) + check_result = + __MIG_check__Request__mach_exception_raise_state_identity_t( + (__Request*)In0P, (__Request**)&In1P); + if (check_result != MACH_MSG_SUCCESS) { + MIG_RETURN_ERROR(OutP, check_result); + } +#endif /* defined(__MIG_check__Request__mach_exception_raise_state_identity_t__defined) \ + */ + + OutP->new_stateCnt = 1296; + + OutP->RetCode = catch_mach_exception_raise_state_identity( + In0P->Head.msgh_request_port, In0P->thread.name, In0P->task.name, + In0P->exception, In0P->code, In0P->codeCnt, &In1P->flavor, + In1P->old_state, In1P->old_stateCnt, OutP->new_state, + &OutP->new_stateCnt); + if (OutP->RetCode != KERN_SUCCESS) { + MIG_RETURN_ERROR(OutP, OutP->RetCode); + } + + OutP->NDR = NDR_record; + + OutP->flavor = In1P->flavor; + OutP->Head.msgh_size = + (mach_msg_size_t)(sizeof(Reply) - 5184) + (((4 * OutP->new_stateCnt))); + + __AfterRcvRpc(2407, "mach_exception_raise_state_identity") +} + +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ + boolean_t mach_exc_server(mach_msg_header_t* InHeadP, + mach_msg_header_t* OutHeadP); + +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ + mig_routine_t mach_exc_server_routine(mach_msg_header_t* InHeadP); + +/* Description of this subsystem, for use in direct RPC */ +const struct catch_mach_exc_subsystem { + mig_server_routine_t server; /* Server routine */ + mach_msg_id_t start; /* Min routine number */ + mach_msg_id_t end; /* Max routine number + 1 */ + unsigned int maxsize; /* Max msg size */ + vm_address_t reserved; /* Reserved */ + struct routine_descriptor /* Array of routine descriptors */ + routine[6]; +} catch_mach_exc_subsystem = { + mach_exc_server_routine, + 2405, + 2411, + (mach_msg_size_t)sizeof(union __ReplyUnion__catch_mach_exc_subsystem), + (vm_address_t)0, + { + {(mig_impl_routine_t)0, (mig_stub_routine_t)_Xmach_exception_raise, 6, + 0, (routine_arg_descriptor_t)0, + (mach_msg_size_t)sizeof(__Reply__mach_exception_raise_t)}, + {(mig_impl_routine_t)0, + (mig_stub_routine_t)_Xmach_exception_raise_state, 9, 0, + (routine_arg_descriptor_t)0, + (mach_msg_size_t)sizeof(__Reply__mach_exception_raise_state_t)}, + {(mig_impl_routine_t)0, + (mig_stub_routine_t)_Xmach_exception_raise_state_identity, 11, 0, + (routine_arg_descriptor_t)0, + (mach_msg_size_t)sizeof( + __Reply__mach_exception_raise_state_identity_t)}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + }}; + +mig_external boolean_t mach_exc_server(mach_msg_header_t* InHeadP, + mach_msg_header_t* OutHeadP) { + /* + * typedef struct { + * mach_msg_header_t Head; + * NDR_record_t NDR; + * kern_return_t RetCode; + * } mig_reply_error_t; + */ + + mig_routine_t routine; + + OutHeadP->msgh_bits = + MACH_MSGH_BITS(MACH_MSGH_BITS_REPLY(InHeadP->msgh_bits), 0); + OutHeadP->msgh_remote_port = InHeadP->msgh_reply_port; + /* Minimal size: routine() will update it if different */ + OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t); + OutHeadP->msgh_local_port = MACH_PORT_NULL; + OutHeadP->msgh_id = InHeadP->msgh_id + 100; + OutHeadP->msgh_reserved = 0; + + if ((InHeadP->msgh_id > 2410) || (InHeadP->msgh_id < 2405) || + ((routine = catch_mach_exc_subsystem.routine[InHeadP->msgh_id - 2405] + .stub_routine) == 0)) { + ((mig_reply_error_t*)OutHeadP)->NDR = NDR_record; + ((mig_reply_error_t*)OutHeadP)->RetCode = MIG_BAD_ID; + return FALSE; + } + (*routine)(InHeadP, OutHeadP); + return TRUE; +} + +mig_external mig_routine_t mach_exc_server_routine(mach_msg_header_t* InHeadP) { + int msgh_id; + + msgh_id = InHeadP->msgh_id - 2405; + + if ((msgh_id > 5) || (msgh_id < 0)) return 0; + + return catch_mach_exc_subsystem.routine[msgh_id].stub_routine; +} diff --git a/src/core/macho/macho.cpp b/src/core/macho/macho.cpp new file mode 100644 index 0000000..bed2d19 --- /dev/null +++ b/src/core/macho/macho.cpp @@ -0,0 +1,617 @@ +#include "macho.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "target.hpp" +extern "C" { +#include "mach_exc.h" +#include "mach_excServer.h" +} + +// Adapted from https://lowlevelbits.org/parsing-mach-o-files/ + +void Macho::readMagic() { + u32 magic = 0; + m_file.seekg(0, std::ios::beg); + m_file.read(std::bit_cast(&magic), sizeof(uint32_t)); + m_magic = magic; +} + +void Macho::is64() { + if (m_magic == MH_MAGIC_64 || m_magic == MH_CIGAM_64) m_is_64 = true; +} + +void Macho::maybeSwapBytes() { + if (m_magic == MH_CIGAM || m_magic == MH_CIGAM_64) m_is_swap = true; +} + +void Macho::dumpHeader(int offset) { + u32 ncmds = 0; + int loadCmdsOffset = offset; + + if (m_is_64) { + constexpr size_t headerSize = sizeof(struct mach_header_64); + auto header = loadBytesAndMaybeSwap(offset); + ncmds = header.ncmds; + loadCmdsOffset += headerSize; + std::cout << Macho::cpuTypeName(header.cputype) << '\n'; + } else { + int headerSize = sizeof(struct mach_header); + auto header = loadBytesAndMaybeSwap(offset); + } + + dumpSegmentCommands(loadCmdsOffset, ncmds); +} + +void Macho::dumpSegmentCommands(int offset, uint32_t ncmds) { + u32 actualOffset = offset; + for (int i = 0; i < ncmds; i++) { + auto cmd = loadBytesAndMaybeSwap(actualOffset); + if (cmd.cmd == LC_SEGMENT_64) { + auto segment = loadBytesAndMaybeSwap(actualOffset); + std::cout << std::format( + "segname: {:<25} offset: 0x{:<12x} vmaddr: 0x{:<18x} " + "vmsize: 0x{:x}", + segment.segname, segment.fileoff, segment.vmaddr, + segment.vmsize) + << '\n'; + dumpSections(actualOffset + sizeof(segment_command_64), + actualOffset + cmd.cmdsize); + } + actualOffset += cmd.cmdsize; + } +} + +std::string Macho::cpuTypeName(cpu_type_t cpuType) { + for (const auto& x : CPU_TYPE_NAMES) { + if (cpuType == x.cpu_type) return x.cpu_name; + } + return "unknown"; +} + +void Macho::dumpSections(uint32_t offset, uint32_t end) { + u32 actualOffset = offset; + while (actualOffset != end) { + auto section = loadBytesAndMaybeSwap(actualOffset); + std::cout << std::format("Section: {}; Address: 0x{:x}", section.sectname, + section.addr) + << '\n'; + actualOffset += sizeof(section_64); + } +} + +Macho::Macho(std::ifstream f, std::string filePath) + : Target(std::move(f), std::move(filePath)) { + readMagic(); + is64(); + maybeSwapBytes(); +} + +// TODO: Add appropriate error messages +i32 Macho::launch(CStringArray& argList) { + pid_t pid = 0; + int status = 0; + posix_spawnattr_t attr = nullptr; + + status = posix_spawnattr_init(&attr); + if (status != 0) return -1; + + status = posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED); + if (status != 0) return -1; + + argList.prepend(m_file_path); + + status = posix_spawn(&pid, m_file_path.c_str(), nullptr, &attr, + argList.data(), nullptr); + posix_spawnattr_destroy(&attr); + if (status != 0) return -1; + m_pid = pid; + + // TODO: This only works with sudo even when codesigned, why? + kern_return_t kr = task_for_pid(mach_task_self(), pid, &this->m_task); + if (kr != KERN_SUCCESS) { + CoreError::error(mach_error_string(kr)); + } + return pid; +} + +i32 Macho::attach() { + // http://uninformed.org/index.cgi?v=4&a=3&p=14 + i32 res = this->setupExceptionPorts(); + ptrace(PT_ATTACHEXC, m_pid, nullptr, 0); + this->readAslrSlide(); + return res; +}; + +// TODO: Make an overload with u32 for 32bit systems +i32 Macho::setBreakpoint(u64 addr) { + u64 actual = addr + m_aslr_slide; + auto sz = static_cast(sizeof(u32)); + vm_offset_t origBuf = 0; + kern_return_t kr = mach_vm_read(m_task, actual, sizeof(u32), &origBuf, &sz); + if (kr != KERN_SUCCESS) { + CoreError::error( + std::format("Error reading memory: {}!\n", mach_error_string(kr))); + return -1; + } + + u32 origIns = *reinterpret_cast(origBuf); + mach_vm_deallocate(mach_task_self(), origBuf, sizeof(u32)); + m_breakpoints[addr] = {.orig_ins = origIns, .enabled = true}; + + // TODO: This is arm64 only + // TODO: brk #0, switch to brk #1, #2... to distinguish different breakpoints + u32 brk = 0xD4200000; + + kr = mach_vm_protect(m_task, actual, sizeof(u32), FALSE, + VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY); + if (kr != KERN_SUCCESS) { + CoreError::error(std::format("Error changing memory protection: {}!\n", + mach_error_string(kr))); + return -1; + } + + kr = mach_vm_write(m_task, actual, reinterpret_cast(&brk), + sizeof(u32)); + if (kr != KERN_SUCCESS) { + CoreError::error( + std::format("Error writing memory: {}!\n", mach_error_string(kr))); + return -1; + } + + kr = mach_vm_protect(m_task, actual, sizeof(u32), FALSE, + VM_PROT_READ | VM_PROT_EXECUTE); + if (kr != KERN_SUCCESS) { + CoreError::error(std::format("Error re-protecting memory: {}!\n", + mach_error_string(kr))); + return -1; + } + + return 0; +} + +void Macho::detach() { throw std::runtime_error("unimplemented"); } + +i32 Macho::setupExceptionPorts() { + i32 res = 0; + MachExcPorts savedPorts{}; + task_get_exception_ports( + m_task, + EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | + EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT | + EXC_MASK_SYSCALL | EXC_MASK_MACH_SYSCALL | EXC_MASK_RPC_ALERT | + EXC_MASK_CRASH, + savedPorts.masks.data(), &savedPorts.excp_type_count, + savedPorts.ports.data(), savedPorts.behaviours.data(), + savedPorts.flavours.data()); + kern_return_t kr = mach_port_allocate(mach_task_self(), + MACH_PORT_RIGHT_RECEIVE, &m_exc_port); + + if (kr != KERN_SUCCESS) { + CoreError::error(mach_error_string(kr)); + res = -1; + } + + kr = mach_port_insert_right(mach_task_self(), m_exc_port, m_exc_port, + MACH_MSG_TYPE_MAKE_SEND); + + if (kr != KERN_SUCCESS) { + CoreError::error(mach_error_string(kr)); + res = -1; + } + + kr = task_set_exception_ports( + m_task, + EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | + EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT | + EXC_MASK_SYSCALL | EXC_MASK_MACH_SYSCALL | EXC_MASK_RPC_ALERT | + EXC_MASK_CRASH, + m_exc_port, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, + ARM_THREAD_STATE64); + + if (kr != KERN_SUCCESS) { + CoreError::error(mach_error_string(kr)); + res = -1; + } + + return res; +} + +void Macho::eventLoop() { + mach_msg_return_t ret = 0; + __RequestUnion__mach_exc_subsystem msgBuf{}; + __ReplyUnion__mach_exc_subsystem rplBuf{}; + + auto* msg = reinterpret_cast(&msgBuf); + auto* rpl = reinterpret_cast(&rplBuf); + + while (m_state == TargetState::RUNNING) { + ret = mach_msg(msg, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, + sizeof(__RequestUnion__mach_exc_subsystem), m_exc_port, 100, + MACH_PORT_NULL); + if (ret == MACH_RCV_TIMED_OUT) { + int status = 0; + if (waitpid(m_pid, &status, WNOHANG) > 0) { + m_state = TargetState::EXITED; + if (WIFEXITED(status)) + std::cout << "Target exited with code " << WEXITSTATUS(status) + << '\n'; + else if (WIFSIGNALED(status)) + std::cout << "Target killed with signal " << WTERMSIG(status) << '\n'; + } + continue; + } + assert(ret == MACH_MSG_SUCCESS && "Did not receive mach message"); + + mach_exc_server(msg, rpl); + + ret = mach_msg(rpl, MACH_SEND_MSG, rpl->msgh_size, 0, MACH_PORT_NULL, 0, + MACH_PORT_NULL); + assert(ret == MACH_MSG_SUCCESS && "Did not send mach message"); + } +} + +extern "C" { + +kern_return_t catch_mach_exception_raise(mach_port_t excPort, + mach_port_t threadPort, + mach_port_t taskPort, + exception_type_t excType, + mach_exception_data_t codes, + mach_msg_type_number_t numCodes) { +#pragma unused(excPort) +#pragma unused(threadPort) +#pragma unused(excType) +#pragma unused(numCodes) +#pragma unused(taskPort) +#pragma unused(codes) + return KERN_FAILURE; +} + +kern_return_t catch_mach_exception_raise_state( + mach_port_t excPort, exception_type_t exc, + const mach_exception_data_t code, // NOLINT(misc-misplaced-const) + mach_msg_type_number_t codeCnt, int* flavour, + const thread_state_t oldState, // NOLINT(misc-misplaced-const) + mach_msg_type_number_t oldStateCnt, thread_state_t newState, + mach_msg_type_number_t* newStateCnt) { +#pragma unused(excPort) +#pragma unused(exc) +#pragma unused(code) +#pragma unused(codeCnt) +#pragma unused(oldStateCnt) +#pragma unused(newState) +#pragma unused(newStateCnt) + + return KERN_FAILURE; +} + +kern_return_t catch_mach_exception_raise_state_identity( + mach_port_t excPort, mach_port_t thread, mach_port_t task, + exception_type_t exc, mach_exception_data_t code, + mach_msg_type_number_t codeCnt, int* flavour, thread_state_t oldState, + mach_msg_type_number_t oldStateCnt, thread_state_t newState, + mach_msg_type_number_t* newStateCnt) { +#pragma unused(excPort) +#pragma unused(task) +#pragma unused(code) +#pragma unused(codeCnt) +#pragma unused(newState) +#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)); + + std::cout << Macho::exceptionReason(exc, codeCnt, code); + std::cout << std::format("PC @ {}\n", + toHex(oldArmState->__pc - macho->getAslrSlide())); + + 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())); + } + + if (exc == EXC_BREAKPOINT) { + macho->restorePrevIns(oldArmState->__pc - macho->getAslrSlide()); + } + + *newStateCnt = oldStateCnt; + return KERN_SUCCESS; +} + +} // extern "C" + +mach_port_t Macho::threadSelect() { + std::cout << "TASK: " << m_task << '\n'; + thread_act_array_t acts{}; + mach_msg_type_number_t numThreads = 0; + + kern_return_t kr = task_threads(m_task, &acts, &numThreads); + + if (kr != KERN_SUCCESS) { + std::cout << "task_threads fail!\n"; + CoreError::error(mach_error_string(kr)); + } + + thread_act_t mainThread = acts[0]; // Cleanup + vm_deallocate(mach_task_self(), reinterpret_cast(acts), + numThreads * sizeof(thread_act_t)); + + return mainThread; +} + +void Macho::resume(ResumeType cond) { + if (!m_started) { + std::cerr << "Target is not running!\n"; + return; + } + + switch (cond) { + case ResumeType::RESUME: + ptrace(PT_THUPDATE, m_pid, + reinterpret_cast(static_cast(m_thread_port)), + 0); + ptrace(PT_CONTINUE, m_pid, reinterpret_cast(1), 0); + task_resume(m_task); + this->setTargetState(TargetState::RUNNING); + break; + } +} + +std::string Macho::exceptionReason(exception_type_t exc, + mach_msg_type_number_t codeCnt, + mach_exception_data_t code) { + const auto signalStr = [] { + std::map res; +#define INSERT_ELEM(p) res.emplace(p, #p); + INSERT_ELEM(SIGHUP); + INSERT_ELEM(SIGINT); + INSERT_ELEM(SIGQUIT); + INSERT_ELEM(SIGILL); + INSERT_ELEM(SIGTRAP); + INSERT_ELEM(SIGABRT); + INSERT_ELEM(SIGFPE); + INSERT_ELEM(SIGKILL); + INSERT_ELEM(SIGBUS); + INSERT_ELEM(SIGSEGV); + INSERT_ELEM(SIGSYS); + INSERT_ELEM(SIGPIPE); + INSERT_ELEM(SIGALRM); + INSERT_ELEM(SIGTERM); + INSERT_ELEM(SIGURG); + INSERT_ELEM(SIGSTOP); + INSERT_ELEM(SIGTSTP); + INSERT_ELEM(SIGCONT); + INSERT_ELEM(SIGCHLD); + INSERT_ELEM(SIGTTIN); + INSERT_ELEM(SIGTTOU); + INSERT_ELEM(SIGIO); + INSERT_ELEM(SIGXCPU); + INSERT_ELEM(SIGXFSZ); + INSERT_ELEM(SIGVTALRM); + INSERT_ELEM(SIGPROF); + INSERT_ELEM(SIGWINCH); + INSERT_ELEM(SIGINFO); + INSERT_ELEM(SIGUSR1); + INSERT_ELEM(SIGUSR2); +#undef INSERT_ELEM + return res; + }; + + const auto machExcStr = [] { + std::map res{}; +#define INSERT_ELEM(p) res.emplace(p, #p); + INSERT_ELEM(EXC_BAD_ACCESS); + INSERT_ELEM(EXC_BAD_INSTRUCTION); + INSERT_ELEM(EXC_ARITHMETIC); + INSERT_ELEM(EXC_EMULATION); + INSERT_ELEM(EXC_SOFTWARE); + INSERT_ELEM(EXC_BREAKPOINT); + INSERT_ELEM(EXC_SYSCALL); + INSERT_ELEM(EXC_MACH_SYSCALL); + INSERT_ELEM(EXC_RPC_ALERT); + INSERT_ELEM(EXC_CRASH); + INSERT_ELEM(EXC_RESOURCE); + INSERT_ELEM(EXC_GUARD); + INSERT_ELEM(EXC_CORPSE_NOTIFY); +#undef INSERT_ELEM + return res; + }; + + std::string reason{}; + + // Unix signal + if (exc == EXC_SOFTWARE && codeCnt >= 2 && code[0] == EXC_SOFT_SIGNAL) { + reason = std::format("signal {}\n", signalStr()[code[1]]); + } else { + reason = std::format("reason {}\n", machExcStr()[exc]); + } + + return reason; +} + +void Macho::readAslrSlide() { + task_dyld_info_data_t dyldInfo; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + kern_return_t kr = task_info( + m_task, TASK_DYLD_INFO, reinterpret_cast(&dyldInfo), &count); + + if (kr != KERN_SUCCESS) { + CoreError::error( + std::format("task_info fail: {}!\n", mach_error_string(kr))); + return; + } + + vm_offset_t infoBuf{}; + auto sz = static_cast(sizeof(dyld_all_image_infos)); + kr = mach_vm_read(m_task, dyldInfo.all_image_info_addr, + sizeof(dyld_all_image_infos), &infoBuf, &sz); + if (kr != KERN_SUCCESS) { + CoreError::error( + std::format("mach_vm_read fail: {}!\n", mach_error_string(kr))); + return; + } + auto* imgInfos = reinterpret_cast(infoBuf); + + // When stopped early (e.g. at dyld_start), dyld hasn't populated the image + // info array yet. Fall back to scanning memory regions for the Mach-O header. + if (imgInfos->infoArrayCount == 0 || imgInfos->infoArray == nullptr) { + mach_vm_deallocate(mach_task_self(), infoBuf, sizeof(dyld_all_image_infos)); + readAslrSlideFromRegions(); + return; + } + + vm_offset_t mainImgBuf{}; + sz = static_cast(sizeof(dyld_image_info)); + kr = mach_vm_read(m_task, + reinterpret_cast(imgInfos->infoArray), + sizeof(dyld_image_info), &mainImgBuf, &sz); + mach_vm_deallocate(mach_task_self(), infoBuf, sizeof(dyld_all_image_infos)); + if (kr != KERN_SUCCESS) { + CoreError::error( + std::format("mach_vm_read fail: {}!\n", mach_error_string(kr))); + return; + } + auto* mainImg = reinterpret_cast(mainImgBuf); + m_aslr_slide = reinterpret_cast(mainImg->imageLoadAddress) - 0x100000000; + mach_vm_deallocate(mach_task_self(), mainImgBuf, sizeof(dyld_image_info)); +} + +void Macho::readAslrSlideFromRegions() { + mach_vm_address_t addr = 0; + mach_vm_size_t size = 0; + vm_region_basic_info_data_64_t info; + mach_msg_type_number_t infoCnt = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t objName = MACH_PORT_NULL; + + while (true) { + kern_return_t kr = mach_vm_region( + m_task, &addr, &size, VM_REGION_BASIC_INFO_64, + reinterpret_cast(&info), &infoCnt, &objName); + if (kr != KERN_SUCCESS) break; + + // Look for executable regions that could contain the main binary + if (info.protection & VM_PROT_EXECUTE) { + vm_offset_t headerBuf{}; + auto sz = static_cast(sizeof(u32)); + kr = mach_vm_read(m_task, addr, sizeof(u32), &headerBuf, &sz); + if (kr == KERN_SUCCESS) { + u32 magic = *reinterpret_cast(headerBuf); + mach_vm_deallocate(mach_task_self(), headerBuf, sizeof(u32)); + if (magic == MH_MAGIC_64) { + m_aslr_slide = addr - 0x100000000; + return; + } + } + } + addr += size; + } + + CoreError::error("Could not determine ASLR slide!\n"); +} + +u64& Macho::getAslrSlide() { return m_aslr_slide; } + +i32 Macho::restorePrevIns(u64 k) { + u64 addr = k + m_aslr_slide; + + kern_return_t kr = + mach_vm_protect(m_task, addr, sizeof(u32), FALSE, + VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY); + if (kr != KERN_SUCCESS) { + CoreError::error(std::format("Error changing memory protection: {}!\n", + mach_error_string(kr))); + return -1; + } + + kr = mach_vm_write(m_task, addr, + reinterpret_cast(&m_breakpoints[k].orig_ins), + sizeof(u32)); + + if (kr != KERN_SUCCESS) { + CoreError::error( + std::format("mach_vm_write error in Macho::restorePrevIns(): {}\n", + mach_error_string(kr))); + return -1; + } + + kr = mach_vm_protect(m_task, addr, sizeof(u32), FALSE, + VM_PROT_READ | VM_PROT_EXECUTE); + if (kr != KERN_SUCCESS) { + CoreError::error(std::format("Error re-protecting memory: {}!\n", + mach_error_string(kr))); + return -1; + } + return 0; +} + +i32 Macho::disableBreakpoint(u64 addr, bool remove) { + auto& bp = m_breakpoints[addr]; + + if (!bp.enabled) return 0; + + i32 res = this->restorePrevIns(addr); + if (res != 0) return 1; + + if (remove) + m_breakpoints.erase(addr); + else + bp.enabled = false; + + return 0; +} diff --git a/src/core/macho/macho.hpp b/src/core/macho/macho.hpp new file mode 100644 index 0000000..1a1e752 --- /dev/null +++ b/src/core/macho/macho.hpp @@ -0,0 +1,82 @@ +#ifndef CAESAR_MACHO_HPP +#define CAESAR_MACHO_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "core/target.hpp" +#include "core/util.hpp" + +struct CpuTypeNames { + cpu_type_t cpu_type; + const char* cpu_name; +}; + +class Macho : public Target { + public: + explicit Macho(std::ifstream f, std::string filePath); + + 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; + void detach() override; + void eventLoop() override; + void resume(ResumeType cond) override; + + static std::string exceptionReason(exception_type_t exc, + mach_msg_type_number_t codeCnt, + mach_exception_data_t code); + void setThreadPort(mach_port_t thread) { m_thread_port = thread; } + mach_port_t& getThreadPort() { return m_thread_port; } + void readAslrSlide(); + u64& getAslrSlide(); + i32 restorePrevIns(u64 k); + + private: + task_t m_task = 0; + u64 m_aslr_slide = 0; + u32 m_magic = 0; + mach_port_t m_exc_port = 0; + mach_port_t m_thread_port = 0; + bool m_is_64 = false; + bool m_is_swap = false; + 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"}, + {.cpu_type = CPU_TYPE_ARM, .cpu_name = "arm"}, + {.cpu_type = CPU_TYPE_ARM64, .cpu_name = "arm64"}}}; + + void readMagic() override; + void is64() override; + + void maybeSwapBytes(); + + template + T loadBytesAndMaybeSwap(uint32_t offset) { + 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); + return buf; + } + + void dumpSegmentCommands(int offset, uint32_t ncmds); + static std::string cpuTypeName(cpu_type_t cpuType); + void dumpSections(uint32_t offset, uint32_t end); + i32 setupExceptionPorts(); + void readAslrSlideFromRegions(); + mach_port_t threadSelect(); // TODO: doesn't work (yet) +}; + +#endif // CAESAR_MACHO_HPP diff --git a/src/core/macho/ports.hpp b/src/core/macho/ports.hpp new file mode 100644 index 0000000..f19ea09 --- /dev/null +++ b/src/core/macho/ports.hpp @@ -0,0 +1,18 @@ +#ifndef CAESAR_PORTS_HPP +#define CAESAR_PORTS_HPP + +#include +#include +#include + +#include + +struct MachExcPorts { + mach_msg_type_number_t excp_type_count{}; + std::array masks{}; + std::array ports{}; + std::array behaviours{}; + std::array flavours{}; +}; + +#endif diff --git a/src/core/target.cpp b/src/core/target.cpp new file mode 100644 index 0000000..b1896d9 --- /dev/null +++ b/src/core/target.cpp @@ -0,0 +1,58 @@ +#include "target.hpp" + +#include + +#include "core/util.hpp" +#ifdef __APPLE__ +#include "macho/macho.hpp" +#endif + +consteval u32 Target::byteArrayToInt(const MagicBytes& bytes) { + return (std::to_integer(bytes[3]) << 24) | + (std::to_integer(bytes[2]) << 16) | + (std::to_integer(bytes[1]) << 8) | + (std::to_integer(bytes[0])); +} + +std::unique_ptr Target::create(const std::string& path) { +#ifdef __APPLE__ + return std::make_unique(std::ifstream(path), path); +#endif + return nullptr; +} + +bool Target::isFileValid(const std::string& filePath) { + const std::unordered_map magics{ + {{byteArrayToInt(MagicBytes{std::byte{0xCF}, std::byte{0xFA}, + std::byte{0xED}, std::byte{0xFE}}), + PlatformType::MACH}, + + {byteArrayToInt(MagicBytes{std::byte{0x7F}, std::byte{'E'}, + std::byte{'L'}, std::byte{'F'}}), + PlatformType::LINUX}, + + {byteArrayToInt(MagicBytes{std::byte{'M'}, std::byte{'Z'}, std::byte{}, + std::byte{}}), + PlatformType::WIN}}}; + + u32 magicRead = 0; + std::ifstream f{filePath}; + f.seekg(0, std::ios::beg); + f.read(std::bit_cast(&magicRead), sizeof(u32)); + + // Unsupported file + if (!magics.contains(magicRead)) return false; + + return magics.at(magicRead) == getPlatform(); +} + +void Target::startEventLoop() { + m_waiter = std::jthread(&Target::eventLoop, this); + while (m_state == TargetState::RUNNING) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if (m_waiter.joinable()) m_waiter.join(); +} + +std::map& Target::getRegisteredBreakpoints() { + return m_breakpoints; +} diff --git a/src/core/target.hpp b/src/core/target.hpp new file mode 100644 index 0000000..bf6f87d --- /dev/null +++ b/src/core/target.hpp @@ -0,0 +1,67 @@ +#ifndef CAESAR_TARGET_H +#define CAESAR_TARGET_H + +#include +#include +#include +#include +#include +#include + +#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 }; + +struct Breakpoint { + u32 orig_ins; + bool enabled; +}; + +class Target { + private: + static consteval u32 byteArrayToInt(const MagicBytes& bytes); + + protected: + std::ifstream m_file; + std::string m_file_path; + std::jthread m_waiter; + std::map m_breakpoints; + i32 m_pid = 0; + std::atomic m_state = TargetState::STOPPED; + + explicit Target(std::ifstream f, std::string filePath) + : m_file(std::move(f)), m_file_path(std::move(filePath)) {} + + virtual void readMagic() = 0; + virtual void is64() = 0; + + public: + bool m_started = false; + + virtual ~Target() = default; + Target() = delete; + + virtual void dumpHeader(int offset) = 0; + 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 void detach() = 0; + virtual void eventLoop() = 0; + virtual void resume(ResumeType cond) = 0; + + void setTargetState(TargetState s) { m_state = s; } + std::atomic& getTargetState() { return m_state; } + i32 pid() const { return m_pid; } + void startEventLoop(); + std::map& getRegisteredBreakpoints(); + + static bool isFileValid(const std::string& filePath); + static std::unique_ptr create(const std::string& path); +}; + +#endif diff --git a/src/core/util.hpp b/src/core/util.hpp index 3dd9dac..1b2a18a 100644 --- a/src/core/util.hpp +++ b/src/core/util.hpp @@ -1,10 +1,21 @@ #ifndef CAESAR_UTIL_HPP #define CAESAR_UTIL_HPP +#ifdef __APPLE__ + #include +#endif + +#include #include #include +#include +#include +#include + +#include "expected.hpp" +#include "typedefs.hpp" template struct SwapDescriptor { @@ -12,16 +23,18 @@ struct SwapDescriptor { static_assert(sizeof(T) % sizeof(uint32_t) == 0, "Struct size must be multiple of 4 bytes"); - uint32_t* fields = reinterpret_cast(ptr); - constexpr size_t num_fields = sizeof(T) / sizeof(uint32_t); + auto* fields = reinterpret_cast(ptr); + constexpr size_t numFields = sizeof(T) / sizeof(u32); // TODO: Compiler builtin, make this more platform agnostic - for (size_t i = 0; i < num_fields; i++) { + for (size_t i = 0; i < numFields; i++) { fields[i] = __builtin_bswap32(fields[i]); } } }; +#ifdef __APPLE__ + struct FieldInfo { size_t offset; size_t size; @@ -33,26 +46,46 @@ struct SwapDescriptor { static void swap(segment_command_64* segment) { char* base = reinterpret_cast(segment); - constexpr FieldInfo fields[] = { - {offsetof(segment_command_64, cmd), 4, true}, - {offsetof(segment_command_64, cmdsize), 4, true}, - {offsetof(segment_command_64, segname), 16, false}, // Skip char array - {offsetof(segment_command_64, vmaddr), 8, true}, - {offsetof(segment_command_64, vmsize), 8, true}, - {offsetof(segment_command_64, fileoff), 8, true}, - {offsetof(segment_command_64, filesize), 8, true}, - {offsetof(segment_command_64, maxprot), 4, true}, - {offsetof(segment_command_64, initprot), 4, true}, - {offsetof(segment_command_64, nsects), 4, true}, - {offsetof(segment_command_64, flags), 4, true}}; + constexpr std::array fields = { + {{.offset = offsetof(segment_command_64, cmd), .size = 4, .swap = true}, + {.offset = offsetof(segment_command_64, cmdsize), + .size = 4, + .swap = true}, + {.offset = offsetof(segment_command_64, segname), + .size = 16, + .swap = false}, // Skip char array + {.offset = offsetof(segment_command_64, vmaddr), + .size = 8, + .swap = true}, + {.offset = offsetof(segment_command_64, vmsize), + .size = 8, + .swap = true}, + {.offset = offsetof(segment_command_64, fileoff), + .size = 8, + .swap = true}, + {.offset = offsetof(segment_command_64, filesize), + .size = 8, + .swap = true}, + {.offset = offsetof(segment_command_64, maxprot), + .size = 4, + .swap = true}, + {.offset = offsetof(segment_command_64, initprot), + .size = 4, + .swap = true}, + {.offset = offsetof(segment_command_64, nsects), + .size = 4, + .swap = true}, + {.offset = offsetof(segment_command_64, flags), + .size = 4, + .swap = true}}}; for (const auto& f : fields) { if (f.swap) { if (f.size == 4) { - uint32_t* ptr = reinterpret_cast(base + f.offset); + auto* ptr = reinterpret_cast(base + f.offset); *ptr = __builtin_bswap32(*ptr); } else if (f.size == 8) { - uint64_t* ptr = reinterpret_cast(base + f.offset); + auto* ptr = reinterpret_cast(base + f.offset); *ptr = __builtin_bswap64(*ptr); } } @@ -65,26 +98,27 @@ struct SwapDescriptor { static void swap(section_64* section) { char* base = reinterpret_cast(section); - constexpr FieldInfo fields[] = {{offsetof(section_64, sectname), 16, false}, - {offsetof(section_64, segname), 16, false}, - {offsetof(section_64, addr), 8, true}, - {offsetof(section_64, size), 8, true}, - {offsetof(section_64, offset), 4, true}, - {offsetof(section_64, align), 4, true}, - {offsetof(section_64, reloff), 4, true}, - {offsetof(section_64, nreloc), 4, true}, - {offsetof(section_64, flags), 4, true}, - {offsetof(section_64, reserved1), 4, true}, - {offsetof(section_64, reserved2), 4, true}, - {offsetof(section_64, reserved3), 4, true}}; + constexpr std::array fields = { + {{.offset = offsetof(section_64, sectname), .size = 16, .swap = false}, + {.offset = offsetof(section_64, segname), .size = 16, .swap = false}, + {.offset = offsetof(section_64, addr), .size = 8, .swap = true}, + {.offset = offsetof(section_64, size), .size = 8, .swap = true}, + {.offset = offsetof(section_64, offset), .size = 4, .swap = true}, + {.offset = offsetof(section_64, align), .size = 4, .swap = true}, + {.offset = offsetof(section_64, reloff), .size = 4, .swap = true}, + {.offset = offsetof(section_64, nreloc), .size = 4, .swap = true}, + {.offset = offsetof(section_64, flags), .size = 4, .swap = true}, + {.offset = offsetof(section_64, reserved1), .size = 4, .swap = true}, + {.offset = offsetof(section_64, reserved2), .size = 4, .swap = true}, + {.offset = offsetof(section_64, reserved3), .size = 4, .swap = true}}}; for (const auto& f : fields) { if (f.swap) { if (f.size == 4) { - uint32_t* ptr = reinterpret_cast(base + f.offset); + auto* ptr = reinterpret_cast(base + f.offset); *ptr = __builtin_bswap32(*ptr); } else if (f.size == 8) { - uint64_t* ptr = reinterpret_cast(base + f.offset); + auto* ptr = reinterpret_cast(base + f.offset); *ptr = __builtin_bswap64(*ptr); } } @@ -93,3 +127,56 @@ struct SwapDescriptor { }; #endif + +enum class PlatformType : std::uint8_t { MACH, LINUX, WIN, UNSUPPORTED }; +consteval static PlatformType getPlatform() { + PlatformType t = PlatformType::UNSUPPORTED; +#if defined(__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; + + char** data() { return ptrs.data(); } + + void prepend(const std::string& s) { + storage.insert(storage.begin(), std::vector(s.begin(), s.end())); + storage.front().push_back('\0'); + rebuildPtrs(); + } + + private: + void rebuildPtrs() { + ptrs.clear(); + ptrs.reserve(storage.size() + 1); + for (auto& s : storage) { + ptrs.push_back(s.data()); + } + ptrs.push_back(nullptr); + } +}; + +inline std::string toHex(u64 addr) { return std::format("{:#018x}", addr); } + +inline Expected strToAddr(const std::string& addr) { + u64 res = 0; + try { + res = static_cast(std::stoull(addr, nullptr, 0)); + return res; + } catch (std::invalid_argument& e) { + return Unexpected{ + std::format("Could not convert from {} to address!", addr)}; + } catch (std::out_of_range& e) { + return Unexpected{"Address provided is out of range!"}; + } +} + +#endif diff --git a/src/error.cpp b/src/error.cpp new file mode 100644 index 0000000..3b25bc0 --- /dev/null +++ b/src/error.cpp @@ -0,0 +1,37 @@ +#include "error.hpp" + +#include + +#include "formatter.hpp" + +void CoreError::_error(const std::string& msg) { + std::cerr << std::format("Error : {}\n", msg); + m_had_error = true; +} + +void CoreError::error(const std::string& msg) { + CoreError& e = CoreError::getInstance(); + e._error(msg); +} + +CoreError& CoreError::getInstance() { + static CoreError instance; + return instance; +} + +void CmdError::_error(TokenType where, const std::string& msg, + CmdErrorType type) { + std::cerr << std::format("{} error at {} : {}\n", type, where, msg); + m_had_error = true; +} + +void CmdError::error(TokenType where, const std::string& msg, + CmdErrorType type) { + CmdError& e = CmdError::getInstance(); + e._error(where, msg, type); +} + +CmdError& CmdError::getInstance() { + static CmdError instance; + return instance; +} diff --git a/src/error.hpp b/src/error.hpp new file mode 100644 index 0000000..ad90945 --- /dev/null +++ b/src/error.hpp @@ -0,0 +1,71 @@ +#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; + +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) +class CoreError { + private: + CoreError() = default; + // NOLINTNEXTLINE(readability-identifier-naming) + void _error(const std::string& msg); + + public: + CoreError(const CoreError&) = delete; + CoreError(CoreError&&) = delete; + CoreError& operator=(const CoreError&) = delete; + CoreError& operator=(CoreError&&) = delete; + + bool m_had_error{false}; + static void error(const std::string& msg); + static CoreError& getInstance(); +}; + +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) +class CmdError { + private: + CmdError() = default; + // NOLINTNEXTLINE(readability-identifier-naming) + void _error(TokenType where, const std::string& msg, CmdErrorType type); + + public: + CmdError(CmdError& other) = delete; + CmdError& operator=(const CmdError& other) = delete; + CmdError(CmdError&& other) = delete; + CmdError& operator=(CmdError&& other) = delete; + + bool m_had_error{false}; + static void error(TokenType where, const std::string& msg, CmdErrorType type); + static CmdError& getInstance(); +}; + +template <> +struct std::formatter : std::formatter { + constexpr auto format(CmdErrorType type, auto& ctx) const { + const auto str = [] { + std::map res; +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define INSERT_ELEM(p) res.emplace(p, #p); + INSERT_ELEM(CmdErrorType::PARSE_ERROR); + INSERT_ELEM(CmdErrorType::SCAN_ERROR); + INSERT_ELEM(CmdErrorType::RUNTIME_ERROR); +#undef INSERT_ELEM + + return res; + }; + + return std::formatter::format(str()[type], ctx); + }; +}; + +#endif diff --git a/src/expected.hpp b/src/expected.hpp new file mode 100644 index 0000000..7015347 --- /dev/null +++ b/src/expected.hpp @@ -0,0 +1,41 @@ +#ifndef CAESAR_EXPECTED_HPP +#define CAESAR_EXPECTED_HPP + +#include +#include + +template +struct Unexpected { + E error; + explicit Unexpected(E e) : error(std::move(e)) {} +}; + +Unexpected(const char*) -> Unexpected; + +template +class Expected { + std::variant m_data; + + 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)) {} + // NOLINTEND(google-explicit-constructor) + + [[nodiscard]] bool hasValue() const { + return std::holds_alternative(m_data); + } + explicit operator bool() const { return hasValue(); } + + Result& value() { return std::get(m_data); } + const Result& value() const { return std::get(m_data); } + + Error& error() { return std::get(m_data); } + const Error& error() const { return std::get(m_data); } + + Result& operator*() { return value(); } + Result* operator->() { return &value(); } +}; + +#endif diff --git a/src/cmd/formatter.hpp b/src/formatter.hpp similarity index 91% rename from src/cmd/formatter.hpp rename to src/formatter.hpp index 448dbf3..deb7099 100644 --- a/src/cmd/formatter.hpp +++ b/src/formatter.hpp @@ -1,16 +1,10 @@ #ifndef FORMATTER_H #define FORMATTER_H -#include -#include -#include -#include -#include - -#include "callable.hpp" -#include "object.hpp" -#include "token.hpp" -#include "token_type.hpp" +#include +#include +#include +#include namespace std { template <> @@ -86,8 +80,8 @@ struct formatter { INSERT_ELEM(TokenType::STRING); INSERT_ELEM(TokenType::NUMBER); INSERT_ELEM(TokenType::NIL); - INSERT_ELEM(TokenType::TRUE); - INSERT_ELEM(TokenType::FALSE); + INSERT_ELEM(TokenType::BOOL_TRUE); + INSERT_ELEM(TokenType::BOOL_FALSE); INSERT_ELEM(TokenType::VAR); INSERT_ELEM(TokenType::END); #undef INSERT_ELEM diff --git a/src/main.cpp b/src/main.cpp index 20ab263..4f73cd9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,42 +1,43 @@ +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include #include -#include #include #include -#include #include #include #include -#include "cmd/error.hpp" -#include "cmd/interpreter.hpp" -#include "cmd/parser.hpp" -#include "cmd/scanner.hpp" -#include "cmd/token.hpp" -#include "object.hpp" -#include "stmnt.hpp" - namespace { // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -Error& e = Error::getInstance(); +CmdError& cmdError = CmdError::getInstance(); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +Context& ctx = Context::getInstance(); void run(const std::string& src) { auto s = Scanner(src); const std::vector tokens = s.scanTokens(); - if (e.had_error) { + if (cmdError.m_had_error) { return; // Stop if scan errors occurred } Parser p = Parser(tokens); std::unique_ptr const statement = p.parse(); - if (e.had_error) { + if (cmdError.m_had_error) { return; } Interpreter interpreter = Interpreter(); Object const result = interpreter.interpret(statement); - if (e.had_error) { + if (cmdError.m_had_error) { return; } if (!std::holds_alternative(result)) { @@ -44,16 +45,6 @@ void run(const std::string& src) { } } -void runFile(const std::string& path) { - std::ifstream const file(path); - std::stringstream buffer; - buffer << file.rdbuf(); - run(buffer.str()); - if (e.had_error) { - exit(65); // NOLINT(concurrency-mt-unsafe) - } -} - void runPrompt() { std::string line; @@ -68,45 +59,35 @@ void runPrompt() { continue; } run(line); - e.had_error = false; + cmdError.m_had_error = false; } std::cout << '\n'; } + +void runFile(const std::string& filePath) { + if (!std::filesystem::exists(filePath)) { + std::cout << "Target does not exist!\n"; + runPrompt(); + } else { + if (Target::isFileValid(filePath)) { + std::cout << std::format("Target set to {}\n", filePath); + Context::setTarget(Target::create(filePath)); + } else + // TODO: Add Mach-O FAT binary magic to Target::isFileValid + std::cout << "Target is valid but cannot be ran on current platform!\n"; + runPrompt(); + } +} } // namespace int main(int argc, char** argv) { - try { - if (argc > 2) { - std::cout << std::format("Usage: {} [script]\n", argv[0]); - return 64; - } else if (argc == 2) { - runFile(argv[1]); - } else { - runPrompt(); - } - } catch (const std::exception& e) { - std::cerr << std::format("Exception: {}\n", e.what()); - return 1; + if (argc > 2) { + std::cout << std::format("Usage: {} [file]\n", argv[0]); + return 64; + } else if (argc == 2) { + runFile(argv[1]); + } else { + runPrompt(); } - return 0; } - -// int main(int argc, char** argv) { -// if (argc > 2) { -// std::cout << "Usage: caesar [file]" << std::endl; -// return 1; -// } -// -// std::ifstream f(argv[1], std::ios::in | std::ios::binary); -// -// if (f.fail()) { -// std::cout << "File does not exist" << std::endl; -// f.close(); -// return 1; -// } -// -// Macho m = Macho(f); -// m.dump(); -// f.close(); -// } diff --git a/src/syscall_decode.cpp b/src/syscall_decode.cpp deleted file mode 100644 index 1ce3ebd..0000000 --- a/src/syscall_decode.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "syscall_decode.h" - -#include - -std::string SyscallDecoder::syscall_err(const struct user_regs_struct& regs) { - int err = -(int)regs.rax; - return fmt::format("{} (Syscall fail ({}))", err, strerror(err)); -} - -constexpr std::string_view SyscallDecoder::syscall_name( - const struct user_regs_struct& regs) { - return "Not implemented yet"; -} diff --git a/src/syscall_decode.h b/src/syscall_decode.h deleted file mode 100644 index 7703a02..0000000 --- a/src/syscall_decode.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef CAESAR_SYSCALL_DECODE_H -#define CAESAR_SYSCALL_DECODE_H - -#include - -#include - -class SyscallDecoder { - private: - // autogen this with m4, read from unistd_64.h for x86_64 - // static constexpr std::map syscall_names = - // std::map(); - public: - // provide human-readable syscall errors - static std::string syscall_err(const struct user_regs_struct& regs); - static constexpr std::string_view syscall_name( - const struct user_regs_struct& regs); -}; - -#endif // !CAESAR_SYSCALL_DECODE_H diff --git a/src/typedefs.hpp b/src/typedefs.hpp new file mode 100644 index 0000000..3a5de84 --- /dev/null +++ b/src/typedefs.hpp @@ -0,0 +1,15 @@ +#ifndef CAESAR_TYPEDEFS_H +#define CAESAR_TYPEDEFS_H + +#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&); + +#endif diff --git a/test.c b/test.c new file mode 100644 index 0000000..0cfca97 --- /dev/null +++ b/test.c @@ -0,0 +1,8 @@ +#include +#include +#include + +int main() { + printf("survived!\n"); + return 0; +} diff --git a/test/cmd/test_environment.cpp b/test/cmd/test_environment.cpp index 5dcf732..8232f3b 100644 --- a/test/cmd/test_environment.cpp +++ b/test/cmd/test_environment.cpp @@ -1,10 +1,11 @@ #include #include +#include +#include +#include #include #include -#include "environment.hpp" -#include "object.hpp" #include "test_helpers.hpp" TEST_CASE("Test environment singleton pattern", "[environment][singleton]") { @@ -168,3 +169,21 @@ TEST_CASE("Test environment type transitions", "[environment][types]") { REQUIRE(std::holds_alternative(result)); } } + +TEST_CASE("Test SubcommandHandler exec", "[subcommand][exec]") { + SubcommandHandler handler({{"test", helpers::testSubcmd}}, "testcmd"); + + SECTION("Execute valid subcommand") { + Object result = handler.exec("test", {"hello"}); + REQUIRE(std::get(result) == "hello"); + } + + SECTION("Execute invalid subcommand produces error") { + auto captured = helpers::captureStream(std::cerr, [&handler]() { + Object result = handler.exec("invalid", {}); + REQUIRE(std::holds_alternative(result)); + }); + REQUIRE(captured.find("Subcommand invalid is not valid") != + std::string::npos); + } +} diff --git a/test/cmd/test_helpers.hpp b/test/cmd/test_helpers.hpp index 8ba3b04..1bdfdad 100644 --- a/test/cmd/test_helpers.hpp +++ b/test/cmd/test_helpers.hpp @@ -2,6 +2,11 @@ #define CAESAR_TEST_HELPERS_HPP #include +#include +#include +#include +#include +#include #include #include #include @@ -9,11 +14,6 @@ #include #include -#include "expr.hpp" -#include "parser.hpp" -#include "scanner.hpp" -#include "stmnt.hpp" -#include "token.hpp" namespace helpers { // Pass here expected amount of tokens WITHOUT end token inline bool checkTokensSize(const size_t actual, const size_t expected) { @@ -35,8 +35,8 @@ inline std::unique_ptr getStmnt(const std::string& input) { } template -inline EType* getTopExpr(const std::unique_ptr& p_stmnt) { - auto stmnt = dynamic_cast(p_stmnt.get()); +inline EType* getTopExpr(const std::unique_ptr& pStmnt) { + auto* stmnt = dynamic_cast(pStmnt.get()); REQUIRE(stmnt != nullptr); auto expr = dynamic_cast(stmnt->m_expr.get()); @@ -71,10 +71,12 @@ class AutoRestoreRdbuf { public: ~AutoRestoreRdbuf() { out.rdbuf(old); } + AutoRestoreRdbuf& operator=(const AutoRestoreRdbuf&) = delete; + AutoRestoreRdbuf& operator=(AutoRestoreRdbuf&&) = delete; AutoRestoreRdbuf(const AutoRestoreRdbuf&) = delete; AutoRestoreRdbuf(AutoRestoreRdbuf&&) = delete; - AutoRestoreRdbuf(std::ostream& out) : out(out), old(out.rdbuf()) {} + explicit AutoRestoreRdbuf(std::ostream& out) : out(out), old(out.rdbuf()) {} }; template @@ -89,6 +91,11 @@ inline std::string captureStream(std::ostream& out, Func&& fn, Args&&... args) { inline Token makeToken(const std::string& name) { return Token(TokenType::IDENTIFIER, name, std::monostate{}); } + +// Simple subcommand for testing +inline Object testSubcmd(const std::vector& args) { + return args.empty() ? std::string("ok") : args[0]; +} } // namespace helpers #endif diff --git a/test/cmd/test_interpreter.cpp b/test/cmd/test_interpreter.cpp index 98d6bda..89a2813 100644 --- a/test/cmd/test_interpreter.cpp +++ b/test/cmd/test_interpreter.cpp @@ -1,10 +1,10 @@ #include #include +#include +#include #include #include -#include "interpreter.hpp" -#include "object.hpp" #include "test_helpers.hpp" TEST_CASE("Test truthy behavior through bang operator", @@ -316,14 +316,19 @@ TEST_CASE("Test visitCallStmnt - function argument mismatch errors", "[interpreter][visitCallStmnt][error]") { Interpreter interp; - SECTION("Too many arguments to print") { + SECTION("Too few arguments to print") { auto captured = helpers::captureStream(std::cerr, [&interp]() { - auto stmnt = helpers::getStmnt("print 42 43"); + auto stmnt = helpers::getStmnt("print"); interp.interpret(stmnt); }); - REQUIRE( - captured.find("Function requires 1 arguments but 2 were provided") != - std::string::npos); + REQUIRE(captured.find("Function requires at least 1 arguments") != + std::string::npos); + } + + SECTION("Extra arguments are allowed for subcommand handling") { + auto stmnt = helpers::getStmnt("print 42 43"); + Object result = interp.interpret(stmnt); + REQUIRE(result == Object{42.0}); } } @@ -351,3 +356,49 @@ TEST_CASE("Test multiple variables in expression", Object result = interp.interpret(exprStmnt); REQUIRE(result == Object{5.0}); } + +TEST_CASE("Test unary minus on non-number produces error", + "[interpreter][unary][error]") { + Interpreter interp; + + SECTION("Unary minus on string") { + auto stmnt = helpers::getStmnt("-\"hello\""); + auto captured = helpers::captureStream(std::cerr, [&interp, &stmnt]() { + Object result = interp.interpret(stmnt); + REQUIRE(result == Object{std::monostate{}}); + }); + REQUIRE(captured.find("Operand must be a number") != std::string::npos); + } + + SECTION("Unary minus on boolean") { + auto stmnt = helpers::getStmnt("-true"); + auto captured = helpers::captureStream(std::cerr, [&interp, &stmnt]() { + Object result = interp.interpret(stmnt); + REQUIRE(result == Object{std::monostate{}}); + }); + REQUIRE(captured.find("Operand must be a number") != std::string::npos); + } +} + +TEST_CASE("Test calling non-callable with arguments produces error", + "[interpreter][visitCallStmnt][error]") { + Interpreter interp; + auto declStmnt = helpers::getStmnt("var notafunc = 42"); + interp.interpret(declStmnt); + + auto captured = helpers::captureStream(std::cerr, [&interp]() { + auto stmnt = helpers::getStmnt("notafunc \"arg\""); + interp.interpret(stmnt); + }); + REQUIRE(captured.find("is not callable") != std::string::npos); +} + +TEST_CASE("Test function call with unknown identifier as string argument", + "[interpreter][visitCallStmnt]") { + Interpreter interp; + // When an identifier is not defined, it's passed as a string literal + auto stmnt = helpers::getStmnt("len unknownident"); + auto captured = helpers::captureStream( + std::cerr, [&interp, &stmnt]() { interp.interpret(stmnt); }); + REQUIRE(captured.empty()); +} diff --git a/test/cmd/test_object.cpp b/test/cmd/test_object.cpp index 8503bc3..3da9f6e 100644 --- a/test/cmd/test_object.cpp +++ b/test/cmd/test_object.cpp @@ -1,9 +1,9 @@ #include #include #include +#include #include -#include "object.hpp" #include "test_helpers.hpp" // This whole file is ugly but it works and I can't find it a better way to do diff --git a/test/cmd/test_parser.cpp b/test/cmd/test_parser.cpp index e18d601..0729e12 100644 --- a/test/cmd/test_parser.cpp +++ b/test/cmd/test_parser.cpp @@ -1,14 +1,14 @@ #include #include +#include +#include +#include +#include #include #include #include -#include "expr.hpp" -#include "object.hpp" -#include "stmnt.hpp" #include "test_helpers.hpp" -#include "token.hpp" TEST_CASE("Test literal parsing", "[parser][expression][literal]") { auto [input, expected_value] = @@ -29,7 +29,7 @@ TEST_CASE("Test literal parsing", "[parser][expression][literal]") { SECTION("Function parsing") { auto stmnt = helpers::getStmnt("print 5"); - auto callStmnt = dynamic_cast(stmnt.get()); + auto* callStmnt = dynamic_cast(stmnt.get()); REQUIRE(callStmnt != nullptr); REQUIRE(callStmnt->m_fn.m_type == TokenType::IDENTIFIER); } @@ -53,15 +53,15 @@ TEST_CASE("Test variable parsing", "[parser][variable][assign]") { SECTION("Test variable parsing with functions as values") { auto stmnt = helpers::getStmnt("var x = print"); - auto varStmnt = dynamic_cast(stmnt.get()); + auto* varStmnt = dynamic_cast(stmnt.get()); REQUIRE(varStmnt != nullptr); - auto var = dynamic_cast(varStmnt->m_initialiser.get()); + auto* var = dynamic_cast(varStmnt->m_initialiser.get()); REQUIRE(var != nullptr); } SECTION("Variable re-assignment") { auto stmnt = helpers::getStmnt("x = 5"); - auto assign = helpers::getTopExpr(stmnt); + auto* assign = helpers::getTopExpr(stmnt); REQUIRE(assign->m_name.m_lexeme == "x"); } } @@ -75,8 +75,8 @@ TEST_CASE("Test grouping expression parsing", SECTION("Grouping with operator") { auto stmnt = helpers::getStmnt("(5+3)"); - auto grouping = helpers::getTopExpr(stmnt); - auto binary = dynamic_cast(grouping->m_expr.get()); + auto* grouping = helpers::getTopExpr(stmnt); + auto* binary = dynamic_cast(grouping->m_expr.get()); REQUIRE(binary != nullptr); REQUIRE(binary->m_op.m_type == TokenType::PLUS); @@ -84,32 +84,32 @@ TEST_CASE("Test grouping expression parsing", SECTION("Grouping overrides precedence") { auto stmnt = helpers::getStmnt("(2+3)*4"); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == TokenType::STAR); - auto grouping = dynamic_cast(binary->m_left.get()); + auto* grouping = dynamic_cast(binary->m_left.get()); REQUIRE(grouping != nullptr); } SECTION("Grouping on the right side") { auto stmnt = helpers::getStmnt("2*(3+4)"); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == TokenType::STAR); - auto grouping = dynamic_cast(binary->m_right.get()); + auto* grouping = dynamic_cast(binary->m_right.get()); REQUIRE(grouping != nullptr); } SECTION("Grouping with unary operator") { auto stmnt = helpers::getStmnt("-(3+4)"); - auto unary = helpers::getTopExpr(stmnt); + auto* unary = helpers::getTopExpr(stmnt); REQUIRE(unary->m_op.m_type == TokenType::MINUS); - auto grouping = dynamic_cast(unary->m_right.get()); + auto* grouping = dynamic_cast(unary->m_right.get()); REQUIRE(grouping != nullptr); } SECTION("String grouping expression") { - auto stmnt = helpers::getStmnt("(\"hello\"+\"world\")"); - auto grouping = helpers::getTopExpr(stmnt); - auto binary = dynamic_cast(grouping->m_expr.get()); + auto stmnt = helpers::getStmnt(R"(("hello"+"world"))"); + auto* grouping = helpers::getTopExpr(stmnt); + auto* binary = dynamic_cast(grouping->m_expr.get()); REQUIRE(binary != nullptr); REQUIRE(binary->m_op.m_type == TokenType::PLUS); } @@ -118,13 +118,13 @@ TEST_CASE("Test grouping expression parsing", TEST_CASE("Test unary expression parsing", "[parser][expression][unary]") { SECTION("Unary expression on number") { auto stmnt = helpers::getStmnt("-5"); - auto unary = helpers::getTopExpr(stmnt); + auto* unary = helpers::getTopExpr(stmnt); REQUIRE(unary->m_op.m_type == TokenType::MINUS); } SECTION("Unary expression on boolean") { auto stmnt = helpers::getStmnt("!true"); - auto unary = helpers::getTopExpr(stmnt); + auto* unary = helpers::getTopExpr(stmnt); REQUIRE(unary->m_op.m_type == TokenType::BANG); } } @@ -138,7 +138,7 @@ TEST_CASE("Test arithmetic operator parsing", {"/", TokenType::SLASH}})); auto stmnt = helpers::getStmnt(std::format("3{}5", op)); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == tokenType); } @@ -146,30 +146,30 @@ TEST_CASE("Test arithmetic operator precedence and associativity", "[parser][expression][operator][binary]") { SECTION("Multiplication before addition") { auto stmnt = helpers::getStmnt("2+3*4"); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == TokenType::PLUS); - auto rhs = dynamic_cast(binary->m_right.get()); + auto* rhs = dynamic_cast(binary->m_right.get()); REQUIRE(rhs != nullptr); REQUIRE(rhs->m_op.m_type == TokenType::STAR); } SECTION("Division before subtraction") { auto stmnt = helpers::getStmnt("3-4/2"); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == TokenType::MINUS); - auto rhs = dynamic_cast(binary->m_right.get()); + auto* rhs = dynamic_cast(binary->m_right.get()); REQUIRE(rhs != nullptr); REQUIRE(rhs->m_op.m_type == TokenType::SLASH); } SECTION("Associativity") { auto stmnt = helpers::getStmnt("5+3-2"); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == TokenType::MINUS); - auto lhs = dynamic_cast(binary->m_left.get()); + auto* lhs = dynamic_cast(binary->m_left.get()); REQUIRE(lhs != nullptr); REQUIRE(lhs->m_op.m_type == TokenType::PLUS); } @@ -184,44 +184,44 @@ TEST_CASE("Test comparison operators", "[parser][expressions][operators]") { {">=", TokenType::GREATER_EQUAL}})); auto stmnt = helpers::getStmnt(std::format("3{}5", op)); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == tokenType); } SECTION("Arithmetic before comparison") { auto stmnt = helpers::getStmnt("2 + 3 < 4 * 2"); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == TokenType::LESS); - auto lhs = dynamic_cast(binary->m_left.get()); + auto* lhs = dynamic_cast(binary->m_left.get()); REQUIRE(lhs != nullptr); REQUIRE(lhs->m_op.m_type == TokenType::PLUS); - auto rhs = dynamic_cast(binary->m_right.get()); + auto* rhs = dynamic_cast(binary->m_right.get()); REQUIRE(rhs != nullptr); REQUIRE(rhs->m_op.m_type == TokenType::STAR); } SECTION("Subtraction before comparison") { auto stmnt = helpers::getStmnt("10 - 2 > 5"); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == TokenType::GREATER); - auto lhs = dynamic_cast(binary->m_left.get()); + auto* lhs = dynamic_cast(binary->m_left.get()); REQUIRE(lhs != nullptr); REQUIRE(lhs->m_op.m_type == TokenType::MINUS); } SECTION("Comparison before equality") { auto stmnt = helpers::getStmnt("3+5 == 5+3"); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == TokenType::EQUAL_EQUAL); - auto lhs = dynamic_cast(binary->m_left.get()); + auto* lhs = dynamic_cast(binary->m_left.get()); REQUIRE(lhs != nullptr); REQUIRE(lhs->m_op.m_type == TokenType::PLUS); - auto rhs = dynamic_cast(binary->m_right.get()); + auto* rhs = dynamic_cast(binary->m_right.get()); REQUIRE(rhs != nullptr); REQUIRE(rhs->m_op.m_type == TokenType::PLUS); } @@ -233,7 +233,7 @@ TEST_CASE("Test equality operator parsing", {{"==", TokenType::EQUAL_EQUAL}, {"!=", TokenType::BANG_EQUAL}})); auto stmnt = helpers::getStmnt(std::format("3{}5", op)); - auto binary = helpers::getTopExpr(stmnt); + auto* binary = helpers::getTopExpr(stmnt); REQUIRE(binary->m_op.m_type == tokenType); } @@ -260,3 +260,87 @@ TEST_CASE("Test parser error handling", "[parser][expressions][errors]") { REQUIRE(captured.find("Invalid assignment target") != std::string::npos); } } + +TEST_CASE("Test expression str() methods", "[expr][str]") { + SECTION("Literal str()") { + Literal numLit(42.0); + REQUIRE(numLit.str() == "42"); + + Literal strLit(std::string("hello")); + REQUIRE(strLit.str() == "hello"); + + Literal boolLit(true); + REQUIRE(boolLit.str() == "true"); + + Literal nilLit(std::monostate{}); + REQUIRE(nilLit.str() == "(null)"); + } + + SECTION("Variable str()") { + Token tok(TokenType::IDENTIFIER, "myvar", std::monostate{}); + Variable var(tok); + REQUIRE(var.str().find("myvar") != std::string::npos); + } + + SECTION("Unary str()") { + Token minusOp(TokenType::MINUS, "-", std::monostate{}); + auto lit = std::make_unique(5.0); + Unary unary(minusOp, std::move(lit)); + auto str = unary.str(); + REQUIRE(str.find('-') != std::string::npos); + REQUIRE(str.find('5') != std::string::npos); + } + + SECTION("Binary str()") { + Token plusOp(TokenType::PLUS, "+", std::monostate{}); + auto left = std::make_unique(2.0); + auto right = std::make_unique(3.0); + Binary binary(std::move(left), plusOp, std::move(right)); + auto str = binary.str(); + REQUIRE(str.find('+') != std::string::npos); + REQUIRE(str.find('2') != std::string::npos); + REQUIRE(str.find('3') != std::string::npos); + } + + SECTION("Grouping str()") { + auto lit = std::make_unique(42.0); + Grouping group(std::move(lit)); + REQUIRE(group.str().find("42") != std::string::npos); + } + + SECTION("Assign str()") { + Token name(TokenType::IDENTIFIER, "x", std::monostate{}); + auto value = std::make_unique(10.0); + Assign assign(name, std::move(value)); + auto str = assign.str(); + REQUIRE(str.find('x') != std::string::npos); + REQUIRE(str.find("10") != std::string::npos); + } +} + +TEST_CASE("Test expression formatters", "[expr][formatter]") { + SECTION("Format unique_ptr") { + std::unique_ptr expr = std::make_unique(123.0); + std::string formatted = std::format("{}", expr); + REQUIRE(formatted == "123"); + } + + SECTION("Format null unique_ptr") { + std::unique_ptr expr = nullptr; + std::string formatted = std::format("{}", expr); + REQUIRE(formatted == "null"); + } + + SECTION("Format Expr*") { + Literal lit(456.0); + Expr* ptr = &lit; + std::string formatted = std::format("{}", ptr); + REQUIRE(formatted == "456"); + } + + SECTION("Format null Expr*") { + Expr* ptr = nullptr; + std::string formatted = std::format("{}", ptr); + REQUIRE(formatted == "null"); + } +} diff --git a/test/cmd/test_scanner.cpp b/test/cmd/test_scanner.cpp index b2a4b0b..24afd06 100644 --- a/test/cmd/test_scanner.cpp +++ b/test/cmd/test_scanner.cpp @@ -1,8 +1,8 @@ #include #include +#include #include "test_helpers.hpp" -#include "token.hpp" TEST_CASE("Test single-character tokenisation", "[scanner]") { auto [input, expected_type] = diff --git a/test/cmd/test_stdlib.cpp b/test/cmd/test_stdlib.cpp new file mode 100644 index 0000000..4dddf2f --- /dev/null +++ b/test/cmd/test_stdlib.cpp @@ -0,0 +1,71 @@ +#include +#include +#include + +#include "test_helpers.hpp" + +TEST_CASE("Test PrintFn", "[stdlib][print]") { + PrintFn print; + + SECTION("arity and str") { + REQUIRE(print.arity() == 1); + REQUIRE(print.str() == ""); + } + + SECTION("call returns first argument") { + std::vector args = {42.0}; + Object result = print.call(args); + REQUIRE(std::get(result) == 42.0); + } + + SECTION("call with string") { + std::vector args = {std::string("test")}; + Object result = print.call(args); + REQUIRE(std::get(result) == "test"); + } +} + +TEST_CASE("Test BreakpointFn without target", "[stdlib][breakpoint]") { + BreakpointFn bp; + + SECTION("arity and str") { + REQUIRE(bp.arity() == 1); + REQUIRE(bp.str() == ""); + } + + 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); + } +} + +TEST_CASE("Test RunFn without target", "[stdlib][run]") { + RunFn run; + + SECTION("arity and str") { + REQUIRE(run.arity() == 0); + REQUIRE(run.str() == ""); + } + + SECTION("call when target is null produces error") { + std::vector args = {}; + auto captured = helpers::captureStream(std::cerr, [&run, &args]() { + Object result = run.call(args); + REQUIRE(std::holds_alternative(result)); + }); + REQUIRE(captured.find("Target is not set") != std::string::npos); + } +} + +TEST_CASE("Test ContinueFn properties", "[stdlib][continue]") { + ContinueFn cont; + + SECTION("arity and str") { + REQUIRE(cont.arity() == 0); + REQUIRE(cont.str() == ""); + } +} diff --git a/vcpkg b/vcpkg index b1e15ef..66c0373 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit b1e15efef6758eaa0beb0a8732cfa66f6a68a81d +Subproject commit 66c0373dc7fca549e5803087b9487edfe3aca0a1