Files
text_editor/src/text_editor/remedybg_plugin.cpp
Krzosa Karol f85697d037 Debugger fix
2026-01-15 08:57:54 +01:00

2263 lines
74 KiB
C++

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 RDBG_Ctx;
bool RDBG_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;
}
}
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(&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));
rdbg_Id cfg_id;
char *exe = Format(scratch, "%S/%S", WorkDir, BinaryUnderDebug).data;
char *args = NULL;
char *work_dir = WorkDir.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 CMD_StartDebugging(HookParam param) {
bool conn = RDBG_InitConnection();
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;
}
} RegisterCommand(CMD_StartDebugging, "f5", "Start debugging, if debugger not active it starts it, uses BinaryUnderDebug");
void CMD_RunToLineInDebugger(HookParam param) {
bool conn = RDBG_InitConnection();
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;
}
} RegisterCommand(CMD_RunToLineInDebugger, "ctrl-f10", "Instruct debugger to execute until line and column under caret");
void CMD_StopDebugging(HookParam param) {
bool conn = RDBG_InitConnection();
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;
}
} RegisterCommand(CMD_StopDebugging, "shift-f5", "Stop debugging");
void CMD_AddBreakpoint(HookParam param) {
if (!RDBG_InitConnection()) {
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;
}
} RegisterCommand(CMD_AddBreakpoint, "f9", "Add a breakpoint at filename + line");
void HOOK_QuitDebugger(HookParam param) {
if (RDBG_Ctx.command_pipe_handle == NULL) {
return;
}
enum rdbg_CommandResult res;
ExitDebugger(&RDBG_Ctx, &res);
CloseConnection(&RDBG_Ctx);
} RegisterHook(HOOK_QuitDebugger, HookKind_AppQuit, "", "exit the connected debugger");
#endif