From 87ff8b86443c56e2c80e6bfcd44c90f5edef719f Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Sat, 23 Jul 2016 02:13:36 +0200 Subject: [PATCH] Added examining memory locations (fix #64) also added a way to run mi commands over unix domain sockets so other applications can communicate with code-debug --- package.json | 19 ++++-- src/backend/backend.ts | 1 + src/backend/mi2/mi2.ts | 8 +++ src/frontend/extension.ts | 125 ++++++++++++++++++++++++++++++++++++++ src/mibase.ts | 38 +++++++++++- 5 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 src/frontend/extension.ts diff --git a/package.json b/package.json index 9cf20721..a3f203a1 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,12 @@ "publisher": "webfreak", "icon": "images/icon-plain.svg", "engines": { - "vscode": "^1.3.0" + "vscode": "^0.10.10" }, + "main": "./out/src/frontend/extension", + "activationEvents": [ + "onCommand:code-debug.examineMemoryLocation" + ], "categories": [ "Debuggers" ], @@ -16,6 +20,12 @@ "url": "https://github.com/WebFreak001/code-debug.git" }, "contributes": { + "commands": [ + { + "command": "code-debug.examineMemoryLocation", + "title": "Code-Debug: Examine memory location" + } + ], "debuggers": [ { "type": "gdb", @@ -347,7 +357,7 @@ }, "autorun": { "type": "array", - "description": "LLDB commands to run when starting to debug", + "description": "lldb commands to run when starting to debug", "default": [] } } @@ -439,7 +449,7 @@ }, "autorun": { "type": "array", - "description": "Mago commands to run when starting to debug", + "description": "mago commands to run when starting to debug", "default": [] } } @@ -460,7 +470,8 @@ "scripts": { "vscode:prepublish": "node ./node_modules/vscode/bin/compile", "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", - "test": "node ./node_modules/vscode/bin/test" + "test": "node ./node_modules/vscode/bin/test", + "postinstall": "node ./node_modules/vscode/bin/install" }, "dependencies": { "vscode-debugadapter": "^1.10.0", diff --git a/src/backend/backend.ts b/src/backend/backend.ts index 344d1a2e..eec67456 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -56,4 +56,5 @@ export interface IBackend { evalExpression(name: string): Thenable; isReady(): boolean; changeVariable(name: string, rawValue: string): Thenable; + examineMemory(from: number, to: number): Thenable; } \ No newline at end of file diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index e29221d0..147f3cdb 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -556,6 +556,14 @@ export class MI2 extends EventEmitter implements IBackend { }); } + examineMemory(from: number, length: number): Thenable { + return new Promise((resolve, reject) => { + this.sendCommand("data-read-memory-bytes 0x" + from.toString(16) + " " + length).then((result) => { + resolve(result.result("memory[0].contents")); + }, reject); + }); + } + evalExpression(name: string): Thenable { return new Promise((resolve, reject) => { this.sendCommand("data-evaluate-expression " + name).then((result) => { diff --git a/src/frontend/extension.ts b/src/frontend/extension.ts new file mode 100644 index 00000000..c72696b6 --- /dev/null +++ b/src/frontend/extension.ts @@ -0,0 +1,125 @@ +import * as vscode from "vscode"; +import * as net from "net"; +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; + +export function activate(context: vscode.ExtensionContext) { + context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider("debugmemory", new MemoryContentProvider())); + context.subscriptions.push(vscode.commands.registerCommand("code-debug.examineMemoryLocation", examineMemory)); +} + +var memoryLocationRegex = /^0x[0-9a-f]+$/; + +function getMemoryRange(range: string) { + if (!range) + return undefined; + range = range.replace(/\s+/g, "").toLowerCase(); + var index; + if ((index = range.indexOf("+")) != -1) { + var from = range.substr(0, index); + var length = range.substr(index + 1); + if (!memoryLocationRegex.exec(from)) + return undefined; + if (memoryLocationRegex.exec(length)) + length = parseInt(length.substr(2), 16).toString(); + return "from=" + encodeURIComponent(from) + "&length=" + encodeURIComponent(length); + } + else if ((index = range.indexOf("-")) != -1) { + var from = range.substr(0, index); + var to = range.substr(index + 1); + if (!memoryLocationRegex.exec(from)) + return undefined; + if (!memoryLocationRegex.exec(to)) + return undefined; + return "from=" + encodeURIComponent(from) + "&to=" + encodeURIComponent(to); + } + else if (memoryLocationRegex.exec(range)) + return "at=" + encodeURIComponent(range); + else return undefined; +} + +function examineMemory() { + let socketlists = path.join(os.tmpdir(), "code-debug-sockets"); + if (!fs.existsSync(socketlists)) + return vscode.window.showErrorMessage("No debugging sessions available"); + fs.readdir(socketlists, (err, files) => { + if (err) + return vscode.window.showErrorMessage("No debugging sessions available"); + var pickedFile = (file) => { + vscode.window.showInputBox({ placeHolder: "Memory Location or Range", validateInput: range => getMemoryRange(range) === undefined ? "Range must either be in format 0xF00-0xF01, 0xF100+32 or 0xABC154" : "" }).then(range => { + vscode.commands.executeCommand("vscode.previewHtml", vscode.Uri.parse("debugmemory://" + file + "#" + getMemoryRange(range))); + }); + }; + if (files.length == 1) + pickedFile(files[0]); + else if (files.length > 0) + vscode.window.showQuickPick(files, { placeHolder: "Running debugging instance" }).then(file => pickedFile(file)); + else + vscode.window.showErrorMessage("No debugging sessions available"); + }); +} + +class MemoryContentProvider implements vscode.TextDocumentContentProvider { + provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Thenable { + return new Promise((resolve, reject) => { + var conn = net.connect(path.join(os.tmpdir(), "code-debug-sockets", uri.authority)); + var from, to; + var highlightAt = -1; + var splits = uri.fragment.split("&"); + if (splits[0].split("=")[0] == "at") { + var loc = parseInt(splits[0].split("=")[1].substr(2), 16); + highlightAt = 64; + from = Math.max(loc - 64, 0); + to = Math.max(loc + 768, 0); + } + else if (splits[0].split("=")[0] == "from") { + from = parseInt(splits[0].split("=")[1].substr(2), 16); + if (splits[1].split("=")[0] == "to") { + to = parseInt(splits[1].split("=")[1].substr(2), 16); + } + else if (splits[1].split("=")[0] == "length") { + to = from + parseInt(splits[1].split("=")[1]); + } + else return reject("Invalid Range"); + } + else return reject("Invalid Range"); + if (to < from) + return reject("Negative Range"); + conn.write("examineMemory " + JSON.stringify([from, to - from + 1])); + conn.once("data", data => { + var formattedCode = ""; + var hexString = data.toString(); + var x = 0; + var asciiLine = ""; + var byteNo = 0; + for (var i = 0; i < hexString.length; i += 2) { + var digit = hexString.substr(i, 2); + var digitNum = parseInt(digit, 16); + if (digitNum >= 32 && digitNum <= 126) + asciiLine += String.fromCharCode(digitNum); + else + asciiLine += "."; + if (highlightAt == byteNo) { + formattedCode += "" + digit + " "; + } else + formattedCode += digit + " "; + if (++x > 16) { + formattedCode += asciiLine + "\n"; + x = 0; + asciiLine = ""; + } + byteNo++; + } + if (x > 0) { + for (var i = 0; i <= 16 - x; i++) { + formattedCode += " "; + } + formattedCode += asciiLine; + } + resolve("

Memory Range from 0x" + from.toString(16) + " to 0x" + to.toString(16) + "

" + formattedCode + "
"); + conn.destroy(); + }); + }); + } +} \ No newline at end of file diff --git a/src/mibase.ts b/src/mibase.ts index 6c0a9bca..5ac69326 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -6,6 +6,9 @@ import { expandValue, isExpandable } from './backend/gdb_expansion'; import { MI2 } from './backend/mi2/mi2'; import { posix } from "path"; import * as systemPath from "path"; +import * as net from "net"; +import * as os from "os"; +import * as fs from "fs"; let resolve = posix.resolve; let relative = posix.relative; @@ -23,6 +26,7 @@ export class MI2DebugSession extends DebugSession { protected debugReady: boolean; protected miDebugger: MI2; protected threadID: number = 1; + protected commandServer: net.Server; public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false, threadID: number = 1) { super(debuggerLinesStartAt1, isServer); @@ -39,6 +43,31 @@ export class MI2DebugSession extends DebugSession { this.miDebugger.on("step-out-end", this.handleBreak.bind(this)); this.miDebugger.on("signal-stop", this.handlePause.bind(this)); this.sendEvent(new InitializedEvent()); + try { + this.commandServer = net.createServer(c => { + c.on("data", data => { + var rawCmd = data.toString(); + var spaceIndex = rawCmd.indexOf(" "); + var func = rawCmd; + var args = []; + if (spaceIndex != -1) { + func = rawCmd.substr(0, spaceIndex); + args = JSON.parse(rawCmd.substr(spaceIndex + 1)); + } + Promise.resolve(this.miDebugger[func].apply(this.miDebugger, args)).then(data => { + c.write(data.toString()); + }); + }); + }); + this.commandServer.on("error", err => { + this.handleMsg("stderr", "Code-Debug Utility Command Server: Error in command socket " + err.toString()); + }); + if (!fs.existsSync(systemPath.join(os.tmpdir(), "code-debug-sockets"))) + fs.mkdirSync(systemPath.join(os.tmpdir(), "code-debug-sockets")); + this.commandServer.listen(systemPath.join(os.tmpdir(), "code-debug-sockets", "Debug-Instance-" + Math.floor(Math.random() * 36 * 36 * 36 * 36).toString(36))); + } catch (e) { + this.handleMsg("stderr", "Code-Debug Utility Command Server: Failed to start " + e.toString()); + } } protected handleMsg(type: string, msg: string) { @@ -78,6 +107,8 @@ export class MI2DebugSession extends DebugSession { this.miDebugger.detach(); else this.miDebugger.stop(); + this.commandServer.close(); + this.commandServer = undefined; this.sendResponse(response); } @@ -88,7 +119,7 @@ export class MI2DebugSession extends DebugSession { }; this.sendResponse(response); }, err => { - this.sendErrorResponse(response, 11, `Could not continue: ${err}`); + this.sendErrorResponse(response, 11, `Could not continue: ${err}`); }); } @@ -179,6 +210,8 @@ export class MI2DebugSession extends DebugSession { stackFrames: ret }; this.sendResponse(response); + }, err => { + this.sendErrorResponse(response, 12, `Failed to get Stack Trace: ${err.toString()}`) }); } @@ -219,8 +252,7 @@ export class MI2DebugSession extends DebugSession { stack.forEach(variable => { if (variable.valueStr !== undefined) { let expanded = expandValue(createVariable, "{" + variable.name + "=" + variable.valueStr + ")"); - if (expanded) - { + if (expanded) { if (typeof expanded[0] == "string") expanded = [ {