jasonmolenda created this revision. jasonmolenda added a reviewer: jingham. Herald added subscribers: teemperor, abidh. Herald added a project: LLDB.
There's a perf problem with Objective-C programs as we add more classes to the Darwin libraries over time - when lldb goes to run its first expression on one of these systems, it will load the ObjC class names from the run time, both the shared cache libraries and any app-contributed classes. lldb has two expressions it runs in the inferior to collect this information into a buffer allocated by lldb - returning the isa pointers and a hash of each class name used to id them. lldb would then read the names of the classes out of memory, which were not localized to one region of memory, and this has become a larger and larger performance issue. This patch modifies: 1. The two jitted functions, g_get_dynamic_class_info_body and g_get_shared_cache_class_info_body, now take the address of an allocated string pool buffer and size. If the address of the string pool is 0, no names are copied. The functions will copy the class names in to the string pool buffer and add an offset to the (isa, hash) that were previously being returned. If an entry in the (isa, hash, stroffset) array does not have an entry in the string pool, UINT32_MAX is used. If lldb's pre-allocated string pool buffer is too small, entries that did not fit will get UINT32_MAX and lldb will read the class names the old slow way. 2. Modifies the two methods that call these jitted functions, AppleObjCRuntimeV2::UpdateISAToDescriptorMapDynamic and AppleObjCRuntimeV2::UpdateISAToDescriptorMapSharedCache, to allocate the string pool buffer, pass the address and size to the jitted expression, scan the array of (isa, hash, stroffset) tuples to find the highest stroffset, read that part of the string pool out of the inferior with a single read [*], pass the buffer to ParseClassInfoArray, deallocate the stringpool from the inferior. [X] nb: I just saw an unintended behavior on the last class name as I was writing this. Given that it uses the highest stroffset it finds to read the string pool back out of the inferior, the final class name won't be copied up. The test in ParseClassInfoArray will not 3. Modifies AppleObjCRuntimeV2::ParseClassInfoArray to grab the class name from the stringpool buffer if the stroffset is != UINT32_MAX and is within the copied buffer size. [X] nb: I just saw an unintended behavior on the last class name as I was writing this. Given that it uses the highest stroffset it finds to read the string pool back out of the inferior, the final class name won't be copied up. The test in ParseClassInfoArray to check that the stroffset is within the bounds of the copied buffer should mean that we read the last class name out of the inferior aka the old method. I'll double check this is handled correctly tomorrow. This patch also includes a change that Frederic Riss wrote the other month but hadn't upstreamed yet, where we detect swift names in the shared cache and don't compute the hash in the jitted function, doing it up in lldb later. Testing the patch shows no testsuite difference on Mac native. As an experiment, I intentionally introduced a bug where the class names in the string pool were corrupted and it caused the testsuite failures I expected. From a performance point of view, this shows the packet behavior I was aiming for. rdar://problem/27798609 Repository: rLLDB LLDB https://reviews.llvm.org/D60957 Files: source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h
Index: source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h =================================================================== --- source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h +++ source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h @@ -297,7 +297,8 @@ UpdateISAToDescriptorMapDynamic(RemoteNXMapTable &hash_table); uint32_t ParseClassInfoArray(const lldb_private::DataExtractor &data, - uint32_t num_class_infos); + uint32_t num_class_infos, + lldb::DataBufferSP strpool_buffer_sp); DescriptorMapUpdateResult UpdateISAToDescriptorMapSharedCache(); Index: source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp =================================================================== --- source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -79,10 +79,11 @@ extern "C" { size_t strlen(const char *); - char *strncpy (char * s1, const char * s2, size_t n); + char *strcpy (char * dst, const char * src); int printf(const char * format, ...); } #define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) +#define UINT32_MAX 0xffffffff typedef struct _NXMapTable { void *prototype; @@ -103,17 +104,21 @@ { Class isa; uint32_t hash; + uint32_t stroffset; } __attribute__((__packed__)); uint32_t __lldb_apple_objc_v2_get_dynamic_class_info (void *gdb_objc_realized_classes_ptr, void *class_infos_ptr, uint32_t class_infos_byte_size, + void *string_pool_ptr, + uint32_t string_pool_byte_size, uint32_t should_log) { DEBUG_PRINTF ("gdb_objc_realized_classes_ptr = %p\n", gdb_objc_realized_classes_ptr); DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); DEBUG_PRINTF ("class_infos_byte_size = %u\n", class_infos_byte_size); + DEBUG_PRINTF ("string_pool_byte_size = %u\n", string_pool_byte_size); const NXMapTable *grc = (const NXMapTable *)gdb_objc_realized_classes_ptr; if (grc) { @@ -123,6 +128,8 @@ const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; BucketInfo *buckets = (BucketInfo *)grc->buckets; + char *string_pool_base = (char *)string_pool_ptr; + uint32_t current_strpool_offset = 0; uint32_t idx = 0; for (unsigned i=0; i<=grc->num_buckets_minus_one; ++i) @@ -131,12 +138,26 @@ { if (idx < max_class_infos) { - const char *s = buckets[i].name_ptr; + const char *name = buckets[i].name_ptr; + const char *s = name; 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 = buckets[i].isa; + class_infos[idx].stroffset = UINT32_MAX; // default to no strpool offset + if (string_pool_base && string_pool_byte_size != 0 && name && h != 0) + { + const int name_len = strlen (name); + const int remaining_strpool = string_pool_byte_size - current_strpool_offset; + if (name_len > 0 && remaining_strpool > (name_len + 1)) + { + DEBUG_PRINTF ("[%u] name %s copied into stringpool offset %u\n", idx, name, current_strpool_offset); + strcpy (string_pool_base + current_strpool_offset, name); + class_infos[idx].stroffset = current_strpool_offset; + current_strpool_offset += name_len + 1; + } + } } ++idx; } @@ -145,6 +166,7 @@ { class_infos[idx].isa = NULL; class_infos[idx].hash = 0; + class_infos[idx].stroffset = UINT32_MAX; // no strpool offset } } return num_classes; @@ -164,13 +186,13 @@ { const char *class_getName(void *objc_class); size_t strlen(const char *); - char *strncpy (char * s1, const char * s2, size_t n); + char *strcpy (char * dst, const char * src); int printf(const char * format, ...); } #define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) +#define UINT32_MAX 0xffffffff - struct objc_classheader_t { int32_t clsOffset; int32_t hiOffset; @@ -212,18 +234,23 @@ { Class isa; uint32_t hash; + uint32_t stroffset; } __attribute__((__packed__)); uint32_t __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, void *class_infos_ptr, uint32_t class_infos_byte_size, + void *string_pool_ptr, + uint32_t string_pool_byte_size, uint32_t should_log) { uint32_t idx = 0; DEBUG_PRINTF ("objc_opt_ro_ptr = %p\n", objc_opt_ro_ptr); DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); + DEBUG_PRINTF ("string_pool_ptr = %p\n", string_pool_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 ("string_pool_byte_size = %u\n", string_pool_byte_size); if (objc_opt_ro_ptr) { const objc_opt_t *objc_opt = (objc_opt_t *)objc_opt_ro_ptr; @@ -254,6 +281,8 @@ const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); DEBUG_PRINTF("max_class_infos = %llu\n", (uint64_t)max_class_infos); ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; + char *string_pool_base = (char *)string_pool_ptr; + uint32_t current_strpool_offset = 0; int32_t invalidEntryOffset = 0; // this is safe to do because the version field order is invariant if (objc_opt->version == 12) @@ -289,8 +318,25 @@ const char *s = name; uint32_t h = 5381; for (unsigned char c = *s; c; c = *++s) + { + // See comment in ParseClassInfoArray + if (c == '.') { h = 0; break; } h = ((h << 5) + h) + c; + } class_infos[idx].hash = h; + class_infos[idx].stroffset = UINT32_MAX; // default to no strpool offset + if (string_pool_base && string_pool_byte_size != 0 && name && h != 0) + { + const int name_len = strlen (name); + const int remaining_strpool = string_pool_byte_size - current_strpool_offset; + if (name_len > 0 && remaining_strpool > (name_len + 1)) + { + DEBUG_PRINTF ("[%u] name %s copied into stringpool offset %u\n", idx, name, current_strpool_offset); + strcpy (string_pool_base + current_strpool_offset, name); + class_infos[idx].stroffset = current_strpool_offset; + current_strpool_offset += name_len + 1; + } + } } else { @@ -321,8 +367,25 @@ const char *s = name; uint32_t h = 5381; for (unsigned char c = *s; c; c = *++s) + { + // See comment in ParseClassInfoArray + if (c == '.') { h = 0; break; } h = ((h << 5) + h) + c; + } class_infos[idx].hash = h; + class_infos[idx].stroffset = UINT32_MAX; // default to no strpool offset + if (string_pool_base && string_pool_byte_size != 0 && name && h != 0) + { + const int name_len = strlen (name); + const int remaining_strpool = string_pool_byte_size - current_strpool_offset; + if (name_len > 0 && remaining_strpool > (name_len + 1)) + { + DEBUG_PRINTF ("[%u] name %s copied into stringpool offset %u\n", idx, name, current_strpool_offset); + strcpy (string_pool_base + current_strpool_offset, name); + current_strpool_offset += name_len + 1; + class_infos[idx].stroffset = current_strpool_offset; + } + } } ++idx; } @@ -1322,16 +1385,19 @@ return DescriptorMapUpdateResult::Fail(); // Next make the runner function for our implementation utility function. - Value value; - value.SetValueType(Value::eValueTypeScalar); - value.SetCompilerType(clang_void_pointer_type); - arguments.PushValue(value); - arguments.PushValue(value); + Value ptr_value; + Value uint32_value; + ptr_value.SetValueType(Value::eValueTypeScalar); + ptr_value.SetCompilerType(clang_void_pointer_type); + uint32_value.SetValueType(Value::eValueTypeScalar); + uint32_value.SetCompilerType(clang_uint32_t_type); - value.SetValueType(Value::eValueTypeScalar); - value.SetCompilerType(clang_uint32_t_type); - arguments.PushValue(value); - arguments.PushValue(value); + arguments.PushValue(ptr_value); // void *gdb_objc_realized_classes_ptr + arguments.PushValue(ptr_value); // void *class_infos_ptr + arguments.PushValue(uint32_value); // uint32_t class_infos_byte_size + arguments.PushValue(ptr_value); // void *string_pool_ptr + arguments.PushValue(uint32_value); // uint32_t string_pool_byte_size + arguments.PushValue(uint32_value); // uint32_t should_log get_class_info_function = m_get_class_info_code->MakeFunctionCaller( clang_uint32_t_type, arguments, thread_sp, error); @@ -1358,11 +1424,21 @@ diagnostics.Clear(); - const uint32_t class_info_byte_size = addr_size + 4; + const uint32_t class_info_byte_size = addr_size + + 4 /* hash */ + + 4 /* stroffset */; const uint32_t class_infos_byte_size = num_classes * class_info_byte_size; lldb::addr_t class_infos_addr = process->AllocateMemory( class_infos_byte_size, ePermissionsReadable | ePermissionsWritable, err); + // Assume classes names will be less than 32 chars on average. If + // we run out of space, entries will have an offset of UINT32_MAX + // and lldb will read the class names out of memory individually. + // Actual average is around 23 characters per class name in 2019. + const uint32_t string_pool_byte_size = num_classes * 32; + addr_t string_pool_addr = process->AllocateMemory( + string_pool_byte_size, ePermissionsReadable | ePermissionsWritable, err); + if (class_infos_addr == LLDB_INVALID_ADDRESS) { if (log) log->Printf("unable to allocate %" PRIu32 @@ -1371,19 +1447,30 @@ return DescriptorMapUpdateResult::Fail(); } + if (string_pool_addr == LLDB_INVALID_ADDRESS) { + if (log) + log->Printf("unable to allocate %" PRIu32 + " bytes in process for shared cache string_pool read, will use slow method", + string_pool_byte_size); + // NB: representing invalid address as 0 for simplicity of checking in jitted expr + string_pool_addr = 0; + } + std::lock_guard<std::mutex> guard(m_get_class_info_args_mutex); // Fill in our function argument values arguments.GetValueAtIndex(0)->GetScalar() = hash_table.GetTableLoadAddress(); arguments.GetValueAtIndex(1)->GetScalar() = class_infos_addr; arguments.GetValueAtIndex(2)->GetScalar() = class_infos_byte_size; + arguments.GetValueAtIndex(3)->GetScalar() = string_pool_addr; + arguments.GetValueAtIndex(4)->GetScalar() = string_pool_byte_size; // Only dump the runtime classes from the expression evaluation if the log is // verbose: Log *type_log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES); bool dump_log = type_log && type_log->GetVerbose(); - arguments.GetValueAtIndex(3)->GetScalar() = dump_log ? 1 : 0; + arguments.GetValueAtIndex(5)->GetScalar() = dump_log ? 1 : 0; bool success = false; @@ -1427,7 +1514,31 @@ DataExtractor class_infos_data(buffer.GetBytes(), buffer.GetByteSize(), process->GetByteOrder(), addr_size); - ParseClassInfoArray(class_infos_data, num_class_infos); + + // Find the ClassInfo with the highest string pool offset, + // read the stringpool into a local buffer. + DataBufferSP strpool_buffer_sp; + if (string_pool_addr) { + uint32_t max_offset_seen = 0; + const int address_size = class_infos_data.GetAddressByteSize(); + offset_t offset = 0; + for (uint32_t i = 0; i < num_class_infos; ++i) { + offset += address_size + 4; /* isa + hash */ + uint32_t strpool_offset = class_infos_data.GetU32(&offset); + if (strpool_offset != UINT32_MAX & strpool_offset > max_offset_seen) + max_offset_seen = strpool_offset; + } + if (max_offset_seen != 0) { + strpool_buffer_sp.reset(new DataBufferHeap (max_offset_seen, 0)); + if (process->ReadMemory (string_pool_addr, strpool_buffer_sp->GetBytes(), + strpool_buffer_sp->GetByteSize(), err) != + strpool_buffer_sp->GetByteSize()) { + strpool_buffer_sp.reset(); + } + } + } + + ParseClassInfoArray(class_infos_data, num_class_infos, strpool_buffer_sp); } } success = true; @@ -1446,18 +1557,21 @@ // Deallocate the memory we allocated for the ClassInfo array process->DeallocateMemory(class_infos_addr); + process->DeallocateMemory(string_pool_addr); return DescriptorMapUpdateResult(success, num_class_infos); } uint32_t AppleObjCRuntimeV2::ParseClassInfoArray(const DataExtractor &data, - uint32_t num_class_infos) { + uint32_t num_class_infos, + DataBufferSP strpool_buffer_sp) { // Parses an array of "num_class_infos" packed ClassInfo structures: // // struct ClassInfo // { // Class isa; // uint32_t hash; + // uint32_t stroffset; // } __attribute__((__packed__)); Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES)); @@ -1483,18 +1597,54 @@ log->Printf("AppleObjCRuntimeV2 found cached isa=0x%" PRIx64 ", ignoring this class info", isa); - offset += 4; + offset += 4; // hash + offset += 4; // stroffset } else { // Read the 32 bit hash for the class name const uint32_t name_hash = data.GetU32(&offset); - ClassDescriptorSP descriptor_sp(new ClassDescriptorV2(*this, isa, NULL)); - AddClass(isa, descriptor_sp, name_hash); + const uint32_t strpool_offset = data.GetU32(&offset); + + bool name_read_from_strpool = false; + const char *name = nullptr; + if (strpool_buffer_sp.get() + && strpool_offset != UINT32_MAX + && strpool_buffer_sp->GetByteSize() > strpool_offset) { + name = (const char*) strpool_buffer_sp->GetBytes() + strpool_offset; + name_read_from_strpool = true; + } + ClassDescriptorSP descriptor_sp(new ClassDescriptorV2(*this, isa, name)); + + // The code in g_get_shared_cache_class_info_body sets the value of the hash + // to 0 to signal a mangled symbol. We use class_getName() in that code to + // find the class name, but this returns a demangled name for Swift symbols. + // For those symbols, recompute the hash here by extracing their name from the + // runtime (this is slow and cannot be done generally for the 45000+ symbols + // of the shared cache. + + if (name_hash) { + AddClass(isa, descriptor_sp, name_hash); + } else { + if (name == nullptr) { + name = descriptor_sp->GetClassName().AsCString(nullptr); + } + AddClass(isa, descriptor_sp, name); + } num_parsed++; - if (should_log) + if (should_log) { + if (name == nullptr) + name = "<unknown>"; + char name_from_strpool_buf[80]; + if (name_read_from_strpool) { + snprintf (name_from_strpool_buf, sizeof (name_from_strpool_buf), + "read from strpool offset %u", strpool_offset); + } else { + strcpy (name_from_strpool_buf, "read directly from memory, not via strpool"); + } log->Printf("AppleObjCRuntimeV2 added isa=0x%" PRIx64 - ", hash=0x%8.8x, name=%s", + ", hash=0x%8.8x, name=%s %s", isa, name_hash, - descriptor_sp->GetClassName().AsCString("<unknown>")); + name, name_from_strpool_buf); + } } } if (should_log) @@ -1579,18 +1729,19 @@ return DescriptorMapUpdateResult::Fail(); // Next make the function caller for our implementation utility function. - Value value; - value.SetValueType(Value::eValueTypeScalar); - // value.SetContext (Value::eContextTypeClangType, clang_void_pointer_type); - value.SetCompilerType(clang_void_pointer_type); - arguments.PushValue(value); - arguments.PushValue(value); + Value ptr_value; + Value uint32_value; + ptr_value.SetValueType(Value::eValueTypeScalar); + ptr_value.SetCompilerType(clang_void_pointer_type); + uint32_value.SetValueType(Value::eValueTypeScalar); + uint32_value.SetCompilerType(clang_uint32_t_type); - value.SetValueType(Value::eValueTypeScalar); - // value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); - value.SetCompilerType(clang_uint32_t_type); - arguments.PushValue(value); - arguments.PushValue(value); + arguments.PushValue(ptr_value); // void *gdb_objc_realized_classes_ptr + arguments.PushValue(ptr_value); // void *class_infos_ptr + arguments.PushValue(uint32_value); // uint32_t class_infos_byte_size + arguments.PushValue(ptr_value); // void *string_pool_ptr + arguments.PushValue(uint32_value); // uint32_t string_pool_byte_size + arguments.PushValue(uint32_value); // uint32_t should_log get_shared_cache_class_info_function = m_get_shared_cache_class_info_code->MakeFunctionCaller( @@ -1609,11 +1760,21 @@ diagnostics.Clear(); - const uint32_t class_info_byte_size = addr_size + 4; + const uint32_t class_info_byte_size = addr_size + + 4 /* hash */ + + 4 /* stroffset */; const uint32_t class_infos_byte_size = num_classes * class_info_byte_size; lldb::addr_t class_infos_addr = process->AllocateMemory( class_infos_byte_size, ePermissionsReadable | ePermissionsWritable, err); + // Assume classes names will be less than 32 chars on average. If + // we run out of space, entries will have an offset of UINT32_MAX + // and lldb will read the class names out of memory individually. + // Actual average is around 23 characters per class name in 2019. + const uint32_t string_pool_byte_size = num_classes * 32; + addr_t string_pool_addr = process->AllocateMemory( + string_pool_byte_size, ePermissionsReadable | ePermissionsWritable, err); + if (class_infos_addr == LLDB_INVALID_ADDRESS) { if (log) log->Printf("unable to allocate %" PRIu32 @@ -1622,18 +1783,29 @@ return DescriptorMapUpdateResult::Fail(); } + if (string_pool_addr == LLDB_INVALID_ADDRESS) { + if (log) + log->Printf("unable to allocate %" PRIu32 + " bytes in process for shared cache string_pool read, will use slow method", + string_pool_byte_size); + // NB: representing invalid address as 0 for simplicity of checking in jitted expr + string_pool_addr = 0; + } + std::lock_guard<std::mutex> guard(m_get_shared_cache_class_info_args_mutex); // Fill in our function argument values arguments.GetValueAtIndex(0)->GetScalar() = objc_opt_ptr; arguments.GetValueAtIndex(1)->GetScalar() = class_infos_addr; arguments.GetValueAtIndex(2)->GetScalar() = class_infos_byte_size; + arguments.GetValueAtIndex(3)->GetScalar() = string_pool_addr; + arguments.GetValueAtIndex(4)->GetScalar() = string_pool_byte_size; // Only dump the runtime classes from the expression evaluation if the log is // verbose: Log *type_log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES); bool dump_log = type_log && type_log->GetVerbose(); - arguments.GetValueAtIndex(3)->GetScalar() = dump_log ? 1 : 0; + arguments.GetValueAtIndex(5)->GetScalar() = dump_log ? 1 : 0; bool success = false; @@ -1691,7 +1863,30 @@ buffer.GetByteSize(), process->GetByteOrder(), addr_size); - ParseClassInfoArray(class_infos_data, num_class_infos); + // Find the ClassInfo with the highest string pool offset, + // read the stringpool into a local buffer. + DataBufferSP strpool_buffer_sp; + if (string_pool_addr) { + uint32_t max_offset_seen = 0; + const int address_size = class_infos_data.GetAddressByteSize(); + offset_t offset = 0; + for (uint32_t i = 0; i < num_class_infos; ++i) { + offset += address_size + 4; /* isa + hash */ + uint32_t strpool_offset = class_infos_data.GetU32(&offset); + if (strpool_offset != UINT32_MAX & strpool_offset > max_offset_seen) + max_offset_seen = strpool_offset; + } + if (max_offset_seen != 0) { + strpool_buffer_sp.reset(new DataBufferHeap (max_offset_seen, 0)); + if (process->ReadMemory (string_pool_addr, strpool_buffer_sp->GetBytes(), + strpool_buffer_sp->GetByteSize(), err) != + strpool_buffer_sp->GetByteSize()) { + strpool_buffer_sp.reset(); + } + } + } + + ParseClassInfoArray(class_infos_data, num_class_infos, strpool_buffer_sp); } } else { success = true; @@ -1711,6 +1906,7 @@ // Deallocate the memory we allocated for the ClassInfo array process->DeallocateMemory(class_infos_addr); + process->DeallocateMemory(string_pool_addr); return DescriptorMapUpdateResult(success, num_class_infos); } @@ -2428,12 +2624,12 @@ ObjCISA isa, ObjCISA &ret_isa) { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES)); + if ((isa & ~m_objc_debug_isa_class_mask) == 0) + return false; + if (log) log->Printf("AOCRT::NPI Evalulate(isa = 0x%" PRIx64 ")", (uint64_t)isa); - if ((isa & ~m_objc_debug_isa_class_mask) == 0) - return false; - // If all of the indexed ISA variables are set, then its possible that this // ISA is indexed, and we should first try to get its value using the index. // Note, we check these variables first as the ObjC runtime will set at least
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits