#!/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' % (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' % (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()