diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h @@ -293,6 +293,50 @@ } }; + /// We can read the class info from the Objective-C runtime using + /// gdb_objc_realized_classes or objc_copyRealizedClassList. The latter is + /// preferred because it includes lazily named classes, but it's not always + /// available or safe to call. + /// + /// We potentially need both for the same process, + /// because we may need to use gdb_objc_realized_classes until dyld is + /// initialized and then switch over to objc_copyRealizedClassList for lazily + /// named classes. + class DynamicClassInfoExtractor { + public: + DynamicClassInfoExtractor(AppleObjCRuntimeV2 &runtime) + : m_runtime(runtime) {} + + enum Helper { gdb_objc_realized_classes, objc_copyRealizedClassList }; + + /// Compute which helper to use. Prefer objc_copyRealizedClassList if it's + /// available and it's safe to call (i.e. dyld is fully initialized). Use + /// gdb_objc_realized_classes otherwise. + Helper ComputeHelper() const; + + UtilityFunction *GetClassInfoUtilityFunction(ExecutionContext &exe_ctx, + Helper helper); + lldb::addr_t &GetClassInfoArgs(Helper helper); + std::mutex &GetMutex() { return m_mutex; } + + private: + std::unique_ptr + GetClassInfoUtilityFunctionImpl(ExecutionContext &exe_ctx, std::string code, + std::string name); + + /// The lifetime of this object is tied to that of the runtime. + AppleObjCRuntimeV2 &m_runtime; + std::mutex m_mutex; + + /// Utility function to read class info using gdb_objc_realized_classes. + std::unique_ptr m_get_class_info_code; + lldb::addr_t m_get_class_info_args = LLDB_INVALID_ADDRESS; + + /// Utility function to read class info using objc_copyRealizedClassList. + std::unique_ptr m_get_class_info2_code; + lldb::addr_t m_get_class_info2_args = LLDB_INVALID_ADDRESS; + }; + AppleObjCRuntimeV2(Process *process, const lldb::ModuleSP &objc_module_sp); ObjCISA GetPointerISA(ObjCISA isa); @@ -301,6 +345,12 @@ bool UpdateISAToDescriptorMapFromMemory(RemoteNXMapTable &hash_table); + /// Update the generation count of realized classes. This is not an exact + /// count but rather a value that is incremented when new classes are realized + /// or destroyed. Unlike the count in gdb_objc_realized_classes, it will + /// change when lazily named classes get realized. + bool RealizedClassGenerationCountChanged(); + DescriptorMapUpdateResult UpdateISAToDescriptorMapDynamic(RemoteNXMapTable &hash_table); @@ -320,6 +370,8 @@ bool GetCFBooleanValuesIfNeeded(); + bool HasSymbol(ConstString Name); + NonPointerISACache *GetNonPointerIsaCache() { if (!m_non_pointer_isa_cache_up) m_non_pointer_isa_cache_up.reset( @@ -331,9 +383,7 @@ lldb::ModuleSP m_objc_module_sp; - std::unique_ptr m_get_class_info_code; - lldb::addr_t m_get_class_info_args; - std::mutex m_get_class_info_args_mutex; + DynamicClassInfoExtractor m_class_info_extractor; std::unique_ptr m_get_shared_cache_class_info_code; lldb::addr_t m_get_shared_cache_class_info_args; @@ -344,12 +394,14 @@ lldb::addr_t m_isa_hash_table_ptr; HashTableSignature m_hash_signature; bool m_has_object_getClass; + bool m_has_objc_copyRealizedClassList; bool m_loaded_objc_opt; std::unique_ptr m_non_pointer_isa_cache_up; std::unique_ptr m_tagged_pointer_vendor_up; EncodingToTypeSP m_encoding_to_type_sp; bool m_noclasses_warning_emitted; llvm::Optional> m_CFBoolean_values; + uint64_t m_realized_class_generation_count; }; } // namespace lldb_private 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 @@ -39,6 +39,7 @@ #include "lldb/Symbol/TypeList.h" #include "lldb/Symbol/VariableList.h" #include "lldb/Target/ABI.h" +#include "lldb/Target/DynamicLoader.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Platform.h" #include "lldb/Target/Process.h" @@ -163,6 +164,72 @@ )"; +static const char *g_get_dynamic_class_info2_name = + "__lldb_apple_objc_v2_get_dynamic_class_info2"; + +static const char *g_get_dynamic_class_info2_body = R"( + +extern "C" { + int printf(const char * format, ...); + void free(void *ptr); + Class* objc_copyRealizedClassList(unsigned int *outCount); + const char* objc_debug_class_getNameRaw(Class cls); +} + +#define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) + +struct ClassInfo +{ + Class isa; + uint32_t hash; +} __attribute__((__packed__)); + +uint32_t +__lldb_apple_objc_v2_get_dynamic_class_info2(void *gdb_objc_realized_classes_ptr, + void *class_infos_ptr, + uint32_t class_infos_byte_size, + uint32_t should_log) +{ + DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); + DEBUG_PRINTF ("class_infos_byte_size = %u\n", class_infos_byte_size); + + const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); + DEBUG_PRINTF ("max_class_infos = %u\n", max_class_infos); + + ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; + + uint32_t count = 0; + Class* realized_class_list = objc_copyRealizedClassList(&count); + + uint32_t idx = 0; + for (uint32_t i=0; i<=count; ++i) + { + if (idx < max_class_infos) + { + Class isa = realized_class_list[i]; + const char *name_ptr = objc_debug_class_getNameRaw(isa); + const char *s = name_ptr; + uint32_t h = 5381; + for (unsigned char c = *s; c; c = *++s) + h = ((h << 5) + h) + c; + class_infos[idx].hash = h; + class_infos[idx].isa = isa; + DEBUG_PRINTF ("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name_ptr); + } + idx++; + } + + if (idx < max_class_infos) + { + class_infos[idx].isa = NULL; + class_infos[idx].hash = 0; + } + + free(realized_class_list); + return count; +} +)"; + // We'll substitute in class_getName or class_getNameRaw depending // on which is present. static const char *g_shared_cache_class_name_funcptr = R"( @@ -415,22 +482,23 @@ AppleObjCRuntimeV2::AppleObjCRuntimeV2(Process *process, const ModuleSP &objc_module_sp) : AppleObjCRuntime(process), m_objc_module_sp(objc_module_sp), - m_get_class_info_code(), m_get_class_info_args(LLDB_INVALID_ADDRESS), - m_get_class_info_args_mutex(), m_get_shared_cache_class_info_code(), + m_class_info_extractor(*this), m_get_shared_cache_class_info_code(), m_get_shared_cache_class_info_args(LLDB_INVALID_ADDRESS), m_get_shared_cache_class_info_args_mutex(), m_decl_vendor_up(), m_tagged_pointer_obfuscator(LLDB_INVALID_ADDRESS), m_isa_hash_table_ptr(LLDB_INVALID_ADDRESS), m_hash_signature(), - m_has_object_getClass(false), m_loaded_objc_opt(false), - m_non_pointer_isa_cache_up(), + m_has_object_getClass(false), m_has_objc_copyRealizedClassList(false), + m_loaded_objc_opt(false), m_non_pointer_isa_cache_up(), m_tagged_pointer_vendor_up( TaggedPointerVendorV2::CreateInstance(*this, objc_module_sp)), m_encoding_to_type_sp(), m_noclasses_warning_emitted(false), - m_CFBoolean_values() { + m_CFBoolean_values(), m_realized_class_generation_count(0) { static const ConstString g_gdb_object_getClass("gdb_object_getClass"); - m_has_object_getClass = - (objc_module_sp->FindFirstSymbolWithNameAndType( - g_gdb_object_getClass, eSymbolTypeCode) != nullptr); + m_has_object_getClass = HasSymbol(g_gdb_object_getClass); + static const ConstString g_objc_copyRealizedClassList( + "objc_copyRealizedClassList"); + m_has_objc_copyRealizedClassList = HasSymbol(g_objc_copyRealizedClassList); + RegisterObjCExceptionRecognizer(process); } @@ -1291,6 +1359,107 @@ return m_isa_hash_table_ptr; } +std::unique_ptr +AppleObjCRuntimeV2::DynamicClassInfoExtractor::GetClassInfoUtilityFunctionImpl( + ExecutionContext &exe_ctx, std::string code, std::string name) { + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); + + LLDB_LOG(log, "Creating utility function {0}", name); + + TypeSystemClang *ast = + ScratchTypeSystemClang::GetForTarget(exe_ctx.GetTargetRef()); + if (!ast) + return {}; + + auto utility_fn_or_error = exe_ctx.GetTargetRef().CreateUtilityFunction( + std::move(code), std::move(name), eLanguageTypeC, exe_ctx); + if (!utility_fn_or_error) { + LLDB_LOG_ERROR( + log, utility_fn_or_error.takeError(), + "Failed to get utility function for implementation lookup: {0}"); + return {}; + } + + // Make some types for our arguments. + CompilerType clang_uint32_t_type = + ast->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); + CompilerType clang_void_pointer_type = + ast->GetBasicType(eBasicTypeVoid).GetPointerType(); + + // Make the runner function for our implementation utility function. + ValueList arguments; + Value value; + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_void_pointer_type); + arguments.PushValue(value); + arguments.PushValue(value); + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_uint32_t_type); + arguments.PushValue(value); + arguments.PushValue(value); + + std::unique_ptr utility_fn = std::move(*utility_fn_or_error); + + Status error; + utility_fn->MakeFunctionCaller(clang_uint32_t_type, arguments, + exe_ctx.GetThreadSP(), error); + + if (error.Fail()) { + LLDB_LOG(log, + "Failed to make function caller for implementation lookup: {0}.", + error.AsCString()); + return {}; + } + + return utility_fn; +} + +UtilityFunction * +AppleObjCRuntimeV2::DynamicClassInfoExtractor::GetClassInfoUtilityFunction( + ExecutionContext &exe_ctx, Helper helper) { + switch (helper) { + case gdb_objc_realized_classes: { + if (!m_get_class_info_code) + m_get_class_info_code = GetClassInfoUtilityFunctionImpl( + exe_ctx, g_get_dynamic_class_info_body, + g_get_dynamic_class_info_name); + return m_get_class_info_code.get(); + } + case objc_copyRealizedClassList: { + if (!m_get_class_info2_code) + m_get_class_info2_code = GetClassInfoUtilityFunctionImpl( + exe_ctx, g_get_dynamic_class_info2_body, + g_get_dynamic_class_info2_name); + return m_get_class_info2_code.get(); + } + }; +} + +lldb::addr_t & +AppleObjCRuntimeV2::DynamicClassInfoExtractor::GetClassInfoArgs(Helper helper) { + switch (helper) { + case gdb_objc_realized_classes: + return m_get_class_info_args; + case objc_copyRealizedClassList: + return m_get_class_info2_args; + } +} + +AppleObjCRuntimeV2::DynamicClassInfoExtractor::Helper +AppleObjCRuntimeV2::DynamicClassInfoExtractor::ComputeHelper() const { + if (!m_runtime.m_has_objc_copyRealizedClassList) + return DynamicClassInfoExtractor::gdb_objc_realized_classes; + + if (Process *process = m_runtime.GetProcess()) { + if (DynamicLoader *loader = process->GetDynamicLoader()) { + if (loader->IsFullyInitialized()) + return DynamicClassInfoExtractor::objc_copyRealizedClassList; + } + } + + return DynamicClassInfoExtractor::gdb_objc_realized_classes; +} + AppleObjCRuntimeV2::DescriptorMapUpdateResult AppleObjCRuntimeV2::UpdateISAToDescriptorMapDynamic( RemoteNXMapTable &hash_table) { @@ -1323,65 +1492,37 @@ Status err; + // Compute which helper we're going to use for this update. + const DynamicClassInfoExtractor::Helper helper = + m_class_info_extractor.ComputeHelper(); + // Read the total number of classes from the hash table - const uint32_t num_classes = hash_table.GetCount(); + const uint32_t num_classes = + helper == DynamicClassInfoExtractor::gdb_objc_realized_classes + ? hash_table.GetCount() + : m_realized_class_generation_count; if (num_classes == 0) { - LLDB_LOGF(log, "No dynamic classes found in gdb_objc_realized_classes."); + LLDB_LOGF(log, "No dynamic classes found."); return DescriptorMapUpdateResult::Success(0); } - // Make some types for our arguments - CompilerType clang_uint32_t_type = - ast->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); - CompilerType clang_void_pointer_type = - ast->GetBasicType(eBasicTypeVoid).GetPointerType(); - - ValueList arguments; - FunctionCaller *get_class_info_function = nullptr; - - if (!m_get_class_info_code) { - auto utility_fn_or_error = GetTargetRef().CreateUtilityFunction( - g_get_dynamic_class_info_body, g_get_dynamic_class_info_name, - eLanguageTypeC, exe_ctx); - if (!utility_fn_or_error) { - LLDB_LOG_ERROR( - log, utility_fn_or_error.takeError(), - "Failed to get utility function for implementation lookup: {0}"); - return DescriptorMapUpdateResult::Fail(); - } - m_get_class_info_code = std::move(*utility_fn_or_error); - - // Next make the runner function for our implementation utility function. - Value value; - value.SetValueType(Value::ValueType::Scalar); - value.SetCompilerType(clang_void_pointer_type); - arguments.PushValue(value); - arguments.PushValue(value); - - value.SetValueType(Value::ValueType::Scalar); - value.SetCompilerType(clang_uint32_t_type); - arguments.PushValue(value); - arguments.PushValue(value); + UtilityFunction *get_class_info_code = + m_class_info_extractor.GetClassInfoUtilityFunction(exe_ctx, helper); + if (!get_class_info_code) { + // The callee will have already logged a useful error message. + return DescriptorMapUpdateResult::Fail(); + } - Status error; - get_class_info_function = m_get_class_info_code->MakeFunctionCaller( - clang_uint32_t_type, arguments, thread_sp, error); + FunctionCaller *get_class_info_function = + get_class_info_code->GetFunctionCaller(); - if (error.Fail()) { - LLDB_LOGF(log, - "Failed to make function caller for implementation lookup: %s.", - error.AsCString()); - return DescriptorMapUpdateResult::Fail(); - } - } else { - get_class_info_function = m_get_class_info_code->GetFunctionCaller(); - if (!get_class_info_function) { - LLDB_LOGF(log, "Failed to get implementation lookup function caller."); - return DescriptorMapUpdateResult::Fail(); - } - arguments = get_class_info_function->GetArgumentValues(); + if (!get_class_info_function) { + LLDB_LOGF(log, "Failed to get implementation lookup function caller."); + return DescriptorMapUpdateResult::Fail(); } + ValueList arguments = get_class_info_function->GetArgumentValues(); + DiagnosticManager diagnostics; const uint32_t class_info_byte_size = addr_size + 4; @@ -1397,7 +1538,7 @@ return DescriptorMapUpdateResult::Fail(); } - std::lock_guard guard(m_get_class_info_args_mutex); + std::lock_guard guard(m_class_info_extractor.GetMutex()); // Fill in our function argument values arguments.GetValueAtIndex(0)->GetScalar() = hash_table.GetTableLoadAddress(); @@ -1417,7 +1558,8 @@ // Write our function arguments into the process so we can run our function if (get_class_info_function->WriteFunctionArguments( - exe_ctx, m_get_class_info_args, arguments, diagnostics)) { + exe_ctx, m_class_info_extractor.GetClassInfoArgs(helper), arguments, + diagnostics)) { EvaluateExpressionOptions options; options.SetUnwindOnError(true); options.SetTryAllThreads(false); @@ -1426,6 +1568,9 @@ options.SetTimeout(process->GetUtilityExpressionTimeout()); options.SetIsForUtilityExpr(true); + CompilerType clang_uint32_t_type = + ast->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); + Value return_value; return_value.SetValueType(Value::ValueType::Scalar); return_value.SetCompilerType(clang_uint32_t_type); @@ -1435,12 +1580,13 @@ // Run the function ExpressionResults results = get_class_info_function->ExecuteFunction( - exe_ctx, &m_get_class_info_args, options, diagnostics, return_value); + exe_ctx, &m_class_info_extractor.GetClassInfoArgs(helper), options, + diagnostics, return_value); if (results == eExpressionCompleted) { // The result is the number of ClassInfo structures that were filled in num_class_infos = return_value.GetScalar().ULong(); - LLDB_LOGF(log, "Discovered %u ObjC classes\n", num_class_infos); + LLDB_LOG(log, "Discovered {0} Objective-C classes", num_class_infos); if (num_class_infos > 0) { // Read the ClassInfo structures DataBufferHeap buffer(num_class_infos * class_info_byte_size, 0); @@ -1539,6 +1685,17 @@ return num_parsed; } +bool AppleObjCRuntimeV2::HasSymbol(ConstString Name) { + if (!m_objc_module_sp) + return false; + if (const Symbol *symbol = m_objc_module_sp->FindFirstSymbolWithNameAndType( + Name, lldb::eSymbolTypeCode)) { + if (symbol->ValueIsAddress() || symbol->GetAddressRef().IsValid()) + return true; + } + return false; +} + AppleObjCRuntimeV2::DescriptorMapUpdateResult AppleObjCRuntimeV2::UpdateISAToDescriptorMapSharedCache() { Process *process = GetProcess(); @@ -1595,21 +1752,11 @@ static ConstString g_class_getName_symbol_name("class_getName"); static ConstString g_class_getNameRaw_symbol_name( "objc_debug_class_getNameRaw"); - ConstString class_name_getter_function_name = g_class_getName_symbol_name; - ObjCLanguageRuntime *objc_runtime = ObjCLanguageRuntime::Get(*process); - if (objc_runtime) { - for (lldb::ModuleSP mod_sp : process->GetTarget().GetImages().Modules()) { - if (objc_runtime->IsModuleObjCLibrary(mod_sp)) { - const Symbol *symbol = mod_sp->FindFirstSymbolWithNameAndType( - g_class_getNameRaw_symbol_name, lldb::eSymbolTypeCode); - if (symbol && - (symbol->ValueIsAddress() || symbol->GetAddressRef().IsValid())) { - class_name_getter_function_name = g_class_getNameRaw_symbol_name; - } - } - } - } + ConstString class_name_getter_function_name = + HasSymbol(g_class_getNameRaw_symbol_name) + ? g_class_getNameRaw_symbol_name + : g_class_getName_symbol_name; // Substitute in the correct class_getName / class_getNameRaw function name, // concatenate the two parts of our expression text. The format string @@ -1721,8 +1868,8 @@ if (results == eExpressionCompleted) { // The result is the number of ClassInfo structures that were filled in num_class_infos = return_value.GetScalar().ULong(); - LLDB_LOGF(log, "Discovered %u ObjC classes in shared cache\n", - num_class_infos); + LLDB_LOG(log, "Discovered {0} Objective-C classes in the shared cache", + num_class_infos); assert(num_class_infos <= num_classes); if (num_class_infos > 0) { if (num_class_infos > num_classes) { @@ -1850,12 +1997,17 @@ // map, whether it was successful or not. m_isa_to_descriptor_stop_id = process->GetStopID(); - if (!m_hash_signature.NeedsUpdate(process, this, hash_table)) + // Ask the runtime is the realized class generation count changed. Unlike + // the hash table, this accounts for lazily named classes. + const bool class_count_changed = RealizedClassGenerationCountChanged(); + + if (!m_hash_signature.NeedsUpdate(process, this, hash_table) && + !class_count_changed) return; m_hash_signature.UpdateSignature(hash_table); - // Grab the dynamically loaded objc classes from the hash table in memory + // Grab the dynamically loaded Objective-C classes from memory. DescriptorMapUpdateResult dynamic_update_result = UpdateISAToDescriptorMapDynamic(hash_table); @@ -1903,6 +2055,35 @@ } } +bool AppleObjCRuntimeV2::RealizedClassGenerationCountChanged() { + Process *process = GetProcess(); + if (!process) + return false; + + Status error; + uint64_t objc_debug_realized_class_generation_count = + ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_realized_class_generation_count"), + GetObjCModule(), error); + if (error.Fail()) + return false; + + if (m_realized_class_generation_count == + objc_debug_realized_class_generation_count) + return false; + + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); + LLDB_LOG(log, + "objc_debug_realized_class_generation_count changed from {0} to {1}", + m_realized_class_generation_count, + objc_debug_realized_class_generation_count); + + m_realized_class_generation_count = + objc_debug_realized_class_generation_count; + + return true; +} + static bool DoesProcessHaveSharedCache(Process &process) { PlatformSP platform_sp = process.GetTarget().GetPlatform(); if (!platform_sp)