-
Notifications
You must be signed in to change notification settings - Fork 19
/
elf.go
462 lines (391 loc) · 12.8 KB
/
elf.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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
// 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 elf
import (
"debug/elf"
"encoding/binary"
"fmt"
"io"
"os"
"github.com/jetsetilly/gopher2600/cartridgeloader"
"github.com/jetsetilly/gopher2600/coprocessor"
"github.com/jetsetilly/gopher2600/environment"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/memory/cpubus"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
)
// Elf implements the mapper.CartMapper interface.
type Elf struct {
env *environment.Environment
loader cartridgeloader.Loader
version string
arm *arm.ARM
mem *elfMemory
// the hook that handles cartridge yields
yieldHook coprocessor.CartYieldHook
// armState is a copy of the ARM's state at the moment of the most recent
// Snapshot. it's used only suring a Plumb() operation
armState *arm.ARMState
}
// elfReaderAt is an implementation of io.ReaderAt and is used with elf.NewFile()
type elfReaderAt struct {
// data from the file being used as the source of ELF data
data []byte
// the offset into the data slice where the ELF file starts
offset int64
}
func (r *elfReaderAt) ReadAt(p []byte, start int64) (n int, err error) {
start += r.offset
end := start + int64(len(p))
if end > int64(len(r.data)) {
end = int64(len(r.data))
}
copy(p, r.data[start:end])
n = int(end - start)
if n < len(p) {
return n, fmt.Errorf("not enough bytes in the ELF data to fill the buffer")
}
return n, nil
}
// NewElf is the preferred method of initialisation for the Elf type.
func NewElf(env *environment.Environment, loader cartridgeloader.Loader, inACE bool) (mapper.CartMapper, error) {
r := &elfReaderAt{}
// open file in the normal way and read all data. close the file
// immediately once all data is rad
o, err := os.Open(loader.Filename)
if err != nil {
return nil, fmt.Errorf("ELF: %w", err)
}
r.data, err = io.ReadAll(o)
if err != nil {
o.Close()
return nil, fmt.Errorf("ELF: %w", err)
}
o.Close()
// if this is an embedded ELF file in an ACE container we need to extract
// the ELF contents. we do this by finding the offset of the ELF file. the
// offset is stored at initial offset 0x28
if inACE {
if len(r.data) < 0x30 {
return nil, fmt.Errorf("ELF: this doesn't look like ELF data embedded in an ACE file")
}
r.offset = int64(r.data[0x28]) | (int64(r.data[0x29]) << 8)
}
// ELF file is read via our elfReaderAt instance
ef, err := elf.NewFile(r)
if err != nil {
return nil, fmt.Errorf("ELF: %w", err)
}
defer ef.Close()
// keeping things simple. only 32bit ELF files supported
if ef.Class != elf.ELFCLASS32 {
return nil, fmt.Errorf("ELF: only 32bit ELF files are supported")
}
// sanity checks on ELF data
if ef.FileHeader.Machine != elf.EM_ARM {
return nil, fmt.Errorf("ELF: is not ARM")
}
if ef.FileHeader.Version != elf.EV_CURRENT {
return nil, fmt.Errorf("ELF: unknown version")
}
if ef.FileHeader.Type != elf.ET_REL {
return nil, fmt.Errorf("ELF: is not relocatable")
}
// big endian byte order is probably fine but we've not tested it
if ef.FileHeader.ByteOrder != binary.LittleEndian {
return nil, fmt.Errorf("ELF: is not little-endian")
}
cart := &Elf{
env: env,
loader: loader,
yieldHook: coprocessor.StubCartYieldHook{},
}
cart.mem = newElfMemory(cart.env)
cart.arm = arm.NewARM(cart.env, cart.mem.model, cart.mem, cart)
cart.mem.Plumb(cart.arm)
err = cart.mem.decode(ef)
if err != nil {
return nil, fmt.Errorf("ELF: %w", err)
}
cart.arm.SetByteOrder(ef.ByteOrder)
cart.mem.busStuffingInit()
// defer VCS reset until the VCS tries to read the cpubus.Reset address
// run arm initialisation functions if present. next call to arm.Run() will
// cause the main function to execute
err = cart.mem.runInitialisation(cart.arm)
if err != nil {
return nil, fmt.Errorf("ELF: %w", err)
}
return cart, nil
}
// MappedBanks implements the mapper.CartMapper interface.
func (cart *Elf) MappedBanks() string {
return fmt.Sprintf("Bank: 0")
}
// ID implements the mapper.CartMapper interface.
func (cart *Elf) ID() string {
return "ELF"
}
// Snapshot implements the mapper.CartMapper interface.
func (cart *Elf) Snapshot() mapper.CartMapper {
n := *cart
// taking a snapshot of ARM state via the ARM itself can cause havoc if
// this instance of the cart is not current (because the ARM pointer itself
// may be stale or pointing to another emulation)
if cart.armState == nil {
n.armState = cart.arm.Snapshot()
} else {
n.armState = cart.armState.Snapshot()
}
n.mem = cart.mem.Snapshot()
return &n
}
// Plumb implements the mapper.CartMapper interface.
func (cart *Elf) PlumbFromDifferentEmulation(env *environment.Environment) {
cart.env = env
if cart.armState == nil {
panic("cannot plumb this ELF instance because the ARM state is nil")
}
cart.arm = arm.NewARM(cart.env, cart.mem.model, cart.mem, cart)
cart.arm.Plumb(cart.env, cart.armState, cart.mem, cart)
cart.armState = nil
cart.mem.Plumb(cart.arm)
cart.yieldHook = &coprocessor.StubCartYieldHook{}
}
// Plumb implements the mapper.CartMapper interface.
func (cart *Elf) Plumb(env *environment.Environment) {
cart.env = env
if cart.armState == nil {
panic("cannot plumb this ELF instance because the ARM state is nil")
}
cart.mem.Plumb(cart.arm)
cart.arm.Plumb(cart.env, cart.armState, cart.mem, cart)
cart.armState = nil
}
// Reset implements the mapper.CartMapper interface.
func (cart *Elf) Reset() {
}
// reset is distinct from Reset(). this reset function is implied by the
// reading of the cpubus.Reset address.
func (cart *Elf) reset() {
// stream bytes rather than injecting them into the VCS as they arrive
cart.mem.stream.active = true
// initialise ROM for the VCS
if cart.mem.stream.active {
cart.mem.stream.push(streamEntry{
addr: 0x1ffc,
data: 0x00,
})
cart.mem.stream.push(streamEntry{
addr: 0x1ffd,
data: 0x10,
})
cart.mem.strongarm.nextRomAddress = 0x1000
cart.mem.stream.startDrain()
} else {
cart.mem.setStrongArmFunction(vcsEmulationInit)
}
// set arguments for initial execution of ARM program
cart.mem.args[argAddrSystemType-argOrigin] = argSystemType_NTSC
cart.mem.args[argAddrClockHz-argOrigin] = 0xef
cart.mem.args[argAddrClockHz-argOrigin+1] = 0xbe
cart.mem.args[argAddrClockHz-argOrigin+2] = 0xad
cart.mem.args[argAddrClockHz-argOrigin+3] = 0xde
cart.arm.SetInitialRegisters(argOrigin)
}
// Access implements the mapper.CartMapper interface.
func (cart *Elf) Access(addr uint16, _ bool) (uint8, uint8, error) {
if cart.mem.stream.active {
if !cart.mem.stream.drain {
cart.runARM(addr)
}
if addr == cart.mem.stream.peek().addr&memorymap.CartridgeBits {
e := cart.mem.stream.pull()
cart.mem.gpio.data[DATA_ODR] = e.data
}
}
cart.mem.busStuffDelay = true
return cart.mem.gpio.data[DATA_ODR], mapper.CartDrivenPins, nil
}
// AccessVolatile implements the mapper.CartMapper interface.
func (cart *Elf) AccessVolatile(addr uint16, data uint8, _ bool) error {
return nil
}
// NumBanks implements the mapper.CartMapper interface.
func (cart *Elf) NumBanks() int {
return 1
}
// GetBank implements the mapper.CartMapper interface.
func (cart *Elf) GetBank(_ uint16) mapper.BankInfo {
return mapper.BankInfo{Number: 0, IsRAM: false}
}
func (cart *Elf) runARM(addr uint16) bool {
if cart.mem.stream.active {
// do nothing with the ARM if the byte stream is draining
if cart.mem.stream.drain {
return true
}
// run preempted snoopDataBus() function if required
if cart.mem.stream.preemptedSnoopDataBus != nil {
if addr != cart.mem.strongarm.nextRomAddress {
return true
}
cart.mem.stream.preemptedSnoopDataBus(cart.mem)
}
}
cart.arm.StartProfiling()
defer cart.arm.ProcessProfiling()
// call arm once and then check for yield conditions
cart.mem.yield, _ = cart.arm.Run()
// keep calling runArm() for as long as program does not need to sync with the VCS...
for cart.mem.yield.Type != coprocessor.YieldSyncWithVCS {
// ... or if the yield hook says to return to the VCS immediately
switch cart.yieldHook.CartYield(cart.mem.yield.Type) {
case coprocessor.YieldHookEnd:
return false
case coprocessor.YieldHookContinue:
cart.mem.yield, _ = cart.arm.Run()
}
}
return true
}
// AccessPassive implements the mapper.CartMapper interface.
func (cart *Elf) AccessPassive(addr uint16, data uint8) error {
// if memory access is not a cartridge address (ie. a TIA or RIOT address)
// then the ARM is running in parallel (ie. no synchronisation)
cart.mem.parallelARM = (addr&memorymap.OriginCart != memorymap.OriginCart)
// if address is the reset address then trigger the reset procedure
if (addr&memorymap.CartridgeBits)|memorymap.OriginCart == (cpubus.Reset&memorymap.CartridgeBits)|memorymap.OriginCart {
// after this call to cart reset, the cartridge will be wanting to run
// the vcsEmulationInit() strongarm function
cart.reset()
}
// set GPIO data and address information
cart.mem.gpio.data[DATA_IDR] = data
cart.mem.gpio.data[ADDR_IDR] = uint8(addr)
cart.mem.gpio.data[ADDR_IDR+1] = uint8(addr >> 8)
// handle ARM synchronisation for non-byte-streaming mode. the sequence of
// calls to runARM() and whatever strongarm function might be active was
// arrived through experimentation. a more efficient way of doing this
// hasn't been discovered yet
if !cart.mem.stream.active {
runStrongarm := func() bool {
if cart.mem.strongarm.running.function == nil {
return false
}
cart.mem.strongarm.running.function(cart.mem)
if cart.mem.strongarm.running.function == nil {
cart.runARM(addr)
if cart.mem.strongarm.running.function != nil {
cart.mem.strongarm.running.function(cart.mem)
}
}
return true
}
if runStrongarm() {
return nil
}
cart.runARM(addr)
if runStrongarm() {
return nil
}
cart.runARM(addr)
if runStrongarm() {
return nil
}
cart.runARM(addr)
return nil
}
// run ARM and strongarm function again
cart.runARM(addr)
return nil
}
// Step implements the mapper.CartMapper interface.
func (cart *Elf) Step(clock float32) {
cart.arm.Step(clock)
}
// IterateBank implements the mapper.CartMapper interface.
func (cart *Elf) CopyBanks() []mapper.BankContent {
return nil
}
// implements arm.CartridgeHook interface.
func (cart *Elf) ARMinterrupt(addr uint32, val1 uint32, val2 uint32) (arm.ARMinterruptReturn, error) {
return arm.ARMinterruptReturn{}, nil
}
// BusStuff implements the mapper.CartBusStuff interface.
func (cart *Elf) BusStuff() (uint8, bool) {
if cart.mem.busStuffDelay {
cart.mem.busStuffDelay = false
return 0, false
}
if cart.mem.stream.active {
if cart.mem.stream.peek().busstuff {
e := cart.mem.stream.pull()
return e.data, true
}
return 0, false
}
if cart.mem.busStuff {
cart.mem.busStuff = false
return cart.mem.busStuffData, true
}
return 0, false
}
// ELFSection implements the coprocessor.CartCoProcRelocatable interface.
func (cart *Elf) ELFSection(name string) ([]uint8, uint32, bool) {
if idx, ok := cart.mem.sectionsByName[name]; ok {
s := cart.mem.sections[idx]
return s.data, s.origin, true
}
return nil, 0, false
}
// CoProcExecutionState implements the coprocessor.CartCoProcBus interface.
func (cart *Elf) CoProcExecutionState() coprocessor.CoProcExecutionState {
if cart.mem.parallelARM {
return coprocessor.CoProcExecutionState{
Sync: coprocessor.CoProcParallel,
Yield: cart.mem.yield,
}
}
return coprocessor.CoProcExecutionState{
Sync: coprocessor.CoProcStrongARMFeed,
Yield: cart.mem.yield,
}
}
// CoProcRegister implements the coprocessor.CartCoProcBus interface.
func (cart *Elf) GetCoProc() coprocessor.CartCoProc {
return cart.arm
}
// SetYieldHook implements the coprocessor.CartCoProcBus interface.
func (cart *Elf) SetYieldHook(hook coprocessor.CartYieldHook) {
cart.yieldHook = hook
}
// GetStatic implements the mapper.CartStaticBus interface.
func (cart *Elf) GetStatic() mapper.CartStatic {
return cart.mem.Snapshot()
}
// StaticWrite implements the mapper.CartStaticBus interface.
func (cart *Elf) PutStatic(segment string, idx int, data uint8) bool {
mem, ok := cart.mem.Reference(segment)
if !ok {
return false
}
if idx >= len(mem) {
return false
}
mem[idx] = data
return true
}