Files
llvm-project/lldb/test/API/macosx/extended-backtrace-api/TestExtendedBacktraceAPI.py
Med Ismail Bennani fb3ab402c1 [lldb/test] Fix BacktraceRecording path for Darwin embedded devices (NFC) (#193436)
rdar://172707080

Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
2026-04-22 10:18:46 -07:00

131 lines
5.0 KiB
Python

"""Test SBThread.GetExtendedBacktraceThread API with queue debugging."""
import os
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test.lldbplatformutil import findBacktraceRecordingDylib
from lldbsuite.test import lldbutil
class TestExtendedBacktraceAPI(TestBase):
NO_DEBUG_INFO_TESTCASE = True
def setUp(self):
TestBase.setUp(self)
self.main_source = "main.m"
@skipUnlessDarwin
@add_test_categories(["objc", "pyapi"])
def test_extended_backtrace_thread_api(self):
"""Test GetExtendedBacktraceThread with queue debugging."""
self.build()
exe = self.getBuildArtifact("a.out")
libbtr_path = findBacktraceRecordingDylib()
self.assertTrue(
libbtr_path,
"libBacktraceRecording.dylib was not found on the system.",
)
self.assertTrue(
os.path.isfile("/usr/lib/system/introspection/libdispatch.dylib"),
"introspection libdispatch dylib not installed.",
)
# Create launch info with environment variables for libBacktraceRecording.
launch_info = lldb.SBLaunchInfo(None)
launch_info.SetWorkingDirectory(self.get_process_working_directory())
launch_info.SetEnvironmentEntries(
[
f"DYLD_INSERT_LIBRARIES={libbtr_path}",
"DYLD_LIBRARY_PATH=/usr/lib/system/introspection",
],
True,
)
# Launch the process and run to breakpoint.
target, process, thread, bp = lldbutil.run_to_name_breakpoint(
self, "do_work_level_5", launch_info=launch_info, bkpt_module="a.out"
)
self.assertTrue(target.IsValid(), VALID_TARGET)
self.assertTrue(process.IsValid(), PROCESS_IS_VALID)
self.assertTrue(thread.IsValid(), "Stopped thread is valid")
self.assertTrue(bp.IsValid(), VALID_BREAKPOINT)
# Call GetNumQueues to ensure queue information is loaded.
num_queues = process.GetNumQueues()
# Check that we can find the com.apple.main-thread queue.
main_thread_queue_found = False
for i in range(num_queues):
queue = process.GetQueueAtIndex(i)
if queue.GetName() == "com.apple.main-thread":
main_thread_queue_found = True
break
# Verify we have at least 5 frames.
self.assertGreaterEqual(
thread.GetNumFrames(),
5,
"Thread should have at least 5 frames in backtrace",
)
# Get frame 2 BEFORE calling GetExtendedBacktraceThread.
# This mimics what Xcode does - it has the frame objects ready.
frame2 = thread.GetFrameAtIndex(2)
self.assertTrue(frame2.IsValid(), "Frame 2 is valid")
# Now test GetExtendedBacktraceThread.
# This is the critical part - getting the extended backtrace calls into
# libBacktraceRecording which does an inferior function call, and this
# invalidates/clears the unwinder state.
extended_thread = thread.GetExtendedBacktraceThread("libdispatch")
# This should be valid since we injected libBacktraceRecording.
self.assertTrue(
extended_thread.IsValid(),
"Extended backtrace thread for 'libdispatch' should be valid with libBacktraceRecording loaded",
)
# The extended thread should have frames.
self.assertGreater(
extended_thread.GetNumFrames(),
0,
"Extended backtrace thread should have at least one frame",
)
# Test frame 2 on the extended backtrace thread.
self.assertGreater(
extended_thread.GetNumFrames(),
2,
"Extended backtrace thread should have at least 3 frames to access frame 2",
)
extended_frame2 = extended_thread.GetFrameAtIndex(2)
self.assertTrue(extended_frame2.IsValid(), "Extended thread frame 2 is valid")
# NOW try to access variables from frame 2 of the ORIGINAL thread.
# This is the key test - after GetExtendedBacktraceThread() has executed
# an inferior function call, the unwinder state may be invalidated.
# Xcode exhibits this bug where variables show "register fp is not available"
# after extended backtrace retrieval.
# Set frame 2 as the selected frame so expect_var_path works.
thread.SetSelectedFrame(2)
variables = frame2.GetVariables(False, True, False, True)
self.assertGreater(
variables.GetSize(), 0, "Frame 2 should have at least one variable"
)
# Test all variables in frame 2, like Xcode does.
# Use expect_var_path to verify each variable is accessible without errors.
for i in range(variables.GetSize()):
var = variables.GetValueAtIndex(i)
var_name = var.GetName()
# This will fail if the variable contains "not available" or has errors.
self.expect_var_path(var_name)