Skip to content

Commit

Permalink
RevDbg: conditional breakpoints implemented.
Browse files Browse the repository at this point in the history
  • Loading branch information
maziac committed Dec 28, 2019
1 parent 38b43d9 commit 6eb3229
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## 0.9.5
- Reverse debugging: Conditional breakpoints.
- Fixed moving of PC to cursor during reverse debugging.
- Changed reverse debug decoration to blue background only due to performance reasons.

Expand Down
13 changes: 13 additions & 0 deletions documentation/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,19 @@ But please note: The history stores only the register values and stack contents.
So whenever a memory location is changed from the program code in reverse debugging this will not be reflected in e.g. the memory view.
You can only rely on the register values.

#### Breakpoints in Reverse Debug Mode

You can also use breakpoints during reverse debugging.
The normal (program counter related) breakpoints work just as you would expect.

It is also possible to add a condition. I.e. you can additionally test for certain register values.

But please note that during reverse debugging the memory contents is not evaluated. A breakpoint that checks for memory is not evaluated correctly. In such a case the breakpoint will fire always.
E.g. a condition like this
~~~
b@(HL) == 0
~~~
will be evaluated to true always so that you don't miss such a breakpoint.


### Code Coverage
Expand Down
13 changes: 11 additions & 2 deletions src/emulatorfactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import * as assert from 'assert';
import { ZesaruxEmulator } from './zesaruxemulator';
import { ZesaruxExtEmulator } from './zesaruxextemulator';
import { EmulatorClass } from './emulator';


/// Different machine emulators.
Expand All @@ -24,10 +25,10 @@ export class EmulatorFactory {
public static createEmulator(emul: EmulatorType) {
switch(emul) {
case EmulatorType.ZESARUX:
Emulator = new ZesaruxEmulator();
EmulatorFactory.setEmulator(new ZesaruxEmulator());
break;
case EmulatorType.ZESARUX_EXT: // Zesarux with own extensions.
Emulator = new ZesaruxExtEmulator();
EmulatorFactory.setEmulator(new ZesaruxExtEmulator());
break;
case EmulatorType.MAME:
assert(false); // needs to be implemented
Expand All @@ -38,6 +39,14 @@ export class EmulatorFactory {
}
}


/**
* Sets the emulator variable.
*/
protected static setEmulator(emulator: EmulatorClass) {
Emulator = emulator;
}

}


Expand Down
4 changes: 2 additions & 2 deletions src/emuldebugadapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1208,9 +1208,9 @@ export class EmulDebugSessionClass extends DebugSession {
// Send event
this.sendEvent(new StoppedEvent('step', EmulDebugSessionClass.THREAD_ID));

// Show a possible error
// Output a possibly error
if(error)
this.showError(error);
vscode.debug.activeDebugConsole.appendLine(error);
});
}

Expand Down
82 changes: 81 additions & 1 deletion src/tests/utility.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import * as assert from 'assert';
import { Utility } from '../utility';
import { Z80Registers } from '../z80Registers';
import { EmulatorClass } from '../emulator';
import { Emulator, EmulatorType, EmulatorFactory } from '../emulatorfactory';
//import { EmulatorClass } from '../emulator';
import { Settings } from '../settings';


Expand Down Expand Up @@ -308,6 +308,86 @@ suite('Utility', () => {
assert.equal(0x0F>>3, res, "Wrong eval result");
});



suite('breakpoints', () => {
setup(() => {
(<any>EmulatorFactory).setEmulator(new EmulatorClass());
});

test('simple', () => {
(<any>Emulator).RegisterCache = "";
let res = Utility.evalExpression('0x1234 == 0x1234', true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression('0x1235 == 0x1234', true);
assert.equal(0, res, "Wrong eval result");
});

test('register SP', () => {
(<any>Emulator).RegisterCache = "PC=80d3 SP=83fb AF=3f08 BC=0000 HL=4000 DE=2000 IX=ffff IY=5c3a AF'=0044 BC'=0001 HL'=f3f3 DE'=0001 I=00 R=0d IM0 IFF12 (PC)=3e020603 (SP)=80f5";
let res = Utility.evalExpression('SP == 0x83FB', true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression('0x83FB == SP', true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression('SP == 0x83FA', true);
assert.equal(0, res, "Wrong eval result");

res = Utility.evalExpression('0x83FB != SP', true);
assert.equal(0, res, "Wrong eval result");
});

test('All registers', () => {
(<any>Emulator).RegisterCache = "PC=80d3 SP=83fb AF=3f08 BC=1234 HL=5678 DE=9abc IX=fedc IY=5c3a AF'=0143 BC'=2345 HL'=f4f3 DE'=89ab I=ab R=0d IM0 IFF12 (PC)=3e020603 (SP)=80f5";

let res = Utility.evalExpression('PC == 0x80D3', true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression('AF == 3F08h', true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression('BC == 0x1234', true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression('DE == 9ABCh', true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression('HL == 5678h', true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression('IX == 0xFEDC', true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression('IY == 0x5C3A', true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression("AF' == 0143h", true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression("BC' == 0x2345", true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression("DE' == 89ABh", true);
assert.equal(1, res, "Wrong eval result");

res = Utility.evalExpression("HL' == F4F3h", true);
assert.equal(1, res, "Wrong eval result");
});


test('memory (exception)', () => {
(<any>Emulator).RegisterCache = "PC=80d3 SP=83fb AF=3f08 BC=1234 HL=5678 DE=9abc IX=fedc IY=5c3a AF'=0143 BC'=2345 HL'=f4f3 DE'=89ab I=ab R=0d IM0 IFF12 (PC)=3e020603 (SP)=80f5";

// It is not supported to retrieve memory locations.
// Therefore a test is done on an exception.
assert.throws( () => {
Utility.evalExpression('b@(1000) == 50', true);
}, "Expected an exception");
});
});

});

});
12 changes: 9 additions & 3 deletions src/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,16 +194,17 @@ export class Utility {
* Examples:
* 2-5*3 => -13, -Dh
* LBL_TEST+1 => 32769, 8001h
* HL' != 1111h
* @param expr The expression to evaluate. May contain math expressions and labels.
* Also evaluates numbers in formats like '$4000', '2FACh', 100111b, 'G'.
* @param evalRegisters If true then register names will also be evaluated.
* @param modulePrefix An optional prefix to use for each label. (sjasmplus)
* @param lastLabel An optional last label to use for local labels. (sjasmplus)
* @returns The evaluated number.
* @returns The evaluated number. (If a boolean expression is evaluated a 1 is returned for true and a 0 for false)
* @throws SyntaxError if 'eval' throws an error or if the label is not found.
*/
public static evalExpression(expr: string, evalRegisters = true, modulePrefix?:string, lastLabel?: string): number {
const exprLabelled = expr.replace(/([\$][0-9a-fA-F]+|[a-fA-F0-9]+h|[0-9]+\S+|0x[a-fA-F0-9]+|[a-zA-Z_\.][a-zA-Z0-9_\.]*|'[\S ]+')/g, (match, p1) => {
const exprLabelled = expr.replace(/([\$][0-9a-fA-F]+|[a-fA-F0-9]+h|[0-9]+\S+|0x[a-fA-F0-9]+|[a-zA-Z_\.][a-zA-Z0-9_\.]*'?|'[\S ]+')/g, (match, p1) => {
let res;
if(evalRegisters) {
// Check if it might be a register name.
Expand Down Expand Up @@ -239,7 +240,12 @@ export class Utility {
});
// Evaluate
const result = eval(exprLabelled);
// return

// Check if boolean
if(typeof(result) == 'boolean')
return (result) ? 1 : 0;

// Return normal number
return result;
}

Expand Down
82 changes: 66 additions & 16 deletions src/zesaruxemulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,11 +662,10 @@ export class ZesaruxEmulator extends EmulatorClass {
this.handleReverseDebugStackForward(currentLine, nextLine);

// Check for breakpoint
const bp = this.checkPcBreakpoints(nextLine);
if(bp) {
reason = 'Breakpoint hit at PC='+Utility.getHexString(bp.address,4);
if(bp.condition)
reason += ', condition: ' + bp.condition;
this.RegisterCache = nextLine;
const condition = this.checkPcBreakpoints(nextLine);
if(condition != undefined) {
reason = condition;
break; // BP hit and condition met.
}

Expand Down Expand Up @@ -1137,11 +1136,10 @@ export class ZesaruxEmulator extends EmulatorClass {
await this.handleReverseDebugStackBack(currentLine, prevLine);

// Check for breakpoint
const bp = this.checkPcBreakpoints(currentLine);
if(bp) {
reason = 'Breakpoint hit at PC='+Utility.getHexString(bp.address,4);
if(bp.condition)
reason += ', condition: ' + bp.condition;
this.RegisterCache = currentLine;
const condition = this.checkPcBreakpoints(currentLine);
if(condition != undefined) {
reason = condition;
break; // BP hit and condition met.
}

Expand Down Expand Up @@ -1201,8 +1199,10 @@ export class ZesaruxEmulator extends EmulatorClass {
while(true) {
// Get next line
nextLine = this.revDbgNext();
if(!nextLine)
if(!nextLine) {
errorText = 'Break: Reached start of instruction history.'
break; // At end of reverse debugging. Simply get the real call stack.
}

// Handle reverse stack
this.handleReverseDebugStackForward(currentLine, nextLine);
Expand All @@ -1217,6 +1217,14 @@ export class ZesaruxEmulator extends EmulatorClass {
if(pc == nextPC0 || pc == nextPC1)
break;

// Check for "real" breakpoint
this.RegisterCache = nextLine;
const condition = this.checkPcBreakpoints(nextLine);
if(condition != undefined) {
errorText = condition;
break; // BP hit and condition met.
}

// Next
currentLine = nextLine as string;
}
Expand Down Expand Up @@ -1453,8 +1461,10 @@ export class ZesaruxEmulator extends EmulatorClass {
while(true) {
// Get next line
nextLine = this.revDbgNext();
if(!nextLine)
if(!nextLine) {
errorText = 'Break: Reached start of instruction history.';
break; // At end of reverse debugging. Simply get the real call stack.
}

// Handle reverse stack
this.handleReverseDebugStackForward(currentLine, nextLine);
Expand All @@ -1471,6 +1481,14 @@ export class ZesaruxEmulator extends EmulatorClass {
}
}

// Check for breakpoint
this.RegisterCache = nextLine;
const condition = this.checkPcBreakpoints(nextLine);
if(condition != undefined) {
errorText = condition;
break; // BP hit and condition met.
}

// Next
currentLine = nextLine as string;
}
Expand Down Expand Up @@ -1907,14 +1925,46 @@ export class ZesaruxEmulator extends EmulatorClass {
* Returns the breakpoint at the given address.
* Note: Checks only breakpoints with a set 'address'.
* @param regs The registers as string, e.g. "PC=0039 SP=ff44 AF=005c BC=ffff HL=10a8 DE=5cb9 IX=ffff IY=5c3a AF'=0044 BC'=174b HL'=107f DE'=0006 I=3f R=06 IM1 IFF-- (PC)=e52a785c (SP)=a2bf"
* @returns A string with the reason. undefined if no breakpoint hit.
*/
protected checkPcBreakpoints(regs: string): EmulatorBreakpoint|undefined {
protected checkPcBreakpoints(regs: string): string|undefined {
assert(this.RegisterCache);
let condition;
const pc = Z80Registers.parsePC(regs);
for(const bp of this.breakpoints) {
if(bp.address == pc)
return bp;
if(bp.address == pc) {
// Check for condition
if(!bp.condition) {
condition = "";
break;
}

// Evaluate condition
try {
const result = Utility.evalExpression(bp.condition, true);
if(result != 0) {
condition = bp.condition;
break;
}
}
catch(e) {
// A problem during evaluation happened,
// e.g. a memory location has been tested which is not possible
// during reverse debugging.
condition = "Could not evaluate: " + bp.condition;
break;
}
}
}

// Text
let reason;
if(condition != undefined) {
reason = 'Breakpoint hit at PC=' + Utility.getHexString(pc,4);
if(condition != "")
reason += ', ' + condition;
}
return undefined;
return reason;
}


Expand Down

0 comments on commit 6eb3229

Please sign in to comment.