From 05d5de4b7da1aab2d976909a886812a69c93a247 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Sat, 10 Jan 2026 14:03:00 +0100 Subject: [PATCH] RemedyBG integration plugin --- src/text_editor/commands.cpp | 14 +- src/text_editor/remedybg_plugin.cpp | 2249 +++++++++++++++++++++++++++ src/text_editor/text_editor.cpp | 9 +- 3 files changed, 2265 insertions(+), 7 deletions(-) create mode 100644 src/text_editor/remedybg_plugin.cpp diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp index 2eea3a9..507e9f6 100644 --- a/src/text_editor/commands.cpp +++ b/src/text_editor/commands.cpp @@ -1112,6 +1112,12 @@ String Coro_CloseAllEx(mco_coro *co) { void Coro_Quit(mco_coro *co) { String res = Coro_CloseAllEx(co); if (res != "Cancel") { + For (GlobalHooks) { + if (it.kind == HookKind_AppQuit) { + ProfileScopeEx(it.name); + it.function({}); + } + } AppIsRunning = false; } } @@ -1120,9 +1126,15 @@ void CMD_Quit(HookParam param) { CoRemove("Coro_Quit"); CoData *data = CoAdd(Coro_Quit); CoResume(data); -} RegisterHook(CMD_Quit, HookKind_AppQuit, "", "Ask user which files he would like to save and exit"); +} RegisterCommand(CMD_Quit, "", "Ask user which files he would like to save and exit"); void CMD_QuitWithoutSaving(HookParam param) { + For (GlobalHooks) { + if (it.kind == HookKind_AppQuit) { + ProfileScopeEx(it.name); + it.function({}); + } + } AppIsRunning = false; } RegisterCommand(CMD_QuitWithoutSaving, "", "Self explanatory"); diff --git a/src/text_editor/remedybg_plugin.cpp b/src/text_editor/remedybg_plugin.cpp new file mode 100644 index 0000000..13d7488 --- /dev/null +++ b/src/text_editor/remedybg_plugin.cpp @@ -0,0 +1,2249 @@ +RegisterVariable(String, BinaryUnderDebug, "build/te.exe"); +RegisterVariable(String, RemedyBGPath, "remedybg.exe"); + +#if OS_WINDOWS + +#define RDBG_MAX_SERVERNAME_LEN 64 + +typedef uint8_t rdbg_Bool; + +// A rolling 32-bit integer is used for any command that takes or returns a UID. +// These UIDs are never persisted and as such, can change between runs of +// RemedyBG. Zero will never be a valid id. +typedef uint32_t rdbg_Id; + +// A string consists of a length followed by an UTF-8 encoded character array of +// 'length' bytes. Strings are never nul-terminated. +#pragma warning(push) +#pragma warning(disable: 4200) +#pragma pack(push, 1) +struct rdbg_String +{ + uint16_t len; + uint8_t data[0]; +}; +#pragma pack(pop) +#pragma warning(pop) + +enum rdbg_CommandResult +{ + RDBG_COMMAND_RESULT_UNKNOWN = 0, + + RDBG_COMMAND_RESULT_OK = 1, + + // Generic failure + RDBG_COMMAND_RESULT_FAIL = 2, + + // Result if the command is aborted due to a specified behavior and + // condition including RDBG_IF_DEBUGGING_TARGET_ABORT_COMMAND or + // RDBG_IF_SESSION_IS_MODIFIED_ABORT_COMMAND. The result can also be returned + // if an unnamed session is saved, prompts for a filename, and the user + // cancels this operation. + RDBG_COMMAND_RESULT_ABORTED = 3, + + // Result if the given command buffer given is less than 2 bytes or if the + // command is not one of the enumerated commands in rdbg_Command. + RDBG_COMMAND_RESULT_INVALID_COMMAND = 4, + + // Result if the response generated is too large to fit in the buffer. + RDBG_COMMAND_RESULT_BUFFER_TOO_SMALL = 5, + + // Result if an opening a file (i.e., a session, text file). + RDBG_COMMAND_RESULT_FAILED_OPENING_FILE = 6, + + // Result if saving a session fails. + RDBG_COMMAND_RESULT_FAILED_SAVING_SESSION = 7, + + // Result if the given ID is invalid. + RDBG_COMMAND_RESULT_INVALID_ID = 8, + + // Result if a command expects the target to be in a particular state (not + // debugging, debugging and suspended, or debugging and executing) and is + // not. + RDBG_COMMAND_RESULT_INVALID_TARGET_STATE = 9, + + // Result if an active configuration does not exist + RDBG_COMMAND_RESULT_FAILED_NO_ACTIVE_CONFIG = 10, + + // Result if the command does not apply to given breakpoint's kind + RDBG_COMMAND_RESULT_INVALID_BREAKPOINT_KIND = 11, +}; + +// Commands that take an rdbg_DebuggingTargetBehavior can specify what should +// happen in the case the target is being debugged. +enum rdbg_DebuggingTargetBehavior +{ + RDBG_IF_DEBUGGING_TARGET_STOP_DEBUGGING = 1, + RDBG_IF_DEBUGGING_TARGET_ABORT_COMMAND = 2 +}; + +// Commands that take an rdbg_ModifiedSessionBehavior can specify what should +// happen when there is an open, modified session. +enum rdbg_ModifiedSessionBehavior +{ + RDBG_IF_SESSION_IS_MODIFIED_SAVE_AND_CONTINUE = 1, + RDBG_IF_SESSION_IS_MODIFIED_CONTINUE_WITHOUT_SAVING = 2, + RDBG_IF_SESSION_IS_MODIFIED_ABORT_COMMAND = 3, +}; + +enum rdbg_TargetState +{ + RDBG_TARGET_STATE_NONE = 1, + RDBG_TARGET_STATE_SUSPENDED = 2, + RDBG_TARGET_STATE_EXECUTING = 3, +}; + +enum rdbg_BreakpointKind +{ + RDBG_BREAKPOINT_KIND_FUNCTION_NAME = 1, + RDBG_BREAKPOINT_KIND_FILENAME_LINE = 2, + RDBG_BREAKPOINT_KIND_ADDRESS = 3, + RDBG_BREAKPOINT_KIND_PROCESSOR = 4, +}; + +enum rdbg_ProcessorBreakpointAccessKind +{ + RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_WRITE = 1, + RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_READ_WRITE = 2, + RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_EXECUTE = 3, +}; + +enum rdbg_Command +{ + // Bring the RemedyBG window to the foreground and activate it. No additional + // arguments follow the command. Returns RDBG_COMMAND_RESULT_OK or + // RDBG_COMMAND_RESULT_FAIL. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_BRING_DEBUGGER_TO_FOREGROUND = 50, + + // Set the size and position of the RemedyBG window. + // + // [cmd :: rdbg_Command (uint16_t)] + // [x :: int32_t] + // [y :: int32_t] + // [width :: int32_t] + // [height :: int32_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SET_WINDOW_POS = 51, + + // Get the size and position of the RemedyBG window. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [x :: int32_t] + // [y :: int32_t] + // [width :: int32_t] + // [height :: int32_t] + // [is_maximized: rdbg_Bool] + RDBG_COMMAND_GET_WINDOW_POS = 52, + + // Set whether to automatically bring the debugger to the foreground whenever + // the target is suspended (breakpoint hit, exception, single-step complete, + // etc.). Defaults to true if not set. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bring_to_foreground_on_suspended :: rdbg_Bool (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SET_BRING_TO_FOREGROUND_ON_SUSPENDED = 53, + + // Exit the RemedyBG application. + // + // [cmd :: rdbg_Command (uint16_t)] + // [dtb :: rdbg_DebuggingTargetBehavior (uint8_t)] + // [msb :: rdbg_ModifiedSessionBehavior (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_EXIT_DEBUGGER = 75, + + // Session + // + + // Returns whether the current session is modified, or "dirty". + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [modified :: rdbg_Bool (uint8_t)] + RDBG_COMMAND_GET_IS_SESSION_MODIFIED = 100, + + // Returns the current session's filename. If the filename has not been set + // for the session then the result will be + // RDBG_COMMAND_RESULT_UNNAMED_SESSION and the length of |filename| will be + // zero. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [filename :: rdbg_String] + RDBG_COMMAND_GET_SESSION_FILENAME = 101, + + // Creates a new session. All configurations are cleared and reset. + // + // [cmd :: rdbg_Command (uint16_t)] + // [dtb :: rdbg_DebuggingTargetBehavior (uint8_t)] + // [msb :: rdbg_ModifiedSessionBehavior (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_NEW_SESSION = 102, + + // Open a session with the given filename. + // + // [command :: rdbg_Command (uint16_t)] + // [dtb :: rdbg_DebuggingTargetBehavior (uint8_t)] + // [msb :: rdbg_ModifiedSessionBehavior (uint8_t)] + // [filename :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_OPEN_SESSION = 103, + + // Save session with its current filename. If the filename is has not been + // specified for the session the user will be prompted. To save with a + // filename see RDBG_COMMAND_SAVE_AS_SESSION, instead. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SAVE_SESSION = 104, + + // Save session with a given filename. + // + // [cmd :: rdbg_Command (uint16_t)] + // [filename :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SAVE_AS_SESSION = 105, + + // Retrieve a list of configurations for the current session. + // + // [cmd :: rdbg_Command (uint16_t) + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_configs :: uint16_t] + // .FOR(num_configs) { + // [uid :: rdbg_Id (uint32_t)] + // [command :: rdbg_String] + // [command_args :: rdbg_String] + // [working_dir :: rdbg_String] + // [environment_vars :: rdbg_String] + // [inherit_environment_vars_from_parent :: rdbg_Bool] + // [break_at_nominal_entry_point :: rdbg_Bool] + // [name :: rdbg_String] + // } + RDBG_COMMAND_GET_SESSION_CONFIGS = 106, + + // Add a new session configuration to the current session. All string + // parameters accept zero length strings. Multiple environment variables + // should be newline, '\n', separated. Returns the a unique ID for the + // configuration. + // + // Note that 'name' is currently optional. + // + // [cmd :: rdbg_Command (uint16_t) + // [command :: rdbg_String] + // [command_args :: rdbg_String] + // [working_dir :: rdbg_String] + // [environment_vars :: rdbg_String] + // [inherit_environment_vars_from_parent :: rdbg_Bool] + // [break_at_nominal_entry_point :: rdbg_Bool] + // [name :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [uid :: rdbg_Id] + RDBG_COMMAND_ADD_SESSION_CONFIG = 107, + + // Sets the active configuration for a session by configuration ID. If the + // ID is not valid for the current session + // RDBG_COMMAND_RESULT_INVALID_ID is returned. + // + // [cmd :: rdbg_Command (uint16_t) + // [id :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SET_ACTIVE_SESSION_CONFIG = 108, + + // Deletes a session configuration by ID. If the ID is not valid for the + // current session RDBG_COMMAND_REMOVE_SESSION_CONFIG is returned. + // + // [cmd :: rdbg_Command (uint16_t) + // [id :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_SESSION_CONFIG = 109, + + // Deletes all session configurations in the current session. + // + // [cmd :: rdbg_Command (uint16_t) + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_ALL_SESSION_CONFIGS = 110, + + // Source Files + // + + // Opens the given file, if not already opened, and navigates to the + // specified line number. The line number is optional and can be elided from + // the command buffer. Returns result along with an ID for the file. + // + // [cmd :: rdbg_Command (uint16_t) + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [id :: rdbg_Id] + RDBG_COMMAND_GOTO_FILE_AT_LINE = 200, + + // Close the file with the given ID. + // + // [cmd :: rdbg_Command (uint16_t)] + // [id :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_CLOSE_FILE = 201, + + // Close all open files + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_CLOSE_ALL_FILES = 202, + + // Returns the current file. If no file is open, returns a zero ID, + // zero-length filename, and zero line number. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [id :: rdbg_Id] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + RDBG_COMMAND_GET_CURRENT_FILE = 203, + + // Retrieve a list of open files. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_files :: uint16_t] + // .FOR(num_files) { + // [id :: rdbg_Id] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // } + RDBG_COMMAND_GET_OPEN_FILES = 204, + + // + // Debugger Control + + // Returns the target state for the current session. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [staste :: rdbg_TargetState (uint16_t)] + RDBG_COMMAND_GET_TARGET_STATE = 300, + + // If the target is stopped, i.e., not currently being debugged, then start + // debugging the active configuration. Setting break_at_entry to true will + // stop execution at the at entry point specified in the configuration: + // either the nominal entry point, such as "main" or "WinMain" or the entry + // point function as described in the PE header. If the target is already + // being debugged, this will return RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // [break_at_entry_point :: rdbg_Bool (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_START_DEBUGGING = 301, + + // Stop debugging the target. If the target is not executing this will return + // RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STOP_DEBUGGING = 302, + + // Restart debugging if the target is being debugging (either suspended or + // executing) and the target was not attached to a process. Otherwise, + // returns RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_RESTART_DEBUGGING = 303, + + // Attach to a process by the given process-id. The value of + // |continue_execution| indicates whether the process should resume execution + // after attached. The debugger target behavior specifies what should happen + // in the case when the target is being debugged (suspended or executing). + // Can return: RDBG_COMMAND_RESULT_OK, RDBG_COMMAND_RESULT_FAIL, or + // RDBG_COMMAND_RESULT_ABORT. + // + // [cmd :: rdbg_Command (uint16_t)] + // [process_id :: uint32_t] + // [continue_execution :: rdbg_Bool] + // [dtb :: rdbg_DebuggingTargetBehavior (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_ATTACH_TO_PROCESS_BY_PID = 304, + + // Attach to a process by the given name. The first process found, in the + // case there are more than one with the same name, is used. The value of + // |continue_execution| indicates whether the process should resume execution + // after attached. The debugger target behavior specifies what should happen + // in the case when the target is being debugged (suspended or executing). + // Can return: RDBG_COMMAND_RESULT_OK, RDBG_COMMAND_RESULT_FAIL, or + // RDBG_COMMAND_RESULT_ABORT. + // + // [cmd :: rdbg_Command (uint16_t)] + // [process_name :: rdbg_String] + // [continue_execution :: rdbg_Bool] + // [dtb :: rdbg_DebuggingTargetBehavior (uint8_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_ATTACH_TO_PROCESS_BY_NAME = 305, + + // Detach from a target that is being debugged. Can return + // RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DETACH_FROM_PROCESS = 306, + + // With the target suspended, step into by line. If a function call occurs, + // this command will enter the function. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STEP_INTO_BY_LINE = 307, + + // With the target suspended, step into by instruction. If a function call + // occurs, this command will enter the function. Can return + // RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STEP_INTO_BY_INSTRUCTION = 308, + + // With the target suspended, step into by line. If a function call occurs, + // this command step over that function and not enter it. Can return + // return RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STEP_OVER_BY_LINE = 309, + + // With the target suspended, step into by instruction. If a function call + // occurs, this command will step over that function and not enter it. Can + // return RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STEP_OVER_BY_INSTRUCTION = 310, + + // With the target suspended, continue running to the call site of the + // current function, i.e., step out. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_STEP_OUT = 311, + + // With the target suspended, continue execution. Can return + // RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_CONTINUE_EXECUTION = 312, + + // When the target is not being debugged or is suspended, run to the given + // filename and line number. + // + // [cmd :: rdbg_Command (uint16_t)] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_RUN_TO_FILE_AT_LINE = 313, + + // Halt the execution of a target that is in the executing state. Can return + // RDBG_COMMAND_RESULT_OK or RDBG_COMMAND_RESULT_INVALID_TARGET_STATE. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_BREAK_EXECUTION = 314, + + // + // Breakpoints + + // Return the current list of breakpoints. These are the user requested + // breakpoints. Resolved breakpoint locations, if any, for a requested + // breakpoint can be obtained using RDBG_COMMAND_GET_BREAKPOINT_LOCATIONS. + // + // * Presently, module name is not used and will always be a zero length + // string. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_bps :: uint16_t] + // .FOR(num_bps) { + // [uid :: rdbg_Id] + // [enabled :: rdbg_Bool] + // [module_name :: rdbg_String] + // [condition_expr :: rdbg_String] + // [kind :: rdbg_BreakpointKind (uint8_t)] + // .SWITCH(kind) { + // .CASE(BreakpointKind_FunctionName): + // [function_name :: rdbg_String] + // [overload_id :: uint32_t] + // .CASE(BreakpointKind_FilenameLine): + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // .CASE(BreakpointKind_Address): + // [address :: uint64_t] + // .CASE(BreakpointKind_Processor): + // [addr_expression :: rdbg_String] + // [num_bytes :: uint8_t] + // [access_kind :: rdbg_ProcessorBreakpointAccessKind (uint8_t)] + // } + // } + RDBG_COMMAND_GET_BREAKPOINTS = 600, + + // Return the list of resolved locations for a particular breakpoint. If the + // ID is not valid for the current session RDBG_COMMAND_RESULT_INVALID_ID is + // returned. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_locs :: uint16_t] + // .FOR(num_locs) { + // [address :: uint64_t] + // [module_name :: rdbg_String] + // [filename :: rdbg_String] + // [actual_line_num :: uint32_t] + // } + RDBG_COMMAND_GET_BREAKPOINT_LOCATIONS = 601, + + // Return a list of function overloads for the given function name. If the + // target is being debugged (suspended or executing) then returns a list of + // function overloads for the given function name, otherwise + // RDBG_COMMAND_RESULT_INVALID_TARGET_STATE is returned. Note that, + // presently, all modules are searched for the given function. + // + // [cmd :: rdbg_Command (uint16_t)] + // [function_name :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_overloads :: uint8_t] + // .FOR(num_overloads) { + // [overload_id :: rdbg_Id] + // [signature :: rdbg_String] + // } + RDBG_COMMAND_GET_FUNCTION_OVERLOADS = 602, + + // Request a breakpoint at the given function name and overload. Pass an + // overload ID of zero to add requested breakpoints for all functions with + // the given name. + // + // [cmd :: rdbg_Command (uint16_t)] + // [function_name :: rdbg_String] + // [overload_id :: rdbg_Id] + // [condition_expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_COMMAND_ADD_BREAKPOINT_AT_FUNCTION = 603, + + // Request a breakpoint at the given source file and line number. + // + // [cmd :: rdbg_Command (uint16_t)] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // [condition_expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_COMMAND_ADD_BREAKPOINT_AT_FILENAME_LINE = 604, + + // Request a breakpoint at the given address. + // + // [cmd :: rdbg_Command (uint16_t)] + // [address :: uint64_t] + // [condition_expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_COMMAND_ADD_BREAKPOINT_AT_ADDRESS = 605, + + // Add a processor (hardware) breakpoint. + // + // [cmd :: rdbg_Command (uint16_t)] + // [addr_expression :: rdbg_String] + // [num_bytes :: uint8_t] + // [access_kind :: rdbg_ProcessorBreakpointAccessKind (uint8_t)] + // [condition_expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_COMMAND_ADD_PROCESSOR_BREAKPOINT = 606, + + // Sets the conditional expression for the given breakpoint. Can pass in a + // zero-length string for none. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // [condition_expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_SET_BREAKPOINT_CONDITION = 607, + + // Given an existing breakpoint of type RDBG_BREAKPOINT_KIND_FILENAME_LINE, + // update its line number to the given one-based value. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // [line_num :: uint32_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_UPDATE_BREAKPOINT_LINE = 608, + + // Enable or disable an existing breakpoint. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // [enable :: rdbg_Bool] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_ENABLE_BREAKPOINT = 609, + + // Delete an existing breakpoint. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_BREAKPOINT = 610, + + // Delete all existing breakpoints. + // + // [cmd :: rdbg_Command (uint16_t)] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_ALL_BREAKPOINTS = 611, + + // Return information about a specific user requested breakpoint. + // + // * Presently, module name is not used and will always be a zero length + // string. + // + // [cmd :: rdbg_Command (uint16_t)] + // [bp_id :: rdbg_Id] + // => + // [uid :: rdbg_Id] + // [enabled :: rdbg_Bool] + // [module_name :: rdbg_String] + // [condition_expr :: rdbg_String] + // [kind :: rdbg_BreakpointKind (uint8_t)] + // .SWITCH(kind) { + // .CASE(BreakpointKind_FunctionName): + // [function_name :: rdbg_String] + // [overload_id :: uint32_t] + // .CASE(BreakpointKind_FilenameLine): + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // .CASE(BreakpointKind_Address): + // [address :: uint64_t] + // .CASE(BreakpointKind_Processor): + // [addr_expression :: rdbg_String] + // [num_bytes :: uint8_t] + // [access_kind :: rdbg_ProcessorBreakpointAccessKind (uint8_t)] + // } + RDBG_COMMAND_GET_BREAKPOINT = 612, + + // + // Watch Window Expressions + + // Return a list of watch expressions for the given, one-based watch window, + // presently ranging in [1,8]. + // + // [cmd :: rdbg_Command (uint16_t)] + // [window_num :: uint8_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [num_watches :: uint16_t] + // .FOR(num_watches) { + // [uid :: rdbg_Id] + // [expr :: rdbg_String] + // [comment :: rdbg_String] + // } + RDBG_COMMAND_GET_WATCHES = 700, + + // Add a watch expresion to the given, one-based watch window. Presently, + // only single line comments are supported. Spaces will replace any newlines + // found in a comment. + // + // [cmd :: rdbg_Command (uint16_t)] + // [window_num :: uint8_t] + // [expr :: rdbg_String] + // [comment :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + // [uid :: rdbg_Id] + RDBG_COMMAND_ADD_WATCH = 701, + + // Updates the expression for a given watch + // + // [cmd :: rdbg_Command (uint16_t)] + // [uid :: rdbg_Id] + // [expr :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_UPDATE_WATCH_EXPRESSION = 702, + + // Updates the comment for a given watch + // + // [cmd :: rdbg_Command (uint16_t)] + // [uid :: rdbg_Id] + // [comment :: rdbg_String] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_UPDATE_WATCH_COMMENT = 703, + + // Delete the given watch + // + // [cmd :: rdbg_Command (uint16_t)] + // [uid :: rdbg_Id] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_WATCH = 704, + + // Delete all watches in the given watch window + // + // [cmd :: rdbg_Command (uint16_t)] + // [window_num :: uint8_t] + // -> + // [result :: rdbg_CommandResult (uint16_t)] + RDBG_COMMAND_DELETE_ALL_WATCHES = 705, +}; + +enum rdbg_SourceLocChangedReason +{ + RDBG_SOURCE_LOC_CHANGED_REASON_UNSPECIFIED = 0, + + // An open-file from the command-line updated the source location + RDBG_SOURCE_LOC_CHANGED_REASON_BY_COMMAND_LINE = 1, + + // A RDBG_COMMAND_GOTO_FILE_AT_LINE from a named-pipes driver updated the source location + RDBG_SOURCE_LOC_CHANGED_REASON_BY_DRIVER = 2, + + // A selection of a breakpoint in breakpoints pane updated the source location + RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_SELECTED = 3, + + // The current stack frame was changed in the callstack pane and updated the source location + RDBG_SOURCE_LOC_CHANGED_REASON_CURRENT_FRAME_CHANGED = 4, + + // The active thread was changed in the threads pane and updated the source location + RDBG_SOURCE_LOC_CHANGED_REASON_ACTIVE_THREAD_CHANGED = 5, + + // + // The process was suspended and updated the source location + // + RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_HIT = 6, + RDBG_SOURCE_LOC_CHANGED_REASON_EXCEPTION_HIT = 7, + RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OVER = 8, + RDBG_SOURCE_LOC_CHANGED_REASON_STEP_IN = 9, + RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OUT = 10, + RDBG_SOURCE_LOC_CHANGED_REASON_NON_USER_BREAKPOINT = 11, + RDBG_SOURCE_LOC_CHANGED_REASON_DEBUG_BREAK = 12, +}; + +enum rdbg_DebugEventKind +{ + // A target being debugged has exited. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [exit_code :: uint32_t] + RDBG_DEBUG_EVENT_KIND_EXIT_PROCESS = 100, + + // The target for the active configuration is now being debugged. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [process_id :: uint32_t] + RDBG_DEBUG_EVENT_KIND_TARGET_STARTED = 101, + + // The debugger has attached to a target process. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [process_id :: uint32_t] + RDBG_DEBUG_EVENT_KIND_TARGET_ATTACHED = 102, + + // The debugger has detached from a target process. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [process_id :: uint32_t] + RDBG_DEBUG_EVENT_KIND_TARGET_DETACHED = 103, + + // The debugger has transitioned from suspended to executing. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [process_id :: uint32_t] + RDBG_DEBUG_EVENT_KIND_TARGET_CONTINUED = 104, + + // The source location changed due to an event in the debugger. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + // [reason :: rdbg_SourceLocChangedReason (uint16_t) ] + RDBG_DEBUG_EVENT_KIND_SOURCE_LOCATION_CHANGED = 200, + + // An external text editor was requested to be launched for the current file + // and line number. The event is sent even if an external text editor is not + // configured and, if configured, even if the external text editor failed to + // launch for any reason. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [filename :: rdbg_String] + // [line_num :: uint32_t] + RDBG_DEBUG_EVENT_KIND_EXTERNAL_TEXT_EDITOR_LAUNCHED = 201, + + // A user breakpoint was hit + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_DEBUG_EVENT_KIND_BREAKPOINT_HIT = 600, + + // The breakpoint with the given ID has been resolved (has a valid location). + // This can happen if the breakpoint was set in module that became loaded, + // for instance. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_DEBUG_EVENT_KIND_BREAKPOINT_RESOLVED = 601, + + // A new user breakpoint was added. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_DEBUG_EVENT_KIND_BREAKPOINT_ADDED = 602, + + // A user breakpoint was modified. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_DEBUG_EVENT_KIND_BREAKPOINT_MODIFIED = 603, + + // A user breakpoint was removed. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [bp_id :: rdbg_Id] + RDBG_DEBUG_EVENT_KIND_BREAKPOINT_REMOVED = 604, + + // An OutputDebugString was received by the debugger. The given string will + // be UTF-8 encoded. + // + // [kind :: rdbg_DebugEventKind (uint16_t)] + // [str :: rdbg_String] + RDBG_DEBUG_EVENT_KIND_OUTPUT_DEBUG_STRING = 800, +}; + +// Sample usage of driving an instance of RemedyBG using named pipes. +// +// To use: first, start an named instance of RemedyBG using the "--servername" +// switch. +// +// remedybg.exe --servername {name} +// +// After RemedyBG is started, this sample can be run to control and make queries +// to the running instance. +// +// driver {name} +// +// The sample code demonstrates the usage of both these pipes and is written +// using non-overlapped IO for simplicity. Depending on your application, using +// non-blocking IO may be preferable. + + +#define COMMAND_BUF_SIZE 8192 +#define REPLY_BUF_SIZE 8192 +#define ERROR_MSG_LEN 512 + +enum PipeType +{ + DebugControlPipe, + DebugEventsPipe +}; + +struct rdb_Buffer +{ + uint8_t* data; + uint8_t* curr; + uint32_t capacity; + + bool err; // true if overflow (read or write) on the buffer +}; + +static void ReinitBuffer(struct rdb_Buffer* buf) +{ + buf->curr = buf->data; + buf->err = false; +} + +struct ClientContext +{ + HANDLE command_pipe_handle; + + struct rdb_Buffer cmd; + struct rdb_Buffer reply; + + // Stateful behavior so we don't have to pass these to every command that + // needs them. + enum rdbg_DebuggingTargetBehavior dbg_target_behavior; + enum rdbg_ModifiedSessionBehavior mod_session_behavior; + + char last_error[ERROR_MSG_LEN]; +}; + +static void WriteError(int err_msg_len, char* err_msg, char* format, ...) +{ + va_list args; + va_start(args, format); + + int n = vsnprintf(err_msg, err_msg_len, format, args); + if (n > 0) + { + FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, GetLastError(), 0, err_msg + n, + err_msg_len - n, 0); + } + + va_end(args); +} + +static bool ContextHadError(struct ClientContext* ctx) +{ + return ctx->last_error[0] != 0 || ctx->cmd.err || ctx->reply.err; +} + +// +// Utilities for working with a ClientContext buffers +// +#define SetErrIfOverwrite(buf, sz) \ + (buf)->err = (buf)->err || ((buf)->curr > ((buf)->data + (buf)->capacity) - sz) + +#define PushBuffer(buf, ty, val) \ + SetErrIfOverwrite(buf, sizeof(ty)); \ + if (!(buf)->err) { *(ty*)((buf)->curr) = (val); (buf)->curr += sizeof(ty); } + +#define PushCommand(buf, cmd) PushBuffer(buf, uint16_t, (uint16_t)(cmd)) +#define PushDebuggingTargetBehavior(buf, dtb) \ + PushBuffer(buf, uint8_t, (uint8_t)dtb) +#define PushModifiedSessionBehavior(buf, msb) \ + PushBuffer(buf, uint8_t, (uint8_t)msb) +#define PushBool(buf, val) PushBuffer(buf, uint8_t, (uint8_t)(val)) +#define PushId(buf, val) PushBuffer(buf, rdbg_Id, (rdbg_Id)(val)) +#define PushU8(buf, val) PushBuffer(buf, uint8_t, (uint8_t)(val)) +#define PushU32(buf, val) PushBuffer(buf, uint32_t, (uint32_t)(val)) +#define PushS32(buf, val) PushBuffer(buf, int32_t, (int32_t)(val)) +#define PushU64(buf, val) PushBuffer(buf, uint64_t, (uint64_t)(val)) +#define PushProcessorBreakpointAccessKind(buf, akind) \ + PushBuffer(buf, uint8_t, (uint8_t)akind) + +static void PushStringZ(struct rdb_Buffer* b, char* str) +{ + uint16_t len = str ? (uint16_t)strlen(str) : 0; + PushBuffer(b, uint16_t, len); + SetErrIfOverwrite(b, len); + if (!b->err && len > 0) + { + memcpy(b->curr, str, len); + b->curr += len; + } +} + +#define PopBuffer(buf, ty) ( \ + (buf)->err = (buf)->err || \ + ((buf)->curr > ((buf)->data + (buf)->capacity) - sizeof(ty)), \ + (buf)->curr += ((buf)->err == 0 ? sizeof(ty) : 0), \ + (buf)->err == 0 ? *(ty*)((buf)->curr - sizeof(ty)) : (ty)0 ) + +#define PopBool(buf) (rdbg_Bool)PopBuffer(buf, uint8_t) +#define PopU8(buf) PopBuffer(buf, uint8_t) +#define PopU16(buf) PopBuffer(buf, uint16_t) +#define PopU32(buf) PopBuffer(buf, uint32_t) +#define PopS32(buf) PopBuffer(buf, int32_t) +#define PopU64(buf) PopBuffer(buf, uint64_t) +#define PopId(buf) (rdbg_Id)PopU32(buf) +#define PopCommandResult(buf) (enum rdbg_CommandResult)PopBuffer(buf, uint16_t) +#define PopTargetState(buf) (enum rdbg_TargetState)PopBuffer(buf, uint16_t) +#define PopBreakpointKind(buf) (enum rdbg_BreakpointKind)PopBuffer(buf, uint8_t) +#define PopProcessorBreakpointAccessKind(buf) \ + (enum rdbg_ProcessorBreakpointAccessKind)PopBuffer(buf, uint8_t) +#define PopDebugEventKind(buf) (enum rdbg_DebugEventKind)PopBuffer(buf, uint16_t) + +static void PopString(struct rdb_Buffer* buf, struct rdbg_String** str) +{ + uint16_t len = PopBuffer(buf, uint16_t); + buf->err = buf->err || buf->curr > buf->data + buf->capacity + len; + if (!buf->err) + { + *str = (struct rdbg_String*)(buf->curr - sizeof(uint16_t)); + buf->curr += len; + } + else + { + *str = 0; + } +} + +#define PIPE_NAME_PREFIX "\\\\.\\pipe\\" +#define PIPE_NAME_PREFIX_LEN 9 + +bool InitConnection(char* server_name, enum PipeType pipe_type, + int last_error_len, char* last_error, HANDLE* ret_pipe_handle) +{ + bool result = false; + + unsigned len = (unsigned)strlen(server_name); + if (len <= RDBG_MAX_SERVERNAME_LEN) + { + char pipe_name[256] = PIPE_NAME_PREFIX; + strcat(pipe_name, server_name); + if (pipe_type == DebugEventsPipe) + { + strcat(pipe_name, "-events"); + } + + DWORD flags = pipe_type == DebugControlPipe ? + GENERIC_READ | GENERIC_WRITE : + GENERIC_READ | FILE_WRITE_ATTRIBUTES; + + HANDLE pipe_handle = CreateFile(pipe_name, flags, 0, NULL, OPEN_EXISTING, + 0, NULL); + if (pipe_handle != INVALID_HANDLE_VALUE) + { + DWORD new_mode = PIPE_READMODE_MESSAGE; + if (SetNamedPipeHandleState(pipe_handle, &new_mode, NULL, NULL)) + { + *ret_pipe_handle = pipe_handle; + result = true; + } + else + { + WriteError(last_error_len, last_error, + "SetNamedPipeHandleState failed: "); + CloseHandle(pipe_handle); + } + } + else + { + WriteError(last_error_len, last_error, "CreateFile failed: "); + } + } + else + { + WriteError(last_error_len, last_error, "Server name too long."); + } + + return result; +} + +static void CloseConnection(struct ClientContext* ctx) +{ + CloseHandle(ctx->command_pipe_handle); +} + +static void TransactCommand(struct ClientContext* ctx) +{ + DWORD bytes_read = 0; + BOOL res = 0; + struct rdb_Buffer* reply = &ctx->reply; + + ReinitBuffer(reply); + + if (!ContextHadError(ctx)) + { + uint8_t* write_ptr = reply->data; + + res = TransactNamedPipe(ctx->command_pipe_handle, ctx->cmd.data, + (uint32_t)(ctx->cmd.curr - ctx->cmd.data), write_ptr, + REPLY_BUF_SIZE, &bytes_read, NULL); + write_ptr += bytes_read; + + while (!res && GetLastError() == ERROR_MORE_DATA) + { + int bytes_left = REPLY_BUF_SIZE - (int)(write_ptr - reply->data); + if (bytes_left > 0) + { + res = ReadFile(ctx->command_pipe_handle, write_ptr, bytes_left, + &bytes_read, NULL); + write_ptr += bytes_read; + } + else + break; // reply buffer full + } + if (res) + { + reply->capacity = (uint32_t)(write_ptr - reply->data); + } + else + { + WriteError(sizeof(ctx->last_error), ctx->last_error, + "TransactCommand failed: "); + } + } +} + +#define BeginCommand(ctx, c) \ + ReinitBuffer(&((ctx)->cmd)); \ + PushCommand(&((ctx)->cmd), c) + +#define SendCommand(ctx, c, res) \ + BeginCommand(ctx, c); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define BeginCommandWithFlags(ctx, c) \ + BeginCommand(ctx, c); \ + PushDebuggingTargetBehavior(&((ctx)->cmd), (ctx)->dbg_target_behavior); \ + PushModifiedSessionBehavior(&((ctx)->cmd), (ctx)->mod_session_behavior) + +#define SendCommandWithFlags(ctx, c, res) \ + BeginCommandWithFlags(ctx, c); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define BringDebuggerToForeground(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_BRING_DEBUGGER_TO_FOREGROUND, res) + +#define SetDebuggerWindowPos(ctx, x, y, cx, cy, res) \ + BeginCommand(ctx, RDBG_COMMAND_SET_WINDOW_POS); \ + PushS32(&(ctx)->cmd, x); \ + PushS32(&(ctx)->cmd, y); \ + PushS32(&(ctx)->cmd, cx); \ + PushS32(&(ctx)->cmd, cy); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define GetDebuggerWindowPos(ctx, res, x, y, cx, cy, is_maximized) \ + SendCommand(ctx, RDBG_COMMAND_GET_WINDOW_POS, res); \ + *(x) = PopId(&((ctx)->reply)); \ + *(y) = PopId(&((ctx)->reply)); \ + *(cx) = PopId(&((ctx)->reply)); \ + *(cy) = PopId(&((ctx)->reply)); \ + *(is_maximized) = PopBool(&((ctx)->reply)) != 0 + +#define SetBringToForegroundOnSuspended(ctx, btfos, res) \ + BeginCommand(ctx, RDBG_COMMAND_SET_BRING_TO_FOREGROUND_ON_SUSPENDED); \ + PushBool(&(ctx)->cmd, btfos); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define ExitDebugger(ctx, res) \ + SendCommandWithFlags(ctx, RDBG_COMMAND_EXIT_DEBUGGER, res) + +#define GetIsSessionModified(ctx, res, is_modified) \ + SendCommand(ctx, RDBG_COMMAND_GET_IS_SESSION_MODIFIED, res); \ + *(is_modified) = PopBool(&((ctx)->reply)) != 0 + +#define GetSessionFilename(ctx, res, filename) \ + SendCommand(ctx, RDBG_COMMAND_GET_SESSION_FILENAME, res); \ + PopString(&((ctx)->reply), filename) + +#define NewSession(ctx, res) \ + SendCommandWithFlags(ctx, RDBG_COMMAND_NEW_SESSION, res) + +#define SendCommandWithString(ctx, c, str, res) \ + BeginCommand(ctx, c); \ + PushStringZ(&(ctx)->cmd, str); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define OpenSession(ctx, filename, res) \ + SendCommandWithString(ctx, RDBG_COMMAND_OPEN_SESSION, filename, res) + +#define SaveSession(ctx, res) \ + SendCommandWithFlags(ctx, RDBG_COMMAND_SAVE_SESSION, res) + +#define SaveAsSession(ctx, filename, res) \ + SendCommandWithString(ctx, RDBG_COMMAND_SAVE_AS_SESSION, filename, res) + +#define GetSessionConfigs(ctx, res, cfg_it) \ + SendCommand(ctx, RDBG_COMMAND_GET_SESSION_CONFIGS, res); \ + BufIterator_Init(cfg_it, PopBuffer(&((ctx)->reply), uint16_t), \ + ((ctx)->reply)) + +#define AddSessionConfig(ctx, command, args, wdir, env, inh, brk, res, id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_SESSION_CONFIG); \ + PushStringZ(&(ctx)->cmd, command); \ + PushStringZ(&(ctx)->cmd, args); \ + PushStringZ(&(ctx)->cmd, wdir); \ + PushStringZ(&(ctx)->cmd, env); \ + PushBool(&(ctx)->cmd, inh); \ + PushBool(&(ctx)->cmd, brk); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(id) = PopId(&((ctx)->reply)) + +#define SendCommandWithId(ctx, c, id, res) \ + BeginCommand(ctx, c); \ + PushId(&(ctx)->cmd, id); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define SetActiveSessionConfig(ctx, id, res) \ + SendCommandWithId(ctx, RDBG_COMMAND_SET_ACTIVE_SESSION_CONFIG, id, res) + +#define DeleteSessionConfig(ctx, id, res) \ + SendCommandWithId(ctx, RDBG_COMMAND_DELETE_SESSION_CONFIG, id, res) + +#define DeleteAllSessionConfigs(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_DELETE_ALL_SESSION_CONFIGS, res) + +#define GoToFileAtLine(ctx, filename, line, res, id) \ + BeginCommand(ctx, RDBG_COMMAND_GOTO_FILE_AT_LINE); \ + PushStringZ(&(ctx)->cmd, filename); \ + PushBuffer(&(ctx)->cmd, uint32_t, line); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(id) = PopId(&((ctx)->reply)) + +#define CloseFileById(ctx, id, res) \ + SendCommandWithId(ctx, RDBG_COMMAND_CLOSE_FILE, id, res) + +#define CloseAllFiles(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_CLOSE_ALL_FILES, res) + +#define GetCurrentFile(ctx, res, file_id, filename, line_num) \ + SendCommand(ctx, RDBG_COMMAND_GET_CURRENT_FILE, res); \ + *(file_id) = PopId(&(ctx)->reply); \ + PopString(&(ctx)->reply, filename); \ + *(line_num) = PopBuffer(&(ctx)->reply, uint32_t) + +#define GetOpenFiles(ctx, res, file_it) \ + SendCommand(ctx, RDBG_COMMAND_GET_OPEN_FILES, res); \ + BufIterator_Init(file_it, PopBuffer(&((ctx)->reply), uint16_t), ((ctx)->reply)) + +#define StartDebugging(ctx, break_at_entry_point, res) \ + BeginCommand(ctx, RDBG_COMMAND_START_DEBUGGING); \ + PushBool(&(ctx)->cmd, break_at_entry_point); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define StopDebugging(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STOP_DEBUGGING, res) + +#define RestartDebugging(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_RESTART_DEBUGGING, res) + +#define AttachToProcessById(ctx, pid, cnt, res) \ + BeginCommand(ctx, RDBG_COMMAND_ATTACH_TO_PROCESS_BY_PID); \ + PushBuffer(&(ctx)->cmd, uint32_t, (uint32_t)(pid)); \ + PushBool(&(ctx)->cmd, cnt); \ + PushDebuggingTargetBehavior(&((ctx)->cmd), (ctx)->dbg_target_behavior); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define AttachToProcessByName(ctx, name, cnt, res) \ + BeginCommand(ctx, RDBG_COMMAND_ATTACH_TO_PROCESS_BY_NAME); \ + PushStringZ(&(ctx)->cmd, name); \ + PushBool(&(ctx)->cmd, cnt); \ + PushDebuggingTargetBehavior(&((ctx)->cmd), (ctx)->dbg_target_behavior); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define DetachFromProcess(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_DETACH_FROM_PROCESS, res) + +#define StepIntoByLine(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STEP_INTO_BY_LINE, res) + +#define StepIntoByInstruction(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STEP_INTO_BY_INSTRUCTION, res) + +#define StepOverByLine(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STEP_OVER_BY_LINE, res) + +#define StepOverByInstruction(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STEP_OVER_BY_INSTRUCTION, res) + +#define StepOut(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_STEP_OUT, res) + +#define ContinueExecution(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_CONTINUE_EXECUTION, res) + +#define RunToFileAtLine(ctx, filename, line, res) \ + BeginCommand(ctx, RDBG_COMMAND_RUN_TO_FILE_AT_LINE); \ + PushStringZ(&(ctx)->cmd, filename); \ + PushBuffer(&(ctx)->cmd, uint32_t, line); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define BreakExecution(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_BREAK_EXECUTION, res) + +#define GetTargetState(ctx, res, state) \ + SendCommand(ctx, RDBG_COMMAND_GET_TARGET_STATE, res); \ + *(state) = PopTargetState(&((ctx)->reply)) + +#define GetAllBreakpoints(ctx, res, bp_it) \ + SendCommand(ctx, RDBG_COMMAND_GET_BREAKPOINTS, res); \ + BufIterator_Init(bp_it, PopBuffer(&((ctx)->reply), uint16_t), ((ctx)->reply)) + +#define GetBreakpoint(ctx, id, res, bp) \ + SendCommandWithId(ctx, RDBG_COMMAND_GET_BREAKPOINT, id, res); \ + PopBreakpoint(&((ctx)->reply), bp) + +#define GetBreakpointLocations(ctx, id, res, num_locs) \ + SendCommandWithId(ctx, RDBG_COMMAND_GET_BREAKPOINT_LOCATIONS, id, res); \ + *(num_locs) = PopU16(&((ctx)->reply)) + +#define GetFunctionOverloads(ctx, res, fn_name, fno_it) \ + BeginCommand(ctx, RDBG_COMMAND_GET_FUNCTION_OVERLOADS); \ + PushStringZ(&(ctx)->cmd, fn_name); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + BufIterator_Init(fno_it, PopU8(&((ctx)->reply)), ((ctx)->reply)) + +#define AddBreakpointAtFn(ctx, fn_name, oid, cond, res, bp_id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_BREAKPOINT_AT_FUNCTION); \ + PushStringZ(&(ctx)->cmd, fn_name); \ + PushId(&(ctx)->cmd, oid) \ + PushStringZ(&(ctx)->cmd, cond); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(bp_id) = PopId(&((ctx)->reply)) + +#define AddBreakpointAtFilenameLine(ctx, file, line, cond, res, bp_id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_BREAKPOINT_AT_FILENAME_LINE); \ + PushStringZ(&(ctx)->cmd, file); \ + PushU32(&(ctx)->cmd, line) \ + PushStringZ(&(ctx)->cmd, cond); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(bp_id) = PopId(&((ctx)->reply)) + +#define AddBreakpointAtAddress(ctx, addr, cond, res, bp_id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_BREAKPOINT_AT_ADDRESS); \ + PushU64(&(ctx)->cmd, addr) \ + PushStringZ(&(ctx)->cmd, cond); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(bp_id) = PopId(&((ctx)->reply)) + +#define AddProcessorBreakpoint(ctx, addr_expr, nbytes, akind, cond, res, bp_id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_PROCESSOR_BREAKPOINT); \ + PushStringZ(&(ctx)->cmd, addr_expr); \ + PushU8(&(ctx)->cmd, nbytes); \ + PushProcessorBreakpointAccessKind(&(ctx)->cmd, akind); \ + PushStringZ(&(ctx)->cmd, cond); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *(bp_id) = PopId(&((ctx)->reply)) + +#define SetBreakpointCondition(ctx, bp_id, cond, res) \ + BeginCommand(ctx, RDBG_COMMAND_SET_BREAKPOINT_CONDITION); \ + PushId(&(ctx)->cmd, bp_id) \ + PushStringZ(&(ctx)->cmd, cond); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define UpdateBreakpointLine(ctx, bp_id, line, res) \ + BeginCommand(ctx, RDBG_COMMAND_UPDATE_BREAKPOINT_LINE); \ + PushId(&(ctx)->cmd, bp_id); \ + PushU32(&(ctx)->cmd, line); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define EnableBreakpoint(ctx, bp_id, enable, res) \ + BeginCommand(ctx, RDBG_COMMAND_ENABLE_BREAKPOINT); \ + PushId(&(ctx)->cmd, bp_id); \ + PushBool(&(ctx)->cmd, enable); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define DeleteBreakpoint(ctx, bp_id, res) \ + BeginCommand(ctx, RDBG_COMMAND_DELETE_BREAKPOINT); \ + PushId(&(ctx)->cmd, bp_id); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define DeleteAllBreakpoints(ctx, res) \ + SendCommand(ctx, RDBG_COMMAND_DELETE_ALL_BREAKPOINTS, res) + +#define GetWatches(ctx, window_num, res, it) \ + BeginCommand(ctx, RDBG_COMMAND_GET_WATCHES); \ + PushU8(&(ctx)->cmd, window_num); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + BufIterator_Init(it, PopU16(&((ctx)->reply)), ((ctx)->reply)) + +#define AddWatch(ctx, window_num, expr, comment, res, id) \ + BeginCommand(ctx, RDBG_COMMAND_ADD_WATCH); \ + PushU8(&(ctx)->cmd, window_num); \ + PushStringZ(&(ctx)->cmd, expr); \ + PushStringZ(&(ctx)->cmd, comment); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)); \ + *id = PopId(&((ctx)->reply)) + +#define UpdateWatchExpression(ctx, id, expr, res) \ + BeginCommand(ctx, RDBG_COMMAND_UPDATE_WATCH_EXPRESSION); \ + PushId(&(ctx)->cmd, id); \ + PushStringZ(&(ctx)->cmd, expr); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define UpdateWatchComment(ctx, id, comment, res) \ + BeginCommand(ctx, RDBG_COMMAND_UPDATE_WATCH_COMMENT); \ + PushId(&(ctx)->cmd, id); \ + PushStringZ(&(ctx)->cmd, comment); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +#define DeleteWatch(ctx, id, res) \ + SendCommandWithId(ctx, RDBG_COMMAND_DELETE_WATCH, id, res) + +#define DeleteAllWatches(ctx, window_num, res) \ + BeginCommand(ctx, RDBG_COMMAND_DELETE_ALL_WATCHES); \ + PushU8(&(ctx)->cmd, window_num); \ + TransactCommand((ctx)); \ + *(res) = PopCommandResult(&((ctx)->reply)) + +struct BufIterator +{ + int n; + int cur_idx; + struct rdb_Buffer buf; +}; + +static void BufIterator_Init(struct BufIterator* it, int n, + struct rdb_Buffer b) +{ + it->n = n; + it->cur_idx = -1; + + // Make a copy of reply buffer so we can make additional calls within loop. + static uint8_t it_buf[REPLY_BUF_SIZE]; + it->buf.data = it_buf; + it->buf.curr = it_buf + (b.curr - b.data); + it->buf.capacity = b.capacity; + it->buf.err = b.err; + if (!b.err) + { + memcpy(it->buf.data, b.data, b.capacity); + } +} + +struct SessionConfig +{ + rdbg_Id id; + struct rdbg_String* command; + struct rdbg_String* command_args; + struct rdbg_String* working_dir; + struct rdbg_String* environment_vars; + rdbg_Bool inherit_environment_vars_from_parent; + rdbg_Bool break_at_nominal_entry_point; +}; + +static bool SessionConfigIt_Next(struct BufIterator* it, + struct SessionConfig* cfg) +{ + bool result = false; + if (++it->cur_idx < it->n) + { + struct rdb_Buffer* b = &it->buf; + + cfg->id = PopId(b); + PopString(b, &cfg->command); + PopString(b, &cfg->command_args); + PopString(b, &cfg->working_dir); + PopString(b, &cfg->environment_vars); + cfg->inherit_environment_vars_from_parent = PopBool(b); + cfg->break_at_nominal_entry_point = PopBool(b); + + result = true; + } + return result; +} + +struct File +{ + rdbg_Id id; + struct rdbg_String* filename; + uint32_t line_num; +}; + +static bool FileIt_Next(struct BufIterator* it, struct File* file) +{ + bool result = false; + if (++it->cur_idx < it->n) + { + struct rdb_Buffer* b = &it->buf; + + file->id = PopId(b); + PopString(b, &file->filename); + file->line_num = PopBuffer(b, uint32_t); + + result = true; + } + return result; +} + +struct Breakpoint +{ + rdbg_Id uid; + rdbg_Bool enabled; + struct rdbg_String* module_name; + struct rdbg_String* condition_expr; + enum rdbg_BreakpointKind kind; + union + { + struct + { + struct rdbg_String* function_name; + rdbg_Id overload_id; + }; + struct + { + struct rdbg_String* filename; + uint32_t line_num; + }; + uint64_t address; + struct + { + struct rdbg_String* expression; + uint8_t num_bytes; + enum rdbg_InternalProcessorBreakpointType access_kind; + }; + }; +}; + +struct BreakpointLocation +{ + uint64_t address; + struct rdbg_String* module_name; + struct rdbg_String* filename; + uint32_t actual_line_num; +}; + +static char* BreakpointKindToString(enum rdbg_BreakpointKind kind) +{ + char* result = ""; + switch (kind) + { + case RDBG_BREAKPOINT_KIND_FUNCTION_NAME: + result = "RDBG_BREAKPOINT_KIND_FUNCTION_NAME"; + break; + case RDBG_BREAKPOINT_KIND_FILENAME_LINE: + result = "RDBG_BREAKPOINT_KIND_FILENAME_LINE"; + break; + case RDBG_BREAKPOINT_KIND_ADDRESS: + result = "RDBG_BREAKPOINT_KIND_ADDRESS"; + break; + case RDBG_BREAKPOINT_KIND_PROCESSOR: + result = "RDBG_BREAKPOINT_KIND_PROCESSOR"; + break; + } + return result; +} + +char *ProcessorBreakpointAccessKindToString( + enum rdbg_ProcessorBreakpointAccessKind kind) +{ + char* result = ""; + switch (kind) + { + case RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_WRITE: + result = "RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_WRITE"; + break; + case RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_READ_WRITE: + result = "RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_READ_WRITE"; + break; + case RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_EXECUTE: + result = "RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_EXECUTE"; + break; + } + return result; +} + +static void PopBreakpoint(struct rdb_Buffer* b, struct Breakpoint* bp) +{ + bp->uid = PopId(b); + bp->enabled = PopBool(b); + PopString(b, &bp->module_name); + PopString(b, &bp->condition_expr); + bp->kind = PopBreakpointKind(b); + switch (bp->kind) + { + case RDBG_BREAKPOINT_KIND_FUNCTION_NAME: + PopString(b, &bp->function_name); + bp->overload_id = PopId(b); + break; + case RDBG_BREAKPOINT_KIND_FILENAME_LINE: + PopString(b, &bp->filename); + bp->line_num = PopU32(b); + break; + case RDBG_BREAKPOINT_KIND_ADDRESS: + bp->address = PopU64(b); + break; + case RDBG_BREAKPOINT_KIND_PROCESSOR: + PopString(b, &bp->expression); + bp->num_bytes = PopU8(b); + bp->access_kind = (enum rdbg_InternalProcessorBreakpointType)PopProcessorBreakpointAccessKind(b); + break; + } +} + +static bool BreakpointIt_Next(struct BufIterator* it, struct Breakpoint* bp) +{ + bool result = false; + if (++it->cur_idx < it->n) + { + PopBreakpoint(&it->buf, bp); + result = true; + } + return result; +} + +struct FunctionOverload +{ + rdbg_Id id; + struct rdbg_String* fn_sig; +}; + +static bool FunctionOverloadIt_Next(struct BufIterator* it, + struct FunctionOverload* overload) +{ + bool result = false; + if (++it->cur_idx < it->n) + { + struct rdb_Buffer* b = &it->buf; + + overload->id = PopId(b); + PopString(b, &overload->fn_sig); + result = true; + } + return result; +} + +struct Watch +{ + rdbg_Id uid; + struct rdbg_String* expression; + struct rdbg_String* comment; +}; + +static bool WatchExpressionIt_Next(struct BufIterator* it, + struct Watch* watch) +{ + bool result = false; + if (++it->cur_idx < it->n) + { + struct rdb_Buffer* b = &it->buf; + + watch->uid = PopId(b); + PopString(b, &watch->expression); + PopString(b, &watch->comment); + + result = true; + } + return result; +} + +static void MaybePrintField(struct rdbg_String* str, char* field_name) +{ + if (str && str->len) + { + fprintf(stderr, "\t%s: %.*s\n", field_name, str->len, + (char*)str->data); + } +} + +static char* SourceLocChangedReasonToString(enum rdbg_SourceLocChangedReason reason) +{ + char* result = ""; + switch (reason) + { + case RDBG_SOURCE_LOC_CHANGED_REASON_UNSPECIFIED: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_UNSPECIFIED"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_BY_COMMAND_LINE: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_BY_COMMAND_LINE"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_BY_DRIVER: + result = "RDBG_SOURCE_LOCATION_CHANGED_REASON_BY_DRIVER"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_SELECTED: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_SELECTED"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_CURRENT_FRAME_CHANGED: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_CURRENT_FRAME_CHANGED"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_ACTIVE_THREAD_CHANGED: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_ACTIVE_THREAD_CHANGED"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_HIT: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_BREAKPOINT_HIT"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_EXCEPTION_HIT: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_EXCEPTION_HIT"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OVER: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OVER"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_STEP_IN: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_STEP_IN"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OUT: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_STEP_OUT"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_NON_USER_BREAKPOINT: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_NON_USER_BREAKPOINT"; + break; + case RDBG_SOURCE_LOC_CHANGED_REASON_DEBUG_BREAK: + result = "RDBG_SOURCE_LOC_CHANGED_REASON_DEBUG_BREAK"; + break; + } + return result; +} + +static void WriteDebugEvent(struct rdb_Buffer* eb) +{ + struct rdbg_String* str; + + enum rdbg_DebugEventKind kind = PopDebugEventKind(eb); + switch (kind) + { + case RDBG_DEBUG_EVENT_KIND_EXIT_PROCESS: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_EXIT_PROCESS\n"); + fprintf(stderr, "\texit_code: %u\n", PopU32(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_TARGET_STARTED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_TARGET_STARTED\n"); + fprintf(stderr, "\tprocess_id: %u\n", PopU32(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_TARGET_ATTACHED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_TARGET_ATTACHED\n"); + fprintf(stderr, "\tprocess_id: %u\n", PopU32(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_TARGET_DETACHED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_TARGET_DETACHED\n"); + fprintf(stderr, "\tprocess_id: %u\n", PopU32(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_TARGET_CONTINUED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_TARGET_CONTINUED\n"); + break; + case RDBG_DEBUG_EVENT_KIND_SOURCE_LOCATION_CHANGED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_SOURCE_LOCATION_CHANGED\n"); + PopString(eb, &str); + if (str && str->len) + { + fprintf(stderr, "\tfilename: %.*s\n", str->len, (char*)str->data); + } + fprintf(stderr, "\tline num: %u\n", PopU32(eb)); + fprintf(stderr, "\treason: %s\n", SourceLocChangedReasonToString((rdbg_SourceLocChangedReason)PopU16(eb))); + break; + case RDBG_DEBUG_EVENT_KIND_EXTERNAL_TEXT_EDITOR_LAUNCHED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_EXTERNAL_TEXT_EDITOR_LAUNCHED\n"); + PopString(eb, &str); + if (str && str->len) + { + fprintf(stderr, "\tfilename: %.*s\n", str->len, (char*)str->data); + } + fprintf(stderr, "\tline num: %u\n", PopU32(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_BREAKPOINT_HIT: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_BREAKPOINT_HIT\n"); + fprintf(stderr, "\tuid: %u\n", PopId(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_BREAKPOINT_RESOLVED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_BREAKPOINT_RESOLVED\n"); + fprintf(stderr, "\tuid: %u\n", PopId(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_BREAKPOINT_ADDED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_BREAKPOINT_ADDED\n"); + fprintf(stderr, "\tuid: %u\n", PopId(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_BREAKPOINT_MODIFIED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_BREAKPOINT_MODIFIED\n"); + fprintf(stderr, "\tuid: %u\n", PopId(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_BREAKPOINT_REMOVED: + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_BREAKPOINT_REMOVED\n"); + fprintf(stderr, "\tuid: %u\n", PopId(eb)); + break; + case RDBG_DEBUG_EVENT_KIND_OUTPUT_DEBUG_STRING: + PopString(eb, &str); + + fprintf(stderr, "RDBG_DEBUG_EVENT_KIND_OUTPUT_DEBUG_STRING\n"); + if (str && str->len) + { + fprintf(stderr, "\tstr: %.*s\n", str->len, (char*)str->data); + } + break; + + default: + fprintf(stderr, "warning: unknown debug event kind received\n"); + } +} + +void DebugControlSample(char* server_name) +{ + static uint8_t command_buf[COMMAND_BUF_SIZE]; + static uint8_t reply_buf[REPLY_BUF_SIZE]; + struct ClientContext ctx = {}; + ctx.cmd.data = command_buf; + ctx.cmd.capacity = sizeof(command_buf); + ctx.reply.data = reply_buf; + ctx.reply.capacity = sizeof(reply_buf); + ctx.dbg_target_behavior = RDBG_IF_DEBUGGING_TARGET_STOP_DEBUGGING; + ctx.mod_session_behavior = RDBG_IF_SESSION_IS_MODIFIED_CONTINUE_WITHOUT_SAVING; + ctx.last_error[0] = 0; + + if (InitConnection(server_name, DebugControlPipe, sizeof(ctx.last_error), + ctx.last_error, &ctx.command_pipe_handle)) + { + /* Sample calls to each debug control command (commented out). + * + enum rdbg_CommandResult res; + StartDebugging(&ctx, false, &res); + BringDebuggerToForeground(&ctx, &res); + SetDebuggerWindowPos(&ctx, 20, 20, 100, 100, &res); + + SetBringToForegroundOnSuspended(&ctx, false, &res); + int x, y, width, height; + bool is_maximized; + GetDebuggerWindowPos(&ctx, &res, &x, &y, &width, &height, &is_maximized); + printf("Window pos: (%d, %d) %d x %d; is maximized: %s\n", + x, y, width, height, is_maximized ? "true" : "false"); + bool is_modified; + GetIsSessionModified(&ctx, &res, &is_modified); + struct rdbg_String* filename; + GetSessionFilename(&ctx, &res, &filename); + if (filename && filename->len) + { + printf("Session filename: %.*s\n", filename->len, filename->data); + } + NewSession(&ctx, &res); + OpenSession(&ctx, "c:\\path\\to\\session.rdbg", &res); + SaveSession(&ctx, &res); + SaveAsSession(&ctx, "c:\\path\\to\\session.rdbg", &res); + struct BufIterator cfg_it; + struct SessionConfig cfg; + GetSessionConfigs(&ctx, &res, &cfg_it); + while (SessionConfigIt_Next(&cfg_it, &cfg)) + { + fprintf(stderr, "Config #%d\n", cfg_it.cur_idx); + fprintf(stderr, "\tuid: %hu\n", cfg.id); + if (cfg.command && cfg.command->len) + { + fprintf(stderr, "\tcommand: %.*s\n", cfg.command->len, + (char*)cfg.command->data); + } + if (cfg.command_args && cfg.command_args->len) + { + fprintf(stderr, "\tcommand_args: %.*s\n", cfg.command_args->len, + (char*)cfg.command_args->data); + } + if (cfg.working_dir && cfg.working_dir->len) + { + fprintf(stderr, "\tworking_dir: %.*s\n", cfg.working_dir->len, + (char*)cfg.working_dir->data); + } + if (cfg.environment_vars && cfg.environment_vars->len) + { + fprintf(stderr, "\tenvironment_vars:\n%.*s\n", + cfg.environment_vars->len, + (char*)cfg.environment_vars->data); + } + fprintf(stderr, "\tinherit_environment_vars_from_parent: %s\n", + cfg.inherit_environment_vars_from_parent ? "true" : "false"); + fprintf(stderr, "\tbreak_at_nominal_entry_point: %s\n", + cfg.break_at_nominal_entry_point ? "true" : "false"); + } + rdbg_Id cfg_id; + AddSessionConfig(&ctx, + "C:\\windows\\system32\\whoami.exe", "/USER", 0, + "A=1\nBB=2\nCCC=3", true, true, &res, &cfg_id); + fprintf(stderr, "Added session conf (ID: %u).\n", cfg_id); + SetActiveSessionConfig(&ctx, cfg_id, &res); + DeleteSessionConfig(&ctx, cfg.id, &res); + DeleteAllSessionConfigs(&ctx, &res); + rdbg_Id cur_file_id; + struct rdbg_String* cur_filename = 0; + uint32_t line_num; + GetCurrentFile(&ctx, &res, &cur_file_id, &cur_filename, &line_num); + if (cur_filename && cur_filename->len) + { + fprintf(stderr, "Topmost file: (%u) %.*s Ln %u\n", + cur_file_id, cur_filename->len, (char*)cur_filename->data, + line_num); + } + rdbg_Id file_id; + GoToFileAtLine(&ctx, "C:\\full\\path\\to\\README.txt", 121, &res, &file_id); + CloseFileById(&ctx, file_id, &res); + CloseAllFiles(&ctx, &res); + struct BufIterator file_it; + struct File file; + GetOpenFiles(&ctx, &res, &file_it); + while (FileIt_Next(&file_it, &file)) + { + fprintf(stderr, "File #%d\n", file_it.cur_idx); + fprintf(stderr, "\tId: %u\n", file.id); + if (file.filename && file.filename->len) + { + fprintf(stderr, "\tfilename: %.*s\n", file.filename->len, + (char*)file.filename->data); + } + fprintf(stderr, "\tLn: %u\n", file.line_num); + } + StopDebugging(&ctx, &res); + RestartDebugging(&ctx, &res); + AttachToProcessById(&ctx, 14368, true, &res); + NewSession(&ctx, &res); + AttachToProcessByName(&ctx, "Calculator.exe", true, &res); + DetachFromProcess(&ctx, &res); + StepIntoByLine(&ctx, &res); + StepIntoByInstruction(&ctx, &res); + StepOverByLine(&ctx, &res); + StepOverByInstruction(&ctx, &res); + StepOut(&ctx, &res); + ContinueExecution(&ctx, &res); + RunToFileAtLine(&ctx, "C:\\full\\path\\to\\test.cpp", 13, &res); + ContinueExecution(&ctx, &res); + BreakExecution(&ctx, &res); + enum rdbg_TargetState state; + GetTargetState(&ctx, &res, &state); + fprintf(stderr, "target state: %hu\n", state); + struct BufIterator bp_it; + struct Breakpoint bp; + GetAllBreakpoints(&ctx, &res, &bp_it); + int idx = 0; + while (BreakpointIt_Next(&bp_it, &bp)) + { + fprintf(stderr, "Breakpoint # %d\n", ++idx); + fprintf(stderr, "\tUID: %u\n", bp.uid); + fprintf(stderr, "\tEnabled: %s\n", bp.enabled ? "true" : "false"); + MaybePrintField(bp.module_name, "Module"); + MaybePrintField(bp.condition_expr, "Condition"); + fprintf(stderr, "\tKind: %s\n", BreakpointKindToString(bp.kind)); + switch (bp.kind) + { + case RDBG_BREAKPOINT_KIND_FUNCTION_NAME: + MaybePrintField(bp.function_name, "Function"); + fprintf(stderr, "\tOverload: %u\n", bp.overload_id); + break; + case RDBG_BREAKPOINT_KIND_FILENAME_LINE: + MaybePrintField(bp.filename, "Filename"); + fprintf(stderr, "\tLine: %u\n", bp.line_num); + break; + case RDBG_BREAKPOINT_KIND_ADDRESS: + fprintf(stderr, "\tAddress: 0x%llx\n", bp.address); + break; + case RDBG_BREAKPOINT_KIND_PROCESSOR: + MaybePrintField(bp.expression, "Expression"); + fprintf(stderr, "\tBytes: %hhu\n", bp.num_bytes); + fprintf(stderr, "\tAccess kind: %s\n", + ProcessorBreakpointAccessKindToString(bp.access_kind)); + break; + } + // Test for call to get information on a single user breakpoint + struct Breakpoint _bp; + GetBreakpoint(&ctx, bp.uid, &res, &_bp); + // See if the breakpoint has been resolved. Not using an iterator + // here, at the moment, because we can only get back one or zero. + uint16_t num_locs; + GetBreakpointLocations(&ctx, bp.uid, &res, &num_locs); + if (num_locs == 1) + { + struct BreakpointLocation loc; + loc.address = PopU64(&ctx.reply); + PopString(&ctx.reply, &loc.module_name); + PopString(&ctx.reply, &loc.filename); + loc.actual_line_num = PopU32(&ctx.reply); + fprintf(stderr, "\t---------\n"); + fprintf(stderr, "\tResolved address: 0x%llx\n", loc.address); + MaybePrintField(loc.module_name, "Module"); + MaybePrintField(loc.module_name, "Filename"); + fprintf(stderr, "\tActual line number: %u\n", + loc.actual_line_num); + } + else + { + fprintf(stderr, "\tUnresolved\n"); + } + } + struct BufIterator fn_overload_it; + struct FunctionOverload overload; + GetFunctionOverloads(&ctx, &res, "SomeFunction", &fn_overload_it); + while (FunctionOverloadIt_Next(&fn_overload_it, &overload)) + { + fprintf(stderr, "Overload %u; sig: ", overload.id); + if (overload.fn_sig && overload.fn_sig->len) + { + fprintf(stderr, "%.*s\n", overload.fn_sig->len, + (char*)overload.fn_sig->data); + } + } + rdbg_Id bp_id; + AddBreakpointAtFn(&ctx, "SomeFunction", 0, "", &res, &bp_id); + AddBreakpointAtFilenameLine(&ctx, "C:\\path\\to\\fn_overloads.cpp", 21, + "", &res, &bp_id); + AddBreakpointAtAddress(&ctx, 0x7FF7F592B703, "", &res, &bp_id); + AddProcessorBreakpoint(&ctx, "&var", 4, + RDBG_PROCESSOR_BREAKPOINT_ACCESS_KIND_WRITE, "", &res, &bp_id); + SetBreakpointCondition(&ctx, bp_id, "$rax == 0", &res); + UpdateBreakpointLine(&ctx, bp_id, 22, &res); + EnableBreakpoint(&ctx, bp_id, false, &res); + EnableBreakpoint(&ctx, bp_id, true, &res); + DeleteBreakpoint(&ctx, bp_id, &res); + DeleteAllBreakpoints(&ctx, &res); + rdbg_Id watch_id; + AddWatch(&ctx, 1, "0xf0c / 0xa", "testing\nblah", &res, &watch_id); + struct BufIterator watch_it; + GetWatches(&ctx, 1, &res, &watch_it); + struct Watch watch; + while (WatchExpressionIt_Next(&watch_it, &watch)) + { + fprintf(stderr, "Watch %hu\n", watch.uid); + if (watch.expression && watch.expression->len) + { + fprintf(stderr, "\tWatch expression: '%.*s'\n", + watch.expression->len, + (char*)watch.expression->data); + } + if (watch.comment && watch.comment->len) + { + fprintf(stderr, "\tWatch comment: '%.*s'\n", + watch.comment->len, + (char*)watch.comment->data); + } + } + UpdateWatchExpression(&ctx, 37, "expr * expr", &res); + UpdateWatchComment(&ctx, 37, "something left after", &res); + DeleteWatch(&ctx, 42, &res); + DeleteAllWatches(&ctx, 1, &res); + ExitDebugger(&ctx, &res); + */ + + if (ContextHadError(&ctx)) + { + fprintf(stderr, "[ERROR] %s (cmd-err:%s)(reply-err:%s)\n", + ctx.last_error, + ctx.cmd.err ? "true" : "false", + ctx.reply.err ? "true" : "false"); + } + CloseConnection(&ctx); + } + else + { + fprintf(stderr, ctx.last_error); + } +} + +void DebugEventsSample(char* server_name) +{ + HANDLE pipe_handle; + char last_error[ERROR_MSG_LEN]; + last_error[0] = 0; + + if (InitConnection(server_name, DebugEventsPipe, sizeof(last_error), + last_error, &pipe_handle)) + { + // blocking read for testing events + while (1) + { + static uint8_t dbg_evt_buf[REPLY_BUF_SIZE]; + struct rdb_Buffer dbg_evt = {}; + dbg_evt.data = dbg_evt_buf; + dbg_evt.capacity = sizeof(dbg_evt_buf); + + DWORD bytes_read = 0; + if (ReadFile(pipe_handle, dbg_evt.data, REPLY_BUF_SIZE, &bytes_read, + NULL)) + { + dbg_evt.curr = dbg_evt.data; + WriteDebugEvent(&dbg_evt); + } + else + { + fprintf(stderr, "ReadFile FAIL: err=%u\n", GetLastError()); + } + } + } + else + { + fprintf(stderr, last_error); + } +} + +///////////////////////////////////////////////////////////////// + +uint8_t command_buf[COMMAND_BUF_SIZE]; +uint8_t reply_buf[REPLY_BUF_SIZE]; +ClientContext RDB_Ctx; + + +bool RDB_InitConnection() { + enum rdbg_CommandResult res; + if (RDB_Ctx.command_pipe_handle != NULL) { + enum rdbg_TargetState state; + GetTargetState(&RDB_Ctx, &res, &state); + if (!ContextHadError(&RDB_Ctx)) { + return true; + } + } + Scratch scratch; + String session_name = Format(scratch, "te%llu", GetTimeNanos()); + String remedy_string = Format(scratch, "%S --servername %S", RemedyBGPath, session_name); + Exec(NullViewID, true, remedy_string, GetMainDir()); + MemoryZero(&RDB_Ctx, sizeof(RDB_Ctx)); + RDB_Ctx.cmd.data = command_buf; + RDB_Ctx.cmd.capacity = sizeof(command_buf); + RDB_Ctx.reply.data = reply_buf; + RDB_Ctx.reply.capacity = sizeof(reply_buf); + RDB_Ctx.dbg_target_behavior = RDBG_IF_DEBUGGING_TARGET_STOP_DEBUGGING; + RDB_Ctx.mod_session_behavior = RDBG_IF_SESSION_IS_MODIFIED_CONTINUE_WITHOUT_SAVING; + RDB_Ctx.last_error[0] = 0; + + bool result = false; + for (int i = 0; i < 64; i += 1) { + Sleep(10); + result = InitConnection(session_name.data, DebugControlPipe, sizeof(RDB_Ctx.last_error), RDB_Ctx.last_error, &RDB_Ctx.command_pipe_handle); + if (result) { + break; + } + } + + if (result == false) { + ReportErrorf("Remedy error: %s", RDB_Ctx.last_error); + RDB_Ctx = {}; + return false; + } + MemoryZero(RDB_Ctx.last_error, sizeof(RDB_Ctx.last_error)); + + rdbg_Id cfg_id; + char *exe = BinaryUnderDebug.data; + char *args = NULL; + char *work_dir = WorkDir.data; + char *env = NULL; + AddSessionConfig(&RDB_Ctx, exe, args, work_dir, env, true, true, &res, &cfg_id); + if (ContextHadError(&RDB_Ctx)) { + ReportErrorf("Remedy error: %s", RDB_Ctx.last_error); + MemoryZero(RDB_Ctx.last_error, sizeof(RDB_Ctx.last_error)); + return true; + } + + SetActiveSessionConfig(&RDB_Ctx, cfg_id, &res); + if (ContextHadError(&RDB_Ctx)) { + ReportErrorf("Remedy error: %s", RDB_Ctx.last_error); + MemoryZero(RDB_Ctx.last_error, sizeof(RDB_Ctx.last_error)); + return true; + } + + return true; +} + +// This should continue execution if stopped @todo: +void CMD_StartDebugging(HookParam param) { + bool conn = RDB_InitConnection(); + if (!conn) { + return; + } + + enum rdbg_CommandResult res; + enum rdbg_TargetState state; + GetTargetState(&RDB_Ctx, &res, &state); + if (ContextHadError(&RDB_Ctx)) { + ReportErrorf("Remedy error: %s", RDB_Ctx.last_error); + MemoryZero(RDB_Ctx.last_error, sizeof(RDB_Ctx.last_error)); + return; + } + + if (state == RDBG_TARGET_STATE_NONE) { + StartDebugging(&RDB_Ctx, false, &res); + if (ContextHadError(&RDB_Ctx)) { + ReportErrorf("Remedy error: %s", RDB_Ctx.last_error); + MemoryZero(RDB_Ctx.last_error, sizeof(RDB_Ctx.last_error)); + return; + } + } else if (state == RDBG_TARGET_STATE_SUSPENDED) { + ContinueExecution(&RDB_Ctx, &res); + if (ContextHadError(&RDB_Ctx)) { + ReportErrorf("Remedy error: %s", RDB_Ctx.last_error); + MemoryZero(RDB_Ctx.last_error, sizeof(RDB_Ctx.last_error)); + return; + } + } + BringDebuggerToForeground(&RDB_Ctx, &res); + if (ContextHadError(&RDB_Ctx)) { + ReportErrorf("Remedy error: %s", RDB_Ctx.last_error); + MemoryZero(RDB_Ctx.last_error, sizeof(RDB_Ctx.last_error)); + return; + } +} RegisterCommand(CMD_StartDebugging, "f5", "Start debugging, if debugger not active it starts it, uses BinaryUnderDebug"); + +void CMD_RunToLineInDebugger(HookParam param) { + bool conn = RDB_InitConnection(); + if (!conn) { + return; + } + + BSet prim = GetBSet(PrimaryWindowID); + + enum rdbg_CommandResult res; + Int line = PosToLine(prim.buffer, GetFront(prim.view->carets[0])); + RunToFileAtLine(&RDB_Ctx, prim.buffer->name.data, (uint32_t)line + 1, &res); + if (res != RDBG_COMMAND_RESULT_OK) { + ReportErrorf("Remedy error: %s", RDB_Ctx.last_error); + MemoryZero(RDB_Ctx.last_error, sizeof(RDB_Ctx.last_error)); + return; + } +} RegisterCommand(CMD_RunToLineInDebugger, "ctrl-f10", "Instruct debugger to execute until line and column under caret"); + +void CMD_StopDebugging(HookParam param) { + bool conn = RDB_InitConnection(); + if (!conn) { + return; + } + + enum rdbg_CommandResult res; + StopDebugging(&RDB_Ctx, &res); + if (res != RDBG_COMMAND_RESULT_OK) { + ReportErrorf("Remedy error: %s", RDB_Ctx.last_error); + MemoryZero(RDB_Ctx.last_error, sizeof(RDB_Ctx.last_error)); + return; + } +} RegisterCommand(CMD_StopDebugging, "shift-f5", "Stop debugging"); + +void HOOK_QuitDebugger(HookParam param) { + bool conn = RDB_InitConnection(); + if (!conn) { + return; + } + + enum rdbg_CommandResult res; + ExitDebugger(&RDB_Ctx, &res); + CloseConnection(&RDB_Ctx); +} RegisterHook(HOOK_QuitDebugger, HookKind_AppQuit, "", "exit the connected debugger"); + + +#endif \ No newline at end of file diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index 00fb328..b87148d 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -34,6 +34,8 @@ #include "draw.cpp" #include "test/tests.cpp" +#include "remedybg_plugin.cpp" + #if OS_WASM EM_JS(void, JS_SetMouseCursor, (const char *cursor_str), { document.getElementById("canvas").style.cursor = UTF8ToString(cursor_str); @@ -425,12 +427,7 @@ void OnCommand(Event event) { } if (event.kind == EVENT_QUIT) { - For (GlobalHooks) { - if (it.kind == HookKind_AppQuit) { - ProfileScopeEx(it.name); - it.function({}); - } - } + CMD_Quit({}); } IF_DEBUG(AssertRanges(main.view->carets));