| #!/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 re |
| import sys |
| import getopt |
| import argparse |
| import os |
| import subprocess |
| from sets import Set |
| |
| |
| framework = "WebCore" |
| build_directory = "" |
| config = "Release" |
| arch = None |
| |
| 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) |
| |
| LLDB_MODULE_NAME = "lldb" |
| try: |
| globals()[LLDB_MODULE_NAME] = __import__(LLDB_MODULE_NAME) |
| except ImportError: |
| print "Failed to import {} from {}".format(LLDB_MODULE_NAME, lldb_framework_path) |
| sys.exit(1) |
| |
| def verify_type(target, type): |
| typename = type.GetName() |
| seenOffset = Set() |
| (end_offset, padding) = verify_type_recursive(target, type, None, 0, 0, 0, seenOffset) |
| 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, seenOffset): |
| 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_offset, member_name) in seenOffset: |
| continue |
| seenOffset.add((member_offset, member_name)) |
| |
| 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, seenOffset) |
| 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 get_first_file_architecture(framework_path): |
| p = re.compile('shared library +(\w+)$') |
| file_result = subprocess.check_output(["file", framework_path]).split('\n') |
| arches = [] |
| for line in file_result: |
| match = p.search(line) |
| if match: |
| arches.append(match.group(1)); |
| |
| if len(arches) > 1: |
| print 'Found architectures %s, using %s' % (arches, arches[0]) |
| |
| if len(arches) > 0: |
| return arches[0] |
| |
| return lldb.LLDB_ARCH_DEFAULT |
| |
| def dump_class(framework_path, classname, architecture): |
| debugger = lldb.SBDebugger.Create() |
| debugger.SetAsync(False) |
| if not architecture: |
| architecture = get_first_file_architecture(framework_path) |
| |
| target = debugger.CreateTargetWithFileAndArch(framework_path, architecture) |
| if not target: |
| print "Failed to make target for " + framework_path; |
| sys.exit(1) |
| |
| module = target.GetModuleAtIndex(0) |
| if not module: |
| print "Failed to get first module in " + framework_path; |
| sys.exit(1) |
| |
| types = module.FindTypes(classname) |
| if types.GetSize(): |
| print 'Found %u types matching "%s" in "%s" for %s' % (len(types), classname, module.file, architecture) |
| 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)') |
| |
| parser.add_argument('-a', '--architecture', dest='arch', action='store', |
| help='Architecture (i386, x86_64, armv7, armv7s, arm64). Uses the first architecture listed by \'file\' by default') |
| |
| 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, args.arch) |
| |
| if __name__ == "__main__": |
| main() |