//===-- AppleObjCRuntimeV2.cpp --------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "lldb/lldb-python.h" #include #include #include #include #include "lldb/lldb-enumerations.h" #include "lldb/Core/ClangForward.h" #include "lldb/Symbol/ClangASTType.h" #include "lldb/Breakpoint/BreakpointLocation.h" #include "lldb/Core/ClangForward.h" #include "lldb/Core/ConstString.h" #include "lldb/Core/DataBufferMemoryMap.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/StreamString.h" #include "lldb/Core/Timer.h" #include "lldb/Core/ValueObjectConstResult.h" #include "lldb/Expression/ClangFunction.h" #include "lldb/Expression/ClangUtilityFunction.h" #include "lldb/Symbol/ClangASTContext.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Symbol/TypeList.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" #include "AppleObjCRuntimeV2.h" #include "AppleObjCTypeVendor.h" #include "AppleObjCTrampolineHandler.h" #include using namespace lldb; using namespace lldb_private; static const char *pluginName = "AppleObjCRuntimeV2"; static const char *pluginDesc = "Apple Objective C Language Runtime - Version 2"; static const char *pluginShort = "language.apple.objc.v2"; const char *AppleObjCRuntimeV2::g_find_class_name_function_name = "__lldb_apple_objc_v2_find_class_name"; const char *AppleObjCRuntimeV2::g_find_class_name_function_body = " \n\ extern \"C\" \n\ { \n\ extern void *gdb_class_getClass (void *objc_class); \n\ extern void *class_getName(void *objc_class); \n\ extern int printf(const char *format, ...); \n\ extern unsigned char class_isMetaClass (void *objc_class); \n\ } \n\ \n\ struct __lldb_objc_object { \n\ void *isa; \n\ }; \n\ \n\ extern \"C\" void *__lldb_apple_objc_v2_find_class_name ( \n\ __lldb_objc_object *object_ptr, \n\ int debug) \n\ { \n\ void *name = 0; \n\ if (debug) \n\ printf (\"\\n*** Called in v2_find_class_name with object: 0x%p\\n\", object_ptr); \n\ // Call gdb_class_getClass so we can tell if the class is good. \n\ void *objc_class = gdb_class_getClass (object_ptr->isa); \n\ if (objc_class) \n\ { \n\ void *actual_class = (void *) [(id) object_ptr class]; \n\ if (actual_class != 0) \n\ { \n\ if (class_isMetaClass(actual_class) == 1) \n\ { \n\ if (debug) \n\ printf (\"\\n*** Found metaclass.\\n\"); \n\ } \n\ else \n\ { \n\ name = class_getName((void *) actual_class); \n\ } \n\ } \n\ if (debug) \n\ printf (\"\\n*** Found name: %s\\n\", name ? name : \"\"); \n\ } \n\ else if (debug) \n\ printf (\"\\n*** gdb_class_getClass returned NULL\\n\"); \n\ return name; \n\ } \n\ "; const char *AppleObjCRuntimeV2::g_summarize_classes_function_name = "__lldb_apple_objc_v2_summarize_classes"; const char *AppleObjCRuntimeV2::g_summarize_classes_function_body = " \n\ \n\ #define NULL (0) \n\ \n\ typedef unsigned int uint32_t; \n\ typedef unsigned long size_t; \n\ \n\ extern \"C\" \n\ { \n\ extern void *memset (void *b, int c, size_t len); \n\ extern char *strncpy (char * s1, const char * s2, size_t n); \n\ \n\ int objc_getClassList(void **buffer, int bufferLen); \n\ const char *class_getName(void *cls); \n\ int printf(const char * format, ...); \n\ } \n\ \n\ // This must be kept in sync with the caller in UpdateISAToDescriptorMapIfNeeded()! \n\ // To get the number of classes, set num_isas_assumed to 0. \n\ \n\ // #define DEBUG \n\ \n\ extern \"C\" uint32_t __lldb_apple_objc_v2_summarize_classes ( \n\ void **isas, \n\ char *names, \n\ uint32_t num_isas_assumed, \n\ uint32_t name_size) \n\ { \n\ #ifdef DEBUG \n\ printf(\"__lldb_apple_objc_v2_summarize_classes(%p, %p, 0x%lx, 0x%lx)\\n\", \n\ isas, names, num_isas_assumed, name_size); \n\ #endif \n\ \n\ if (num_isas_assumed == 0) \n\ return objc_getClassList (NULL, 0); \n\ \n\ memset (isas, 0, num_isas_assumed * sizeof(void*)); \n\ memset (names, 0, num_isas_assumed * name_size); \n\ \n\ int num_isas = objc_getClassList (isas, num_isas_assumed); \n\ \n\ if (num_isas_assumed > num_isas) \n\ num_isas_assumed = num_isas; \n\ \n\ int isa_index; \n\ \n\ for (isa_index = 0; isa_index < num_isas_assumed; ++isa_index) \n\ { \n\ const char *isa_name = class_getName (isas[isa_index]); \n\ #ifdef DEBUG \n\ printf(\"%p -- %s\\n\", isas[isa_index], isa_name); \n\ #endif \n\ if (isa_name) \n\ strncpy (names + (name_size * isa_index), isa_name, name_size); \n\ } \n\ \n\ return num_isas; \n\ } \n\ "; AppleObjCRuntimeV2::AppleObjCRuntimeV2 (Process *process, const ModuleSP &objc_module_sp) : AppleObjCRuntime (process), m_get_class_name_function (), m_get_class_name_code (), m_get_class_name_args (LLDB_INVALID_ADDRESS), m_get_class_name_args_mutex (Mutex::eMutexTypeNormal), m_isas_allocation (LLDB_INVALID_ADDRESS), m_names_allocation (LLDB_INVALID_ADDRESS), m_summarize_classes_args (LLDB_INVALID_ADDRESS), m_summarize_classes_args_mutex (Mutex::eMutexTypeNormal), m_type_vendor_ap (), m_isa_hash_table_ptr (LLDB_INVALID_ADDRESS), m_hash_signature (), m_has_object_getClass (false), m_loaded_objc_opt (false) { 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::RunFunctionToFindClassName(addr_t object_addr, Thread *thread, char *name_dst, size_t max_name_len) { // Since we are going to run code we have to make sure only one thread at a time gets to try this. Mutex::Locker locker(m_get_class_name_args_mutex); StreamString errors; LogSP log(GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); // FIXME - a more appropriate log channel? int32_t debug; if (log && log->GetVerbose()) debug = 1; else debug = 0; ValueList dispatch_values; Value void_ptr_value; ClangASTContext *clang_ast_context = m_process->GetTarget().GetScratchClangASTContext(); clang_type_t clang_void_ptr_type = clang_ast_context->GetVoidPtrType(false); void_ptr_value.SetValueType (Value::eValueTypeScalar); void_ptr_value.SetContext (Value::eContextTypeClangType, clang_void_ptr_type); void_ptr_value.GetScalar() = object_addr; dispatch_values.PushValue (void_ptr_value); Value int_value; clang_type_t clang_int_type = clang_ast_context->GetBuiltinTypeForEncodingAndBitSize(eEncodingSint, 32); int_value.SetValueType (Value::eValueTypeScalar); int_value.SetContext (Value::eContextTypeClangType, clang_int_type); int_value.GetScalar() = debug; dispatch_values.PushValue (int_value); ExecutionContext exe_ctx; thread->CalculateExecutionContext(exe_ctx); Address find_class_name_address; if (!m_get_class_name_code.get()) { m_get_class_name_code.reset (new ClangUtilityFunction (g_find_class_name_function_body, g_find_class_name_function_name)); if (!m_get_class_name_code->Install(errors, exe_ctx)) { if (log) log->Printf ("Failed to install implementation lookup: %s.", errors.GetData()); m_get_class_name_code.reset(); return false; } find_class_name_address.Clear(); find_class_name_address.SetOffset(m_get_class_name_code->StartAddress()); } else { find_class_name_address.Clear(); find_class_name_address.SetOffset(m_get_class_name_code->StartAddress()); } // Next make the runner function for our implementation utility function. if (!m_get_class_name_function.get()) { m_get_class_name_function.reset(new ClangFunction (*m_process, clang_ast_context, clang_void_ptr_type, find_class_name_address, dispatch_values)); errors.Clear(); unsigned num_errors = m_get_class_name_function->CompileFunction(errors); if (num_errors) { if (log) log->Printf ("Error compiling function: \"%s\".", errors.GetData()); return false; } errors.Clear(); if (!m_get_class_name_function->WriteFunctionWrapper(exe_ctx, errors)) { if (log) log->Printf ("Error Inserting function: \"%s\".", errors.GetData()); return false; } } if (m_get_class_name_code.get() == NULL || m_get_class_name_function.get() == NULL) return false; // Finally, write down the arguments, and call the function. Note that we will re-use the same space in the target // for the args. We're locking this to ensure that only one thread at a time gets to call this function, so we don't // have to worry about overwriting the arguments. if (!m_get_class_name_function->WriteFunctionArguments (exe_ctx, m_get_class_name_args, find_class_name_address, dispatch_values, errors)) return false; const bool stop_others = true; const bool try_all_threads = true; const bool unwind_on_error = true; const bool ignore_breakpoints = true; ExecutionResults results = m_get_class_name_function->ExecuteFunction (exe_ctx, &m_get_class_name_args, errors, stop_others, 100000, try_all_threads, unwind_on_error, ignore_breakpoints, void_ptr_value); if (results != eExecutionCompleted) { if (log) log->Printf("Error evaluating our find class name function: %d.\n", results); return false; } addr_t result_ptr = void_ptr_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); Error error; size_t chars_read = m_process->ReadCStringFromMemory (result_ptr, name_dst, max_name_len, error); // If we exhausted our buffer before finding a NULL we're probably off in the weeds somewhere... if (error.Fail() || chars_read == max_name_len) return false; else return true; } bool AppleObjCRuntimeV2::GetDynamicTypeAndAddress (ValueObject &in_value, DynamicValueType use_dynamic, TypeAndOrName &class_type_or_name, Address &address) { // The Runtime is attached to a particular process, you shouldn't pass in a value from another process. assert (in_value.GetProcessSP().get() == m_process); assert (m_process != NULL); // 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); } } if (type_sp) return true; } } return 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) == eAppleObjC_V2) return new AppleObjCRuntimeV2 (process, objc_module_sp); else return NULL; } else return NULL; } void AppleObjCRuntimeV2::Initialize() { PluginManager::RegisterPlugin (pluginName, pluginDesc, CreateInstance); } void AppleObjCRuntimeV2::Terminate() { PluginManager::UnregisterPlugin (CreateInstance); } //------------------------------------------------------------------ // PluginInterface protocol //------------------------------------------------------------------ const char * AppleObjCRuntimeV2::GetPluginName() { return pluginName; } const char * AppleObjCRuntimeV2::GetShortPluginName() { return pluginShort; } 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, Breakpoint::Exact, 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; } ClangUtilityFunction * 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 < sizeof(check_function_code)); return new ClangUtilityFunction(check_function_code, name); } size_t AppleObjCRuntimeV2::GetByteOffsetForIvar (ClangASTType &parent_ast_type, const char *ivar_name) { const char *class_name = parent_ast_type.GetConstTypeName().AsCString(); if (!class_name || *class_name == '\0' || !ivar_name || *ivar_name == '\0') return LLDB_INVALID_IVAR_OFFSET; std::string buffer("OBJC_IVAR_$_"); buffer.append (class_name); buffer.push_back ('.'); buffer.append (ivar_name); ConstString ivar_const_str (buffer.c_str()); SymbolContextList sc_list; Target &target = m_process->GetTarget(); target.GetImages().FindSymbolsWithNameAndType(ivar_const_str, eSymbolTypeObjCIVar, sc_list); SymbolContext ivar_offset_symbol; if (sc_list.GetSize() != 1 || !sc_list.GetContextAtIndex(0, ivar_offset_symbol) || ivar_offset_symbol.symbol == NULL) return LLDB_INVALID_IVAR_OFFSET; addr_t ivar_offset_address = ivar_offset_symbol.symbol->GetAddress().GetLoadAddress (&target); Error error; uint32_t ivar_offset = m_process->ReadUnsignedIntegerFromMemory (ivar_offset_address, 4, LLDB_INVALID_IVAR_OFFSET, error); return ivar_offset; } // tagged pointers are marked by having their least-significant bit // set. this makes them "invalid" as pointers because they violate // the alignment requirements. of course, this detection algorithm // is not accurate (it might become better by incorporating further // knowledge about the internals of tagged pointers) bool AppleObjCRuntimeV2::IsTaggedPointer(addr_t ptr) { return (ptr & 1); } 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) { } 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 actualy 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 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; } 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; } class RemoteObjCOpt { public: RemoteObjCOpt (Process* process, lldb::addr_t load_addr) : m_process(process), m_end_iterator(*this, -1ll), m_load_addr(load_addr), m_classheader_size(sizeof(int32_t) * 2) { lldb::addr_t cursor = load_addr; Error err; // uint32_t version; m_version = m_process->ReadUnsignedIntegerFromMemory(cursor, sizeof(uint32_t), 0, err); cursor += sizeof(uint32_t); if (!IsValid()) return; // int32_t selopt_offset; cursor += sizeof(int32_t); // int32_t headeropt_offset; cursor += sizeof(int32_t); // int32_t clsopt_offset; { Scalar clsopt_offset; m_process->ReadScalarIntegerFromMemory(cursor, sizeof(int32_t), /*is_signed*/ true, clsopt_offset, err); m_clsopt_offset = clsopt_offset.SInt(); cursor += sizeof(int32_t); } m_clsopt_ptr = load_addr + m_clsopt_offset; cursor = m_clsopt_ptr; // uint32_t capacity; m_capacity = m_process->ReadUnsignedIntegerFromMemory(cursor, sizeof(uint32_t), 0, err); cursor += sizeof(uint32_t); // uint32_t occupied; cursor += sizeof(uint32_t); // uint32_t shift; cursor += sizeof(uint32_t); // uint32_t mask; m_mask = m_process->ReadUnsignedIntegerFromMemory(cursor, sizeof(uint32_t), 0, err); cursor += sizeof(uint32_t); // uint32_t zero; m_zero_offset = cursor - m_clsopt_ptr; cursor += sizeof(uint32_t); // uint32_t unused; cursor += sizeof(uint32_t); // uint64_t salt; cursor += sizeof(uint64_t); // uint32_t scramble[256]; cursor += sizeof(uint32_t) * 256; // uint8_t tab[mask+1]; cursor += sizeof(uint8_t) * (m_mask + 1); // uint8_t checkbytes[capacity]; cursor += sizeof(uint8_t) * m_capacity; // int32_t offset[capacity]; cursor += sizeof(int32_t) * m_capacity; // objc_classheader_t clsOffsets[capacity]; m_clsOffsets_ptr = cursor; cursor += (m_classheader_size * m_capacity); // uint32_t duplicateCount; m_duplicateCount = m_process->ReadUnsignedIntegerFromMemory(cursor, sizeof(uint32_t), 0, err); cursor += sizeof(uint32_t); // objc_classheader_t duplicateOffsets[duplicateCount]; m_duplicateOffsets_ptr = cursor; } friend class const_iterator; class const_iterator { public: const_iterator (RemoteObjCOpt &parent, int64_t 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) { 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; } ObjCLanguageRuntime::ObjCISA operator*() const { if (m_index == -1) return 0; Error err; return isaForIndex(err); } private: ObjCLanguageRuntime::ObjCISA isaForIndex(Error &err) const { if (m_index >= m_parent.m_capacity + m_parent.m_duplicateCount) return 0; // index out of range lldb::addr_t classheader_ptr; if (m_index >= m_parent.m_capacity) { // index in the duplicate offsets uint32_t index = (uint32_t)((uint64_t)m_index - (uint64_t)m_parent.m_capacity); classheader_ptr = m_parent.m_duplicateOffsets_ptr + (index * m_parent.m_classheader_size); } else { // index in the offsets uint32_t index = (uint32_t)m_index; classheader_ptr = m_parent.m_clsOffsets_ptr + (index * m_parent.m_classheader_size); } Scalar clsOffset; m_parent.m_process->ReadScalarIntegerFromMemory(classheader_ptr, sizeof(int32_t), /*is_signed*/ true, clsOffset, err); if (!err.Success()) return 0; int32_t clsOffset_int = clsOffset.SInt(); if (clsOffset_int & 0x1) return 0; // not even if (clsOffset_int == m_parent.m_zero_offset) return 0; // == offsetof(objc_clsopt_t, zero) return m_parent.m_clsopt_ptr + (int64_t)clsOffset_int; } void AdvanceToValidIndex () { if (m_index == -1) return; Error err; m_index--; while (m_index >= 0) { ObjCLanguageRuntime::ObjCISA objc_isa = isaForIndex(err); if (objc_isa) return; m_index--; } } RemoteObjCOpt &m_parent; int64_t m_index; }; const_iterator begin () { if (!IsValid()) return m_end_iterator; else return const_iterator(*this, (int64_t)m_capacity + (int64_t)m_duplicateCount); } const_iterator end () { return m_end_iterator; } private: bool IsValid() { return (m_version == 12); } // contents of objc_opt struct uint32_t m_version; int32_t m_clsopt_offset; lldb::addr_t m_clsopt_ptr; // contents of objc_clsopt struct uint32_t m_capacity; uint32_t m_mask; uint32_t m_duplicateCount; lldb::addr_t m_clsOffsets_ptr; lldb::addr_t m_duplicateOffsets_ptr; int32_t m_zero_offset; lldb_private::Process *m_process; const_iterator m_end_iterator; lldb::addr_t m_load_addr; const size_t m_classheader_size; }; class ClassDescriptorV2 : public ObjCLanguageRuntime::ClassDescriptor { public: friend class lldb_private::AppleObjCRuntimeV2; private: // The constructor should only be invoked by the runtime as it builds its caches // or populates them. A ClassDescriptorV2 should only ever exist in a cache. ClassDescriptorV2 (AppleObjCRuntimeV2 &runtime, ObjCLanguageRuntime::ObjCISA isa, const char *name) : m_runtime (runtime), m_objc_class_ptr (isa), m_name (name) { } public: virtual ConstString GetClassName () { if (!m_name) { lldb_private::Process *process = m_runtime.GetProcess(); if (process) { std::auto_ptr objc_class; std::auto_ptr class_ro; std::auto_ptr class_rw; if (!Read_objc_class(process, objc_class)) return m_name; if (!Read_class_row(process, *objc_class, class_ro, class_rw)) return m_name; m_name = ConstString(class_ro->m_name.c_str()); } } return m_name; } virtual ObjCLanguageRuntime::ClassDescriptorSP GetSuperclass () { lldb_private::Process *process = m_runtime.GetProcess(); if (!process) return ObjCLanguageRuntime::ClassDescriptorSP(); std::auto_ptr objc_class; if (!Read_objc_class(process, objc_class)) return ObjCLanguageRuntime::ClassDescriptorSP(); return m_runtime.ObjCLanguageRuntime::GetClassDescriptor(objc_class->m_superclass); } virtual bool IsValid () { return true; // any Objective-C v2 runtime class descriptor we vend is valid } virtual bool IsTagged () { return false; // we use a special class for tagged descriptors } virtual uint64_t GetInstanceSize () { lldb_private::Process *process = m_runtime.GetProcess(); if (process) { std::auto_ptr objc_class; std::auto_ptr class_ro; std::auto_ptr class_rw; if (!Read_objc_class(process, objc_class)) return 0; if (!Read_class_row(process, *objc_class, class_ro, class_rw)) return 0; return class_ro->m_instanceSize; } return 0; } virtual ObjCLanguageRuntime::ObjCISA GetISA () { return m_objc_class_ptr; } virtual bool Describe (std::function const &superclass_func, std::function const &instance_method_func, std::function const &class_method_func, std::function const &ivar_func) { lldb_private::Process *process = m_runtime.GetProcess(); std::auto_ptr objc_class; std::auto_ptr class_ro; std::auto_ptr class_rw; if (!Read_objc_class(process, objc_class)) return 0; if (!Read_class_row(process, *objc_class, class_ro, class_rw)) return 0; static ConstString NSObject_name("NSObject"); if (m_name != NSObject_name && superclass_func) superclass_func(objc_class->m_superclass); if (instance_method_func) { std::auto_ptr base_method_list; base_method_list.reset(new method_list_t); if (!base_method_list->Read(process, class_ro->m_baseMethods_ptr)) return false; if (base_method_list->m_entsize != method_t::GetSize(process)) return false; std::auto_ptr method; method.reset(new method_t); for (uint32_t i = 0, e = base_method_list->m_count; i < e; ++i) { method->Read(process, base_method_list->m_first_ptr + (i * base_method_list->m_entsize)); if (instance_method_func(method->m_name.c_str(), method->m_types.c_str())) break; } } if (class_method_func) { ClassDescriptorV2 metaclass(m_runtime, objc_class->m_isa, NULL); // The metaclass is not in the cache // We don't care about the metaclass's superclass, or its class methods. Its instance methods are // our class methods. metaclass.Describe(std::function (nullptr), class_method_func, std::function (nullptr), std::function (nullptr)); } if (ivar_func) { std::auto_ptr ivar_list; ivar_list.reset(new ivar_list_t); if (!ivar_list->Read(process, class_ro->m_ivars_ptr)) return false; if (ivar_list->m_entsize != ivar_t::GetSize(process)) return false; std::auto_ptr ivar; ivar.reset(new ivar_t); for (uint32_t i = 0, e = ivar_list->m_count; i < e; ++i) { ivar->Read(process, ivar_list->m_first_ptr + (i * ivar_list->m_entsize)); if (ivar_func(ivar->m_name.c_str(), ivar->m_type.c_str(), ivar->m_offset_ptr, ivar->m_size)) break; } } return true; } virtual ~ClassDescriptorV2 () { } private: static const uint32_t RW_REALIZED = (1 << 31); struct objc_class_t { ObjCLanguageRuntime::ObjCISA m_isa; // The class's metaclass. ObjCLanguageRuntime::ObjCISA m_superclass; lldb::addr_t m_cache_ptr; lldb::addr_t m_vtable_ptr; lldb::addr_t m_data_ptr; uint8_t m_flags; objc_class_t () : m_isa (0), m_superclass (0), m_cache_ptr (0), m_vtable_ptr (0), m_data_ptr (0), m_flags (0) { } void Clear() { m_isa = 0; m_superclass = 0; m_cache_ptr = 0; m_vtable_ptr = 0; m_data_ptr = 0; m_flags = 0; } bool Read(Process *process, lldb::addr_t addr) { size_t ptr_size = process->GetAddressByteSize(); size_t objc_class_size = ptr_size // uintptr_t isa; + ptr_size // Class superclass; + ptr_size // void *cache; + ptr_size // IMP *vtable; + ptr_size; // uintptr_t data_NEVER_USE; DataBufferHeap objc_class_buf (objc_class_size, '\0'); Error error; process->ReadMemory(addr, objc_class_buf.GetBytes(), objc_class_size, error); if (error.Fail()) { return false; } DataExtractor extractor(objc_class_buf.GetBytes(), objc_class_size, process->GetByteOrder(), process->GetAddressByteSize()); uint32_t cursor = 0; m_isa = extractor.GetAddress_unchecked(&cursor); // uintptr_t isa; m_superclass = extractor.GetAddress_unchecked(&cursor); // Class superclass; m_cache_ptr = extractor.GetAddress_unchecked(&cursor); // void *cache; m_vtable_ptr = extractor.GetAddress_unchecked(&cursor); // IMP *vtable; lldb::addr_t data_NEVER_USE = extractor.GetAddress_unchecked(&cursor); // uintptr_t data_NEVER_USE; m_flags = (uint8_t)(data_NEVER_USE & (lldb::addr_t)3); m_data_ptr = data_NEVER_USE & ~(lldb::addr_t)3; return true; } }; struct class_ro_t { uint32_t m_flags; uint32_t m_instanceStart; uint32_t m_instanceSize; uint32_t m_reserved; lldb::addr_t m_ivarLayout_ptr; lldb::addr_t m_name_ptr; lldb::addr_t m_baseMethods_ptr; lldb::addr_t m_baseProtocols_ptr; lldb::addr_t m_ivars_ptr; lldb::addr_t m_weakIvarLayout_ptr; lldb::addr_t m_baseProperties_ptr; std::string m_name; bool Read(Process *process, lldb::addr_t addr) { size_t ptr_size = process->GetAddressByteSize(); size_t size = sizeof(uint32_t) // uint32_t flags; + sizeof(uint32_t) // uint32_t instanceStart; + sizeof(uint32_t) // uint32_t instanceSize; + (ptr_size == 8 ? sizeof(uint32_t) : 0) // uint32_t reserved; // __LP64__ only + ptr_size // const uint8_t *ivarLayout; + ptr_size // const char *name; + ptr_size // const method_list_t *baseMethods; + ptr_size // const protocol_list_t *baseProtocols; + ptr_size // const ivar_list_t *ivars; + ptr_size // const uint8_t *weakIvarLayout; + ptr_size; // const property_list_t *baseProperties; DataBufferHeap buffer (size, '\0'); Error error; process->ReadMemory(addr, buffer.GetBytes(), size, error); if (error.Fail()) { return false; } DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), process->GetAddressByteSize()); uint32_t cursor = 0; m_flags = extractor.GetU32_unchecked(&cursor); m_instanceStart = extractor.GetU32_unchecked(&cursor); m_instanceSize = extractor.GetU32_unchecked(&cursor); if (ptr_size == 8) m_reserved = extractor.GetU32_unchecked(&cursor); else m_reserved = 0; m_ivarLayout_ptr = extractor.GetAddress_unchecked(&cursor); m_name_ptr = extractor.GetAddress_unchecked(&cursor); m_baseMethods_ptr = extractor.GetAddress_unchecked(&cursor); m_baseProtocols_ptr = extractor.GetAddress_unchecked(&cursor); m_ivars_ptr = extractor.GetAddress_unchecked(&cursor); m_weakIvarLayout_ptr = extractor.GetAddress_unchecked(&cursor); m_baseProperties_ptr = extractor.GetAddress_unchecked(&cursor); DataBufferHeap name_buf(1024, '\0'); process->ReadCStringFromMemory(m_name_ptr, (char*)name_buf.GetBytes(), name_buf.GetByteSize(), error); if (error.Fail()) { return false; } m_name.assign((char*)name_buf.GetBytes()); return true; } }; struct class_rw_t { uint32_t m_flags; uint32_t m_version; lldb::addr_t m_ro_ptr; union { lldb::addr_t m_method_list_ptr; lldb::addr_t m_method_lists_ptr; }; lldb::addr_t m_properties_ptr; lldb::addr_t m_protocols_ptr; ObjCLanguageRuntime::ObjCISA m_firstSubclass; ObjCLanguageRuntime::ObjCISA m_nextSiblingClass; bool Read(Process *process, lldb::addr_t addr) { size_t ptr_size = process->GetAddressByteSize(); size_t size = sizeof(uint32_t) // uint32_t flags; + sizeof(uint32_t) // uint32_t version; + ptr_size // const class_ro_t *ro; + ptr_size // union { method_list_t **method_lists; method_list_t *method_list; }; + ptr_size // struct chained_property_list *properties; + ptr_size // const protocol_list_t **protocols; + ptr_size // Class firstSubclass; + ptr_size; // Class nextSiblingClass; DataBufferHeap buffer (size, '\0'); Error error; process->ReadMemory(addr, buffer.GetBytes(), size, error); if (error.Fail()) { return false; } DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), process->GetAddressByteSize()); uint32_t cursor = 0; m_flags = extractor.GetU32_unchecked(&cursor); m_version = extractor.GetU32_unchecked(&cursor); m_ro_ptr = extractor.GetAddress_unchecked(&cursor); m_method_list_ptr = extractor.GetAddress_unchecked(&cursor); m_properties_ptr = extractor.GetAddress_unchecked(&cursor); m_firstSubclass = extractor.GetAddress_unchecked(&cursor); m_nextSiblingClass = extractor.GetAddress_unchecked(&cursor); return true; } }; struct method_list_t { uint32_t m_entsize; uint32_t m_count; lldb::addr_t m_first_ptr; bool Read(Process *process, lldb::addr_t addr) { size_t size = sizeof(uint32_t) // uint32_t entsize_NEVER_USE; + sizeof(uint32_t); // uint32_t count; DataBufferHeap buffer (size, '\0'); Error error; process->ReadMemory(addr, buffer.GetBytes(), size, error); if (error.Fail()) { return false; } DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), process->GetAddressByteSize()); uint32_t cursor = 0; m_entsize = extractor.GetU32_unchecked(&cursor) & ~(uint32_t)3; m_count = extractor.GetU32_unchecked(&cursor); m_first_ptr = addr + cursor; return true; } }; struct method_t { lldb::addr_t m_name_ptr; lldb::addr_t m_types_ptr; lldb::addr_t m_imp_ptr; std::string m_name; std::string m_types; static size_t GetSize(Process *process) { size_t ptr_size = process->GetAddressByteSize(); return ptr_size // SEL name; + ptr_size // const char *types; + ptr_size; // IMP imp; } bool Read(Process *process, lldb::addr_t addr) { size_t size = GetSize(process); DataBufferHeap buffer (size, '\0'); Error error; process->ReadMemory(addr, buffer.GetBytes(), size, error); if (error.Fail()) { return false; } DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), process->GetAddressByteSize()); uint32_t cursor = 0; m_name_ptr = extractor.GetAddress_unchecked(&cursor); m_types_ptr = extractor.GetAddress_unchecked(&cursor); m_imp_ptr = extractor.GetAddress_unchecked(&cursor); const size_t buffer_size = 1024; size_t count; DataBufferHeap string_buf(buffer_size, 0); count = process->ReadCStringFromMemory(m_name_ptr, (char*)string_buf.GetBytes(), buffer_size, error); m_name.assign((char*)string_buf.GetBytes(), count); count = process->ReadCStringFromMemory(m_types_ptr, (char*)string_buf.GetBytes(), buffer_size, error); m_types.assign((char*)string_buf.GetBytes(), count); return true; } }; struct ivar_list_t { uint32_t m_entsize; uint32_t m_count; lldb::addr_t m_first_ptr; bool Read(Process *process, lldb::addr_t addr) { size_t size = sizeof(uint32_t) // uint32_t entsize; + sizeof(uint32_t); // uint32_t count; DataBufferHeap buffer (size, '\0'); Error error; process->ReadMemory(addr, buffer.GetBytes(), size, error); if (error.Fail()) { return false; } DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), process->GetAddressByteSize()); uint32_t cursor = 0; m_entsize = extractor.GetU32_unchecked(&cursor); m_count = extractor.GetU32_unchecked(&cursor); m_first_ptr = addr + cursor; return true; } }; struct ivar_t { lldb::addr_t m_offset_ptr; lldb::addr_t m_name_ptr; lldb::addr_t m_type_ptr; uint32_t m_alignment; uint32_t m_size; std::string m_name; std::string m_type; static size_t GetSize(Process *process) { size_t ptr_size = process->GetAddressByteSize(); return ptr_size // uintptr_t *offset; + ptr_size // const char *name; + ptr_size // const char *type; + sizeof(uint32_t) // uint32_t alignment; + sizeof(uint32_t); // uint32_t size; } bool Read(Process *process, lldb::addr_t addr) { size_t size = GetSize(process); DataBufferHeap buffer (size, '\0'); Error error; process->ReadMemory(addr, buffer.GetBytes(), size, error); if (error.Fail()) { return false; } DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), process->GetAddressByteSize()); uint32_t cursor = 0; m_offset_ptr = extractor.GetAddress_unchecked(&cursor); m_name_ptr = extractor.GetAddress_unchecked(&cursor); m_type_ptr = extractor.GetAddress_unchecked(&cursor); m_alignment = extractor.GetU32_unchecked(&cursor); m_size = extractor.GetU32_unchecked(&cursor); const size_t buffer_size = 1024; size_t count; DataBufferHeap string_buf(buffer_size, 0); count = process->ReadCStringFromMemory(m_name_ptr, (char*)string_buf.GetBytes(), buffer_size, error); m_name.assign((char*)string_buf.GetBytes(), count); count = process->ReadCStringFromMemory(m_type_ptr, (char*)string_buf.GetBytes(), buffer_size, error); m_type.assign((char*)string_buf.GetBytes(), count); return true; } }; bool Read_objc_class (Process* process, std::auto_ptr &objc_class) { objc_class.reset(new objc_class_t); bool ret = objc_class->Read (process, m_objc_class_ptr); if (!ret) objc_class.reset(); return ret; } bool Read_class_row (Process* process, const objc_class_t &objc_class, std::auto_ptr &class_ro, std::auto_ptr &class_rw) { class_ro.reset(); class_rw.reset(); Error error; uint32_t class_row_t_flags = process->ReadUnsignedIntegerFromMemory(objc_class.m_data_ptr, sizeof(uint32_t), 0, error); if (!error.Success()) return false; if (class_row_t_flags & RW_REALIZED) { class_rw.reset(new class_rw_t); if (!class_rw->Read(process, objc_class.m_data_ptr)) { class_rw.reset(); return false; } class_ro.reset(new class_ro_t); if (!class_ro->Read(process, class_rw->m_ro_ptr)) { class_rw.reset(); class_ro.reset(); return false; } } else { class_ro.reset(new class_ro_t); if (!class_ro->Read(process, objc_class.m_data_ptr)) { class_ro.reset(); return false; } } return true; } AppleObjCRuntimeV2 &m_runtime; // The runtime, so we can read information lazily. lldb::addr_t m_objc_class_ptr; // The address of the objc_class_t. (I.e., objects of this class type have this as their ISA) ConstString m_name; // May be NULL }; class ClassDescriptorV2Tagged : public ObjCLanguageRuntime::ClassDescriptor { public: ClassDescriptorV2Tagged (ValueObject &isa_pointer) { m_valid = false; uint64_t value = isa_pointer.GetValueAsUnsigned(0); ProcessSP process_sp = isa_pointer.GetProcessSP(); if (process_sp) m_pointer_size = process_sp->GetAddressByteSize(); else { m_name = ConstString(""); m_pointer_size = 0; return; } m_valid = true; m_class_bits = (value & 0xE) >> 1; lldb::TargetSP target_sp = isa_pointer.GetTargetSP(); LazyBool is_lion = IsLion(target_sp); // TODO: check for OSX version - for now assume Mtn Lion if (is_lion == eLazyBoolCalculate) { // if we can't determine the matching table (e.g. we have no Foundation), // assume this is not a valid tagged pointer m_valid = false; } else if (is_lion == eLazyBoolNo) { switch (m_class_bits) { case 0: m_name = ConstString("NSAtom"); break; case 3: m_name = ConstString("NSNumber"); break; case 4: m_name = ConstString("NSDateTS"); break; case 5: m_name = ConstString("NSManagedObject"); break; case 6: m_name = ConstString("NSDate"); break; default: m_valid = false; break; } } else { switch (m_class_bits) { case 1: m_name = ConstString("NSNumber"); break; case 5: m_name = ConstString("NSManagedObject"); break; case 6: m_name = ConstString("NSDate"); break; case 7: m_name = ConstString("NSDateTS"); break; default: m_valid = false; break; } } if (!m_valid) m_name = ConstString(""); else { m_info_bits = (value & 0xF0ULL) >> 4; m_value_bits = (value & ~0x0000000000000000FFULL) >> 8; } } virtual ConstString GetClassName () { return m_name; } virtual ObjCLanguageRuntime::ClassDescriptorSP GetSuperclass () { // tagged pointers can represent a class that has a superclass, but since that information is not // stored in the object itself, we would have to query the runtime to discover the hierarchy // for the time being, we skip this step in the interest of static discovery return ObjCLanguageRuntime::ClassDescriptorSP(new ObjCLanguageRuntime::ClassDescriptor_Invalid()); } virtual bool IsValid () { return m_valid; } virtual bool IsKVO () { return false; // tagged pointers are not KVO'ed } virtual bool IsCFType () { return false; // tagged pointers are not CF objects } virtual bool IsTagged () { return true; // we use this class to describe tagged pointers } virtual uint64_t GetInstanceSize () { return (IsValid() ? m_pointer_size : 0); } virtual ObjCLanguageRuntime::ObjCISA GetISA () { return 0; // tagged pointers have no ISA } virtual uint64_t GetClassBits () { return (IsValid() ? m_class_bits : 0); } // these calls are not part of any formal tagged pointers specification virtual uint64_t GetValueBits () { return (IsValid() ? m_value_bits : 0); } virtual uint64_t GetInfoBits () { return (IsValid() ? m_info_bits : 0); } virtual ~ClassDescriptorV2Tagged () {} protected: // TODO make this into a smarter OS version detector LazyBool IsLion (lldb::TargetSP &target_sp) { if (!target_sp) return eLazyBoolCalculate; const ModuleList& modules = target_sp->GetImages(); for (uint32_t idx = 0; idx < modules.GetSize(); idx++) { lldb::ModuleSP module_sp = modules.GetModuleAtIndex(idx); if (!module_sp) continue; if (strcmp(module_sp->GetFileSpec().GetFilename().AsCString(""),"Foundation") == 0) { uint32_t major = UINT32_MAX; module_sp->GetVersion(&major,1); if (major == UINT32_MAX) return eLazyBoolCalculate; return (major > 900 ? eLazyBoolNo : eLazyBoolYes); } } return eLazyBoolCalculate; } private: ConstString m_name; uint8_t m_pointer_size; bool m_valid; uint64_t m_class_bits; uint64_t m_info_bits; uint64_t m_value_bits; }; ObjCLanguageRuntime::ClassDescriptorSP AppleObjCRuntimeV2::GetClassDescriptor (ValueObject& valobj) { ClassDescriptorSP objc_class_sp; // 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.GetValue().GetContextType() != Value::eContextTypeInvalid) { addr_t isa_pointer = valobj.GetPointerValue(); // tagged pointer if (IsTaggedPointer(isa_pointer)) { objc_class_sp.reset (new ClassDescriptorV2Tagged(valobj)); // probably an invalid tagged pointer - say it's wrong if (objc_class_sp->IsValid()) return objc_class_sp; else objc_class_sp.reset(); } 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 = ObjCLanguageRuntime::GetClassDescriptor (isa); if (isa && !objc_class_sp) { lldb::LogSP 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()); static ConstString g_gdb_objc_realized_classes("gdb_objc_realized_classes"); const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType(g_gdb_objc_realized_classes, lldb::eSymbolTypeData); if (symbol) { lldb::addr_t gdb_objc_realized_classes_ptr = symbol->GetAddress().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; } void AppleObjCRuntimeV2::UpdateISAToDescriptorMapIfNeeded() { Timer scoped_timer (__PRETTY_FUNCTION__, __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, wether it was successful or not. m_isa_to_descriptor_cache_stop_id = process->GetStopID(); if (!m_hash_signature.NeedsUpdate(process, this, hash_table)) return; m_hash_signature.UpdateSignature (hash_table); do { Mutex::Locker locker(m_summarize_classes_args_mutex); lldb::LogSP log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); ExecutionContext exe_ctx; ThreadSP thread_sp = process->GetThreadList().GetSelectedThread(); if (!thread_sp) break; thread_sp->CalculateExecutionContext(exe_ctx); ClangASTContext *clang_ast_context = process->GetTarget().GetScratchClangASTContext(); if (!clang_ast_context) break; Address summarize_classes_address; StreamString errors; if (!m_summarize_classes_code.get()) { m_summarize_classes_code.reset (new ClangUtilityFunction (g_summarize_classes_function_body, g_summarize_classes_function_name)); errors.Clear(); if (!m_summarize_classes_code->Install(errors, exe_ctx)) { if (log) log->Printf ("Failed to install implementation lookup: %s.", errors.GetData()); m_summarize_classes_code.reset(); break; } summarize_classes_address.Clear(); summarize_classes_address.SetOffset(m_summarize_classes_code->StartAddress()); } else { summarize_classes_address.Clear(); summarize_classes_address.SetOffset(m_summarize_classes_code->StartAddress()); } const uint32_t name_size = 32; Error err; if (m_isas_allocation != LLDB_INVALID_ADDRESS) process->DeallocateMemory(m_isas_allocation); if (m_names_allocation != LLDB_INVALID_ADDRESS) process->DeallocateMemory(m_names_allocation); // This must be kept in sync with the definition in g_summarize_classes_function_body! clang_type_t clang_uint32_t_type = clang_ast_context->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); clang_type_t clang_void_pointer_type = clang_ast_context->CreatePointerType(clang_ast_context->GetBuiltInType_void()); Value isas_value; isas_value.SetValueType (Value::eValueTypeScalar); isas_value.SetContext (Value::eContextTypeClangType, clang_void_pointer_type); isas_value.GetScalar() = 0; Value names_value; names_value.SetValueType (Value::eValueTypeScalar); names_value.SetContext (Value::eContextTypeClangType, clang_void_pointer_type); names_value.GetScalar() = 0; Value num_isas_assumed_value; num_isas_assumed_value.SetValueType (Value::eValueTypeScalar); num_isas_assumed_value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); num_isas_assumed_value.GetScalar() = 0; Value name_size_value; name_size_value.SetValueType (Value::eValueTypeScalar); name_size_value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); name_size_value.GetScalar() = 0; ValueList dispatch_values; dispatch_values.PushValue (isas_value); dispatch_values.PushValue (names_value); dispatch_values.PushValue (num_isas_assumed_value); dispatch_values.PushValue (name_size_value); // Next make the runner function for our implementation utility function. if (!m_summarize_classes_function.get()) { m_summarize_classes_function.reset(new ClangFunction (*m_process, clang_ast_context, clang_uint32_t_type, summarize_classes_address, dispatch_values)); errors.Clear(); unsigned num_errors = m_summarize_classes_function->CompileFunction(errors); if (num_errors) { if (log) log->Printf ("Error compiling function: \"%s\".", errors.GetData()); break; } errors.Clear(); if (!m_summarize_classes_function->WriteFunctionWrapper(exe_ctx, errors)) { if (log) log->Printf ("Error Inserting function: \"%s\".", errors.GetData()); break; } } if (m_summarize_classes_code.get() == NULL || m_summarize_classes_function.get() == NULL) break; // Write the initial arguments: (NULL, NULL, 0, 0). The return value will be the // new value of num_isas. { errors.Clear(); if (!m_summarize_classes_function->WriteFunctionArguments (exe_ctx, m_summarize_classes_args, summarize_classes_address, dispatch_values, errors)) { if (log) log->Printf ("Error writing function arguments: \"%s\".", errors.GetData()); break; } } bool stop_others = true; bool try_all_threads = false; bool unwind_on_error = true; bool ignore_breakpoints = true; Value num_isas_value; num_isas_value.SetValueType (Value::eValueTypeScalar); num_isas_value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); num_isas_value.GetScalar() = 0; errors.Clear(); ExecutionResults results = m_summarize_classes_function->ExecuteFunction (exe_ctx, &m_summarize_classes_args, errors, stop_others, 100000, try_all_threads, unwind_on_error, ignore_breakpoints, num_isas_value); if (results != eExecutionCompleted) { if (log) log->Printf("Error evaluating our find class name function: %s.\n", errors.GetData()); break; } // With the result from that, allocate real buffers. uint32_t num_isas = num_isas_value.GetScalar().ULong(); if (log) log->Printf("Fast class summary mode reports %u isas\n", num_isas); uint32_t isas_allocation_size = num_isas * process->GetAddressByteSize(); uint32_t names_allocation_size = num_isas * name_size; if (m_isas_allocation == LLDB_INVALID_ADDRESS) { m_isas_allocation = process->AllocateMemory(isas_allocation_size, ePermissionsReadable | ePermissionsWritable, err); if (m_isas_allocation == LLDB_INVALID_ADDRESS) break; } if (m_names_allocation == LLDB_INVALID_ADDRESS) { m_names_allocation = process->AllocateMemory(names_allocation_size, ePermissionsReadable | ePermissionsWritable, err); if (m_names_allocation == LLDB_INVALID_ADDRESS) break; } // Write the final arguments. dispatch_values.Clear(); isas_value.GetScalar() = m_isas_allocation; names_value.GetScalar() = m_names_allocation; num_isas_assumed_value.GetScalar() = num_isas; name_size_value.GetScalar() = name_size; dispatch_values.PushValue (isas_value); dispatch_values.PushValue (names_value); dispatch_values.PushValue (num_isas_assumed_value); dispatch_values.PushValue (name_size_value); { errors.Clear(); if (!m_summarize_classes_function->WriteFunctionArguments (exe_ctx, m_summarize_classes_args, summarize_classes_address, dispatch_values, errors)) { if (log) log->Printf ("Error writing function arguments: \"%s\".", errors.GetData()); break; } } errors.Clear(); results = m_summarize_classes_function->ExecuteFunction (exe_ctx, &m_summarize_classes_args, errors, stop_others, 100000, try_all_threads, unwind_on_error, ignore_breakpoints, num_isas_value); if (results != eExecutionCompleted) { if (log) log->Printf("Error evaluating our find class name function: %s.\n", errors.GetData()); break; } DataBufferHeap isas_buffer(isas_allocation_size, 0); DataBufferHeap names_buffer(names_allocation_size, 0); if (process->ReadMemory(m_isas_allocation, isas_buffer.GetBytes(), isas_allocation_size, err) != isas_allocation_size) break; if (process->ReadMemory(m_names_allocation, names_buffer.GetBytes(), names_allocation_size, err) != names_allocation_size) break; DataExtractor isa_extractor(isas_buffer.GetBytes(), isas_allocation_size, process->GetByteOrder(), process->GetAddressByteSize()); uint32_t offset_ptr = 0; for (size_t index = 0; index < num_isas; ++index) { uint64_t isa = isa_extractor.GetPointer(&offset_ptr); const char *name = (const char*)(names_buffer.GetBytes() + (name_size * index)); if (log && log->GetVerbose()) log->Printf("Fast class summary mode found isa 0x%llx (%s)", isa, name); // The name can only be relied upon if it is NULL-terminated. // Otherwise it ran off its allocation and has been partially overwritten by the next name. ClassDescriptorSP descriptor_sp; if (name[name_size - 1] == '\0') descriptor_sp.reset(new ClassDescriptorV2(*this, isa, name)); else descriptor_sp.reset(new ClassDescriptorV2(*this, isa, NULL)); m_isa_to_descriptor_cache[isa] = descriptor_sp; } return; } while(0); do { lldb::LogSP log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); uint32_t num_map_table_isas = 0; uint32_t num_objc_opt_ro_isas = 0; ModuleSP objc_module_sp(GetObjCModule()); if (objc_module_sp) { for (RemoteNXMapTable::element elt : hash_table) { ++num_map_table_isas; if (m_isa_to_descriptor_cache.count(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()); m_isa_to_descriptor_cache[elt.second] = descriptor_sp; } if (!m_loaded_objc_opt) { m_loaded_objc_opt = true; ObjectFile *objc_object = objc_module_sp->GetObjectFile(); if (objc_object) { SectionList *section_list = objc_object->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) { lldb::addr_t objc_opt_ptr = objc_opt_section_sp->GetLoadBaseAddress(&process->GetTarget()); if (objc_opt_ptr != LLDB_INVALID_ADDRESS) { RemoteObjCOpt objc_opt(process, objc_opt_ptr); for (ObjCLanguageRuntime::ObjCISA objc_isa : objc_opt) { ++num_objc_opt_ro_isas; if (m_isa_to_descriptor_cache.count(objc_isa)) continue; ClassDescriptorSP descriptor_sp = ClassDescriptorSP(new ClassDescriptorV2(*this, objc_isa, NULL)); if (log && log->GetVerbose()) log->Printf("AppleObjCRuntimeV2 added (ObjCISA)0x%" PRIx64 " (%s) from static table to isa->descriptor cache", objc_isa, descriptor_sp->GetClassName().AsCString()); m_isa_to_descriptor_cache[objc_isa] = descriptor_sp; } } } } } } } } } while (0); } else { m_isa_to_descriptor_cache_stop_id = UINT32_MAX; } } // 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); } TypeVendor * AppleObjCRuntimeV2::GetTypeVendor() { if (!m_type_vendor_ap.get()) m_type_vendor_ap.reset(new AppleObjCTypeVendor(*this)); return m_type_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_$_"); if (name_strref.startswith(ivar_prefix)) { llvm::StringRef ivar_skipped_prefix = name_strref.substr(ivar_prefix.size()); std::pair 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::GetClassDescriptor(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(nullptr), std::function(nullptr), std::function(nullptr), ivar_func); } } } } return ret; }