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 12import commands 13import platform 14import os 15import re 16import signal 17import sys 18 19try: 20 # Just try for LLDB in case PYTHONPATH is already correctly setup 21 import lldb 22except 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 50import commands 51import optparse 52import shlex 53import time 54 55def 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 61def 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 65base classes and members in structs and classes. It will summarize the types 66and how much padding was found. If no types are specified with the --types TYPENAME 67option, all structure and class types will be verified. If no modules are 68specified with the --module option, only the target's main executable will be 69searched. 70''' 71 else: 72 usage = "usage: %prog [options] EXEPATH [EXEPATH ...]" 73 description='''This command will help check for padding in between 74base classes and members in structures and classes. It will summarize the types 75and how much padding was found. One or more paths to executable files must be 76specified and targets will be created with these modules. If no types are 77specified with the --types TYPENAME option, all structure and class types will 78be 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 92def 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 108def 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 168def 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 183def 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 221if __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 250elif 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'