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