Skip to content

Commit ecfd5b1

Browse files
committed
Restructuring the stacktrace generation code to use more helpers and to simplify the implementation.
1 parent 5d49b8e commit ecfd5b1

File tree

4 files changed

+165
-107
lines changed

4 files changed

+165
-107
lines changed

lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,56 @@ def test_stackTrace(self):
4949

5050
events = self.continue_to_next_stop()
5151

52-
stackFrames = self.get_stackFrames(threadId=events[0]["body"]["threadId"])
52+
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
53+
threadId=events[0]["body"]["threadId"]
54+
)
5355
self.assertGreaterEqual(len(stackFrames), 3, "expect >= 3 frames")
56+
self.assertEqual(len(stackFrames), totalFrames)
5457
self.assertEqual(stackFrames[0]["name"], "one")
5558
self.assertEqual(stackFrames[1]["name"], "two")
5659
self.assertEqual(stackFrames[2]["name"], "three")
5760

5861
stackLabels = [
59-
frame
60-
for frame in stackFrames
62+
(i, frame)
63+
for i, frame in enumerate(stackFrames)
6164
if frame.get("presentationHint", "") == "label"
6265
]
6366
self.assertEqual(len(stackLabels), 2, "expected two label stack frames")
6467
self.assertRegex(
65-
stackLabels[0]["name"],
68+
stackLabels[0][1]["name"],
6669
"Enqueued from com.apple.root.default-qos \(Thread \d\)",
6770
)
6871
self.assertRegex(
69-
stackLabels[1]["name"], "Enqueued from com.apple.main-thread \(Thread \d\)"
72+
stackLabels[1][1]["name"],
73+
"Enqueued from com.apple.main-thread \(Thread \d\)",
7074
)
75+
76+
for i, frame in stackLabels:
77+
# Ensure requesting startFrame+levels across thread backtraces works as expected.
78+
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
79+
threadId=events[0]["body"]["threadId"], startFrame=i - 1, levels=3
80+
)
81+
self.assertEqual(len(stackFrames), 3, "expected 3 frames with levels=3")
82+
self.assertGreaterEqual(
83+
totalFrames, i + 3, "total frames should include a pagination offset"
84+
)
85+
self.assertEqual(stackFrames[1], frame)
86+
87+
# Ensure requesting startFrame+levels at the beginning of a thread backtraces works as expected.
88+
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
89+
threadId=events[0]["body"]["threadId"], startFrame=i, levels=3
90+
)
91+
self.assertEqual(len(stackFrames), 3, "expected 3 frames with levels=3")
92+
self.assertGreaterEqual(
93+
totalFrames, i + 3, "total frames should include a pagination offset"
94+
)
95+
self.assertEqual(stackFrames[0], frame)
96+
97+
# Ensure requests with startFrame+levels that end precisely on the last frame includes the totalFrames pagination offset.
98+
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
99+
threadId=events[0]["body"]["threadId"], startFrame=i - 1, levels=1
100+
)
101+
self.assertEqual(len(stackFrames), 1, "expected 1 frames with levels=1")
102+
self.assertGreaterEqual(
103+
totalFrames, i, "total frames should include a pagination offset"
104+
)

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,30 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) {
769769
return llvm::json::Value(std::move(object));
770770
}
771771

772+
llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread) {
773+
std::string name;
774+
lldb::SBStream stream;
775+
if (g_dap.thread_format &&
776+
thread.GetDescriptionWithFormat(g_dap.thread_format, stream).Success()) {
777+
name = stream.GetData();
778+
} else {
779+
const uint32_t thread_idx =
780+
thread.GetExtendedBacktraceOriginatingIndexID();
781+
const char *queue_name = thread.GetQueueName();
782+
if (queue_name != nullptr) {
783+
name = llvm::formatv("Enqueued from {0} (Thread {1})", queue_name,
784+
thread_idx);
785+
} else {
786+
name = llvm::formatv("Thread {0}", thread_idx);
787+
}
788+
}
789+
790+
return llvm::json::Value(
791+
llvm::json::Object{{"id", thread.GetThreadID() + 1},
792+
{"name", name},
793+
{"presentationHint", "label"}});
794+
}
795+
772796
// Response to `setInstructionBreakpoints` request.
773797
// "Breakpoint": {
774798
// "type": "object",

lldb/tools/lldb-dap/JSONUtils.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,25 @@ llvm::json::Value CreateSource(llvm::StringRef source_path);
322322
/// definition outlined by Microsoft.
323323
llvm::json::Value CreateStackFrame(lldb::SBFrame &frame);
324324

325+
/// Create a "StackFrame" label object for a LLDB thread.
326+
///
327+
/// This function will fill in the following keys in the returned
328+
/// object:
329+
/// "id" - the thread ID as an integer
330+
/// "name" - the thread name as a string which combines the LLDB
331+
/// thread index ID along with the string name of the thread
332+
/// from the OS if it has a name.
333+
/// "presentationHint" - "label"
334+
///
335+
/// \param[in] thread
336+
/// The LLDB thread to use when populating out the "Thread"
337+
/// object.
338+
///
339+
/// \return
340+
/// A "StackFrame" JSON object with that follows the formal JSON
341+
/// definition outlined by Microsoft.
342+
llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread);
343+
325344
/// Create a "instruction" object for a LLDB disassemble object as described in
326345
/// the Visual Studio Code debug adaptor definition.
327346
///

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

Lines changed: 83 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,79 @@ void SetSourceMapFromArguments(const llvm::json::Object &arguments) {
641641
}
642642
}
643643

644+
// Fill in the stack frames of the thread.
645+
//
646+
// Threads stacks may contain runtime specific extended backtraces, when
647+
// constructing a stack trace first report the full thread stack trace then
648+
// perform a breadth first traversal of any extended backtrace frames.
649+
//
650+
// For example:
651+
//
652+
// Thread (id=th0) stack=[s0, s1, s2, s3]
653+
// \ Extended backtrace "libdispatch" Thread (id=th1) stack=[s0, s1]
654+
// \ Extended backtrace "libdispatch" Thread (id=th2) stack=[s0, s1]
655+
// \ Extended backtrace "Application Specific Backtrace" Thread (id=th3)
656+
// stack=[s0, s1, s2]
657+
//
658+
// Which will flatten into:
659+
//
660+
// 0. th0->s0
661+
// 1. th0->s1
662+
// 2. th0->s2
663+
// 3. th0->s3
664+
// 4. label - Enqueued from th1, sf=-1, i=-4
665+
// 5. th1->s0
666+
// 6. th1->s1
667+
// 7. label - Enqueued from th2
668+
// 8. th2->s0
669+
// 9. th2->s1
670+
// 10. label - Application Specific Backtrace
671+
// 11. th3->s0
672+
// 12. th3->s1
673+
// 13. th3->s2
674+
//
675+
// s=3,l=3 = [th0->s3, label1, th1->s0]
676+
bool FillStackFrames(lldb::SBThread &thread, llvm::json::Array &stack_frames,
677+
int64_t &offset, const int64_t start_frame,
678+
const int64_t levels) {
679+
bool reached_end_of_stack = false;
680+
for (int64_t i = start_frame;
681+
static_cast<int64_t>(stack_frames.size()) < levels; i++) {
682+
if (i == -1) {
683+
stack_frames.emplace_back(CreateExtendedStackFrameLabel(thread));
684+
continue;
685+
}
686+
687+
lldb::SBFrame frame = thread.GetFrameAtIndex(i);
688+
if (!frame.IsValid()) {
689+
offset += thread.GetNumFrames() + 1 /* label between threads */;
690+
reached_end_of_stack = true;
691+
break;
692+
}
693+
694+
stack_frames.emplace_back(CreateStackFrame(frame));
695+
}
696+
697+
if (g_dap.enable_display_extended_backtrace && reached_end_of_stack) {
698+
// Check for any extended backtraces.
699+
for (uint32_t bt = 0;
700+
bt < thread.GetProcess().GetNumExtendedBacktraceTypes(); bt++) {
701+
lldb::SBThread backtrace = thread.GetExtendedBacktraceThread(
702+
thread.GetProcess().GetExtendedBacktraceTypeAtIndex(bt));
703+
if (!backtrace.IsValid())
704+
continue;
705+
706+
reached_end_of_stack = FillStackFrames(
707+
backtrace, stack_frames, offset,
708+
(start_frame - offset) > 0 ? start_frame - offset : -1, levels);
709+
if (static_cast<int64_t>(stack_frames.size()) >= levels)
710+
break;
711+
}
712+
}
713+
714+
return reached_end_of_stack;
715+
}
716+
644717
// "AttachRequest": {
645718
// "allOf": [ { "$ref": "#/definitions/Request" }, {
646719
// "type": "object",
@@ -3234,114 +3307,22 @@ void request_stackTrace(const llvm::json::Object &request) {
32343307
lldb::SBError error;
32353308
auto arguments = request.getObject("arguments");
32363309
lldb::SBThread thread = g_dap.GetLLDBThread(*arguments);
3237-
llvm::json::Array stackFrames;
3310+
llvm::json::Array stack_frames;
32383311
llvm::json::Object body;
32393312

3240-
// Threads stacks may contain runtime specific extended backtraces, when
3241-
// constructing a stack trace first report the full thread stack trace then
3242-
// perform a breadth first traversal of any extended backtrace frames.
3243-
//
3244-
// For example:
3245-
//
3246-
// Thread (id=th0) stack=[s0, s1, s2, s3]
3247-
// \ Extended backtrace "libdispatch" Thread (id=th1) stack=[s0, s1]
3248-
// \ Extended backtrace "libdispatch" Thread (id=th2) stack=[s0, s1]
3249-
// \ Extended backtrace "Application Specific Backtrace" Thread (id=th3)
3250-
// stack=[s0, s1, s2]
3251-
//
3252-
// Which will flatten into:
3253-
//
3254-
// 0. th0->s0
3255-
// 1. th0->s1
3256-
// 2. th0->s2
3257-
// 3. th0->s3
3258-
// 4. label - Enqueued from th1
3259-
// 5. th1->s0
3260-
// 6. th1->s1
3261-
// 7. label - Enqueued from th2
3262-
// 8. th2->s0
3263-
// 9. th2->s1
3264-
// 10. label - Application Specific Backtrace
3265-
// 11. th3->s0
3266-
// 12. th3->s1
3267-
// 13. th3->s2
3268-
32693313
if (thread.IsValid()) {
3270-
const auto startFrame = GetUnsigned(arguments, "startFrame", 0);
3314+
const auto start_frame = GetUnsigned(arguments, "startFrame", 0);
32713315
const auto levels = GetUnsigned(arguments, "levels", 0);
3272-
const auto endFrame = (levels == 0) ? INT64_MAX : (startFrame + levels);
3273-
bool done = false;
32743316
int64_t offset = 0;
3275-
lldb::SBProcess process = thread.GetProcess();
3276-
llvm::SmallVector<lldb::SBThread> threadCluster{{thread}};
3277-
3278-
for (uint32_t i = startFrame; i < endFrame && !threadCluster.empty(); ++i) {
3279-
lldb::SBThread current = threadCluster.front();
3280-
lldb::SBFrame frame = current.GetFrameAtIndex(i - offset);
3281-
3282-
// If we don't have a valid frame, check if we have any extended frames to
3283-
// report.
3284-
// *NOTE*: Threads can be chained across mutliple backtraces, so we
3285-
// need to keep track of each backtrace we've traversed fully in the
3286-
// offset.
3287-
while (!frame.IsValid() && current.IsValid() && !threadCluster.empty()) {
3288-
offset += current.GetNumFrames() +
3289-
1 /* one extra frame for a label between threads*/;
3290-
threadCluster.pop_back();
3291-
3292-
if (!g_dap.enable_display_extended_backtrace) {
3293-
break;
3294-
}
3295-
3296-
// Check for any extended backtraces.
3297-
for (uint32_t i = 0; i < process.GetNumExtendedBacktraceTypes(); i++) {
3298-
lldb::SBThread backtrace = current.GetExtendedBacktraceThread(
3299-
process.GetExtendedBacktraceTypeAtIndex(i));
3300-
if (backtrace.IsValid()) {
3301-
threadCluster.emplace_back(backtrace);
3302-
}
3303-
}
3304-
3305-
if (threadCluster.empty())
3306-
break;
3307-
3308-
current = threadCluster.front();
3309-
frame = current.GetFrameAtIndex(0);
3310-
}
3311-
3312-
// If we're out of extended backtraces, no more frames to load.
3313-
if (!frame.IsValid() && threadCluster.empty()) {
3314-
done = true;
3315-
break;
3316-
}
3317-
3318-
// Between the thread and extended backtrace add a label.
3319-
if (offset != 0 && (i - offset) == 0) {
3320-
const uint32_t thread_idx =
3321-
current.GetExtendedBacktraceOriginatingIndexID();
3322-
const char *queue_name = current.GetQueueName();
3323-
std::string name;
3324-
if (queue_name != nullptr) {
3325-
name = llvm::formatv("Enqueued from {0} (Thread {1})", queue_name,
3326-
thread_idx);
3327-
} else {
3328-
name = llvm::formatv("Thread {0}", thread_idx);
3329-
}
3330-
stackFrames.emplace_back(
3331-
llvm::json::Object{{"id", thread.GetThreadID() + 1},
3332-
{"name", name},
3333-
{"presentationHint", "label"}});
3334-
} else {
3335-
stackFrames.emplace_back(CreateStackFrame(frame));
3336-
}
3337-
}
3338-
3339-
// If we loaded all the frames, set the total frame to the current total,
3340-
// otherwise use the totalFrames to indicate more data is available.
3341-
body.try_emplace("totalFrames", startFrame + stackFrames.size() +
3342-
(done ? 0 : StackPageSize));
3317+
bool reached_end_of_stack =
3318+
FillStackFrames(thread, stack_frames, offset, start_frame,
3319+
levels == 0 ? INT64_MAX : levels);
3320+
body.try_emplace("totalFrames",
3321+
start_frame + stack_frames.size() +
3322+
(reached_end_of_stack ? 0 : StackPageSize));
33433323
}
3344-
body.try_emplace("stackFrames", std::move(stackFrames));
3324+
3325+
body.try_emplace("stackFrames", std::move(stack_frames));
33453326
response.try_emplace("body", std::move(body));
33463327
g_dap.SendJSON(llvm::json::Value(std::move(response)));
33473328
}

0 commit comments

Comments
 (0)