Retry of D84974

- Fix a small issue caused by a conflicting name (GetObject) on Windows.
  The fix was to rename the internal GetObject function to
  GetNextFunction.
This commit is contained in:
Walter Erquinigo 2020-09-01 18:52:14 -07:00 committed by Walter Erquinigo
parent 4ff4708d39
commit 132e57bc59
11 changed files with 363 additions and 82 deletions

View File

@ -282,7 +282,7 @@ class VSCodeTestCaseBase(TestBase):
trace=False, initCommands=None, preRunCommands=None,
stopCommands=None, exitCommands=None, terminateCommands=None,
sourcePath=None, debuggerRoot=None, launchCommands=None,
sourceMap=None, disconnectAutomatically=True):
sourceMap=None, disconnectAutomatically=True, runInTerminal=False):
'''Sending launch request to vscode
'''
@ -316,10 +316,16 @@ class VSCodeTestCaseBase(TestBase):
sourcePath=sourcePath,
debuggerRoot=debuggerRoot,
launchCommands=launchCommands,
sourceMap=sourceMap)
sourceMap=sourceMap,
runInTerminal=runInTerminal)
if not (response and response['success']):
self.assertTrue(response['success'],
'launch failed (%s)' % (response['message']))
# We need to trigger a request_configurationDone after we've successfully
# attached a runInTerminal process to finish initialization.
if runInTerminal:
self.vscode.request_configurationDone()
def build_and_launch(self, program, args=None, cwd=None, env=None,
stopOnEntry=False, disableASLR=True,
@ -327,7 +333,7 @@ class VSCodeTestCaseBase(TestBase):
trace=False, initCommands=None, preRunCommands=None,
stopCommands=None, exitCommands=None,
terminateCommands=None, sourcePath=None,
debuggerRoot=None):
debuggerRoot=None, runInTerminal=False):
'''Build the default Makefile target, create the VSCode debug adaptor,
and launch the process.
'''
@ -337,4 +343,4 @@ class VSCodeTestCaseBase(TestBase):
self.launch(program, args, cwd, env, stopOnEntry, disableASLR,
disableSTDIO, shellExpandArguments, trace,
initCommands, preRunCommands, stopCommands, exitCommands,
terminateCommands, sourcePath, debuggerRoot)
terminateCommands, sourcePath, debuggerRoot, runInTerminal=runInTerminal)

View File

@ -300,12 +300,29 @@ class DebugCommunication(object):
self.send_packet(command)
done = False
while not done:
response = self.recv_packet(filter_type='response')
if response is None:
response_or_request = self.recv_packet(filter_type=['response', 'request'])
if response_or_request is None:
desc = 'no response for "%s"' % (command['command'])
raise ValueError(desc)
self.validate_response(command, response)
return response
if response_or_request['type'] == 'response':
self.validate_response(command, response_or_request)
return response_or_request
else:
if response_or_request['command'] == 'runInTerminal':
subprocess.Popen(response_or_request['arguments']['args'],
env=response_or_request['arguments']['env'])
self.send_packet({
"type": "response",
"seq": -1,
"request_seq": response_or_request['seq'],
"success": True,
"command": "runInTerminal",
"body": {}
}, set_sequence=False)
else:
desc = 'unkonwn reverse request "%s"' % (response_or_request['command'])
raise ValueError(desc)
return None
def wait_for_event(self, filter=None, timeout=None):
@ -599,7 +616,8 @@ class DebugCommunication(object):
trace=False, initCommands=None, preRunCommands=None,
stopCommands=None, exitCommands=None,
terminateCommands=None ,sourcePath=None,
debuggerRoot=None, launchCommands=None, sourceMap=None):
debuggerRoot=None, launchCommands=None, sourceMap=None,
runInTerminal=False):
args_dict = {
'program': program
}
@ -638,6 +656,8 @@ class DebugCommunication(object):
args_dict['launchCommands'] = launchCommands
if sourceMap:
args_dict['sourceMap'] = sourceMap
if runInTerminal:
args_dict['runInTerminal'] = runInTerminal
command_dict = {
'command': 'launch',
'type': 'request',

View File

@ -0,0 +1,3 @@
C_SOURCES := main.c
include Makefile.rules

View File

@ -0,0 +1,48 @@
"""
Test lldb-vscode runInTerminal reverse request
"""
import unittest2
import vscode
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
import lldbvscode_testcase
import time
import os
class TestVSCode_runInTerminal(lldbvscode_testcase.VSCodeTestCaseBase):
mydir = TestBase.compute_mydir(__file__)
@skipUnlessDarwin
@skipIfRemote
def test_runInTerminal(self):
'''
Tests the "runInTerminal" reverse request. It makes sure that the IDE can
launch the inferior with the correct environment variables and arguments.
'''
program = self.getBuildArtifact("a.out")
source = 'main.c'
self.build_and_launch(program, stopOnEntry=True, runInTerminal=True, args=["foobar"], env=["FOO=bar"])
breakpoint_line = line_number(source, '// breakpoint')
self.set_source_breakpoints(source, [breakpoint_line])
self.continue_to_next_stop()
# We verify we actually stopped inside the loop
counter = int(self.vscode.get_local_variable_value('counter'))
self.assertTrue(counter > 0)
# We verify we were able to set the launch arguments
argc = int(self.vscode.get_local_variable_value('argc'))
self.assertEqual(argc, 2)
argv1 = self.vscode.request_evaluate('argv[1]')['body']['result']
self.assertIn('foobar', argv1)
# We verify we were able to set the environment
env = self.vscode.request_evaluate('foo')['body']['result']
self.assertIn('bar', env)

View File

@ -0,0 +1,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
const char *foo = getenv("FOO");
for (int counter = 1;; counter++) {
sleep(1); // breakpoint
}
return 0;
}

View File

@ -998,4 +998,44 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) {
return llvm::json::Value(std::move(object));
}
/// See
/// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal
llvm::json::Object
CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request) {
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");
std::vector<std::string> args = GetStrings(launch_request_arguments, "args");
// The program path must be the first entry in the "args" field
args.insert(args.begin(),
GetString(launch_request_arguments, "program").str());
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) {
size_t index = env.find("=");
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;
}
} // namespace lldb_vscode

View File

@ -443,6 +443,18 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit);
/// Create a runInTerminal reverse request object
///
/// \param[in] launch_request
/// The original launch_request object whose fields are used to construct
/// the reverse request object.
///
/// \return
/// A "runInTerminal" JSON object that follows the specification outlined by
/// Microsoft.
llvm::json::Object
CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request);
} // namespace lldb_vscode
#endif

View File

@ -38,7 +38,8 @@ VSCode::VSCode()
{"swift_catch", "Swift Catch", lldb::eLanguageTypeSwift},
{"swift_throw", "Swift Throw", lldb::eLanguageTypeSwift}}),
focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false),
stop_at_entry(false), is_attach(false) {
stop_at_entry(false), is_attach(false),
reverse_request_seq(0), waiting_for_run_in_terminal(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
@ -362,4 +363,71 @@ void VSCode::SetTarget(const lldb::SBTarget target) {
}
}
PacketStatus VSCode::GetNextObject(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 = request_handlers.find(std::string(command));
if (handler_pos != 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;
}
PacketStatus VSCode::SendReverseRequest(llvm::json::Object request,
llvm::json::Object &response) {
request.try_emplace("seq", ++reverse_request_seq);
SendJSON(llvm::json::Value(std::move(request)));
while (true) {
PacketStatus status = GetNextObject(response);
const auto packet_type = GetString(response, "type");
if (packet_type == "response")
return status;
else {
// Not our response, we got another packet
HandleObject(response);
}
}
return PacketStatus::EndOfFile;
}
void VSCode::RegisterRequestCallback(std::string request,
RequestCallback callback) {
request_handlers[request] = callback;
}
} // namespace lldb_vscode

View File

@ -9,6 +9,7 @@
#ifndef LLDB_TOOLS_LLDB_VSCODE_VSCODE_H
#define LLDB_TOOLS_LLDB_VSCODE_VSCODE_H
#include <condition_variable>
#include <iosfwd>
#include <map>
#include <set>
@ -19,6 +20,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"
@ -65,6 +67,15 @@ enum class OutputType { Console, Stdout, Stderr, Telemetry };
enum VSCodeBroadcasterBits { eBroadcastBitStopEventThread = 1u << 0 };
typedef void (*RequestCallback)(const llvm::json::Object &command);
enum class PacketStatus {
Success = 0,
EndOfFile,
JSONMalformed,
JSONNotObject
};
struct VSCode {
InputStream input;
OutputStream output;
@ -91,6 +102,10 @@ struct VSCode {
bool sent_terminated_event;
bool stop_at_entry;
bool is_attach;
uint32_t reverse_request_seq;
std::map<std::string, RequestCallback> request_handlers;
std::condition_variable request_in_terminal_cv;
bool waiting_for_run_in_terminal;
// 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;
@ -152,6 +167,36 @@ struct VSCode {
/// 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 GetNextObject(llvm::json::Object &object);
bool HandleObject(const llvm::json::Object &object);
/// Send a Debug Adapter Protocol reverse request to the IDE
///
/// \param[in] request
/// The payload of the request to send.
///
/// \param[out] response
/// The response of the IDE. It might be undefined if there was an error.
///
/// \return
/// A \a PacketStatus object indicating the sucess or failure of the
/// request.
PacketStatus SendReverseRequest(llvm::json::Object request,
llvm::json::Object &response);
/// Registers a callback handler for a Debug Adapter Protocol request
///
/// \param[in] request
/// The name of the request following the Debug Adapter Protocol
/// specification.
///
/// \param[in] callback
/// The callback to execute when the given request is triggered by the
/// IDE.
void RegisterRequestCallback(std::string request, RequestCallback callback);
};
extern VSCode g_vsc;

View File

@ -384,7 +384,12 @@ void EventThreadFunction() {
break;
case lldb::eStateSuspended:
break;
case lldb::eStateStopped:
case lldb::eStateStopped: {
if (g_vsc.waiting_for_run_in_terminal) {
g_vsc.waiting_for_run_in_terminal = false;
g_vsc.request_in_terminal_cv.notify_one();
}
}
// Only report a stopped event if the process was not restarted.
if (!lldb::SBProcess::GetRestartedFromEvent(event)) {
SendStdOutStdErr(process);
@ -1374,6 +1379,9 @@ void request_initialize(const llvm::json::Object &request) {
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);
@ -1433,6 +1441,49 @@ void request_initialize(const llvm::json::Object &request) {
g_vsc.SendJSON(llvm::json::Value(std::move(response)));
}
void request_runInTerminal(const llvm::json::Object &launch_request,
llvm::json::Object &launch_response) {
// We have already created a target that has a valid "program" path to the
// executable. We will attach to the next process whose name matches that
// of the target's.
g_vsc.is_attach = true;
lldb::SBAttachInfo attach_info;
lldb::SBError error;
attach_info.SetWaitForLaunch(true, /*async*/ true);
g_vsc.target.Attach(attach_info, error);
llvm::json::Object reverse_request =
CreateRunInTerminalReverseRequest(launch_request);
llvm::json::Object reverse_response;
lldb_vscode::PacketStatus status =
g_vsc.SendReverseRequest(reverse_request, reverse_response);
if (status != lldb_vscode::PacketStatus::Success)
error.SetErrorString("Process cannot be launched by IDE.");
if (error.Success()) {
// Wait for the attach stop event to happen or for a timeout.
g_vsc.waiting_for_run_in_terminal = true;
static std::mutex mutex;
std::unique_lock<std::mutex> locker(mutex);
g_vsc.request_in_terminal_cv.wait_for(locker, std::chrono::seconds(10));
auto attached_pid = g_vsc.target.GetProcess().GetProcessID();
if (attached_pid == LLDB_INVALID_PROCESS_ID)
error.SetErrorString("Failed to attach to a process");
else
SendProcessEvent(Attach);
}
if (error.Fail()) {
launch_response["success"] = llvm::json::Value(false);
EmplaceSafeString(launch_response, "message",
std::string(error.GetCString()));
} else {
launch_response["success"] = llvm::json::Value(true);
g_vsc.SendJSON(CreateEventObject("initialized"));
}
}
// "LaunchRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@ -1505,6 +1556,12 @@ void request_launch(const llvm::json::Object &request) {
return;
}
if (GetBoolean(arguments, "runInTerminal", false)) {
request_runInTerminal(request, response);
g_vsc.SendJSON(llvm::json::Value(std::move(response)));
return;
}
// Instantiate a launch info instance for the target.
auto launch_info = g_vsc.target.GetLaunchInfo();
@ -2831,39 +2888,35 @@ void request__testGetTargetBreakpoints(const llvm::json::Object &request) {
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;
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
@ -2895,6 +2948,8 @@ int main(int argc, char *argv[]) {
// Initialize LLDB first before we do anything.
lldb::SBDebugger::Initialize();
RegisterRequestCallbacks();
int portno = -1;
LLDBVSCodeOptTable T;
@ -2937,49 +2992,17 @@ int main(int argc, char *argv[]) {
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::PacketStatus status = g_vsc.GetObject(object);
if (status == lldb_vscode::PacketStatus::EndOfFile)
break;
if (status != lldb_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;
}

View File

@ -175,6 +175,11 @@
"type": "array",
"description": "Commands executed at the end of debugging session.",
"default": []
},
"runInTerminal": {
"type": "boolean",
"description": "Launch the program inside an integrated terminal in the IDE. Useful for debugging interactive command line programs",
"default": false
}
}
},