This is an archive of the discontinued LLVM Phabricator instance.

Enable Launching the Debugee in VSCode Terminal
ClosedPublic

Authored by wallace on Jul 30 2020, 12:44 PM.

Details

Summary

A "Launch In Terminal" option is added to the debug settings in IDE. When turn this option on, the debugee will be launched inside the integrated terminal so that user can input values for debugging. This diff involves the runInTerminal request sent from lldb to IDE and lldb won't attach the program until the runInTerminal response is received. A response handler for the runInTerminal response from IDE to lldb is added to handle the reverse requests and corresponding responses.
The screenshot shows what it looks like when the debugee is launched in terminal.

Diff Detail

Event Timeline

Herald added a project: Restricted Project. · View Herald TranscriptJul 30 2020, 12:44 PM
aelitashen requested review of this revision.Jul 30 2020, 12:44 PM
aelitashen edited the summary of this revision. (Show Details)Jul 30 2020, 12:58 PM
wallace retitled this revision from Enable Launching the Debugee in VSCode Terminal to [WIP] Enable Launching the Debugee in VSCode Terminal.Jul 30 2020, 1:05 PM
wallace requested changes to this revision.Jul 30 2020, 1:15 PM

This is very good for a first implementation. You need to write a test for this as well. Once this works, you have to work on the launcher helper Greg and I mentioned to you that will help you guarantee you can always attach to the target.

Regarding the test, you have to build the python code that will mimic the runInTerminal reverse request, along with its response, which will just launch the debuggee inside a terminal.

For launching a process inside a shell from python, you can use the subprocess.run function and pass the shell=True attribute.

For adding the runInTerminal reverse request in python, take a look at the file packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py. You can see examples like request_launch, where it sends data and waits for a response using the send_recv method. You should add support for reverse requests.

lldb/tools/lldb-vscode/lldb-vscode.cpp
1382

s/VScode/VSCode

1605

move the entire contents of this if to another function for readability

1609

does it work if you don't specify the "seq" number? If it works without it, I would leave it empty

1612

what if cwd is not specified in the arguments? I imagine this might break or at least the IDE wouldn't be able to launch the target properly.
If "cwd" is not defined, then use llvm::sys::fs::current_path() instead. Does this make sense @clayborg ?

1615

what happens if it fails to attach? Try running a program that dies right away and that lldb can't attach. Does the debug session terminate correctly?

1615

mention here that VSCode doesn't respond back the pid of the target, so we can only attach by process name, which is already set up in the request_launch method

1618–1621

run clang-format

1625

remove this comment

1625

shouldn't you return at this point and not execute anything that happens after this?
The lines

g_vsc.SendJSON(llvm::json::Value(std::move(response)));
SendProcessEvent(Launch); // Maybe change this for Attach as you'll end up actually doing an attach
g_vsc.SendJSON(llvm::json::Value(CreateEventObject("initialized")));

probably should only be sent after you were able to attach.

1625

remove this

3009

you should use the command here, instead of the hardcoded string "runInTerminal"

This revision now requires changes to proceed.Jul 30 2020, 1:15 PM
clayborg requested changes to this revision.Jul 30 2020, 4:46 PM

So there is a fundamental change needed for this patch to work. I tried to fully detail this in the inline comments. Let me know if you have any questions.

lldb/tools/lldb-vscode/lldb-vscode.cpp
1609

Remove this line. I will explain more in next inline comment!

1612

yes, we need to set this correctly if it isn't set in the launch.json and your suggestion makes sense.

1624

So I am not sure we need to do all of the response handler stuff since we need to get the result of this before we can proceed. So we need to be able to send this request and receive the response right here. So we need to add that ability to VSCode (the type definition of g_vsc).

This code should be:

llvm::json::Object reverseResponse = g_vsc.SendReverseRequest(llvm::json::Value(std::move(reverseRequest)));

Then we need to extract the pid from the response right in this function. Right now we don't have a thread that is listening for more packets, we just have the main thread getting the current packet ("launch" in this case) and we are currently handling this function, so there is no way we will get a response in this function if we don't do this.

You will need to write the "SendReverseRequest()" function in VSCode and it will rely on refactoring that I mentioned below in the main() function where we make VSCode::GetObject() and VSCode::HandleObject() and uses the new "PacketStatus" enum. The SendReverseRequest function will do something like:

struct VSCode {
...
  uint32_t reverse_request_seq = 0;  
  PacketStatus SendReverseRequest(const llvm::json::Value &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(request);
    bool got_response = false;
    while (!got_response) {
      llvm::json::Object response;
      Status status = GetObject(response);
      if (status == PacketStatus::Success) {
        const auto packet_type = GetString(response, "type");
        if (packet_type == "response")
          return status;
        // Not our response, we got another packet
       HandleObject(response)
      } else {
        return status;
      }
    }
  }

The "VSCode::GetResponse" is a new function as well that we will make from

2996–3006

We need to move these lines into VSCode.h and VSCode.cpp into a function called GetObject():

enum class PacketStatus {
  Success = 0
  EndOfFile,
  JSONMalformed,
  JSONNotObject
};

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 = std::move(json_value->getAsObject());
  if (!object) {
    if (log)
      *log << "error: json packet isn't a object" << std::endl;
    return PacketStatus::JSONNotObject;
  }
  return PacketStatus::Success;
}

This will allow us to use this function in the VSCode::SendReverseRequest(). Then this code becomes:

llvm::json::Object object;
PacketStatus status = g_vsc.GetObject(object);
if (status == PacketStatus::EndOfFile)
  break;
if (status != PacketStatus::Success)
  return 1; // Fatal error

We need to do this so we can re-use the VSCode::GetObject in VSCode::SendReverseRequest().

3005–3006

This should also be moved into VSCode.h/VSCode.cpp:

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
}

}

Then this code becomes:

if (!g_vsc.HandleObject(object))

return 1;
clayborg added inline comments.Jul 30 2020, 4:51 PM
lldb/tools/lldb-vscode/lldb-vscode.cpp
1615

The response has the process ID of what we need to attach to right? Why do we need to do this by name?

Add a attacher thread to avoid race condition

Remove uncomplete tests

Harbormaster completed remote builds in B68453: Diff 285725.
clayborg requested changes to this revision.Aug 14 2020, 4:32 PM

So there is a lot of noise in this patch that is just reformatting on code that hasn't changed. It would be nice to get rid of any changes that are whitespace/indentation/formatting only. Also see inlined comments for needed changes.

lldb/tools/lldb-vscode/VSCode.cpp
390

add "const" to before "llvm::json::Object &request"

lldb/tools/lldb-vscode/VSCode.h
170–177

Revert all changes to this function. Seems like there are some forward declarations to functions in here for some reason that aren't needed.

lldb/tools/lldb-vscode/lldb-vscode.cpp
1446

Add more comment here to explain how we are attaching. Maybe something like:

// We have already created a target that has a valid "program" path to the executable.
// We will attach to the next process whose basename matches that of the target's 
// executable by waiting for the next process to launch that matches. This help LLDB
// ignore any existing processes that are already running that match this executable
// name and wait for the next one to launch.
1453–1454

Inline the lambda, add a comment, and enable async mode to ensure we don't have a race condition:

// Here we launch a thread to do the attach. We disable async events so
// that when we call g_vsc.target.Attach(...) it will not return unless the attach
// succeeds. In the event the attach never happens, the user will be able to hit
// the square stop button in the debug session and it will kill lldb-vscode. If
// this ever changes in the IDE we will need to do more work to ensure we can
// nicely timeout from the attach after a set period of time.
std::thread attacher([&]() { 
   g_vsc.debugger.SetAsync(false);
   g_vsc.target.Attach(attach_info, error); 
   g_vsc.debugger.SetAsync(true);
});
1491–1511

Need to clean up this logic a bit. We have two "if (error.Success())" statements and the flow of the code is hard to follow.

if (error.Success()) {
  // IDE doesn't respond back the pid of the target from the runInTerminal 
  // response, so we have lldb attach by name and wait, so grab the process
  // ID from the target.
  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);
}

// Check error again as it might have been modified with a new error above.
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);
}
1605–1610

Remove all new indentation that was added for the else scope and use early return:

if (GetBoolean(arguments, "launchInTerminal", false)) {
  request_runInTerminal(request, response);
  g_vsc.SendJSON(llvm::json::Value(std::move(response)));
  return;
}
This revision now requires changes to proceed.Aug 14 2020, 4:32 PM
wallace retitled this revision from [WIP] Enable Launching the Debugee in VSCode Terminal to Enable Launching the Debugee in VSCode Terminal.Aug 16 2020, 11:16 PM
clayborg added inline comments.Aug 18 2020, 1:05 PM
lldb/tools/lldb-vscode/VSCode.h
177

I thought this was the contents of a "lldb::SBTarget CreateTargetFromArguments(const llvm::json::Object &arguments, lldb::SBError &error);" function!

Ignore this bad comment!

clayborg added inline comments.Aug 18 2020, 1:30 PM
lldb/tools/lldb-vscode/VSCode.cpp
390

I see we are modifying "request" below, so we can't make it "const". There are two ways to fix this:
1 - change it to be "llvm::json::Object request" and then use std::move() when calling SendReverseRequest.
2 - just leave as a reference and modify the original object.

I think I prefer option #1.

clayborg added inline comments.Aug 18 2020, 1:41 PM
lldb/tools/lldb-vscode/VSCode.cpp
390

Or we can leave this as "const" as originally suggested and add a function to VSCode:

llvm::json::Object VSCode::CreateReverseRequest(std::string command) {
  llvm::json::Object request;
  request.try_emplace("type", "request");
  request.try_emplace("command", command);
  request.try_emplace("seq", ++reverse_request_seq);
  return request;
}

And call that in request_runInTerminal when creating the reverse request packet. See other inline comment for details.

lldb/tools/lldb-vscode/lldb-vscode.cpp
1458–1460

This could become:

llvm::json::Object reverseRequest = g_vsc.CreateReverseRequest("runInTerminal");

And then we wouldn't need to add the "seq" key/value pair in SendReverseRequest

Refactor code and optimize logic

I tried rebase onto the commit that Walter created just for formatting but I am not sure why it didn't remove those formatting noise..

lldb/tools/lldb-vscode/VSCode.cpp
390

I eventually choose #1 cuz const keeps raising problem when calling function like try_emplace() and move() :(

clayborg requested changes to this revision.Aug 19 2020, 11:46 AM

All of the code looks good now and we need to add a test. This means we will need to modify vscode.py in the test suite to be able to received the reverse request and just launch the process. We don't need to launch the program in a terminal, just spawn the process using subprocess.

This revision now requires changes to proceed.Aug 19 2020, 11:46 AM

@Walter Please help me fix the tests. Looks like in the new added runInTerminal tests, it cannot hit the breakpoint and capture the local variable.

wallace commandeered this revision.Aug 20 2020, 2:01 PM
wallace edited reviewers, added: aelitashen; removed: wallace.

We found a very strange issue with lldb not stopping at any breakpoint after attaching. I'll figure that out

wallace updated this revision to Diff 289542.EditedSep 2 2020, 12:33 PM

Now this feature is fully functional. Besiding adding a test, I was able to debug LLDB from an integrated terminal in VSCode. Stdin and stdout work correctly.

This is LLDB being debugger

Regarding the implementation, I'm attaching asynchronously to the inferior using the wait-for logic. As soon as the first stop event happens, the response from the launch request is sent to the IDE to finish the configuration.

As a last detail, when the debug session ends, VSCode closes the terminal.

clayborg accepted this revision.Sep 2 2020, 2:34 PM
This revision is now accepted and ready to land.Sep 2 2020, 2:34 PM

the added test TestVSCode_runInTerminal consistently fails for me locally with

Failed to attach to the target process. Timed out trying to get messages from the runInTerminal launcher

I've asked other people and they've also reported consistent (or flaky) failures of this test. Any idea of what could be going wrong?

Herald added a project: Restricted Project. · View Herald TranscriptNov 2 2022, 3:16 PM
labath added a subscriber: labath.Nov 3 2022, 7:19 AM

I'm pretty sure that's because YAMA prevents the attach operation. The test probably needs to launch the binary through something like this.