Skip to content

Commit c0d6680

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 (cherry picked from commit 730c8e1)
1 parent d41f263 commit c0d6680

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,
@@ -776,9 +777,46 @@ bool StackFrameList::SetFrameAtIndex(uint32_t idx, StackFrameSP &frame_sp) {
776777
return false; // resize failed, out of memory?
777778
}
778779

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

784822
uint32_t StackFrameList::SetSelectedFrame(lldb_private::StackFrame *frame) {
@@ -787,17 +825,19 @@ uint32_t StackFrameList::SetSelectedFrame(lldb_private::StackFrame *frame) {
787825
const_iterator begin = m_frames.begin();
788826
const_iterator end = m_frames.end();
789827
m_selected_frame_idx = 0;
828+
790829
for (pos = begin; pos != end; ++pos) {
791830
if (pos->get() == frame) {
792831
m_selected_frame_idx = std::distance(begin, pos);
793832
uint32_t inlined_depth = GetCurrentInlinedDepth();
794833
if (inlined_depth != UINT32_MAX)
795-
m_selected_frame_idx -= inlined_depth;
834+
m_selected_frame_idx = *m_selected_frame_idx - inlined_depth;
796835
break;
797836
}
798837
}
838+
799839
SetDefaultFileAndLineToSelectedFrame();
800-
return m_selected_frame_idx;
840+
return *m_selected_frame_idx;
801841
}
802842

803843
bool StackFrameList::SetSelectedFrameByIndex(uint32_t idx) {
@@ -825,10 +865,16 @@ void StackFrameList::SetDefaultFileAndLineToSelectedFrame() {
825865

826866
// The thread has been run, reset the number stack frames to zero so we can
827867
// determine how many frames we have lazily.
868+
// Note, we don't actually re-use StackFrameLists, we always make a new
869+
// StackFrameList every time we stop, and then copy frame information frame
870+
// by frame from the old to the new StackFrameList. So the comment above,
871+
// does not describe how StackFrameLists are currently used.
872+
// Clear is currently only used to clear the list in the destructor.
828873
void StackFrameList::Clear() {
829874
std::lock_guard<std::recursive_mutex> guard(m_mutex);
830875
m_frames.clear();
831876
m_concrete_frames_fetched = 0;
877+
m_selected_frame_idx.reset();
832878
}
833879

834880
lldb::StackFrameSP

lldb/source/Target/Thread.cpp

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -605,36 +605,9 @@ std::string Thread::GetStopDescriptionRaw() {
605605
return raw_stop_description;
606606
}
607607

608-
void Thread::SelectMostRelevantFrame() {
609-
Log *log = GetLog(LLDBLog::Thread);
610-
611-
auto frames_list_sp = GetStackFrameList();
612-
613-
// Only the top frame should be recognized.
614-
auto frame_sp = frames_list_sp->GetFrameAtIndex(0);
615-
616-
auto recognized_frame_sp = frame_sp->GetRecognizedFrame();
617-
618-
if (!recognized_frame_sp) {
619-
LLDB_LOG(log, "Frame #0 not recognized");
620-
return;
621-
}
622-
623-
if (StackFrameSP most_relevant_frame_sp =
624-
recognized_frame_sp->GetMostRelevantFrame()) {
625-
LLDB_LOG(log, "Found most relevant frame at index {0}",
626-
most_relevant_frame_sp->GetFrameIndex());
627-
SetSelectedFrame(most_relevant_frame_sp.get());
628-
} else {
629-
LLDB_LOG(log, "No relevant frame!");
630-
}
631-
}
632-
633608
void Thread::WillStop() {
634609
ThreadPlan *current_plan = GetCurrentPlan();
635610

636-
SelectMostRelevantFrame();
637-
638611
// FIXME: I may decide to disallow threads with no plans. In which
639612
// case this should go to an assert.
640613

0 commit comments

Comments
 (0)