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 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".')
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'