|  | #!/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() |