增加汇编调试功能
Signed-off-by: Haoyang Chen <chenhaoyang@kylinos.cn>
This commit is contained in:
parent
fd7d8ee164
commit
fc6a3de435
|
@ -10,7 +10,7 @@
|
||||||
"debug"
|
"debug"
|
||||||
],
|
],
|
||||||
"license": "public domain",
|
"license": "public domain",
|
||||||
"version": "0.26.0",
|
"version": "0.26.1",
|
||||||
"publisher": "webfreak",
|
"publisher": "webfreak",
|
||||||
"icon": "images/icon.png",
|
"icon": "images/icon.png",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -1049,7 +1049,10 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ssh2": "^1.6.0",
|
"ssh2": "^1.6.0",
|
||||||
"vscode-debugadapter": "^1.45.0",
|
"vscode-debugadapter": "^1.45.0",
|
||||||
"vscode-debugprotocol": "^1.45.0"
|
"vscode-debugprotocol": "^1.45.0",
|
||||||
|
"node-interval-tree": "^1.3.3",
|
||||||
|
"json-stream-stringify": "^2.0.4",
|
||||||
|
"stream-json": "^1.7.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mocha": "^5.2.6",
|
"@types/mocha": "^5.2.6",
|
||||||
|
|
|
@ -11,6 +11,11 @@ export interface Breakpoint {
|
||||||
countCondition?: string;
|
countCondition?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OurInstructionBreakpoint extends DebugProtocol.InstructionBreakpoint {
|
||||||
|
address: number;
|
||||||
|
number: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Thread {
|
export interface Thread {
|
||||||
id: number;
|
id: number;
|
||||||
targetId: string;
|
targetId: string;
|
||||||
|
@ -64,15 +69,18 @@ export interface IBackend {
|
||||||
stepOut(): Thenable<boolean>;
|
stepOut(): Thenable<boolean>;
|
||||||
loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]>;
|
loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]>;
|
||||||
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]>;
|
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]>;
|
||||||
|
addInstrBreakPoint(breakpoint: OurInstructionBreakpoint): Promise<OurInstructionBreakpoint>;
|
||||||
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean>;
|
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean>;
|
||||||
clearBreakPoints(source?: string): Thenable<any>;
|
clearBreakPoints(source?: string): Thenable<any>;
|
||||||
getThreads(): Thenable<Thread[]>;
|
getThreads(): Thenable<Thread[]>;
|
||||||
getStack(startFrame: number, maxLevels: number, thread: number): Thenable<Stack[]>;
|
getStack(startFrame: number, maxLevels: number, thread: number): Thenable<Stack[]>;
|
||||||
|
getRegisters(): Promise<any[]>;
|
||||||
getStackVariables(thread: number, frame: number): Thenable<Variable[]>;
|
getStackVariables(thread: number, frame: number): Thenable<Variable[]>;
|
||||||
evalExpression(name: string, thread: number, frame: number): Thenable<any>;
|
evalExpression(name: string, thread: number, frame: number): Thenable<any>;
|
||||||
isReady(): boolean;
|
isReady(): boolean;
|
||||||
changeVariable(name: string, rawValue: string): Thenable<any>;
|
changeVariable(name: string, rawValue: string): Thenable<any>;
|
||||||
examineMemory(from: number, to: number): Thenable<any>;
|
examineMemory(from: number, to: number): Thenable<any>;
|
||||||
|
record(): Thenable<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VariableObject {
|
export class VariableObject {
|
||||||
|
|
|
@ -0,0 +1,369 @@
|
||||||
|
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||||
|
import * as childProcess from 'child_process';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import * as stream from 'stream';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
const readline = require('readline');
|
||||||
|
import { SSHArguments, ValuesFormattingMode } from './backend';
|
||||||
|
|
||||||
|
export interface DisassemblyInstruction {
|
||||||
|
address: string;
|
||||||
|
functionName: string;
|
||||||
|
offset: number;
|
||||||
|
instruction: string;
|
||||||
|
opcodes: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ADAPTER_DEBUG_MODE {
|
||||||
|
NONE = 'none',
|
||||||
|
PARSED = 'parsed',
|
||||||
|
BOTH = 'both',
|
||||||
|
RAW = 'raw',
|
||||||
|
VSCODE = 'vscode'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElfSection {
|
||||||
|
name: string;
|
||||||
|
address: number; // New base address
|
||||||
|
addressOrig: number; // original base address in Elf file
|
||||||
|
}
|
||||||
|
export interface SymbolFile {
|
||||||
|
file: string;
|
||||||
|
offset?: number;
|
||||||
|
textaddress?: number;
|
||||||
|
sections: ElfSection[];
|
||||||
|
sectionMap: {[name: string]: ElfSection};
|
||||||
|
}
|
||||||
|
export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
||||||
|
cwd: string;
|
||||||
|
target: string;
|
||||||
|
gdbpath: string;
|
||||||
|
env: any;
|
||||||
|
//objdumpPath:string;
|
||||||
|
//symbolFiles: SymbolFile[];
|
||||||
|
debugger_args: string[];
|
||||||
|
pathSubstitutions: { [index: string]: string };
|
||||||
|
arguments: string;
|
||||||
|
terminal: string;
|
||||||
|
autorun: string[];
|
||||||
|
stopAtEntry: boolean | string;
|
||||||
|
ssh: SSHArguments;
|
||||||
|
valuesFormatting: ValuesFormattingMode;
|
||||||
|
printCalls: boolean;
|
||||||
|
showDevDebugOutput: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
|
||||||
|
cwd: string;
|
||||||
|
target: string;
|
||||||
|
gdbpath: string;
|
||||||
|
env: any;
|
||||||
|
//objdumpPath:string;
|
||||||
|
//symbolFiles: SymbolFile[];
|
||||||
|
debugger_args: string[];
|
||||||
|
pathSubstitutions: { [index: string]: string };
|
||||||
|
executable: string;
|
||||||
|
remote: boolean;
|
||||||
|
autorun: string[];
|
||||||
|
stopAtConnect: boolean;
|
||||||
|
stopAtEntry: boolean | string;
|
||||||
|
ssh: SSHArguments;
|
||||||
|
valuesFormatting: ValuesFormattingMode;
|
||||||
|
printCalls: boolean;
|
||||||
|
showDevDebugOutput: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create a symbolFile object properly with required elements
|
||||||
|
export function defSymbolFile(file: string): SymbolFile {
|
||||||
|
const ret: SymbolFile = {
|
||||||
|
file: file,
|
||||||
|
sections: [],
|
||||||
|
sectionMap: {}
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hexFormat(value: number, padding: number = 8, includePrefix: boolean = true): string {
|
||||||
|
let base = (value).toString(16);
|
||||||
|
base = base.padStart(padding, '0');
|
||||||
|
return includePrefix ? '0x' + base : base;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigurationArguments extends DebugProtocol.LaunchRequestArguments {
|
||||||
|
name: string;
|
||||||
|
request: string;
|
||||||
|
toolchainPath: string;
|
||||||
|
toolchainPrefix: string;
|
||||||
|
executable: string;
|
||||||
|
servertype: string;
|
||||||
|
serverpath: string;
|
||||||
|
gdbPath: string;
|
||||||
|
objdumpPath: string;
|
||||||
|
serverArgs: string[];
|
||||||
|
serverCwd: string;
|
||||||
|
device: string;
|
||||||
|
loadFiles: string[];
|
||||||
|
symbolFiles: SymbolFile[];
|
||||||
|
debuggerArgs: string[];
|
||||||
|
preLaunchCommands: string[];
|
||||||
|
postLaunchCommands: string[];
|
||||||
|
overrideLaunchCommands: string[];
|
||||||
|
preAttachCommands: string[];
|
||||||
|
postAttachCommands: string[];
|
||||||
|
overrideAttachCommands: string[];
|
||||||
|
preRestartCommands: string[];
|
||||||
|
postRestartCommands: string[];
|
||||||
|
overrideRestartCommands: string[];
|
||||||
|
postStartSessionCommands: string[];
|
||||||
|
postRestartSessionCommands: string[];
|
||||||
|
overrideGDBServerStartedRegex: string;
|
||||||
|
breakAfterReset: boolean;
|
||||||
|
//svdFile: string;
|
||||||
|
//svdAddrGapThreshold: number;
|
||||||
|
//ctiOpenOCDConfig: CTIOpenOCDConfig;
|
||||||
|
//rttConfig: RTTConfiguration;
|
||||||
|
//swoConfig: SWOConfiguration;
|
||||||
|
graphConfig: any[];
|
||||||
|
/// Triple slashes will cause the line to be ignored by the options-doc.py script
|
||||||
|
/// We don't expect the following to be in booleann form or have the value of 'none' after
|
||||||
|
/// The config provider has done the conversion. If it exists, it means output 'something'
|
||||||
|
showDevDebugOutput: ADAPTER_DEBUG_MODE;
|
||||||
|
showDevDebugTimestamps: boolean;
|
||||||
|
cwd: string;
|
||||||
|
extensionPath: string;
|
||||||
|
rtos: string;
|
||||||
|
//interface: 'jtag' | 'swd' | 'cjtag';
|
||||||
|
targetId: string | number;
|
||||||
|
runToMain: boolean; // Deprecated: kept here for backwards compatibility
|
||||||
|
runToEntryPoint: string;
|
||||||
|
registerUseNaturalFormat: boolean;
|
||||||
|
variableUseNaturalFormat: boolean;
|
||||||
|
//chainedConfigurations: ChainedConfigurations;
|
||||||
|
|
||||||
|
// pvtRestartOrReset: boolean;
|
||||||
|
// pvtPorts: { [name: string]: number; };
|
||||||
|
// pvtParent: ConfigurationArguments;
|
||||||
|
// pvtMyConfigFromParent: ChainedConfig; // My configuration coming from the parent
|
||||||
|
// pvtAvoidPorts: number[];
|
||||||
|
// pvtVersion: string; // Version from package.json
|
||||||
|
|
||||||
|
numberOfProcessors: number;
|
||||||
|
targetProcessor: number;
|
||||||
|
|
||||||
|
|
||||||
|
// QEMU Specific
|
||||||
|
cpu: string;
|
||||||
|
machine: string;
|
||||||
|
|
||||||
|
// External
|
||||||
|
gdbTarget: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class HrTimer {
|
||||||
|
private start: bigint;
|
||||||
|
constructor() {
|
||||||
|
this.start = process.hrtime.bigint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public restart(): void {
|
||||||
|
this.start = process.hrtime.bigint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStart(): bigint {
|
||||||
|
return this.start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public deltaNs(): string {
|
||||||
|
return (process.hrtime.bigint() - this.start).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public deltaUs(): string {
|
||||||
|
return this.toStringWithRes(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deltaMs(): string {
|
||||||
|
return this.toStringWithRes(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createPaddedMs(padding: number): string {
|
||||||
|
const hrUs = this.deltaMs().padStart(padding, '0');
|
||||||
|
// const hrUsPadded = (hrUs.length < padding) ? '0'.repeat(padding - hrUs.length) + hrUs : '' + hrUs ;
|
||||||
|
// return hrUsPadded;
|
||||||
|
return hrUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public createDateTimestamp(): string {
|
||||||
|
const hrUs = this.createPaddedMs(6);
|
||||||
|
const date = new Date();
|
||||||
|
const ret = `[${date.toISOString()}, +${hrUs}ms]`;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private toStringWithRes(res: number) {
|
||||||
|
const diff = process.hrtime.bigint() - this.start + BigInt((10 ** res) / 2);
|
||||||
|
let ret = diff.toString();
|
||||||
|
ret = ret.length <= res ? '0' : ret.substr(0, ret.length - res);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// For callback `cb`, fatal = false when file exists but header does not match. fatal = true means
|
||||||
|
// we could not even read the file. Use `cb` to print what ever messages you want. It is optional.
|
||||||
|
//
|
||||||
|
// Returns true if the ELF header match the elf magic number, false in all other cases
|
||||||
|
//
|
||||||
|
export function validateELFHeader(exe: string, cb?: (str: string, fatal: boolean) => void): boolean {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(exe)) {
|
||||||
|
if (cb) {
|
||||||
|
cb(`File not found "executable": "${exe}"`, true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const buffer = Buffer.alloc(16);
|
||||||
|
const fd = fs.openSync(exe, 'r');
|
||||||
|
const n = fs.readSync(fd, buffer, 0, 16, 0);
|
||||||
|
fs.closeSync(fd);
|
||||||
|
if (n !== 16) {
|
||||||
|
if (cb) {
|
||||||
|
cb(`Could not read 16 bytes from "executable": "${exe}"`, true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// First four chars are 0x7f, 'E', 'L', 'F'
|
||||||
|
if ((buffer[0] !== 0x7f) || (buffer[1] !== 0x45) || (buffer[2] !== 0x4c) || (buffer[3] !== 0x46)) {
|
||||||
|
if (cb) {
|
||||||
|
cb(`Not a valid ELF file "executable": "${exe}". Many debug functions can fail or not work properly`, false);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (cb) {
|
||||||
|
cb(`Could not read file "executable": "${exe}" ${e ? e.toString() : ''}`, true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// You have two choices.
|
||||||
|
// 1. Get events that you subscribe to or
|
||||||
|
// 2. get immediate callback and you will not get events
|
||||||
|
//
|
||||||
|
// There are three events
|
||||||
|
// emit('error', err) -- only emit
|
||||||
|
// emit('close') and cb(null)
|
||||||
|
// emit('line', line) or cb(line) -- NOT both, line can be empty ''
|
||||||
|
// emit('exit', code, signal) -- Only emit, NA for a stream Readable
|
||||||
|
//
|
||||||
|
// Either way, you will get a promise though. On Error though no rejection is issued and instead, it will
|
||||||
|
// emit and error and resolve to false
|
||||||
|
//
|
||||||
|
// You can chose to change the callback anytime -- perhaps based on the state of your parser. The
|
||||||
|
// callback has to return true to continue reading or false to end reading
|
||||||
|
//
|
||||||
|
// On exit for program, you only get an event. No call back.
|
||||||
|
//
|
||||||
|
// Why? Stuff like objdump/nm can produce very large output and reading them into a mongo
|
||||||
|
// string is a disaster waiting to happen. It is slow and will fail at some point. On small
|
||||||
|
// output, it may be faster but not on large ones. Tried using temp files but that was also
|
||||||
|
// slow. In this mechanism we use streams and NodeJS readline to hook things up and read
|
||||||
|
// things line at a time. Most of that kind of output needs to be parsed line at a time anyways
|
||||||
|
//
|
||||||
|
// Another benefit was we can run two programs at the same time and get the output of both in
|
||||||
|
// the same time as running just one. NodeJS is amazing juggling stuff and although not-multi threaded
|
||||||
|
// it almost look like it
|
||||||
|
//
|
||||||
|
// Finally, you can also use a file or a stream to read instead of a program to run.
|
||||||
|
//
|
||||||
|
export class SpawnLineReader extends EventEmitter {
|
||||||
|
public callback: (line: string) => boolean;
|
||||||
|
private promise: Promise<boolean>;
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public startWithProgram(
|
||||||
|
prog: string, args: readonly string[] = [],
|
||||||
|
spawnOpts: childProcess.SpawnOptions = {}, cb: (line: string) => boolean = null): Promise<boolean> {
|
||||||
|
if (this.promise) { throw new Error('SpawnLineReader: can\'t reuse this object'); }
|
||||||
|
this.callback = cb;
|
||||||
|
this.promise = new Promise<boolean>((resolve) => {
|
||||||
|
try {
|
||||||
|
const child = childProcess.spawn(prog, args, spawnOpts);
|
||||||
|
child.on('error', (err) => {
|
||||||
|
this.emit('error', err);
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
child.on('exit', (code: number, signal: string) => {
|
||||||
|
this.emit('exit', code, signal);
|
||||||
|
// read-line will resolve. Not us
|
||||||
|
});
|
||||||
|
this.doReadline(child.stdout, resolve);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
this.emit('error', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public startWithStream(rStream: stream.Readable, cb: (line: string) => boolean = null): Promise<boolean> {
|
||||||
|
if (this.promise) { throw new Error('SpawnLineReader: can\'t reuse this object'); }
|
||||||
|
this.callback = cb;
|
||||||
|
this.promise = new Promise<boolean>((resolve) => {
|
||||||
|
this.doReadline(rStream, resolve);
|
||||||
|
});
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public startWithFile(filename: fs.PathLike, options: string | any = null, cb: (line: string, err?: any) => boolean = null): Promise<boolean> {
|
||||||
|
if (this.promise) { throw new Error('SpawnLineReader: can\'t reuse this object'); }
|
||||||
|
this.callback = cb;
|
||||||
|
this.promise = new Promise<boolean>((resolve) => {
|
||||||
|
const readStream = fs.createReadStream(filename, options || {flags: 'r'});
|
||||||
|
readStream.on('error', ((e) => {
|
||||||
|
this.emit('error', e);
|
||||||
|
resolve(false);
|
||||||
|
}));
|
||||||
|
readStream.on('open', (() => {
|
||||||
|
this.doReadline(readStream, resolve);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private doReadline(rStream: stream.Readable, resolve) {
|
||||||
|
try {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: rStream,
|
||||||
|
crlfDelay: Infinity,
|
||||||
|
console: false
|
||||||
|
});
|
||||||
|
rl.on('line', (line) => {
|
||||||
|
if (this.callback) {
|
||||||
|
if (!this.callback(line)) {
|
||||||
|
rl.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.emit('line', line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rl.once('close', () => {
|
||||||
|
if (this.callback) {
|
||||||
|
this.callback(null);
|
||||||
|
}
|
||||||
|
rStream.destroy();
|
||||||
|
this.emit('close');
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
this.emit('error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +1,14 @@
|
||||||
import { Breakpoint, IBackend, Thread, Stack, SSHArguments, Variable, VariableObject, MIError } from "../backend";
|
import { Breakpoint, OurInstructionBreakpoint, IBackend, Thread, Stack, SSHArguments, Variable, VariableObject, MIError } from "../backend";
|
||||||
import * as ChildProcess from "child_process";
|
import * as ChildProcess from "child_process";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { parseMI, MINode } from '../mi_parse';
|
import { parseMI, MINode } from '../mi_parse';
|
||||||
import * as linuxTerm from '../linux/console';
|
import * as linuxTerm from '../linux/console';
|
||||||
|
import { hexFormat } from '../common'
|
||||||
import * as net from "net";
|
import * as net from "net";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { Client } from "ssh2";
|
import { Client } from "ssh2";
|
||||||
|
import * as os from 'os';
|
||||||
|
|
||||||
export function escape(str: string) {
|
export function escape(str: string) {
|
||||||
return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
||||||
|
@ -16,6 +18,24 @@ const nonOutput = /^(?:\d*|undefined)[\*\+\=]|[\~\@\&\^]/;
|
||||||
const gdbMatch = /(?:\d*|undefined)\(gdb\)/;
|
const gdbMatch = /(?:\d*|undefined)\(gdb\)/;
|
||||||
const numRegex = /\d+/;
|
const numRegex = /\d+/;
|
||||||
|
|
||||||
|
export interface ReadMemResults {
|
||||||
|
startAddress: string;
|
||||||
|
endAddress: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseReadMemResults(node: MINode): ReadMemResults {
|
||||||
|
const startAddress = node.resultRecords.results[0][1][0][0][1];
|
||||||
|
const endAddress = node.resultRecords.results[0][1][0][2][1];
|
||||||
|
const data = node.resultRecords.results[0][1][0][3][1];
|
||||||
|
const ret: ReadMemResults = {
|
||||||
|
startAddress: startAddress,
|
||||||
|
endAddress: endAddress,
|
||||||
|
data: data
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
function couldBeOutput(line: string) {
|
function couldBeOutput(line: string) {
|
||||||
if (nonOutput.exec(line))
|
if (nonOutput.exec(line))
|
||||||
return false;
|
return false;
|
||||||
|
@ -25,6 +45,8 @@ function couldBeOutput(line: string) {
|
||||||
const trace = false;
|
const trace = false;
|
||||||
|
|
||||||
export class MI2 extends EventEmitter implements IBackend {
|
export class MI2 extends EventEmitter implements IBackend {
|
||||||
|
protected nextTokenComing = 1; // This will be the next token output from gdb
|
||||||
|
protected needOutput: { [index: number]: '' } = {};
|
||||||
constructor(public application: string, public preargs: string[], public extraargs: string[], procEnv: any, public extraCommands: string[] = []) {
|
constructor(public application: string, public preargs: string[], public extraargs: string[], procEnv: any, public extraCommands: string[] = []) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -46,6 +68,7 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
}
|
}
|
||||||
this.procEnv = env;
|
this.procEnv = env;
|
||||||
}
|
}
|
||||||
|
this.cpuArch = os.arch();
|
||||||
}
|
}
|
||||||
|
|
||||||
load(cwd: string, target: string, procArgs: string, separateConsole: string): Thenable<any> {
|
load(cwd: string, target: string, procArgs: string, separateConsole: string): Thenable<any> {
|
||||||
|
@ -333,8 +356,14 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
const parsed = parseMI(line);
|
const parsed = parseMI(line);
|
||||||
if (this.debugOutput)
|
if (this.debugOutput)
|
||||||
this.log("log", "GDB -> App: " + JSON.stringify(parsed));
|
this.log("log", "GDB -> App: " + JSON.stringify(parsed));
|
||||||
let handled = false;
|
|
||||||
if (parsed.token !== undefined) {
|
if (parsed.token !== undefined) {
|
||||||
|
if (this.needOutput[parsed.token] !== undefined) {
|
||||||
|
parsed.output = this.needOutput[parsed.token];
|
||||||
|
}
|
||||||
|
this.nextTokenComing = parsed.token + 1;
|
||||||
|
}
|
||||||
|
let handled = false;
|
||||||
|
if (parsed.token !== undefined && parsed.resultRecords) {
|
||||||
if (this.handlers[parsed.token]) {
|
if (this.handlers[parsed.token]) {
|
||||||
this.handlers[parsed.token](parsed);
|
this.handlers[parsed.token](parsed);
|
||||||
delete this.handlers[parsed.token];
|
delete this.handlers[parsed.token];
|
||||||
|
@ -347,7 +376,11 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
if (parsed.outOfBandRecord) {
|
if (parsed.outOfBandRecord) {
|
||||||
parsed.outOfBandRecord.forEach(record => {
|
parsed.outOfBandRecord.forEach(record => {
|
||||||
if (record.isStream) {
|
if (record.isStream) {
|
||||||
this.log(record.type, record.content);
|
if ((record.type === 'console') && (this.needOutput[this.nextTokenComing] !== undefined)) {
|
||||||
|
this.needOutput[this.nextTokenComing] += record.content;
|
||||||
|
} else {
|
||||||
|
this.log(record.type, record.content);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (record.type == "exec") {
|
if (record.type == "exec") {
|
||||||
this.emit("exec-async-output", parsed);
|
this.emit("exec-async-output", parsed);
|
||||||
|
@ -508,21 +541,21 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
next(reverse: boolean = false): Thenable<boolean> {
|
next(reverse: boolean = false, instruction?: boolean): Thenable<boolean> {
|
||||||
if (trace)
|
if (trace)
|
||||||
this.log("stderr", "next");
|
this.log("stderr", "next");
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.sendCommand("exec-next" + (reverse ? " --reverse" : "")).then((info) => {
|
this.sendCommand((instruction ? 'exec-next-instruction' :'exec-next') + (reverse ? " --reverse" : "")).then((info) => {
|
||||||
resolve(info.resultRecords.resultClass == "running");
|
resolve(info.resultRecords.resultClass == "running");
|
||||||
}, reject);
|
}, reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
step(reverse: boolean = false): Thenable<boolean> {
|
step(reverse: boolean = false, instruction?: boolean): Thenable<boolean> {
|
||||||
if (trace)
|
if (trace)
|
||||||
this.log("stderr", "step");
|
this.log("stderr", "step");
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.sendCommand("exec-step" + (reverse ? " --reverse" : "")).then((info) => {
|
this.sendCommand((instruction ? 'exec-step-instruction' : 'exec-step') + (reverse ? " --reverse" : "")).then((info) => {
|
||||||
resolve(info.resultRecords.resultClass == "running");
|
resolve(info.resultRecords.resultClass == "running");
|
||||||
}, reject);
|
}, reject);
|
||||||
});
|
});
|
||||||
|
@ -629,6 +662,31 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addInstrBreakPoint(breakpoint: OurInstructionBreakpoint): Promise<OurInstructionBreakpoint> {
|
||||||
|
if (trace) {
|
||||||
|
this.log('stderr', 'addInstrBreakPoint');
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let bkptArgs = '';
|
||||||
|
if (breakpoint.condition) {
|
||||||
|
bkptArgs += `-c "${breakpoint.condition}" `;
|
||||||
|
}
|
||||||
|
|
||||||
|
bkptArgs += '*' + hexFormat(breakpoint.address);
|
||||||
|
|
||||||
|
this.sendCommand(`break-insert ${bkptArgs}`).then((result) => {
|
||||||
|
if (result.resultRecords.resultClass === 'done') {
|
||||||
|
const bkptNum = parseInt(result.result('bkpt.number'));
|
||||||
|
breakpoint.number = bkptNum;
|
||||||
|
resolve(breakpoint);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject(new MIError(result.result('msg') || 'Internal error', `Setting breakpoint at ${bkptArgs}`));
|
||||||
|
}
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean> {
|
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean> {
|
||||||
if (trace)
|
if (trace)
|
||||||
this.log("stderr", "removeBreakPoint");
|
this.log("stderr", "removeBreakPoint");
|
||||||
|
@ -690,8 +748,12 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
|
|
||||||
const options: string[] = [];
|
const options: string[] = [];
|
||||||
|
|
||||||
if (thread != 0)
|
if (thread != 0){
|
||||||
options.push("--thread " + thread);
|
options.push("--thread " + thread);
|
||||||
|
if (this.status == 'stopped'){
|
||||||
|
this.currentThreadId = thread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const depth: number = (await this.sendCommand(["stack-info-depth"].concat(options).join(" "))).result("depth").valueOf();
|
const depth: number = (await this.sendCommand(["stack-info-depth"].concat(options).join(" "))).result("depth").valueOf();
|
||||||
const lowFrame: number = startFrame ? startFrame : 0;
|
const lowFrame: number = startFrame ? startFrame : 0;
|
||||||
|
@ -734,10 +796,54 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRegisters(): Promise<any[]> {
|
||||||
|
|
||||||
|
if (!this.regNames || this.regNames.length == 0){
|
||||||
|
const regNameResult = await this.sendCommand(`data-list-register-names`);
|
||||||
|
this.regNames = regNameResult.result("register-names");
|
||||||
|
if (!this.regNames|| this.regNames.length == 0) return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.status == 'stopped' && this.currentThreadId){
|
||||||
|
await this.sendCommand(`thread-select ${this.currentThreadId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.sendCommand(`data-list-register-values x`);
|
||||||
|
const regValues = result.result("register-values");
|
||||||
|
|
||||||
|
if (trace)
|
||||||
|
this.log("stderr", `getRegisters: ${this.regNames.length} ${regValues.length}`);
|
||||||
|
|
||||||
|
var ret = [];
|
||||||
|
let reg_index = 0;
|
||||||
|
for (let name_index = 0; name_index < this.regNames.length; name_index++) {
|
||||||
|
const name = this.regNames[name_index];
|
||||||
|
if (!name || name == '') continue;
|
||||||
|
const reg = regValues[reg_index];
|
||||||
|
const value = MINode.valueOf(reg, "value");
|
||||||
|
const type = MINode.valueOf(reg, "type");
|
||||||
|
ret.push({
|
||||||
|
name: name,
|
||||||
|
evaluateName: name,
|
||||||
|
value: value,
|
||||||
|
type: type,
|
||||||
|
variablesReference: 0
|
||||||
|
});
|
||||||
|
reg_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
async getStackVariables(thread: number, frame: number): Promise<Variable[]> {
|
async getStackVariables(thread: number, frame: number): Promise<Variable[]> {
|
||||||
if (trace)
|
if (trace)
|
||||||
this.log("stderr", "getStackVariables");
|
this.log("stderr", "getStackVariables");
|
||||||
|
|
||||||
|
if (!thread){
|
||||||
|
if (this.status == 'stopped'){
|
||||||
|
this.currentThreadId = thread;
|
||||||
|
}
|
||||||
|
}
|
||||||
const result = await this.sendCommand(`stack-list-variables --thread ${thread} --frame ${frame} --simple-values`);
|
const result = await this.sendCommand(`stack-list-variables --thread ${thread} --frame ${frame} --simple-values`);
|
||||||
const variables = result.result("variables");
|
const variables = result.result("variables");
|
||||||
const ret: Variable[] = [];
|
const ret: Variable[] = [];
|
||||||
|
@ -759,7 +865,7 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
if (trace)
|
if (trace)
|
||||||
this.log("stderr", "examineMemory");
|
this.log("stderr", "examineMemory");
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.sendCommand("data-read-memory-bytes 0x" + from.toString(16) + " " + length).then((result) => {
|
this.sendCommand("data-read-memory-bytes 0x" + from.toString(16) + " " + length, true).then((result) => {
|
||||||
resolve(result.result("memory[0].contents"));
|
resolve(result.result("memory[0].contents"));
|
||||||
}, reject);
|
}, reject);
|
||||||
});
|
});
|
||||||
|
@ -796,6 +902,9 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
let command = "data-evaluate-expression ";
|
let command = "data-evaluate-expression ";
|
||||||
if (thread != 0) {
|
if (thread != 0) {
|
||||||
command += `--thread ${thread} --frame ${frame} `;
|
command += `--thread ${thread} --frame ${frame} `;
|
||||||
|
if (this.status == 'stopped'){
|
||||||
|
this.currentThreadId = thread;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
command += name;
|
command += name;
|
||||||
|
|
||||||
|
@ -873,8 +982,10 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
|
|
||||||
sendCommand(command: string, suppressFailure: boolean = false): Thenable<MINode> {
|
sendCommand(command: string, suppressFailure: boolean = false): Thenable<MINode> {
|
||||||
const sel = this.currentToken++;
|
const sel = this.currentToken++;
|
||||||
|
this.needOutput[sel] = '';
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.handlers[sel] = (node: MINode) => {
|
this.handlers[sel] = (node: MINode) => {
|
||||||
|
delete this.needOutput[sel];
|
||||||
if (node && node.resultRecords && node.resultRecords.resultClass === "error") {
|
if (node && node.resultRecords && node.resultRecords.resultClass === "error") {
|
||||||
if (suppressFailure) {
|
if (suppressFailure) {
|
||||||
this.log("stderr", `WARNING: Error executing command '${command}'`);
|
this.log("stderr", `WARNING: Error executing command '${command}'`);
|
||||||
|
@ -898,11 +1009,14 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
isRecord:boolean = false;
|
isRecord:boolean = false;
|
||||||
|
currentThreadId:number = 0;
|
||||||
status: 'running' | 'stopped' | 'none' = 'none';
|
status: 'running' | 'stopped' | 'none' = 'none';
|
||||||
|
cpuArch:string = '';
|
||||||
prettyPrint: boolean = true;
|
prettyPrint: boolean = true;
|
||||||
printCalls: boolean;
|
printCalls: boolean;
|
||||||
debugOutput: boolean;
|
debugOutput: boolean;
|
||||||
features: string[];
|
features: string[];
|
||||||
|
regNames:any[] = [];
|
||||||
public procEnv: any;
|
public procEnv: any;
|
||||||
protected isSSH: boolean;
|
protected isSSH: boolean;
|
||||||
protected sshReady: boolean;
|
protected sshReady: boolean;
|
||||||
|
|
|
@ -58,6 +58,7 @@ export class MINode implements MIInfo {
|
||||||
token: number;
|
token: number;
|
||||||
outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[];
|
outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[];
|
||||||
resultRecords: { resultClass: string, results: [string, any][] };
|
resultRecords: { resultClass: string, results: [string, any][] };
|
||||||
|
public output: string = '';
|
||||||
|
|
||||||
constructor(token: number, info: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[], result: { resultClass: string, results: [string, any][] }) {
|
constructor(token: number, info: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[], result: { resultClass: string, results: [string, any][] }) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
|
|
|
@ -0,0 +1,986 @@
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
import { DisassemblyInstruction, SpawnLineReader, SymbolFile, validateELFHeader } from './common';
|
||||||
|
import { IntervalTree, Interval } from 'node-interval-tree';
|
||||||
|
import JsonStreamStringify from 'json-stream-stringify';
|
||||||
|
const StreamArray = require('stream-json/streamers/StreamArray');
|
||||||
|
import * as zlib from 'zlib';
|
||||||
|
|
||||||
|
//import { SymbolInformation as SymbolInformation } from '../symbols';
|
||||||
|
import { GDBDebugSession } from '../gdb';
|
||||||
|
import { hexFormat } from './common'
|
||||||
|
import { MINode } from './mi_parse';
|
||||||
|
|
||||||
|
export enum SymbolType {
|
||||||
|
Function,
|
||||||
|
File,
|
||||||
|
Object,
|
||||||
|
Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SymbolScope {
|
||||||
|
Local,
|
||||||
|
Global,
|
||||||
|
Neither,
|
||||||
|
Both
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SymbolInformation {
|
||||||
|
addressOrig: number;
|
||||||
|
address: number;
|
||||||
|
length: number;
|
||||||
|
name: string;
|
||||||
|
file: number | string; // The actual file name parsed (more reliable with nm)
|
||||||
|
section?: string; // Not available with nm
|
||||||
|
type: SymbolType;
|
||||||
|
scope: SymbolScope;
|
||||||
|
isStatic: boolean;
|
||||||
|
// line?: number; // Only available when using nm
|
||||||
|
instructions: DisassemblyInstruction[];
|
||||||
|
hidden: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const OBJDUMP_SYMBOL_RE = RegExp(/^([0-9a-f]{8})\s([lg\ !])([w\ ])([C\ ])([W\ ])([I\ ])([dD\ ])([FfO\ ])\s(.*?)\t([0-9a-f]+)\s(.*)$/);
|
||||||
|
const NM_SYMBOL_RE = RegExp(/^([0-9a-f]+).*\t(.+):[0-9]+/); // For now, we only need two things
|
||||||
|
const debugConsoleLogging = false;
|
||||||
|
const TYPE_MAP: { [id: string]: SymbolType } = {
|
||||||
|
'F': SymbolType.Function,
|
||||||
|
'f': SymbolType.File,
|
||||||
|
'O': SymbolType.Object,
|
||||||
|
' ': SymbolType.Normal
|
||||||
|
};
|
||||||
|
|
||||||
|
const SCOPE_MAP: { [id: string]: SymbolScope } = {
|
||||||
|
'l': SymbolScope.Local,
|
||||||
|
'g': SymbolScope.Global,
|
||||||
|
' ': SymbolScope.Neither,
|
||||||
|
'!': SymbolScope.Both
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SymbolNode implements Interval {
|
||||||
|
constructor(
|
||||||
|
public readonly symbol: SymbolInformation, // Only functions and objects
|
||||||
|
public readonly low: number, // Inclusive near as I can tell
|
||||||
|
public readonly high: number // Inclusive near as I can tell
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMemoryRegion {
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
vmaStart: number; // Virtual memory address
|
||||||
|
vmaStartOrig: number;
|
||||||
|
lmaStart: number; // Load memory address
|
||||||
|
attrs: string[];
|
||||||
|
}
|
||||||
|
export class MemoryRegion1 {
|
||||||
|
public name: string;
|
||||||
|
constructor(obj: string) {
|
||||||
|
this.name = obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class MemoryRegion implements IMemoryRegion {
|
||||||
|
public vmaEnd: number; // Inclusive
|
||||||
|
public lmaEnd: number; // Exclusive
|
||||||
|
public name: string;
|
||||||
|
public size: number;
|
||||||
|
public vmaStart: number;
|
||||||
|
public lmaStart: number;
|
||||||
|
public vmaStartOrig: number;
|
||||||
|
public attrs: string[];
|
||||||
|
constructor(obj: IMemoryRegion) {
|
||||||
|
Object.assign(this, obj);
|
||||||
|
this.vmaEnd = this.vmaStart + this.size + 1;
|
||||||
|
this.lmaEnd = this.lmaStart + this.size + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inVmaRegion(addr: number) {
|
||||||
|
return (addr >= this.vmaStart) && (addr < this.vmaEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public inLmaRegion(addr: number) {
|
||||||
|
return (addr >= this.lmaStart) && (addr < this.lmaEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public inRegion(addr: number) {
|
||||||
|
return this.inVmaRegion(addr) || this.inLmaRegion(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISymbolTableSerData {
|
||||||
|
version: number;
|
||||||
|
memoryRegions: MemoryRegion[];
|
||||||
|
fileTable: string[];
|
||||||
|
symbolKeys: string[];
|
||||||
|
allSymbols: any[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace last part of the path containing a program to another. If search string not found
|
||||||
|
// or replacement the same as search string, then null is returned
|
||||||
|
function replaceProgInPath(filepath: string, search: string | RegExp, replace: string): string {
|
||||||
|
if (os.platform() === 'win32') {
|
||||||
|
filepath = filepath.toLowerCase().replace(/\\/g, '/');
|
||||||
|
}
|
||||||
|
const ix = filepath.lastIndexOf('/');
|
||||||
|
const prefix = (ix >= 0) ? filepath.substring(0, ix + 1) : '' ;
|
||||||
|
const suffix = filepath.substring(ix + 1);
|
||||||
|
const replaced = suffix.replace(search, replace);
|
||||||
|
if (replaced === suffix) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const ret = prefix + replaced;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trace = true;
|
||||||
|
|
||||||
|
interface ExecPromise {
|
||||||
|
args: string[];
|
||||||
|
promise: Promise<any>;
|
||||||
|
}
|
||||||
|
export class SymbolTable {
|
||||||
|
private allSymbols: SymbolInformation[] = [];
|
||||||
|
private fileTable: string[] = [];
|
||||||
|
public memoryRegions: MemoryRegion[] = [];
|
||||||
|
|
||||||
|
// The following are caches that are either created on demand or on symbol load. Helps performance
|
||||||
|
// on large executables since most of our searches are linear. Or, to avoid a search entirely if possible
|
||||||
|
// Case sensitivity for path names is an issue: We follow just what gcc records so inherently case-sensitive
|
||||||
|
// or case-preserving. We don't try to re-interpret/massage those path-names (but we do Normalize).
|
||||||
|
private staticsByFile: {[file: string]: SymbolInformation[]} = {};
|
||||||
|
private globalVars: SymbolInformation[] = [];
|
||||||
|
private globalFuncsMap: {[key: string]: SymbolInformation} = {}; // Key is function name
|
||||||
|
private staticVars: SymbolInformation[] = [];
|
||||||
|
private staticFuncsMap: {[key: string]: SymbolInformation[]} = {}; // Key is function name
|
||||||
|
private fileMap: {[key: string]: string[]} = {}; // Potential list of file aliases we found
|
||||||
|
public symbolsAsIntervalTree: IntervalTree<SymbolNode> = new IntervalTree<SymbolNode>();
|
||||||
|
public symbolsByAddress: Map<number, SymbolInformation> = new Map<number, SymbolInformation>();
|
||||||
|
public symbolsByAddressOrig: Map<number, SymbolInformation> = new Map<number, SymbolInformation>();
|
||||||
|
private varsByFile: {[path: string]: VariablesInFile} = null;
|
||||||
|
private nmPromises: ExecPromise[] = [];
|
||||||
|
|
||||||
|
private objdumpPath: string;
|
||||||
|
|
||||||
|
constructor(private gdbSession: GDBDebugSession, private executables: SymbolFile[]) {
|
||||||
|
const args = this.gdbSession.args;
|
||||||
|
//this.objdumpPath = args.objdumpPath;
|
||||||
|
if (!this.objdumpPath) {
|
||||||
|
this.objdumpPath ='objdump';
|
||||||
|
const tmp = replaceProgInPath(args.gdbpath ? args.gdbpath : 'gdb', /gdb/i, 'objdump');
|
||||||
|
this.objdumpPath = tmp || this.objdumpPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSymtableSerializedFName(exeName: string) {
|
||||||
|
return this.createFileMapCacheFileName(exeName, '-syms') + '.gz';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CurrentVersion = 1;
|
||||||
|
private serializeSymbolTable(exeName: string) {
|
||||||
|
const fMap: {[key: string]: number} = {};
|
||||||
|
const keys = this.allSymbols.length > 0 ? Object.keys(this.allSymbols[0]) : [];
|
||||||
|
this.fileTable = [];
|
||||||
|
const syms = [];
|
||||||
|
for (const sym of this.allSymbols) {
|
||||||
|
const fName: string = sym.file as string;
|
||||||
|
let id: number = fMap[fName];
|
||||||
|
if (id === undefined) {
|
||||||
|
id = this.fileTable.length;
|
||||||
|
this.fileTable.push(fName);
|
||||||
|
fMap[fName] = id;
|
||||||
|
}
|
||||||
|
const tmp = sym.file;
|
||||||
|
sym.file = id;
|
||||||
|
syms.push(Object.values(sym));
|
||||||
|
sym.file = tmp;
|
||||||
|
}
|
||||||
|
const serObj: ISymbolTableSerData = {
|
||||||
|
version: SymbolTable.CurrentVersion,
|
||||||
|
memoryRegions: this.memoryRegions,
|
||||||
|
fileTable: this.fileTable,
|
||||||
|
symbolKeys: keys,
|
||||||
|
allSymbols: syms
|
||||||
|
};
|
||||||
|
|
||||||
|
const fName = this.createSymtableSerializedFName(exeName);
|
||||||
|
const fStream = fs.createWriteStream(fName, { flags: 'w' });
|
||||||
|
fStream.on('error', () => {
|
||||||
|
console.error('Saving symbol table failed!!!');
|
||||||
|
});
|
||||||
|
fStream.on('close', () => {
|
||||||
|
console.log('Saved symbol table');
|
||||||
|
});
|
||||||
|
const jsonStream = new JsonStreamStringify([serObj]);
|
||||||
|
jsonStream.on('error', () => {
|
||||||
|
console.error('Saving symbol table JsonStreamStringify() failed!!!');
|
||||||
|
});
|
||||||
|
jsonStream
|
||||||
|
.pipe(zlib.createGzip())
|
||||||
|
.pipe(fStream)
|
||||||
|
.on('finish', () => {
|
||||||
|
console.log('Pipe ended');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private deSerializeSymbolTable(exeName: string): Promise<boolean> {
|
||||||
|
return new Promise<boolean>((resolve) => {
|
||||||
|
const fName = this.createSymtableSerializedFName(exeName);
|
||||||
|
if (!fs.existsSync(fName)) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fStream = fs.createReadStream(fName);
|
||||||
|
fStream.on('error', () => {
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.time('abc');
|
||||||
|
const jsonStream = StreamArray.withParser();
|
||||||
|
jsonStream.on('data', ({key, value}) => {
|
||||||
|
console.timeLog('abc', 'Parsed data:');
|
||||||
|
fStream.close();
|
||||||
|
reconstruct(value);
|
||||||
|
});
|
||||||
|
fStream
|
||||||
|
.pipe(zlib.createGunzip())
|
||||||
|
.pipe(jsonStream.input);
|
||||||
|
|
||||||
|
const reconstruct = (data: any) => {
|
||||||
|
try {
|
||||||
|
const serObj: ISymbolTableSerData = data as ISymbolTableSerData;
|
||||||
|
if (!serObj || (serObj.version !== SymbolTable.CurrentVersion)) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.fileMap = {};
|
||||||
|
for (const f of serObj.fileTable) {
|
||||||
|
if (f !== null) { // Yes, there one null in there
|
||||||
|
this.addPathVariations(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allSymbols = [];
|
||||||
|
const keys = serObj.symbolKeys;
|
||||||
|
const n = keys.length;
|
||||||
|
for (const values of serObj.allSymbols) {
|
||||||
|
const sym: any = {};
|
||||||
|
values.forEach((v, i) => sym[keys[i]] = v);
|
||||||
|
sym.file = serObj.fileTable[sym.file as number];
|
||||||
|
this.addSymbol(sym/* as SymbolInformation*/);
|
||||||
|
}
|
||||||
|
this.memoryRegions = [];
|
||||||
|
for (const m of serObj.memoryRegions) {
|
||||||
|
this.memoryRegions.push(new MemoryRegion(m));
|
||||||
|
}
|
||||||
|
console.timeEnd('abc');
|
||||||
|
resolve(true);
|
||||||
|
} catch (e) {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Problem statement:
|
||||||
|
* We need a read the symbol table for multiple types of information and none of the tools so far
|
||||||
|
* give all all we need
|
||||||
|
*
|
||||||
|
* 1. List of static variables by file
|
||||||
|
* 2. List og globals
|
||||||
|
* 3. Functions (global and static) with their addresses and lengths
|
||||||
|
*
|
||||||
|
* Things we tried:
|
||||||
|
* 1.-Wi option objdump -- produces super large output (100MB+) and take minutes to produce and parse
|
||||||
|
* 2. Using gdb: We can get variable/function to file information but no addresses -- not super fast but
|
||||||
|
* inconvenient. We have a couple of do it a couple of different ways and it is still ugly
|
||||||
|
* 3. Use nm: This looked super promising until we found out it is super inacurate in telling the type of
|
||||||
|
* symbol. It classifies variables as functions and vice-versa. But for figuring out which variable
|
||||||
|
* belongs to which file that is pretty accurate
|
||||||
|
* 4. Use readelf. This went nowhere because you can't get even basic file to symbol mapping from this
|
||||||
|
* and it is not as universal for handling file formats as objdump.
|
||||||
|
*
|
||||||
|
* So, we are not using option 3 and fall back to option 2. We will never go back to option 1
|
||||||
|
*
|
||||||
|
* Another problem is that we may have to query for symbols using different ways -- partial file names,
|
||||||
|
* full path names, etc. So, we keep a map of file to statics.
|
||||||
|
*
|
||||||
|
* Other uses for objdump is to get a section headers for memory regions that can be used for disassembly
|
||||||
|
*
|
||||||
|
* We avoid splitting the output(s) into lines and then parse line at a time.
|
||||||
|
*/
|
||||||
|
public loadSymbols(): Promise<void> {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
const total = 'Total running objdump & nm';
|
||||||
|
console.time(total);
|
||||||
|
try {
|
||||||
|
await this.loadFromObjdumpAndNm();
|
||||||
|
|
||||||
|
const nxtLabel = 'Postprocessing symbols';
|
||||||
|
console.time(nxtLabel);
|
||||||
|
this.categorizeSymbols();
|
||||||
|
this.sortGlobalVars();
|
||||||
|
resolve();
|
||||||
|
console.timeEnd(nxtLabel);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// We treat this is non-fatal, but why did it fail?
|
||||||
|
this.gdbSession.handleMsg('log', `Error: objdump failed! statics/globals/functions may not be properly classified: ${e.toString()}`);
|
||||||
|
this.gdbSession.handleMsg('log', ' ENOENT means program not found. If that is not the issue, please report this problem.');
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
console.timeEnd(total);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private rttSymbol;
|
||||||
|
public readonly rttSymbolName = '_SEGGER_RTT';
|
||||||
|
private addSymbol(sym: SymbolInformation) {
|
||||||
|
const oldSym = this.symbolsByAddress.get(sym.address);
|
||||||
|
if (oldSym) {
|
||||||
|
// Probably should take the new one. Dups can come from multiple symbol (elf) files
|
||||||
|
// Not sure `symbolsAsIntervalTree` can handle duplicates and we have to do a linear search
|
||||||
|
// in allSymbols. This shouldn't really happen unless user loads duplicate symbol files
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.rttSymbol && (sym.name === this.rttSymbolName) && (sym.type === SymbolType.Object) && (sym.length > 0)) {
|
||||||
|
this.rttSymbol = sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allSymbols.push(sym);
|
||||||
|
if ((sym.type === SymbolType.Function) || (sym.length > 0)) {
|
||||||
|
const treeSym = new SymbolNode(sym, sym.address, sym.address + Math.max(1, sym.length) - 1);
|
||||||
|
this.symbolsAsIntervalTree.insert(treeSym);
|
||||||
|
}
|
||||||
|
this.symbolsByAddress.set(sym.address, sym);
|
||||||
|
this.symbolsByAddressOrig.set(sym.addressOrig, sym);
|
||||||
|
}
|
||||||
|
|
||||||
|
private objdumpReader: SpawnLineReader;
|
||||||
|
private currentObjDumpFile: string = null;
|
||||||
|
|
||||||
|
private readObjdumpHeaderLine(symF: SymbolFile, line: string, err: any): boolean {
|
||||||
|
if (!line) {
|
||||||
|
return line === '' ? true : false;
|
||||||
|
}
|
||||||
|
const entry = RegExp(/^\s*[0-9]+\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+(.*)$/);
|
||||||
|
// Header:
|
||||||
|
// Idx Name Size VMA LMA File off Algn
|
||||||
|
// Sample entry:
|
||||||
|
// 0 .cy_m0p_image 000025d4 10000000 10000000 00010000 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||||||
|
// 1 2 3 4 5 6 7
|
||||||
|
// const entry = RegExp(/^\s*[0-9]+\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)[^\n]+\n\s*([^\r\n]*)\r?\n/gm);
|
||||||
|
const match = line.match(entry);
|
||||||
|
if (match) {
|
||||||
|
const attrs = match[7].trim().toLowerCase().split(/[,\s]+/g);
|
||||||
|
if (!attrs.find((s) => s === 'alloc')) {
|
||||||
|
// Technically we only need regions marked for code but lets get all non-debug, non-comment stuff
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const name = match[1];
|
||||||
|
const offset = symF.offset || 0;
|
||||||
|
const vmaOrig = parseInt(match[3], 16);
|
||||||
|
let vmaStart = vmaOrig + offset;
|
||||||
|
const section = symF.sectionMap[name];
|
||||||
|
if ((name === '.text') && (typeof symF.textaddress === 'number')) {
|
||||||
|
vmaStart = symF.textaddress;
|
||||||
|
if (!section) {
|
||||||
|
symF.sections.push({
|
||||||
|
address: vmaStart,
|
||||||
|
addressOrig: vmaOrig,
|
||||||
|
name: name
|
||||||
|
});
|
||||||
|
symF.sectionMap[name] = symF.sections[symF.sections.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (section) {
|
||||||
|
section.addressOrig = vmaStart;
|
||||||
|
vmaStart = section.address;
|
||||||
|
}
|
||||||
|
const region = new MemoryRegion({
|
||||||
|
name: name,
|
||||||
|
size: parseInt(match[2], 16), // size
|
||||||
|
vmaStart: vmaStart, // vma
|
||||||
|
vmaStartOrig: vmaOrig,
|
||||||
|
lmaStart: parseInt(match[4], 16), // lma
|
||||||
|
attrs: attrs
|
||||||
|
});
|
||||||
|
this.memoryRegions.push(region);
|
||||||
|
} else {
|
||||||
|
const memRegionsEnd = RegExp(/^SYMBOL TABLE:/);
|
||||||
|
if (memRegionsEnd.test(line)) {
|
||||||
|
this.objdumpReader.callback = this.readObjdumpSymbolLine.bind(this, symF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readObjdumpSymbolLine(symF: SymbolFile, line: string, err: any): boolean {
|
||||||
|
if (!line) {
|
||||||
|
return line === '' ? true : false;
|
||||||
|
}
|
||||||
|
const match = line.match(OBJDUMP_SYMBOL_RE);
|
||||||
|
if (match) {
|
||||||
|
if (match[7] === 'd' && match[8] === 'f') {
|
||||||
|
if (match[11]) {
|
||||||
|
this.currentObjDumpFile = SymbolTable.NormalizePath(match[11].trim());
|
||||||
|
} else {
|
||||||
|
// This can happen with C++. Inline and template methods/variables/functions/etc. are listed with
|
||||||
|
// an empty file association. So, symbols after this line can come from multiple compilation
|
||||||
|
// units with no clear owner. These can be locals, globals or other.
|
||||||
|
this.currentObjDumpFile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const type = TYPE_MAP[match[8]];
|
||||||
|
const scope = SCOPE_MAP[match[2]];
|
||||||
|
let name = match[11].trim();
|
||||||
|
let hidden = false;
|
||||||
|
|
||||||
|
if (name.startsWith('.hidden')) {
|
||||||
|
name = name.substring(7).trim();
|
||||||
|
hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const secName = match[9].trim();
|
||||||
|
const offset = symF.offset || 0;
|
||||||
|
const addr = parseInt(match[1], 16);
|
||||||
|
const section = symF.sectionMap[secName];
|
||||||
|
const newaddr = addr + (section ? addr - section.addressOrig : offset);
|
||||||
|
const sym: SymbolInformation = {
|
||||||
|
addressOrig: addr,
|
||||||
|
address: newaddr,
|
||||||
|
name: name,
|
||||||
|
file: this.currentObjDumpFile,
|
||||||
|
type: type,
|
||||||
|
scope: scope,
|
||||||
|
section: match[9].trim(),
|
||||||
|
length: parseInt(match[10], 16),
|
||||||
|
isStatic: (scope === SymbolScope.Local) && this.currentObjDumpFile ? true : false,
|
||||||
|
instructions: null,
|
||||||
|
hidden: hidden
|
||||||
|
};
|
||||||
|
this.addSymbol(sym);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadFromObjdumpAndNm() {
|
||||||
|
return new Promise<void>(async (resolves, reject) => {
|
||||||
|
let rejected = false;
|
||||||
|
const objdumpPromises: ExecPromise[] = [];
|
||||||
|
for (const symbolFile of this.executables) {
|
||||||
|
const executable = symbolFile.file;
|
||||||
|
if (!validateELFHeader(executable)) {
|
||||||
|
this.gdbSession.handleMsg('log',
|
||||||
|
`Warn: ${executable} is not an ELF file format. Some features won't work -- Globals, Locals, disassembly, etc.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const spawnOpts = {cwd: this.gdbSession.args.cwd};
|
||||||
|
const objdumpStart = Date.now();
|
||||||
|
const objDumpArgs = [
|
||||||
|
'--syms', // Of course, we want symbols
|
||||||
|
'-C', // Demangle
|
||||||
|
'-h', // Want section headers
|
||||||
|
'-w', // Don't wrap lines (wide format)
|
||||||
|
executable];
|
||||||
|
this.currentObjDumpFile = null;
|
||||||
|
this.objdumpReader = new SpawnLineReader();
|
||||||
|
this.objdumpReader.on('error', (e) => {
|
||||||
|
rejected = true;
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
this.objdumpReader.on('exit', (code, signal) => {
|
||||||
|
console.log('objdump exited', code, signal);
|
||||||
|
});
|
||||||
|
this.objdumpReader.on('close', (code, signal) => {
|
||||||
|
this.objdumpReader = undefined;
|
||||||
|
this.currentObjDumpFile = null;
|
||||||
|
if (trace || this.gdbSession.args.showDevDebugOutput) {
|
||||||
|
const ms = Date.now() - objdumpStart;
|
||||||
|
this.gdbSession.handleMsg('log', `Finished reading symbols from objdump: Time: ${ms} ms\n`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (trace || this.gdbSession.args.showDevDebugOutput) {
|
||||||
|
this.gdbSession.handleMsg('log', `Reading symbols from ${this.objdumpPath} ${objDumpArgs.join(' ')}\n`);
|
||||||
|
}
|
||||||
|
objdumpPromises.push({
|
||||||
|
args: [this.objdumpPath, ...objDumpArgs],
|
||||||
|
// tslint:disable-next-line: max-line-length
|
||||||
|
promise: this.objdumpReader.startWithProgram(this.objdumpPath, objDumpArgs, spawnOpts, this.readObjdumpHeaderLine.bind(this, symbolFile))
|
||||||
|
});
|
||||||
|
|
||||||
|
const nmStart = Date.now();
|
||||||
|
const nmProg = replaceProgInPath(this.objdumpPath, /objdump/i, 'nm');
|
||||||
|
const nmArgs = [
|
||||||
|
'--defined-only',
|
||||||
|
'-S', // Want size as well
|
||||||
|
'-l', // File/line info
|
||||||
|
'-C', // Demangle
|
||||||
|
'-p', // do bother sorting
|
||||||
|
// Do not use posix format. It is inaccurate
|
||||||
|
executable
|
||||||
|
];
|
||||||
|
const nmReader = new SpawnLineReader();
|
||||||
|
nmReader.on('error', (e) => {
|
||||||
|
this.gdbSession.handleMsg('log', `Error: ${nmProg} failed! statics/global/functions may not be properly classified: ${e.toString()}\n`);
|
||||||
|
this.gdbSession.handleMsg('log', ' Expecting `nm` next to `objdump`. If that is not the problem please report this.\n');
|
||||||
|
this.nmPromises = [];
|
||||||
|
});
|
||||||
|
nmReader.on('exit', (code, signal) => {
|
||||||
|
// console.log('nm exited', code, signal);
|
||||||
|
});
|
||||||
|
nmReader.on('close', () => {
|
||||||
|
if (trace || this.gdbSession.args.showDevDebugOutput) {
|
||||||
|
const ms = Date.now() - nmStart;
|
||||||
|
this.gdbSession.handleMsg('log', `Finished reading symbols from nm: Time: ${ms} ms\n`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (trace || this.gdbSession.args.showDevDebugOutput) {
|
||||||
|
this.gdbSession.handleMsg('log', `Reading symbols from ${nmProg} ${nmArgs.join(' ')}\n`);
|
||||||
|
}
|
||||||
|
this.nmPromises.push({
|
||||||
|
args: [nmProg, ...nmArgs],
|
||||||
|
promise: nmReader.startWithProgram(nmProg, nmArgs, spawnOpts, this.readNmSymbolLine.bind(this, symbolFile))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (!rejected) {
|
||||||
|
rejected = true;
|
||||||
|
reject(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Yes, we launch both programs and wait for both to finish. Running them back to back
|
||||||
|
// takes almost twice as much time. Neither should technically fail.
|
||||||
|
await this.waitOnProgs(objdumpPromises);
|
||||||
|
// Yes, we don't wait for this. We have enough to move on
|
||||||
|
this.finishNmSymbols();
|
||||||
|
resolves();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitOnProgs(promises: ExecPromise[]): Promise<void> {
|
||||||
|
for (const p of promises) {
|
||||||
|
try {
|
||||||
|
await p.promise;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
this.gdbSession.handleMsg('log', `Failed running: ${[p.args.join(' ')]}.\n ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
private finishNmSymbolsPromise: Promise<void>;
|
||||||
|
private finishNmSymbols(): Promise<void> {
|
||||||
|
if (!this.nmPromises.length) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
if (this.finishNmSymbolsPromise) {
|
||||||
|
return this.finishNmSymbolsPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.finishNmSymbolsPromise = new Promise<void>(async (resolve) => {
|
||||||
|
try {
|
||||||
|
await this.waitOnProgs(this.nmPromises);
|
||||||
|
// This part needs to run after both of the above finished
|
||||||
|
for (const item of this.addressToFileOrig) {
|
||||||
|
const sym = this.symbolsByAddressOrig.get(item[0]);
|
||||||
|
if (sym) {
|
||||||
|
sym.file = item[1];
|
||||||
|
} else {
|
||||||
|
console.error('Unknown symbol address. Need to investigate', hexFormat(item[0]), item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// console.log('???');
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.addressToFileOrig.clear();
|
||||||
|
this.nmPromises = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.finishNmSymbolsPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addressToFileOrig: Map<number, string> = new Map<number, string>(); // These are addresses used before re-mapped via symbol-files
|
||||||
|
private readNmSymbolLine(symF: SymbolFile, line: string, err: any): boolean {
|
||||||
|
const match = line && line.match(NM_SYMBOL_RE);
|
||||||
|
if (match) {
|
||||||
|
const offset = symF.offset || 0;
|
||||||
|
const address = parseInt(match[1], 16) + offset;
|
||||||
|
const file = SymbolTable.NormalizePath(match[2]);
|
||||||
|
this.addressToFileOrig.set(address, file);
|
||||||
|
this.addPathVariations(file);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateSymbolSize(node: SymbolNode, len: number) {
|
||||||
|
this.symbolsAsIntervalTree.remove(node);
|
||||||
|
node.symbol.length = len;
|
||||||
|
node = new SymbolNode(node.symbol, node.low, node.low + len - 1);
|
||||||
|
this.symbolsAsIntervalTree.insert(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sortGlobalVars() {
|
||||||
|
// We only sort globalVars. Want to preserve statics original order though.
|
||||||
|
this.globalVars.sort((a, b) => a.name.localeCompare(b.name, undefined, {sensitivity: 'base'}));
|
||||||
|
|
||||||
|
// double underscore variables are less interesting. Push it down to the bottom
|
||||||
|
const doubleUScores: SymbolInformation[] = [];
|
||||||
|
while (this.globalVars.length > 0) {
|
||||||
|
if (this.globalVars[0].name.startsWith('__')) {
|
||||||
|
doubleUScores.push(this.globalVars.shift());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.globalVars = this.globalVars.concat(doubleUScores);
|
||||||
|
}
|
||||||
|
|
||||||
|
private categorizeSymbols() {
|
||||||
|
for (const sym of this.allSymbols) {
|
||||||
|
const scope = sym.scope;
|
||||||
|
const type = sym.type;
|
||||||
|
if (scope !== SymbolScope.Local) {
|
||||||
|
if (type === SymbolType.Function) {
|
||||||
|
sym.scope = SymbolScope.Global;
|
||||||
|
this.globalFuncsMap[sym.name] = sym;
|
||||||
|
} else if (type === SymbolType.Object) {
|
||||||
|
if (scope === SymbolScope.Global) {
|
||||||
|
this.globalVars.push(sym);
|
||||||
|
} else {
|
||||||
|
// These fail gdb create-vars. So ignoring them. C++ generates them.
|
||||||
|
if (debugConsoleLogging) {
|
||||||
|
console.log('SymbolTable: ignoring non local object: ' + sym.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (sym.file) {
|
||||||
|
// Yes, you can have statics with no file association in C++. They are neither
|
||||||
|
// truly global or local. Some can be considered global but not sure how to filter.
|
||||||
|
if (type === SymbolType.Object) {
|
||||||
|
this.staticVars.push(sym);
|
||||||
|
} else if (type === SymbolType.Function) {
|
||||||
|
const tmp = this.staticFuncsMap[sym.name];
|
||||||
|
if (tmp) {
|
||||||
|
tmp.push(sym);
|
||||||
|
} else {
|
||||||
|
this.staticFuncsMap[sym.name] = [sym];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type === SymbolType.Function) {
|
||||||
|
sym.scope = SymbolScope.Global;
|
||||||
|
this.globalFuncsMap[sym.name] = sym;
|
||||||
|
} else if (type === SymbolType.Object) {
|
||||||
|
// We are currently ignoring Local objects with no file association for objects.
|
||||||
|
// Revisit later with care and decide how to classify them
|
||||||
|
if (debugConsoleLogging) {
|
||||||
|
console.log('SymbolTable: ignoring local object: ' + sym.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public printSyms(cb?: (str: string) => any) {
|
||||||
|
cb = cb || console.log;
|
||||||
|
for (const sym of this.allSymbols) {
|
||||||
|
let str = sym.name ;
|
||||||
|
if (sym.type === SymbolType.Function) {
|
||||||
|
str += ' (f)';
|
||||||
|
} else if (sym.type === SymbolType.Object) {
|
||||||
|
str += ' (o)';
|
||||||
|
}
|
||||||
|
if (sym.file) {
|
||||||
|
str += ' (s)';
|
||||||
|
}
|
||||||
|
cb(str);
|
||||||
|
if (sym.file) {
|
||||||
|
const maps = this.fileMap[sym.file];
|
||||||
|
if (maps) {
|
||||||
|
for (const f of maps) {
|
||||||
|
cb('\t' + f);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cb('\tNoMap for? ' + sym.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public printToFile(fName: string): void {
|
||||||
|
try {
|
||||||
|
const outFd = fs.openSync(fName, 'w');
|
||||||
|
this.printSyms((str) => {
|
||||||
|
fs.writeSync(outFd, str);
|
||||||
|
fs.writeSync(outFd, '\n');
|
||||||
|
});
|
||||||
|
fs.closeSync(outFd);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log('printSymsToFile: failed' + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addToFileMap(key: string, newMap: string): string[] {
|
||||||
|
newMap = SymbolTable.NormalizePath(newMap);
|
||||||
|
const value = this.fileMap[key] || [];
|
||||||
|
if (value.indexOf(newMap) === -1) {
|
||||||
|
value.push(newMap);
|
||||||
|
}
|
||||||
|
this.fileMap[key] = value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addPathVariations(fileString: string) {
|
||||||
|
const curName = SymbolTable.NormalizePath(fileString);
|
||||||
|
const curSimpleName = path.basename(curName);
|
||||||
|
this.addToFileMap(curSimpleName, curSimpleName);
|
||||||
|
this.addToFileMap(curSimpleName, curName);
|
||||||
|
this.addToFileMap(curName, curSimpleName);
|
||||||
|
return { curSimpleName, curName };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getFileNameHashed(fileName: string): string {
|
||||||
|
try {
|
||||||
|
fileName = SymbolTable.NormalizePath(fileName);
|
||||||
|
const schemaVer = SymbolTable.CurrentVersion; // Please increment if schema changes or how objdump is invoked changes
|
||||||
|
const stats = fs.statSync(fileName); // Can fail
|
||||||
|
const cwd = process.cwd;
|
||||||
|
const str = `${fileName}-${stats.mtimeMs}-${cwd}`;
|
||||||
|
const hasher = crypto.createHash('sha256');
|
||||||
|
hasher.update(str);
|
||||||
|
const ret = hasher.digest('hex');
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createFileMapCacheFileName(fileName: string, suffix = '') {
|
||||||
|
const hash = this.getFileNameHashed(fileName) + suffix + '.json';
|
||||||
|
const fName = path.join(os.tmpdir(), 'Cortex-Debug-' + hash);
|
||||||
|
return fName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFunctionAtAddress(address: number): SymbolInformation {
|
||||||
|
const symNodes = this.symbolsAsIntervalTree.search(address, address);
|
||||||
|
for (const symNode of symNodes) {
|
||||||
|
if (symNode && (symNode.symbol.type === SymbolType.Function)) {
|
||||||
|
return symNode.symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
// return this.allSymbols.find((s) => s.type === SymbolType.Function && s.address <= address && (s.address + s.length) > address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFunctionSymbols(): SymbolInformation[] {
|
||||||
|
return this.allSymbols.filter((s) => s.type === SymbolType.Function);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGlobalVariables(): SymbolInformation[] {
|
||||||
|
return this.globalVars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getStaticVariableNames(file: string): Promise<string[]> {
|
||||||
|
if (this.varsByFile) {
|
||||||
|
const nfile = SymbolTable.NormalizePath(file);
|
||||||
|
const obj = this.varsByFile[nfile] || this.varsByFile[file];
|
||||||
|
if (obj) {
|
||||||
|
return obj.staticNames; // Could be empty array
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
await this.finishNmSymbols();
|
||||||
|
const syms = this.getStaticVariables(file);
|
||||||
|
const ret = syms.map((s) => s.name);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStaticVariables(file: string): SymbolInformation[] {
|
||||||
|
if (!file) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const nfile = SymbolTable.NormalizePath(file);
|
||||||
|
let ret = this.staticsByFile[file];
|
||||||
|
if (!ret) {
|
||||||
|
ret = [];
|
||||||
|
for (const s of this.staticVars) {
|
||||||
|
if ((s.file === nfile) || (s.file === file)) {
|
||||||
|
ret.push(s);
|
||||||
|
} else {
|
||||||
|
const maps = this.fileMap[s.file];
|
||||||
|
if (maps && (maps.indexOf(nfile) !== -1)) {
|
||||||
|
ret.push(s);
|
||||||
|
} else if (maps && (maps.indexOf(file) !== -1)) {
|
||||||
|
ret.push(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.staticsByFile[file] = ret;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFunctionByName(name: string, file?: string): SymbolInformation {
|
||||||
|
if (file) { // Try to find static function first
|
||||||
|
const nfile = SymbolTable.NormalizePath(file);
|
||||||
|
const syms = this.staticFuncsMap[name];
|
||||||
|
if (syms) {
|
||||||
|
for (const s of syms) { // Try exact matches first (maybe not needed)
|
||||||
|
if ((s.file === file) || (s.file === nfile)) { return s; }
|
||||||
|
}
|
||||||
|
for (const s of syms) { // Try any match
|
||||||
|
const maps = this.fileMap[s.file]; // Bunch of files/aliases that may have the same symbol name
|
||||||
|
if (maps && (maps.indexOf(nfile) !== -1)) {
|
||||||
|
return s;
|
||||||
|
} else if (maps && (maps.indexOf(file) !== -1)) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to global scope
|
||||||
|
const ret = this.globalFuncsMap[name];
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGlobalOrStaticVarByName(name: string, file?: string): SymbolInformation {
|
||||||
|
if (!file && this.rttSymbol && (name === this.rttSymbolName) ) {
|
||||||
|
return this.rttSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file) { // If a file is given only search for static variables by file
|
||||||
|
const nfile = SymbolTable.NormalizePath(file);
|
||||||
|
for (const s of this.staticVars) {
|
||||||
|
if ((s.name === name) && ((s.file === file) || (s.file === nfile))) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try globals first and then statics
|
||||||
|
for (const s of this.globalVars.concat(this.staticVars)) {
|
||||||
|
if (s.name === name) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadSymbolsFromGdb(waitOn: Promise<void>): Promise<boolean> {
|
||||||
|
return new Promise<boolean>((resolve, reject) => {
|
||||||
|
waitOn.then(async () => {
|
||||||
|
if (true) {
|
||||||
|
// gdb is un-reliable for getting symbol information. Most of the time it works but we have
|
||||||
|
// reports of it taking 30+ seconds to dump symbols from small executable (C++) and we have
|
||||||
|
// also seen it run out of memory and crash on a well decked out Mac. Also seen asserts.
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if (!this.gdbSession.miDebugger.gdbVarsPromise) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await this.gdbSession.miDebugger.gdbVarsPromise;
|
||||||
|
nextTick(() => {
|
||||||
|
this.varsByFile = {};
|
||||||
|
const dbgInfo = result.result('symbols.debug');
|
||||||
|
for (const item of dbgInfo || []) {
|
||||||
|
const fullname = getProp(item, 'fullname');
|
||||||
|
const filename = getProp(item, 'fullname');
|
||||||
|
const symbols = getProp(item, 'symbols');
|
||||||
|
if (symbols && (symbols.length > 0) && (fullname || filename)) {
|
||||||
|
const fInfo = new VariablesInFile(fullname || filename);
|
||||||
|
for (const sym of symbols) {
|
||||||
|
fInfo.add(sym);
|
||||||
|
}
|
||||||
|
this.varsByFile[fInfo.filename] = fInfo;
|
||||||
|
if (fullname && (fullname !== fInfo.filename)) {
|
||||||
|
this.varsByFile[fullname] = fInfo;
|
||||||
|
}
|
||||||
|
if (filename && (filename !== fInfo.filename)) {
|
||||||
|
this.varsByFile[filename] = fInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}, (e) => {
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NormalizePath(pathName: string): string {
|
||||||
|
if (!pathName) { return pathName; }
|
||||||
|
if (os.platform() === 'win32') {
|
||||||
|
// Do this so path.normalize works properly
|
||||||
|
pathName = pathName.replace(/\//g, '\\');
|
||||||
|
} else {
|
||||||
|
pathName = pathName.replace(/\\/g, '/');
|
||||||
|
}
|
||||||
|
pathName = path.normalize(pathName);
|
||||||
|
if (os.platform() === 'win32') {
|
||||||
|
pathName = pathName.toLowerCase();
|
||||||
|
}
|
||||||
|
return pathName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProp(ary: any, name: string): any {
|
||||||
|
if (ary) {
|
||||||
|
for (const item of ary) {
|
||||||
|
if (item[0] === name) {
|
||||||
|
return item[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
interface SymInfoFromGdb {
|
||||||
|
name: string;
|
||||||
|
line: number;
|
||||||
|
}
|
||||||
|
class VariablesInFile {
|
||||||
|
public statics: SymInfoFromGdb[] = [];
|
||||||
|
public globals: SymInfoFromGdb[] = [];
|
||||||
|
public staticNames: string[] = [];
|
||||||
|
constructor(public filename: string) {}
|
||||||
|
public add(item: MINode) {
|
||||||
|
const isStatic = (getProp(item, 'description') || '').startsWith('static');
|
||||||
|
const tmp: SymInfoFromGdb = {
|
||||||
|
name: getProp(item, 'name'),
|
||||||
|
line: parseInt(getProp(item, 'line') || '1')
|
||||||
|
};
|
||||||
|
if (isStatic) {
|
||||||
|
this.statics.push(tmp);
|
||||||
|
this.staticNames.push(tmp.name);
|
||||||
|
} else {
|
||||||
|
this.globals.push(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/gdb.ts
23
src/gdb.ts
|
@ -3,6 +3,7 @@ import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEv
|
||||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||||
import { MI2, escape } from "./backend/mi2/mi2";
|
import { MI2, escape } from "./backend/mi2/mi2";
|
||||||
import { SSHArguments, ValuesFormattingMode } from './backend/backend';
|
import { SSHArguments, ValuesFormattingMode } from './backend/backend';
|
||||||
|
import { GdbDisassembler } from './backend/disasm';
|
||||||
|
|
||||||
export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
||||||
cwd: string;
|
cwd: string;
|
||||||
|
@ -39,7 +40,10 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum
|
||||||
showDevDebugOutput: boolean;
|
showDevDebugOutput: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GDBDebugSession extends MI2DebugSession {
|
export class GDBDebugSession extends MI2DebugSession {
|
||||||
|
public args:LaunchRequestArguments | AttachRequestArguments;
|
||||||
|
protected disassember: GdbDisassembler;
|
||||||
|
|
||||||
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
||||||
response.body.supportsGotoTargetsRequest = true;
|
response.body.supportsGotoTargetsRequest = true;
|
||||||
response.body.supportsHitConditionalBreakpoints = true;
|
response.body.supportsHitConditionalBreakpoints = true;
|
||||||
|
@ -49,11 +53,26 @@ class GDBDebugSession extends MI2DebugSession {
|
||||||
response.body.supportsEvaluateForHovers = true;
|
response.body.supportsEvaluateForHovers = true;
|
||||||
response.body.supportsSetVariable = true;
|
response.body.supportsSetVariable = true;
|
||||||
response.body.supportsStepBack = true;
|
response.body.supportsStepBack = true;
|
||||||
|
|
||||||
|
response.body.supportsDisassembleRequest = true;
|
||||||
|
response.body.supportsReadMemoryRequest = true;
|
||||||
|
response.body.supportsInstructionBreakpoints = true;
|
||||||
|
|
||||||
this.sendResponse(response);
|
this.sendResponse(response);
|
||||||
}
|
}
|
||||||
|
// tslint:disable-next-line: max-line-length
|
||||||
|
public sendErrorResponsePub(response: DebugProtocol.Response, codeOrMessage: number | DebugProtocol.Message, format?: string, variables?: any, dest?: any): void {
|
||||||
|
this.sendErrorResponse(response, codeOrMessage, format, variables, dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected disassembleRequest( response: DebugProtocol.DisassembleResponse, args: DebugProtocol.DisassembleArguments, request?: DebugProtocol.Request): void {
|
||||||
|
this.disassember.disassembleProtocolRequest(response,args,request);
|
||||||
|
}
|
||||||
|
|
||||||
protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
|
protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
|
||||||
this.miDebugger = new MI2(args.gdbpath || "gdb", ["-q", "--interpreter=mi2"], args.debugger_args, args.env);
|
this.miDebugger = new MI2(args.gdbpath || "gdb", ["-q", "--interpreter=mi2"], args.debugger_args, args.env);
|
||||||
|
this.args = args;
|
||||||
|
this.disassember = new GdbDisassembler(this, args.showDevDebugOutput);
|
||||||
this.setPathSubstitutions(args.pathSubstitutions);
|
this.setPathSubstitutions(args.pathSubstitutions);
|
||||||
this.initDebugger();
|
this.initDebugger();
|
||||||
this.quit = false;
|
this.quit = false;
|
||||||
|
@ -113,6 +132,8 @@ class GDBDebugSession extends MI2DebugSession {
|
||||||
this.miDebugger.printCalls = !!args.printCalls;
|
this.miDebugger.printCalls = !!args.printCalls;
|
||||||
this.miDebugger.debugOutput = !!args.showDevDebugOutput;
|
this.miDebugger.debugOutput = !!args.showDevDebugOutput;
|
||||||
this.stopAtEntry = args.stopAtEntry;
|
this.stopAtEntry = args.stopAtEntry;
|
||||||
|
this.args = args;
|
||||||
|
this.disassember = new GdbDisassembler(this, args.showDevDebugOutput);
|
||||||
if (args.ssh !== undefined) {
|
if (args.ssh !== undefined) {
|
||||||
if (args.ssh.forwardX11 === undefined)
|
if (args.ssh.forwardX11 === undefined)
|
||||||
args.ssh.forwardX11 = true;
|
args.ssh.forwardX11 = true;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as DebugAdapter from 'vscode-debugadapter';
|
import * as DebugAdapter from 'vscode-debugadapter';
|
||||||
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, ContinuedEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
|
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, ContinuedEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
|
||||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||||
import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend';
|
import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError , OurInstructionBreakpoint} from './backend/backend';
|
||||||
import { MINode } from './backend/mi_parse';
|
import { MINode } from './backend/mi_parse';
|
||||||
import { expandValue, isExpandable } from './backend/gdb_expansion';
|
import { expandValue, isExpandable } from './backend/gdb_expansion';
|
||||||
import { MI2 } from './backend/mi2/mi2';
|
import { MI2 } from './backend/mi2/mi2';
|
||||||
|
@ -20,7 +20,7 @@ export enum RunCommand { CONTINUE, RUN, NONE }
|
||||||
|
|
||||||
const STACK_HANDLES_START = 1000;
|
const STACK_HANDLES_START = 1000;
|
||||||
const VAR_HANDLES_START = 512 * 256 + 1000;
|
const VAR_HANDLES_START = 512 * 256 + 1000;
|
||||||
|
const VARIABLES_TAG_REGISTER = 0xab;
|
||||||
export class MI2DebugSession extends DebugSession {
|
export class MI2DebugSession extends DebugSession {
|
||||||
protected variableHandles = new Handles<string | VariableObject | ExtendedVariable>(VAR_HANDLES_START);
|
protected variableHandles = new Handles<string | VariableObject | ExtendedVariable>(VAR_HANDLES_START);
|
||||||
protected variableHandlesReverse: { [id: string]: number } = {};
|
protected variableHandlesReverse: { [id: string]: number } = {};
|
||||||
|
@ -89,6 +89,9 @@ export class MI2DebugSession extends DebugSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get_miDebugger():MI2{
|
||||||
|
return this.miDebugger;
|
||||||
|
}
|
||||||
protected setValuesFormattingMode(mode: ValuesFormattingMode) {
|
protected setValuesFormattingMode(mode: ValuesFormattingMode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "disabled":
|
case "disabled":
|
||||||
|
@ -106,7 +109,7 @@ export class MI2DebugSession extends DebugSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleMsg(type: string, msg: string) {
|
public handleMsg(type: string, msg: string) {
|
||||||
if (type == "target")
|
if (type == "target")
|
||||||
type = "stdout";
|
type = "stdout";
|
||||||
if (type == "log")
|
if (type == "log")
|
||||||
|
@ -190,7 +193,8 @@ export class MI2DebugSession extends DebugSession {
|
||||||
value: res.result("value")
|
value: res.result("value")
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
await this.miDebugger.changeVariable(args.name, args.value);
|
let name = args.variablesReference == VARIABLES_TAG_REGISTER ? `$${args.name}` : args.name;
|
||||||
|
await this.miDebugger.changeVariable(name, args.value);
|
||||||
response.body = {
|
response.body = {
|
||||||
value: args.value
|
value: args.value
|
||||||
};
|
};
|
||||||
|
@ -259,6 +263,40 @@ export class MI2DebugSession extends DebugSession {
|
||||||
this.sendErrorResponse(response, 9, msg.toString());
|
this.sendErrorResponse(response, 9, msg.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
protected setInstructionBreakpointsRequest(
|
||||||
|
response: DebugProtocol.SetInstructionBreakpointsResponse,
|
||||||
|
args: DebugProtocol.SetInstructionBreakpointsArguments, request?: DebugProtocol.Request): void {
|
||||||
|
this.miDebugger.clearBreakPoints().then(() => {
|
||||||
|
const all = args.breakpoints.map(brk => {
|
||||||
|
const addr = parseInt(brk.instructionReference) + brk.offset || 0;
|
||||||
|
const bpt: OurInstructionBreakpoint = { ...brk, number: -1, address: addr };
|
||||||
|
return this.miDebugger.addInstrBreakPoint(bpt).catch((err: MIError) => err);
|
||||||
|
});
|
||||||
|
Promise.all(all).then(brkpoints => {
|
||||||
|
response.body = {
|
||||||
|
breakpoints: brkpoints.map((bp) => {
|
||||||
|
if (bp instanceof MIError) {
|
||||||
|
return {
|
||||||
|
verified: false,
|
||||||
|
message: bp.message
|
||||||
|
} as DebugProtocol.Breakpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: bp.number,
|
||||||
|
verified: true
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
this.sendResponse(response);
|
||||||
|
|
||||||
|
}, msg => {
|
||||||
|
this.sendErrorResponse(response, 9, msg.toString());
|
||||||
|
});
|
||||||
|
}, msg => {
|
||||||
|
this.sendErrorResponse(response, 9, msg.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
|
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
|
||||||
if (!this.miDebugger) {
|
if (!this.miDebugger) {
|
||||||
|
@ -313,13 +351,14 @@ export class MI2DebugSession extends DebugSession {
|
||||||
}
|
}
|
||||||
source = new Source(element.fileName, path);
|
source = new Source(element.fileName, path);
|
||||||
}
|
}
|
||||||
|
let tmp_stackFrame = new StackFrame(
|
||||||
ret.push(new StackFrame(
|
|
||||||
this.threadAndLevelToFrameId(args.threadId, element.level),
|
this.threadAndLevelToFrameId(args.threadId, element.level),
|
||||||
element.function + "@" + element.address,
|
element.function + "@" + element.address,
|
||||||
source,
|
source,
|
||||||
element.line,
|
element.line,
|
||||||
0));
|
0);
|
||||||
|
tmp_stackFrame.instructionPointerReference = element.address
|
||||||
|
ret.push(tmp_stackFrame);
|
||||||
});
|
});
|
||||||
response.body = {
|
response.body = {
|
||||||
stackFrames: ret
|
stackFrames: ret
|
||||||
|
@ -401,7 +440,7 @@ export class MI2DebugSession extends DebugSession {
|
||||||
protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
||||||
const scopes = new Array<Scope>();
|
const scopes = new Array<Scope>();
|
||||||
scopes.push(new Scope("Local", STACK_HANDLES_START + (parseInt(args.frameId as any) || 0), false));
|
scopes.push(new Scope("Local", STACK_HANDLES_START + (parseInt(args.frameId as any) || 0), false));
|
||||||
|
scopes.push(new Scope("Registers", VARIABLES_TAG_REGISTER, false));
|
||||||
response.body = {
|
response.body = {
|
||||||
scopes: scopes
|
scopes: scopes
|
||||||
};
|
};
|
||||||
|
@ -411,6 +450,15 @@ export class MI2DebugSession extends DebugSession {
|
||||||
protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise<void> {
|
protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise<void> {
|
||||||
const variables: DebugProtocol.Variable[] = [];
|
const variables: DebugProtocol.Variable[] = [];
|
||||||
let id: number | string | VariableObject | ExtendedVariable;
|
let id: number | string | VariableObject | ExtendedVariable;
|
||||||
|
|
||||||
|
if (args.variablesReference == VARIABLES_TAG_REGISTER) {
|
||||||
|
var results = await this.miDebugger.getRegisters();
|
||||||
|
response.body = {
|
||||||
|
variables: results
|
||||||
|
};
|
||||||
|
this.sendResponse(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (args.variablesReference < VAR_HANDLES_START) {
|
if (args.variablesReference < VAR_HANDLES_START) {
|
||||||
id = args.variablesReference - STACK_HANDLES_START;
|
id = args.variablesReference - STACK_HANDLES_START;
|
||||||
} else {
|
} else {
|
||||||
|
@ -648,7 +696,7 @@ export class MI2DebugSession extends DebugSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
|
protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
|
||||||
this.miDebugger.step(true).then(done => {
|
this.miDebugger.step(true, args.granularity === 'instruction').then(done => {
|
||||||
this.sendResponse(response);
|
this.sendResponse(response);
|
||||||
}, msg => {
|
}, msg => {
|
||||||
this.sendErrorResponse(response, 4, `Could not step back: ${msg} - Try running 'target record-full' before stepping back`);
|
this.sendErrorResponse(response, 4, `Could not step back: ${msg} - Try running 'target record-full' before stepping back`);
|
||||||
|
@ -656,7 +704,7 @@ export class MI2DebugSession extends DebugSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected stepInRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
|
protected stepInRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
|
||||||
this.miDebugger.step().then(done => {
|
this.miDebugger.step(false, args.granularity === 'instruction').then(done => {
|
||||||
this.sendResponse(response);
|
this.sendResponse(response);
|
||||||
}, msg => {
|
}, msg => {
|
||||||
this.sendErrorResponse(response, 4, `Could not step in: ${msg}`);
|
this.sendErrorResponse(response, 4, `Could not step in: ${msg}`);
|
||||||
|
|
Loading…
Reference in New Issue