mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-12-21 18:29:45 +00:00
Trace ProDOS MLI calls
This commit is contained in:
parent
48afaa471b
commit
636895a7e4
@ -46,6 +46,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
|
||||
- Fast disk mode to set max speed while using the disks.
|
||||
- Single file executable with embedded ROMs and DOS 3.3
|
||||
- Pause (thanks a2geek)
|
||||
- ProDOS MLI calls tracing
|
||||
|
||||
|
||||
## Running the emulator
|
||||
@ -175,6 +176,8 @@ Only valid on SDL mode
|
||||
dump to the console the CPU execution. Use F11 to toggle.
|
||||
-traceHD
|
||||
dump to the console the hd commands
|
||||
-traceMLI
|
||||
dump to the console the calls to ProDOS machine langunage interface calls to $BF00
|
||||
-traceSS
|
||||
dump to the console the sofswitches calls
|
||||
-vidHDSlot int
|
||||
|
23
apple2.go
23
apple2.go
@ -28,6 +28,7 @@ type Apple2 struct {
|
||||
profile bool
|
||||
showSpeed bool
|
||||
paused bool
|
||||
traceMLI *traceProDOS
|
||||
}
|
||||
|
||||
const (
|
||||
@ -51,10 +52,9 @@ func (a *Apple2) Run() {
|
||||
// Run a 6502 step
|
||||
if !a.paused {
|
||||
a.cpu.ExecuteInstruction()
|
||||
a.executionTrace()
|
||||
} else {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
referenceTime = time.Now()
|
||||
speedReferenceTime = referenceTime
|
||||
}
|
||||
|
||||
// Execute meta commands
|
||||
@ -62,10 +62,17 @@ func (a *Apple2) Run() {
|
||||
for commandsPending {
|
||||
select {
|
||||
case command := <-a.commandChannel:
|
||||
if command == CommandKill {
|
||||
switch command {
|
||||
case CommandKill:
|
||||
return
|
||||
case CommandPauseUnpauseEmulator:
|
||||
a.paused = !a.paused
|
||||
referenceTime = time.Now()
|
||||
speedReferenceTime = referenceTime
|
||||
default:
|
||||
// Execute the other commands
|
||||
a.executeCommand(command)
|
||||
}
|
||||
a.executeCommand(command)
|
||||
default:
|
||||
commandsPending = false
|
||||
}
|
||||
@ -178,8 +185,6 @@ func (a *Apple2) executeCommand(command int) {
|
||||
a.cpu.SetTrace(!a.cpu.GetTrace())
|
||||
case CommandReset:
|
||||
a.cpu.Reset()
|
||||
case CommandPauseUnpauseEmulator:
|
||||
a.paused = !a.paused
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,6 +201,12 @@ func (a *Apple2) releaseFastMode() {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Apple2) executionTrace() {
|
||||
if a.traceMLI != nil {
|
||||
a.traceMLI.inspect()
|
||||
}
|
||||
}
|
||||
|
||||
type persistent interface {
|
||||
save(io.Writer) error
|
||||
load(io.Reader) error
|
||||
|
@ -44,10 +44,13 @@ func newApple2eEnhanced() *Apple2 {
|
||||
return &a
|
||||
}
|
||||
|
||||
func (a *Apple2) setup(isColor bool, clockMhz float64, fastMode bool) {
|
||||
func (a *Apple2) setup(isColor bool, clockMhz float64, fastMode bool, traceMLI bool) {
|
||||
a.commandChannel = make(chan int, 100)
|
||||
a.isColor = isColor
|
||||
a.fastMode = fastMode
|
||||
if traceMLI {
|
||||
a.traceMLI = newTraceProDOS(a)
|
||||
}
|
||||
|
||||
if clockMhz <= 0 {
|
||||
// Full speed
|
||||
|
@ -109,6 +109,11 @@ func MainApple() *Apple2 {
|
||||
"profile",
|
||||
false,
|
||||
"generate profile trace to analyse with pprof")
|
||||
traceMLI := flag.Bool(
|
||||
"traceMLI",
|
||||
false,
|
||||
"dump to the console the calls to ProDOS machine langunage interface calls to $BF00")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *wozImage != "" {
|
||||
@ -178,7 +183,7 @@ func MainApple() *Apple2 {
|
||||
panic("Model not supported")
|
||||
}
|
||||
|
||||
a.setup(!*mono, *cpuClock, *fastDisk)
|
||||
a.setup(!*mono, *cpuClock, *fastDisk, *traceMLI)
|
||||
a.cpu.SetTrace(*traceCPU)
|
||||
a.io.setTrace(*traceSS)
|
||||
a.io.setPanicNotImplemented(*panicSS)
|
||||
|
@ -103,6 +103,16 @@ func (s *State) GetTrace() bool {
|
||||
return s.trace
|
||||
}
|
||||
|
||||
// GetPCAndSP returns the current program counter and stack pointer. Used to trace MLI calls
|
||||
func (s *State) GetPCAndSP() (uint16, uint8) {
|
||||
return s.reg.getPC(), s.reg.getSP()
|
||||
}
|
||||
|
||||
// GetCarryAndAcc returns the value of te carry flag and the accumulator. Used to trace MLI calls
|
||||
func (s *State) GetCarryAndAcc() (bool, uint8) {
|
||||
return s.reg.getFlag(flagC), s.reg.getA()
|
||||
}
|
||||
|
||||
// Save saves the CPU state (registers and cycle counter)
|
||||
func (s *State) Save(w io.Writer) error {
|
||||
err := binary.Write(w, binary.BigEndian, s.cycles)
|
||||
|
274
traceProDOS.go
Normal file
274
traceProDOS.go
Normal file
@ -0,0 +1,274 @@
|
||||
package apple2
|
||||
|
||||
import "fmt"
|
||||
|
||||
type traceProDOS struct {
|
||||
a *Apple2
|
||||
callPending bool // We assume MLI is not reentrant
|
||||
functionCode uint8
|
||||
paramsAdddress uint16
|
||||
returnAddress uint16
|
||||
}
|
||||
|
||||
const (
|
||||
mliAddress uint16 = 0xbf00
|
||||
biAddress uint16 = 0xbe03
|
||||
)
|
||||
|
||||
func newTraceProDOS(a *Apple2) *traceProDOS {
|
||||
var t traceProDOS
|
||||
t.a = a
|
||||
return &t
|
||||
}
|
||||
|
||||
func (t *traceProDOS) inspect() {
|
||||
pc, ps := t.a.cpu.GetPCAndSP()
|
||||
if pc == mliAddress {
|
||||
/*
|
||||
MLI has been called (provided we are running proDOS and the proper page)
|
||||
Calls to MLI must be:
|
||||
JSR $BF00
|
||||
DFB function_code
|
||||
DW addr_op_parms
|
||||
*/
|
||||
if t.callPending {
|
||||
if t.functionCode == 0x65 {
|
||||
// QUIT when successfull does not return
|
||||
fmt.Printf("Ok \n")
|
||||
} else {
|
||||
fmt.Print("<there was a call pending>\n")
|
||||
}
|
||||
}
|
||||
caller := uint16(t.a.mmu.Peek(0x100+uint16(ps+1))) +
|
||||
uint16(t.a.mmu.Peek(0x100+uint16(ps+2)))<<8 - 2
|
||||
t.functionCode = t.a.mmu.Peek(caller + 3)
|
||||
t.paramsAdddress = uint16(t.a.mmu.Peek(caller+4)) + uint16(t.a.mmu.Peek(caller+5))<<8
|
||||
t.returnAddress = caller + 6
|
||||
fmt.Printf("MLI call $%02x from $%04x", t.functionCode, caller)
|
||||
switch t.functionCode {
|
||||
case 0x40:
|
||||
fmt.Printf(" ALLOC_INTERRUPT()")
|
||||
case 0x41:
|
||||
fmt.Printf(" DEALLOC_INTERRUPT()")
|
||||
case 0x65:
|
||||
fmt.Printf(" QUIT()")
|
||||
case 0x80:
|
||||
fmt.Printf(" READ_BLOCK(unit=%s, block=$%04x)", parseUnit(t.paramByte(1)), t.paramWord(4))
|
||||
case 0x81:
|
||||
fmt.Printf(" WRITE_BLOCK(unit=%s, block=$%04x)", parseUnit(t.paramByte(1)), t.paramWord(4))
|
||||
case 0x82:
|
||||
fmt.Printf(" GET_TIME()")
|
||||
case 0xc0:
|
||||
fmt.Printf(" CREATE(\"%s\")", t.paramString(1))
|
||||
case 0xc1:
|
||||
fmt.Printf(" DESTROY(\"%s\")", t.paramString(1))
|
||||
case 0xc2:
|
||||
fmt.Printf(" RENAME(old=\"%s\", new=\"%s\")", t.paramString(1), t.paramString(3))
|
||||
case 0xc3:
|
||||
fmt.Printf(" GET_FILE_INFO(\"%s\")", t.paramString(1))
|
||||
case 0xc4:
|
||||
fmt.Printf(" SET_FILE_INFO(\"%s\")", t.paramString(1))
|
||||
case 0xc5:
|
||||
fmt.Printf(" ONLINE(unit=%s)", parseUnit(t.paramByte(1)))
|
||||
case 0xc6:
|
||||
fmt.Printf(" SET_PREFIX(\"%s\")", t.paramString(1))
|
||||
case 0xc7:
|
||||
fmt.Printf(" GET_PREFIX()")
|
||||
case 0xc8:
|
||||
fmt.Printf(" OPEN(\"%s\")", t.paramString(1))
|
||||
case 0xc9:
|
||||
fmt.Printf(" NEWLINE(ref=%v, mask=$%02x, char=$%02x)", t.paramByte(1), t.paramByte(2), t.paramByte(3))
|
||||
case 0xca:
|
||||
fmt.Printf(" READ(ref=%v, len=%v)", t.paramByte(1), t.paramWord(4))
|
||||
case 0xcb:
|
||||
fmt.Printf(" WRITE(ref=%v, len=%v)", t.paramByte(1), t.paramWord(4))
|
||||
case 0xcc:
|
||||
fmt.Printf(" CLOSE(ref=%v)", t.paramByte(1))
|
||||
case 0xcd:
|
||||
fmt.Printf(" FLUSH(ref=%v)", t.paramByte(1))
|
||||
case 0xce:
|
||||
fmt.Printf(" SET_MARK(ref=%v, pos=%v)", t.paramByte(1), t.paramLen(2))
|
||||
case 0xcf:
|
||||
fmt.Printf(" GET_MARK(ref=%v)", t.paramByte(1))
|
||||
case 0xd1:
|
||||
fmt.Printf(" GET_EOF(ref=%v)", t.paramByte(1))
|
||||
case 0xd2:
|
||||
fmt.Printf(" SET_BUF(ref=%v)", t.paramByte(1))
|
||||
case 0xd3:
|
||||
fmt.Printf(" GET_BUF(ref=%v)", t.paramByte(1))
|
||||
}
|
||||
fmt.Printf(" => ")
|
||||
|
||||
t.callPending = true
|
||||
} else if t.callPending && pc == t.returnAddress {
|
||||
error, acc := t.a.cpu.GetCarryAndAcc()
|
||||
if error {
|
||||
fmt.Printf("error $%02x: %v\n", acc, getMliErrorText(acc))
|
||||
} else {
|
||||
switch t.functionCode {
|
||||
case 0x82: // Get Time
|
||||
// Globals will be updated
|
||||
date := uint16(t.a.mmu.Peek(0xbf90)) + uint16(t.a.mmu.Peek(0xbf91))<<8
|
||||
minute := t.a.mmu.Peek(0xbf92)
|
||||
hour := t.a.mmu.Peek(0xbf93)
|
||||
fmt.Printf("%04v-%02v-%02v %02v:%02v\n",
|
||||
date>>9+1900, (date>>5)&0x1f, date&0x1f, // Review Y2K
|
||||
hour, minute)
|
||||
case 0xc5: // Online
|
||||
dataAddress := t.paramWord(2)
|
||||
for {
|
||||
b := t.a.mmu.Peek(dataAddress)
|
||||
dataAddress++
|
||||
if b == 0 {
|
||||
fmt.Printf("\n")
|
||||
break
|
||||
}
|
||||
unit := parseUnit(b)
|
||||
size := b & 0xf
|
||||
if size != 0 {
|
||||
// No error
|
||||
name := ""
|
||||
for i := uint8(0); i < size; i++ {
|
||||
name += string(t.a.mmu.Peek(dataAddress+uint16(i)) & 0x7f)
|
||||
}
|
||||
fmt.Printf("%s: \"%s\" ", unit, name)
|
||||
} else {
|
||||
err := t.a.mmu.Peek(dataAddress)
|
||||
fmt.Printf("%s: error $%02x ", unit, err)
|
||||
}
|
||||
if t.paramByte(1) != 0 {
|
||||
fmt.Printf("\n")
|
||||
break // Only one entry requested
|
||||
}
|
||||
dataAddress += 15
|
||||
}
|
||||
case 0xc7: // Get prefix
|
||||
fmt.Printf("\"%v\"\n", t.paramString(1))
|
||||
case 0xc8: // Open file
|
||||
fmt.Printf("ref: %v\n", t.paramByte(5))
|
||||
case 0xca: // Read
|
||||
fmt.Printf("%v bytes read \n", t.paramByte(6))
|
||||
case 0xcb: // Write
|
||||
fmt.Printf("%v bytes written \n", t.paramByte(6))
|
||||
case 0xcf: // File position
|
||||
fmt.Printf("%v\n", t.paramLen(2))
|
||||
case 0xd1: // File size
|
||||
fmt.Printf("%v bytes\n", t.paramLen(2))
|
||||
default:
|
||||
fmt.Printf("Ok\n")
|
||||
}
|
||||
}
|
||||
t.callPending = false
|
||||
} else if pc == biAddress {
|
||||
s := ""
|
||||
for i := uint16(1); i < 256; i++ {
|
||||
ch := t.a.mmu.Peek(0x200 + i)
|
||||
if ch == 0 || ch == 0x8d {
|
||||
break
|
||||
}
|
||||
s += string(ch)
|
||||
}
|
||||
fmt.Printf("Prodos BI exec: \"%s\".\n", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *traceProDOS) paramByte(pos uint16) uint8 {
|
||||
return t.a.mmu.Peek(t.paramsAdddress + pos)
|
||||
}
|
||||
|
||||
func (t *traceProDOS) paramWord(pos uint16) uint16 {
|
||||
// Two bytes
|
||||
return uint16(t.a.mmu.Peek(t.paramsAdddress+pos)) + uint16(t.a.mmu.Peek(t.paramsAdddress+pos+1))<<8
|
||||
}
|
||||
|
||||
func (t *traceProDOS) paramLen(pos uint16) uint32 {
|
||||
// Three bytes
|
||||
return uint32(t.paramWord(pos)) + uint32(t.paramByte(pos+2))<<16
|
||||
}
|
||||
|
||||
func (t *traceProDOS) paramString(pos uint16) string {
|
||||
address := t.paramWord(pos)
|
||||
size := t.a.mmu.Peek(address)
|
||||
s := ""
|
||||
for i := uint8(0); i < size; i++ {
|
||||
s += string(t.a.mmu.Peek(address+1+uint16(i)) & 0x7f)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func parseUnit(unit uint8) string {
|
||||
if unit == 0 {
|
||||
return "All"
|
||||
}
|
||||
drive := unit >> 7
|
||||
slot := (unit >> 4) & 7
|
||||
return fmt.Sprintf("S%v,D%v", slot, drive+1)
|
||||
}
|
||||
|
||||
func getMliErrorText(code uint8) string {
|
||||
// From https://prodos8.com/docs/techref/quick-reference-card/
|
||||
switch code {
|
||||
case 0x00:
|
||||
return "No error"
|
||||
case 0x01:
|
||||
return "Bad system call number"
|
||||
case 0x04:
|
||||
return "Bad system call parameter count"
|
||||
case 0x25:
|
||||
return "Interrupt table full"
|
||||
case 0x27:
|
||||
return "I/O error"
|
||||
case 0x28:
|
||||
return "No device connected"
|
||||
case 0x2B:
|
||||
return "Disk write protected"
|
||||
case 0x2E:
|
||||
return "Disk switched"
|
||||
case 0x40:
|
||||
return "Invalid pathname"
|
||||
case 0x42:
|
||||
return "Maximum number of files open"
|
||||
case 0x43:
|
||||
return "Invalid reference number"
|
||||
case 0x44:
|
||||
return "Directory not found"
|
||||
case 0x45:
|
||||
return "Volume not found"
|
||||
case 0x46:
|
||||
return "File not found"
|
||||
case 0x47:
|
||||
return "Duplicate filename"
|
||||
case 0x48:
|
||||
return "Volume full"
|
||||
case 0x49:
|
||||
return "Volume directory full"
|
||||
case 0x4A:
|
||||
return "Incompatible file format, also a ProDOS directory"
|
||||
case 0x4B:
|
||||
return "Unsupported storage_type"
|
||||
case 0x4C:
|
||||
return "End of file encountered"
|
||||
case 0x4D:
|
||||
return "Position out of range"
|
||||
case 0x4E:
|
||||
return "File access error, also file locked"
|
||||
case 0x50:
|
||||
return "File is open"
|
||||
case 0x51:
|
||||
return "Directory structure damaged"
|
||||
case 0x52:
|
||||
return "Not a ProDOS volume"
|
||||
case 0x53:
|
||||
return "Invalid system call parameter"
|
||||
case 0x55:
|
||||
return "Volume Control Block table full"
|
||||
case 0x56:
|
||||
return "Bad buffer address"
|
||||
case 0x57:
|
||||
return "Duplicate volume"
|
||||
case 0x5A:
|
||||
return "File structure damaged"
|
||||
default:
|
||||
return "Unknown error"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user