mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-12-23 00:30:21 +00:00
288 lines
8.0 KiB
Go
288 lines
8.0 KiB
Go
package izapple2
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
/*
|
|
See:
|
|
https://github.com/bobbimanners/Applecorn
|
|
Chapter 45 https://raw.githubusercontent.com/bobbimanners/Applecorn/main/Manuals/Acorn%20BBC%20Micro%20User%20Guide.pdf
|
|
http://beebwiki.mdfs.net/Category:MOS_API
|
|
http://beebwiki.mdfs.net/OSBYTEs
|
|
http://mdfs.net/Docs/Comp/BBC/Osbyte00
|
|
|
|
*/
|
|
|
|
type traceApplecorn struct {
|
|
a *Apple2
|
|
skipConsole bool
|
|
osbyteNames [256]string
|
|
call mosCallData
|
|
wasInKernel bool
|
|
}
|
|
|
|
type mosCallData struct {
|
|
api uint16
|
|
a, x, y uint8
|
|
skipLog bool
|
|
}
|
|
|
|
const (
|
|
applecornKernelStart uint16 = 0xc000 // Code above this is out of BBC territory
|
|
applecornTraceAreaStart uint16 = 0xd000
|
|
applecornNoCaller uint16 = 0xffff
|
|
applecornRomTitle uint16 = 0x8009
|
|
)
|
|
|
|
func newTraceApplecorn(a *Apple2, skipConsole bool) *traceApplecorn {
|
|
var t traceApplecorn
|
|
t.a = a
|
|
t.skipConsole = skipConsole
|
|
t.osbyteNames[0x02] = "select input device"
|
|
t.osbyteNames[0x03] = "select output device"
|
|
t.osbyteNames[0x7c] = "clear escape condition"
|
|
t.osbyteNames[0x7d] = "set escape condition"
|
|
t.osbyteNames[0x7e] = "ack detection of ESC"
|
|
t.osbyteNames[0x7f] = "check for end-of-file on an opened file"
|
|
t.osbyteNames[0x80] = "read ADC channel"
|
|
t.osbyteNames[0x81] = "read key with time lim"
|
|
t.osbyteNames[0x82] = "read high order address"
|
|
t.osbyteNames[0x83] = "read bottom of user mem"
|
|
t.osbyteNames[0x84] = "read top of user mem"
|
|
t.osbyteNames[0x85] = "top user mem for mode"
|
|
t.osbyteNames[0x86] = "read cursor pos"
|
|
t.osbyteNames[0x8b] = "set filing system options"
|
|
t.osbyteNames[0xDA] = "clear VDU queue"
|
|
return &t
|
|
}
|
|
|
|
func (t *traceApplecorn) inspect() {
|
|
if !t.a.mmu.altMainRAMActiveRead {
|
|
// We want to trace only the activity on the Acorn memory space
|
|
return
|
|
}
|
|
|
|
pc, sp := t.a.cpu.GetPCAndSP()
|
|
|
|
if pc == 0x8000 {
|
|
activeROM := t.getTerminatedString(applecornRomTitle, 0)
|
|
regA, _, _, _ := t.a.cpu.GetAXYP()
|
|
fmt.Printf("BBC MOS call to $%04x LANGUAGE(ROM=\"%s\", A=%02x)\n", pc, activeROM, regA)
|
|
} else if pc == 0x8003 {
|
|
activeROM := t.getTerminatedString(applecornRomTitle, 0)
|
|
service, _, regY, _ := t.a.cpu.GetAXYP()
|
|
switch service {
|
|
case 4: // OSCLI
|
|
address := t.a.mmu.peekWord(0xf2 + uint16(regY))
|
|
command := t.getTerminatedString(address, 0x0d)
|
|
fmt.Printf("BBC MOS call to $%04x SERVICE_OSCLI(ROM=\"%s\", A=%v, \"%s\")\n",
|
|
pc, activeROM, service, command)
|
|
case 6: // Error
|
|
address := t.a.mmu.peekWord(0xfd)
|
|
faultNumber := t.a.mmu.Peek(address)
|
|
faultMessage := address + 1
|
|
faultString := t.getTerminatedString(faultMessage, 0)
|
|
fmt.Printf("BBC MOS call to $%04x SERVICE_ERROR(ROM=\"%s\", A=%v, #=%v, \"%s\")\n",
|
|
pc, activeROM, service, faultNumber, faultString)
|
|
case 7: // OSBYTE
|
|
pA := t.a.mmu.Peek(0xef)
|
|
pX := t.a.mmu.Peek(0xf0)
|
|
pY := t.a.mmu.Peek(0xf1)
|
|
fmt.Printf("BBC MOS call to $%04x SERVICE_OSBYTE%02x(ROM=\"%s\", A=%v, pX=%02x, pY=%02x)\n",
|
|
pc, pA, activeROM, service, pX, pY)
|
|
case 8: // OSWORD
|
|
pA := t.a.mmu.Peek(0xef)
|
|
fmt.Printf("BBC MOS call to $%04x SERVICE_OSWORD%02x(ROM=\"%s\", A=%v)\n",
|
|
pc, pA, activeROM, service)
|
|
case 9: // *HELP
|
|
address := t.a.mmu.peekWord(0xf2 + uint16(regY))
|
|
command := t.getTerminatedString(address, 0x0d)
|
|
fmt.Printf("BBC MOS call to $%04x SERVICE_HELP(ROM=\"%s\", A=%v, \"%s\")\n",
|
|
pc, activeROM, service, command)
|
|
default:
|
|
fmt.Printf("BBC MOS call to $%04x SERVICE(ROM=\"%s\", A=%v)\n",
|
|
pc, activeROM, service)
|
|
|
|
}
|
|
}
|
|
|
|
inKernel := pc >= applecornKernelStart
|
|
if !t.wasInKernel && inKernel && pc >= applecornTraceAreaStart {
|
|
regA, regX, regY, _ := t.a.cpu.GetAXYP()
|
|
|
|
s := "UNKNOWN"
|
|
skip := false
|
|
|
|
// Page 2 vectors
|
|
switch pc {
|
|
case t.vector(0x0208): // OSCLI vector
|
|
pc = 0xfff7
|
|
case t.vector(0x020A): // OSBYTE vector
|
|
pc = 0xfff4
|
|
case t.vector(0x020C): // OSWORD vector
|
|
pc = 0xfff1
|
|
case t.vector(0x020E): // OSWRCH vector
|
|
pc = 0xffee
|
|
case t.vector(0x0210): // OSRDCH vector
|
|
pc = 0xffe0
|
|
case t.vector(0x0212): // OSFILE vector
|
|
pc = 0xffdd
|
|
case t.vector(0x0214): // OSARGS vector
|
|
pc = 0xffda
|
|
case t.vector(0x0216): // OSBGET vector
|
|
pc = 0xffd7
|
|
case t.vector(0x0218): // OSBPUT vector
|
|
pc = 0xffd4
|
|
case t.vector(0x021A): // OSGBPB vector
|
|
pc = 0xffd1
|
|
case t.vector(0x021C): // OSFIND vector
|
|
pc = 0xffce
|
|
case t.vector(0xfffe): // BRK vector
|
|
pc = 0xfffe
|
|
}
|
|
|
|
// Jump area
|
|
switch pc {
|
|
case 0xffb9:
|
|
s = "OSDRM(?)"
|
|
case 0xffbc:
|
|
s = "VDUCHR(?)"
|
|
case 0xffbf:
|
|
s = "OSEVEN(?)"
|
|
case 0xffc2:
|
|
s = "OSINIT(?)"
|
|
case 0xffc5:
|
|
s = "OSREAD(?)"
|
|
case 0xffc8:
|
|
ch := ""
|
|
if regA >= 0x20 && regA < 0x7f {
|
|
ch = string(regA)
|
|
}
|
|
s = fmt.Sprintf("OSNWRCH(A=%02x, '%v')", regA, ch)
|
|
skip = t.skipConsole
|
|
case 0xffcb:
|
|
s = "OSNRDCH()"
|
|
skip = t.skipConsole
|
|
case 0xffce:
|
|
if regA == 0 {
|
|
s = fmt.Sprintf("OSFIND('close',HANDLE=%v", regY)
|
|
} else {
|
|
filenameAddress := uint16(regX) + uint16(regY)<<8
|
|
filename := t.getTerminatedString(filenameAddress, 0x0d)
|
|
s = fmt.Sprintf("OSFIND('open',FILE='%s')", filename)
|
|
}
|
|
case 0xffd1:
|
|
s = "OSGBPB(?)"
|
|
case 0xffd4:
|
|
s = "OSBPUT(?)"
|
|
case 0xffd7:
|
|
s = "OSBGET(?)"
|
|
case 0xffda:
|
|
s = "OSARGS(?)"
|
|
s = fmt.Sprintf("OSARGS(HANDLE=%v,A=%02x)", regY, regA)
|
|
case 0xffdd:
|
|
controlBlock := uint16(regX) + uint16(regY)<<8
|
|
filenameAddress := t.a.mmu.peekWord(controlBlock)
|
|
filename := t.getTerminatedString(filenameAddress, 0x0d)
|
|
s = fmt.Sprintf("OSFILE(A=%02x,FILE='%s')", regA, filename)
|
|
case 0xffe0:
|
|
s = "OSRDCH()"
|
|
skip = t.skipConsole
|
|
case 0xffe3: // This fallbacks to OSWRCH
|
|
ch := ""
|
|
if regA >= 0x20 && regA < 0x7f {
|
|
ch = string(regA)
|
|
}
|
|
s = fmt.Sprintf("OSASCI(A=%02x, '%v')", regA, ch)
|
|
skip = t.skipConsole
|
|
case 0xffe7: // This fallbacks to OSWRCH
|
|
s = "OSNEWL()"
|
|
skip = t.skipConsole
|
|
case 0xffec: // This fallbacks to OSWRCH
|
|
skip = t.skipConsole
|
|
s = "OSNECR()"
|
|
case 0xffee:
|
|
ch := ""
|
|
if regA >= 0x20 && regA < 0x7f {
|
|
ch = string(regA)
|
|
}
|
|
s = fmt.Sprintf("OSWRCH(A=%02x, '%v')", regA, ch)
|
|
skip = t.skipConsole
|
|
case 0xfff1:
|
|
xy := uint16(regX) + uint16(regY)<<8
|
|
switch regA {
|
|
case 0: // Read line from input
|
|
lineAddress := t.a.mmu.peekWord(xy)
|
|
maxLength := t.a.mmu.Peek(xy + 2)
|
|
s = fmt.Sprintf("OSWORD('read line';A=%02x,XY=%04x,BUF=%04x,MAX=%02x)", regA, xy, lineAddress, maxLength)
|
|
default:
|
|
s = fmt.Sprintf("OSWORD(A=%02x,XY=%04x)", regA, xy)
|
|
}
|
|
case 0xfff4:
|
|
s = fmt.Sprintf("OSBYTE('%s';A=%02x,X=%02x,Y=%02x)", t.osbyteNames[regA], regA, regX, regY)
|
|
case 0xfff7:
|
|
xy := uint16(regX) + uint16(regY)<<8
|
|
command := t.getTerminatedString(xy, 0x0d)
|
|
s = fmt.Sprintf("OSCLI(\"%s\")", command)
|
|
case 0xfffe:
|
|
address := t.a.mmu.peekWord(0x100+uint16(sp+2)) - 1
|
|
faultNumber := t.a.mmu.Peek(address)
|
|
faultMessage := address + 1
|
|
faultString := t.getTerminatedString(faultMessage, 0)
|
|
s = fmt.Sprintf("BRK(number=%v,message='%s')", faultNumber, faultString)
|
|
}
|
|
|
|
if s == "UNKNOWN" && t.skipConsole {
|
|
// Let's also skip not known calls
|
|
skip = true
|
|
}
|
|
|
|
t.call.api = pc
|
|
t.call.a = regA
|
|
t.call.x = regX
|
|
t.call.y = regY
|
|
t.call.skipLog = skip
|
|
if !skip {
|
|
fmt.Printf("BBC MOS call to $%04x %s ", pc, s)
|
|
}
|
|
}
|
|
|
|
if t.wasInKernel && !inKernel && !t.call.skipLog {
|
|
// Returning from the call
|
|
regA, regX, regY, _ := t.a.cpu.GetAXYP()
|
|
s := ""
|
|
switch t.call.api {
|
|
case 0xfff1: // OSWORD
|
|
cbAddress := uint16(t.call.x) + uint16(t.call.y)<<8
|
|
switch t.call.a {
|
|
case 0: // Read line from input
|
|
lineAddress := t.a.mmu.peekWord(cbAddress)
|
|
line := t.getTerminatedString(lineAddress, '\r')
|
|
s = fmt.Sprintf(",line='%s',cbaddress=%04x,lineAddress=%04x", line, cbAddress, lineAddress)
|
|
}
|
|
}
|
|
|
|
fmt.Printf("=> (A=%02x,X=%02x,Y=%02x%s)\n", regA, regX, regY, s)
|
|
}
|
|
|
|
t.wasInKernel = inKernel
|
|
}
|
|
|
|
func (t *traceApplecorn) getTerminatedString(address uint16, terminator uint8) string {
|
|
s := ""
|
|
for {
|
|
ch := t.a.mmu.Peek(address)
|
|
if ch == terminator {
|
|
break
|
|
}
|
|
s += string(ch)
|
|
address++
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (t *traceApplecorn) vector(address uint16) uint16 {
|
|
return t.a.mmu.peekWord(address)
|
|
}
|