Skip to content

[lldb-dap] Add 'source' references to stack frames without source files. #128268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,19 @@ def request_stackTrace(
print("[%3u] %s" % (idx, name))
return response

def request_source(self, sourceReference):
"""Request a source from a 'Source' reference."""
command_dict = {
"command": "source",
"type": "request",
"arguments": {
"source": {"sourceReference": sourceReference},
# legacy version of the request
"sourceReference": sourceReference,
},
}
return self.send_recv(command_dict)

def request_threads(self):
"""Request a list of all threads and combine any information from any
"stopped" events since those contain more information about why a
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/tools/lldb-dap/source/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
C_SOURCES := main.c

include Makefile.rules
106 changes: 106 additions & 0 deletions lldb/test/API/tools/lldb-dap/source/TestDAP_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
Test lldb-dap source request
"""


import os

import lldbdap_testcase
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *


class TestDAP_source(lldbdap_testcase.DAPTestCaseBase):
@skipIfWindows
def test_source(self):
"""
Tests the 'source' packet.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
source = self.getSourcePath("main.c")
breakpoint_line = line_number(source, "breakpoint")

lines = [breakpoint_line]
breakpoint_ids = self.set_source_breakpoints(source, lines)
self.assertEqual(
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
)

self.continue_to_breakpoints(breakpoint_ids)

response = self.dap_server.request_source(sourceReference=0)
self.assertFalse(response["success"], "verify invalid sourceReference fails")

(stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount()
frameCount = len(stackFrames)
self.assertGreaterEqual(frameCount, 3, "verify we got up to main at least")
self.assertEqual(
totalFrames,
frameCount,
"verify total frames returns a speculative page size",
)
wantFrames = [
{
"name": "handler",
"line": 8,
"source": {
"name": "main.c",
"path": source,
"containsSourceReference": False,
},
},
{
"name": "add",
"source": {
"name": "add",
"path": program + "`add",
"containsSourceReference": True,
},
},
{
"name": "main",
"line": 12,
"source": {
"name": "main.c",
"path": source,
"containsSourceReference": False,
},
},
]
for idx, want in enumerate(wantFrames):
got = stackFrames[idx]
name = self.get_dict_value(got, ["name"])
self.assertEqual(name, want["name"])

if "line" in want:
line = self.get_dict_value(got, ["line"])
self.assertEqual(line, want["line"])

wantSource = want["source"]
source_name = self.get_dict_value(got, ["source", "name"])
self.assertEqual(source_name, wantSource["name"])

source_path = self.get_dict_value(got, ["source", "path"])
self.assertEqual(source_path, wantSource["path"])

if wantSource["containsSourceReference"]:
sourceReference = self.get_dict_value(
got, ["source", "sourceReference"]
)
response = self.dap_server.request_source(
sourceReference=sourceReference
)
self.assertTrue(response["success"])
self.assertGreater(
len(self.get_dict_value(response, ["body", "content"])),
0,
"verify content returned disassembly",
)
self.assertEqual(
self.get_dict_value(response, ["body", "mimeType"]),
"text/x-lldb.disassembly",
"verify mime type returned",
)
else:
self.assertNotIn("sourceReference", got["source"])
14 changes: 14 additions & 0 deletions lldb/test/API/tools/lldb-dap/source/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <stdio.h>

__attribute__((nodebug)) static void add(int i, int j, void handler(int)) {
handler(i + j);
}

static void handler(int result) {
printf("result %d\n", result); // breakpoint
}

int main(int argc, char const *argv[]) {
add(2, 3, handler);
return 0;
}
41 changes: 39 additions & 2 deletions lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@
#include "DAP.h"
#include "EventHelper.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
#include "RequestHandler.h"
#include "lldb/API/SBFrame.h"
#include "lldb/API/SBInstructionList.h"
#include "lldb/API/SBProcess.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBTarget.h"
#include "lldb/API/SBThread.h"
#include "llvm/Support/JSON.h"

namespace lldb_dap {

Expand Down Expand Up @@ -74,8 +82,37 @@ namespace lldb_dap {
void SourceRequestHandler::operator()(const llvm::json::Object &request) {
llvm::json::Object response;
FillResponse(request, response);
llvm::json::Object body{{"content", ""}};
response.try_emplace("body", std::move(body));
const auto *arguments = request.getObject("arguments");
const auto *source = arguments->getObject("source");
llvm::json::Object body;
int64_t source_ref = GetUnsigned(
source, "sourceReference", GetUnsigned(arguments, "sourceReference", 0));

if (source_ref) {
lldb::SBProcess process = dap.target.GetProcess();
// Upper 32 bits is the thread index ID
lldb::SBThread thread =
process.GetThreadByIndexID(GetLLDBThreadIndexID(source_ref));
// Lower 32 bits is the frame index
lldb::SBFrame frame = thread.GetFrameAtIndex(GetLLDBFrameID(source_ref));
if (!frame.IsValid()) {
response["success"] = false;
response["message"] = "source not found";
} else {
lldb::SBInstructionList insts =
frame.GetSymbol().GetInstructions(dap.target);
lldb::SBStream stream;
insts.GetDescription(stream);
body["content"] = stream.GetData();
body["mimeType"] = "text/x-lldb.disassembly";
response.try_emplace("body", std::move(body));
}
} else {
response["success"] = false;
response["message"] =
"invalid arguments, expected source.sourceReference to be set";
}

dap.SendJSON(llvm::json::Value(std::move(response)));
}

Expand Down
58 changes: 33 additions & 25 deletions lldb/tools/lldb-dap/JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//===----------------------------------------------------------------------===//

#include "JSONUtils.h"

#include "BreakpointBase.h"
#include "DAP.h"
#include "ExceptionBreakpoint.h"
Expand Down Expand Up @@ -41,11 +40,11 @@
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/raw_ostream.h"
#include <chrono>
#include <climits>
#include <cstddef>
#include <iomanip>
#include <optional>
Expand Down Expand Up @@ -698,16 +697,6 @@ llvm::json::Value CreateSource(llvm::StringRef source_path) {
return llvm::json::Value(std::move(source));
}

static std::optional<llvm::json::Value> CreateSource(lldb::SBFrame &frame) {
auto line_entry = frame.GetLineEntry();
// A line entry of 0 indicates the line is compiler generated i.e. no source
// file is associated with the frame.
if (line_entry.GetFileSpec().IsValid() && line_entry.GetLine() != 0)
return CreateSource(line_entry);

return {};
}

// "StackFrame": {
// "type": "object",
// "description": "A Stackframe contains the source location.",
Expand Down Expand Up @@ -799,21 +788,40 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame,

EmplaceSafeString(object, "name", frame_name);

auto source = CreateSource(frame);

if (source) {
object.try_emplace("source", *source);
auto line_entry = frame.GetLineEntry();
auto line = line_entry.GetLine();
if (line && line != LLDB_INVALID_LINE_NUMBER)
object.try_emplace("line", line);
else
object.try_emplace("line", 0);
auto line_entry = frame.GetLineEntry();
// A line entry of 0 indicates the line is compiler generated i.e. no source
// file is associated with the frame.
if (line_entry.GetFileSpec().IsValid() &&
(line_entry.GetLine() != 0 ||
line_entry.GetLine() != LLDB_INVALID_LINE_NUMBER)) {
object.try_emplace("source", CreateSource(line_entry));
object.try_emplace("line", line_entry.GetLine());
auto column = line_entry.GetColumn();
object.try_emplace("column", column);
} else {
object.try_emplace("line", 0);
object.try_emplace("column", 0);
} else if (frame.GetSymbol().IsValid()) {
// If no source is associated with the frame, use the DAPFrameID to track
// the 'source' and generate assembly.
llvm::json::Object source;
EmplaceSafeString(source, "name", frame_name);
char buf[PATH_MAX] = {0};
size_t size = frame.GetModule().GetFileSpec().GetPath(buf, PATH_MAX);
EmplaceSafeString(source, "path",
std::string(buf, size) + '`' + frame_name);
source.try_emplace("sourceReference", MakeDAPFrameID(frame));
// Mark the source as deemphasized since users will only be able to view
// assembly for these frames.
EmplaceSafeString(source, "presentationHint", "deemphasize");
object.try_emplace("source", std::move(source));

// Calculate the line of the current PC from the start of the current
// symbol.
lldb::addr_t inst_offset = frame.GetPCAddress().GetOffset() -
frame.GetSymbol().GetStartAddress().GetOffset();
lldb::addr_t inst_line =
inst_offset / (frame.GetThread().GetProcess().GetAddressByteSize() / 2);
// Line numbers are 1-based.
object.try_emplace("line", inst_line + 1);
object.try_emplace("column", 1);
}

const auto pc = frame.GetPC();
Expand Down