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