32 Star 22 Fork 15

openKylin/native-debug

加入 Gitee
与超过 1400万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
mibase.ts 46.41 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029
import * as DebugAdapter 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 { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError, OurInstructionBreakpoint } from './backend/backend';
import { MINode } from './backend/mi_parse';
import { expandValue, isExpandable } from './backend/gdb_expansion';
import { MI2 } from './backend/mi2/mi2';
import { execSync } from 'child_process';
import { posix } from "path";
import * as systemPath from "path";
import * as net from "net";
import * as os from "os";
import * as fs from "fs";
import { hexFormat } from './backend/common';
import logger from './backend/log';
export type InferiorTerminal = 'integrated' | 'external' | '';
class ExtendedVariable {
constructor(public name, public options) {
}
}
export enum RunCommand { CONTINUE, RUN, NONE }
const STACK_HANDLES_START = 1000;
const VAR_HANDLES_START = 512 * 256 + 1000;
const VARIABLES_TAG_REGISTER = 0xab;
class VariableScope {
constructor(public readonly name: string, public readonly threadId: number, public readonly level: number) {
}
public static variableName(handle: number, name: string): string {
return `var_${handle}_${name}`;
}
}
export class MI2DebugSession extends DebugSession {
protected variableHandles = new Handles<VariableScope | string | VariableObject | ExtendedVariable>();
protected variableHandlesReverse: { [id: string]: number } = {};
protected scopeHandlesReverse: { [key: string]: number } = {};
protected useVarObjects: boolean;
protected quit: boolean;
protected attached: boolean;
protected initialRunCommand: RunCommand;
protected stopAtEntry: boolean | string;
protected isSSH: boolean;
protected sourceFileMap: Map<string, string>;
protected started: boolean;
protected crashed: boolean;
protected miDebugger: MI2;
protected commandServer: net.Server;
protected serverPath: string;
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
super(debuggerLinesStartAt1, isServer);
}
protected async initDebugger() {
this.miDebugger.on("launcherror", this.launchError.bind(this));
this.miDebugger.on("quit", this.quitEvent.bind(this));
this.miDebugger.on("exited-normally", this.quitEvent.bind(this));
this.miDebugger.on("stopped", this.stopEvent.bind(this));
this.miDebugger.on("msg", this.handleMsg.bind(this));
this.miDebugger.on("breakpoint", this.handleBreakpoint.bind(this));
this.miDebugger.on("watchpoint", this.handleBreak.bind(this)); // consider to parse old/new, too (otherwise it is in the console only)
this.miDebugger.on("step-end", this.handleBreak.bind(this));
// this.miDebugger.on("step-out-end", this.handleBreak.bind(this)); // was combined into step-end
this.miDebugger.on("step-other", this.handleBreak.bind(this));
this.miDebugger.on("signal-stop", this.handlePause.bind(this));
this.miDebugger.on("thread-created", this.threadCreatedEvent.bind(this));
this.miDebugger.on("thread-exited", this.threadExitedEvent.bind(this));
this.miDebugger.once("debug-ready", (() => this.sendEvent(new InitializedEvent())));
try {
const socketlists = systemPath.join(os.tmpdir(), "code-debug-sockets");
if (fs.existsSync(socketlists)) {
await cleanInvalidSocketPath(socketlists);
}
this.commandServer = net.createServer(c => {
c.on("data", data => {
const rawCmd = data.toString();
const spaceIndex = rawCmd.indexOf(" ");
let func = rawCmd;
let args = [];
if (spaceIndex != -1) {
func = rawCmd.substr(0, spaceIndex);
args = JSON.parse(rawCmd.substr(spaceIndex + 1));
}
Promise.resolve(this.miDebugger[func].apply(this.miDebugger, args)).then(data => {
c.write(data instanceof Object ? JSON.stringify(data).toString() : data.toString());
});
});
});
this.commandServer.on("error", err => {
if (process.platform != "win32")
this.handleMsg("stderr", "Code-Debug WARNING: Utility Command Server: Error in command socket " + err.toString() + "\nCode-Debug WARNING: The examine memory location command won't work");
});
if (!fs.existsSync(systemPath.join(os.tmpdir(), "code-debug-sockets")))
fs.mkdirSync(systemPath.join(os.tmpdir(), "code-debug-sockets"));
this.commandServer.listen(this.serverPath = systemPath.join(os.tmpdir(), "code-debug-sockets", ("Debug-Instance-" + Math.floor(Math.random() * 36 * 36 * 36 * 36).toString(36)).toLowerCase()));
} catch (e) {
if (process.platform != "win32")
this.handleMsg("stderr", "Code-Debug WARNING: Utility Command Server: Failed to start " + e.toString() + "\nCode-Debug WARNING: The examine memory location command won't work");
}
}
public get_miDebugger(): MI2 {
return this.miDebugger;
}
protected checkCommand(debuggerName: string): boolean {
try {
if (process.platform === 'win32' && debuggerName.includes("\\")) {
const command = 'dir';
execSync(`${command} ${debuggerName}`, { stdio: 'ignore' });
return true;
}
else {
const command = process.platform === 'win32' ? 'where' : 'command -v';
execSync(`${command} ${debuggerName}`, { stdio: 'ignore' });
logger.debug(`Found debugger ${debuggerName}`);
return true;
}
} catch (error) {
return false;
}
}
protected setValuesFormattingMode(mode: ValuesFormattingMode) {
switch (mode) {
case "disabled":
this.useVarObjects = true;
this.miDebugger.prettyPrint = false;
break;
case "prettyPrinters":
this.useVarObjects = true;
this.miDebugger.prettyPrint = true;
break;
case "parseText":
default:
this.useVarObjects = false;
this.miDebugger.prettyPrint = false;
}
}
public handleMsg(type: string, msg: string) {
if (type == "target")
type = "stdout";
if (type == "log")
type = "stderr";
this.sendEvent(new OutputEvent(msg, type));
}
protected handleBreakpoint(info: MINode) {
const event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id")));
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all";
this.sendEvent(event);
}
protected handleBreak(info?: MINode) {
const event = new StoppedEvent("step", info ? parseInt(info.record("thread-id")) : 1);
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info ? info.record("stopped-threads") == "all" : true;
this.sendEvent(event);
}
protected handlePause(info: MINode) {
const event = new StoppedEvent("user request", parseInt(info.record("thread-id")));
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all";
this.sendEvent(event);
}
protected stopEvent(info: MINode) {
if (!this.started)
this.crashed = true;
if (!this.quit) {
const event = new StoppedEvent("exception", parseInt(info.record("thread-id")));
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all";
this.sendEvent(event);
}
}
protected threadCreatedEvent(info: MINode) {
this.sendEvent(new ThreadEvent("started", info.record("id")));
}
protected threadExitedEvent(info: MINode) {
this.sendEvent(new ThreadEvent("exited", info.record("id")));
}
protected quitEvent() {
this.quit = true;
this.sendEvent(new TerminatedEvent());
if (this.serverPath)
fs.unlink(this.serverPath, (err) => {
console.error("Failed to unlink debug server");
});
}
protected launchError(err: any) {
this.handleMsg("stderr", "Could not start debugger process, does the program exist in filesystem?\n");
this.handleMsg("stderr", err.toString() + "\n");
this.quitEvent();
}
protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void {
if (this.attached)
this.miDebugger.detach();
else
this.miDebugger.stop();
this.commandServer.close();
this.commandServer = undefined;
this.sendResponse(response);
}
protected async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise<void> {
try {
if (this.useVarObjects) {
let name = args.name;
const parent = this.variableHandles.get(args.variablesReference);
if (parent instanceof VariableScope) {
name = VariableScope.variableName(args.variablesReference, name);
} else if (parent instanceof VariableObject) {
name = `${parent.name}.${name}`;
}
const res = await this.miDebugger.varAssign(name, args.value);
response.body = {
value: res.result("value")
};
} else {
/* 字符串不修改 */
if (args.value[0] == "\"") {
this.sendErrorResponse(response, 20, "Could not modify string variable");
return;
}
let name = args.name;
let id;
if (args.variablesReference >= VAR_HANDLES_START) {
const pointerCombineChar = ".";
id = this.variableHandles.get(args.variablesReference);
if (id === "arr") {
name = id[0] == '*' ? id.substr(1) : id + args.name;
}
else if (typeof id == "string") {
name = id[0] == '*' ? id.substr(1) : id + pointerCombineChar + args.name;
}
}
/* 修复寄存器无法修改 */
else if (args.variablesReference == VARIABLES_TAG_REGISTER) {
name = `$${args.name}`;
}
await this.miDebugger.changeVariable(name, args.value);
response.body = {
value: args.value
};
}
this.sendResponse(response);
} catch (err) {
this.sendErrorResponse(response, 11, `Could not continue: ${err}`);
}
}
protected setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void {
const all = [];
args.breakpoints.forEach(brk => {
all.push(this.miDebugger.addBreakPoint({ raw: brk.name, condition: brk.condition, countCondition: brk.hitCondition }));
});
Promise.all(all).then(brkpoints => {
const finalBrks = [];
brkpoints.forEach(brkp => {
if (brkp[0])
finalBrks.push({ line: brkp[1].line });
});
response.body = {
breakpoints: finalBrks
};
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 10, msg.toString());
});
}
protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {
this.miDebugger.clearBreakPoints(args.source.path).then(() => {
let path = args.source.path;
if (this.isSSH) {
// convert local path to ssh path
if (path.indexOf("\\") != -1)
path = path.replace(/\\/g, "/").toLowerCase();
// ideCWD is the local path, gdbCWD is the ssh path, both had the replacing \ -> / done up-front
for (const [ideCWD, gdbCWD] of this.sourceFileMap) {
if (path.startsWith(ideCWD)) {
path = posix.relative(ideCWD, path);
path = posix.join(gdbCWD, path); // we combined a guaranteed path with relative one (works with GDB both on GNU/Linux and Win32)
break;
}
}
}
const all = args.breakpoints.map(brk => {
return this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition, logMessage: brk.logMessage });
});
Promise.all(all).then(brkpoints => {
const finalBrks = [];
brkpoints.forEach(brkp => {
// TODO: Currently all breakpoints returned are marked as verified,
// which leads to verified breakpoints on a broken lldb.
const bp: any = new DebugAdapter.Breakpoint(brkp[0], brkp[1].line);
bp.id = brkp[1].id;
bp.message = brkp[1].message?.toString();
finalBrks.push(bp);
});
response.body = {
breakpoints: finalBrks
};
this.handleMsg("stdout", `response: ${JSON.stringify(response)}\n`);
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 9, msg.toString());
});
}, msg => {
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 {
if (!this.miDebugger) {
this.sendResponse(response);
return;
}
this.miDebugger.getThreads().then(threads => {
response.body = {
threads: []
};
for (const thread of threads) {
const threadName = thread.name || thread.targetId || "<unnamed>";
response.body.threads.push(new Thread(thread.id, thread.id + ":" + threadName));
}
this.sendResponse(response);
}).catch(error => {
this.sendErrorResponse(response, 17, `Could not get threads: ${error}`);
});
}
// Supports 65535 threads.
protected threadAndLevelToFrameId(threadId: number, level: number) {
return level << 16 | threadId;
}
protected frameIdToThreadAndLevel(frameId: number) {
return [frameId & 0xffff, frameId >> 16];
}
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
this.miDebugger.getStack(args.startFrame, args.levels, args.threadId).then(stack => {
const ret: StackFrame[] = [];
stack.forEach(element => {
let source = undefined;
let path = element.file;
if (path) {
if (this.isSSH) {
// convert ssh path to local path
if (path.indexOf("\\") != -1)
path = path.replace(/\\/g, "/").toLowerCase();
// ideCWD is the local path, gdbCWD is the ssh path, both had the replacing \ -> / done up-front
for (const [ideCWD, gdbCWD] of this.sourceFileMap) {
if (path.startsWith(gdbCWD)) {
path = posix.relative(gdbCWD, path); // only operates on "/" paths
path = systemPath.resolve(ideCWD, path); // will do the conversion to "\" on Win32
break;
}
}
} else if (process.platform === "win32") {
if (path.startsWith("\\cygdrive\\") || path.startsWith("/cygdrive/")) {
path = path[10] + ":" + path.substr(11); // replaces /cygdrive/c/foo/bar.txt with c:/foo/bar.txt
}
}
source = new Source(element.fileName, path);
}
const tmpStackFrame = new StackFrame(
this.threadAndLevelToFrameId(args.threadId, element.level),
element.function + "@" + element.address,
source,
element.line,
0);
tmpStackFrame.instructionPointerReference = element.address
ret.push(tmpStackFrame);
});
response.body = {
stackFrames: ret
};
this.sendResponse(response);
}, err => {
this.sendErrorResponse(response, 12, `Failed to get Stack Trace: ${err.toString()}`);
});
}
protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void {
const promises: Thenable<any>[] = [];
let entryPoint: string | undefined = undefined;
let runToStart: boolean = false;
// Setup temporary breakpoint for the entry point if needed.
switch (this.initialRunCommand) {
case RunCommand.CONTINUE:
case RunCommand.NONE:
if (typeof this.stopAtEntry == 'boolean' && this.stopAtEntry)
entryPoint = "main"; // sensible default
else if (typeof this.stopAtEntry == 'string')
entryPoint = this.stopAtEntry;
break;
case RunCommand.RUN:
if (typeof this.stopAtEntry == 'boolean' && this.stopAtEntry) {
if (this.miDebugger.features.includes("exec-run-start-option"))
runToStart = true;
else
entryPoint = "main"; // sensible fallback
} else if (typeof this.stopAtEntry == 'string')
entryPoint = this.stopAtEntry;
break;
default:
throw new Error('Unhandled run command: ' + RunCommand[this.initialRunCommand]);
}
if (entryPoint)
promises.push(this.miDebugger.setEntryBreakPoint(entryPoint));
switch (this.initialRunCommand) {
case RunCommand.CONTINUE:
promises.push(this.miDebugger.continue().then(() => {
// Some debuggers will provide an out-of-band status that they are stopped
// when attaching (e.g., gdb), so the client assumes we are stopped and gets
// confused if we start running again on our own.
//
// If we don't send this event, the client may start requesting data (such as
// stack frames, local variables, etc.) since they believe the target is
// stopped. Furthermore the client may not be indicating the proper status
// to the user (may indicate stopped when the target is actually running).
this.sendEvent(new ContinuedEvent(1, true));
}));
break;
case RunCommand.RUN:
promises.push(this.miDebugger.start(runToStart).then(() => {
this.started = true;
if (this.crashed)
this.handlePause(undefined);
}));
break;
case RunCommand.NONE:
// Not all debuggers seem to provide an out-of-band status that they are stopped
// when attaching (e.g., lldb), so the client assumes we are running and gets
// confused when we don't actually run or continue. Therefore, we'll force a
// stopped event to be sent to the client (just in case) to synchronize the state.
const event: DebugProtocol.StoppedEvent = new StoppedEvent("pause", 1);
event.body.description = "paused on attach";
event.body.allThreadsStopped = true;
this.sendEvent(event);
break;
default:
throw new Error('Unhandled run command: ' + RunCommand[this.initialRunCommand]);
}
Promise.all(promises).then(() => {
this.sendResponse(response);
}).catch(err => {
this.sendErrorResponse(response, 18, `Could not run/continue: ${err.toString()}`);
});
}
protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
const scopes = new Array<Scope>();
const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId);
const createScope = (scopeName: string, expensive: boolean): Scope => {
const key: string = scopeName + ":" + threadId + ":" + level;
let handle: number;
if (this.scopeHandlesReverse.hasOwnProperty(key)) {
handle = this.scopeHandlesReverse[key];
} else {
handle = this.variableHandles.create(new VariableScope(scopeName, threadId, level));
this.scopeHandlesReverse[key] = handle;
}
return new Scope(scopeName, handle, expensive);
};
scopes.push(createScope("Locals", false));
scopes.push(createScope("Registers", false));
response.body = {
scopes: scopes
};
this.sendResponse(response);
}
protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise<void> {
const variables: DebugProtocol.Variable[] = [];
let id: VariableScope | string | VariableObject | ExtendedVariable = this.variableHandles.get(args.variablesReference);
if (typeof id == "string" && id.includes("static ")) {
id = id.replace("static ", "");
}
const createVariable = (arg: string | VariableObject, options?: any) => {
if (options)
return this.variableHandles.create(new ExtendedVariable(typeof arg === 'string' ? arg : arg.name, options));
else
return this.variableHandles.create(arg);
};
const findOrCreateVariable = (varObj: VariableObject): number => {
let id: number;
if (this.variableHandlesReverse.hasOwnProperty(varObj.name)) {
id = this.variableHandlesReverse[varObj.name];
} else {
id = createVariable(varObj);
this.variableHandlesReverse[varObj.name] = id;
}
return varObj.isCompound() ? id : 0;
};
logger.debug(`variablesRequest: typeof id:${typeof id}`);
if (id instanceof VariableScope) {
try {
if (id.name == "Registers") {
const registers = await this.miDebugger.getRegisters();
for (const reg of registers) {
variables.push({
name: reg.name,
value: reg.valueStr,
variablesReference: 0
});
}
} else {
const stack: Variable[] = await this.miDebugger.getStackVariables(id.threadId, id.level);
for (const variable of stack) {
if (this.useVarObjects) {
try {
const varObjName = VariableScope.variableName(args.variablesReference, variable.name);
let varObj: VariableObject;
try {
const changes = await this.miDebugger.varUpdate(varObjName);
const changelist = changes.result("changelist");
changelist.forEach((change: any) => {
const name = MINode.valueOf(change, "name");
const vId = this.variableHandlesReverse[name];
const v = this.variableHandles.get(vId) as any;
v.applyChanges(change);
});
const varId = this.variableHandlesReverse[varObjName];
varObj = this.variableHandles.get(varId) as any;
} catch (err) {
if (err instanceof MIError && (err.message == "Variable object not found" || err.message.endsWith("does not exist"))) {
varObj = await this.miDebugger.varCreate(id.threadId, id.level, variable.name, varObjName);
const varId = findOrCreateVariable(varObj);
varObj.exp = variable.name;
varObj.id = varId;
} else {
throw err;
}
}
variables.push(varObj.toProtocolVariable());
} catch (err) {
variables.push({
name: variable.name,
value: `<${err}>`,
variablesReference: 0
});
}
} else {
if (variable.valueStr !== undefined) {
let expanded = expandValue(createVariable, `{${variable.name}=${variable.valueStr}}`, "", variable.raw);
if (expanded) {
if (typeof expanded[0] == "string")
expanded = [
{
name: "<value>",
value: prettyStringArray(expanded),
variablesReference: 0
}
];
variables.push(expanded[0]);
}
} else
variables.push({
name: variable.name,
type: variable.type,
value: variable.type,
variablesReference: createVariable(variable.name)
});
}
}
}
response.body = {
variables: variables
};
this.sendResponse(response);
} catch (err) {
this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`);
}
} else if (typeof id == "string") {
// Variable members
let variable;
try {
// TODO: this evals on an (effectively) unknown thread for multithreaded programs.
variable = await this.miDebugger.evalExpression(JSON.stringify(id), 0, 0);
try {
let variableValue = variable.result("value");
// 过滤掉 "\\000 <repeats x times>" 部分
logger.debug(`variablesRequest: variableValue:${variableValue}`);
const pattern = /'([^']*)' <repeats (\d+) times>/g;
variableValue = variableValue.replace(pattern, (_, char: string, count: string) => {
const repeatCount = parseInt(count, 10) + 1;
const repeatedArray = Array(repeatCount).fill(char);
return `{${repeatedArray.map(item => `'${item}'`).join(', ')}}`;
});
logger.debug(`variablesRequest: variableValue1:${variableValue}`);
// variableValue = variableValue.replace(/ '\\000' <repeats \d+ times>,/g, "");
let expanded = expandValue(createVariable, variableValue, id, variable);
if (!expanded) {
this.sendResponse(response);
//this.sendErrorResponse(response, 2, `Could not expand variable`);
} else {
if (typeof expanded[0] == "string")
expanded = [
{
name: "<value>",
value: prettyStringArray(expanded),
variablesReference: 0
}
];
response.body = {
variables: expanded
};
this.sendResponse(response);
}
} catch (e) {
this.sendResponse(response);
//this.sendErrorResponse(response, 2, `Could not expand variable3: ${e}`);
}
} catch (err) {
this.sendResponse(response);
// this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`);
}
} else if (typeof id == "object") {
if (id instanceof VariableObject) {
// Variable members
let children: VariableObject[];
try {
children = await this.miDebugger.varListChildren(id.name);
const vars = children.map(child => {
const varId = findOrCreateVariable(child);
child.id = varId;
return child.toProtocolVariable();
});
response.body = {
variables: vars
};
this.sendResponse(response);
} catch (err) {
this.sendResponse(response);
//this.sendErrorResponse(response, 1, `Could not expand variable4: ${err}`);
}
} else if (id instanceof ExtendedVariable) {
const varReq = id;
if (varReq.options.arg) {
const strArr = [];
let argsPart = true;
let arrIndex = 0;
const submit = () => {
response.body = {
variables: strArr
};
this.sendResponse(response);
};
const addOne = async () => {
// TODO: this evals on an (effectively) unknown thread for multithreaded programs.
const variable = await this.miDebugger.evalExpression(JSON.stringify(`${varReq.name}+${arrIndex})`), 0, 0);
try {
let root = varReq.name;
const t = varReq.name.indexOf('(');//如果变量名称带 ( 说明它是一个参数
const name: string = varReq.name;
if (t) {
root = name.substring(0, t) + name.substring(t + 1);
}
const expanded = expandValue(createVariable, variable.result("value"), root, variable);
if (!expanded) {
this.sendResponse(response);
//this.sendErrorResponse(response, 15, `Could not expand variable`);
} else {
if (typeof expanded == "string") {
if (expanded == "<nullptr>") {
if (argsPart)
argsPart = false;
else
return submit();
} else if (expanded[0] != '"') {
strArr.push({
name: "value",
value: expanded,
variablesReference: 0
});
return submit();
}
strArr.push({
name: `[${(arrIndex++)}]`,
value: expanded,
variablesReference: 0
});
addOne();
} else {
expanded.forEach(element => {
let evaluateName;
if (this.variableHandlesReverse.hasOwnProperty(element.name)) {
evaluateName = this.variableHandlesReverse[element.name];
}
strArr.push({
name: element.name,
evaluateName: element.evaluateName,
value: element.value,
variablesReference: element.variablesReference
});
});
submit();
}
}
} catch (e) {
this.sendResponse(response);
//this.sendErrorResponse(response, 14, `Could not expand variable5: ${e}`);
}
};
addOne();
} else
this.sendErrorResponse(response, 13, `Unimplemented variable request options: ${JSON.stringify(varReq.options)}`);
} else {
response.body = {
variables: id
};
this.sendResponse(response);
}
} else {
response.body = {
variables: variables
};
this.sendResponse(response);
}
}
protected pauseRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
this.miDebugger.interrupt().then(done => {
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 3, `Could not pause: ${msg}`);
});
}
protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void {
this.miDebugger.continue(true).then(done => {
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 2, `Could not continue: ${msg}`);
});
}
protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
this.miDebugger.continue().then(done => {
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 2, `Could not continue: ${msg}`);
});
}
protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
this.miDebugger.step(true, args.granularity === 'instruction').then(done => {
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 4, `Could not step back: ${msg} - Try running 'target record-full' before stepping back`);
});
}
protected stepInRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
this.miDebugger.step(false, args.granularity === 'instruction').then(done => {
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 4, `Could not step in: ${msg}`);
});
}
protected stepOutRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
this.miDebugger.stepOut().then(done => {
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 5, `Could not step out: ${msg}`);
});
}
protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
this.miDebugger.next().then(done => {
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 6, `Could not step over: ${msg}`);
});
}
protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId);
if (args.context == "watch" || args.context == "hover") {
/* 悬停查看变量时不报错 */
this.miDebugger.evalExpression(args.expression, threadId, level, args.context == "hover").then((res) => {
response.body = {
variablesReference: 0,
result: args.expression[0] == '$' ? hexFormat(parseInt(res.result("value"))) : res.result("value")
};
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 7, msg.toString());
});
} else {
this.miDebugger.sendUserInput(args.expression, threadId, level).then(output => {
if (typeof output == "undefined")
response.body = {
result: "",
variablesReference: 0
};
else
response.body = {
result: JSON.stringify(output),
variablesReference: 0
};
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 8, msg.toString());
});
}
}
protected gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void {
this.miDebugger.goto(args.source.path, args.line).then(done => {
response.body = {
targets: [{
id: 1,
label: args.source.name,
column: args.column,
line: args.line
}]
};
this.sendResponse(response);
}, msg => {
this.sendErrorResponse(response, 16, `Could not jump: ${msg}`);
});
}
protected gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void {
this.sendResponse(response);
}
private addSourceFileMapEntry(gdbCWD: string, ideCWD: string): void {
// if it looks like a Win32 path convert to "/"-style for comparisions and to all-lower-case
if (ideCWD.indexOf("\\") != -1)
ideCWD = ideCWD.replace(/\\/g, "/").toLowerCase();
if (!ideCWD.endsWith("/"))
ideCWD = ideCWD + "/"
// ensure that we only replace complete paths
if (gdbCWD.indexOf("\\") != -1)
gdbCWD = gdbCWD.replace(/\\/g, "/").toLowerCase();
if (!gdbCWD.endsWith("/"))
gdbCWD = gdbCWD + "/"
this.sourceFileMap.set(ideCWD, gdbCWD);
}
protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB: string, fallbackIDE: string): void {
this.sourceFileMap = new Map<string, string>();
if (configMap === undefined) {
this.addSourceFileMapEntry(fallbackGDB, fallbackIDE);
} else {
for (const [gdbPath, localPath] of Object.entries(configMap)) {
this.addSourceFileMapEntry(gdbPath, localPath);
}
}
}
public async createIntegratedTerminalLinux(args: DebugProtocol.RunInTerminalRequestArguments) {
const mkdirAsync = fs.promises.mkdir;
const mkdtempAsync = async (tempDir: string, prefix: string): Promise<string> => {
const name = `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1e9)}`;
const newDirPath = `${tempDir}/${name}`;
try {
await mkdirAsync(newDirPath, { recursive: true });
return newDirPath;
} catch (err) {
throw new Error(`Error creating temp directory: ${err.message}`);
}
};
const ttyTmpDir = await mkdtempAsync(os.tmpdir(), 'debug');
(async () => {
try {
fs.writeFileSync(
`${ttyTmpDir}/get-tty`,
`#!/usr/bin/env sh
clear
echo "The input and output of program will be here."
echo "Warning of set controlling terminal fail can be ignored."
tty > ${ttyTmpDir}/ttynameTmp
mv ${ttyTmpDir}/ttynameTmp ${ttyTmpDir}/ttyname
# wait for debug to finish
# prefer using tail to detect PID exit, but that requires GNU tail
tail -f --pid=${process.pid} /dev/null 2>/dev/null || while kill -s 0 ${process.pid} 2>/dev/null; do sleep 1s; done
# cleanup
rm ${ttyTmpDir}/ttyname
rm ${ttyTmpDir}/get-tty
rmdir ${ttyTmpDir}
`
);
let watcher: fs.FSWatcher | undefined;
const ttyNamePromise = new Promise<string>((resolve) => {
watcher = fs.watch(ttyTmpDir, (_eventType, filename) => {
if (filename === 'ttyname') {
watcher?.close();
resolve(
fs.readFileSync(`${ttyTmpDir}/ttyname`).toString().trim()
);
}
});
});
args.args = ['/bin/sh', `${ttyTmpDir}/get-tty`];
const response = await new Promise<DebugProtocol.Response>(
(resolve) =>
this.sendRequest(
'runInTerminal',
args,
3000,
resolve
)
);
if (response.success) {
const tty = await ttyNamePromise;
this.miDebugger.emit("debug-ready");
await this.miDebugger.sendCommand(`inferior-tty-set ${tty}`);
return;
} else {
watcher?.close();
const message = `could not start the terminal on the client: ${response.message}`;
throw new Error(message);
}
} catch (err) {
console.error(err);
}
})();
}
}
function prettyStringArray(strings) {
if (typeof strings == "object") {
if (strings.length !== undefined)
return strings.join(", ");
else
return JSON.stringify(strings);
} else return strings;
}
async function cleanInvalidSocketPath(socketlists: string) {
return new Promise((resolve, reject) => {
fs.readdir(socketlists, (err, files) => {
if (!err) {
if (files.length == 0) resolve('');
files.forEach((file) => {
try {
const conn = net.connect(systemPath.join(socketlists, file));
conn.setTimeout(200);
conn.on('error', () => {
fs.unlink(systemPath.join(socketlists, file), (err) => {
if (err)
console.error("Failed to unlink invalid debug server");
resolve('');
});
});
}
catch {
fs.unlink(systemPath.join(socketlists, file), (err) => {
if (err)
console.error("Failed to unlink invalid debug server");
resolve('');
});
}
})
}
resolve('');
});
});
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/openkylin/native-debug.git
git@gitee.com:openkylin/native-debug.git
openkylin
native-debug
native-debug
main

搜索帮助