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