Tell the person where it hurts
A fault stopped the machine and printed a line to standard error. On a terminal that is a diagnosis. Behind a window it is a frozen picture and no reason at all, because the message went somewhere nobody was looking - the machine looked hung and was not. It had stopped, and said so invisibly. CosmOS catches all five faults now and says what happened on the screen, with the address, in red. A FAULT ENDS THE PROGRAM, NOT THE MACHINE. That is the answer to "carry on or start again", and it is not a compromise: a bare RETI from most of these meets the instruction that failed and fails again, so carrying on was never on offer. But the machine is almost never what is broken. Everything the shell puts back when a program exits - the Stack, its vectors, the drive, the working directory, the console, the screen - is exactly what wants putting back after one dies, so the handler sets a status and joins handleExit. You are back at the prompt, and the program is recorded as having STOPPED rather than finished, because saying "finished" under a red fault message would be the shell contradicting itself. A fault below where programs load is the system's own, and there is nothing to go back to. That one says so and stops. THE SCREEN GOES BACK TO A MODE TEXT CAN BE SEEN IN, and that is the part that matters rather than the part that is prettiest. A program that faulted in bitmap mode left the console with no text rows, so it draws nothing at all: the message would be perfectly correct and completely invisible, which is the one thing it must never be. Two palette entries go back for the same reason, since a program that wrote its own colours can leave every ink the same as every paper. Only the two the message needs, so the rest of what the program chose is left alone. Both halves are checked by looking at the PICTURE, because the serial line was never where the problem was. Crash blind ruins the palette and drops into bitmap mode before it faults; without the mode the screen comes back 320 by 200 with nothing on it, and without the palette it is the right size with the message present and unreadable. Each break loses the red on its own. Crash is also a program worth having: it breaks in whichever of the five ways you name, so a fault screen can be looked at without having written a bug first. Two things found on the way: The native assembler keeps its OWN copy of the reserved vector names, so it did not know NoHandler or NoDevice and built a cosmos.bin that differed from the host assembler's. Caught by native.sh, which is exactly the drift that test exists for. And cosmosMonitor had dead input. It assembles code into 0x8000 and runs it, and that code faults - which used to kill the machine, so everything after it in the file had never run. It runs now, and the recording grew by sixty lines of monitor session that had been unreachable since the day the fault was put there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
000a6d39cb
commit
fd9c4c75f8
@@ -0,0 +1,145 @@
|
||||
; Breaks on purpose, in whichever of the four ways it is asked for.
|
||||
;
|
||||
; Every one of these used to stop the machine and print a line to a standard error that
|
||||
; nobody behind a window is looking at, so the machine appeared to hang. The system catches
|
||||
; all of them now and says what happened and where, and this is what says so - and what a
|
||||
; person can run when they want to see the fault screen without having written a bug first.
|
||||
;
|
||||
; Crash opcode a byte in the middle of the code that does not decode
|
||||
; Crash service a SWI naming a service the system does not implement
|
||||
; Crash bank a transfer out of a bank that has nothing registered in it
|
||||
; Crash device asking the console to interrupt, with no handler installed
|
||||
; Crash blind the same bad byte, but from a screen with nowhere to print at all
|
||||
;
|
||||
; The last one is the odd one: it is not something this program does wrong, it is something
|
||||
; it fails to have done. The interrupt arrives from outside once the console has anything to
|
||||
; say, and with no input it is the END of input that arrives.
|
||||
;
|
||||
; Written by Anachronaut
|
||||
|
||||
#Include services.asm
|
||||
|
||||
#Program
|
||||
|
||||
#Base 0x4000
|
||||
|
||||
start:
|
||||
SETD.0 Argument
|
||||
INIB 0d15
|
||||
SWI osArgument
|
||||
|
||||
SETD.0 Argument
|
||||
SETD.1 WordOpcode
|
||||
CALL textSame
|
||||
BRQ crashOpcode
|
||||
SETD.0 Argument
|
||||
SETD.1 WordService
|
||||
CALL textSame
|
||||
BRQ crashService
|
||||
SETD.0 Argument
|
||||
SETD.1 WordBank
|
||||
CALL textSame
|
||||
BRQ crashBank
|
||||
SETD.0 Argument
|
||||
SETD.1 WordDevice
|
||||
CALL textSame
|
||||
BRQ crashDevice
|
||||
SETD.0 Argument
|
||||
SETD.1 WordBlind
|
||||
CALL textSame
|
||||
BRQ crashBlind
|
||||
|
||||
SETD.0 Usage
|
||||
SWI osPrintString
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
INIA 0x01
|
||||
SWI osExit
|
||||
|
||||
; ---- The case the fault screen exists for ----
|
||||
;
|
||||
; Bitmap mode has no text rows, so the console draws NOTHING there: a program that faults
|
||||
; here leaves the system with a message to print and nowhere to print it. Putting the screen
|
||||
; back into a mode that has characters in it is the difference between a diagnosis and a
|
||||
; machine that appears to have hung.
|
||||
crashBlind:
|
||||
; ---- And with the colours ruined as well ----
|
||||
;
|
||||
; A known mode is only half a known screen. This makes the ink of attribute one the same as
|
||||
; its paper, which is what a program that wrote its own palette can easily leave behind -
|
||||
; and a message printed into that is perfectly present and completely invisible.
|
||||
INIA 0d4
|
||||
OUTA 0xE3
|
||||
INIA 0x30
|
||||
OUTA 0xE2
|
||||
INIA 0x03
|
||||
OUTA 0xE8 ; Video memory as bank four.
|
||||
|
||||
INIA 0d4
|
||||
OUTA 0xE3
|
||||
INIA 0xFC
|
||||
OUTA 0xE4
|
||||
INIA 0x40
|
||||
OUTA 0xE5 ; 0xFC40, the two entries attribute one draws from.
|
||||
RSTA
|
||||
INIB 0d8
|
||||
crashBlindWipe:
|
||||
OUTA 0xE9
|
||||
DECB
|
||||
BNB crashBlindWipe ; Both of them black, ink and paper alike.
|
||||
|
||||
INIA 0x02
|
||||
OUTA 0x31
|
||||
|
||||
crashOpcode:
|
||||
0x00 ; Not an instruction, and never will be.
|
||||
|
||||
crashService:
|
||||
SWI 0d40 ; Forty is nobody's.
|
||||
|
||||
crashBank:
|
||||
INIA 0d9 ; Nothing is registered there.
|
||||
OUTA 0xE0
|
||||
RSTA
|
||||
OUTA 0xE1
|
||||
OUTA 0xE2
|
||||
INIA 0d1
|
||||
OUTA 0xE3 ; Into Data Memory.
|
||||
RSTA
|
||||
OUTA 0xE4
|
||||
OUTA 0xE5
|
||||
OUTA 0xE6
|
||||
INIA 0d16
|
||||
OUTA 0xE7
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Blit, from a bank that is not there.
|
||||
|
||||
crashDevice:
|
||||
INIA 0x02 ; Interrupt me when the console has something to say.
|
||||
OUTA 0x02
|
||||
SIF
|
||||
crashWait:
|
||||
; Never touches the console, so whatever happens next came from outside.
|
||||
BRI crashWait
|
||||
|
||||
#Data
|
||||
|
||||
#Base 0x2000
|
||||
|
||||
WordOpcode:
|
||||
"opcode"
|
||||
WordService:
|
||||
"service"
|
||||
WordBank:
|
||||
"bank"
|
||||
WordDevice:
|
||||
"device"
|
||||
WordBlind:
|
||||
"blind"
|
||||
Usage:
|
||||
"Crash opcode | service | bank | device | blind"
|
||||
|
||||
Argument:
|
||||
#Reserve 0d16
|
||||
|
||||
#Include text.asm
|
||||
@@ -2258,7 +2258,7 @@ ReservedLeft:
|
||||
ReservedWalk:
|
||||
0x00 0x00
|
||||
ReservedCount:
|
||||
0d5
|
||||
0d7
|
||||
VecHandlerName:
|
||||
#Reserve 0d23
|
||||
|
||||
@@ -2285,6 +2285,12 @@ ReservedNames:
|
||||
"BankFault"
|
||||
#Reserve 0d5
|
||||
0d4
|
||||
"NoHandler"
|
||||
#Reserve 0d5
|
||||
0d5
|
||||
"NoDevice"
|
||||
#Reserve 0d6
|
||||
0d6
|
||||
|
||||
ProgPut:
|
||||
0x00 0x00
|
||||
|
||||
@@ -599,8 +599,48 @@ from every assembly file in it. Several are old programs written for the bare ma
|
||||
| Wander | Goes to the directory it is given and reads a file there by a bare name. The only thing that moves the machine from inside a program, and so the only thing that can check the shell puts the working directory back afterwards. |
|
||||
| More | A forward-only pager. Space advances a screen, Return one line, and q stops. |
|
||||
| Press | Says what the console handed it, in hexadecimal and by name. It reads a line and then keys, because the keys that are not characters are dropped in line mode and delivered in key mode, and both halves of that rule want showing. |
|
||||
| Crash | Breaks on purpose, in whichever of the four ways the system now catches, so that a fault screen can be looked at without having written a bug first. |
|
||||
| Mode | Forty columns or eighty, whichever the screen is not in. Ten instructions and no data at all, which is the point of it: it is the smallest shape a loadable program can take, and the loader used to stop the machine dead on one. |
|
||||
|
||||
### When Something Goes Wrong:
|
||||
|
||||
A fault used to stop the machine and print a line to whatever was behind it. On a terminal
|
||||
that is a diagnosis; behind a window it is a frozen picture and no reason at all, because the
|
||||
message went to a standard error nobody was looking at. **The machine looked hung and was
|
||||
not** - it had stopped, and said so somewhere invisible.
|
||||
|
||||
CosmOS catches all five faults the machine can raise and says what happened on the screen:
|
||||
|
||||
```
|
||||
> Crash opcode
|
||||
that byte is not an instruction, at 404E
|
||||
A 00 B 0F Q 00
|
||||
the program was stopped
|
||||
>
|
||||
```
|
||||
|
||||
**A fault ends the program, not the machine.** That is not a compromise. A bare `RETI` from
|
||||
most faults meets the very instruction that failed and fails again, so carrying on is not on
|
||||
offer - but the machine is almost never what is broken. Everything the shell puts back when a
|
||||
program exits, which is the Stack, any vectors it installed, the drive, the working directory,
|
||||
the console and the screen, is exactly what wants putting back after one dies. So you are
|
||||
returned to the prompt, and the program is recorded as having stopped rather than finished.
|
||||
|
||||
A fault *below* where programs load is the system's own code, and there is nothing to go back
|
||||
to. That one says so and stops.
|
||||
|
||||
**The screen is put back into a mode text can be seen in first**, and that is the part that
|
||||
matters most rather than the part that is prettiest. A program that faulted in bitmap mode
|
||||
left the console with no text rows at all, so it draws nothing - the message about what went
|
||||
wrong would be perfectly correct and completely invisible. Two palette entries are rewritten
|
||||
for the same reason, since a program that wrote its own colours can leave every ink the same
|
||||
as every paper. Only the two the message needs are touched; the rest of what the program
|
||||
chose is left alone.
|
||||
|
||||
The address is where it happened, and it is exact. For a missing service or a device with
|
||||
nobody listening it is the address *after* the instruction, because those two are the faults
|
||||
where the instruction did dispatch and it was the entry that was empty.
|
||||
|
||||
### The Monitor:
|
||||
|
||||
The monitor is **part of the shell**, not a program the shell loads, and that is the whole reason it works. A loaded program occupies the one place a loaded program goes, so a monitor that was an application could never look at any other application: loading the thing you wanted to inspect would replace the thing doing the inspecting.
|
||||
|
||||
@@ -1260,6 +1260,204 @@ historyAddDone:
|
||||
STA.1
|
||||
RET
|
||||
|
||||
; ---- When something goes wrong that nothing can carry on past ----
|
||||
;
|
||||
; A fault used to stop the machine and print a line to whatever was behind it. On a terminal
|
||||
; that is a diagnosis; behind a window it is a frozen picture and no reason at all, because
|
||||
; the message goes to a standard error nobody is looking at. The machine looked hung and was
|
||||
; not - it had stopped, and said so somewhere invisible.
|
||||
;
|
||||
; So the system catches all five and says it on the screen instead.
|
||||
;
|
||||
; ---- A fault ends the PROGRAM, not the machine ----
|
||||
;
|
||||
; That is the answer to "carry on or start again", and it is not a compromise: a bare RETI
|
||||
; from most of these meets the very instruction that failed and fails again, so carrying on
|
||||
; is not on offer. But the machine is almost never what is broken. Everything the shell puts
|
||||
; back when a program exits - the Stack, the vectors it installed, the drive, the working
|
||||
; directory, the console, the screen - is exactly what wants putting back after one dies, so
|
||||
; a fault in a loaded program joins handleExit and you are back at the prompt.
|
||||
;
|
||||
; A fault BELOW where programs load is the system's own, and there is nothing to go back to.
|
||||
; That one says so and stops.
|
||||
;
|
||||
; ---- What a handler must not do ----
|
||||
;
|
||||
; Fault. There is no double fault rule on this machine: a handler that commits the fault it
|
||||
; was called about is called again, forever, and each time costs another frame of Stack. So
|
||||
; nothing below asks for a service, touches a disk, or reaches anything that can refuse.
|
||||
faultBadOpcode:
|
||||
SETD.0 FaultOpcode
|
||||
BRI faultPlain
|
||||
faultGuard:
|
||||
SETD.0 FaultGuard
|
||||
BRI faultPlain
|
||||
faultBank:
|
||||
SETD.0 FaultBank
|
||||
faultPlain:
|
||||
; The Stack Pointer names the frame, and it has to be taken before anything pushes. Nothing
|
||||
; above here does: a SETD and a branch move no Stack.
|
||||
MVSD.3
|
||||
RSTA
|
||||
SETD.1 FaultNumbered
|
||||
STA.1
|
||||
BRI faultSay
|
||||
|
||||
faultNoHandler:
|
||||
SETD.0 FaultNoHandler
|
||||
BRI faultNumbered
|
||||
faultNoDevice:
|
||||
SETD.0 FaultNoDevice
|
||||
faultNumbered:
|
||||
MVSD.3
|
||||
; These two are the only faults that say WHICH one, and the machine hands that over in Q -
|
||||
; the one thing a handler here is given in a register. Kept at once, before printing can
|
||||
; disturb it.
|
||||
MVQA
|
||||
SETD.1 FaultNumber
|
||||
STA.1
|
||||
INIA 0x01
|
||||
SETD.1 FaultNumbered
|
||||
STA.1
|
||||
|
||||
faultSay:
|
||||
; The screen before the words. There is no use saying any of this somewhere it cannot be
|
||||
; read, and a program that faulted may have left the screen with nowhere to put a letter.
|
||||
CALL faultScreen
|
||||
|
||||
CALL printString
|
||||
SETD.1 FaultNumbered
|
||||
LDA.1
|
||||
BRA faultWhere
|
||||
SETD.1 FaultNumber
|
||||
LDA.1
|
||||
CALL printByteHex
|
||||
faultWhere:
|
||||
SETD.0 FaultAt
|
||||
CALL printString
|
||||
CALL faultAddress
|
||||
CALL newLine
|
||||
|
||||
; The registers as the frame kept them, which is what they were when it happened.
|
||||
SETD.0 FaultRegisters
|
||||
CALL printString
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d3
|
||||
LDA.0
|
||||
CALL printByteHex
|
||||
SETD.0 FaultB
|
||||
CALL printString
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d4
|
||||
LDA.0
|
||||
CALL printByteHex
|
||||
SETD.0 FaultQ
|
||||
CALL printString
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d2
|
||||
LDA.0
|
||||
CALL printByteHex
|
||||
CALL newLine
|
||||
|
||||
; ---- Whose fault it was ----
|
||||
;
|
||||
; Where it happened says which, and one byte of the address decides it: a loaded program
|
||||
; begins at 0x4000 and everything below that is the system.
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d13
|
||||
LDA.0
|
||||
INIB 0x40
|
||||
CCF
|
||||
SUB
|
||||
BRC faultInSystem
|
||||
|
||||
; A program that will not be carrying on. handleExit does all of the putting back, and
|
||||
; takes the status the program is deemed to have stopped with in A.
|
||||
INIA 0x01
|
||||
SETD.1 FaultStopped
|
||||
STA.1
|
||||
INIA 0xFF
|
||||
BRI handleExit
|
||||
|
||||
faultInSystem:
|
||||
; Nothing to go back to: the shell IS what faulted, and its Stack, its variables and its
|
||||
; place in its own code are all suspect. Saying so and stopping is the only honest answer,
|
||||
; and it is a great deal better than the frozen picture this used to be.
|
||||
SETD.0 FaultSystem
|
||||
CALL printString
|
||||
CALL newLine
|
||||
HALT
|
||||
|
||||
; The two bytes of the address in the frame, printed high half first.
|
||||
faultAddress:
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d13
|
||||
LDA.0
|
||||
CALL printByteHex
|
||||
INCD.0
|
||||
LDA.0
|
||||
CALL printByteHex
|
||||
RET
|
||||
|
||||
; ---- A screen this can be read on ----
|
||||
;
|
||||
; "Put the screen in a known mode" is not tidiness. A program that left the screen in bitmap
|
||||
; mode left nowhere to draw a character at all - the console draws nothing when there are no
|
||||
; text rows - so without this, the message about what went wrong is invisible, which is the
|
||||
; one thing it must never be.
|
||||
faultScreen:
|
||||
INIA 0x01
|
||||
OUTA 0x31 ; Eighty columns of text, whatever was being used.
|
||||
|
||||
; And the whole view back to the corner. A scrolled origin or a fraction of a cell puts
|
||||
; every character somewhere other than where it says it is.
|
||||
RSTA
|
||||
OUTA 0x34
|
||||
OUTA 0x36
|
||||
OUTA 0x37
|
||||
OUTA 0x38
|
||||
|
||||
; ---- Colours it can be read in ----
|
||||
;
|
||||
; A known mode is only half of a known screen: a program that wrote its own palette may
|
||||
; have left every ink the same as every paper. Attribute one draws in palette entries 16
|
||||
; and 17, so those two are written and the rest of the program's colours are left alone -
|
||||
; there is nothing to be gained here by taking away more than is needed.
|
||||
CALL screenBank
|
||||
INIA 0d4
|
||||
OUTA 0xE3
|
||||
INIA 0xFC
|
||||
OUTA 0xE4
|
||||
INIA 0x40
|
||||
OUTA 0xE5 ; 0xFC40, which is entry sixteen.
|
||||
|
||||
RSTA
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9 ; Black paper.
|
||||
INIA 0xD0
|
||||
OUTA 0xE9
|
||||
INIA 0x40
|
||||
OUTA 0xE9
|
||||
INIA 0x38
|
||||
OUTA 0xE9
|
||||
RSTA
|
||||
OUTA 0xE9 ; Red ink, the same red the machine wakes up with.
|
||||
|
||||
INIA 0x01
|
||||
OUTA 0x06 ; And draw in it.
|
||||
|
||||
; A console that can be printed to at all: line mode, a cursor, nothing interrupting.
|
||||
INIA 0x04
|
||||
OUTA 0x02
|
||||
RET
|
||||
|
||||
; ---- A command that did not work ----
|
||||
;
|
||||
; The one place a failure is recorded, so that the thing reading lines out of a file can
|
||||
@@ -4010,7 +4208,26 @@ exitWorked:
|
||||
INIA 0x04
|
||||
OUTA 0x02
|
||||
|
||||
; The ink too, and for the same reason as the cursor: a program that chose a colour is not
|
||||
; around to put it back, and neither is one the fault screen printed for. The shell owns
|
||||
; the prompt, so the shell is what makes sure it is readable.
|
||||
RSTA
|
||||
OUTA 0x06
|
||||
|
||||
; ---- Finished, or stopped ----
|
||||
;
|
||||
; A program that faulted did not finish, and saying so would be the shell's own word
|
||||
; against what the fault screen just said in red immediately above it.
|
||||
SETD.1 FaultStopped
|
||||
LDA.1
|
||||
BRA exitFinished
|
||||
RSTA
|
||||
STA.1 ; Cleared, so the next program is not blamed for this one.
|
||||
SETD.0 Stopped
|
||||
BRI exitSay
|
||||
exitFinished:
|
||||
SETD.0 Finished
|
||||
exitSay:
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
@@ -5259,6 +5476,28 @@ LoadedText:
|
||||
"loaded, starting at "
|
||||
NothingLoaded:
|
||||
"nothing is loaded"
|
||||
Stopped:
|
||||
"the program was stopped"
|
||||
FaultOpcode:
|
||||
"that byte is not an instruction"
|
||||
FaultGuard:
|
||||
"a write into a fenced off part of a bank"
|
||||
FaultBank:
|
||||
"a bank that is not there, or an address past its end"
|
||||
FaultNoHandler:
|
||||
"nothing is installed at service "
|
||||
FaultNoDevice:
|
||||
"nothing is installed for the device on port "
|
||||
FaultAt:
|
||||
", at "
|
||||
FaultRegisters:
|
||||
" A "
|
||||
FaultB:
|
||||
" B "
|
||||
FaultQ:
|
||||
" Q "
|
||||
FaultSystem:
|
||||
"that was the system itself, so there is nowhere to carry on from. Start the machine again."
|
||||
Finished:
|
||||
"finished"
|
||||
|
||||
@@ -5682,6 +5921,19 @@ EditWalkBack:
|
||||
EditWasControl:
|
||||
0x00
|
||||
|
||||
; ---- What the fault screen is saying ----
|
||||
;
|
||||
; Whether this cause names an entry as well as itself, and which one. Only the two faults
|
||||
; about a missing handler do, and for those the machine puts the number in Q.
|
||||
FaultNumbered:
|
||||
0x00
|
||||
FaultNumber:
|
||||
0x00
|
||||
; Set when a program is being ended by a fault rather than by asking. handleExit reads it to
|
||||
; choose its last word, and clears it.
|
||||
FaultStopped:
|
||||
0x00
|
||||
|
||||
; Whether the line being read is one to remember. The shell's are; a program's are not.
|
||||
EditKeepHistory:
|
||||
0x00
|
||||
@@ -5726,6 +5978,11 @@ CommandLine:
|
||||
#Vectors
|
||||
|
||||
Boot boot
|
||||
BadOpcode faultBadOpcode
|
||||
GuardViolation faultGuard
|
||||
BankFault faultBank
|
||||
NoHandler faultNoHandler
|
||||
NoDevice faultNoDevice
|
||||
osPrintString handlePrintString
|
||||
osReadLine handleReadLine
|
||||
osExit handleExit
|
||||
|
||||
Reference in New Issue
Block a user