-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[lldb][lldb-dap] Added readMemory and WriteMemory, var type correction #108036
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -13,6 +13,7 @@ | |||||||||||||||||
#include <cassert> | ||||||||||||||||||
#include <climits> | ||||||||||||||||||
#include <cstdarg> | ||||||||||||||||||
#include <cstdint> | ||||||||||||||||||
#include <cstdio> | ||||||||||||||||||
#include <cstdlib> | ||||||||||||||||||
#include <cstring> | ||||||||||||||||||
|
@@ -39,6 +40,7 @@ | |||||||||||||||||
#include <sys/prctl.h> | ||||||||||||||||||
#endif | ||||||||||||||||||
|
||||||||||||||||||
#include "lldb/lldb-types.h" | ||||||||||||||||||
#include <algorithm> | ||||||||||||||||||
#include <array> | ||||||||||||||||||
#include <chrono> | ||||||||||||||||||
|
@@ -61,6 +63,7 @@ | |||||||||||||||||
#include "llvm/Option/ArgList.h" | ||||||||||||||||||
#include "llvm/Option/OptTable.h" | ||||||||||||||||||
#include "llvm/Option/Option.h" | ||||||||||||||||||
#include "llvm/Support/Base64.h" | ||||||||||||||||||
#include "llvm/Support/Errno.h" | ||||||||||||||||||
#include "llvm/Support/FileSystem.h" | ||||||||||||||||||
#include "llvm/Support/InitLLVM.h" | ||||||||||||||||||
|
@@ -1725,6 +1728,10 @@ void request_initialize(const llvm::json::Object &request) { | |||||||||||||||||
body.try_emplace("supportsDataBreakpoints", true); | ||||||||||||||||||
// The debug adapter support for instruction breakpoint. | ||||||||||||||||||
body.try_emplace("supportsInstructionBreakpoints", true); | ||||||||||||||||||
// The debug adapter support for read. | ||||||||||||||||||
body.try_emplace("supportsReadMemoryRequest", true); | ||||||||||||||||||
// The debug adapter support for write. | ||||||||||||||||||
body.try_emplace("supportsWriteMemoryRequest", true); | ||||||||||||||||||
|
||||||||||||||||||
// Put in non-DAP specification lldb specific information. | ||||||||||||||||||
llvm::json::Object lldb_json; | ||||||||||||||||||
|
@@ -4332,6 +4339,232 @@ void request_setInstructionBreakpoints(const llvm::json::Object &request) { | |||||||||||||||||
g_dap.SendJSON(llvm::json::Value(std::move(response))); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// "ReadMemoryRequest": { | ||||||||||||||||||
// "allOf": [ { "$ref": "#/definitions/Request" }, { | ||||||||||||||||||
// "type": "object", | ||||||||||||||||||
// "description": "Reads bytes from memory at the provided location.\n | ||||||||||||||||||
// Clients should only call this request if the corresponding capability | ||||||||||||||||||
// `supportsReadMemoryRequest` is true.", | ||||||||||||||||||
// "properties": { | ||||||||||||||||||
// "command": { | ||||||||||||||||||
// "type": "string", | ||||||||||||||||||
// "enum": [ "readMemory" ] | ||||||||||||||||||
// }, | ||||||||||||||||||
// "arguments": { | ||||||||||||||||||
// "$ref": "#/definitions/ReadMemoryArguments" | ||||||||||||||||||
// } | ||||||||||||||||||
// }, | ||||||||||||||||||
// "required": [ "command", "arguments" ] | ||||||||||||||||||
// }] | ||||||||||||||||||
// }, | ||||||||||||||||||
// "ReadMemoryArguments": { | ||||||||||||||||||
// "type": "object", | ||||||||||||||||||
// "description": "Arguments for `readMemory` request.", | ||||||||||||||||||
// "properties": { | ||||||||||||||||||
// "memoryReference": { | ||||||||||||||||||
// "type": "string", | ||||||||||||||||||
// "description": "Memory reference to the base location from which data | ||||||||||||||||||
// should be read." | ||||||||||||||||||
// }, | ||||||||||||||||||
// "offset": { | ||||||||||||||||||
// "type": "integer", | ||||||||||||||||||
// "description": "Offset (in bytes) to be applied to the reference | ||||||||||||||||||
// location before reading data. Can be negative." | ||||||||||||||||||
// }, | ||||||||||||||||||
// "count": { | ||||||||||||||||||
// "type": "integer", | ||||||||||||||||||
// "description": "Number of bytes to read at the specified location and | ||||||||||||||||||
// offset." | ||||||||||||||||||
// } | ||||||||||||||||||
// }, | ||||||||||||||||||
// "required": [ "memoryReference", "count" ] | ||||||||||||||||||
// }, | ||||||||||||||||||
// "ReadMemoryResponse": { | ||||||||||||||||||
// "allOf": [ { "$ref": "#/definitions/Response" }, { | ||||||||||||||||||
// "type": "object", | ||||||||||||||||||
// "description": "Response to `readMemory` request.", | ||||||||||||||||||
// "properties": { | ||||||||||||||||||
// "body": { | ||||||||||||||||||
// "type": "object", | ||||||||||||||||||
// "properties": { | ||||||||||||||||||
// "address": { | ||||||||||||||||||
// "type": "string", | ||||||||||||||||||
// "description": "The address of the first byte of data returned. | ||||||||||||||||||
// \nTreated as a hex value if prefixed with `0x`, or as a decimal | ||||||||||||||||||
// value otherwise." | ||||||||||||||||||
// }, | ||||||||||||||||||
// "unreadableBytes": { | ||||||||||||||||||
// "type": "integer", | ||||||||||||||||||
// "description": "The number of unreadable bytes encountered after | ||||||||||||||||||
// the last successfully read byte.\nThis can be used to determine | ||||||||||||||||||
// the number of bytes that should be skipped before a subsequent | ||||||||||||||||||
// `readMemory` request succeeds." | ||||||||||||||||||
// }, | ||||||||||||||||||
// "data": { | ||||||||||||||||||
// "type": "string", | ||||||||||||||||||
// "description": "The bytes read from memory, encoded using | ||||||||||||||||||
// base64. If the decoded length of `data` is less than the | ||||||||||||||||||
// requested `count` in the original `readMemory` request, and | ||||||||||||||||||
// `unreadableBytes` is zero or omitted, then the client should | ||||||||||||||||||
// assume it's reached the end of readable memory." | ||||||||||||||||||
// } | ||||||||||||||||||
// }, | ||||||||||||||||||
// "required": [ "address" ] | ||||||||||||||||||
// } | ||||||||||||||||||
// } | ||||||||||||||||||
// }] | ||||||||||||||||||
// }, | ||||||||||||||||||
void request_readMemory(const llvm::json::Object &request) { | ||||||||||||||||||
llvm::json::Object response; | ||||||||||||||||||
llvm::json::Array response_readMemory; | ||||||||||||||||||
llvm::json::Object body; | ||||||||||||||||||
lldb::SBError error; | ||||||||||||||||||
FillResponse(request, response); | ||||||||||||||||||
|
||||||||||||||||||
auto arguments = request.getObject("arguments"); | ||||||||||||||||||
llvm::StringRef memoryReference = GetString(arguments, "memoryReference"); | ||||||||||||||||||
const size_t count = GetUnsigned(arguments, "count", 0); | ||||||||||||||||||
const auto offset = GetSigned(arguments, "offset", 0); | ||||||||||||||||||
Comment on lines
+4426
to
+4427
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "offset" isn't optional, so if this value isn't in the arguments, we should return an error. Also if we fail to decode this key/value pair, we should not reset the offset to zero, but return an error. We might want to add a new GetSigned() overload like:
And have it return an error if:
If it is optional, then this code is fine. |
||||||||||||||||||
|
||||||||||||||||||
lldb::addr_t address; | ||||||||||||||||||
memoryReference.getAsInteger(0, address); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error checking in case this fails? What if "memoryReference" contains an invalid string? We should detect and return a value error and add a unit test for this. This function returns true to indicate an error so this can be:
|
||||||||||||||||||
lldb::addr_t address_offset = address + offset; | ||||||||||||||||||
// address as string | ||||||||||||||||||
std::string address_offset_str(std::to_string(address_offset)); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this isn't hex (decimal), lets encode this as hex for readability when looking at packet logs. |
||||||||||||||||||
|
||||||||||||||||||
// make storage for the memory to read | ||||||||||||||||||
// need to use a vector because VC++ 2019 errors out on char buffer[count] | ||||||||||||||||||
Comment on lines
+4435
to
+4436
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Variable-length arrays (a C++ extension) are also forbidden by the coding standards, so there's no need to justify this choice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed justification |
||||||||||||||||||
std::vector<char> buffer(count); | ||||||||||||||||||
|
||||||||||||||||||
// read the memory add to storage | ||||||||||||||||||
lldb::SBProcess process = g_dap.target.GetProcess(); | ||||||||||||||||||
size_t countRead = | ||||||||||||||||||
process.ReadMemory(address_offset, buffer.data(), count, error); | ||||||||||||||||||
const llvm::StringRef inputBytes = {buffer.data(), countRead}; | ||||||||||||||||||
// return the read memory in base64 | ||||||||||||||||||
std::string data = llvm::encodeBase64(inputBytes); | ||||||||||||||||||
|
||||||||||||||||||
// body: address, data? in spec formats | ||||||||||||||||||
body.try_emplace("address", std::move(address_offset_str)); | ||||||||||||||||||
body.try_emplace("data", std::move(data)); | ||||||||||||||||||
Comment on lines
+4445
to
+4449
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
If you don't make |
||||||||||||||||||
|
||||||||||||||||||
response.try_emplace("body", std::move(body)); | ||||||||||||||||||
g_dap.SendJSON(llvm::json::Value(std::move(response))); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// "WriteMemoryRequest": { | ||||||||||||||||||
// "allOf": [ { "$ref": "#/definitions/Request" }, { | ||||||||||||||||||
// "type": "object", | ||||||||||||||||||
// "description": "Writes bytes to memory at the provided location.\n | ||||||||||||||||||
// Clients should only call this request if the corresponding | ||||||||||||||||||
// capability `supportsWriteMemoryRequest` is true.", | ||||||||||||||||||
// "properties": { | ||||||||||||||||||
// "command": { | ||||||||||||||||||
// "type": "string", | ||||||||||||||||||
// "enum": [ "writeMemory" ] | ||||||||||||||||||
// }, | ||||||||||||||||||
// "arguments": { | ||||||||||||||||||
// "$ref": "#/definitions/WriteMemoryArguments" | ||||||||||||||||||
// } | ||||||||||||||||||
// }, | ||||||||||||||||||
// "required": [ "command", "arguments" ] | ||||||||||||||||||
// }] | ||||||||||||||||||
// }, | ||||||||||||||||||
// "WriteMemoryArguments": { | ||||||||||||||||||
// "type": "object", | ||||||||||||||||||
// "description": "Arguments for `writeMemory` request.", | ||||||||||||||||||
// "properties": { | ||||||||||||||||||
// "memoryReference": { | ||||||||||||||||||
// "type": "string", | ||||||||||||||||||
// "description": "Memory reference to the base location to which | ||||||||||||||||||
// data should be written." | ||||||||||||||||||
// }, | ||||||||||||||||||
// "offset": { | ||||||||||||||||||
// "type": "integer", | ||||||||||||||||||
// "description": "Offset (in bytes) to be applied to the reference | ||||||||||||||||||
// location before writing data. Can be negative." | ||||||||||||||||||
// }, | ||||||||||||||||||
// "allowPartial": { | ||||||||||||||||||
// "type": "boolean", | ||||||||||||||||||
// "description": "Property to control partial writes. If true, the | ||||||||||||||||||
// debug adapter should attempt to write memory even if the entire | ||||||||||||||||||
// memory region is not writable. In such a case the debug adapter | ||||||||||||||||||
// should stop after hitting the first byte of memory that cannot be | ||||||||||||||||||
// written and return the number of bytes written in the response | ||||||||||||||||||
// via the `offset` and `bytesWritten` properties.\nIf false or | ||||||||||||||||||
// missing, a debug adapter should attempt to verify the region is | ||||||||||||||||||
// writable before writing, and fail the response if it is not." | ||||||||||||||||||
// }, | ||||||||||||||||||
// "data": { | ||||||||||||||||||
// "type": "string", | ||||||||||||||||||
// "description": "Bytes to write, encoded using base64." | ||||||||||||||||||
// } | ||||||||||||||||||
// }, | ||||||||||||||||||
// "required": [ "memoryReference", "data" ] | ||||||||||||||||||
// }, | ||||||||||||||||||
// "WriteMemoryResponse": { | ||||||||||||||||||
// "allOf": [ { "$ref": "#/definitions/Response" }, { | ||||||||||||||||||
// "type": "object", | ||||||||||||||||||
// "description": "Response to `writeMemory` request.", | ||||||||||||||||||
// "properties": { | ||||||||||||||||||
// "body": { | ||||||||||||||||||
// "type": "object", | ||||||||||||||||||
// "properties": { | ||||||||||||||||||
// "offset": { | ||||||||||||||||||
// "type": "integer", | ||||||||||||||||||
// "description": "Property that should be returned when | ||||||||||||||||||
// `allowPartial` is true to indicate the offset of the first | ||||||||||||||||||
// byte of data successfully written. Can be negative." | ||||||||||||||||||
// }, | ||||||||||||||||||
// "bytesWritten": { | ||||||||||||||||||
// "type": "integer", | ||||||||||||||||||
// "description": "Property that should be returned when | ||||||||||||||||||
// `allowPartial` is true to indicate the number of bytes | ||||||||||||||||||
// starting from address that were successfully written." | ||||||||||||||||||
// } | ||||||||||||||||||
// } | ||||||||||||||||||
// } | ||||||||||||||||||
// } | ||||||||||||||||||
// }] | ||||||||||||||||||
// }, | ||||||||||||||||||
|
||||||||||||||||||
void request_writeMemory(const llvm::json::Object &request) { | ||||||||||||||||||
llvm::json::Object response; | ||||||||||||||||||
llvm::json::Array response_writeMemory; | ||||||||||||||||||
llvm::json::Object body; | ||||||||||||||||||
FillResponse(request, response); | ||||||||||||||||||
|
||||||||||||||||||
auto arguments = request.getObject("arguments"); | ||||||||||||||||||
llvm::StringRef memoryReference = GetString(arguments, "memoryReference"); | ||||||||||||||||||
const auto offset = GetSigned(arguments, "offset", 0); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the new llvm::Expected<uint64_t> version of GetSigned() as mentioned above. This can't be missing from what I read from the spec if the arg isn't optional. If it is optional, then this code is fine. |
||||||||||||||||||
llvm::StringRef data64 = GetString(arguments, "data"); | ||||||||||||||||||
|
||||||||||||||||||
lldb::addr_t address; | ||||||||||||||||||
memoryReference.getAsInteger(0, address); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error checking in case this fails? What if "memoryReference" contains an invalid string? We should detect and return a value error and add a unit test for this. This function returns true to indicate an error so this can be:
|
||||||||||||||||||
lldb::addr_t address_offset = address + offset; | ||||||||||||||||||
|
||||||||||||||||||
std::vector<char> output; | ||||||||||||||||||
lldb::SBError writeError; | ||||||||||||||||||
int64_t countWrite = 0; | ||||||||||||||||||
|
||||||||||||||||||
llvm::Error decodeError = llvm::decodeBase64(data64, output); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you need to return a failed response if there was a decoding error, otherwise the debug adapter will crash because you didn't handle the Error case. |
||||||||||||||||||
|
||||||||||||||||||
int64_t dataLen = output.size(); | ||||||||||||||||||
|
||||||||||||||||||
// write the memory | ||||||||||||||||||
if (decodeError.success()) { | ||||||||||||||||||
lldb::SBProcess process = g_dap.target.GetProcess(); | ||||||||||||||||||
countWrite = | ||||||||||||||||||
process.WriteMemory(address_offset, static_cast<void *>(output.data()), | ||||||||||||||||||
dataLen, writeError); | ||||||||||||||||||
Comment on lines
+4557
to
+4559
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how does this behave if the dataLen is much bigger than the writable memory? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to this, can you handle |
||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
body.try_emplace("bytesWritten", std::move(countWrite)); | ||||||||||||||||||
|
||||||||||||||||||
response.try_emplace("body", std::move(body)); | ||||||||||||||||||
g_dap.SendJSON(llvm::json::Value(std::move(response))); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
void RegisterRequestCallbacks() { | ||||||||||||||||||
g_dap.RegisterRequestCallback("attach", request_attach); | ||||||||||||||||||
g_dap.RegisterRequestCallback("completions", request_completions); | ||||||||||||||||||
|
@@ -4367,6 +4600,10 @@ void RegisterRequestCallbacks() { | |||||||||||||||||
// Instruction breakpoint request | ||||||||||||||||||
g_dap.RegisterRequestCallback("setInstructionBreakpoints", | ||||||||||||||||||
request_setInstructionBreakpoints); | ||||||||||||||||||
// ReadMemory request | ||||||||||||||||||
g_dap.RegisterRequestCallback("readMemory", request_readMemory); | ||||||||||||||||||
// WriteMemory request | ||||||||||||||||||
g_dap.RegisterRequestCallback("writeMemory", request_writeMemory); | ||||||||||||||||||
// Custom requests | ||||||||||||||||||
g_dap.RegisterRequestCallback("compileUnits", request_compileUnits); | ||||||||||||||||||
g_dap.RegisterRequestCallback("modules", request_modules); | ||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use size_t, it is 32 bits on a 32 bit CPUs. Use
uint64_t
. Also Count can't be zero, if it is return an error. We might also want to watch for a huge value being passed into this and return less bytes if the value is too large and set "unreadableBytes" to zero so it knows it can ask for more. If this value isn't present we should return an error. We might want to add a new GetUnsigned() overload like:And have it return an error if:
We can just take the returned error and convert it to a string and return the error.