569 lines · python
1#!/usr/bin/env python2 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" just7# import it with the full path using the "command script import"8# command9# (lldb) command script import /path/to/cmdtemplate.py10# ---------------------------------------------------------------------11 12import inspect13import lldb14import argparse15import shlex16import sys17 18# Each new breakpoint gets a unique ID starting from 1.19nextid = 120# List of breakpoint set from python, the key is the ID and the value the21# actual breakpoint. These are NOT LLDB SBBreakpoint objects.22breakpoints = dict()23 24exprOptions = lldb.SBExpressionOptions()25exprOptions.SetIgnoreBreakpoints()26exprOptions.SetLanguage(lldb.eLanguageTypeC)27 28 29class MlirDebug:30 """MLIR debugger commands31 This is the class that hooks into LLDB and registers the `mlir` command.32 Other providers can register subcommands below this one.33 """34 35 lldb_command = "mlir"36 parser = None37 38 def __init__(self, debugger, unused):39 super().__init__()40 self.create_options()41 self.help_string = MlirDebug.parser.format_help()42 43 @classmethod44 def create_options(cls):45 if MlirDebug.parser:46 return MlirDebug.parser47 usage = "usage: %s [options]" % (cls.lldb_command)48 description = "TODO."49 50 # Pass add_help_option = False, since this keeps the command in line51 # with lldb commands, and we wire up "help command" to work by52 # providing the long & short help methods below.53 MlirDebug.parser = argparse.ArgumentParser(54 prog=cls.lldb_command, usage=usage, description=description, add_help=False55 )56 MlirDebug.subparsers = MlirDebug.parser.add_subparsers(dest="command")57 return MlirDebug.parser58 59 def get_short_help(self):60 return "MLIR debugger commands"61 62 def get_long_help(self):63 return self.help_string64 65 def __call__(self, debugger, command, exe_ctx, result):66 # Use the Shell Lexer to properly parse up command options just like a67 # shell would68 command_args = shlex.split(command)69 70 try:71 args = MlirDebug.parser.parse_args(command_args)72 except:73 result.SetError("option parsing failed")74 raise75 args.func(args, debugger, command, exe_ctx, result)76 77 @classmethod78 def on_process_start(frame, bp_loc, dict):79 print("Process started")80 81 82class SetControl:83 # Define the subcommands that controls what to do when a breakpoint is hit.84 # The key is the subcommand name, the value is a tuple of the command ID to85 # pass to MLIR and the help string.86 commands = {87 "apply": (1, "Apply the current action and continue the execution"),88 "skip": (2, "Skip the current action and continue the execution"),89 "step": (3, "Step into the current action"),90 "next": (4, "Step over the current action"),91 "finish": (5, "Step out of the current action"),92 }93 94 @classmethod95 def register_mlir_subparser(cls):96 for cmd, (cmdInt, help) in cls.commands.items():97 parser = MlirDebug.subparsers.add_parser(98 cmd,99 help=help,100 )101 parser.set_defaults(func=cls.process_options)102 103 @classmethod104 def process_options(cls, options, debugger, command, exe_ctx, result):105 frame = exe_ctx.GetFrame()106 if not frame.IsValid():107 result.SetError("No valid frame (program not running?)")108 return109 cmdInt = cls.commands.get(options.command, None)110 if not cmdInt:111 result.SetError("Invalid command: %s" % (options.command))112 return113 114 result = frame.EvaluateExpression(115 "((bool (*)(int))mlirDebuggerSetControl)(%d)" % (cmdInt[0]),116 exprOptions,117 )118 if not result.error.Success():119 print("Error setting up command: %s" % (result.error))120 return121 debugger.SetAsync(True)122 result = exe_ctx.GetProcess().Continue()123 debugger.SetAsync(False)124 125 126class PrintContext:127 @classmethod128 def register_mlir_subparser(cls):129 cls.parser = MlirDebug.subparsers.add_parser(130 "context", help="Print the current context"131 )132 cls.parser.set_defaults(func=cls.process_options)133 134 @classmethod135 def process_options(cls, options, debugger, command, exe_ctx, result):136 frame = exe_ctx.GetFrame()137 if not frame.IsValid():138 result.SetError("Can't print context without a valid frame")139 return140 result = frame.EvaluateExpression(141 "((bool (*)())&mlirDebuggerPrintContext)()", exprOptions142 )143 if not result.error.Success():144 print("Error printing context: %s" % (result.error))145 return146 147 148class Backtrace:149 @classmethod150 def register_mlir_subparser(cls):151 cls.parser = MlirDebug.subparsers.add_parser(152 "backtrace", aliases=["bt"], help="Print the current backtrace"153 )154 cls.parser.set_defaults(func=cls.process_options)155 cls.parser.add_argument("--context", default=False, action="store_true")156 157 @classmethod158 def process_options(cls, options, debugger, command, exe_ctx, result):159 frame = exe_ctx.GetFrame()160 if not frame.IsValid():161 result.SetError(162 "Can't backtrace without a valid frame (program not running?)"163 )164 result = frame.EvaluateExpression(165 "((bool(*)(bool))mlirDebuggerPrintActionBacktrace)(%d)" % (options.context),166 exprOptions,167 )168 if not result.error.Success():169 print("Error printing breakpoints: %s" % (result.error))170 return171 172 173###############################################################################174# Cursor manipulation175###############################################################################176 177 178class PrintCursor:179 @classmethod180 def register_mlir_subparser(cls):181 cls.parser = MlirDebug.subparsers.add_parser(182 "cursor-print", aliases=["cursor-p"], help="Print the current cursor"183 )184 cls.parser.add_argument(185 "--print-region", "--regions", "-r", default=False, action="store_true"186 )187 cls.parser.set_defaults(func=cls.process_options)188 189 @classmethod190 def process_options(cls, options, debugger, command, exe_ctx, result):191 frame = exe_ctx.GetFrame()192 if not frame.IsValid():193 result.SetError(194 "Can't print cursor without a valid frame (program not running?)"195 )196 result = frame.EvaluateExpression(197 "((bool(*)(bool))mlirDebuggerCursorPrint)(%d)" % (options.print_region),198 exprOptions,199 )200 if not result.error.Success():201 print("Error printing cursor: %s" % (result.error))202 return203 204 205class SelectCursorFromContext:206 @classmethod207 def register_mlir_subparser(cls):208 cls.parser = MlirDebug.subparsers.add_parser(209 "cursor-select-from-context",210 aliases=["cursor-s"],211 help="Select the cursor from the current context",212 )213 cls.parser.add_argument("index", type=int, help="Index in the context")214 cls.parser.set_defaults(func=cls.process_options)215 216 @classmethod217 def process_options(cls, options, debugger, command, exe_ctx, result):218 frame = exe_ctx.GetFrame()219 if not frame.IsValid():220 result.SetError(221 "Can't manipulate cursor without a valid frame (program not running?)"222 )223 result = frame.EvaluateExpression(224 "((bool(*)(int))mlirDebuggerCursorSelectIRUnitFromContext)(%d)"225 % options.index,226 exprOptions,227 )228 if not result.error.Success():229 print("Error manipulating cursor: %s" % (result.error))230 return231 232 233class CursorSelectParent:234 @classmethod235 def register_mlir_subparser(cls):236 cls.parser = MlirDebug.subparsers.add_parser(237 "cursor-parent", aliases=["cursor-up"], help="Select the cursor parent"238 )239 cls.parser.set_defaults(func=cls.process_options)240 241 @classmethod242 def process_options(cls, options, debugger, command, exe_ctx, result):243 frame = exe_ctx.GetFrame()244 if not frame.IsValid():245 result.SetError(246 "Can't manipulate cursor without a valid frame (program not running?)"247 )248 result = frame.EvaluateExpression(249 "((bool(*)())mlirDebuggerCursorSelectParentIRUnit)()",250 exprOptions,251 )252 if not result.error.Success():253 print("Error manipulating cursor: %s" % (result.error))254 return255 256 257class SelectCursorChild:258 @classmethod259 def register_mlir_subparser(cls):260 cls.parser = MlirDebug.subparsers.add_parser(261 "cursor-child", aliases=["cursor-c"], help="Select the nth child"262 )263 cls.parser.add_argument("index", type=int, help="Index of the child to select")264 cls.parser.set_defaults(func=cls.process_options)265 266 @classmethod267 def process_options(cls, options, debugger, command, exe_ctx, result):268 frame = exe_ctx.GetFrame()269 if not frame.IsValid():270 result.SetError(271 "Can't manipulate cursor without a valid frame (program not running?)"272 )273 result = frame.EvaluateExpression(274 "((bool(*)(int))mlirDebuggerCursorSelectChildIRUnit)(%d)" % options.index,275 exprOptions,276 )277 if not result.error.Success():278 print("Error manipulating cursor: %s" % (result.error))279 return280 281 282class CursorSelecPrevious:283 @classmethod284 def register_mlir_subparser(cls):285 cls.parser = MlirDebug.subparsers.add_parser(286 "cursor-previous",287 aliases=["cursor-prev"],288 help="Select the cursor previous element",289 )290 cls.parser.set_defaults(func=cls.process_options)291 292 @classmethod293 def process_options(cls, options, debugger, command, exe_ctx, result):294 frame = exe_ctx.GetFrame()295 if not frame.IsValid():296 result.SetError(297 "Can't manipulate cursor without a valid frame (program not running?)"298 )299 result = frame.EvaluateExpression(300 "((bool(*)())mlirDebuggerCursorSelectPreviousIRUnit)()",301 exprOptions,302 )303 if not result.error.Success():304 print("Error manipulating cursor: %s" % (result.error))305 return306 307 308class CursorSelecNext:309 @classmethod310 def register_mlir_subparser(cls):311 cls.parser = MlirDebug.subparsers.add_parser(312 "cursor-next", aliases=["cursor-n"], help="Select the cursor next element"313 )314 cls.parser.set_defaults(func=cls.process_options)315 316 @classmethod317 def process_options(cls, options, debugger, command, exe_ctx, result):318 frame = exe_ctx.GetFrame()319 if not frame.IsValid():320 result.SetError(321 "Can't manipulate cursor without a valid frame (program not running?)"322 )323 result = frame.EvaluateExpression(324 "((bool(*)())mlirDebuggerCursorSelectNextIRUnit)()",325 exprOptions,326 )327 if not result.error.Success():328 print("Error manipulating cursor: %s" % (result.error))329 return330 331 332###############################################################################333# Breakpoints334###############################################################################335 336 337class EnableBreakpoint:338 @classmethod339 def register_mlir_subparser(cls):340 cls.parser = MlirDebug.subparsers.add_parser(341 "enable", help="Enable a single breakpoint (given its ID)"342 )343 cls.parser.add_argument("id", help="ID of the breakpoint to enable")344 cls.parser.set_defaults(func=cls.process_options)345 346 @classmethod347 def process_options(cls, options, debugger, command, exe_ctx, result):348 bp = breakpoints.get(int(options.id), None)349 if not bp:350 result.SetError("No breakpoint with ID %d" % int(options.id))351 return352 bp.enable(exe_ctx.GetFrame())353 354 355class DisableBreakpoint:356 @classmethod357 def register_mlir_subparser(cls):358 cls.parser = MlirDebug.subparsers.add_parser(359 "disable", help="Disable a single breakpoint (given its ID)"360 )361 cls.parser.add_argument("id", help="ID of the breakpoint to disable")362 cls.parser.set_defaults(func=cls.process_options)363 364 @classmethod365 def process_options(cls, options, debugger, command, exe_ctx, result):366 bp = breakpoints.get(int(options.id), None)367 if not bp:368 result.SetError("No breakpoint with ID %s" % options.id)369 return370 bp.disable(exe_ctx.GetFrame())371 372 373class ListBreakpoints:374 @classmethod375 def register_mlir_subparser(cls):376 cls.parser = MlirDebug.subparsers.add_parser(377 "list", help="List all current breakpoints"378 )379 cls.parser.set_defaults(func=cls.process_options)380 381 @classmethod382 def process_options(cls, options, debugger, command, exe_ctx, result):383 for id, bp in sorted(breakpoints.items()):384 print(id, type(id), str(bp), "enabled" if bp.isEnabled else "disabled")385 386 387class Breakpoint:388 def __init__(self):389 global nextid390 self.id = nextid391 nextid += 1392 breakpoints[self.id] = self393 self.isEnabled = True394 395 def enable(self, frame=None):396 self.isEnabled = True397 if not frame or not frame.IsValid():398 return399 # use a C cast to force the type of the breakpoint handle to be void * so400 # that we don't rely on DWARF. Also add a fake bool return value otherwise401 # LLDB can't signal any error with the expression evaluation (at least I don't know how).402 cmd = (403 "((bool (*)(void *))mlirDebuggerEnableBreakpoint)((void *)%s)" % self.handle404 )405 result = frame.EvaluateExpression(cmd, exprOptions)406 if not result.error.Success():407 print("Error enabling breakpoint: %s" % (result.error))408 return409 410 def disable(self, frame=None):411 self.isEnabled = False412 if not frame or not frame.IsValid():413 return414 # use a C cast to force the type of the breakpoint handle to be void * so415 # that we don't rely on DWARF. Also add a fake bool return value otherwise416 # LLDB can't signal any error with the expression evaluation (at least I don't know how).417 cmd = (418 "((bool (*)(void *)) mlirDebuggerDisableBreakpoint)((void *)%s)"419 % self.handle420 )421 result = frame.EvaluateExpression(cmd, exprOptions)422 if not result.error.Success():423 print("Error disabling breakpoint: %s" % (result.error))424 return425 426 427class TagBreakpoint(Breakpoint):428 mlir_subcommand = "break-on-tag"429 430 def __init__(self, tag):431 super().__init__()432 self.tag = tag433 434 def __str__(self):435 return "[%d] TagBreakpoint(%s)" % (self.id, self.tag)436 437 @classmethod438 def register_mlir_subparser(cls):439 cls.parser = MlirDebug.subparsers.add_parser(440 cls.mlir_subcommand, help="add a breakpoint on actions' tag matching"441 )442 cls.parser.set_defaults(func=cls.process_options)443 cls.parser.add_argument("tag", help="tag to match")444 445 @classmethod446 def process_options(cls, options, debugger, command, exe_ctx, result):447 breakpoint = TagBreakpoint(options.tag)448 print("Added breakpoint %s" % str(breakpoint))449 450 frame = exe_ctx.GetFrame()451 if frame.IsValid():452 breakpoint.install(frame)453 454 def install(self, frame):455 result = frame.EvaluateExpression(456 '((void *(*)(const char *))mlirDebuggerAddTagBreakpoint)("%s")'457 % (self.tag),458 exprOptions,459 )460 if not result.error.Success():461 print("Error installing breakpoint: %s" % (result.error))462 return463 # Save the handle, this is necessary to implement enable/disable.464 self.handle = result.GetValue()465 466 467class FileLineBreakpoint(Breakpoint):468 mlir_subcommand = "break-on-file"469 470 def __init__(self, file, line, col):471 super().__init__()472 self.file = file473 self.line = line474 self.col = col475 476 def __str__(self):477 return "[%d] FileLineBreakpoint(%s, %d, %d)" % (478 self.id,479 self.file,480 self.line,481 self.col,482 )483 484 @classmethod485 def register_mlir_subparser(cls):486 cls.parser = MlirDebug.subparsers.add_parser(487 cls.mlir_subcommand,488 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",489 )490 cls.parser.set_defaults(func=cls.process_options)491 cls.parser.add_argument("location", type=str)492 493 @classmethod494 def process_options(cls, options, debugger, command, exe_ctx, result):495 split_loc = options.location.split(":")496 file = split_loc[0]497 line = int(split_loc[1]) if len(split_loc) > 1 else -1498 col = int(split_loc[2]) if len(split_loc) > 2 else -1499 breakpoint = FileLineBreakpoint(file, line, col)500 print("Added breakpoint %s" % str(breakpoint))501 502 frame = exe_ctx.GetFrame()503 if frame.IsValid():504 breakpoint.install(frame)505 506 def install(self, frame):507 result = frame.EvaluateExpression(508 '((void *(*)(const char *, int, int))mlirDebuggerAddFileLineColLocBreakpoint)("%s", %d, %d)'509 % (self.file, self.line, self.col),510 exprOptions,511 )512 if not result.error.Success():513 print("Error installing breakpoint: %s" % (result.error))514 return515 # Save the handle, this is necessary to implement enable/disable.516 self.handle = result.GetValue()517 518 519def on_start(frame, bpno, err):520 print("MLIR debugger attaching...")521 for _, bp in sorted(breakpoints.items()):522 if bp.isEnabled:523 print("Installing breakpoint %s" % (str(bp)))524 bp.install(frame)525 else:526 print("Skipping disabled breakpoint %s" % (str(bp)))527 528 return True529 530 531def __lldb_init_module(debugger, dict):532 target = debugger.GetTargetAtIndex(0)533 debugger.SetAsync(False)534 if not target:535 print("No target is loaded, please load a target before loading this script.")536 return537 if debugger.GetNumTargets() > 1:538 print(539 "Multiple targets (%s) loaded, attaching MLIR debugging to %s"540 % (debugger.GetNumTargets(), target)541 )542 543 # Register all classes that have a register_lldb_command method544 module_name = __name__545 parser = MlirDebug.create_options()546 MlirDebug.__doc__ = parser.format_help()547 548 # Add the MLIR entry point to LLDB as a command.549 command = "command script add -o -c %s.%s %s" % (550 module_name,551 MlirDebug.__name__,552 MlirDebug.lldb_command,553 )554 debugger.HandleCommand(command)555 556 main_bp = target.BreakpointCreateByName("main")557 main_bp.SetScriptCallbackFunction("action_debugging.on_start")558 main_bp.SetAutoContinue(auto_continue=True)559 560 on_breackpoint = target.BreakpointCreateByName("mlirDebuggerBreakpointHook")561 562 print(563 'The "{0}" command has been installed for target `{1}`, type "help {0}" or "{0} '564 '--help" for detailed help.'.format(MlirDebug.lldb_command, target)565 )566 for _name, cls in inspect.getmembers(sys.modules[module_name]):567 if inspect.isclass(cls) and getattr(cls, "register_mlir_subparser", None):568 cls.register_mlir_subparser()569