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