Add a tool that dumps class and struct member layout, showing padding
authorsimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 12 Aug 2015 19:31:38 +0000 (19:31 +0000)
committersimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 12 Aug 2015 19:31:38 +0000 (19:31 +0000)
https://bugs.webkit.org/show_bug.cgi?id=147898

Reviewed by Zalan Bujtas.

This 'dump-class-layout' script uses the lldb Python bindings to collect data
about data member layout, and displays it.

Sample output:

      +0 { 72} WTF::ListHashSet<WebCore::URL, WebCore::URLHash>::Node
      +0 < 56>     WebCore::URL m_value;
      +0 <  8>         WTF::String m_string;
      +0 <  8>             WTF::RefPtr<WTF::StringImpl> m_impl;
      +0 <  8>                 WTF::StringImpl * m_ptr;
      +8 <  1>         bool:1 m_isValid;
      +8 <  1>         bool:1 m_protocolIsInHTTPFamily;
      +9 <  3>         <PADDING>
     +12 <  4>         int m_schemeEnd;
     +16 <  4>         int m_userStart;
     +20 <  4>         int m_userEnd;
     +24 <  4>         int m_passwordEnd;
     +28 <  4>         int m_hostEnd;
     +32 <  4>         int m_portEnd;
     +36 <  4>         int m_pathAfterLastSlash;
     +40 <  4>         int m_pathEnd;
     +44 <  4>         int m_queryEnd;
     +48 <  4>         int m_fragmentEnd;
     +52 <  4>         <PADDING>
     +52 <  4>     <PADDING>
     +56 <  8>     WTF::ListHashSetNode<WebCore::URL> * m_prev;
     +64 <  8>     WTF::ListHashSetNode<WebCore::URL> * m_next;
    Total byte size: 72
    Total pad bytes: 11
    Padding percentage: 15.28 %

* Scripts/dump-class-layout: Added.
(webkit_build_dir):
(developer_dir):
(import_lldb):
(find_build_directory):
(verify_type):
(verify_type_recursive):
(dump_class):
(main):
(main.or):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@188350 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/Scripts/dump-class-layout [new file with mode: 0755]

index bd1857f..02325a8 100644 (file)
@@ -1,3 +1,52 @@
+2015-08-12  Simon Fraser  <simon.fraser@apple.com>
+
+        Add a tool that dumps class and struct member layout, showing padding
+        https://bugs.webkit.org/show_bug.cgi?id=147898
+
+        Reviewed by Zalan Bujtas.
+        
+        This 'dump-class-layout' script uses the lldb Python bindings to collect data
+        about data member layout, and displays it.
+        
+        Sample output:
+
+              +0 { 72} WTF::ListHashSet<WebCore::URL, WebCore::URLHash>::Node
+              +0 < 56>     WebCore::URL m_value;
+              +0 <  8>         WTF::String m_string;
+              +0 <  8>             WTF::RefPtr<WTF::StringImpl> m_impl;
+              +0 <  8>                 WTF::StringImpl * m_ptr;
+              +8 <  1>         bool:1 m_isValid;
+              +8 <  1>         bool:1 m_protocolIsInHTTPFamily;
+              +9 <  3>         <PADDING>
+             +12 <  4>         int m_schemeEnd;
+             +16 <  4>         int m_userStart;
+             +20 <  4>         int m_userEnd;
+             +24 <  4>         int m_passwordEnd;
+             +28 <  4>         int m_hostEnd;
+             +32 <  4>         int m_portEnd;
+             +36 <  4>         int m_pathAfterLastSlash;
+             +40 <  4>         int m_pathEnd;
+             +44 <  4>         int m_queryEnd;
+             +48 <  4>         int m_fragmentEnd;
+             +52 <  4>         <PADDING>
+             +52 <  4>     <PADDING>
+             +56 <  8>     WTF::ListHashSetNode<WebCore::URL> * m_prev;
+             +64 <  8>     WTF::ListHashSetNode<WebCore::URL> * m_next;
+            Total byte size: 72
+            Total pad bytes: 11
+            Padding percentage: 15.28 %
+
+        * Scripts/dump-class-layout: Added.
+        (webkit_build_dir):
+        (developer_dir):
+        (import_lldb):
+        (find_build_directory):
+        (verify_type):
+        (verify_type_recursive):
+        (dump_class):
+        (main):
+        (main.or):
+
 2015-08-12  Alex Christensen  <achristensen@webkit.org>
 
         Fix Debug CMake builds on Windows
diff --git a/Tools/Scripts/dump-class-layout b/Tools/Scripts/dump-class-layout
new file mode 100755 (executable)
index 0000000..914adb0
--- /dev/null
@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2011-2015 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+import sys
+import getopt
+import argparse
+import os
+import subprocess;
+
+
+framework = "WebCore"
+build_directory = ""
+config = "Release"
+
+def webkit_build_dir():
+    scriptpath = os.path.dirname(os.path.realpath(__file__))
+    return subprocess.check_output([os.path.join(scriptpath, "webkit-build-directory"), "--top-level"]).strip()
+
+def developer_dir():
+    return subprocess.check_output(["xcode-select", "--print-path"])
+
+def import_lldb():
+    xcode_contents_path = os.path.split(developer_dir())[0];
+    lldb_framework_path = os.path.join(xcode_contents_path, "SharedFrameworks", "LLDB.framework", "Resources", "Python");
+    sys.path.append(lldb_framework_path)
+    import lldb
+
+def find_build_directory():
+    return
+
+
+def verify_type(target, type):
+    typename = type.GetName()
+    (end_offset, padding) = verify_type_recursive(target, type, None, 0, 0, 0)
+    byte_size = type.GetByteSize()
+    print 'Total byte size: %u' % (byte_size)
+    print 'Total pad bytes: %u' % (padding)
+    if padding > 0:
+        print 'Padding percentage: %2.2f %%' % ((float(padding) / float(byte_size)) * 100.0)
+    print
+
+def verify_type_recursive(target, type, member_name, depth, base_offset, padding):
+    prev_end_offset = base_offset
+    typename = type.GetName()
+    byte_size = type.GetByteSize()
+    if member_name and member_name != typename:
+        print '%+4u <%3u> %s%s %s;' % (base_offset, byte_size, '    ' * depth, typename, member_name)
+    else:
+        print '%+4u {%3u} %s%s' % (base_offset, byte_size, '    ' * depth, typename)
+
+    members = type.members
+    if members:
+        for member_idx, member in enumerate(members):
+            member_type = member.GetType()
+            member_canonical_type = member_type.GetCanonicalType()
+            member_type_class = member_canonical_type.GetTypeClass()
+            member_name = member.GetName()
+            member_offset = member.GetOffsetInBytes()
+            member_total_offset = member_offset + base_offset
+            member_byte_size = member_type.GetByteSize()
+            member_is_class_or_struct = False
+            if member_type_class == lldb.eTypeClassStruct or member_type_class == lldb.eTypeClassClass:
+                member_is_class_or_struct = True
+            if member_idx == 0 and member_offset == target.GetAddressByteSize() and type.IsPolymorphicClass():
+                ptr_size = target.GetAddressByteSize()
+                print '%+4u <%3u> %s__vtbl_ptr_type * _vptr;' % (prev_end_offset, ptr_size, '    ' * (depth + 1))
+                prev_end_offset = ptr_size
+            else:
+                if prev_end_offset < member_total_offset:
+                    member_padding = member_total_offset - prev_end_offset
+                    padding = padding + member_padding
+                    print '%+4u <%3u> %s<PADDING>' % (prev_end_offset, member_padding, '    ' * (depth + 1))
+
+            if member_is_class_or_struct:
+                (prev_end_offset, padding) = verify_type_recursive(target, member_canonical_type, member_name, depth + 1, member_total_offset, padding)
+            else:
+                prev_end_offset = member_total_offset + member_byte_size
+                member_typename = member_type.GetName()
+                if member.IsBitfield():
+                    print '%+4u <%3u> %s%s:%u %s;' % (member_total_offset, member_byte_size, '    ' * (depth + 1), member_typename, member.GetBitfieldSizeInBits(), member_name)
+                else:
+                    print '%+4u <%3u> %s%s %s;' % (member_total_offset, member_byte_size, '    ' * (depth + 1), member_typename, member_name)
+
+        if prev_end_offset < byte_size:
+            last_member_padding = byte_size - prev_end_offset
+            print '%+4u <%3u> %s<PADDING>' % (prev_end_offset, last_member_padding, '    ' * (depth + 1))
+            padding += last_member_padding
+    else:
+        if type.IsPolymorphicClass():
+            ptr_size = target.GetAddressByteSize()
+            print '%+4u <%3u> %s__vtbl_ptr_type * _vptr;' % (prev_end_offset, ptr_size, '    ' * (depth + 1))
+            prev_end_offset = ptr_size
+        prev_end_offset = base_offset + byte_size
+
+    return (prev_end_offset, padding)
+
+def dump_class(framework, classname):
+    debugger = lldb.SBDebugger.Create()
+    debugger.SetAsync (False)
+    target = debugger.CreateTargetWithFileAndArch(framework, lldb.LLDB_ARCH_DEFAULT)
+    if not target:
+        print "Failed to make target for " + framework;
+        sys.exit(1)
+
+    module = target.GetModuleAtIndex(0)
+    if not module:
+        print "Failed to get first module in " + framework;
+        sys.exit(1)
+
+    types = module.FindTypes(classname)
+    if types.GetSize():
+        print 'Found %u types matching "%s" in "%s"' % (len(types), classname, module.file)
+        for type in types:
+            verify_type(target, type)
+    else:
+        print 'error: no type matches "%s" in "%s"' % (classname, module.file)
+
+    lldb.SBDebugger.Destroy(debugger)
+
+def main():
+    parser = argparse.ArgumentParser(description='Dumps the in-memory layout of the given class or classes, showing padding holes.')
+    parser.add_argument('framework', metavar='framework',
+        help='name of the framework containing the class (e.g. "WebCore")')
+    parser.add_argument('classname', metavar='classname',
+        help='name of the class or struct to dump')
+
+    parser.add_argument('-b', '--build-directory', dest='build_directory', action='store',
+        help='Path to the directory under which build files are kept (should not include configuration)')
+
+    parser.add_argument('-c', '--configuration', dest='config', action='store',
+        help='Configuration (Debug or Release)')
+
+    args = parser.parse_args()
+    build_dir = webkit_build_dir()
+
+    if args.config == None:
+        args.config = "Release"
+
+    if not args.build_directory == None:
+        build_dir = args.build_directory
+
+    target_path = os.path.join(build_dir, args.config, args.framework + ".framework", args.framework);
+    import_lldb()
+    dump_class(target_path, args.classname)
+
+if __name__ == "__main__":
+    main()