From 457713602c649ada2cd674ac8c744e6071f95f43 Mon Sep 17 00:00:00 2001 From: Alex Langford Date: Tue, 11 Nov 2025 10:00:42 -0800 Subject: [PATCH 1/3] [lldb] Fix reading duplicate objc class metdata from shared cache (#167405) The code for v16 of the shared cache objc class layout was copy/pasted from the previous versions incorrectly. Namely, the wrong class offset list was used and the class_infos index was never updated. rdar://164430695 (cherry picked from commit 4d2741361a3e1cebbcd7abb28b7abdcbe74c91b7) --- .../ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp index d2b9a1477a234..87f9309fac4e9 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -542,7 +542,7 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, for (uint32_t i=0; iversion >= 12 && objc_opt->version <= 15) From b2d7d3ed28dad8f59c9c987d643c72c9d83da1ba Mon Sep 17 00:00:00 2001 From: Alex Langford Date: Wed, 12 Nov 2025 10:52:26 -0800 Subject: [PATCH 2/3] [lldb] Adapt shared cache ObjC class metadata extraction for long term growth (#167579) On Apple's platforms, the size of the shared cache grows steadily. As it grows, so does its list of ObjC classes. LLDB currently accepts an upper limit to the number of classes when it extracts the class information. Every few years we will hit the limit and increase the upper limit of classes. This approach is fundamentally unsustainable. On top of needing to manually adjust the number every few years, our current method requires us to allocate memory in the inferior process. On macOS this is usually not a problem, but on embedded devices there is usually a limit to how much memory a process can allocate before they are killed by the OS. My solution involves running the metadata extraction logic multiple times. I've added a new parameter to our utility function `start_idx` that keeps track of where it stopped during the previous run so that it may pick up again where it stopped. rdar://91398396 (cherry picked from commit ec4207baed7bb45a14436480697c283a21bc036f) --- .../AppleObjCRuntime/AppleObjCRuntimeV2.cpp | 268 +++++++++++------- 1 file changed, 160 insertions(+), 108 deletions(-) diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp index 87f9309fac4e9..5b83a116c0cea 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -423,6 +423,7 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, void *class_infos_ptr, uint64_t *relative_selector_offset, uint32_t class_infos_byte_size, + uint32_t *start_idx, uint32_t should_log) { *relative_selector_offset = 0; @@ -431,6 +432,7 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, DEBUG_PRINTF ("shared_cache_base_ptr = %p\n", shared_cache_base_ptr); DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); DEBUG_PRINTF ("class_infos_byte_size = %u (%llu class infos)\n", class_infos_byte_size, (uint64_t)(class_infos_byte_size/sizeof(ClassInfo))); + DEBUG_PRINTF ("start_idx = %u\n", *start_idx); if (objc_opt_ro_ptr) { const objc_opt_t *objc_opt = (objc_opt_t *)objc_opt_ro_ptr; @@ -485,7 +487,11 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, DEBUG_PRINTF ("clsopt->mask = 0x%8.8x\n", clsopt->mask); DEBUG_PRINTF ("classOffsets = %p\n", classOffsets); - for (uint32_t i=0; icapacity; ++i) + const uint32_t original_start_idx = *start_idx; + + // Always start at the start_idx here. If it's greater than the capacity, + // it will skip the loop entirely and go to the duplicate handling below. + for (uint32_t i=*start_idx; icapacity; ++i) { const uint64_t objectCacheOffset = classOffsets[i].objectCacheOffset; DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset); @@ -529,59 +535,77 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, else { DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n"); + *start_idx = i; + break; } ++idx; } - const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity]; - const uint32_t duplicate_count = *duplicate_count_ptr; - const objc_classheader_v16_t *duplicateClassOffsets = (const objc_classheader_v16_t *)(&duplicate_count_ptr[1]); + if (idx < max_class_infos) { + const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity]; + const uint32_t duplicate_count = *duplicate_count_ptr; + const objc_classheader_v16_t *duplicateClassOffsets = (const objc_classheader_v16_t *)(&duplicate_count_ptr[1]); - DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count); - DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets); + DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count); + DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets); - for (uint32_t i=0; icapacity ? + 0 : + *start_idx - clsopt->capacity; - if (classOffsets[i].isDuplicate) { - DEBUG_PRINTF("isDuplicate = true\n"); - continue; // duplicate - } - - if (objectCacheOffset == 0) { - DEBUG_PRINTF("objectCacheOffset == invalidEntryOffset\n"); - continue; // invalid offset - } - - if (class_infos && idx < max_class_infos) + for (uint32_t i=duplicate_start_idx; iversion >= 12 && objc_opt->version <= 15) @@ -1972,6 +1996,9 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor:: CompilerType clang_uint64_t_pointer_type = scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 64) .GetPointerType(); + CompilerType clang_uint32_t_pointer_type = + scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32) + .GetPointerType(); // Next make the function caller for our implementation utility function. ValueList arguments; @@ -1989,6 +2016,13 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor:: value.SetValueType(Value::ValueType::Scalar); value.SetCompilerType(clang_uint32_t_type); arguments.PushValue(value); + + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_uint32_t_pointer_type); + arguments.PushValue(value); + + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_uint32_t_type); arguments.PushValue(value); std::unique_ptr utility_fn = std::move(*utility_fn_or_error); @@ -2326,10 +2360,7 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { // The number of entries to pre-allocate room for. // Each entry is (addrsize + 4) bytes - // FIXME: It is not sustainable to continue incrementing this value every time - // the shared cache grows. This is because it requires allocating memory in - // the inferior process and some inferior processes have small memory limits. - const uint32_t max_num_classes = 212992; + const uint32_t max_num_classes_in_buffer = 212992; UtilityFunction *get_class_info_code = GetClassInfoUtilityFunction(exe_ctx); if (!get_class_info_code) { @@ -2351,15 +2382,22 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { DiagnosticManager diagnostics; const uint32_t class_info_byte_size = addr_size + 4; - const uint32_t class_infos_byte_size = max_num_classes * class_info_byte_size; + const uint32_t class_infos_byte_size = + max_num_classes_in_buffer * class_info_byte_size; lldb::addr_t class_infos_addr = process->AllocateMemory( class_infos_byte_size, ePermissionsReadable | ePermissionsWritable, err); const uint32_t relative_selector_offset_addr_size = 64; lldb::addr_t relative_selector_offset_addr = process->AllocateMemory(relative_selector_offset_addr_size, ePermissionsReadable | ePermissionsWritable, err); + constexpr uint32_t class_info_start_idx_byte_size = sizeof(uint32_t); + lldb::addr_t class_info_start_idx_addr = + process->AllocateMemory(class_info_start_idx_byte_size, + ePermissionsReadable | ePermissionsWritable, err); - if (class_infos_addr == LLDB_INVALID_ADDRESS) { + if (class_infos_addr == LLDB_INVALID_ADDRESS || + relative_selector_offset_addr == LLDB_INVALID_ADDRESS || + class_info_start_idx_addr == LLDB_INVALID_ADDRESS) { LLDB_LOGF(log, "unable to allocate %" PRIu32 " bytes in process for shared cache read", @@ -2367,6 +2405,17 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { return DescriptorMapUpdateResult::Fail(); } + const uint32_t start_idx_init_value = 0; + size_t bytes_written = process->WriteMemory( + class_info_start_idx_addr, &start_idx_init_value, sizeof(uint32_t), err); + if (bytes_written != sizeof(uint32_t)) { + LLDB_LOGF(log, + "unable to write %" PRIu32 + " bytes in process for shared cache read", + class_infos_byte_size); + return DescriptorMapUpdateResult::Fail(); + } + std::lock_guard guard(m_mutex); // Fill in our function argument values @@ -2375,12 +2424,13 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { arguments.GetValueAtIndex(2)->GetScalar() = class_infos_addr; arguments.GetValueAtIndex(3)->GetScalar() = relative_selector_offset_addr; arguments.GetValueAtIndex(4)->GetScalar() = class_infos_byte_size; + arguments.GetValueAtIndex(5)->GetScalar() = class_info_start_idx_addr; // Only dump the runtime classes from the expression evaluation if the log is // verbose: Log *type_log = GetLog(LLDBLog::Types); bool dump_log = type_log && type_log->GetVerbose(); - arguments.GetValueAtIndex(5)->GetScalar() = dump_log ? 1 : 0; + arguments.GetValueAtIndex(6)->GetScalar() = dump_log ? 1 : 0; bool success = false; @@ -2407,78 +2457,80 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { diagnostics.Clear(); - // Run the function - ExpressionResults results = - get_shared_cache_class_info_function->ExecuteFunction( - exe_ctx, &m_args, 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_LOG(log, "Discovered {0} Objective-C classes in the shared cache", - num_class_infos); - // Assert if there were more classes than we pre-allocated - // room for. - assert(num_class_infos <= max_num_classes); - if (num_class_infos > 0) { - if (num_class_infos > max_num_classes) { - num_class_infos = max_num_classes; - - success = false; - } else { + uint32_t num_class_infos_read = 0; + bool already_read_relative_selector_offset = false; + + do { + // Run the function. + ExpressionResults results = + get_shared_cache_class_info_function->ExecuteFunction( + exe_ctx, &m_args, options, diagnostics, return_value); + + if (results == eExpressionCompleted) { + // The result is the number of ClassInfo structures that were filled in. + num_class_infos_read = return_value.GetScalar().ULong(); + num_class_infos += num_class_infos_read; + LLDB_LOG(log, "Discovered {0} Objective-C classes in the shared cache", + num_class_infos_read); + if (num_class_infos_read > 0) { success = true; - } - // Read the relative selector offset. - DataBufferHeap relative_selector_offset_buffer(64, 0); - if (process->ReadMemory(relative_selector_offset_addr, - relative_selector_offset_buffer.GetBytes(), - relative_selector_offset_buffer.GetByteSize(), - err) == - relative_selector_offset_buffer.GetByteSize()) { - DataExtractor relative_selector_offset_data( - relative_selector_offset_buffer.GetBytes(), - relative_selector_offset_buffer.GetByteSize(), - process->GetByteOrder(), addr_size); - lldb::offset_t offset = 0; - uint64_t relative_selector_offset = - relative_selector_offset_data.GetU64(&offset); - if (relative_selector_offset > 0) { - // The offset is relative to the objc_opt struct. - m_runtime.SetRelativeSelectorBaseAddr(objc_opt_ptr + - relative_selector_offset); + // Read the relative selector offset. This only needs to occur once no + // matter how many times the function is called. + if (!already_read_relative_selector_offset) { + DataBufferHeap relative_selector_offset_buffer(64, 0); + if (process->ReadMemory( + relative_selector_offset_addr, + relative_selector_offset_buffer.GetBytes(), + relative_selector_offset_buffer.GetByteSize(), + err) == relative_selector_offset_buffer.GetByteSize()) { + DataExtractor relative_selector_offset_data( + relative_selector_offset_buffer.GetBytes(), + relative_selector_offset_buffer.GetByteSize(), + process->GetByteOrder(), addr_size); + lldb::offset_t offset = 0; + uint64_t relative_selector_offset = + relative_selector_offset_data.GetU64(&offset); + if (relative_selector_offset > 0) { + // The offset is relative to the objc_opt struct. + m_runtime.SetRelativeSelectorBaseAddr(objc_opt_ptr + + relative_selector_offset); + } + } + already_read_relative_selector_offset = true; } - } - - // Read the ClassInfo structures - DataBufferHeap class_infos_buffer( - num_class_infos * class_info_byte_size, 0); - if (process->ReadMemory(class_infos_addr, class_infos_buffer.GetBytes(), - class_infos_buffer.GetByteSize(), - err) == class_infos_buffer.GetByteSize()) { - DataExtractor class_infos_data(class_infos_buffer.GetBytes(), - class_infos_buffer.GetByteSize(), - process->GetByteOrder(), addr_size); - m_runtime.ParseClassInfoArray(class_infos_data, num_class_infos); + // Read the ClassInfo structures + DataBufferHeap class_infos_buffer( + num_class_infos_read * class_info_byte_size, 0); + if (process->ReadMemory(class_infos_addr, + class_infos_buffer.GetBytes(), + class_infos_buffer.GetByteSize(), + err) == class_infos_buffer.GetByteSize()) { + DataExtractor class_infos_data(class_infos_buffer.GetBytes(), + class_infos_buffer.GetByteSize(), + process->GetByteOrder(), addr_size); + + m_runtime.ParseClassInfoArray(class_infos_data, + num_class_infos_read); + } } - } else { - success = true; - } - } else { - if (log) { + } else if (log) { LLDB_LOGF(log, "Error evaluating our find class name function."); diagnostics.Dump(log); + break; } - } - } else { - if (log) { - LLDB_LOGF(log, "Error writing function arguments."); - diagnostics.Dump(log); - } + } while (num_class_infos_read == max_num_classes_in_buffer); + } else if (log) { + LLDB_LOGF(log, "Error writing function arguments."); + diagnostics.Dump(log); } - // Deallocate the memory we allocated for the ClassInfo array + LLDB_LOG(log, "Processed {0} Objective-C classes total from the shared cache", + num_class_infos); + // Cleanup memory we allocated in the process. + process->DeallocateMemory(relative_selector_offset_addr); + process->DeallocateMemory(class_info_start_idx_addr); process->DeallocateMemory(class_infos_addr); return DescriptorMapUpdateResult(success, false, num_class_infos); From c2ca9d6260f2db72fe05c709b3616a0cf61f8d64 Mon Sep 17 00:00:00 2001 From: Alex Langford Date: Wed, 12 Nov 2025 13:08:24 -0800 Subject: [PATCH 3/3] [lldb] Split up shared cache objc metadata extractor body (#167761) After my previous change (#167579), the string exceeded 16380 single-byte characters. MSVC did not like this, so I'm splitting it up into two strings. (cherry picked from commit 6806349d69751463ecc43a80702612ee2f59354b) --- .../ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp index 5b83a116c0cea..a3118106d8c8c 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -325,7 +325,7 @@ extern "C" static const char *g_get_shared_cache_class_info_name = "__lldb_apple_objc_v2_get_shared_cache_class_info"; -static const char *g_get_shared_cache_class_info_body = R"( +static const char *g_get_shared_cache_class_info_definitions = R"( extern "C" { @@ -416,6 +416,9 @@ struct ClassInfo Class isa; uint32_t hash; } __attribute__((__packed__)); +)"; + +static const char *g_get_shared_cache_class_info_body = R"( uint32_t __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, @@ -1975,6 +1978,7 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor:: class_name_getter_function_name.AsCString(), class_name_getter_function_name.AsCString()); + shared_class_expression += g_get_shared_cache_class_info_definitions; shared_class_expression += g_get_shared_cache_class_info_body; auto utility_fn_or_error = exe_ctx.GetTargetRef().CreateUtilityFunction(