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