From 1e298815c4d84b7aeeb0f6fd66c10150432f9396 Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Sat, 2 Jul 2016 23:35:28 +0200 Subject: [PATCH] Added mago-mi support (fix #67) --- package.json | 96 +++++++++++++++++++++++++++++++++++++- src/backend/mi2/mi2mago.ts | 47 +++++++++++++++++++ src/mago.ts | 89 +++++++++++++++++++++++++++++++++++ src/mibase.ts | 17 +++---- 4 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 src/backend/mi2/mi2mago.ts create mode 100644 src/mago.ts diff --git a/package.json b/package.json index 0bb4253..ffacb63 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "debug", - "displayName": "Debug", - "description": "GDB & LLDB support for VSCode", + "displayName": "Native Debug", + "description": "GDB, LLDB & Mago-MI Debugger support for VSCode", "version": "0.8.1", "publisher": "webfreak", "icon": "images/icon-plain.svg", @@ -362,6 +362,98 @@ "cwd": "${workspaceRoot}" } ] + }, + { + "type": "mago-mi", + "extensions": [], + "program": "./out/src/mago.js", + "runtime": "node", + "label": "Mago-MI", + "enableBreakpointsFor": { + "languageIds": [ + "d" + ] + }, + "configurationAttributes": { + "launch": { + "required": [ + "target" + ], + "properties": { + "target": { + "type": "string", + "description": "Path of executable" + }, + "arguments": { + "type": "string", + "description": "Arguments to append after the executable" + }, + "cwd": { + "type": "string", + "description": "Path of project" + }, + "magomipath": { + "type": "string", + "description": "Path to the mago-mi executable or the command if in PATH", + "default": "mago-mi" + }, + "printCalls": { + "type": "boolean", + "description": "Prints all mago calls to console", + "default": false + }, + "autorun": { + "type": "array", + "description": "mago commands to run when starting to debug", + "default": [] + } + } + }, + "attach": { + "required": [ + "target" + ], + "properties": { + "target": { + "type": "string", + "description": "PID of running program or program name" + }, + "printCalls": { + "type": "boolean", + "description": "Prints all mago calls to console", + "default": false + }, + "executable": { + "type": "string", + "description": "Path of executable for debugging symbols" + }, + "magomipath": { + "type": "string", + "description": "Path to the mago-mi executable or the command if in PATH", + "default": "mago-mi" + }, + "cwd": { + "type": "string", + "description": "Path of project", + "default": "${workspaceRoot}" + }, + "autorun": { + "type": "array", + "description": "Mago commands to run when starting to debug", + "default": [] + } + } + } + }, + "initialConfigurations": [ + { + "name": "Debug", + "type": "mago-mi", + "request": "launch", + "target": "./bin/executable", + "cwd": "${workspaceRoot}" + } + ] } ] }, diff --git a/src/backend/mi2/mi2mago.ts b/src/backend/mi2/mi2mago.ts new file mode 100644 index 0000000..de357d5 --- /dev/null +++ b/src/backend/mi2/mi2mago.ts @@ -0,0 +1,47 @@ +import { MI2_LLDB } from "./mi2lldb" +import { Stack } from "../backend" +import { MINode } from "../mi_parse" + +export class MI2_Mago extends MI2_LLDB { + getStack(maxLevels: number): Thenable { + return new Promise((resolve, reject) => { + let command = "stack-list-frames"; + this.sendCommand(command).then((result) => { + let stack = result.resultRecords.results; + this.log("stdout", JSON.stringify(result.resultRecords.results.length)); + let ret: Stack[] = []; + let remaining = []; + let addToStack = (element) => { + this.log("stdout", JSON.stringify(element)); + let level = MINode.valueOf(element, "frame.level"); + let addr = MINode.valueOf(element, "frame.addr"); + let func = MINode.valueOf(element, "frame.func"); + let filename = MINode.valueOf(element, "file"); + let file = MINode.valueOf(element, "fullname"); + let line = 0; + let lnstr = MINode.valueOf(element, "line"); + if (lnstr) + line = parseInt(lnstr); + let from = parseInt(MINode.valueOf(element, "from")); + ret.push({ + address: addr, + fileName: filename || "", + file: file || "", + function: func || from || "", + level: level, + line: line + }); + } + stack.forEach(element => { + if (element) + if (element[0] == "stack") { + addToStack(element[1]); + } else remaining.push(element); + }); + if (remaining.length) + addToStack(remaining); + resolve(ret); + }, reject); + }); + } +} \ No newline at end of file diff --git a/src/mago.ts b/src/mago.ts new file mode 100644 index 0000000..c5a35ad --- /dev/null +++ b/src/mago.ts @@ -0,0 +1,89 @@ +import { MI2DebugSession } from './mibase'; +import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { MI2_Mago } from "./backend/mi2/mi2mago"; +import { SSHArguments } from './backend/backend'; + +export interface LaunchRequestArguments { + cwd: string; + target: string; + magomipath: string; + arguments: string; + autorun: string[]; + printCalls: boolean; +} + +export interface AttachRequestArguments { + cwd: string; + target: string; + magomipath: string; + executable: string; + autorun: string[]; + printCalls: boolean; +} + +class MagoDebugSession extends MI2DebugSession { + public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { + super(debuggerLinesStartAt1, isServer, 0); + } + + protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + response.body.supportsConfigurationDoneRequest = true; + response.body.supportsConditionalBreakpoints = true; + response.body.supportsFunctionBreakpoints = true; + response.body.supportsEvaluateForHovers = true; + this.sendResponse(response); + } + + getThreadID() { + return 0; + } + + protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { + this.miDebugger = new MI2_Mago(args.magomipath || "mago-mi", []); + this.initDebugger(); + this.quit = false; + this.attached = false; + this.needContinue = false; + this.isSSH = false; + this.started = false; + this.crashed = false; + this.debugReady = false; + this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.load(args.cwd, args.target, args.arguments, undefined).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + setTimeout(() => { + this.miDebugger.emit("ui-break-done"); + }, 50); + this.sendResponse(response); + this.miDebugger.start().then(() => { + this.started = true; + if (this.crashed) + this.handlePause(undefined); + }); + }); + } + + protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { + this.miDebugger = new MI2_Mago(args.magomipath || "mago-mi", []); + this.initDebugger(); + this.quit = false; + this.attached = true; + this.needContinue = true; + this.isSSH = false; + this.debugReady = false; + this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }); + } +} + +DebugSession.run(MagoDebugSession); \ No newline at end of file diff --git a/src/mibase.ts b/src/mibase.ts index 968f655..dab5036 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -11,7 +11,6 @@ let resolve = posix.resolve; let relative = posix.relative; export class MI2DebugSession extends DebugSession { - protected static THREAD_ID = 1; protected variableHandles = new Handles(); protected quit: boolean; protected attached: boolean; @@ -23,9 +22,11 @@ export class MI2DebugSession extends DebugSession { protected crashed: boolean; protected debugReady: boolean; protected miDebugger: MI2; + protected threadID: number = 1; - public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { + public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false, threadID: number = 1) { super(debuggerLinesStartAt1, isServer); + this.threadID = threadID; } protected initDebugger() { @@ -49,22 +50,22 @@ export class MI2DebugSession extends DebugSession { } protected handleBreakpoint(info: MINode) { - this.sendEvent(new StoppedEvent("breakpoint", MI2DebugSession.THREAD_ID)); + this.sendEvent(new StoppedEvent("breakpoint", this.threadID)); } protected handleBreak(info: MINode) { - this.sendEvent(new StoppedEvent("step", MI2DebugSession.THREAD_ID)); + this.sendEvent(new StoppedEvent("step", this.threadID)); } protected handlePause(info: MINode) { - this.sendEvent(new StoppedEvent("user request", MI2DebugSession.THREAD_ID)); + this.sendEvent(new StoppedEvent("user request", this.threadID)); } protected stopEvent(info: MINode) { if (!this.started) this.crashed = true; if (!this.quit) - this.sendEvent(new StoppedEvent("exception", MI2DebugSession.THREAD_ID)); + this.sendEvent(new StoppedEvent("exception", this.threadID)); } protected quitEvent() { @@ -146,7 +147,7 @@ export class MI2DebugSession extends DebugSession { protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { response.body = { threads: [ - new Thread(MI2DebugSession.THREAD_ID, "Thread 1") + new Thread(this.threadID, "Thread 1") ] }; this.sendResponse(response); @@ -203,7 +204,7 @@ export class MI2DebugSession extends DebugSession { if (typeof id == "string") { if (id.startsWith("@frame:")) { - this.miDebugger.getStackVariables(1, parseInt(id.substr("@frame:".length))).then(stack => { + this.miDebugger.getStackVariables(this.threadID, parseInt(id.substr("@frame:".length))).then(stack => { stack.forEach(variable => { if (variable[1] !== undefined) { let expanded = expandValue(createVariable, "{" + variable[0] + "=" + variable[1] + ")");