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( 31 os.path.realpath( 32 xcode_dir + 33 '/../SharedFrameworks/LLDB.framework/Resources/Python')) 34 lldb_python_dirs.append( 35 xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python') 36 lldb_python_dirs.append( 37 '/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python') 38 success = False 39 for lldb_python_dir in lldb_python_dirs: 40 if os.path.exists(lldb_python_dir): 41 if not (sys.path.__contains__(lldb_python_dir)): 42 sys.path.append(lldb_python_dir) 43 try: 44 import lldb 45 except ImportError: 46 pass 47 else: 48 print 'imported lldb from: "%s"' % (lldb_python_dir) 49 success = True 50 break 51 if not success: 52 print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly" 53 sys.exit(1) 54 55import commands 56import optparse 57import shlex 58import time 59 60 61def regex_option_callback(option, opt_str, value, parser): 62 if opt_str == "--std": 63 value = '^std::' 64 regex = re.compile(value) 65 parser.values.skip_type_regexes.append(regex) 66 67 68def create_types_options(for_lldb_command): 69 if for_lldb_command: 70 usage = "usage: %prog [options]" 71 description = '''This command will help check for padding in between 72base classes and members in structs and classes. It will summarize the types 73and how much padding was found. If no types are specified with the --types TYPENAME 74option, all structure and class types will be verified. If no modules are 75specified with the --module option, only the target's main executable will be 76searched. 77''' 78 else: 79 usage = "usage: %prog [options] EXEPATH [EXEPATH ...]" 80 description = '''This command will help check for padding in between 81base classes and members in structures and classes. It will summarize the types 82and how much padding was found. One or more paths to executable files must be 83specified and targets will be created with these modules. If no types are 84specified with the --types TYPENAME option, all structure and class types will 85be verified in all specified modules. 86''' 87 parser = optparse.OptionParser( 88 description=description, 89 prog='framestats', 90 usage=usage) 91 if not for_lldb_command: 92 parser.add_option( 93 '-a', 94 '--arch', 95 type='string', 96 dest='arch', 97 help='The architecture to use when creating the debug target.', 98 default=None) 99 parser.add_option( 100 '-p', 101 '--platform', 102 type='string', 103 metavar='platform', 104 dest='platform', 105 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".') 106 parser.add_option( 107 '-m', 108 '--module', 109 action='append', 110 type='string', 111 metavar='MODULE', 112 dest='modules', 113 help='Specify one or more modules which will be used to verify the types.', 114 default=[]) 115 parser.add_option( 116 '-d', 117 '--debug', 118 action='store_true', 119 dest='debug', 120 help='Pause 10 seconds to wait for a debugger to attach.', 121 default=False) 122 parser.add_option( 123 '-t', 124 '--type', 125 action='append', 126 type='string', 127 metavar='TYPENAME', 128 dest='typenames', 129 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.', 130 default=[]) 131 parser.add_option( 132 '-v', 133 '--verbose', 134 action='store_true', 135 dest='verbose', 136 help='Enable verbose logging and information.', 137 default=False) 138 parser.add_option( 139 '-s', 140 '--skip-type-regex', 141 action="callback", 142 callback=regex_option_callback, 143 type='string', 144 metavar='REGEX', 145 dest='skip_type_regexes', 146 help='Regular expressions that, if they match the current member typename, will cause the type to no be recursively displayed.', 147 default=[]) 148 parser.add_option( 149 '--std', 150 action="callback", 151 callback=regex_option_callback, 152 metavar='REGEX', 153 dest='skip_type_regexes', 154 help="Don't' recurse into types in the std namespace.", 155 default=[]) 156 return parser 157 158 159def verify_type(target, options, type): 160 print type 161 typename = type.GetName() 162 # print 'type: %s' % (typename) 163 (end_offset, padding) = verify_type_recursive( 164 target, options, type, None, 0, 0, 0) 165 byte_size = type.GetByteSize() 166 # if end_offset < byte_size: 167 # last_member_padding = byte_size - end_offset 168 # print '%+4u <%u> padding' % (end_offset, last_member_padding) 169 # padding += last_member_padding 170 print 'Total byte size: %u' % (byte_size) 171 print 'Total pad bytes: %u' % (padding) 172 if padding > 0: 173 print 'Padding percentage: %2.2f %%' % ((float(padding) / float(byte_size)) * 100.0) 174 print 175 176 177def verify_type_recursive( 178 target, 179 options, 180 type, 181 member_name, 182 depth, 183 base_offset, 184 padding): 185 prev_end_offset = base_offset 186 typename = type.GetName() 187 byte_size = type.GetByteSize() 188 if member_name and member_name != typename: 189 print '%+4u <%3u> %s%s %s;' % (base_offset, byte_size, ' ' * depth, typename, member_name) 190 else: 191 print '%+4u {%3u} %s%s' % (base_offset, byte_size, ' ' * depth, typename) 192 193 for type_regex in options.skip_type_regexes: 194 match = type_regex.match(typename) 195 if match: 196 return (base_offset + byte_size, padding) 197 198 members = type.members 199 if members: 200 for member_idx, member in enumerate(members): 201 member_type = member.GetType() 202 member_canonical_type = member_type.GetCanonicalType() 203 member_type_class = member_canonical_type.GetTypeClass() 204 member_name = member.GetName() 205 member_offset = member.GetOffsetInBytes() 206 member_total_offset = member_offset + base_offset 207 member_byte_size = member_type.GetByteSize() 208 member_is_class_or_struct = False 209 if member_type_class == lldb.eTypeClassStruct or member_type_class == lldb.eTypeClassClass: 210 member_is_class_or_struct = True 211 if member_idx == 0 and member_offset == target.GetAddressByteSize( 212 ) and type.IsPolymorphicClass(): 213 ptr_size = target.GetAddressByteSize() 214 print '%+4u <%3u> %s__vtbl_ptr_type * _vptr;' % (prev_end_offset, ptr_size, ' ' * (depth + 1)) 215 prev_end_offset = ptr_size 216 else: 217 if prev_end_offset < member_total_offset: 218 member_padding = member_total_offset - prev_end_offset 219 padding = padding + member_padding 220 print '%+4u <%3u> %s<PADDING>' % (prev_end_offset, member_padding, ' ' * (depth + 1)) 221 222 if member_is_class_or_struct: 223 (prev_end_offset, 224 padding) = verify_type_recursive(target, 225 options, 226 member_canonical_type, 227 member_name, 228 depth + 1, 229 member_total_offset, 230 padding) 231 else: 232 prev_end_offset = member_total_offset + member_byte_size 233 member_typename = member_type.GetName() 234 if member.IsBitfield(): 235 print '%+4u <%3u> %s%s:%u %s;' % (member_total_offset, member_byte_size, ' ' * (depth + 1), member_typename, member.GetBitfieldSizeInBits(), member_name) 236 else: 237 print '%+4u <%3u> %s%s %s;' % (member_total_offset, member_byte_size, ' ' * (depth + 1), member_typename, member_name) 238 239 if prev_end_offset < byte_size: 240 last_member_padding = byte_size - prev_end_offset 241 print '%+4u <%3u> %s<PADDING>' % (prev_end_offset, last_member_padding, ' ' * (depth + 1)) 242 padding += last_member_padding 243 else: 244 if type.IsPolymorphicClass(): 245 ptr_size = target.GetAddressByteSize() 246 print '%+4u <%3u> %s__vtbl_ptr_type * _vptr;' % (prev_end_offset, ptr_size, ' ' * (depth + 1)) 247 prev_end_offset = ptr_size 248 prev_end_offset = base_offset + byte_size 249 250 return (prev_end_offset, padding) 251 252 253def check_padding_command(debugger, command, result, dict): 254 # Use the Shell Lexer to properly parse up command options just like a 255 # shell would 256 command_args = shlex.split(command) 257 parser = create_types_options(True) 258 try: 259 (options, args) = parser.parse_args(command_args) 260 except: 261 # if you don't handle exceptions, passing an incorrect argument to the OptionParser will cause LLDB to exit 262 # (courtesy of OptParse dealing with argument errors by throwing SystemExit) 263 result.SetStatus(lldb.eReturnStatusFailed) 264 # returning a string is the same as returning an error whose 265 # description is the string 266 return "option parsing failed" 267 verify_types(debugger.GetSelectedTarget(), options) 268 269 270@lldb.command("parse_all_struct_class_types") 271def parse_all_struct_class_types(debugger, command, result, dict): 272 command_args = shlex.split(command) 273 for f in command_args: 274 error = lldb.SBError() 275 target = debugger.CreateTarget(f, None, None, False, error) 276 module = target.GetModuleAtIndex(0) 277 print "Parsing all types in '%s'" % (module) 278 types = module.GetTypes(lldb.eTypeClassClass | lldb.eTypeClassStruct) 279 for t in types: 280 print t 281 print "" 282 283 284def verify_types(target, options): 285 286 if not target: 287 print 'error: invalid target' 288 return 289 290 modules = list() 291 if len(options.modules) == 0: 292 # Append just the main executable if nothing was specified 293 module = target.modules[0] 294 if module: 295 modules.append(module) 296 else: 297 for module_name in options.modules: 298 module = lldb.target.module[module_name] 299 if module: 300 modules.append(module) 301 302 if modules: 303 for module in modules: 304 print 'module: %s' % (module.file) 305 if options.typenames: 306 for typename in options.typenames: 307 types = module.FindTypes(typename) 308 if types.GetSize(): 309 print 'Found %u types matching "%s" in "%s"' % (len(types), typename, module.file) 310 for type in types: 311 verify_type(target, options, type) 312 else: 313 print 'error: no type matches "%s" in "%s"' % (typename, module.file) 314 else: 315 types = module.GetTypes( 316 lldb.eTypeClassClass | lldb.eTypeClassStruct) 317 print 'Found %u types in "%s"' % (len(types), module.file) 318 for type in types: 319 verify_type(target, options, type) 320 else: 321 print 'error: no modules' 322 323if __name__ == '__main__': 324 debugger = lldb.SBDebugger.Create() 325 parser = create_types_options(False) 326 327 # try: 328 (options, args) = parser.parse_args(sys.argv[1:]) 329 # except: 330 # print "error: option parsing failed" 331 # sys.exit(1) 332 333 if options.debug: 334 print "Waiting for debugger to attach to process %d" % os.getpid() 335 os.kill(os.getpid(), signal.SIGSTOP) 336 337 for path in args: 338 # in a command - the lldb.* convenience variables are not to be used 339 # and their values (if any) are undefined 340 # this is the best practice to access those objects from within a 341 # command 342 error = lldb.SBError() 343 target = debugger.CreateTarget(path, 344 options.arch, 345 options.platform, 346 True, 347 error) 348 if error.Fail(): 349 print error.GetCString() 350 continue 351 verify_types(target, options) 352 353elif getattr(lldb, 'debugger', None): 354 lldb.debugger.HandleCommand( 355 'command script add -f types.check_padding_command check_padding') 356 print '"check_padding" command installed, use the "--help" option for detailed help' 357