aelitashen updated this revision to Diff 285725. aelitashen added a comment.
Add a attacher thread to avoid race condition Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D84974/new/ https://reviews.llvm.org/D84974 Files: lldb/test/API/tools/lldb-vscode/runInTerminal/Makefile lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py lldb/test/API/tools/lldb-vscode/runInTerminal/main.cpp lldb/tools/lldb-vscode/VSCode.cpp lldb/tools/lldb-vscode/VSCode.h lldb/tools/lldb-vscode/lldb-vscode.cpp
Index: lldb/tools/lldb-vscode/lldb-vscode.cpp =================================================================== --- lldb/tools/lldb-vscode/lldb-vscode.cpp +++ lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -343,7 +343,7 @@ char buffer[1024]; size_t count; while ((count = process.GetSTDOUT(buffer, sizeof(buffer))) > 0) - g_vsc.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count)); + g_vsc.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count)); while ((count = process.GetSTDERR(buffer, sizeof(buffer))) > 0) g_vsc.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count)); } @@ -448,10 +448,10 @@ if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded) { body.try_emplace("reason", "new"); } else if (event_mask & - lldb::SBTarget::eBroadcastBitModulesUnloaded) { + lldb::SBTarget::eBroadcastBitModulesUnloaded) { body.try_emplace("reason", "removed"); } else if (event_mask & - lldb::SBTarget::eBroadcastBitSymbolsLoaded) { + lldb::SBTarget::eBroadcastBitSymbolsLoaded) { body.try_emplace("reason", "changed"); } body.try_emplace("module", module_value); @@ -873,7 +873,9 @@ // "CompletionsRequest": { // "allOf": [ { "$ref": "#/definitions/Request" }, { // "type": "object", -// "description": "Returns a list of possible completions for a given caret position and text.\nThe CompletionsRequest may only be called if the 'supportsCompletionsRequest' capability exists and is true.", +// "description": "Returns a list of possible completions for a given caret +// position and text.\nThe CompletionsRequest may only be called if the +// 'supportsCompletionsRequest' capability exists and is true.", // "properties": { // "command": { // "type": "string", @@ -892,19 +894,23 @@ // "properties": { // "frameId": { // "type": "integer", -// "description": "Returns completions in the scope of this stack frame. If not specified, the completions are returned for the global scope." +// "description": "Returns completions in the scope of this stack frame. +// If not specified, the completions are returned for the global scope." // }, // "text": { // "type": "string", -// "description": "One or more source lines. Typically this is the text a user has typed into the debug console before he asked for completion." +// "description": "One or more source lines. Typically this is the text a +// user has typed into the debug console before he asked for completion." // }, // "column": { // "type": "integer", -// "description": "The character position for which to determine the completion proposals." +// "description": "The character position for which to determine the +// completion proposals." // }, // "line": { // "type": "integer", -// "description": "An optional line for which to determine the completion proposals. If missing the first line of the text is assumed." +// "description": "An optional line for which to determine the completion +// proposals. If missing the first line of the text is assumed." // } // }, // "required": [ "text", "column" ] @@ -933,39 +939,51 @@ // }, // "CompletionItem": { // "type": "object", -// "description": "CompletionItems are the suggestions returned from the CompletionsRequest.", -// "properties": { +// "description": "CompletionItems are the suggestions returned from the +// CompletionsRequest.", "properties": { // "label": { // "type": "string", -// "description": "The label of this completion item. By default this is also the text that is inserted when selecting this completion." +// "description": "The label of this completion item. By default this is +// also the text that is inserted when selecting this completion." // }, // "text": { // "type": "string", -// "description": "If text is not falsy then it is inserted instead of the label." +// "description": "If text is not falsy then it is inserted instead of the +// label." // }, // "sortText": { // "type": "string", -// "description": "A string that should be used when comparing this item with other items. When `falsy` the label is used." +// "description": "A string that should be used when comparing this item +// with other items. When `falsy` the label is used." // }, // "type": { // "$ref": "#/definitions/CompletionItemType", -// "description": "The item's type. Typically the client uses this information to render the item in the UI with an icon." +// "description": "The item's type. Typically the client uses this +// information to render the item in the UI with an icon." // }, // "start": { // "type": "integer", -// "description": "This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added.\nIf missing the text is added at the location specified by the CompletionsRequest's 'column' attribute." +// "description": "This value determines the location (in the +// CompletionsRequest's 'text' attribute) where the completion text is +// added.\nIf missing the text is added at the location specified by the +// CompletionsRequest's 'column' attribute." // }, // "length": { // "type": "integer", -// "description": "This value determines how many characters are overwritten by the completion text.\nIf missing the value 0 is assumed which results in the completion text being inserted." +// "description": "This value determines how many characters are +// overwritten by the completion text.\nIf missing the value 0 is assumed +// which results in the completion text being inserted." // } // }, // "required": [ "label" ] // }, // "CompletionItemType": { // "type": "string", -// "description": "Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them.", -// "enum": [ "method", "function", "constructor", "field", "variable", "class", "interface", "module", "property", "unit", "value", "enum", "keyword", "snippet", "text", "color", "file", "reference", "customcolor" ] +// "description": "Some predefined types for the CompletionItem. Please note +// that not all clients have specific icons for all of them.", "enum": [ +// "method", "function", "constructor", "field", "variable", "class", +// "interface", "module", "property", "unit", "value", "enum", "keyword", +// "snippet", "text", "color", "file", "reference", "customcolor" ] // } void request_completions(const llvm::json::Object &request) { llvm::json::Object response; @@ -992,9 +1010,7 @@ lldb::SBStringList matches; lldb::SBStringList descriptions; g_vsc.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions( - text.c_str(), - actual_column, - 0, -1, matches, descriptions); + text.c_str(), actual_column, 0, -1, matches, descriptions); size_t count = std::min((uint32_t)100, matches.GetSize()); targets.reserve(count); for (size_t i = 0; i < count; i++) { @@ -1004,8 +1020,8 @@ llvm::json::Object item; llvm::StringRef match_ref = match; - for(llvm::StringRef commit_point: {".", "->"}) { - if (match_ref.contains(commit_point)){ + for (llvm::StringRef commit_point : {".", "->"}) { + if (match_ref.contains(commit_point)) { match_ref = match_ref.rsplit(commit_point).second; } } @@ -1160,7 +1176,8 @@ } else { SetValueForKey(value, body, "result"); auto value_typename = value.GetType().GetDisplayTypeName(); - EmplaceSafeString(body, "type", value_typename ? value_typename : NO_TYPENAME); + EmplaceSafeString(body, "type", + value_typename ? value_typename : NO_TYPENAME); if (value.MightHaveChildren()) { auto variablesReference = VARIDX_TO_VARREF(g_vsc.variables.GetSize()); g_vsc.variables.Append(value); @@ -1229,8 +1246,8 @@ if (module_id == curr_module.GetUUIDString()) { int num_units = curr_module.GetNumCompileUnits(); for (int j = 0; j < num_units; j++) { - auto curr_unit = curr_module.GetCompileUnitAtIndex(j);\ - units.emplace_back(CreateCompileUnit(curr_unit));\ + auto curr_unit = curr_module.GetCompileUnitAtIndex(j); + units.emplace_back(CreateCompileUnit(curr_unit)); } body.try_emplace("compileUnits", std::move(units)); break; @@ -1357,6 +1374,9 @@ filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp)); } body.try_emplace("exceptionBreakpointFilters", std::move(filters)); + // The debug adapter supports launching a debugee in intergrated VSCode + // terminal. + body.try_emplace("supportsRunInTerminalRequest", true); // The debug adapter supports stepping back via the stepBack and // reverseContinue requests. body.try_emplace("supportsStepBack", false); @@ -1416,6 +1436,77 @@ g_vsc.SendJSON(llvm::json::Value(std::move(response))); } +void request_runInTerminal(const llvm::json::Object &request, + llvm::json::Object &launch_response) { + // Fill out attach info + g_vsc.is_attach = true; + lldb::SBError error; + lldb::SBAttachInfo attach_info; + attach_info.SetWaitForLaunch(true, false); + // Manually set stopOnEntry as lldb cannot gather this info from the request + g_vsc.stop_at_entry = true; + auto attach_func = [&]() { g_vsc.target.Attach(attach_info, error); }; + std::thread attacher(attach_func); + + // Send reverse request for run in terminal + auto arguments = request.getObject("arguments"); + llvm::json::Object reverseRequest; + reverseRequest.try_emplace("type", "request"); + reverseRequest.try_emplace("command", "runInTerminal"); + llvm::json::Object runInTerminalArgs; + runInTerminalArgs.try_emplace("kind", "integrated"); + const auto cwd = GetString(arguments, "cwd"); + if (!cwd.empty()) + runInTerminalArgs.try_emplace("cwd", cwd); + + std::vector<std::string> commands = GetStrings(arguments, "args"); + commands.insert(commands.begin(), + std::string(GetString(arguments, "program").data())); + runInTerminalArgs.try_emplace("args", commands); + std::vector<std::string> envVars = GetStrings(arguments, "env"); + llvm::json::Object environment; + for (std::string envVar : envVars) { + size_t ind = envVar.find("="); + environment.try_emplace(envVar.substr(0, ind), envVar.substr(ind + 1)); + } + runInTerminalArgs.try_emplace("env", + llvm::json::Value(std::move(environment))); + reverseRequest.try_emplace("arguments", + llvm::json::Value(std::move(runInTerminalArgs))); + llvm::json::Object reverseResponse; + lldb_vscode::VSCode::PacketStatus status = + g_vsc.SendReverseRequest(reverseRequest, reverseResponse); + if (status == lldb_vscode::VSCode::PacketStatus::Success) { + attacher.join(); + } else { + // Reverse Request handshake fails + error.SetErrorString("Process cannot be launched by IDE."); + } + + if (error.Success()) { + // IDE doesn't respond back the pid of the target, so lldb can only attach + // by process name, which is already set up in request_launch(). + auto attached_pid = g_vsc.target.GetProcess().GetProcessID(); + if (attached_pid == LLDB_INVALID_PROCESS_ID) { + error.SetErrorString("Failed to attach to a process"); + } + } + if (error.Fail()) { + launch_response["success"] = llvm::json::Value(false); + EmplaceSafeString(launch_response, "message", + std::string(error.GetCString())); + return; + } + if (error.Success()) { + SendProcessEvent(Attach); + launch_response["success"] = llvm::json::Value(true); + } else { + // Attach fails + launch_response["success"] = llvm::json::Value(false); + } + g_vsc.SendJSON(CreateEventObject("initialized")); +} + // "LaunchRequest": { // "allOf": [ { "$ref": "#/definitions/Request" }, { // "type": "object", @@ -1488,64 +1579,69 @@ return; } - // Instantiate a launch info instance for the target. - auto launch_info = g_vsc.target.GetLaunchInfo(); - - // Grab the current working directory if there is one and set it in the - // launch info. - const auto cwd = GetString(arguments, "cwd"); - if (!cwd.empty()) - launch_info.SetWorkingDirectory(cwd.data()); - - // Extract any extra arguments and append them to our program arguments for - // when we launch - auto args = GetStrings(arguments, "args"); - if (!args.empty()) - launch_info.SetArguments(MakeArgv(args).data(), true); - - // Pass any environment variables along that the user specified. - auto envs = GetStrings(arguments, "env"); - if (!envs.empty()) - launch_info.SetEnvironmentEntries(MakeArgv(envs).data(), true); - - auto flags = launch_info.GetLaunchFlags(); - - if (GetBoolean(arguments, "disableASLR", true)) - flags |= lldb::eLaunchFlagDisableASLR; - if (GetBoolean(arguments, "disableSTDIO", false)) - flags |= lldb::eLaunchFlagDisableSTDIO; - if (GetBoolean(arguments, "shellExpandArguments", false)) - flags |= lldb::eLaunchFlagShellExpandArguments; - const bool detatchOnError = GetBoolean(arguments, "detachOnError", false); - launch_info.SetDetachOnError(detatchOnError); - launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug | - lldb::eLaunchFlagStopAtEntry); - - // Run any pre run LLDB commands the user specified in the launch.json - g_vsc.RunPreRunCommands(); - if (launchCommands.empty()) { - // Disable async events so the launch will be successful when we return from - // the launch call and the launch will happen synchronously - g_vsc.debugger.SetAsync(false); - g_vsc.target.Launch(launch_info, error); - g_vsc.debugger.SetAsync(true); + if (GetBoolean(arguments, "launchInTerminal", false)) { + request_runInTerminal(request, response); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); } else { - g_vsc.RunLLDBCommands("Running launchCommands:", launchCommands); - // The custom commands might have created a new target so we should use the - // selected target after these commands are run. - g_vsc.target = g_vsc.debugger.GetSelectedTarget(); - } + // Instantiate a launch info instance for the target. + auto launch_info = g_vsc.target.GetLaunchInfo(); + + // Grab the current working directory if there is one and set it in the + // launch info. + const auto cwd = GetString(arguments, "cwd"); + if (!cwd.empty()) + launch_info.SetWorkingDirectory(cwd.data()); + + // Extract any extra arguments and append them to our program arguments for + // when we launch + auto args = GetStrings(arguments, "args"); + if (!args.empty()) + launch_info.SetArguments(MakeArgv(args).data(), true); + + // Pass any environment variables along that the user specified. + auto envs = GetStrings(arguments, "env"); + if (!envs.empty()) + launch_info.SetEnvironmentEntries(MakeArgv(envs).data(), true); + + auto flags = launch_info.GetLaunchFlags(); + + if (GetBoolean(arguments, "disableASLR", true)) + flags |= lldb::eLaunchFlagDisableASLR; + if (GetBoolean(arguments, "disableSTDIO", false)) + flags |= lldb::eLaunchFlagDisableSTDIO; + if (GetBoolean(arguments, "shellExpandArguments", false)) + flags |= lldb::eLaunchFlagShellExpandArguments; + const bool detatchOnError = GetBoolean(arguments, "detachOnError", false); + launch_info.SetDetachOnError(detatchOnError); + launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug | + lldb::eLaunchFlagStopAtEntry); + + // Run any pre run LLDB commands the user specified in the launch.json + g_vsc.RunPreRunCommands(); + if (launchCommands.empty()) { + // Disable async events so the launch will be successful when we return + // from the launch call and the launch will happen synchronously + g_vsc.debugger.SetAsync(false); + g_vsc.target.Launch(launch_info, error); + g_vsc.debugger.SetAsync(true); + } else { + g_vsc.RunLLDBCommands("Running launchCommands:", launchCommands); + // The custom commands might have created a new target so we should use + // the selected target after these commands are run. + g_vsc.target = g_vsc.debugger.GetSelectedTarget(); + } - if (error.Fail()) { - response["success"] = llvm::json::Value(false); - EmplaceSafeString(response, "message", std::string(error.GetCString())); - } - g_vsc.SendJSON(llvm::json::Value(std::move(response))); + if (error.Fail()) { + response["success"] = llvm::json::Value(false); + EmplaceSafeString(response, "message", std::string(error.GetCString())); + } - SendProcessEvent(Launch); - g_vsc.SendJSON(llvm::json::Value(CreateEventObject("initialized"))); - // Reenable async events and start the event thread to catch async events. - // g_vsc.debugger.SetAsync(true); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + SendProcessEvent(Launch); + g_vsc.SendJSON(llvm::json::Value(CreateEventObject("initialized"))); + // Reenable async events and start the event thread to catch async events. + // g_vsc.debugger.SetAsync(true); + } } // "NextRequest": { @@ -2080,7 +2176,7 @@ // Disable any function breakpoints that aren't in the request_bps. // There is no call to remove function breakpoints other than calling this // function with a smaller or empty "breakpoints" list. - for (auto &pair: g_vsc.function_breakpoints) { + for (auto &pair : g_vsc.function_breakpoints) { auto request_pos = request_bps.find(pair.first()); if (request_pos == request_bps.end()) { // This function breakpoint no longer exists delete it from LLDB @@ -2098,7 +2194,7 @@ } } // Remove any breakpoints that are no longer in our list - for (const auto &name: remove_names) + for (const auto &name : remove_names) g_vsc.function_breakpoints.erase(name); // Any breakpoints that are left in "request_bps" are breakpoints that @@ -2814,39 +2910,35 @@ g_vsc.SendJSON(llvm::json::Value(std::move(response))); } -const std::map<std::string, RequestCallback> &GetRequestHandlers() { -#define REQUEST_CALLBACK(name) \ - { #name, request_##name } - static std::map<std::string, RequestCallback> g_request_handlers = { - // VSCode Debug Adaptor requests - REQUEST_CALLBACK(attach), - REQUEST_CALLBACK(completions), - REQUEST_CALLBACK(continue), - REQUEST_CALLBACK(configurationDone), - REQUEST_CALLBACK(disconnect), - REQUEST_CALLBACK(evaluate), - REQUEST_CALLBACK(exceptionInfo), - REQUEST_CALLBACK(getCompileUnits), - REQUEST_CALLBACK(initialize), - REQUEST_CALLBACK(launch), - REQUEST_CALLBACK(next), - REQUEST_CALLBACK(pause), - REQUEST_CALLBACK(scopes), - REQUEST_CALLBACK(setBreakpoints), - REQUEST_CALLBACK(setExceptionBreakpoints), - REQUEST_CALLBACK(setFunctionBreakpoints), - REQUEST_CALLBACK(setVariable), - REQUEST_CALLBACK(source), - REQUEST_CALLBACK(stackTrace), - REQUEST_CALLBACK(stepIn), - REQUEST_CALLBACK(stepOut), - REQUEST_CALLBACK(threads), - REQUEST_CALLBACK(variables), - // Testing requests - REQUEST_CALLBACK(_testGetTargetBreakpoints), - }; -#undef REQUEST_CALLBACK - return g_request_handlers; +const void RegisterRequestCallbacks() { + g_vsc.RegisterRequestCallback("attach", request_attach); + g_vsc.RegisterRequestCallback("completions", request_completions); + g_vsc.RegisterRequestCallback("continue", request_continue); + g_vsc.RegisterRequestCallback("configurationDone", request_configurationDone); + g_vsc.RegisterRequestCallback("disconnect", request_disconnect); + g_vsc.RegisterRequestCallback("evaluate", request_evaluate); + g_vsc.RegisterRequestCallback("exceptionInfo", request_exceptionInfo); + g_vsc.RegisterRequestCallback("getCompileUnits", request_getCompileUnits); + g_vsc.RegisterRequestCallback("initialize", request_initialize); + g_vsc.RegisterRequestCallback("launch", request_launch); + g_vsc.RegisterRequestCallback("next", request_next); + g_vsc.RegisterRequestCallback("pause", request_pause); + g_vsc.RegisterRequestCallback("scopes", request_scopes); + g_vsc.RegisterRequestCallback("setBreakpoints", request_setBreakpoints); + g_vsc.RegisterRequestCallback("setExceptionBreakpoints", + request_setExceptionBreakpoints); + g_vsc.RegisterRequestCallback("setFunctionBreakpoints", + request_setFunctionBreakpoints); + g_vsc.RegisterRequestCallback("setVariable", request_setVariable); + g_vsc.RegisterRequestCallback("source", request_source); + g_vsc.RegisterRequestCallback("stackTrace", request_stackTrace); + g_vsc.RegisterRequestCallback("stepIn", request_stepIn); + g_vsc.RegisterRequestCallback("stepOut", request_stepOut); + g_vsc.RegisterRequestCallback("threads", request_threads); + g_vsc.RegisterRequestCallback("variables", request_variables); + // Testing requests + g_vsc.RegisterRequestCallback("_testGetTargetBreakpoints", + request__testGetTargetBreakpoints); } } // anonymous namespace @@ -2874,7 +2966,7 @@ } int main(int argc, char *argv[]) { - + RegisterRequestCallbacks(); // Initialize LLDB first before we do anything. lldb::SBDebugger::Initialize(); @@ -2920,48 +3012,17 @@ g_vsc.output.descriptor = StreamDescriptor::from_file(fileno(stdout), false); } - auto request_handlers = GetRequestHandlers(); uint32_t packet_idx = 0; while (!g_vsc.sent_terminated_event) { - std::string json = g_vsc.ReadJSON(); - if (json.empty()) + llvm::json::Object object; + lldb_vscode::VSCode::PacketStatus status = g_vsc.GetObject(object); + if (status == lldb_vscode::VSCode::PacketStatus::EndOfFile) break; + if (status != lldb_vscode::VSCode::PacketStatus::Success) + return 1; // Fatal error - llvm::StringRef json_sref(json); - llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref); - if (!json_value) { - auto error = json_value.takeError(); - if (g_vsc.log) { - std::string error_str; - llvm::raw_string_ostream strm(error_str); - strm << error; - strm.flush(); - - *g_vsc.log << "error: failed to parse JSON: " << error_str << std::endl - << json << std::endl; - } + if (!g_vsc.HandleObject(object)) return 1; - } - - auto object = json_value->getAsObject(); - if (!object) { - if (g_vsc.log) - *g_vsc.log << "error: json packet isn't a object" << std::endl; - return 1; - } - - const auto packet_type = GetString(object, "type"); - if (packet_type == "request") { - const auto command = GetString(object, "command"); - auto handler_pos = request_handlers.find(std::string(command)); - if (handler_pos != request_handlers.end()) { - handler_pos->second(*object); - } else { - if (g_vsc.log) - *g_vsc.log << "error: unhandled command \"" << command.data() << std::endl; - return 1; - } - } ++packet_idx; } Index: lldb/tools/lldb-vscode/VSCode.h =================================================================== --- lldb/tools/lldb-vscode/VSCode.h +++ lldb/tools/lldb-vscode/VSCode.h @@ -19,6 +19,7 @@ #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include "lldb/API/SBAttachInfo.h" @@ -61,6 +62,7 @@ typedef llvm::DenseMap<uint32_t, SourceBreakpoint> SourceBreakpointMap; typedef llvm::StringMap<FunctionBreakpoint> FunctionBreakpointMap; +typedef void (*RequestCallback)(const llvm::json::Object &command); enum class OutputType { Console, Stdout, Stderr, Telemetry }; enum VSCodeBroadcasterBits { eBroadcastBitStopEventThread = 1u << 0 }; @@ -91,6 +93,14 @@ bool sent_terminated_event; bool stop_at_entry; bool is_attach; + uint32_t reverse_request_seq = 0; + enum class PacketStatus { + Success = 0, + EndOfFile, + JSONMalformed, + JSONNotObject + }; + std::map<std::string, RequestCallback> m_request_handlers; // Keep track of the last stop thread index IDs as threads won't go away // unless we send a "thread" event to indicate the thread exited. llvm::DenseSet<lldb::tid_t> thread_ids; @@ -146,13 +156,21 @@ /// /// \return /// An SBTarget object. - lldb::SBTarget CreateTargetFromArguments( - const llvm::json::Object &arguments, - lldb::SBError &error); + lldb::SBTarget CreateTargetFromArguments(const llvm::json::Object &arguments, + lldb::SBError &error); /// Set given target object as a current target for lldb-vscode and start /// listeing for its breakpoint events. void SetTarget(const lldb::SBTarget target); + + const std::map<std::string, RequestCallback> &GetRequestHandlers(); + + PacketStatus GetObject(llvm::json::Object &object); + bool HandleObject(const llvm::json::Object &object); + PacketStatus SendReverseRequest(llvm::json::Object &request, + llvm::json::Object &response); + + void RegisterRequestCallback(std::string request, RequestCallback callback); }; extern VSCode g_vsc; Index: lldb/tools/lldb-vscode/VSCode.cpp =================================================================== --- lldb/tools/lldb-vscode/VSCode.cpp +++ lldb/tools/lldb-vscode/VSCode.cpp @@ -6,9 +6,9 @@ // //===----------------------------------------------------------------------===// -#include <stdarg.h> #include <fstream> #include <mutex> +#include <stdarg.h> #include "LLDBUtils.h" #include "VSCode.h" @@ -16,9 +16,9 @@ #if defined(_WIN32) #define NOMINMAX -#include <windows.h> #include <fcntl.h> #include <io.h> +#include <windows.h> #endif using namespace lldb_vscode; @@ -41,9 +41,9 @@ stop_at_entry(false), is_attach(false) { const char *log_file_path = getenv("LLDBVSCODE_LOG"); #if defined(_WIN32) -// Windows opens stdout and stdin in text mode which converts \n to 13,10 -// while the value is just 10 on Darwin/Linux. Setting the file mode to binary -// fixes this. + // Windows opens stdout and stdin in text mode which converts \n to 13,10 + // while the value is just 10 on Darwin/Linux. Setting the file mode to binary + // fixes this. int result = _setmode(fileno(stdout), _O_BINARY); assert(result); result = _setmode(fileno(stdin), _O_BINARY); @@ -54,8 +54,7 @@ log.reset(new std::ofstream(log_file_path)); } -VSCode::~VSCode() { -} +VSCode::~VSCode() {} int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const { auto pos = source_map.find(sourceReference); @@ -232,8 +231,8 @@ va_start(args, format); int actual_length = vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); - SendOutput(o, llvm::StringRef(buffer, - std::min<int>(actual_length, sizeof(buffer)))); + SendOutput( + o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer)))); } int64_t VSCode::GetNextSourceReference() { @@ -313,9 +312,9 @@ RunLLDBCommands("Running terminateCommands:", terminate_commands); } -lldb::SBTarget VSCode::CreateTargetFromArguments( - const llvm::json::Object &arguments, - lldb::SBError &error) { +lldb::SBTarget +VSCode::CreateTargetFromArguments(const llvm::json::Object &arguments, + lldb::SBError &error) { // Grab the name of the program we need to debug and create a target using // the given program as an argument. Executable file can be a source of target // architecture and platform, if they differ from the host. Setting exe path @@ -330,18 +329,15 @@ llvm::StringRef platform_name = GetString(arguments, "platformName"); llvm::StringRef program = GetString(arguments, "program"); auto target = this->debugger.CreateTarget( - program.data(), - target_triple.data(), - platform_name.data(), - true, // Add dependent modules. - error - ); + program.data(), target_triple.data(), platform_name.data(), + true, // Add dependent modules. + error); if (error.Fail()) { // Update message if there was an error. error.SetErrorStringWithFormat( - "Could not create a target for a program '%s': %s.", - program.data(), error.GetCString()); + "Could not create a target for a program '%s': %s.", program.data(), + error.GetCString()); } return target; @@ -359,11 +355,83 @@ listener.StartListeningForEvents(this->broadcaster, eBroadcastBitStopEventThread); listener.StartListeningForEvents( - this->target.GetBroadcaster(), - lldb::SBTarget::eBroadcastBitModulesLoaded | - lldb::SBTarget::eBroadcastBitModulesUnloaded | - lldb::SBTarget::eBroadcastBitSymbolsLoaded); + this->target.GetBroadcaster(), + lldb::SBTarget::eBroadcastBitModulesLoaded | + lldb::SBTarget::eBroadcastBitModulesUnloaded | + lldb::SBTarget::eBroadcastBitSymbolsLoaded); + } +} + +VSCode::PacketStatus VSCode::GetObject(llvm::json::Object &object) { + std::string json = ReadJSON(); + if (json.empty()) + return PacketStatus::EndOfFile; + + llvm::StringRef json_sref(json); + llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref); + if (!json_value) { + auto error = json_value.takeError(); + if (log) { + std::string error_str; + llvm::raw_string_ostream strm(error_str); + strm << error; + strm.flush(); + *log << "error: failed to parse JSON: " << error_str << std::endl + << json << std::endl; + } + return PacketStatus::JSONMalformed; } + object = *json_value->getAsObject(); + if (!json_value->getAsObject()) { + if (log) + *log << "error: json packet isn't a object" << std::endl; + return PacketStatus::JSONNotObject; + } + return PacketStatus::Success; +} + +bool VSCode::HandleObject(const llvm::json::Object &object) { + const auto packet_type = GetString(object, "type"); + if (packet_type == "request") { + const auto command = GetString(object, "command"); + auto handler_pos = m_request_handlers.find(std::string(command)); + if (handler_pos != m_request_handlers.end()) { + handler_pos->second(object); + return true; // Success + } else { + if (log) + *log << "error: unhandled command \"" << command.data() << std::endl; + return false; // Fail + } + } + return false; +} + +VSCode::PacketStatus VSCode::SendReverseRequest(llvm::json::Object &request, + llvm::json::Object &response) { + // Put the right "seq" into the packet here, so we don't have to do it from + // where we send the reverse request. + request.try_emplace("seq", ++reverse_request_seq); + SendJSON(llvm::json::Value(std::move(request))); + bool got_response = false; + while (!got_response) { + PacketStatus status = GetObject(response); + const auto packet_type = GetString(response, "type"); + if (packet_type == "response") { + if (status == PacketStatus::Success) { + return status; + // Not our response, we got another packet + HandleObject(response); + } else { + return status; + } + } + } +} + +void VSCode::RegisterRequestCallback(std::string request, + RequestCallback callback) { + m_request_handlers[request] = callback; } } // namespace lldb_vscode Index: lldb/test/API/tools/lldb-vscode/runInTerminal/main.cpp =================================================================== --- /dev/null +++ lldb/test/API/tools/lldb-vscode/runInTerminal/main.cpp @@ -0,0 +1,9 @@ + +int multiply(int x, int y) { + return x * y; // breakpoint 1 +} + +int main(int argc, char const *argv[]) { + int result = multiply(argc, 20); + return result < 0; +} Index: lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py =================================================================== --- /dev/null +++ lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py @@ -0,0 +1,70 @@ +""" +Test lldb-vscode setBreakpoints request +""" + +from __future__ import print_function + +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase + + +class TestVSCode_console(lldbvscode_testcase.VSCodeTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + def check_lldb_command(self, lldb_command, contains_string, assert_msg): + response = self.vscode.request_evaluate('`%s' % (lldb_command)) + output = response['body']['result'] + self.assertTrue(contains_string in output, + ("""Verify %s by checking the command output:\n""" + """'''\n%s'''\nfor the string: "%s" """ % ( + assert_msg, output, contains_string))) + + @skipIfWindows + @skipIfRemote + def test_scopes_variables_setVariable_evaluate(self): + ''' + Tests that the "scopes" request causes the currently selected + thread and frame to be updated. There are no DAP packets that tell + lldb-vscode which thread and frame are selected other than the + "scopes" request. lldb-vscode will now select the thread and frame + for the latest "scopes" request that it receives. + + The LLDB command interpreter needs to have the right thread and + frame selected so that commands executed in the debug console act + on the right scope. This applies both to the expressions that are + evaluated and the lldb commands that start with the backtick + character. + ''' + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = 'main.cpp' + breakpoint1_line = line_number(source, '// breakpoint 1') + lines = [breakpoint1_line] + # Set breakpoint in the thread function so we can step the threads + breakpoint_ids = self.set_source_breakpoints(source, lines) + self.assertTrue(len(breakpoint_ids) == len(lines), + "expect correct number of breakpoints") + self.continue_to_breakpoints(breakpoint_ids) + # Cause a "scopes" to be sent for frame zero which should update the + # selected thread and frame to frame 0. + self.vscode.get_local_variables(frameIndex=0) + # Verify frame #0 is selected in the command interpreter by running + # the "frame select" command with no frame index which will print the + # currently selected frame. + self.check_lldb_command("frame select", "frame #0", + "frame 0 is selected") + + # Cause a "scopes" to be sent for frame one which should update the + # selected thread and frame to frame 1. + self.vscode.get_local_variables(frameIndex=1) + # Verify frame #1 is selected in the command interpreter by running + # the "frame select" command with no frame index which will print the + # currently selected frame. + + self.check_lldb_command("frame select", "frame #1", + "frame 1 is selected") Index: lldb/test/API/tools/lldb-vscode/runInTerminal/Makefile =================================================================== --- /dev/null +++ lldb/test/API/tools/lldb-vscode/runInTerminal/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits