1、解决在函数中点击单步跳出时提示出现异常问题

2、解决在main函数for循环中点击单步跳出卡死问题
This commit is contained in:
xuhong 2023-11-22 15:07:53 +08:00
parent f4bca2266c
commit 3169fd6f9f
1 changed files with 162 additions and 132 deletions

View File

@ -19,26 +19,26 @@ const gdbMatch = /(?:\d*|undefined)\(gdb\)/;
const numRegex = /\d+/;
function isPositiveInteger(str: string): boolean {
const regex = /^[1-9]\d*$/;
return regex.test(str);
const regex = /^[1-9]\d*$/;
return regex.test(str);
}
export interface ReadMemResults {
startAddress: string;
endAddress: string;
data: string;
startAddress: string;
endAddress: string;
data: string;
}
export function parseReadMemResults(node: MINode): ReadMemResults {
const startAddress = node.resultRecords.results[0][1][0][0][1];
const endAddress = node.resultRecords.results[0][1][0][2][1];
const data = node.resultRecords.results[0][1][0][3][1];
const ret: ReadMemResults = {
startAddress: startAddress,
endAddress: endAddress,
data: data
};
return ret;
const startAddress = node.resultRecords.results[0][1][0][0][1];
const endAddress = node.resultRecords.results[0][1][0][2][1];
const data = node.resultRecords.results[0][1][0][3][1];
const ret: ReadMemResults = {
startAddress: startAddress,
endAddress: endAddress,
data: data
};
return ret;
}
function couldBeOutput(line: string) {
@ -173,7 +173,7 @@ export class MI2 extends EventEmitter implements IBackend {
if (args.bootstrap) sshCMD = args.bootstrap + " && " + sshCMD;
this.sshConn.exec(sshCMD, execArgs, (err, stream) => {
if (err) {
this.log("stderr", "Could not run " + this.application + "(" + sshCMD +") over ssh!");
this.log("stderr", "Could not run " + this.application + "(" + sshCMD + ") over ssh!");
if (err === undefined) {
err = "<reason unknown>"
}
@ -337,7 +337,7 @@ export class MI2 extends EventEmitter implements IBackend {
}
onOutputStderr(lines) {
lines = <string[]> lines.split('\n');
lines = <string[]>lines.split('\n');
lines.forEach(line => {
this.log("stderr", line);
});
@ -352,7 +352,7 @@ export class MI2 extends EventEmitter implements IBackend {
}
onOutput(lines) {
lines = <string[]> lines.split('\n');
lines = <string[]>lines.split('\n');
lines.forEach(line => {
if (couldBeOutput(line)) {
if (!gdbMatch.exec(line))
@ -362,11 +362,11 @@ export class MI2 extends EventEmitter implements IBackend {
if (this.debugOutput)
this.log("log", "GDB -> App: " + JSON.stringify(parsed));
if (parsed.token !== undefined) {
if (this.needOutput[parsed.token] !== undefined) {
parsed.output = this.needOutput[parsed.token];
}
this.nextTokenComing = parsed.token + 1;
}
if (this.needOutput[parsed.token] !== undefined) {
parsed.output = this.needOutput[parsed.token];
}
this.nextTokenComing = parsed.token + 1;
}
let handled = false;
if (parsed.token !== undefined && parsed.resultRecords) {
if (this.handlers[parsed.token]) {
@ -376,16 +376,16 @@ export class MI2 extends EventEmitter implements IBackend {
}
}
if (!handled && parsed.resultRecords && parsed.resultRecords.resultClass == "error") {
// this.log("stderr", parsed.result("msg") || line);
// this.log("stderr", parsed.result("msg") || line);
}
if (parsed.outOfBandRecord) {
parsed.outOfBandRecord.forEach(record => {
if (record.isStream) {
if ((record.type === 'console') && (this.needOutput[this.nextTokenComing] !== undefined)) {
this.needOutput[this.nextTokenComing] += record.content;
} else {
this.log(record.type, record.content);
}
if ((record.type === 'console') && (this.needOutput[this.nextTokenComing] !== undefined)) {
this.needOutput[this.nextTokenComing] += record.content;
} else {
this.log(record.type, record.content);
}
} else {
if (record.type == "exec") {
this.emit("exec-async-output", parsed);
@ -405,55 +405,78 @@ export class MI2 extends EventEmitter implements IBackend {
} else {
if (trace)
this.log("stderr", "stop: " + reason);
switch (reason) {
case "breakpoint-hit":
this.emit("breakpoint", parsed);
break;
case "watchpoint-trigger":
case "read-watchpoint-trigger":
case "access-watchpoint-trigger":
this.emit("watchpoint", parsed);
break;
case "function-finished":
// identical result -> send step-end
// this.emit("step-out-end", parsed);
// break;
case "location-reached":
case "end-stepping-range":
this.emit("step-end", parsed);
break;
case "watchpoint-scope":
case "solib-event":
case "syscall-entry":
case "syscall-return":
// TODO: inform the user
this.emit("step-end", parsed);
break;
case "fork":
case "vfork":
case "exec":
// TODO: inform the user, possibly add second inferiour
this.emit("step-end", parsed);
break;
case "signal-received":
this.emit("signal-stop", parsed);
break;
case "exited-normally":
this.emit("exited-normally", parsed);
break;
case "exited": // exit with error code != 0
this.log("stderr", "Program exited with code " + parsed.record("exit-code"));
this.emit("exited-normally", parsed);
break;
// case "exited-signalled": // consider handling that explicit possible
// this.log("stderr", "Program exited because of signal " + parsed.record("signal"));
// this.emit("stoped", parsed);
// break;
default:
const fnSendMsg = (reason) =>{
switch (reason) {
case typeof reason == "object":
{
this.log("console", "Not implemented stop reason (assuming exception): " + reason + `${reason[0]}` + "<");
this.emit("stopped", parsed);
break;
}
case "function-finished,breakpoint-hit":
case "breakpoint-hit,function-finished":
case "breakpoint-hit":
this.emit("breakpoint", parsed);
break;
case "watchpoint-trigger":
case "read-watchpoint-trigger":
case "access-watchpoint-trigger":
this.emit("watchpoint", parsed);
break;
case "function-finished":
// identical result -> send step-end
// this.emit("step-out-end", parsed);
// break;
case "location-reached":
case "end-stepping-range":
this.emit("step-end", parsed);
break;
case "watchpoint-scope":
case "solib-event":
case "syscall-entry":
case "syscall-return":
// TODO: inform the user
this.emit("step-end", parsed);
break;
case "fork":
case "vfork":
case "exec":
// TODO: inform the user, possibly add second inferiour
this.emit("step-end", parsed);
break;
case "signal-received":
this.emit("signal-stop", parsed);
break;
case "exited-normally":
this.emit("exited-normally", parsed);
break;
case "exited": // exit with error code != 0
this.log("stderr", "Program exited with code " + parsed.record("exit-code"));
this.emit("exited-normally", parsed);
break;
// case "exited-signalled": // consider handling that explicit possible
// this.log("stderr", "Program exited because of signal " + parsed.record("signal"));
// this.emit("stoped", parsed);
// break;
default:
this.log("console", "Not implemented stop reason (assuming exception): " + reason + ` ${reason[0]}` + "<");
this.emit("stopped", parsed);
break;
}
}
if (typeof reason == "object") {
reason.forEach(item=>{
fnSendMsg(item);
})
}
else if (typeof reason == "string") {
fnSendMsg(reason);
}
else {
this.log("console", "Not implemented stop reason (assuming exception): " + reason);
this.emit("stopped", parsed);
break;
}
}
} else
@ -550,7 +573,7 @@ export class MI2 extends EventEmitter implements IBackend {
if (trace)
this.log("stderr", "next");
return new Promise((resolve, reject) => {
this.sendCommand((instruction ? 'exec-next-instruction' :'exec-next') + (reverse ? " --reverse" : "")).then((info) => {
this.sendCommand((instruction ? 'exec-next-instruction' : 'exec-next') + (reverse ? " --reverse" : "")).then((info) => {
resolve(info.resultRecords.resultClass == "running");
}, reject);
});
@ -571,6 +594,7 @@ export class MI2 extends EventEmitter implements IBackend {
this.log("stderr", "stepOut");
return new Promise((resolve, reject) => {
this.sendCommand("exec-finish" + (reverse ? " --reverse" : "")).then((info) => {
this.log("stderr", `WARNING: stepOutxh++++ '${JSON.stringify(info)}'`);
resolve(info.resultRecords.resultClass == "running");
}, reject);
});
@ -614,7 +638,7 @@ export class MI2 extends EventEmitter implements IBackend {
setLogPoint(bkptNum, command): Thenable<any> {
if (trace)
this.log("stderr", "setLogoPoint");
command = "printf \"log msg:%d\\n\", i" ;
command = "printf \"log msg:%d\\n\", i";
return this.sendCommand("break-commands " + bkptNum + " " + command);
}
@ -633,10 +657,10 @@ export class MI2 extends EventEmitter implements IBackend {
if (breakpoint.countCondition[0] == ">")
location += "-i " + numRegex.exec(breakpoint.countCondition.substr(1))[0] + " ";
else {
if(!isPositiveInteger(breakpoint.countCondition)) {
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");
resolve([false, undefined]);
return;
if (!isPositiveInteger(breakpoint.countCondition)) {
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");
resolve([false, undefined]);
return;
}
const match = numRegex.exec(breakpoint.countCondition)[0];
if (match.length != breakpoint.countCondition.length) {
@ -651,9 +675,9 @@ export class MI2 extends EventEmitter implements IBackend {
location += '"' + escape(breakpoint.raw) + '"';
else
location += '"' + escape(breakpoint.file) + ":" + breakpoint.line + '"';
if(breakpoint.logMessage){
if (breakpoint.logMessage) {
//do send logmsg command;
this.log("stderr", "logmsg:"+breakpoint.logMessage);
this.log("stderr", "logmsg:" + breakpoint.logMessage);
// -break-commands 1 "printf \"log msg:%d\\n\", i" "continue"
//this.sendCommand(" -break-commands 1 "printf \"log msg:%d\\n\", i" "continue" ")
}
@ -667,8 +691,8 @@ export class MI2 extends EventEmitter implements IBackend {
raw: breakpoint.raw,
line: parseInt(result.result("bkpt.line")),
condition: breakpoint.condition,
message:undefined,
verified:undefined
message: undefined,
verified: undefined
};
if (breakpoint.condition) {
this.setBreakPointCondition(bkptNum, breakpoint.condition).then((result) => {
@ -678,12 +702,12 @@ export class MI2 extends EventEmitter implements IBackend {
} else {
resolve([false, undefined]);
}
}, (msg)=>{
}, (msg) => {
newBrk.message = msg;
resolve([false, newBrk]);
});
}
else if(breakpoint.logMessage) {
}
else if (breakpoint.logMessage) {
this.setLogPoint(bkptNum, breakpoint.logMessage).then((result) => {
if (result.resultRecords.resultClass == "done") {
this.breakpoints.set(newBrk, bkptNum);
@ -704,30 +728,30 @@ export class MI2 extends EventEmitter implements IBackend {
});
}
addInstrBreakPoint(breakpoint: OurInstructionBreakpoint): Promise<OurInstructionBreakpoint> {
if (trace) {
this.log('stderr', 'addInstrBreakPoint');
}
return new Promise((resolve, reject) => {
let bkptArgs = '';
if (breakpoint.condition) {
bkptArgs += `-c "${breakpoint.condition}" `;
}
addInstrBreakPoint(breakpoint: OurInstructionBreakpoint): Promise<OurInstructionBreakpoint> {
if (trace) {
this.log('stderr', 'addInstrBreakPoint');
}
return new Promise((resolve, reject) => {
let bkptArgs = '';
if (breakpoint.condition) {
bkptArgs += `-c "${breakpoint.condition}" `;
}
bkptArgs += '*' + hexFormat(breakpoint.address);
bkptArgs += '*' + hexFormat(breakpoint.address);
this.sendCommand(`break-insert ${bkptArgs}`).then((result) => {
if (result.resultRecords.resultClass === 'done') {
const bkptNum = parseInt(result.result('bkpt.number'));
breakpoint.number = bkptNum;
resolve(breakpoint);
}
else {
reject(new MIError(result.result('msg') || 'Internal error', `Setting breakpoint at ${bkptArgs}`));
}
}, reject);
});
}
this.sendCommand(`break-insert ${bkptArgs}`).then((result) => {
if (result.resultRecords.resultClass === 'done') {
const bkptNum = parseInt(result.result('bkpt.number'));
breakpoint.number = bkptNum;
resolve(breakpoint);
}
else {
reject(new MIError(result.result('msg') || 'Internal error', `Setting breakpoint at ${bkptArgs}`));
}
}, reject);
});
}
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean> {
if (trace)
@ -790,9 +814,9 @@ export class MI2 extends EventEmitter implements IBackend {
const options: string[] = [];
if (thread != 0){
if (thread != 0) {
options.push("--thread " + thread);
if (this.status == 'stopped'){
if (this.status == 'stopped') {
this.currentThreadId = thread;
}
}
@ -840,13 +864,13 @@ export class MI2 extends EventEmitter implements IBackend {
async getRegisters(): Promise<any[]> {
if (!this.regNames || this.regNames.length == 0){
if (!this.regNames || this.regNames.length == 0) {
const regNameResult = await this.sendCommand(`data-list-register-names`);
this.regNames = regNameResult.result("register-names");
if (!this.regNames|| this.regNames.length == 0) return [];
if (!this.regNames || this.regNames.length == 0) return [];
}
if (this.status == 'stopped' && this.currentThreadId){
if (this.status == 'stopped' && this.currentThreadId) {
await this.sendCommand(`thread-select ${this.currentThreadId}`);
}
@ -866,7 +890,7 @@ export class MI2 extends EventEmitter implements IBackend {
const type = MINode.valueOf(reg, "type");
ret.push({
name: name,
evaluateName: '$'+name, //修复寄存器无法watch
evaluateName: '$' + name, //修复寄存器无法watch
value: value,
type: type,
variablesReference: 0
@ -881,8 +905,8 @@ export class MI2 extends EventEmitter implements IBackend {
if (trace)
this.log("stderr", "getStackVariables");
if (!thread){
if (this.status == 'stopped'){
if (!thread) {
if (this.status == 'stopped') {
this.currentThreadId = thread;
}
}
@ -920,7 +944,7 @@ export class MI2 extends EventEmitter implements IBackend {
return new Promise((resolve, reject) => {
if (this.status != 'stopped') {
resolve(this.isRecord ? {result:'0', oldValue:'1'} : { result:'0', oldValue:'0'});
resolve(this.isRecord ? { result: '0', oldValue: '1' } : { result: '0', oldValue: '0' });
this.log("stderr", `WARNING: The program is ${this.status} and cannot ${this.isRecord ? 'stop' : 'start'} reverse debugging.`);
return;
}
@ -928,11 +952,11 @@ export class MI2 extends EventEmitter implements IBackend {
this.isRecord = !this.isRecord;
this.sendCliCommand(this.isRecord ? "record" : "record stop").then((result) => {
if (result.resultRecords.resultClass == "done") {
resolve(this.isRecord ? {result:'1', oldValue:'0'} : { result:'1', oldValue:'1'});
resolve(this.isRecord ? { result: '1', oldValue: '0' } : { result: '1', oldValue: '1' });
}
else {
this.isRecord = !this.isRecord; //restore
resolve(this.isRecord ? {result:'0', oldValue:'1'} : { result:'0', oldValue:'0'});
resolve(this.isRecord ? { result: '0', oldValue: '1' } : { result: '0', oldValue: '0' });
}
}, reject);
});
@ -944,7 +968,7 @@ export class MI2 extends EventEmitter implements IBackend {
let command = "data-evaluate-expression ";
if (thread != 0) {
command += `--thread ${thread} --frame ${frame} `;
if (this.status == 'stopped'){
if (this.status == 'stopped') {
this.currentThreadId = thread;
}
}
@ -1015,8 +1039,7 @@ export class MI2 extends EventEmitter implements IBackend {
this.log("log", raw);
if (this.isSSH)
this.stream.write(raw + "\n");
else
{
else {
this.process.stdin.write(raw + "\n");
}
}
@ -1038,12 +1061,19 @@ export class MI2 extends EventEmitter implements IBackend {
delete this.needOutput[sel];
if (node && node.resultRecords && node.resultRecords.resultClass === "error") {
if (!suppressFailure) {
//this.log("stderr", `WARNING: Error executing command '${command}'`);
resolve(node);
} else
this.log("stderr", `WARNING: Error executing command '${command}'`);
if(command.includes("exec-finish") && node.resultRecords.results[0][1].includes("not meaningful in the outermost frame")){
node.resultRecords.resultClass = "running";
node.resultRecords.results = [];
}
// resolve(node);
} else {
this.log("stderr", `WARNING: reject '${command}'`);
reject(new MIError(node.result("msg") || "Internal error", command));
} else
}
} else {
resolve(node);
}
};
this.sendRaw(sel + "-" + command);
});
@ -1058,15 +1088,15 @@ export class MI2 extends EventEmitter implements IBackend {
return /^-|[^\w\d\/_\-\.]/g.test(text) ? ('"' + escape(text) + '"') : text;
}
isRecord:boolean = false;
currentThreadId:number = 0;
isRecord: boolean = false;
currentThreadId: number = 0;
status: 'running' | 'stopped' | 'none' = 'none';
cpuArch:string = '';
cpuArch: string = '';
prettyPrint: boolean = true;
printCalls: boolean;
debugOutput: boolean;
features: string[];
regNames:any[] = [];
regNames: any[] = [];
public procEnv: any;
protected isSSH: boolean;
protected sshReady: boolean;