aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-02-02 15:23:03 -0500
committerAustin Adams <git@austinjadams.com>2019-02-02 15:23:03 -0500
commit6575f9c05d90f4b0d87d4befd5c36eebeb0e9551 (patch)
tree96c0ada43c173667f5cd75e01fcb0e9422b2fe9a
parentd0b5dbe5be56d40ff34f99d2862f0da7c413983d (diff)
downloadnovice-6575f9c05d90f4b0d87d4befd5c36eebeb0e9551.tar.gz
novice-6575f9c05d90f4b0d87d4befd5c36eebeb0e9551.tar.xz
Avoid infinite loops by blowing up after 8192 executions
-rw-r--r--novice/cli.test.ts4
-rw-r--r--novice/cli.ts15
-rw-r--r--novice/simulator/cli-debugger.ts3
-rw-r--r--novice/simulator/debugger.test.ts46
-rw-r--r--novice/simulator/debugger.ts14
-rw-r--r--novice/simulator/simulator.test.ts16
-rw-r--r--novice/simulator/simulator.ts14
7 files changed, 98 insertions, 14 deletions
diff --git a/novice/cli.test.ts b/novice/cli.test.ts
index 8415c58..c243d24 100644
--- a/novice/cli.test.ts
+++ b/novice/cli.test.ts
@@ -410,7 +410,7 @@ describe('cli', () => {
// @ts-ignore
expect(StreamIO.mock.calls).toEqual([[mockStdin, stdout]]);
// @ts-ignore
- expect(Simulator.mock.calls).toEqual([[mockSimConfig.isa, mockIo]]);
+ expect(Simulator.mock.calls).toEqual([[mockSimConfig.isa, mockIo, 8192]]);
// @ts-ignore
expect(mockSim.run.mock.calls).toEqual([[]]);
});
@@ -436,7 +436,7 @@ describe('cli', () => {
// @ts-ignore
expect(StreamIO.mock.calls).toEqual([[mockStdin, stdout]]);
// @ts-ignore
- expect(Simulator.mock.calls).toEqual([[mockSimConfig.isa, mockIo]]);
+ expect(Simulator.mock.calls).toEqual([[mockSimConfig.isa, mockIo, 8192]]);
// @ts-ignore
expect(mockSim.run.mock.calls).toEqual([[]]);
});
diff --git a/novice/cli.ts b/novice/cli.ts
index 8b85def..3ace92c 100644
--- a/novice/cli.ts
+++ b/novice/cli.ts
@@ -42,6 +42,12 @@ async function main(argv: string[], stdin: Readable, stdout: Writable,
{ defaultValue: 'lc3',
help: 'simulator configuration to use. ' +
'default: %(defaultValue)s' });
+ simParser.addArgument(['-x', '--max-exec'],
+ { dest: 'maxExec',
+ type: 'int',
+ defaultValue: 1 << 13,
+ help: 'max instructions to execute. pass -1 for ' +
+ 'no limit. default: %(defaultValue)s' });
const dbgParser = sub.addParser('dbg', { description:
'interactively debug an object file'});
@@ -60,7 +66,7 @@ async function main(argv: string[], stdin: Readable, stdout: Writable,
case 'asm':
return await asm(args.config, args.file, args.outputFile, args.outputFormat, stdout, stderr);
case 'sim':
- return await sim(args.config, args.file, stdin, stdout, stderr);
+ return await sim(args.config, args.file, args.maxExec, stdin, stdout, stderr);
case 'dbg':
return await dbg(args.config, args.file, stdin, stdout, stderr);
case 'tablegen':
@@ -121,8 +127,9 @@ function hasObjectFileExt(path: string, cfg: SimulatorConfig) {
return path.endsWith('.' + cfg.loader.fileExt());
}
-async function sim(configName: string, path: string, stdin: Readable,
- stdout: Writable, stderr: Writable): Promise<number> {
+async function sim(configName: string, path: string, maxExec: number,
+ stdin: Readable, stdout: Writable, stderr: Writable):
+ Promise<number> {
try {
const cfg = getSimulatorConfig(configName);
@@ -135,7 +142,7 @@ async function sim(configName: string, path: string, stdin: Readable,
));
const io = new StreamIO(stdin, stdout);
- const simulator = new Simulator(cfg.isa, io);
+ const simulator = new Simulator(cfg.isa, io, maxExec);
const isObjectFile = hasObjectFileExt(path, cfg);
if (isObjectFile) {
diff --git a/novice/simulator/cli-debugger.ts b/novice/simulator/cli-debugger.ts
index 35124d2..b14bf34 100644
--- a/novice/simulator/cli-debugger.ts
+++ b/novice/simulator/cli-debugger.ts
@@ -56,8 +56,9 @@ class CliDebugger extends Debugger {
private commands: Command[];
public constructor(isa: Isa, stdin: Readable, stdout: Writable) {
+ const DEFAULT_MAX_EXEC = 1 << 13;
const io = new PromptIO();
- super(isa, io);
+ super(isa, io, DEFAULT_MAX_EXEC);
this.stdin = stdin;
this.stdout = stdout;
diff --git a/novice/simulator/debugger.test.ts b/novice/simulator/debugger.test.ts
index 2b5d992..3e60f54 100644
--- a/novice/simulator/debugger.test.ts
+++ b/novice/simulator/debugger.test.ts
@@ -15,7 +15,51 @@ describe('debugger', () => {
describe('lc-3 programs', () => {
beforeEach(() => {
- dbg = new Debugger(getIsa('lc3'), io);
+ dbg = new Debugger(getIsa('lc3'), io, 128);
+ });
+
+ describe('zeroed memory', () => {
+ beforeEach(() => {
+ // 1024 nops
+ for (let i = 0; i < (1 << 10); i++) {
+ dbg.store(0x3000 + i, 0x0000); // nop
+ }
+ });
+
+ it('runs 128 instructions for each continue', () => {
+ return expect(dbg.cont()).rejects.toThrow('infinite loop').then(() => {
+ expect(dbg.getPc()).toEqual(0x3080);
+ }).then(() => {
+ // This is a promise, so return it
+ return expect(dbg.cont()).rejects.toThrow('infinite loop');
+ }).then(() => {
+ // The crucial check: did continue go for another 128
+ // instructions?
+ expect(dbg.getPc()).toEqual(0x3100);
+ });
+ });
+
+ it('respects maxExec = -1', () => {
+ // set maxExec = -1 instead
+ dbg = new Debugger(getIsa('lc3'), io, -1);
+ dbg.store(0x3100, 0xf025); // halt 256 deep, much past the OG
+ // 128 max
+
+ return dbg.cont().then(() => {
+ expect(dbg.isHalted()).toBe(true);
+ expect(dbg.getPc()).toEqual(0x3101);
+ });
+ });
+ });
+
+ describe('infinite loop', () => {
+ beforeEach(() => {
+ dbg.store(0x3000, 0b0000111111111111); // brnzp -1
+ });
+
+ it('stops after 128 executions', () => {
+ return expect(dbg.cont()).rejects.toThrow('infinite loop');
+ });
});
describe('prints 3 bangs', () => {
diff --git a/novice/simulator/debugger.ts b/novice/simulator/debugger.ts
index dceb4b6..a8558c6 100644
--- a/novice/simulator/debugger.ts
+++ b/novice/simulator/debugger.ts
@@ -9,8 +9,8 @@ class Debugger extends Simulator {
protected interrupt: boolean;
protected symbTable: SymbTable;
- public constructor(isa: Isa, io: IO) {
- super(isa, io);
+ public constructor(isa: Isa, io: IO, maxExec: number) {
+ super(isa, io, maxExec);
this.nextBreakpoint = 0;
this.breakpoints = {};
@@ -28,9 +28,19 @@ class Debugger extends Simulator {
// This way, if you've stopped at a breakpoint and press
// "continue" it actually will
let first = true;
+ // Reset dynamic instruction count for each "continuation"
+ this.numExec = 0;
while ((first || !this.breakpoints.hasOwnProperty(this.pc))
&& !this.halted && !this.interrupt) {
+ if (this.maxExec >= 0 && this.numExec >= this.maxExec) {
+ throw new Error(`hit maximum executed instruction count ` +
+ `${this.maxExec}. this may indicate an ` +
+ `infinite loop in code. continuing will ` +
+ `continue execution for another ` +
+ `${this.maxExec} instructions.`);
+ }
+
first = false;
await this.step();
}
diff --git a/novice/simulator/simulator.test.ts b/novice/simulator/simulator.test.ts
index 1681415..1a292c1 100644
--- a/novice/simulator/simulator.test.ts
+++ b/novice/simulator/simulator.test.ts
@@ -12,7 +12,7 @@ describe('simulator', () => {
describe('lc-3 programs', () => {
beforeEach(() => {
- sim = new Simulator(getIsa('lc3'), io);
+ sim = new Simulator(getIsa('lc3'), io, 128);
});
describe('halt', () => {
@@ -54,6 +54,16 @@ describe('simulator', () => {
});
});
+ describe('infinite loop', () => {
+ beforeEach(() => {
+ sim.store(0x3000, 0b0000111111111111); // brnzp -1
+ });
+
+ it('run()', () => {
+ return expect(sim.run()).rejects.toThrow('infinite loop');
+ });
+ });
+
describe('adding two numbers', () => {
beforeEach(() => {
sim.store(0x3000, 0b0101001001100000); // and r1, r1, 0
@@ -900,7 +910,7 @@ describe('simulator', () => {
describe('lc-2200 programs', () => {
beforeEach(() => {
- sim = new Simulator(getIsa('lc2200'), io);
+ sim = new Simulator(getIsa('lc2200'), io, 128);
});
describe('halt', () => {
@@ -1254,7 +1264,7 @@ describe('simulator', () => {
describe('rama-2200 programs', () => {
beforeEach(() => {
- sim = new Simulator(getIsa('rama2200'), io);
+ sim = new Simulator(getIsa('rama2200'), io, 128);
});
describe('halt', () => {
diff --git a/novice/simulator/simulator.ts b/novice/simulator/simulator.ts
index 6b85499..b006841 100644
--- a/novice/simulator/simulator.ts
+++ b/novice/simulator/simulator.ts
@@ -12,11 +12,14 @@ class Simulator {
protected halted: boolean;
protected isa: Isa;
protected io: IO;
+ protected maxExec: number;
protected log: MachineStateLogEntry[];
+ protected numExec: number;
- public constructor(isa: Isa, io: IO) {
+ public constructor(isa: Isa, io: IO, maxExec: number) {
this.isa = isa;
this.io = io;
+ this.maxExec = maxExec;
this.pc = isa.pc.resetVector;
this.mem = {};
this.regs = {
@@ -25,6 +28,7 @@ class Simulator {
};
this.halted = false;
this.log = [];
+ this.numExec = 0;
this.resetRegs();
}
@@ -50,6 +54,8 @@ class Simulator {
return;
}
+ this.numExec++;
+
const ir = this.load(this.pc);
this.pc += this.isa.pc.increment;
@@ -97,6 +103,12 @@ class Simulator {
public async run(): Promise<void> {
while (!this.halted) {
+ if (this.maxExec >= 0 && this.numExec >= this.maxExec) {
+ throw new Error(`hit maximum executed instruction count ` +
+ `${this.maxExec}. this may indicate an ` +
+ `infinite loop in code`);
+ }
+
await this.step();
}
}