diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -1554,7 +1554,7 @@ 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 @@ 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; } 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 @@ -38,11 +38,11 @@ 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 @@ 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 @@ 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 m_impl_code; std::mutex m_impl_function_mutex; lldb::addr_t m_impl_fn_addr; 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 @@ -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 @@ "__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 @@ 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 @@ 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 @@ // 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 @@ 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$, 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 @@ } } + // 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 @@ 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 @@ } 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 @@ // 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 @@ // 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 @@ // 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 @@ 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 @@ // 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 @@ dispatch_values.PushValue(flag_value); ret_plan_sp = std::make_shared( - 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); 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 @@ -24,7 +24,8 @@ 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 @@ 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 { 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 @@ -32,13 +32,15 @@ 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 @@ } } - // 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 @@ } 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 @@ 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( GetThread(), target_so_addr, false); diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h --- a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h @@ -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 @@ 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 @@ } private: - // We keep a map of ->Implementation so we don't have to call - // the resolver function over and over. + // We keep two maps of ->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 @@ } } - 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 MsgImplMap; + typedef std::map MsgImplStrMap; typedef std::map ISAToDescriptorMap; typedef std::multimap HashToISAMap; typedef ISAToDescriptorMap::iterator ISAToDescriptorIterator; @@ -391,6 +426,7 @@ typedef ThreadSafeDenseMap 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; diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp --- a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp @@ -37,7 +37,7 @@ 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 @@ 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(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 @@ 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 = diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCKVO.py b/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCKVO.py --- a/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCKVO.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCKVO.py @@ -56,7 +56,7 @@ ' 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:'])