<< Previous Message Main Index Next Message >>
<< Previous Message in Thread This Month Next Message in Thread >>
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
<< Previous Message Main Index Next Message >>
<< Previous Message in Thread This Month Next Message in Thread >>