| //===-- AppleObjCRuntimeV1.cpp --------------------------------------*- C++ |
| //-*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "AppleObjCRuntimeV1.h" |
| #include "AppleObjCDeclVendor.h" |
| #include "AppleObjCTrampolineHandler.h" |
| |
| #include "clang/AST/Type.h" |
| |
| #include "lldb/Breakpoint/BreakpointLocation.h" |
| #include "lldb/Core/Module.h" |
| #include "lldb/Core/PluginManager.h" |
| #include "lldb/Core/Scalar.h" |
| #include "lldb/Expression/FunctionCaller.h" |
| #include "lldb/Expression/UtilityFunction.h" |
| #include "lldb/Symbol/ClangASTContext.h" |
| #include "lldb/Symbol/Symbol.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 "lldb/Utility/ConstString.h" |
| #include "lldb/Utility/Log.h" |
| #include "lldb/Utility/Status.h" |
| #include "lldb/Utility/StreamString.h" |
| |
| #include <vector> |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| |
| AppleObjCRuntimeV1::AppleObjCRuntimeV1(Process *process) |
| : AppleObjCRuntime(process), m_hash_signature(), |
| m_isa_hash_table_ptr(LLDB_INVALID_ADDRESS) {} |
| |
| // for V1 runtime we just try to return a class name as that is the minimum |
| // level of support required for the data formatters to work |
| bool AppleObjCRuntimeV1::GetDynamicTypeAndAddress( |
| ValueObject &in_value, lldb::DynamicValueType use_dynamic, |
| TypeAndOrName &class_type_or_name, Address &address, |
| Value::ValueType &value_type) { |
| class_type_or_name.Clear(); |
| value_type = Value::ValueType::eValueTypeScalar; |
| if (CouldHaveDynamicValue(in_value)) { |
| auto class_descriptor(GetClassDescriptor(in_value)); |
| if (class_descriptor && class_descriptor->IsValid() && |
| class_descriptor->GetClassName()) { |
| const addr_t object_ptr = in_value.GetPointerValue(); |
| address.SetRawAddress(object_ptr); |
| class_type_or_name.SetName(class_descriptor->GetClassName()); |
| } |
| } |
| return class_type_or_name.IsEmpty() == false; |
| } |
| |
| //------------------------------------------------------------------ |
| // Static Functions |
| //------------------------------------------------------------------ |
| lldb_private::LanguageRuntime * |
| AppleObjCRuntimeV1::CreateInstance(Process *process, |
| lldb::LanguageType language) { |
| // FIXME: This should be a MacOS or iOS process, and we need to look for the |
| // OBJC section to make |
| // sure we aren't using the V1 runtime. |
| if (language == eLanguageTypeObjC) { |
| ModuleSP objc_module_sp; |
| |
| if (AppleObjCRuntime::GetObjCVersion(process, objc_module_sp) == |
| ObjCRuntimeVersions::eAppleObjC_V1) |
| return new AppleObjCRuntimeV1(process); |
| else |
| return NULL; |
| } else |
| return NULL; |
| } |
| |
| void AppleObjCRuntimeV1::Initialize() { |
| PluginManager::RegisterPlugin( |
| GetPluginNameStatic(), "Apple Objective-C Language Runtime - Version 1", |
| CreateInstance); |
| } |
| |
| void AppleObjCRuntimeV1::Terminate() { |
| PluginManager::UnregisterPlugin(CreateInstance); |
| } |
| |
| lldb_private::ConstString AppleObjCRuntimeV1::GetPluginNameStatic() { |
| static ConstString g_name("apple-objc-v1"); |
| return g_name; |
| } |
| |
| //------------------------------------------------------------------ |
| // PluginInterface protocol |
| //------------------------------------------------------------------ |
| ConstString AppleObjCRuntimeV1::GetPluginName() { |
| return GetPluginNameStatic(); |
| } |
| |
| uint32_t AppleObjCRuntimeV1::GetPluginVersion() { return 1; } |
| |
| BreakpointResolverSP |
| AppleObjCRuntimeV1::CreateExceptionResolver(Breakpoint *bkpt, bool catch_bp, |
| bool throw_bp) { |
| BreakpointResolverSP resolver_sp; |
| |
| if (throw_bp) |
| resolver_sp.reset(new BreakpointResolverName( |
| bkpt, "objc_exception_throw", eFunctionNameTypeBase, |
| eLanguageTypeUnknown, Breakpoint::Exact, 0, eLazyBoolNo)); |
| // FIXME: don't do catch yet. |
| return resolver_sp; |
| } |
| |
| struct BufStruct { |
| char contents[2048]; |
| }; |
| |
| UtilityFunction *AppleObjCRuntimeV1::CreateObjectChecker(const char *name) { |
| std::unique_ptr<BufStruct> buf(new BufStruct); |
| |
| int strformatsize = snprintf(&buf->contents[0], sizeof(buf->contents), |
| "struct __objc_class " |
| " \n" |
| "{ " |
| " \n" |
| " struct __objc_class *isa; " |
| " \n" |
| " struct __objc_class *super_class; " |
| " \n" |
| " const char *name; " |
| " \n" |
| " // rest of struct elided because unused " |
| " \n" |
| "}; " |
| " \n" |
| " " |
| " \n" |
| "struct __objc_object " |
| " \n" |
| "{ " |
| " \n" |
| " struct __objc_class *isa; " |
| " \n" |
| "}; " |
| " \n" |
| " " |
| " \n" |
| "extern \"C\" void " |
| " \n" |
| "%s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) " |
| " \n" |
| "{ " |
| " \n" |
| " struct __objc_object *obj = (struct " |
| "__objc_object*)$__lldb_arg_obj; \n" |
| " if ($__lldb_arg_obj == (void *)0) " |
| " \n" |
| " return; // nil is ok " |
| " (int)strlen(obj->isa->name); " |
| " \n" |
| "} " |
| " \n", |
| name); |
| assert(strformatsize < (int)sizeof(buf->contents)); |
| (void)strformatsize; |
| |
| Status error; |
| return GetTargetRef().GetUtilityFunctionForLanguage( |
| buf->contents, eLanguageTypeObjC, name, error); |
| } |
| |
| AppleObjCRuntimeV1::ClassDescriptorV1::ClassDescriptorV1( |
| ValueObject &isa_pointer) { |
| Initialize(isa_pointer.GetValueAsUnsigned(0), isa_pointer.GetProcessSP()); |
| } |
| |
| AppleObjCRuntimeV1::ClassDescriptorV1::ClassDescriptorV1( |
| ObjCISA isa, lldb::ProcessSP process_sp) { |
| Initialize(isa, process_sp); |
| } |
| |
| void AppleObjCRuntimeV1::ClassDescriptorV1::Initialize( |
| ObjCISA isa, lldb::ProcessSP process_sp) { |
| if (!isa || !process_sp) { |
| m_valid = false; |
| return; |
| } |
| |
| m_valid = true; |
| |
| Status error; |
| |
| m_isa = process_sp->ReadPointerFromMemory(isa, error); |
| |
| if (error.Fail()) { |
| m_valid = false; |
| return; |
| } |
| |
| uint32_t ptr_size = process_sp->GetAddressByteSize(); |
| |
| if (!IsPointerValid(m_isa, ptr_size)) { |
| m_valid = false; |
| return; |
| } |
| |
| m_parent_isa = process_sp->ReadPointerFromMemory(m_isa + ptr_size, error); |
| |
| if (error.Fail()) { |
| m_valid = false; |
| return; |
| } |
| |
| if (!IsPointerValid(m_parent_isa, ptr_size, true)) { |
| m_valid = false; |
| return; |
| } |
| |
| lldb::addr_t name_ptr = |
| process_sp->ReadPointerFromMemory(m_isa + 2 * ptr_size, error); |
| |
| if (error.Fail()) { |
| m_valid = false; |
| return; |
| } |
| |
| lldb::DataBufferSP buffer_sp(new DataBufferHeap(1024, 0)); |
| |
| size_t count = process_sp->ReadCStringFromMemory( |
| name_ptr, (char *)buffer_sp->GetBytes(), 1024, error); |
| |
| if (error.Fail()) { |
| m_valid = false; |
| return; |
| } |
| |
| if (count) |
| m_name = ConstString((char *)buffer_sp->GetBytes()); |
| else |
| m_name = ConstString(); |
| |
| m_instance_size = process_sp->ReadUnsignedIntegerFromMemory( |
| m_isa + 5 * ptr_size, ptr_size, 0, error); |
| |
| if (error.Fail()) { |
| m_valid = false; |
| return; |
| } |
| |
| m_process_wp = lldb::ProcessWP(process_sp); |
| } |
| |
| AppleObjCRuntime::ClassDescriptorSP |
| AppleObjCRuntimeV1::ClassDescriptorV1::GetSuperclass() { |
| if (!m_valid) |
| return AppleObjCRuntime::ClassDescriptorSP(); |
| ProcessSP process_sp = m_process_wp.lock(); |
| if (!process_sp) |
| return AppleObjCRuntime::ClassDescriptorSP(); |
| return ObjCLanguageRuntime::ClassDescriptorSP( |
| new AppleObjCRuntimeV1::ClassDescriptorV1(m_parent_isa, process_sp)); |
| } |
| |
| AppleObjCRuntime::ClassDescriptorSP |
| AppleObjCRuntimeV1::ClassDescriptorV1::GetMetaclass() const { |
| return ClassDescriptorSP(); |
| } |
| |
| bool AppleObjCRuntimeV1::ClassDescriptorV1::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) const { |
| return false; |
| } |
| |
| lldb::addr_t AppleObjCRuntimeV1::GetISAHashTablePointer() { |
| if (m_isa_hash_table_ptr == LLDB_INVALID_ADDRESS) { |
| ModuleSP objc_module_sp(GetObjCModule()); |
| |
| if (!objc_module_sp) |
| return LLDB_INVALID_ADDRESS; |
| |
| static ConstString g_objc_debug_class_hash("_objc_debug_class_hash"); |
| |
| const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( |
| g_objc_debug_class_hash, lldb::eSymbolTypeData); |
| if (symbol && symbol->ValueIsAddress()) { |
| Process *process = GetProcess(); |
| if (process) { |
| |
| lldb::addr_t objc_debug_class_hash_addr = |
| symbol->GetAddressRef().GetLoadAddress(&process->GetTarget()); |
| |
| if (objc_debug_class_hash_addr != LLDB_INVALID_ADDRESS) { |
| Status error; |
| lldb::addr_t objc_debug_class_hash_ptr = |
| process->ReadPointerFromMemory(objc_debug_class_hash_addr, error); |
| if (objc_debug_class_hash_ptr != 0 && |
| objc_debug_class_hash_ptr != LLDB_INVALID_ADDRESS) { |
| m_isa_hash_table_ptr = objc_debug_class_hash_ptr; |
| } |
| } |
| } |
| } |
| } |
| return m_isa_hash_table_ptr; |
| } |
| |
| void AppleObjCRuntimeV1::UpdateISAToDescriptorMapIfNeeded() { |
| // TODO: implement HashTableSignature... |
| Process *process = GetProcess(); |
| |
| if (process) { |
| // 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(); |
| |
| Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); |
| |
| ProcessSP process_sp = process->shared_from_this(); |
| |
| ModuleSP objc_module_sp(GetObjCModule()); |
| |
| if (!objc_module_sp) |
| return; |
| |
| uint32_t isa_count = 0; |
| |
| lldb::addr_t hash_table_ptr = GetISAHashTablePointer(); |
| if (hash_table_ptr != LLDB_INVALID_ADDRESS) { |
| // Read the NXHashTable struct: |
| // |
| // typedef struct { |
| // const NXHashTablePrototype *prototype; |
| // unsigned count; |
| // unsigned nbBuckets; |
| // void *buckets; |
| // const void *info; |
| // } NXHashTable; |
| |
| Status error; |
| DataBufferHeap buffer(1024, 0); |
| if (process->ReadMemory(hash_table_ptr, buffer.GetBytes(), 20, error) == |
| 20) { |
| const uint32_t addr_size = m_process->GetAddressByteSize(); |
| const ByteOrder byte_order = m_process->GetByteOrder(); |
| DataExtractor data(buffer.GetBytes(), buffer.GetByteSize(), byte_order, |
| addr_size); |
| lldb::offset_t offset = addr_size; // Skip prototype |
| const uint32_t count = data.GetU32(&offset); |
| const uint32_t num_buckets = data.GetU32(&offset); |
| const addr_t buckets_ptr = data.GetPointer(&offset); |
| if (m_hash_signature.NeedsUpdate(count, num_buckets, buckets_ptr)) { |
| m_hash_signature.UpdateSignature(count, num_buckets, buckets_ptr); |
| |
| const uint32_t data_size = num_buckets * 2 * sizeof(uint32_t); |
| buffer.SetByteSize(data_size); |
| |
| if (process->ReadMemory(buckets_ptr, buffer.GetBytes(), data_size, |
| error) == data_size) { |
| data.SetData(buffer.GetBytes(), buffer.GetByteSize(), byte_order); |
| offset = 0; |
| for (uint32_t bucket_idx = 0; bucket_idx < num_buckets; |
| ++bucket_idx) { |
| const uint32_t bucket_isa_count = data.GetU32(&offset); |
| const lldb::addr_t bucket_data = data.GetU32(&offset); |
| |
| if (bucket_isa_count == 0) |
| continue; |
| |
| isa_count += bucket_isa_count; |
| |
| ObjCISA isa; |
| if (bucket_isa_count == 1) { |
| // When we only have one entry in the bucket, the bucket data |
| // is the "isa" |
| isa = bucket_data; |
| if (isa) { |
| if (!ISAIsCached(isa)) { |
| ClassDescriptorSP descriptor_sp( |
| new ClassDescriptorV1(isa, process_sp)); |
| |
| if (log && log->GetVerbose()) |
| log->Printf("AppleObjCRuntimeV1 added (ObjCISA)0x%" PRIx64 |
| " from _objc_debug_class_hash to " |
| "isa->descriptor cache", |
| isa); |
| |
| AddClass(isa, descriptor_sp); |
| } |
| } |
| } else { |
| // When we have more than one entry in the bucket, the bucket |
| // data is a pointer to an array of "isa" values |
| addr_t isa_addr = bucket_data; |
| for (uint32_t isa_idx = 0; isa_idx < bucket_isa_count; |
| ++isa_idx, isa_addr += addr_size) { |
| isa = m_process->ReadPointerFromMemory(isa_addr, error); |
| |
| if (isa && isa != LLDB_INVALID_ADDRESS) { |
| if (!ISAIsCached(isa)) { |
| ClassDescriptorSP descriptor_sp( |
| new ClassDescriptorV1(isa, process_sp)); |
| |
| if (log && log->GetVerbose()) |
| log->Printf( |
| "AppleObjCRuntimeV1 added (ObjCISA)0x%" PRIx64 |
| " from _objc_debug_class_hash to isa->descriptor " |
| "cache", |
| isa); |
| |
| AddClass(isa, descriptor_sp); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } else { |
| m_isa_to_descriptor_stop_id = UINT32_MAX; |
| } |
| } |
| |
| DeclVendor *AppleObjCRuntimeV1::GetDeclVendor() { |
| return nullptr; |
| } |