Skip to content

Commit 167febc

Browse files
committed
We can't let GetStackFrameCount get interrupted or it will give the
wrong answer. Plus, it's useful in some places to have a way to force the full stack to be created even in the face of interruption. Moreover, most of the time when you're just getting frames, you don't need to know the number of frames in the stack to start with. You just keep calling Thread::GetStackFrameAtIndex(index++) and when you get a null StackFrameSP back, you're done. That's also more amenable to interruption if you are doing some work frame by frame. So this patch makes GetStackFrameCount always return the full count, suspending interruption. I also went through all the places that use GetStackFrameCount to make sure that they really needed the full stack walk. In many cases, they did not. For instance frame select -r 10 was getting the number of frames just to check whether cur_frame_idx + 10 was within the stack. It's better in that case to see if that frame exists first, since that doesn't force a full stack walk, and only deal with walking off the end of the stack if it doesn't... I also added a test for some of these behaviors. Differential Revision: https://reviews.llvm.org/D150236 (cherry picked from commit e19387e)
1 parent 3942501 commit 167febc

File tree

8 files changed

+131
-20
lines changed

8 files changed

+131
-20
lines changed

lldb/include/lldb/Target/StackFrameList.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ class StackFrameList {
100100

101101
bool SetFrameAtIndex(uint32_t idx, lldb::StackFrameSP &frame_sp);
102102

103-
void GetFramesUpTo(uint32_t end_idx);
103+
/// Realizes frames up to (and including) end_idx (which can be greater than
104+
/// the actual number of frames.)
105+
/// Returns true if the function was interrupted, false otherwise.
106+
bool GetFramesUpTo(uint32_t end_idx,
107+
InterruptionControl allow_interrupt = AllowInterruption);
104108

105109
void GetOnlyConcreteFramesUpTo(uint32_t end_idx, Unwind &unwinder);
106110

lldb/include/lldb/lldb-private-enumerations.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,9 @@ enum SelectMostRelevant : bool {
293293
DoNoSelectMostRelevantFrame = false,
294294
};
295295

296+
enum InterruptionControl : bool {
297+
AllowInterruption = true,
298+
DoNotAllowInterruption = false,
299+
};
300+
296301
#endif // LLDB_LLDB_PRIVATE_ENUMERATIONS_H

lldb/source/Commands/CommandCompletions.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,10 +729,14 @@ void CommandCompletions::FrameIndexes(CommandInterpreter &interpreter,
729729
return;
730730

731731
lldb::ThreadSP thread_sp = exe_ctx.GetThreadSP();
732+
Debugger &dbg = interpreter.GetDebugger();
732733
const uint32_t frame_num = thread_sp->GetStackFrameCount();
733734
for (uint32_t i = 0; i < frame_num; ++i) {
734735
lldb::StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(i);
735736
StreamString strm;
737+
// Dumping frames can be slow, allow interruption.
738+
if (dbg.InterruptRequested())
739+
break;
736740
frame_sp->Dump(&strm, false, true);
737741
request.TryCompleteCurrentArg(std::to_string(i), strm.GetString());
738742
}

lldb/source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,7 @@ void SystemRuntimeMacOSX::AddThreadExtendedInfoPacketHints(
220220
}
221221

222222
bool SystemRuntimeMacOSX::SafeToCallFunctionsOnThisThread(ThreadSP thread_sp) {
223-
if (thread_sp && thread_sp->GetStackFrameCount() > 0 &&
224-
thread_sp->GetFrameWithConcreteFrameIndex(0)) {
223+
if (thread_sp && thread_sp->GetFrameWithConcreteFrameIndex(0)) {
225224
const SymbolContext sym_ctx(
226225
thread_sp->GetFrameWithConcreteFrameIndex(0)->GetSymbolContext(
227226
eSymbolContextSymbol));

lldb/source/Target/StackFrameList.cpp

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ void StackFrameList::ResetCurrentInlinedDepth() {
8585
return;
8686

8787
std::lock_guard<std::recursive_mutex> guard(m_mutex);
88-
89-
GetFramesUpTo(0);
88+
89+
GetFramesUpTo(0, DoNotAllowInterruption);
9090
if (m_frames.empty())
9191
return;
9292
if (!m_frames[0]->IsInlined()) {
@@ -436,21 +436,23 @@ void StackFrameList::SynthesizeTailCallFrames(StackFrame &next_frame) {
436436
next_frame.SetFrameIndex(m_frames.size());
437437
}
438438

439-
void StackFrameList::GetFramesUpTo(uint32_t end_idx) {
439+
bool StackFrameList::GetFramesUpTo(uint32_t end_idx,
440+
InterruptionControl allow_interrupt) {
440441
// Do not fetch frames for an invalid thread.
442+
bool was_interrupted = false;
441443
if (!m_thread.IsValid())
442-
return;
444+
return false;
443445

444446
// We've already gotten more frames than asked for, or we've already finished
445447
// unwinding, return.
446448
if (m_frames.size() > end_idx || GetAllFramesFetched())
447-
return;
449+
return false;
448450

449451
Unwind &unwinder = m_thread.GetUnwinder();
450452

451453
if (!m_show_inlined_frames) {
452454
GetOnlyConcreteFramesUpTo(end_idx, unwinder);
453-
return;
455+
return false;
454456
}
455457

456458
#if defined(DEBUG_STACK_FRAMES)
@@ -474,13 +476,6 @@ void StackFrameList::GetFramesUpTo(uint32_t end_idx) {
474476
StackFrameSP unwind_frame_sp;
475477
Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger();
476478
do {
477-
// Check for interruption here when building the frames - this is the
478-
// expensive part, Dump later on is cheap.
479-
if (dbg.InterruptRequested()) {
480-
Log *log = GetLog(LLDBLog::Host);
481-
LLDB_LOG(log, "Interrupted %s", __FUNCTION__);
482-
break;
483-
}
484479
uint32_t idx = m_concrete_frames_fetched++;
485480
lldb::addr_t pc = LLDB_INVALID_ADDRESS;
486481
lldb::addr_t cfa = LLDB_INVALID_ADDRESS;
@@ -512,6 +507,15 @@ void StackFrameList::GetFramesUpTo(uint32_t end_idx) {
512507
cfa = unwind_frame_sp->m_id.GetCallFrameAddress();
513508
}
514509
} else {
510+
// Check for interruption when building the frames.
511+
// Do the check in idx > 0 so that we'll always create a 0th frame.
512+
if (allow_interrupt && dbg.InterruptRequested()) {
513+
Log *log = GetLog(LLDBLog::Host);
514+
LLDB_LOG(log, "Interrupted %s", __FUNCTION__);
515+
was_interrupted = true;
516+
break;
517+
}
518+
515519
const bool success =
516520
unwinder.GetFrameInfoAtIndex(idx, cfa, pc, behaves_like_zeroth_frame);
517521
if (!success) {
@@ -624,14 +628,19 @@ void StackFrameList::GetFramesUpTo(uint32_t end_idx) {
624628
Dump(&s);
625629
s.EOL();
626630
#endif
631+
// Don't report interrupted if we happen to have gotten all the frames:
632+
if (!GetAllFramesFetched())
633+
return was_interrupted;
634+
return false;
627635
}
628636

629637
uint32_t StackFrameList::GetNumFrames(bool can_create) {
630638
std::lock_guard<std::recursive_mutex> guard(m_mutex);
631639

632-
if (can_create)
633-
GetFramesUpTo(UINT32_MAX);
634-
640+
if (can_create) {
641+
// Don't allow interrupt or we might not return the correct count
642+
GetFramesUpTo(UINT32_MAX, DoNotAllowInterruption);
643+
}
635644
return GetVisibleStackFrameIndex(m_frames.size());
636645
}
637646

@@ -672,7 +681,13 @@ StackFrameSP StackFrameList::GetFrameAtIndex(uint32_t idx) {
672681

673682
// GetFramesUpTo will fill m_frames with as many frames as you asked for, if
674683
// there are that many. If there weren't then you asked for too many frames.
675-
GetFramesUpTo(idx);
684+
// GetFramesUpTo returns true if interrupted:
685+
if (GetFramesUpTo(idx)) {
686+
Log *log = GetLog(LLDBLog::Thread);
687+
LLDB_LOG(log, "GetFrameAtIndex was interrupted");
688+
return {};
689+
}
690+
676691
if (idx < m_frames.size()) {
677692
if (m_show_inlined_frames) {
678693
// When inline frames are enabled we actually create all the frames in
@@ -942,6 +957,14 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
942957
else
943958
marker = unselected_marker;
944959
}
960+
// Check for interruption here. If we're fetching arguments, this loop
961+
// can go slowly:
962+
Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger();
963+
if (dbg.InterruptRequested()) {
964+
Log *log = GetLog(LLDBLog::Host);
965+
LLDB_LOG(log, "Interrupted %s", __FUNCTION__);
966+
break;
967+
}
945968

946969
if (!frame_sp->GetStatus(strm, show_frame_info,
947970
num_frames_with_source > (first_frame - frame_idx),
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
C_SOURCES := main.c
2+
CFLAGS_EXTRAS := -std=c99
3+
4+
include Makefile.rules
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""
2+
Ensure that when the interrupt is raised we still make frame 0.
3+
and make sure "GetNumFrames" isn't interrupted.
4+
"""
5+
6+
import lldb
7+
import lldbsuite.test.lldbutil as lldbutil
8+
from lldbsuite.test.lldbtest import *
9+
10+
11+
class TestInterruptingBacktrace(TestBase):
12+
13+
NO_DEBUG_INFO_TESTCASE = True
14+
15+
def test_backtrace_interrupt(self):
16+
"""Use RequestInterrupt followed by stack operations
17+
to ensure correct interrupt behavior for stacks."""
18+
self.build()
19+
self.main_source_file = lldb.SBFileSpec("main.c")
20+
self.bt_interrupt_test()
21+
22+
def bt_interrupt_test(self):
23+
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
24+
"Set a breakpoint here", self.main_source_file)
25+
26+
# Now continue, and when we stop we will have crashed.
27+
process.Continue()
28+
self.dbg.RequestInterrupt()
29+
30+
# Be sure to turn this off again:
31+
def cleanup ():
32+
if self.dbg.InterruptRequested():
33+
self.dbg.CancelInterruptRequest()
34+
self.addTearDownHook(cleanup)
35+
36+
frame_0 = thread.GetFrameAtIndex(0)
37+
self.assertTrue(frame_0.IsValid(), "Got a good 0th frame")
38+
# The interrupt flag is up already, so any attempt to backtrace
39+
# should be cut short:
40+
frame_1 = thread.GetFrameAtIndex(1)
41+
self.assertFalse(frame_1.IsValid(), "Prevented from getting more frames")
42+
# Since GetNumFrames is a contract, we don't interrupt it:
43+
num_frames = thread.GetNumFrames()
44+
print(f"Number of frames: {num_frames}")
45+
self.assertGreater(num_frames, 1, "Got many frames")
46+
47+
self.dbg.CancelInterruptRequest()
48+
49+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <stdio.h>
2+
3+
// This example is meant to recurse infinitely.
4+
// The extra struct is just to make the frame dump
5+
// more complicated.
6+
7+
struct Foo {
8+
int a;
9+
int b;
10+
char *c;
11+
};
12+
13+
int
14+
forgot_termination(int input, struct Foo my_foo) {
15+
return forgot_termination(++input, my_foo);
16+
}
17+
18+
int
19+
main()
20+
{
21+
struct Foo myFoo = {100, 300, "A string you will print a lot"}; // Set a breakpoint here
22+
return forgot_termination(1, myFoo);
23+
}

0 commit comments

Comments
 (0)