Skip to content

Commit 730c8e1

Browse files
jiminghamJDevlieghere
authored andcommitted
[lldb] Move "SelectMostRelevantFrame" from Thread::WillStop
SelectMostRelevantFrame triggers the StackFrameRecognizer construction, which can run arbitrary Python code, call expressions etc. WillStop gets called on every private stop while the recognizers are a user-facing feature, so first off doing this work on every stop is inefficient. But more importantly, you can get in to situations where the recognizer causes an expression to get run, then when we fetch the stop event at the end of the expression evaluation, we call WillStop again on the expression handling thread, which will do the same StackFrameRecognizer work again. If anyone is locking along that path, you will end up with a deadlock between the two threads. The example that brought this to my attention was the objc_exception_throw recognizer which can cause the objc runtime introspection functions to get run, and those take a lock in AppleObjCRuntimeV2::DynamicClassInfoExtractor::UpdateISAToDescriptorMap along this path, so the second thread servicing the expression deadlocks against the first thread waiting for the expression to complete. It makes more sense to have the frame recognizers run on demand, either when someone asks for the variables for the frame, or when someone does GetSelectedFrame. The former already worked that way, the only reason this was being done in WillStop was because the StackFrameRecognizers can change the SelectedFrame, so you needed to run them before the anyone requested the SelectedFrame. This patch moves SelectMostRelevantFrame to StackFrameList, and runs it when GetSelectedFrame is called for the first time on a given stop. If you call SetSelectedFrame before GetSelectedFrame, then you should NOT run the recognizer & change the frame out from under you. This patch also makes that work. There were already tests for this behavior, and for the feature that caused the hang, but the hang is racy, and it doesn't trigger all the time, so I don't have a way to test that explicitly. One more detail: it's actually pretty easy to end up calling GetSelectedFrame, for instance if you ask for the best ExecutionContext from an ExecutionContextRef it will fill the StackFrame with the result of GetSelectedFrame and that would still have the same problems if this happens on the Private State Thread. So this patch also short-circuits SelectMostRelevantFrame if run on the that thread. I can't think of any reason the computations that go on on the Private State Thread would actually want the SelectedFrame - that's a user-facing concept, so avoiding that complication is the best way to go. rdar://107643231 Differential revision: https://reviews.llvm.org/D147753
1 parent 6ff177d commit 730c8e1

File tree

4 files changed

+59
-38
lines changed

4 files changed

+59
-38
lines changed

lldb/include/lldb/Target/StackFrameList.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class StackFrameList {
4646
uint32_t SetSelectedFrame(lldb_private::StackFrame *frame);
4747

4848
/// Get the currently selected frame index.
49-
uint32_t GetSelectedFrameIndex() const;
49+
uint32_t GetSelectedFrameIndex();
5050

5151
/// Mark a stack frame as the currently selected frame using the frame index
5252
/// \p idx. Like \ref GetFrameAtIndex, invisible frames cannot be selected.
@@ -110,6 +110,8 @@ class StackFrameList {
110110

111111
void SetCurrentInlinedDepth(uint32_t new_depth);
112112

113+
void SelectMostRelevantFrame();
114+
113115
typedef std::vector<lldb::StackFrameSP> collection;
114116
typedef collection::iterator iterator;
115117
typedef collection::const_iterator const_iterator;
@@ -134,8 +136,10 @@ class StackFrameList {
134136
/// changes.
135137
collection m_frames;
136138

137-
/// The currently selected frame.
138-
uint32_t m_selected_frame_idx;
139+
/// The currently selected frame. An optional is used to record whether anyone
140+
/// has set the selected frame on this stack yet. We only let recognizers
141+
/// change the frame if this is the first time GetSelectedFrame is called.
142+
std::optional<uint32_t> m_selected_frame_idx;
139143

140144
/// The number of concrete frames fetched while filling the frame list. This
141145
/// is only used when synthetic frames are enabled.

lldb/include/lldb/Target/Thread.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,6 @@ class Thread : public std::enable_shared_from_this<Thread>,
217217

218218
virtual void RefreshStateAfterStop() = 0;
219219

220-
void SelectMostRelevantFrame();
221-
222220
std::string GetStopDescription();
223221

224222
std::string GetStopDescriptionRaw();

lldb/source/Target/StackFrameList.cpp

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "lldb/Target/Process.h"
1919
#include "lldb/Target/RegisterContext.h"
2020
#include "lldb/Target/StackFrame.h"
21+
#include "lldb/Target/StackFrameRecognizer.h"
2122
#include "lldb/Target/StopInfo.h"
2223
#include "lldb/Target/Target.h"
2324
#include "lldb/Target/Thread.h"
@@ -38,7 +39,7 @@ StackFrameList::StackFrameList(Thread &thread,
3839
const lldb::StackFrameListSP &prev_frames_sp,
3940
bool show_inline_frames)
4041
: m_thread(thread), m_prev_frames_sp(prev_frames_sp), m_mutex(), m_frames(),
41-
m_selected_frame_idx(0), m_concrete_frames_fetched(0),
42+
m_selected_frame_idx(), m_concrete_frames_fetched(0),
4243
m_current_inlined_depth(UINT32_MAX),
4344
m_current_inlined_pc(LLDB_INVALID_ADDRESS),
4445
m_show_inlined_frames(show_inline_frames) {
@@ -252,7 +253,7 @@ struct CallDescriptor {
252253
using CallSequence = std::vector<CallDescriptor>;
253254

254255
/// Find the unique path through the call graph from \p begin (with return PC
255-
/// \p return_pc) to \p end. On success this path is stored into \p path, and
256+
/// \p return_pc) to \p end. On success this path is stored into \p path, and
256257
/// on failure \p path is unchanged.
257258
static void FindInterveningFrames(Function &begin, Function &end,
258259
ExecutionContext &exe_ctx, Target &target,
@@ -781,9 +782,46 @@ bool StackFrameList::SetFrameAtIndex(uint32_t idx, StackFrameSP &frame_sp) {
781782
return false; // resize failed, out of memory?
782783
}
783784

784-
uint32_t StackFrameList::GetSelectedFrameIndex() const {
785+
void StackFrameList::SelectMostRelevantFrame() {
786+
// Don't call into the frame recognizers on the private state thread as
787+
// they can cause code to run in the target, and that can cause deadlocks
788+
// when fetching stop events for the expression.
789+
if (m_thread.GetProcess()->CurrentThreadIsPrivateStateThread())
790+
return;
791+
792+
Log *log = GetLog(LLDBLog::Thread);
793+
794+
// Only the top frame should be recognized.
795+
StackFrameSP frame_sp = GetFrameAtIndex(0);
796+
if (!frame_sp) {
797+
LLDB_LOG(log, "Failed to construct Frame #0");
798+
return;
799+
}
800+
801+
RecognizedStackFrameSP recognized_frame_sp = frame_sp->GetRecognizedFrame();
802+
803+
if (!recognized_frame_sp) {
804+
LLDB_LOG(log, "Frame #0 not recognized");
805+
return;
806+
}
807+
808+
if (StackFrameSP most_relevant_frame_sp =
809+
recognized_frame_sp->GetMostRelevantFrame()) {
810+
LLDB_LOG(log, "Found most relevant frame at index {0}",
811+
most_relevant_frame_sp->GetFrameIndex());
812+
SetSelectedFrame(most_relevant_frame_sp.get());
813+
} else {
814+
LLDB_LOG(log, "No relevant frame!");
815+
}
816+
}
817+
818+
uint32_t StackFrameList::GetSelectedFrameIndex() {
785819
std::lock_guard<std::recursive_mutex> guard(m_mutex);
786-
return m_selected_frame_idx;
820+
if (!m_selected_frame_idx)
821+
SelectMostRelevantFrame();
822+
if (!m_selected_frame_idx)
823+
m_selected_frame_idx = 0;
824+
return *m_selected_frame_idx;
787825
}
788826

789827
uint32_t StackFrameList::SetSelectedFrame(lldb_private::StackFrame *frame) {
@@ -792,17 +830,19 @@ uint32_t StackFrameList::SetSelectedFrame(lldb_private::StackFrame *frame) {
792830
const_iterator begin = m_frames.begin();
793831
const_iterator end = m_frames.end();
794832
m_selected_frame_idx = 0;
833+
795834
for (pos = begin; pos != end; ++pos) {
796835
if (pos->get() == frame) {
797836
m_selected_frame_idx = std::distance(begin, pos);
798837
uint32_t inlined_depth = GetCurrentInlinedDepth();
799838
if (inlined_depth != UINT32_MAX)
800-
m_selected_frame_idx -= inlined_depth;
839+
m_selected_frame_idx = *m_selected_frame_idx - inlined_depth;
801840
break;
802841
}
803842
}
843+
804844
SetDefaultFileAndLineToSelectedFrame();
805-
return m_selected_frame_idx;
845+
return *m_selected_frame_idx;
806846
}
807847

808848
bool StackFrameList::SetSelectedFrameByIndex(uint32_t idx) {
@@ -830,10 +870,16 @@ void StackFrameList::SetDefaultFileAndLineToSelectedFrame() {
830870

831871
// The thread has been run, reset the number stack frames to zero so we can
832872
// determine how many frames we have lazily.
873+
// Note, we don't actually re-use StackFrameLists, we always make a new
874+
// StackFrameList every time we stop, and then copy frame information frame
875+
// by frame from the old to the new StackFrameList. So the comment above,
876+
// does not describe how StackFrameLists are currently used.
877+
// Clear is currently only used to clear the list in the destructor.
833878
void StackFrameList::Clear() {
834879
std::lock_guard<std::recursive_mutex> guard(m_mutex);
835880
m_frames.clear();
836881
m_concrete_frames_fetched = 0;
882+
m_selected_frame_idx.reset();
837883
}
838884

839885
lldb::StackFrameSP

lldb/source/Target/Thread.cpp

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -588,36 +588,9 @@ std::string Thread::GetStopDescriptionRaw() {
588588
return raw_stop_description;
589589
}
590590

591-
void Thread::SelectMostRelevantFrame() {
592-
Log *log = GetLog(LLDBLog::Thread);
593-
594-
auto frames_list_sp = GetStackFrameList();
595-
596-
// Only the top frame should be recognized.
597-
auto frame_sp = frames_list_sp->GetFrameAtIndex(0);
598-
599-
auto recognized_frame_sp = frame_sp->GetRecognizedFrame();
600-
601-
if (!recognized_frame_sp) {
602-
LLDB_LOG(log, "Frame #0 not recognized");
603-
return;
604-
}
605-
606-
if (StackFrameSP most_relevant_frame_sp =
607-
recognized_frame_sp->GetMostRelevantFrame()) {
608-
LLDB_LOG(log, "Found most relevant frame at index {0}",
609-
most_relevant_frame_sp->GetFrameIndex());
610-
SetSelectedFrame(most_relevant_frame_sp.get());
611-
} else {
612-
LLDB_LOG(log, "No relevant frame!");
613-
}
614-
}
615-
616591
void Thread::WillStop() {
617592
ThreadPlan *current_plan = GetCurrentPlan();
618593

619-
SelectMostRelevantFrame();
620-
621594
// FIXME: I may decide to disallow threads with no plans. In which
622595
// case this should go to an assert.
623596

0 commit comments

Comments
 (0)