blob: d41864455836e3b0e2b06546b9bab62668586ec5 [file] [log] [blame]
#!/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()