2018-08-17 01:59:38 +08:00
|
|
|
//===-- JSONUtils.cpp -------------------------------------------*- C++ -*-===//
|
|
|
|
//
|
2019-01-19 16:50:56 +08:00
|
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
2018-08-17 01:59:38 +08:00
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2018-11-06 06:55:30 +08:00
|
|
|
#include <algorithm>
|
2020-07-25 04:30:04 +08:00
|
|
|
#include <iomanip>
|
|
|
|
#include <sstream>
|
2018-11-06 06:55:30 +08:00
|
|
|
|
[lldb-vscode] Correctly return source mapped breakpoints for setBreakpoints request
Summary:
When using source maps for a breakpoint, in order to find the actual source that breakpoint has resolved, we
need to use a query similar to what CommandObjectSource::DumpLinesInSymbolContexts does, which is the logic
used by the CLI to display the source line at a given breakpoint. That's necessary because from a breakpoint
location you only have an address, which points to the original source location, not the source mapped one.
in the setBreakpoints request handler, we haven't been doing such query and we were returning the original
source location, which was breaking the UX of VSCode, as many breakpoints were being set but not displayed
in the source file next to each line. Besides, clicking the source path of a breakpoint in the breakpoints
view in the debug tab was not working for this case, as VSCode was trying to open a non-existent file, thus
showing an error to the user.
Ideally, we should do the query mentioned above to find the actual source location to respond to the IDE,
however, that query is expensive and users can have an arbitrary number of breakpoints set. As a simpler fix,
the request sent by VSCode already contains the full source path, which exists because the user set it from
the IDE itself, so we can simply reuse it instead of querying from the SB API.
I wrote a test for this, and found out that I had to move SetSourceMapFromArguments after RunInitCommands in
lldb-vscode.cpp, because an init command used in all tests is `settings clear -all`, which would clear the
source map unless specified after initCommands. And in any case, I think it makes sense to have initCommands
run before anything the debugger would do.
Reviewers: clayborg, kusmour, labath, aadsm
Subscribers: lldb-commits
Tags: #lldb
Differential Revision: https://reviews.llvm.org/D76968
2020-03-28 10:31:14 +08:00
|
|
|
#include "llvm/ADT/Optional.h"
|
2018-08-17 01:59:38 +08:00
|
|
|
#include "llvm/Support/FormatAdapters.h"
|
[lldb-vscode] Correctly return source mapped breakpoints for setBreakpoints request
Summary:
When using source maps for a breakpoint, in order to find the actual source that breakpoint has resolved, we
need to use a query similar to what CommandObjectSource::DumpLinesInSymbolContexts does, which is the logic
used by the CLI to display the source line at a given breakpoint. That's necessary because from a breakpoint
location you only have an address, which points to the original source location, not the source mapped one.
in the setBreakpoints request handler, we haven't been doing such query and we were returning the original
source location, which was breaking the UX of VSCode, as many breakpoints were being set but not displayed
in the source file next to each line. Besides, clicking the source path of a breakpoint in the breakpoints
view in the debug tab was not working for this case, as VSCode was trying to open a non-existent file, thus
showing an error to the user.
Ideally, we should do the query mentioned above to find the actual source location to respond to the IDE,
however, that query is expensive and users can have an arbitrary number of breakpoints set. As a simpler fix,
the request sent by VSCode already contains the full source path, which exists because the user set it from
the IDE itself, so we can simply reuse it instead of querying from the SB API.
I wrote a test for this, and found out that I had to move SetSourceMapFromArguments after RunInitCommands in
lldb-vscode.cpp, because an init command used in all tests is `settings clear -all`, which would clear the
source map unless specified after initCommands. And in any case, I think it makes sense to have initCommands
run before anything the debugger would do.
Reviewers: clayborg, kusmour, labath, aadsm
Subscribers: lldb-commits
Tags: #lldb
Differential Revision: https://reviews.llvm.org/D76968
2020-03-28 10:31:14 +08:00
|
|
|
#include "llvm/Support/Path.h"
|
2020-04-28 19:13:45 +08:00
|
|
|
#include "llvm/Support/ScopedPrinter.h"
|
2018-08-17 01:59:38 +08:00
|
|
|
|
|
|
|
#include "lldb/API/SBBreakpoint.h"
|
|
|
|
#include "lldb/API/SBBreakpointLocation.h"
|
2021-04-22 06:06:44 +08:00
|
|
|
#include "lldb/API/SBDeclaration.h"
|
2018-08-17 01:59:38 +08:00
|
|
|
#include "lldb/API/SBValue.h"
|
2018-08-17 02:24:59 +08:00
|
|
|
#include "lldb/Host/PosixApi.h"
|
2018-08-17 01:59:38 +08:00
|
|
|
|
|
|
|
#include "ExceptionBreakpoint.h"
|
|
|
|
#include "JSONUtils.h"
|
|
|
|
#include "LLDBUtils.h"
|
|
|
|
#include "VSCode.h"
|
|
|
|
|
|
|
|
namespace lldb_vscode {
|
|
|
|
|
2018-11-16 03:49:57 +08:00
|
|
|
void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key,
|
|
|
|
llvm::StringRef str) {
|
|
|
|
if (LLVM_LIKELY(llvm::json::isUTF8(str)))
|
|
|
|
obj.try_emplace(key, str.str());
|
|
|
|
else
|
|
|
|
obj.try_emplace(key, llvm::json::fixUTF8(str));
|
|
|
|
}
|
|
|
|
|
2018-08-17 01:59:38 +08:00
|
|
|
llvm::StringRef GetAsString(const llvm::json::Value &value) {
|
|
|
|
if (auto s = value.getAsString())
|
|
|
|
return *s;
|
|
|
|
return llvm::StringRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gets a string from a JSON object using the key, or returns an empty string.
|
|
|
|
llvm::StringRef GetString(const llvm::json::Object &obj, llvm::StringRef key) {
|
2020-04-28 19:13:45 +08:00
|
|
|
if (llvm::Optional<llvm::StringRef> value = obj.getString(key))
|
|
|
|
return *value;
|
2018-08-17 01:59:38 +08:00
|
|
|
return llvm::StringRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
llvm::StringRef GetString(const llvm::json::Object *obj, llvm::StringRef key) {
|
|
|
|
if (obj == nullptr)
|
|
|
|
return llvm::StringRef();
|
|
|
|
return GetString(*obj, key);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gets an unsigned integer from a JSON object using the key, or returns the
|
|
|
|
// specified fail value.
|
|
|
|
uint64_t GetUnsigned(const llvm::json::Object &obj, llvm::StringRef key,
|
|
|
|
uint64_t fail_value) {
|
|
|
|
if (auto value = obj.getInteger(key))
|
|
|
|
return (uint64_t)*value;
|
|
|
|
return fail_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t GetUnsigned(const llvm::json::Object *obj, llvm::StringRef key,
|
|
|
|
uint64_t fail_value) {
|
|
|
|
if (obj == nullptr)
|
|
|
|
return fail_value;
|
|
|
|
return GetUnsigned(*obj, key, fail_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GetBoolean(const llvm::json::Object &obj, llvm::StringRef key,
|
|
|
|
bool fail_value) {
|
|
|
|
if (auto value = obj.getBoolean(key))
|
|
|
|
return *value;
|
|
|
|
if (auto value = obj.getInteger(key))
|
|
|
|
return *value != 0;
|
|
|
|
return fail_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GetBoolean(const llvm::json::Object *obj, llvm::StringRef key,
|
|
|
|
bool fail_value) {
|
|
|
|
if (obj == nullptr)
|
|
|
|
return fail_value;
|
|
|
|
return GetBoolean(*obj, key, fail_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t GetSigned(const llvm::json::Object &obj, llvm::StringRef key,
|
|
|
|
int64_t fail_value) {
|
|
|
|
if (auto value = obj.getInteger(key))
|
|
|
|
return *value;
|
|
|
|
return fail_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t GetSigned(const llvm::json::Object *obj, llvm::StringRef key,
|
|
|
|
int64_t fail_value) {
|
|
|
|
if (obj == nullptr)
|
|
|
|
return fail_value;
|
|
|
|
return GetSigned(*obj, key, fail_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) {
|
2018-12-15 08:15:33 +08:00
|
|
|
return obj.find(key) != obj.end();
|
2018-08-17 01:59:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> GetStrings(const llvm::json::Object *obj,
|
|
|
|
llvm::StringRef key) {
|
|
|
|
std::vector<std::string> strs;
|
|
|
|
auto json_array = obj->getArray(key);
|
|
|
|
if (!json_array)
|
|
|
|
return strs;
|
|
|
|
for (const auto &value : *json_array) {
|
|
|
|
switch (value.kind()) {
|
|
|
|
case llvm::json::Value::String:
|
|
|
|
strs.push_back(value.getAsString()->str());
|
|
|
|
break;
|
|
|
|
case llvm::json::Value::Number:
|
2020-04-28 19:13:45 +08:00
|
|
|
case llvm::json::Value::Boolean:
|
|
|
|
strs.push_back(llvm::to_string(value));
|
2018-08-17 01:59:38 +08:00
|
|
|
break;
|
|
|
|
case llvm::json::Value::Null:
|
|
|
|
case llvm::json::Value::Object:
|
|
|
|
case llvm::json::Value::Array:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return strs;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetValueForKey(lldb::SBValue &v, llvm::json::Object &object,
|
|
|
|
llvm::StringRef key) {
|
2018-11-16 03:49:57 +08:00
|
|
|
|
2018-08-17 01:59:38 +08:00
|
|
|
llvm::StringRef value = v.GetValue();
|
|
|
|
llvm::StringRef summary = v.GetSummary();
|
|
|
|
llvm::StringRef type_name = v.GetType().GetDisplayTypeName();
|
2018-11-16 03:49:57 +08:00
|
|
|
|
2018-08-17 01:59:38 +08:00
|
|
|
std::string result;
|
|
|
|
llvm::raw_string_ostream strm(result);
|
|
|
|
if (!value.empty()) {
|
|
|
|
strm << value;
|
|
|
|
if (!summary.empty())
|
|
|
|
strm << ' ' << summary;
|
|
|
|
} else if (!summary.empty()) {
|
|
|
|
strm << ' ' << summary;
|
|
|
|
} else if (!type_name.empty()) {
|
|
|
|
strm << type_name;
|
|
|
|
lldb::addr_t address = v.GetLoadAddress();
|
|
|
|
if (address != LLDB_INVALID_ADDRESS)
|
|
|
|
strm << " @ " << llvm::format_hex(address, 0);
|
|
|
|
}
|
|
|
|
strm.flush();
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, key, result);
|
2018-08-17 01:59:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void FillResponse(const llvm::json::Object &request,
|
|
|
|
llvm::json::Object &response) {
|
|
|
|
// Fill in all of the needed response fields to a "request" and set "success"
|
|
|
|
// to true by default.
|
|
|
|
response.try_emplace("type", "response");
|
|
|
|
response.try_emplace("seq", (int64_t)0);
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(response, "command", GetString(request, "command"));
|
2018-08-17 01:59:38 +08:00
|
|
|
const int64_t seq = GetSigned(request, "seq", 0);
|
|
|
|
response.try_emplace("request_seq", seq);
|
|
|
|
response.try_emplace("success", true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// "Scope": {
|
|
|
|
// "type": "object",
|
|
|
|
// "description": "A Scope is a named container for variables. Optionally
|
|
|
|
// a scope can map to a source or a range within a source.",
|
|
|
|
// "properties": {
|
|
|
|
// "name": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "Name of the scope such as 'Arguments', 'Locals'."
|
|
|
|
// },
|
2021-11-08 22:44:31 +08:00
|
|
|
// "presentationHint": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "An optional hint for how to present this scope in the
|
|
|
|
// UI. If this attribute is missing, the scope is shown
|
|
|
|
// with a generic UI.",
|
|
|
|
// "_enum": [ "arguments", "locals", "registers" ],
|
|
|
|
// },
|
2018-08-17 01:59:38 +08:00
|
|
|
// "variablesReference": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "The variables of this scope can be retrieved by
|
|
|
|
// passing the value of variablesReference to the
|
|
|
|
// VariablesRequest."
|
|
|
|
// },
|
|
|
|
// "namedVariables": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "The number of named variables in this scope. The
|
|
|
|
// client can use this optional information to present
|
|
|
|
// the variables in a paged UI and fetch them in chunks."
|
|
|
|
// },
|
|
|
|
// "indexedVariables": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "The number of indexed variables in this scope. The
|
|
|
|
// client can use this optional information to present
|
|
|
|
// the variables in a paged UI and fetch them in chunks."
|
|
|
|
// },
|
|
|
|
// "expensive": {
|
|
|
|
// "type": "boolean",
|
|
|
|
// "description": "If true, the number of variables in this scope is
|
|
|
|
// large or expensive to retrieve."
|
|
|
|
// },
|
|
|
|
// "source": {
|
|
|
|
// "$ref": "#/definitions/Source",
|
|
|
|
// "description": "Optional source for this scope."
|
|
|
|
// },
|
|
|
|
// "line": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "Optional start line of the range covered by this
|
|
|
|
// scope."
|
|
|
|
// },
|
|
|
|
// "column": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "Optional start column of the range covered by this
|
|
|
|
// scope."
|
|
|
|
// },
|
|
|
|
// "endLine": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "Optional end line of the range covered by this scope."
|
|
|
|
// },
|
|
|
|
// "endColumn": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "Optional end column of the range covered by this
|
|
|
|
// scope."
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// "required": [ "name", "variablesReference", "expensive" ]
|
|
|
|
// }
|
|
|
|
llvm::json::Value CreateScope(const llvm::StringRef name,
|
|
|
|
int64_t variablesReference,
|
|
|
|
int64_t namedVariables, bool expensive) {
|
|
|
|
llvm::json::Object object;
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, "name", name.str());
|
2021-11-08 22:44:31 +08:00
|
|
|
|
|
|
|
// TODO: Support "arguments" scope. At the moment lldb-vscode includes the
|
|
|
|
// arguments into the "locals" scope.
|
|
|
|
if (variablesReference == VARREF_LOCALS) {
|
|
|
|
object.try_emplace("presentationHint", "locals");
|
|
|
|
} else if (variablesReference == VARREF_REGS) {
|
|
|
|
object.try_emplace("presentationHint", "registers");
|
|
|
|
}
|
|
|
|
|
2018-08-17 01:59:38 +08:00
|
|
|
object.try_emplace("variablesReference", variablesReference);
|
|
|
|
object.try_emplace("expensive", expensive);
|
|
|
|
object.try_emplace("namedVariables", namedVariables);
|
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
}
|
|
|
|
|
|
|
|
// "Breakpoint": {
|
|
|
|
// "type": "object",
|
|
|
|
// "description": "Information about a Breakpoint created in setBreakpoints
|
|
|
|
// or setFunctionBreakpoints.",
|
|
|
|
// "properties": {
|
|
|
|
// "id": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "An optional unique identifier for the breakpoint."
|
|
|
|
// },
|
|
|
|
// "verified": {
|
|
|
|
// "type": "boolean",
|
|
|
|
// "description": "If true breakpoint could be set (but not necessarily
|
|
|
|
// at the desired location)."
|
|
|
|
// },
|
|
|
|
// "message": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "An optional message about the state of the breakpoint.
|
|
|
|
// This is shown to the user and can be used to explain
|
|
|
|
// why a breakpoint could not be verified."
|
|
|
|
// },
|
|
|
|
// "source": {
|
|
|
|
// "$ref": "#/definitions/Source",
|
|
|
|
// "description": "The source where the breakpoint is located."
|
|
|
|
// },
|
|
|
|
// "line": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "The start line of the actual range covered by the
|
|
|
|
// breakpoint."
|
|
|
|
// },
|
|
|
|
// "column": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "An optional start column of the actual range covered
|
|
|
|
// by the breakpoint."
|
|
|
|
// },
|
|
|
|
// "endLine": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "An optional end line of the actual range covered by
|
|
|
|
// the breakpoint."
|
|
|
|
// },
|
|
|
|
// "endColumn": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "An optional end column of the actual range covered by
|
|
|
|
// the breakpoint. If no end line is given, then the end
|
|
|
|
// column is assumed to be in the start line."
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// "required": [ "verified" ]
|
|
|
|
// }
|
[lldb-vscode] Correctly return source mapped breakpoints for setBreakpoints request
Summary:
When using source maps for a breakpoint, in order to find the actual source that breakpoint has resolved, we
need to use a query similar to what CommandObjectSource::DumpLinesInSymbolContexts does, which is the logic
used by the CLI to display the source line at a given breakpoint. That's necessary because from a breakpoint
location you only have an address, which points to the original source location, not the source mapped one.
in the setBreakpoints request handler, we haven't been doing such query and we were returning the original
source location, which was breaking the UX of VSCode, as many breakpoints were being set but not displayed
in the source file next to each line. Besides, clicking the source path of a breakpoint in the breakpoints
view in the debug tab was not working for this case, as VSCode was trying to open a non-existent file, thus
showing an error to the user.
Ideally, we should do the query mentioned above to find the actual source location to respond to the IDE,
however, that query is expensive and users can have an arbitrary number of breakpoints set. As a simpler fix,
the request sent by VSCode already contains the full source path, which exists because the user set it from
the IDE itself, so we can simply reuse it instead of querying from the SB API.
I wrote a test for this, and found out that I had to move SetSourceMapFromArguments after RunInitCommands in
lldb-vscode.cpp, because an init command used in all tests is `settings clear -all`, which would clear the
source map unless specified after initCommands. And in any case, I think it makes sense to have initCommands
run before anything the debugger would do.
Reviewers: clayborg, kusmour, labath, aadsm
Subscribers: lldb-commits
Tags: #lldb
Differential Revision: https://reviews.llvm.org/D76968
2020-03-28 10:31:14 +08:00
|
|
|
llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp,
|
|
|
|
llvm::Optional<llvm::StringRef> request_path,
|
|
|
|
llvm::Optional<uint32_t> request_line) {
|
2018-08-17 01:59:38 +08:00
|
|
|
// Each breakpoint location is treated as a separate breakpoint for VS code.
|
|
|
|
// They don't have the notion of a single breakpoint with multiple locations.
|
|
|
|
llvm::json::Object object;
|
2020-01-30 06:11:40 +08:00
|
|
|
if (!bp.IsValid())
|
2018-08-17 01:59:38 +08:00
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
|
2020-01-30 06:11:40 +08:00
|
|
|
object.try_emplace("verified", bp.GetNumResolvedLocations() > 0);
|
|
|
|
object.try_emplace("id", bp.GetID());
|
|
|
|
// VS Code DAP doesn't currently allow one breakpoint to have multiple
|
|
|
|
// locations so we just report the first one. If we report all locations
|
|
|
|
// then the IDE starts showing the wrong line numbers and locations for
|
|
|
|
// other source file and line breakpoints in the same file.
|
|
|
|
|
|
|
|
// Below we search for the first resolved location in a breakpoint and report
|
|
|
|
// this as the breakpoint location since it will have a complete location
|
|
|
|
// that is at least loaded in the current process.
|
|
|
|
lldb::SBBreakpointLocation bp_loc;
|
|
|
|
const auto num_locs = bp.GetNumLocations();
|
[lldb-vscode] Correctly return source mapped breakpoints for setBreakpoints request
Summary:
When using source maps for a breakpoint, in order to find the actual source that breakpoint has resolved, we
need to use a query similar to what CommandObjectSource::DumpLinesInSymbolContexts does, which is the logic
used by the CLI to display the source line at a given breakpoint. That's necessary because from a breakpoint
location you only have an address, which points to the original source location, not the source mapped one.
in the setBreakpoints request handler, we haven't been doing such query and we were returning the original
source location, which was breaking the UX of VSCode, as many breakpoints were being set but not displayed
in the source file next to each line. Besides, clicking the source path of a breakpoint in the breakpoints
view in the debug tab was not working for this case, as VSCode was trying to open a non-existent file, thus
showing an error to the user.
Ideally, we should do the query mentioned above to find the actual source location to respond to the IDE,
however, that query is expensive and users can have an arbitrary number of breakpoints set. As a simpler fix,
the request sent by VSCode already contains the full source path, which exists because the user set it from
the IDE itself, so we can simply reuse it instead of querying from the SB API.
I wrote a test for this, and found out that I had to move SetSourceMapFromArguments after RunInitCommands in
lldb-vscode.cpp, because an init command used in all tests is `settings clear -all`, which would clear the
source map unless specified after initCommands. And in any case, I think it makes sense to have initCommands
run before anything the debugger would do.
Reviewers: clayborg, kusmour, labath, aadsm
Subscribers: lldb-commits
Tags: #lldb
Differential Revision: https://reviews.llvm.org/D76968
2020-03-28 10:31:14 +08:00
|
|
|
for (size_t i = 0; i < num_locs; ++i) {
|
2020-01-30 06:11:40 +08:00
|
|
|
bp_loc = bp.GetLocationAtIndex(i);
|
|
|
|
if (bp_loc.IsResolved())
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// If not locations are resolved, use the first location.
|
|
|
|
if (!bp_loc.IsResolved())
|
|
|
|
bp_loc = bp.GetLocationAtIndex(0);
|
2018-08-17 01:59:38 +08:00
|
|
|
auto bp_addr = bp_loc.GetAddress();
|
[lldb-vscode] Correctly return source mapped breakpoints for setBreakpoints request
Summary:
When using source maps for a breakpoint, in order to find the actual source that breakpoint has resolved, we
need to use a query similar to what CommandObjectSource::DumpLinesInSymbolContexts does, which is the logic
used by the CLI to display the source line at a given breakpoint. That's necessary because from a breakpoint
location you only have an address, which points to the original source location, not the source mapped one.
in the setBreakpoints request handler, we haven't been doing such query and we were returning the original
source location, which was breaking the UX of VSCode, as many breakpoints were being set but not displayed
in the source file next to each line. Besides, clicking the source path of a breakpoint in the breakpoints
view in the debug tab was not working for this case, as VSCode was trying to open a non-existent file, thus
showing an error to the user.
Ideally, we should do the query mentioned above to find the actual source location to respond to the IDE,
however, that query is expensive and users can have an arbitrary number of breakpoints set. As a simpler fix,
the request sent by VSCode already contains the full source path, which exists because the user set it from
the IDE itself, so we can simply reuse it instead of querying from the SB API.
I wrote a test for this, and found out that I had to move SetSourceMapFromArguments after RunInitCommands in
lldb-vscode.cpp, because an init command used in all tests is `settings clear -all`, which would clear the
source map unless specified after initCommands. And in any case, I think it makes sense to have initCommands
run before anything the debugger would do.
Reviewers: clayborg, kusmour, labath, aadsm
Subscribers: lldb-commits
Tags: #lldb
Differential Revision: https://reviews.llvm.org/D76968
2020-03-28 10:31:14 +08:00
|
|
|
|
|
|
|
if (request_path)
|
|
|
|
object.try_emplace("source", CreateSource(*request_path));
|
|
|
|
|
2018-08-17 01:59:38 +08:00
|
|
|
if (bp_addr.IsValid()) {
|
|
|
|
auto line_entry = bp_addr.GetLineEntry();
|
|
|
|
const auto line = line_entry.GetLine();
|
|
|
|
if (line != UINT32_MAX)
|
|
|
|
object.try_emplace("line", line);
|
|
|
|
object.try_emplace("source", CreateSource(line_entry));
|
|
|
|
}
|
[lldb-vscode] Correctly return source mapped breakpoints for setBreakpoints request
Summary:
When using source maps for a breakpoint, in order to find the actual source that breakpoint has resolved, we
need to use a query similar to what CommandObjectSource::DumpLinesInSymbolContexts does, which is the logic
used by the CLI to display the source line at a given breakpoint. That's necessary because from a breakpoint
location you only have an address, which points to the original source location, not the source mapped one.
in the setBreakpoints request handler, we haven't been doing such query and we were returning the original
source location, which was breaking the UX of VSCode, as many breakpoints were being set but not displayed
in the source file next to each line. Besides, clicking the source path of a breakpoint in the breakpoints
view in the debug tab was not working for this case, as VSCode was trying to open a non-existent file, thus
showing an error to the user.
Ideally, we should do the query mentioned above to find the actual source location to respond to the IDE,
however, that query is expensive and users can have an arbitrary number of breakpoints set. As a simpler fix,
the request sent by VSCode already contains the full source path, which exists because the user set it from
the IDE itself, so we can simply reuse it instead of querying from the SB API.
I wrote a test for this, and found out that I had to move SetSourceMapFromArguments after RunInitCommands in
lldb-vscode.cpp, because an init command used in all tests is `settings clear -all`, which would clear the
source map unless specified after initCommands. And in any case, I think it makes sense to have initCommands
run before anything the debugger would do.
Reviewers: clayborg, kusmour, labath, aadsm
Subscribers: lldb-commits
Tags: #lldb
Differential Revision: https://reviews.llvm.org/D76968
2020-03-28 10:31:14 +08:00
|
|
|
// We try to add request_line as a fallback
|
|
|
|
if (request_line)
|
|
|
|
object.try_emplace("line", *request_line);
|
2018-08-17 01:59:38 +08:00
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
}
|
|
|
|
|
2020-07-25 04:30:04 +08:00
|
|
|
static uint64_t GetDebugInfoSizeInSection(lldb::SBSection section) {
|
|
|
|
uint64_t debug_info_size = 0;
|
|
|
|
llvm::StringRef section_name(section.GetName());
|
|
|
|
if (section_name.startswith(".debug") || section_name.startswith("__debug") ||
|
|
|
|
section_name.startswith(".apple") || section_name.startswith("__apple"))
|
|
|
|
debug_info_size += section.GetFileByteSize();
|
|
|
|
size_t num_sub_sections = section.GetNumSubSections();
|
|
|
|
for (size_t i = 0; i < num_sub_sections; i++) {
|
|
|
|
debug_info_size +=
|
|
|
|
GetDebugInfoSizeInSection(section.GetSubSectionAtIndex(i));
|
|
|
|
}
|
|
|
|
return debug_info_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint64_t GetDebugInfoSize(lldb::SBModule module) {
|
|
|
|
uint64_t debug_info_size = 0;
|
|
|
|
size_t num_sections = module.GetNumSections();
|
|
|
|
for (size_t i = 0; i < num_sections; i++) {
|
|
|
|
debug_info_size += GetDebugInfoSizeInSection(module.GetSectionAtIndex(i));
|
|
|
|
}
|
|
|
|
return debug_info_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::string ConvertDebugInfoSizeToString(uint64_t debug_info) {
|
|
|
|
std::ostringstream oss;
|
|
|
|
oss << std::fixed << std::setprecision(1);
|
|
|
|
if (debug_info < 1024) {
|
|
|
|
oss << debug_info << "B";
|
|
|
|
} else if (debug_info < 1024 * 1024) {
|
|
|
|
double kb = double(debug_info) / 1024.0;
|
|
|
|
oss << kb << "KB";
|
|
|
|
} else if (debug_info < 1024 * 1024 * 1024) {
|
|
|
|
double mb = double(debug_info) / (1024.0 * 1024.0);
|
|
|
|
oss << mb << "MB";
|
|
|
|
} else {
|
|
|
|
double gb = double(debug_info) / (1024.0 * 1024.0 * 1024.0);
|
|
|
|
oss << gb << "GB";
|
|
|
|
}
|
|
|
|
return oss.str();
|
|
|
|
}
|
2020-07-14 03:10:49 +08:00
|
|
|
llvm::json::Value CreateModule(lldb::SBModule &module) {
|
|
|
|
llvm::json::Object object;
|
|
|
|
if (!module.IsValid())
|
|
|
|
return llvm::json::Value(std::move(object));
|
2020-07-14 09:26:41 +08:00
|
|
|
const char *uuid = module.GetUUIDString();
|
|
|
|
object.try_emplace("id", uuid ? std::string(uuid) : std::string(""));
|
2020-07-14 03:10:49 +08:00
|
|
|
object.try_emplace("name", std::string(module.GetFileSpec().GetFilename()));
|
|
|
|
char module_path_arr[PATH_MAX];
|
|
|
|
module.GetFileSpec().GetPath(module_path_arr, sizeof(module_path_arr));
|
|
|
|
std::string module_path(module_path_arr);
|
|
|
|
object.try_emplace("path", module_path);
|
|
|
|
if (module.GetNumCompileUnits() > 0) {
|
2020-07-25 04:30:04 +08:00
|
|
|
std::string symbol_str = "Symbols loaded.";
|
2020-09-02 06:59:51 +08:00
|
|
|
std::string debug_info_size;
|
2020-07-25 04:30:04 +08:00
|
|
|
uint64_t debug_info = GetDebugInfoSize(module);
|
|
|
|
if (debug_info > 0) {
|
2020-09-02 06:59:51 +08:00
|
|
|
debug_info_size = ConvertDebugInfoSizeToString(debug_info);
|
2020-07-25 04:30:04 +08:00
|
|
|
}
|
|
|
|
object.try_emplace("symbolStatus", symbol_str);
|
2020-09-02 06:59:51 +08:00
|
|
|
object.try_emplace("debugInfoSize", debug_info_size);
|
2020-07-14 03:10:49 +08:00
|
|
|
char symbol_path_arr[PATH_MAX];
|
2020-07-25 04:30:04 +08:00
|
|
|
module.GetSymbolFileSpec().GetPath(symbol_path_arr,
|
|
|
|
sizeof(symbol_path_arr));
|
2020-07-14 03:10:49 +08:00
|
|
|
std::string symbol_path(symbol_path_arr);
|
|
|
|
object.try_emplace("symbolFilePath", symbol_path);
|
|
|
|
} else {
|
|
|
|
object.try_emplace("symbolStatus", "Symbols not found.");
|
|
|
|
}
|
|
|
|
std::string loaded_addr = std::to_string(
|
|
|
|
module.GetObjectFileHeaderAddress().GetLoadAddress(g_vsc.target));
|
|
|
|
object.try_emplace("addressRange", loaded_addr);
|
|
|
|
std::string version_str;
|
|
|
|
uint32_t version_nums[3];
|
2020-07-25 04:30:04 +08:00
|
|
|
uint32_t num_versions =
|
|
|
|
module.GetVersion(version_nums, sizeof(version_nums) / sizeof(uint32_t));
|
|
|
|
for (uint32_t i = 0; i < num_versions; ++i) {
|
2020-07-14 03:10:49 +08:00
|
|
|
if (!version_str.empty())
|
|
|
|
version_str += ".";
|
|
|
|
version_str += std::to_string(version_nums[i]);
|
|
|
|
}
|
|
|
|
if (!version_str.empty())
|
|
|
|
object.try_emplace("version", version_str);
|
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
}
|
|
|
|
|
[lldb-vscode] Correctly return source mapped breakpoints for setBreakpoints request
Summary:
When using source maps for a breakpoint, in order to find the actual source that breakpoint has resolved, we
need to use a query similar to what CommandObjectSource::DumpLinesInSymbolContexts does, which is the logic
used by the CLI to display the source line at a given breakpoint. That's necessary because from a breakpoint
location you only have an address, which points to the original source location, not the source mapped one.
in the setBreakpoints request handler, we haven't been doing such query and we were returning the original
source location, which was breaking the UX of VSCode, as many breakpoints were being set but not displayed
in the source file next to each line. Besides, clicking the source path of a breakpoint in the breakpoints
view in the debug tab was not working for this case, as VSCode was trying to open a non-existent file, thus
showing an error to the user.
Ideally, we should do the query mentioned above to find the actual source location to respond to the IDE,
however, that query is expensive and users can have an arbitrary number of breakpoints set. As a simpler fix,
the request sent by VSCode already contains the full source path, which exists because the user set it from
the IDE itself, so we can simply reuse it instead of querying from the SB API.
I wrote a test for this, and found out that I had to move SetSourceMapFromArguments after RunInitCommands in
lldb-vscode.cpp, because an init command used in all tests is `settings clear -all`, which would clear the
source map unless specified after initCommands. And in any case, I think it makes sense to have initCommands
run before anything the debugger would do.
Reviewers: clayborg, kusmour, labath, aadsm
Subscribers: lldb-commits
Tags: #lldb
Differential Revision: https://reviews.llvm.org/D76968
2020-03-28 10:31:14 +08:00
|
|
|
void AppendBreakpoint(lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints,
|
|
|
|
llvm::Optional<llvm::StringRef> request_path,
|
|
|
|
llvm::Optional<uint32_t> request_line) {
|
|
|
|
breakpoints.emplace_back(CreateBreakpoint(bp, request_path, request_line));
|
2018-08-17 01:59:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// "Event": {
|
|
|
|
// "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
|
|
|
|
// "type": "object",
|
|
|
|
// "description": "Server-initiated event.",
|
|
|
|
// "properties": {
|
|
|
|
// "type": {
|
|
|
|
// "type": "string",
|
|
|
|
// "enum": [ "event" ]
|
|
|
|
// },
|
|
|
|
// "event": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "Type of event."
|
|
|
|
// },
|
|
|
|
// "body": {
|
|
|
|
// "type": [ "array", "boolean", "integer", "null", "number" ,
|
|
|
|
// "object", "string" ],
|
|
|
|
// "description": "Event-specific information."
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// "required": [ "type", "event" ]
|
|
|
|
// }]
|
|
|
|
// },
|
|
|
|
// "ProtocolMessage": {
|
|
|
|
// "type": "object",
|
|
|
|
// "description": "Base class of requests, responses, and events.",
|
|
|
|
// "properties": {
|
|
|
|
// "seq": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "Sequence number."
|
|
|
|
// },
|
|
|
|
// "type": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "Message type.",
|
|
|
|
// "_enum": [ "request", "response", "event" ]
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// "required": [ "seq", "type" ]
|
|
|
|
// }
|
2018-08-17 02:24:59 +08:00
|
|
|
llvm::json::Object CreateEventObject(const llvm::StringRef event_name) {
|
2018-08-17 01:59:38 +08:00
|
|
|
llvm::json::Object event;
|
|
|
|
event.try_emplace("seq", 0);
|
|
|
|
event.try_emplace("type", "event");
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(event, "event", event_name);
|
2018-08-17 01:59:38 +08:00
|
|
|
return event;
|
|
|
|
}
|
|
|
|
|
|
|
|
// "ExceptionBreakpointsFilter": {
|
|
|
|
// "type": "object",
|
|
|
|
// "description": "An ExceptionBreakpointsFilter is shown in the UI as an
|
|
|
|
// option for configuring how exceptions are dealt with.",
|
|
|
|
// "properties": {
|
|
|
|
// "filter": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The internal ID of the filter. This value is passed
|
|
|
|
// to the setExceptionBreakpoints request."
|
|
|
|
// },
|
|
|
|
// "label": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The name of the filter. This will be shown in the UI."
|
|
|
|
// },
|
|
|
|
// "default": {
|
|
|
|
// "type": "boolean",
|
|
|
|
// "description": "Initial value of the filter. If not specified a value
|
|
|
|
// 'false' is assumed."
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// "required": [ "filter", "label" ]
|
|
|
|
// }
|
|
|
|
llvm::json::Value
|
|
|
|
CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) {
|
|
|
|
llvm::json::Object object;
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, "filter", bp.filter);
|
|
|
|
EmplaceSafeString(object, "label", bp.label);
|
2018-08-17 01:59:38 +08:00
|
|
|
object.try_emplace("default", bp.default_value);
|
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
}
|
|
|
|
|
|
|
|
// "Source": {
|
|
|
|
// "type": "object",
|
|
|
|
// "description": "A Source is a descriptor for source code. It is returned
|
|
|
|
// from the debug adapter as part of a StackFrame and it is
|
|
|
|
// used by clients when specifying breakpoints.",
|
|
|
|
// "properties": {
|
|
|
|
// "name": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The short name of the source. Every source returned
|
|
|
|
// from the debug adapter has a name. When sending a
|
|
|
|
// source to the debug adapter this name is optional."
|
|
|
|
// },
|
|
|
|
// "path": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The path of the source to be shown in the UI. It is
|
|
|
|
// only used to locate and load the content of the
|
|
|
|
// source if no sourceReference is specified (or its
|
|
|
|
// value is 0)."
|
|
|
|
// },
|
|
|
|
// "sourceReference": {
|
|
|
|
// "type": "number",
|
|
|
|
// "description": "If sourceReference > 0 the contents of the source must
|
|
|
|
// be retrieved through the SourceRequest (even if a path
|
|
|
|
// is specified). A sourceReference is only valid for a
|
|
|
|
// session, so it must not be used to persist a source."
|
|
|
|
// },
|
|
|
|
// "presentationHint": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "An optional hint for how to present the source in the
|
|
|
|
// UI. A value of 'deemphasize' can be used to indicate
|
|
|
|
// that the source is not available or that it is
|
|
|
|
// skipped on stepping.",
|
|
|
|
// "enum": [ "normal", "emphasize", "deemphasize" ]
|
|
|
|
// },
|
|
|
|
// "origin": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The (optional) origin of this source: possible values
|
|
|
|
// 'internal module', 'inlined content from source map',
|
|
|
|
// etc."
|
|
|
|
// },
|
|
|
|
// "sources": {
|
|
|
|
// "type": "array",
|
|
|
|
// "items": {
|
|
|
|
// "$ref": "#/definitions/Source"
|
|
|
|
// },
|
|
|
|
// "description": "An optional list of sources that are related to this
|
|
|
|
// source. These may be the source that generated this
|
|
|
|
// source."
|
|
|
|
// },
|
|
|
|
// "adapterData": {
|
|
|
|
// "type":["array","boolean","integer","null","number","object","string"],
|
|
|
|
// "description": "Optional data that a debug adapter might want to loop
|
|
|
|
// through the client. The client should leave the data
|
|
|
|
// intact and persist it across sessions. The client
|
|
|
|
// should not interpret the data."
|
|
|
|
// },
|
|
|
|
// "checksums": {
|
|
|
|
// "type": "array",
|
|
|
|
// "items": {
|
|
|
|
// "$ref": "#/definitions/Checksum"
|
|
|
|
// },
|
|
|
|
// "description": "The checksums associated with this file."
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry) {
|
|
|
|
llvm::json::Object object;
|
|
|
|
lldb::SBFileSpec file = line_entry.GetFileSpec();
|
|
|
|
if (file.IsValid()) {
|
|
|
|
const char *name = file.GetFilename();
|
|
|
|
if (name)
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, "name", name);
|
2018-08-17 01:59:38 +08:00
|
|
|
char path[PATH_MAX] = "";
|
|
|
|
file.GetPath(path, sizeof(path));
|
|
|
|
if (path[0]) {
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, "path", std::string(path));
|
2018-08-17 01:59:38 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
}
|
|
|
|
|
[lldb-vscode] Correctly return source mapped breakpoints for setBreakpoints request
Summary:
When using source maps for a breakpoint, in order to find the actual source that breakpoint has resolved, we
need to use a query similar to what CommandObjectSource::DumpLinesInSymbolContexts does, which is the logic
used by the CLI to display the source line at a given breakpoint. That's necessary because from a breakpoint
location you only have an address, which points to the original source location, not the source mapped one.
in the setBreakpoints request handler, we haven't been doing such query and we were returning the original
source location, which was breaking the UX of VSCode, as many breakpoints were being set but not displayed
in the source file next to each line. Besides, clicking the source path of a breakpoint in the breakpoints
view in the debug tab was not working for this case, as VSCode was trying to open a non-existent file, thus
showing an error to the user.
Ideally, we should do the query mentioned above to find the actual source location to respond to the IDE,
however, that query is expensive and users can have an arbitrary number of breakpoints set. As a simpler fix,
the request sent by VSCode already contains the full source path, which exists because the user set it from
the IDE itself, so we can simply reuse it instead of querying from the SB API.
I wrote a test for this, and found out that I had to move SetSourceMapFromArguments after RunInitCommands in
lldb-vscode.cpp, because an init command used in all tests is `settings clear -all`, which would clear the
source map unless specified after initCommands. And in any case, I think it makes sense to have initCommands
run before anything the debugger would do.
Reviewers: clayborg, kusmour, labath, aadsm
Subscribers: lldb-commits
Tags: #lldb
Differential Revision: https://reviews.llvm.org/D76968
2020-03-28 10:31:14 +08:00
|
|
|
llvm::json::Value CreateSource(llvm::StringRef source_path) {
|
|
|
|
llvm::json::Object source;
|
|
|
|
llvm::StringRef name = llvm::sys::path::filename(source_path);
|
|
|
|
EmplaceSafeString(source, "name", name);
|
|
|
|
EmplaceSafeString(source, "path", source_path);
|
|
|
|
return llvm::json::Value(std::move(source));
|
|
|
|
}
|
|
|
|
|
2018-08-17 01:59:38 +08:00
|
|
|
llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) {
|
|
|
|
disasm_line = 0;
|
|
|
|
auto line_entry = frame.GetLineEntry();
|
|
|
|
if (line_entry.GetFileSpec().IsValid())
|
|
|
|
return CreateSource(line_entry);
|
|
|
|
|
|
|
|
llvm::json::Object object;
|
|
|
|
const auto pc = frame.GetPC();
|
|
|
|
|
|
|
|
lldb::SBInstructionList insts;
|
|
|
|
lldb::SBFunction function = frame.GetFunction();
|
|
|
|
lldb::addr_t low_pc = LLDB_INVALID_ADDRESS;
|
|
|
|
if (function.IsValid()) {
|
|
|
|
low_pc = function.GetStartAddress().GetLoadAddress(g_vsc.target);
|
|
|
|
auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc);
|
|
|
|
if (addr_srcref != g_vsc.addr_to_source_ref.end()) {
|
|
|
|
// We have this disassembly cached already, return the existing
|
|
|
|
// sourceReference
|
|
|
|
object.try_emplace("sourceReference", addr_srcref->second);
|
|
|
|
disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc);
|
|
|
|
} else {
|
|
|
|
insts = function.GetInstructions(g_vsc.target);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
lldb::SBSymbol symbol = frame.GetSymbol();
|
|
|
|
if (symbol.IsValid()) {
|
|
|
|
low_pc = symbol.GetStartAddress().GetLoadAddress(g_vsc.target);
|
|
|
|
auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc);
|
|
|
|
if (addr_srcref != g_vsc.addr_to_source_ref.end()) {
|
|
|
|
// We have this disassembly cached already, return the existing
|
|
|
|
// sourceReference
|
|
|
|
object.try_emplace("sourceReference", addr_srcref->second);
|
|
|
|
disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc);
|
|
|
|
} else {
|
|
|
|
insts = symbol.GetInstructions(g_vsc.target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const auto num_insts = insts.GetSize();
|
|
|
|
if (low_pc != LLDB_INVALID_ADDRESS && num_insts > 0) {
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, "name", frame.GetFunctionName());
|
2018-08-17 01:59:38 +08:00
|
|
|
SourceReference source;
|
|
|
|
llvm::raw_string_ostream src_strm(source.content);
|
|
|
|
std::string line;
|
|
|
|
for (size_t i = 0; i < num_insts; ++i) {
|
|
|
|
lldb::SBInstruction inst = insts.GetInstructionAtIndex(i);
|
|
|
|
const auto inst_addr = inst.GetAddress().GetLoadAddress(g_vsc.target);
|
|
|
|
const char *m = inst.GetMnemonic(g_vsc.target);
|
|
|
|
const char *o = inst.GetOperands(g_vsc.target);
|
|
|
|
const char *c = inst.GetComment(g_vsc.target);
|
|
|
|
if (pc == inst_addr)
|
|
|
|
disasm_line = i + 1;
|
|
|
|
const auto inst_offset = inst_addr - low_pc;
|
|
|
|
int spaces = 0;
|
|
|
|
if (inst_offset < 10)
|
|
|
|
spaces = 3;
|
|
|
|
else if (inst_offset < 100)
|
|
|
|
spaces = 2;
|
|
|
|
else if (inst_offset < 1000)
|
|
|
|
spaces = 1;
|
|
|
|
line.clear();
|
|
|
|
llvm::raw_string_ostream line_strm(line);
|
|
|
|
line_strm << llvm::formatv("{0:X+}: <{1}> {2} {3,12} {4}", inst_addr,
|
2018-11-16 03:49:57 +08:00
|
|
|
inst_offset, llvm::fmt_repeat(' ', spaces), m,
|
|
|
|
o);
|
2018-11-06 06:55:30 +08:00
|
|
|
|
|
|
|
// If there is a comment append it starting at column 60 or after one
|
|
|
|
// space past the last char
|
|
|
|
const uint32_t comment_row = std::max(line_strm.str().size(), (size_t)60);
|
2018-08-17 01:59:38 +08:00
|
|
|
if (c && c[0]) {
|
|
|
|
if (line.size() < comment_row)
|
|
|
|
line_strm.indent(comment_row - line_strm.str().size());
|
|
|
|
line_strm << " # " << c;
|
|
|
|
}
|
|
|
|
src_strm << line_strm.str() << "\n";
|
|
|
|
source.addr_to_line[inst_addr] = i + 1;
|
|
|
|
}
|
|
|
|
// Flush the source stream
|
|
|
|
src_strm.str();
|
|
|
|
auto sourceReference = VSCode::GetNextSourceReference();
|
|
|
|
g_vsc.source_map[sourceReference] = std::move(source);
|
|
|
|
g_vsc.addr_to_source_ref[low_pc] = sourceReference;
|
|
|
|
object.try_emplace("sourceReference", sourceReference);
|
|
|
|
}
|
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
}
|
|
|
|
|
|
|
|
// "StackFrame": {
|
|
|
|
// "type": "object",
|
|
|
|
// "description": "A Stackframe contains the source location.",
|
|
|
|
// "properties": {
|
|
|
|
// "id": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "An identifier for the stack frame. It must be unique
|
|
|
|
// across all threads. This id can be used to retrieve
|
|
|
|
// the scopes of the frame with the 'scopesRequest' or
|
|
|
|
// to restart the execution of a stackframe."
|
|
|
|
// },
|
|
|
|
// "name": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The name of the stack frame, typically a method name."
|
|
|
|
// },
|
|
|
|
// "source": {
|
|
|
|
// "$ref": "#/definitions/Source",
|
|
|
|
// "description": "The optional source of the frame."
|
|
|
|
// },
|
|
|
|
// "line": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "The line within the file of the frame. If source is
|
|
|
|
// null or doesn't exist, line is 0 and must be ignored."
|
|
|
|
// },
|
|
|
|
// "column": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "The column within the line. If source is null or
|
|
|
|
// doesn't exist, column is 0 and must be ignored."
|
|
|
|
// },
|
|
|
|
// "endLine": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "An optional end line of the range covered by the
|
|
|
|
// stack frame."
|
|
|
|
// },
|
|
|
|
// "endColumn": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "An optional end column of the range covered by the
|
|
|
|
// stack frame."
|
|
|
|
// },
|
|
|
|
// "moduleId": {
|
|
|
|
// "type": ["integer", "string"],
|
|
|
|
// "description": "The module associated with this frame, if any."
|
|
|
|
// },
|
|
|
|
// "presentationHint": {
|
|
|
|
// "type": "string",
|
|
|
|
// "enum": [ "normal", "label", "subtle" ],
|
|
|
|
// "description": "An optional hint for how to present this frame in
|
|
|
|
// the UI. A value of 'label' can be used to indicate
|
|
|
|
// that the frame is an artificial frame that is used
|
|
|
|
// as a visual label or separator. A value of 'subtle'
|
|
|
|
// can be used to change the appearance of a frame in
|
|
|
|
// a 'subtle' way."
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// "required": [ "id", "name", "line", "column" ]
|
|
|
|
// }
|
|
|
|
llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) {
|
|
|
|
llvm::json::Object object;
|
|
|
|
int64_t frame_id = MakeVSCodeFrameID(frame);
|
|
|
|
object.try_emplace("id", frame_id);
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, "name", frame.GetFunctionName());
|
2018-08-17 01:59:38 +08:00
|
|
|
int64_t disasm_line = 0;
|
|
|
|
object.try_emplace("source", CreateSource(frame, disasm_line));
|
|
|
|
|
|
|
|
auto line_entry = frame.GetLineEntry();
|
|
|
|
if (disasm_line > 0) {
|
|
|
|
object.try_emplace("line", disasm_line);
|
|
|
|
} else {
|
|
|
|
auto line = line_entry.GetLine();
|
|
|
|
if (line == UINT32_MAX)
|
|
|
|
line = 0;
|
|
|
|
object.try_emplace("line", line);
|
|
|
|
}
|
|
|
|
object.try_emplace("column", line_entry.GetColumn());
|
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
}
|
|
|
|
|
|
|
|
// "Thread": {
|
|
|
|
// "type": "object",
|
|
|
|
// "description": "A Thread",
|
|
|
|
// "properties": {
|
|
|
|
// "id": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "Unique identifier for the thread."
|
|
|
|
// },
|
|
|
|
// "name": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "A name of the thread."
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// "required": [ "id", "name" ]
|
|
|
|
// }
|
|
|
|
llvm::json::Value CreateThread(lldb::SBThread &thread) {
|
|
|
|
llvm::json::Object object;
|
|
|
|
object.try_emplace("id", (int64_t)thread.GetThreadID());
|
|
|
|
char thread_str[64];
|
|
|
|
snprintf(thread_str, sizeof(thread_str), "Thread #%u", thread.GetIndexID());
|
|
|
|
const char *name = thread.GetName();
|
|
|
|
if (name) {
|
|
|
|
std::string thread_with_name(thread_str);
|
|
|
|
thread_with_name += ' ';
|
|
|
|
thread_with_name += name;
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, "name", thread_with_name);
|
2018-08-17 01:59:38 +08:00
|
|
|
} else {
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, "name", std::string(thread_str));
|
2018-08-17 01:59:38 +08:00
|
|
|
}
|
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
}
|
|
|
|
|
|
|
|
// "StoppedEvent": {
|
|
|
|
// "allOf": [ { "$ref": "#/definitions/Event" }, {
|
|
|
|
// "type": "object",
|
|
|
|
// "description": "Event message for 'stopped' event type. The event
|
|
|
|
// indicates that the execution of the debuggee has stopped
|
|
|
|
// due to some condition. This can be caused by a break
|
|
|
|
// point previously set, a stepping action has completed,
|
|
|
|
// by executing a debugger statement etc.",
|
|
|
|
// "properties": {
|
|
|
|
// "event": {
|
|
|
|
// "type": "string",
|
|
|
|
// "enum": [ "stopped" ]
|
|
|
|
// },
|
|
|
|
// "body": {
|
|
|
|
// "type": "object",
|
|
|
|
// "properties": {
|
|
|
|
// "reason": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The reason for the event. For backward
|
|
|
|
// compatibility this string is shown in the UI if
|
|
|
|
// the 'description' attribute is missing (but it
|
|
|
|
// must not be translated).",
|
|
|
|
// "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ]
|
|
|
|
// },
|
|
|
|
// "description": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The full reason for the event, e.g. 'Paused
|
|
|
|
// on exception'. This string is shown in the UI
|
|
|
|
// as is."
|
|
|
|
// },
|
|
|
|
// "threadId": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "The thread which was stopped."
|
|
|
|
// },
|
|
|
|
// "text": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "Additional information. E.g. if reason is
|
|
|
|
// 'exception', text contains the exception name.
|
|
|
|
// This string is shown in the UI."
|
|
|
|
// },
|
|
|
|
// "allThreadsStopped": {
|
|
|
|
// "type": "boolean",
|
|
|
|
// "description": "If allThreadsStopped is true, a debug adapter
|
|
|
|
// can announce that all threads have stopped.
|
|
|
|
// The client should use this information to
|
|
|
|
// enable that all threads can be expanded to
|
|
|
|
// access their stacktraces. If the attribute
|
|
|
|
// is missing or false, only the thread with the
|
|
|
|
// given threadId can be expanded."
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// "required": [ "reason" ]
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// "required": [ "event", "body" ]
|
|
|
|
// }]
|
|
|
|
// }
|
|
|
|
llvm::json::Value CreateThreadStopped(lldb::SBThread &thread,
|
|
|
|
uint32_t stop_id) {
|
2018-08-17 02:24:59 +08:00
|
|
|
llvm::json::Object event(CreateEventObject("stopped"));
|
2018-08-17 01:59:38 +08:00
|
|
|
llvm::json::Object body;
|
|
|
|
switch (thread.GetStopReason()) {
|
|
|
|
case lldb::eStopReasonTrace:
|
|
|
|
case lldb::eStopReasonPlanComplete:
|
|
|
|
body.try_emplace("reason", "step");
|
|
|
|
break;
|
|
|
|
case lldb::eStopReasonBreakpoint: {
|
|
|
|
ExceptionBreakpoint *exc_bp = g_vsc.GetExceptionBPFromStopReason(thread);
|
|
|
|
if (exc_bp) {
|
|
|
|
body.try_emplace("reason", "exception");
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(body, "description", exc_bp->label);
|
2018-08-17 01:59:38 +08:00
|
|
|
} else {
|
|
|
|
body.try_emplace("reason", "breakpoint");
|
2020-01-30 06:11:40 +08:00
|
|
|
char desc_str[64];
|
|
|
|
uint64_t bp_id = thread.GetStopReasonDataAtIndex(0);
|
|
|
|
uint64_t bp_loc_id = thread.GetStopReasonDataAtIndex(1);
|
|
|
|
snprintf(desc_str, sizeof(desc_str), "breakpoint %" PRIu64 ".%" PRIu64,
|
|
|
|
bp_id, bp_loc_id);
|
|
|
|
EmplaceSafeString(body, "description", desc_str);
|
2018-08-17 01:59:38 +08:00
|
|
|
}
|
|
|
|
} break;
|
|
|
|
case lldb::eStopReasonWatchpoint:
|
|
|
|
case lldb::eStopReasonInstrumentation:
|
|
|
|
body.try_emplace("reason", "breakpoint");
|
|
|
|
break;
|
2020-11-10 05:36:26 +08:00
|
|
|
case lldb::eStopReasonProcessorTrace:
|
|
|
|
body.try_emplace("reason", "processor trace");
|
2018-08-17 01:59:38 +08:00
|
|
|
break;
|
2020-11-10 05:36:26 +08:00
|
|
|
case lldb::eStopReasonSignal:
|
2018-08-17 01:59:38 +08:00
|
|
|
case lldb::eStopReasonException:
|
|
|
|
body.try_emplace("reason", "exception");
|
|
|
|
break;
|
|
|
|
case lldb::eStopReasonExec:
|
|
|
|
body.try_emplace("reason", "entry");
|
|
|
|
break;
|
2021-03-29 23:05:30 +08:00
|
|
|
case lldb::eStopReasonFork:
|
|
|
|
body.try_emplace("reason", "fork");
|
|
|
|
break;
|
|
|
|
case lldb::eStopReasonVFork:
|
|
|
|
body.try_emplace("reason", "vfork");
|
|
|
|
break;
|
|
|
|
case lldb::eStopReasonVForkDone:
|
|
|
|
body.try_emplace("reason", "vforkdone");
|
|
|
|
break;
|
2018-08-17 01:59:38 +08:00
|
|
|
case lldb::eStopReasonThreadExiting:
|
|
|
|
case lldb::eStopReasonInvalid:
|
|
|
|
case lldb::eStopReasonNone:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (stop_id == 0)
|
|
|
|
body.try_emplace("reason", "entry");
|
|
|
|
const lldb::tid_t tid = thread.GetThreadID();
|
|
|
|
body.try_emplace("threadId", (int64_t)tid);
|
|
|
|
// If no description has been set, then set it to the default thread stopped
|
|
|
|
// description. If we have breakpoints that get hit and shouldn't be reported
|
|
|
|
// as breakpoints, then they will set the description above.
|
|
|
|
if (ObjectContainsKey(body, "description")) {
|
|
|
|
char description[1024];
|
|
|
|
if (thread.GetStopDescription(description, sizeof(description))) {
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(body, "description", std::string(description));
|
2018-08-17 01:59:38 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (tid == g_vsc.focus_tid) {
|
|
|
|
body.try_emplace("threadCausedFocus", true);
|
|
|
|
}
|
|
|
|
body.try_emplace("preserveFocusHint", tid != g_vsc.focus_tid);
|
|
|
|
body.try_emplace("allThreadsStopped", true);
|
|
|
|
event.try_emplace("body", std::move(body));
|
|
|
|
return llvm::json::Value(std::move(event));
|
|
|
|
}
|
|
|
|
|
2021-04-28 07:02:38 +08:00
|
|
|
const char *GetNonNullVariableName(lldb::SBValue v) {
|
|
|
|
const char *name = v.GetName();
|
|
|
|
return name ? name : "<null>";
|
|
|
|
}
|
|
|
|
|
2021-04-22 06:06:44 +08:00
|
|
|
std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v,
|
|
|
|
bool is_name_duplicated) {
|
|
|
|
lldb::SBStream name_builder;
|
2021-04-28 07:02:38 +08:00
|
|
|
name_builder.Print(GetNonNullVariableName(v));
|
2021-04-22 06:06:44 +08:00
|
|
|
if (is_name_duplicated) {
|
|
|
|
lldb::SBDeclaration declaration = v.GetDeclaration();
|
2021-04-28 07:02:38 +08:00
|
|
|
const char *file_name = declaration.GetFileSpec().GetFilename();
|
2021-04-22 06:06:44 +08:00
|
|
|
const uint32_t line = declaration.GetLine();
|
|
|
|
|
2021-04-28 07:02:38 +08:00
|
|
|
if (file_name != nullptr && line > 0)
|
|
|
|
name_builder.Printf(" @ %s:%u", file_name, line);
|
|
|
|
else if (const char *location = v.GetLocation())
|
|
|
|
name_builder.Printf(" @ %s", location);
|
2021-04-22 06:06:44 +08:00
|
|
|
}
|
|
|
|
return name_builder.GetData();
|
|
|
|
}
|
|
|
|
|
2018-08-17 01:59:38 +08:00
|
|
|
// "Variable": {
|
|
|
|
// "type": "object",
|
|
|
|
// "description": "A Variable is a name/value pair. Optionally a variable
|
|
|
|
// can have a 'type' that is shown if space permits or when
|
|
|
|
// hovering over the variable's name. An optional 'kind' is
|
|
|
|
// used to render additional properties of the variable,
|
|
|
|
// e.g. different icons can be used to indicate that a
|
|
|
|
// variable is public or private. If the value is
|
|
|
|
// structured (has children), a handle is provided to
|
|
|
|
// retrieve the children with the VariablesRequest. If
|
|
|
|
// the number of named or indexed children is large, the
|
|
|
|
// numbers should be returned via the optional
|
|
|
|
// 'namedVariables' and 'indexedVariables' attributes. The
|
|
|
|
// client can use this optional information to present the
|
|
|
|
// children in a paged UI and fetch them in chunks.",
|
|
|
|
// "properties": {
|
|
|
|
// "name": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The variable's name."
|
|
|
|
// },
|
|
|
|
// "value": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The variable's value. This can be a multi-line text,
|
|
|
|
// e.g. for a function the body of a function."
|
|
|
|
// },
|
|
|
|
// "type": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "The type of the variable's value. Typically shown in
|
|
|
|
// the UI when hovering over the value."
|
|
|
|
// },
|
|
|
|
// "presentationHint": {
|
|
|
|
// "$ref": "#/definitions/VariablePresentationHint",
|
|
|
|
// "description": "Properties of a variable that can be used to determine
|
|
|
|
// how to render the variable in the UI."
|
|
|
|
// },
|
|
|
|
// "evaluateName": {
|
|
|
|
// "type": "string",
|
|
|
|
// "description": "Optional evaluatable name of this variable which can
|
|
|
|
// be passed to the 'EvaluateRequest' to fetch the
|
|
|
|
// variable's value."
|
|
|
|
// },
|
|
|
|
// "variablesReference": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "If variablesReference is > 0, the variable is
|
|
|
|
// structured and its children can be retrieved by
|
|
|
|
// passing variablesReference to the VariablesRequest."
|
|
|
|
// },
|
|
|
|
// "namedVariables": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "The number of named child variables. The client can
|
|
|
|
// use this optional information to present the children
|
|
|
|
// in a paged UI and fetch them in chunks."
|
|
|
|
// },
|
|
|
|
// "indexedVariables": {
|
|
|
|
// "type": "integer",
|
|
|
|
// "description": "The number of indexed child variables. The client
|
|
|
|
// can use this optional information to present the
|
|
|
|
// children in a paged UI and fetch them in chunks."
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// "required": [ "name", "value", "variablesReference" ]
|
|
|
|
// }
|
|
|
|
llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
|
2021-04-22 06:06:44 +08:00
|
|
|
int64_t varID, bool format_hex,
|
|
|
|
bool is_name_duplicated) {
|
2018-08-17 01:59:38 +08:00
|
|
|
llvm::json::Object object;
|
2021-04-22 06:06:44 +08:00
|
|
|
EmplaceSafeString(object, "name",
|
|
|
|
CreateUniqueVariableNameForDisplay(v, is_name_duplicated));
|
|
|
|
|
2018-08-17 01:59:38 +08:00
|
|
|
if (format_hex)
|
|
|
|
v.SetFormat(lldb::eFormatHex);
|
|
|
|
SetValueForKey(v, object, "value");
|
|
|
|
auto type_cstr = v.GetType().GetDisplayTypeName();
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, "type", type_cstr ? type_cstr : NO_TYPENAME);
|
2018-08-17 01:59:38 +08:00
|
|
|
if (varID != INT64_MAX)
|
|
|
|
object.try_emplace("id", varID);
|
|
|
|
if (v.MightHaveChildren())
|
|
|
|
object.try_emplace("variablesReference", variablesReference);
|
|
|
|
else
|
|
|
|
object.try_emplace("variablesReference", (int64_t)0);
|
|
|
|
lldb::SBStream evaluateStream;
|
|
|
|
v.GetExpressionPath(evaluateStream);
|
|
|
|
const char *evaluateName = evaluateStream.GetData();
|
|
|
|
if (evaluateName && evaluateName[0])
|
2018-11-16 03:49:57 +08:00
|
|
|
EmplaceSafeString(object, "evaluateName", std::string(evaluateName));
|
2018-08-17 01:59:38 +08:00
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
}
|
|
|
|
|
2020-07-14 03:10:49 +08:00
|
|
|
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) {
|
|
|
|
llvm::json::Object object;
|
|
|
|
char unit_path_arr[PATH_MAX];
|
|
|
|
unit.GetFileSpec().GetPath(unit_path_arr, sizeof(unit_path_arr));
|
|
|
|
std::string unit_path(unit_path_arr);
|
|
|
|
object.try_emplace("compileUnitPath", unit_path);
|
|
|
|
return llvm::json::Value(std::move(object));
|
|
|
|
}
|
|
|
|
|
2020-09-02 09:52:14 +08:00
|
|
|
/// See
|
|
|
|
/// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal
|
|
|
|
llvm::json::Object
|
[vscode] Improve runInTerminal and support linux
Depends on D93874.
runInTerminal was using --wait-for, but it was some problems because it uses process polling looking for a single instance of the debuggee:
- it gets to know of the target late, which renders breakpoints in the main function almost impossible
- polling might fail if there are already other processes with the same name
- polling might also fail on some linux machine, as it's implemented with the ps command, and the ps command's args and output are not standard everywhere
As a better way to implement this so that it works well on Darwin and Linux, I'm using now the following process:
- lldb-vscode notices the runInTerminal, so it spawns lldb-vscode with a special flag --launch-target <target>. This flags tells lldb-vscode to wait to be attached and then it execs the target program. I'm using lldb-vscode itself to do this, because it makes finding the launcher program easier. Also no CMAKE INSTALL scripts are needed.
- Besides this, the debugger creates a temporary FIFO file where the launcher program will write its pid to. That way the debugger will be sure of which program to attach.
- Once attach happend, the debugger creates a second temporary file to notify the launcher program that it has been attached, so that it can then exec. I'm using this instead of using a signal or a similar mechanism because I don't want the launcher program to wait indefinitely to be attached in case the debugger crashed. That would pollute the process list with a lot of hanging processes. Instead, I'm setting a 20 seconds timeout (that's an overkill) and the launcher program seeks in intervals the second tepmorary file.
Some notes:
- I preferred not to use sockets because it requires a lot of code and I only need a pid. It would also require a lot of code when windows support is implemented.
- I didn't add Windows support, as I don't have a windows machine, but adding support for it should be easy, as the FIFO file can be implemented with a named pipe, which is standard on Windows and works pretty much the same way.
The existing test which didn't pass on Linux, now passes.
Differential Revision: https://reviews.llvm.org/D93951
2020-12-29 04:00:47 +08:00
|
|
|
CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
|
|
|
|
llvm::StringRef debug_adaptor_path,
|
|
|
|
llvm::StringRef comm_file) {
|
2020-09-02 09:52:14 +08:00
|
|
|
llvm::json::Object reverse_request;
|
|
|
|
reverse_request.try_emplace("type", "request");
|
|
|
|
reverse_request.try_emplace("command", "runInTerminal");
|
|
|
|
|
|
|
|
llvm::json::Object run_in_terminal_args;
|
|
|
|
// This indicates the IDE to open an embedded terminal, instead of opening the
|
|
|
|
// terminal in a new window.
|
|
|
|
run_in_terminal_args.try_emplace("kind", "integrated");
|
|
|
|
|
|
|
|
auto launch_request_arguments = launch_request.getObject("arguments");
|
|
|
|
// The program path must be the first entry in the "args" field
|
[vscode] Improve runInTerminal and support linux
Depends on D93874.
runInTerminal was using --wait-for, but it was some problems because it uses process polling looking for a single instance of the debuggee:
- it gets to know of the target late, which renders breakpoints in the main function almost impossible
- polling might fail if there are already other processes with the same name
- polling might also fail on some linux machine, as it's implemented with the ps command, and the ps command's args and output are not standard everywhere
As a better way to implement this so that it works well on Darwin and Linux, I'm using now the following process:
- lldb-vscode notices the runInTerminal, so it spawns lldb-vscode with a special flag --launch-target <target>. This flags tells lldb-vscode to wait to be attached and then it execs the target program. I'm using lldb-vscode itself to do this, because it makes finding the launcher program easier. Also no CMAKE INSTALL scripts are needed.
- Besides this, the debugger creates a temporary FIFO file where the launcher program will write its pid to. That way the debugger will be sure of which program to attach.
- Once attach happend, the debugger creates a second temporary file to notify the launcher program that it has been attached, so that it can then exec. I'm using this instead of using a signal or a similar mechanism because I don't want the launcher program to wait indefinitely to be attached in case the debugger crashed. That would pollute the process list with a lot of hanging processes. Instead, I'm setting a 20 seconds timeout (that's an overkill) and the launcher program seeks in intervals the second tepmorary file.
Some notes:
- I preferred not to use sockets because it requires a lot of code and I only need a pid. It would also require a lot of code when windows support is implemented.
- I didn't add Windows support, as I don't have a windows machine, but adding support for it should be easy, as the FIFO file can be implemented with a named pipe, which is standard on Windows and works pretty much the same way.
The existing test which didn't pass on Linux, now passes.
Differential Revision: https://reviews.llvm.org/D93951
2020-12-29 04:00:47 +08:00
|
|
|
std::vector<std::string> args = {
|
|
|
|
debug_adaptor_path.str(), "--comm-file", comm_file.str(),
|
|
|
|
"--launch-target", GetString(launch_request_arguments, "program").str()};
|
|
|
|
std::vector<std::string> target_args =
|
|
|
|
GetStrings(launch_request_arguments, "args");
|
|
|
|
args.insert(args.end(), target_args.begin(), target_args.end());
|
2020-09-02 09:52:14 +08:00
|
|
|
run_in_terminal_args.try_emplace("args", args);
|
|
|
|
|
|
|
|
const auto cwd = GetString(launch_request_arguments, "cwd");
|
|
|
|
if (!cwd.empty())
|
|
|
|
run_in_terminal_args.try_emplace("cwd", cwd);
|
|
|
|
|
|
|
|
// We need to convert the input list of environments variables into a
|
|
|
|
// dictionary
|
|
|
|
std::vector<std::string> envs = GetStrings(launch_request_arguments, "env");
|
|
|
|
llvm::json::Object environment;
|
|
|
|
for (const std::string &env : envs) {
|
2020-12-17 02:53:18 +08:00
|
|
|
size_t index = env.find('=');
|
2020-09-02 09:52:14 +08:00
|
|
|
environment.try_emplace(env.substr(0, index), env.substr(index + 1));
|
|
|
|
}
|
|
|
|
run_in_terminal_args.try_emplace("env",
|
|
|
|
llvm::json::Value(std::move(environment)));
|
|
|
|
|
|
|
|
reverse_request.try_emplace(
|
|
|
|
"arguments", llvm::json::Value(std::move(run_in_terminal_args)));
|
|
|
|
return reverse_request;
|
|
|
|
}
|
|
|
|
|
[vscode] Improve runInTerminal and support linux
Depends on D93874.
runInTerminal was using --wait-for, but it was some problems because it uses process polling looking for a single instance of the debuggee:
- it gets to know of the target late, which renders breakpoints in the main function almost impossible
- polling might fail if there are already other processes with the same name
- polling might also fail on some linux machine, as it's implemented with the ps command, and the ps command's args and output are not standard everywhere
As a better way to implement this so that it works well on Darwin and Linux, I'm using now the following process:
- lldb-vscode notices the runInTerminal, so it spawns lldb-vscode with a special flag --launch-target <target>. This flags tells lldb-vscode to wait to be attached and then it execs the target program. I'm using lldb-vscode itself to do this, because it makes finding the launcher program easier. Also no CMAKE INSTALL scripts are needed.
- Besides this, the debugger creates a temporary FIFO file where the launcher program will write its pid to. That way the debugger will be sure of which program to attach.
- Once attach happend, the debugger creates a second temporary file to notify the launcher program that it has been attached, so that it can then exec. I'm using this instead of using a signal or a similar mechanism because I don't want the launcher program to wait indefinitely to be attached in case the debugger crashed. That would pollute the process list with a lot of hanging processes. Instead, I'm setting a 20 seconds timeout (that's an overkill) and the launcher program seeks in intervals the second tepmorary file.
Some notes:
- I preferred not to use sockets because it requires a lot of code and I only need a pid. It would also require a lot of code when windows support is implemented.
- I didn't add Windows support, as I don't have a windows machine, but adding support for it should be easy, as the FIFO file can be implemented with a named pipe, which is standard on Windows and works pretty much the same way.
The existing test which didn't pass on Linux, now passes.
Differential Revision: https://reviews.llvm.org/D93951
2020-12-29 04:00:47 +08:00
|
|
|
std::string JSONToString(const llvm::json::Value &json) {
|
|
|
|
std::string data;
|
|
|
|
llvm::raw_string_ostream os(data);
|
|
|
|
os << json;
|
|
|
|
os.flush();
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2018-08-17 01:59:38 +08:00
|
|
|
} // namespace lldb_vscode
|