It completes the job of using EvaluateExpressionOptions consistently throughout the inferior function calling mechanism in lldb begun in Greg's patch r194009. It removes a handful of alternate calls into the ClangUserExpression/ClangFunction/ThreadPlanCallFunction which were there for convenience. Using the EvaluateExpressionOptions removes the need for them. Using that it gets the --debug option from Greg's patch to work cleanly. It also adds another EvaluateExpressionOption to not trap exceptions when running expressions. You shouldn't use this option unless you KNOW your expression can't throw beyond itself. This is: <rdar://problem/15374885> At present this is only available through the SB API's or python. It fixes a bug where function calls would unset the ObjC & C++ exception breakpoints without checking whether they were set by somebody else already. llvm-svn: 194182
2658 lines
96 KiB
C++
2658 lines
96 KiB
C++
//===-- 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 "AppleObjCTypeVendor.h"
|
|
#include "AppleObjCTrampolineHandler.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 (%zu 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_type_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))
|
|
{
|
|
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
|
|
TypeVendor* vendor = GetTypeVendor();
|
|
if (vendor)
|
|
{
|
|
std::vector<ClangASTType> types;
|
|
if (vendor->FindTypes(class_name, false, 1, types) && types.size() && types.at(0).IsValid())
|
|
class_type_or_name.SetClangASTType(types.at(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)
|
|
{
|
|
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 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 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<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;
|
|
}
|
|
|
|
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::unique_ptr<objc_class_t> objc_class;
|
|
std::unique_ptr<class_ro_t> class_ro;
|
|
std::unique_ptr<class_rw_t> 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::unique_ptr<objc_class_t> objc_class;
|
|
|
|
if (!Read_objc_class(process, objc_class))
|
|
return ObjCLanguageRuntime::ClassDescriptorSP();
|
|
|
|
return m_runtime.ObjCLanguageRuntime::GetClassDescriptorFromISA(objc_class->m_superclass);
|
|
}
|
|
|
|
virtual bool
|
|
IsValid ()
|
|
{
|
|
return true; // any Objective-C v2 runtime class descriptor we vend is valid
|
|
}
|
|
|
|
// a custom descriptor is used for tagged pointers
|
|
virtual bool
|
|
GetTaggedPointerInfo (uint64_t* info_bits = NULL,
|
|
uint64_t* value_bits = NULL,
|
|
uint64_t* payload = NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual uint64_t
|
|
GetInstanceSize ()
|
|
{
|
|
lldb_private::Process *process = m_runtime.GetProcess();
|
|
|
|
if (process)
|
|
{
|
|
std::unique_ptr<objc_class_t> objc_class;
|
|
std::unique_ptr<class_ro_t> class_ro;
|
|
std::unique_ptr<class_rw_t> 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 <void (ObjCLanguageRuntime::ObjCISA)> const &superclass_func,
|
|
std::function <bool (const char *, const char *)> const &instance_method_func,
|
|
std::function <bool (const char *, const char *)> const &class_method_func,
|
|
std::function <bool (const char *, const char *, lldb::addr_t, uint64_t)> const &ivar_func)
|
|
{
|
|
lldb_private::Process *process = m_runtime.GetProcess();
|
|
|
|
std::unique_ptr<objc_class_t> objc_class;
|
|
std::unique_ptr<class_ro_t> class_ro;
|
|
std::unique_ptr<class_rw_t> 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::unique_ptr<method_list_t> 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::unique_ptr<method_t> 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 <void (ObjCLanguageRuntime::ObjCISA)> (nullptr),
|
|
class_method_func,
|
|
std::function <bool (const char *, const char *)> (nullptr),
|
|
std::function <bool (const char *, const char *, lldb::addr_t, uint64_t)> (nullptr));
|
|
}
|
|
|
|
if (ivar_func)
|
|
{
|
|
ivar_list_t ivar_list;
|
|
if (!ivar_list.Read(process, class_ro->m_ivars_ptr))
|
|
return false;
|
|
|
|
if (ivar_list.m_entsize != ivar_t::GetSize(process))
|
|
return false;
|
|
|
|
ivar_t ivar;
|
|
|
|
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());
|
|
|
|
lldb::offset_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());
|
|
|
|
lldb::offset_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());
|
|
|
|
lldb::offset_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());
|
|
|
|
lldb::offset_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());
|
|
|
|
lldb::offset_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());
|
|
|
|
lldb::offset_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());
|
|
|
|
lldb::offset_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::unique_ptr<objc_class_t> &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::unique_ptr<class_ro_t> &class_ro, std::unique_ptr<class_rw_t> &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
|
|
};
|
|
|
|
// tagged pointer descriptor
|
|
class ClassDescriptorV2Tagged : public ObjCLanguageRuntime::ClassDescriptor
|
|
{
|
|
public:
|
|
ClassDescriptorV2Tagged (ConstString class_name,
|
|
uint64_t payload)
|
|
{
|
|
m_name = class_name;
|
|
if (!m_name)
|
|
{
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
m_valid = true;
|
|
m_payload = payload;
|
|
m_info_bits = (m_payload & 0xF0ULL) >> 4;
|
|
m_value_bits = (m_payload & ~0x0000000000000000FFULL) >> 8;
|
|
}
|
|
|
|
ClassDescriptorV2Tagged (ObjCLanguageRuntime::ClassDescriptorSP actual_class_sp,
|
|
uint64_t payload)
|
|
{
|
|
if (!actual_class_sp)
|
|
{
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
m_name = actual_class_sp->GetClassName();
|
|
if (!m_name)
|
|
{
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
m_valid = true;
|
|
m_payload = payload;
|
|
m_info_bits = (m_payload & 0x0FULL);
|
|
m_value_bits = (m_payload & ~0x0FULL) >> 4;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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
|
|
GetTaggedPointerInfo (uint64_t* info_bits = NULL,
|
|
uint64_t* value_bits = NULL,
|
|
uint64_t* payload = NULL)
|
|
{
|
|
if (info_bits)
|
|
*info_bits = GetInfoBits();
|
|
if (value_bits)
|
|
*value_bits = GetValueBits();
|
|
if (payload)
|
|
*payload = GetPayload();
|
|
return true;
|
|
}
|
|
|
|
virtual uint64_t
|
|
GetInstanceSize ()
|
|
{
|
|
return (IsValid() ? m_pointer_size : 0);
|
|
}
|
|
|
|
virtual ObjCLanguageRuntime::ObjCISA
|
|
GetISA ()
|
|
{
|
|
return 0; // tagged pointers have no ISA
|
|
}
|
|
|
|
// 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 uint64_t
|
|
GetPayload ()
|
|
{
|
|
return (IsValid() ? m_payload : 0);
|
|
}
|
|
|
|
virtual
|
|
~ClassDescriptorV2Tagged ()
|
|
{}
|
|
|
|
private:
|
|
ConstString m_name;
|
|
uint8_t m_pointer_size;
|
|
bool m_valid;
|
|
uint64_t m_info_bits;
|
|
uint64_t m_value_bits;
|
|
uint64_t m_payload;
|
|
|
|
};
|
|
|
|
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 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));
|
|
|
|
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
|
|
ExecutionResults results = m_get_class_info_function->ExecuteFunction (exe_ctx,
|
|
&m_get_class_info_args,
|
|
options,
|
|
errors,
|
|
return_value);
|
|
|
|
if (results == eExecutionCompleted)
|
|
{
|
|
// 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", isa, name_hash);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = 16*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));
|
|
|
|
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
|
|
ExecutionResults results = m_get_shared_cache_class_info_function->ExecuteFunction (exe_ctx,
|
|
&m_get_shared_cache_class_info_args,
|
|
options,
|
|
errors,
|
|
return_value);
|
|
|
|
if (results == eExecutionCompleted)
|
|
{
|
|
// 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);
|
|
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;
|
|
}
|
|
|
|
|
|
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, wether 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 dynamicly 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);
|
|
}
|
|
|
|
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_$_");
|
|
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);
|
|
}
|
|
|
|
// we use the version of Foundation to make assumptions about the ObjC runtime on a target
|
|
uint32_t
|
|
AppleObjCRuntimeV2::TaggedPointerVendorLegacy::GetFoundationVersion (Target &target)
|
|
{
|
|
const ModuleList& modules = target.GetImages();
|
|
uint32_t major = UINT32_MAX;
|
|
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)
|
|
{
|
|
module_sp->GetVersion(&major,1);
|
|
break;
|
|
}
|
|
}
|
|
return major;
|
|
}
|
|
|
|
ObjCLanguageRuntime::ClassDescriptorSP
|
|
AppleObjCRuntimeV2::TaggedPointerVendorLegacy::GetClassDescriptor (lldb::addr_t ptr)
|
|
{
|
|
if (!IsPossibleTaggedPointer(ptr))
|
|
return ObjCLanguageRuntime::ClassDescriptorSP();
|
|
|
|
Process* process(m_runtime.GetProcess());
|
|
|
|
if (m_Foundation_version == 0)
|
|
m_Foundation_version = GetFoundationVersion(process->GetTarget());
|
|
|
|
if (m_Foundation_version == UINT32_MAX)
|
|
return ObjCLanguageRuntime::ClassDescriptorSP();
|
|
|
|
uint64_t class_bits = (ptr & 0xE) >> 1;
|
|
ConstString name;
|
|
|
|
// TODO: make a table
|
|
if (m_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 == 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;
|
|
}
|