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