#if PLUGIN_REMEDYBG #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 RDBG_Ctx; bool RDBG_InitConnection(mco_coro *co, bool create_session = true) { CCtx *ctx = GetCoroutineContext(); ReportConsolef("Remedybg: InitConnection"); enum rdbg_CommandResult res; if (RDBG_Ctx.command_pipe_handle != NULL) { enum rdbg_TargetState state; GetTargetState(&RDBG_Ctx, &res, &state); if (!ContextHadError(&RDBG_Ctx)) { return true; } } String file = ""; if (create_session) { if (BinaryUnderDebug.len) { Window *window = GetWindow(PrimaryWindowID); ResolvedOpen res = ResolveOpen(ctx->arena, window, BinaryUnderDebug, ResolveOpenMeta_DontError | ResolveOpenMeta_DontExec); if (res.kind == OpenKind_Goto) { file = Intern(&GlobalInternTable, res.path); } else { ReportErrorf("Failed to find the executable pointer by BinaryUnderDebug: %S", BinaryUnderDebug); return false; } } if (file.len == 0) { Scratch scratch; for (FileIter it = IterateFiles(scratch, ProjectFolder); IsValid(it); Advance(&it)) { if (EndsWith(it.filename, ".rdbg")) { file = Intern(&GlobalInternTable, it.absolute_path); break; } } } if (file.len == 0) { ReportErrorf("Couldn't find neither .rdbg file, nor use the BinaryUnderDebug variable to locate the binary"); return false; } } String session_name = Format(ctx->arena, "te%llu", GetTimeNanos()); String remedy_string = Format(ctx->arena, "%S --servername %S", RemedyBGPath, session_name); ReportConsolef("Remedybg: %S", remedy_string); ExecArgs args = ShellArgs(ctx->arena, LogView->id, remedy_string, GetPrimaryDirectory()); args.scroll_to_end = true; Exec(args); MemoryZero(&RDBG_Ctx, sizeof(RDBG_Ctx)); RDBG_Ctx.cmd.data = command_buf; RDBG_Ctx.cmd.capacity = sizeof(command_buf); RDBG_Ctx.reply.data = reply_buf; RDBG_Ctx.reply.capacity = sizeof(reply_buf); RDBG_Ctx.dbg_target_behavior = RDBG_IF_DEBUGGING_TARGET_STOP_DEBUGGING; RDBG_Ctx.mod_session_behavior = RDBG_IF_SESSION_IS_MODIFIED_CONTINUE_WITHOUT_SAVING; RDBG_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(RDBG_Ctx.last_error), RDBG_Ctx.last_error, &RDBG_Ctx.command_pipe_handle); if (result) { break; } } if (result == false) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); RDBG_Ctx = {}; return false; } MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); if (create_session) { if (EndsWith(file, ".rdbg")) { ReportConsolef("Remedybg: OpenSession %S", file); OpenSession(&RDBG_Ctx, file.data, &res); } else { ReportConsolef("Remedybg: CreateSession %S", file); rdbg_Id cfg_id; char *exe = file.data; char *args = NULL; char *work_dir = ProjectFolder.data; char *env = NULL; AddSessionConfig(&RDBG_Ctx, exe, args, work_dir, env, true, true, &res, &cfg_id); if (ContextHadError(&RDBG_Ctx)) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); return true; } SetActiveSessionConfig(&RDBG_Ctx, cfg_id, &res); if (ContextHadError(&RDBG_Ctx)) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); return true; } } } return true; } void CO_StartDebugging(mco_coro *co) { bool conn = RDBG_InitConnection(co); if (!conn) { return; } enum rdbg_CommandResult res; enum rdbg_TargetState state; GetTargetState(&RDBG_Ctx, &res, &state); if (ContextHadError(&RDBG_Ctx)) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); return; } if (state == RDBG_TARGET_STATE_NONE) { StartDebugging(&RDBG_Ctx, false, &res); if (ContextHadError(&RDBG_Ctx)) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); return; } } else if (state == RDBG_TARGET_STATE_SUSPENDED) { ContinueExecution(&RDBG_Ctx, &res); if (ContextHadError(&RDBG_Ctx)) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); return; } } BringDebuggerToForeground(&RDBG_Ctx, &res); if (ContextHadError(&RDBG_Ctx)) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); return; } } RegisterCoroutineCommand(CO_StartDebugging, "f5", "Start debugging, if debugger not active it starts it, uses BinaryUnderDebug"); void CO_RunToLineInDebugger(mco_coro *co) { bool conn = RDBG_InitConnection(co); if (!conn) { return; } BSet prim = GetBSet(PrimaryWindowID); enum rdbg_CommandResult res; Int line = PosToLine(prim.buffer, GetFront(prim.view->carets[0])); RunToFileAtLine(&RDBG_Ctx, prim.buffer->name.data, (uint32_t)line + 1, &res); if (res != RDBG_COMMAND_RESULT_OK) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); return; } } RegisterCoroutineCommand(CO_RunToLineInDebugger, "ctrl-f10", "Instruct debugger to execute until line and column under caret"); void CO_StopDebugging(mco_coro *co) { bool conn = RDBG_InitConnection(co); if (!conn) { return; } enum rdbg_CommandResult res; StopDebugging(&RDBG_Ctx, &res); if (res != RDBG_COMMAND_RESULT_OK) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); return; } } RegisterCoroutineCommand(CO_StopDebugging, "shift-f5", "Stop debugging"); void CO_AddBreakpoint(mco_coro *co) { bool conn = RDBG_InitConnection(co); if (!conn) { return; } BSet prim = GetBSet(PrimaryWindowID); Int line = PosToLine(prim.buffer, GetFront(prim.view->carets[0])); enum rdbg_CommandResult res; rdbg_Id bp_id; AddBreakpointAtFilenameLine(&RDBG_Ctx, prim.buffer->name.data, (uint32_t)line + 1, "", &res, &bp_id); if (res != RDBG_COMMAND_RESULT_OK) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); return; } } RegisterCoroutineCommand(CO_AddBreakpoint, "f9", "Add a breakpoint at filename + line"); void CO_SelfAttachDebugger(mco_coro *co) { bool conn = RDBG_InitConnection(co, false); if (!conn) { return; } enum rdbg_CommandResult res; AttachToProcessById(&RDBG_Ctx, GetCurrentProcessId(), true, &res); if (res != RDBG_COMMAND_RESULT_OK) { ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); return; } } RegisterCoroutineCommand(CO_SelfAttachDebugger, "", "Spawn and self attach the debugger to the text editor"); void QuitDebugger() { if (RDBG_Ctx.command_pipe_handle == NULL) { return; } enum rdbg_CommandResult res; ExitDebugger(&RDBG_Ctx, &res); CloseConnection(&RDBG_Ctx); } #endif