In cases where the Objective-C ivar symbols are stripped out,
authorSean Callanan <scallanan@apple.com>
Thu, 15 Nov 2012 02:02:04 +0000 (02:02 +0000)
committerSean Callanan <scallanan@apple.com>
Thu, 15 Nov 2012 02:02:04 +0000 (02:02 +0000)
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.

<rdar://problem/12628122>

llvm-svn: 168018

lldb/include/lldb/Expression/ClangExpressionDeclMap.h
lldb/include/lldb/Target/ObjCLanguageRuntime.h
lldb/source/Expression/ClangExpressionDeclMap.cpp
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeVendor.cpp
lldb/test/lang/objc/objc-ivar-stripped/Makefile [new file with mode: 0644]
lldb/test/lang/objc/objc-ivar-stripped/TestObjCIvarStripped.py [new file with mode: 0644]
lldb/test/lang/objc/objc-ivar-stripped/main.m [new file with mode: 0644]

index a300053..a32ac33 100644 (file)
@@ -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);
     
index cbfd4a1..140e5f6 100644 (file)
@@ -114,8 +114,9 @@ public:
         // This should return true iff the interface could be completed
         virtual bool
         Describe (std::function <void (ObjCISA)> const &superclass_func,
-                  std::function <void (const char*, const char*)> const &instance_method_func,
-                  std::function <void (const char*, const char*)> const &class_method_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)
         {
             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.
     ///
index f54d53e..4cd74a8 100644 (file)
@@ -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)
         {
index 5b758c8..9fb6f4e 100644 (file)
@@ -1047,8 +1047,9 @@ public:
     
     virtual bool
     Describe (std::function <void (ObjCLanguageRuntime::ObjCISA)> const &superclass_func,
-              std::function <void (const char *, const char *)> const &instance_method_func,
-              std::function <void (const char *, const char *)> const &class_method_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();
 
@@ -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 <void (ObjCLanguageRuntime::ObjCISA)> (nullptr),
                                class_method_func,
-                               std::function <void (const char *, const char *)> (nullptr));
+                               std::function <bool (const char *, const char *)> (nullptr),
+                               std::function <bool (const char *, const char *, lldb::addr_t, uint64_t)> (nullptr));
+        }
+        
+        if (ivar_func)
+        {
+            std::auto_ptr <ivar_list_t> 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_t> 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_t> &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<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::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<void (ObjCISA)>(nullptr),
+                                         std::function<bool (const char *, const char *)>(nullptr),
+                                         std::function<bool (const char *, const char *)>(nullptr),
+                                         ivar_func);
+                }
+            }
+        }
+    } 
+    
+    return ret;
+}
index 8dafd0f..5940182 100644 (file)
@@ -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);
index 1ab1063..69f4209 100644 (file)
@@ -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 <bool (const char *, const char *, lldb::addr_t, uint64_t)> (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 (file)
index 0000000..4365ed9
--- /dev/null
@@ -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 (file)
index 0000000..fef0921
--- /dev/null
@@ -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 (file)
index 0000000..ed9c1d9
--- /dev/null
@@ -0,0 +1,33 @@
+#import <Foundation/Foundation.h>
+
+@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.
+  }
+}