Compilers and language runtimes often use helper functions that are fundamentally uninteresting when debugging anything but the compiler/runtime itself. This patch introduces a user-extensible mechanism that allows for these frames to be hidden from backtraces and automatically skipped over when navigating the stack with `up` and `down`. This does not affect the numbering of frames, so `f <N>` will still provide access to the hidden frames. The `bt` output will also print a hint that frames have been hidden. My primary motivation for this feature is to hide thunks in the Swift programming language, but I'm including an example recognizer for `std::function::operator()` that I wished for myself many times while debugging LLDB. rdar://126629381 Example output. (Yes, my proof-of-concept recognizer could hide even more frames if we had a method that returned the function name without the return type or I used something that isn't based off regex, but it's really only meant as an example). before: ``` (lldb) thread backtrace --filtered=false * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10 frame #1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25 frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12 frame #3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12 frame #4: 0x00000001000026bc a.out`std::__1::__function::__func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10 frame #5: 0x0000000100003c38 a.out`std::__1::__function::__value_func<int (int, int)>::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12 frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10 frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10 frame #8: 0x0000000183cdf154 dyld`start + 2476 (lldb) ``` after ``` (lldb) bt * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10 frame #1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25 frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12 frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10 frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10 frame #8: 0x0000000183cdf154 dyld`start + 2476 Note: Some frames were hidden by frame recognizers ```
85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
import lldb
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
|
|
|
|
class LibCxxStdFunctionRecognizerTestCase(TestBase):
|
|
NO_DEBUG_INFO_TESTCASE = True
|
|
|
|
@add_test_categories(["libc++"])
|
|
def test_backtrace(self):
|
|
"""Test that std::function implementation details are hidden in bt"""
|
|
self.build()
|
|
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
|
self, "// break here", lldb.SBFileSpec("main.cpp")
|
|
)
|
|
# Filtered.
|
|
self.expect(
|
|
"thread backtrace",
|
|
ordered=True,
|
|
substrs=["frame", "foo", "frame", "main"],
|
|
)
|
|
self.expect(
|
|
"thread backtrace", matching=False, patterns=["frame.*std::__1::__function"]
|
|
)
|
|
# Unfiltered.
|
|
self.expect(
|
|
"thread backtrace -u",
|
|
ordered=True,
|
|
patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"],
|
|
)
|
|
self.expect(
|
|
"thread backtrace --unfiltered",
|
|
ordered=True,
|
|
patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"],
|
|
)
|
|
|
|
@add_test_categories(["libc++"])
|
|
def test_up_down(self):
|
|
"""Test that std::function implementation details are skipped"""
|
|
self.build()
|
|
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
|
self, "// break here", lldb.SBFileSpec("main.cpp")
|
|
)
|
|
frame = thread.GetSelectedFrame()
|
|
# up
|
|
self.assertIn("foo", frame.GetFunctionName())
|
|
start_idx = frame.GetFrameID()
|
|
i = 0
|
|
while i < thread.GetNumFrames():
|
|
self.expect("up")
|
|
frame = thread.GetSelectedFrame()
|
|
if frame.GetFunctionName() == "main":
|
|
break
|
|
end_idx = frame.GetFrameID()
|
|
self.assertLess(i, end_idx - start_idx, "skipped frames")
|
|
|
|
# Back down again.
|
|
start_idx = frame.GetFrameID()
|
|
for i in range(1, thread.GetNumFrames()):
|
|
self.expect("down")
|
|
frame = thread.GetSelectedFrame()
|
|
if "foo" in frame.GetFunctionName():
|
|
break
|
|
end_idx = frame.GetFrameID()
|
|
self.assertLess(i, start_idx - end_idx, "skipped frames")
|
|
|
|
@add_test_categories(["libc++"])
|
|
def test_api(self):
|
|
"""Test that std::function implementation details are skipped"""
|
|
self.build()
|
|
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
|
self, "// break here", lldb.SBFileSpec("main.cpp")
|
|
)
|
|
frame = thread.GetSelectedFrame()
|
|
num_hidden = 0
|
|
for i in range(1, thread.GetNumFrames()):
|
|
thread.SetSelectedFrame(i)
|
|
frame = thread.GetSelectedFrame()
|
|
if frame.IsHidden():
|
|
num_hidden += 1
|
|
|
|
self.assertGreater(num_hidden, 0)
|
|
self.assertLess(num_hidden, thread.GetNumFrames())
|