| //===-- 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 <string> |
| #include <vector> |
| #include <stdint.h> |
| |
| #include "lldb/lldb-enumerations.h" |
| #include "lldb/Core/ClangForward.h" |
| #include "lldb/Symbol/ClangASTType.h" |
| |
| #include "lldb/Core/ClangForward.h" |
| #include "lldb/Core/ConstString.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/ValueObjectVariable.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/Symbol/VariableList.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 "AppleObjCClassDescriptorV2.h" |
| #include "AppleObjCTypeEncodingParser.h" |
| #include "AppleObjCDeclVendor.h" |
| #include "AppleObjCTrampolineHandler.h" |
| |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/DeclObjC.h" |
| |
| #include <vector> |
| |
| 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 ENABLE_DEBUG_PRINTF // COMMENT THIS LINE OUT PRIOR TO CHECKIN |
| #ifdef ENABLE_DEBUG_PRINTF |
| #define DEBUG_PRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__) |
| #else |
| #define DEBUG_PRINTF(fmt, ...) |
| #endif |
| |
| 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) |
| { |
| 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 ENABLE_DEBUG_PRINTF // COMMENT THIS LINE OUT PRIOR TO CHECKIN |
| #ifdef ENABLE_DEBUG_PRINTF |
| #define DEBUG_PRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__) |
| #else |
| #define DEBUG_PRINTF(fmt, ...) |
| #endif |
| |
| |
| 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 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 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 (%" PRIu64 " class infos)\n", class_infos_byte_size, (size_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; |
| 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) |
| { |
| const objc_clsopt_t* 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); |
| ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; |
| int32_t zeroOffset = 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); |
| for (uint32_t i=0; i<clsopt->capacity; ++i) |
| { |
| const int32_t clsOffset = classOffsets[i].clsOffset; |
| if (clsOffset & 1) |
| continue; // duplicate |
| else if (clsOffset == zeroOffset) |
| continue; // zero 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; |
| } |
| |
| 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 == zeroOffset) |
| continue; // zero 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) |
| { |
| lldb::addr_t symbol_load_addr = symbol->GetAddress().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_function(), |
| m_get_class_info_code(), |
| m_get_class_info_args (LLDB_INVALID_ADDRESS), |
| m_get_class_info_args_mutex (Mutex::eMutexTypeNormal), |
| m_get_shared_cache_class_info_function(), |
| 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 (Mutex::eMutexTypeNormal), |
| 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(TaggedPointerVendor::CreateInstance(*this,objc_module_sp)), |
| m_encoding_to_type_sp() |
| { |
| static const ConstString g_gdb_object_getClass("gdb_object_getClass"); |
| m_has_object_getClass = (objc_module_sp->FindFirstSymbolWithNameAndType(g_gdb_object_getClass, eSymbolTypeCode) != NULL); |
| } |
| |
| AppleObjCRuntimeV2::~AppleObjCRuntimeV2() |
| { |
| } |
| |
| 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); |
| |
| class_type_or_name.Clear(); |
| |
| // 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 ClangASTType 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.SetClangASTType(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) == eAppleObjC_V2) |
| return new AppleObjCRuntimeV2 (process, objc_module_sp); |
| else |
| return NULL; |
| } |
| else |
| return NULL; |
| } |
| |
| void |
| AppleObjCRuntimeV2::Initialize() |
| { |
| PluginManager::RegisterPlugin (GetPluginNameStatic(), |
| "Apple Objective C Language Runtime - Version 2", |
| CreateInstance); |
| } |
| |
| 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, |
| 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 < (int)sizeof(check_function_code)); |
| |
| return new ClangUtilityFunction(check_function_code, name); |
| } |
| |
| size_t |
| AppleObjCRuntimeV2::GetByteOffsetForIvar (ClangASTType &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->GetAddress().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.GetClangType().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::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; |
| } |
| |
| bool |
| AppleObjCRuntimeV2::UpdateISAToDescriptorMapDynamic(RemoteNXMapTable &hash_table) |
| { |
| Process *process = GetProcess(); |
| |
| if (process == NULL) |
| return false; |
| |
| Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); |
| |
| ExecutionContext exe_ctx; |
| |
| ThreadSP thread_sp = process->GetThreadList().GetSelectedThread(); |
| |
| if (!thread_sp) |
| return false; |
| |
| thread_sp->CalculateExecutionContext(exe_ctx); |
| ClangASTContext *ast = process->GetTarget().GetScratchClangASTContext(); |
| |
| if (!ast) |
| return false; |
| |
| Address function_address; |
| |
| StreamString errors; |
| |
| 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 false; |
| } |
| |
| // Make some types for our arguments |
| ClangASTType clang_uint32_t_type = ast->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); |
| ClangASTType clang_void_pointer_type = ast->GetBasicType(eBasicTypeVoid).GetPointerType(); |
| |
| if (!m_get_class_info_code.get()) |
| { |
| m_get_class_info_code.reset (new ClangUtilityFunction (g_get_dynamic_class_info_body, |
| g_get_dynamic_class_info_name)); |
| |
| errors.Clear(); |
| |
| if (!m_get_class_info_code->Install(errors, exe_ctx)) |
| { |
| if (log) |
| log->Printf ("Failed to install implementation lookup: %s.", errors.GetData()); |
| m_get_class_info_code.reset(); |
| } |
| } |
| |
| if (m_get_class_info_code.get()) |
| function_address.SetOffset(m_get_class_info_code->StartAddress()); |
| else |
| return false; |
| |
| ValueList arguments; |
| |
| // Next make the runner function for our implementation utility function. |
| if (!m_get_class_info_function.get()) |
| { |
| Value value; |
| value.SetValueType (Value::eValueTypeScalar); |
| // value.SetContext (Value::eContextTypeClangType, clang_void_pointer_type); |
| value.SetClangType (clang_void_pointer_type); |
| arguments.PushValue (value); |
| arguments.PushValue (value); |
| |
| value.SetValueType (Value::eValueTypeScalar); |
| // value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); |
| value.SetClangType (clang_uint32_t_type); |
| arguments.PushValue (value); |
| |
| m_get_class_info_function.reset(new ClangFunction (*m_process, |
| clang_uint32_t_type, |
| function_address, |
| arguments, |
| "objc-v2-isa-to-descriptor")); |
| |
| if (m_get_class_info_function.get() == NULL) |
| return false; |
| |
| errors.Clear(); |
| |
| unsigned num_errors = m_get_class_info_function->CompileFunction(errors); |
| if (num_errors) |
| { |
| if (log) |
| log->Printf ("Error compiling function: \"%s\".", errors.GetData()); |
| return false; |
| } |
| |
| errors.Clear(); |
| |
| if (!m_get_class_info_function->WriteFunctionWrapper(exe_ctx, errors)) |
| { |
| if (log) |
| log->Printf ("Error Inserting function: \"%s\".", errors.GetData()); |
| return false; |
| } |
| } |
| else |
| { |
| arguments = m_get_class_info_function->GetArgumentValues (); |
| } |
| |
| 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) |
| return false; |
| |
| Mutex::Locker locker(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; |
| |
| bool success = false; |
| |
| errors.Clear(); |
| |
| // Write our function arguments into the process so we can run our function |
| if (m_get_class_info_function->WriteFunctionArguments (exe_ctx, |
| m_get_class_info_args, |
| function_address, |
| arguments, |
| errors)) |
| { |
| 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.SetClangType (clang_uint32_t_type); |
| return_value.GetScalar() = 0; |
| |
| errors.Clear(); |
| |
| // Run the function |
| ExpressionResults results = m_get_class_info_function->ExecuteFunction (exe_ctx, |
| &m_get_class_info_args, |
| options, |
| errors, |
| return_value); |
| |
| if (results == eExpressionCompleted) |
| { |
| // The result is the number of ClassInfo structures that were filled in |
| uint32_t 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: %s.\n", errors.GetData()); |
| } |
| } |
| else |
| { |
| if (log) |
| log->Printf ("Error writing function arguments: \"%s\".", errors.GetData()); |
| } |
| |
| // Deallocate the memory we allocated for the ClassInfo array |
| process->DeallocateMemory(class_infos_addr); |
| |
| return success; |
| } |
| |
| void |
| 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(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); |
| |
| // 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)) |
| { |
| 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); |
| if (log && log->GetVerbose()) |
| log->Printf("AppleObjCRuntimeV2 added isa=0x%" PRIx64 ", hash=0x%8.8x, name=%s", isa, name_hash,descriptor_sp->GetClassName().AsCString("<unknown>")); |
| } |
| } |
| } |
| |
| bool |
| AppleObjCRuntimeV2::UpdateISAToDescriptorMapSharedCache() |
| { |
| Process *process = GetProcess(); |
| |
| if (process == NULL) |
| return false; |
| |
| Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); |
| |
| ExecutionContext exe_ctx; |
| |
| ThreadSP thread_sp = process->GetThreadList().GetSelectedThread(); |
| |
| if (!thread_sp) |
| return false; |
| |
| thread_sp->CalculateExecutionContext(exe_ctx); |
| ClangASTContext *ast = process->GetTarget().GetScratchClangASTContext(); |
| |
| if (!ast) |
| return false; |
| |
| Address function_address; |
| |
| StreamString errors; |
| |
| const uint32_t addr_size = process->GetAddressByteSize(); |
| |
| Error err; |
| |
| const lldb::addr_t objc_opt_ptr = GetSharedCacheReadOnlyAddress(); |
| |
| if (objc_opt_ptr == LLDB_INVALID_ADDRESS) |
| return false; |
| |
| // Read the total number of classes from the hash table |
| const uint32_t num_classes = 128*1024; |
| if (num_classes == 0) |
| { |
| if (log) |
| log->Printf ("No dynamic classes found in gdb_objc_realized_classes_addr."); |
| return false; |
| } |
| |
| // Make some types for our arguments |
| ClangASTType clang_uint32_t_type = ast->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); |
| ClangASTType clang_void_pointer_type = ast->GetBasicType(eBasicTypeVoid).GetPointerType(); |
| |
| if (!m_get_shared_cache_class_info_code.get()) |
| { |
| m_get_shared_cache_class_info_code.reset (new ClangUtilityFunction (g_get_shared_cache_class_info_body, |
| g_get_shared_cache_class_info_name)); |
| |
| errors.Clear(); |
| |
| if (!m_get_shared_cache_class_info_code->Install(errors, exe_ctx)) |
| { |
| if (log) |
| log->Printf ("Failed to install implementation lookup: %s.", errors.GetData()); |
| m_get_shared_cache_class_info_code.reset(); |
| } |
| } |
| |
| if (m_get_shared_cache_class_info_code.get()) |
| function_address.SetOffset(m_get_shared_cache_class_info_code->StartAddress()); |
| else |
| return false; |
| |
| ValueList arguments; |
| |
| // Next make the runner function for our implementation utility function. |
| if (!m_get_shared_cache_class_info_function.get()) |
| { |
| Value value; |
| value.SetValueType (Value::eValueTypeScalar); |
| //value.SetContext (Value::eContextTypeClangType, clang_void_pointer_type); |
| value.SetClangType (clang_void_pointer_type); |
| arguments.PushValue (value); |
| arguments.PushValue (value); |
| |
| value.SetValueType (Value::eValueTypeScalar); |
| //value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); |
| value.SetClangType (clang_uint32_t_type); |
| arguments.PushValue (value); |
| |
| m_get_shared_cache_class_info_function.reset(new ClangFunction (*m_process, |
| clang_uint32_t_type, |
| function_address, |
| arguments, |
| "objc-isa-to-descriptor-shared-cache")); |
| |
| if (m_get_shared_cache_class_info_function.get() == NULL) |
| return false; |
| |
| errors.Clear(); |
| |
| unsigned num_errors = m_get_shared_cache_class_info_function->CompileFunction(errors); |
| if (num_errors) |
| { |
| if (log) |
| log->Printf ("Error compiling function: \"%s\".", errors.GetData()); |
| return false; |
| } |
| |
| errors.Clear(); |
| |
| if (!m_get_shared_cache_class_info_function->WriteFunctionWrapper(exe_ctx, errors)) |
| { |
| if (log) |
| log->Printf ("Error Inserting function: \"%s\".", errors.GetData()); |
| return false; |
| } |
| } |
| else |
| { |
| arguments = m_get_shared_cache_class_info_function->GetArgumentValues (); |
| } |
| |
| 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) |
| return false; |
| |
| Mutex::Locker locker(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; |
| |
| bool success = false; |
| |
| errors.Clear(); |
| |
| // Write our function arguments into the process so we can run our function |
| if (m_get_shared_cache_class_info_function->WriteFunctionArguments (exe_ctx, |
| m_get_shared_cache_class_info_args, |
| function_address, |
| arguments, |
| errors)) |
| { |
| 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.SetClangType (clang_uint32_t_type); |
| return_value.GetScalar() = 0; |
| |
| errors.Clear(); |
| |
| // Run the function |
| ExpressionResults results = m_get_shared_cache_class_info_function->ExecuteFunction (exe_ctx, |
| &m_get_shared_cache_class_info_args, |
| options, |
| errors, |
| return_value); |
| |
| if (results == eExpressionCompleted) |
| { |
| // The result is the number of ClassInfo structures that were filled in |
| uint32_t 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: %s.\n", errors.GetData()); |
| } |
| } |
| else |
| { |
| if (log) |
| log->Printf ("Error writing function arguments: \"%s\".", errors.GetData()); |
| } |
| |
| // Deallocate the memory we allocated for the ClassInfo array |
| process->DeallocateMemory(class_infos_addr); |
| |
| return success; |
| } |
| |
| |
| bool |
| AppleObjCRuntimeV2::UpdateISAToDescriptorMapFromMemory (RemoteNXMapTable &hash_table) |
| { |
| Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); |
| |
| 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() |
| { |
| 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, 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 |
| 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) |
| UpdateISAToDescriptorMapSharedCache(); |
| } |
| else |
| { |
| m_isa_to_descriptor_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); |
| } |
| |
| 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::TaggedPointerVendor* |
| AppleObjCRuntimeV2::TaggedPointerVendor::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); |
| |
| |
| // 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) : |
| TaggedPointerVendor(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::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; |
| } |