xref: /llvm-project/lldb/examples/python/types.py (revision 694b49f0f8ade5036f6025c83fead31fab01e579)
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 sys
16
17try:
18    # Just try for LLDB in case PYTHONPATH is already correctly setup
19    import lldb
20except ImportError:
21    lldb_python_dirs = list()
22    # lldb is not in the PYTHONPATH, try some defaults for the current platform
23    platform_system = platform.system()
24    if platform_system == 'Darwin':
25        # On Darwin, try the currently selected Xcode directory
26        xcode_dir = commands.getoutput("xcode-select --print-path")
27        if xcode_dir:
28            lldb_python_dirs.append(os.path.realpath(xcode_dir + '/../SharedFrameworks/LLDB.framework/Resources/Python'))
29            lldb_python_dirs.append(xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
30        lldb_python_dirs.append('/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
31    success = False
32    for lldb_python_dir in lldb_python_dirs:
33        if os.path.exists(lldb_python_dir):
34            if not (sys.path.__contains__(lldb_python_dir)):
35                sys.path.append(lldb_python_dir)
36                try:
37                    import lldb
38                except ImportError:
39                    pass
40                else:
41                    print 'imported lldb from: "%s"' % (lldb_python_dir)
42                    success = True
43                    break
44    if not success:
45        print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly"
46        sys.exit(1)
47
48import commands
49import optparse
50import shlex
51import time
52
53def create_types_options():
54    usage = "usage: %prog [options]"
55    description='''This command will help you verify that types in your program
56are packed efficiently by showing all types and their sizes and showing the
57padding bytes that waste space.
58'''
59    parser = optparse.OptionParser(description=description, prog='framestats',usage=usage)
60    parser.add_option('-a', '--arch', type='string', dest='arch', help='The architecture to use when creating the debug target.', default=None)
61    parser.add_option('-p', '--platform', type='string', metavar='platform', dest='platform', help='specify one platform by name')
62    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=[])
63    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=[])
64    parser.add_option('-d', '--debug', action='store_true', dest='debug', help='Pause 10 seconds to wait for a debugger to attach.', default=False)
65    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False)
66    return parser
67
68def verify_type (target, type):
69    print type
70    typename = type.GetName()
71    # print 'type: %s' % (typename)
72    (end_offset, padding) = verify_type_recursive (target, type, None, 0, 0, 0)
73    byte_size = type.GetByteSize()
74    # if end_offset < byte_size:
75    #     last_member_padding = byte_size - end_offset
76    #     print '%+4u <%u> padding' % (end_offset, last_member_padding)
77    #     padding += last_member_padding
78    print 'Total byte size: %u' % (byte_size)
79    print 'Total pad bytes: %u' % (padding)
80    if padding > 0:
81        print 'Padding percentage: %2.2f %%' % ((float(padding) / float(byte_size)) * 100.0)
82    print
83
84def verify_type_recursive (target, type, member_name, depth, base_offset, padding):
85    prev_end_offset = base_offset
86    typename = type.GetName()
87    byte_size = type.GetByteSize()
88    if member_name and member_name != typename:
89        print '%+4u <%3u> %s%s %s;' % (base_offset, type.GetByteSize(), '    ' * depth, typename, member_name)
90    else:
91        print '%+4u <%3u> %s%s' % (base_offset, type.GetByteSize(), '    ' * depth, typename)
92
93    members = type.members
94    if members:
95        for member_idx, member in enumerate(members):
96            member_type = member.GetType()
97            member_canonical_type = member_type.GetCanonicalType()
98            member_type_class = member_canonical_type.GetTypeClass()
99            member_name = member.GetName()
100            member_offset = member.GetOffsetInBytes()
101            member_total_offset = member_offset + base_offset
102            member_byte_size = member_type.GetByteSize()
103            is_class_or_struct = False
104            if member_type_class == lldb.eTypeClassStruct or member_type_class == lldb.eTypeClassClass:
105                is_class_or_struct = True
106            if member_idx == 0 and member_offset == target.GetAddressByteSize() and type.IsPolymorphicClass():
107                ptr_size = target.GetAddressByteSize()
108                print '%+4u <%3u> %s__vtbl_ptr_type * _vptr;' % (prev_end_offset, ptr_size, '    ' * (depth + 1))
109                prev_end_offset = ptr_size
110            else:
111                if prev_end_offset < member_total_offset:
112                    member_padding = member_total_offset - prev_end_offset
113                    padding = padding + member_padding
114                    print '%+4u <%3u> %s<PADDING>' % (prev_end_offset, member_padding, '    ' * (depth + 1))
115
116            if is_class_or_struct:
117                (prev_end_offset, padding) = verify_type_recursive (target, member_canonical_type, member_name, depth + 1, member_total_offset, padding)
118            else:
119                prev_end_offset = member_total_offset + member_byte_size
120                if member.IsBitfield():
121                    print '%+4u <%3u> %s%s:%u %s;' % (member_total_offset, member_byte_size, '    ' * (depth + 1), member_type.GetName(), member.GetBitfieldSizeInBits(), member_name)
122                else:
123                    print '%+4u <%3u> %s%s %s;' % (member_total_offset, member_byte_size, '    ' * (depth + 1), member_type.GetName(), member_name)
124
125        if prev_end_offset < byte_size:
126            last_member_padding = byte_size - prev_end_offset
127            print '%+4u <%3u> %s<PADDING>' % (prev_end_offset, last_member_padding, '    ' * (depth + 1))
128            padding += last_member_padding
129    else:
130        if type.IsPolymorphicClass():
131            ptr_size = target.GetAddressByteSize()
132            print '%+4u <%3u> %s__vtbl_ptr_type * _vptr;' % (prev_end_offset, ptr_size, '    ' * (depth + 1))
133            prev_end_offset = ptr_size
134        prev_end_offset = base_offset + type.GetByteSize()
135
136    return (prev_end_offset, padding)
137
138def types_command (debugger, command, result, dict):
139    # Use the Shell Lexer to properly parse up command options just like a
140    # shell would
141    command_args = shlex.split(command)
142    verify_types(debugger, command_args)
143
144def verify_types (debugger, command_args):
145
146    parser = create_types_options()
147    try:
148        (options, args) = parser.parse_args(command_args)
149    except:
150        # if you don't handle exceptions, passing an incorrect argument to the OptionParser will cause LLDB to exit
151        # (courtesy of OptParse dealing with argument errors by throwing SystemExit)
152        result.SetStatus (lldb.eReturnStatusFailed)
153        return "option parsing failed" # returning a string is the same as returning an error whose description is the string
154
155    if options.debug:
156        print 'Waiting for debugger to attach...'
157        for i in range(10):
158            time.sleep(1)
159            print '.'
160
161    for path in args:
162    # in a command - the lldb.* convenience variables are not to be used
163    # and their values (if any) are undefined
164    # this is the best practice to access those objects from within a command
165        error = lldb.SBError()
166        target = debugger.CreateTarget (path,
167                                        options.arch,
168                                        options.platform,
169                                        True,
170                                        error)
171        if error.Fail():
172            print error.GetCString()
173            continue
174
175        modules = list()
176        if len(options.modules) == 0:
177            # Append just the main executable if nothing was specified
178            module = target.modules[0]
179            if module:
180                modules.append(module)
181        else:
182            for module_name in options.modules:
183                module = lldb.target.module[module_name]
184                if module:
185                    modules.append(module)
186
187        if modules:
188            for module in modules:
189                print 'module: %s' % (module.file)
190                if options.typenames:
191                    for typename in options.typenames:
192                        types = module.FindTypes(typename)
193                        if types.GetSize():
194                            print 'Found %u types matching "%s" in "%s"' % (len(types), typename, module.file)
195                            for type in types:
196                                verify_type (target, type)
197                        else:
198                            print 'error: no type matches "%s" in "%s"' % (typename, module.file)
199                else:
200                    types = module.GetTypes(lldb.eTypeClassClass | lldb.eTypeClassStruct)
201                    print 'Found %u types in "%s"' % (len(types), module.file)
202                    for type in types:
203                        verify_type (target, type)
204        else:
205            print 'error: no modules'
206
207if __name__ == '__main__':
208    debugger = lldb.SBDebugger.Create()
209    verify_types (debugger, sys.argv[1:])
210elif getattr(lldb, 'debugger', None):
211    lldb.debugger.HandleCommand('command script add -f types.types_command types')
212    print '"types" command installed, use the "--help" option for detailed help'