Let’s dig into ancient system software and file an entry for the 2026 “Obsolete operating systems bug bounty” with a report that would have been pointless even back then.
Luckily (sadly?), there is no such bug bounty, but the strange behavior is real nonetheless. Here goes:
SetRGB4 overwrites memory
Summary
On Kickstart 1.3:
When address zero contains a Copper instruction to set a color register,
a call to SetRGB4(vp,n,r,g,b) will overwrite the word at address 2 if the color number n
matches the color register at address 0.
Come again. What?
Read the steps to reproduce and it will become clearer…
Steps to reproduce
- Write the word
0x0186to address zero - Call graphics.library’s SetRGB4 to set color #3 to RGB 7/8/9
- Observe memory contents at address zero
movem.l d0-a6,-(a7) ; save registers for later
move.l 4,a6 ; get exec, graphics, intuition
move.l 156(a6),a6
move.l 368(a2),a5
move.l ib_ActiveScreen(a5),a5
lea sc_ViewPort(a5),a0
moveq #3,d0 ; n
moveq #7,d1 ; r
moveq #8,d2 ; g
moveq #9,d3 ; b
move.w #$0186,0 ; set memory at 0
jsr _LVOSetRGB4(a6) ; SetRGB4(a0:vp,d0:n,d1:r,d2:g,d3:b)
move.l 4,a6 ; prepare text
lea .data(pc),a1
move.l 0,(a1)
lea .fmt(pc),a0
lea .put(pc),a2
lea .buf(pc),a3
jsr _LVORawDoFmt(a6)
movem.l (a7)+,d0-a6 ; restore BCPL environment
lea .buf(pc),a0
move.l a0,d1
moveq #-1,d2
.cnt addq.l #1,d2
tst.b (a0)+
bne.b .cnt
moveq #16,d0
move.l 172(a2),a4 ; writeoutput
jsr (a5)
moveq #0,d0
rts
.put move.b d0,(a3)+
rts
.data dc.l 0
.fmt dc.b 'Memory at address 0 contains: %08lx',10,0
.buf ds.b 256
You would expect the longword
0x01860000 at address zero — after a fresh boot, address zero contains
0x00000000, and you just put a 0x0186 at the start.
But actually you will find 0x01860789 there.
Why?
It seems like some Copper list rewriting magic goes haywire.
- In a Copper list,
0186 xxxxwrites a value to color register 3 - Some function tries to patch Copper instructions for color #3:
0186 xxxxbecomes0186 0789everywhere - That’s fine for the current screen, but address zero is also treated as part of the Copper list that needs patching
Ask me how I found out!
How did you find out?
I was working on a tiny intro for ROMA.EXE when I ran into this. Using address zero as a counter was a hack to save two bytes:
; we're doing a BCPL call before and we know
; a0 will contain 00000000 (system memory base)
; now we need execbase in a6
move.l (a0)+,a2 ; a0 = 00000004, a2 = 00000000
move.l (a0),a6 ; a6 = execbase
; now we can use (a2) as a scratch register
; (we do this because all other registers are in use)
In the intro, I’m calling SetRGB4 repeatedly. It was only when the counter
value accidentally was equal to
390 (0186 in hex) that things started to behave oddly.
That was fun to debug. :) But not enough fun to keep digging and present the actual culprit in the ROM code here – the SetRGB4 code path is a hairy mess of branches, stack manipulation, and subroutine calls. Who knows what other surprises are lingering in there?
My workaround was to multiply the values by 7, which avoids 390 altogether…
By the way: Having a Copper list at address zero isn’t as weird as it sounds – demos for the No-CPU runner start like that. But fear not: SetRGB4 poses no threat in No-CPU land, as there is no CPU to execute it! :)