V5 Debugger Server Protocol v1
Authors & Copyright
All programming, documentation, and spec authoring was done by Hunter Stasonis
This program and its documentation have been placed under the MIT License, and is provided without warranty of any kind
Constants and Definitions
Throughout this document many words many be underlined with a dotted line, hovering over these words will show their definition.
Communication
v5dbg communicates over USB serial. The debug server opens the pseudofile provided by the PROS kernel located at /ser/sout in write mode, messages are serialized and written to this file using fwrite. COBS and stream multiplexing should be disabled for this file so data can be read raw by the debugger.
Messages
Messages are payloads of data sent from either the debugger, or debug server. All messages are formatted as follows, left to right.
- Message begin
- Protocol version
- Message type
- Message payload
- Newline
An example message can be seen as %1:1:0, message type one is program suspend. It should be noted that if more than two message separator characters are used then any located after the third are ignored and are merged into the message payload.
If we have the message %1:2:0:1:2:3 then the message payload should be 0:1:2:3, the extra message separator characters do not mess up the parser state and does not throw a warning or error of any kind
Message Types
Message types are defined as enums within v5dbg/protocol.h (include directory), the DEBUGGER_MESSAGE_MAX is used as a basic check to determine if a message type is invalid.
Message IDs can be considered unsigned ints since they never can be negative, and a message should be assumed invalid or corrupted if so.
enum v5dbg_message_type_e
{
/// @brief Connection opened
DEBUGGER_MESSAGE_OPEN = 0,
/// @brief Request program suspension, assume it has occurred when a DEBUG_MESSAGE_RSUSPEND is parsed
DEBUGGER_MESSAGE_SUSPEND = 1,
/// @brief Connection closed
DEBUGGER_MESSAGE_CLOSE = 2,
/// @brief Allocate a string, can be processed by the debugger however it likes
DEBUGGER_MESSAGE_ALLOCATE_STRING = 3,
/// @brief Resume the program from a suspended state
DEBUGGER_MESSAGE_RESUME = 4,
/// @brief Respond with DEBUGGER_MESSAGE_RTHREADS containing a comma separated list of every thread being managed by
/// v5dbg
DEBUGGER_MESSAGE_THREADS = 5,
/// @brief Return message for DEBUGGER_MESSAGE_THREADS
DEBUGGER_MESSAGE_RTHREADS = 6,
/// @brief Get the vstack for the given thread index
DEBUGGER_MESSAGE_VSTACK_FOR = 7,
/// @brief Return message for DEBUGGER_MESSAGE_VSTACK_FOR, keep accepting messages until DEBUGGER_MESSAGE_VSTACK_END
DEBUGGER_MESSAGE_RVSTACK = 8,
/// @brief Stop accepting DEBUGGER_MESSAGE_RVSTACK messages
DEBUGGER_MESSAGE_VSTACK_END = 9,
/// @brief List memory for the given stack frame with thread ID
DEBUGGER_MESSAGE_LMEM_FOR = 10,
/// @brief Returned message for DEBUGGER_MESSAGE_LMEM_FOR, keep accepting messages until DEBUGGER_MESSAGE_LMEM_END
DEBUGGER_MESSAGE_RLMEM = 11,
/// @brief Stop accepting DEBUGGER_MESSAGE_RLMEM messages
DEBUGGER_MESSAGE_LMEM_END = 12,
/// @brief Max debugger message ID
DEBUGGER_MESSAGE_MAX = 13
};
Subarguments
Subarguments introduce a way for messages to include more complex data in their 3rd data field. Since the message parser only parses up to the 2nd message separator character the third data field can have message separators located in it.
The subargs parser allows for another array of elements to be placed into the data field, the simplest way to perform this would be to split the data field on the message separator character. If we want to include data inside the data field that has message separators(such as C++ typenames) we need an escape character.
The [ and ] character
When the first character of an element is an [ we ignore any message separator characters we come across until we encounter a ] character located before a message separator or a newline.
Without subarguments
Imagine we have this message:
%1:1:std::vector<int>:helloWorld
It's parameters are broken down into:
11std::vector<int>:helloWorld
When we split the data parameter of the message by the message separator we get:
std:vector<int>helloWorld
This IS NOT what we want!
With subarguments
Taking our old string from the previous example and adding subargumrnts we get:
%1:1:[std::vector<int>]:helloWorld
It's parameters are broken down the same way during message parsing
Now instead of splitting on the message seperator the subargument parser is called creating:
std::vector<int>helloWorld
This IS what we want!
References
You can find the official implementation of a subargs parser on GitHub
Behaviors
Messages may be marked with icons, hovering over them will show information about the specific message type. Messages that do not state their argument type can be assumed to not use subarguments, or comma splitting.
If messages have more than one argument they will specify their argument type next to their enum name in the title.
DEBUGGER_MESSAGE_OPEN
Sent by the debug server to the debugger at program startup and every 2 seconds that the program is running. The debugger will only connect to the debug server if it can detect and read this message, the debugger will also print a hang message if it fails to receive an OPEN message atleast once every 5 seconds.
DEBUGGER_MESSAGE_ALLOCATE_STRING
When sent to the debug server this message sent unaltered back to the debugger, this was used for testing.
DEBUGGER_MESSAGE_SUSPEND
Requests the debug server to suspend all of it's supervised tasks, no response is sent to the client.
DEBUGGER_MESSAGE_RESUME
Requests the debug server to resume all of it's supervised tasks, no response is sent to the client.
DEBUGGER_MESSAGE_THREADS
Responds with a single DEBUGGER_MESSAGE_RTHREADS message containing all the debugger's supervised threads.
DEBUGGER_MESSAGE_RTHREADS
Response to a DEBUGGER_MESSAGE_THREADS message.
The following parameters repeat for every thread.
| Parameter | Docs |
|---|---|
| Index 0 | String Name of the thread |
| Index 1 | unsigned int ID of the thread |
Example message
%1:6:Worker Thread,0,Odom Thread,1,OpControl,2
DEBUGGER_MESSAGE_VSTACK_FOR
When sent to the debug server it returns a series of DEBUGGER_MESSAGE_RVSTACK messages for each frame of the stack before ending with a DEBUGGER_MESSAGE_VSTACK_END message.
| Parameter | Docs |
|---|---|
| Index 0 | unsigned int Thread ID to grab the callstack for |
DEBUGGER_MESSAGE_RVSTACK
This message is sent repeatedly until a DEBUGGER_MESSAGE_VSTACK_END message is sent.
| Parameter | Docs |
|---|---|
| Index 0 | unsigned int of the frame ID |
| Index 1 | String Name of the function |
| Index 2 | String File path to the function |
| Index 3 | unsigned int Line number inside of the file where this function begins |
DEBUGGER_MESSAGE_VSTACK_END
This message is sent to signal that the debugger can stop waiting for DEBUGGER_MESSAGE_RVSTACK messages.
The data field may be filled with any data the server wishes but it can be ignored.
DEBUGGER_MESSAGE_LMEM_FOR
This message is sent to the debug server to request a series of DEBUGGER_MESSAGE_RLMEM messages terminated by a DEBUGGER_MESSAGE_LMEM_END which contains captured local memory.
| Parameter | Docs |
|---|---|
| Index 0 | unsigned int Frame ID |
| Index 1 | unsigned int Thread ID |
DEBUGGER_MESSAGE_RLMEM
This message is sent repeatedly until a DEBUGGER_MESSAGE_LMEM_END message is sent.
| Parameter | Docs |
|---|---|
| Index 0 | String C++ typename of this variable |
| Index 1 | String Name of this variable |
| Index 2 | String Path to the file this variable is in |
| Index 3 | unsigned int Line number this variable was exposed on |
| Index 4 | String Pretty printed buffer for this variable |
DEBUGGER_MESSAGE_LMEM_END
This message is sent to signal that the debugger can stop waiting for DEBUGGER_MESSAGE_RLMEM messages.
The data field may be filled with any data the server wishes but it can be ignored.