-
Notifications
You must be signed in to change notification settings - Fork 20
/
entry.go
299 lines (250 loc) · 9.01 KB
/
entry.go
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package disassembly
import (
"fmt"
"strings"
"github.com/jetsetilly/gopher2600/hardware/cpu/execution"
"github.com/jetsetilly/gopher2600/hardware/cpu/instructions"
"github.com/jetsetilly/gopher2600/hardware/cpu/registers"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
)
// EntryLevel describes the level of the Entry.
type EntryLevel int
// List of valid EntryLevel in increasing reliability.
//
// Decoded entries have been decoded as though every byte point is a valid
// instruction. Blessed entries meanwhile take into consideration the preceding
// instruction and the number of bytes it would have consumed.
//
// Decoded entries are useful in the event of the CPU landing on an address that
// didn't look like an instruction at disassembly time.
//
// Blessed instructions are deemed to be more accurate because they have been
// reached according to the flow of the instructions from the start address.
//
// For normal debugging operations there is no need to use EntryLevelUnused
// outside of the disassembly package. It used for the unusual case where a
// bank is not able to be referenced from the Entry address. See M-Network for
// an example of this, where Bank 7 cannot be mapped to the lower segment.
const (
EntryLevelUnmappable EntryLevel = iota
EntryLevelDecoded
EntryLevelBlessed
EntryLevelExecuted
)
// Entry is a disassambled instruction. The constituent parts of the
// disassembly. It is a representation of execution.Instruction.
type Entry struct {
dsm *Disassembly
// the bank this entry belongs to. note that this is just the bank number;
// we're not storing a copy of mapper.BankInfo. that's not needed for
// disassembly purposes
Bank int
// the level of reliability of the information in the Entry.
//
// note that it is possible for EntryLevelExecuted entries to be partially
// executed. check Result.Final if required.
Level EntryLevel
// copy of the CPU execution. must not be updated except through
// updateExecutionEntry() function.
//
// not that the the Final field of execution.Result may be false is the
// emulation is stopped mid-execution.
Result execution.Result
// the entries below are not defined if Level == EntryLevelUnused
// string representations of information in execution.Result
//
// entry.GetField() will apply white spacing padding suitable for columnation
Label Label
Bytecode string
Address string
Operator string
Operand Operand
}
// some fields in the disassembly entry are updated on every execution.
func (e *Entry) updateExecutionEntry(result execution.Result) {
e.Result = result
// update address in label. we probably don't need to do this but it might
// be useful to know what the *actual* address of the instruction. ie.
// which mirror is used by the program at that point in the execution.
e.Label.address = e.Result.Address
// update result instance in Operand fields
e.Operand.result = e.Result
// indicate that entry has been executed
e.Level = EntryLevelExecuted
}
// Cycles returns the number of cycles annotated if actual cycles differs from
// the number of cycles in the definition. for executed branch instructions this
// will always be the case.
func (e *Entry) Cycles() string {
if e.Level < EntryLevelExecuted {
// the Defn field may be unassigned
if e.Result.Defn == nil {
return "-"
}
return e.Result.Defn.Cycles.Formatted
}
if e.Result.Final {
return fmt.Sprintf("%d", e.Result.Cycles)
}
// if entry hasn't been executed yet or if actual cycles is different to
// the cycles defined for the entry then return an annotated string
return fmt.Sprintf("%d of %s", e.Result.Cycles, e.Result.Defn.Cycles.Formatted)
}
// Notes returns a string returning notes about the most recent execution. The
// information is made up of the BranchSuccess, PageFault and CPUBug fields.
func (e *Entry) Notes() string {
if e.Level < EntryLevelExecuted {
return ""
}
if !e.Result.Final {
return ""
}
s := strings.Builder{}
if e.Result.Defn.IsBranch() {
if e.Result.BranchSuccess {
s.WriteString("branch succeeded ")
} else {
s.WriteString("branch failed ")
}
if e.Result.PageFault {
s.WriteString("with page-fault ")
}
} else {
if e.Result.PageFault {
s.WriteString("page-fault ")
}
}
if e.Result.CPUBug != "" {
s.WriteString(e.Result.CPUBug)
}
return s.String()
}
// add decoration to operand according to the addressing mode of the entry.
// operand taken as an argument because it is called from two different contexts.
func addrModeDecoration(operand string, mode instructions.AddressingMode) string {
s := operand
switch mode {
case instructions.Implied:
case instructions.Immediate:
s = fmt.Sprintf("#%s", operand)
case instructions.Relative:
case instructions.Absolute:
case instructions.ZeroPage:
case instructions.Indirect:
s = fmt.Sprintf("(%s)", operand)
case instructions.IndexedIndirect:
s = fmt.Sprintf("(%s,X)", operand)
case instructions.IndirectIndexed:
s = fmt.Sprintf("(%s),Y", operand)
case instructions.AbsoluteIndexedX:
s = fmt.Sprintf("%s,X", operand)
case instructions.AbsoluteIndexedY:
s = fmt.Sprintf("%s,Y", operand)
case instructions.ZeroPageIndexedX:
s = fmt.Sprintf("%s,X", operand)
case instructions.ZeroPageIndexedY:
s = fmt.Sprintf("%s,Y", operand)
}
return s
}
// absolute branch destination returns the branch operand as the address of the
// branched PC, rather than an offset value.
func absoluteBranchDestination(addr uint16, operand uint16) uint16 {
// create a mock register with the instruction's address as the initial value
pc := registers.NewProgramCounter(addr)
// all 6502 branch instructions are 2 bytes in length
pc.Add(2)
// because we're doing 16 bit arithmetic with an 8bit value, we need to
// make sure the sign bit has been propogated to the more-significant bits
if operand&0x0080 == 0x0080 {
operand |= 0xff00
}
// add the 2s-complement value to the mock program counter
pc.Add(operand)
return pc.Address()
}
// Label implements the Stringer interface. The String() implementation
// returns any address label for the entry. Use GetField() function for
// a white-space padded label.
type Label struct {
dsm *Disassembly
address uint16
bank int
}
// String returns the address label as a symbol (if a symbol is available)
// if a symbol is not available then the the bool return value will be false.
func (lb Label) String() string {
if lb.dsm.Prefs.Symbols.Get().(bool) {
ma, _ := memorymap.MapAddress(lb.address, true)
if e, ok := lb.dsm.Sym.GetLabel(lb.bank, ma); ok {
return e.Symbol
}
}
return ""
}
// Operand implements the Stringer interface. The String() implementation
// returns the operand (with symbols if appropriate). Use GetField function for
// white-space padded operand string.
type Operand struct {
nonSymbolic string
dsm *Disassembly
result execution.Result
bank int
}
// String returns the operand as a symbol (if a symbol is available) if
// a symbol is not available then the the bool return value will be false.
func (op Operand) String() string {
if op.dsm == nil || !op.dsm.Prefs.Symbols.Get().(bool) {
return op.nonSymbolic
}
s := op.nonSymbolic
// use symbol for the operand if available/appropriate. we should only do
// this if operand has been decoded
if op.result.Defn.AddressingMode == instructions.Immediate {
// TODO: immediate symbols
} else if op.result.ByteCount > 1 {
// instruction data is only valid if bytecount is 2 or more
operand := op.result.InstructionData
switch op.result.Defn.Effect {
case instructions.Flow:
if op.result.Defn.IsBranch() {
operand = absoluteBranchDestination(op.result.Address, operand)
// look up mock program counter value in symbol table
if e, ok := op.dsm.Sym.GetLabel(op.bank, operand); ok {
s = e.Symbol
}
} else if e, ok := op.dsm.Sym.GetLabel(op.bank, operand); ok {
s = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
}
case instructions.Subroutine:
if e, ok := op.dsm.Sym.GetLabel(op.bank, operand); ok {
s = e.Symbol
}
case instructions.Read:
if e, ok := op.dsm.Sym.GetSymbol(operand, true); ok {
s = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
}
case instructions.Write:
fallthrough
case instructions.RMW:
if e, ok := op.dsm.Sym.GetSymbol(operand, false); ok {
s = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
}
}
}
return s
}