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