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