From e0b23b5198baa1499edaf0209c164e81007c4dfe Mon Sep 17 00:00:00 2001 From: Sean Callanan Date: Thu, 15 Nov 2012 02:02:04 +0000 Subject: [PATCH] In cases where the Objective-C ivar symbols are stripped out, expressions that refer to ivars will not work because Clang emits IR that refers to them to get the ivar offsets. However, it is possible to search the runtime for these values. I have added support for reading the relevant tables to the Objective-C runtime, and extended ClangExpressionDeclMap to query that information if and only if it doesn't find the symbols in the binary. Also added a testcase. llvm-svn: 168018 --- .../lldb/Expression/ClangExpressionDeclMap.h | 6 + lldb/include/lldb/Target/ObjCLanguageRuntime.h | 14 +- lldb/source/Expression/ClangExpressionDeclMap.cpp | 17 +- .../ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp | 177 ++++++++++++++++++++- .../ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h | 3 + .../ObjC/AppleObjCRuntime/AppleObjCTypeVendor.cpp | 9 +- lldb/test/lang/objc/objc-ivar-stripped/Makefile | 15 ++ .../objc-ivar-stripped/TestObjCIvarStripped.py | 63 ++++++++ lldb/test/lang/objc/objc-ivar-stripped/main.m | 33 ++++ 9 files changed, 326 insertions(+), 11 deletions(-) create mode 100644 lldb/test/lang/objc/objc-ivar-stripped/Makefile create mode 100644 lldb/test/lang/objc/objc-ivar-stripped/TestObjCIvarStripped.py create mode 100644 lldb/test/lang/objc/objc-ivar-stripped/main.m diff --git a/lldb/include/lldb/Expression/ClangExpressionDeclMap.h b/lldb/include/lldb/Expression/ClangExpressionDeclMap.h index a300053..a32ac33 100644 --- a/lldb/include/lldb/Expression/ClangExpressionDeclMap.h +++ b/lldb/include/lldb/Expression/ClangExpressionDeclMap.h @@ -328,6 +328,11 @@ public: /// The target to find the symbol in. If not provided, /// then the current parsing context's Target. /// + /// @param[in] process + /// The process to use. For Objective-C symbols, the process's + /// Objective-C language runtime may be queried if the process + /// is non-NULL. + /// /// @param[in] name /// The name of the symbol. /// @@ -336,6 +341,7 @@ public: //------------------------------------------------------------------ lldb::addr_t GetSymbolAddress (Target &target, + Process *process, const ConstString &name, lldb::SymbolType symbol_type); diff --git a/lldb/include/lldb/Target/ObjCLanguageRuntime.h b/lldb/include/lldb/Target/ObjCLanguageRuntime.h index cbfd4a1..140e5f6 100644 --- a/lldb/include/lldb/Target/ObjCLanguageRuntime.h +++ b/lldb/include/lldb/Target/ObjCLanguageRuntime.h @@ -114,8 +114,9 @@ public: // This should return true iff the interface could be completed virtual bool Describe (std::function const &superclass_func, - std::function const &instance_method_func, - std::function const &class_method_func) + std::function const &instance_method_func, + std::function const &class_method_func, + std::function const &ivar_func) { return false; } @@ -285,6 +286,15 @@ public: virtual size_t GetByteOffsetForIvar (ClangASTType &parent_qual_type, const char *ivar_name); + // Given the name of an Objective-C runtime symbol (e.g., ivar offset symbol), + // try to determine from the runtime what the value of that symbol would be. + // Useful when the underlying binary is stripped. + virtual lldb::addr_t + LookupRuntimeSymbol (const ConstString &name) + { + return LLDB_INVALID_ADDRESS; + } + //------------------------------------------------------------------ /// Chop up an objective C function prototype. /// diff --git a/lldb/source/Expression/ClangExpressionDeclMap.cpp b/lldb/source/Expression/ClangExpressionDeclMap.cpp index f54d53e..4cd74a8 100644 --- a/lldb/source/Expression/ClangExpressionDeclMap.cpp +++ b/lldb/source/Expression/ClangExpressionDeclMap.cpp @@ -40,6 +40,7 @@ #include "lldb/Symbol/Variable.h" #include "lldb/Symbol/VariableList.h" #include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/ObjCLanguageRuntime.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/StackFrame.h" @@ -749,7 +750,7 @@ ClangExpressionDeclMap::GetFunctionAddress } addr_t -ClangExpressionDeclMap::GetSymbolAddress (Target &target, const ConstString &name, lldb::SymbolType symbol_type) +ClangExpressionDeclMap::GetSymbolAddress (Target &target, Process *process, const ConstString &name, lldb::SymbolType symbol_type) { SymbolContextList sc_list; @@ -808,6 +809,16 @@ ClangExpressionDeclMap::GetSymbolAddress (Target &target, const ConstString &nam } } + if (symbol_load_addr == LLDB_INVALID_ADDRESS && process) + { + ObjCLanguageRuntime *runtime = process->GetObjCLanguageRuntime(); + + if (runtime) + { + symbol_load_addr = runtime->LookupRuntimeSymbol(name); + } + } + return symbol_load_addr; } @@ -819,7 +830,7 @@ ClangExpressionDeclMap::GetSymbolAddress (const ConstString &name, lldb::SymbolT if (!m_parser_vars->m_exe_ctx.GetTargetPtr()) return false; - return GetSymbolAddress(m_parser_vars->m_exe_ctx.GetTargetRef(), name, symbol_type); + return GetSymbolAddress(m_parser_vars->m_exe_ctx.GetTargetRef(), m_parser_vars->m_exe_ctx.GetProcessPtr(), name, symbol_type); } // Interface for IRInterpreter @@ -1840,7 +1851,7 @@ ClangExpressionDeclMap::DoMaterializeOneVariable } else if (sym) { - addr_t location_load_addr = GetSymbolAddress(*target, name, lldb::eSymbolTypeAny); + addr_t location_load_addr = GetSymbolAddress(*target, process, name, lldb::eSymbolTypeAny); if (location_load_addr == LLDB_INVALID_ADDRESS) { diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp index 5b758c8..9fb6f4e 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -1047,8 +1047,9 @@ public: virtual bool Describe (std::function const &superclass_func, - std::function const &instance_method_func, - std::function const &class_method_func) + std::function const &instance_method_func, + std::function const &class_method_func, + std::function const &ivar_func) { lldb_private::Process *process = m_runtime.GetProcess(); @@ -1084,7 +1085,8 @@ public: { method->Read(process, base_method_list->m_first_ptr + (i * base_method_list->m_entsize)); - instance_method_func(method->m_name.c_str(), method->m_types.c_str()); + if (instance_method_func(method->m_name.c_str(), method->m_types.c_str())) + break; } } @@ -1097,9 +1099,32 @@ public: metaclass.Describe(std::function (nullptr), class_method_func, - std::function (nullptr)); + std::function (nullptr), + std::function (nullptr)); + } + + if (ivar_func) + { + std::auto_ptr ivar_list; + + ivar_list.reset(new ivar_list_t); + if (!ivar_list->Read(process, class_ro->m_ivars_ptr)) + return false; + + if (ivar_list->m_entsize != ivar_t::GetSize(process)) + return false; + + std::auto_ptr ivar; + ivar.reset(new ivar_t); + + for (uint32_t i = 0, e = ivar_list->m_count; i < e; ++i) + { + ivar->Read(process, ivar_list->m_first_ptr + (i * ivar_list->m_entsize)); + + if (ivar_func(ivar->m_name.c_str(), ivar->m_type.c_str(), ivar->m_offset_ptr, ivar->m_size)) + break; + } } - while (0); return true; } @@ -1392,6 +1417,98 @@ private: } }; + struct ivar_list_t + { + uint32_t m_entsize; + uint32_t m_count; + lldb::addr_t m_first_ptr; + + bool Read(Process *process, lldb::addr_t addr) + { + size_t size = sizeof(uint32_t) // uint32_t entsize; + + sizeof(uint32_t); // uint32_t count; + + DataBufferHeap buffer (size, '\0'); + Error error; + + process->ReadMemory(addr, buffer.GetBytes(), size, error); + if (error.Fail()) + { + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), process->GetAddressByteSize()); + + uint32_t cursor = 0; + + m_entsize = extractor.GetU32_unchecked(&cursor); + m_count = extractor.GetU32_unchecked(&cursor); + m_first_ptr = addr + cursor; + + return true; + } + }; + + struct ivar_t + { + lldb::addr_t m_offset_ptr; + lldb::addr_t m_name_ptr; + lldb::addr_t m_type_ptr; + uint32_t m_alignment; + uint32_t m_size; + + std::string m_name; + std::string m_type; + + static size_t GetSize(Process *process) + { + size_t ptr_size = process->GetAddressByteSize(); + + return ptr_size // uintptr_t *offset; + + ptr_size // const char *name; + + ptr_size // const char *type; + + sizeof(uint32_t) // uint32_t alignment; + + sizeof(uint32_t); // uint32_t size; + } + + bool Read(Process *process, lldb::addr_t addr) + { + size_t size = GetSize(process); + + DataBufferHeap buffer (size, '\0'); + Error error; + + process->ReadMemory(addr, buffer.GetBytes(), size, error); + if (error.Fail()) + { + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), process->GetAddressByteSize()); + + uint32_t cursor = 0; + + m_offset_ptr = extractor.GetAddress_unchecked(&cursor); + m_name_ptr = extractor.GetAddress_unchecked(&cursor); + m_type_ptr = extractor.GetAddress_unchecked(&cursor); + m_alignment = extractor.GetU32_unchecked(&cursor); + m_size = extractor.GetU32_unchecked(&cursor); + + const size_t buffer_size = 1024; + size_t count; + + DataBufferHeap string_buf(buffer_size, 0); + + count = process->ReadCStringFromMemory(m_name_ptr, (char*)string_buf.GetBytes(), buffer_size, error); + m_name.assign((char*)string_buf.GetBytes(), count); + + count = process->ReadCStringFromMemory(m_type_ptr, (char*)string_buf.GetBytes(), buffer_size, error); + m_type.assign((char*)string_buf.GetBytes(), count); + + return true; + } + }; + bool Read_objc_class (Process* process, std::auto_ptr &objc_class) { objc_class.reset(new objc_class_t); @@ -1864,3 +1981,53 @@ AppleObjCRuntimeV2::GetTypeVendor() return m_type_vendor_ap.get(); } + +lldb::addr_t +AppleObjCRuntimeV2::LookupRuntimeSymbol (const ConstString &name) +{ + lldb::addr_t ret = LLDB_INVALID_ADDRESS; + + const char *name_cstr = name.AsCString(); + + if (name_cstr) + { + llvm::StringRef name_strref(name_cstr); + + static const llvm::StringRef ivar_prefix("OBJC_IVAR_$_"); + + if (name_strref.startswith(ivar_prefix)) + { + llvm::StringRef ivar_skipped_prefix = name_strref.substr(ivar_prefix.size()); + std::pair class_and_ivar = ivar_skipped_prefix.split('.'); + + if (class_and_ivar.first.size() && class_and_ivar.second.size()) + { + const ConstString class_name_cs(class_and_ivar.first); + ClassDescriptorSP descriptor = ObjCLanguageRuntime::GetClassDescriptor(class_name_cs); + + if (descriptor) + { + const ConstString ivar_name_cs(class_and_ivar.second); + const char *ivar_name_cstr = ivar_name_cs.AsCString(); + + auto ivar_func = [&ret, ivar_name_cstr](const char *name, const char *type, lldb::addr_t offset_addr, uint64_t size) + { + if (!strcmp(name, ivar_name_cstr)) + { + ret = offset_addr; + return true; + } + return false; + }; + + descriptor->Describe(std::function(nullptr), + std::function(nullptr), + std::function(nullptr), + ivar_func); + } + } + } + } + + return ret; +} diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h index 8dafd0f..5940182 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h @@ -99,6 +99,9 @@ public: virtual TypeVendor * GetTypeVendor(); + virtual lldb::addr_t + LookupRuntimeSymbol (const ConstString &name); + protected: virtual lldb::BreakpointResolverSP CreateExceptionResolver (Breakpoint *bkpt, bool catch_bp, bool throw_bp); diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeVendor.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeVendor.cpp index 1ab1063..69f4209 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeVendor.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeVendor.cpp @@ -529,6 +529,8 @@ AppleObjCTypeVendor::FinishDecl(clang::ObjCInterfaceDecl *interface_decl) if (method_decl) interface_decl->addDecl(method_decl); + + return false; }; auto class_method_func = [log, interface_decl, this](const char *name, const char *types) @@ -542,6 +544,8 @@ AppleObjCTypeVendor::FinishDecl(clang::ObjCInterfaceDecl *interface_decl) if (method_decl) interface_decl->addDecl(method_decl); + + return false; }; if (log) @@ -552,7 +556,10 @@ AppleObjCTypeVendor::FinishDecl(clang::ObjCInterfaceDecl *interface_decl) } - if (!descriptor->Describe(superclass_func, instance_method_func, class_method_func)) + if (!descriptor->Describe(superclass_func, + instance_method_func, + class_method_func, + std::function (nullptr))) return false; if (log) diff --git a/lldb/test/lang/objc/objc-ivar-stripped/Makefile b/lldb/test/lang/objc/objc-ivar-stripped/Makefile new file mode 100644 index 0000000..4365ed9 --- /dev/null +++ b/lldb/test/lang/objc/objc-ivar-stripped/Makefile @@ -0,0 +1,15 @@ +LEVEL = ../../../make + +OBJC_SOURCES := main.m +LDFLAGS = $(CFLAGS) -lobjc -framework Foundation + +default: a.out.stripped + +a.out.stripped: a.out.dSYM + strip -o a.out.stripped a.out + +clean:: + rm -f a.out.stripped + rm -rf a.out.stripped.dSYM + +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/lang/objc/objc-ivar-stripped/TestObjCIvarStripped.py b/lldb/test/lang/objc/objc-ivar-stripped/TestObjCIvarStripped.py new file mode 100644 index 0000000..fef0921 --- /dev/null +++ b/lldb/test/lang/objc/objc-ivar-stripped/TestObjCIvarStripped.py @@ -0,0 +1,63 @@ +"""Test printing ObjC objects that use unbacked properties - so that the static ivar offsets are incorrect.""" + +import os, time +import unittest2 +import lldb +from lldbtest import * +import lldbutil + +class TestObjCIvarStripped(TestBase): + + mydir = os.path.join("lang", "objc", "objc-ivar-stripped") + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @python_api_test + @dsym_test + def test_with_dsym_and_python_api(self): + """Test that we can find stripped Objective-C ivars in the runtime""" + self.buildDsym() + self.objc_ivar_offsets() + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line numbers to break inside main(). + self.main_source = "main.m" + self.stop_line = line_number(self.main_source, '// Set breakpoint here.') + + def objc_ivar_offsets(self): + """Test that we can find stripped Objective-C ivars in the runtime""" + exe = os.path.join(os.getcwd(), "a.out.stripped") + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + breakpoint = target.BreakpointCreateByLocation(self.main_source, self.stop_line) + self.assertTrue(breakpoint, VALID_BREAKPOINT) + + process = target.LaunchSimple (None, None, os.getcwd()) + self.assertTrue (process, "Created a process.") + self.assertTrue (process.GetState() == lldb.eStateStopped, "Stopped it too.") + + thread_list = lldbutil.get_threads_stopped_at_breakpoint (process, breakpoint) + self.assertTrue (len(thread_list) == 1) + thread = thread_list[0] + + frame = thread.GetFrameAtIndex(0) + self.assertTrue (frame, "frame 0 is valid") + + # Test the expression for mc->_foo + + error = lldb.SBError() + + ivar = frame.EvaluateExpression ("(mc->_foo)") + self.assertTrue(ivar, "Got result for mc->_foo") + ivar_value = ivar.GetValueAsSigned (error) + self.assertTrue (error.Success()) + self.assertTrue (ivar_value == 3) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/lang/objc/objc-ivar-stripped/main.m b/lldb/test/lang/objc/objc-ivar-stripped/main.m new file mode 100644 index 0000000..ed9c1d9 --- /dev/null +++ b/lldb/test/lang/objc/objc-ivar-stripped/main.m @@ -0,0 +1,33 @@ +#import + +@interface MyClass : NSObject { +@public + int _foo; +}; + +-(id)init; +@end + +@implementation MyClass + +-(id)init +{ + if ([super init]) + { + _foo = 3; + } + + return self; +} + +@end + +int main () +{ + @autoreleasepool + { + MyClass *mc = [[MyClass alloc] init]; + + NSLog(@"%d", mc->_foo); // Set breakpoint here. + } +} -- 2.7.4