|  | #!/usr/bin/python | 
|  |  | 
|  | #---------------------------------------------------------------------- | 
|  | # Be sure to add the python path that points to the LLDB shared library. | 
|  | # | 
|  | # # To use this in the embedded python interpreter using "lldb" just | 
|  | # import it with the full path using the "command script import" | 
|  | # command | 
|  | #   (lldb) command script import /path/to/cmdtemplate.py | 
|  | #---------------------------------------------------------------------- | 
|  |  | 
|  | import commands | 
|  | import platform | 
|  | import os | 
|  | import re | 
|  | import signal | 
|  | import sys | 
|  |  | 
|  | try: | 
|  | # Just try for LLDB in case PYTHONPATH is already correctly setup | 
|  | import lldb | 
|  | except ImportError: | 
|  | lldb_python_dirs = list() | 
|  | # lldb is not in the PYTHONPATH, try some defaults for the current platform | 
|  | platform_system = platform.system() | 
|  | if platform_system == 'Darwin': | 
|  | # On Darwin, try the currently selected Xcode directory | 
|  | xcode_dir = commands.getoutput("xcode-select --print-path") | 
|  | if xcode_dir: | 
|  | lldb_python_dirs.append( | 
|  | os.path.realpath( | 
|  | xcode_dir + | 
|  | '/../SharedFrameworks/LLDB.framework/Resources/Python')) | 
|  | lldb_python_dirs.append( | 
|  | xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python') | 
|  | lldb_python_dirs.append( | 
|  | '/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python') | 
|  | success = False | 
|  | for lldb_python_dir in lldb_python_dirs: | 
|  | if os.path.exists(lldb_python_dir): | 
|  | if not (sys.path.__contains__(lldb_python_dir)): | 
|  | sys.path.append(lldb_python_dir) | 
|  | try: | 
|  | import lldb | 
|  | except ImportError: | 
|  | pass | 
|  | else: | 
|  | print 'imported lldb from: "%s"' % (lldb_python_dir) | 
|  | success = True | 
|  | break | 
|  | if not success: | 
|  | print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly" | 
|  | sys.exit(1) | 
|  |  | 
|  | import commands | 
|  | import optparse | 
|  | import shlex | 
|  | import time | 
|  |  | 
|  |  | 
|  | def regex_option_callback(option, opt_str, value, parser): | 
|  | if opt_str == "--std": | 
|  | value = '^std::' | 
|  | regex = re.compile(value) | 
|  | parser.values.skip_type_regexes.append(regex) | 
|  |  | 
|  |  | 
|  | def create_types_options(for_lldb_command): | 
|  | if for_lldb_command: | 
|  | usage = "usage: %prog [options]" | 
|  | description = '''This command will help check for padding in between | 
|  | base classes and members in structs and classes. It will summarize the types | 
|  | and how much padding was found. If no types are specified with the --types TYPENAME | 
|  | option, all structure and class types will be verified. If no modules are | 
|  | specified with the --module option, only the target's main executable will be | 
|  | searched. | 
|  | ''' | 
|  | else: | 
|  | usage = "usage: %prog [options] EXEPATH [EXEPATH ...]" | 
|  | description = '''This command will help check for padding in between | 
|  | base classes and members in structures and classes. It will summarize the types | 
|  | and how much padding was found. One or more paths to executable files must be | 
|  | specified and targets will be created with these modules. If no types are | 
|  | specified with the --types TYPENAME option, all structure and class types will | 
|  | be verified in all specified modules. | 
|  | ''' | 
|  | parser = optparse.OptionParser( | 
|  | description=description, | 
|  | prog='framestats', | 
|  | usage=usage) | 
|  | if not for_lldb_command: | 
|  | parser.add_option( | 
|  | '-a', | 
|  | '--arch', | 
|  | type='string', | 
|  | dest='arch', | 
|  | help='The architecture to use when creating the debug target.', | 
|  | default=None) | 
|  | parser.add_option( | 
|  | '-p', | 
|  | '--platform', | 
|  | type='string', | 
|  | metavar='platform', | 
|  | dest='platform', | 
|  | help='Specify the platform to use when creating the debug target. Valid values include "localhost", "darwin-kernel", "ios-simulator", "remote-freebsd", "remote-macosx", "remote-ios", "remote-linux".') | 
|  | parser.add_option( | 
|  | '-m', | 
|  | '--module', | 
|  | action='append', | 
|  | type='string', | 
|  | metavar='MODULE', | 
|  | dest='modules', | 
|  | help='Specify one or more modules which will be used to verify the types.', | 
|  | default=[]) | 
|  | parser.add_option( | 
|  | '-d', | 
|  | '--debug', | 
|  | action='store_true', | 
|  | dest='debug', | 
|  | help='Pause 10 seconds to wait for a debugger to attach.', | 
|  | default=False) | 
|  | parser.add_option( | 
|  | '-t', | 
|  | '--type', | 
|  | action='append', | 
|  | type='string', | 
|  | metavar='TYPENAME', | 
|  | dest='typenames', | 
|  | help='Specify one or more type names which should be verified. If no type names are specified, all class and struct types will be verified.', | 
|  | default=[]) | 
|  | parser.add_option( | 
|  | '-v', | 
|  | '--verbose', | 
|  | action='store_true', | 
|  | dest='verbose', | 
|  | help='Enable verbose logging and information.', | 
|  | default=False) | 
|  | parser.add_option( | 
|  | '-s', | 
|  | '--skip-type-regex', | 
|  | action="callback", | 
|  | callback=regex_option_callback, | 
|  | type='string', | 
|  | metavar='REGEX', | 
|  | dest='skip_type_regexes', | 
|  | help='Regular expressions that, if they match the current member typename, will cause the type to no be recursively displayed.', | 
|  | default=[]) | 
|  | parser.add_option( | 
|  | '--std', | 
|  | action="callback", | 
|  | callback=regex_option_callback, | 
|  | metavar='REGEX', | 
|  | dest='skip_type_regexes', | 
|  | help="Don't' recurse into types in the std namespace.", | 
|  | default=[]) | 
|  | return parser | 
|  |  | 
|  |  | 
|  | def verify_type(target, options, type): | 
|  | print type | 
|  | typename = type.GetName() | 
|  | # print 'type: %s' % (typename) | 
|  | (end_offset, padding) = verify_type_recursive( | 
|  | target, options, type, None, 0, 0, 0) | 
|  | byte_size = type.GetByteSize() | 
|  | # if end_offset < byte_size: | 
|  | #     last_member_padding = byte_size - end_offset | 
|  | #     print '%+4u <%u> padding' % (end_offset, last_member_padding) | 
|  | #     padding += last_member_padding | 
|  | 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, | 
|  | options, | 
|  | 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) | 
|  |  | 
|  | for type_regex in options.skip_type_regexes: | 
|  | match = type_regex.match(typename) | 
|  | if match: | 
|  | return (base_offset + byte_size, padding) | 
|  |  | 
|  | 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, | 
|  | options, | 
|  | 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 check_padding_command(debugger, command, result, dict): | 
|  | # Use the Shell Lexer to properly parse up command options just like a | 
|  | # shell would | 
|  | command_args = shlex.split(command) | 
|  | parser = create_types_options(True) | 
|  | try: | 
|  | (options, args) = parser.parse_args(command_args) | 
|  | except: | 
|  | # if you don't handle exceptions, passing an incorrect argument to the OptionParser will cause LLDB to exit | 
|  | # (courtesy of OptParse dealing with argument errors by throwing SystemExit) | 
|  | result.SetStatus(lldb.eReturnStatusFailed) | 
|  | # returning a string is the same as returning an error whose | 
|  | # description is the string | 
|  | return "option parsing failed" | 
|  | verify_types(debugger.GetSelectedTarget(), options) | 
|  |  | 
|  |  | 
|  | @lldb.command("parse_all_struct_class_types") | 
|  | def parse_all_struct_class_types(debugger, command, result, dict): | 
|  | command_args = shlex.split(command) | 
|  | for f in command_args: | 
|  | error = lldb.SBError() | 
|  | target = debugger.CreateTarget(f, None, None, False, error) | 
|  | module = target.GetModuleAtIndex(0) | 
|  | print "Parsing all types in '%s'" % (module) | 
|  | types = module.GetTypes(lldb.eTypeClassClass | lldb.eTypeClassStruct) | 
|  | for t in types: | 
|  | print t | 
|  | print "" | 
|  |  | 
|  |  | 
|  | def verify_types(target, options): | 
|  |  | 
|  | if not target: | 
|  | print 'error: invalid target' | 
|  | return | 
|  |  | 
|  | modules = list() | 
|  | if len(options.modules) == 0: | 
|  | # Append just the main executable if nothing was specified | 
|  | module = target.modules[0] | 
|  | if module: | 
|  | modules.append(module) | 
|  | else: | 
|  | for module_name in options.modules: | 
|  | module = lldb.target.module[module_name] | 
|  | if module: | 
|  | modules.append(module) | 
|  |  | 
|  | if modules: | 
|  | for module in modules: | 
|  | print 'module: %s' % (module.file) | 
|  | if options.typenames: | 
|  | for typename in options.typenames: | 
|  | types = module.FindTypes(typename) | 
|  | if types.GetSize(): | 
|  | print 'Found %u types matching "%s" in "%s"' % (len(types), typename, module.file) | 
|  | for type in types: | 
|  | verify_type(target, options, type) | 
|  | else: | 
|  | print 'error: no type matches "%s" in "%s"' % (typename, module.file) | 
|  | else: | 
|  | types = module.GetTypes( | 
|  | lldb.eTypeClassClass | lldb.eTypeClassStruct) | 
|  | print 'Found %u types in "%s"' % (len(types), module.file) | 
|  | for type in types: | 
|  | verify_type(target, options, type) | 
|  | else: | 
|  | print 'error: no modules' | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | debugger = lldb.SBDebugger.Create() | 
|  | parser = create_types_options(False) | 
|  |  | 
|  | # try: | 
|  | (options, args) = parser.parse_args(sys.argv[1:]) | 
|  | # except: | 
|  | #     print "error: option parsing failed" | 
|  | #     sys.exit(1) | 
|  |  | 
|  | if options.debug: | 
|  | print "Waiting for debugger to attach to process %d" % os.getpid() | 
|  | os.kill(os.getpid(), signal.SIGSTOP) | 
|  |  | 
|  | for path in args: | 
|  | # in a command - the lldb.* convenience variables are not to be used | 
|  | # and their values (if any) are undefined | 
|  | # this is the best practice to access those objects from within a | 
|  | # command | 
|  | error = lldb.SBError() | 
|  | target = debugger.CreateTarget(path, | 
|  | options.arch, | 
|  | options.platform, | 
|  | True, | 
|  | error) | 
|  | if error.Fail(): | 
|  | print error.GetCString() | 
|  | continue | 
|  | verify_types(target, options) | 
|  |  | 
|  | elif getattr(lldb, 'debugger', None): | 
|  | lldb.debugger.HandleCommand( | 
|  | 'command script add -f types.check_padding_command check_padding') | 
|  | print '"check_padding" command installed, use the "--help" option for detailed help' |