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