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