Skip to content

Commit b2d7d3e

Browse files
committed
[lldb] Adapt shared cache ObjC class metadata extraction for long term growth (llvm#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 ec4207b)
1 parent 4577136 commit b2d7d3e

File tree

1 file changed

+160
-108
lines changed

1 file changed

+160
-108
lines changed

lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp

Lines changed: 160 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr,
423423
void *class_infos_ptr,
424424
uint64_t *relative_selector_offset,
425425
uint32_t class_infos_byte_size,
426+
uint32_t *start_idx,
426427
uint32_t should_log)
427428
{
428429
*relative_selector_offset = 0;
@@ -431,6 +432,7 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr,
431432
DEBUG_PRINTF ("shared_cache_base_ptr = %p\n", shared_cache_base_ptr);
432433
DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr);
433434
DEBUG_PRINTF ("class_infos_byte_size = %u (%llu class infos)\n", class_infos_byte_size, (uint64_t)(class_infos_byte_size/sizeof(ClassInfo)));
435+
DEBUG_PRINTF ("start_idx = %u\n", *start_idx);
434436
if (objc_opt_ro_ptr)
435437
{
436438
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,
485487
DEBUG_PRINTF ("clsopt->mask = 0x%8.8x\n", clsopt->mask);
486488
DEBUG_PRINTF ("classOffsets = %p\n", classOffsets);
487489
488-
for (uint32_t i=0; i<clsopt->capacity; ++i)
490+
const uint32_t original_start_idx = *start_idx;
491+
492+
// Always start at the start_idx here. If it's greater than the capacity,
493+
// it will skip the loop entirely and go to the duplicate handling below.
494+
for (uint32_t i=*start_idx; i<clsopt->capacity; ++i)
489495
{
490496
const uint64_t objectCacheOffset = classOffsets[i].objectCacheOffset;
491497
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,
529535
else
530536
{
531537
DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n");
538+
*start_idx = i;
539+
break;
532540
}
533541
++idx;
534542
}
535543
536-
const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity];
537-
const uint32_t duplicate_count = *duplicate_count_ptr;
538-
const objc_classheader_v16_t *duplicateClassOffsets = (const objc_classheader_v16_t *)(&duplicate_count_ptr[1]);
544+
if (idx < max_class_infos) {
545+
const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity];
546+
const uint32_t duplicate_count = *duplicate_count_ptr;
547+
const objc_classheader_v16_t *duplicateClassOffsets = (const objc_classheader_v16_t *)(&duplicate_count_ptr[1]);
539548
540-
DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count);
541-
DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets);
549+
DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count);
550+
DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets);
542551
543-
for (uint32_t i=0; i<duplicate_count; ++i)
544-
{
545-
const uint64_t objectCacheOffset = duplicateClassOffsets[i].objectCacheOffset;
546-
DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset);
552+
const uint32_t duplicate_start_idx =
553+
*start_idx < clsopt->capacity ?
554+
0 :
555+
*start_idx - clsopt->capacity;
547556
548-
if (classOffsets[i].isDuplicate) {
549-
DEBUG_PRINTF("isDuplicate = true\n");
550-
continue; // duplicate
551-
}
552-
553-
if (objectCacheOffset == 0) {
554-
DEBUG_PRINTF("objectCacheOffset == invalidEntryOffset\n");
555-
continue; // invalid offset
556-
}
557-
558-
if (class_infos && idx < max_class_infos)
557+
for (uint32_t i=duplicate_start_idx; i<duplicate_count; ++i)
559558
{
560-
class_infos[idx].isa = (Class)((uint8_t *)shared_cache_base_ptr + objectCacheOffset);
559+
const uint64_t objectCacheOffset = duplicateClassOffsets[i].objectCacheOffset;
560+
DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset);
561561
562-
// Lookup the class name.
563-
const char *name = class_name_lookup_func(class_infos[idx].isa);
564-
DEBUG_PRINTF("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name);
562+
if (duplicateClassOffsets[i].isDuplicate) {
563+
DEBUG_PRINTF("isDuplicate = true\n");
564+
continue; // duplicate
565+
}
565566
566-
// Hash the class name so we don't have to read it.
567-
const char *s = name;
568-
uint32_t h = 5381;
569-
for (unsigned char c = *s; c; c = *++s)
567+
if (objectCacheOffset == 0) {
568+
DEBUG_PRINTF("objectCacheOffset == invalidEntryOffset\n");
569+
continue; // invalid offset
570+
}
571+
572+
if (class_infos && idx < max_class_infos)
570573
{
571-
// class_getName demangles swift names and the hash must
572-
// be calculated on the mangled name. hash==0 means lldb
573-
// will fetch the mangled name and compute the hash in
574-
// ParseClassInfoArray.
575-
if (c == '.')
574+
class_infos[idx].isa = (Class)((uint8_t *)shared_cache_base_ptr + objectCacheOffset);
575+
576+
// Lookup the class name.
577+
const char *name = class_name_lookup_func(class_infos[idx].isa);
578+
DEBUG_PRINTF("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name);
579+
580+
// Hash the class name so we don't have to read it.
581+
const char *s = name;
582+
uint32_t h = 5381;
583+
for (unsigned char c = *s; c; c = *++s)
576584
{
577-
h = 0;
578-
break;
585+
// class_getName demangles swift names and the hash must
586+
// be calculated on the mangled name. hash==0 means lldb
587+
// will fetch the mangled name and compute the hash in
588+
// ParseClassInfoArray.
589+
if (c == '.')
590+
{
591+
h = 0;
592+
break;
593+
}
594+
h = ((h << 5) + h) + c;
579595
}
580-
h = ((h << 5) + h) + c;
596+
class_infos[idx].hash = h;
597+
} else {
598+
DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n");
599+
*start_idx = i;
600+
break;
581601
}
582-
class_infos[idx].hash = h;
602+
++idx;
583603
}
584-
++idx;
604+
}
605+
// Always make sure start_idx gets updated. Otherwise we have an infinite
606+
// loop if there are exactly max_class_infos number of classes.
607+
if (*start_idx == original_start_idx) {
608+
*start_idx = idx;
585609
}
586610
}
587611
else if (objc_opt->version >= 12 && objc_opt->version <= 15)
@@ -1972,6 +1996,9 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::
19721996
CompilerType clang_uint64_t_pointer_type =
19731997
scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 64)
19741998
.GetPointerType();
1999+
CompilerType clang_uint32_t_pointer_type =
2000+
scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32)
2001+
.GetPointerType();
19752002

19762003
// Next make the function caller for our implementation utility function.
19772004
ValueList arguments;
@@ -1989,6 +2016,13 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::
19892016
value.SetValueType(Value::ValueType::Scalar);
19902017
value.SetCompilerType(clang_uint32_t_type);
19912018
arguments.PushValue(value);
2019+
2020+
value.SetValueType(Value::ValueType::Scalar);
2021+
value.SetCompilerType(clang_uint32_t_pointer_type);
2022+
arguments.PushValue(value);
2023+
2024+
value.SetValueType(Value::ValueType::Scalar);
2025+
value.SetCompilerType(clang_uint32_t_type);
19922026
arguments.PushValue(value);
19932027

19942028
std::unique_ptr<UtilityFunction> utility_fn = std::move(*utility_fn_or_error);
@@ -2326,10 +2360,7 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() {
23262360

23272361
// The number of entries to pre-allocate room for.
23282362
// Each entry is (addrsize + 4) bytes
2329-
// FIXME: It is not sustainable to continue incrementing this value every time
2330-
// the shared cache grows. This is because it requires allocating memory in
2331-
// the inferior process and some inferior processes have small memory limits.
2332-
const uint32_t max_num_classes = 212992;
2363+
const uint32_t max_num_classes_in_buffer = 212992;
23332364

23342365
UtilityFunction *get_class_info_code = GetClassInfoUtilityFunction(exe_ctx);
23352366
if (!get_class_info_code) {
@@ -2351,22 +2382,40 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() {
23512382
DiagnosticManager diagnostics;
23522383

23532384
const uint32_t class_info_byte_size = addr_size + 4;
2354-
const uint32_t class_infos_byte_size = max_num_classes * class_info_byte_size;
2385+
const uint32_t class_infos_byte_size =
2386+
max_num_classes_in_buffer * class_info_byte_size;
23552387
lldb::addr_t class_infos_addr = process->AllocateMemory(
23562388
class_infos_byte_size, ePermissionsReadable | ePermissionsWritable, err);
23572389
const uint32_t relative_selector_offset_addr_size = 64;
23582390
lldb::addr_t relative_selector_offset_addr =
23592391
process->AllocateMemory(relative_selector_offset_addr_size,
23602392
ePermissionsReadable | ePermissionsWritable, err);
2393+
constexpr uint32_t class_info_start_idx_byte_size = sizeof(uint32_t);
2394+
lldb::addr_t class_info_start_idx_addr =
2395+
process->AllocateMemory(class_info_start_idx_byte_size,
2396+
ePermissionsReadable | ePermissionsWritable, err);
23612397

2362-
if (class_infos_addr == LLDB_INVALID_ADDRESS) {
2398+
if (class_infos_addr == LLDB_INVALID_ADDRESS ||
2399+
relative_selector_offset_addr == LLDB_INVALID_ADDRESS ||
2400+
class_info_start_idx_addr == LLDB_INVALID_ADDRESS) {
23632401
LLDB_LOGF(log,
23642402
"unable to allocate %" PRIu32
23652403
" bytes in process for shared cache read",
23662404
class_infos_byte_size);
23672405
return DescriptorMapUpdateResult::Fail();
23682406
}
23692407

2408+
const uint32_t start_idx_init_value = 0;
2409+
size_t bytes_written = process->WriteMemory(
2410+
class_info_start_idx_addr, &start_idx_init_value, sizeof(uint32_t), err);
2411+
if (bytes_written != sizeof(uint32_t)) {
2412+
LLDB_LOGF(log,
2413+
"unable to write %" PRIu32
2414+
" bytes in process for shared cache read",
2415+
class_infos_byte_size);
2416+
return DescriptorMapUpdateResult::Fail();
2417+
}
2418+
23702419
std::lock_guard<std::mutex> guard(m_mutex);
23712420

23722421
// Fill in our function argument values
@@ -2375,12 +2424,13 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() {
23752424
arguments.GetValueAtIndex(2)->GetScalar() = class_infos_addr;
23762425
arguments.GetValueAtIndex(3)->GetScalar() = relative_selector_offset_addr;
23772426
arguments.GetValueAtIndex(4)->GetScalar() = class_infos_byte_size;
2427+
arguments.GetValueAtIndex(5)->GetScalar() = class_info_start_idx_addr;
23782428
// Only dump the runtime classes from the expression evaluation if the log is
23792429
// verbose:
23802430
Log *type_log = GetLog(LLDBLog::Types);
23812431
bool dump_log = type_log && type_log->GetVerbose();
23822432

2383-
arguments.GetValueAtIndex(5)->GetScalar() = dump_log ? 1 : 0;
2433+
arguments.GetValueAtIndex(6)->GetScalar() = dump_log ? 1 : 0;
23842434

23852435
bool success = false;
23862436

@@ -2407,78 +2457,80 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() {
24072457

24082458
diagnostics.Clear();
24092459

2410-
// Run the function
2411-
ExpressionResults results =
2412-
get_shared_cache_class_info_function->ExecuteFunction(
2413-
exe_ctx, &m_args, options, diagnostics, return_value);
2414-
2415-
if (results == eExpressionCompleted) {
2416-
// The result is the number of ClassInfo structures that were filled in
2417-
num_class_infos = return_value.GetScalar().ULong();
2418-
LLDB_LOG(log, "Discovered {0} Objective-C classes in the shared cache",
2419-
num_class_infos);
2420-
// Assert if there were more classes than we pre-allocated
2421-
// room for.
2422-
assert(num_class_infos <= max_num_classes);
2423-
if (num_class_infos > 0) {
2424-
if (num_class_infos > max_num_classes) {
2425-
num_class_infos = max_num_classes;
2426-
2427-
success = false;
2428-
} else {
2460+
uint32_t num_class_infos_read = 0;
2461+
bool already_read_relative_selector_offset = false;
2462+
2463+
do {
2464+
// Run the function.
2465+
ExpressionResults results =
2466+
get_shared_cache_class_info_function->ExecuteFunction(
2467+
exe_ctx, &m_args, options, diagnostics, return_value);
2468+
2469+
if (results == eExpressionCompleted) {
2470+
// The result is the number of ClassInfo structures that were filled in.
2471+
num_class_infos_read = return_value.GetScalar().ULong();
2472+
num_class_infos += num_class_infos_read;
2473+
LLDB_LOG(log, "Discovered {0} Objective-C classes in the shared cache",
2474+
num_class_infos_read);
2475+
if (num_class_infos_read > 0) {
24292476
success = true;
2430-
}
24312477

2432-
// Read the relative selector offset.
2433-
DataBufferHeap relative_selector_offset_buffer(64, 0);
2434-
if (process->ReadMemory(relative_selector_offset_addr,
2435-
relative_selector_offset_buffer.GetBytes(),
2436-
relative_selector_offset_buffer.GetByteSize(),
2437-
err) ==
2438-
relative_selector_offset_buffer.GetByteSize()) {
2439-
DataExtractor relative_selector_offset_data(
2440-
relative_selector_offset_buffer.GetBytes(),
2441-
relative_selector_offset_buffer.GetByteSize(),
2442-
process->GetByteOrder(), addr_size);
2443-
lldb::offset_t offset = 0;
2444-
uint64_t relative_selector_offset =
2445-
relative_selector_offset_data.GetU64(&offset);
2446-
if (relative_selector_offset > 0) {
2447-
// The offset is relative to the objc_opt struct.
2448-
m_runtime.SetRelativeSelectorBaseAddr(objc_opt_ptr +
2449-
relative_selector_offset);
2478+
// Read the relative selector offset. This only needs to occur once no
2479+
// matter how many times the function is called.
2480+
if (!already_read_relative_selector_offset) {
2481+
DataBufferHeap relative_selector_offset_buffer(64, 0);
2482+
if (process->ReadMemory(
2483+
relative_selector_offset_addr,
2484+
relative_selector_offset_buffer.GetBytes(),
2485+
relative_selector_offset_buffer.GetByteSize(),
2486+
err) == relative_selector_offset_buffer.GetByteSize()) {
2487+
DataExtractor relative_selector_offset_data(
2488+
relative_selector_offset_buffer.GetBytes(),
2489+
relative_selector_offset_buffer.GetByteSize(),
2490+
process->GetByteOrder(), addr_size);
2491+
lldb::offset_t offset = 0;
2492+
uint64_t relative_selector_offset =
2493+
relative_selector_offset_data.GetU64(&offset);
2494+
if (relative_selector_offset > 0) {
2495+
// The offset is relative to the objc_opt struct.
2496+
m_runtime.SetRelativeSelectorBaseAddr(objc_opt_ptr +
2497+
relative_selector_offset);
2498+
}
2499+
}
2500+
already_read_relative_selector_offset = true;
24502501
}
2451-
}
2452-
2453-
// Read the ClassInfo structures
2454-
DataBufferHeap class_infos_buffer(
2455-
num_class_infos * class_info_byte_size, 0);
2456-
if (process->ReadMemory(class_infos_addr, class_infos_buffer.GetBytes(),
2457-
class_infos_buffer.GetByteSize(),
2458-
err) == class_infos_buffer.GetByteSize()) {
2459-
DataExtractor class_infos_data(class_infos_buffer.GetBytes(),
2460-
class_infos_buffer.GetByteSize(),
2461-
process->GetByteOrder(), addr_size);
24622502

2463-
m_runtime.ParseClassInfoArray(class_infos_data, num_class_infos);
2503+
// Read the ClassInfo structures
2504+
DataBufferHeap class_infos_buffer(
2505+
num_class_infos_read * class_info_byte_size, 0);
2506+
if (process->ReadMemory(class_infos_addr,
2507+
class_infos_buffer.GetBytes(),
2508+
class_infos_buffer.GetByteSize(),
2509+
err) == class_infos_buffer.GetByteSize()) {
2510+
DataExtractor class_infos_data(class_infos_buffer.GetBytes(),
2511+
class_infos_buffer.GetByteSize(),
2512+
process->GetByteOrder(), addr_size);
2513+
2514+
m_runtime.ParseClassInfoArray(class_infos_data,
2515+
num_class_infos_read);
2516+
}
24642517
}
2465-
} else {
2466-
success = true;
2467-
}
2468-
} else {
2469-
if (log) {
2518+
} else if (log) {
24702519
LLDB_LOGF(log, "Error evaluating our find class name function.");
24712520
diagnostics.Dump(log);
2521+
break;
24722522
}
2473-
}
2474-
} else {
2475-
if (log) {
2476-
LLDB_LOGF(log, "Error writing function arguments.");
2477-
diagnostics.Dump(log);
2478-
}
2523+
} while (num_class_infos_read == max_num_classes_in_buffer);
2524+
} else if (log) {
2525+
LLDB_LOGF(log, "Error writing function arguments.");
2526+
diagnostics.Dump(log);
24792527
}
24802528

2481-
// Deallocate the memory we allocated for the ClassInfo array
2529+
LLDB_LOG(log, "Processed {0} Objective-C classes total from the shared cache",
2530+
num_class_infos);
2531+
// Cleanup memory we allocated in the process.
2532+
process->DeallocateMemory(relative_selector_offset_addr);
2533+
process->DeallocateMemory(class_info_start_idx_addr);
24822534
process->DeallocateMemory(class_infos_addr);
24832535

24842536
return DescriptorMapUpdateResult(success, false, num_class_infos);

0 commit comments

Comments
 (0)