File: action_debugging.py

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (568 lines) | stat: -rw-r--r-- 20,111 bytes parent folder | download | duplicates (12)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
#!/usr/bin/env python

# ---------------------------------------------------------------------
# Be sure to add the python path that points to the LLDB shared library.
#
# # To use this in the embedded python interpreter using "lldb" just
# import it with the full path using the "command script import"
# command
#   (lldb) command script import /path/to/cmdtemplate.py
# ---------------------------------------------------------------------

import inspect
import lldb
import argparse
import shlex
import sys

# Each new breakpoint gets a unique ID starting from 1.
nextid = 1
# List of breakpoint set from python, the key is the ID and the value the
# actual breakpoint. These are NOT LLDB SBBreakpoint objects.
breakpoints = dict()

exprOptions = lldb.SBExpressionOptions()
exprOptions.SetIgnoreBreakpoints()
exprOptions.SetLanguage(lldb.eLanguageTypeC)


class MlirDebug:
    """MLIR debugger commands
    This is the class that hooks into LLDB and registers the `mlir` command.
    Other providers can register subcommands below this one.
    """

    lldb_command = "mlir"
    parser = None

    def __init__(self, debugger, unused):
        super().__init__()
        self.create_options()
        self.help_string = MlirDebug.parser.format_help()

    @classmethod
    def create_options(cls):
        if MlirDebug.parser:
            return MlirDebug.parser
        usage = "usage: %s [options]" % (cls.lldb_command)
        description = "TODO."

        # Pass add_help_option = False, since this keeps the command in line
        # with lldb commands, and we wire up "help command" to work by
        # providing the long & short help methods below.
        MlirDebug.parser = argparse.ArgumentParser(
            prog=cls.lldb_command, usage=usage, description=description, add_help=False
        )
        MlirDebug.subparsers = MlirDebug.parser.add_subparsers(dest="command")
        return MlirDebug.parser

    def get_short_help(self):
        return "MLIR debugger commands"

    def get_long_help(self):
        return self.help_string

    def __call__(self, debugger, command, exe_ctx, result):
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            args = MlirDebug.parser.parse_args(command_args)
        except:
            result.SetError("option parsing failed")
            raise
        args.func(args, debugger, command, exe_ctx, result)

    @classmethod
    def on_process_start(frame, bp_loc, dict):
        print("Process started")


class SetControl:
    # Define the subcommands that controls what to do when a breakpoint is hit.
    # The key is the subcommand name, the value is a tuple of the command ID to
    # pass to MLIR and the help string.
    commands = {
        "apply": (1, "Apply the current action and continue the execution"),
        "skip": (2, "Skip the current action and continue the execution"),
        "step": (3, "Step into the current action"),
        "next": (4, "Step over the current action"),
        "finish": (5, "Step out of the current action"),
    }

    @classmethod
    def register_mlir_subparser(cls):
        for cmd, (cmdInt, help) in cls.commands.items():
            parser = MlirDebug.subparsers.add_parser(
                cmd,
                help=help,
            )
            parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        frame = exe_ctx.GetFrame()
        if not frame.IsValid():
            result.SetError("No valid frame (program not running?)")
            return
        cmdInt = cls.commands.get(options.command, None)
        if not cmdInt:
            result.SetError("Invalid command: %s" % (options.command))
            return

        result = frame.EvaluateExpression(
            "((bool (*)(int))mlirDebuggerSetControl)(%d)" % (cmdInt[0]),
            exprOptions,
        )
        if not result.error.Success():
            print("Error setting up command: %s" % (result.error))
            return
        debugger.SetAsync(True)
        result = exe_ctx.GetProcess().Continue()
        debugger.SetAsync(False)


class PrintContext:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "context", help="Print the current context"
        )
        cls.parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        frame = exe_ctx.GetFrame()
        if not frame.IsValid():
            result.SetError("Can't print context without a valid frame")
            return
        result = frame.EvaluateExpression(
            "((bool (*)())&mlirDebuggerPrintContext)()", exprOptions
        )
        if not result.error.Success():
            print("Error printing context: %s" % (result.error))
            return


class Backtrace:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "backtrace", aliases=["bt"], help="Print the current backtrace"
        )
        cls.parser.set_defaults(func=cls.process_options)
        cls.parser.add_argument("--context", default=False, action="store_true")

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        frame = exe_ctx.GetFrame()
        if not frame.IsValid():
            result.SetError(
                "Can't backtrace without a valid frame (program not running?)"
            )
        result = frame.EvaluateExpression(
            "((bool(*)(bool))mlirDebuggerPrintActionBacktrace)(%d)" % (options.context),
            exprOptions,
        )
        if not result.error.Success():
            print("Error printing breakpoints: %s" % (result.error))
            return


###############################################################################
# Cursor manipulation
###############################################################################


class PrintCursor:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "cursor-print", aliases=["cursor-p"], help="Print the current cursor"
        )
        cls.parser.add_argument(
            "--print-region", "--regions", "-r", default=False, action="store_true"
        )
        cls.parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        frame = exe_ctx.GetFrame()
        if not frame.IsValid():
            result.SetError(
                "Can't print cursor without a valid frame (program not running?)"
            )
        result = frame.EvaluateExpression(
            "((bool(*)(bool))mlirDebuggerCursorPrint)(%d)" % (options.print_region),
            exprOptions,
        )
        if not result.error.Success():
            print("Error printing cursor: %s" % (result.error))
            return


class SelectCursorFromContext:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "cursor-select-from-context",
            aliases=["cursor-s"],
            help="Select the cursor from the current context",
        )
        cls.parser.add_argument("index", type=int, help="Index in the context")
        cls.parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        frame = exe_ctx.GetFrame()
        if not frame.IsValid():
            result.SetError(
                "Can't manipulate cursor without a valid frame (program not running?)"
            )
        result = frame.EvaluateExpression(
            "((bool(*)(int))mlirDebuggerCursorSelectIRUnitFromContext)(%d)"
            % options.index,
            exprOptions,
        )
        if not result.error.Success():
            print("Error manipulating cursor: %s" % (result.error))
            return


class CursorSelectParent:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "cursor-parent", aliases=["cursor-up"], help="Select the cursor parent"
        )
        cls.parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        frame = exe_ctx.GetFrame()
        if not frame.IsValid():
            result.SetError(
                "Can't manipulate cursor without a valid frame (program not running?)"
            )
        result = frame.EvaluateExpression(
            "((bool(*)())mlirDebuggerCursorSelectParentIRUnit)()",
            exprOptions,
        )
        if not result.error.Success():
            print("Error manipulating cursor: %s" % (result.error))
            return


class SelectCursorChild:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "cursor-child", aliases=["cursor-c"], help="Select the nth child"
        )
        cls.parser.add_argument("index", type=int, help="Index of the child to select")
        cls.parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        frame = exe_ctx.GetFrame()
        if not frame.IsValid():
            result.SetError(
                "Can't manipulate cursor without a valid frame (program not running?)"
            )
        result = frame.EvaluateExpression(
            "((bool(*)(int))mlirDebuggerCursorSelectChildIRUnit)(%d)" % options.index,
            exprOptions,
        )
        if not result.error.Success():
            print("Error manipulating cursor: %s" % (result.error))
            return


class CursorSelecPrevious:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "cursor-previous",
            aliases=["cursor-prev"],
            help="Select the cursor previous element",
        )
        cls.parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        frame = exe_ctx.GetFrame()
        if not frame.IsValid():
            result.SetError(
                "Can't manipulate cursor without a valid frame (program not running?)"
            )
        result = frame.EvaluateExpression(
            "((bool(*)())mlirDebuggerCursorSelectPreviousIRUnit)()",
            exprOptions,
        )
        if not result.error.Success():
            print("Error manipulating cursor: %s" % (result.error))
            return


class CursorSelecNext:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "cursor-next", aliases=["cursor-n"], help="Select the cursor next element"
        )
        cls.parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        frame = exe_ctx.GetFrame()
        if not frame.IsValid():
            result.SetError(
                "Can't manipulate cursor without a valid frame (program not running?)"
            )
        result = frame.EvaluateExpression(
            "((bool(*)())mlirDebuggerCursorSelectNextIRUnit)()",
            exprOptions,
        )
        if not result.error.Success():
            print("Error manipulating cursor: %s" % (result.error))
            return


###############################################################################
# Breakpoints
###############################################################################


class EnableBreakpoint:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "enable", help="Enable a single breakpoint (given its ID)"
        )
        cls.parser.add_argument("id", help="ID of the breakpoint to enable")
        cls.parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        bp = breakpoints.get(int(options.id), None)
        if not bp:
            result.SetError("No breakpoint with ID %d" % int(options.id))
            return
        bp.enable(exe_ctx.GetFrame())


class DisableBreakpoint:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "disable", help="Disable a single breakpoint (given its ID)"
        )
        cls.parser.add_argument("id", help="ID of the breakpoint to disable")
        cls.parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        bp = breakpoints.get(int(options.id), None)
        if not bp:
            result.SetError("No breakpoint with ID %s" % options.id)
            return
        bp.disable(exe_ctx.GetFrame())


class ListBreakpoints:
    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            "list", help="List all current breakpoints"
        )
        cls.parser.set_defaults(func=cls.process_options)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        for id, bp in sorted(breakpoints.items()):
            print(id, type(id), str(bp), "enabled" if bp.isEnabled else "disabled")


class Breakpoint:
    def __init__(self):
        global nextid
        self.id = nextid
        nextid += 1
        breakpoints[self.id] = self
        self.isEnabled = True

    def enable(self, frame=None):
        self.isEnabled = True
        if not frame or not frame.IsValid():
            return
        # use a C cast to force the type of the breakpoint handle to be void * so
        # that we don't rely on DWARF. Also add a fake bool return value otherwise
        # LLDB can't signal any error with the expression evaluation (at least I don't know how).
        cmd = (
            "((bool (*)(void *))mlirDebuggerEnableBreakpoint)((void *)%s)" % self.handle
        )
        result = frame.EvaluateExpression(cmd, exprOptions)
        if not result.error.Success():
            print("Error enabling breakpoint: %s" % (result.error))
            return

    def disable(self, frame=None):
        self.isEnabled = False
        if not frame or not frame.IsValid():
            return
        # use a C cast to force the type of the breakpoint handle to be void * so
        # that we don't rely on DWARF. Also add a fake bool return value otherwise
        # LLDB can't signal any error with the expression evaluation (at least I don't know how).
        cmd = (
            "((bool (*)(void *)) mlirDebuggerDisableBreakpoint)((void *)%s)"
            % self.handle
        )
        result = frame.EvaluateExpression(cmd, exprOptions)
        if not result.error.Success():
            print("Error disabling breakpoint: %s" % (result.error))
            return


class TagBreakpoint(Breakpoint):
    mlir_subcommand = "break-on-tag"

    def __init__(self, tag):
        super().__init__()
        self.tag = tag

    def __str__(self):
        return "[%d] TagBreakpoint(%s)" % (self.id, self.tag)

    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            cls.mlir_subcommand, help="add a breakpoint on actions' tag matching"
        )
        cls.parser.set_defaults(func=cls.process_options)
        cls.parser.add_argument("tag", help="tag to match")

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        breakpoint = TagBreakpoint(options.tag)
        print("Added breakpoint %s" % str(breakpoint))

        frame = exe_ctx.GetFrame()
        if frame.IsValid():
            breakpoint.install(frame)

    def install(self, frame):
        result = frame.EvaluateExpression(
            '((void *(*)(const char *))mlirDebuggerAddTagBreakpoint)("%s")'
            % (self.tag),
            exprOptions,
        )
        if not result.error.Success():
            print("Error installing breakpoint: %s" % (result.error))
            return
        # Save the handle, this is necessary to implement enable/disable.
        self.handle = result.GetValue()


class FileLineBreakpoint(Breakpoint):
    mlir_subcommand = "break-on-file"

    def __init__(self, file, line, col):
        super().__init__()
        self.file = file
        self.line = line
        self.col = col

    def __str__(self):
        return "[%d] FileLineBreakpoint(%s, %d, %d)" % (
            self.id,
            self.file,
            self.line,
            self.col,
        )

    @classmethod
    def register_mlir_subparser(cls):
        cls.parser = MlirDebug.subparsers.add_parser(
            cls.mlir_subcommand,
            help="add a breakpoint that filters on location of the IR affected by an action. The syntax is file:line:col where file and col are optional",
        )
        cls.parser.set_defaults(func=cls.process_options)
        cls.parser.add_argument("location", type=str)

    @classmethod
    def process_options(cls, options, debugger, command, exe_ctx, result):
        split_loc = options.location.split(":")
        file = split_loc[0]
        line = int(split_loc[1]) if len(split_loc) > 1 else -1
        col = int(split_loc[2]) if len(split_loc) > 2 else -1
        breakpoint = FileLineBreakpoint(file, line, col)
        print("Added breakpoint %s" % str(breakpoint))

        frame = exe_ctx.GetFrame()
        if frame.IsValid():
            breakpoint.install(frame)

    def install(self, frame):
        result = frame.EvaluateExpression(
            '((void *(*)(const char *, int, int))mlirDebuggerAddFileLineColLocBreakpoint)("%s", %d, %d)'
            % (self.file, self.line, self.col),
            exprOptions,
        )
        if not result.error.Success():
            print("Error installing breakpoint: %s" % (result.error))
            return
        # Save the handle, this is necessary to implement enable/disable.
        self.handle = result.GetValue()


def on_start(frame, bpno, err):
    print("MLIR debugger attaching...")
    for _, bp in sorted(breakpoints.items()):
        if bp.isEnabled:
            print("Installing breakpoint %s" % (str(bp)))
            bp.install(frame)
        else:
            print("Skipping disabled breakpoint %s" % (str(bp)))

    return True


def __lldb_init_module(debugger, dict):
    target = debugger.GetTargetAtIndex(0)
    debugger.SetAsync(False)
    if not target:
        print("No target is loaded, please load a target before loading this script.")
        return
    if debugger.GetNumTargets() > 1:
        print(
            "Multiple targets (%s) loaded, attaching MLIR debugging to %s"
            % (debugger.GetNumTargets(), target)
        )

    # Register all classes that have a register_lldb_command method
    module_name = __name__
    parser = MlirDebug.create_options()
    MlirDebug.__doc__ = parser.format_help()

    # Add the MLIR entry point to LLDB as a command.
    command = "command script add -o -c %s.%s %s" % (
        module_name,
        MlirDebug.__name__,
        MlirDebug.lldb_command,
    )
    debugger.HandleCommand(command)

    main_bp = target.BreakpointCreateByName("main")
    main_bp.SetScriptCallbackFunction("action_debugging.on_start")
    main_bp.SetAutoContinue(auto_continue=True)

    on_breackpoint = target.BreakpointCreateByName("mlirDebuggerBreakpointHook")

    print(
        'The "{0}" command has been installed for target `{1}`, type "help {0}" or "{0} '
        '--help" for detailed help.'.format(MlirDebug.lldb_command, target)
    )
    for _name, cls in inspect.getmembers(sys.modules[module_name]):
        if inspect.isclass(cls) and getattr(cls, "register_mlir_subparser", None):
            cls.register_mlir_subparser()