This proposal covers both disassembly and memory access, since they both depend on the same underlying primitive - a "memory reference".
Memory references
A memory reference is an opaque identifier that can be used (along with an optional byte offset) to identify a single location in memory. An adapter has the option of providing a memory reference on several protocol objects that represent things a frontend might want to display in a memory view or use as a starting point for disassembly:
export interface EvaluateResponse {
// ...
/** Memory reference to an adapter-determined location appropriate for this result. For pointer types, this is generally a reference to the memory address contained in the pointer. */
memoryReference?: string;
}
export interface Variable {
// ...
/** Memory reference for the variable if the variable represents executable code, such as a function pointer. */
memoryReference?: string;
}
export interface StackFrame {
// ...
/** Memory reference for the current instruction pointer in this frame. */
instructionPointerReference?: string;
}
export interface GotoTarget {
// ...
/** Memory reference for the instruction pointer value represented by this target. */
instructionPointerReference?: string;
}
Memory references are expressed as strings rather than numbers because the most common implementation will likely be to use the address directly, and a 64-bit memory address will not fit in a JSON number.
Reading memory
Reading memory from a location represented by a memory reference and offset is accomplished via a new readMemory
request:
export interface Capabilities {
// ...
/** The debug adapter supports the 'readMemory' request. */
supportsReadMemoryRequest?: boolean;
}
/** Reads bytes from memory at the provided location. */
export interface ReadMemoryRequest extends Request {
// command: 'readMemory'
arguments: ReadMemoryArguments;
}
/** Arguments for 'readMemory' request. */
export interface ReadMemoryArguments {
/** Memory reference to the base location from which data should be read. */
memoryReference: string;
/** Optional offset (in bytes) to be applied to the reference location before reading data. Can be negative. */
offset?: number;
/** Number of bytes to read at the specified location and offset. */
count: number;
}
/** Response to 'readMemory' request. */
export interface ReadMemoryResponse extends Response {
body: {
/** The address of the first byte of data returned. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */
address: string;
/** The number of unreadable bytes encountered after the last successfully read byte. This can be used to determine the number of bytes that must be skipped before a subsequent 'readMemory' request will succeed. */
unreadableBytes?: number;
/** The bytes read from memory, encoded using base64. **/
data: string;
}
}
Note that the read memory request should be considered to have succeeded even if none of the requested bytes are available. For instance, a read might extend into a page that is not mapped - in this case, the adapter would be expected to return the bytes it could read (if any), along with a value for unreadableBytes
that can be used to skip the unmapped page.
Additionally, it is intended that a zero-byte read (that is, a readMemory
request with a count
of zero) could be used to determine the actual address identified by the memory reference and offset.
Disassembly
Disassembling code at a location represented by a memory reference is accomplished via a new disassemble
request:
export interface Capabilities {
// ...
/** The debug adapters supports the 'disassemble' request. */
supportsDisassembleRequest?: boolean;
}
/** Disassembles code stored at the provided location. */
export interface DisassembleRequest extends Request {
// command: 'disassemble'
arguments: DisassembleArguments;
}
/** Arguments for 'disassemble' request. */
export interface DisassembleArguments {
/** Memory reference to the base location containing the instructions to disassemble. */
memoryReference: string;
/** Optional offset (in bytes) to be applied to the reference location before disassembling. Can be negative. */
offset?: number;
/** Optional offset (in instructions) to be applied after the byte offset (if any) before disassembling. Can be negative. */
instructionOffset?: number;
/** Number of instructions to disassemble starting at the specified location and offset. An adapter must return exactly this number of instructions - any unavailable instructions should be replaced with an implementation-defined 'invalid instruction' value. */
instructionCount: number;
/** If true, the adapter should attempt to resolve memory addresses and other values to symbolic names. */
resolveSymbols?: boolean;
}
/** Response to 'disassemble' request */
export interface DisassembleResponse extends Response {
body: {
/** The list of disassembled instructions. */
instructions: DisassembledInstruction[];
}
}
/** Represents a single disassembled instruction. */
export interface DisassembledInstruction {
/** The address of the instruction. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */
address: string;
/** Raw bytes representing the instruction and its operands, in an implementation-defined format. */
instructionBytes?: string;
/** Text representing the instruction and its operands, in an implementation-defined format. */
instruction: string;
/** Name of the symbol that correponds with the location of this instruction, if any. */
symbol?: string;
/** Source location that coresponds to this instruction, if any. Should always be set (if available) on the first instruction returned, but can be omitted afterwards if this instruction maps to the same source file as the previous instruction. */
location?: Source;
/** The line within the source location that corresponds to this instruction, if any. */
line?: number;
/** The column within the line that corresponds to this instruction, if any. */
column?: number;
/** The end line of the range that corresponds to this instruction, if any. */
endLine?: number;
/** The end column of the range that corresponds to this instruction, if any. */
endColumn?: number;
}
The location
, line
, column
, endLine
and endColumn
members on DisassembledInstruction
are intended to provide a mapping between a disassembled instruction and the source code it was generated from, if this information is available to the adapter.
Formatting
A debugger UI that implements this proposal will likely want to display addresses differently for 32-bit vs 64-bit processes. This information would be conveyed via an additional property on the process
event:
export interface ProcessEvent extends Event {
// ...
/** The size of a pointer or address for this process, in bits. */
pointerLength?: number;
}
CC: @gregg-miskelly