| //===-- 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 "AppleObjCRuntimeV2.h" |
| #include "AppleObjCTrampolineHandler.h" |
| |
| #include "llvm/Support/MachO.h" |
| #include "clang/AST/Type.h" |
| |
| #include "lldb/Breakpoint/BreakpointLocation.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/StreamString.h" |
| #include "lldb/Expression/ClangFunction.h" |
| #include "lldb/Expression/ClangUtilityFunction.h" |
| #include "lldb/Symbol/ClangASTContext.h" |
| #include "lldb/Target/ExecutionContext.h" |
| #include "lldb/Target/Process.h" |
| #include "lldb/Target/RegisterContext.h" |
| #include "lldb/Target/StopInfo.h" |
| #include "lldb/Target/Target.h" |
| #include "lldb/Target/Thread.h" |
| |
| #include <vector> |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| |
| static const char *pluginName = "AppleObjCRuntimeV2"; |
| static const char *pluginDesc = "Apple Objective C Language Runtime - Version 2"; |
| static const char *pluginShort = "language.apple.objc.v2"; |
| |
| |
| const char *AppleObjCRuntimeV2::g_find_class_name_function_name = "__lldb_apple_objc_v2_find_class_name"; |
| const char *AppleObjCRuntimeV2::g_find_class_name_function_body = " \n\ |
| extern \"C\" \n\ |
| { \n\ |
| extern void *gdb_class_getClass (void *objc_class); \n\ |
| extern void *class_getName(void *objc_class); \n\ |
| extern int printf(const char *format, ...); \n\ |
| } \n\ |
| \n\ |
| struct __lldb_objc_object { \n\ |
| void *isa; \n\ |
| }; \n\ |
| \n\ |
| extern \"C\" void *__lldb_apple_objc_v2_find_class_name ( \n\ |
| __lldb_objc_object *object_ptr, \n\ |
| int debug) \n\ |
| { \n\ |
| void *name = 0; \n\ |
| if (debug) \n\ |
| printf (\"\\n*** Called in v2_find_class_name with object: 0x%p\\n\", object_ptr); \n\ |
| // Call gdb_class_getClass so we can tell if the class is good. \n\ |
| void *objc_class = gdb_class_getClass (object_ptr->isa); \n\ |
| if (objc_class) \n\ |
| { \n\ |
| void *actual_class = (void *) [(id) object_ptr class]; \n\ |
| if (actual_class != 0) \n\ |
| name = class_getName((void *) actual_class); \n\ |
| if (debug) \n\ |
| printf (\"\\n*** Found name: %s\\n\", name ? name : \"<NOT FOUND>\"); \n\ |
| } \n\ |
| else if (debug) \n\ |
| printf (\"\\n*** gdb_class_getClass returned NULL\\n\"); \n\ |
| return name; \n\ |
| } \n\ |
| "; |
| |
| const char *AppleObjCRuntimeV2::g_objc_class_symbol_prefix = "OBJC_CLASS_$_"; |
| const char *AppleObjCRuntimeV2::g_objc_class_data_section_name = "__objc_data"; |
| |
| AppleObjCRuntimeV2::AppleObjCRuntimeV2 (Process *process, ModuleSP &objc_module_sp) : |
| lldb_private::AppleObjCRuntime (process), |
| m_get_class_name_args(LLDB_INVALID_ADDRESS), |
| m_get_class_name_args_mutex(Mutex::eMutexTypeNormal) |
| { |
| m_has_object_getClass = (objc_module_sp->FindFirstSymbolWithNameAndType(ConstString("gdb_object_getClass")) != NULL); |
| } |
| |
| bool |
| AppleObjCRuntimeV2::RunFunctionToFindClassName(lldb::addr_t object_addr, Thread *thread, char *name_dst, size_t max_name_len) |
| { |
| // Since we are going to run code we have to make sure only one thread at a time gets to try this. |
| Mutex::Locker (m_get_class_name_args_mutex); |
| |
| StreamString errors; |
| |
| LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); // FIXME - a more appropriate log channel? |
| |
| int32_t debug; |
| if (log) |
| debug = 1; |
| else |
| debug = 0; |
| |
| ValueList dispatch_values; |
| |
| Value void_ptr_value; |
| ClangASTContext *clang_ast_context = m_process->GetTarget().GetScratchClangASTContext(); |
| |
| lldb::clang_type_t clang_void_ptr_type = clang_ast_context->GetVoidPtrType(false); |
| void_ptr_value.SetValueType (Value::eValueTypeScalar); |
| void_ptr_value.SetContext (Value::eContextTypeClangType, clang_void_ptr_type); |
| void_ptr_value.GetScalar() = object_addr; |
| |
| dispatch_values.PushValue (void_ptr_value); |
| |
| Value int_value; |
| lldb::clang_type_t clang_int_type = clang_ast_context->GetBuiltinTypeForEncodingAndBitSize(lldb::eEncodingSint, 32); |
| int_value.SetValueType (Value::eValueTypeScalar); |
| int_value.SetContext (Value::eContextTypeClangType, clang_int_type); |
| int_value.GetScalar() = debug; |
| |
| dispatch_values.PushValue (int_value); |
| |
| ExecutionContext exe_ctx; |
| thread->CalculateExecutionContext(exe_ctx); |
| |
| Address find_class_name_address; |
| |
| if (!m_get_class_name_code.get()) |
| { |
| m_get_class_name_code.reset (new ClangUtilityFunction (g_find_class_name_function_body, |
| g_find_class_name_function_name)); |
| |
| if (!m_get_class_name_code->Install(errors, exe_ctx)) |
| { |
| if (log) |
| log->Printf ("Failed to install implementation lookup: %s.", errors.GetData()); |
| m_get_class_name_code.reset(); |
| return false; |
| } |
| find_class_name_address.Clear(); |
| find_class_name_address.SetOffset(m_get_class_name_code->StartAddress()); |
| } |
| else |
| { |
| find_class_name_address.Clear(); |
| find_class_name_address.SetOffset(m_get_class_name_code->StartAddress()); |
| } |
| |
| // Next make the runner function for our implementation utility function. |
| if (!m_get_class_name_function.get()) |
| { |
| m_get_class_name_function.reset(new ClangFunction (*m_process, |
| clang_ast_context, |
| clang_void_ptr_type, |
| find_class_name_address, |
| dispatch_values)); |
| |
| errors.Clear(); |
| unsigned num_errors = m_get_class_name_function->CompileFunction(errors); |
| if (num_errors) |
| { |
| if (log) |
| log->Printf ("Error compiling function: \"%s\".", errors.GetData()); |
| return false; |
| } |
| |
| errors.Clear(); |
| if (!m_get_class_name_function->WriteFunctionWrapper(exe_ctx, errors)) |
| { |
| if (log) |
| log->Printf ("Error Inserting function: \"%s\".", errors.GetData()); |
| return false; |
| } |
| } |
| |
| if (m_get_class_name_code.get() == NULL || m_get_class_name_function.get() == NULL) |
| return false; |
| |
| // Finally, write down the arguments, and call the function. Note that we will re-use the same space in the target |
| // for the args. We're locking this to ensure that only one thread at a time gets to call this function, so we don't |
| // have to worry about overwriting the arguments. |
| |
| if (!m_get_class_name_function->WriteFunctionArguments (exe_ctx, m_get_class_name_args, find_class_name_address, dispatch_values, errors)) |
| return false; |
| |
| bool stop_others = true; |
| bool try_all_threads = true; |
| bool unwind_on_error = true; |
| |
| ExecutionResults results = m_get_class_name_function->ExecuteFunction (exe_ctx, |
| &m_get_class_name_args, |
| errors, |
| stop_others, |
| 1000000, |
| try_all_threads, |
| unwind_on_error, |
| void_ptr_value); |
| |
| if (results != eExecutionCompleted) |
| { |
| if (log) |
| log->Printf("Error evaluating our find class name function: %d.\n", results); |
| return false; |
| } |
| |
| lldb::addr_t result_ptr = void_ptr_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); |
| size_t chars_read = m_process->ReadCStringFromMemory (result_ptr, name_dst, max_name_len); |
| |
| // If we exhausted our buffer before finding a NULL we're probably off in the weeds somewhere... |
| if (chars_read == max_name_len) |
| return false; |
| else |
| return true; |
| |
| } |
| |
| bool |
| AppleObjCRuntimeV2::GetDynamicTypeAndAddress (ValueObject &in_value, |
| lldb::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.GetUpdatePoint().GetProcess() == m_process); |
| |
| // 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. |
| AddressType address_type; |
| lldb::addr_t original_ptr = in_value.GetPointerValue(address_type, true); |
| |
| // ObjC only has single inheritance, so the objects all start at the same pointer value. |
| address.SetSection (NULL); |
| address.SetOffset (original_ptr); |
| |
| if (original_ptr == LLDB_INVALID_ADDRESS) |
| return false; |
| |
| Target *target = m_process->CalculateTarget(); |
| |
| char memory_buffer[16]; |
| DataExtractor data(memory_buffer, sizeof(memory_buffer), |
| m_process->GetByteOrder(), |
| m_process->GetAddressByteSize()); |
| size_t address_byte_size = m_process->GetAddressByteSize(); |
| Error error; |
| size_t bytes_read = m_process->ReadMemory (original_ptr, |
| memory_buffer, |
| address_byte_size, |
| error); |
| if (!error.Success() || (bytes_read != address_byte_size)) |
| { |
| return false; |
| } |
| |
| uint32_t offset_ptr = 0; |
| lldb::addr_t isa_addr = data.GetAddress (&offset_ptr); |
| |
| if (offset_ptr == 0) |
| return false; |
| |
| // Make sure the class address is readable, otherwise this is not a good object: |
| bytes_read = m_process->ReadMemory (isa_addr, |
| memory_buffer, |
| address_byte_size, |
| error); |
| if (bytes_read != address_byte_size) |
| return false; |
| |
| // First check the cache... |
| |
| SymbolContext sc; |
| |
| class_type_or_name = LookupInClassNameCache (isa_addr); |
| |
| if (!class_type_or_name.IsEmpty()) |
| { |
| if (class_type_or_name.GetTypeSP() != NULL) |
| return true; |
| else |
| return false; |
| } |
| |
| const char *class_name = NULL; |
| Address isa_address; |
| target->GetSectionLoadList().ResolveLoadAddress (isa_addr, isa_address); |
| |
| if (isa_address.IsValid()) |
| { |
| // If the ISA pointer points to one of the sections in the binary, then see if we can |
| // get the class name from the symbols. |
| |
| const Section *section = isa_address.GetSection(); |
| |
| if (section) |
| { |
| // If this points to a section that we know about, then this is |
| // some static class or nothing. See if it is in the right section |
| // and if its name is the right form. |
| ConstString section_name = section->GetName(); |
| if (section_name == ConstString(g_objc_class_data_section_name)) |
| { |
| isa_address.CalculateSymbolContext(&sc); |
| if (sc.symbol) |
| { |
| class_name = sc.symbol->GetName().AsCString(); |
| if (strstr (class_name, g_objc_class_symbol_prefix) == class_name) |
| class_name += strlen (g_objc_class_symbol_prefix); |
| else |
| return false; |
| } |
| } |
| } |
| } |
| |
| char class_buffer[1024]; |
| if (class_name == NULL && use_dynamic != lldb::eDynamicDontRunTarget) |
| { |
| // If the class address didn't point into the binary, or |
| // it points into the right section but there wasn't a symbol |
| // there, try to look it up by calling the class method in the target. |
| ExecutionContextScope *exe_scope = in_value.GetUpdatePoint().GetExecutionContextScope(); |
| Thread *thread_to_use; |
| if (exe_scope) |
| thread_to_use = exe_scope->CalculateThread(); |
| |
| if (thread_to_use == NULL) |
| thread_to_use = m_process->GetThreadList().GetSelectedThread().get(); |
| |
| if (thread_to_use == NULL) |
| return false; |
| |
| if (!RunFunctionToFindClassName (original_ptr, thread_to_use, class_buffer, 1024)) |
| return false; |
| |
| class_name = class_buffer; |
| |
| } |
| |
| if (class_name != NULL && *class_name != '\0') |
| { |
| class_type_or_name.SetName (class_name); |
| |
| TypeList class_types; |
| uint32_t num_matches = target->GetImages().FindTypes (sc, |
| class_type_or_name.GetName(), |
| true, |
| UINT32_MAX, |
| class_types); |
| if (num_matches == 1) |
| { |
| class_type_or_name.SetTypeSP (class_types.GetTypeAtIndex(0)); |
| return true; |
| } |
| else |
| { |
| for (size_t i = 0; i < num_matches; i++) |
| { |
| lldb::TypeSP this_type(class_types.GetTypeAtIndex(i)); |
| if (this_type) |
| { |
| if (ClangASTContext::IsObjCClassType(this_type->GetClangFullType())) |
| { |
| // There can only be one type with a given name, |
| // so we've just found duplicate definitions, and this |
| // one will do as well as any other. |
| // We don't consider something to have a dynamic type if |
| // it is the same as the static type. So compare against |
| // the value we were handed: |
| |
| clang::ASTContext *in_ast_ctx = in_value.GetClangAST (); |
| clang::ASTContext *this_ast_ctx = this_type->GetClangAST (); |
| if (in_ast_ctx != this_ast_ctx |
| || !ClangASTContext::AreTypesSame (in_ast_ctx, |
| in_value.GetClangType(), |
| this_type->GetClangFullType())) |
| { |
| class_type_or_name.SetTypeSP (this_type); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| AddToClassNameCache (isa_addr, class_type_or_name); |
| if (class_type_or_name.GetTypeSP()) |
| return true; |
| else |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| //------------------------------------------------------------------ |
| // Static Functions |
| //------------------------------------------------------------------ |
| lldb_private::LanguageRuntime * |
| AppleObjCRuntimeV2::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) == eAppleObjC_V2) |
| return new AppleObjCRuntimeV2 (process, objc_module_sp); |
| else |
| return NULL; |
| } |
| else |
| return NULL; |
| } |
| |
| void |
| AppleObjCRuntimeV2::Initialize() |
| { |
| PluginManager::RegisterPlugin (pluginName, |
| pluginDesc, |
| CreateInstance); |
| } |
| |
| void |
| AppleObjCRuntimeV2::Terminate() |
| { |
| PluginManager::UnregisterPlugin (CreateInstance); |
| } |
| |
| //------------------------------------------------------------------ |
| // PluginInterface protocol |
| //------------------------------------------------------------------ |
| const char * |
| AppleObjCRuntimeV2::GetPluginName() |
| { |
| return pluginName; |
| } |
| |
| const char * |
| AppleObjCRuntimeV2::GetShortPluginName() |
| { |
| return pluginShort; |
| } |
| |
| uint32_t |
| AppleObjCRuntimeV2::GetPluginVersion() |
| { |
| return 1; |
| } |
| |
| void |
| AppleObjCRuntimeV2::SetExceptionBreakpoints () |
| { |
| if (!m_process) |
| return; |
| |
| if (!m_objc_exception_bp_sp) |
| { |
| m_objc_exception_bp_sp = m_process->GetTarget().CreateBreakpoint (NULL, |
| "__cxa_throw", |
| eFunctionNameTypeBase, |
| true); |
| } |
| } |
| |
| ClangUtilityFunction * |
| AppleObjCRuntimeV2::CreateObjectChecker(const char *name) |
| { |
| char check_function_code[1024]; |
| |
| 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\" void \n" |
| "%s(void *$__lldb_arg_obj) \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" |
| "} \n", |
| name); |
| } |
| else |
| { |
| len = ::snprintf (check_function_code, |
| sizeof(check_function_code), |
| "extern \"C\" void *gdb_class_getClass(void *); \n" |
| "extern \"C\" void \n" |
| "%s(void *$__lldb_arg_obj) \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" |
| "} \n", |
| name); |
| } |
| |
| assert (len < sizeof(check_function_code)); |
| |
| return new ClangUtilityFunction(check_function_code, name); |
| } |