[Lldb-commits] [lldb] 29c7e6c - Clang added a new feature to the ObjC compiler that will translate method

Jim Ingham via lldb-commits lldb-commits at lists.llvm.org
Thu Jan 23 12:41:55 PST 2020


Author: Jim Ingham
Date: 2020-01-23T12:41:14-08:00
New Revision: 29c7e6c8c97f6b1186f012d614e9a1100e8c06cb

URL: https://github.com/llvm/llvm-project/commit/29c7e6c8c97f6b1186f012d614e9a1100e8c06cb
DIFF: https://github.com/llvm/llvm-project/commit/29c7e6c8c97f6b1186f012d614e9a1100e8c06cb.diff

LOG: Clang added a new feature to the ObjC compiler that will translate method
calls to commonly un-overridden methods into a function that checks whether
the method is overridden anywhere and if not directly dispatches to the
NSObject implementation.

That means if you do override any of these methods, "step-in" will not step
into your code, since we hit the wrapper function, which has no debug info,
and immediately step out again.

Add code to recognize these functions as "trampolines" and a thread plan that
will get us from the function to the user code, if overridden.

<rdar://problem/54404114>

Differential Revision: https://reviews.llvm.org/D73225

Added: 
    lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/Makefile
    lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/TestObjCDirectDispatchStepping.py
    lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/stepping-tests.m

Modified: 
    lldb/include/lldb/Target/ThreadPlan.h
    lldb/include/lldb/Target/ThreadPlanStepInRange.h
    lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp
    lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h
    lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp
    lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Target/ThreadPlan.h b/lldb/include/lldb/Target/ThreadPlan.h
index ff87ed23cda5..b25ee74766d4 100644
--- a/lldb/include/lldb/Target/ThreadPlan.h
+++ b/lldb/include/lldb/Target/ThreadPlan.h
@@ -461,8 +461,12 @@ class ThreadPlan : public std::enable_shared_from_this<ThreadPlan>,
   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
index a120c98fb36e..61ab475729aa 100644
--- a/lldb/include/lldb/Target/ThreadPlanStepInRange.h
+++ b/lldb/include/lldb/Target/ThreadPlanStepInRange.h
@@ -49,6 +49,12 @@ class ThreadPlanStepInRange : public ThreadPlanStepRange,
 
   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
index 000000000000..9906470d5308
--- /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
index 000000000000..99ff5b805925
--- /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
index 000000000000..b919a7ba131d
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/lang/objc/direct-dispatch-step/stepping-tests.m
@@ -0,0 +1,117 @@
+#import <Foundation/Foundation.h>
+
+ at interface OverridesALot: NSObject
+
+- (void)boring;
+
+ at end
+
+ at 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");
+}
+
+ at end
+
+ at interface OverridesInit: NSObject
+
+- (void)boring;
+
+ at end
+
+ at implementation OverridesInit
+
+- (id)init {
+  NSLog(@"init");
+  return [super init];
+}
+
+ at 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.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp
index aee5a7333866..5b5e416b20d8 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp
@@ -657,6 +657,27 @@ const AppleObjCTrampolineHandler::DispatchFunction
          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 @@ AppleObjCTrampolineHandler::AppleObjCTrampolineHandler(
       m_msgSend_map.insert(std::pair<lldb::addr_t, int>(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 @@ AppleObjCTrampolineHandler::SetupDispatchFunction(Thread &thread,
   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<void(lldb::addr_t, 
+                       const DispatchFunction &)> 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 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
     // 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 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
     // 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 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
       // 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 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
       // stop_others value passed in to us here:
       const bool trampoline_stop_others = false;
       ret_plan_sp = std::make_shared<AppleThreadPlanStepThroughObjCTrampoline>(
-          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 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
       }
     }
   }
+  
+  // 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<AppleThreadPlanStepThroughDirectDispatch> (
+          thread, *this, opt_name, trampoline_stop_others, step_in_should_stop);
+    }
+  }
 
   return ret_plan_sp;
 }

diff  --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h
index d120d671eeb3..91ca03c58016 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h
@@ -47,6 +47,9 @@ class AppleObjCTrampolineHandler {
 
   lldb::addr_t SetupDispatchFunction(Thread &thread,
                                      ValueList &dispatch_values);
+  const DispatchFunction *FindDispatchFunction(lldb::addr_t addr);
+  void ForEachDispatchFunction(std::function<void(lldb::addr_t, 
+                                                  const DispatchFunction &)>);
 
 private:
   static const char *g_lookup_implementation_function_name;
@@ -136,11 +139,13 @@ class AppleObjCTrampolineHandler {
   };
 
   static const DispatchFunction g_dispatch_functions[];
+  static const char *g_opt_dispatch_names[];
 
-  typedef std::map<lldb::addr_t, int> MsgsendMap; // This table maps an dispatch
+  using MsgsendMap = std::map<lldb::addr_t, int>; // 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/AppleThreadPlanStepThroughObjCTrampoline.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp
index af630eee7265..ba9fb7f2e6ae 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp
@@ -29,7 +29,7 @@ using namespace lldb_private;
 // 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 @@ bool AppleThreadPlanStepThroughObjCTrampoline::InitializeFunctionCaller() {
   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 @@ bool AppleThreadPlanStepThroughObjCTrampoline::InitializeFunctionCaller() {
     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 @@ bool AppleThreadPlanStepThroughObjCTrampoline::ShouldStop(Event *event_ptr) {
       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 @@ bool AppleThreadPlanStepThroughObjCTrampoline::ShouldStop(Event *event_ptr) {
 
     m_run_to_sp = std::make_shared<ThreadPlanRunToAddress>(
         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::MischiefManaged() {
 }
 
 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();
+}

diff  --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h
index 96f37851a35f..01f7b3849d39 100644
--- 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 @@ namespace lldb_private {
 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 @@ class AppleThreadPlanStepThroughObjCTrampoline : public ThreadPlan {
 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<lldb::BreakpointSP> 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


        


More information about the lldb-commits mailing list