Home | History | Annotate | Download | only in python
      1 #!/usr/bin/python
      2 
      3 #----------------------------------------------------------------------
      4 # Be sure to add the python path that points to the LLDB shared library.
      5 #
      6 # # To use this in the embedded python interpreter using "lldb" just
      7 # import it with the full path using the "command script import" 
      8 # command
      9 #   (lldb) command script import /path/to/cmdtemplate.py
     10 #----------------------------------------------------------------------
     11 
     12 import commands
     13 import platform
     14 import os
     15 import re
     16 import signal
     17 import sys
     18 
     19 try: 
     20     # Just try for LLDB in case PYTHONPATH is already correctly setup
     21     import lldb
     22 except ImportError:
     23     lldb_python_dirs = list()
     24     # lldb is not in the PYTHONPATH, try some defaults for the current platform
     25     platform_system = platform.system()
     26     if platform_system == 'Darwin':
     27         # On Darwin, try the currently selected Xcode directory
     28         xcode_dir = commands.getoutput("xcode-select --print-path")
     29         if xcode_dir:
     30             lldb_python_dirs.append(os.path.realpath(xcode_dir + '/../SharedFrameworks/LLDB.framework/Resources/Python'))
     31             lldb_python_dirs.append(xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
     32         lldb_python_dirs.append('/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
     33     success = False
     34     for lldb_python_dir in lldb_python_dirs:
     35         if os.path.exists(lldb_python_dir):
     36             if not (sys.path.__contains__(lldb_python_dir)):
     37                 sys.path.append(lldb_python_dir)
     38                 try: 
     39                     import lldb
     40                 except ImportError:
     41                     pass
     42                 else:
     43                     print 'imported lldb from: "%s"' % (lldb_python_dir)
     44                     success = True
     45                     break
     46     if not success:
     47         print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly"
     48         sys.exit(1)
     49 
     50 import commands
     51 import optparse
     52 import shlex
     53 import time
     54 
     55 def regex_option_callback(option, opt_str, value, parser):
     56     if opt_str == "--std":
     57         value = '^std::'
     58     regex = re.compile(value)
     59     parser.values.skip_type_regexes.append (regex)
     60 
     61 def create_types_options(for_lldb_command):
     62     if for_lldb_command:
     63         usage = "usage: %prog [options]"
     64         description='''This command will help check for padding in between
     65 base classes and members in structs and classes. It will summarize the types
     66 and how much padding was found. If no types are specified with the --types TYPENAME
     67 option, all structure and class types will be verified. If no modules are 
     68 specified with the --module option, only the target's main executable will be
     69 searched.
     70 '''
     71     else:
     72         usage = "usage: %prog [options] EXEPATH [EXEPATH ...]"
     73         description='''This command will help check for padding in between
     74 base classes and members in structures and classes. It will summarize the types
     75 and how much padding was found. One or more paths to executable files must be
     76 specified and targets will be created with these modules. If no types are 
     77 specified with the --types TYPENAME option, all structure and class types will
     78 be verified in all specified modules.
     79 '''
     80     parser = optparse.OptionParser(description=description, prog='framestats',usage=usage)
     81     if not for_lldb_command:
     82         parser.add_option('-a', '--arch', type='string', dest='arch', help='The architecture to use when creating the debug target.', default=None)
     83         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".')
     84     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=[])
     85     parser.add_option('-d', '--debug', action='store_true', dest='debug', help='Pause 10 seconds to wait for a debugger to attach.', default=False)
     86     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=[])
     87     parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='Enable verbose logging and information.', default=False)
     88     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=[])
     89     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=[])
     90     return parser
     91 
     92 def verify_type (target, options, type):
     93     print type
     94     typename = type.GetName()
     95     # print 'type: %s' % (typename)
     96     (end_offset, padding) = verify_type_recursive (target, options, type, None, 0, 0, 0)
     97     byte_size = type.GetByteSize()
     98     # if end_offset < byte_size:
     99     #     last_member_padding = byte_size - end_offset
    100     #     print '%+4u <%u> padding' % (end_offset, last_member_padding)
    101     #     padding += last_member_padding
    102     print 'Total byte size: %u' % (byte_size)
    103     print 'Total pad bytes: %u' % (padding)
    104     if padding > 0:
    105         print 'Padding percentage: %2.2f %%' % ((float(padding) / float(byte_size)) * 100.0)
    106     print
    107 
    108 def verify_type_recursive (target, options, type, member_name, depth, base_offset, padding):
    109     prev_end_offset = base_offset
    110     typename = type.GetName()
    111     byte_size = type.GetByteSize()
    112     if member_name and member_name != typename:
    113         print '%+4u <%3u> %s%s %s;' % (base_offset, byte_size, '    ' * depth, typename, member_name)
    114     else:
    115         print '%+4u {%3u} %s%s' % (base_offset, byte_size, '    ' * depth, typename)
    116     
    117     for type_regex in options.skip_type_regexes:
    118         match = type_regex.match (typename)
    119         if match:
    120             return (base_offset + byte_size, padding)
    121                 
    122     members = type.members
    123     if members:
    124         for member_idx, member in enumerate(members):
    125             member_type = member.GetType()
    126             member_canonical_type = member_type.GetCanonicalType()
    127             member_type_class = member_canonical_type.GetTypeClass()
    128             member_name = member.GetName()
    129             member_offset = member.GetOffsetInBytes()
    130             member_total_offset = member_offset + base_offset
    131             member_byte_size = member_type.GetByteSize()
    132             member_is_class_or_struct = False
    133             if member_type_class == lldb.eTypeClassStruct or member_type_class == lldb.eTypeClassClass:
    134                 member_is_class_or_struct = True
    135             if member_idx == 0 and member_offset == target.GetAddressByteSize() and type.IsPolymorphicClass():
    136                 ptr_size = target.GetAddressByteSize()
    137                 print '%+4u <%3u> %s__vtbl_ptr_type * _vptr;' % (prev_end_offset, ptr_size, '    ' * (depth + 1))
    138                 prev_end_offset = ptr_size
    139             else:
    140                 if prev_end_offset < member_total_offset:
    141                     member_padding = member_total_offset - prev_end_offset
    142                     padding = padding + member_padding
    143                     print '%+4u <%3u> %s<PADDING>' % (prev_end_offset, member_padding, '    ' * (depth + 1))
    144         
    145             if member_is_class_or_struct:
    146                 (prev_end_offset, padding) = verify_type_recursive (target, options, member_canonical_type, member_name, depth + 1, member_total_offset, padding)
    147             else:
    148                 prev_end_offset = member_total_offset + member_byte_size
    149                 member_typename = member_type.GetName()
    150                 if member.IsBitfield():
    151                     print '%+4u <%3u> %s%s:%u %s;' % (member_total_offset, member_byte_size, '    ' * (depth + 1), member_typename, member.GetBitfieldSizeInBits(), member_name)
    152                 else:
    153                     print '%+4u <%3u> %s%s %s;' % (member_total_offset, member_byte_size, '    ' * (depth + 1), member_typename, member_name)
    154     
    155         if prev_end_offset < byte_size:
    156             last_member_padding = byte_size - prev_end_offset
    157             print '%+4u <%3u> %s<PADDING>' % (prev_end_offset, last_member_padding, '    ' * (depth + 1))
    158             padding += last_member_padding
    159     else:
    160         if type.IsPolymorphicClass():
    161             ptr_size = target.GetAddressByteSize()
    162             print '%+4u <%3u> %s__vtbl_ptr_type * _vptr;' % (prev_end_offset, ptr_size, '    ' * (depth + 1))
    163             prev_end_offset = ptr_size
    164         prev_end_offset = base_offset + byte_size
    165     
    166     return (prev_end_offset, padding)
    167     
    168 def check_padding_command (debugger, command, result, dict):
    169     # Use the Shell Lexer to properly parse up command options just like a 
    170     # shell would
    171     command_args = shlex.split(command)
    172     parser = create_types_options(True)
    173     try:
    174         (options, args) = parser.parse_args(command_args)
    175     except:
    176         # if you don't handle exceptions, passing an incorrect argument to the OptionParser will cause LLDB to exit
    177         # (courtesy of OptParse dealing with argument errors by throwing SystemExit)
    178         result.SetStatus (lldb.eReturnStatusFailed)
    179         return "option parsing failed" # returning a string is the same as returning an error whose description is the string
    180     verify_types(options, debugger.GetSelectedTarget(), command_args)
    181     
    182     
    183 def verify_types (target, options):
    184 
    185     if not target:
    186         print 'error: invalid target'
    187         return
    188     
    189     modules = list()
    190     if len(options.modules) == 0:
    191         # Append just the main executable if nothing was specified
    192         module = target.modules[0]
    193         if module:
    194             modules.append(module)
    195     else:
    196         for module_name in options.modules:
    197             module = lldb.target.module[module_name]
    198             if module:
    199                 modules.append(module)
    200     
    201     if modules:
    202         for module in modules:
    203             print 'module: %s' % (module.file)
    204             if options.typenames:
    205                 for typename in options.typenames:
    206                     types = module.FindTypes(typename)
    207                     if types.GetSize():
    208                         print 'Found %u types matching "%s" in "%s"' % (len(types), typename, module.file)
    209                         for type in types:
    210                             verify_type (target, options, type)
    211                     else:
    212                         print 'error: no type matches "%s" in "%s"' % (typename, module.file)
    213             else:
    214                 types = module.GetTypes(lldb.eTypeClassClass | lldb.eTypeClassStruct)
    215                 print 'Found %u types in "%s"' % (len(types), module.file)
    216                 for type in types:
    217                     verify_type (target, options, type)
    218     else:
    219         print 'error: no modules'
    220 
    221 if __name__ == '__main__':
    222     debugger = lldb.SBDebugger.Create()
    223     parser = create_types_options(False)
    224     
    225     # try:
    226     (options, args) = parser.parse_args(sys.argv[1:])
    227     # except:
    228     #     print "error: option parsing failed" 
    229     #     sys.exit(1)
    230     
    231     if options.debug:
    232         print "Waiting for debugger to attach to process %d" % os.getpid()
    233         os.kill(os.getpid(), signal.SIGSTOP)
    234         
    235     for path in args:
    236     # in a command - the lldb.* convenience variables are not to be used
    237     # and their values (if any) are undefined
    238     # this is the best practice to access those objects from within a command
    239         error = lldb.SBError()
    240         target = debugger.CreateTarget (path,
    241                                         options.arch,
    242                                         options.platform,
    243                                         True,
    244                                         error)
    245         if error.Fail():
    246             print error.GetCString()
    247             continue
    248         verify_types (target, options)
    249     
    250 elif getattr(lldb, 'debugger', None):
    251     lldb.debugger.HandleCommand('command script add -f types.check_padding_command check_padding')
    252     print '"check_padding" command installed, use the "--help" option for detailed help'