Skip to content

Commit a2c4747

Browse files
bulbazordgit-crd
authored andcommitted
[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
1 parent 800d6ed commit a2c4747

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
@@ -418,6 +418,7 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr,
418418
void *class_infos_ptr,
419419
uint64_t *relative_selector_offset,
420420
uint32_t class_infos_byte_size,
421+
uint32_t *start_idx,
421422
uint32_t should_log)
422423
{
423424
*relative_selector_offset = 0;
@@ -426,6 +427,7 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr,
426427
DEBUG_PRINTF ("shared_cache_base_ptr = %p\n", shared_cache_base_ptr);
427428
DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr);
428429
DEBUG_PRINTF ("class_infos_byte_size = %u (%llu class infos)\n", class_infos_byte_size, (uint64_t)(class_infos_byte_size/sizeof(ClassInfo)));
430+
DEBUG_PRINTF ("start_idx = %u\n", *start_idx);
429431
if (objc_opt_ro_ptr)
430432
{
431433
const objc_opt_t *objc_opt = (objc_opt_t *)objc_opt_ro_ptr;
@@ -480,7 +482,11 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr,
480482
DEBUG_PRINTF ("clsopt->mask = 0x%8.8x\n", clsopt->mask);
481483
DEBUG_PRINTF ("classOffsets = %p\n", classOffsets);
482484
483-
for (uint32_t i=0; i<clsopt->capacity; ++i)
485+
const uint32_t original_start_idx = *start_idx;
486+
487+
// Always start at the start_idx here. If it's greater than the capacity,
488+
// it will skip the loop entirely and go to the duplicate handling below.
489+
for (uint32_t i=*start_idx; i<clsopt->capacity; ++i)
484490
{
485491
const uint64_t objectCacheOffset = classOffsets[i].objectCacheOffset;
486492
DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset);
@@ -524,59 +530,77 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr,
524530
else
525531
{
526532
DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n");
533+
*start_idx = i;
534+
break;
527535
}
528536
++idx;
529537
}
530538
531-
const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity];
532-
const uint32_t duplicate_count = *duplicate_count_ptr;
533-
const objc_classheader_v16_t *duplicateClassOffsets = (const objc_classheader_v16_t *)(&duplicate_count_ptr[1]);
539+
if (idx < max_class_infos) {
540+
const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity];
541+
const uint32_t duplicate_count = *duplicate_count_ptr;
542+
const objc_classheader_v16_t *duplicateClassOffsets = (const objc_classheader_v16_t *)(&duplicate_count_ptr[1]);
534543
535-
DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count);
536-
DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets);
544+
DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count);
545+
DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets);
537546
538-
for (uint32_t i=0; i<duplicate_count; ++i)
539-
{
540-
const uint64_t objectCacheOffset = duplicateClassOffsets[i].objectCacheOffset;
541-
DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset);
547+
const uint32_t duplicate_start_idx =
548+
*start_idx < clsopt->capacity ?
549+
0 :
550+
*start_idx - clsopt->capacity;
542551
543-
if (classOffsets[i].isDuplicate) {
544-
DEBUG_PRINTF("isDuplicate = true\n");
545-
continue; // duplicate
546-
}
547-
548-
if (objectCacheOffset == 0) {
549-
DEBUG_PRINTF("objectCacheOffset == invalidEntryOffset\n");
550-
continue; // invalid offset
551-
}
552-
553-
if (class_infos && idx < max_class_infos)
552+
for (uint32_t i=duplicate_start_idx; i<duplicate_count; ++i)
554553
{
555-
class_infos[idx].isa = (Class)((uint8_t *)shared_cache_base_ptr + objectCacheOffset);
554+
const uint64_t objectCacheOffset = duplicateClassOffsets[i].objectCacheOffset;
555+
DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset);
556556
557-
// Lookup the class name.
558-
const char *name = class_name_lookup_func(class_infos[idx].isa);
559-
DEBUG_PRINTF("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name);
557+
if (duplicateClassOffsets[i].isDuplicate) {
558+
DEBUG_PRINTF("isDuplicate = true\n");
559+
continue; // duplicate
560+
}
560561
561-
// Hash the class name so we don't have to read it.
562-
const char *s = name;
563-
uint32_t h = 5381;
564-
for (unsigned char c = *s; c; c = *++s)
562+
if (objectCacheOffset == 0) {
563+
DEBUG_PRINTF("objectCacheOffset == invalidEntryOffset\n");
564+
continue; // invalid offset
565+
}
566+
567+
if (class_infos && idx < max_class_infos)
565568
{
566-
// class_getName demangles swift names and the hash must
567-
// be calculated on the mangled name. hash==0 means lldb
568-
// will fetch the mangled name and compute the hash in
569-
// ParseClassInfoArray.
570-
if (c == '.')
569+
class_infos[idx].isa = (Class)((uint8_t *)shared_cache_base_ptr + objectCacheOffset);
570+
571+
// Lookup the class name.
572+
const char *name = class_name_lookup_func(class_infos[idx].isa);
573+
DEBUG_PRINTF("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name);
574+
575+
// Hash the class name so we don't have to read it.
576+
const char *s = name;
577+
uint32_t h = 5381;
578+
for (unsigned char c = *s; c; c = *++s)
571579
{
572-
h = 0;
573-
break;
580+
// class_getName demangles swift names and the hash must
581+
// be calculated on the mangled name. hash==0 means lldb
582+
// will fetch the mangled name and compute the hash in
583+
// ParseClassInfoArray.
584+
if (c == '.')
585+
{
586+
h = 0;
587+
break;
588+
}
589+
h = ((h << 5) + h) + c;
574590
}
575-
h = ((h << 5) + h) + c;
591+
class_infos[idx].hash = h;
592+
} else {
593+
DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n");
594+
*start_idx = i;
595+
break;
576596
}
577-
class_infos[idx].hash = h;
597+
++idx;
578598
}
579-
++idx;
599+
}
600+
// Always make sure start_idx gets updated. Otherwise we have an infinite
601+
// loop if there are exactly max_class_infos number of classes.
602+
if (*start_idx == original_start_idx) {
603+
*start_idx = idx;
580604
}
581605
}
582606
else if (objc_opt->version >= 12 && objc_opt->version <= 15)
@@ -1959,6 +1983,9 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::
19591983
CompilerType clang_uint64_t_pointer_type =
19601984
scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 64)
19611985
.GetPointerType();
1986+
CompilerType clang_uint32_t_pointer_type =
1987+
scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32)
1988+
.GetPointerType();
19621989

19631990
// Next make the function caller for our implementation utility function.
19641991
ValueList arguments;
@@ -1976,6 +2003,13 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::
19762003
value.SetValueType(Value::ValueType::Scalar);
19772004
value.SetCompilerType(clang_uint32_t_type);
19782005
arguments.PushValue(value);
2006+
2007+
value.SetValueType(Value::ValueType::Scalar);
2008+
value.SetCompilerType(clang_uint32_t_pointer_type);
2009+
arguments.PushValue(value);
2010+
2011+
value.SetValueType(Value::ValueType::Scalar);
2012+
value.SetCompilerType(clang_uint32_t_type);
19792013
arguments.PushValue(value);
19802014

19812015
std::unique_ptr<UtilityFunction> utility_fn = std::move(*utility_fn_or_error);
@@ -2313,10 +2347,7 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() {
23132347

23142348
// The number of entries to pre-allocate room for.
23152349
// Each entry is (addrsize + 4) bytes
2316-
// FIXME: It is not sustainable to continue incrementing this value every time
2317-
// the shared cache grows. This is because it requires allocating memory in
2318-
// the inferior process and some inferior processes have small memory limits.
2319-
const uint32_t max_num_classes = 212992;
2350+
const uint32_t max_num_classes_in_buffer = 212992;
23202351

23212352
UtilityFunction *get_class_info_code = GetClassInfoUtilityFunction(exe_ctx);
23222353
if (!get_class_info_code) {
@@ -2338,22 +2369,40 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() {
23382369
DiagnosticManager diagnostics;
23392370

23402371
const uint32_t class_info_byte_size = addr_size + 4;
2341-
const uint32_t class_infos_byte_size = max_num_classes * class_info_byte_size;
2372+
const uint32_t class_infos_byte_size =
2373+
max_num_classes_in_buffer * class_info_byte_size;
23422374
lldb::addr_t class_infos_addr = process->AllocateMemory(
23432375
class_infos_byte_size, ePermissionsReadable | ePermissionsWritable, err);
23442376
const uint32_t relative_selector_offset_addr_size = 64;
23452377
lldb::addr_t relative_selector_offset_addr =
23462378
process->AllocateMemory(relative_selector_offset_addr_size,
23472379
ePermissionsReadable | ePermissionsWritable, err);
2380+
constexpr uint32_t class_info_start_idx_byte_size = sizeof(uint32_t);
2381+
lldb::addr_t class_info_start_idx_addr =
2382+
process->AllocateMemory(class_info_start_idx_byte_size,
2383+
ePermissionsReadable | ePermissionsWritable, err);
23482384

2349-
if (class_infos_addr == LLDB_INVALID_ADDRESS) {
2385+
if (class_infos_addr == LLDB_INVALID_ADDRESS ||
2386+
relative_selector_offset_addr == LLDB_INVALID_ADDRESS ||
2387+
class_info_start_idx_addr == LLDB_INVALID_ADDRESS) {
23502388
LLDB_LOGF(log,
23512389
"unable to allocate %" PRIu32
23522390
" bytes in process for shared cache read",
23532391
class_infos_byte_size);
23542392
return DescriptorMapUpdateResult::Fail();
23552393
}
23562394

2395+
const uint32_t start_idx_init_value = 0;
2396+
size_t bytes_written = process->WriteMemory(
2397+
class_info_start_idx_addr, &start_idx_init_value, sizeof(uint32_t), err);
2398+
if (bytes_written != sizeof(uint32_t)) {
2399+
LLDB_LOGF(log,
2400+
"unable to write %" PRIu32
2401+
" bytes in process for shared cache read",
2402+
class_infos_byte_size);
2403+
return DescriptorMapUpdateResult::Fail();
2404+
}
2405+
23572406
std::lock_guard<std::mutex> guard(m_mutex);
23582407

23592408
// Fill in our function argument values
@@ -2362,12 +2411,13 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() {
23622411
arguments.GetValueAtIndex(2)->GetScalar() = class_infos_addr;
23632412
arguments.GetValueAtIndex(3)->GetScalar() = relative_selector_offset_addr;
23642413
arguments.GetValueAtIndex(4)->GetScalar() = class_infos_byte_size;
2414+
arguments.GetValueAtIndex(5)->GetScalar() = class_info_start_idx_addr;
23652415
// Only dump the runtime classes from the expression evaluation if the log is
23662416
// verbose:
23672417
Log *type_log = GetLog(LLDBLog::Types);
23682418
bool dump_log = type_log && type_log->GetVerbose();
23692419

2370-
arguments.GetValueAtIndex(5)->GetScalar() = dump_log ? 1 : 0;
2420+
arguments.GetValueAtIndex(6)->GetScalar() = dump_log ? 1 : 0;
23712421

23722422
bool success = false;
23732423

@@ -2394,78 +2444,80 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() {
23942444

23952445
diagnostics.Clear();
23962446

2397-
// Run the function
2398-
ExpressionResults results =
2399-
get_shared_cache_class_info_function->ExecuteFunction(
2400-
exe_ctx, &m_args, options, diagnostics, return_value);
2401-
2402-
if (results == eExpressionCompleted) {
2403-
// The result is the number of ClassInfo structures that were filled in
2404-
num_class_infos = return_value.GetScalar().ULong();
2405-
LLDB_LOG(log, "Discovered {0} Objective-C classes in the shared cache",
2406-
num_class_infos);
2407-
// Assert if there were more classes than we pre-allocated
2408-
// room for.
2409-
assert(num_class_infos <= max_num_classes);
2410-
if (num_class_infos > 0) {
2411-
if (num_class_infos > max_num_classes) {
2412-
num_class_infos = max_num_classes;
2413-
2414-
success = false;
2415-
} else {
2447+
uint32_t num_class_infos_read = 0;
2448+
bool already_read_relative_selector_offset = false;
2449+
2450+
do {
2451+
// Run the function.
2452+
ExpressionResults results =
2453+
get_shared_cache_class_info_function->ExecuteFunction(
2454+
exe_ctx, &m_args, options, diagnostics, return_value);
2455+
2456+
if (results == eExpressionCompleted) {
2457+
// The result is the number of ClassInfo structures that were filled in.
2458+
num_class_infos_read = return_value.GetScalar().ULong();
2459+
num_class_infos += num_class_infos_read;
2460+
LLDB_LOG(log, "Discovered {0} Objective-C classes in the shared cache",
2461+
num_class_infos_read);
2462+
if (num_class_infos_read > 0) {
24162463
success = true;
2417-
}
24182464

2419-
// Read the relative selector offset.
2420-
DataBufferHeap relative_selector_offset_buffer(64, 0);
2421-
if (process->ReadMemory(relative_selector_offset_addr,
2422-
relative_selector_offset_buffer.GetBytes(),
2423-
relative_selector_offset_buffer.GetByteSize(),
2424-
err) ==
2425-
relative_selector_offset_buffer.GetByteSize()) {
2426-
DataExtractor relative_selector_offset_data(
2427-
relative_selector_offset_buffer.GetBytes(),
2428-
relative_selector_offset_buffer.GetByteSize(),
2429-
process->GetByteOrder(), addr_size);
2430-
lldb::offset_t offset = 0;
2431-
uint64_t relative_selector_offset =
2432-
relative_selector_offset_data.GetU64(&offset);
2433-
if (relative_selector_offset > 0) {
2434-
// The offset is relative to the objc_opt struct.
2435-
m_runtime.SetRelativeSelectorBaseAddr(objc_opt_ptr +
2436-
relative_selector_offset);
2465+
// Read the relative selector offset. This only needs to occur once no
2466+
// matter how many times the function is called.
2467+
if (!already_read_relative_selector_offset) {
2468+
DataBufferHeap relative_selector_offset_buffer(64, 0);
2469+
if (process->ReadMemory(
2470+
relative_selector_offset_addr,
2471+
relative_selector_offset_buffer.GetBytes(),
2472+
relative_selector_offset_buffer.GetByteSize(),
2473+
err) == relative_selector_offset_buffer.GetByteSize()) {
2474+
DataExtractor relative_selector_offset_data(
2475+
relative_selector_offset_buffer.GetBytes(),
2476+
relative_selector_offset_buffer.GetByteSize(),
2477+
process->GetByteOrder(), addr_size);
2478+
lldb::offset_t offset = 0;
2479+
uint64_t relative_selector_offset =
2480+
relative_selector_offset_data.GetU64(&offset);
2481+
if (relative_selector_offset > 0) {
2482+
// The offset is relative to the objc_opt struct.
2483+
m_runtime.SetRelativeSelectorBaseAddr(objc_opt_ptr +
2484+
relative_selector_offset);
2485+
}
2486+
}
2487+
already_read_relative_selector_offset = true;
24372488
}
2438-
}
2439-
2440-
// Read the ClassInfo structures
2441-
DataBufferHeap class_infos_buffer(
2442-
num_class_infos * class_info_byte_size, 0);
2443-
if (process->ReadMemory(class_infos_addr, class_infos_buffer.GetBytes(),
2444-
class_infos_buffer.GetByteSize(),
2445-
err) == class_infos_buffer.GetByteSize()) {
2446-
DataExtractor class_infos_data(class_infos_buffer.GetBytes(),
2447-
class_infos_buffer.GetByteSize(),
2448-
process->GetByteOrder(), addr_size);
24492489

2450-
m_runtime.ParseClassInfoArray(class_infos_data, num_class_infos);
2490+
// Read the ClassInfo structures
2491+
DataBufferHeap class_infos_buffer(
2492+
num_class_infos_read * class_info_byte_size, 0);
2493+
if (process->ReadMemory(class_infos_addr,
2494+
class_infos_buffer.GetBytes(),
2495+
class_infos_buffer.GetByteSize(),
2496+
err) == class_infos_buffer.GetByteSize()) {
2497+
DataExtractor class_infos_data(class_infos_buffer.GetBytes(),
2498+
class_infos_buffer.GetByteSize(),
2499+
process->GetByteOrder(), addr_size);
2500+
2501+
m_runtime.ParseClassInfoArray(class_infos_data,
2502+
num_class_infos_read);
2503+
}
24512504
}
2452-
} else {
2453-
success = true;
2454-
}
2455-
} else {
2456-
if (log) {
2505+
} else if (log) {
24572506
LLDB_LOGF(log, "Error evaluating our find class name function.");
24582507
diagnostics.Dump(log);
2508+
break;
24592509
}
2460-
}
2461-
} else {
2462-
if (log) {
2463-
LLDB_LOGF(log, "Error writing function arguments.");
2464-
diagnostics.Dump(log);
2465-
}
2510+
} while (num_class_infos_read == max_num_classes_in_buffer);
2511+
} else if (log) {
2512+
LLDB_LOGF(log, "Error writing function arguments.");
2513+
diagnostics.Dump(log);
24662514
}
24672515

2468-
// Deallocate the memory we allocated for the ClassInfo array
2516+
LLDB_LOG(log, "Processed {0} Objective-C classes total from the shared cache",
2517+
num_class_infos);
2518+
// Cleanup memory we allocated in the process.
2519+
process->DeallocateMemory(relative_selector_offset_addr);
2520+
process->DeallocateMemory(class_info_start_idx_addr);
24692521
process->DeallocateMemory(class_infos_addr);
24702522

24712523
return DescriptorMapUpdateResult(success, false, num_class_infos);

0 commit comments

Comments
 (0)