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