From 38252ae1bc596c51c901e9a890aea44ab8c93216 Mon Sep 17 00:00:00 2001 From: water Date: Fri, 30 Oct 2020 22:15:38 -0400 Subject: [PATCH 1/8] set up the compiler to ptrace the runtime --- CMakeLists.txt | 3 + common/cross_os_debug/CMakeLists.txt | 2 + common/cross_os_debug/xdbg.cpp | 102 ++++++++++++ common/cross_os_debug/xdbg.h | 47 ++++++ common/listener_common.h | 7 +- game/CMakeLists.txt | 8 +- game/kernel/kboot.cpp | 4 + game/kernel/kdsnetm.cpp | 22 +-- game/kernel/kdsnetm.h | 14 +- game/kernel/klisten.cpp | 14 +- game/kernel/kprint.cpp | 35 +++-- game/kernel/ksocket.cpp | 18 +-- goalc/CMakeLists.txt | 1 + goalc/compiler/Compiler.cpp | 29 ++-- goalc/compiler/Compiler.h | 12 ++ goalc/compiler/compilation/Atoms.cpp | 3 + goalc/compiler/compilation/Debug.cpp | 22 +++ goalc/listener/CMakeLists.txt | 4 +- goalc/listener/Listener.cpp | 146 +++++++++++++++++- goalc/listener/Listener.h | 20 ++- .../with_game/attach-debugger.gc | 1 + test/goalc/test_with_game.cpp | 21 ++- test/test_kernel.cpp | 2 +- 23 files changed, 453 insertions(+), 84 deletions(-) create mode 100644 common/cross_os_debug/CMakeLists.txt create mode 100644 common/cross_os_debug/xdbg.cpp create mode 100644 common/cross_os_debug/xdbg.h create mode 100644 goalc/compiler/compilation/Debug.cpp create mode 100644 test/goalc/source_templates/with_game/attach-debugger.gc diff --git a/CMakeLists.txt b/CMakeLists.txt index 58f6dadcfb..90658485a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,9 @@ add_subdirectory(common/util) # build cross platform socket library add_subdirectory(common/cross_sockets) +# build cross platform debug library +add_subdirectory(common/cross_os_debug) + # build decompiler add_subdirectory(decompiler) diff --git a/common/cross_os_debug/CMakeLists.txt b/common/cross_os_debug/CMakeLists.txt new file mode 100644 index 0000000000..74bb2b04ba --- /dev/null +++ b/common/cross_os_debug/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(cross_os_debug SHARED + xdbg.cpp) \ No newline at end of file diff --git a/common/cross_os_debug/xdbg.cpp b/common/cross_os_debug/xdbg.cpp new file mode 100644 index 0000000000..7bc42120a4 --- /dev/null +++ b/common/cross_os_debug/xdbg.cpp @@ -0,0 +1,102 @@ +#include +#include "xdbg.h" + +#ifdef __linux +#include +#include +#include +#include +#include +#include +#elif _WIN32 + +#endif + +namespace xdbg { +#ifdef __linux + +ThreadID::ThreadID(pid_t _id) : id(_id) {} + +ThreadID::ThreadID(const std::string& str) { + id = std::stoi(str); +} + +std::string ThreadID::to_string() const { + return std::to_string(id); +} + +ThreadID get_current_thread_id() { + return ThreadID(syscall(SYS_gettid)); +} + +bool attach_and_break(const ThreadID& tid) { + auto rv = ptrace(PTRACE_ATTACH, tid.id, nullptr, nullptr); + if (rv == -1) { + printf("[Debugger] Failed to attach %s\n", strerror(errno)); + return false; + } else { + printf("[Debugger] PTRACE_ATTACHED! Waiting for process to stop...\n"); + + // we could technically hang here forever if runtime ignores the signal. + int status; + if (waitpid(tid.id, &status, 0) < 0) { + printf("[Debugger] Failed to waitpid: %s. The runtime is probably in a bad state now.\n", + strerror(errno)); + return false; + } + + if (!WIFSTOPPED(status)) { + printf("[Debugger] Failed to STOP: %s. The runtime is probably in a bad state now.\n", + strerror(errno)); + return false; + } + return true; + } +} + +void allow_debugging() { + // modern Linux has "security features" which prevent processes from accessing memory of others. + // we disable these for the GOAL runtime process so the debugger can connect. + if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) < 0) { + printf("[Debugger] Failed to PR_SET_PTRACER %s\n", strerror(errno)); + } +} + +bool detach_and_resume(const ThreadID& tid) { + if (ptrace(PTRACE_DETACH, tid.id, nullptr, nullptr) < 0) { + printf("[Debugger] Failed to detach: %s\n", strerror(errno)); + return false; + } + return true; +} + +#elif _WIN32 + +ThreadID::ThreadID() {} // todo + +ThreadID::ThreadID(const std::string& str) { + // todo +} + +std::string ThreadID::to_string() const { + // todo + return "0"; +} + +ThreadID get_current_thread_id() { + // todo + return {}; +} + +bool attach_and_break(const ThreadID& tid) { + return false; +} + +bool detach_and_resume(const ThreadID& tid) { + return false; +} + +void allow_debugging() {} + +#endif +} // namespace xdbg diff --git a/common/cross_os_debug/xdbg.h b/common/cross_os_debug/xdbg.h new file mode 100644 index 0000000000..afdf475e07 --- /dev/null +++ b/common/cross_os_debug/xdbg.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#ifdef __linux +#include +#elif _WIN32 +// todo - windows includes +#endif + +namespace xdbg { +#ifdef __linux + +struct ThreadID { + pid_t id = 0; + + std::string to_string() const; + explicit ThreadID(const std::string& str); + explicit ThreadID(pid_t _id); + ThreadID() = default; +}; + +#elif _WIN32 +struct ThreadID { + // todo - whatever windows uses to identify a thread + std::string to_string() const; + ThreadID(const std::string& str); + ThreadID(); // todo - add id type here, like in linux version +}; +#endif + +struct DebugContext { + ThreadID tid; + uintptr_t base; + uint32_t s7; + bool valid = false; + bool running = true; +}; + +// Functions +ThreadID get_current_thread_id(); +bool attach_and_break(const ThreadID& tid); +void allow_debugging(); +bool detach_and_resume(const ThreadID& tid); + +} // namespace xdbg diff --git a/common/listener_common.h b/common/listener_common.h index d2c0d5a3cc..979a34b491 100644 --- a/common/listener_common.h +++ b/common/listener_common.h @@ -46,7 +46,6 @@ enum ListenerToTargetMsgKind : u16 { /*! * The full header of a listener message, including the Deci2Header - * TODO - there are other copies of this somewhere */ struct ListenerMessageHeader { Deci2Header deci2_header; //! The header used for DECI2 communication @@ -57,9 +56,11 @@ struct ListenerMessageHeader { u16 u6; //! Unknown u32 msg_size; //! Size of data after this header - u64 u8; //! Unknown + u64 msg_id; //! Message ID number, target echoes this back. }; -constexpr int DECI2_PORT = 8112; // TODO - is this a good choise? +constexpr int DECI2_PORT = 8112; // TODO - is this a good choice? + +constexpr u16 DECI2_PROTOCOL = 0xe042; #endif // JAK1_LISTENER_COMMON_H diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index e815845b7c..db0f8696fd 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -81,12 +81,12 @@ add_executable(gk main.cpp) IF (WIN32) # set stuff for windows - target_link_libraries(runtime mman cross_sockets common_util spdlog) - target_link_libraries(gk cross_sockets mman common_util runtime spdlog) + target_link_libraries(runtime mman cross_sockets common_util spdlog cross_os_debug) + target_link_libraries(gk cross_sockets mman common_util runtime spdlog cross_os_debug) ELSE() # set stuff for other systems - target_link_libraries(runtime pthread cross_sockets common_util spdlog) - target_link_libraries(gk cross_sockets pthread common_util runtime spdlog) + target_link_libraries(runtime pthread cross_sockets common_util spdlog cross_os_debug) + target_link_libraries(gk cross_sockets pthread common_util runtime spdlog cross_os_debug) ENDIF() diff --git a/game/kernel/kboot.cpp b/game/kernel/kboot.cpp index ace3785582..0f41b9c303 100644 --- a/game/kernel/kboot.cpp +++ b/game/kernel/kboot.cpp @@ -8,6 +8,7 @@ #include #include +#include "common/cross_os_debug/xdbg.h" #include "common/common_types.h" #include "game/sce/libscf.h" #include "kboot.h" @@ -76,6 +77,9 @@ void kboot_init_globals() { * Add call to sceDeci2Reset when GOAL shuts down. */ s32 goal_main(int argc, const char* const* argv) { + // Added for OpenGOAL's debugger + xdbg::allow_debugging(); + // Initialize global variables based on command line parameters // This call is not present in the retail version of the game // but the function is, and it likely goes here. diff --git a/game/kernel/kdsnetm.cpp b/game/kernel/kdsnetm.cpp index da07a7a8bb..a45e965c95 100644 --- a/game/kernel/kdsnetm.cpp +++ b/game/kernel/kdsnetm.cpp @@ -37,7 +37,7 @@ void InitGoalProto() { MsgErr("gproto: open proto error\n"); } else { protoBlock.send_buffer = nullptr; - protoBlock.receive_buffer = MessBufArea.cast().c(); + protoBlock.receive_buffer = MessBufArea.cast().c(); protoBlock.send_status = -1; protoBlock.last_receive_size = -1; protoBlock.receive_progress = 0; @@ -171,29 +171,29 @@ s32 SendFromBufferD(s32 msg_kind, u64 p2, char* data, s32 size) { // retry at most 10 times until we complete without an error. for (s32 i = 0; i < 10; i++) { // or'd with 0x20000000 to get noncache version - GoalMessageHeader* header = (GoalMessageHeader*)(data - sizeof(GoalMessageHeader)); - protoBlock.send_remaining = size + sizeof(GoalMessageHeader); + ListenerMessageHeader* header = (ListenerMessageHeader*)(data - sizeof(ListenerMessageHeader)); + protoBlock.send_remaining = size + sizeof(ListenerMessageHeader); protoBlock.send_buffer = header; protoBlock.send_ptr = (u8*)header; - protoBlock.send_status = size + sizeof(GoalMessageHeader); + protoBlock.send_status = size + sizeof(ListenerMessageHeader); // FlushCache(0); // set DECI2 message header - header->deci2_hdr.len = protoBlock.send_remaining; - header->deci2_hdr.rsvd = 0; - header->deci2_hdr.proto = DECI2_PROTOCOL; - header->deci2_hdr.src = 'E'; // from EE - header->deci2_hdr.dst = 'H'; // to HOST + header->deci2_header.len = protoBlock.send_remaining; + header->deci2_header.rsvd = 0; + header->deci2_header.proto = DECI2_PROTOCOL; + header->deci2_header.src = 'E'; // from EE + header->deci2_header.dst = 'H'; // to HOST // set GOAL message header - header->msg_kind = (u16)msg_kind; + header->msg_kind = (ListenerMessageKind)msg_kind; header->u6 = 0; header->msg_size = size; header->msg_id = p2; // start send! - auto rv = sceDeci2ReqSend(protoBlock.socket, header->deci2_hdr.dst); + auto rv = sceDeci2ReqSend(protoBlock.socket, header->deci2_header.dst); if (rv < 0) { printf("1sceDeci2ReqSend fail, reason code = %08x\n", rv); return 0xfffffffa; diff --git a/game/kernel/kdsnetm.h b/game/kernel/kdsnetm.h index 7326f74f23..1ebc21d05e 100644 --- a/game/kernel/kdsnetm.h +++ b/game/kernel/kdsnetm.h @@ -12,20 +12,10 @@ #include "Ptr.h" #include "common/listener_common.h" -struct GoalMessageHeader { - Deci2Header deci2_hdr; - u16 msg_kind; - u16 u6; - u32 msg_size; - u64 msg_id; -}; - -constexpr u16 DECI2_PROTOCOL = 0xe042; - struct GoalProtoBlock { s32 socket = 0; - GoalMessageHeader* send_buffer = nullptr; - GoalMessageHeader* receive_buffer = nullptr; + ListenerMessageHeader* send_buffer = nullptr; + ListenerMessageHeader* receive_buffer = nullptr; u8* send_ptr = nullptr; s32 send_remaining = 0; s32 send_status = diff --git a/game/kernel/klisten.cpp b/game/kernel/klisten.cpp index 322d049e33..01b1457aa7 100644 --- a/game/kernel/klisten.cpp +++ b/game/kernel/klisten.cpp @@ -49,7 +49,7 @@ void InitListener() { new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE).cast()), make_string_from_c("kernel"), kernel_packages->value); // if(MasterDebug) { - // SendFromBufferD(MSG_ACK, 0, AckBufArea + sizeof(GoalMessageHeader), 0); + // SendFromBufferD(MSG_ACK, 0, AckBufArea + sizeof(ListenerMessageHeader), 0); // } } @@ -60,15 +60,15 @@ void ClearPending() { if (!MasterDebug) { // if we aren't debugging print the print buffer to stdout. if (PrintPending.offset != 0) { - auto size = strlen(PrintBufArea.cast().c() + sizeof(GoalMessageHeader)); + auto size = strlen(PrintBufArea.cast().c() + sizeof(ListenerMessageHeader)); if (size > 0) { - printf("%s", PrintBufArea.cast().c() + sizeof(GoalMessageHeader)); + printf("%s", PrintBufArea.cast().c() + sizeof(ListenerMessageHeader)); } } } else { if (ListenerStatus) { if (OutputPending.offset != 0) { - Ptr msg = OutputBufArea.cast() + sizeof(GoalMessageHeader); + Ptr msg = OutputBufArea.cast() + sizeof(ListenerMessageHeader); auto size = strlen(msg.c()); // note - if size is ever greater than 2^16 this will cause an issue. SendFromBuffer(msg.c(), size); @@ -76,7 +76,7 @@ void ClearPending() { } if (PrintPending.offset != 0) { - char* msg = PrintBufArea.cast().c() + sizeof(GoalMessageHeader); + char* msg = PrintBufArea.cast().c() + sizeof(ListenerMessageHeader); auto size = strlen(msg); while (size > 0) { // sends larger than 64 kB are broken by the GoalProtoBuffer thing, so they are split @@ -104,8 +104,8 @@ void ClearPending() { void SendAck() { if (MasterDebug) { SendFromBufferD(u16(ListenerMessageKind::MSG_ACK), protoBlock.msg_id, - AckBufArea + sizeof(GoalMessageHeader), - strlen(AckBufArea + sizeof(GoalMessageHeader))); + AckBufArea + sizeof(ListenerMessageHeader), + strlen(AckBufArea + sizeof(ListenerMessageHeader))); } } diff --git a/game/kernel/kprint.cpp b/game/kernel/kprint.cpp index 44f1ae27a8..98822b9b74 100644 --- a/game/kernel/kprint.cpp +++ b/game/kernel/kprint.cpp @@ -5,11 +5,12 @@ #include #include -#include -#include +#include +#include #include "common/goal_constants.h" #include "common/common_types.h" +#include "common/cross_os_debug/xdbg.h" #include "kprint.h" #include "kmachine.h" #include "kboot.h" @@ -94,7 +95,7 @@ void init_output() { */ void clear_output() { if (MasterDebug) { - kstrcpy((char*)Ptr(OutputBufArea + sizeof(GoalMessageHeader)).c(), ""); + kstrcpy((char*)Ptr(OutputBufArea + sizeof(ListenerMessageHeader)).c(), ""); OutputPending = Ptr(0); } } @@ -105,7 +106,7 @@ void clear_output() { * EXACT */ void clear_print() { - *Ptr(PrintBufArea + sizeof(GoalMessageHeader)) = 0; + *Ptr(PrintBufArea + sizeof(ListenerMessageHeader)) = 0; PrintPending = Ptr(0); } @@ -116,8 +117,14 @@ void clear_print() { */ void reset_output() { if (MasterDebug) { - sprintf(OutputBufArea.cast().c() + sizeof(GoalMessageHeader), "reset #x%x\n", s7.offset); - OutputPending = OutputBufArea + sizeof(GoalMessageHeader); + // original GOAL: + // sprintf(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader), "reset #x%x\n", + // s7.offset); + + // modified for OpenGOAL: + sprintf(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader), "reset #x%x #x%lx %s\n", + s7.offset, (uintptr_t)g_ee_main_mem, xdbg::get_current_thread_id().to_string().c_str()); + OutputPending = OutputBufArea + sizeof(ListenerMessageHeader); } } @@ -126,10 +133,10 @@ void reset_output() { * DONE, EXACT */ void output_unload(const char* name) { - if (!MasterDebug) { - sprintf(strend(OutputBufArea.cast().c() + sizeof(GoalMessageHeader)), "unload \"%s\"\n", - name); - OutputPending = OutputBufArea + sizeof(GoalMessageHeader); + if (MasterDebug) { + sprintf(strend(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader)), + "unload \"%s\"\n", name); + OutputPending = OutputBufArea + sizeof(ListenerMessageHeader); } } @@ -138,14 +145,14 @@ void output_unload(const char* name) { */ void output_segment_load(const char* name, Ptr link_block, u32 flags) { if (MasterDebug) { - char* buffer = strend(OutputBufArea.cast().c() + sizeof(GoalMessageHeader)); + char* buffer = strend(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader)); char true_str[] = "t"; char false_str[] = "nil"; char* flag_str = (flags & LINK_FLAG_OUTPUT_TRUE) ? true_str : false_str; auto lbp = link_block.cast(); sprintf(buffer, "load \"%s\" %s #x%x #x%x #x%x\n", name, flag_str, lbp->code_infos[0].offset, lbp->code_infos[1].offset, lbp->code_infos[2].offset); - OutputPending = OutputBufArea + sizeof(GoalMessageHeader); + OutputPending = OutputBufArea + sizeof(ListenerMessageHeader); } } @@ -160,7 +167,7 @@ void cprintf(const char* format, ...) { va_start(args, format); char* str = PrintPending.cast().c(); if (!PrintPending.offset) - str = PrintBufArea.cast().c() + sizeof(GoalMessageHeader); + str = PrintBufArea.cast().c() + sizeof(ListenerMessageHeader); PrintPending = make_ptr(strend(str)).cast(); vsprintf((char*)PrintPending.c(), format, args); @@ -594,7 +601,7 @@ s32 format_impl(uint64_t* args) { // set up print pending char* print_temp = PrintPending.cast().c(); if (!PrintPending.offset) { - print_temp = PrintBufArea.cast().c() + sizeof(GoalMessageHeader); + print_temp = PrintBufArea.cast().c() + sizeof(ListenerMessageHeader); } PrintPending = make_ptr(strend(print_temp)).cast(); diff --git a/game/kernel/ksocket.cpp b/game/kernel/ksocket.cpp index 4eb0194017..34322c2250 100644 --- a/game/kernel/ksocket.cpp +++ b/game/kernel/ksocket.cpp @@ -23,20 +23,20 @@ u32 ReceiveToBuffer(char* buff) { // if we received less than the size of the message header, we either got nothing, or there was an // error - if (protoBlock.last_receive_size < (int)sizeof(GoalMessageHeader)) { + if (protoBlock.last_receive_size < (int)sizeof(ListenerMessageHeader)) { return -1; } // FlushCache(0); - GoalMessageHeader* gbuff = protoBlock.receive_buffer; + ListenerMessageHeader* gbuff = protoBlock.receive_buffer; u32 msg_size = gbuff->msg_size; // check it's our protocol - if (gbuff->deci2_hdr.proto == DECI2_PROTOCOL) { + if (gbuff->deci2_header.proto == DECI2_PROTOCOL) { // null terminate - ((u8*)gbuff)[sizeof(GoalMessageHeader) + msg_size] = '\0'; + ((u8*)gbuff)[sizeof(ListenerMessageHeader) + msg_size] = '\0'; // copy stuff to block - protoBlock.msg_kind = gbuff->msg_kind; + protoBlock.msg_kind = u32(gbuff->msg_kind); protoBlock.msg_id = gbuff->msg_id; // and mark message as received! protoBlock.last_receive_size = -1; @@ -44,7 +44,7 @@ u32 ReceiveToBuffer(char* buff) { // not our protocol, something has gone wrong. MsgErr("dkernel: got a bad packet to goal proto (goal #x%lx bytes %d %d %d %ld %d)\n", (int64_t)protoBlock.receive_buffer, protoBlock.last_receive_size, - protoBlock.receive_buffer->msg_kind, protoBlock.receive_buffer->u6, + u32(protoBlock.receive_buffer->msg_kind), protoBlock.receive_buffer->u6, protoBlock.receive_buffer->msg_id, msg_size); protoBlock.last_receive_size = -1; return -1; @@ -68,7 +68,7 @@ s32 SendFromBuffer(char* buff, s32 size) { */ void InitListenerConnect() { if (MasterDebug) { - kstrcpy(AckBufArea + sizeof(GoalMessageHeader), "ack"); + kstrcpy(AckBufArea + sizeof(ListenerMessageHeader), "ack"); } } @@ -90,14 +90,14 @@ Ptr WaitForMessageAndAck() { if (!MasterDebug) { MessCount = -1; } else { - MessCount = ReceiveToBuffer((char*)MessBufArea.c() + sizeof(GoalMessageHeader)); + MessCount = ReceiveToBuffer((char*)MessBufArea.c() + sizeof(ListenerMessageHeader)); } if (MessCount < 0) { return Ptr(0); } - return MessBufArea.cast() + sizeof(GoalMessageHeader); + return MessBufArea.cast() + sizeof(ListenerMessageHeader); } /*! diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 43d1d12cfa..094012be6a 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -19,6 +19,7 @@ add_library(compiler compiler/compilation/Macro.cpp compiler/compilation/Math.cpp compiler/compilation/Define.cpp + compiler/compilation/Debug.cpp compiler/compilation/Function.cpp compiler/compilation/ControlFlow.cpp compiler/compilation/Type.cpp diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 1ad7aa34a4..6e9f2d8cba 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -174,17 +174,8 @@ std::vector Compiler::codegen_object_file(FileEnv* env) { std::vector Compiler::run_test(const std::string& source_code) { try { - if (!m_listener.is_connected()) { - for (int i = 0; i < 1000; i++) { - m_listener.connect_to_target(); - std::this_thread::sleep_for(std::chrono::microseconds(10000)); - if (m_listener.is_connected()) { - break; - } - } - if (!m_listener.is_connected()) { - throw std::runtime_error("Compiler::run_test couldn't connect!"); - } + if (!connect_to_target()) { + throw std::runtime_error("Compiler::run_test couldn't connect!"); } auto code = m_goos.reader.read_from_file({source_code}); @@ -206,6 +197,22 @@ std::vector Compiler::run_test(const std::string& source_code) { } } +bool Compiler::connect_to_target() { + if (!m_listener.is_connected()) { + for (int i = 0; i < 1000; i++) { + m_listener.connect_to_target(); + std::this_thread::sleep_for(std::chrono::microseconds(10000)); + if (m_listener.is_connected()) { + break; + } + } + if (!m_listener.is_connected()) { + return false; + } + } + return true; +} + std::vector Compiler::run_test_no_load(const std::string& source_code) { auto code = m_goos.reader.read_from_file({source_code}); compile_object_file("test-code", code, true); diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index f8fb923ab7..90a071d409 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -33,6 +33,15 @@ class Compiler { std::vector run_test_no_load(const std::string& source_code); void shutdown_target(); void enable_throw_on_redefines() { m_throw_on_define_extern_redefinition = true; } + const xdbg::DebugContext& get_debug_state() { + return m_listener.get_debug_context(); + } + + void poke_target() { + m_listener.send_poke(); + } + + bool connect_to_target(); private: void init_logger(); @@ -182,6 +191,9 @@ class Compiler { Val* compile_define_extern(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_set(const goos::Object& form, const goos::Object& rest, Env* env); + // Debug + Val* compile_dbg(const goos::Object& form, const goos::Object& rest, Env* env); + // Macro Val* compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_quote(const goos::Object& form, const goos::Object& rest, Env* env); diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index 98fa31681e..d017ad1966 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -52,6 +52,9 @@ static const std::unordered_map< {"define-extern", &Compiler::compile_define_extern}, {"set!", &Compiler::compile_set}, + // DEBUGGING + {"dbg", &Compiler::compile_dbg}, + // TYPE {"deftype", &Compiler::compile_deftype}, {"defmethod", &Compiler::compile_defmethod}, diff --git a/goalc/compiler/compilation/Debug.cpp b/goalc/compiler/compilation/Debug.cpp new file mode 100644 index 0000000000..74ce1a3fc3 --- /dev/null +++ b/goalc/compiler/compilation/Debug.cpp @@ -0,0 +1,22 @@ +#include "goalc/compiler/Compiler.h" +#include "third-party/fmt/core.h" + +Val* Compiler::compile_dbg(const goos::Object& form, const goos::Object& rest, Env* env) { + // todo - do something with args. + (void)form; + (void)rest; + (void)env; + auto& ctx = m_listener.get_debug_context(); + if (!ctx.valid) { + fmt::print("[Debugger] Could not start debugger because there is no valid debugging context\n"); + return get_none(); + } + + if (xdbg::attach_and_break(ctx.tid)) { + ctx.running = false; + fmt::print("Debugger connected.\n"); + } else { + fmt::print("ERROR\n"); + } + return get_none(); +} \ No newline at end of file diff --git a/goalc/listener/CMakeLists.txt b/goalc/listener/CMakeLists.txt index 4cc5c90330..e3e79ffecf 100644 --- a/goalc/listener/CMakeLists.txt +++ b/goalc/listener/CMakeLists.txt @@ -1,7 +1,7 @@ add_library(listener SHARED Listener.cpp) IF (WIN32) - target_link_libraries(listener cross_sockets) + target_link_libraries(listener cross_sockets cross_os_debug) ELSE () - target_link_libraries(listener cross_sockets pthread) + target_link_libraries(listener cross_sockets pthread cross_os_debug) ENDIF () \ No newline at end of file diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index e24393fc90..9f7cf7b6bc 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -49,7 +49,19 @@ Listener::~Listener() { } } +/*! + * Disconnect if we are connected and shut down the receiving thread. + */ void Listener::disconnect() { + if (m_debug_context.valid && !m_debug_context.running) { + printf( + "[Listener] The listener was shut down while the debugger has paused the runtime, " + "resuming\n"); + xdbg::detach_and_resume(m_debug_context.tid); + m_debug_context.valid = false; + } + + m_debug_context.valid = false; m_connected = false; if (receive_thread_running) { rcv_thread.join(); @@ -57,6 +69,9 @@ void Listener::disconnect() { } } +/*! + * Are we currently connected? Returns false if we are currently disconnecting. + */ bool Listener::is_connected() const { return m_connected; } @@ -250,6 +265,10 @@ void Listener::receive_func() { if (hdr->msg_kind == filter) { message_record.emplace_back(str_buff); } + + if (hdr->msg_kind == ListenerMessageKind::MSG_OUTPUT) { + handle_output_message(str_buff); + } rcv_mtx.unlock(); } break; @@ -261,6 +280,9 @@ void Listener::receive_func() { } } +/*! + * Start recording messages of the given kind. + */ void Listener::record_messages(ListenerMessageKind kind) { if (filter != ListenerMessageKind::MSG_INVALID) { printf("[Listener] Already recording!\n"); @@ -268,13 +290,22 @@ void Listener::record_messages(ListenerMessageKind kind) { filter = kind; } +/*! + * Stop recording messages and return a list of messages. + */ std::vector Listener::stop_recording_messages() { + rcv_mtx.lock(); filter = ListenerMessageKind::MSG_INVALID; auto result = message_record; message_record.clear(); + rcv_mtx.unlock(); return result; } +/*! + * Send a "CODE" message for the target to execute as the Listener Function. + * Returns once the target acks the code. + */ void Listener::send_code(std::vector& code) { got_ack = false; int total_size = code.size() + sizeof(ListenerMessageHeader); @@ -287,38 +318,53 @@ void Listener::send_code(std::vector& code) { auto* buffer_data = (char*)(header + 1); header->deci2_header.rsvd = 0; header->deci2_header.len = total_size; - header->deci2_header.proto = 0xe042; // todo don't hardcode + header->deci2_header.proto = DECI2_PROTOCOL; header->deci2_header.src = 'H'; header->deci2_header.dst = 'E'; header->msg_size = code.size(); header->ltt_msg_kind = LTT_MSG_CODE; header->u6 = 0; - header->u8 = 0; + header->msg_id = 0; memcpy(buffer_data, code.data(), code.size()); send_buffer(total_size); } +/*! + * Send a message to tell the target to reset. The shutdown parameter tells the target to shutdown. + * Waits for the target to ack the shutdown message. + */ void Listener::send_reset(bool shutdown) { if (!m_connected) { printf("Not connected, so cannot reset target.\n"); return; } + + if (m_debug_context.valid && !m_debug_context.running) { + printf("Tried to reset a halted target, resuming...\n"); + xdbg::detach_and_resume(m_debug_context.tid); + m_debug_context.valid = false; + } + auto* header = (ListenerMessageHeader*)m_buffer; header->deci2_header.rsvd = 0; header->deci2_header.len = sizeof(ListenerMessageHeader); - header->deci2_header.proto = 0xe042; // todo don't hardcode + header->deci2_header.proto = DECI2_PROTOCOL; header->deci2_header.src = 'H'; header->deci2_header.dst = 'E'; header->msg_size = 0; header->ltt_msg_kind = LTT_MSG_RESET; header->u6 = 0; - header->u8 = shutdown ? UINT64_MAX : 0; + header->msg_id = shutdown ? UINT64_MAX : 0; send_buffer(sizeof(ListenerMessageHeader)); disconnect(); close_socket(listen_socket); - printf("closed connection to target\n"); + printf("[Listener] Closed connection to target\n"); } +/*! + * Send a "poke" message. + * This makes the target think its connect and flush any buffers that are pending. + */ void Listener::send_poke() { if (!m_connected) { printf("Not connected, so cannot poke target.\n"); @@ -327,16 +373,20 @@ void Listener::send_poke() { auto* header = (ListenerMessageHeader*)m_buffer; header->deci2_header.rsvd = 0; header->deci2_header.len = sizeof(ListenerMessageHeader); - header->deci2_header.proto = 0xe042; // todo don't hardcode + header->deci2_header.proto = DECI2_PROTOCOL; header->deci2_header.src = 'H'; header->deci2_header.dst = 'E'; header->msg_size = 0; header->ltt_msg_kind = LTT_MSG_POKE; header->u6 = 0; - header->u8 = 0; + header->msg_id = 0; send_buffer(sizeof(ListenerMessageHeader)); } +/*! + * Low level send of the m_buffer. + * Waits for the target to respond or times out and prints an error. + */ void Listener::send_buffer(int sz) { int wrote = 0; @@ -363,11 +413,16 @@ void Listener::send_buffer(int sz) { printf(" OK\n"); } } else { - printf(" NG - target has timed out. If it has died, disconnect with (disconnect-target)\n"); + printf( + " Error - target has timed out. If it is stuck in a loop, it must be manually killed.\n"); } } +/*! + * Wait for the target to send an ack. + */ bool Listener::wait_for_ack() { + // todo, check the message ID. if (!m_connected) { printf("wait_for_ack called when not connected!\n"); return false; @@ -383,4 +438,79 @@ bool Listener::wait_for_ack() { return false; } +void Listener::handle_output_message(const char* msg) { + std::string all(msg); + + std::string::size_type last_line = 0, line = all.find('\n'); + + while (line != std::string::npos) { + auto str = all.substr(last_line, line - last_line); + last_line = line + 1; + line = all.find('\n', last_line); + + auto x = str.find(' '); + auto kind = str.substr(0, x); + if (kind == "reset") { + assert(x + 1 < str.length()); + auto next = str.find(' ', x + 1); + auto s7_str = str.substr(x, next - x); + x = next; + + assert(x + 1 < str.length()); + next = str.find(' ', x + 1); + auto base_str = str.substr(x, next - x); + x = next; + + assert(x + 1 < str.length()); + next = str.find(' ', x + 1); + auto tid_str = str.substr(x, next - x); + + m_debug_context.s7 = std::stoul(s7_str.substr(3), nullptr, 16); + m_debug_context.base = std::stoull(base_str.substr(3), nullptr, 16); + m_debug_context.tid = xdbg::ThreadID(tid_str.substr(1)); + m_debug_context.valid = true; + + printf("[Debugger] New debug info! s7 = 0x%x, base = 0x%lx, tid = %s\n", m_debug_context.s7, + m_debug_context.base, m_debug_context.tid.to_string().c_str()); + + } else if (kind == "load") { + assert(x + 1 < str.length()); + auto next = str.find(' ', x + 1); + auto name_str = str.substr(x, next - x); + x = next; + + assert(x + 1 < str.length()); + next = str.find(' ', x + 1); + auto load_kind_str = str.substr(x, next - x); + x = next; + + std::string seg_strings[3]; + + for (auto& seg_string : seg_strings) { + assert(x + 1 < str.length()); + next = str.find(' ', x + 1); + seg_string = str.substr(x, next - x); + } + + LoadEntry entry; + entry.load_string = load_kind_str.substr(1); + for (int i = 0; i < 3; i++) { + entry.segments[i] = std::stoul(seg_strings[i].substr(3), nullptr, 16); + } + + add_load(name_str.substr(2, name_str.length() - 3), entry); + } else { + // todo unload + printf("[Listener Warning] unknown output kind \"%s\"\n", kind.c_str()); + } + } +} + +void Listener::add_load(const std::string& name, const LoadEntry& le) { + if (m_load_entries.find(name) != m_load_entries.end()) { + printf("[Listener Error] The runtime has loaded %s twice!\n", name.c_str()); + } + m_load_entries[name] = le; +} + } // namespace listener diff --git a/goalc/listener/Listener.h b/goalc/listener/Listener.h index 437c38a033..e22d9637b6 100644 --- a/goalc/listener/Listener.h +++ b/goalc/listener/Listener.h @@ -12,10 +12,18 @@ #include #include #include +#include #include "common/common_types.h" #include "common/listener_common.h" +#include "common/cross_os_debug/xdbg.h" namespace listener { + +struct LoadEntry { + uint32_t segments[3] = {0, 0, 0}; + std::string load_string; +}; + class Listener { public: static constexpr int BUFFER_SIZE = 32 * 1024 * 1024; @@ -31,11 +39,20 @@ class Listener { void send_poke(); void disconnect(); void send_code(std::vector& code); - bool most_recent_send_was_acked() { return got_ack; } + bool most_recent_send_was_acked() const { return got_ack; } + bool get_load_entry(const std::string& name, LoadEntry* out = nullptr); + std::vector get_all_loaded(); + + xdbg::DebugContext& get_debug_context() { return m_debug_context; } private: + void add_load(const std::string& name, const LoadEntry& le); + void do_unload(const std::string& name); + void send_buffer(int sz); bool wait_for_ack(); + void handle_output_message(const char* msg); + xdbg::DebugContext m_debug_context; char* m_buffer = nullptr; //! buffer for incoming messages bool m_connected = false; //! do we think we are connected? @@ -49,6 +66,7 @@ class Listener { void receive_func(); ListenerMessageKind filter = ListenerMessageKind::MSG_INVALID; std::vector message_record; + std::unordered_map m_load_entries; char ack_recv_buff[512]; }; } // namespace listener diff --git a/test/goalc/source_templates/with_game/attach-debugger.gc b/test/goalc/source_templates/with_game/attach-debugger.gc new file mode 100644 index 0000000000..694613d49e --- /dev/null +++ b/test/goalc/source_templates/with_game/attach-debugger.gc @@ -0,0 +1 @@ +(dbg) diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index f1aa763e83..958a6ebb07 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -144,4 +144,23 @@ TEST(TypeConsistency, TypeConsistency) { compiler.enable_throw_on_redefines(); compiler.run_test_no_load("test/goalc/source_templates/with_game/test-build-game.gc"); compiler.run_test_no_load("decompiler/config/all-types.gc"); -} \ No newline at end of file +} + +#ifdef __linux +TEST(Debugger, DebuggerBasicConnect) { + Compiler compiler; + + if(!fork()) { + GoalTest::runtime_no_kernel(); + } else { + compiler.connect_to_target(); + compiler.poke_target(); + compiler.run_test("test/goalc/source_templates/with_game/attach-debugger.gc"); + EXPECT_TRUE(compiler.get_debug_state().valid); + EXPECT_FALSE(compiler.get_debug_state().running); + compiler.shutdown_target(); + + EXPECT_TRUE(wait(nullptr) >= 0); + } +} +#endif \ No newline at end of file diff --git a/test/test_kernel.cpp b/test/test_kernel.cpp index fb4b17a4c9..edea6ce300 100644 --- a/test/test_kernel.cpp +++ b/test/test_kernel.cpp @@ -334,7 +334,7 @@ TEST(Kernel, PrintBuffer) { clear_print(); cprintf("test!\n"); - std::string result = PrintBufArea.cast().c() + sizeof(GoalMessageHeader); + std::string result = PrintBufArea.cast().c() + sizeof(ListenerMessageHeader); EXPECT_EQ(result, "test!\n"); delete[] mem; From 23058c3bb17e6eeec61576cd52761d44a5c026a8 Mon Sep 17 00:00:00 2001 From: water Date: Fri, 30 Oct 2020 22:20:55 -0400 Subject: [PATCH 2/8] clang format --- goalc/compiler/Compiler.h | 8 ++------ test/goalc/test_with_game.cpp | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 90a071d409..bc042d3dd0 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -33,13 +33,9 @@ class Compiler { std::vector run_test_no_load(const std::string& source_code); void shutdown_target(); void enable_throw_on_redefines() { m_throw_on_define_extern_redefinition = true; } - const xdbg::DebugContext& get_debug_state() { - return m_listener.get_debug_context(); - } + const xdbg::DebugContext& get_debug_state() { return m_listener.get_debug_context(); } - void poke_target() { - m_listener.send_poke(); - } + void poke_target() { m_listener.send_poke(); } bool connect_to_target(); diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 958a6ebb07..de3f4cb404 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -150,7 +150,7 @@ TEST(TypeConsistency, TypeConsistency) { TEST(Debugger, DebuggerBasicConnect) { Compiler compiler; - if(!fork()) { + if (!fork()) { GoalTest::runtime_no_kernel(); } else { compiler.connect_to_target(); From bd40c9d5139649f0a6f83ebadc1262f388e1ffdd Mon Sep 17 00:00:00 2001 From: water Date: Sat, 31 Oct 2020 10:27:22 -0400 Subject: [PATCH 3/8] move debugger state to a separate Debugger class --- common/cross_os_debug/xdbg.h | 2 - goalc/CMakeLists.txt | 1 + goalc/compiler/Compiler.cpp | 1 + goalc/compiler/Compiler.h | 6 ++- goalc/compiler/compilation/Debug.cpp | 11 ++-- goalc/debugger/Debugger.cpp | 53 +++++++++++++++++++ goalc/debugger/Debugger.h | 25 +++++++++ goalc/listener/Listener.cpp | 32 +++++------ goalc/listener/Listener.h | 6 ++- test/goalc/CMakeLists.txt | 3 +- ...tests.cpp => all_goalc_template_tests.cpp} | 0 test/goalc/test_debugger.cpp | 24 +++++++++ test/goalc/test_with_game.cpp | 21 +------- 13 files changed, 140 insertions(+), 45 deletions(-) create mode 100644 goalc/debugger/Debugger.cpp create mode 100644 goalc/debugger/Debugger.h rename test/goalc/{all_goalc_tests.cpp => all_goalc_template_tests.cpp} (100%) create mode 100644 test/goalc/test_debugger.cpp diff --git a/common/cross_os_debug/xdbg.h b/common/cross_os_debug/xdbg.h index afdf475e07..542565873e 100644 --- a/common/cross_os_debug/xdbg.h +++ b/common/cross_os_debug/xdbg.h @@ -34,8 +34,6 @@ struct DebugContext { ThreadID tid; uintptr_t base; uint32_t s7; - bool valid = false; - bool running = true; }; // Functions diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 094012be6a..680212c8ea 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -25,6 +25,7 @@ add_library(compiler compiler/compilation/Type.cpp compiler/compilation/Static.cpp compiler/Util.cpp + debugger/Debugger.cpp logger/Logger.cpp regalloc/IRegister.cpp regalloc/Allocator.cpp diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 6e9f2d8cba..759e8e1615 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -12,6 +12,7 @@ using namespace goos; Compiler::Compiler() { init_logger(); init_settings(); + m_listener.add_debugger(&m_debugger); m_ts.add_builtin_types(); m_global_env = std::make_unique(); m_none = std::make_unique(m_ts.make_typespec("none")); diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index bc042d3dd0..34e6926ef0 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -9,6 +9,7 @@ #include "goalc/listener/Listener.h" #include "common/goos/Interpreter.h" #include "goalc/compiler/IR.h" +#include "goalc/debugger/Debugger.h" #include "CompilerSettings.h" enum MathMode { MATH_INT, MATH_BINT, MATH_FLOAT, MATH_INVALID }; @@ -33,7 +34,9 @@ class Compiler { std::vector run_test_no_load(const std::string& source_code); void shutdown_target(); void enable_throw_on_redefines() { m_throw_on_define_extern_redefinition = true; } - const xdbg::DebugContext& get_debug_state() { return m_listener.get_debug_context(); } + Debugger& get_debugger() { + return m_debugger; + } void poke_target() { m_listener.send_poke(); } @@ -107,6 +110,7 @@ class Compiler { std::unique_ptr m_none = nullptr; bool m_want_exit = false; listener::Listener m_listener; + Debugger m_debugger; goos::Interpreter m_goos; std::unordered_map m_symbol_types; std::unordered_map, goos::Object> m_global_constants; diff --git a/goalc/compiler/compilation/Debug.cpp b/goalc/compiler/compilation/Debug.cpp index 74ce1a3fc3..9032353e64 100644 --- a/goalc/compiler/compilation/Debug.cpp +++ b/goalc/compiler/compilation/Debug.cpp @@ -6,14 +6,17 @@ Val* Compiler::compile_dbg(const goos::Object& form, const goos::Object& rest, E (void)form; (void)rest; (void)env; - auto& ctx = m_listener.get_debug_context(); - if (!ctx.valid) { + if (!m_debugger.is_valid()) { fmt::print("[Debugger] Could not start debugger because there is no valid debugging context\n"); return get_none(); } - if (xdbg::attach_and_break(ctx.tid)) { - ctx.running = false; + if (m_debugger.is_attached()) { + fmt::print("[Debugger] Could not start debugger because the debugger is already attached.\n"); + return get_none(); + } + + if (m_debugger.attach_and_break()) { fmt::print("Debugger connected.\n"); } else { fmt::print("ERROR\n"); diff --git a/goalc/debugger/Debugger.cpp b/goalc/debugger/Debugger.cpp new file mode 100644 index 0000000000..e7a47f363c --- /dev/null +++ b/goalc/debugger/Debugger.cpp @@ -0,0 +1,53 @@ +#include "Debugger.h" +#include "third-party/fmt/core.h" + +bool Debugger::is_halted() const { + return m_context_valid && !m_running; +} + +bool Debugger::is_valid() const { + return m_context_valid; +} + +void Debugger::invalidate() { + m_context_valid = false; +} + +bool Debugger::is_attached() const { + return m_context_valid && m_attached; +} + +void Debugger::detach() { + if (is_valid() && m_attached) { + xdbg::detach_and_resume(m_debug_context.tid); + m_context_valid = false; + m_attached = false; + } + // todo, should we print something if we can't detach? +} + +void Debugger::set_context(u32 s7, uintptr_t base, const std::string& thread_id) { + m_debug_context.s7 = s7; + m_debug_context.base = base; + m_debug_context.tid = xdbg::ThreadID(thread_id); + m_context_valid = true; +} + +std::string Debugger::get_context_string() const { + return fmt::format("valid = {}, s7 = 0x{:x}, base = 0x{:x}, tid = {}\n", is_valid(), + m_debug_context.s7, m_debug_context.base, m_debug_context.tid.to_string()); +} + +bool Debugger::attach_and_break() { + if (is_valid() && !m_attached) { + if (xdbg::attach_and_break(m_debug_context.tid)) { + m_attached = true; + m_running = false; + return true; + } + } else { + fmt::print("[Debugger] attach_and_break can't be done when valid = {} and attached = {}\n", + is_valid(), m_attached); + } + return false; +} \ No newline at end of file diff --git a/goalc/debugger/Debugger.h b/goalc/debugger/Debugger.h new file mode 100644 index 0000000000..721b6ad05b --- /dev/null +++ b/goalc/debugger/Debugger.h @@ -0,0 +1,25 @@ +#pragma once + +#include "common/common_types.h" +#include "common/cross_os_debug/xdbg.h" + +class Debugger { + public: + Debugger() = default; + bool is_halted() const; // are we halted? + bool is_valid() const; + bool is_attached() const; + bool is_running() const; + void detach(); + void invalidate(); + void set_context(u32 s7, uintptr_t base, const std::string& thread_id); + std::string get_context_string() const; + + bool attach_and_break(); + + private: + xdbg::DebugContext m_debug_context; + bool m_context_valid = false; + bool m_running = true; + bool m_attached = false; +}; diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index 9f7cf7b6bc..ebc8c1ee3b 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -53,15 +53,16 @@ Listener::~Listener() { * Disconnect if we are connected and shut down the receiving thread. */ void Listener::disconnect() { - if (m_debug_context.valid && !m_debug_context.running) { + if (m_debugger && m_debugger->is_halted()) { printf( "[Listener] The listener was shut down while the debugger has paused the runtime, " "resuming\n"); - xdbg::detach_and_resume(m_debug_context.tid); - m_debug_context.valid = false; + m_debugger->detach(); } - m_debug_context.valid = false; + if (m_debugger) { + m_debugger->invalidate(); + } m_connected = false; if (receive_thread_running) { rcv_thread.join(); @@ -339,10 +340,9 @@ void Listener::send_reset(bool shutdown) { return; } - if (m_debug_context.valid && !m_debug_context.running) { - printf("Tried to reset a halted target, resuming...\n"); - xdbg::detach_and_resume(m_debug_context.tid); - m_debug_context.valid = false; + if (m_debugger && m_debugger->is_halted()) { + printf("Tried to reset a halted target, detaching...\n"); + m_debugger->detach(); } auto* header = (ListenerMessageHeader*)m_buffer; @@ -465,13 +465,11 @@ void Listener::handle_output_message(const char* msg) { next = str.find(' ', x + 1); auto tid_str = str.substr(x, next - x); - m_debug_context.s7 = std::stoul(s7_str.substr(3), nullptr, 16); - m_debug_context.base = std::stoull(base_str.substr(3), nullptr, 16); - m_debug_context.tid = xdbg::ThreadID(tid_str.substr(1)); - m_debug_context.valid = true; - - printf("[Debugger] New debug info! s7 = 0x%x, base = 0x%lx, tid = %s\n", m_debug_context.s7, - m_debug_context.base, m_debug_context.tid.to_string().c_str()); + if (m_debugger) { + m_debugger->set_context(std::stoul(s7_str.substr(3), nullptr, 16), + std::stoull(base_str.substr(3), nullptr, 16), tid_str.substr(1)); + printf("[Debugger] Context: %s\n", m_debugger->get_context_string().c_str()); + } } else if (kind == "load") { assert(x + 1 < str.length()); @@ -513,4 +511,8 @@ void Listener::add_load(const std::string& name, const LoadEntry& le) { m_load_entries[name] = le; } +void Listener::add_debugger(Debugger* debugger) { + m_debugger = debugger; +} + } // namespace listener diff --git a/goalc/listener/Listener.h b/goalc/listener/Listener.h index e22d9637b6..3b6f41b52d 100644 --- a/goalc/listener/Listener.h +++ b/goalc/listener/Listener.h @@ -16,6 +16,7 @@ #include "common/common_types.h" #include "common/listener_common.h" #include "common/cross_os_debug/xdbg.h" +#include "goalc/debugger/Debugger.h" namespace listener { @@ -39,11 +40,11 @@ class Listener { void send_poke(); void disconnect(); void send_code(std::vector& code); + void add_debugger(Debugger* debugger); bool most_recent_send_was_acked() const { return got_ack; } bool get_load_entry(const std::string& name, LoadEntry* out = nullptr); std::vector get_all_loaded(); - xdbg::DebugContext& get_debug_context() { return m_debug_context; } private: void add_load(const std::string& name, const LoadEntry& le); @@ -52,7 +53,6 @@ class Listener { void send_buffer(int sz); bool wait_for_ack(); void handle_output_message(const char* msg); - xdbg::DebugContext m_debug_context; char* m_buffer = nullptr; //! buffer for incoming messages bool m_connected = false; //! do we think we are connected? @@ -61,6 +61,8 @@ class Listener { bool got_ack = false; bool waiting_for_ack = false; + Debugger* m_debugger = nullptr; + std::thread rcv_thread; std::mutex rcv_mtx; void receive_func(); diff --git a/test/goalc/CMakeLists.txt b/test/goalc/CMakeLists.txt index 70b071c2cf..01a8d14bde 100644 --- a/test/goalc/CMakeLists.txt +++ b/test/goalc/CMakeLists.txt @@ -1,7 +1,8 @@ # TODO - probably a more cmakey way to do this set(GOALC_TEST_CASES - "goalc/all_goalc_tests.cpp" + "goalc/all_goalc_template_tests.cpp" + goalc/test_debugger.cpp ) set(GOALC_TEST_FRAMEWORK_SOURCES diff --git a/test/goalc/all_goalc_tests.cpp b/test/goalc/all_goalc_template_tests.cpp similarity index 100% rename from test/goalc/all_goalc_tests.cpp rename to test/goalc/all_goalc_template_tests.cpp diff --git a/test/goalc/test_debugger.cpp b/test/goalc/test_debugger.cpp new file mode 100644 index 0000000000..f0e2edf15e --- /dev/null +++ b/test/goalc/test_debugger.cpp @@ -0,0 +1,24 @@ +#include "gtest/gtest.h" +#include "goalc/compiler/Compiler.h" +#include "test/goalc/framework/test_runner.h" + +#ifdef __linux +TEST(Debugger, DebuggerBasicConnect) { + Compiler compiler; + // evidently you can't ptrace threads in your own process, so we need to run the runtime in a + // separate process. + if (!fork()) { + GoalTest::runtime_no_kernel(); + } else { + compiler.connect_to_target(); + compiler.poke_target(); + compiler.run_test("test/goalc/source_templates/with_game/attach-debugger.gc"); + EXPECT_TRUE(compiler.get_debugger().is_valid()); + EXPECT_TRUE(compiler.get_debugger().is_halted()); + compiler.shutdown_target(); // will detach/unhalt, then send the usual shutdown message + + // and now the child process should be done! + EXPECT_TRUE(wait(nullptr) >= 0); + } +} +#endif diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index de3f4cb404..f1aa763e83 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -144,23 +144,4 @@ TEST(TypeConsistency, TypeConsistency) { compiler.enable_throw_on_redefines(); compiler.run_test_no_load("test/goalc/source_templates/with_game/test-build-game.gc"); compiler.run_test_no_load("decompiler/config/all-types.gc"); -} - -#ifdef __linux -TEST(Debugger, DebuggerBasicConnect) { - Compiler compiler; - - if (!fork()) { - GoalTest::runtime_no_kernel(); - } else { - compiler.connect_to_target(); - compiler.poke_target(); - compiler.run_test("test/goalc/source_templates/with_game/attach-debugger.gc"); - EXPECT_TRUE(compiler.get_debug_state().valid); - EXPECT_FALSE(compiler.get_debug_state().running); - compiler.shutdown_target(); - - EXPECT_TRUE(wait(nullptr) >= 0); - } -} -#endif \ No newline at end of file +} \ No newline at end of file From 51445ac21afa7e35b5a37c6fe4b80615a9a46d7e Mon Sep 17 00:00:00 2001 From: water Date: Sat, 31 Oct 2020 11:31:56 -0400 Subject: [PATCH 4/8] support registers and break and continue --- common/common_types.h | 11 ++++ common/cross_os_debug/xdbg.cpp | 95 +++++++++++++++++++++++++++- common/cross_os_debug/xdbg.h | 16 +++++ doc/goal_dbg_doc.md | 44 +++++++++++++ goalc/compiler/Compiler.h | 7 +- goalc/compiler/compilation/Atoms.cpp | 3 + goalc/compiler/compilation/Debug.cpp | 59 +++++++++++++++++ goalc/debugger/Debugger.cpp | 38 +++++++++++ goalc/debugger/Debugger.h | 5 +- goalc/listener/Listener.h | 1 - test/goalc/test_debugger.cpp | 24 +++++++ 11 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 doc/goal_dbg_doc.md diff --git a/common/common_types.h b/common/common_types.h index 2ce985659a..f9ce83170c 100644 --- a/common/common_types.h +++ b/common/common_types.h @@ -19,4 +19,15 @@ using s16 = int16_t; using s32 = int32_t; using s64 = int64_t; +struct u128 { + union { + u64 du64[2]; + u32 du32[4]; + u16 du16[8]; + u8 du8[16]; + float f[4]; + }; +}; +static_assert(sizeof(u128) == 16, "u128"); + #endif // JAK1_COMMON_TYPES_H diff --git a/common/cross_os_debug/xdbg.cpp b/common/cross_os_debug/xdbg.cpp index 7bc42120a4..ec6b4298af 100644 --- a/common/cross_os_debug/xdbg.cpp +++ b/common/cross_os_debug/xdbg.cpp @@ -1,4 +1,5 @@ #include +#include "third-party/fmt/core.h" #include "xdbg.h" #ifdef __linux @@ -7,6 +8,7 @@ #include #include #include +#include #include #elif _WIN32 @@ -30,12 +32,16 @@ ThreadID get_current_thread_id() { } bool attach_and_break(const ThreadID& tid) { - auto rv = ptrace(PTRACE_ATTACH, tid.id, nullptr, nullptr); + auto rv = ptrace(PTRACE_SEIZE, tid.id, nullptr, nullptr); if (rv == -1) { printf("[Debugger] Failed to attach %s\n", strerror(errno)); return false; } else { printf("[Debugger] PTRACE_ATTACHED! Waiting for process to stop...\n"); + if (ptrace(PTRACE_INTERRUPT, tid.id, nullptr, nullptr) < 0) { + printf("[Debugger] Failed to PTRACE_INTERRUPT %s\n", strerror(errno)); + return false; + } // we could technically hang here forever if runtime ignores the signal. int status; @@ -70,6 +76,65 @@ bool detach_and_resume(const ThreadID& tid) { return true; } +bool get_regs_now(const ThreadID& tid, Regs* out) { + user regs = {}; + if (ptrace(PTRACE_GETREGS, tid.id, nullptr, ®s) < 0) { + printf("[Debugger] Failed to PTRACE_GETREGS %s\n", strerror(errno)); + return false; + } + + out->gprs[0] = regs.regs.rax; + out->gprs[1] = regs.regs.rcx; + out->gprs[2] = regs.regs.rdx; + out->gprs[3] = regs.regs.rbx; + out->gprs[4] = regs.regs.rsp; + out->gprs[5] = regs.regs.rbp; + out->gprs[6] = regs.regs.rsi; + out->gprs[7] = regs.regs.rdi; + out->gprs[8] = regs.regs.r8; + out->gprs[9] = regs.regs.r9; + out->gprs[10] = regs.regs.r10; + out->gprs[11] = regs.regs.r11; + out->gprs[12] = regs.regs.r12; + out->gprs[13] = regs.regs.r13; + out->gprs[14] = regs.regs.r14; + out->gprs[15] = regs.regs.r15; + out->rip = regs.regs.rip; + + // todo, get fprs. + return true; +} + +bool break_now(const ThreadID& tid) { + if (ptrace(PTRACE_INTERRUPT, tid.id, nullptr, nullptr) < 0) { + printf("[Debugger] Failed to PTRACE_INTERRUPT %s\n", strerror(errno)); + return false; + } + + int status; + if (waitpid(tid.id, &status, 0) < 0) { + printf("[Debugger] Failed to waitpid: %s. The runtime is probably in a bad state now.\n", + strerror(errno)); + return false; + } + + if (!WIFSTOPPED(status)) { + printf("[Debugger] Failed to STOP: %s. The runtime is probably in a bad state now.\n", + strerror(errno)); + return false; + } + + return true; +} + +bool cont_now(const ThreadID& tid) { + if (ptrace(PTRACE_CONT, tid.id, nullptr, nullptr) < 0) { + printf("[Debugger] Failed to PTRACE_CONT %s\n", strerror(errno)); + return false; + } + return true; +} + #elif _WIN32 ThreadID::ThreadID() {} // todo @@ -98,5 +163,33 @@ bool detach_and_resume(const ThreadID& tid) { void allow_debugging() {} +bool break_now(const ThreadID& tid) { + return false; +} + +bool cont_now(const ThreadID& tid) { + return false; +} + +bool get_regs_now(const ThreadID& tid, Regs* out) { + return false; +} + #endif + +const char* gpr_names[] = {"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", + " r8", " r9", "r10", "r11", "r12", "r13", "r14", "r15"}; + +std::string Regs::print_gprs() const { + std::string result; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + int idx = i * 4 + j; + result += fmt::format("{}: 0x{:016x} ", gpr_names[idx], gprs[idx]); + } + result += "\n"; + } + result += fmt::format("rip: 0x{:016x}\n", rip); + return result; +} } // namespace xdbg diff --git a/common/cross_os_debug/xdbg.h b/common/cross_os_debug/xdbg.h index 542565873e..0eb7abd842 100644 --- a/common/cross_os_debug/xdbg.h +++ b/common/cross_os_debug/xdbg.h @@ -2,6 +2,7 @@ #include #include +#include "common/common_types.h" #ifdef __linux #include @@ -36,10 +37,25 @@ struct DebugContext { uint32_t s7; }; +struct Regs { + u64 gprs[16]; + u128 xmms[16]; + + u64 rip; + + std::string print_gprs() const; + std::string print_xmms_as_flt() const; + std::string print_xmms_as_int() const; + std::string print_xmms_as_flt_vec() const; +}; + // Functions ThreadID get_current_thread_id(); bool attach_and_break(const ThreadID& tid); void allow_debugging(); bool detach_and_resume(const ThreadID& tid); +bool get_regs_now(const ThreadID& tid, Regs* out); +bool break_now(const ThreadID& tid); +bool cont_now(const ThreadID& tid); } // namespace xdbg diff --git a/doc/goal_dbg_doc.md b/doc/goal_dbg_doc.md new file mode 100644 index 0000000000..a47d16755f --- /dev/null +++ b/doc/goal_dbg_doc.md @@ -0,0 +1,44 @@ +# OpenGOAL Debugger +Currently the debugger only works on Linux. All the platform specific stuff is in `xdbg.cpp`. + +## `(dbs)` +Print the status of the debugger and listener. The listener status is whether or not there is a socket connection open between the compiler and the target. The "debug context" is information that the runtime sends to the compiler so it can find the correct thread to debug. In order to debug, you need both. + +## `(dbg)` +Attach the debugger. This will stop the target. + +Example of connecting to the target for debugging: + +```lisp +OpenGOAL Compiler 0.1 + +;; attach the listener over the network +g> (lt) +[Listener] Socket connected established! (took 0 tries). Waiting for version... +Got version 0.1 OK! + +;; this message is sent from the target from kprint.cpp and contains the "debug context" +[OUTPUT] reset #x147d24 #x2000000000 1062568 + +;; the debugger gets the message and remembers it so it can connect in the future. +[Debugger] Context: valid = true, s7 = 0x147d24, base = 0x2000000000, tid = 1062568 + +;; attach the debugger and halt +gc> (dbg) +[Debugger] PTRACE_ATTACHED! Waiting for process to stop... +Debugger connected. + +;; print the debugger status +gc> (dbs) + Listener connected? true + Debugger context? true + Attached? true + Halted? true + Context: valid = true, s7 = 0x147d24, base = 0x2000000000, tid = 1062568 +``` + +## `(:cont)` +Continue the target if it has been stopped. + +## `(:break)` +Immediately stop the target if it is running. Will print some registers. diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 34e6926ef0..eb9be79387 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -34,9 +34,7 @@ class Compiler { std::vector run_test_no_load(const std::string& source_code); void shutdown_target(); void enable_throw_on_redefines() { m_throw_on_define_extern_redefinition = true; } - Debugger& get_debugger() { - return m_debugger; - } + Debugger& get_debugger() { return m_debugger; } void poke_target() { m_listener.send_poke(); } @@ -193,6 +191,9 @@ class Compiler { // Debug Val* compile_dbg(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_dbs(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_break(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_cont(const goos::Object& form, const goos::Object& rest, Env* env); // Macro Val* compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env); diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index d017ad1966..fc1cf15a83 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -53,7 +53,10 @@ static const std::unordered_map< {"set!", &Compiler::compile_set}, // DEBUGGING + {"dbs", &Compiler::compile_dbs}, {"dbg", &Compiler::compile_dbg}, + {":cont", &Compiler::compile_cont}, + {":break", &Compiler::compile_break}, // TYPE {"deftype", &Compiler::compile_deftype}, diff --git a/goalc/compiler/compilation/Debug.cpp b/goalc/compiler/compilation/Debug.cpp index 9032353e64..7f535e8474 100644 --- a/goalc/compiler/compilation/Debug.cpp +++ b/goalc/compiler/compilation/Debug.cpp @@ -21,5 +21,64 @@ Val* Compiler::compile_dbg(const goos::Object& form, const goos::Object& rest, E } else { fmt::print("ERROR\n"); } + + return get_none(); +} + +Val* Compiler::compile_dbs(const goos::Object& form, const goos::Object& rest, Env* env) { + // todo - do something with args. + (void)form; + (void)rest; + (void)env; + + fmt::print(" Listener connected? {}\n", m_listener.is_connected()); + fmt::print(" Debugger context? {}\n", m_debugger.is_valid()); + if (m_debugger.is_valid()) { + fmt::print(" Attached? {}\n", m_debugger.is_attached()); + + if (m_debugger.is_attached()) { + fmt::print(" Halted? {}\n", m_debugger.is_halted()); + } + + fmt::print(" Context: {}\n", m_debugger.get_context_string()); + } + + if (m_debugger.is_valid()) { + } else { + fmt::print("There is no valid debug context from the target."); + } + + return get_none(); +} + +Val* Compiler::compile_cont(const goos::Object& form, const goos::Object& rest, Env* env) { + // todo - do something with args. + (void)form; + (void)rest; + (void)env; + + if (m_debugger.is_valid() && m_debugger.is_attached() && m_debugger.is_halted()) { + m_debugger.do_continue(); + } else { + fmt::print("Couldn't do :cont. Valid {}, attached {}, halted {}\n", m_debugger.is_valid(), + m_debugger.is_attached(), m_debugger.is_halted()); + } + + return get_none(); +} + +Val* Compiler::compile_break(const goos::Object& form, const goos::Object& rest, Env* env) { + // todo - do something with args. + (void)form; + (void)rest; + (void)env; + + if (m_debugger.is_valid() && m_debugger.is_attached() && m_debugger.is_running()) { + m_debugger.do_break(); + } else { + fmt::print("Couldn't do :break. Valid {}, attached {}, running {}\n", m_debugger.is_valid(), + m_debugger.is_attached(), m_debugger.is_running()); + } + return get_none(); } \ No newline at end of file diff --git a/goalc/debugger/Debugger.cpp b/goalc/debugger/Debugger.cpp index e7a47f363c..948adf3174 100644 --- a/goalc/debugger/Debugger.cpp +++ b/goalc/debugger/Debugger.cpp @@ -1,3 +1,4 @@ +#include #include "Debugger.h" #include "third-party/fmt/core.h" @@ -13,6 +14,10 @@ void Debugger::invalidate() { m_context_valid = false; } +bool Debugger::is_running() const { + return m_context_valid && m_attached && m_running; +} + bool Debugger::is_attached() const { return m_context_valid && m_attached; } @@ -43,6 +48,13 @@ bool Debugger::attach_and_break() { if (xdbg::attach_and_break(m_debug_context.tid)) { m_attached = true; m_running = false; + + xdbg::Regs regs; + if (!xdbg::get_regs_now(m_debug_context.tid, ®s)) { + fmt::print("[Debugger] get_regs_now failed after break, something is wrong\n"); + } else { + fmt::print("{}", regs.print_gprs()); + } return true; } } else { @@ -50,4 +62,30 @@ bool Debugger::attach_and_break() { is_valid(), m_attached); } return false; +} + +bool Debugger::do_break() { + assert(is_valid() && is_attached() && is_running()); + if (!xdbg::break_now(m_debug_context.tid)) { + return false; + } else { + m_running = false; + xdbg::Regs regs; + if (!xdbg::get_regs_now(m_debug_context.tid, ®s)) { + fmt::print("[Debugger] get_regs_now failed after break, something is wrong\n"); + } else { + fmt::print("{}", regs.print_gprs()); + } + return true; + } +} + +bool Debugger::do_continue() { + assert(is_valid() && is_attached() && is_halted()); + if (!xdbg::cont_now(m_debug_context.tid)) { + return false; + } else { + m_running = true; + return true; + } } \ No newline at end of file diff --git a/goalc/debugger/Debugger.h b/goalc/debugger/Debugger.h index 721b6ad05b..d811d9f0c4 100644 --- a/goalc/debugger/Debugger.h +++ b/goalc/debugger/Debugger.h @@ -6,7 +6,7 @@ class Debugger { public: Debugger() = default; - bool is_halted() const; // are we halted? + bool is_halted() const; // are we halted? bool is_valid() const; bool is_attached() const; bool is_running() const; @@ -17,6 +17,9 @@ class Debugger { bool attach_and_break(); + bool do_break(); + bool do_continue(); + private: xdbg::DebugContext m_debug_context; bool m_context_valid = false; diff --git a/goalc/listener/Listener.h b/goalc/listener/Listener.h index 3b6f41b52d..6dbfe0a97c 100644 --- a/goalc/listener/Listener.h +++ b/goalc/listener/Listener.h @@ -45,7 +45,6 @@ class Listener { bool get_load_entry(const std::string& name, LoadEntry* out = nullptr); std::vector get_all_loaded(); - private: void add_load(const std::string& name, const LoadEntry& le); void do_unload(const std::string& name); diff --git a/test/goalc/test_debugger.cpp b/test/goalc/test_debugger.cpp index f0e2edf15e..70f77339f5 100644 --- a/test/goalc/test_debugger.cpp +++ b/test/goalc/test_debugger.cpp @@ -21,4 +21,28 @@ TEST(Debugger, DebuggerBasicConnect) { EXPECT_TRUE(wait(nullptr) >= 0); } } + +TEST(Debugger, DebuggerBreakAndContinue) { + Compiler compiler; + // evidently you can't ptrace threads in your own process, so we need to run the runtime in a + // separate process. + if (!fork()) { + GoalTest::runtime_no_kernel(); + } else { + compiler.connect_to_target(); + compiler.poke_target(); + compiler.run_test("test/goalc/source_templates/with_game/attach-debugger.gc"); + EXPECT_TRUE(compiler.get_debugger().is_valid()); + EXPECT_TRUE(compiler.get_debugger().is_halted()); + for (int i = 0; i < 20; i++) { + EXPECT_TRUE(compiler.get_debugger().do_continue()); + EXPECT_TRUE(compiler.get_debugger().do_break()); + } + compiler.shutdown_target(); + + // and now the child process should be done! + EXPECT_TRUE(wait(nullptr) >= 0); + } +} + #endif From c480dbe3b15a69547f1de00896a7f2be2a5cb855 Mon Sep 17 00:00:00 2001 From: water Date: Sat, 31 Oct 2020 11:45:57 -0400 Subject: [PATCH 5/8] documentation and fix windows --- common/cross_os_debug/CMakeLists.txt | 4 +- common/cross_os_debug/xdbg.cpp | 57 ++++++++++++++++++++++++---- common/cross_os_debug/xdbg.h | 6 +++ goalc/debugger/Debugger.cpp | 49 ++++++++++++++++++++++-- goalc/debugger/Debugger.h | 5 +++ 5 files changed, 108 insertions(+), 13 deletions(-) diff --git a/common/cross_os_debug/CMakeLists.txt b/common/cross_os_debug/CMakeLists.txt index 74bb2b04ba..b29f36f8d5 100644 --- a/common/cross_os_debug/CMakeLists.txt +++ b/common/cross_os_debug/CMakeLists.txt @@ -1,2 +1,4 @@ add_library(cross_os_debug SHARED - xdbg.cpp) \ No newline at end of file + xdbg.cpp) + +target_link_libraries(cross_os_debug fmt) \ No newline at end of file diff --git a/common/cross_os_debug/xdbg.cpp b/common/cross_os_debug/xdbg.cpp index ec6b4298af..5ddd27c3e9 100644 --- a/common/cross_os_debug/xdbg.cpp +++ b/common/cross_os_debug/xdbg.cpp @@ -1,3 +1,8 @@ +/*! + * @file xdbg.cpp + * Debugging utility library. This hides the platform specific details of the debugger. + */ + #include #include "third-party/fmt/core.h" #include "xdbg.h" @@ -17,8 +22,14 @@ namespace xdbg { #ifdef __linux +/*! + * In Linux, a ThreadID is just the pid_t of the thread. + */ ThreadID::ThreadID(pid_t _id) : id(_id) {} +/*! + * In Linux, the string representation of a ThreadID is just the number printed in base 10 + */ ThreadID::ThreadID(const std::string& str) { id = std::stoi(str); } @@ -27,16 +38,36 @@ std::string ThreadID::to_string() const { return std::to_string(id); } +/*! + * Get the ThreadID of whatever called this function. + */ ThreadID get_current_thread_id() { return ThreadID(syscall(SYS_gettid)); } +/*! + * Called by the target to do any setup required for the debugger to attach (allowing tracing) + * Will be called from the GOAL thread. + */ +void allow_debugging() { + // modern Linux has "security features" which prevent processes from accessing memory of others. + // we disable these for the GOAL runtime process so the debugger can connect. + if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) < 0) { + printf("[Debugger] Failed to PR_SET_PTRACER %s\n", strerror(errno)); + } +} + +/*! + * Attach to the given thread ID and halt it. + */ bool attach_and_break(const ThreadID& tid) { + // SEIZE attaches without halting, but is required to use PTRACE_INTERRUPT in the future. auto rv = ptrace(PTRACE_SEIZE, tid.id, nullptr, nullptr); if (rv == -1) { printf("[Debugger] Failed to attach %s\n", strerror(errno)); return false; } else { + // we attached, now send break printf("[Debugger] PTRACE_ATTACHED! Waiting for process to stop...\n"); if (ptrace(PTRACE_INTERRUPT, tid.id, nullptr, nullptr) < 0) { printf("[Debugger] Failed to PTRACE_INTERRUPT %s\n", strerror(errno)); @@ -51,6 +82,7 @@ bool attach_and_break(const ThreadID& tid) { return false; } + // double check that we stopped for the right reason if (!WIFSTOPPED(status)) { printf("[Debugger] Failed to STOP: %s. The runtime is probably in a bad state now.\n", strerror(errno)); @@ -60,14 +92,9 @@ bool attach_and_break(const ThreadID& tid) { } } -void allow_debugging() { - // modern Linux has "security features" which prevent processes from accessing memory of others. - // we disable these for the GOAL runtime process so the debugger can connect. - if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) < 0) { - printf("[Debugger] Failed to PR_SET_PTRACER %s\n", strerror(errno)); - } -} - +/*! + * Detach from the given thread and resume it if it's halted. + */ bool detach_and_resume(const ThreadID& tid) { if (ptrace(PTRACE_DETACH, tid.id, nullptr, nullptr) < 0) { printf("[Debugger] Failed to detach: %s\n", strerror(errno)); @@ -76,6 +103,9 @@ bool detach_and_resume(const ThreadID& tid) { return true; } +/*! + * Get all registers now. Must be attached and stopped + */ bool get_regs_now(const ThreadID& tid, Regs* out) { user regs = {}; if (ptrace(PTRACE_GETREGS, tid.id, nullptr, ®s) < 0) { @@ -105,6 +135,10 @@ bool get_regs_now(const ThreadID& tid, Regs* out) { return true; } +/*! + * Break the given thread. Must be attached and running. + * Waits for the given thread to actually stop first. + */ bool break_now(const ThreadID& tid) { if (ptrace(PTRACE_INTERRUPT, tid.id, nullptr, nullptr) < 0) { printf("[Debugger] Failed to PTRACE_INTERRUPT %s\n", strerror(errno)); @@ -127,6 +161,9 @@ bool break_now(const ThreadID& tid) { return true; } +/*! + * Continue the given thread. Must be attached and not running. + */ bool cont_now(const ThreadID& tid) { if (ptrace(PTRACE_CONT, tid.id, nullptr, nullptr) < 0) { printf("[Debugger] Failed to PTRACE_CONT %s\n", strerror(errno)); @@ -180,6 +217,10 @@ bool get_regs_now(const ThreadID& tid, Regs* out) { const char* gpr_names[] = {"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", " r8", " r9", "r10", "r11", "r12", "r13", "r14", "r15"}; +/*! + * Print GPR register values, including rip. + * Splits into 5 lines. + */ std::string Regs::print_gprs() const { std::string result; for (int i = 0; i < 4; i++) { diff --git a/common/cross_os_debug/xdbg.h b/common/cross_os_debug/xdbg.h index 0eb7abd842..684fbcb2e2 100644 --- a/common/cross_os_debug/xdbg.h +++ b/common/cross_os_debug/xdbg.h @@ -1,3 +1,9 @@ +/*! + * @file xdbg.h + * Debugging utility library. This hides the platform specific details of the debugger. + * Nothing in here should hold state, that should all be managed in Debugger. + */ + #pragma once #include diff --git a/goalc/debugger/Debugger.cpp b/goalc/debugger/Debugger.cpp index 948adf3174..53c930df3a 100644 --- a/goalc/debugger/Debugger.cpp +++ b/goalc/debugger/Debugger.cpp @@ -1,27 +1,52 @@ +/*! + * @file Debugger.h + * The OpenGOAL debugger. + */ + #include #include "Debugger.h" #include "third-party/fmt/core.h" +/*! + * Is the target halted? If we don't know or aren't connected, returns false. + */ bool Debugger::is_halted() const { return m_context_valid && !m_running; } +/*! + * Is the target running and attached? Note that this returns false if it's running, but not + * attached to the debugger. + */ +bool Debugger::is_running() const { + return m_context_valid && m_attached && m_running; +} + +/*! + * Do we have a valid debugging context? Without this we cannot attach or do any debugging. + */ bool Debugger::is_valid() const { return m_context_valid; } +/*! + * Invalidate the current debugging context. For example if the target restarts. + */ void Debugger::invalidate() { m_context_valid = false; } -bool Debugger::is_running() const { - return m_context_valid && m_attached && m_running; -} - +/*! + * Are we attached to a valid target? + */ bool Debugger::is_attached() const { return m_context_valid && m_attached; } +/*! + * If attached, detach. If halted and attached, will unhalt. + * Will silently do nothing if we aren't attached. + */ void Debugger::detach() { if (is_valid() && m_attached) { xdbg::detach_and_resume(m_debug_context.tid); @@ -31,6 +56,9 @@ void Debugger::detach() { // todo, should we print something if we can't detach? } +/*! + * Set the debug context to allow Debugger to attach. + */ void Debugger::set_context(u32 s7, uintptr_t base, const std::string& thread_id) { m_debug_context.s7 = s7; m_debug_context.base = base; @@ -38,11 +66,18 @@ void Debugger::set_context(u32 s7, uintptr_t base, const std::string& thread_id) m_context_valid = true; } +/*! + * Get information about the context for debugging the debugger. + */ std::string Debugger::get_context_string() const { return fmt::format("valid = {}, s7 = 0x{:x}, base = 0x{:x}, tid = {}\n", is_valid(), m_debug_context.s7, m_debug_context.base, m_debug_context.tid.to_string()); } +/*! + * Attach the debugger to the current context (must be valid) and break. + * Returns once the target actually stops. + */ bool Debugger::attach_and_break() { if (is_valid() && !m_attached) { if (xdbg::attach_and_break(m_debug_context.tid)) { @@ -64,6 +99,9 @@ bool Debugger::attach_and_break() { return false; } +/*! + * Stop the target. Must be attached and not stopped. + */ bool Debugger::do_break() { assert(is_valid() && is_attached() && is_running()); if (!xdbg::break_now(m_debug_context.tid)) { @@ -80,6 +118,9 @@ bool Debugger::do_break() { } } +/*! + * Continue the target, must be attached and stopped. + */ bool Debugger::do_continue() { assert(is_valid() && is_attached() && is_halted()); if (!xdbg::cont_now(m_debug_context.tid)) { diff --git a/goalc/debugger/Debugger.h b/goalc/debugger/Debugger.h index d811d9f0c4..0653e8f426 100644 --- a/goalc/debugger/Debugger.h +++ b/goalc/debugger/Debugger.h @@ -1,3 +1,8 @@ +/*! + * @file Debugger.h + * The OpenGOAL debugger. + */ + #pragma once #include "common/common_types.h" From 490534be9dc88e86871d69d6efb16e97792539b2 Mon Sep 17 00:00:00 2001 From: water Date: Sat, 31 Oct 2020 11:51:38 -0400 Subject: [PATCH 6/8] make listener part of compiler, not a separate library --- goalc/CMakeLists.txt | 7 +++---- goalc/listener/CMakeLists.txt | 7 ------- test/CMakeLists.txt | 4 ++-- 3 files changed, 5 insertions(+), 13 deletions(-) delete mode 100644 goalc/listener/CMakeLists.txt diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 680212c8ea..d52ed1fb6c 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -1,5 +1,3 @@ -add_subdirectory(listener) - add_library(compiler SHARED emitter/CodeTester.cpp @@ -27,6 +25,7 @@ add_library(compiler compiler/Util.cpp debugger/Debugger.cpp logger/Logger.cpp + listener/Listener.cpp regalloc/IRegister.cpp regalloc/Allocator.cpp regalloc/allocate.cpp @@ -36,10 +35,10 @@ add_library(compiler add_executable(goalc main.cpp) IF (WIN32) - target_link_libraries(compiler goos type_system listener mman common_util spdlog) + target_link_libraries(compiler goos type_system mman common_util spdlog cross_os_debug cross_sockets) ELSE () - target_link_libraries(compiler goos type_system listener common_util spdlog) + target_link_libraries(compiler goos type_system common_util spdlog cross_os_debug cross_sockets) ENDIF () target_link_libraries(goalc goos compiler type_system) diff --git a/goalc/listener/CMakeLists.txt b/goalc/listener/CMakeLists.txt deleted file mode 100644 index e3e79ffecf..0000000000 --- a/goalc/listener/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -add_library(listener SHARED Listener.cpp) - -IF (WIN32) - target_link_libraries(listener cross_sockets cross_os_debug) -ELSE () - target_link_libraries(listener cross_sockets pthread cross_os_debug) -ENDIF () \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 91c6d9a9b5..1e1a315d3c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,9 +24,9 @@ IF (WIN32) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # TODO - split out these declarations for platform specific includes - target_link_libraries(goalc-test cross_sockets goos common_util listener runtime compiler type_system gtest mman) + target_link_libraries(goalc-test cross_sockets goos common_util runtime compiler type_system gtest mman) ELSE() - target_link_libraries(goalc-test cross_sockets goos common_util listener runtime compiler type_system gtest) + target_link_libraries(goalc-test cross_sockets goos common_util runtime compiler type_system gtest) ENDIF() if(CMAKE_COMPILER_IS_GNUCXX AND CODE_COVERAGE) From d8f0850c5059f6c7ee138e2f302ff851069e2295 Mon Sep 17 00:00:00 2001 From: water Date: Sat, 31 Oct 2020 13:54:39 -0400 Subject: [PATCH 7/8] implement memory read and write --- common/cross_os_debug/xdbg.cpp | 83 +++++++++++++++++++ common/cross_os_debug/xdbg.h | 25 ++++++ common/goal_constants.h | 9 ++ doc/goal_dbg_doc.md | 9 ++ game/runtime.cpp | 11 +-- goalc/compiler/Compiler.cpp | 29 ++++++- goalc/compiler/Compiler.h | 4 +- goalc/compiler/compilation/Atoms.cpp | 1 + goalc/compiler/compilation/Debug.cpp | 24 ++++++ goalc/debugger/Debugger.cpp | 17 +++- goalc/debugger/Debugger.h | 14 ++++ test/goalc/framework/test_runner.cpp | 5 +- .../with_game/attach-debugger.gc | 1 - test/goalc/test_debugger.cpp | 65 ++++++++++++++- test/goalc/test_strings.cpp | 2 +- test/goalc/test_with_game.cpp | 4 +- 16 files changed, 283 insertions(+), 20 deletions(-) delete mode 100644 test/goalc/source_templates/with_game/attach-debugger.gc diff --git a/common/cross_os_debug/xdbg.cpp b/common/cross_os_debug/xdbg.cpp index 5ddd27c3e9..895b589c9e 100644 --- a/common/cross_os_debug/xdbg.cpp +++ b/common/cross_os_debug/xdbg.cpp @@ -4,17 +4,21 @@ */ #include +#include "common/goal_constants.h" +#include "common/util/Timer.h" #include "third-party/fmt/core.h" #include "xdbg.h" #ifdef __linux #include +#include #include #include #include #include #include #include +#include #elif _WIN32 #endif @@ -92,6 +96,61 @@ bool attach_and_break(const ThreadID& tid) { } } +/*! + * Open memory of target. Assumes we are already connected and halted. + */ +bool open_memory(const ThreadID& tid, MemoryHandle* out) { + int fd = open(fmt::format("/proc/{}/mem", tid.id).c_str(), O_RDWR); + if (fd < -1) { + printf("[Debugger] Failed to open memory: %s.\n", strerror(errno)); + return false; + } + out->fd = fd; + return true; +} + +/*! + * Close memory of target. + */ +bool close_memory(const ThreadID& tid, MemoryHandle* handle) { + (void)tid; + if (close(handle->fd) < 0) { + printf("[Debugger] Failed to close memory: %s.\n", strerror(errno)); + return false; + } + return true; +} + +/*! + * Read data from target's EE memory + */ +bool read_goal_memory(u8* dest_buffer, + int size, + u32 goal_addr, + const DebugContext& context, + const MemoryHandle& mem) { + if (pread(mem.fd, dest_buffer, size, context.base + goal_addr) != size) { + printf("[Debugger] Failed to read memory: %s.\n", strerror(errno)); + return false; + } + return true; +} + +/*! + * Write data into target's EE memory + */ +bool write_goal_memory(const u8* src_buffer, + int size, + u32 goal_addr, + const DebugContext& context, + const MemoryHandle& mem) { + if (pwrite(mem.fd, src_buffer, size, context.base + goal_addr) != size) { + printf("[Debugger] Failed to write memory: %s.\n", strerror(errno)); + return false; + } + return true; +} + /*! * Detach from the given thread and resume it if it's halted. */ @@ -212,6 +271,30 @@ bool get_regs_now(const ThreadID& tid, Regs* out) { return false; } +bool open_memory(const ThreadID& tid, MemoryHandle* out) { + return false; +} + +bool close_memory(const ThreadID& tid, MemoryHandle* handle) { + return false; +} + +bool read_goal_memory(u8* dest_buffer, + int size, + u32 goal_addr, + const DebugContext& context, + const MemoryHandle& mem) { + return false; +} + +bool write_goal_memory(const u8* src_buffer, + int size, + u32 goal_addr, + const DebugContext& context, + const MemoryHandle& mem) { + return false; +} + #endif const char* gpr_names[] = {"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", diff --git a/common/cross_os_debug/xdbg.h b/common/cross_os_debug/xdbg.h index 684fbcb2e2..91411ce49c 100644 --- a/common/cross_os_debug/xdbg.h +++ b/common/cross_os_debug/xdbg.h @@ -28,6 +28,10 @@ struct ThreadID { ThreadID() = default; }; +struct MemoryHandle { + int fd; +}; + #elif _WIN32 struct ThreadID { // todo - whatever windows uses to identify a thread @@ -63,5 +67,26 @@ bool detach_and_resume(const ThreadID& tid); bool get_regs_now(const ThreadID& tid, Regs* out); bool break_now(const ThreadID& tid); bool cont_now(const ThreadID& tid); +bool open_memory(const ThreadID& tid, MemoryHandle* out); +bool close_memory(const ThreadID& tid, MemoryHandle* handle); +bool read_goal_memory(u8* dest_buffer, + int size, + u32 goal_addr, + const DebugContext& context, + const MemoryHandle& mem); + +bool write_goal_memory(const u8* src_buffer, + int size, + u32 goal_addr, + const DebugContext& context, + const MemoryHandle& mem); + +template +bool write_goal_value(T& value, + u32 goal_addr, + const DebugContext& context, + const MemoryHandle& handle) { + return write_goal_memory(&value, sizeof(value), goal_addr, context, handle); +} } // namespace xdbg diff --git a/common/goal_constants.h b/common/goal_constants.h index 6fe677a659..7759c81ec9 100644 --- a/common/goal_constants.h +++ b/common/goal_constants.h @@ -23,4 +23,13 @@ constexpr u32 GOAL_COPY_METHOD = 6; // method ID of GOAL copy constexpr u32 GOAL_RELOC_METHOD = 7; // method ID of GOAL relocate constexpr u32 GOAL_MEMUSAGE_METHOD = 8; // method ID of GOAL mem-usage +constexpr int EE_MAIN_MEM_LOW_PROTECT = 1024 * 1024; +constexpr int EE_MAIN_MEM_SIZE = 128 * (1 << 20); // 128 MB, same as PS2 TOOL +constexpr u64 EE_MAIN_MEM_MAP = 0x2000000000; // intentionally > 32-bit to catch pointer bugs + +// when true, attempt to map the EE memory in the low 2 GB of RAM +// this allows us to use EE pointers as real pointers. However, this might not always work, +// so this should be used only for debugging. +constexpr bool EE_MEM_LOW_MAP = false; + #endif // JAK_GOAL_CONSTANTS_H diff --git a/doc/goal_dbg_doc.md b/doc/goal_dbg_doc.md index a47d16755f..7e75acf48a 100644 --- a/doc/goal_dbg_doc.md +++ b/doc/goal_dbg_doc.md @@ -42,3 +42,12 @@ Continue the target if it has been stopped. ## `(:break)` Immediately stop the target if it is running. Will print some registers. + +## `(:dump-all-mem )` +Dump all GOAL memory to a file. Must be stopped. +``` +(:dump-all-mem "mem.bin") +``` +The path is relative to the Jak project folder. + +The file will be the exact size of `EE_MAIN_MEM_SIZE`, but the first `EE_LOW_MEM_PROTECT` bytes are zero, as these cannot be written or read. \ No newline at end of file diff --git a/game/runtime.cpp b/game/runtime.cpp index 8d325c90cc..cb9033c947 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -45,6 +45,8 @@ #include "game/overlord/overlord.h" #include "game/overlord/srpc.h" +#include "common/goal_constants.h" + u8* g_ee_main_mem = nullptr; namespace { @@ -95,13 +97,6 @@ void deci2_runner(SystemThreadInterface& iface) { } // EE System -constexpr int EE_MAIN_MEM_SIZE = 128 * (1 << 20); // 128 MB, same as PS2 TOOL -constexpr u64 EE_MAIN_MEM_MAP = 0x2000000000; // intentionally > 32-bit to catch pointer bugs - -// when true, attempt to map the EE memory in the low 2 GB of RAM -// this allows us to use EE pointers as real pointers. However, this might not always work, -// so this should be used only for debugging. -constexpr bool EE_MEM_LOW_MAP = false; /*! * SystemThread Function for the EE (PS2 Main CPU) @@ -137,7 +132,7 @@ void ee_runner(SystemThreadInterface& iface) { // prevent access to the first 1 MB of memory. // On the PS2 this is the kernel and can't be accessed either. // this may not work well on systems with a page size > 1 MB. - mprotect((void*)g_ee_main_mem, 1024 * 1024, PROT_NONE); + mprotect((void*)g_ee_main_mem, EE_MAIN_MEM_LOW_PROTECT, PROT_NONE); fileio_init_globals(); kboot_init_globals(); kdgo_init_globals(); diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 759e8e1615..ed6dcf20d6 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -173,10 +173,10 @@ std::vector Compiler::codegen_object_file(FileEnv* env) { return gen.run(); } -std::vector Compiler::run_test(const std::string& source_code) { +std::vector Compiler::run_test_from_file(const std::string& source_code) { try { if (!connect_to_target()) { - throw std::runtime_error("Compiler::run_test couldn't connect!"); + throw std::runtime_error("Compiler::run_test_from_file couldn't connect!"); } auto code = m_goos.reader.read_from_file({source_code}); @@ -198,6 +198,31 @@ std::vector Compiler::run_test(const std::string& source_code) { } } +std::vector Compiler::run_test_from_string(const std::string& src) { + try { + if (!connect_to_target()) { + throw std::runtime_error("Compiler::run_test_from_file couldn't connect!"); + } + + auto code = m_goos.reader.read_from_string({src}); + auto compiled = compile_object_file("test-code", code, true); + if (compiled->is_empty()) { + return {}; + } + color_object_file(compiled); + auto data = codegen_object_file(compiled); + m_listener.record_messages(ListenerMessageKind::MSG_PRINT); + m_listener.send_code(data); + if (!m_listener.most_recent_send_was_acked()) { + gLogger.log(MSG_ERR, "Runtime is not responding after sending test code. Did it crash?\n"); + } + return m_listener.stop_recording_messages(); + } catch (std::exception& e) { + fmt::print("[Compiler] Failed to compile test program from string {}: {}\n", src, e.what()); + throw e; + } +} + bool Compiler::connect_to_target() { if (!m_listener.is_connected()) { for (int i = 0; i < 1000; i++) { diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index eb9be79387..be5a0fa058 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -30,7 +30,8 @@ class Compiler { void ice(const std::string& err); None* get_none() { return m_none.get(); } - std::vector run_test(const std::string& source_code); + std::vector run_test_from_file(const std::string& source_code); + std::vector run_test_from_string(const std::string& src); std::vector run_test_no_load(const std::string& source_code); void shutdown_target(); void enable_throw_on_redefines() { m_throw_on_define_extern_redefinition = true; } @@ -194,6 +195,7 @@ class Compiler { Val* compile_dbs(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_break(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_cont(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_dump_all(const goos::Object& form, const goos::Object& rest, Env* env); // Macro Val* compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env); diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index fc1cf15a83..8ca7552f5a 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -57,6 +57,7 @@ static const std::unordered_map< {"dbg", &Compiler::compile_dbg}, {":cont", &Compiler::compile_cont}, {":break", &Compiler::compile_break}, + {":dump-all-mem", &Compiler::compile_dump_all}, // TYPE {"deftype", &Compiler::compile_deftype}, diff --git a/goalc/compiler/compilation/Debug.cpp b/goalc/compiler/compilation/Debug.cpp index 7f535e8474..531ba4eb96 100644 --- a/goalc/compiler/compilation/Debug.cpp +++ b/goalc/compiler/compilation/Debug.cpp @@ -1,4 +1,5 @@ #include "goalc/compiler/Compiler.h" +#include "common/util/FileUtil.h" #include "third-party/fmt/core.h" Val* Compiler::compile_dbg(const goos::Object& form, const goos::Object& rest, Env* env) { @@ -80,5 +81,28 @@ Val* Compiler::compile_break(const goos::Object& form, const goos::Object& rest, m_debugger.is_attached(), m_debugger.is_running()); } + return get_none(); +} + +Val* Compiler::compile_dump_all(const goos::Object& form, const goos::Object& rest, Env* env) { + (void)env; + if (!m_debugger.is_halted()) { + fmt::print("Couldn't dump memory. Must be attached and halted.\n"); + return get_none(); + } + + auto args = get_va(form, rest); + va_check(form, args, {{goos::ObjectType::STRING}}, {}); + auto dest_file = args.unnamed.at(0).as_string()->data; + auto buffer = new u8[EE_MAIN_MEM_SIZE]; + memset(buffer, 0, EE_MAIN_MEM_SIZE); + + if (!m_debugger.read_memory(buffer + EE_MAIN_MEM_LOW_PROTECT, + EE_MAIN_MEM_SIZE - EE_MAIN_MEM_LOW_PROTECT, + EE_MAIN_MEM_LOW_PROTECT)) { + fmt::print("Reading memory failed, not dumping.\n"); + } else { + file_util::write_binary_file(file_util::get_file_path({dest_file}), buffer, EE_MAIN_MEM_SIZE); + } return get_none(); } \ No newline at end of file diff --git a/goalc/debugger/Debugger.cpp b/goalc/debugger/Debugger.cpp index 53c930df3a..e1c578527d 100644 --- a/goalc/debugger/Debugger.cpp +++ b/goalc/debugger/Debugger.cpp @@ -11,7 +11,7 @@ * Is the target halted? If we don't know or aren't connected, returns false. */ bool Debugger::is_halted() const { - return m_context_valid && !m_running; + return m_context_valid && m_attached && !m_running; } /*! @@ -49,6 +49,7 @@ bool Debugger::is_attached() const { */ void Debugger::detach() { if (is_valid() && m_attached) { + xdbg::close_memory(m_debug_context.tid, &m_memory_handle); xdbg::detach_and_resume(m_debug_context.tid); m_context_valid = false; m_attached = false; @@ -81,6 +82,10 @@ std::string Debugger::get_context_string() const { bool Debugger::attach_and_break() { if (is_valid() && !m_attached) { if (xdbg::attach_and_break(m_debug_context.tid)) { + if (!xdbg::open_memory(m_debug_context.tid, &m_memory_handle)) { + return false; + } + m_attached = true; m_running = false; @@ -129,4 +134,14 @@ bool Debugger::do_continue() { m_running = true; return true; } +} + +bool Debugger::read_memory(u8* dest_buffer, int size, u32 goal_addr) { + assert(is_valid() && is_attached() && is_halted()); + return xdbg::read_goal_memory(dest_buffer, size, goal_addr, m_debug_context, m_memory_handle); +} + +bool Debugger::write_memory(const u8* src_buffer, int size, u32 goal_addr) { + assert(is_valid() && is_attached() && is_halted()); + return xdbg::write_goal_memory(src_buffer, size, goal_addr, m_debug_context, m_memory_handle); } \ No newline at end of file diff --git a/goalc/debugger/Debugger.h b/goalc/debugger/Debugger.h index 0653e8f426..d386e35723 100644 --- a/goalc/debugger/Debugger.h +++ b/goalc/debugger/Debugger.h @@ -25,8 +25,22 @@ class Debugger { bool do_break(); bool do_continue(); + bool read_memory(u8* dest_buffer, int size, u32 goal_addr); + bool write_memory(const u8* src_buffer, int size, u32 goal_addr); + + template + bool write_value(const T& value, u32 goal_addr) { + return write_memory((const u8*)&value, sizeof(T), goal_addr); + } + + template + bool read_value(T* value, u32 goal_addr) { + return read_memory((u8*)value, sizeof(T), goal_addr); + } + private: xdbg::DebugContext m_debug_context; + xdbg::MemoryHandle m_memory_handle; bool m_context_valid = false; bool m_running = true; bool m_attached = false; diff --git a/test/goalc/framework/test_runner.cpp b/test/goalc/framework/test_runner.cpp index b09333931a..2e18be2e9e 100644 --- a/test/goalc/framework/test_runner.cpp +++ b/test/goalc/framework/test_runner.cpp @@ -48,7 +48,8 @@ void CompilerTestRunner::run_test(const std::string& test_category, const std::vector& expected, MatchParam truncate) { fprintf(stderr, "Testing %s\n", test_file.c_str()); - auto result = c->run_test("test/goalc/source_generated/" + test_category + "/" + test_file); + auto result = + c->run_test_from_file("test/goalc/source_generated/" + test_category + "/" + test_file); if (!truncate.is_wildcard) { for (auto& x : result) { x = x.substr(0, truncate.value); @@ -85,7 +86,7 @@ void CompilerTestRunner::run_test(const std::string& test_category, void CompilerTestRunner::run_always_pass(const std::string& test_category, const std::string& test_file) { - c->run_test("test/goalc/source_generated/" + test_category + "/" + test_file); + c->run_test_from_file("test/goalc/source_generated/" + test_category + "/" + test_file); tests.push_back({{}, {}, test_file, true}); } diff --git a/test/goalc/source_templates/with_game/attach-debugger.gc b/test/goalc/source_templates/with_game/attach-debugger.gc deleted file mode 100644 index 694613d49e..0000000000 --- a/test/goalc/source_templates/with_game/attach-debugger.gc +++ /dev/null @@ -1 +0,0 @@ -(dbg) diff --git a/test/goalc/test_debugger.cpp b/test/goalc/test_debugger.cpp index 70f77339f5..332e4b66a9 100644 --- a/test/goalc/test_debugger.cpp +++ b/test/goalc/test_debugger.cpp @@ -9,10 +9,11 @@ TEST(Debugger, DebuggerBasicConnect) { // separate process. if (!fork()) { GoalTest::runtime_no_kernel(); + exit(0); } else { compiler.connect_to_target(); compiler.poke_target(); - compiler.run_test("test/goalc/source_templates/with_game/attach-debugger.gc"); + compiler.run_test_from_string("(dbg)"); EXPECT_TRUE(compiler.get_debugger().is_valid()); EXPECT_TRUE(compiler.get_debugger().is_halted()); compiler.shutdown_target(); // will detach/unhalt, then send the usual shutdown message @@ -28,10 +29,11 @@ TEST(Debugger, DebuggerBreakAndContinue) { // separate process. if (!fork()) { GoalTest::runtime_no_kernel(); + exit(0); } else { compiler.connect_to_target(); compiler.poke_target(); - compiler.run_test("test/goalc/source_templates/with_game/attach-debugger.gc"); + compiler.run_test_from_string("(dbg)"); EXPECT_TRUE(compiler.get_debugger().is_valid()); EXPECT_TRUE(compiler.get_debugger().is_halted()); for (int i = 0; i < 20; i++) { @@ -45,4 +47,63 @@ TEST(Debugger, DebuggerBreakAndContinue) { } } +TEST(Debugger, DebuggerReadMemory) { + Compiler compiler; + // evidently you can't ptrace threads in your own process, so we need to run the runtime in a + // separate process. + if (!fork()) { + GoalTest::runtime_no_kernel(); + exit(0); + } else { + compiler.connect_to_target(); + compiler.poke_target(); + compiler.run_test_from_string("(dbg)"); + EXPECT_TRUE(compiler.get_debugger().do_continue()); + auto result = compiler.run_test_from_string("\"test_string!\""); + EXPECT_TRUE(compiler.get_debugger().do_break()); + auto addr = std::stoi(result.at(0)); + u8 str_buff[256]; + compiler.get_debugger().read_memory(str_buff, 256, addr + 4); + + EXPECT_EQ(0, strcmp((char*)str_buff, "test_string!")); + compiler.shutdown_target(); + + // and now the child process should be done! + EXPECT_TRUE(wait(nullptr) >= 0); + } +} + +TEST(Debugger, DebuggerWriteMemory) { + Compiler compiler; + // evidently you can't ptrace threads in your own process, so we need to run the runtime in a + // separate process. + if (!fork()) { + GoalTest::runtime_no_kernel(); + exit(0); + } else { + compiler.connect_to_target(); + compiler.poke_target(); + compiler.run_test_from_string("(dbg)"); + EXPECT_TRUE(compiler.get_debugger().do_continue()); + auto result = compiler.run_test_from_string("(define x (the int 123)) 'x"); + EXPECT_TRUE(compiler.get_debugger().do_break()); + auto addr = std::stoi(result.at(0)); + u32 value; + EXPECT_TRUE(compiler.get_debugger().read_value(&value, addr)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(compiler.get_debugger().write_value(456, addr)); + EXPECT_TRUE(compiler.get_debugger().read_value(&value, addr)); + EXPECT_EQ(value, 456); + + EXPECT_TRUE(compiler.get_debugger().do_continue()); + result = compiler.run_test_from_string("x"); + EXPECT_EQ(456, std::stoi(result.at(0))); + + compiler.shutdown_target(); + + // and now the child process should be done! + EXPECT_TRUE(wait(nullptr) >= 0); + } +} + #endif diff --git a/test/goalc/test_strings.cpp b/test/goalc/test_strings.cpp index 41a5e061b9..252ddad9f9 100644 --- a/test/goalc/test_strings.cpp +++ b/test/goalc/test_strings.cpp @@ -85,4 +85,4 @@ TEST_F(StringTests, Formatting) { // expected += "test P (with type) 1447236\n"; // // // todo, finish format testing. -// runner.run_test("test-format.gc", {expected}, expected.size()); +// runner.run_test_from_file("test-format.gc", {expected}, expected.size()); diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index f1aa763e83..ef70673251 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -31,10 +31,10 @@ class WithGameTests : public testing::TestWithParam { runtime_thread = std::thread((GoalTest::runtime_with_kernel)); runner.c = &compiler; - compiler.run_test("test/goalc/source_templates/with_game/test-load-game.gc"); + compiler.run_test_from_file("test/goalc/source_templates/with_game/test-load-game.gc"); try { - compiler.run_test("test/goalc/source_templates/with_game/test-build-game.gc"); + compiler.run_test_from_file("test/goalc/source_templates/with_game/test-build-game.gc"); } catch (std::exception& e) { fprintf(stderr, "caught exception %s\n", e.what()); EXPECT_TRUE(false); From 5d9fae057570901c644e855992d4ebe7a92134eb Mon Sep 17 00:00:00 2001 From: water Date: Sat, 31 Oct 2020 14:00:17 -0400 Subject: [PATCH 8/8] fix for windows --- common/cross_os_debug/xdbg.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/cross_os_debug/xdbg.h b/common/cross_os_debug/xdbg.h index 91411ce49c..f58e2725de 100644 --- a/common/cross_os_debug/xdbg.h +++ b/common/cross_os_debug/xdbg.h @@ -39,6 +39,10 @@ struct ThreadID { ThreadID(const std::string& str); ThreadID(); // todo - add id type here, like in linux version }; + +struct MemoryHandle { + int a; +}; #endif struct DebugContext {