| #!/usr/bin/env 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 |
| #---------------------------------------------------------------------- |
| |
| from __future__ import print_function |
| |
| import platform |
| import os |
| import re |
| import signal |
| import sys |
| import subprocess |
| |
| 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 = subprocess.check_output("xcode-select --print-path", shell=True) |
| 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 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') |