Date : Fri, 20 Oct 2006 23:51:13 +0100 (BST)
From : Greg Cook <debounce@...>
Subject: Re: Bugs in OS 1.20 (long)
On Sat, 14 Oct 2006 16:47:12 +0200, John Kortink <kortink@...>
wrote:
> Two major bugs I've just found in BBC OS 1.20 may be worthy
> of attention if you use the ROM filing system, directly or
> indirectly (e.g. if you produce ROMs containing ROM filing
> system data).
>
> Acorn must have fixed these silently in later OS versions
> (at least in the Master they seem to be).
>
> The culprit is a piece of routine at F232 which, after a
> file load (tape/rom FS) intends to return the file size
> in the OSFILE parameter block. It assumes the file size
> is (&CC) (i.e. ?&CC+256*?&CD) and that the parameter block
> address is (&C8) (i.e. ?&C8+256*?&C9).
>
> Bug #1 : &CC/CD are not updated UNLESS the file has loaded
> under *OPT 1 2 (i.e. verbosely).
>
> Bug #2 : a *RUN does not do an OSFILE, but jumps straight
> into the 'file load' routine without setting up a parameter
> block, nor, therefore, its address in (&C8) !
>
> Result 1 : OSFILE returns an incorrect file size (whatever
> was in &CC/CD /before/ the file loaded) if not done under
> *OPT 1 2.
>
> Result 2 : *RUNning a file randomly corrupts four contiguous
> bytes in memory, in that the file size (correct or not) is
> written to a non-existing parameter block, (&C8)+10 onwards,
> i.e. where &C8/C9 are unset, as /before/ the file loaded.
>
> &C8/C9 will be 'wrong' easily, e.g. they may have been used
> by previous filing systems (as selected before *ROM).
>
> Conclusion :
>
> a) Never use the file size returned from an OSFILE file load
> unless you're certain *OPT 1 2 has been issued.
>
> b) NEVER *RUN a file.
>
> I'm not 100% sure that these apply to the tape filing system
> as well, but they probably do, and they certainly apply to
> the ROM filing system.
>
> Especially the *RUN problem is a /huge/ bug if you ask me.
> Beware !
>
>
> John Kortink
>
> --
>
> Email : kortink@...
> Homepage : http://www.inter.nl.net/users/J.Kortink
>
> GoMMC, the ultimate BBC B/B+/Master storage system :
> http://web.inter.nl.net/users/J.Kortink/home/hardware/gommc
As John notes on the GoMMC mailing list, 'Result 2' (data corruption)
can be avoided by entering *LOAD <filename> followed by CALL <address>.
I put *TAPE in between, just to make sure.
I expect these bugs do appear in CFS, it's just that the default option
is to show the file information, so 'Result 1' disappears. The *RUN
bug is not well known because typically when files are *RUN from tape
(mostly games) the pointer has been left at a 'harmless' value (&FFFF
from powerup, or the MOS's OSFILE block at &02EE.)
If you are assembling a ROM image containing files that will be *RUN,
the simplest fix is to set the pointer at &C8,&C9 to the value &00C2,
whenever the data pointer at &F6,&F7 is initialised. This protects
user memory but returns *nothing* to an OSFILE block.
(Aside: I've often rued the fact that Acorn couldn't leave space for
patches, such as to return the load/execution address; perhaps they
removed that capability to make room for something else, yet they
placed a high priority on the unrolled CLS loop. Maybe there'd be room
if the assembler used zero-page instructions wherever possible...)
Where the above fix is unacceptable the ROM service code can peek at
the 6502 stack to see if the *RUN code is loading the file, and if so
initialise the OSFILE pointer. (This will be non-robust with 2^-16
probability of failure if the user makes service call &D directly.)
The file length can be computed during service call &E; if there is no
token to mark when to recalculate the length then it is more efficient
to do it on every call.
Below is the basic RFS service code from the AUG with a paged ROM
header plus two inserts to work around the bugs John mentions. The
resulting code is 51 bytes larger than the plain service code. Since
the routine at &EE18 may be called from &F351 or &F790, the *RUN return
address may appear at one of two depths in the stack.
Finally, there's a third bug I've noticed in RFS and CFS; on opening an
empty file with OPENIN, EOF#<handle> will correctly return nonzero, but
BGET#<handle> will succeed once, returning &FE. Therefore the idiom
REPEAT:...BGET#fh%...:UNTIL EOF#fh% will malfunction on empty files.
Greg Cook
debounce@...
http://homepages.tesco.net/~rainstorm/
8000 OPT pass%
8000 \ Based on basic server code
8000 \ from the Advanced User Guide, pp 330, 331
8000 .lang
8000 4C 7C 80 JMP exit
8003 .svc
8003 4C 38 80 JMP serve
8006 .type
8006 82 EQUB &82
8007 .cpptr
8007 28 EQUB copyr-lang
8008 .bnver
8008 00 EQUB &00
8009 .title
8009 42 75 67
66 69 78
20 52 4F
4D 46 53 EQUS "Bugfix ROMFS"
8015 .ver
8015 00 EQUB &00
8016 30 2E 30
31 20 28
32 30 20
4F 63 74
20 32 30
30 36 29 EQUS "0.01 (20 Oct 2006)"
8028 .copyr
8028 00 EQUB &00
8029 28 43 29
32 30 30
36 20 47
2E 43 6F
6F 6B EQUS "(C)2006 G.Cook"
8037 00 EQUB &00
8038 .serve
8038 C9 0D CMP #&0D \ Is it a ROM initialising call?
803A D0 41 BNE ninit \ No - branch
803C 48 PHA \ Save the service call type
803D 98 TYA \ Get logical ROM number
803E 49 0F EOR #&0F \ Adjust it (do 15-x)
8040 C5 F4 CMP &F4 \ Compare with this ROM's number
8042 90 37 BCC notus \ Number less, this ROM already been done
8044 A9 A7 LDA #data MOD 256 \ Low byte of data address
8046 85 F6 STA &F6 \ Location &F6 and &F7 reserved for this
8048 A9 80 LDA #data DIV 256 \ High byte of data address
804A 85 F7 STA &F7 \ Save high byte
804C A5 F4 LDA &F4 \ this ROM's number
804E 49 0F EOR #&0F \ Adjust it back (15-x)
8050 85 F5 STA &F5 \ pass over any higher non-filing system
ROMs
8052 \ #################### INSERT 1
8052 8A TXA \ transfer X to A
8053 48 PHA \ save it
8054 BA TSX \ transfer 6502 stack pointer to X
8055 B8 CLV
8056 50 07 BVC test \ jump into test
8058 .next
8058 70 1B BVS init2 \ tested two addrs? if so restore AX and
exit
805A E8 INX \ else increment copy of stack pointer
805B E8 INX \ to point to second address
805C 18 CLC \ clear carry flag
805D 69 80 ADC #&80 \ set overflow flag
805F .test
805F BD 16 01 LDA &0116,X \ peek high byte of a return address in
stack
8062 C9 F3 CMP #&F3 \ does it match address of *RUN?
8064 D0 F2 BNE next \ if not try next address
8066 BD 15 01 LDA &0115,X \ else peek low byte
8069 C9 0F CMP #&0F \ is it the return address into *RUN?
806B D0 EB BNE next \ if not try next address
806D .fix
806D A9 C2 LDA #&C2 \ else initialise OSFILE pointer
806F 85 C8 STA &C8 \ to copy file length variable in zero
page
8071 A9 00 LDA #&00 \ onto itself. &CE and &CF (unused by
MOS)
8073 85 C9 STA &C9 \ will be cleared when loading is
finished.
8075 .init2
8075 68 PLA \ get old value of X
8076 AA TAX \ return it to X
8077 \ #################### END INSERT 1
8077 68 PLA \ Discard service type
8078 A9 00 LDA #0 \ Set service type to no-operation
807A 60 RTS \ Exit
807B .notus
807B 68 PLA \ Restore service type
807C .exit
807C 60 RTS \ Exit
807D .ninit
807D C9 0E CMP #&0E \ Is it a byte request call?
807F D0 FB BNE exit \ No - exit
8081 48 PHA \ Save service type
8082 A5 F5 LDA &F5 \ Find the current ROM number
8084 49 0F EOR #&0F \ Adjust it
8086 C5 F4 CMP &F4 \ compare it with this ROM
8088 D0 F1 BNE notus \ Not the same, must be for another ROM
808A A0 00 LDY #0 \ Prepare to get the data
808C B1 F6 LDA (&F6),Y \ Get the data
808E A8 TAY \ In the Y register ready for exit
808F \ #################### INSERT 2
808F AD C6 03 LDA &03C6 \ get low byte of block number
8092 18 CLC \ add high byte of block length
8093 6D C9 03 ADC &03C9
8096 85 CD STA &CD \ store as high byte of length
8098 AD C8 03 LDA &03C8 \ get low byte of block length
809B 85 CC STA &CC \ store as low byte of length
809D \ #################### END INSERT 2
809D E6 F6 INC &F6 \ Increment the address for next time
809F D0 02 BNE ninc7 \ No need to increment &F7
80A1 E6 F7 INC &F7
80A3 .ninc7
80A3 68 PLA \ Discard service type
80A4 A9 00 LDA #0 \ Set service type to no-operation
80A6 60 RTS \ Exit
80A7 .data \ Data onward from here
Send instant messages to your online friends http://uk.messenger.yahoo.com