Skip to content

Commit aff8b19

Browse files
committed
Adding unit tests and included the 'path' on the frames with disassembly formatted like '<module><symbol>' for example '/usr/lib/system/libsystem_c.dylib_qsort'.
1 parent fad22ce commit aff8b19

File tree

8 files changed

+204
-24
lines changed

8 files changed

+204
-24
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: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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_stackTrace(self):
16+
"""
17+
Tests the 'source' packet.
18+
"""
19+
program = self.getBuildArtifact("a.out")
20+
self.build_and_launch(program)
21+
source = "main.c"
22+
self.source_path = os.path.join(os.getcwd(), source)
23+
self.qsort_call = line_number(source, "qsort call")
24+
25+
lines = [self.qsort_call]
26+
breakpoint_ids = self.set_source_breakpoints(source, lines)
27+
self.assertEqual(
28+
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
29+
)
30+
31+
self.continue_to_breakpoints(breakpoint_ids)
32+
33+
response = self.dap_server.request_source(sourceReference=0)
34+
self.assertFalse(response["success"], "verify invalid sourceReference fails")
35+
36+
(stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount()
37+
frameCount = len(stackFrames)
38+
self.assertGreaterEqual(
39+
frameCount, 3, "verify we get frames from system librarys (libc qsort)"
40+
)
41+
self.assertEqual(
42+
totalFrames,
43+
frameCount,
44+
"verify total frames returns a speculative page size",
45+
)
46+
expectedFrames = [
47+
{
48+
"name": "comp",
49+
"line": 14,
50+
"sourceName": "main.c",
51+
"containsSourceReference": False,
52+
},
53+
{"name": "qsort", "sourceName": "qsort", "containsSourceReference": True},
54+
{
55+
"name": "main",
56+
"line": 25,
57+
"sourceName": "main.c",
58+
"containsSourceReference": False,
59+
},
60+
]
61+
for idx, expected in enumerate(expectedFrames):
62+
frame = stackFrames[idx]
63+
frame_name = self.get_dict_value(frame, ["name"])
64+
self.assertRegex(frame_name, expected["name"])
65+
source_name = self.get_dict_value(frame, ["source", "name"])
66+
self.assertRegex(source_name, expected["sourceName"])
67+
if expected["containsSourceReference"]:
68+
sourceReference = self.get_dict_value(
69+
frame, ["source", "sourceReference"]
70+
)
71+
response = self.dap_server.request_source(
72+
sourceReference=sourceReference
73+
)
74+
self.assertTrue(response["success"])
75+
self.assertGreater(
76+
len(self.get_dict_value(response, ["body", "content"])),
77+
0,
78+
"verify content returned",
79+
)
80+
self.assertEqual(
81+
self.get_dict_value(response, ["body", "mimeType"]),
82+
"text/x-lldb.disassembly",
83+
"verify mime type returned",
84+
)
85+
else:
86+
self.assertNotIn("sourceReference", frame["source"])
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <unistd.h>
4+
5+
int comp(const void *first, const void *second) {
6+
const int a = *((const int *)first);
7+
const int b = *((const int *)second);
8+
if (a == b) // qsort call
9+
return 0;
10+
if (a > b)
11+
return 1;
12+
return -1;
13+
}
14+
15+
int main(int argc, char const *argv[]) {
16+
int numbers[] = {4, 5, 2, 3, 1, 0, 9, 8, 6, 7};
17+
qsort(numbers, sizeof(numbers) / sizeof(int), sizeof(int), comp);
18+
19+
return 0;
20+
}

lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ def test_stackTrace(self):
6969
self.recurse_end = line_number(source, "recurse end")
7070
self.recurse_call = line_number(source, "recurse call")
7171
self.recurse_invocation = line_number(source, "recurse invocation")
72+
self.qsort_call = line_number(source, "qsort call")
7273

73-
lines = [self.recurse_end]
74+
lines = [self.recurse_end, self.qsort_call]
7475

7576
# Set breakpoint at a point of deepest recuusion
7677
breakpoint_ids = self.set_source_breakpoints(source, lines)
@@ -195,14 +196,53 @@ def test_stackTrace(self):
195196
)
196197
self.verify_stackFrames(startFrame, stackFrames)
197198

198-
# Verify we get not frames when startFrame is too high
199+
# Verify we do not recive frames when startFrame is out of range
199200
startFrame = 1000
200201
levels = 1
201202
stackFrames = self.get_stackFrames(startFrame=startFrame, levels=levels)
202203
self.assertEqual(
203204
0, len(stackFrames), "verify zero frames with startFrame out of bounds"
204205
)
205206

207+
# Verify a stack frame from an external library (libc`qsort) to ensure
208+
# frames without source code return a valid source reference.
209+
self.continue_to_breakpoints(breakpoint_ids)
210+
(stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount()
211+
frameCount = len(stackFrames)
212+
self.assertGreaterEqual(
213+
frameCount, 3, "verify we get frames from system librarys (libc qsort)"
214+
)
215+
self.assertEqual(
216+
totalFrames,
217+
frameCount,
218+
"verify total frames returns a speculative page size",
219+
)
220+
expectedFrames = [
221+
{
222+
"name": "comp",
223+
"line": 14,
224+
"sourceName": "main.c",
225+
"containsSourceReference": False,
226+
},
227+
{"name": "qsort", "sourceName": "qsort", "containsSourceReference": True},
228+
{
229+
"name": "main",
230+
"line": 25,
231+
"sourceName": "main.c",
232+
"containsSourceReference": False,
233+
},
234+
]
235+
for idx, expected in enumerate(expectedFrames):
236+
frame = stackFrames[idx]
237+
frame_name = self.get_dict_value(frame, ["name"])
238+
self.assertRegex(frame_name, expected["name"])
239+
source_name = self.get_dict_value(frame, ["source", "name"])
240+
self.assertRegex(source_name, expected["sourceName"])
241+
if expected["containsSourceReference"]:
242+
self.assertIn("sourceReference", frame["source"])
243+
else:
244+
self.assertNotIn("sourceReference", frame["source"])
245+
206246
@skipIfWindows
207247
def test_functionNameWithArgs(self):
208248
"""

lldb/test/API/tools/lldb-dap/stackTrace/main.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <stdio.h>
2+
#include <stdlib.h>
23
#include <unistd.h>
34

45
int recurse(int x) {
@@ -7,7 +8,21 @@ int recurse(int x) {
78
return recurse(x - 1) + x; // recurse call
89
}
910

11+
int comp(const void *first, const void *second) {
12+
const int a = *((const int *)first);
13+
const int b = *((const int *)second);
14+
if (a == b) // qsort call
15+
return 0;
16+
if (a > b)
17+
return 1;
18+
return -1;
19+
}
20+
1021
int main(int argc, char const *argv[]) {
1122
recurse(40); // recurse invocation
23+
24+
int numbers[] = {4, 5, 2, 3, 1, 0, 9, 8, 6, 7};
25+
qsort(numbers, sizeof(numbers) / sizeof(int), sizeof(int), comp);
26+
1227
return 0;
1328
}

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "llvm/Support/Compiler.h"
4242
#include "llvm/Support/Format.h"
4343
#include "llvm/Support/FormatVariadic.h"
44+
#include "llvm/Support/JSON.h"
4445
#include "llvm/Support/Path.h"
4546
#include "llvm/Support/ScopedPrinter.h"
4647
#include "llvm/Support/raw_ostream.h"
@@ -50,6 +51,7 @@
5051
#include <optional>
5152
#include <sstream>
5253
#include <string>
54+
#include <sys/syslimits.h>
5355
#include <utility>
5456
#include <vector>
5557

@@ -697,24 +699,6 @@ llvm::json::Value CreateSource(llvm::StringRef source_path) {
697699
return llvm::json::Value(std::move(source));
698700
}
699701

700-
static llvm::json::Value CreateSource(lldb::SBFrame &frame,
701-
llvm::StringRef frame_name) {
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-
llvm::json::Object source;
709-
EmplaceSafeString(source, "name", frame_name);
710-
source.try_emplace("sourceReference", MakeDAPFrameID(frame));
711-
// If we don't have a filespec then we don't have the original source. Mark
712-
// the source as deemphasized since users will only be able to view assembly
713-
// for these frames.
714-
EmplaceSafeString(source, "presentationHint", "deemphasize");
715-
return std::move(source);
716-
}
717-
718702
// "StackFrame": {
719703
// "type": "object",
720704
// "description": "A Stackframe contains the source location.",
@@ -806,20 +790,38 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame,
806790

807791
EmplaceSafeString(object, "name", frame_name);
808792

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

lldb/tools/lldb-dap/lldb-dap.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include <algorithm>
4747
#include <array>
4848
#include <condition_variable>
49+
#include <climits>
4950
#include <cstdint>
5051
#include <cstdio>
5152
#include <cstdlib>

0 commit comments

Comments
 (0)