Adapt the ObjC stepping algorithm to deal with "selector-stubs" in clang.
authorJim Ingham <jingham@apple.com>
Fri, 8 Apr 2022 19:25:53 +0000 (12:25 -0700)
committerJim Ingham <jingham@apple.com>
Sat, 9 Apr 2022 00:45:16 +0000 (17:45 -0700)
Clang is adding a feature to ObjC code generation, where instead of calling
objc_msgSend directly with an object & selector, it generates a stub that
gets passed only the object and the stub figures out the selector.

This patch adds support for following that dispatch method into the implementation
function.

lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp
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
lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp
lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h
lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCKVO.py

index b8104af..ccc68b5 100644 (file)
@@ -1554,7 +1554,7 @@ AppleObjCRuntimeV2::DynamicClassInfoExtractor::GetClassInfoUtilityFunctionImpl(
   if (!utility_fn_or_error) {
     LLDB_LOG_ERROR(
         log, utility_fn_or_error.takeError(),
-        "Failed to get utility function for implementation lookup: {0}");
+        "Failed to get utility function for dynamic info extractor: {0}");
     return {};
   }
 
@@ -1684,7 +1684,7 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::
   if (!utility_fn_or_error) {
     LLDB_LOG_ERROR(
         log, utility_fn_or_error.takeError(),
-        "Failed to get utility function for implementation lookup: {0}");
+        "Failed to get utility function for shared class info extractor: {0}");
     return nullptr;
   }
 
index 7164a49..f9ccaf0 100644 (file)
@@ -33,6 +33,7 @@
 #include "lldb/Utility/Log.h"
 
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/ScopeExit.h"
 
 #include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h"
 
@@ -45,239 +46,138 @@ const char *AppleObjCTrampolineHandler::g_lookup_implementation_function_name =
     "__lldb_objc_find_implementation_for_selector";
 const char *AppleObjCTrampolineHandler::
     g_lookup_implementation_with_stret_function_code =
-        "                               \n\
-extern \"C\"                                                                 \n\
-{                                                                            \n\
-    extern void *class_getMethodImplementation(void *objc_class, void *sel); \n\
-    extern void *class_getMethodImplementation_stret(void *objc_class,       \n\
-                                                     void *sel);             \n\
-    extern void * object_getClass (id object);                               \n\
-    extern void * sel_getUid(char *name);                                    \n\
-    extern int printf(const char *format, ...);                              \n\
-}                                                                            \n\
-extern \"C\" void * __lldb_objc_find_implementation_for_selector (           \n\
-                                                    void *object,            \n\
-                                                    void *sel,               \n\
-                                                    int is_stret,            \n\
-                                                    int is_super,            \n\
-                                                    int is_super2,           \n\
-                                                    int is_fixup,            \n\
-                                                    int is_fixed,            \n\
-                                                    int debug)               \n\
-{                                                                            \n\
-    struct __lldb_imp_return_struct                                          \n\
-    {                                                                        \n\
-        void *class_addr;                                                    \n\
-        void *sel_addr;                                                      \n\
-        void *impl_addr;                                                     \n\
-    };                                                                       \n\
-                                                                             \n\
-    struct __lldb_objc_class {                                               \n\
-        void *isa;                                                           \n\
-        void *super_ptr;                                                     \n\
-    };                                                                       \n\
-    struct __lldb_objc_super {                                               \n\
-        void *receiver;                                                      \n\
-        struct __lldb_objc_class *class_ptr;                                 \n\
-    };                                                                       \n\
-    struct __lldb_msg_ref {                                                  \n\
-        void *dont_know;                                                     \n\
-        void *sel;                                                           \n\
-    };                                                                       \n\
-                                                                             \n\
-    struct __lldb_imp_return_struct return_struct;                           \n\
-                                                                             \n\
-    if (debug)                                                               \n\
-        printf (\"\\n*** Called with obj: 0x%p sel: 0x%p is_stret: %d is_super: %d, \"\n\
-                \"is_super2: %d, is_fixup: %d, is_fixed: %d\\n\",            \n\
-                 object, sel, is_stret, is_super, is_super2, is_fixup, is_fixed);\n\
-    if (is_super)                                                            \n\
-    {                                                                        \n\
-        if (is_super2)                                                       \n\
-        {                                                                    \n\
-            return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr->super_ptr;\n\
-        }                                                                    \n\
-        else                                                                 \n\
-        {                                                                    \n\
-            return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr;\n\
-        }                                                                    \n\
-    }                                                                        \n\
-    else                                                                     \n\
-    {                                                                        \n\
-        // This code seems a little funny, but has its reasons...            \n\
-                                                                             \n\
-        // The call to [object class] is here because if this is a           \n\
-        // class, and has not been called into yet, we need to do            \n\
-        // something to force the class to initialize itself.                \n\
-        // Then the call to object_getClass will actually return the         \n\
-        // correct class, either the class if object is a class              \n\
-        // instance, or the meta-class if it is a class pointer.             \n\
-        void *class_ptr = (void *) [(id) object class];                      \n\
-        return_struct.class_addr = (id)  object_getClass((id) object);       \n\
-        if (debug)                                                           \n\
-        {                                                                    \n\
-            if (class_ptr == object)                                         \n\
-            {                                                                \n\
-                printf (\"Found a class object, need to use the meta class %p -> %p\\n\",\n\
-                        class_ptr, return_struct.class_addr);                \n\
-            }                                                                \n\
-            else                                                             \n\
-            {                                                                \n\
-                 printf (\"[object class] returned: %p object_getClass: %p.\\n\", \n\
-                 class_ptr, return_struct.class_addr);                       \n\
-            }                                                                \n\
-        }                                                                    \n\
-    }                                                                        \n\
-                                                                             \n\
-    if (is_fixup)                                                            \n\
-    {                                                                        \n\
-        if (is_fixed)                                                        \n\
-        {                                                                    \n\
-            return_struct.sel_addr = ((__lldb_msg_ref *) sel)->sel;          \n\
-        }                                                                    \n\
-        else                                                                 \n\
-        {                                                                    \n\
-            char *sel_name = (char *) ((__lldb_msg_ref *) sel)->sel;         \n\
-            return_struct.sel_addr = sel_getUid (sel_name);                  \n\
-            if (debug)                                                       \n\
-                printf (\"\\n*** Got fixed up selector: %p for name %s.\\n\",\n\
-                        return_struct.sel_addr, sel_name);                   \n\
-        }                                                                    \n\
-    }                                                                        \n\
-    else                                                                     \n\
-    {                                                                        \n\
-        return_struct.sel_addr = sel;                                        \n\
-    }                                                                        \n\
-                                                                             \n\
-    if (is_stret)                                                            \n\
-    {                                                                        \n\
-        return_struct.impl_addr =                                            \n\
-          class_getMethodImplementation_stret (return_struct.class_addr,     \n\
-                                               return_struct.sel_addr);      \n\
-    }                                                                        \n\
-    else                                                                     \n\
-    {                                                                        \n\
-        return_struct.impl_addr =                                            \n\
-            class_getMethodImplementation (return_struct.class_addr,         \n\
-                                           return_struct.sel_addr);          \n\
-    }                                                                        \n\
-    if (debug)                                                               \n\
-        printf (\"\\n*** Returning implementation: %p.\\n\",                 \n\
-                          return_struct.impl_addr);                          \n\
-                                                                             \n\
-    return return_struct.impl_addr;                                          \n\
-}                                                                            \n\
-";
+        R"(
+  if (is_stret) {
+    return_struct.impl_addr =
+    class_getMethodImplementation_stret (return_struct.class_addr,
+                                         return_struct.sel_addr);
+  } else {
+    return_struct.impl_addr =
+        class_getMethodImplementation (return_struct.class_addr,
+                                       return_struct.sel_addr);
+  }
+  if (debug)
+    printf ("\n*** Returning implementation: %p.\n",
+            return_struct.impl_addr);
+
+  return return_struct.impl_addr;
+}
+)";
 const char *
     AppleObjCTrampolineHandler::g_lookup_implementation_no_stret_function_code =
-        "                      \n\
-extern \"C\"                                                                 \n\
-{                                                                            \n\
-    extern void *class_getMethodImplementation(void *objc_class, void *sel); \n\
-    extern void * object_getClass (id object);                               \n\
-    extern void * sel_getUid(char *name);                                    \n\
-    extern int printf(const char *format, ...);                              \n\
-}                                                                            \n\
-extern \"C\" void * __lldb_objc_find_implementation_for_selector (void *object,                                 \n\
-                                                    void *sel,               \n\
-                                                    int is_stret,            \n\
-                                                    int is_super,            \n\
-                                                    int is_super2,           \n\
-                                                    int is_fixup,            \n\
-                                                    int is_fixed,            \n\
-                                                    int debug)               \n\
-{                                                                            \n\
-    struct __lldb_imp_return_struct                                          \n\
-    {                                                                        \n\
-        void *class_addr;                                                    \n\
-        void *sel_addr;                                                      \n\
-        void *impl_addr;                                                     \n\
-    };                                                                       \n\
-                                                                             \n\
-    struct __lldb_objc_class {                                               \n\
-        void *isa;                                                           \n\
-        void *super_ptr;                                                     \n\
-    };                                                                       \n\
-    struct __lldb_objc_super {                                               \n\
-        void *receiver;                                                      \n\
-        struct __lldb_objc_class *class_ptr;                                 \n\
-    };                                                                       \n\
-    struct __lldb_msg_ref {                                                  \n\
-        void *dont_know;                                                     \n\
-        void *sel;                                                           \n\
-    };                                                                       \n\
-                                                                             \n\
-    struct __lldb_imp_return_struct return_struct;                           \n\
-                                                                             \n\
-    if (debug)                                                               \n\
-        printf (\"\\n*** Called with obj: 0x%p sel: 0x%p is_stret: %d is_super: %d, \"                          \n\
-                \"is_super2: %d, is_fixup: %d, is_fixed: %d\\n\",            \n\
-                 object, sel, is_stret, is_super, is_super2, is_fixup, is_fixed);                               \n\
-    if (is_super)                                                            \n\
-    {                                                                        \n\
-        if (is_super2)                                                       \n\
-        {                                                                    \n\
-            return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr->super_ptr;                    \n\
-        }                                                                    \n\
-        else                                                                 \n\
-        {                                                                    \n\
-            return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr;                               \n\
-        }                                                                    \n\
-    }                                                                        \n\
-    else                                                                     \n\
-    {                                                                        \n\
-        // This code seems a little funny, but has its reasons...            \n\
-        // The call to [object class] is here because if this is a class, and has not been called into          \n\
-        // yet, we need to do something to force the class to initialize itself.                                \n\
-        // Then the call to object_getClass will actually return the correct class, either the class            \n\
-        // if object is a class instance, or the meta-class if it is a class pointer.                           \n\
-        void *class_ptr = (void *) [(id) object class];                      \n\
-        return_struct.class_addr = (id)  object_getClass((id) object);       \n\
-        if (debug)                                                           \n\
-        {                                                                    \n\
-            if (class_ptr == object)                                         \n\
-            {                                                                \n\
-                printf (\"Found a class object, need to return the meta class %p -> %p\\n\",                    \n\
-                        class_ptr, return_struct.class_addr);                \n\
-            }                                                                \n\
-            else                                                             \n\
-            {                                                                \n\
-                 printf (\"[object class] returned: %p object_getClass: %p.\\n\",                               \n\
-                 class_ptr, return_struct.class_addr);                       \n\
-            }                                                                \n\
-        }                                                                    \n\
-    }                                                                        \n\
-                                                                             \n\
-    if (is_fixup)                                                            \n\
-    {                                                                        \n\
-        if (is_fixed)                                                        \n\
-        {                                                                    \n\
-            return_struct.sel_addr = ((__lldb_msg_ref *) sel)->sel;          \n\
-        }                                                                    \n\
-        else                                                                 \n\
-        {                                                                    \n\
-            char *sel_name = (char *) ((__lldb_msg_ref *) sel)->sel;         \n\
-            return_struct.sel_addr = sel_getUid (sel_name);                  \n\
-            if (debug)                                                       \n\
-                printf (\"\\n*** Got fixed up selector: %p for name %s.\\n\",\n\
-                        return_struct.sel_addr, sel_name);                   \n\
-        }                                                                    \n\
-    }                                                                        \n\
-    else                                                                     \n\
-    {                                                                        \n\
-        return_struct.sel_addr = sel;                                        \n\
-    }                                                                        \n\
-                                                                             \n\
-    return_struct.impl_addr =                                                \n\
-      class_getMethodImplementation (return_struct.class_addr,               \n\
-                                     return_struct.sel_addr);                \n\
-    if (debug)                                                               \n\
-        printf (\"\\n*** Returning implementation: 0x%p.\\n\",               \n\
-          return_struct.impl_addr);                                          \n\
-                                                                             \n\
-    return return_struct.impl_addr;                                          \n\
-}                                                                            \n\
-";
+        R"(
+  return_struct.impl_addr =
+    class_getMethodImplementation (return_struct.class_addr,
+                                   return_struct.sel_addr);
+  if (debug)
+    printf ("\n*** getMethodImpletation for addr: 0x%p sel: 0x%p result: 0x%p.\n",
+            return_struct.class_addr, return_struct.sel_addr, return_struct.impl_addr);
+
+  return return_struct.impl_addr;
+}
+)";
+
+const char
+    *AppleObjCTrampolineHandler::g_lookup_implementation_function_common_code =
+        R"(
+extern "C"
+{
+  extern void *class_getMethodImplementation(void *objc_class, void *sel);
+  extern void *class_getMethodImplementation_stret(void *objc_class, void *sel);
+  extern void * object_getClass (id object);
+  extern void * sel_getUid(char *name);
+  extern int printf(const char *format, ...);
+}
+extern "C" void * 
+__lldb_objc_find_implementation_for_selector (void *object,
+                                              void *sel,
+                                              int is_str_ptr,
+                                              int is_stret,
+                                              int is_super,
+                                              int is_super2,
+                                              int is_fixup,
+                                              int is_fixed,
+                                              int debug)
+{
+  struct __lldb_imp_return_struct {
+    void *class_addr;
+    void *sel_addr;
+    void *impl_addr;
+  };
+
+  struct __lldb_objc_class {
+    void *isa;
+    void *super_ptr;
+  };
+  struct __lldb_objc_super {
+    void *receiver;
+    struct __lldb_objc_class *class_ptr;
+  };
+  struct __lldb_msg_ref {
+    void *dont_know;
+    void *sel;
+  };
+
+  struct __lldb_imp_return_struct return_struct;
+                                                                           
+  if (debug)
+    printf ("\n*** Called with obj: %p sel: %p is_str_ptr: %d "
+            "is_stret: %d is_super: %d, "
+            "is_super2: %d, is_fixup: %d, is_fixed: %d\n",
+             object, sel, is_str_ptr, is_stret,
+             is_super, is_super2, is_fixup, is_fixed);
+
+  if (is_str_ptr) {
+    if (debug)
+      printf("*** Turning string: '%s'", sel);
+    sel = sel_getUid((char *)sel);
+    if (debug)
+      printf("*** into sel to %p", sel);
+  }
+  if (is_super) {
+    if (is_super2) {
+      return_struct.class_addr 
+          = ((__lldb_objc_super *) object)->class_ptr->super_ptr;
+    } else {
+      return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr;
+    }
+    if (debug)
+      printf("*** Super, class addr: %p\n", return_struct.class_addr);
+  } else {
+    // This code seems a little funny, but has its reasons...
+    // The call to [object class] is here because if this is a class, and has 
+    // not been called into yet, we need to do something to force the class to 
+    // initialize itself.
+    // Then the call to object_getClass will actually return the correct class, 
+    // either the class if object is a class instance, or the meta-class if it 
+    // is a class pointer.
+    void *class_ptr = (void *) [(id) object class];
+    return_struct.class_addr = (id)  object_getClass((id) object);
+    if (debug) {
+      if (class_ptr == object) {
+        printf ("Found a class object, need to return the meta class %p -> %p\n",
+                class_ptr, return_struct.class_addr);
+      } else {
+         printf ("[object class] returned: %p object_getClass: %p.\n",
+                 class_ptr, return_struct.class_addr);
+      }
+    }
+  }
+
+  if (is_fixup) {
+    if (is_fixed) {
+        return_struct.sel_addr = ((__lldb_msg_ref *) sel)->sel;
+    } else {
+      char *sel_name = (char *) ((__lldb_msg_ref *) sel)->sel;
+      return_struct.sel_addr = sel_getUid (sel_name);
+      if (debug)
+        printf ("\n*** Got fixed up selector: %p for name %s.\n",
+                return_struct.sel_addr, sel_name);
+    }
+  } else {
+    return_struct.sel_addr = sel;
+  }
+)";
 
 AppleObjCTrampolineHandler::AppleObjCVTables::VTableRegion::VTableRegion(
     AppleObjCVTables *owner, lldb::addr_t header_addr)
@@ -676,7 +576,6 @@ const char *AppleObjCTrampolineHandler::g_opt_dispatch_names[] = {
 AppleObjCTrampolineHandler::AppleObjCTrampolineHandler(
     const ProcessSP &process_sp, const ModuleSP &objc_module_sp)
     : m_process_wp(), m_objc_module_sp(objc_module_sp),
-      m_lookup_implementation_function_code(nullptr),
       m_impl_fn_addr(LLDB_INVALID_ADDRESS),
       m_impl_stret_fn_addr(LLDB_INVALID_ADDRESS),
       m_msg_forward_addr(LLDB_INVALID_ADDRESS) {
@@ -729,17 +628,24 @@ AppleObjCTrampolineHandler::AppleObjCTrampolineHandler(
           get_impl_name.AsCString());
     }
     return;
-  } else if (m_impl_stret_fn_addr == LLDB_INVALID_ADDRESS) {
+  }
+  
+  // We will either set the implementation to the _stret or non_stret version,
+  // so either way it's safe to start filling the m_lookup_..._code here.
+  m_lookup_implementation_function_code.assign(
+          g_lookup_implementation_function_common_code);
+
+  if (m_impl_stret_fn_addr == LLDB_INVALID_ADDRESS) {
     // It there is no stret return lookup function, assume that it is the same
     // as the straight lookup:
     m_impl_stret_fn_addr = m_impl_fn_addr;
     // Also we will use the version of the lookup code that doesn't rely on the
     // stret version of the function.
-    m_lookup_implementation_function_code =
-        g_lookup_implementation_no_stret_function_code;
+    m_lookup_implementation_function_code.append(
+        g_lookup_implementation_no_stret_function_code);
   } else {
-    m_lookup_implementation_function_code =
-        g_lookup_implementation_with_stret_function_code;
+    m_lookup_implementation_function_code.append(
+        g_lookup_implementation_with_stret_function_code);
   }
 
   // Look up the addresses for the objc dispatch functions and cache
@@ -806,7 +712,7 @@ AppleObjCTrampolineHandler::SetupDispatchFunction(Thread &thread,
     // First stage is to make the ClangUtility to hold our injected function:
 
     if (!m_impl_code) {
-      if (m_lookup_implementation_function_code != nullptr) {
+      if (!m_lookup_implementation_function_code.empty()) {
         auto utility_fn_or_error = exe_ctx.GetTargetRef().CreateUtilityFunction(
             m_lookup_implementation_function_code,
             g_lookup_implementation_function_name, eLanguageTypeC, exe_ctx);
@@ -891,13 +797,43 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
 
   DispatchFunction vtable_dispatch = {"vtable", false, false, false,
                                       DispatchFunction::eFixUpFixed};
-
-  // First step is to look and see if we are in one of the known ObjC
+  // The selector specific stubs are a wrapper for objc_msgSend.  They don't get
+  // passed a SEL, but instead the selector string is encoded in the stub
+  // name, in the form:
+  //   objc_msgSend$SelectorName
+  // and the stub figures out the uniqued selector.  If we find ourselves in
+  // one of these stubs, we strip off the selector string and pass that to the
+  // implementation finder function, which looks up the SEL (you have to do this
+  // in process) and passes that to the runtime lookup function.
+  DispatchFunction sel_stub_dispatch = {"sel-specific-stub", false, false,
+                                        false, DispatchFunction::eFixUpNone};
+
+  // First step is to see if we're in a selector-specific dispatch stub.
+  // Those are of the form _objc_msgSend$<SELECTOR>, so see if the current
+  // function has that name:
+  Address func_addr;
+  Target &target = thread.GetProcess()->GetTarget();
+  llvm::StringRef sym_name;
+  const DispatchFunction *this_dispatch = nullptr;
+
+  if (target.ResolveLoadAddress(curr_pc, func_addr)) {
+    Symbol *curr_sym = func_addr.CalculateSymbolContextSymbol();
+    if (curr_sym)
+      sym_name = curr_sym->GetName().GetStringRef();
+
+    if (!sym_name.empty() && !sym_name.consume_front("objc_msgSend$"))
+      sym_name = {};
+    else
+      this_dispatch = &sel_stub_dispatch;
+  }
+  bool in_selector_stub = !sym_name.empty();
+  // Second 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.
 
-  const DispatchFunction *this_dispatch = FindDispatchFunction(curr_pc);
-  
+  if (!in_selector_stub)
+    this_dispatch = FindDispatchFunction(curr_pc);
+
   // Next check to see if we are in a vtable region:
 
   if (!this_dispatch && m_vtables_up) {
@@ -910,11 +846,15 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
     }
   }
 
+  // Since we set this_dispatch in both the vtable & sel specific stub cases
+  // this if will be used for all three of those cases.
   if (this_dispatch) {
     Log *log = GetLog(LLDBLog::Step);
 
     // We are decoding a method dispatch.  First job is to pull the
-    // arguments out:
+    // arguments out.  If we are in a regular stub, we get self & selector,
+    // but if we are in a selector-specific stub, we'll have to get that from
+    // the string sym_name.
 
     lldb::StackFrameSP thread_cur_frame = thread.GetStackFrameAtIndex(0);
 
@@ -944,11 +884,17 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
     int obj_index;
     int sel_index;
 
+    // If this is a selector-specific stub then just push one value, 'cause
+    // we only get the object.
     // If this is a struct return dispatch, then the first argument is
     // 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) {
+    // the selector is the third.
+    // Otherwise the object is the first and the selector the second.
+    if (in_selector_stub) {
+      obj_index = 0;
+      sel_index = 1;
+      argument_values.PushValue(void_ptr_value);
+    } else if (this_dispatch->stret_return) {
       obj_index = 1;
       sel_index = 2;
       argument_values.PushValue(void_ptr_value);
@@ -975,15 +921,17 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
     }
 
     ExecutionContext exe_ctx(thread.shared_from_this());
-    Process *process = exe_ctx.GetProcessPtr();
     // isa_addr will store the class pointer that the method is being
     // dispatched to - so either the class directly or the super class
     // if this is one of the objc_msgSendSuper flavors.  That's mostly
     // used to look up the class/selector pair in our cache.
 
     lldb::addr_t isa_addr = LLDB_INVALID_ADDRESS;
-    lldb::addr_t sel_addr =
-        argument_values.GetValueAtIndex(sel_index)->GetScalar().ULongLong();
+    lldb::addr_t sel_addr = LLDB_INVALID_ADDRESS;
+    // If we are not in a selector stub, get the sel address from the arguments.
+    if (!in_selector_stub)
+      sel_addr =
+          argument_values.GetValueAtIndex(sel_index)->GetScalar().ULongLong();
 
     // Figure out the class this is being dispatched to and see if
     // we've already cached this method call, If so we can push a
@@ -998,14 +946,14 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
         // to dig the super out of the class and use that.
 
         Value super_value(*(argument_values.GetValueAtIndex(obj_index)));
-        super_value.GetScalar() += process->GetAddressByteSize();
+        super_value.GetScalar() += process_sp->GetAddressByteSize();
         super_value.ResolveValue(&exe_ctx);
 
         if (super_value.GetScalar().IsValid()) {
 
           // isa_value now holds the class pointer.  The second word of the
           // class pointer is the super-class pointer:
-          super_value.GetScalar() += process->GetAddressByteSize();
+          super_value.GetScalar() += process_sp->GetAddressByteSize();
           super_value.ResolveValue(&exe_ctx);
           if (super_value.GetScalar().IsValid())
             isa_addr = super_value.GetScalar().ULongLong();
@@ -1024,7 +972,7 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
         // this structure.
 
         Value super_value(*(argument_values.GetValueAtIndex(obj_index)));
-        super_value.GetScalar() += process->GetAddressByteSize();
+        super_value.GetScalar() += process_sp->GetAddressByteSize();
         super_value.ResolveValue(&exe_ctx);
 
         if (super_value.GetScalar().IsValid()) {
@@ -1060,20 +1008,22 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
     // Okay, we've got the address of the class for which we're resolving this,
     // let's see if it's in our cache:
     lldb::addr_t impl_addr = LLDB_INVALID_ADDRESS;
-
+    // If this is a regular dispatch, look up the sel in our addr to sel cache:
     if (isa_addr != LLDB_INVALID_ADDRESS) {
-      if (log) {
-        LLDB_LOGF(log,
-                  "Resolving call for class - 0x%" PRIx64
-                  " and selector - 0x%" PRIx64,
-                  isa_addr, sel_addr);
-      }
       ObjCLanguageRuntime *objc_runtime =
           ObjCLanguageRuntime::Get(*thread.GetProcess());
       assert(objc_runtime != nullptr);
-
-      impl_addr = objc_runtime->LookupInMethodCache(isa_addr, sel_addr);
+      if (!in_selector_stub) {
+        LLDB_LOG(log, "Resolving call for class - {0} and selector - {1}",
+                 isa_addr, sel_addr);
+        impl_addr = objc_runtime->LookupInMethodCache(isa_addr, sel_addr);
+      } else {
+        LLDB_LOG(log, "Resolving call for class - {0} and selector - {1}",
+                 isa_addr, sym_name);
+        impl_addr = objc_runtime->LookupInMethodCache(isa_addr, sym_name);
+      }
     }
+    // If it is a selector-specific stub dispatch, look in the string cache:
 
     if (impl_addr != LLDB_INVALID_ADDRESS) {
       // Yup, it was in the cache, so we can run to that address directly.
@@ -1091,20 +1041,52 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
       ValueList dispatch_values;
 
       // We've will inject a little function in the target that takes the
-      // object, selector and some flags,
+      // object, selector/selector string and some flags,
       // and figures out the implementation.  Looks like:
       //      void *__lldb_objc_find_implementation_for_selector (void *object,
       //                                                          void *sel,
+      //                                                          int
+      //                                                          is_str_ptr,
       //                                                          int is_stret,
       //                                                          int is_super,
       //                                                          int is_super2,
       //                                                          int is_fixup,
       //                                                          int is_fixed,
       //                                                          int debug)
+      // If we don't have an actual SEL, but rather a string version of the
+      // selector WE injected, set is_str_ptr to true, and sel to the address
+      // of the string.
       // So set up the arguments for that call.
 
       dispatch_values.PushValue(*(argument_values.GetValueAtIndex(obj_index)));
-      dispatch_values.PushValue(*(argument_values.GetValueAtIndex(sel_index)));
+      lldb::addr_t sel_str_addr = LLDB_INVALID_ADDRESS;
+      if (!in_selector_stub) {
+        // If we don't have a selector string, push the selector from arguments.
+        dispatch_values.PushValue(
+            *(argument_values.GetValueAtIndex(sel_index)));
+      } else {
+        // Otherwise, inject the string into the target, and push that value for
+        // the sel argument.
+        Status error;
+        sel_str_addr = process_sp->AllocateMemory(
+            sym_name.size() + 1, ePermissionsReadable | ePermissionsWritable,
+            error);
+        if (sel_str_addr == LLDB_INVALID_ADDRESS || error.Fail()) {
+          LLDB_LOG(log,
+                   "Could not allocate memory for selector string {0}: {1}",
+                   sym_name, error);
+          return ret_plan_sp;
+        }
+        process_sp->WriteMemory(sel_str_addr, sym_name.str().c_str(),
+                                sym_name.size() + 1, error);
+        if (error.Fail()) {
+          LLDB_LOG(log, "Could not write string to address {0}", sel_str_addr);
+          return ret_plan_sp;
+        }
+        Value sel_ptr_value(void_ptr_value);
+        sel_ptr_value.GetScalar() = sel_str_addr;
+        dispatch_values.PushValue(sel_ptr_value);
+      }
 
       Value flag_value;
       CompilerType clang_int_type =
@@ -1114,6 +1096,12 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
       // flag_value.SetContext (Value::eContextTypeClangType, clang_int_type);
       flag_value.SetCompilerType(clang_int_type);
 
+      if (in_selector_stub)
+        flag_value.GetScalar() = 1;
+      else
+        flag_value.GetScalar() = 0;
+      dispatch_values.PushValue(flag_value);
+
       if (this_dispatch->stret_return)
         flag_value.GetScalar() = 1;
       else
@@ -1158,7 +1146,8 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
       dispatch_values.PushValue(flag_value);
 
       ret_plan_sp = std::make_shared<AppleThreadPlanStepThroughObjCTrampoline>(
-          thread, *this, dispatch_values, isa_addr, sel_addr);
+          thread, *this, dispatch_values, isa_addr, sel_addr, sel_str_addr,
+          sym_name);
       if (log) {
         StreamString s;
         ret_plan_sp->GetDescription(&s, eDescriptionLevelFull);
index 546b500..a6e1e16 100644 (file)
@@ -38,11 +38,11 @@ public:
   public:
     enum FixUpState { eFixUpNone, eFixUpFixed, eFixUpToFix };
 
-    const char *name;
-    bool stret_return;
-    bool is_super;
-    bool is_super2;
-    FixUpState fixedup;
+    const char *name = nullptr;
+    bool stret_return = false;
+    bool is_super = false;
+    bool is_super2 = false;
+    FixUpState fixedup = eFixUpNone;
   };
 
   lldb::addr_t SetupDispatchFunction(Thread &thread,
@@ -52,9 +52,19 @@ public:
                                                   const DispatchFunction &)>);
 
 private:
+  /// These hold the code for the function that finds the implementation of
+  /// an ObjC message send given the class & selector and the kind of dispatch.
+  /// There are two variants depending on whether the platform uses a separate
+  /// _stret passing convention (e.g. Intel) or not (e.g. ARM).  The difference
+  /// is only at the very end of the function, so the code is broken into the
+  /// common prefix and the suffix, which get composed appropriately before
+  /// the function gets compiled.
+  /// \{
   static const char *g_lookup_implementation_function_name;
+  static const char *g_lookup_implementation_function_common_code;
   static const char *g_lookup_implementation_with_stret_function_code;
   static const char *g_lookup_implementation_no_stret_function_code;
+  /// \}
 
   class AppleObjCVTables {
   public:
@@ -144,7 +154,7 @@ private:
   MsgsendMap m_opt_dispatch_map;
   lldb::ProcessWP m_process_wp;
   lldb::ModuleSP m_objc_module_sp;
-  const char *m_lookup_implementation_function_code;
+  std::string m_lookup_implementation_function_code;
   std::unique_ptr<UtilityFunction> m_impl_code;
   std::mutex m_impl_function_mutex;
   lldb::addr_t m_impl_fn_addr;
index b212518..547a1f6 100644 (file)
@@ -32,13 +32,15 @@ using namespace lldb_private;
 AppleThreadPlanStepThroughObjCTrampoline::
     AppleThreadPlanStepThroughObjCTrampoline(
         Thread &thread, AppleObjCTrampolineHandler &trampoline_handler,
-        ValueList &input_values, lldb::addr_t isa_addr, lldb::addr_t sel_addr)
+        ValueList &input_values, lldb::addr_t isa_addr, lldb::addr_t sel_addr,
+        lldb::addr_t sel_str_addr, llvm::StringRef sel_str)
     : ThreadPlan(ThreadPlan::eKindGeneric,
                  "MacOSX Step through ObjC Trampoline", thread, eVoteNoOpinion,
                  eVoteNoOpinion),
       m_trampoline_handler(trampoline_handler),
       m_args_addr(LLDB_INVALID_ADDRESS), m_input_values(input_values),
-      m_isa_addr(isa_addr), m_sel_addr(sel_addr), m_impl_function(nullptr) {}
+      m_isa_addr(isa_addr), m_sel_addr(sel_addr), m_impl_function(nullptr),
+      m_sel_str_addr(sel_str_addr), m_sel_str(sel_str) {}
 
 // Destructor
 AppleThreadPlanStepThroughObjCTrampoline::
@@ -126,8 +128,10 @@ bool AppleThreadPlanStepThroughObjCTrampoline::ShouldStop(Event *event_ptr) {
     }
   }
 
-  // Second stage, if all went well with the function calling, then fetch the
-  // target address, and queue up a "run to that address" plan.
+  // Second stage, if all went well with the function calling,  get the
+  // implementation function address, and queue up a "run to that address" plan.
+  Log *log = GetLog(LLDBLog::Step);
+
   if (!m_run_to_sp) {
     Value target_addr_value;
     ExecutionContext exc_ctx;
@@ -142,7 +146,6 @@ bool AppleThreadPlanStepThroughObjCTrampoline::ShouldStop(Event *event_ptr) {
     }
     Address target_so_addr;
     target_so_addr.SetOpcodeLoadAddress(target_addr, exc_ctx.GetTargetPtr());
-    Log *log = GetLog(LLDBLog::Step);
     if (target_addr == 0) {
       LLDB_LOGF(log, "Got target implementation of 0x0, stopping.");
       SetPlanComplete();
@@ -174,13 +177,25 @@ bool AppleThreadPlanStepThroughObjCTrampoline::ShouldStop(Event *event_ptr) {
     ObjCLanguageRuntime *objc_runtime =
         ObjCLanguageRuntime::Get(*GetThread().GetProcess());
     assert(objc_runtime != nullptr);
-    objc_runtime->AddToMethodCache(m_isa_addr, m_sel_addr, target_addr);
-    LLDB_LOGF(log,
-              "Adding {isa-addr=0x%" PRIx64 ", sel-addr=0x%" PRIx64
-              "} = addr=0x%" PRIx64 " to cache.",
-              m_isa_addr, m_sel_addr, target_addr);
-
-    // Extract the target address from the value:
+    if (m_sel_str_addr != LLDB_INVALID_ADDRESS) {
+      // Cache the string -> implementation and free the string in the target.
+      Status dealloc_error =
+          GetThread().GetProcess()->DeallocateMemory(m_sel_str_addr);
+      // For now just log this:
+      if (dealloc_error.Fail())
+        LLDB_LOG(log, "Failed to deallocate the sel str at {0} - error: {1}",
+                 m_sel_str_addr, dealloc_error);
+      objc_runtime->AddToMethodCache(m_isa_addr, m_sel_str, target_addr);
+      LLDB_LOG(log,
+               "Adding \\{isa-addr={0}, sel-addr={1}\\} = addr={2} to cache.",
+               m_isa_addr, m_sel_str, target_addr);
+    } else {
+      objc_runtime->AddToMethodCache(m_isa_addr, m_sel_addr, target_addr);
+      LLDB_LOGF(log,
+                "Adding {isa-addr=0x%" PRIx64 ", sel-addr=0x%" PRIx64
+                "} = addr=0x%" PRIx64 " to cache.",
+                m_isa_addr, m_sel_addr, target_addr);
+    }
 
     m_run_to_sp = std::make_shared<ThreadPlanRunToAddress>(
         GetThread(), target_so_addr, false);
index b5b4507..ba4fb5a 100644 (file)
@@ -24,7 +24,8 @@ class AppleThreadPlanStepThroughObjCTrampoline : public ThreadPlan {
 public:
   AppleThreadPlanStepThroughObjCTrampoline(
       Thread &thread, AppleObjCTrampolineHandler &trampoline_handler,
-      ValueList &values, lldb::addr_t isa_addr, lldb::addr_t sel_addr);
+      ValueList &values, lldb::addr_t isa_addr, lldb::addr_t sel_addr,
+      lldb::addr_t sel_str_addr, llvm::StringRef sel_str);
 
   ~AppleThreadPlanStepThroughObjCTrampoline() override;
 
@@ -70,6 +71,13 @@ private:
   FunctionCaller *m_impl_function; /// This is a pointer to a impl function that
                                    /// is owned by the client that pushes this
                                    /// plan.
+  lldb::addr_t m_sel_str_addr; /// If this is not LLDB_INVALID_ADDRESS then it
+                               /// is the address we wrote the selector string
+                               /// to.  We need to deallocate it when the
+                               /// function call is done.
+  std::string m_sel_str;       /// This is the string we wrote to memory - we
+                               /// use it for caching, but only if
+                               /// m_sel_str_addr is non-null.
 };
 
 class AppleThreadPlanStepThroughDirectDispatch: public ThreadPlanStepOut {
index cba34f1..b387de3 100644 (file)
@@ -37,7 +37,7 @@ char ObjCLanguageRuntime::ID = 0;
 ObjCLanguageRuntime::~ObjCLanguageRuntime() = default;
 
 ObjCLanguageRuntime::ObjCLanguageRuntime(Process *process)
-    : LanguageRuntime(process), m_impl_cache(),
+    : LanguageRuntime(process), m_impl_cache(), m_impl_str_cache(),
       m_has_new_literals_and_indexing(eLazyBoolCalculate),
       m_isa_to_descriptor(), m_hash_to_isa_map(), m_type_size_cache(),
       m_isa_to_descriptor_stop_id(UINT32_MAX), m_complete_class_cache(),
@@ -75,6 +75,18 @@ void ObjCLanguageRuntime::AddToMethodCache(lldb::addr_t class_addr,
       ClassAndSel(class_addr, selector), impl_addr));
 }
 
+void ObjCLanguageRuntime::AddToMethodCache(lldb::addr_t class_addr,
+                                           llvm::StringRef sel_str,
+                                           lldb::addr_t impl_addr) {
+  Log *log = GetLog(LLDBLog::Step);
+
+  LLDB_LOG(log, "Caching: class {0} selector {1} implementation {2}.",
+           class_addr, sel_str, impl_addr);
+
+  m_impl_str_cache.insert(std::pair<ClassAndSelStr, lldb::addr_t>(
+      ClassAndSelStr(class_addr, sel_str), impl_addr));
+}
+
 lldb::addr_t ObjCLanguageRuntime::LookupInMethodCache(lldb::addr_t class_addr,
                                                       lldb::addr_t selector) {
   MsgImplMap::iterator pos, end = m_impl_cache.end();
@@ -84,6 +96,15 @@ lldb::addr_t ObjCLanguageRuntime::LookupInMethodCache(lldb::addr_t class_addr,
   return LLDB_INVALID_ADDRESS;
 }
 
+lldb::addr_t ObjCLanguageRuntime::LookupInMethodCache(lldb::addr_t class_addr,
+                                                      llvm::StringRef sel_str) {
+  MsgImplStrMap::iterator pos, end = m_impl_str_cache.end();
+  pos = m_impl_str_cache.find(ClassAndSelStr(class_addr, sel_str));
+  if (pos != end)
+    return (*pos).second;
+  return LLDB_INVALID_ADDRESS;
+}
+
 lldb::TypeSP
 ObjCLanguageRuntime::LookupInCompleteClassCache(ConstString &name) {
   CompleteClassMap::iterator complete_class_iter =
index 15fce04..9431b42 100644 (file)
@@ -22,6 +22,7 @@
 #include "lldb/Symbol/CompilerType.h"
 #include "lldb/Symbol/Type.h"
 #include "lldb/Target/LanguageRuntime.h"
+#include "lldb/Utility/ConstString.h"
 #include "lldb/lldb-private.h"
 
 class CommandObjectObjC_ClassTable_Dump;
@@ -242,11 +243,19 @@ public:
 
   virtual bool HasReadObjCLibrary() = 0;
 
+  // These two methods actually use different caches.  The only time we'll
+  // cache a sel_str is if we found a "selector specific stub" for the selector
+  // and conversely we only add to the SEL cache if we saw a regular dispatch.
   lldb::addr_t LookupInMethodCache(lldb::addr_t class_addr, lldb::addr_t sel);
+  lldb::addr_t LookupInMethodCache(lldb::addr_t class_addr,
+                                   llvm::StringRef sel_str);
 
   void AddToMethodCache(lldb::addr_t class_addr, lldb::addr_t sel,
                         lldb::addr_t impl_addr);
 
+  void AddToMethodCache(lldb::addr_t class_addr, llvm::StringRef sel_str,
+                        lldb::addr_t impl_addr);
+
   TypeAndOrName LookupInClassNameCache(lldb::addr_t class_addr);
 
   void AddToClassNameCache(lldb::addr_t class_addr, const char *name,
@@ -343,20 +352,22 @@ protected:
   }
 
 private:
-  // We keep a map of <Class,Selector>->Implementation so we don't have to call
-  // the resolver function over and over.
+  // We keep two maps of <Class,Selector>->Implementation so we don't have
+  // to call the resolver function over and over.
+  // The first comes from regular obj_msgSend type dispatch, and maps the
+  // class + uniqued SEL value to an implementation.
+  // The second comes from the "selector-specific stubs", which are always
+  // of the form _objc_msgSend$SelectorName, so we don't know the uniqued
+  // selector, only the string name.
 
   // FIXME: We need to watch for the loading of Protocols, and flush the cache
   // for any
   // class that we see so changed.
 
   struct ClassAndSel {
-    ClassAndSel() {
-      sel_addr = LLDB_INVALID_ADDRESS;
-      class_addr = LLDB_INVALID_ADDRESS;
-    }
+    ClassAndSel() = default;
 
-    ClassAndSel(lldb::addr_t in_sel_addr, lldb::addr_t in_class_addr)
+    ClassAndSel(lldb::addr_t in_class_addr, lldb::addr_t in_sel_addr)
         : class_addr(in_class_addr), sel_addr(in_sel_addr) {}
 
     bool operator==(const ClassAndSel &rhs) {
@@ -379,11 +390,35 @@ private:
       }
     }
 
-    lldb::addr_t class_addr;
-    lldb::addr_t sel_addr;
+    lldb::addr_t class_addr = LLDB_INVALID_ADDRESS;
+    lldb::addr_t sel_addr = LLDB_INVALID_ADDRESS;
+  };
+
+  struct ClassAndSelStr {
+    ClassAndSelStr() = default;
+
+    ClassAndSelStr(lldb::addr_t in_class_addr, llvm::StringRef in_sel_name)
+        : class_addr(in_class_addr), sel_name(in_sel_name) {}
+
+    bool operator==(const ClassAndSelStr &rhs) {
+      return class_addr == rhs.class_addr && sel_name == rhs.sel_name;
+    }
+
+    bool operator<(const ClassAndSelStr &rhs) const {
+      if (class_addr < rhs.class_addr)
+        return true;
+      else if (class_addr > rhs.class_addr)
+        return false;
+      else
+        return ConstString::Compare(sel_name, rhs.sel_name);
+    }
+
+    lldb::addr_t class_addr = LLDB_INVALID_ADDRESS;
+    ConstString sel_name;
   };
 
   typedef std::map<ClassAndSel, lldb::addr_t> MsgImplMap;
+  typedef std::map<ClassAndSelStr, lldb::addr_t> MsgImplStrMap;
   typedef std::map<ObjCISA, ClassDescriptorSP> ISAToDescriptorMap;
   typedef std::multimap<uint32_t, ObjCISA> HashToISAMap;
   typedef ISAToDescriptorMap::iterator ISAToDescriptorIterator;
@@ -391,6 +426,7 @@ private:
   typedef ThreadSafeDenseMap<void *, uint64_t> TypeSizeCache;
 
   MsgImplMap m_impl_cache;
+  MsgImplStrMap m_impl_str_cache;
   LazyBool m_has_new_literals_and_indexing;
   ISAToDescriptorMap m_isa_to_descriptor;
   HashToISAMap m_hash_to_isa_map;
index fe4e3ed..36727d3 100644 (file)
@@ -57,7 +57,7 @@ class ObjCDataFormatterKVO(ObjCDataFormatterTestCase):
                 ' 21 key/value pairs'
             ])
 
-        lldbutil.run_break_set_by_regexp(self, 'setAtoms')
+        lldbutil.run_break_set_by_symbol(self, '-[Molecule setAtoms:]')
 
         self.runCmd("continue")
         self.expect("frame variable _cmd", substrs=['setAtoms:'])