aboutsummaryrefslogtreecommitdiffgithub
path: root/novice/simulator/debugger.ts
blob: ab79b483dd91ac55fcf63bf640f37194b97aa469 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import { Fields, getRegAliases, InstructionSpec, IO, Isa,
         SymbTable } from '../isa';
import { maskTo, maxUnsignedVal, sextTo } from '../util';
import { Simulator } from './simulator';

class Debugger extends Simulator {
    protected nextBreakpoint: number;
    // Map of address -> breakpoint number
    protected breakpoints: {[addr: number]: number};
    protected interrupt: boolean;
    protected symbTable: SymbTable;

    public constructor(isa: Isa, io: IO, maxExec: number) {
        super(isa, io, maxExec);

        this.nextBreakpoint = 0;
        this.breakpoints = {};
        this.interrupt = false;
        this.symbTable = {};
    }

    public getSymbTable(): SymbTable {
        return this.symbTable;
    }

    // continue
    public async cont(): Promise<void> {
        // Ignore breakpoints the first time through the loop
        // 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();
        }

        this.interrupt = false;
    }

    public addBreakpoint(addr: number): void {
        this.validateAddr(addr);

        if (this.breakpoints.hasOwnProperty(addr)) {
            throw new Error(`address 0x${addr.toString(16)} is already a ` +
                            `breakpoint`);
        }

        this.breakpoints[addr] = this.nextBreakpoint++;
    }

    public disassembleAt(pc: number): string|null {
        return this.disassemble(pc, this.load(pc));
    }

    public disassemble(pc: number, ir: number): string|null {
        let spec: InstructionSpec|null;
        let fields: Fields|null;

        try {
            [spec, fields] = this.decode(ir);
        } catch (err) {
            spec = fields = null;
        }

        if (!spec || !fields) {
            return null;
        }

        return this.reassemble(pc, spec, fields);
    }

    public reassemble(pc: number, spec: InstructionSpec, fields: Fields): string {
        const operands: string[] = [];

        for (const field of spec.fields) {
            switch (field.kind) {
                case 'const':
                    // Not provided in assembly code, so skip
                    break;

                case 'imm':
                    let labels: string[] = [];
                    if (field.label) {
                        const targetPc = pc + this.isa.pc.increment +
                                         fields.imms[field.name];
                        labels = this.labelsForAddr(targetPc);
                    }

                    if (labels.length > 0) {
                        operands.push(labels[0]);
                    } else {
                        operands.push(fields.imms[field.name].toString());
                    }
                    break;

                case 'reg':
                    const regid = fields.regs[field.name];
                    const str = (typeof regid === 'string') ? regid :
                                regid[0] + (this.lookupRegAlias(...regid) || regid[1]);

                    operands.push(str);
            }
        }

        const ops = operands.join(', ');

        return ops ? `${spec.op} ${ops}` : spec.op;
    }

    public disassembleRegion(fromPc: number, toPc: number):
            // pc, unsigned, signed, instruction, labels
            [number, number, number, string|null, string[]][] {
        this.validateAddr(fromPc);
        this.validateAddr(toPc);

        if (fromPc > toPc) {
            [fromPc, toPc] = [toPc, fromPc];
        }

        const result: [number, number, number, string|null, string[]][] = [];

        for (let pc = fromPc; pc <= toPc; pc += this.isa.pc.increment) {
            const word = this.load(pc);
            const sext = sextTo(word, this.isa.mem.word);
            const labels = this.labelsForAddr(pc);
            let disassembled = this.disassemble(pc, word);

            // If cannot disassemble and a printable ascii character,
            // stick that bad boy in there
            if (!disassembled && ' '.charCodeAt(0) <= word &&
                    word <= '~'.charCodeAt(0)) {
                disassembled = `'${String.fromCharCode(word)}'`;
            }

            result.push([pc, word, sext, disassembled, labels]);
        }

        return result;
    }

    protected lookupRegAlias(prefix: string, regno: number): string|null {
        const aliases = getRegAliases(this.isa, prefix);
        // Choose the lexicographically first matching alias (so it's
        // deterministic)
        let minAlias: string|null = null;
        // TODO: ~O(1) instead please
        for (const alias in aliases) {
            if (aliases[alias] === regno && (!minAlias || alias < minAlias)) {
                minAlias = alias;
            }
        }
        return minAlias;
    }

    private labelsForAddr(pc: number): string[] {
        // TODO: replace with something less grossly inefficient
        const results: string[] = [];

        for (const label in this.symbTable) {
            if (this.symbTable[label] === pc) {
                results.push(label);
            }
        }

        // Make sure order is deterministic
        results.sort();

        return results;
    }

    private validateAddr(addr: number): void {
        // TODO: check if NaN or infinity or whatever

        if (addr < 0) {
            throw new Error('cannot set breakpoint for negative address ' +
                            addr);
        }

        if (addr > maxUnsignedVal(this.isa.mem.space)) {
            throw new Error(`address 0x${addr.toString(16)} is too large`);
        }
    }
}

export { Debugger };