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