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