The Objective-C runtime and the shared cache has changed slightly. Given a class_ro_t, the baseMethods ivar is now a pointer union and may either be a method_list_t pointer or a pointer to a relative list of lists. The entries of this relative list of lists are indexes that refer to a specific image in the shared cache in addition to a pointer offset to find the accompanying method_list_t. We have to go over each of these entries, parse it, and then if the relevant image is loaded in the process, we add those methods to the relevant clang Decl. In order to determine if an image is loaded, the Objective-C runtime exposes a symbol that lets us determine if a particular image is loaded. We maintain a data structure SharedCacheImageHeaders to keep track of that information. There is a known issue where if an image is loaded after we create a Decl for a class, the Decl will not have the relevant methods from that image (i.e. for Categories). rdar://107957209 Differential Revision: https://reviews.llvm.org/D153597
259 lines
8.9 KiB
Python
259 lines
8.9 KiB
Python
# encoding: utf-8
|
|
"""
|
|
Test lldb Obj-C exception support.
|
|
"""
|
|
|
|
|
|
import lldb
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
|
|
|
|
class ObjCExceptionsTestCase(TestBase):
|
|
@skipIf(compiler="clang", compiler_version=["<", "13.0"])
|
|
def test_objc_exceptions_at_throw(self):
|
|
self.build()
|
|
|
|
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
|
|
self.assertTrue(target, VALID_TARGET)
|
|
|
|
launch_info = lldb.SBLaunchInfo(["a.out", "0"])
|
|
launch_info.SetLaunchFlags(lldb.eLaunchFlagInheritTCCFromParent)
|
|
lldbutil.run_to_name_breakpoint(
|
|
self, "objc_exception_throw", launch_info=launch_info
|
|
)
|
|
|
|
self.expect(
|
|
"thread list",
|
|
substrs=["stopped", "stop reason = hit Objective-C exception"],
|
|
)
|
|
|
|
self.expect(
|
|
"thread exception",
|
|
substrs=[
|
|
"(NSException *) exception = ",
|
|
'"SomeReason"',
|
|
],
|
|
)
|
|
|
|
target = self.dbg.GetSelectedTarget()
|
|
thread = target.GetProcess().GetSelectedThread()
|
|
frame = thread.GetSelectedFrame()
|
|
|
|
opts = lldb.SBVariablesOptions()
|
|
opts.SetIncludeRecognizedArguments(True)
|
|
variables = frame.GetVariables(opts)
|
|
|
|
self.assertEqual(variables.GetSize(), 1)
|
|
self.assertEqual(variables.GetValueAtIndex(0).name, "exception")
|
|
self.assertEqual(
|
|
variables.GetValueAtIndex(0).GetValueType(), lldb.eValueTypeVariableArgument
|
|
)
|
|
|
|
lldbutil.run_to_source_breakpoint(
|
|
self,
|
|
"// Set break point at this line.",
|
|
lldb.SBFileSpec("main.mm"),
|
|
launch_info=launch_info,
|
|
)
|
|
|
|
self.expect(
|
|
"thread list",
|
|
STOPPED_DUE_TO_BREAKPOINT,
|
|
substrs=["stopped", "stop reason = breakpoint"],
|
|
)
|
|
|
|
target = self.dbg.GetSelectedTarget()
|
|
thread = target.GetProcess().GetSelectedThread()
|
|
frame = thread.GetSelectedFrame()
|
|
|
|
# No exception being currently thrown/caught at this point
|
|
self.assertFalse(thread.GetCurrentException().IsValid())
|
|
self.assertFalse(thread.GetCurrentExceptionBacktrace().IsValid())
|
|
|
|
self.expect(
|
|
"frame variable e1", substrs=["(NSException *) e1 = ", '"SomeReason"']
|
|
)
|
|
|
|
self.expect(
|
|
"frame variable *e1",
|
|
substrs=[
|
|
"(NSException) *e1 = ",
|
|
"name = ",
|
|
'"ExceptionName"',
|
|
"reason = ",
|
|
'"SomeReason"',
|
|
"userInfo = ",
|
|
"1 key/value pair",
|
|
"reserved = ",
|
|
],
|
|
)
|
|
|
|
e1 = frame.FindVariable("e1")
|
|
self.assertTrue(e1)
|
|
self.assertEqual(e1.type.name, "NSException *")
|
|
self.assertEqual(e1.GetSummary(), '"SomeReason"')
|
|
self.assertEqual(e1.GetChildMemberWithName("name").description, "ExceptionName")
|
|
self.assertEqual(e1.GetChildMemberWithName("reason").description, "SomeReason")
|
|
userInfo = e1.GetChildMemberWithName("userInfo").dynamic
|
|
self.assertEqual(userInfo.summary, "1 key/value pair")
|
|
self.assertEqual(
|
|
userInfo.GetChildAtIndex(0).GetChildAtIndex(0).description, "some_key"
|
|
)
|
|
self.assertEqual(
|
|
userInfo.GetChildAtIndex(0).GetChildAtIndex(1).description, "some_value"
|
|
)
|
|
|
|
self.expect(
|
|
"frame variable e2", substrs=["(NSException *) e2 = ", '"SomeReason"']
|
|
)
|
|
|
|
self.expect(
|
|
"frame variable *e2",
|
|
substrs=[
|
|
"(NSException) *e2 = ",
|
|
"name = ",
|
|
'"ThrownException"',
|
|
"reason = ",
|
|
'"SomeReason"',
|
|
"userInfo = ",
|
|
"1 key/value pair",
|
|
"reserved = ",
|
|
],
|
|
)
|
|
|
|
e2 = frame.FindVariable("e2")
|
|
self.assertTrue(e2)
|
|
self.assertEqual(e2.type.name, "NSException *")
|
|
self.assertEqual(e2.GetSummary(), '"SomeReason"')
|
|
self.assertEqual(
|
|
e2.GetChildMemberWithName("name").description, "ThrownException"
|
|
)
|
|
self.assertEqual(e2.GetChildMemberWithName("reason").description, "SomeReason")
|
|
userInfo = e2.GetChildMemberWithName("userInfo").dynamic
|
|
self.assertEqual(userInfo.summary, "1 key/value pair")
|
|
self.assertEqual(
|
|
userInfo.GetChildAtIndex(0).GetChildAtIndex(0).description, "some_key"
|
|
)
|
|
self.assertEqual(
|
|
userInfo.GetChildAtIndex(0).GetChildAtIndex(1).description, "some_value"
|
|
)
|
|
reserved = e2.GetChildMemberWithName("reserved").dynamic
|
|
self.assertGreater(reserved.num_children, 0)
|
|
callStackReturnAddresses = [
|
|
reserved.GetChildAtIndex(i).GetChildAtIndex(1)
|
|
for i in range(0, reserved.GetNumChildren())
|
|
if reserved.GetChildAtIndex(i).GetChildAtIndex(0).description
|
|
== "callStackReturnAddresses"
|
|
][0].dynamic
|
|
children = [
|
|
callStackReturnAddresses.GetChildAtIndex(i)
|
|
for i in range(0, callStackReturnAddresses.num_children)
|
|
]
|
|
|
|
pcs = [i.unsigned for i in children]
|
|
names = [
|
|
target.ResolveSymbolContextForAddress(
|
|
lldb.SBAddress(pc, target), lldb.eSymbolContextSymbol
|
|
)
|
|
.GetSymbol()
|
|
.name
|
|
for pc in pcs
|
|
]
|
|
for n in ["objc_exception_throw", "foo(int)", "main"]:
|
|
self.assertIn(
|
|
n, names, "%s is in the exception backtrace (%s)" % (n, names)
|
|
)
|
|
|
|
@skipIf(compiler="clang", compiler_version=["<", "13.0"])
|
|
def test_objc_exceptions_at_abort(self):
|
|
self.build()
|
|
|
|
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
|
|
self.assertTrue(target, VALID_TARGET)
|
|
|
|
self.runCmd("run 0")
|
|
|
|
# We should be stopped at pthread_kill because of an unhandled exception
|
|
self.expect("thread list", substrs=["stopped", "stop reason = signal SIGABRT"])
|
|
|
|
self.expect(
|
|
"thread exception",
|
|
substrs=[
|
|
"(NSException *) exception = ",
|
|
'"SomeReason"',
|
|
"libobjc.A.dylib`objc_exception_throw",
|
|
"a.out`foo",
|
|
"at main.mm:16",
|
|
"a.out`rethrow",
|
|
"at main.mm:27",
|
|
"a.out`main",
|
|
],
|
|
)
|
|
|
|
process = self.dbg.GetSelectedTarget().process
|
|
thread = process.GetSelectedThread()
|
|
|
|
# There is an exception being currently processed at this point
|
|
self.assertTrue(thread.GetCurrentException().IsValid())
|
|
self.assertTrue(thread.GetCurrentExceptionBacktrace().IsValid())
|
|
|
|
history_thread = thread.GetCurrentExceptionBacktrace()
|
|
self.assertGreaterEqual(history_thread.num_frames, 4)
|
|
for n in ["objc_exception_throw", "foo(int)", "rethrow(int)", "main"]:
|
|
self.assertEqual(
|
|
len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1
|
|
)
|
|
|
|
self.runCmd("kill")
|
|
|
|
self.runCmd("run 1")
|
|
# We should be stopped at pthread_kill because of an unhandled exception
|
|
self.expect("thread list", substrs=["stopped", "stop reason = signal SIGABRT"])
|
|
|
|
self.expect(
|
|
"thread exception",
|
|
substrs=[
|
|
"(MyCustomException *) exception = ",
|
|
"libobjc.A.dylib`objc_exception_throw",
|
|
"a.out`foo",
|
|
"at main.mm:18",
|
|
"a.out`rethrow",
|
|
"at main.mm:27",
|
|
"a.out`main",
|
|
],
|
|
)
|
|
|
|
process = self.dbg.GetSelectedTarget().process
|
|
thread = process.GetSelectedThread()
|
|
|
|
history_thread = thread.GetCurrentExceptionBacktrace()
|
|
self.assertGreaterEqual(history_thread.num_frames, 4)
|
|
for n in ["objc_exception_throw", "foo(int)", "rethrow(int)", "main"]:
|
|
self.assertEqual(
|
|
len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1
|
|
)
|
|
|
|
@skipIf(compiler="clang", compiler_version=["<", "13.0"])
|
|
def test_cxx_exceptions_at_abort(self):
|
|
self.build()
|
|
|
|
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
|
|
self.assertTrue(target, VALID_TARGET)
|
|
|
|
self.runCmd("run 2")
|
|
|
|
# We should be stopped at pthread_kill because of an unhandled exception
|
|
self.expect("thread list", substrs=["stopped", "stop reason = signal SIGABRT"])
|
|
|
|
self.expect("thread exception", substrs=["exception ="])
|
|
|
|
process = self.dbg.GetSelectedTarget().process
|
|
thread = process.GetSelectedThread()
|
|
|
|
self.assertTrue(thread.GetCurrentException().IsValid())
|
|
|
|
# C++ exception backtraces are not exposed in the API (yet).
|
|
self.assertFalse(thread.GetCurrentExceptionBacktrace().IsValid())
|