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