[lldb] When starting in a hidden frame, don't skip over hidden frames when navigating up/down (#166394)

When stopped in a hidden frame (either because we selected the hidden
frame or hit a breakpoint inside it), a user most likely is intersted in
exploring the immediate frames around it. But currently issuing
`up`/`down` commands will unconditionally skip over all hidden frames.

This patch makes it so `up`/`down` commands don't skip hidden frames if
the frame we started it was a hidden frame.
This commit is contained in:
Michael Buch
2025-11-04 16:24:24 +00:00
committed by GitHub
parent a50d036c0b
commit 4749bf56a6
4 changed files with 78 additions and 22 deletions

View File

@@ -265,6 +265,29 @@ public:
Options *GetOptions() override { return &m_options; }
private:
void SkipHiddenFrames(Thread &thread, uint32_t frame_idx) {
uint32_t candidate_idx = frame_idx;
const unsigned max_depth = 12;
for (unsigned num_try = 0; num_try < max_depth; ++num_try) {
if (candidate_idx == 0 && *m_options.relative_frame_offset == -1) {
candidate_idx = UINT32_MAX;
break;
}
candidate_idx += *m_options.relative_frame_offset;
if (auto candidate_sp = thread.GetStackFrameAtIndex(candidate_idx)) {
if (candidate_sp->IsHidden())
continue;
// Now candidate_idx is the first non-hidden frame.
break;
}
candidate_idx = UINT32_MAX;
break;
};
if (candidate_idx != UINT32_MAX)
m_options.relative_frame_offset = candidate_idx - frame_idx;
}
protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
// No need to check "thread" for validity as eCommandRequiresThread ensures
@@ -278,28 +301,13 @@ protected:
if (frame_idx == UINT32_MAX)
frame_idx = 0;
// If moving up/down by one, skip over hidden frames.
if (*m_options.relative_frame_offset == 1 ||
*m_options.relative_frame_offset == -1) {
uint32_t candidate_idx = frame_idx;
const unsigned max_depth = 12;
for (unsigned num_try = 0; num_try < max_depth; ++num_try) {
if (candidate_idx == 0 && *m_options.relative_frame_offset == -1) {
candidate_idx = UINT32_MAX;
break;
}
candidate_idx += *m_options.relative_frame_offset;
if (auto candidate_sp = thread->GetStackFrameAtIndex(candidate_idx)) {
if (candidate_sp->IsHidden())
continue;
// Now candidate_idx is the first non-hidden frame.
break;
}
candidate_idx = UINT32_MAX;
break;
};
if (candidate_idx != UINT32_MAX)
m_options.relative_frame_offset = candidate_idx - frame_idx;
// If moving up/down by one, skip over hidden frames, unless we started
// in a hidden frame.
if ((*m_options.relative_frame_offset == 1 ||
*m_options.relative_frame_offset == -1)) {
if (auto current_frame_sp = thread->GetStackFrameAtIndex(frame_idx);
!current_frame_sp->IsHidden())
SkipHiddenFrames(*thread, frame_idx);
}
if (*m_options.relative_frame_offset < 0) {

View File

@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@@ -0,0 +1,32 @@
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class NavigateHiddenFrameTestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True
@add_test_categories(["libc++"])
def test(self):
"""Test going up/down a backtrace but we started in a hidden frame."""
self.build()
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "Break here", lldb.SBFileSpec("main.cpp")
)
# up
self.assertIn("__impl2", thread.selected_frame.GetFunctionName())
self.expect("up")
self.assertIn("__impl1", thread.selected_frame.GetFunctionName())
self.expect("up")
self.assertIn("__impl", thread.selected_frame.GetFunctionName())
self.expect("up")
self.assertIn("non_impl", thread.selected_frame.GetFunctionName())
# Back down again.
self.expect("down")
self.assertIn("__impl", thread.selected_frame.GetFunctionName())
self.expect("down")
self.assertIn("__impl1", thread.selected_frame.GetFunctionName())
self.expect("down")
self.assertIn("__impl2", thread.selected_frame.GetFunctionName())

View File

@@ -0,0 +1,13 @@
namespace std {
namespace __1 {
static const char *__impl2() { return "Break here"; }
static const char *__impl1() { return __impl2(); }
static const char *__impl() { return __impl1(); }
static const char *non_impl() { return __impl(); }
} // namespace __1
} // namespace std
int main() {
std::__1::non_impl();
__builtin_debugtrap();
}