|  | //===-- AppleObjCRuntimeV2.cpp ----------------------------------*- C++ -*-===// | 
|  | // | 
|  | //                     The LLVM Compiler Infrastructure | 
|  | // | 
|  | // This file is distributed under the University of Illinois Open Source | 
|  | // License. See LICENSE.TXT for details. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | // C Includes | 
|  | #include <stdint.h> | 
|  |  | 
|  | // C++ Includes | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | // Other libraries and framework includes | 
|  | #include "clang/AST/ASTContext.h" | 
|  | #include "clang/AST/DeclObjC.h" | 
|  |  | 
|  | // Project includes | 
|  | #include "lldb/Core/ClangForward.h" | 
|  | #include "lldb/Symbol/CompilerType.h" | 
|  | #include "lldb/lldb-enumerations.h" | 
|  |  | 
|  | #include "lldb/Core/ClangForward.h" | 
|  | #include "lldb/Core/ConstString.h" | 
|  | #include "lldb/Core/Debugger.h" | 
|  | #include "lldb/Core/Error.h" | 
|  | #include "lldb/Core/Log.h" | 
|  | #include "lldb/Core/Module.h" | 
|  | #include "lldb/Core/PluginManager.h" | 
|  | #include "lldb/Core/Scalar.h" | 
|  | #include "lldb/Core/Section.h" | 
|  | #include "lldb/Core/Stream.h" | 
|  | #include "lldb/Core/StreamString.h" | 
|  | #include "lldb/Core/Timer.h" | 
|  | #include "lldb/Core/ValueObjectVariable.h" | 
|  | #include "lldb/Expression/DiagnosticManager.h" | 
|  | #include "lldb/Expression/FunctionCaller.h" | 
|  | #include "lldb/Expression/UtilityFunction.h" | 
|  | #include "lldb/Host/StringConvert.h" | 
|  | #include "lldb/Interpreter/CommandObject.h" | 
|  | #include "lldb/Interpreter/CommandObjectMultiword.h" | 
|  | #include "lldb/Interpreter/CommandReturnObject.h" | 
|  | #include "lldb/Interpreter/OptionValueBoolean.h" | 
|  | #include "lldb/Symbol/ClangASTContext.h" | 
|  | #include "lldb/Symbol/ObjectFile.h" | 
|  | #include "lldb/Symbol/Symbol.h" | 
|  | #include "lldb/Symbol/TypeList.h" | 
|  | #include "lldb/Symbol/VariableList.h" | 
|  | #include "lldb/Target/ExecutionContext.h" | 
|  | #include "lldb/Target/Platform.h" | 
|  | #include "lldb/Target/Process.h" | 
|  | #include "lldb/Target/RegisterContext.h" | 
|  | #include "lldb/Target/Target.h" | 
|  | #include "lldb/Target/Thread.h" | 
|  |  | 
|  | #include "AppleObjCClassDescriptorV2.h" | 
|  | #include "AppleObjCDeclVendor.h" | 
|  | #include "AppleObjCRuntimeV2.h" | 
|  | #include "AppleObjCTrampolineHandler.h" | 
|  | #include "AppleObjCTypeEncodingParser.h" | 
|  |  | 
|  | using namespace lldb; | 
|  | using namespace lldb_private; | 
|  |  | 
|  | // 2 second timeout when running utility functions | 
|  | #define UTILITY_FUNCTION_TIMEOUT_USEC 2 * 1000 * 1000 | 
|  |  | 
|  | static const char *g_get_dynamic_class_info_name = | 
|  | "__lldb_apple_objc_v2_get_dynamic_class_info"; | 
|  | // Testing using the new C++11 raw string literals. If this breaks GCC then we | 
|  | // will | 
|  | // need to revert to the code above... | 
|  | static const char *g_get_dynamic_class_info_body = R"( | 
|  |  | 
|  | extern "C" | 
|  | { | 
|  | size_t strlen(const char *); | 
|  | char *strncpy (char * s1, const char * s2, size_t n); | 
|  | int printf(const char * format, ...); | 
|  | } | 
|  | #define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) | 
|  |  | 
|  | typedef struct _NXMapTable { | 
|  | void *prototype; | 
|  | unsigned num_classes; | 
|  | unsigned num_buckets_minus_one; | 
|  | void *buckets; | 
|  | } NXMapTable; | 
|  |  | 
|  | #define NX_MAPNOTAKEY   ((void *)(-1)) | 
|  |  | 
|  | typedef struct BucketInfo | 
|  | { | 
|  | const char *name_ptr; | 
|  | Class isa; | 
|  | } BucketInfo; | 
|  |  | 
|  | struct ClassInfo | 
|  | { | 
|  | Class isa; | 
|  | uint32_t hash; | 
|  | } __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, | 
|  | 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); | 
|  | const NXMapTable *grc = (const NXMapTable *)gdb_objc_realized_classes_ptr; | 
|  | if (grc) | 
|  | { | 
|  | const unsigned num_classes = grc->num_classes; | 
|  | if (class_infos_ptr) | 
|  | { | 
|  | const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); | 
|  | ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; | 
|  | BucketInfo *buckets = (BucketInfo *)grc->buckets; | 
|  |  | 
|  | uint32_t idx = 0; | 
|  | for (unsigned i=0; i<=grc->num_buckets_minus_one; ++i) | 
|  | { | 
|  | if (buckets[i].name_ptr != NX_MAPNOTAKEY) | 
|  | { | 
|  | if (idx < max_class_infos) | 
|  | { | 
|  | const char *s = buckets[i].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 = buckets[i].isa; | 
|  | } | 
|  | ++idx; | 
|  | } | 
|  | } | 
|  | if (idx < max_class_infos) | 
|  | { | 
|  | class_infos[idx].isa = NULL; | 
|  | class_infos[idx].hash = 0; | 
|  | } | 
|  | } | 
|  | return num_classes; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | )"; | 
|  |  | 
|  | static const char *g_get_shared_cache_class_info_name = | 
|  | "__lldb_apple_objc_v2_get_shared_cache_class_info"; | 
|  | // Testing using the new C++11 raw string literals. If this breaks GCC then we | 
|  | // will | 
|  | // need to revert to the code above... | 
|  | static const char *g_get_shared_cache_class_info_body = R"( | 
|  |  | 
|  | extern "C" | 
|  | { | 
|  | const char *class_getName(void *objc_class); | 
|  | size_t strlen(const char *); | 
|  | char *strncpy (char * s1, const char * s2, size_t n); | 
|  | int printf(const char * format, ...); | 
|  | } | 
|  |  | 
|  | #define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) | 
|  |  | 
|  |  | 
|  | struct objc_classheader_t { | 
|  | int32_t clsOffset; | 
|  | int32_t hiOffset; | 
|  | }; | 
|  |  | 
|  | struct objc_clsopt_t { | 
|  | uint32_t capacity; | 
|  | uint32_t occupied; | 
|  | uint32_t shift; | 
|  | uint32_t mask; | 
|  | uint32_t zero; | 
|  | uint32_t unused; | 
|  | uint64_t salt; | 
|  | uint32_t scramble[256]; | 
|  | uint8_t tab[0]; // tab[mask+1] | 
|  | //  uint8_t checkbytes[capacity]; | 
|  | //  int32_t offset[capacity]; | 
|  | //  objc_classheader_t clsOffsets[capacity]; | 
|  | //  uint32_t duplicateCount; | 
|  | //  objc_classheader_t duplicateOffsets[duplicateCount]; | 
|  | }; | 
|  |  | 
|  | struct objc_opt_t { | 
|  | uint32_t version; | 
|  | int32_t selopt_offset; | 
|  | int32_t headeropt_offset; | 
|  | int32_t clsopt_offset; | 
|  | }; | 
|  |  | 
|  | struct objc_opt_v14_t { | 
|  | uint32_t version; | 
|  | uint32_t flags; | 
|  | int32_t selopt_offset; | 
|  | int32_t headeropt_offset; | 
|  | int32_t clsopt_offset; | 
|  | }; | 
|  |  | 
|  | struct ClassInfo | 
|  | { | 
|  | Class isa; | 
|  | uint32_t hash; | 
|  | }  __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, | 
|  | 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 ("class_infos_byte_size = %u (%llu class infos)\n", class_infos_byte_size, (uint64_t)(class_infos_byte_size/sizeof(ClassInfo))); | 
|  | if (objc_opt_ro_ptr) | 
|  | { | 
|  | const objc_opt_t *objc_opt = (objc_opt_t *)objc_opt_ro_ptr; | 
|  | const objc_opt_v14_t* objc_opt_v14 = (objc_opt_v14_t*)objc_opt_ro_ptr; | 
|  | const bool is_v14_format = objc_opt->version >= 14; | 
|  | if (is_v14_format) | 
|  | { | 
|  | DEBUG_PRINTF ("objc_opt->version = %u\n", objc_opt_v14->version); | 
|  | DEBUG_PRINTF ("objc_opt->flags = %u\n", objc_opt_v14->flags); | 
|  | DEBUG_PRINTF ("objc_opt->selopt_offset = %d\n", objc_opt_v14->selopt_offset); | 
|  | DEBUG_PRINTF ("objc_opt->headeropt_offset = %d\n", objc_opt_v14->headeropt_offset); | 
|  | DEBUG_PRINTF ("objc_opt->clsopt_offset = %d\n", objc_opt_v14->clsopt_offset); | 
|  | } | 
|  | else | 
|  | { | 
|  | DEBUG_PRINTF ("objc_opt->version = %u\n", objc_opt->version); | 
|  | DEBUG_PRINTF ("objc_opt->selopt_offset = %d\n", objc_opt->selopt_offset); | 
|  | DEBUG_PRINTF ("objc_opt->headeropt_offset = %d\n", objc_opt->headeropt_offset); | 
|  | DEBUG_PRINTF ("objc_opt->clsopt_offset = %d\n", objc_opt->clsopt_offset); | 
|  | } | 
|  | if (objc_opt->version == 12 || objc_opt->version == 13 || objc_opt->version == 14 || objc_opt->version == 15) | 
|  | { | 
|  | const objc_clsopt_t* clsopt = NULL; | 
|  | if (is_v14_format) | 
|  | clsopt = (const objc_clsopt_t*)((uint8_t *)objc_opt_v14 + objc_opt_v14->clsopt_offset); | 
|  | else | 
|  | clsopt = (const objc_clsopt_t*)((uint8_t *)objc_opt + objc_opt->clsopt_offset); | 
|  | 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; | 
|  | int32_t invalidEntryOffset = 0; | 
|  | // this is safe to do because the version field order is invariant | 
|  | if (objc_opt->version == 12) | 
|  | invalidEntryOffset = 16; | 
|  | const uint8_t *checkbytes = &clsopt->tab[clsopt->mask+1]; | 
|  | const int32_t *offsets = (const int32_t *)(checkbytes + clsopt->capacity); | 
|  | const objc_classheader_t *classOffsets = (const objc_classheader_t *)(offsets + clsopt->capacity); | 
|  | DEBUG_PRINTF ("clsopt->capacity = %u\n", clsopt->capacity); | 
|  | DEBUG_PRINTF ("clsopt->mask = 0x%8.8x\n", clsopt->mask); | 
|  | DEBUG_PRINTF ("classOffsets = %p\n", classOffsets); | 
|  | DEBUG_PRINTF("invalidEntryOffset = %d\n", invalidEntryOffset); | 
|  | for (uint32_t i=0; i<clsopt->capacity; ++i) | 
|  | { | 
|  | const int32_t clsOffset = classOffsets[i].clsOffset; | 
|  | DEBUG_PRINTF("clsOffset[%u] = %u\n", i, clsOffset); | 
|  | if (clsOffset & 1) | 
|  | { | 
|  | DEBUG_PRINTF("clsOffset & 1\n"); | 
|  | continue; // duplicate | 
|  | } | 
|  | else if (clsOffset == invalidEntryOffset) | 
|  | { | 
|  | DEBUG_PRINTF("clsOffset == invalidEntryOffset\n"); | 
|  | continue; // invalid offset | 
|  | } | 
|  |  | 
|  | if (class_infos && idx < max_class_infos) | 
|  | { | 
|  | class_infos[idx].isa = (Class)((uint8_t *)clsopt + clsOffset); | 
|  | const char *name = class_getName (class_infos[idx].isa); | 
|  | DEBUG_PRINTF ("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name); | 
|  | // Hash the class name so we don't have to read it | 
|  | 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; | 
|  | } | 
|  | else | 
|  | { | 
|  | DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n"); | 
|  | } | 
|  | ++idx; | 
|  | } | 
|  |  | 
|  | const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity]; | 
|  | const uint32_t duplicate_count = *duplicate_count_ptr; | 
|  | const objc_classheader_t *duplicateClassOffsets = (const objc_classheader_t *)(&duplicate_count_ptr[1]); | 
|  | DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count); | 
|  | DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets); | 
|  | for (uint32_t i=0; i<duplicate_count; ++i) | 
|  | { | 
|  | const int32_t clsOffset = duplicateClassOffsets[i].clsOffset; | 
|  | if (clsOffset & 1) | 
|  | continue; // duplicate | 
|  | else if (clsOffset == invalidEntryOffset) | 
|  | continue; // invalid offset | 
|  |  | 
|  | if (class_infos && idx < max_class_infos) | 
|  | { | 
|  | class_infos[idx].isa = (Class)((uint8_t *)clsopt + clsOffset); | 
|  | const char *name = class_getName (class_infos[idx].isa); | 
|  | DEBUG_PRINTF ("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name); | 
|  | // Hash the class name so we don't have to read it | 
|  | 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; | 
|  | } | 
|  | ++idx; | 
|  | } | 
|  | } | 
|  | DEBUG_PRINTF ("%u class_infos\n", idx); | 
|  | DEBUG_PRINTF ("done\n"); | 
|  | } | 
|  | return idx; | 
|  | } | 
|  |  | 
|  |  | 
|  | )"; | 
|  |  | 
|  | static uint64_t | 
|  | ExtractRuntimeGlobalSymbol(Process *process, ConstString name, | 
|  | const ModuleSP &module_sp, Error &error, | 
|  | bool read_value = true, uint8_t byte_size = 0, | 
|  | uint64_t default_value = LLDB_INVALID_ADDRESS, | 
|  | SymbolType sym_type = lldb::eSymbolTypeData) { | 
|  | if (!process) { | 
|  | error.SetErrorString("no process"); | 
|  | return default_value; | 
|  | } | 
|  | if (!module_sp) { | 
|  | error.SetErrorString("no module"); | 
|  | return default_value; | 
|  | } | 
|  | if (!byte_size) | 
|  | byte_size = process->GetAddressByteSize(); | 
|  | const Symbol *symbol = | 
|  | module_sp->FindFirstSymbolWithNameAndType(name, lldb::eSymbolTypeData); | 
|  | if (symbol && symbol->ValueIsAddress()) { | 
|  | lldb::addr_t symbol_load_addr = | 
|  | symbol->GetAddressRef().GetLoadAddress(&process->GetTarget()); | 
|  | if (symbol_load_addr != LLDB_INVALID_ADDRESS) { | 
|  | if (read_value) | 
|  | return process->ReadUnsignedIntegerFromMemory( | 
|  | symbol_load_addr, byte_size, default_value, error); | 
|  | else | 
|  | return symbol_load_addr; | 
|  | } else { | 
|  | error.SetErrorString("symbol address invalid"); | 
|  | return default_value; | 
|  | } | 
|  | } else { | 
|  | error.SetErrorString("no symbol"); | 
|  | return default_value; | 
|  | } | 
|  | } | 
|  |  | 
|  | AppleObjCRuntimeV2::AppleObjCRuntimeV2(Process *process, | 
|  | const ModuleSP &objc_module_sp) | 
|  | : AppleObjCRuntime(process), 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_get_shared_cache_class_info_args(LLDB_INVALID_ADDRESS), | 
|  | m_get_shared_cache_class_info_args_mutex(), m_decl_vendor_ap(), | 
|  | 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_ap( | 
|  | NonPointerISACache::CreateInstance(*this, objc_module_sp)), | 
|  | m_tagged_pointer_vendor_ap( | 
|  | TaggedPointerVendorV2::CreateInstance(*this, objc_module_sp)), | 
|  | m_encoding_to_type_sp(), m_noclasses_warning_emitted(false), | 
|  | m_CFBoolean_values() { | 
|  | static const ConstString g_gdb_object_getClass("gdb_object_getClass"); | 
|  | m_has_object_getClass = (objc_module_sp->FindFirstSymbolWithNameAndType( | 
|  | g_gdb_object_getClass, eSymbolTypeCode) != NULL); | 
|  | } | 
|  |  | 
|  | bool AppleObjCRuntimeV2::GetDynamicTypeAndAddress( | 
|  | ValueObject &in_value, DynamicValueType use_dynamic, | 
|  | TypeAndOrName &class_type_or_name, Address &address, | 
|  | Value::ValueType &value_type) { | 
|  | // We should never get here with a null process... | 
|  | assert(m_process != NULL); | 
|  |  | 
|  | // The Runtime is attached to a particular process, you shouldn't pass in a | 
|  | // value from another process. | 
|  | // Note, however, the process might be NULL (e.g. if the value was made with | 
|  | // SBTarget::EvaluateExpression...) | 
|  | // in which case it is sufficient if the target's match: | 
|  |  | 
|  | Process *process = in_value.GetProcessSP().get(); | 
|  | if (process) | 
|  | assert(process == m_process); | 
|  | else | 
|  | assert(in_value.GetTargetSP().get() == m_process->CalculateTarget().get()); | 
|  |  | 
|  | class_type_or_name.Clear(); | 
|  | value_type = Value::ValueType::eValueTypeScalar; | 
|  |  | 
|  | // Make sure we can have a dynamic value before starting... | 
|  | if (CouldHaveDynamicValue(in_value)) { | 
|  | // First job, pull out the address at 0 offset from the object  That will be | 
|  | // the ISA pointer. | 
|  | ClassDescriptorSP objc_class_sp(GetNonKVOClassDescriptor(in_value)); | 
|  | if (objc_class_sp) { | 
|  | const addr_t object_ptr = in_value.GetPointerValue(); | 
|  | address.SetRawAddress(object_ptr); | 
|  |  | 
|  | ConstString class_name(objc_class_sp->GetClassName()); | 
|  | class_type_or_name.SetName(class_name); | 
|  | TypeSP type_sp(objc_class_sp->GetType()); | 
|  | if (type_sp) | 
|  | class_type_or_name.SetTypeSP(type_sp); | 
|  | else { | 
|  | type_sp = LookupInCompleteClassCache(class_name); | 
|  | if (type_sp) { | 
|  | objc_class_sp->SetType(type_sp); | 
|  | class_type_or_name.SetTypeSP(type_sp); | 
|  | } else { | 
|  | // try to go for a CompilerType at least | 
|  | DeclVendor *vendor = GetDeclVendor(); | 
|  | if (vendor) { | 
|  | std::vector<clang::NamedDecl *> decls; | 
|  | if (vendor->FindDecls(class_name, false, 1, decls) && decls.size()) | 
|  | class_type_or_name.SetCompilerType( | 
|  | ClangASTContext::GetTypeForDecl(decls[0])); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return class_type_or_name.IsEmpty() == false; | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------ | 
|  | // Static Functions | 
|  | //------------------------------------------------------------------ | 
|  | LanguageRuntime *AppleObjCRuntimeV2::CreateInstance(Process *process, | 
|  | LanguageType language) { | 
|  | // FIXME: This should be a MacOS or iOS process, and we need to look for the | 
|  | // OBJC section to make | 
|  | // sure we aren't using the V1 runtime. | 
|  | if (language == eLanguageTypeObjC) { | 
|  | ModuleSP objc_module_sp; | 
|  |  | 
|  | if (AppleObjCRuntime::GetObjCVersion(process, objc_module_sp) == | 
|  | ObjCRuntimeVersions::eAppleObjC_V2) | 
|  | return new AppleObjCRuntimeV2(process, objc_module_sp); | 
|  | else | 
|  | return NULL; | 
|  | } else | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | class CommandObjectObjC_ClassTable_Dump : public CommandObjectParsed { | 
|  | public: | 
|  | class CommandOptions : public Options { | 
|  | public: | 
|  | CommandOptions() : Options(), m_verbose(false, false) {} | 
|  |  | 
|  | ~CommandOptions() override = default; | 
|  |  | 
|  | Error SetOptionValue(uint32_t option_idx, const char *option_arg, | 
|  | ExecutionContext *execution_context) override { | 
|  | Error error; | 
|  | const int short_option = m_getopt_table[option_idx].val; | 
|  | switch (short_option) { | 
|  | case 'v': | 
|  | m_verbose.SetCurrentValue(true); | 
|  | m_verbose.SetOptionWasSet(); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | error.SetErrorStringWithFormat("unrecognized short option '%c'", | 
|  | short_option); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | void OptionParsingStarting(ExecutionContext *execution_context) override { | 
|  | m_verbose.Clear(); | 
|  | } | 
|  |  | 
|  | const OptionDefinition *GetDefinitions() override { return g_option_table; } | 
|  |  | 
|  | OptionValueBoolean m_verbose; | 
|  | static OptionDefinition g_option_table[]; | 
|  | }; | 
|  |  | 
|  | CommandObjectObjC_ClassTable_Dump(CommandInterpreter &interpreter) | 
|  | : CommandObjectParsed( | 
|  | interpreter, "dump", "Dump information on Objective-C classes " | 
|  | "known to the current process.", | 
|  | "language objc class-table dump", | 
|  | eCommandRequiresProcess | eCommandProcessMustBeLaunched | | 
|  | eCommandProcessMustBePaused), | 
|  | m_options() { | 
|  | CommandArgumentEntry arg; | 
|  | CommandArgumentData index_arg; | 
|  |  | 
|  | // Define the first (and only) variant of this arg. | 
|  | index_arg.arg_type = eArgTypeRegularExpression; | 
|  | index_arg.arg_repetition = eArgRepeatOptional; | 
|  |  | 
|  | // There is only one variant this argument could be; put it into the | 
|  | // argument entry. | 
|  | arg.push_back(index_arg); | 
|  |  | 
|  | // Push the data for the first argument into the m_arguments vector. | 
|  | m_arguments.push_back(arg); | 
|  | } | 
|  |  | 
|  | ~CommandObjectObjC_ClassTable_Dump() override = default; | 
|  |  | 
|  | Options *GetOptions() override { return &m_options; } | 
|  |  | 
|  | protected: | 
|  | bool DoExecute(Args &command, CommandReturnObject &result) override { | 
|  | std::unique_ptr<RegularExpression> regex_up; | 
|  | switch (command.GetArgumentCount()) { | 
|  | case 0: | 
|  | break; | 
|  | case 1: { | 
|  | regex_up.reset(new RegularExpression()); | 
|  | if (!regex_up->Compile(command.GetArgumentAtIndex(0))) { | 
|  | result.AppendError( | 
|  | "invalid argument - please provide a valid regular expression"); | 
|  | result.SetStatus(lldb::eReturnStatusFailed); | 
|  | return false; | 
|  | } | 
|  | break; | 
|  | } | 
|  | default: { | 
|  | result.AppendError("please provide 0 or 1 arguments"); | 
|  | result.SetStatus(lldb::eReturnStatusFailed); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | Process *process = m_exe_ctx.GetProcessPtr(); | 
|  | ObjCLanguageRuntime *objc_runtime = process->GetObjCLanguageRuntime(); | 
|  | if (objc_runtime) { | 
|  | auto iterators_pair = objc_runtime->GetDescriptorIteratorPair(); | 
|  | auto iterator = iterators_pair.first; | 
|  | auto &std_out = result.GetOutputStream(); | 
|  | for (; iterator != iterators_pair.second; iterator++) { | 
|  | if (iterator->second) { | 
|  | const char *class_name = | 
|  | iterator->second->GetClassName().AsCString("<unknown>"); | 
|  | if (regex_up && class_name && !regex_up->Execute(class_name)) | 
|  | continue; | 
|  | std_out.Printf("isa = 0x%" PRIx64, iterator->first); | 
|  | std_out.Printf(" name = %s", class_name); | 
|  | std_out.Printf(" instance size = %" PRIu64, | 
|  | iterator->second->GetInstanceSize()); | 
|  | std_out.Printf(" num ivars = %" PRIuPTR, | 
|  | (uintptr_t)iterator->second->GetNumIVars()); | 
|  | if (auto superclass = iterator->second->GetSuperclass()) { | 
|  | std_out.Printf(" superclass = %s", | 
|  | superclass->GetClassName().AsCString("<unknown>")); | 
|  | } | 
|  | std_out.Printf("\n"); | 
|  | if (m_options.m_verbose) { | 
|  | for (size_t i = 0; i < iterator->second->GetNumIVars(); i++) { | 
|  | auto ivar = iterator->second->GetIVarAtIndex(i); | 
|  | std_out.Printf( | 
|  | "  ivar name = %s type = %s size = %" PRIu64 | 
|  | " offset = %" PRId32 "\n", | 
|  | ivar.m_name.AsCString("<unknown>"), | 
|  | ivar.m_type.GetDisplayTypeName().AsCString("<unknown>"), | 
|  | ivar.m_size, ivar.m_offset); | 
|  | } | 
|  | iterator->second->Describe( | 
|  | nullptr, | 
|  | [objc_runtime, &std_out](const char *name, | 
|  | const char *type) -> bool { | 
|  | std_out.Printf("  instance method name = %s type = %s\n", | 
|  | name, type); | 
|  | return false; | 
|  | }, | 
|  | [objc_runtime, &std_out](const char *name, | 
|  | const char *type) -> bool { | 
|  | std_out.Printf("  class method name = %s type = %s\n", name, | 
|  | type); | 
|  | return false; | 
|  | }, | 
|  | nullptr); | 
|  | } | 
|  | } else { | 
|  | if (regex_up && !regex_up->Execute("")) | 
|  | continue; | 
|  | std_out.Printf("isa = 0x%" PRIx64 " has no associated class.\n", | 
|  | iterator->first); | 
|  | } | 
|  | } | 
|  | result.SetStatus(lldb::eReturnStatusSuccessFinishResult); | 
|  | return true; | 
|  | } else { | 
|  | result.AppendError("current process has no Objective-C runtime loaded"); | 
|  | result.SetStatus(lldb::eReturnStatusFailed); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | CommandOptions m_options; | 
|  | }; | 
|  |  | 
|  | OptionDefinition | 
|  | CommandObjectObjC_ClassTable_Dump::CommandOptions::g_option_table[] = { | 
|  | {LLDB_OPT_SET_ALL, false, "verbose", 'v', OptionParser::eNoArgument, | 
|  | nullptr, nullptr, 0, eArgTypeNone, | 
|  | "Print ivar and method information in detail"}, | 
|  | {0, false, nullptr, 0, 0, nullptr, nullptr, 0, eArgTypeNone, nullptr}}; | 
|  |  | 
|  | class CommandObjectMultiwordObjC_TaggedPointer_Info | 
|  | : public CommandObjectParsed { | 
|  | public: | 
|  | CommandObjectMultiwordObjC_TaggedPointer_Info(CommandInterpreter &interpreter) | 
|  | : CommandObjectParsed( | 
|  | interpreter, "info", "Dump information on a tagged pointer.", | 
|  | "language objc tagged-pointer info", | 
|  | eCommandRequiresProcess | eCommandProcessMustBeLaunched | | 
|  | eCommandProcessMustBePaused) { | 
|  | CommandArgumentEntry arg; | 
|  | CommandArgumentData index_arg; | 
|  |  | 
|  | // Define the first (and only) variant of this arg. | 
|  | index_arg.arg_type = eArgTypeAddress; | 
|  | index_arg.arg_repetition = eArgRepeatPlus; | 
|  |  | 
|  | // There is only one variant this argument could be; put it into the | 
|  | // argument entry. | 
|  | arg.push_back(index_arg); | 
|  |  | 
|  | // Push the data for the first argument into the m_arguments vector. | 
|  | m_arguments.push_back(arg); | 
|  | } | 
|  |  | 
|  | ~CommandObjectMultiwordObjC_TaggedPointer_Info() override = default; | 
|  |  | 
|  | protected: | 
|  | bool DoExecute(Args &command, CommandReturnObject &result) override { | 
|  | if (command.GetArgumentCount() == 0) { | 
|  | result.AppendError("this command requires arguments"); | 
|  | result.SetStatus(lldb::eReturnStatusFailed); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | Process *process = m_exe_ctx.GetProcessPtr(); | 
|  | ExecutionContext exe_ctx(process); | 
|  | ObjCLanguageRuntime *objc_runtime = process->GetObjCLanguageRuntime(); | 
|  | if (objc_runtime) { | 
|  | ObjCLanguageRuntime::TaggedPointerVendor *tagged_ptr_vendor = | 
|  | objc_runtime->GetTaggedPointerVendor(); | 
|  | if (tagged_ptr_vendor) { | 
|  | for (size_t i = 0; i < command.GetArgumentCount(); i++) { | 
|  | const char *arg_str = command.GetArgumentAtIndex(i); | 
|  | if (!arg_str) | 
|  | continue; | 
|  | Error error; | 
|  | lldb::addr_t arg_addr = Args::StringToAddress( | 
|  | &exe_ctx, arg_str, LLDB_INVALID_ADDRESS, &error); | 
|  | if (arg_addr == 0 || arg_addr == LLDB_INVALID_ADDRESS || error.Fail()) | 
|  | continue; | 
|  | auto descriptor_sp = tagged_ptr_vendor->GetClassDescriptor(arg_addr); | 
|  | if (!descriptor_sp) | 
|  | continue; | 
|  | uint64_t info_bits = 0; | 
|  | uint64_t value_bits = 0; | 
|  | uint64_t payload = 0; | 
|  | if (descriptor_sp->GetTaggedPointerInfo(&info_bits, &value_bits, | 
|  | &payload)) { | 
|  | result.GetOutputStream().Printf( | 
|  | "0x%" PRIx64 " is tagged.\n\tpayload = 0x%" PRIx64 | 
|  | "\n\tvalue = 0x%" PRIx64 "\n\tinfo bits = 0x%" PRIx64 | 
|  | "\n\tclass = %s\n", | 
|  | (uint64_t)arg_addr, payload, value_bits, info_bits, | 
|  | descriptor_sp->GetClassName().AsCString("<unknown>")); | 
|  | } else { | 
|  | result.GetOutputStream().Printf("0x%" PRIx64 " is not tagged.\n", | 
|  | (uint64_t)arg_addr); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | result.AppendError("current process has no tagged pointer support"); | 
|  | result.SetStatus(lldb::eReturnStatusFailed); | 
|  | return false; | 
|  | } | 
|  | result.SetStatus(lldb::eReturnStatusSuccessFinishResult); | 
|  | return true; | 
|  | } else { | 
|  | result.AppendError("current process has no Objective-C runtime loaded"); | 
|  | result.SetStatus(lldb::eReturnStatusFailed); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | class CommandObjectMultiwordObjC_ClassTable : public CommandObjectMultiword { | 
|  | public: | 
|  | CommandObjectMultiwordObjC_ClassTable(CommandInterpreter &interpreter) | 
|  | : CommandObjectMultiword( | 
|  | interpreter, "class-table", | 
|  | "Commands for operating on the Objective-C class table.", | 
|  | "class-table <subcommand> [<subcommand-options>]") { | 
|  | LoadSubCommand( | 
|  | "dump", | 
|  | CommandObjectSP(new CommandObjectObjC_ClassTable_Dump(interpreter))); | 
|  | } | 
|  |  | 
|  | ~CommandObjectMultiwordObjC_ClassTable() override = default; | 
|  | }; | 
|  |  | 
|  | class CommandObjectMultiwordObjC_TaggedPointer : public CommandObjectMultiword { | 
|  | public: | 
|  | CommandObjectMultiwordObjC_TaggedPointer(CommandInterpreter &interpreter) | 
|  | : CommandObjectMultiword( | 
|  | interpreter, "tagged-pointer", | 
|  | "Commands for operating on Objective-C tagged pointers.", | 
|  | "class-table <subcommand> [<subcommand-options>]") { | 
|  | LoadSubCommand( | 
|  | "info", | 
|  | CommandObjectSP( | 
|  | new CommandObjectMultiwordObjC_TaggedPointer_Info(interpreter))); | 
|  | } | 
|  |  | 
|  | ~CommandObjectMultiwordObjC_TaggedPointer() override = default; | 
|  | }; | 
|  |  | 
|  | class CommandObjectMultiwordObjC : public CommandObjectMultiword { | 
|  | public: | 
|  | CommandObjectMultiwordObjC(CommandInterpreter &interpreter) | 
|  | : CommandObjectMultiword( | 
|  | interpreter, "objc", | 
|  | "Commands for operating on the Objective-C language runtime.", | 
|  | "objc <subcommand> [<subcommand-options>]") { | 
|  | LoadSubCommand("class-table", | 
|  | CommandObjectSP( | 
|  | new CommandObjectMultiwordObjC_ClassTable(interpreter))); | 
|  | LoadSubCommand("tagged-pointer", | 
|  | CommandObjectSP(new CommandObjectMultiwordObjC_TaggedPointer( | 
|  | interpreter))); | 
|  | } | 
|  |  | 
|  | ~CommandObjectMultiwordObjC() override = default; | 
|  | }; | 
|  |  | 
|  | void AppleObjCRuntimeV2::Initialize() { | 
|  | PluginManager::RegisterPlugin( | 
|  | GetPluginNameStatic(), "Apple Objective C Language Runtime - Version 2", | 
|  | CreateInstance, | 
|  | [](CommandInterpreter &interpreter) -> lldb::CommandObjectSP { | 
|  | return CommandObjectSP(new CommandObjectMultiwordObjC(interpreter)); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void AppleObjCRuntimeV2::Terminate() { | 
|  | PluginManager::UnregisterPlugin(CreateInstance); | 
|  | } | 
|  |  | 
|  | lldb_private::ConstString AppleObjCRuntimeV2::GetPluginNameStatic() { | 
|  | static ConstString g_name("apple-objc-v2"); | 
|  | return g_name; | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------ | 
|  | // PluginInterface protocol | 
|  | //------------------------------------------------------------------ | 
|  | lldb_private::ConstString AppleObjCRuntimeV2::GetPluginName() { | 
|  | return GetPluginNameStatic(); | 
|  | } | 
|  |  | 
|  | uint32_t AppleObjCRuntimeV2::GetPluginVersion() { return 1; } | 
|  |  | 
|  | BreakpointResolverSP | 
|  | AppleObjCRuntimeV2::CreateExceptionResolver(Breakpoint *bkpt, bool catch_bp, | 
|  | bool throw_bp) { | 
|  | BreakpointResolverSP resolver_sp; | 
|  |  | 
|  | if (throw_bp) | 
|  | resolver_sp.reset(new BreakpointResolverName( | 
|  | bkpt, "objc_exception_throw", eFunctionNameTypeBase, | 
|  | eLanguageTypeUnknown, Breakpoint::Exact, 0, eLazyBoolNo)); | 
|  | // FIXME: We don't do catch breakpoints for ObjC yet. | 
|  | // Should there be some way for the runtime to specify what it can do in this | 
|  | // regard? | 
|  | return resolver_sp; | 
|  | } | 
|  |  | 
|  | UtilityFunction *AppleObjCRuntimeV2::CreateObjectChecker(const char *name) { | 
|  | char check_function_code[2048]; | 
|  |  | 
|  | int len = 0; | 
|  | if (m_has_object_getClass) { | 
|  | len = ::snprintf(check_function_code, sizeof(check_function_code), | 
|  | "extern \"C\" void *gdb_object_getClass(void *);          " | 
|  | "                                \n" | 
|  | "extern \"C\"  int printf(const char *format, ...);       " | 
|  | "                                \n" | 
|  | "extern \"C\" void                                        " | 
|  | "                                \n" | 
|  | "%s(void *$__lldb_arg_obj, void *$__lldb_arg_selector)    " | 
|  | "                                \n" | 
|  | "{                                                        " | 
|  | "                                \n" | 
|  | "   if ($__lldb_arg_obj == (void *)0)                     " | 
|  | "                                \n" | 
|  | "       return; // nil is ok                              " | 
|  | "                                \n" | 
|  | "   if (!gdb_object_getClass($__lldb_arg_obj))            " | 
|  | "                                \n" | 
|  | "       *((volatile int *)0) = 'ocgc';                    " | 
|  | "                                \n" | 
|  | "   else if ($__lldb_arg_selector != (void *)0)           " | 
|  | "                                \n" | 
|  | "   {                                                     " | 
|  | "                                \n" | 
|  | "        signed char responds = (signed char) [(id) " | 
|  | "$__lldb_arg_obj                       \n" | 
|  | "                                                " | 
|  | "respondsToSelector:                      \n" | 
|  | "                                       (struct " | 
|  | "objc_selector *) $__lldb_arg_selector];   \n" | 
|  | "       if (responds == (signed char) 0)                  " | 
|  | "                                \n" | 
|  | "           *((volatile int *)0) = 'ocgc';                " | 
|  | "                                \n" | 
|  | "   }                                                     " | 
|  | "                                \n" | 
|  | "}                                                        " | 
|  | "                                \n", | 
|  | name); | 
|  | } else { | 
|  | len = ::snprintf(check_function_code, sizeof(check_function_code), | 
|  | "extern \"C\" void *gdb_class_getClass(void *);           " | 
|  | "                                \n" | 
|  | "extern \"C\"  int printf(const char *format, ...);       " | 
|  | "                                \n" | 
|  | "extern \"C\"  void                                       " | 
|  | "                                \n" | 
|  | "%s(void *$__lldb_arg_obj, void *$__lldb_arg_selector)    " | 
|  | "                                \n" | 
|  | "{                                                        " | 
|  | "                                \n" | 
|  | "   if ($__lldb_arg_obj == (void *)0)                     " | 
|  | "                                \n" | 
|  | "       return; // nil is ok                              " | 
|  | "                                \n" | 
|  | "    void **$isa_ptr = (void **)$__lldb_arg_obj;          " | 
|  | "                                \n" | 
|  | "    if (*$isa_ptr == (void *)0 || " | 
|  | "!gdb_class_getClass(*$isa_ptr))                        \n" | 
|  | "       *((volatile int *)0) = 'ocgc';                    " | 
|  | "                                \n" | 
|  | "   else if ($__lldb_arg_selector != (void *)0)           " | 
|  | "                                \n" | 
|  | "   {                                                     " | 
|  | "                                \n" | 
|  | "        signed char responds = (signed char) [(id) " | 
|  | "$__lldb_arg_obj                       \n" | 
|  | "                                                " | 
|  | "respondsToSelector:                      \n" | 
|  | "                                        (struct " | 
|  | "objc_selector *) $__lldb_arg_selector];  \n" | 
|  | "       if (responds == (signed char) 0)                  " | 
|  | "                                \n" | 
|  | "           *((volatile int *)0) = 'ocgc';                " | 
|  | "                                \n" | 
|  | "   }                                                     " | 
|  | "                                \n" | 
|  | "}                                                        " | 
|  | "                                \n", | 
|  | name); | 
|  | } | 
|  |  | 
|  | assert(len < (int)sizeof(check_function_code)); | 
|  |  | 
|  | Error error; | 
|  | return GetTargetRef().GetUtilityFunctionForLanguage( | 
|  | check_function_code, eLanguageTypeObjC, name, error); | 
|  | } | 
|  |  | 
|  | size_t AppleObjCRuntimeV2::GetByteOffsetForIvar(CompilerType &parent_ast_type, | 
|  | const char *ivar_name) { | 
|  | uint32_t ivar_offset = LLDB_INVALID_IVAR_OFFSET; | 
|  |  | 
|  | const char *class_name = parent_ast_type.GetConstTypeName().AsCString(); | 
|  | if (class_name && class_name[0] && ivar_name && ivar_name[0]) { | 
|  | //---------------------------------------------------------------------- | 
|  | // Make the objective C V2 mangled name for the ivar offset from the | 
|  | // class name and ivar name | 
|  | //---------------------------------------------------------------------- | 
|  | std::string buffer("OBJC_IVAR_$_"); | 
|  | buffer.append(class_name); | 
|  | buffer.push_back('.'); | 
|  | buffer.append(ivar_name); | 
|  | ConstString ivar_const_str(buffer.c_str()); | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  | // Try to get the ivar offset address from the symbol table first using | 
|  | // the name we created above | 
|  | //---------------------------------------------------------------------- | 
|  | SymbolContextList sc_list; | 
|  | Target &target = m_process->GetTarget(); | 
|  | target.GetImages().FindSymbolsWithNameAndType(ivar_const_str, | 
|  | eSymbolTypeObjCIVar, sc_list); | 
|  |  | 
|  | addr_t ivar_offset_address = LLDB_INVALID_ADDRESS; | 
|  |  | 
|  | Error error; | 
|  | SymbolContext ivar_offset_symbol; | 
|  | if (sc_list.GetSize() == 1 && | 
|  | sc_list.GetContextAtIndex(0, ivar_offset_symbol)) { | 
|  | if (ivar_offset_symbol.symbol) | 
|  | ivar_offset_address = | 
|  | ivar_offset_symbol.symbol->GetLoadAddress(&target); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  | // If we didn't get the ivar offset address from the symbol table, fall | 
|  | // back to getting it from the runtime | 
|  | //---------------------------------------------------------------------- | 
|  | if (ivar_offset_address == LLDB_INVALID_ADDRESS) | 
|  | ivar_offset_address = LookupRuntimeSymbol(ivar_const_str); | 
|  |  | 
|  | if (ivar_offset_address != LLDB_INVALID_ADDRESS) | 
|  | ivar_offset = m_process->ReadUnsignedIntegerFromMemory( | 
|  | ivar_offset_address, 4, LLDB_INVALID_IVAR_OFFSET, error); | 
|  | } | 
|  | return ivar_offset; | 
|  | } | 
|  |  | 
|  | // tagged pointers are special not-a-real-pointer values that contain both type | 
|  | // and value information | 
|  | // this routine attempts to check with as little computational effort as | 
|  | // possible whether something | 
|  | // could possibly be a tagged pointer - false positives are possible but false | 
|  | // negatives shouldn't | 
|  | bool AppleObjCRuntimeV2::IsTaggedPointer(addr_t ptr) { | 
|  | if (!m_tagged_pointer_vendor_ap) | 
|  | return false; | 
|  | return m_tagged_pointer_vendor_ap->IsPossibleTaggedPointer(ptr); | 
|  | } | 
|  |  | 
|  | class RemoteNXMapTable { | 
|  | public: | 
|  | RemoteNXMapTable() | 
|  | : m_count(0), m_num_buckets_minus_one(0), | 
|  | m_buckets_ptr(LLDB_INVALID_ADDRESS), m_process(NULL), | 
|  | m_end_iterator(*this, -1), m_load_addr(LLDB_INVALID_ADDRESS), | 
|  | m_map_pair_size(0), m_invalid_key(0) {} | 
|  |  | 
|  | void Dump() { | 
|  | printf("RemoteNXMapTable.m_load_addr = 0x%" PRIx64 "\n", m_load_addr); | 
|  | printf("RemoteNXMapTable.m_count = %u\n", m_count); | 
|  | printf("RemoteNXMapTable.m_num_buckets_minus_one = %u\n", | 
|  | m_num_buckets_minus_one); | 
|  | printf("RemoteNXMapTable.m_buckets_ptr = 0x%" PRIX64 "\n", m_buckets_ptr); | 
|  | } | 
|  |  | 
|  | bool ParseHeader(Process *process, lldb::addr_t load_addr) { | 
|  | m_process = process; | 
|  | m_load_addr = load_addr; | 
|  | m_map_pair_size = m_process->GetAddressByteSize() * 2; | 
|  | m_invalid_key = | 
|  | m_process->GetAddressByteSize() == 8 ? UINT64_MAX : UINT32_MAX; | 
|  | Error err; | 
|  |  | 
|  | // This currently holds true for all platforms we support, but we might | 
|  | // need to change this to use get the actually byte size of "unsigned" | 
|  | // from the target AST... | 
|  | const uint32_t unsigned_byte_size = sizeof(uint32_t); | 
|  | // Skip the prototype as we don't need it (const struct +NXMapTablePrototype | 
|  | // *prototype) | 
|  |  | 
|  | bool success = true; | 
|  | if (load_addr == LLDB_INVALID_ADDRESS) | 
|  | success = false; | 
|  | else { | 
|  | lldb::addr_t cursor = load_addr + m_process->GetAddressByteSize(); | 
|  |  | 
|  | // unsigned count; | 
|  | m_count = m_process->ReadUnsignedIntegerFromMemory( | 
|  | cursor, unsigned_byte_size, 0, err); | 
|  | if (m_count) { | 
|  | cursor += unsigned_byte_size; | 
|  |  | 
|  | // unsigned nbBucketsMinusOne; | 
|  | m_num_buckets_minus_one = m_process->ReadUnsignedIntegerFromMemory( | 
|  | cursor, unsigned_byte_size, 0, err); | 
|  | cursor += unsigned_byte_size; | 
|  |  | 
|  | // void *buckets; | 
|  | m_buckets_ptr = m_process->ReadPointerFromMemory(cursor, err); | 
|  |  | 
|  | success = m_count > 0 && m_buckets_ptr != LLDB_INVALID_ADDRESS; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!success) { | 
|  | m_count = 0; | 
|  | m_num_buckets_minus_one = 0; | 
|  | m_buckets_ptr = LLDB_INVALID_ADDRESS; | 
|  | } | 
|  | return success; | 
|  | } | 
|  |  | 
|  | // const_iterator mimics NXMapState and its code comes from NXInitMapState and | 
|  | // NXNextMapState. | 
|  | typedef std::pair<ConstString, ObjCLanguageRuntime::ObjCISA> element; | 
|  |  | 
|  | friend class const_iterator; | 
|  | class const_iterator { | 
|  | public: | 
|  | const_iterator(RemoteNXMapTable &parent, int index) | 
|  | : m_parent(parent), m_index(index) { | 
|  | AdvanceToValidIndex(); | 
|  | } | 
|  |  | 
|  | const_iterator(const const_iterator &rhs) | 
|  | : m_parent(rhs.m_parent), m_index(rhs.m_index) { | 
|  | // AdvanceToValidIndex() has been called by rhs already. | 
|  | } | 
|  |  | 
|  | const_iterator &operator=(const const_iterator &rhs) { | 
|  | // AdvanceToValidIndex() has been called by rhs already. | 
|  | assert(&m_parent == &rhs.m_parent); | 
|  | m_index = rhs.m_index; | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | bool operator==(const const_iterator &rhs) const { | 
|  | if (&m_parent != &rhs.m_parent) | 
|  | return false; | 
|  | if (m_index != rhs.m_index) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool operator!=(const const_iterator &rhs) const { | 
|  | return !(operator==(rhs)); | 
|  | } | 
|  |  | 
|  | const_iterator &operator++() { | 
|  | AdvanceToValidIndex(); | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | const element operator*() const { | 
|  | if (m_index == -1) { | 
|  | // TODO find a way to make this an error, but not an assert | 
|  | return element(); | 
|  | } | 
|  |  | 
|  | lldb::addr_t pairs_ptr = m_parent.m_buckets_ptr; | 
|  | size_t map_pair_size = m_parent.m_map_pair_size; | 
|  | lldb::addr_t pair_ptr = pairs_ptr + (m_index * map_pair_size); | 
|  |  | 
|  | Error err; | 
|  |  | 
|  | lldb::addr_t key = | 
|  | m_parent.m_process->ReadPointerFromMemory(pair_ptr, err); | 
|  | if (!err.Success()) | 
|  | return element(); | 
|  | lldb::addr_t value = m_parent.m_process->ReadPointerFromMemory( | 
|  | pair_ptr + m_parent.m_process->GetAddressByteSize(), err); | 
|  | if (!err.Success()) | 
|  | return element(); | 
|  |  | 
|  | std::string key_string; | 
|  |  | 
|  | m_parent.m_process->ReadCStringFromMemory(key, key_string, err); | 
|  | if (!err.Success()) | 
|  | return element(); | 
|  |  | 
|  | return element(ConstString(key_string.c_str()), | 
|  | (ObjCLanguageRuntime::ObjCISA)value); | 
|  | } | 
|  |  | 
|  | private: | 
|  | void AdvanceToValidIndex() { | 
|  | if (m_index == -1) | 
|  | return; | 
|  |  | 
|  | const lldb::addr_t pairs_ptr = m_parent.m_buckets_ptr; | 
|  | const size_t map_pair_size = m_parent.m_map_pair_size; | 
|  | const lldb::addr_t invalid_key = m_parent.m_invalid_key; | 
|  | Error err; | 
|  |  | 
|  | while (m_index--) { | 
|  | lldb::addr_t pair_ptr = pairs_ptr + (m_index * map_pair_size); | 
|  | lldb::addr_t key = | 
|  | m_parent.m_process->ReadPointerFromMemory(pair_ptr, err); | 
|  |  | 
|  | if (!err.Success()) { | 
|  | m_index = -1; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (key != invalid_key) | 
|  | return; | 
|  | } | 
|  | } | 
|  | RemoteNXMapTable &m_parent; | 
|  | int m_index; | 
|  | }; | 
|  |  | 
|  | const_iterator begin() { | 
|  | return const_iterator(*this, m_num_buckets_minus_one + 1); | 
|  | } | 
|  |  | 
|  | const_iterator end() { return m_end_iterator; } | 
|  |  | 
|  | uint32_t GetCount() const { return m_count; } | 
|  |  | 
|  | uint32_t GetBucketCount() const { return m_num_buckets_minus_one; } | 
|  |  | 
|  | lldb::addr_t GetBucketDataPointer() const { return m_buckets_ptr; } | 
|  |  | 
|  | lldb::addr_t GetTableLoadAddress() const { return m_load_addr; } | 
|  |  | 
|  | private: | 
|  | // contents of _NXMapTable struct | 
|  | uint32_t m_count; | 
|  | uint32_t m_num_buckets_minus_one; | 
|  | lldb::addr_t m_buckets_ptr; | 
|  | lldb_private::Process *m_process; | 
|  | const_iterator m_end_iterator; | 
|  | lldb::addr_t m_load_addr; | 
|  | size_t m_map_pair_size; | 
|  | lldb::addr_t m_invalid_key; | 
|  | }; | 
|  |  | 
|  | AppleObjCRuntimeV2::HashTableSignature::HashTableSignature() | 
|  | : m_count(0), m_num_buckets(0), m_buckets_ptr(0) {} | 
|  |  | 
|  | void AppleObjCRuntimeV2::HashTableSignature::UpdateSignature( | 
|  | const RemoteNXMapTable &hash_table) { | 
|  | m_count = hash_table.GetCount(); | 
|  | m_num_buckets = hash_table.GetBucketCount(); | 
|  | m_buckets_ptr = hash_table.GetBucketDataPointer(); | 
|  | } | 
|  |  | 
|  | bool AppleObjCRuntimeV2::HashTableSignature::NeedsUpdate( | 
|  | Process *process, AppleObjCRuntimeV2 *runtime, | 
|  | RemoteNXMapTable &hash_table) { | 
|  | if (!hash_table.ParseHeader(process, runtime->GetISAHashTablePointer())) { | 
|  | return false; // Failed to parse the header, no need to update anything | 
|  | } | 
|  |  | 
|  | // Check with out current signature and return true if the count, | 
|  | // number of buckets or the hash table address changes. | 
|  | if (m_count == hash_table.GetCount() && | 
|  | m_num_buckets == hash_table.GetBucketCount() && | 
|  | m_buckets_ptr == hash_table.GetBucketDataPointer()) { | 
|  | // Hash table hasn't changed | 
|  | return false; | 
|  | } | 
|  | // Hash table data has changed, we need to update | 
|  | return true; | 
|  | } | 
|  |  | 
|  | ObjCLanguageRuntime::ClassDescriptorSP | 
|  | AppleObjCRuntimeV2::GetClassDescriptorFromISA(ObjCISA isa) { | 
|  | ObjCLanguageRuntime::ClassDescriptorSP class_descriptor_sp; | 
|  | if (m_non_pointer_isa_cache_ap.get()) | 
|  | class_descriptor_sp = m_non_pointer_isa_cache_ap->GetClassDescriptor(isa); | 
|  | if (!class_descriptor_sp) | 
|  | class_descriptor_sp = ObjCLanguageRuntime::GetClassDescriptorFromISA(isa); | 
|  | return class_descriptor_sp; | 
|  | } | 
|  |  | 
|  | ObjCLanguageRuntime::ClassDescriptorSP | 
|  | AppleObjCRuntimeV2::GetClassDescriptor(ValueObject &valobj) { | 
|  | ClassDescriptorSP objc_class_sp; | 
|  | if (valobj.IsBaseClass()) { | 
|  | ValueObject *parent = valobj.GetParent(); | 
|  | // if I am my own parent, bail out of here fast.. | 
|  | if (parent && parent != &valobj) { | 
|  | ClassDescriptorSP parent_descriptor_sp = GetClassDescriptor(*parent); | 
|  | if (parent_descriptor_sp) | 
|  | return parent_descriptor_sp->GetSuperclass(); | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  | // if we get an invalid VO (which might still happen when playing around | 
|  | // with pointers returned by the expression parser, don't consider this | 
|  | // a valid ObjC object) | 
|  | if (valobj.GetCompilerType().IsValid()) { | 
|  | addr_t isa_pointer = valobj.GetPointerValue(); | 
|  |  | 
|  | // tagged pointer | 
|  | if (IsTaggedPointer(isa_pointer)) { | 
|  | return m_tagged_pointer_vendor_ap->GetClassDescriptor(isa_pointer); | 
|  | } else { | 
|  | ExecutionContext exe_ctx(valobj.GetExecutionContextRef()); | 
|  |  | 
|  | Process *process = exe_ctx.GetProcessPtr(); | 
|  | if (process) { | 
|  | Error error; | 
|  | ObjCISA isa = process->ReadPointerFromMemory(isa_pointer, error); | 
|  | if (isa != LLDB_INVALID_ADDRESS) { | 
|  | objc_class_sp = GetClassDescriptorFromISA(isa); | 
|  | if (isa && !objc_class_sp) { | 
|  | Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); | 
|  | if (log) | 
|  | log->Printf("0x%" PRIx64 | 
|  | ": AppleObjCRuntimeV2::GetClassDescriptor() ISA was " | 
|  | "not in class descriptor cache 0x%" PRIx64, | 
|  | isa_pointer, isa); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return objc_class_sp; | 
|  | } | 
|  |  | 
|  | lldb::addr_t AppleObjCRuntimeV2::GetISAHashTablePointer() { | 
|  | if (m_isa_hash_table_ptr == LLDB_INVALID_ADDRESS) { | 
|  | Process *process = GetProcess(); | 
|  |  | 
|  | ModuleSP objc_module_sp(GetObjCModule()); | 
|  |  | 
|  | if (!objc_module_sp) | 
|  | return LLDB_INVALID_ADDRESS; | 
|  |  | 
|  | static ConstString g_gdb_objc_realized_classes("gdb_objc_realized_classes"); | 
|  |  | 
|  | const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( | 
|  | g_gdb_objc_realized_classes, lldb::eSymbolTypeAny); | 
|  | if (symbol) { | 
|  | lldb::addr_t gdb_objc_realized_classes_ptr = | 
|  | symbol->GetLoadAddress(&process->GetTarget()); | 
|  |  | 
|  | if (gdb_objc_realized_classes_ptr != LLDB_INVALID_ADDRESS) { | 
|  | Error error; | 
|  | m_isa_hash_table_ptr = process->ReadPointerFromMemory( | 
|  | gdb_objc_realized_classes_ptr, error); | 
|  | } | 
|  | } | 
|  | } | 
|  | return m_isa_hash_table_ptr; | 
|  | } | 
|  |  | 
|  | AppleObjCRuntimeV2::DescriptorMapUpdateResult | 
|  | AppleObjCRuntimeV2::UpdateISAToDescriptorMapDynamic( | 
|  | RemoteNXMapTable &hash_table) { | 
|  | Process *process = GetProcess(); | 
|  |  | 
|  | if (process == NULL) | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  |  | 
|  | uint32_t num_class_infos = 0; | 
|  |  | 
|  | Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); | 
|  |  | 
|  | ExecutionContext exe_ctx; | 
|  |  | 
|  | ThreadSP thread_sp = process->GetThreadList().GetExpressionExecutionThread(); | 
|  |  | 
|  | if (!thread_sp) | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  |  | 
|  | thread_sp->CalculateExecutionContext(exe_ctx); | 
|  | ClangASTContext *ast = process->GetTarget().GetScratchClangASTContext(); | 
|  |  | 
|  | if (!ast) | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  |  | 
|  | Address function_address; | 
|  |  | 
|  | DiagnosticManager diagnostics; | 
|  |  | 
|  | const uint32_t addr_size = process->GetAddressByteSize(); | 
|  |  | 
|  | Error err; | 
|  |  | 
|  | // Read the total number of classes from the hash table | 
|  | const uint32_t num_classes = hash_table.GetCount(); | 
|  | if (num_classes == 0) { | 
|  | if (log) | 
|  | log->Printf("No dynamic classes found in gdb_objc_realized_classes."); | 
|  | 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.get()) { | 
|  | Error error; | 
|  | m_get_class_info_code.reset(GetTargetRef().GetUtilityFunctionForLanguage( | 
|  | g_get_dynamic_class_info_body, eLanguageTypeObjC, | 
|  | g_get_dynamic_class_info_name, error)); | 
|  | if (error.Fail()) { | 
|  | if (log) | 
|  | log->Printf( | 
|  | "Failed to get Utility Function for implementation lookup: %s", | 
|  | error.AsCString()); | 
|  | m_get_class_info_code.reset(); | 
|  | } else { | 
|  | diagnostics.Clear(); | 
|  |  | 
|  | if (!m_get_class_info_code->Install(diagnostics, exe_ctx)) { | 
|  | if (log) { | 
|  | log->Printf("Failed to install implementation lookup"); | 
|  | diagnostics.Dump(log); | 
|  | } | 
|  | m_get_class_info_code.reset(); | 
|  | } | 
|  | } | 
|  | if (!m_get_class_info_code.get()) | 
|  | 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.SetValueType(Value::eValueTypeScalar); | 
|  | value.SetCompilerType(clang_uint32_t_type); | 
|  | arguments.PushValue(value); | 
|  | arguments.PushValue(value); | 
|  |  | 
|  | get_class_info_function = m_get_class_info_code->MakeFunctionCaller( | 
|  | clang_uint32_t_type, arguments, thread_sp, error); | 
|  |  | 
|  | if (error.Fail()) { | 
|  | if (log) | 
|  | log->Printf( | 
|  | "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) { | 
|  | if (log) { | 
|  | log->Printf("Failed to get implementation lookup function caller."); | 
|  | diagnostics.Dump(log); | 
|  | } | 
|  |  | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  | } | 
|  | arguments = get_class_info_function->GetArgumentValues(); | 
|  | } | 
|  |  | 
|  | diagnostics.Clear(); | 
|  |  | 
|  | const uint32_t class_info_byte_size = addr_size + 4; | 
|  | 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); | 
|  |  | 
|  | if (class_infos_addr == LLDB_INVALID_ADDRESS) { | 
|  | if (log) | 
|  | log->Printf("unable to allocate %" PRIu32 | 
|  | " bytes in process for shared cache read", | 
|  | class_infos_byte_size); | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  | } | 
|  |  | 
|  | 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() = | 
|  | (GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES) == nullptr ? 0 : 1); | 
|  |  | 
|  | bool success = false; | 
|  |  | 
|  | diagnostics.Clear(); | 
|  |  | 
|  | // 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)) { | 
|  | EvaluateExpressionOptions options; | 
|  | options.SetUnwindOnError(true); | 
|  | options.SetTryAllThreads(false); | 
|  | options.SetStopOthers(true); | 
|  | options.SetIgnoreBreakpoints(true); | 
|  | options.SetTimeoutUsec(UTILITY_FUNCTION_TIMEOUT_USEC); | 
|  |  | 
|  | Value return_value; | 
|  | return_value.SetValueType(Value::eValueTypeScalar); | 
|  | // return_value.SetContext (Value::eContextTypeClangType, | 
|  | // clang_uint32_t_type); | 
|  | return_value.SetCompilerType(clang_uint32_t_type); | 
|  | return_value.GetScalar() = 0; | 
|  |  | 
|  | diagnostics.Clear(); | 
|  |  | 
|  | // Run the function | 
|  | ExpressionResults results = get_class_info_function->ExecuteFunction( | 
|  | exe_ctx, &m_get_class_info_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(); | 
|  | if (log) | 
|  | log->Printf("Discovered %u ObjC classes\n", num_class_infos); | 
|  | if (num_class_infos > 0) { | 
|  | // Read the ClassInfo structures | 
|  | DataBufferHeap buffer(num_class_infos * class_info_byte_size, 0); | 
|  | if (process->ReadMemory(class_infos_addr, buffer.GetBytes(), | 
|  | buffer.GetByteSize(), | 
|  | err) == buffer.GetByteSize()) { | 
|  | DataExtractor class_infos_data(buffer.GetBytes(), | 
|  | buffer.GetByteSize(), | 
|  | process->GetByteOrder(), addr_size); | 
|  | ParseClassInfoArray(class_infos_data, num_class_infos); | 
|  | } | 
|  | } | 
|  | success = true; | 
|  | } else { | 
|  | if (log) { | 
|  | log->Printf("Error evaluating our find class name function."); | 
|  | diagnostics.Dump(log); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | if (log) { | 
|  | log->Printf("Error writing function arguments."); | 
|  | diagnostics.Dump(log); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Deallocate the memory we allocated for the ClassInfo array | 
|  | process->DeallocateMemory(class_infos_addr); | 
|  |  | 
|  | return DescriptorMapUpdateResult(success, num_class_infos); | 
|  | } | 
|  |  | 
|  | uint32_t AppleObjCRuntimeV2::ParseClassInfoArray(const DataExtractor &data, | 
|  | uint32_t num_class_infos) { | 
|  | // Parses an array of "num_class_infos" packed ClassInfo structures: | 
|  | // | 
|  | //    struct ClassInfo | 
|  | //    { | 
|  | //        Class isa; | 
|  | //        uint32_t hash; | 
|  | //    } __attribute__((__packed__)); | 
|  |  | 
|  | Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); | 
|  |  | 
|  | uint32_t num_parsed = 0; | 
|  |  | 
|  | // Iterate through all ClassInfo structures | 
|  | lldb::offset_t offset = 0; | 
|  | for (uint32_t i = 0; i < num_class_infos; ++i) { | 
|  | ObjCISA isa = data.GetPointer(&offset); | 
|  |  | 
|  | if (isa == 0) { | 
|  | if (log) | 
|  | log->Printf( | 
|  | "AppleObjCRuntimeV2 found NULL isa, ignoring this class info"); | 
|  | continue; | 
|  | } | 
|  | // Check if we already know about this ISA, if we do, the info will | 
|  | // never change, so we can just skip it. | 
|  | if (ISAIsCached(isa)) { | 
|  | if (log) | 
|  | log->Printf("AppleObjCRuntimeV2 found cached isa=0x%" PRIx64 | 
|  | ", ignoring this class info", | 
|  | isa); | 
|  | offset += 4; | 
|  | } 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); | 
|  | num_parsed++; | 
|  | if (log) | 
|  | log->Printf("AppleObjCRuntimeV2 added isa=0x%" PRIx64 | 
|  | ", hash=0x%8.8x, name=%s", | 
|  | isa, name_hash, | 
|  | descriptor_sp->GetClassName().AsCString("<unknown>")); | 
|  | } | 
|  | } | 
|  | if (log) | 
|  | log->Printf("AppleObjCRuntimeV2 parsed %" PRIu32 " class infos", | 
|  | num_parsed); | 
|  | return num_parsed; | 
|  | } | 
|  |  | 
|  | AppleObjCRuntimeV2::DescriptorMapUpdateResult | 
|  | AppleObjCRuntimeV2::UpdateISAToDescriptorMapSharedCache() { | 
|  | Process *process = GetProcess(); | 
|  |  | 
|  | if (process == NULL) | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  |  | 
|  | Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); | 
|  |  | 
|  | ExecutionContext exe_ctx; | 
|  |  | 
|  | ThreadSP thread_sp = process->GetThreadList().GetExpressionExecutionThread(); | 
|  |  | 
|  | if (!thread_sp) | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  |  | 
|  | thread_sp->CalculateExecutionContext(exe_ctx); | 
|  | ClangASTContext *ast = process->GetTarget().GetScratchClangASTContext(); | 
|  |  | 
|  | if (!ast) | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  |  | 
|  | Address function_address; | 
|  |  | 
|  | DiagnosticManager diagnostics; | 
|  |  | 
|  | const uint32_t addr_size = process->GetAddressByteSize(); | 
|  |  | 
|  | Error err; | 
|  |  | 
|  | uint32_t num_class_infos = 0; | 
|  |  | 
|  | const lldb::addr_t objc_opt_ptr = GetSharedCacheReadOnlyAddress(); | 
|  |  | 
|  | if (objc_opt_ptr == LLDB_INVALID_ADDRESS) | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  |  | 
|  | const uint32_t num_classes = 128 * 1024; | 
|  |  | 
|  | // 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_shared_cache_class_info_function = nullptr; | 
|  |  | 
|  | if (!m_get_shared_cache_class_info_code.get()) { | 
|  | Error error; | 
|  | m_get_shared_cache_class_info_code.reset( | 
|  | GetTargetRef().GetUtilityFunctionForLanguage( | 
|  | g_get_shared_cache_class_info_body, eLanguageTypeObjC, | 
|  | g_get_shared_cache_class_info_name, error)); | 
|  | if (error.Fail()) { | 
|  | if (log) | 
|  | log->Printf( | 
|  | "Failed to get Utility function for implementation lookup: %s.", | 
|  | error.AsCString()); | 
|  | m_get_shared_cache_class_info_code.reset(); | 
|  | } else { | 
|  | diagnostics.Clear(); | 
|  |  | 
|  | if (!m_get_shared_cache_class_info_code->Install(diagnostics, exe_ctx)) { | 
|  | if (log) { | 
|  | log->Printf("Failed to install implementation lookup."); | 
|  | diagnostics.Dump(log); | 
|  | } | 
|  | m_get_shared_cache_class_info_code.reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!m_get_shared_cache_class_info_code.get()) | 
|  | 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.SetValueType(Value::eValueTypeScalar); | 
|  | // value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); | 
|  | value.SetCompilerType(clang_uint32_t_type); | 
|  | arguments.PushValue(value); | 
|  | arguments.PushValue(value); | 
|  |  | 
|  | get_shared_cache_class_info_function = | 
|  | m_get_shared_cache_class_info_code->MakeFunctionCaller( | 
|  | clang_uint32_t_type, arguments, thread_sp, error); | 
|  |  | 
|  | if (get_shared_cache_class_info_function == nullptr) | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  |  | 
|  | } else { | 
|  | get_shared_cache_class_info_function = | 
|  | m_get_shared_cache_class_info_code->GetFunctionCaller(); | 
|  | if (get_shared_cache_class_info_function == nullptr) | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  | arguments = get_shared_cache_class_info_function->GetArgumentValues(); | 
|  | } | 
|  |  | 
|  | diagnostics.Clear(); | 
|  |  | 
|  | const uint32_t class_info_byte_size = addr_size + 4; | 
|  | 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); | 
|  |  | 
|  | if (class_infos_addr == LLDB_INVALID_ADDRESS) { | 
|  | if (log) | 
|  | log->Printf("unable to allocate %" PRIu32 | 
|  | " bytes in process for shared cache read", | 
|  | class_infos_byte_size); | 
|  | return DescriptorMapUpdateResult::Fail(); | 
|  | } | 
|  |  | 
|  | 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() = | 
|  | (GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES) == nullptr ? 0 : 1); | 
|  |  | 
|  | bool success = false; | 
|  |  | 
|  | diagnostics.Clear(); | 
|  |  | 
|  | // Write our function arguments into the process so we can run our function | 
|  | if (get_shared_cache_class_info_function->WriteFunctionArguments( | 
|  | exe_ctx, m_get_shared_cache_class_info_args, arguments, | 
|  | diagnostics)) { | 
|  | EvaluateExpressionOptions options; | 
|  | options.SetUnwindOnError(true); | 
|  | options.SetTryAllThreads(false); | 
|  | options.SetStopOthers(true); | 
|  | options.SetIgnoreBreakpoints(true); | 
|  | options.SetTimeoutUsec(UTILITY_FUNCTION_TIMEOUT_USEC); | 
|  |  | 
|  | Value return_value; | 
|  | return_value.SetValueType(Value::eValueTypeScalar); | 
|  | // return_value.SetContext (Value::eContextTypeClangType, | 
|  | // clang_uint32_t_type); | 
|  | return_value.SetCompilerType(clang_uint32_t_type); | 
|  | return_value.GetScalar() = 0; | 
|  |  | 
|  | diagnostics.Clear(); | 
|  |  | 
|  | // Run the function | 
|  | ExpressionResults results = | 
|  | get_shared_cache_class_info_function->ExecuteFunction( | 
|  | exe_ctx, &m_get_shared_cache_class_info_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(); | 
|  | if (log) | 
|  | log->Printf("Discovered %u ObjC classes in shared cache\n", | 
|  | num_class_infos); | 
|  | #ifdef LLDB_CONFIGURATION_DEBUG | 
|  | assert(num_class_infos <= num_classes); | 
|  | #endif | 
|  | if (num_class_infos > 0) { | 
|  | if (num_class_infos > num_classes) { | 
|  | num_class_infos = num_classes; | 
|  |  | 
|  | success = false; | 
|  | } else { | 
|  | success = true; | 
|  | } | 
|  |  | 
|  | // Read the ClassInfo structures | 
|  | DataBufferHeap buffer(num_class_infos * class_info_byte_size, 0); | 
|  | if (process->ReadMemory(class_infos_addr, buffer.GetBytes(), | 
|  | buffer.GetByteSize(), | 
|  | err) == buffer.GetByteSize()) { | 
|  | DataExtractor class_infos_data(buffer.GetBytes(), | 
|  | buffer.GetByteSize(), | 
|  | process->GetByteOrder(), addr_size); | 
|  |  | 
|  | ParseClassInfoArray(class_infos_data, num_class_infos); | 
|  | } | 
|  | } else { | 
|  | success = true; | 
|  | } | 
|  | } else { | 
|  | if (log) { | 
|  | log->Printf("Error evaluating our find class name function."); | 
|  | diagnostics.Dump(log); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | if (log) { | 
|  | log->Printf("Error writing function arguments."); | 
|  | diagnostics.Dump(log); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Deallocate the memory we allocated for the ClassInfo array | 
|  | process->DeallocateMemory(class_infos_addr); | 
|  |  | 
|  | return DescriptorMapUpdateResult(success, num_class_infos); | 
|  | } | 
|  |  | 
|  | bool AppleObjCRuntimeV2::UpdateISAToDescriptorMapFromMemory( | 
|  | RemoteNXMapTable &hash_table) { | 
|  | Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); | 
|  |  | 
|  | Process *process = GetProcess(); | 
|  |  | 
|  | if (process == NULL) | 
|  | return false; | 
|  |  | 
|  | uint32_t num_map_table_isas = 0; | 
|  |  | 
|  | ModuleSP objc_module_sp(GetObjCModule()); | 
|  |  | 
|  | if (objc_module_sp) { | 
|  | for (RemoteNXMapTable::element elt : hash_table) { | 
|  | ++num_map_table_isas; | 
|  |  | 
|  | if (ISAIsCached(elt.second)) | 
|  | continue; | 
|  |  | 
|  | ClassDescriptorSP descriptor_sp = ClassDescriptorSP( | 
|  | new ClassDescriptorV2(*this, elt.second, elt.first.AsCString())); | 
|  |  | 
|  | if (log && log->GetVerbose()) | 
|  | log->Printf("AppleObjCRuntimeV2 added (ObjCISA)0x%" PRIx64 | 
|  | " (%s) from dynamic table to isa->descriptor cache", | 
|  | elt.second, elt.first.AsCString()); | 
|  |  | 
|  | AddClass(elt.second, descriptor_sp, elt.first.AsCString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | return num_map_table_isas > 0; | 
|  | } | 
|  |  | 
|  | lldb::addr_t AppleObjCRuntimeV2::GetSharedCacheReadOnlyAddress() { | 
|  | Process *process = GetProcess(); | 
|  |  | 
|  | if (process) { | 
|  | ModuleSP objc_module_sp(GetObjCModule()); | 
|  |  | 
|  | if (objc_module_sp) { | 
|  | ObjectFile *objc_object = objc_module_sp->GetObjectFile(); | 
|  |  | 
|  | if (objc_object) { | 
|  | SectionList *section_list = objc_module_sp->GetSectionList(); | 
|  |  | 
|  | if (section_list) { | 
|  | SectionSP text_segment_sp( | 
|  | section_list->FindSectionByName(ConstString("__TEXT"))); | 
|  |  | 
|  | if (text_segment_sp) { | 
|  | SectionSP objc_opt_section_sp( | 
|  | text_segment_sp->GetChildren().FindSectionByName( | 
|  | ConstString("__objc_opt_ro"))); | 
|  |  | 
|  | if (objc_opt_section_sp) { | 
|  | return objc_opt_section_sp->GetLoadBaseAddress( | 
|  | &process->GetTarget()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return LLDB_INVALID_ADDRESS; | 
|  | } | 
|  |  | 
|  | void AppleObjCRuntimeV2::UpdateISAToDescriptorMapIfNeeded() { | 
|  | Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); | 
|  |  | 
|  | Timer scoped_timer(LLVM_PRETTY_FUNCTION, LLVM_PRETTY_FUNCTION); | 
|  |  | 
|  | // Else we need to check with our process to see when the map was updated. | 
|  | Process *process = GetProcess(); | 
|  |  | 
|  | if (process) { | 
|  | RemoteNXMapTable hash_table; | 
|  |  | 
|  | // Update the process stop ID that indicates the last time we updated the | 
|  | // map, whether it was successful or not. | 
|  | m_isa_to_descriptor_stop_id = process->GetStopID(); | 
|  |  | 
|  | if (!m_hash_signature.NeedsUpdate(process, this, hash_table)) | 
|  | return; | 
|  |  | 
|  | m_hash_signature.UpdateSignature(hash_table); | 
|  |  | 
|  | // Grab the dynamically loaded objc classes from the hash table in memory | 
|  | DescriptorMapUpdateResult dynamic_update_result = | 
|  | UpdateISAToDescriptorMapDynamic(hash_table); | 
|  |  | 
|  | // Now get the objc classes that are baked into the Objective C runtime | 
|  | // in the shared cache, but only once per process as this data never | 
|  | // changes | 
|  | if (!m_loaded_objc_opt) { | 
|  | // it is legitimately possible for the shared cache to be empty - in that | 
|  | // case, the dynamic hash table | 
|  | // will contain all the class information we need; the situation we're | 
|  | // trying to detect is one where | 
|  | // we aren't seeing class information from the runtime - in order to | 
|  | // detect that vs. just the shared cache | 
|  | // being empty or sparsely populated, we set an arbitrary (very low) | 
|  | // threshold for the number of classes | 
|  | // that we want to see in a "good" scenario - anything below that is | 
|  | // suspicious (Foundation alone has thousands | 
|  | // of classes) | 
|  | const uint32_t num_classes_to_warn_at = 500; | 
|  |  | 
|  | DescriptorMapUpdateResult shared_cache_update_result = | 
|  | UpdateISAToDescriptorMapSharedCache(); | 
|  |  | 
|  | if (log) | 
|  | log->Printf("attempted to read objc class data - results: " | 
|  | "[dynamic_update]: ran: %s, count: %" PRIu32 | 
|  | " [shared_cache_update]: ran: %s, count: %" PRIu32, | 
|  | dynamic_update_result.m_update_ran ? "yes" : "no", | 
|  | dynamic_update_result.m_num_found, | 
|  | shared_cache_update_result.m_update_ran ? "yes" : "no", | 
|  | shared_cache_update_result.m_num_found); | 
|  |  | 
|  | // warn if: | 
|  | // - we could not run either expression | 
|  | // - we found fewer than num_classes_to_warn_at classes total | 
|  | if ((false == shared_cache_update_result.m_update_ran) || | 
|  | (false == dynamic_update_result.m_update_ran)) | 
|  | WarnIfNoClassesCached( | 
|  | SharedCacheWarningReason::eExpressionExecutionFailure); | 
|  | else if (dynamic_update_result.m_num_found + | 
|  | shared_cache_update_result.m_num_found < | 
|  | num_classes_to_warn_at) | 
|  | WarnIfNoClassesCached(SharedCacheWarningReason::eNotEnoughClassesRead); | 
|  | else | 
|  | m_loaded_objc_opt = true; | 
|  | } | 
|  | } else { | 
|  | m_isa_to_descriptor_stop_id = UINT32_MAX; | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool DoesProcessHaveSharedCache(Process &process) { | 
|  | PlatformSP platform_sp = process.GetTarget().GetPlatform(); | 
|  | if (!platform_sp) | 
|  | return true; // this should not happen | 
|  |  | 
|  | ConstString platform_plugin_name = platform_sp->GetPluginName(); | 
|  | if (platform_plugin_name) { | 
|  | llvm::StringRef platform_plugin_name_sr = | 
|  | platform_plugin_name.GetStringRef(); | 
|  | if (platform_plugin_name_sr.endswith("-simulator")) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void AppleObjCRuntimeV2::WarnIfNoClassesCached( | 
|  | SharedCacheWarningReason reason) { | 
|  | if (m_noclasses_warning_emitted) | 
|  | return; | 
|  |  | 
|  | if (GetProcess() && !DoesProcessHaveSharedCache(*GetProcess())) { | 
|  | // Simulators do not have the objc_opt_ro class table so don't actually | 
|  | // complain to the user | 
|  | m_noclasses_warning_emitted = true; | 
|  | return; | 
|  | } | 
|  |  | 
|  | Debugger &debugger(GetProcess()->GetTarget().GetDebugger()); | 
|  | if (auto stream = debugger.GetAsyncOutputStream()) { | 
|  | switch (reason) { | 
|  | case SharedCacheWarningReason::eNotEnoughClassesRead: | 
|  | stream->PutCString("warning: could not find Objective-C class data in " | 
|  | "the process. This may reduce the quality of type " | 
|  | "information available.\n"); | 
|  | m_noclasses_warning_emitted = true; | 
|  | break; | 
|  | case SharedCacheWarningReason::eExpressionExecutionFailure: | 
|  | stream->PutCString("warning: could not execute support code to read " | 
|  | "Objective-C class data in the process. This may " | 
|  | "reduce the quality of type information available.\n"); | 
|  | m_noclasses_warning_emitted = true; | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO: should we have a transparent_kvo parameter here to say if we | 
|  | // want to replace the KVO swizzled class with the actual user-level type? | 
|  | ConstString | 
|  | AppleObjCRuntimeV2::GetActualTypeName(ObjCLanguageRuntime::ObjCISA isa) { | 
|  | if (isa == g_objc_Tagged_ISA) { | 
|  | static const ConstString g_objc_tagged_isa_name("_lldb_Tagged_ObjC_ISA"); | 
|  | return g_objc_tagged_isa_name; | 
|  | } | 
|  | if (isa == g_objc_Tagged_ISA_NSAtom) { | 
|  | static const ConstString g_objc_tagged_isa_nsatom_name("NSAtom"); | 
|  | return g_objc_tagged_isa_nsatom_name; | 
|  | } | 
|  | if (isa == g_objc_Tagged_ISA_NSNumber) { | 
|  | static const ConstString g_objc_tagged_isa_nsnumber_name("NSNumber"); | 
|  | return g_objc_tagged_isa_nsnumber_name; | 
|  | } | 
|  | if (isa == g_objc_Tagged_ISA_NSDateTS) { | 
|  | static const ConstString g_objc_tagged_isa_nsdatets_name("NSDateTS"); | 
|  | return g_objc_tagged_isa_nsdatets_name; | 
|  | } | 
|  | if (isa == g_objc_Tagged_ISA_NSManagedObject) { | 
|  | static const ConstString g_objc_tagged_isa_nsmanagedobject_name( | 
|  | "NSManagedObject"); | 
|  | return g_objc_tagged_isa_nsmanagedobject_name; | 
|  | } | 
|  | if (isa == g_objc_Tagged_ISA_NSDate) { | 
|  | static const ConstString g_objc_tagged_isa_nsdate_name("NSDate"); | 
|  | return g_objc_tagged_isa_nsdate_name; | 
|  | } | 
|  | return ObjCLanguageRuntime::GetActualTypeName(isa); | 
|  | } | 
|  |  | 
|  | DeclVendor *AppleObjCRuntimeV2::GetDeclVendor() { | 
|  | if (!m_decl_vendor_ap.get()) | 
|  | m_decl_vendor_ap.reset(new AppleObjCDeclVendor(*this)); | 
|  |  | 
|  | return m_decl_vendor_ap.get(); | 
|  | } | 
|  |  | 
|  | lldb::addr_t AppleObjCRuntimeV2::LookupRuntimeSymbol(const ConstString &name) { | 
|  | lldb::addr_t ret = LLDB_INVALID_ADDRESS; | 
|  |  | 
|  | const char *name_cstr = name.AsCString(); | 
|  |  | 
|  | if (name_cstr) { | 
|  | llvm::StringRef name_strref(name_cstr); | 
|  |  | 
|  | static const llvm::StringRef ivar_prefix("OBJC_IVAR_$_"); | 
|  | static const llvm::StringRef class_prefix("OBJC_CLASS_$_"); | 
|  |  | 
|  | if (name_strref.startswith(ivar_prefix)) { | 
|  | llvm::StringRef ivar_skipped_prefix = | 
|  | name_strref.substr(ivar_prefix.size()); | 
|  | std::pair<llvm::StringRef, llvm::StringRef> class_and_ivar = | 
|  | ivar_skipped_prefix.split('.'); | 
|  |  | 
|  | if (class_and_ivar.first.size() && class_and_ivar.second.size()) { | 
|  | const ConstString class_name_cs(class_and_ivar.first); | 
|  | ClassDescriptorSP descriptor = | 
|  | ObjCLanguageRuntime::GetClassDescriptorFromClassName(class_name_cs); | 
|  |  | 
|  | if (descriptor) { | 
|  | const ConstString ivar_name_cs(class_and_ivar.second); | 
|  | const char *ivar_name_cstr = ivar_name_cs.AsCString(); | 
|  |  | 
|  | auto ivar_func = [&ret, ivar_name_cstr]( | 
|  | const char *name, const char *type, lldb::addr_t offset_addr, | 
|  | uint64_t size) -> lldb::addr_t { | 
|  | if (!strcmp(name, ivar_name_cstr)) { | 
|  | ret = offset_addr; | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | }; | 
|  |  | 
|  | descriptor->Describe( | 
|  | std::function<void(ObjCISA)>(nullptr), | 
|  | std::function<bool(const char *, const char *)>(nullptr), | 
|  | std::function<bool(const char *, const char *)>(nullptr), | 
|  | ivar_func); | 
|  | } | 
|  | } | 
|  | } else if (name_strref.startswith(class_prefix)) { | 
|  | llvm::StringRef class_skipped_prefix = | 
|  | name_strref.substr(class_prefix.size()); | 
|  | const ConstString class_name_cs(class_skipped_prefix); | 
|  | ClassDescriptorSP descriptor = | 
|  | GetClassDescriptorFromClassName(class_name_cs); | 
|  |  | 
|  | if (descriptor) | 
|  | ret = descriptor->GetISA(); | 
|  | } | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | AppleObjCRuntimeV2::NonPointerISACache * | 
|  | AppleObjCRuntimeV2::NonPointerISACache::CreateInstance( | 
|  | AppleObjCRuntimeV2 &runtime, const lldb::ModuleSP &objc_module_sp) { | 
|  | Process *process(runtime.GetProcess()); | 
|  |  | 
|  | Error error; | 
|  |  | 
|  | auto objc_debug_isa_magic_mask = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_isa_magic_mask"), objc_module_sp, error); | 
|  | if (error.Fail()) | 
|  | return NULL; | 
|  |  | 
|  | auto objc_debug_isa_magic_value = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_isa_magic_value"), objc_module_sp, | 
|  | error); | 
|  | if (error.Fail()) | 
|  | return NULL; | 
|  |  | 
|  | auto objc_debug_isa_class_mask = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_isa_class_mask"), objc_module_sp, error); | 
|  | if (error.Fail()) | 
|  | return NULL; | 
|  |  | 
|  | // we might want to have some rules to outlaw these other values (e.g if the | 
|  | // mask is zero but the value is non-zero, ...) | 
|  |  | 
|  | return new NonPointerISACache(runtime, objc_debug_isa_class_mask, | 
|  | objc_debug_isa_magic_mask, | 
|  | objc_debug_isa_magic_value); | 
|  | } | 
|  |  | 
|  | AppleObjCRuntimeV2::TaggedPointerVendorV2 * | 
|  | AppleObjCRuntimeV2::TaggedPointerVendorV2::CreateInstance( | 
|  | AppleObjCRuntimeV2 &runtime, const lldb::ModuleSP &objc_module_sp) { | 
|  | Process *process(runtime.GetProcess()); | 
|  |  | 
|  | Error error; | 
|  |  | 
|  | auto objc_debug_taggedpointer_mask = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_mask"), objc_module_sp, | 
|  | error); | 
|  | if (error.Fail()) | 
|  | return new TaggedPointerVendorLegacy(runtime); | 
|  |  | 
|  | auto objc_debug_taggedpointer_slot_shift = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_slot_shift"), | 
|  | objc_module_sp, error, true, 4); | 
|  | if (error.Fail()) | 
|  | return new TaggedPointerVendorLegacy(runtime); | 
|  |  | 
|  | auto objc_debug_taggedpointer_slot_mask = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_slot_mask"), | 
|  | objc_module_sp, error, true, 4); | 
|  | if (error.Fail()) | 
|  | return new TaggedPointerVendorLegacy(runtime); | 
|  |  | 
|  | auto objc_debug_taggedpointer_payload_lshift = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_payload_lshift"), | 
|  | objc_module_sp, error, true, 4); | 
|  | if (error.Fail()) | 
|  | return new TaggedPointerVendorLegacy(runtime); | 
|  |  | 
|  | auto objc_debug_taggedpointer_payload_rshift = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_payload_rshift"), | 
|  | objc_module_sp, error, true, 4); | 
|  | if (error.Fail()) | 
|  | return new TaggedPointerVendorLegacy(runtime); | 
|  |  | 
|  | auto objc_debug_taggedpointer_classes = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_classes"), objc_module_sp, | 
|  | error, false); | 
|  | if (error.Fail()) | 
|  | return new TaggedPointerVendorLegacy(runtime); | 
|  |  | 
|  | // try to detect the "extended tagged pointer" variables - if any are missing, | 
|  | // use the non-extended vendor | 
|  | do { | 
|  | auto objc_debug_taggedpointer_ext_mask = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_ext_mask"), | 
|  | objc_module_sp, error); | 
|  | if (error.Fail()) | 
|  | break; | 
|  |  | 
|  | auto objc_debug_taggedpointer_ext_slot_shift = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_ext_slot_shift"), | 
|  | objc_module_sp, error, true, 4); | 
|  | if (error.Fail()) | 
|  | break; | 
|  |  | 
|  | auto objc_debug_taggedpointer_ext_slot_mask = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_ext_slot_mask"), | 
|  | objc_module_sp, error, true, 4); | 
|  | if (error.Fail()) | 
|  | break; | 
|  |  | 
|  | auto objc_debug_taggedpointer_ext_classes = ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_ext_classes"), | 
|  | objc_module_sp, error, false); | 
|  | if (error.Fail()) | 
|  | break; | 
|  |  | 
|  | auto objc_debug_taggedpointer_ext_payload_lshift = | 
|  | ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_ext_payload_lshift"), | 
|  | objc_module_sp, error, true, 4); | 
|  | if (error.Fail()) | 
|  | break; | 
|  |  | 
|  | auto objc_debug_taggedpointer_ext_payload_rshift = | 
|  | ExtractRuntimeGlobalSymbol( | 
|  | process, ConstString("objc_debug_taggedpointer_ext_payload_rshift"), | 
|  | objc_module_sp, error, true, 4); | 
|  | if (error.Fail()) | 
|  | break; | 
|  |  | 
|  | return new TaggedPointerVendorExtended( | 
|  | runtime, objc_debug_taggedpointer_mask, | 
|  | objc_debug_taggedpointer_ext_mask, objc_debug_taggedpointer_slot_shift, | 
|  | objc_debug_taggedpointer_ext_slot_shift, | 
|  | objc_debug_taggedpointer_slot_mask, | 
|  | objc_debug_taggedpointer_ext_slot_mask, | 
|  | objc_debug_taggedpointer_payload_lshift, | 
|  | objc_debug_taggedpointer_payload_rshift, | 
|  | objc_debug_taggedpointer_ext_payload_lshift, | 
|  | objc_debug_taggedpointer_ext_payload_rshift, | 
|  | objc_debug_taggedpointer_classes, objc_debug_taggedpointer_ext_classes); | 
|  | } while (false); | 
|  |  | 
|  | // we might want to have some rules to outlaw these values (e.g if the table's | 
|  | // address is zero) | 
|  |  | 
|  | return new TaggedPointerVendorRuntimeAssisted( | 
|  | runtime, objc_debug_taggedpointer_mask, | 
|  | objc_debug_taggedpointer_slot_shift, objc_debug_taggedpointer_slot_mask, | 
|  | objc_debug_taggedpointer_payload_lshift, | 
|  | objc_debug_taggedpointer_payload_rshift, | 
|  | objc_debug_taggedpointer_classes); | 
|  | } | 
|  |  | 
|  | bool AppleObjCRuntimeV2::TaggedPointerVendorLegacy::IsPossibleTaggedPointer( | 
|  | lldb::addr_t ptr) { | 
|  | return (ptr & 1); | 
|  | } | 
|  |  | 
|  | ObjCLanguageRuntime::ClassDescriptorSP | 
|  | AppleObjCRuntimeV2::TaggedPointerVendorLegacy::GetClassDescriptor( | 
|  | lldb::addr_t ptr) { | 
|  | if (!IsPossibleTaggedPointer(ptr)) | 
|  | return ObjCLanguageRuntime::ClassDescriptorSP(); | 
|  |  | 
|  | uint32_t foundation_version = m_runtime.GetFoundationVersion(); | 
|  |  | 
|  | if (foundation_version == LLDB_INVALID_MODULE_VERSION) | 
|  | return ObjCLanguageRuntime::ClassDescriptorSP(); | 
|  |  | 
|  | uint64_t class_bits = (ptr & 0xE) >> 1; | 
|  | ConstString name; | 
|  |  | 
|  | // TODO: make a table | 
|  | if (foundation_version >= 900) { | 
|  | switch (class_bits) { | 
|  | case 0: | 
|  | name = ConstString("NSAtom"); | 
|  | break; | 
|  | case 3: | 
|  | name = ConstString("NSNumber"); | 
|  | break; | 
|  | case 4: | 
|  | name = ConstString("NSDateTS"); | 
|  | break; | 
|  | case 5: | 
|  | name = ConstString("NSManagedObject"); | 
|  | break; | 
|  | case 6: | 
|  | name = ConstString("NSDate"); | 
|  | break; | 
|  | default: | 
|  | return ObjCLanguageRuntime::ClassDescriptorSP(); | 
|  | } | 
|  | } else { | 
|  | switch (class_bits) { | 
|  | case 1: | 
|  | name = ConstString("NSNumber"); | 
|  | break; | 
|  | case 5: | 
|  | name = ConstString("NSManagedObject"); | 
|  | break; | 
|  | case 6: | 
|  | name = ConstString("NSDate"); | 
|  | break; | 
|  | case 7: | 
|  | name = ConstString("NSDateTS"); | 
|  | break; | 
|  | default: | 
|  | return ObjCLanguageRuntime::ClassDescriptorSP(); | 
|  | } | 
|  | } | 
|  | return ClassDescriptorSP(new ClassDescriptorV2Tagged(name, ptr)); | 
|  | } | 
|  |  | 
|  | AppleObjCRuntimeV2::TaggedPointerVendorRuntimeAssisted:: | 
|  | TaggedPointerVendorRuntimeAssisted( | 
|  | AppleObjCRuntimeV2 &runtime, uint64_t objc_debug_taggedpointer_mask, | 
|  | uint32_t objc_debug_taggedpointer_slot_shift, | 
|  | uint32_t objc_debug_taggedpointer_slot_mask, | 
|  | uint32_t objc_debug_taggedpointer_payload_lshift, | 
|  | uint32_t objc_debug_taggedpointer_payload_rshift, | 
|  | lldb::addr_t objc_debug_taggedpointer_classes) | 
|  | : TaggedPointerVendorV2(runtime), m_cache(), | 
|  | m_objc_debug_taggedpointer_mask(objc_debug_taggedpointer_mask), | 
|  | m_objc_debug_taggedpointer_slot_shift( | 
|  | objc_debug_taggedpointer_slot_shift), | 
|  | m_objc_debug_taggedpointer_slot_mask(objc_debug_taggedpointer_slot_mask), | 
|  | m_objc_debug_taggedpointer_payload_lshift( | 
|  | objc_debug_taggedpointer_payload_lshift), | 
|  | m_objc_debug_taggedpointer_payload_rshift( | 
|  | objc_debug_taggedpointer_payload_rshift), | 
|  | m_objc_debug_taggedpointer_classes(objc_debug_taggedpointer_classes) {} | 
|  |  | 
|  | bool AppleObjCRuntimeV2::TaggedPointerVendorRuntimeAssisted:: | 
|  | IsPossibleTaggedPointer(lldb::addr_t ptr) { | 
|  | return (ptr & m_objc_debug_taggedpointer_mask) != 0; | 
|  | } | 
|  |  | 
|  | ObjCLanguageRuntime::ClassDescriptorSP | 
|  | AppleObjCRuntimeV2::TaggedPointerVendorRuntimeAssisted::GetClassDescriptor( | 
|  | lldb::addr_t ptr) { | 
|  | ClassDescriptorSP actual_class_descriptor_sp; | 
|  | uint64_t data_payload; | 
|  |  | 
|  | if (!IsPossibleTaggedPointer(ptr)) | 
|  | return ObjCLanguageRuntime::ClassDescriptorSP(); | 
|  |  | 
|  | uintptr_t slot = (ptr >> m_objc_debug_taggedpointer_slot_shift) & | 
|  | m_objc_debug_taggedpointer_slot_mask; | 
|  |  | 
|  | CacheIterator iterator = m_cache.find(slot), end = m_cache.end(); | 
|  | if (iterator != end) { | 
|  | actual_class_descriptor_sp = iterator->second; | 
|  | } else { | 
|  | Process *process(m_runtime.GetProcess()); | 
|  | uintptr_t slot_ptr = slot * process->GetAddressByteSize() + | 
|  | m_objc_debug_taggedpointer_classes; | 
|  | Error error; | 
|  | uintptr_t slot_data = process->ReadPointerFromMemory(slot_ptr, error); | 
|  | if (error.Fail() || slot_data == 0 || | 
|  | slot_data == uintptr_t(LLDB_INVALID_ADDRESS)) | 
|  | return nullptr; | 
|  | actual_class_descriptor_sp = | 
|  | m_runtime.GetClassDescriptorFromISA((ObjCISA)slot_data); | 
|  | if (!actual_class_descriptor_sp) | 
|  | return ObjCLanguageRuntime::ClassDescriptorSP(); | 
|  | m_cache[slot] = actual_class_descriptor_sp; | 
|  | } | 
|  |  | 
|  | data_payload = | 
|  | (((uint64_t)ptr << m_objc_debug_taggedpointer_payload_lshift) >> | 
|  | m_objc_debug_taggedpointer_payload_rshift); | 
|  |  | 
|  | return ClassDescriptorSP( | 
|  | new ClassDescriptorV2Tagged(actual_class_descriptor_sp, data_payload)); | 
|  | } | 
|  |  | 
|  | AppleObjCRuntimeV2::TaggedPointerVendorExtended::TaggedPointerVendorExtended( | 
|  | AppleObjCRuntimeV2 &runtime, uint64_t objc_debug_taggedpointer_mask, | 
|  | uint64_t objc_debug_taggedpointer_ext_mask, | 
|  | uint32_t objc_debug_taggedpointer_slot_shift, | 
|  | uint32_t objc_debug_taggedpointer_ext_slot_shift, | 
|  | uint32_t objc_debug_taggedpointer_slot_mask, | 
|  | uint32_t objc_debug_taggedpointer_ext_slot_mask, | 
|  | uint32_t objc_debug_taggedpointer_payload_lshift, | 
|  | uint32_t objc_debug_taggedpointer_payload_rshift, | 
|  | uint32_t objc_debug_taggedpointer_ext_payload_lshift, | 
|  | uint32_t objc_debug_taggedpointer_ext_payload_rshift, | 
|  | lldb::addr_t objc_debug_taggedpointer_classes, | 
|  | lldb::addr_t objc_debug_taggedpointer_ext_classes) | 
|  | : TaggedPointerVendorRuntimeAssisted( | 
|  | runtime, objc_debug_taggedpointer_mask, | 
|  | objc_debug_taggedpointer_slot_shift, | 
|  | objc_debug_taggedpointer_slot_mask, | 
|  | objc_debug_taggedpointer_payload_lshift, | 
|  | objc_debug_taggedpointer_payload_rshift, | 
|  | objc_debug_taggedpointer_classes), | 
|  | m_ext_cache(), | 
|  | m_objc_debug_taggedpointer_ext_mask(objc_debug_taggedpointer_ext_mask), | 
|  | m_objc_debug_taggedpointer_ext_slot_shift( | 
|  | objc_debug_taggedpointer_ext_slot_shift), | 
|  | m_objc_debug_taggedpointer_ext_slot_mask( | 
|  | objc_debug_taggedpointer_ext_slot_mask), | 
|  | m_objc_debug_taggedpointer_ext_payload_lshift( | 
|  | objc_debug_taggedpointer_ext_payload_lshift), | 
|  | m_objc_debug_taggedpointer_ext_payload_rshift( | 
|  | objc_debug_taggedpointer_ext_payload_rshift), | 
|  | m_objc_debug_taggedpointer_ext_classes( | 
|  | objc_debug_taggedpointer_ext_classes) {} | 
|  |  | 
|  | bool AppleObjCRuntimeV2::TaggedPointerVendorExtended:: | 
|  | IsPossibleExtendedTaggedPointer(lldb::addr_t ptr) { | 
|  | if (!IsPossibleTaggedPointer(ptr)) | 
|  | return false; | 
|  |  | 
|  | if (m_objc_debug_taggedpointer_ext_mask == 0) | 
|  | return false; | 
|  |  | 
|  | return ((ptr & m_objc_debug_taggedpointer_ext_mask) == | 
|  | m_objc_debug_taggedpointer_ext_mask); | 
|  | } | 
|  |  | 
|  | ObjCLanguageRuntime::ClassDescriptorSP | 
|  | AppleObjCRuntimeV2::TaggedPointerVendorExtended::GetClassDescriptor( | 
|  | lldb::addr_t ptr) { | 
|  | ClassDescriptorSP actual_class_descriptor_sp; | 
|  | uint64_t data_payload; | 
|  |  | 
|  | if (!IsPossibleTaggedPointer(ptr)) | 
|  | return ObjCLanguageRuntime::ClassDescriptorSP(); | 
|  |  | 
|  | if (!IsPossibleExtendedTaggedPointer(ptr)) | 
|  | return this->TaggedPointerVendorRuntimeAssisted::GetClassDescriptor(ptr); | 
|  |  | 
|  | uintptr_t slot = (ptr >> m_objc_debug_taggedpointer_ext_slot_shift) & | 
|  | m_objc_debug_taggedpointer_ext_slot_mask; | 
|  |  | 
|  | CacheIterator iterator = m_ext_cache.find(slot), end = m_ext_cache.end(); | 
|  | if (iterator != end) { | 
|  | actual_class_descriptor_sp = iterator->second; | 
|  | } else { | 
|  | Process *process(m_runtime.GetProcess()); | 
|  | uintptr_t slot_ptr = slot * process->GetAddressByteSize() + | 
|  | m_objc_debug_taggedpointer_ext_classes; | 
|  | Error error; | 
|  | uintptr_t slot_data = process->ReadPointerFromMemory(slot_ptr, error); | 
|  | if (error.Fail() || slot_data == 0 || | 
|  | slot_data == uintptr_t(LLDB_INVALID_ADDRESS)) | 
|  | return nullptr; | 
|  | actual_class_descriptor_sp = | 
|  | m_runtime.GetClassDescriptorFromISA((ObjCISA)slot_data); | 
|  | if (!actual_class_descriptor_sp) | 
|  | return ObjCLanguageRuntime::ClassDescriptorSP(); | 
|  | m_ext_cache[slot] = actual_class_descriptor_sp; | 
|  | } | 
|  |  | 
|  | data_payload = | 
|  | (((uint64_t)ptr << m_objc_debug_taggedpointer_ext_payload_lshift) >> | 
|  | m_objc_debug_taggedpointer_ext_payload_rshift); | 
|  |  | 
|  | return ClassDescriptorSP( | 
|  | new ClassDescriptorV2Tagged(actual_class_descriptor_sp, data_payload)); | 
|  | } | 
|  |  | 
|  | AppleObjCRuntimeV2::NonPointerISACache::NonPointerISACache( | 
|  | AppleObjCRuntimeV2 &runtime, uint64_t objc_debug_isa_class_mask, | 
|  | uint64_t objc_debug_isa_magic_mask, uint64_t objc_debug_isa_magic_value) | 
|  | : m_runtime(runtime), m_cache(), | 
|  | m_objc_debug_isa_class_mask(objc_debug_isa_class_mask), | 
|  | m_objc_debug_isa_magic_mask(objc_debug_isa_magic_mask), | 
|  | m_objc_debug_isa_magic_value(objc_debug_isa_magic_value) {} | 
|  |  | 
|  | ObjCLanguageRuntime::ClassDescriptorSP | 
|  | AppleObjCRuntimeV2::NonPointerISACache::GetClassDescriptor(ObjCISA isa) { | 
|  | ObjCISA real_isa = 0; | 
|  | if (EvaluateNonPointerISA(isa, real_isa) == false) | 
|  | return ObjCLanguageRuntime::ClassDescriptorSP(); | 
|  | auto cache_iter = m_cache.find(real_isa); | 
|  | if (cache_iter != m_cache.end()) | 
|  | return cache_iter->second; | 
|  | auto descriptor_sp = | 
|  | m_runtime.ObjCLanguageRuntime::GetClassDescriptorFromISA(real_isa); | 
|  | if (descriptor_sp) // cache only positive matches since the table might grow | 
|  | m_cache[real_isa] = descriptor_sp; | 
|  | return descriptor_sp; | 
|  | } | 
|  |  | 
|  | bool AppleObjCRuntimeV2::NonPointerISACache::EvaluateNonPointerISA( | 
|  | ObjCISA isa, ObjCISA &ret_isa) { | 
|  | if ((isa & ~m_objc_debug_isa_class_mask) == 0) | 
|  | return false; | 
|  | if ((isa & m_objc_debug_isa_magic_mask) == m_objc_debug_isa_magic_value) { | 
|  | ret_isa = isa & m_objc_debug_isa_class_mask; | 
|  | return (ret_isa != 0); // this is a pointer so 0 is not a valid value | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | ObjCLanguageRuntime::EncodingToTypeSP AppleObjCRuntimeV2::GetEncodingToType() { | 
|  | if (!m_encoding_to_type_sp) | 
|  | m_encoding_to_type_sp.reset(new AppleObjCTypeEncodingParser(*this)); | 
|  | return m_encoding_to_type_sp; | 
|  | } | 
|  |  | 
|  | lldb_private::AppleObjCRuntime::ObjCISA | 
|  | AppleObjCRuntimeV2::GetPointerISA(ObjCISA isa) { | 
|  | ObjCISA ret = isa; | 
|  |  | 
|  | if (m_non_pointer_isa_cache_ap) | 
|  | m_non_pointer_isa_cache_ap->EvaluateNonPointerISA(isa, ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | bool AppleObjCRuntimeV2::GetCFBooleanValuesIfNeeded() { | 
|  | if (m_CFBoolean_values) | 
|  | return true; | 
|  |  | 
|  | static ConstString g_kCFBooleanFalse("kCFBooleanFalse"); | 
|  | static ConstString g_kCFBooleanTrue("kCFBooleanTrue"); | 
|  |  | 
|  | std::function<lldb::addr_t(ConstString)> get_symbol = | 
|  | [this](ConstString sym) -> lldb::addr_t { | 
|  | SymbolContextList sc_list; | 
|  | if (GetProcess()->GetTarget().GetImages().FindSymbolsWithNameAndType( | 
|  | g_kCFBooleanFalse, lldb::eSymbolTypeData, sc_list) == 1) { | 
|  | SymbolContext sc; | 
|  | sc_list.GetContextAtIndex(0, sc); | 
|  | if (sc.symbol) | 
|  | return sc.symbol->GetLoadAddress(&GetProcess()->GetTarget()); | 
|  | } | 
|  |  | 
|  | return LLDB_INVALID_ADDRESS; | 
|  | }; | 
|  |  | 
|  | lldb::addr_t false_addr = get_symbol(g_kCFBooleanFalse); | 
|  | lldb::addr_t true_addr = get_symbol(g_kCFBooleanTrue); | 
|  |  | 
|  | return (m_CFBoolean_values = {false_addr, true_addr}).operator bool(); | 
|  | } | 
|  |  | 
|  | void AppleObjCRuntimeV2::GetValuesForGlobalCFBooleans(lldb::addr_t &cf_true, | 
|  | lldb::addr_t &cf_false) { | 
|  | if (GetCFBooleanValuesIfNeeded()) { | 
|  | cf_true = m_CFBoolean_values->second; | 
|  | cf_false = m_CFBoolean_values->first; | 
|  | } else | 
|  | this->AppleObjCRuntime::GetValuesForGlobalCFBooleans(cf_true, cf_false); | 
|  | } |