💾 Archived View for gemini.susa.net › BBC_JM_Bug.gmi captured on 2022-07-16 at 13:42:53. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2021-11-30)

-=-=-=-=-=-=-

Fixing a Bug in 38 Year Old Software

I am periodically reminded of just how good we've got it today. Languages, libraries, debuggers, editors, shells, greps, awks, seds. The list goes on.

So, I got my latest reminder when I spotted a bug in a maths program that seemed to think that 5.02 + 2.00 = 7.03.

Now, the reason for this is not really the fault of the programmer. The bahaviour of the function STR$(), to convert a number to a string, changed from Acorn's BBC BASIC 1 to BASIC 2. The maths program worked as expected in the version it was written for, but the error showed up when it was run on the later version.

A time before coding-standards

One of the shortcomings of 8-bit computers is they typically had a 16 bit address bus, which limits the amout of memory the CPU can see to 64K. The BBC had 32K of space dedicated to ROM, so the remaining 32K had to accomodate a framebuffer, operating system and file system data, and buffers for data on the external busses.

Inevitably this affects how much code you can fit into the remaining RAM, and programmers tended to write what looks like obfuscated code, but is mainly an attempt to minimise the amount of space that their code consumes. The example below of a single line of code that implements a FOR loop.

15080FORI=5TO6:VDU31,10,I:PRINTCHR$(145
):NEXT:VDU31,10,8:PRINTCHR$(145):VDU31,
10,10:PRINTCHR$(145):FORI=12TO16:VDU31,
10,I:PRINTCHR$(145):NEXT

or perhaps a line of conditional logic...

20175I$=I$+CHR$(IS):IC=IC+1:IF BLF=TRUE
 THEN VDU31,H,V:PRINTCHR$(128+C)CHR$(14
1)I$;CHR$(140);:VDU31,H,V,10:PRINTCHR$(
128+C)CHR$(141)I$CHR$(140);:VDU8 ELSE P
RINTCHR$(IS);

When the code is hundreds of lines like that, it's a challenge to grok - particularly when LIST is the only means of browsing the code.

There's a vague notion of paging, which is enabled with Ctrl-N and pauses after around 2/3 of a screen of text. The LIST command also allows ranges. e.g. LIST 10,100 would list all lines numbered in that range, but it's impossible to know in advance how many lines will fit on a screen, since logical lines can wrap across many physical lines, as shown above. It comes down to trial, error, and memory.

A time before bookmarks

If you see a procedure call (PROCsomething), it's a chore to look to see what it does, since you lose your current position in the program. No bookmarks, no indication at all how far through the code you are. And when you decide that you really do need to know exactly what the procedure does, you then have the arduous task of finding it. The excerpt below is an example of what you might have to look through to find your procedure. There are a few in there among the lines that I present as they might appear wrapped on screen.

0125 IFIC=IL THENVDU9
20130 IC=IC-1:IFRIGHT$(I$,1)="."THENID=
0
20140 IFIC=0THENI$="" ELSEI$=LEFT$(I$,I
C)
20145 IF BLF=TRUE THEN VDU127,9,11,127,
10 ELSE VDU127
20150 ENDPROC
20160 DEFPROCInputConcat
20170 IFIC=IL THEN 20190
20175 I$=I$+CHR$(IS):IC=IC+1:IF BLF=TRU
E THEN VDU31,H,V:PRINTCHR$(128+C)CHR$(1
41)I$;CHR$(140);:VDU31,H,V,10:PRINTCHR$
(128+C)CHR$(141)I$CHR$(140);:VDU8 ELSE 
PRINTCHR$(IS);
20180 IFIC=IL THEN VDU8
20190 ENDPROC
20200 DEFPROCInputFilter(IT)
20210 ON IT GOTO20220,20230,20240,20250
,20260,20270,20280,20290
20230 IF((IS OR 32)>96 AND (IS OR 32)<1
23) OR IS=46 OR IS=32 OR IS=45 THENPROC
InputConcat
20235GOTO20390
20240 IL=1:IF(IS OR 32)=110 OR (IS OR 3
2)=121 THENPROCInputConcat
20245GOTO20390
20250   IF(IS>47 AND IS<58)OR(IS=46 AND
 ID=0)OR(IS=45 AND IC=0)THENPROCInputCo
ncat:ID=ID-(IS=46)
20255GOTO20390
20270 IFIS>=IU+48 AND IS<=IV+48 THENPRO
CInputConcat
20275GOTO20390
20290 IF IS=49 AND IC=0 OR IS=48 AND IC
>0 THEN PROCInputConcat
20295 GOTO 20390
20390 ENDPROC
20400DEFPROCFORMAT(I,IP)
20410LOCAL IJ,IM,IG:IM=SGN(I):IF ABS(I)
<.1 THEN I=ABS(I)+1:I$="0"+RIGHT$(STR$(
I),LEN(STR$(I))-1) ELSE I$=STR$(I):IFIM
=-1THENI$=RIGHT$(I$,LEN(I$)-1)
20415IF IP=0 THEN20450
20430 FOR IJ=1TOLEN(I$):IF MID$(I$,IJ,1
)="."THEN IG=IJ:IJ=LEN(I$)
20440 NEXT:IF IG=0 THEN I$=I$+".":IG=LE
N(I$)
20445 I$=LEFT$(I$+"00",IG+IP)
20450IF IM=-1 THEN I$="-"+I$
20460ENDPROC

When you've identified your procedure and figured out what it does, you now have to remember where exactly it was called from so you can go back and continue from where you left off.

A time before breakpoints

There's no concept of debugging, beyond PRINT. There's also no editor, neither screen nor line editing. To change something on a line, you have to re-enter it as you would like it to look. To address this shortcoming, the BBC has a COPY key. Essentially, you move the curson to somewhere on the screen, and each time you press COPY, the character under the cursor is copied to the command-line and the cursor advanced. This saves a lot of time re-entering and altering a line of the program, but I constantly find myself using cursor left and right to edit the actual line, which can't be done.

Needless to say, adding PRINT statements here and there to get a handle on what's going on in the code is a tedious process. Not as tedious as trying to follow the code, but tedious nonetheless.

Perseverence is rewarded

However, when debugging this code, it was the PRINT statements that saved the day. I had spent ages figuring out the arithmetic logic that the program used, and I could find absolutely nothing wrong with it. When I dumped some values being passed to PROCFORMAT() (which is shown above), I could finally see that it was displaying the wrong number to the player.

So, the player was answering the wrong question rather then the computer calculating the wrong answer! For example, one of the random chosen numbers was 5.03, which was displayed as 5.02. The reason why turned out to be STR$(5.03) was returning the string "5.02999999" in BASIC version 2. Previously it would return "5.03" as you might expect. I simply added (0.0005 x the sign of I) to 'I' in the procedure above to fix it.

I think it was a bug, because STR$() is supposed to honour a format-specifier (named '@%') that gets used for PRINT and, so far as I can tell, it gets ignored completely. I can only reproduce this bug in BASIC 2, none before or after exhibit this behaviour. However, it's probably the most ubiquitous version of BBC BASIC.

Programmers were tougher in the old days

The experience was fun as a one-off, but I admit that I don't relish the idea of debugging much code like this, nor writing anything substantial in such a rudimentary environment, where I have to think through the mechanics of simply getting a line of code into the program. I think there are some benefits to working this way, since we can get a bit lazy when we're spoiled for choice with powerful tools, but ultimately it's hard not to concede that we have really got it good these days.