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.
Details
- Reviewers
clayborg • aelitashen - Commits
- rGde6caf871be7: run in terminal
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
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 | ||
---|---|---|
1377 | s/VScode/VSCode | |
1582–1584 | move the entire contents of this if to another function for readability | |
1586 | does it work if you don't specify the "seq" number? If it works without it, I would leave it empty | |
1589 | 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. | |
1590 | 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 | |
1595–1598 | run clang-format | |
1602 | remove this comment | |
1602 | shouldn't you return at this point and not execute anything that happens after this? 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. | |
1633–1634 | 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? | |
1642 | remove this | |
3029 | you should use the command here, instead of the hardcoded string "runInTerminal" |
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 | ||
---|---|---|
1586 | Remove this line. I will explain more in next inline comment! | |
1589 | yes, we need to set this correctly if it isn't set in the launch.json and your suggestion makes sense. | |
1601 | 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 | |
3016–3026 | 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(). | |
3025–3026 | 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; |
lldb/tools/lldb-vscode/lldb-vscode.cpp | ||
---|---|---|
1590 | The response has the process ID of what we need to attach to right? Why do we need to do this by name? |
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 | ||
---|---|---|
410 | add "const" to before "llvm::json::Object &request" | |
lldb/tools/lldb-vscode/VSCode.h | ||
166–173 | 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 | ||
1441 | 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. | |
1448–1449 | 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); }); | |
1486–1506 | 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); } | |
1582–1585 | 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; } |
lldb/tools/lldb-vscode/VSCode.h | ||
---|---|---|
173 | I thought this was the contents of a "lldb::SBTarget CreateTargetFromArguments(const llvm::json::Object &arguments, lldb::SBError &error);" function! Ignore this bad comment! |
lldb/tools/lldb-vscode/VSCode.cpp | ||
---|---|---|
410 | I see we are modifying "request" below, so we can't make it "const". There are two ways to fix this: I think I prefer option #1. |
lldb/tools/lldb-vscode/VSCode.cpp | ||
---|---|---|
410 | 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 | ||
1453–1455 | 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 |
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 | ||
---|---|---|
410 | I eventually choose #1 cuz const keeps raising problem when calling function like try_emplace() and move() :( |
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.
@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.
We found a very strange issue with lldb not stopping at any breakpoint after attaching. I'll figure that out
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.
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?
I'm pretty sure that's because YAMA prevents the attach operation. The test probably needs to launch the binary through something like this.
clang-tidy: error: 'lldb/API/SBAttachInfo.h' file not found [clang-diagnostic-error]
not useful
clang-tidy: error: 'lldb/API/SBAttachInfo.h' file not found [clang-diagnostic-error]
not useful