diff --git a/lldb/include/lldb/Target/ThreadPlan.h b/lldb/include/lldb/Target/ThreadPlan.h --- a/lldb/include/lldb/Target/ThreadPlan.h +++ b/lldb/include/lldb/Target/ThreadPlan.h @@ -461,8 +461,12 @@ virtual void WillPop(); // This pushes a plan onto the plan stack of the current plan's thread. + // Also sets the plans to private and not master plans. A plan pushed by + // another thread plan is never either of the above. void PushPlan(lldb::ThreadPlanSP &thread_plan_sp) { m_thread.PushPlan(thread_plan_sp); + thread_plan_sp->SetPrivate(false); + thread_plan_sp->SetIsMasterPlan(false); } ThreadPlanKind GetKind() const { return m_kind; } diff --git a/lldb/include/lldb/Target/ThreadPlanStepInRange.h b/lldb/include/lldb/Target/ThreadPlanStepInRange.h --- a/lldb/include/lldb/Target/ThreadPlanStepInRange.h +++ b/lldb/include/lldb/Target/ThreadPlanStepInRange.h @@ -49,6 +49,12 @@ bool IsVirtualStep() override; + // Plans that are implementing parts of a step in might need to follow the + // behavior of this plan w.r.t. StepThrough. They can get that from here. + static uint32_t GetDefaultFlagsValue() { + return s_default_flag_values; + } + protected: static bool DefaultShouldStopHereCallback(ThreadPlan *current_plan, Flags &flags, diff --git a/lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/Makefile b/lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/Makefile new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/Makefile @@ -0,0 +1,4 @@ +OBJC_SOURCES := stepping-tests.m +LD_EXTRAS := -lobjc -framework Foundation + +include Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/TestObjCDirectDispatchStepping.py b/lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/TestObjCDirectDispatchStepping.py new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/TestObjCDirectDispatchStepping.py @@ -0,0 +1,50 @@ +"""Test stepping through ObjC method dispatch in various forms.""" + +from __future__ import print_function + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestObjCDirectDispatchStepping(TestBase): + + mydir = TestBase.compute_mydir(__file__) + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line numbers that we will step to in main: + self.main_source = lldb.SBFileSpec("stepping-tests.m") + + @skipUnlessDarwin + @add_test_categories(['pyapi', 'basic_process']) + def test_with_python_api(self): + """Test stepping through the 'direct dispatch' optimized method calls.""" + self.build() + + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self, + "Stop here to start stepping", + self.main_source) + stop_bkpt = target.BreakpointCreateBySourceRegex("// Stop Location [0-9]+", self.main_source) + self.assertEqual(stop_bkpt.GetNumLocations(), 15) + + # Here we step through all the overridden methods of OverridesALot + # The last continue will get us to the call ot OverridesInit. + for idx in range(2,16): + thread.StepInto() + func_name = thread.GetFrameAtIndex(0).GetFunctionName() + self.assertTrue("OverridesALot" in func_name, "%d'th step did not match name: %s"%(idx, func_name)) + stop_threads = lldbutil.continue_to_breakpoint(process, stop_bkpt) + self.assertEqual(len(stop_threads), 1) + self.assertEqual(stop_threads[0], thread) + + thread.StepInto() + func_name = thread.GetFrameAtIndex(0).GetFunctionName() + self.assertEqual(func_name, "-[OverridesInit init]", "Stopped in [OverridesInit init]") + + + diff --git a/lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/stepping-tests.m b/lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/stepping-tests.m new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/stepping-tests.m @@ -0,0 +1,117 @@ +#import + +@interface OverridesALot: NSObject + +- (void)boring; + +@end + +@implementation OverridesALot + ++ (id)alloc { + NSLog(@"alloc"); + return [super alloc]; +} + ++ (id)allocWithZone: (NSZone *)z { + NSLog(@"allocWithZone:"); + return [super allocWithZone: z]; +} + ++ (id)new { + NSLog(@"new"); + return [super new]; +} + +- (id)init { + NSLog(@"init"); + return [super init]; +} + +- (id)self { + NSLog(@"self"); + return [super self]; +} + ++ (id)class { + NSLog(@"class"); + return [super class]; +} + +- (BOOL)isKindOfClass: (Class)c { + NSLog(@"isKindOfClass:"); + return [super isKindOfClass: c]; +} + +- (BOOL)respondsToSelector: (SEL)s { + NSLog(@"respondsToSelector:"); + return [super respondsToSelector: s]; +} + +- (id)retain { + NSLog(@"retain"); + return [super retain]; +} + +- (oneway void)release { + NSLog(@"release"); + [super release]; +} + +- (id)autorelease { + NSLog(@"autorelease"); + return [super autorelease]; +} + +- (void)boring { + NSLog(@"boring"); +} + +@end + +@interface OverridesInit: NSObject + +- (void)boring; + +@end + +@implementation OverridesInit + +- (id)init { + NSLog(@"init"); + return [super init]; +} + +@end + +int main() { + id obj; + + // First make an object of the class that overrides everything, + // and make sure we step into all the methods: + + obj = [OverridesALot alloc]; // Stop here to start stepping + [obj release]; // Stop Location 2 + + obj = [OverridesALot allocWithZone: NULL]; // Stop Location 3 + [obj release]; // Stop Location 4 + + obj = [OverridesALot new]; // Stop Location 5 + [obj release]; // Stop Location 6 + + obj = [[OverridesALot alloc] init]; // Stop Location 7 + [obj self]; // Stop Location 8 + [obj isKindOfClass: [OverridesALot class]]; // Stop Location 9 + [obj respondsToSelector: @selector(hello)]; // Stop Location 10 + [obj retain]; // Stop Location 11 + [obj autorelease]; // Stop Location 12 + [obj boring]; // Stop Location 13 + [obj release]; // Stop Location 14 + + // Now try a class that only overrides init but not alloc, to make + // sure we get into the second method in a combined call: + + obj = [[OverridesInit alloc] init]; // Stop Location 15 + + return 0; // Stop Location 15 +} diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h @@ -47,6 +47,9 @@ lldb::addr_t SetupDispatchFunction(Thread &thread, ValueList &dispatch_values); + const DispatchFunction *FindDispatchFunction(lldb::addr_t addr); + void ForEachDispatchFunction(std::function); private: static const char *g_lookup_implementation_function_name; @@ -136,11 +139,13 @@ }; static const DispatchFunction g_dispatch_functions[]; + static const char *g_opt_dispatch_names[]; - typedef std::map MsgsendMap; // This table maps an dispatch + using MsgsendMap = std::map; // This table maps an dispatch // fn address to the index in // g_dispatch_functions MsgsendMap m_msgSend_map; + MsgsendMap m_opt_dispatch_map; lldb::ProcessWP m_process_wp; lldb::ModuleSP m_objc_module_sp; const char *m_lookup_implementation_function_code; diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp @@ -657,6 +657,27 @@ DispatchFunction::eFixUpFixed}, }; +// This is the table of ObjC "accelerated dispatch" functions. They are a set +// of objc methods that are "seldom overridden" and so the compiler replaces the +// objc_msgSend with a call to one of the dispatch functions. That will check +// whether the method has been overridden, and directly call the Foundation +// implementation if not. +// This table is supposed to be complete. If ones get added in the future, we +// will have to add them to the table. +const char *AppleObjCTrampolineHandler::g_opt_dispatch_names[] = { + "objc_alloc", + "objc_autorelease", + "objc_release", + "objc_retain", + "objc_alloc_init", + "objc_allocWithZone", + "objc_opt_class", + "objc_opt_isKindOfClass", + "objc_opt_new", + "objc_opt_respondsToSelector", + "objc_opt_self", +}; + AppleObjCTrampolineHandler::AppleObjCTrampolineHandler( const ProcessSP &process_sp, const ModuleSP &objc_module_sp) : m_process_wp(), m_objc_module_sp(objc_module_sp), @@ -751,6 +772,20 @@ m_msgSend_map.insert(std::pair(sym_addr, i)); } } + + // Similarly, cache the addresses of the "optimized dispatch" function. + for (size_t i = 0; i != llvm::array_lengthof(g_opt_dispatch_names); i++) { + ConstString name_const_str(g_opt_dispatch_names[i]); + const Symbol *msgSend_symbol = + m_objc_module_sp->FindFirstSymbolWithNameAndType(name_const_str, + eSymbolTypeCode); + if (msgSend_symbol && msgSend_symbol->ValueIsAddress()) { + lldb::addr_t sym_addr = + msgSend_symbol->GetAddressRef().GetOpcodeLoadAddress(target); + + m_opt_dispatch_map.emplace(sym_addr, i); + } + } // Build our vtable dispatch handler here: m_vtables_up.reset(new AppleObjCVTables(process_sp, m_objc_module_sp)); @@ -846,45 +881,53 @@ return args_addr; } +const AppleObjCTrampolineHandler::DispatchFunction * +AppleObjCTrampolineHandler::FindDispatchFunction(lldb::addr_t addr) { + MsgsendMap::iterator pos; + pos = m_msgSend_map.find(addr); + if (pos != m_msgSend_map.end()) { + return &g_dispatch_functions[(*pos).second]; + } + return nullptr; +} + +void +AppleObjCTrampolineHandler::ForEachDispatchFunction( + std::function callback) { + for (auto elem : m_msgSend_map) { + callback(elem.first, g_dispatch_functions[elem.second]); + } +} + ThreadPlanSP AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread, bool stop_others) { ThreadPlanSP ret_plan_sp; lldb::addr_t curr_pc = thread.GetRegisterContext()->GetPC(); - DispatchFunction this_dispatch; - bool found_it = false; + DispatchFunction vtable_dispatch + = {"vtable", 0, false, false, DispatchFunction::eFixUpFixed}; // First step is to look and see if we are in one of the known ObjC // dispatch functions. We've already compiled a table of same, so // consult it. - MsgsendMap::iterator pos; - pos = m_msgSend_map.find(curr_pc); - if (pos != m_msgSend_map.end()) { - this_dispatch = g_dispatch_functions[(*pos).second]; - found_it = true; - } - + const DispatchFunction *this_dispatch = FindDispatchFunction(curr_pc); + // Next check to see if we are in a vtable region: - if (!found_it) { + if (!this_dispatch && m_vtables_up) { uint32_t flags; - if (m_vtables_up) { - found_it = m_vtables_up->IsAddressInVTables(curr_pc, flags); - if (found_it) { - this_dispatch.name = "vtable"; - this_dispatch.stret_return = - (flags & AppleObjCVTables::eOBJC_TRAMPOLINE_STRET) == - AppleObjCVTables::eOBJC_TRAMPOLINE_STRET; - this_dispatch.is_super = false; - this_dispatch.is_super2 = false; - this_dispatch.fixedup = DispatchFunction::eFixUpFixed; - } + if (m_vtables_up->IsAddressInVTables(curr_pc, flags)) { + vtable_dispatch.stret_return = + (flags & AppleObjCVTables::eOBJC_TRAMPOLINE_STRET) == + AppleObjCVTables::eOBJC_TRAMPOLINE_STRET; + this_dispatch = &vtable_dispatch; } } - if (found_it) { + if (this_dispatch) { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP)); // We are decoding a method dispatch. First job is to pull the @@ -921,7 +964,7 @@ // the return struct pointer, and the object is the second, and // the selector is the third. Otherwise the object is the first // and the selector the second. - if (this_dispatch.stret_return) { + if (this_dispatch->stret_return) { obj_index = 1; sel_index = 2; argument_values.PushValue(void_ptr_value); @@ -963,8 +1006,8 @@ // run-to-address plan directly. Otherwise we have to figure out // where the implementation lives. - if (this_dispatch.is_super) { - if (this_dispatch.is_super2) { + if (this_dispatch->is_super) { + if (this_dispatch->is_super2) { // In the objc_msgSendSuper2 case, we don't get the object // directly, we get a structure containing the object and the // class to which the super message is being sent. So we need @@ -1087,25 +1130,25 @@ // flag_value.SetContext (Value::eContextTypeClangType, clang_int_type); flag_value.SetCompilerType(clang_int_type); - if (this_dispatch.stret_return) + if (this_dispatch->stret_return) flag_value.GetScalar() = 1; else flag_value.GetScalar() = 0; dispatch_values.PushValue(flag_value); - if (this_dispatch.is_super) + if (this_dispatch->is_super) flag_value.GetScalar() = 1; else flag_value.GetScalar() = 0; dispatch_values.PushValue(flag_value); - if (this_dispatch.is_super2) + if (this_dispatch->is_super2) flag_value.GetScalar() = 1; else flag_value.GetScalar() = 0; dispatch_values.PushValue(flag_value); - switch (this_dispatch.fixedup) { + switch (this_dispatch->fixedup) { case DispatchFunction::eFixUpNone: flag_value.GetScalar() = 0; dispatch_values.PushValue(flag_value); @@ -1135,7 +1178,7 @@ // stop_others value passed in to us here: const bool trampoline_stop_others = false; ret_plan_sp = std::make_shared( - thread, this, dispatch_values, isa_addr, sel_addr, + thread, *this, dispatch_values, isa_addr, sel_addr, trampoline_stop_others); if (log) { StreamString s; @@ -1144,6 +1187,26 @@ } } } + + // Finally, check if we have hit an "optimized dispatch" function. This will + // either directly call the base implementation or dispatch an objc_msgSend + // if the method has been overridden. So we just do a "step in/step out", + // setting a breakpoint on objc_msgSend, and if we hit the msgSend, we + // will automatically step in again. That's the job of the + // AppleThreadPlanStepThroughDirectDispatch. + if (!this_dispatch && !ret_plan_sp) { + MsgsendMap::iterator pos; + pos = m_opt_dispatch_map.find(curr_pc); + if (pos != m_opt_dispatch_map.end()) { + + const char *opt_name = g_opt_dispatch_names[(*pos).second]; + + bool trampoline_stop_others = false; + LazyBool step_in_should_stop = eLazyBoolCalculate; + ret_plan_sp = std::make_shared ( + thread, *this, opt_name, trampoline_stop_others, step_in_should_stop); + } + } return ret_plan_sp; } diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h @@ -12,6 +12,9 @@ #include "AppleObjCTrampolineHandler.h" #include "lldb/Core/Value.h" #include "lldb/Target/ThreadPlan.h" +#include "lldb/Target/ThreadPlanStepInRange.h" +#include "lldb/Target/ThreadPlanStepOut.h" +#include "lldb/Target/ThreadPlanShouldStopHere.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-types.h" @@ -20,7 +23,7 @@ class AppleThreadPlanStepThroughObjCTrampoline : public ThreadPlan { public: AppleThreadPlanStepThroughObjCTrampoline( - Thread &thread, AppleObjCTrampolineHandler *trampoline_handler, + Thread &thread, AppleObjCTrampolineHandler &trampoline_handler, ValueList &values, lldb::addr_t isa_addr, lldb::addr_t sel_addr, bool stop_others); @@ -52,23 +55,60 @@ private: bool InitializeFunctionCaller(); - AppleObjCTrampolineHandler *m_trampoline_handler; // FIXME - ensure this - // doesn't go away on us? - // SP maybe? - lldb::addr_t m_args_addr; // Stores the address for our step through function - // result structure. - // lldb::addr_t m_object_addr; // This is only for Description. + AppleObjCTrampolineHandler &m_trampoline_handler; /// The handler itself. + lldb::addr_t m_args_addr; /// Stores the address for our step through function + /// result structure. ValueList m_input_values; - lldb::addr_t m_isa_addr; // isa_addr and sel_addr are the keys we will use to - // cache the implementation. + lldb::addr_t m_isa_addr; /// isa_addr and sel_addr are the keys we will use to + /// cache the implementation. lldb::addr_t m_sel_addr; - lldb::ThreadPlanSP m_func_sp; // This is the function call plan. We fill it - // at start, then set it - // to NULL when this plan is done. That way we know to go to: - lldb::ThreadPlanSP m_run_to_sp; // The plan that runs to the target. - FunctionCaller *m_impl_function; // This is a pointer to a impl function that - // is owned by the client that pushes this plan. - bool m_stop_others; + lldb::ThreadPlanSP m_func_sp; /// This is the function call plan. We fill it + /// at start, then set it to NULL when this plan + /// is done. That way we know to go on to: + lldb::ThreadPlanSP m_run_to_sp; /// The plan that runs to the target. + FunctionCaller *m_impl_function; /// This is a pointer to a impl function that + /// is owned by the client that pushes this + /// plan. + bool m_stop_others; /// Whether we should stop other threads. +}; + +class AppleThreadPlanStepThroughDirectDispatch: public ThreadPlanStepOut { +public: + AppleThreadPlanStepThroughDirectDispatch( + Thread &thread, AppleObjCTrampolineHandler &handler, + llvm::StringRef dispatch_func_name, bool stop_others, + LazyBool step_in_avoids_code_without_debug_info); + + ~AppleThreadPlanStepThroughDirectDispatch() override; + + void GetDescription(Stream *s, lldb::DescriptionLevel level) override; + + bool ShouldStop(Event *event_ptr) override; + + bool StopOthers() override { return m_stop_others; } + + bool MischiefManaged() override; + + bool DoWillResume(lldb::StateType resume_state, bool current_plan) override; + + void SetFlagsToDefault() override { + GetFlags().Set(ThreadPlanStepInRange::GetDefaultFlagsValue()); + } + +protected: + bool DoPlanExplainsStop(Event *event_ptr) override; + + AppleObjCTrampolineHandler &m_trampoline_handler; + std::string m_dispatch_func_name; /// Which dispatch function we're stepping + /// through. + lldb::ThreadPlanSP m_objc_step_through_sp; /// When we hit an objc_msgSend, + /// we'll use this plan to get to + /// its target. + std::vector m_msgSend_bkpts; /// Breakpoints on the objc + /// dispatch functions. + bool m_at_msg_send; /// Are we currently handling an msg_send + bool m_stop_others; /// Whether we should stop other threads. + }; } // namespace lldb_private diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp @@ -29,7 +29,7 @@ // ThreadPlanStepThroughObjCTrampoline constructor AppleThreadPlanStepThroughObjCTrampoline:: AppleThreadPlanStepThroughObjCTrampoline( - Thread &thread, AppleObjCTrampolineHandler *trampoline_handler, + Thread &thread, AppleObjCTrampolineHandler &trampoline_handler, ValueList &input_values, lldb::addr_t isa_addr, lldb::addr_t sel_addr, bool stop_others) : ThreadPlan(ThreadPlan::eKindGeneric, @@ -56,13 +56,13 @@ if (!m_func_sp) { DiagnosticManager diagnostics; m_args_addr = - m_trampoline_handler->SetupDispatchFunction(m_thread, m_input_values); + m_trampoline_handler.SetupDispatchFunction(m_thread, m_input_values); if (m_args_addr == LLDB_INVALID_ADDRESS) { return false; } m_impl_function = - m_trampoline_handler->GetLookupImplementationFunctionCaller(); + m_trampoline_handler.GetLookupImplementationFunctionCaller(); ExecutionContext exc_ctx; EvaluateExpressionOptions options; options.SetUnwindOnError(true); @@ -72,7 +72,7 @@ m_func_sp = m_impl_function->GetThreadPlanToCallFunction( exc_ctx, m_args_addr, options, diagnostics); m_func_sp->SetOkayToDiscard(true); - m_thread.QueueThreadPlan(m_func_sp, false); + PushPlan(m_func_sp); } return true; } @@ -145,7 +145,7 @@ SetPlanComplete(); return true; } - if (m_trampoline_handler->AddrIsMsgForward(target_addr)) { + if (m_trampoline_handler.AddrIsMsgForward(target_addr)) { LLDB_LOGF(log, "Implementation lookup returned msgForward function: 0x%" PRIx64 ", stopping.", @@ -181,8 +181,7 @@ m_run_to_sp = std::make_shared( m_thread, target_so_addr, m_stop_others); - m_thread.QueueThreadPlan(m_run_to_sp, false); - m_run_to_sp->SetPrivate(true); + PushPlan(m_run_to_sp); return false; } else if (m_thread.IsThreadPlanDone(m_run_to_sp.get())) { // Third stage, work the run to target plan. @@ -199,3 +198,227 @@ } bool AppleThreadPlanStepThroughObjCTrampoline::WillStop() { return true; } + +// Objective-C uses optimized dispatch functions for some common and seldom +// overridden methods. For instance +// [object respondsToSelector:]; +// will get compiled to: +// objc_opt_respondsToSelector(object); +// This checks whether the selector has been overridden, directly calling the +// implementation if it hasn't and calling objc_msgSend if it has. +// +// We need to get into the overridden implementation. We'll do that by +// setting a breakpoint on objc_msgSend, and doing a "step out". If we stop +// at objc_msgSend, we can step through to the target of the send, and see if +// that's a place we want to stop. +// +// A couple of complexities. The checking code might call some other method, +// so we might see objc_msgSend more than once. Also, these optimized dispatch +// functions might dispatch more than one message at a time (e.g. alloc followed +// by init.) So we can't give up at the first objc_msgSend. +// That means among other things that we have to handle the "ShouldStopHere" - +// since we can't just return control to the plan that's controlling us on the +// first step. + +AppleThreadPlanStepThroughDirectDispatch + ::AppleThreadPlanStepThroughDirectDispatch( + Thread &thread, AppleObjCTrampolineHandler &handler, + llvm::StringRef dispatch_func_name, bool stop_others, + LazyBool step_in_avoids_code_without_debug_info) + : ThreadPlanStepOut(thread, nullptr, true /* first instruction */, + stop_others, + eVoteNoOpinion, eVoteNoOpinion, + 0 /* Step out of zeroth frame */, + eLazyBoolNo /* Our parent plan will decide this + when we are done */, + true /* Run to branch for inline step out */, + false /* Don't gather the return value */), + m_trampoline_handler(handler), + m_dispatch_func_name(dispatch_func_name), m_at_msg_send(false), + m_stop_others(stop_others) { + // Set breakpoints on the dispatch functions: + auto bkpt_callback = [&] (lldb::addr_t addr, + const AppleObjCTrampolineHandler + ::DispatchFunction &dispatch) { + m_msgSend_bkpts.push_back(GetTarget().CreateBreakpoint(addr, + true /* internal */, + false /* hard */)); + m_msgSend_bkpts.back()->SetThreadID(GetThread().GetID()); + }; + handler.ForEachDispatchFunction(bkpt_callback); + + // We'll set the step-out plan in the DidPush so it gets queued in the right + // order. + + bool avoid_nodebug = true; + + switch (step_in_avoids_code_without_debug_info) { + case eLazyBoolYes: + avoid_nodebug = true; + break; + case eLazyBoolNo: + avoid_nodebug = false; + break; + case eLazyBoolCalculate: + avoid_nodebug = GetThread().GetStepInAvoidsNoDebug(); + break; + } + if (avoid_nodebug) + GetFlags().Set(ThreadPlanShouldStopHere::eStepInAvoidNoDebug); + else + GetFlags().Clear(ThreadPlanShouldStopHere::eStepInAvoidNoDebug); + // We only care about step in. Our parent plan will figure out what to + // do when we've stepped out again. + GetFlags().Clear(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug); +} + +AppleThreadPlanStepThroughDirectDispatch:: + ~AppleThreadPlanStepThroughDirectDispatch() { + for (BreakpointSP bkpt_sp : m_msgSend_bkpts) { + GetTarget().RemoveBreakpointByID(bkpt_sp->GetID()); + } +} + +void AppleThreadPlanStepThroughDirectDispatch::GetDescription( + Stream *s, lldb::DescriptionLevel level) { + switch (level) { + case lldb::eDescriptionLevelBrief: + s->PutCString("Step through ObjC direct dispatch function."); + break; + default: + s->Printf("Step through ObjC direct dispatch '%s' using breakpoints: ", + m_dispatch_func_name.c_str()); + bool first = true; + for (auto bkpt_sp : m_msgSend_bkpts) { + if (!first) { + s->PutCString(", "); + } + first = false; + s->Printf("%d", bkpt_sp->GetID()); + } + (*s) << "."; + break; + } +} + +bool +AppleThreadPlanStepThroughDirectDispatch::DoPlanExplainsStop(Event *event_ptr) { + if (ThreadPlanStepOut::DoPlanExplainsStop(event_ptr)) + return true; + + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + + // Check if the breakpoint is one of ours msgSend dispatch breakpoints. + + StopReason stop_reason = eStopReasonNone; + if (stop_info_sp) + stop_reason = stop_info_sp->GetStopReason(); + + // See if this is one of our msgSend breakpoints: + if (stop_reason == eStopReasonBreakpoint) { + ProcessSP process_sp = GetThread().GetProcess(); + uint64_t break_site_id = stop_info_sp->GetValue(); + BreakpointSiteSP site_sp + = process_sp->GetBreakpointSiteList().FindByID(break_site_id); + // Some other plan might have deleted the site's last owner before this + // got to us. In which case, it wasn't our breakpoint... + if (!site_sp) + return false; + + for (BreakpointSP break_sp : m_msgSend_bkpts) { + if (site_sp->IsBreakpointAtThisSite(break_sp->GetID())) { + // If we aren't the only one with a breakpoint on this site, then we + // should just stop and return control to the user. + if (site_sp->GetNumberOfOwners() > 1) { + SetPlanComplete(true); + return false; + } + m_at_msg_send = true; + return true; + } + } + } + + // We're done here. If one of our sub-plans explained the stop, they + // would have already answered true to PlanExplainsStop, and if they were + // done, we'll get called to figure out what to do in ShouldStop... + return false; +} + +bool AppleThreadPlanStepThroughDirectDispatch + ::DoWillResume(lldb::StateType resume_state, bool current_plan) { + ThreadPlanStepOut::DoWillResume(resume_state, current_plan); + m_at_msg_send = false; + return true; +} + +bool AppleThreadPlanStepThroughDirectDispatch::ShouldStop(Event *event_ptr) { + // If step out plan finished, that means we didn't find our way into a method + // implementation. Either we went directly to the default implementation, + // of the overridden implementation didn't have debug info. + // So we should mark ourselves as done. + const bool step_out_should_stop = ThreadPlanStepOut::ShouldStop(event_ptr); + if (step_out_should_stop) { + SetPlanComplete(true); + return true; + } + + // If we have a step through plan, then w're in the process of getting + // through an ObjC msgSend. If we arrived at the target function, then + // check whether we have debug info, and if we do, stop. + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP)); + + if (m_objc_step_through_sp && m_objc_step_through_sp->IsPlanComplete()) { + // If the plan failed for some reason, we should probably just let the + // step over plan get us out of here... We don't need to do anything about + // the step through plan, it is done and will get popped when we continue. + if (!m_objc_step_through_sp->PlanSucceeded()) { + LLDB_LOGF(log, "ObjC Step through plan failed. Stepping out."); + } + Status error; + if (InvokeShouldStopHereCallback(eFrameCompareYounger, error)) { + SetPlanComplete(true); + return true; + } + // If we didn't want to stop at this msgSend, there might be another so + // we should just continue on with the step out and see if our breakpoint + // triggers again. + m_objc_step_through_sp.reset(); + for (BreakpointSP bkpt_sp : m_msgSend_bkpts) { + bkpt_sp->SetEnabled(true); + } + return false; + } + + // If we hit an msgSend breakpoint, then we should queue the step through + // plan: + + if (m_at_msg_send) { + LanguageRuntime *objc_runtime + = GetThread().GetProcess()->GetLanguageRuntime(eLanguageTypeObjC); + // There's no way we could have gotten here without an ObjC language + // runtime. + assert(objc_runtime); + m_objc_step_through_sp + = objc_runtime->GetStepThroughTrampolinePlan(GetThread(), m_stop_others); + // If we failed to find the target for this dispatch, just keep going and + // let the step out complete. + if (!m_objc_step_through_sp) { + LLDB_LOG(log, "Couldn't find target for message dispatch, continuing."); + return false; + } + // Otherwise push the step through plan and continue. + GetThread().QueueThreadPlan(m_objc_step_through_sp, false); + for (BreakpointSP bkpt_sp : m_msgSend_bkpts) { + bkpt_sp->SetEnabled(false); + } + return false; + } + return true; +} + +bool AppleThreadPlanStepThroughDirectDispatch::MischiefManaged() { + if (IsPlanComplete()) + return true; + return ThreadPlanStepOut::MischiefManaged(); +}