Decompile minified dist/ bundles into human-readable TypeScript source

Reconstructed the original TypeScript source code from the two minified
webpack bundles (dist/adapter.js and dist/extension.js).

Source structure:
- src/common.ts: Shared types (NumberFormat, events, SymbolType/Scope)
- src/utils.ts: Utility functions (hexFormat, binaryFormat, etc.)
- src/extension.ts: VS Code extension entry point
- src/backend/: Debug adapter (GDB/MI2 protocol, symbol table, etc.)
- src/frontend/: VS Code UI providers (peripherals, registers, memory, disassembly)
This commit is contained in:
Jason2866
2026-03-11 15:54:07 +01:00
parent 3983f9ef76
commit ae9a84e351
23 changed files with 7490 additions and 2 deletions
+3
View File
@@ -0,0 +1,3 @@
node_modules/
out/
.vscode-test/
+2 -2
View File
@@ -178,7 +178,7 @@
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+2342
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
{
"name": "platformio-vscode-debug",
"version": "1.4.1",
"description": "PlatformIO Debugger for VSCode",
"main": "dist/extension.js",
"engines": {
"vscode": "^1.35.0"
},
"repository": {
"type": "git",
"url": "https://github.com/platformio/platformio-vscode-ide.git"
},
"scripts": {
"build": "webpack --mode production",
"lint": "tslint ./src/**/*.ts",
"postinstall": "node ./node_modules/@types/vscode/bin/install || true"
},
"dependencies": {
"copy-paste": "^1.3.0",
"vscode-debugadapter": "1.35.0",
"vscode-debugprotocol": "1.35.0",
"xml2js": "^0.4.23"
},
"devDependencies": {
"@types/node": "^12.0.0",
"@types/vscode": "~1.44.0",
"ts-loader": "^9.2.6",
"tslint": "^6.1.3",
"typescript": "^4.5.5",
"webpack": "~5.68.0",
"webpack-cli": "~4.9.2"
}
}
File diff suppressed because it is too large Load Diff
+284
View File
@@ -0,0 +1,284 @@
import { MINode } from './mi_parse';
const VARIABLE_ASSIGNMENT_REGEX = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*|\[\d+\])\s*=\s*/;
const VARIABLE_NAME_REGEX = /^[a-zA-Z_\-][a-zA-Z0-9_\-]*/;
const ANGLE_BRACKET_REGEX = /^\<.+?\>/;
const HEX_STRING_REGEX = /^(0x[0-9a-fA-F]+\s*)"/;
const HEX_REGEX = /^0x[0-9a-fA-F]+/;
const NULL_PTR_REGEX = /^0x0+\b/;
const CHAR_CODE_STRING_REGEX = /^(\d+) ['"]/;
const NUMBER_REGEX = /^\d+(\.\d+)?/;
export function isExpandable(value: string): number {
value = value.trim();
if (value.length === 0) {
return 0;
}
if (value.startsWith('{...}')) {
return 2;
}
if (value[0] === '{') {
return 1;
}
if (value.startsWith('true') || value.startsWith('false') || NULL_PTR_REGEX.exec(value) || HEX_STRING_REGEX.exec(value)) {
return 0;
}
if (HEX_REGEX.exec(value)) {
return 2;
}
// Check other patterns but always return 0 for them
CHAR_CODE_STRING_REGEX.exec(value) || NUMBER_REGEX.exec(value) || VARIABLE_NAME_REGEX.exec(value) || ANGLE_BRACKET_REGEX.exec(value);
return 0;
}
export function expandValue(
createVariableReference: (name: string | any, options?: any) => number,
value: string,
root: string = '',
extra?: any
): any {
const parseQuotedString = (): string => {
value = value.trim();
if (value[0] !== '"' && value[0] !== "'") {
return '';
}
let pos = 1;
let scanning = true;
const quote = value[0];
let remaining = value.substr(1);
let escaped = false;
while (scanning) {
if (escaped) {
escaped = false;
} else if (remaining[0] === '\\') {
escaped = true;
} else if (remaining[0] === quote) {
scanning = false;
}
remaining = remaining.substr(1);
pos++;
}
const result = value.substr(0, pos).trim();
value = value.substr(pos).trim();
return result;
};
const stack = [root];
let lastAssignedVariable = '';
const buildFullExpression = (name: string): string => {
let fullPath = '';
let derefPrefix = '';
stack.push(name);
stack.forEach((part) => {
derefPrefix = '';
if (part === '') {
// skip
} else if (part.startsWith('[')) {
fullPath += part;
} else if (fullPath) {
while (part.startsWith('*')) {
derefPrefix += '*';
part = part.substr(1);
}
fullPath = fullPath + '.' + part;
} else {
fullPath = part;
}
});
stack.pop();
return derefPrefix + fullPath;
};
let parseValue: () => any;
let parseCommaValue: () => any;
let parseNamedValue: (pushToStack?: boolean) => any;
let parseCommaNamedValue: (pushToStack?: boolean) => any;
let createResult: (name: string, val: any) => any;
parseValue = (): any => {
value = value.trim();
if (value[0] === '"') {
return parseQuotedString();
}
if (value[0] === '{') {
// Parse object/array
return (() => {
value = value.trim();
if (value[0] !== '{') {
return;
}
value = value.substr(1).trim();
if (value[0] === '}') {
value = value.substr(1).trim();
return [];
}
if (value.startsWith('...')) {
value = value.substr(3).trim();
if (value[0] === '}') {
value = value.substr(1).trim();
return '<...>';
}
}
// Check if this is an array or named values
const equalsIdx = value.indexOf('=');
const braceIdx = value.indexOf('{');
const commaIdx = value.indexOf(',');
let checkIdx = braceIdx;
if (commaIdx !== -1 && commaIdx < braceIdx) {
checkIdx = commaIdx;
}
if ((checkIdx !== -1 && equalsIdx > checkIdx) || equalsIdx === -1) {
// Array of values
const arr: any[] = [];
stack.push('[0]');
let val = parseValue();
stack.pop();
arr.push(createResult('[0]', val));
let index = 0;
for (;;) {
stack.push('[' + (++index) + ']');
val = parseCommaValue();
if (!val) {
stack.pop();
break;
}
stack.pop();
arr.push(createResult('[' + index + ']', val));
}
value = value.substr(1).trim();
return arr;
}
// Named values
let named = parseNamedValue(true);
if (named) {
const arr: any[] = [];
arr.push(named);
while ((named = parseCommaNamedValue(true))) {
arr.push(named);
}
value = value.substr(1).trim();
return arr;
}
})();
}
// Parse primitive value
return (() => {
value = value.trim();
let match: RegExpExecArray;
let result: any;
if (value.length === 0) {
result = undefined;
} else if (value.startsWith('true')) {
result = 'true';
value = value.substr(4).trim();
} else if (value.startsWith('false')) {
result = 'false';
value = value.substr(5).trim();
} else if ((match = NULL_PTR_REGEX.exec(value))) {
result = '<nullptr>';
value = value.substr(match[0].length).trim();
} else if ((match = HEX_STRING_REGEX.exec(value))) {
value = value.substr(match[1].length).trim();
result = parseQuotedString();
} else if ((match = HEX_REGEX.exec(value))) {
result = '*' + match[0];
value = value.substr(match[0].length).trim();
} else if ((match = CHAR_CODE_STRING_REGEX.exec(value))) {
result = match[1];
value = value.substr(match[0].length - 1);
result += ' ' + parseQuotedString();
} else if (
(match = NUMBER_REGEX.exec(value)) ||
(match = VARIABLE_NAME_REGEX.exec(value)) ||
(match = ANGLE_BRACKET_REGEX.exec(value))
) {
result = match[0];
value = value.substr(match[0].length).trim();
} else {
result = value;
}
return result;
})();
};
parseNamedValue = (pushToStack: boolean = false): any => {
value = value.trim();
const match = VARIABLE_ASSIGNMENT_REGEX.exec(value);
if (!match) {
return;
}
value = value.substr(match[0].length).trim();
const name = (lastAssignedVariable = match[1]);
if (pushToStack) {
stack.push(lastAssignedVariable);
}
const val = parseValue();
if (pushToStack) {
stack.pop();
}
return createResult(name, val);
};
createResult = (name: string, val: any): any => {
let variablesReference = 0;
if (typeof val === 'object') {
variablesReference = createVariableReference(val);
val = 'Object';
}
if (typeof val === 'string' && val.startsWith('*0x')) {
if (extra && MINode.valueOf(extra, 'arg') === '1') {
variablesReference = createVariableReference(buildFullExpression('*(' + name), { arg: true });
val = '<args>';
} else {
variablesReference = createVariableReference(buildFullExpression('*' + name));
val = 'Object@' + val;
}
}
if (typeof val === 'string' && val.startsWith('<...>')) {
variablesReference = createVariableReference(buildFullExpression(name));
val = '...';
}
return {
name,
value: val,
variablesReference,
};
};
parseCommaValue = (): any => {
value = value.trim();
if (value[0] === ',') {
value = value.substr(1).trim();
return parseValue();
}
};
parseCommaNamedValue = (pushToStack: boolean = false): any => {
value = value.trim();
if (value[0] === ',') {
value = value.substr(1).trim();
return parseNamedValue(pushToStack);
}
};
value = value.trim();
return parseValue();
}
+567
View File
@@ -0,0 +1,567 @@
import * as childProcess from 'child_process';
import { EventEmitter } from 'events';
import { VariableObject, MIError } from './types';
import { parseMI, MINode } from '../mi_parse';
export function escape(str: string): string {
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}
const MI_OUTPUT_REGEX = /^(?:\d*|undefined)[\*\+\=]|[\~\@\&\^]/;
const GDB_PROMPT_REGEX = /(?:\d*|undefined)\(gdb\)/;
const BREAK_COUNT_REGEX = /\d+/;
function isPlainOutput(line: string): boolean {
return !MI_OUTPUT_REGEX.exec(line);
}
export class MI2 extends EventEmitter {
private process: childProcess.ChildProcess;
private currentToken: number = 1;
private handlers: { [token: number]: (result: MINode) => void } = {};
public printCalls: boolean;
public debugOutput: boolean;
private debugReadyFired: boolean = false;
private debugReadyTimeout: any;
private buffer: string;
private errbuf: string;
constructor(
public application: string,
public args: string[]
) {
super();
}
connect(cwd: string, commands: string[]): Promise<boolean> {
return new Promise((resolve, reject) => {
const args = [...this.args];
const env = Object.create(process.env);
if (process.env.PLATFORMIO_PATH) {
env.PATH = process.env.PLATFORMIO_PATH;
env.Path = process.env.PLATFORMIO_PATH;
}
this.process = childProcess.spawn(this.application, args, {
cwd,
env,
shell: process.platform === 'win32',
});
this.process.stdout.on('data', this.stdout.bind(this));
this.process.stderr.on('data', this.stderr.bind(this));
this.process.on('exit', (() => {
this.emit('quit');
}).bind(this));
this.process.on('error', ((err: Error) => {
this.emit('launcherror', err);
}).bind(this));
const initCommands = [
this.sendCommand('gdb-set target-async on', true),
...commands.map((cmd) => this.sendCommand(cmd)),
];
Promise.all(initCommands).then(() => {
resolve(true);
}, reject);
});
}
stdout(data: any): void {
this.buffer += typeof data === 'string' ? data : data.toString('utf8');
const newlineIndex = this.buffer.lastIndexOf('\n');
if (newlineIndex !== -1) {
this.onOutput(this.buffer.substr(0, newlineIndex));
this.buffer = this.buffer.substr(newlineIndex + 1);
}
if (this.buffer.length) {
if (this.onOutputPartial(this.buffer)) {
this.buffer = '';
}
}
}
stderr(data: any): void {
this.errbuf += typeof data === 'string' ? data : data.toString('utf8');
const newlineIndex = this.errbuf.lastIndexOf('\n');
if (newlineIndex !== -1) {
this.onOutputStderr(this.errbuf.substr(0, newlineIndex));
this.errbuf = this.errbuf.substr(newlineIndex + 1);
}
if (this.errbuf.length) {
this.logNoNewLine('stderr', this.errbuf);
this.errbuf = '';
}
}
onOutputStderr(output: string): void {
const lines = output.split('\n');
lines.forEach((line) => {
this.log('stderr', line);
});
}
onOutputPartial(line: string): boolean {
if (isPlainOutput(line)) {
this.logNoNewLine('stdout', line);
return true;
}
return false;
}
onOutput(output: string): void {
const lines = output.split('\n');
lines.forEach((line) => {
if (isPlainOutput(line)) {
if (!GDB_PROMPT_REGEX.exec(line)) {
this.log('stdout', line);
}
} else {
const parsed = parseMI(line);
if (this.debugOutput) {
this.log('log', 'GDB -> App: ' + JSON.stringify(parsed));
}
let handled = false;
if (parsed.token !== undefined && this.handlers[parsed.token]) {
this.handlers[parsed.token](parsed);
delete this.handlers[parsed.token];
handled = true;
}
if (!handled && parsed.resultRecords && parsed.resultRecords.resultClass === 'error') {
this.log('stderr', parsed.result('msg') || line);
}
if (parsed.outOfBandRecord) {
parsed.outOfBandRecord.forEach((record: any) => {
if (record.isStream) {
if (record.content.includes('PlatformIO: Initialization completed')) {
this.debugReadyTimeout = setTimeout(() => {
this.debugReadyFired = true;
this.emit('debug-ready');
}, 200);
this.once('generic-stopped', () => {
if (!this.debugReadyFired) {
clearTimeout(this.debugReadyTimeout);
this.emit('debug-ready');
}
});
}
this.log(record.type, record.content);
} else if (record.type === 'exec') {
this.emit('exec-async-output', parsed);
if (record.asyncClass === 'running') {
this.emit('running', parsed);
} else if (record.asyncClass === 'stopped') {
const reason = parsed.record('reason');
if (reason === 'breakpoint-hit') {
this.emit('breakpoint', parsed);
} else if (reason === 'end-stepping-range') {
this.emit('step-end', parsed);
} else if (reason === 'function-finished') {
this.emit('step-out-end', parsed);
} else if (reason === 'signal-received') {
this.emit('signal-stop', parsed);
} else if (reason === 'exited-normally') {
this.emit('exited-normally', parsed);
} else if (reason === 'exited') {
this.log('stderr', 'Program exited with code ' + parsed.record('exit-code'));
this.emit('exited-normally', parsed);
} else {
if (this.debugReadyFired) {
this.log('console', 'Not implemented stop reason (assuming exception): ' + reason);
}
this.emit('stopped', parsed);
}
this.emit('generic-stopped', parsed);
} else {
this.log('log', JSON.stringify(parsed));
}
} else if (record.type === 'notify') {
if (record.asyncClass === 'thread-created') {
const threadId = parsed.result('id');
const threadGroupId = parsed.result('group-id');
this.emit('thread-created', { threadId, threadGroupId });
} else if (record.asyncClass === 'thread-exited') {
const threadId = parsed.result('id');
const threadGroupId = parsed.result('group-id');
this.emit('thread-exited', { threadId, threadGroupId });
} else if (record.asyncClass === 'thread-selected') {
const threadId = parsed.result('id');
this.emit('thread-selected', { threadId });
}
}
});
handled = true;
}
if (parsed.token === undefined && parsed.resultRecords === undefined && parsed.outOfBandRecord.length === 0) {
handled = true;
}
if (!handled) {
this.log('log', 'Unhandled: ' + JSON.stringify(parsed));
}
}
});
}
stop(forceKill: boolean = false): void {
if (forceKill) {
const proc = this.process;
const killTimeout = setTimeout(() => {
process.kill(-proc.pid);
}, 1000);
this.process.on('exit', (code: any) => {
clearTimeout(killTimeout);
});
}
this.sendRaw('-gdb-exit');
}
detach(): void {
const proc = this.process;
const killTimeout = setTimeout(() => {
process.kill(-proc.pid);
}, 1000);
this.process.on('exit', (code: any) => {
clearTimeout(killTimeout);
});
this.sendRaw('-target-detach');
}
interrupt(threadId: number): Promise<boolean> {
return new Promise((resolve, reject) => {
this.sendCommand(`exec-interrupt --thread ${threadId}`).then(
(result) => {
resolve(result.resultRecords.resultClass === 'done');
},
reject
);
});
}
continue(threadId: number): Promise<boolean> {
return new Promise((resolve, reject) => {
this.sendCommand(`exec-continue --thread ${threadId}`).then(
(result) => {
resolve(result.resultRecords.resultClass === 'running');
},
reject
);
});
}
next(threadId: number, instruction: boolean): Promise<boolean> {
return new Promise((resolve, reject) => {
const command = instruction ? 'exec-next-instruction' : 'exec-next';
this.sendCommand(`${command} --thread ${threadId}`).then(
(result) => {
resolve(result.resultRecords.resultClass === 'running');
},
reject
);
});
}
step(threadId: number, instruction: boolean): Promise<boolean> {
return new Promise((resolve, reject) => {
const command = instruction ? 'exec-step-instruction' : 'exec-step';
this.sendCommand(`${command} --thread ${threadId}`).then(
(result) => {
resolve(result.resultRecords.resultClass === 'running');
},
reject
);
});
}
stepOut(threadId: number): Promise<boolean> {
return new Promise((resolve, reject) => {
this.sendCommand(`exec-finish --thread ${threadId}`).then(
(result) => {
resolve(result.resultRecords.resultClass === 'running');
},
reject
);
});
}
restart(commands: string[]): Promise<boolean> {
return this._sendCommandSequence(commands);
}
_sendCommandSequence(commands: string[]): Promise<boolean> {
return new Promise((resolve, reject) => {
const executeNext = ((remaining: string[]) => {
if (remaining.length === 0) {
resolve(true);
return;
}
const cmd = remaining[0];
this.sendCommand(cmd).then(
(result) => {
executeNext(remaining.slice(1));
},
reject
);
}).bind(this);
executeNext(commands);
});
}
changeVariable(name: string, rawValue: string): Promise<MINode> {
return this.sendCommand('gdb-set var ' + name + '=' + rawValue);
}
setBreakPointCondition(breakpointNumber: number, condition: string): Promise<MINode> {
return this.sendCommand('break-condition ' + breakpointNumber + ' ' + condition);
}
addBreakPoint(breakpoint: any): Promise<any> {
return new Promise((resolve, reject) => {
let args = '';
if (breakpoint.countCondition) {
if (breakpoint.countCondition[0] === '>') {
args += '-i ' + BREAK_COUNT_REGEX.exec(breakpoint.countCondition.substr(1))[0] + ' ';
} else {
const count = BREAK_COUNT_REGEX.exec(breakpoint.countCondition)[0];
if (count.length !== breakpoint.countCondition.length) {
this.log(
'stderr',
"Unsupported break count expression: '" +
breakpoint.countCondition +
"'. Only supports 'X' for breaking once after X times or '>X' for ignoring the first X breaks"
);
args += '-t ';
} else if (parseInt(count) !== 0) {
args += '-t -i ' + parseInt(count) + ' ';
}
}
}
if (breakpoint.raw) {
args += '*' + escape(breakpoint.raw);
} else {
args += '"' + escape(breakpoint.file) + ':' + breakpoint.line + '"';
}
this.sendCommand(`break-insert ${args}`).then(
(result) => {
if (result.resultRecords.resultClass === 'done') {
const bkptNumber = parseInt(result.result('bkpt.number'));
breakpoint.number = bkptNumber;
if (breakpoint.condition) {
this.setBreakPointCondition(bkptNumber, breakpoint.condition).then(
(condResult) => {
if (condResult.resultRecords.resultClass === 'done') {
resolve(breakpoint);
} else {
resolve(null);
}
},
reject
);
} else {
resolve(breakpoint);
}
} else {
resolve(null);
}
},
reject
);
});
}
removeBreakpoints(breakpointNumbers: number[]): Promise<boolean> {
return new Promise((resolve, reject) => {
if (breakpointNumbers.length === 0) {
resolve(true);
} else {
const cmd = 'break-delete ' + breakpointNumbers.join(' ');
this.sendCommand(cmd).then(
(result) => {
resolve(result.resultRecords.resultClass === 'done');
},
reject
);
}
});
}
getFrame(threadId: number, frameLevel: number): Promise<any> {
return new Promise((resolve, reject) => {
const cmd = `stack-info-frame --thread ${threadId} --frame ${frameLevel}`;
this.sendCommand(cmd).then(
(result) => {
const frame = result.result('frame');
const level = MINode.valueOf(frame, 'level');
const address = MINode.valueOf(frame, 'addr');
const func = MINode.valueOf(frame, 'func');
const file = MINode.valueOf(frame, 'file');
const fullname = MINode.valueOf(frame, 'fullname');
let line = 0;
const lineStr = MINode.valueOf(frame, 'line');
if (lineStr) {
line = parseInt(lineStr);
}
resolve({
address,
fileName: file,
file: fullname,
function: func,
level,
line,
});
},
reject
);
});
}
getStack(threadId: number, startFrame: number, levels: number): Promise<any[]> {
return new Promise((resolve, reject) => {
this.sendCommand(`stack-list-frames --thread ${threadId} ${startFrame} ${levels}`).then(
(result) => {
const stack = result.result('stack');
const frames: any[] = [];
stack.forEach((entry: any) => {
const level = MINode.valueOf(entry, '@frame.level');
const address = MINode.valueOf(entry, '@frame.addr');
const func = MINode.valueOf(entry, '@frame.func');
const file = MINode.valueOf(entry, '@frame.file');
const fullname = MINode.valueOf(entry, '@frame.fullname');
let line = 0;
const lineStr = MINode.valueOf(entry, '@frame.line');
if (lineStr) {
line = parseInt(lineStr);
}
const from = parseInt(MINode.valueOf(entry, '@frame.from'));
frames.push({
address,
fileName: file,
file: fullname,
function: func || from,
level,
line,
});
});
resolve(frames);
},
reject
);
});
}
async getStackVariables(threadId: number, frameLevel: number): Promise<any[]> {
const result = await this.sendCommand(
`stack-list-variables --thread ${threadId} --frame ${frameLevel} --simple-values`
);
const variables = result.result('variables');
const vars: any[] = [];
for (const variable of variables) {
const name = MINode.valueOf(variable, 'name');
const valueStr = MINode.valueOf(variable, 'value');
const type = MINode.valueOf(variable, 'type');
vars.push({ name, valueStr, type, raw: variable });
}
return vars;
}
async examineMemory(address: number, length: number): Promise<string> {
let result = '';
let currentAddress = address;
while (length > 0) {
const chunkSize = length > 1024 ? 1024 : length;
const response = await this.sendCommand(
`data-read-memory-bytes 0x${currentAddress.toString(16)} ${chunkSize}`
);
result += response.result('memory[0].contents');
length -= chunkSize;
currentAddress += chunkSize;
}
return result;
}
evalExpression(expression: string): Promise<MINode> {
return new Promise((resolve, reject) => {
this.sendCommand('data-evaluate-expression ' + expression).then(
(result) => {
resolve(result);
},
reject
);
});
}
async varCreate(expression: string, name: string = '-'): Promise<VariableObject> {
const result = await this.sendCommand(`var-create ${name} @ "${expression}"`);
return new VariableObject(result.result(''));
}
async varEvalExpression(name: string): Promise<MINode> {
return this.sendCommand(`var-evaluate-expression ${name}`);
}
async varListChildren(name: string): Promise<VariableObject[]> {
const result = await this.sendCommand(`var-list-children --all-values ${name}`);
return (result.result('children') || []).map((child: any) => new VariableObject(child[1]));
}
async varUpdate(name: string = '*'): Promise<MINode> {
return this.sendCommand(`var-update --all-values ${name}`);
}
async varAssign(name: string, value: string): Promise<MINode> {
return this.sendCommand(`var-assign ${name} ${value}`);
}
logNoNewLine(type: string, message: string): void {
this.emit('msg', type, message);
}
log(type: string, message: string): void {
this.emit('msg', type, message[message.length - 1] === '\n' ? message : message + '\n');
}
sendUserInput(command: string): Promise<MINode> {
if (command.startsWith('-')) {
return this.sendCommand(command.substr(1));
}
return this.sendCommand(`interpreter-exec console "${command}"`);
}
sendRaw(raw: string): void {
if (this.printCalls) {
this.log('log', raw);
}
this.process.stdin.write(raw + '\n');
}
sendCommand(command: string, suppressErrors: boolean = false): Promise<MINode> {
const token = this.currentToken++;
return new Promise((resolve, reject) => {
this.handlers[token] = (result: MINode) => {
if (result && result.resultRecords && result.resultRecords.resultClass === 'error') {
if (suppressErrors) {
this.log('stderr', `WARNING: Error executing command '${command}'`);
resolve(result);
} else {
reject(new MIError(result.result('msg') || 'Internal error', command));
}
} else {
resolve(result);
}
};
this.sendRaw(token + '-' + command);
});
}
isReady(): boolean {
return !!this.process;
}
}
+83
View File
@@ -0,0 +1,83 @@
import { MINode } from '../mi_parse';
export class VariableObject {
public name: string;
public exp: string;
public numchild: number;
public type: string;
public value: string;
public threadId: string;
public frozen: boolean;
public dynamic: boolean;
public displayhint: string;
public hasMore: boolean;
public id: number;
public fullExp: string;
constructor(node: any) {
this.name = MINode.valueOf(node, 'name');
this.exp = MINode.valueOf(node, 'exp');
this.numchild = parseInt(MINode.valueOf(node, 'numchild'));
this.type = MINode.valueOf(node, 'type');
this.value = MINode.valueOf(node, 'value');
this.threadId = MINode.valueOf(node, 'thread-id');
this.frozen = !!MINode.valueOf(node, 'frozen');
this.dynamic = !!MINode.valueOf(node, 'dynamic');
this.displayhint = MINode.valueOf(node, 'displayhint');
this.hasMore = !!MINode.valueOf(node, 'has_more');
}
applyChanges(node: any): void {
this.value = MINode.valueOf(node, 'value');
if (MINode.valueOf(node, 'type_changed')) {
this.type = MINode.valueOf(node, 'new_type');
}
this.dynamic = !!MINode.valueOf(node, 'dynamic');
this.displayhint = MINode.valueOf(node, 'displayhint');
this.hasMore = !!MINode.valueOf(node, 'has_more');
}
isCompound(): boolean {
return (
this.numchild > 0 ||
this.value === '{...}' ||
(this.dynamic && (this.displayhint === 'array' || this.displayhint === 'map'))
);
}
toProtocolVariable(): any {
return {
name: this.exp,
evaluateName: this.fullExp || this.exp,
value: this.value === undefined ? '<unknown>' : this.value,
type: this.type,
presentationHint: { kind: this.displayhint },
variablesReference: this.id,
};
}
}
export class MIError {
public readonly message: string;
public readonly source: string;
constructor(message: string, source: string) {
Object.defineProperty(this, 'name', {
get: () => this.constructor.name,
});
Object.defineProperty(this, 'message', {
get: () => message,
});
Object.defineProperty(this, 'source', {
get: () => source,
});
Error.captureStackTrace(this, this.constructor);
}
toString(): string {
return `${(this as any).message} (from ${(this as any).source})`;
}
}
Object.setPrototypeOf(MIError, Object.create(Error.prototype));
(MIError as any).prototype.constructor = MIError;
+317
View File
@@ -0,0 +1,317 @@
const OCTAL_ESCAPE_REGEX = /^[0-7]{3}/;
export class MINode {
public token: number;
public outOfBandRecord: any[];
public resultRecords: any;
constructor(token: number, outOfBandRecord: any[], resultRecords: any) {
this.token = token;
this.outOfBandRecord = outOfBandRecord;
this.resultRecords = resultRecords;
}
static valueOf(startNode: any, path: string): any {
if (!startNode) {
return undefined;
}
const KEY_REGEX = /^\.?([a-zA-Z_\-][a-zA-Z0-9_\-]*)/;
const INDEX_REGEX = /^\[(\d+)\](?:$|\.)/;
path = path.trim();
if (!path) {
return startNode;
}
let current = startNode;
do {
let match = KEY_REGEX.exec(path);
if (match) {
path = path.substr(match[0].length);
if (!current.length || typeof current === 'string') {
return undefined;
} else {
const matches: any[] = [];
for (const item of current) {
if (item[0] === match[1]) {
matches.push(item[1]);
}
}
if (matches.length > 1) {
current = matches;
} else if (matches.length === 1) {
current = matches[0];
} else {
return undefined;
}
}
} else if (path[0] === '@') {
current = [current];
path = path.substr(1);
} else {
match = INDEX_REGEX.exec(path);
if (!match) {
return undefined;
}
path = path.substr(match[0].length);
const index = parseInt(match[1]);
if (current.length && typeof current !== 'string' && index >= 0 && index < current.length) {
current = current[index];
} else if (index !== 0) {
return undefined;
}
}
path = path.trim();
} while (path);
return current;
}
record(path: string): any {
if (this.outOfBandRecord) {
return MINode.valueOf(this.outOfBandRecord[0].output, path);
}
}
result(path: string): any {
if (this.resultRecords) {
return MINode.valueOf(this.resultRecords.results, path);
}
}
}
const OUT_OF_BAND_REGEX = /^(?:(\d*|undefined)([\*\+\=])|([\~\@\&]))/;
const RESULT_REGEX = /^(\d*)\^(done|running|connected|error|exit)/;
const NEWLINE_REGEX = /^\r\n?/;
const VARIABLE_NAME_REGEX = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*)/;
const ASYNC_CLASS_REGEX = /^(.*?),/;
function parseString(input: string): [string, string] {
if (input[0] !== '"') {
return ['', input];
}
let pos = 1;
let scanning = true;
let str = input.substr(1);
let escaped = false;
while (scanning) {
if (escaped) {
escaped = false;
} else if (str[0] === '\\') {
escaped = true;
} else if (str[0] === '"') {
scanning = false;
}
str = str.substr(1);
pos++;
}
let result: string;
try {
result = parseCString(input.substr(0, pos));
} catch (e) {
result = input.substr(0, pos);
}
return [result, input.substr(pos)];
}
function parseCString(str: string): string {
const buffer = Buffer.alloc(str.length * 4);
let offset = 0;
if (str[0] !== '"' || str[str.length - 1] !== '"') {
throw new Error('Not a valid string');
}
str = str.slice(1, -1);
let escaped = false;
for (let i = 0; i < str.length; i++) {
if (escaped) {
let octalMatch;
if (str[i] === '\\') {
offset += buffer.write('\\', offset);
} else if (str[i] === '"') {
offset += buffer.write('"', offset);
} else if (str[i] === "'") {
offset += buffer.write("'", offset);
} else if (str[i] === 'n') {
offset += buffer.write('\n', offset);
} else if (str[i] === 'r') {
offset += buffer.write('\r', offset);
} else if (str[i] === 't') {
offset += buffer.write('\t', offset);
} else if (str[i] === 'b') {
offset += buffer.write('\b', offset);
} else if (str[i] === 'f') {
offset += buffer.write('\f', offset);
} else if (str[i] === 'v') {
offset += buffer.write('\v', offset);
} else if (str[i] === '0') {
offset += buffer.write('\0', offset);
} else if ((octalMatch = OCTAL_ESCAPE_REGEX.exec(str.substr(i)))) {
buffer.writeUInt8(parseInt(octalMatch[0], 8), offset++);
i += 2;
} else {
offset += buffer.write(str[i], offset);
}
escaped = false;
} else if (str[i] === '\\') {
escaped = true;
} else {
if (str[i] === '"') {
throw new Error('Not a valid string');
}
offset += buffer.write(str[i], offset);
}
}
return buffer.slice(0, offset).toString('utf8');
}
export function parseMI(output: string): MINode {
let token: number;
const outOfBandRecords: any[] = [];
let resultRecords: any;
const asyncClassMap: { [key: string]: string } = {
'*': 'exec',
'+': 'status',
'=': 'notify',
};
const streamClassMap: { [key: string]: string } = {
'~': 'console',
'@': 'target',
'&': 'log',
};
let parseValue: () => any;
let parseResult: () => any;
let parseCommaValue: () => any;
let parseCommaResult: () => any;
parseValue = (): any => {
if (output[0] === '"') {
const [str, rest] = parseString(output);
output = rest;
return str;
} else if (output[0] === '{' || output[0] === '[') {
const isList = output[0] === '[';
output = output.substr(1);
if (output[0] === '}' || output[0] === ']') {
output = output.substr(1);
return [];
}
if (isList) {
let val = parseValue();
if (val) {
const arr: any[] = [];
arr.push(val);
while ((val = parseCommaValue()) !== undefined) {
arr.push(val);
}
output = output.substr(1);
return arr;
}
}
let result = parseResult();
if (result) {
const arr: any[] = [];
arr.push(result);
while ((result = parseCommaResult())) {
arr.push(result);
}
output = output.substr(1);
return arr;
}
output = (isList ? '[' : '{') + output;
}
return undefined;
};
parseResult = (): any => {
const match = VARIABLE_NAME_REGEX.exec(output);
if (match) {
output = output.substr(match[0].length + 1); // +1 for '='
return [match[1], parseValue()];
}
};
parseCommaValue = (): any => {
if (output[0] === ',') {
output = output.substr(1);
return parseValue();
}
};
parseCommaResult = (): any => {
if (output[0] === ',') {
output = output.substr(1);
return parseResult();
}
};
let match: RegExpExecArray;
while ((match = OUT_OF_BAND_REGEX.exec(output))) {
output = output.substr(match[0].length);
if (match[1] && token === undefined && match[1] !== 'undefined') {
token = parseInt(match[1]);
}
if (match[2]) {
const classMatch = ASYNC_CLASS_REGEX.exec(output);
output = output.substr(classMatch[1].length);
const record: any = {
isStream: false,
type: asyncClassMap[match[2]],
asyncClass: classMatch[1],
output: [],
};
let res;
while ((res = parseCommaResult())) {
record.output.push(res);
}
outOfBandRecords.push(record);
} else if (match[3]) {
const [content, rest] = parseString(output);
output = rest;
const record: any = {
isStream: true,
type: streamClassMap[match[3]],
content,
};
outOfBandRecords.push(record);
}
output = output.replace(NEWLINE_REGEX, '');
}
if ((match = RESULT_REGEX.exec(output))) {
output = output.substr(match[0].length);
if (match[1] && token === undefined) {
token = parseInt(match[1]);
}
resultRecords = {
resultClass: match[2],
results: [],
};
let res;
while ((res = parseCommaResult())) {
resultRecords.results.push(res);
}
output = output.replace(NEWLINE_REGEX, '');
}
return new MINode(token, outOfBandRecords || [], resultRecords);
}
+167
View File
@@ -0,0 +1,167 @@
import * as childProcess from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { SymbolType, SymbolScope } from '../common';
const SYMBOL_REGEX = /^([0-9a-f]{8})\s([lg\ !])([w\ ])([C\ ])([W\ ])([I\ ])([dD\ ])([FfO\ ])\s([^\s]+)\s([0-9a-f]+)\s(.*)\r?$/;
const DEMANGLED_NAME_REGEX = /^_Z[^\d]*(\d+)(.+)$/;
const TYPE_MAP: { [key: string]: SymbolType } = {
'F': SymbolType.Function,
'f': SymbolType.File,
'O': SymbolType.Object,
' ': SymbolType.Normal,
};
const SCOPE_MAP: { [key: string]: SymbolScope } = {
'l': SymbolScope.Local,
'g': SymbolScope.Global,
' ': SymbolScope.Neither,
'!': SymbolScope.Both,
};
export interface SymbolInformation {
address: number;
type: SymbolType;
scope: SymbolScope;
section: string;
length: number;
name: string;
file: string | null;
instructions: any[] | null;
hidden: boolean;
}
export class SymbolTable {
private symbols: SymbolInformation[] = [];
constructor(
private toolchainBinDir: string,
private executable: string
) {}
loadSymbols(): void {
let objdumpPath = '';
fs.readdirSync(this.toolchainBinDir).forEach((file) => {
if (file.includes('objdump') && fs.existsSync(path.join(this.toolchainBinDir, file))) {
objdumpPath = path.join(this.toolchainBinDir, file);
}
});
if (!objdumpPath) {
throw new Error('Could not find "objdump" program');
}
const lines = childProcess
.spawnSync(objdumpPath, ['--syms', this.executable])
.stdout.toString()
.split('\n');
let currentFile: string | null = null;
for (const line of lines) {
const match = line.match(SYMBOL_REGEX);
if (!match) {
continue;
}
const type = TYPE_MAP[match[8]];
const scope = SCOPE_MAP[match[2]];
let name = match[11].trim();
let hidden = false;
if (match[7] === 'd' && match[8] === 'f') {
currentFile = name;
} else {
if (name.startsWith('.hidden')) {
name = name.substring(7).trim();
hidden = true;
}
const demangledMatch = name.match(DEMANGLED_NAME_REGEX);
if (demangledMatch) {
if (type !== SymbolType.Function) {
continue;
}
const nameLength = parseInt(demangledMatch[1]);
name = demangledMatch[2].substr(0, nameLength);
if (demangledMatch[2].length > nameLength) {
const nestedMatch = demangledMatch[2].substr(nameLength).match(/^(\d+)(.+)$/);
if (nestedMatch) {
name += '::' + nestedMatch[2].substr(0, parseInt(nestedMatch[1]));
}
}
}
this.symbols.push({
address: parseInt(match[1], 16),
type,
scope,
section: match[9].trim(),
length: parseInt(match[10], 16),
name,
file: scope === SymbolScope.Local ? currentFile : null,
instructions: null,
hidden,
});
}
}
}
getFunctionAtAddress(address: number): SymbolInformation | undefined {
const matches = this.symbols.filter(
(sym) =>
sym.type === SymbolType.Function &&
sym.address <= address &&
sym.address + sym.length > address
);
if (matches && matches.length !== 0) {
return matches[0];
}
}
getFunctionSymbols(): SymbolInformation[] {
return this.symbols.filter((sym) => sym.type === SymbolType.Function);
}
getGlobalVariables(): SymbolInformation[] {
return this.symbols.filter(
(sym) => sym.type === SymbolType.Object && sym.scope === SymbolScope.Global
);
}
getStaticVariables(file: string): SymbolInformation[] {
return this.symbols.filter(
(sym) =>
sym.type === SymbolType.Object &&
sym.scope === SymbolScope.Local &&
sym.file === file
);
}
getFunctionByName(name: string, file: string): SymbolInformation | null {
let matches = this.symbols.filter(
(sym) =>
sym.type === SymbolType.Function &&
sym.scope === SymbolScope.Local &&
sym.name === name &&
sym.file === file
);
if (matches.length !== 0) {
return matches[0];
}
matches = this.symbols.filter(
(sym) =>
sym.type === SymbolType.Function &&
sym.scope !== SymbolScope.Local &&
sym.name === name
);
if (matches.length !== 0) {
return matches[0];
}
return null;
}
}
+40
View File
@@ -0,0 +1,40 @@
import { Event } from 'vscode-debugadapter';
export enum NumberFormat {
Auto = 0,
Hexidecimal = 1,
Decimal = 2,
Binary = 3,
}
export class AdapterOutputEvent extends Event {
constructor(content: string, type: string) {
super('adapter-output', { content, type });
}
}
export class StoppedEvent extends Event {
constructor(reason: string, threadId: number, allThreadsStopped: boolean) {
super('stopped', { reason, threadId, allThreadsStopped });
}
}
export class TelemetryEvent extends Event {
constructor(category: string, action: string, label: string, parameters: any = {}) {
super('record-event', { category, action, label, parameters });
}
}
export enum SymbolType {
Function = 0,
File = 1,
Object = 2,
Normal = 3,
}
export enum SymbolScope {
Local = 0,
Global = 1,
Neither = 2,
Both = 3,
}
+431
View File
@@ -0,0 +1,431 @@
import * as copyPaste from 'copy-paste';
import * as vscode from 'vscode';
import { NumberFormat, SymbolScope } from './common';
import { encodeDisassembly } from './utils';
import { PlatformIODebugConfigurationProvider } from './frontend/configprovider';
import { DisassemblyContentProvider } from './frontend/disassembly_content_provider';
import { DisassemblyTreeProvider } from './frontend/disassembly_tree_provider';
import { MemoryContentProvider } from './frontend/memory_content_provider';
import { MemoryTreeProvider } from './frontend/memory_tree_provider';
import { PeripheralTreeProvider, RecordType as PeripheralRecordType } from './frontend/peripheral';
import { RegisterTreeProvider, RecordType as RegisterRecordType } from './frontend/registers';
class PlatformIODebugExtension {
private adapterOutputChannel: vscode.OutputChannel = null;
private functionSymbols: any[] = null;
private context: vscode.ExtensionContext;
private registerProvider: RegisterTreeProvider;
private peripheralProvider: PeripheralTreeProvider;
private memoryTreeProvider: MemoryTreeProvider;
private disassemblyTreeProvider: DisassemblyTreeProvider;
private memoryContentProvider: MemoryContentProvider;
constructor(context: vscode.ExtensionContext) {
this.context = context;
this.registerProvider = new RegisterTreeProvider();
this.peripheralProvider = new PeripheralTreeProvider();
this.memoryTreeProvider = new MemoryTreeProvider();
this.disassemblyTreeProvider = new DisassemblyTreeProvider();
this.memoryContentProvider = new MemoryContentProvider();
const peripheralTreeView = vscode.window.createTreeView('platformio-debug.peripherals', {
treeDataProvider: this.peripheralProvider,
});
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider(
'platformio-debug',
new PlatformIODebugConfigurationProvider()
),
peripheralTreeView,
peripheralTreeView.onDidExpandElement(
this.peripheralProvider.onDidExpandElement.bind(this.peripheralProvider)
),
peripheralTreeView.onDidCollapseElement(
this.peripheralProvider.onDidCollapseElement.bind(this.peripheralProvider)
),
vscode.window.registerTreeDataProvider('platformio-debug.registers', this.registerProvider),
vscode.window.registerTreeDataProvider('platformio-debug.memory', this.memoryTreeProvider),
vscode.window.registerTreeDataProvider('platformio-debug.disassembly', this.disassemblyTreeProvider),
vscode.workspace.registerTextDocumentContentProvider('examinememory', this.memoryContentProvider),
vscode.workspace.registerTextDocumentContentProvider('disassembly', new DisassemblyContentProvider()),
vscode.commands.registerCommand('platformio-debug.peripherals.updateNode', this.peripheralsUpdateNode.bind(this)),
vscode.commands.registerCommand('platformio-debug.peripherals.selectedNode', this.peripheralsSelectedNode.bind(this)),
vscode.commands.registerCommand('platformio-debug.peripherals.copyValue', this.peripheralsCopyValue.bind(this)),
vscode.commands.registerCommand('platformio-debug.peripherals.setFormat', this.peripheralsSetFormat.bind(this)),
vscode.commands.registerCommand('platformio-debug.registers.selectedNode', this.registersSelectedNode.bind(this)),
vscode.commands.registerCommand('platformio-debug.registers.copyValue', this.registersCopyValue.bind(this)),
vscode.commands.registerCommand('platformio-debug.registers.setFormat', this.registersSetFormat.bind(this)),
vscode.commands.registerCommand('platformio-debug.memory.deleteHistoryItem', this.memoryDeleteHistoryItem.bind(this)),
vscode.commands.registerCommand('platformio-debug.memory.clearHistory', this.memoryClearHistory.bind(this)),
vscode.commands.registerCommand('platformio-debug.examineMemory', this.examineMemory.bind(this)),
vscode.commands.registerCommand('platformio-debug.viewDisassembly', this.showDisassembly.bind(this)),
vscode.commands.registerCommand('platformio-debug.setForceDisassembly', this.setForceDisassembly.bind(this)),
vscode.debug.onDidReceiveDebugSessionCustomEvent(this.receivedCustomEvent.bind(this)),
vscode.debug.onDidStartDebugSession(this.debugSessionStarted.bind(this)),
vscode.debug.onDidTerminateDebugSession(this.debugSessionTerminated.bind(this)),
vscode.window.onDidChangeActiveTextEditor(this.activeEditorChanged.bind(this)),
vscode.window.onDidChangeTextEditorSelection((e) => {
if (e && e.textEditor.document.fileName.endsWith('.dbgmem')) {
this.memoryContentProvider.handleSelection(e);
}
})
);
}
private isPIODebugSession(): boolean {
return vscode.debug.activeDebugSession && vscode.debug.activeDebugSession.type === 'platformio-debug';
}
private activeEditorChanged(editor: vscode.TextEditor): void {
if (!editor || !this.isPIODebugSession()) {
return;
}
const uri = editor.document.uri;
if (uri.scheme === 'file') {
vscode.debug.activeDebugSession.customRequest('set-active-editor', { path: uri.path });
} else if (uri.scheme === 'disassembly') {
vscode.debug.activeDebugSession.customRequest('set-active-editor', {
path: `${uri.scheme}://${uri.authority}${uri.path}`,
});
}
}
private async showDisassembly(): Promise<void> {
if (!this.isPIODebugSession()) {
vscode.window.showErrorMessage('No debugging session available');
return;
}
if (!this.functionSymbols) {
try {
const result = await vscode.debug.activeDebugSession.customRequest('load-function-symbols');
this.functionSymbols = result.functionSymbols;
} catch (e) {
vscode.window.showErrorMessage('Unable to load symbol table. Disassembly view unavailable.');
}
}
try {
const funcName = await vscode.window.showInputBox({
placeHolder: 'main',
ignoreFocusOut: true,
prompt: 'Function Name to Disassemble',
});
const matches = this.functionSymbols.filter((s) => s.name === funcName);
let uri: string;
if (matches.length === 1) {
uri = encodeDisassembly(matches[0].name, matches[0].file);
} else if (matches.length > 1) {
const selected = await vscode.window.showQuickPick(
matches.map((m) => ({
label: m.name,
name: m.name,
file: m.file,
scope: m.scope,
description:
m.scope === SymbolScope.Global ? 'Global Scope' : `Static in ${m.file}`,
})),
{ ignoreFocusOut: true }
);
uri = encodeDisassembly(selected.name, selected.file);
} else {
vscode.window.showErrorMessage(`No function with name ${funcName} found.`);
return;
}
if (uri) {
vscode.window.showTextDocument(vscode.Uri.parse(uri));
}
} catch (e) {
vscode.window.showErrorMessage('Unable to show disassembly.');
}
}
private setForceDisassembly(force?: string): void {
const doSet = (value: string) => {
const forced = value === 'Forced';
this.disassemblyTreeProvider.updateForcedState(forced);
return vscode.debug.activeDebugSession.customRequest('set-force-disassembly', { force: forced });
};
if (force) {
return doSet(force) as any;
}
vscode.window
.showQuickPick(
[
{
label: 'Auto',
description: 'Show disassembly for functions when source cannot be located.',
},
{
label: 'Forced',
description: 'Always show disassembly for functions.',
},
],
{ matchOnDescription: true, ignoreFocusOut: true }
)
.then(
(selected) => {
doSet(selected.label);
},
(err) => {}
);
}
private memoryDeleteHistoryItem(item: any): void {
const [address, length] = item.label.split('+');
this.memoryTreeProvider.deleteHistory(address, length);
}
private memoryClearHistory(): void {
this.memoryTreeProvider.clearHistory();
}
private examineMemory(address?: string, length?: string): any {
function validateInput(input: string): string | null {
if (/^0x[0-9a-f]{1,8}$/i.test(input) || /^[0-9]+$/i.test(input)) {
return input;
}
return null;
}
if (!this.isPIODebugSession()) {
vscode.window.showErrorMessage('No debugging session available');
return;
}
if (address && length) {
return this.showMemoryContent(address, length);
}
vscode.window
.showInputBox({
placeHolder: 'Prefix with 0x for hexidecimal format',
ignoreFocusOut: true,
prompt: 'A start memory address',
})
.then(
(addressInput) => {
if (validateInput(addressInput)) {
vscode.window
.showInputBox({
placeHolder: 'Prefix with 0x for hexidecimal format',
ignoreFocusOut: true,
prompt: 'How many bytes to read?',
})
.then(
(lengthInput) => {
if (validateInput(lengthInput)) {
this.memoryTreeProvider.pushHistory(addressInput, lengthInput);
this.showMemoryContent(addressInput, lengthInput);
} else {
vscode.window.showErrorMessage('Invalid length entered');
}
},
(err) => {}
);
} else {
vscode.window.showErrorMessage('Invalid memory address entered');
}
},
(err) => {}
);
}
private showMemoryContent(address: string, length: string): void {
vscode.workspace
.openTextDocument(
vscode.Uri.parse(
`examinememory:///Memory%20[${address}+${length}].dbgmem?address=${address}&length=${length}&timestamp=${new Date().getTime()}`
)
)
.then(
(doc) => {
vscode.window.showTextDocument(doc, { viewColumn: 2, preview: false });
},
(error) => {
vscode.window.showErrorMessage(`Failed to examine memory: ${error}`);
}
);
}
private peripheralsUpdateNode(node: any): void {
node.node.performUpdate().then(
(result: boolean) => {
if (result) {
this.peripheralProvider.refresh();
}
},
(error: any) => {
vscode.window.showErrorMessage(`Unable to update value: ${error.toString()}`);
}
);
}
private peripheralsSelectedNode(node: any): void {
if (node.recordType !== PeripheralRecordType.Field) {
node.expanded = !node.expanded;
}
node.selected().then(
(result: boolean) => {
if (result) {
this.peripheralProvider.refresh();
}
},
(error: any) => {}
);
}
private peripheralsCopyValue(node: any): void {
const value = node.node.getCopyValue();
if (value) {
copyPaste.copy(value);
}
}
private async peripheralsSetFormat(node: any): Promise<void> {
const selected = await vscode.window.showQuickPick([
{ label: 'Auto', description: 'Automatically choose format (Inherits from parent)', value: NumberFormat.Auto },
{ label: 'Hex', description: 'Format value in hexidecimal', value: NumberFormat.Hexidecimal },
{ label: 'Decimal', description: 'Format value in decimal', value: NumberFormat.Decimal },
{ label: 'Binary', description: 'Format value in binary', value: NumberFormat.Binary },
]);
node.node.setFormat(selected.value);
this.peripheralProvider.refresh();
}
private registersSelectedNode(node: any): void {
if (node.recordType !== RegisterRecordType.Field) {
node.expanded = !node.expanded;
}
}
private registersCopyValue(node: any): void {
const value = node.node.getCopyValue();
if (value) {
copyPaste.copy(value);
}
}
private async registersSetFormat(node: any): Promise<void> {
const selected = await vscode.window.showQuickPick([
{ label: 'Auto', description: 'Automatically choose format (Inherits from parent)', value: NumberFormat.Auto },
{ label: 'Hex', description: 'Format value in hexidecimal', value: NumberFormat.Hexidecimal },
{ label: 'Decimal', description: 'Format value in decimal', value: NumberFormat.Decimal },
{ label: 'Binary', description: 'Format value in binary', value: NumberFormat.Binary },
]);
node.node.setFormat(selected.value);
this.registerProvider.refresh();
}
private debugSessionStarted(session: vscode.DebugSession): void {
if (session.type === 'platformio-debug') {
this.functionSymbols = null;
session.customRequest('get-arguments').then(
(args: any) => {
this.registerProvider.debugSessionStarted(
this.context.workspaceState.get('debugRegistersTreeState')
);
this.peripheralProvider.debugSessionStarted(
args.svdPath,
this.context.workspaceState.get('debugPeripheralsTreeState')
);
this.memoryTreeProvider.debugSessionStarted(
this.context.workspaceState.get('debugMemoryTreeState')
);
this.disassemblyTreeProvider.debugSessionStarted();
},
(error: any) => {
console.error(error);
}
);
}
}
private debugSessionTerminated(session: vscode.DebugSession): void {
if (session.type === 'platformio-debug') {
this.context.workspaceState.update(
'debugRegistersTreeState',
this.registerProvider.dumpSettings()
);
this.context.workspaceState.update(
'debugPeripheralsTreeState',
this.peripheralProvider.dumpSettings()
);
this.context.workspaceState.update(
'debugMemoryTreeState',
this.memoryTreeProvider.dumpSettings()
);
this.registerProvider.debugSessionTerminated();
this.peripheralProvider.debugSessionTerminated();
this.memoryTreeProvider.debugSessionTerminated();
this.disassemblyTreeProvider.debugSessionTerminated();
}
}
private receivedCustomEvent(e: vscode.DebugSessionCustomEvent): void {
if (!this.isPIODebugSession()) {
return;
}
switch (e.event) {
case 'custom-stop':
this.receivedStopEvent(e);
break;
case 'custom-continued':
this.receivedContinuedEvent(e);
break;
case 'adapter-output':
this.receivedAdapterOutput(e);
break;
case 'record-event':
this.receivedEvent(e);
break;
}
}
private receivedStopEvent(e: vscode.DebugSessionCustomEvent): void {
this.peripheralProvider.debugStopped();
this.registerProvider.debugStopped();
vscode.workspace.textDocuments
.filter((doc) => doc.fileName.endsWith('.dbgmem'))
.forEach((doc) => {
this.memoryContentProvider.update(doc);
});
}
private receivedContinuedEvent(e: vscode.DebugSessionCustomEvent): void {
this.peripheralProvider.debugContinued();
this.registerProvider.debugContinued();
}
private receivedEvent(e: vscode.DebugSessionCustomEvent): void {}
private receivedAdapterOutput(e: vscode.DebugSessionCustomEvent): void {
if (!this.adapterOutputChannel) {
this.adapterOutputChannel = vscode.window.createOutputChannel('Adapter Output');
}
let content: string = e.body.content;
if (!content.endsWith('\n')) {
content += '\n';
}
this.adapterOutputChannel.append(content);
}
}
export function activate(context: vscode.ExtensionContext): PlatformIODebugExtension {
return new PlatformIODebugExtension(context);
}
export function deactivate(): void {}
+14
View File
@@ -0,0 +1,14 @@
import * as vscode from 'vscode';
export class PlatformIODebugConfigurationProvider implements vscode.DebugConfigurationProvider {
constructor() {}
async resolveDebugConfiguration(
folder: vscode.WorkspaceFolder | undefined,
config: vscode.DebugConfiguration,
token?: vscode.CancellationToken
): Promise<vscode.DebugConfiguration> {
(config as any).cwd = folder ? folder.uri.fsPath : vscode.workspace.rootPath;
return config;
}
}
@@ -0,0 +1,33 @@
import * as vscode from 'vscode';
import { parseQuery } from '../utils';
export class DisassemblyContentProvider implements vscode.TextDocumentContentProvider {
provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Thenable<string> {
return new Promise((resolve, reject) => {
const params = parseQuery(uri.query);
vscode.debug.activeDebugSession
.customRequest('disassemble', { function: params.func, file: params.file })
.then(
(result: any) => {
const instructions = result.instructions;
let output = '';
instructions.forEach((instruction: any) => {
output += `${instruction.address}: ${this.padEnd(15, instruction.opcodes)} \t${instruction.instruction}\n`;
});
resolve(output);
},
(error: any) => {
vscode.window.showErrorMessage(error.message);
reject(error.message);
}
);
});
}
private padEnd(targetLength: number, str: string): string {
for (let i = str.length; i < targetLength; i++) {
str += ' ';
}
return str;
}
}
+50
View File
@@ -0,0 +1,50 @@
import * as vscode from 'vscode';
export class DisassemblyTreeProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
private _onDidChangeTreeData = new vscode.EventEmitter<vscode.TreeItem>();
public onDidChangeTreeData = this._onDidChangeTreeData.event;
private forced: boolean = false;
refresh(): void {
this._onDidChangeTreeData.fire();
}
getChildren(element?: vscode.TreeItem): vscode.TreeItem[] {
if (!vscode.debug.activeDebugSession) {
return [];
}
const disassembleItem = new vscode.TreeItem('Disassemble function');
disassembleItem.command = {
title: 'Disassemble function',
command: 'platformio-debug.viewDisassembly',
};
const switchLabel = 'Switch to ' + (this.forced ? 'code' : 'assembly');
const switchItem = new vscode.TreeItem(switchLabel);
switchItem.command = {
title: switchLabel,
command: 'platformio-debug.setForceDisassembly',
arguments: [this.forced ? 'Auto' : 'Forced'],
};
return [disassembleItem, switchItem];
}
getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
return element;
}
updateForcedState(forced: boolean): void {
this.forced = forced;
this.refresh();
}
debugSessionStarted(): void {
this.refresh();
}
debugSessionTerminated(): void {
this.updateForcedState(false);
}
}
+158
View File
@@ -0,0 +1,158 @@
import * as vscode from 'vscode';
import { hexFormat, parseQuery } from '../utils';
export class MemoryContentProvider implements vscode.TextDocumentContentProvider {
private _onDidChange = new vscode.EventEmitter<vscode.Uri>();
public onDidChange = this._onDidChange.event;
private firstBytePos = 10;
private lastBytePos = this.firstBytePos + 48 - 1;
private firstAsciiPos = this.lastBytePos + 3;
private lastAsciiPos = this.firstAsciiPos + 16;
private smallDecorationType = vscode.window.createTextEditorDecorationType({
borderWidth: '1px',
borderStyle: 'solid',
overviewRulerColor: 'blue',
overviewRulerLane: vscode.OverviewRulerLane.Right,
light: { borderColor: 'darkblue' },
dark: { borderColor: 'lightblue' },
});
provideTextDocumentContent(uri: vscode.Uri): Thenable<string> {
return new Promise((resolve, reject) => {
const params = parseQuery(uri.query);
const address = params.address.startsWith('0x')
? parseInt(params.address.substring(2), 16)
: parseInt(params.address, 10);
const length = params.length.startsWith('0x')
? parseInt(params.length.substring(2), 16)
: parseInt(params.length, 10);
vscode.debug.activeDebugSession
.customRequest('read-memory', { address, length: length || 32 })
.then(
(result: any) => {
const bytes: number[] = result.bytes;
let rowAddress = address - (address % 16);
const offset = address - rowAddress;
let output = '';
output += ' Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F \t\n';
output += hexFormat(rowAddress, 8, false) + ': ';
let asciiStr = '';
for (let i = 0; i < offset; i++) {
output += ' ';
asciiStr += ' ';
}
for (let i = 0; i < length; i++) {
const byte = bytes[i];
output += hexFormat(byte, 2, false).toUpperCase() + ' ';
asciiStr +=
byte <= 32 || (byte >= 127 && byte <= 159)
? '.'
: String.fromCharCode(bytes[i]);
if ((address + i) % 16 === 15 && i < length - 1) {
output += ' ' + asciiStr;
asciiStr = '';
output += '\n';
rowAddress += 16;
output += hexFormat(rowAddress, 8, false) + ': ';
}
}
const remaining = (16 - ((address + length) % 16)) % 16;
for (let i = 0; i < remaining; i++) {
output += ' ';
}
output += ' ' + asciiStr;
output += '\n';
resolve(output);
},
(error: any) => {
vscode.window.showErrorMessage(
`Unable to read memory from ${hexFormat(address, 8)} to ${hexFormat(address + length, 8)}`
);
reject(error.toString());
}
);
});
}
update(document: vscode.TextDocument): void {
this._onDidChange.fire(document.uri);
}
getOffset(position: vscode.Position): number | undefined {
if (position.line < 1 || position.character < this.firstBytePos) {
return;
}
let offset = 16 * (position.line - 1);
const charOffset = position.character - this.firstBytePos;
if (position.character >= this.firstBytePos && position.character <= this.lastBytePos) {
offset += Math.floor(charOffset / 3);
} else if (position.character >= this.firstAsciiPos) {
offset += position.character - this.firstAsciiPos;
}
return offset;
}
getPosition(offset: number, isAscii: boolean = false): vscode.Position {
const line = 1 + Math.floor(offset / 16);
let character = offset % 16;
if (isAscii) {
character += this.firstAsciiPos;
} else {
character = this.firstBytePos + 3 * character;
}
return new vscode.Position(line, character);
}
getRanges(startOffset: number, endOffset: number, isAscii: boolean): vscode.Range[] {
const startPos = this.getPosition(startOffset, isAscii);
let endPos = this.getPosition(endOffset, isAscii);
endPos = new vscode.Position(endPos.line, endPos.character + (isAscii ? 1 : 2));
const ranges: vscode.Range[] = [];
const startChar = isAscii ? this.firstAsciiPos : this.firstBytePos;
const endChar = isAscii ? this.lastAsciiPos : this.lastBytePos;
for (let line = startPos.line; line <= endPos.line; ++line) {
const lineStart = new vscode.Position(line, line === startPos.line ? startPos.character : startChar);
const lineEnd = new vscode.Position(line, line === endPos.line ? endPos.character : endChar);
ranges.push(new vscode.Range(lineStart, lineEnd));
}
return ranges;
}
handleSelection(event: vscode.TextEditorSelectionChangeEvent): void {
const lineCount = event.textEditor.document.lineCount;
if (
event.selections[0].start.line + 1 === lineCount ||
event.selections[0].end.line + 1 === lineCount
) {
event.textEditor.setDecorations(this.smallDecorationType, []);
return;
}
const startOffset = this.getOffset(event.selections[0].start);
const endOffset = this.getOffset(event.selections[0].end);
if (startOffset === undefined || endOffset === undefined) {
event.textEditor.setDecorations(this.smallDecorationType, []);
return;
}
let ranges = this.getRanges(startOffset, endOffset, false);
ranges = ranges.concat(this.getRanges(startOffset, endOffset, true));
event.textEditor.setDecorations(this.smallDecorationType, ranges);
}
}
+79
View File
@@ -0,0 +1,79 @@
import * as vscode from 'vscode';
export class MemoryTreeProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
private _onDidChangeTreeData = new vscode.EventEmitter<vscode.TreeItem>();
public onDidChangeTreeData = this._onDidChangeTreeData.event;
private history: string[] = [];
refresh(): void {
this._onDidChangeTreeData.fire();
}
dumpSettings(): string[] {
return this.history;
}
getChildren(element?: vscode.TreeItem): vscode.TreeItem[] {
if (!vscode.debug.activeDebugSession) {
return [];
}
if (this.history.length) {
return this.getHistoryNodes();
}
const item = new vscode.TreeItem('Enter address...');
item.command = {
title: 'Enter memory address...',
command: 'platformio-debug.examineMemory',
};
return [item];
}
private getHistoryNodes(): vscode.TreeItem[] {
return this.history.map((entry) => {
const item = new vscode.TreeItem(entry);
item.command = {
title: `Examine memory at ${entry}`,
command: 'platformio-debug.examineMemory',
arguments: entry.split('+'),
};
return item;
});
}
getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
return element;
}
pushHistory(address: string, length: string): void {
const entry = `${address}+${length}`;
if (!this.history.includes(entry)) {
this.history.push(entry);
this.refresh();
}
}
deleteHistory(address: string, length: string): void {
const entry = `${address}+${length}`;
if (this.history.includes(entry)) {
this.history = this.history.filter((e) => e !== entry);
this.refresh();
}
}
clearHistory(): void {
this.history = [];
this.refresh();
}
debugSessionStarted(savedState: string[]): void {
this.history = savedState || [];
this.refresh();
}
debugSessionTerminated(): void {
this.history = [];
this.refresh();
}
}
File diff suppressed because it is too large Load Diff
+360
View File
@@ -0,0 +1,360 @@
import * as vscode from 'vscode';
import { NumberFormat } from '../common';
import { hexFormat, binaryFormat, extractBits } from '../utils';
export enum RecordType {
Register = 0,
Field = 1,
}
export class TreeNode extends vscode.TreeItem {
constructor(
public label: string,
public collapsibleState: vscode.TreeItemCollapsibleState,
public contextValue: string,
public node: BaseNode
) {
super(label, collapsibleState);
this.command = {
command: 'platformio-debug.registers.selectedNode',
arguments: [node],
title: 'Selected Node',
};
}
}
export class BaseNode {
public format: NumberFormat = NumberFormat.Auto;
public expanded: boolean = false;
constructor(public recordType: RecordType) {}
getChildren(): BaseNode[] {
return [];
}
getTreeNode(): TreeNode {
return null;
}
getCopyValue(): string | null {
return null;
}
setFormat(format: NumberFormat): void {
this.format = format;
}
}
export class RegisterNode extends BaseNode {
public name: string;
public index: number;
public fields: FieldNode[];
public currentValue: number;
constructor(name: string, index: number) {
super(RecordType.Register);
this.name = name;
this.index = index;
if (name.toUpperCase() === 'XPSR' || name.toUpperCase() === 'CPSR') {
this.fields = [
new FieldNode('Negative Flag (N)', 31, 1, this),
new FieldNode('Zero Flag (Z)', 30, 1, this),
new FieldNode('Carry or borrow flag (C)', 29, 1, this),
new FieldNode('Overflow Flag (V)', 28, 1, this),
new FieldNode('Saturation Flag (Q)', 27, 1, this),
new FieldNode('GE', 16, 4, this),
new FieldNode('Interrupt Number', 0, 8, this),
new FieldNode('ICI/IT', 25, 2, this),
new FieldNode('ICI/IT', 10, 6, this),
new FieldNode('Thumb State (T)', 24, 1, this),
];
} else if (name.toUpperCase() === 'CONTROL') {
this.fields = [
new FieldNode('FPCA', 2, 1, this),
new FieldNode('SPSEL', 1, 1, this),
new FieldNode('nPRIV', 0, 1, this),
];
}
this.currentValue = 0;
}
extractBits(offset: number, width: number): number {
return extractBits(this.currentValue, offset, width);
}
getTreeNode(): TreeNode {
let label = `${this.name} = `;
switch (this.getFormat()) {
case NumberFormat.Decimal:
label += this.currentValue.toString();
break;
case NumberFormat.Binary:
label += binaryFormat(this.currentValue, 32, false, true);
break;
default:
label += hexFormat(this.currentValue, 8);
break;
}
if (this.fields && this.fields.length > 0) {
return new TreeNode(
label,
this.expanded ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed,
'register',
this
);
}
return new TreeNode(label, vscode.TreeItemCollapsibleState.None, 'register', this);
}
getChildren(): BaseNode[] {
return this.fields;
}
setValue(value: number): void {
this.currentValue = value;
}
getCopyValue(): string {
switch (this.getFormat()) {
case NumberFormat.Decimal:
return this.currentValue.toString();
case NumberFormat.Binary:
return binaryFormat(this.currentValue, 32);
default:
return hexFormat(this.currentValue, 8);
}
}
getFormat(): NumberFormat {
return this.format;
}
dumpSettings(): any[] {
const settings: any[] = [];
if (this.expanded || this.format !== NumberFormat.Auto) {
settings.push({
node: this.name,
format: this.format,
expanded: this.expanded,
});
}
if (this.fields) {
settings.push(
...this.fields.map((field) => field.dumpSettings()).filter((s) => s !== null)
);
}
return settings;
}
}
export class FieldNode extends BaseNode {
constructor(
public name: string,
public offset: number,
public size: number,
public register: RegisterNode
) {
super(RecordType.Field);
}
getTreeNode(): TreeNode {
const value = this.register.extractBits(this.offset, this.size);
let label = `${this.name} = `;
switch (this.getFormat()) {
case NumberFormat.Decimal:
label += value.toString();
break;
case NumberFormat.Binary:
label += binaryFormat(value, this.size, false, true);
break;
case NumberFormat.Hexidecimal:
label += hexFormat(value, Math.ceil(this.size / 4), true);
break;
default:
label +=
this.size >= 4
? hexFormat(value, Math.ceil(this.size / 4), true)
: binaryFormat(value, this.size, false, true);
break;
}
return new TreeNode(label, vscode.TreeItemCollapsibleState.None, 'field', this);
}
getCopyValue(): string {
const value = this.register.extractBits(this.offset, this.size);
switch (this.getFormat()) {
case NumberFormat.Decimal:
return value.toString();
case NumberFormat.Binary:
return binaryFormat(value, this.size);
case NumberFormat.Hexidecimal:
return hexFormat(value, Math.ceil(this.size / 4), true);
default:
return this.size >= 4
? hexFormat(value, Math.ceil(this.size / 4), true)
: binaryFormat(value, this.size);
}
}
getFormat(): NumberFormat {
return this.format === NumberFormat.Auto ? this.register.getFormat() : this.format;
}
dumpSettings(): any {
if (this.format !== NumberFormat.Auto) {
return { node: `${this.register.name}.${this.name}`, format: this.format };
}
return null;
}
}
export class RegisterTreeProvider implements vscode.TreeDataProvider<TreeNode> {
private _onDidChangeTreeData = new vscode.EventEmitter<TreeNode>();
public onDidChangeTreeData = this._onDidChangeTreeData.event;
private loaded: boolean = false;
private viewExpanded: boolean = false;
private registers: RegisterNode[] = [];
private registerMap: { [index: number]: RegisterNode } = {};
private initialSettings: any[];
refresh(): void {
this._onDidChangeTreeData.fire();
}
dumpSettings(): any[] {
const settings: any[] = [];
this.registers.forEach((reg) => {
settings.push(...reg.dumpSettings());
});
return settings;
}
fetchRegisterList(): void {
if (!vscode.debug.activeDebugSession) {
return;
}
if (this.loaded) {
this._fetchRegisterValues();
} else {
vscode.debug.activeDebugSession.customRequest('read-register-list').then((names: any) => {
this.loaded = true;
this.createRegisters(names);
this._fetchRegisterValues();
});
}
}
private _fetchRegisterValues(): void {
vscode.debug.activeDebugSession.customRequest('read-registers').then((registers: any) => {
registers.forEach((reg: any) => {
const index = parseInt(reg.number, 10);
const value = parseInt(reg.value, 16);
const registerNode = this.registerMap[index];
if (registerNode) {
registerNode.setValue(value);
}
});
this.refresh();
});
}
getTreeItem(element: TreeNode): TreeNode {
return element;
}
private createRegisters(names: string[]): void {
this.registerMap = {};
this.registers = [];
names.forEach((name, index) => {
if (name) {
const reg = new RegisterNode(name, index);
this.registers.push(reg);
this.registerMap[index] = reg;
}
});
if (this.initialSettings) {
this.initialSettings.forEach((setting) => {
if (setting.node.indexOf('.') === -1) {
const reg = this.registers.find((r) => r.name === setting.node);
if (reg) {
if (setting.expanded) {
reg.expanded = setting.expanded;
}
if (setting.format) {
reg.setFormat(setting.format);
}
}
} else {
const [regName, fieldName] = setting.node.split('.');
const reg = this.registers.find((r) => r.name === regName);
if (reg) {
const field = reg.getChildren().find((f: any) => f.name === fieldName);
if (field && setting.format) {
field.setFormat(setting.format);
}
}
}
});
}
this.refresh();
}
updateRegisterValues(values: any[]): void {
values.forEach((val) => {
this.registerMap[val.number].setValue(val.value);
});
this.refresh();
}
getChildren(element?: TreeNode): any[] {
this.viewExpanded = true;
if (!vscode.debug.activeDebugSession) {
return [];
}
if (this.registers.length > 0) {
if (element) {
return element.node.getChildren().map((child) => child.getTreeNode());
}
return this.registers.map((reg) => reg.getTreeNode());
}
if (!this.loaded) {
setTimeout(() => this.fetchRegisterList(), 1000);
}
return [new TreeNode('Loading...', vscode.TreeItemCollapsibleState.None, 'message', null)];
}
debugSessionTerminated(): void {
this.loaded = false;
this.registers = [];
this.registerMap = {};
this.refresh();
}
debugSessionStarted(savedState: any[]): void {
this.loaded = false;
this.registers = [];
this.registerMap = {};
this.initialSettings = savedState;
}
debugStopped(): void {
if (this.viewExpanded) {
this.fetchRegisterList();
}
}
debugContinued(): void {}
}
+63
View File
@@ -0,0 +1,63 @@
export function hexFormat(value: number, padding: number = 8, includePrefix: boolean = true): string {
let result = value.toString(16);
while (result.length < padding) {
result = '0' + result;
}
return includePrefix ? '0x' + result : result;
}
export function binaryFormat(
value: number,
padding: number = 0,
includePrefix: boolean = true,
groupByNibble: boolean = false
): string {
let result = (value >>> 0).toString(2);
while (result.length < padding) {
result = '0' + result;
}
if (groupByNibble) {
const extraZeros = 4 - (result.length % 4);
for (let i = 0; i < extraZeros; i++) {
result = '0' + result;
}
const groups = result.match(/[01]{4}/g);
result = groups.join(' ');
result = result.substring(extraZeros);
}
return includePrefix ? '0b' + result : result;
}
export function createMask(offset: number, width: number): number {
let mask = 0;
const end = offset + width - 1;
for (let i = offset; i <= end; i++) {
mask = (mask | (1 << i)) >>> 0;
}
return mask;
}
export function extractBits(value: number, offset: number, width: number): number {
return ((value & createMask(offset, width)) >>> offset) >>> 0;
}
export function parseQuery(queryString: string): { [key: string]: string } {
const params: { [key: string]: string } = {};
const pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&');
for (const pair of pairs) {
const parts = pair.split('=');
params[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1] || '');
}
return params;
}
export function encodeDisassembly(name: string, file: string): string {
let uri = 'disassembly:///';
if (file) {
uri += `${file}:`;
}
uri += `${name}.dbgasm?func=${name}&file=${file || ''}`;
return uri;
}
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": ["es6"],
"sourceMap": true,
"strict": false,
"rootDir": "src",
"skipLibCheck": true,
"esModuleInterop": true
},
"exclude": ["node_modules", "dist", ".vscode-test"]
}