While wandering through the Color/Extended/Disk BASIC Unraveled books trying to figure out how the RAM hooks worked, I came across a technique that I had never used.
So of course I’m going to digress with a bunch of other stuff first.
GOTO and GOSUB
In BASIC, you can run code using GOTO or GOSUB. GOTO jumps to a specific line number and runs from there. If that code needs to get back to the main loop, it has to do so with another GOTO.
10 REM MAIN LOOP 20 A$=INKEY$:IF A$="" THEN 20 30 IF A$="L" THEN GOTO 100 40 IF A$="R" THEN GOTO 200 50 GOTO 10 100 REM MOVE LEFT ... 190 GOTO 10 200 REM MOVE RIGHT ... 290 GOTO 10
This is fine for code that does one specific thing at one specific place, but the routines at 100 and 200 could not be used anywhere else in the program unless after such use they always resumed running at line 10.
GOSUB is often a better option, since it eliminates the need for the subroutine to know where it must GOTO at the end:
10 REM MAIN LOOP 20 A$=INKEY$:IF A$="" THEN 20 30 IF A$="L" THEN GOSUB 100 40 IF A$="R" THEN GOSUB 200 50 GOTO 10 100 REM MOVE LEFT ... 190 RETURN 200 REM MOVE RIGHT ... 290 RETURN
There are inefficiencies to the above code, as well as some potential problems, but it’s good enough for an example.
When GOSUB is seen, BASIC remembers the exact spot after the line number and saves it somewhere. It then jumps to that line number, and when a RETURN is seen, it retrieves the saved location and jumps back there to continue executing.
The location is saved on a stack, so you can GOSUB from a GOSUB from a GOSUB, as long as there is enough memory to remember all those locations.
Stack Notes
Think of the stack like a stack of POST-IT(tm) notes. When a GOSUB happens, the return location is written on a piece of paper, then that paper is placed somewhere. If another GOSUB is seen, that location is written on paper and then stuck on top of the previous one, and so on. You end up with a stack of locations. When a RETURN is seen, it grabs the top piece of paper and returns to that location, then that paper is discarded.
10 PRINT "TEST START" 20 GOSUB 100 30 PRINT "TEST END" 40 END 100 REM FIRST 110 PRINT " FIRST START" 120 GOSUB 200 130 PRINT " FIRST END" 140 RETURN 200 REM SECOND 210 PRINT " SECOND START" 220 PRINT " SECOND END" 230 RETURN
Running that program prints:
TEST START FIRST START SECOND START SECOND END FIRST END TEST END
Test calls First which calls Second. When Second returns, it returns back to First. When First returns, it returns back to Start.
If you ever leave a GOSUB with a GOTO, that return location is still there, saved, and that memory is never returned to the BASIC program. This will crash a program:
10 PRINT X 20 X=X+1 30 GOSUB 10
Each GOSUB adds a return location to the BASIC stack, and since the program is recursively calling itself without ever RETURNing, it will eventually run out of BASIC stack space. In the test I just did, I received an ?OM ERROR (out of memory) at count 3247. On a system with less RAM available (smaller RAM, larger program, etc.) that will happen more often.
This is a STACK OVERFLOW, and languages like C, assembly, etc. can all have them. (I assume that’s where the Q&A site www.stackoverflow.com got its name from.)
Some environments have stack checking, and they will terminate the offending program with an error message when this happens. This is what happened with the ?OM ERROR. Beyond BASIC, operating systems generally take care of this stack checking. Programs written in C or 6809 assembly running under OS-9 most certainly will get terminated with a stack overflow if they try to use more than the OS reserved for them. (Ah, if I only understood this way back then. I just knew to keep adding more memory to a command until it ran without crashing…)
Assembly GOTO and GOSUB
In 6809 assembly, a GOTO equivalent would be like a BRx branch instruction or a JMP jump instruction. The earlier BASIC example might look like this in CoCo assembly:
mainloop jsr [$a002] * Call ROM POLCAT routine, key comes back in A. beq mainloop * If A="", GOTO mainloop. cmpa 'L * Compare A to character "L". beq moveleft * If A="L", GOTO moveleft. cmpa 'R * Compare A to character "R". beq moveright * If A="R", GOTO moveright. bra mainloop * GOTO mainloop. moveleft ... bra mainloop * GOTO mainloop. moveright ... bra mainloop * GOTO mainloop.
For very simple logic, assembly can be quite similar to BASIC.
GOSUB would be BSR branch subroutine or JSR jump subroutine operation. Here is what the second BASIC example might look like in assembly:
jsr [$a002] * Call ROM POLCAT routine, key comes back in A. beq mainloop * If A="", GOTO mainloop. cmpa 'L * Compare A to character "L". bsr moveleft * If A="L", GOSUB moveleft. cmpa 'R * Compare A to character "R". bsr moveright * If A="R", GOSUB moveright. bra mainloop * GOTO mainloop. moveleft ... rts * RETURN. moveright ... rts * RETURN.
Very simple code like this would be a good way for a BASIC programmer to tip-toe in to the land of assembly language. It’s quite fun, until you realize how much work is needed for anything that is not as simple ;-)
And now the third example… Since assembly does not have a PRINT command, I created a simple subroutine that uses the ROM CHROUT routine to print out whatever character is in the A register.
* lwasm jsrtest.asm -fbasic -ojsrtest.bas --map org $3f00 start * 10 PRINT "TEST START" ldx #teststartmsg * X=Start of message. jsr print * GOSUB print. * 20 GOSUB 100 jsr first * GOSUB first. ldx #testendmsg * X=Start of message. * 30 PRINT "TEST END" jsr print * GOSUB print. * 40 END rts * RETURN first * 110 PRINT " FIRST START" ldx #firststartmsg * X=Start of message. jsr print * GOSUB print. * 120 GOSUB 200 jsr second * 130 PRINT " FIRST END" ldx #firstendmsg * X=Start of message. jsr print * GOSUB print. * 140 RETURN rts * RETURN second * 210 PRINT " SECOND START" ldx #secondstartmsg * X=Start of message. jsr print * GOSUB print. * 230 PRINT " SECOND END" ldx #secondendmsg * X=Start of message. jsr print * GOSUB print. * 240 RETURN rts * RETURN * PRINT subroutine. Prints the string pointed to by X. print lda ,x+ beq done jsr [$a002] bra print done lda #13 jsr [$a002] rts * Data storage for the string messages. teststartmsg fcc "TEST START" fcb 0 testendmsg fcc "TEST END" fcb 0 firststartmsg fcc " FIRST START" fcb 0 firstendmsg fcc " FIRST END" fcb 0 secondstartmsg fcc " SECOND START" fcb 0 secondendmsg fcc " SECOND END" fcb 0
Here is a BASIC loader for the above assembly routine. You can load and RUN this, then type EXEC &H3F00 to run it.
10 READ A,B 20 IF A=-1 THEN 70 30 FOR C = A TO B 40 READ D:POKE C,D 50 NEXT C 60 GOTO 10 70 END 80 DATA 16128,16267,142,63,62,189,63,45,189,63,16,142,63,73,189,63,45,57,142,63,82,189,63,45,189,63,32,142,63,96,189,63,45,57,142,63,108,189,63,45,142,63,125,189,63,45,57,166,128,39,6,173,159,160,2,32,246,134,13,173,159,160,2,57,84,69,83,84,32 90 DATA 83,84,65,82,84,0,84,69,83,84,32,69,78,68,0,32,32,70,73,82,83,84,32,83,84,65,82,84,0,32,32,70,73,82,83,84,32,69,78,68,0,32,32,32,32,83,69,67,79,78,68,32,83,84,65,82,84,0,32,32,32,32,83,69,67,79,78,68,32,69,78,68,0,-1,-1
Stack Overflow in assembly
Just for fun… Here is the GOSUB crash program in assembly. 99% of this code is just a crappy routine I had to write to print out a decimal number.
org $3f00 start ldx #0 * X=0 loop * 10 PRINT X jsr printx * GOSUB printx. * 20 X=X+1 leax 1,x * X=X+1 * 30 GOSUB 10 bsr loop * GOSUB loop. rts * Return to BASIC. * * Crappy routine I just put together to try to print out a decimal number. * printx * Init buffer to 000000. lda #'0 sta numberstring sta numberstring+1 sta numberstring+2 sta numberstring+3 sta numberstring+4 sta numberstring+5 * X is our counter. tfr x,d * Copy X to D tenthousands cmpd #10000 blt thousands subd #10000 inc numberstring bra tenthousands thousands cmpd #1000 blt hundreds subd #1000 inc numberstring+1 bra thousands hundreds cmpd #100 blt tens subd #100 inc numberstring+2 bra hundreds tens cmpd #10 blt ones subd #10 inc numberstring+3 bra hundreds ones cmpd #0 blt print subd #1 inc numberstring+4 print ldy #numberstring printloop lda ,y+ jsr [$a002] cmpy #bufferend bne printloop lda #13 jsr [$a002] rts numberstring fcb 5 * Holds 00000-99999 bufferend equ numberstring+5
Thank you for ignoring my poorly-coded “printx” subroutine.
When I run this, it crashes after printing 08141. I believe it is a much smaller number than the BASIC one because it has much less memory for the stack. Since this program starts in memory at the 32K mark (&H3F00), the stack has from end of RAM (&HFF00) down to the end of this program. As the stack grows, without stack checking, it eventually overwrites the running assembly code, crashing the computer.
Let’s pretend we never did that.
What are we learning?
At the start of this article, I mentioned something I just learned from looking at other assembly code. I learned how to get out of an assembly GOSUB routine without needing to return. Just like BASIC, calling a subroutine recursively will cause a crash. Unlike BASIC, there is no stack checking when running raw 6809 code without an operating system, so it can really crash BASIC and require a reset of the computer.
There is a way to GOTO out of an assembly routine without leaving that GOSUB program counter memory on the stack. You simply move the stack pointer by 2 places.
For example, say you had assembly code that was like this BASIC:
10 GOSUB 100 100 GOSUB 200 200 ...
The stack would look like this:
<- Next GOSUB would be stored here. [200] <- Top of stack. RETURN would use this. [100] [ 10]
BASIC has no way to throw away whatever GOSUB entry is on the top of the stack, but it is simple to do in assembly just by adding 2 to the S (stack pointer) register.
start jsr first * GOSUB first. rts * RETURN first jsr second * GOSUB second. rts * RETURN second leas 2,S * Move stack pointer down two bytes. rts * RETURN
By the time the code gets to “second”, the assembly stack should look like this:
<- Next bsr/jsr would be stored here. [first] <- Top of stack. RTS would use this. [start]
When the second routine does “leas 2,s”, the stack pointer moves down and it looks like this:
[xxxxx] <- Next bsr/jsr would be stored here. [start] <- Top of stack. RTS would use this.
Side Note: Data on the stack is never erased, but will be overwritten the next time something is stored there. The [xxxxx] is actually still [first].
Now if the subroutine does an RTS, it will be returning to start and not first. Thus, if you add that to the assembly and run it, the output will be:
TEST START FIRST START SECOND START SECOND END TEST END
I do not know of a legal way to do the same in BASIC, but I am sure there is some POKE that could be done to achieve the same thing.
The Microsoft BASIC ROMs do this trick often, when patching in new routines that override some function.
And now it’s time for a brain break.
Until next time…
BASIC09 internally does something a little different at times – calling a subroutine that is somewhat generic, but checking some state right before exit to modify JSR/(L)BSR’s return address on the stack, so that it can to somewheres else than where it came from when an RTS is encountered. If I remember correctly, Grfdrv did this in spots, too.
This sounds like another rabbit hole I might enjoy going down.
(insert “well, akchyually” meme)
OS9 actually has no way to identify if the stack overflows in a user process simply because the hardware has no way to set things up to cause a trap to the operating system when writing to memory that is not allocated to the process. It may be able to detect an overflow in some cases, but those would be fairly rare. Even if there was hardware support, there isn’t enough address space to use it effectively for detecting stack overflows.
C code might detect an overflow more often if, and only if, the compiler has added stack checking code in the generated program. It’s extremely unlikely that assembly programmers would had such code to their projects.
On modern systems with memory protection, the process layout is often set up so there are guard pages on either side of the stack area that are marked “not present” so a page fault triggers if something accesses those addresses. That allows the operating system to detect most stack overflows (and underflows) (or to allocate more memory to the stack, if appropriate). Even then, there are access patterns that can skip right over those guard pages and land on other valid memory or the stack pointer could be moved by the process, but most programs don’t do that.
Does this mean “myself bsr myself” will crash OS-9/6809?
Almost certainly. Under OS9, that will cause the stack to wrap around into IO space which will then get scribbled on. It’s anybody’s guess what that will do to the system. If that doesn’t crash, it will overwrite the interrupt vectors and system entry code (coco3) or the kernel (coco1/2). Under basic, it will probably overwrite itself first and crash that way.
I know C has a stack check error, but I thought OS-9 had one as well. What would it be checking against, at runtime?
FYI: in your first assembly example, you need some conditional branches between the CMP and BSR instructions. I mention it so someone doesn’t try using code like that and wonder why it always calls both subroutines.
As William Astle already mentioned … some “bne” to skip the following “bsr” would be need. Probably like this:
jsr [$a002] * Call ROM POLCAT routine, key comes back in A.
beq mainloop * If A=””, GOTO mainloop.
cmpa ‘L * Compare A to character “L”.
bne *+2
bsr moveleft * If A=”L”, GOSUB moveleft.
cmpa ‘R * Compare A to character “R”.
bne *+2
bsr moveright * If A=”R”, GOSUB moveright.
bra mainloop * GOTO mainloop.
ooops.
In addition, the BSR scheme has another minor drawback. Every BSR comes back to the calling position in the query chain. It is to be expected that register A is preserved in the called routine, because the chain proceeds querying A. If not, this could lead to fire up a routine for a key which wasn’t pressed. Maybe this could be addressed with placing the common return point on the stack and each called routine put back control to the main loop by just using RTS … I know, some kind of hacking the stack ;)
mainloop:
jsr [$a002]
beq mainloop
pshs mainloop,pcr
cmpa ‘L
beq moveleft
…
* unknown key (end of chain)
rts
moveleft:
…
rts
FYI: you can’t push a constant onto the stack. You’d have to load the address of mainloop into a register and push that.
PRELOOP
BSR MAINLOOP stack MAINLOOP entry point (for what good it would do)
MAINLOOP
JSR [$a002]
BEQ MAINLOOP
PSHS MAINLOOP,PCR
CMPA ‘L
BEQ MOVELEFT
…
* unknown key (end of chain)
*clean up stack
LEAS 2.S
RTS
*if it’s necessary to continue the MAINLOOP, then
JMP[,S] continued
*or a 16 bit return value can be returned using the stack
STD ,S
PULS D,PC
Whoops, forgot to cut out that problematic PSHS MAINLOOP,PCR bit 😅
Give me a few to wrap my head around this. I have a hard time visualizing the stack.
In general, if the the number keys to check grows larger (just imagine a command processor for an basic extension with more than a dozen commands) I tend to use a classic table approach. Search through a string of keys/command characters, if found take the index to a jump table, put the return address (input loop, command loop) on the stack and branch to the jump table entry address. These routines could simply leave with RTS or PULS …,PC
That sounds like using INSTR/ON GOTO in assembly. I shall have to learn that.