Skip to content

Commit 162eb32

Browse files
ashgtiJDevlieghere
andauthored
[lldb-dap] Add 'source' references to stack frames without source files. (#128268)
This adds 'source' references to all stack frames. When opening a stack frame users will see the disassembly of the frame if the source is not available. This works around the odd behavior of navigating frames without the VSCode disassembly view open, which causes 'step' to step in the first frame with a source instead of the active frame. This fixes #128260 Old behavior: https://github.com/user-attachments/assets/3f40582d-ac96-451a-a5ae-498a323bf30e New behavior: https://github.com/user-attachments/assets/3a3f9ac6-3e6c-4795-9bb2-1132b3916b6f --------- Co-authored-by: Jonas Devlieghere <[email protected]>
1 parent 6d0cfbc commit 162eb32

File tree

6 files changed

+208
-27
lines changed

6 files changed

+208
-27
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,19 @@ def request_stackTrace(
10681068
print("[%3u] %s" % (idx, name))
10691069
return response
10701070

1071+
def request_source(self, sourceReference):
1072+
"""Request a source from a 'Source' reference."""
1073+
command_dict = {
1074+
"command": "source",
1075+
"type": "request",
1076+
"arguments": {
1077+
"source": {"sourceReference": sourceReference},
1078+
# legacy version of the request
1079+
"sourceReference": sourceReference,
1080+
},
1081+
}
1082+
return self.send_recv(command_dict)
1083+
10711084
def request_threads(self):
10721085
"""Request a list of all threads and combine any information from any
10731086
"stopped" events since those contain more information about why a
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""
2+
Test lldb-dap source request
3+
"""
4+
5+
6+
import os
7+
8+
import lldbdap_testcase
9+
from lldbsuite.test.decorators import *
10+
from lldbsuite.test.lldbtest import *
11+
12+
13+
class TestDAP_source(lldbdap_testcase.DAPTestCaseBase):
14+
@skipIfWindows
15+
def test_source(self):
16+
"""
17+
Tests the 'source' packet.
18+
"""
19+
program = self.getBuildArtifact("a.out")
20+
self.build_and_launch(program)
21+
source = self.getSourcePath("main.c")
22+
breakpoint_line = line_number(source, "breakpoint")
23+
24+
lines = [breakpoint_line]
25+
breakpoint_ids = self.set_source_breakpoints(source, lines)
26+
self.assertEqual(
27+
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
28+
)
29+
30+
self.continue_to_breakpoints(breakpoint_ids)
31+
32+
response = self.dap_server.request_source(sourceReference=0)
33+
self.assertFalse(response["success"], "verify invalid sourceReference fails")
34+
35+
(stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount()
36+
frameCount = len(stackFrames)
37+
self.assertGreaterEqual(frameCount, 3, "verify we got up to main at least")
38+
self.assertEqual(
39+
totalFrames,
40+
frameCount,
41+
"verify total frames returns a speculative page size",
42+
)
43+
wantFrames = [
44+
{
45+
"name": "handler",
46+
"line": 8,
47+
"source": {
48+
"name": "main.c",
49+
"path": source,
50+
"containsSourceReference": False,
51+
},
52+
},
53+
{
54+
"name": "add",
55+
"source": {
56+
"name": "add",
57+
"path": program + "`add",
58+
"containsSourceReference": True,
59+
},
60+
},
61+
{
62+
"name": "main",
63+
"line": 12,
64+
"source": {
65+
"name": "main.c",
66+
"path": source,
67+
"containsSourceReference": False,
68+
},
69+
},
70+
]
71+
for idx, want in enumerate(wantFrames):
72+
got = stackFrames[idx]
73+
name = self.get_dict_value(got, ["name"])
74+
self.assertEqual(name, want["name"])
75+
76+
if "line" in want:
77+
line = self.get_dict_value(got, ["line"])
78+
self.assertEqual(line, want["line"])
79+
80+
wantSource = want["source"]
81+
source_name = self.get_dict_value(got, ["source", "name"])
82+
self.assertEqual(source_name, wantSource["name"])
83+
84+
source_path = self.get_dict_value(got, ["source", "path"])
85+
self.assertEqual(source_path, wantSource["path"])
86+
87+
if wantSource["containsSourceReference"]:
88+
sourceReference = self.get_dict_value(
89+
got, ["source", "sourceReference"]
90+
)
91+
response = self.dap_server.request_source(
92+
sourceReference=sourceReference
93+
)
94+
self.assertTrue(response["success"])
95+
self.assertGreater(
96+
len(self.get_dict_value(response, ["body", "content"])),
97+
0,
98+
"verify content returned disassembly",
99+
)
100+
self.assertEqual(
101+
self.get_dict_value(response, ["body", "mimeType"]),
102+
"text/x-lldb.disassembly",
103+
"verify mime type returned",
104+
)
105+
else:
106+
self.assertNotIn("sourceReference", got["source"])
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <stdio.h>
2+
3+
__attribute__((nodebug)) static void add(int i, int j, void handler(int)) {
4+
handler(i + j);
5+
}
6+
7+
static void handler(int result) {
8+
printf("result %d\n", result); // breakpoint
9+
}
10+
11+
int main(int argc, char const *argv[]) {
12+
add(2, 3, handler);
13+
return 0;
14+
}

lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99
#include "DAP.h"
1010
#include "EventHelper.h"
1111
#include "JSONUtils.h"
12+
#include "LLDBUtils.h"
1213
#include "RequestHandler.h"
14+
#include "lldb/API/SBFrame.h"
15+
#include "lldb/API/SBInstructionList.h"
16+
#include "lldb/API/SBProcess.h"
17+
#include "lldb/API/SBStream.h"
18+
#include "lldb/API/SBTarget.h"
19+
#include "lldb/API/SBThread.h"
20+
#include "llvm/Support/JSON.h"
1321

1422
namespace lldb_dap {
1523

@@ -74,8 +82,37 @@ namespace lldb_dap {
7482
void SourceRequestHandler::operator()(const llvm::json::Object &request) const {
7583
llvm::json::Object response;
7684
FillResponse(request, response);
77-
llvm::json::Object body{{"content", ""}};
78-
response.try_emplace("body", std::move(body));
85+
const auto *arguments = request.getObject("arguments");
86+
const auto *source = arguments->getObject("source");
87+
llvm::json::Object body;
88+
int64_t source_ref = GetUnsigned(
89+
source, "sourceReference", GetUnsigned(arguments, "sourceReference", 0));
90+
91+
if (source_ref) {
92+
lldb::SBProcess process = dap.target.GetProcess();
93+
// Upper 32 bits is the thread index ID
94+
lldb::SBThread thread =
95+
process.GetThreadByIndexID(GetLLDBThreadIndexID(source_ref));
96+
// Lower 32 bits is the frame index
97+
lldb::SBFrame frame = thread.GetFrameAtIndex(GetLLDBFrameID(source_ref));
98+
if (!frame.IsValid()) {
99+
response["success"] = false;
100+
response["message"] = "source not found";
101+
} else {
102+
lldb::SBInstructionList insts =
103+
frame.GetSymbol().GetInstructions(dap.target);
104+
lldb::SBStream stream;
105+
insts.GetDescription(stream);
106+
body["content"] = stream.GetData();
107+
body["mimeType"] = "text/x-lldb.disassembly";
108+
response.try_emplace("body", std::move(body));
109+
}
110+
} else {
111+
response["success"] = false;
112+
response["message"] =
113+
"invalid arguments, expected source.sourceReference to be set";
114+
}
115+
79116
dap.SendJSON(llvm::json::Value(std::move(response)));
80117
}
81118

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "JSONUtils.h"
10-
1110
#include "BreakpointBase.h"
1211
#include "DAP.h"
1312
#include "ExceptionBreakpoint.h"
@@ -41,11 +40,11 @@
4140
#include "llvm/Support/Compiler.h"
4241
#include "llvm/Support/Format.h"
4342
#include "llvm/Support/FormatVariadic.h"
43+
#include "llvm/Support/JSON.h"
4444
#include "llvm/Support/Path.h"
4545
#include "llvm/Support/ScopedPrinter.h"
4646
#include "llvm/Support/raw_ostream.h"
4747
#include <chrono>
48-
#include <climits>
4948
#include <cstddef>
5049
#include <iomanip>
5150
#include <optional>
@@ -698,16 +697,6 @@ llvm::json::Value CreateSource(llvm::StringRef source_path) {
698697
return llvm::json::Value(std::move(source));
699698
}
700699

701-
static std::optional<llvm::json::Value> CreateSource(lldb::SBFrame &frame) {
702-
auto line_entry = frame.GetLineEntry();
703-
// A line entry of 0 indicates the line is compiler generated i.e. no source
704-
// file is associated with the frame.
705-
if (line_entry.GetFileSpec().IsValid() && line_entry.GetLine() != 0)
706-
return CreateSource(line_entry);
707-
708-
return {};
709-
}
710-
711700
// "StackFrame": {
712701
// "type": "object",
713702
// "description": "A Stackframe contains the source location.",
@@ -799,21 +788,40 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame,
799788

800789
EmplaceSafeString(object, "name", frame_name);
801790

802-
auto source = CreateSource(frame);
803-
804-
if (source) {
805-
object.try_emplace("source", *source);
806-
auto line_entry = frame.GetLineEntry();
807-
auto line = line_entry.GetLine();
808-
if (line && line != LLDB_INVALID_LINE_NUMBER)
809-
object.try_emplace("line", line);
810-
else
811-
object.try_emplace("line", 0);
791+
auto line_entry = frame.GetLineEntry();
792+
// A line entry of 0 indicates the line is compiler generated i.e. no source
793+
// file is associated with the frame.
794+
if (line_entry.GetFileSpec().IsValid() &&
795+
(line_entry.GetLine() != 0 ||
796+
line_entry.GetLine() != LLDB_INVALID_LINE_NUMBER)) {
797+
object.try_emplace("source", CreateSource(line_entry));
798+
object.try_emplace("line", line_entry.GetLine());
812799
auto column = line_entry.GetColumn();
813800
object.try_emplace("column", column);
814-
} else {
815-
object.try_emplace("line", 0);
816-
object.try_emplace("column", 0);
801+
} else if (frame.GetSymbol().IsValid()) {
802+
// If no source is associated with the frame, use the DAPFrameID to track
803+
// the 'source' and generate assembly.
804+
llvm::json::Object source;
805+
EmplaceSafeString(source, "name", frame_name);
806+
char buf[PATH_MAX] = {0};
807+
size_t size = frame.GetModule().GetFileSpec().GetPath(buf, PATH_MAX);
808+
EmplaceSafeString(source, "path",
809+
std::string(buf, size) + '`' + frame_name);
810+
source.try_emplace("sourceReference", MakeDAPFrameID(frame));
811+
// Mark the source as deemphasized since users will only be able to view
812+
// assembly for these frames.
813+
EmplaceSafeString(source, "presentationHint", "deemphasize");
814+
object.try_emplace("source", std::move(source));
815+
816+
// Calculate the line of the current PC from the start of the current
817+
// symbol.
818+
lldb::addr_t inst_offset = frame.GetPCAddress().GetOffset() -
819+
frame.GetSymbol().GetStartAddress().GetOffset();
820+
lldb::addr_t inst_line =
821+
inst_offset / (frame.GetThread().GetProcess().GetAddressByteSize() / 2);
822+
// Line numbers are 1-based.
823+
object.try_emplace("line", inst_line + 1);
824+
object.try_emplace("column", 1);
817825
}
818826

819827
const auto pc = frame.GetPC();

0 commit comments

Comments
 (0)