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