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