|
| 1 | +# @file pmdb - A gdb-like JavaScript debugger interface for pmjs |
| 2 | +# Enabled by `pmjs --inspect` |
| 3 | +# @author Tom Tang <[email protected]> |
| 4 | +# @date July 2023 |
| 5 | + |
| 6 | +def debuggerInput(prompt: str): |
| 7 | + try: |
| 8 | + return input(prompt) # blocking |
| 9 | + except KeyboardInterrupt: |
| 10 | + print("\b\bQuit") # to match the behaviour of gdb |
| 11 | + return "" |
| 12 | + except Exception as e: |
| 13 | + print(e) |
| 14 | + return "" |
| 15 | + |
| 16 | +def pmdbEnable(debuggerGlobalObject): |
| 17 | + debuggerGlobalObject.eval("""(debuggerInput, _pythonPrint) => { |
| 18 | + const dbg = new Debugger() |
| 19 | + const mainDebuggee = dbg.addDebuggee(mainGlobal) |
| 20 | + dbg.uncaughtExceptionHook = (e) => { |
| 21 | + _pythonPrint(e) |
| 22 | + } |
| 23 | +
|
| 24 | + function makeDebuggeeValue (val) { |
| 25 | + if (val instanceof Debugger.Object) { |
| 26 | + return dbg.adoptDebuggeeValue(val) |
| 27 | + } else { |
| 28 | + // See https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Object.html#makedebuggeevalue-value |
| 29 | + return mainDebuggee.makeDebuggeeValue(val) |
| 30 | + } |
| 31 | + } |
| 32 | +
|
| 33 | + function print (...args) { |
| 34 | + const logger = makeDebuggeeValue(mainGlobal.console.log) |
| 35 | + logger.apply(logger, args.map(makeDebuggeeValue)) |
| 36 | + } |
| 37 | + |
| 38 | + function printErr (...args) { |
| 39 | + const logger = makeDebuggeeValue(mainGlobal.console.error) |
| 40 | + logger.apply(logger, args.map(makeDebuggeeValue)) |
| 41 | + } |
| 42 | + |
| 43 | + function getCommandInputs () { |
| 44 | + const input = debuggerInput("(pmdb) > ") // blocking |
| 45 | + const [_, command, rest] = input.match(/\\s*(\\w+)?(?:\\s+(.*))?/) |
| 46 | + return { command, rest } |
| 47 | + } |
| 48 | +
|
| 49 | + function enterDebuggerLoop (frame) { |
| 50 | + const metadata = frame.script.getOffsetMetadata(frame.offset) |
| 51 | + if (!metadata.isBreakpoint) { |
| 52 | + // This bytecode offset does not qualify as a breakpoint, skipping |
| 53 | + return |
| 54 | + } |
| 55 | + |
| 56 | + blockingLoop: while (true) { |
| 57 | + const { command, rest } = getCommandInputs() // blocking |
| 58 | + switch (command) { |
| 59 | + case "c": |
| 60 | + case "cont": |
| 61 | + // Continue execution until next breakpoint or `debugger` statement |
| 62 | + frame.onStep = undefined // clear step next handler |
| 63 | + break blockingLoop; |
| 64 | + case "n": |
| 65 | + case "next": |
| 66 | + // Step next |
| 67 | + frame.onStep = function () { enterDebuggerLoop(this) } // add handler |
| 68 | + break blockingLoop; |
| 69 | + case "bt": |
| 70 | + case "backtrace": |
| 71 | + // Print backtrace of current execution frame |
| 72 | + // FIXME: we currently implement this using Error.stack |
| 73 | + print(frame.eval("(new Error).stack.split('\\\\n').slice(1).join('\\\\n')").return) |
| 74 | + continue blockingLoop; |
| 75 | + case "l": |
| 76 | + case "line": { |
| 77 | + // Print current line |
| 78 | + const src = frame.script.source.text |
| 79 | + const line = src.split('\\n').slice(metadata.lineNumber-1, metadata.lineNumber).join('\\n') |
| 80 | + print(line) |
| 81 | + continue blockingLoop; |
| 82 | + } |
| 83 | + case "p": |
| 84 | + case "exec": |
| 85 | + case "print": { |
| 86 | + // Execute an expression in debugging script's context and print its value |
| 87 | + const result = frame.eval(rest) |
| 88 | + if (result.throw) printErr(result.throw) // on error |
| 89 | + else print(result.return) // on success |
| 90 | + continue blockingLoop; |
| 91 | + } |
| 92 | + case "q": |
| 93 | + case "quit": |
| 94 | + case "kill": |
| 95 | + // Force exit the program |
| 96 | + mainGlobal.python.exit(127) |
| 97 | + break blockingLoop; |
| 98 | + case "": |
| 99 | + case undefined: |
| 100 | + // no-op |
| 101 | + continue blockingLoop; |
| 102 | + default: |
| 103 | + print(`Undefined command: "${command}"`) |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | +
|
| 108 | + dbg.onDebuggerStatement = (frame) => enterDebuggerLoop(frame) |
| 109 | + }""")(debuggerInput, print) |
0 commit comments