diff --git a/build.bat b/build.bat index 9d384c56..19c07e29 100644 --- a/build.bat +++ b/build.bat @@ -195,6 +195,7 @@ pushd build if "%raddbg%"=="1" set didbuild=1 && %compile% ..\src\raddbg\raddbg_main.c %compile_link% %link_icon% %out%raddbg.exe || exit /b 1 if "%raddbg_non_graphical%"=="1" set didbuild=1 && %compile% -DWM_STUB=1 -DR_BACKEND=R_BACKEND_STUB ..\src\raddbg\raddbg_main.c %compile_link% %link_icon% %out%raddbg_non_graphical.exe || exit /b 1 if "%com_shim%"=="1" set didbuild=1 && %compile% ..\src\com_shim\com_shim_main.c %compile_link% %out%com_shim.exe || exit /b 1 +if "%raddbg_server%"=="1" set didbuild=1 && %compile% ..\src\raddbg_server\raddbg_server_main.c %compile_link% %out%raddbg_server.exe || exit /b 1 if "%radlink%"=="1" set didbuild=1 && %compile% ..\src\linker\lnk.c %compile_link% %linker% /NOIMPLIB %linker% /NATVIS:"%~dp0\src\linker\linker.natvis" %out%radlink.exe || exit /b 1 if "%radbin%"=="1" set didbuild=1 && %compile% ..\src\radbin\radbin_main.c %compile_link% %out%radbin.exe || exit /b 1 if "%raddump%"=="1" set didbuild=1 && %compile% ..\src\raddump\raddump_main.c %compile_link% %out%raddump.exe || exit /b 1 diff --git a/src/raddbg_server/raddbg_server.c b/src/raddbg_server/raddbg_server.c new file mode 100644 index 00000000..ae447aab --- /dev/null +++ b/src/raddbg_server/raddbg_server.c @@ -0,0 +1,2 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) diff --git a/src/raddbg_server/raddbg_server.h b/src/raddbg_server/raddbg_server.h new file mode 100644 index 00000000..807cc683 --- /dev/null +++ b/src/raddbg_server/raddbg_server.h @@ -0,0 +1,92 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef RADDBG_SERVER_H +#define RADDBG_SERVER_H + +//////////////////////////////// +//~ rjf: Messages (Client -> Server) + +#define RDS_MSG_MAGIC 0x7273 + +typedef enum RDS_MsgKind +{ + RDS_MsgKind_Null, + + //- rjf: debuggee attaching/detaching ops + RDS_MsgKind_Launch, + RDS_MsgKind_Attach, + RDS_MsgKind_Kill, + RDS_MsgKind_Detach, + + //- rjf: running ops + RDS_MsgKind_Run, + RDS_MsgKind_SingleStep, + RDS_MsgKind_Halt, + + //- rjf: memory ops + RDS_MsgKind_MemoryReserve, + RDS_MsgKind_MemoryCommit, + RDS_MsgKind_MemoryDecommit, + RDS_MsgKind_MemoryRelease, + RDS_MsgKind_MemoryProtect, + RDS_MsgKind_MemoryRead, + RDS_MsgKind_MemoryWrite, + + //- rjf: thread ops + RDS_MsgKind_ThreadRegBlockRead, + RDS_MsgKind_ThreadRegBlockWrite, + RDS_MsgKind_ThreadGetModuleTLSVAddr, + + //- rjf: system ops + RDS_MsgKind_ListSystemProcesses, +} +RDS_MsgKind; + +typedef U32 RDS_MsgFlags; +enum +{ + RDS_MsgFlag_InheritEnv = (1<<0), + RDS_MsgFlag_DebugSubprocesses = (1<<1), + RDS_MsgFlag_IgnorePreviousException = (1<<2), + RDS_MsgFlag_RunEntitiesAreUnfrozen = (1<<3), + RDS_MsgFlag_RunEntitiesAreProcesses = (1<<4), +}; + +typedef struct RDS_Msg RDS_Msg; +struct RDS_Msg +{ + RDS_MsgKind kind; + RDS_MsgFlags flags; + U64 id; + + //- rjf: general parameters + Rng1U64 vaddr_range; + AccessFlags access_flags; + DMN_Handle entity; + U64 pid; + + //- rjf: process launching parameters + String8List command_line; + String8 path; + String8List env; + + //- rjf: running parameters + DMN_TrapChunkList traps; + DMN_HandleArray run_entities; +}; + +//////////////////////////////// +//~ rjf: Responses (Server -> Client responses to messages) + +typedef struct RDS_Response RDS_Response; +struct RDS_Response +{ + U64 id; + String8 data; + DMN_Handle entity; + U64 pid; + U64 demon_event_count; +}; + +#endif // RADDBG_SERVER_H diff --git a/src/raddbg_server/raddbg_server_main.c b/src/raddbg_server/raddbg_server_main.c new file mode 100644 index 00000000..ced90431 --- /dev/null +++ b/src/raddbg_server/raddbg_server_main.c @@ -0,0 +1,300 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Build Options + +#define BUILD_TITLE "The RAD Debugger Server" +#define BUILD_CONSOLE_INTERFACE 1 + +//////////////////////////////// +//~ rjf: Includes + +//- rjf: [h] +#include "base/base_inc.h" +#include "x64/x64_inc.h" +#include "arch/arch_inc.h" +#include "socket/socket_inc.h" +#include "coff/coff.h" +#include "coff/coff_parse.h" +#include "pe/pe.h" +#include "elf/elf.h" +#include "gnu/gnu.h" +#include "gnu/gnu_parse.h" +#include "elf/elf_parse.h" +#include "demon/demon_inc.h" +#include "raddbg_server/raddbg_server.h" + +//- rjf: [c] +#include "base/base_inc.c" +#include "x64/x64_inc.c" +#include "arch/arch_inc.c" +#include "socket/socket_inc.c" +#include "coff/coff.c" +#include "coff/coff_parse.c" +#include "pe/pe.c" +#include "elf/elf.c" +#include "gnu/gnu.c" +#include "gnu/gnu_parse.c" +#include "elf/elf_parse.c" +#include "demon/demon_inc.c" +#include "raddbg_server/raddbg_server.c" + +//////////////////////////////// +//~ rjf: Globals + +typedef struct RDS_State RDS_State; +struct RDS_State +{ + Arena *arena; + SOCK_Session sock_session; + + //- rjf: client -> demon messages + RWMutex c2d_rw_mutex; + CondVar c2d_cv; + Arena *c2d_arena; + String8List c2d_msgs; + + //- rjf: demon run state + B32 demon_is_running; +}; + +global RDS_State *rds_state = 0; + +//////////////////////////////// +//~ rjf: Demon Thread + +internal void +dmn_thread__entry_point(void *p) +{ + ThreadNameF("dmn_thread"); + DMN_CtrlCtx *ctrl_ctx = dmn_ctrl_begin(); + for(;;) + { + Temp scratch = scratch_begin(0, 0); + + //////////////////////////// + //- rjf: get next messages + // + String8List msgs = {0}; + RWMutexScope(rds_state->c2d_rw_mutex, 1) for(;;) + { + if(rds_state->c2d_msgs.node_count != 0) + { + msgs = str8_list_copy(scratch.arena, &rds_state->c2d_msgs); + MemoryZeroStruct(&rds_state->c2d_msgs); + arena_clear(rds_state->c2d_arena); + break; + } + cond_var_wait_rw_w(rds_state->c2d_cv, rds_state->c2d_rw_mutex, max_U64); + } + + //////////////////////////// + //- rjf: do messages + // + for(String8Node *msg_n = msgs.first; msg_n != 0; msg_n = msg_n->next) + { + ////////////////////////// + //- rjf: unpack message + // + String8 data = msg_n->string; + U16 magic_maybe = 0; + str8_deserial_read_struct(data, 0, &magic_maybe); + + ////////////////////////// + //- rjf: parse message + // + RDS_Msg msg = {0}; + if(magic_maybe == RDS_MSG_MAGIC) + { + U64 off = sizeof(magic_maybe); + + // rjf: read flat parts + off += str8_deserial_read_struct(data, off, &msg.kind); + off += str8_deserial_read_struct(data, off, &msg.flags); + off += str8_deserial_read_struct(data, off, &msg.id); + off += str8_deserial_read_struct(data, off, &msg.vaddr_range); + off += str8_deserial_read_struct(data, off, &msg.access_flags); + off += str8_deserial_read_struct(data, off, &msg.entity); + off += str8_deserial_read_struct(data, off, &msg.pid); + + // rjf: read command line strings + { + U64 cmd_line_string_count = 0; + off += str8_deserial_read_struct(data, off, &cmd_line_string_count); + for EachIndex(idx, cmd_line_string_count) + { + String8 string = {0}; + off += str8_deserial_read_struct(data, off, &string.size); + string = str8_substr(data, r1u64(off, off+string.size)); + str8_list_push(scratch.arena, &msg.command_line, string); + } + } + + // rjf: read path + { + String8 string = {0}; + off += str8_deserial_read_struct(data, off, &string.size); + string = str8_substr(data, r1u64(off, off+string.size)); + msg.path = string; + } + + // rjf: read environment strings + { + U64 env_string_count = 0; + off += str8_deserial_read_struct(data, off, &env_string_count); + for EachIndex(idx, env_string_count) + { + String8 string = {0}; + off += str8_deserial_read_struct(data, off, &string.size); + string = str8_substr(data, r1u64(off, off+string.size)); + str8_list_push(scratch.arena, &msg.env, string); + } + } + + // rjf: read traps + { + U64 trap_count = 0; + off += str8_deserial_read_struct(data, off, &trap_count); + for EachIndex(idx, trap_count) + { + DMN_Trap trap = {0}; + off += str8_deserial_read_struct(data, off, &trap.process); + off += str8_deserial_read_struct(data, off, &trap.vaddr); + off += str8_deserial_read_struct(data, off, &trap.id); + off += str8_deserial_read_struct(data, off, &trap.flags); + off += str8_deserial_read_struct(data, off, &trap.size); + dmn_trap_chunk_list_push(scratch.arena, &msg.traps, trap_count, &trap); + } + } + } + + ////////////////////////// + //- rjf: do message + // + switch(msg.kind) + { + default:{}break; + + //- rjf: debuggee attaching/detaching ops + + case RDS_MsgKind_Launch:{}break; + case RDS_MsgKind_Attach:{}break; + case RDS_MsgKind_Kill:{}break; + case RDS_MsgKind_Detach:{}break; + + //- rjf: running ops + + case RDS_MsgKind_Run:{}break; + case RDS_MsgKind_SingleStep:{}break; + case RDS_MsgKind_Halt:{}break; + + //- rjf: memory ops + + case RDS_MsgKind_MemoryReserve:{}break; + case RDS_MsgKind_MemoryCommit:{}break; + case RDS_MsgKind_MemoryDecommit:{}break; + case RDS_MsgKind_MemoryRelease:{}break; + case RDS_MsgKind_MemoryProtect:{}break; + case RDS_MsgKind_MemoryRead:{}break; + case RDS_MsgKind_MemoryWrite:{}break; + + //- rjf: thread ops + + case RDS_MsgKind_ThreadRegBlockRead:{}break; + case RDS_MsgKind_ThreadRegBlockWrite:{}break; + case RDS_MsgKind_ThreadGetModuleTLSVAddr:{}break; + + //- rjf: system ops + + case RDS_MsgKind_ListSystemProcesses: + { + + }break; + } + } + + scratch_end(scratch); + } +} + +//////////////////////////////// +//~ rjf: Entry Point + +internal void +entry_point(CmdLine *cmd_line) +{ + //- rjf: get port argument + U64 port = 7273; + { + U64 port_maybe = 0; + String8 port_arg_string = cmd_line_string(cmd_line, s("port")); + if(try_u64_from_str8_c_rules(port_arg_string, &port_maybe)) + { + port = port_maybe; + } + } + + //- rjf: set up shared state + Arena *arena = arena_alloc(); + rds_state = push_array(arena, RDS_State, 1); + rds_state->arena = arena; + rds_state->sock_session = sock_session_open((U16)port, 0); + rds_state->c2d_rw_mutex = rw_mutex_alloc(); + rds_state->c2d_cv = cond_var_alloc(); + rds_state->c2d_arena = arena_alloc(); + + //- rjf: launch demon thread + thread_launch(dmn_thread__entry_point, 0); + + //- rjf: loop: wait for messages, issue commands to demon + for(;;) + { + Temp scratch = scratch_begin(0, 0); + + //- rjf: get next message from client + fprintf(stderr, "[waiting for message]\n"); + SOCK_Protocol protocol = SOCK_Protocol_TCP; + SOCK_Endpoint endpoint = {0}; + String8 data = {0}; + sock_recv(scratch.arena, rds_state->sock_session, &protocol, &endpoint, &data, max_U64); + + //- rjf: parse magic prefix + U16 magic_maybe = 0; + str8_deserial_read_struct(data, 0, &magic_maybe); + + //- rjf: log message info + { + String8 endpoint_string = sock_string_from_endpoint(scratch.arena, endpoint); + fprintf(stderr, "[got message]\n"); + fprintf(stderr, " sender: %.*s\n", str8_varg(endpoint_string)); + fprintf(stderr, " magic: 0x%x\n", (int)magic_maybe); + } + + //- rjf: the only kind of message we handle on the main thread is halting, + // since we need to interrupt the actual controller. just look for that one + // code here, and handle it - otherwise, all we need to do is send the message + // blob to the demon thread. + RDS_MsgKind peek_kind = RDS_MsgKind_Null; + if(magic_maybe == RDS_MSG_MAGIC) + { + str8_deserial_read_struct(data, 2, &peek_kind); + if(peek_kind == RDS_MsgKind_Halt && rds_state->demon_is_running) + { + dmn_halt(0, 0); + } + } + + //- rjf: not halt? -> okay, send to demon thread, if it's ready. + if(magic_maybe == RDS_MSG_MAGIC && peek_kind != RDS_MsgKind_Halt && !rds_state->demon_is_running) + { + RWMutexScope(rds_state->c2d_rw_mutex, 1) + { + str8_list_push(rds_state->c2d_arena, &rds_state->c2d_msgs, data); + } + cond_var_broadcast(rds_state->c2d_cv); + } + + scratch_end(scratch); + } +} diff --git a/src/socket/socket.c b/src/socket/socket.c index b52c0b84..e6cb2996 100644 --- a/src/socket/socket.c +++ b/src/socket/socket.c @@ -96,3 +96,31 @@ sock_endpoint_from_string(String8 address_and_port) SOCK_Endpoint ep = sock_endpoint_from_string_port(addr, port); return ep; } + +internal String8 +sock_string_from_endpoint(Arena *arena, SOCK_Endpoint endpoint) +{ + String8 result = {0}; + switch(endpoint.kind) + { + default:{}break; + case SOCK_EndpointKind_IPv4: + { + result = str8f(arena, "%i.%i.%i.%i:%i", (int)endpoint.address_u8[0], (int)endpoint.address_u8[1], (int)endpoint.address_u8[2], (int)endpoint.address_u8[3], (int)endpoint.port); + }break; + case SOCK_EndpointKind_IPv6: + { + result = str8f(arena, "[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]:%i", + (int)endpoint.address_u16[0], + (int)endpoint.address_u16[1], + (int)endpoint.address_u16[2], + (int)endpoint.address_u16[3], + (int)endpoint.address_u16[4], + (int)endpoint.address_u16[5], + (int)endpoint.address_u16[6], + (int)endpoint.address_u16[7], + (int)endpoint.port); + }break; + } + return result; +} diff --git a/src/socket/socket.h b/src/socket/socket.h index f0307022..2903915b 100644 --- a/src/socket/socket.h +++ b/src/socket/socket.h @@ -59,6 +59,7 @@ typedef SOCK_WAKEUP_FUNCTION_DEF(SOCK_WakeupFunctionType); internal SOCK_Endpoint sock_endpoint_from_string_port(String8 address, U16 port); internal SOCK_Endpoint sock_endpoint_from_string(String8 address_and_port); +internal String8 sock_string_from_endpoint(Arena *arena, SOCK_Endpoint endpoint); //////////////////////////////// //~ rjf: @per_os_impl Top-Level Layer Calls