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
Executable
+277
@@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env bash
|
||||
# Checks SplitDisk against the SBFS format.
|
||||
#
|
||||
# The tool and the SplitBit side are two implementations of one written specification,
|
||||
# and nothing but that document keeps them the same. This checks the host half on its
|
||||
# own: that a file put onto a disk comes back off it byte for byte, that the sizes which
|
||||
# exercise the block and tail arithmetic all survive, and that the things the format
|
||||
# says cannot happen are refused rather than half done.
|
||||
#
|
||||
# Written by Anachronaut
|
||||
|
||||
set -u
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
TOOL="$ROOT/SplitDisk"
|
||||
WORK="$ROOT/Tests/build/disk"
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
FAILED_NAMES=()
|
||||
|
||||
GREEN=$'\033[32m'; RED=$'\033[31m'; RESET=$'\033[0m'
|
||||
[ -t 1 ] || { GREEN=""; RED=""; RESET=""; }
|
||||
|
||||
check() {
|
||||
local name="$1"; shift
|
||||
if "$@" >/dev/null 2>&1; then
|
||||
PASS=$((PASS + 1)); printf " [%sok %s] %s\n" "$GREEN" "$RESET" "$name"
|
||||
else
|
||||
FAIL=$((FAIL + 1)); FAILED_NAMES+=("$name")
|
||||
printf " [%sFAIL%s] %s\n" "$RED" "$RESET" "$name"
|
||||
fi
|
||||
}
|
||||
|
||||
# The opposite: the command is supposed to fail, and passing would be the bug.
|
||||
refuses() {
|
||||
local name="$1"; shift
|
||||
if "$@" >/dev/null 2>&1; then
|
||||
FAIL=$((FAIL + 1)); FAILED_NAMES+=("$name")
|
||||
printf " [%sFAIL%s] %s (it was allowed)\n" "$RED" "$RESET" "$name"
|
||||
else
|
||||
PASS=$((PASS + 1)); printf " [%sok %s] %s\n" "$GREEN" "$RESET" "$name"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ ! -x "$TOOL" ]; then
|
||||
echo "SplitDisk is not built."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$WORK"; mkdir -p "$WORK"
|
||||
cd "$WORK" || exit 1
|
||||
|
||||
echo "Checking SplitDisk against the SBFS format."
|
||||
|
||||
check "format a disk" "$TOOL" format work.img 64 2
|
||||
refuses "refuse a disk with no room" "$TOOL" format tiny.img 2 4
|
||||
refuses "refuse an unformatted disk" "$TOOL" list /dev/null
|
||||
|
||||
# The sizes that exercise every corner of blocks-plus-tail: nothing at all, less than a
|
||||
# block, exactly a block, a part block, and an exact multiple.
|
||||
: > empty.bin
|
||||
printf 'x' > one.bin
|
||||
head -c 256 /dev/urandom > exact.bin
|
||||
head -c 700 /dev/urandom > part.bin
|
||||
head -c 768 /dev/urandom > whole.bin
|
||||
|
||||
for f in empty.bin one.bin exact.bin part.bin whole.bin; do
|
||||
check "put $f" "$TOOL" put work.img "$f"
|
||||
done
|
||||
|
||||
roundTrip() {
|
||||
"$TOOL" get work.img "$1" "got_$1" >/dev/null 2>&1 || return 1
|
||||
cmp -s "$1" "got_$1"
|
||||
}
|
||||
for f in empty.bin one.bin exact.bin part.bin whole.bin; do
|
||||
check "$f comes back byte for byte" roundTrip "$f"
|
||||
done
|
||||
|
||||
refuses "refuse a name of 29 characters" "$TOOL" put work.img part.bin twentyNineCharactersLong.asm
|
||||
refuses "refuse a duplicate name" "$TOOL" put work.img one.bin
|
||||
refuses "refuse a file that is not there" "$TOOL" get work.img nosuch.bin out.bin
|
||||
check "delete" "$TOOL" delete work.img one.bin
|
||||
refuses "the deleted file is gone" "$TOOL" get work.img one.bin out.bin
|
||||
check "the name can be used again" "$TOOL" put work.img one.bin
|
||||
|
||||
# Contiguous files mean a disk can have room without having room in one piece. That is a
|
||||
# consequence of the format rather than a bug, so it is checked rather than worked around.
|
||||
"$TOOL" format frag.img 16 1 >/dev/null 2>&1
|
||||
head -c 1024 /dev/urandom > a.bin; cp a.bin b.bin; cp a.bin c.bin
|
||||
"$TOOL" put frag.img a.bin >/dev/null 2>&1
|
||||
"$TOOL" put frag.img b.bin >/dev/null 2>&1
|
||||
"$TOOL" put frag.img c.bin >/dev/null 2>&1
|
||||
"$TOOL" delete frag.img a.bin >/dev/null 2>&1
|
||||
"$TOOL" delete frag.img c.bin >/dev/null 2>&1
|
||||
head -c 2048 /dev/urandom > big.bin
|
||||
refuses "refuse a file with no run long enough" "$TOOL" put frag.img big.bin
|
||||
head -c 512 /dev/urandom > fits.bin
|
||||
check "but one that fits the gap goes on" "$TOOL" put frag.img fits.bin
|
||||
|
||||
# ---- Directories ----
|
||||
#
|
||||
# Version two, which adds a parent to each entry and a flag bit saying an entry is a
|
||||
# directory. Both come out of bytes the entry had already set aside, so nothing moved and
|
||||
# a version one disk needs no converting: zero in those bytes means the root, which is
|
||||
# exactly where every file on a flat disk is.
|
||||
#
|
||||
# The version is therefore a statement about what is ON a disk rather than about what made
|
||||
# it, and these check that it is only raised when it becomes true.
|
||||
"$TOOL" format tree.img 64 2 >/dev/null 2>&1
|
||||
printf 'a file in the root' > root.txt
|
||||
check "a fresh disk is flat" "$TOOL" put tree.img root.txt
|
||||
version() { "$TOOL" list "$1" 2>/dev/null | head -1 | grep -q "version $2"; }
|
||||
check "and says it is version 1" version tree.img 1
|
||||
check "make a directory" "$TOOL" mkdir tree.img /Apps
|
||||
check "which raises it to version 2" version tree.img 2
|
||||
check "make one inside it" "$TOOL" mkdir tree.img /Apps/Source
|
||||
check "put a file down a path" "$TOOL" put tree.img root.txt /Apps/Source/deep.txt
|
||||
|
||||
# The point of the whole exercise: a name means something different in each place, so the
|
||||
# same one can be used twice without either being in the other's way.
|
||||
check "the same name in two places" "$TOOL" put tree.img root.txt /Apps/root.txt
|
||||
roundTripAt() {
|
||||
"$TOOL" get tree.img "$1" got_deep.txt >/dev/null 2>&1 || return 1
|
||||
cmp -s root.txt got_deep.txt
|
||||
}
|
||||
check "it comes back byte for byte" roundTripAt /Apps/Source/deep.txt
|
||||
check ". and .. walk the path" roundTripAt /Apps/./Source/../root.txt
|
||||
check ".. from the root is the root" roundTripAt /Apps/../../root.txt
|
||||
|
||||
# Each of these is a way the tree could be made to contradict itself, and each is refused
|
||||
# rather than half done.
|
||||
refuses "no file where a directory goes" "$TOOL" put tree.img root.txt /root.txt/x.txt
|
||||
refuses "no putting into thin air" "$TOOL" put tree.img root.txt /Nowhere/x.txt
|
||||
refuses "no duplicate in one directory" "$TOOL" mkdir tree.img /Apps
|
||||
refuses "no getting a directory" "$TOOL" get tree.img /Apps out.bin
|
||||
refuses "delete will not take a directory" "$TOOL" delete tree.img /Apps
|
||||
refuses "rmdir will not take a file" "$TOOL" rmdir tree.img /root.txt
|
||||
refuses "nor the root" "$TOOL" rmdir tree.img /
|
||||
|
||||
# THE REFUSAL THAT MATTERS MOST. Parents are entry indices and a freed index is handed out
|
||||
# again, so removing a directory with things still in it would let the next file created
|
||||
# adopt them. Emptying it first is the only safe order.
|
||||
refuses "no removing an occupied one" "$TOOL" rmdir tree.img /Apps/Source
|
||||
check "empty it first" "$TOOL" delete tree.img /Apps/Source/deep.txt
|
||||
check "then it goes" "$TOOL" rmdir tree.img /Apps/Source
|
||||
|
||||
# A path is names with separators between them, and a name is still twenty two characters.
|
||||
refuses "refuse a 23 character component" "$TOOL" mkdir tree.img /Apps/abcdefghijklmnopqrstuvw
|
||||
refuses "refuse a path naming nothing" "$TOOL" mkdir tree.img /Apps/
|
||||
|
||||
# A directory costs an entry and no blocks at all, which is what keeps the flat array of
|
||||
# entries the whole allocation map. If a directory ever took a block, this would drop.
|
||||
blocksFree() { "$TOOL" list "$1" 2>/dev/null | tail -1 | sed 's/.*used, //; s/ blocks free.*//'; }
|
||||
before=$(blocksFree tree.img)
|
||||
"$TOOL" mkdir tree.img /Empty >/dev/null 2>&1
|
||||
check "a directory costs no blocks" [ "$before" = "$(blocksFree tree.img)" ]
|
||||
|
||||
# ---- A directory no bigger than the parent field can name ----
|
||||
#
|
||||
# Eight entries to a block and the parent is an index plus one in two bytes, so entry
|
||||
# 65535 has no parent number: adding one wraps to zero, and zero is the root. Such an
|
||||
# entry does not refuse what is put inside it. It writes the thing into the ROOT while
|
||||
# reporting the path that was asked for, and then cannot find it again - so the same
|
||||
# create succeeds over and over, piling up entries of one name in one directory, which is
|
||||
# the exact corruption rename exists to refuse.
|
||||
refuses "no directory past the wrap" "$TOOL" format huge.img 65535 8192
|
||||
check "the largest that fits" "$TOOL" format huge.img 65535 8191
|
||||
|
||||
# And a disk claiming one, which is what something that never checked would have written.
|
||||
# The claim is in the superblock, so it does not need a disk that size to be made.
|
||||
"$TOOL" format lying.img 64 2 >/dev/null
|
||||
printf '\x20\x00' | dd of=lying.img bs=1 seek=10 conv=notrunc status=none
|
||||
refuses "nor reading one that claims it" "$TOOL" list lying.img
|
||||
|
||||
# ---- A boot area, and the two halves of the superblock that describe it ----
|
||||
#
|
||||
# bootBlocks and directoryStart say the same thing from two sides, so a disk where they
|
||||
# disagree is one where there is no way to tell which is wrong. Both are refused.
|
||||
check "format with a boot area" "$TOOL" format boot.img 512 4 32
|
||||
check "and it reads back" "$TOOL" list boot.img
|
||||
refuses "no boot area bigger than a disk" "$TOOL" format small.img 32 2 64
|
||||
check "and none at all is still fine" "$TOOL" format plain.img 64 2
|
||||
|
||||
bootField() { python3 -c "
|
||||
import sys
|
||||
f = open(sys.argv[1], 'r+b'); f.seek(int(sys.argv[2])); f.write(bytes.fromhex(sys.argv[3]))
|
||||
" "$@"; }
|
||||
cp boot.img lying.boot.img
|
||||
bootField lying.boot.img 14 0010 # Claims 16 blocks a slot, directory says 32.
|
||||
refuses "nor a boot area that disagrees" "$TOOL" list lying.boot.img
|
||||
cp boot.img badslot.img
|
||||
bootField badslot.img 16 07 # Names slot 7, and there are two.
|
||||
refuses "nor a slot that does not exist" "$TOOL" list badslot.img
|
||||
|
||||
# ---- Writing a boot slot, and choosing between them ----
|
||||
#
|
||||
# Two commands rather than one, deliberately: writing a slot and starting from it are
|
||||
# different decisions, and joining them would make every write a commitment.
|
||||
printf 'not really a bootloader' > stage.bin
|
||||
check "write a boot slot" "$TOOL" boot boot.img stage.bin 0
|
||||
check "and the other one" "$TOOL" boot boot.img stage.bin 1
|
||||
check "choose which one starts" "$TOOL" bootslot boot.img 1
|
||||
refuses "no third slot to write" "$TOOL" boot boot.img stage.bin 2
|
||||
refuses "nor a third to choose" "$TOOL" bootslot boot.img 2
|
||||
refuses "no boot slot without an area" "$TOOL" boot plain.img stage.bin 0
|
||||
|
||||
# A slot holds what it holds. Something too big for one is refused rather than cut off,
|
||||
# because half a bootloader is the failure with no way back.
|
||||
head -c 9000 /dev/zero > toobig.bin # A slot on boot.img is 32 blocks, so 8192.
|
||||
refuses "nor more than a slot holds" "$TOOL" boot boot.img toobig.bin 0
|
||||
|
||||
# THE WHOLE SLOT IS WRITTEN, not just the part the file fills. A slot still holding the
|
||||
# tail of whatever was there before is one whose contents depend on its history.
|
||||
printf 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' > long.bin
|
||||
"$TOOL" boot boot.img long.bin 0 >/dev/null
|
||||
"$TOOL" boot boot.img stage.bin 0 >/dev/null
|
||||
check "and it is written whole" python3 -c "
|
||||
import sys
|
||||
d = open('boot.img','rb').read()
|
||||
slot = d[256:256 + 32 * 256]
|
||||
sys.exit(1 if b'aaaa' in slot else 0)"
|
||||
|
||||
# ---- How the last start went ----
|
||||
check "the boot state reads" "$TOOL" bootstate boot.img
|
||||
check "and can be set" "$TOOL" bootstate boot.img 2
|
||||
refuses "but only to a state there is" "$TOOL" bootstate boot.img 7
|
||||
check "a fresh disk is settled" python3 -c "
|
||||
import sys
|
||||
sys.exit(0 if open('plain.img','rb').read()[17] == 0 else 1)"
|
||||
|
||||
# ---- Mirroring a host directory ----
|
||||
#
|
||||
# What the system disk is built with. Every file goes through put and every directory
|
||||
# through mkdir, so this is a walk over machinery already checked above - what wants
|
||||
# checking is the walk: that it goes all the way down, that it leaves behind what it was
|
||||
# told to, and that it REFUSES a name the format cannot hold rather than skipping it, since
|
||||
# a disk quietly missing a file is the failure a mirror exists to prevent.
|
||||
mkdir -p tree/inner/deeper tree/leave
|
||||
printf 'top' > tree/top.txt
|
||||
printf 'inner' > tree/inner/middle.txt
|
||||
printf 'deep' > tree/inner/deeper/bottom.txt
|
||||
printf 'not this' > tree/leave/ignored.txt
|
||||
: > tree/.hidden
|
||||
|
||||
"$TOOL" format mirror.img 256 8 >/dev/null
|
||||
check "mirror a directory tree" "$TOOL" mirror mirror.img tree /
|
||||
|
||||
"$TOOL" list mirror.img > mirrored.txt 2>&1
|
||||
grep -q '/inner/deeper/bottom.txt' mirrored.txt \
|
||||
&& { PASS=$((PASS + 1)); printf " [%sok %s] %s\n" "$GREEN" "$RESET" "it goes all the way down"; } \
|
||||
|| { FAIL=$((FAIL + 1)); FAILED_NAMES+=("depth"); printf " [%sFAIL%s] %s\n" "$RED" "$RESET" "it goes all the way down"; }
|
||||
grep -q 'hidden' mirrored.txt \
|
||||
&& { FAIL=$((FAIL + 1)); FAILED_NAMES+=("hidden"); printf " [%sFAIL%s] %s\n" "$RED" "$RESET" "and leaves dotfiles behind"; } \
|
||||
|| { PASS=$((PASS + 1)); printf " [%sok %s] %s\n" "$GREEN" "$RESET" "and leaves dotfiles behind"; }
|
||||
|
||||
# Named on the command line, which is how a project keeps what it builds out of what it
|
||||
# wrote.
|
||||
"$TOOL" format skipped.img 256 8 >/dev/null
|
||||
check "mirror with something left out" "$TOOL" mirror skipped.img tree / leave
|
||||
"$TOOL" list skipped.img > skipped.txt 2>&1
|
||||
grep -q 'ignored.txt' skipped.txt \
|
||||
&& { FAIL=$((FAIL + 1)); FAILED_NAMES+=("skip"); printf " [%sFAIL%s] %s\n" "$RED" "$RESET" "and the skipped one is not there"; } \
|
||||
|| { PASS=$((PASS + 1)); printf " [%sok %s] %s\n" "$GREEN" "$RESET" "and the skipped one is not there"; }
|
||||
|
||||
# Twenty-three characters, one more than a directory entry holds.
|
||||
printf 'too long' > tree/aNameOfTwentyThreeChars
|
||||
"$TOOL" format refused.img 256 8 >/dev/null
|
||||
refuses "a name too long stops the mirror" "$TOOL" mirror refused.img tree /
|
||||
rm -f tree/aNameOfTwentyThreeChars
|
||||
|
||||
echo
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo "All $PASS disk tool checks passed."
|
||||
exit 0
|
||||
fi
|
||||
echo "$PASS passed, $FAIL failed: ${FAILED_NAMES[*]}"
|
||||
exit 1
|
||||
@@ -22,6 +22,7 @@ Break.sbx 149
|
||||
Grid.sbx 559
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Crash.sbx 632
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
@@ -33,7 +34,7 @@ outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
crossed.txt 560
|
||||
20 files, 1 directory
|
||||
21 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -12,6 +12,7 @@ Break.sbx 149
|
||||
Grid.sbx 559
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Crash.sbx 632
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
@@ -22,7 +23,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
19 files, 1 directory
|
||||
20 files, 1 directory
|
||||
> drive 1
|
||||
> dir
|
||||
other.txt 28
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
CosmOS
|
||||
> Crash opcode
|
||||
that byte is not an instruction, at 4081
|
||||
A 00 B 0F Q 00
|
||||
the program was stopped
|
||||
> Crash service
|
||||
nothing is installed at service 28, at 4084
|
||||
A 00 B 0F Q 00
|
||||
the program was stopped
|
||||
> Crash bank
|
||||
a bank that is not there, or an address past its end, at 409E
|
||||
A 01 B 0F Q 00
|
||||
the program was stopped
|
||||
> Crash device
|
||||
nothing is installed for the device on port 00, at 40A5
|
||||
A 02 B 0F Q 00
|
||||
the program was stopped
|
||||
> Crash sideways
|
||||
Crash opcode | service | bank | device | blind
|
||||
finished
|
||||
> dir
|
||||
greet.sbx 211
|
||||
hello.sbx 53
|
||||
Life.sbx 1396
|
||||
Snake.sbx 2164
|
||||
Keys.sbx 664
|
||||
Say.sbx 156
|
||||
Break.sbx 149
|
||||
Grid.sbx 559
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Crash.sbx 632
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
bad.script 45
|
||||
plain.script 24
|
||||
cross.script 280
|
||||
nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
20 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
[exit 0]
|
||||
@@ -0,0 +1,9 @@
|
||||
CosmOS
|
||||
> monitor
|
||||
x examine, d disassemble, a assemble, s set, b bank, g go, exit leaves
|
||||
* g 3F00
|
||||
that byte is not an instruction, at 3F00
|
||||
A 01 B 7F Q 00
|
||||
that was the system itself, so there is nowhere to carry on from. Start the machine again.
|
||||
Execution halted.
|
||||
[exit 0]
|
||||
@@ -19,6 +19,7 @@ Break.sbx 149
|
||||
Grid.sbx 559
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Crash.sbx 632
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
@@ -29,7 +30,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
19 files, 1 directory
|
||||
20 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -51,8 +51,73 @@ bank 00
|
||||
8009 12 AND
|
||||
800A 00 ?
|
||||
800B 00 ?
|
||||
* g 8000Fault: 0x00 at Program Address 0x800A is not an instruction.
|
||||
|
||||
* g 8000
|
||||
H
|
||||
that byte is not an instruction, at 800A
|
||||
A 85 B 3F Q 05
|
||||
the program was stopped
|
||||
* d 8000
|
||||
8000 26 48 INIA 48
|
||||
8002 D1 00 OUTA 00
|
||||
8004 26 0A INIA 0A
|
||||
8006 D1 00 OUTA 00
|
||||
8008 18 SHR
|
||||
8009 12 AND
|
||||
800A 00 ?
|
||||
800B 00 ?
|
||||
* b data
|
||||
bank 01
|
||||
* s 8100 68 65 6C 6C 6F 2C 20 74 79 70 65 64 0A 00
|
||||
* b program
|
||||
bank 00
|
||||
* a 8200
|
||||
8200: SETD.0 8100
|
||||
8204: SWI 10
|
||||
8206: lda.2
|
||||
8208: LDD.0.1
|
||||
820B: frobnicate
|
||||
no such instruction
|
||||
820B: INIA
|
||||
that one needs a value after it
|
||||
820B: SWI 12
|
||||
820D: .
|
||||
* d 8200
|
||||
8200 47 00 81 00 SETD.0 8100
|
||||
8204 72 10 SWI 10
|
||||
8206 42 02 LDA.2
|
||||
8208 4A 00 01 LDD.0.1
|
||||
820B 72 12 SWI 12
|
||||
820D 00 ?
|
||||
820E 00 ?
|
||||
820F 00 ?
|
||||
* g 8200
|
||||
hello, typed
|
||||
finished
|
||||
* exit
|
||||
> dir
|
||||
greet.sbx 211
|
||||
hello.sbx 53
|
||||
Life.sbx 1396
|
||||
Snake.sbx 2164
|
||||
Keys.sbx 664
|
||||
Say.sbx 156
|
||||
Break.sbx 149
|
||||
Grid.sbx 559
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Crash.sbx 632
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
bad.script 45
|
||||
plain.script 24
|
||||
cross.script 280
|
||||
nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
20 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
[exit 1]
|
||||
[exit 0]
|
||||
|
||||
@@ -12,6 +12,7 @@ Break.sbx 149
|
||||
Grid.sbx 559
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Crash.sbx 632
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
@@ -22,7 +23,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
19 files, 1 directory
|
||||
20 files, 1 directory
|
||||
> load
|
||||
load what?
|
||||
> load nosuch.sbx
|
||||
|
||||
@@ -10,6 +10,7 @@ Break.sbx 149
|
||||
Grid.sbx 559
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Crash.sbx 632
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
@@ -20,7 +21,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
19 files, 1 directory
|
||||
20 files, 1 directory
|
||||
> load Say.sbx
|
||||
loaded, starting at 4000
|
||||
> run the disk took its time
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Crash opcode
|
||||
Crash service
|
||||
Crash bank
|
||||
Crash device
|
||||
Crash sideways
|
||||
dir
|
||||
exit
|
||||
@@ -0,0 +1,2 @@
|
||||
monitor
|
||||
g 3F00
|
||||
@@ -124,6 +124,11 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/
|
||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||
"$ROOT/Programs/CosmOS/Apps/Mode.asm" -o "$WORK/Mode.sbx" >/dev/null
|
||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Mode.sbx" >/dev/null
|
||||
# Crash.sbx breaks in each of the four ways the system now catches, which is the only way to
|
||||
# reach a fault screen from a recorded test.
|
||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||
"$ROOT/Programs/CosmOS/Apps/Crash.asm" -o "$WORK/Crash.sbx" >/dev/null
|
||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Crash.sbx" >/dev/null
|
||||
printf 'this is not a program' > notes.txt
|
||||
"$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null
|
||||
|
||||
|
||||
@@ -326,6 +326,20 @@ cosmosPressKeys | CosmOS/Source/cosmos.asm | run | -
|
||||
# a forty column screen, which is a width nothing else here ever runs at - and the shell
|
||||
# reads that width for itself now, every line it edits.
|
||||
cosmosNoData | CosmOS/Source/cosmos.asm | run | cosmosNoData.in | - | disks/cosmos.img
|
||||
# The system catching a fault instead of the machine stopping on one. All four causes, one
|
||||
# after another, and a dir at the end: THE POINT IS THE LINE AFTER THE FAULT. Every one of
|
||||
# these used to stop the machine dead, and behind a window it looked like a hang because the
|
||||
# diagnosis went to a standard error nobody was looking at.
|
||||
#
|
||||
# The addresses are exact and are the reason the recording is worth having. Note that the
|
||||
# missing service reports the address PAST its SWI, where the others report the instruction
|
||||
# that failed: those two faults are the only ones where the instruction did dispatch and it
|
||||
# was the entry that was empty.
|
||||
cosmosFault | CosmOS/Source/cosmos.asm | run | cosmosFault.in | - | disks/cosmos.img
|
||||
# And a fault in the system's own code, which has nowhere to go back to. Reached through the
|
||||
# monitor by jumping into the unwritten space above CosmOS and below where programs load,
|
||||
# which is zeroes - so it faults at once and corrupts nothing on the way.
|
||||
cosmosFaultSystem | CosmOS/Source/cosmos.asm | run | cosmosFaultSys.in | - | disks/cosmos.img
|
||||
# The shell editing the line it is being given, which is the whole reason the keys were
|
||||
# made to arrive. Every line here is typed wrong and then corrected with a different key, and
|
||||
# every one of them comes out as the same command - so the recording says both what the
|
||||
|
||||
@@ -911,6 +911,48 @@ inked scrollback 2 1 \
|
||||
&& result ok "what scrolled off is still there" "the origin went back and found it" \
|
||||
|| result no "what scrolled off is still there" "nothing at 2,1"
|
||||
|
||||
# ---- The fault screen, from a screen with nowhere to print on it ----
|
||||
#
|
||||
# Every other test of the fault screen reads what came down the serial line, and the serial
|
||||
# line is not where the problem was: a program that faulted in BITMAP MODE left the console
|
||||
# with no text rows, so it drew nothing at all, and the machine looked hung while it was
|
||||
# merely unable to say so. What has to be checked is the PICTURE.
|
||||
#
|
||||
# Two things about it, and both matter. The picture is 640 by 400, which is the eighty column
|
||||
# text mode - so the screen really was put back, from a mode that has no characters in it.
|
||||
# And it holds the fault red, which says the message was drawn and drawn in a colour that can
|
||||
# be read whatever palette the program had left behind.
|
||||
python3 -c "open('$BUILD/blind.keys','wb').write(b'Crash blind\n' + b'\x00'*400)"
|
||||
timeout 30 "$EMU" --fast --cycles 8000000 --keyboard "$BUILD/blind.keys" \
|
||||
--screen "$BUILD/blind.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \
|
||||
"$BUILD/cosmos.bin" > "$BUILD/blind.out" 2>&1 || true
|
||||
|
||||
if [ -f "$BUILD/blind.ppm" ]; then
|
||||
SIZE="$(head -c 20 "$BUILD/blind.ppm" | sed -n '2p')"
|
||||
[ "$SIZE" = "640 400" ] \
|
||||
&& result ok "a fault puts the screen back where text can be seen" "eighty columns again, from bitmap mode" \
|
||||
|| result no "a fault puts the screen back where text can be seen" "the picture is $SIZE"
|
||||
|
||||
REDDISH="$(python3 - "$BUILD/blind.ppm" <<'PY2'
|
||||
import sys
|
||||
data = open(sys.argv[1], "rb").read()
|
||||
parts = data.split(b"\n", 3)
|
||||
pixels = parts[3]
|
||||
# The red the machine wakes up with, which is the one the fault screen writes into the
|
||||
# entries attribute one draws from. Counted rather than looked for once, so a single stray
|
||||
# pixel of it could not pass for a message.
|
||||
red = sum(1 for i in range(0, len(pixels) - 2, 3)
|
||||
if (pixels[i], pixels[i + 1], pixels[i + 2]) == (0xD0, 0x40, 0x38))
|
||||
print("yes" if red > 200 else "only %d red pixels" % red)
|
||||
PY2
|
||||
)"
|
||||
[ "$REDDISH" = "yes" ] \
|
||||
&& result ok "and says what happened in a colour that can be read" "the message is drawn in the fault red" \
|
||||
|| result no "and says what happened in a colour that can be read" "$REDDISH"
|
||||
else
|
||||
result no "a fault puts the screen back where text can be seen" "no picture was written"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo "All $PASS video checks passed."
|
||||
|
||||
Reference in New Issue
Block a user