568 lines
20 KiB
Python
568 lines
20 KiB
Python
#!/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()
|