Breakpoints: SWI osBreak, and s refuses a read only bank
A breakpoint that shows every register as the program had them, waits for a key, and carries on. NOTHING IS OVERWRITTEN, and that is the design rather than a shortcut. A breakpoint poked into a running program has to replace an instruction, and putting that instruction back in order to continue is the same act as disarming the breakpoint; firing a second time would mean stepping over the restored instruction and putting the breakpoint back behind it, and this machine cannot step a single instruction. SWI is two bytes, dispatches through a vector, and its frame already holds the address after it, so RETI resumes at the next instruction with nothing to restore and nothing to re-arm. It fires every time it is reached. The price is that a breakpoint is part of the program: a build with them in has different addresses from a build without. That is the bargain every machine with a break instruction makes. Every value shown comes out of the frame rather than the registers, because by the time the handler runs the registers are the handler's. Apps/Break.asm stops twice so that the second stop is checked as well as the first. Also here, found by the test that came with it: the monitor's s wrote into whichever bank was selected, and bank 2 is the controller's own table, published read only. Writing to it was refused, and a refusal nobody catches stops the machine - so selecting the bank table to look at it and then typing s killed the session. bankPresent now keeps the whole flags byte and s declines. The recorded output of cosmosMonitor had contained that crash, having been blessed without being read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b36d438132
commit
5fd995aa62
@@ -0,0 +1,56 @@
|
||||
; Stopping a program to look at it.
|
||||
;
|
||||
; SWI osBreak shows every register as this program had them, waits for a key, and carries
|
||||
; on. It is two bytes and it fires every time it is reached.
|
||||
;
|
||||
; WHY IT IS AN INSTRUCTION RATHER THAN SOMETHING SET FROM OUTSIDE. A breakpoint that was
|
||||
; poked into a running program would have to overwrite an instruction, and then putting that
|
||||
; instruction back in order to continue is the same act as disarming the breakpoint. Firing
|
||||
; a second time would mean stepping over the restored instruction and putting the breakpoint
|
||||
; back behind it, and this machine cannot step one instruction. Nothing is overwritten here,
|
||||
; so there is nothing to restore and nothing to re-arm.
|
||||
;
|
||||
; The price is that it is part of the program. A build with breakpoints in it has different
|
||||
; addresses from a build without, which is the same bargain every machine makes that has a
|
||||
; break instruction.
|
||||
;
|
||||
; Correct output is two stops, showing A and B changing between them, and the addresses of
|
||||
; the two SWIs.
|
||||
|
||||
#Include services.asm
|
||||
|
||||
#Program
|
||||
|
||||
#Base 0x2000
|
||||
|
||||
start:
|
||||
SETD.0 Banner
|
||||
SWI osPrintString
|
||||
|
||||
INIA 0d17
|
||||
INIB 0d34
|
||||
SETD.0 Marker
|
||||
SWI osBreak
|
||||
|
||||
; Something for the second stop to show as different.
|
||||
INIA 0d68
|
||||
INIB 0d85
|
||||
SETD.0 Banner
|
||||
SWI osBreak
|
||||
|
||||
SETD.0 DoneText
|
||||
SWI osPrintString
|
||||
SWI osExit
|
||||
|
||||
#Data
|
||||
|
||||
#Base 0x1000
|
||||
|
||||
Banner:
|
||||
"two stops, and what the registers were at each
|
||||
"
|
||||
Marker:
|
||||
"marker"
|
||||
DoneText:
|
||||
"carried on to the end
|
||||
"
|
||||
@@ -985,6 +985,127 @@ serviceNoDisk:
|
||||
STA.2
|
||||
RETI
|
||||
|
||||
; A breakpoint. Shows every register as the interrupted program had them, waits for a key,
|
||||
; and returns as though nothing happened.
|
||||
;
|
||||
; EVERY VALUE COMES OUT OF THE FRAME, not out of the registers, because by the time this
|
||||
; runs the registers belong to the handler. The frame is what the program had, and RETI is
|
||||
; going to give it all back, so what is shown is what will be resumed with.
|
||||
;
|
||||
; DP3 holds the frame throughout. It survives a CALL, and console.asm promises not to
|
||||
; disturb it, which is what lets the printing routines be used between one field and the
|
||||
; next. The Stack Pointer comes back to the same place after a balanced call, so the frame
|
||||
; stays where it was found.
|
||||
;
|
||||
; +1 Status +2 Q +3 A +4 B +5 DP3 +7 DP2 +9 DP1 +11 DP0 +13 where it resumes
|
||||
handleBreak:
|
||||
MVSD.3
|
||||
|
||||
; Where it broke, which is two before where it resumes: the SWI and the vector it names.
|
||||
SETD.0 BreakText
|
||||
CALL printString
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d13
|
||||
LDA.0
|
||||
PSHA ; The high half, while the low one is worked on.
|
||||
INCD.0
|
||||
LDA.0
|
||||
INIB 0d2
|
||||
CCF
|
||||
SUB ; Two back from where it resumes: the SWI and the vector it names.
|
||||
MVQA
|
||||
POPB
|
||||
BRC breakBorrowed ; It borrowed, so the high half comes down by one.
|
||||
BRI breakAddress
|
||||
breakBorrowed:
|
||||
DECB
|
||||
breakAddress:
|
||||
PSHA ; low
|
||||
PSHB ; high
|
||||
POPA
|
||||
CALL printByteHex
|
||||
POPA
|
||||
CALL printByteHex
|
||||
CALL newLine
|
||||
|
||||
SETD.0 ARegText
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d3
|
||||
CALL breakByte
|
||||
SETD.0 BRegText
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d4
|
||||
CALL breakByte
|
||||
SETD.0 QRegText
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d2
|
||||
CALL breakByte
|
||||
SETD.0 SRegText
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d1
|
||||
CALL breakByte
|
||||
CALL newLine
|
||||
|
||||
SETD.0 DP0Text
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d11
|
||||
CALL breakWord
|
||||
SETD.0 DP1Text
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d9
|
||||
CALL breakWord
|
||||
SETD.0 DP2Text
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d7
|
||||
CALL breakWord
|
||||
SETD.0 DP3Text
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d5
|
||||
CALL breakWord
|
||||
CALL newLine
|
||||
|
||||
; Anything typed carries on. Reading the data port waits however the console is set, which
|
||||
; is one key if the program asked for key mode and a whole line if it did not - and either
|
||||
; way it is the program's own console being borrowed for a moment.
|
||||
SETD.0 ResumeText
|
||||
CALL printString
|
||||
INA 0x00
|
||||
CALL newLine
|
||||
RETI
|
||||
|
||||
; DP0 names a field and DP1 points at it in the frame. The caller does the stepping, with
|
||||
; DPUP and a number written into the program, because a routine cannot hand a pointer back:
|
||||
; CALL saves DP0 to DP2 and RET puts them back, so a walk done in here would be undone on
|
||||
; the way out. Written that way first, and every field showed the frame's first byte.
|
||||
breakByte:
|
||||
CALL printString
|
||||
LDA.1
|
||||
CALL printByteHex
|
||||
INIA 0x20
|
||||
OUTA 0x00
|
||||
RET
|
||||
|
||||
; The same for the two byte fields, most significant first the way the frame holds them.
|
||||
breakWord:
|
||||
CALL printString
|
||||
LDA.1
|
||||
CALL printByteHex
|
||||
INCD.1
|
||||
LDA.1
|
||||
CALL printByteHex
|
||||
INIA 0x20
|
||||
OUTA 0x00
|
||||
RET
|
||||
|
||||
; A and B together are a number. Prints it in decimal without leading zeroes, which covers
|
||||
; a line number and a byte count both, so there is no need for one service each.
|
||||
handlePrintNumber:
|
||||
@@ -1279,6 +1400,16 @@ dumpNoBank:
|
||||
; The cursor is left alone. Somebody poking a byte is usually looking at something else, and
|
||||
; having the address they were reading move underneath them would be a poor reward.
|
||||
doSet:
|
||||
; A bank can be present and still refuse to be written: the controller's own table is
|
||||
; published read only, and writing to it is refused. A refusal nobody catches stops the
|
||||
; machine, which is a poor answer to somebody looking around with b and then typing s.
|
||||
CALL bankPresent
|
||||
SETD.0 BankFlags
|
||||
LDA.0
|
||||
INIB 0x02 ; The read only bit of that bank's record.
|
||||
AND
|
||||
BNQ setReadOnly
|
||||
|
||||
SETD.1 TextRest
|
||||
LDD.0.1
|
||||
CALL textHexWord
|
||||
@@ -1309,6 +1440,12 @@ setByte:
|
||||
POPD.0
|
||||
BRI setByte
|
||||
|
||||
setReadOnly:
|
||||
SETD.0 ReadOnlyText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
setWhat:
|
||||
SETD.0 SetUsage
|
||||
CALL printString
|
||||
@@ -1694,6 +1831,8 @@ bankPresent:
|
||||
LDA.0
|
||||
OUTA 0xE2
|
||||
INA 0xE9 ; The flags byte of that bank's record.
|
||||
SETD.0 BankFlags
|
||||
STA.0 ; Kept whole, since present is not the only thing it says.
|
||||
INIB 0x01
|
||||
AND
|
||||
RET
|
||||
@@ -1788,6 +1927,26 @@ NothingLoaded:
|
||||
Finished:
|
||||
"finished"
|
||||
|
||||
BreakText:
|
||||
"break at "
|
||||
ARegText:
|
||||
"A "
|
||||
BRegText:
|
||||
"B "
|
||||
QRegText:
|
||||
"Q "
|
||||
SRegText:
|
||||
"S "
|
||||
DP0Text:
|
||||
"DP0 "
|
||||
DP1Text:
|
||||
"DP1 "
|
||||
DP2Text:
|
||||
"DP2 "
|
||||
DP3Text:
|
||||
"DP3 "
|
||||
ResumeText:
|
||||
"press a key "
|
||||
MonitorPrompt:
|
||||
"* "
|
||||
UnknownText:
|
||||
@@ -1813,6 +1972,8 @@ BankIs:
|
||||
"bank "
|
||||
SetUsage:
|
||||
"s <address> <byte> <byte> ..."
|
||||
ReadOnlyText:
|
||||
"that bank will not be written"
|
||||
GoUsage:
|
||||
"g <address>"
|
||||
DirName:
|
||||
@@ -1886,6 +2047,8 @@ DumpCount:
|
||||
0x00
|
||||
BankWas:
|
||||
0x00
|
||||
BankFlags:
|
||||
0x00
|
||||
ShowAsCode:
|
||||
0x00
|
||||
DumpRecord:
|
||||
@@ -2006,4 +2169,5 @@ CommandLine:
|
||||
osFileDelete handleFileDelete
|
||||
osFileRename handleFileRename
|
||||
osPrintNumber handlePrintNumber
|
||||
osBreak handleBreak
|
||||
Device 0x20 diskDone
|
||||
|
||||
@@ -51,3 +51,17 @@
|
||||
; number, in decimal, without leading zeroes. A and B together, so one service covers both
|
||||
; a line number and a byte count and there is no need for two.
|
||||
osPrintNumber 0d24
|
||||
|
||||
; ---- Stopping to look ----
|
||||
;
|
||||
; A breakpoint. Put SWI osBreak anywhere in a program and the system shows every register as
|
||||
; the program had them, waits for a key, and carries on.
|
||||
;
|
||||
; NOTHING IS OVERWRITTEN, which is what makes this simple. A breakpoint that replaced an
|
||||
; instruction would have to put it back to continue, and putting it back disarms the
|
||||
; breakpoint - so firing twice would need the instruction to be stepped over and the
|
||||
; breakpoint replaced behind it, and this machine has no way to step one instruction. An SWI
|
||||
; costs two bytes of the program and fires for ever, because there was never anything to
|
||||
; restore. The price is that it is part of the program: a build with breakpoints in it has
|
||||
; different addresses from one without.
|
||||
osBreak 0d25
|
||||
|
||||
Reference in New Issue
Block a user