Prerequisites:

1) Apple II

2) Basic Prompt

Using only the instructions provided, can you crash the Apple II?

I'm curious if this is even possible because I have only ever programmed the machines in an Emulator, and I thought it would be cool to see what was possible on the Apple II in real life, as I am under the impression the machine had no memory protections.

For instance, with the PEEK command, I am under the impression that you could actually sweep it through the entire memory of the machine and not trigger a crash.

  • 1
    Can you define what kind of "crash" you're looking for? I recall saying "load" but forgetting a filename on my 2c would hang forever for a nonexistent tape drive, and presumably you could poke values into memory to cause all kinds of failures… – Peter Cooper Jr. Jul 22 '16 at 19:50
  • Hopefully a little more theatrical than that :) dumping memory and showing an error would be fun. – baordog Jul 22 '16 at 19:55
  • 2
    If we are allowing POKE, then any sort of undefined behaviour is possible, from video glitch crashes to hangs to reboots, I'm sure. – jdv Jul 22 '16 at 23:46
  • @jdv: A lot of locations on the Apple ][ and Apple // are triggered on access rather than on write (limiting actions to writes would require adding an extra gate), so peek can be plenty disruptive on that platform. – supercat Jul 25 '16 at 18:47
  • If we are allowing call, there's no end to the havoc you could wreak :-) For example, you could poke an infinite loop into memory somewhere and then just call it. – paxdiablo Nov 23 at 1:40

Applesoft is usually fairly well-behaved. For instance, this will give an OUT OF MEMORY error:

10 GOSUB 20
20 GOTO 10
RUN

However, there are many Applesoft bugs, mostly benign. Wikipedia mentions a couple that "crash":

  • A deficiency in Applesoft's error-trapping with ONERR means that the system stack would not be reset if an error-handling routine did not invoke RESUME, potentially leading to a crash.
  • Due to a short-cut in the programming of the overflow test when evaluating 16 bit numbers, entering any number from 437760 to 440319 for a new line number at the BASIC prompt will cause Applesoft to crash, usually to a monitor prompt.

E.g. for the second one you can just type the following and press return:

437760

Crash behaviour is unpredictable because (a) as you say, there is no memory protection - if the CPU starts executing arbitrary memory locations then "anything" can happen, and (b) I/O is memory-mapped - even just reading memory can cause problems. The following will usually hang the machine:

PRINT PEEK(49280) : REM $C080 SWITCHES OUT ROM/SWITCHES IN LC BANK 2 RAM

Uninitialized RAM will be a mix of #$00 and #$FF. If the CPU executes #$00 (i.e. BRK) the break vector($FFFE/$FFFF) might point to $0000, which could be anything - leading to another BRK etc.

On a NMOS 6502 (not 65C02) various opcodes actually hang the CPU:

POKE 768,2: CALL 768: REM EXECUTE HLT OPCODE

If you want to see what a BRK crash looks like:

POKE 768,0: CALL 768: REM EXECUTE BRK OPCODE

It will print the BRK address + 2, A/X/Y registers, P status register, and stack pointer. E.g:

0302-    A=03 X=9D Y=00 P=36 S=F2
*
  • What's special about the range $6AE00 to $6B7FF? I've seen such behavior, but find myself bewildered as to what would cause it to yield wackiness? My guess would be that code that looks for line numbers to be below 65280 [$FF00] decides that the above values meet that criterion, but that the decimal-to-binary conversion somehow ends up yielding values in that range. Since the system tests for immediate mode by looking for the MSB of the line number being $FF, having the system inconsistently think a line is or isn't immediate mode could wreak havoc on things. – supercat Dec 15 '16 at 16:11
  • @supercat The range $AB00 to $ABFF, which is 43776 to 44031, when followed by an additional decimal digit (0-9), triggers the bug. Look at my answer for the details on the actual bug (it's much too long for a comment). – Mitchell Spector Dec 15 '16 at 20:58
  • 1
    also 'PRINT 5+"A"+-5' (i.e. positive number+string+negative number) will crash Basic, but in a way that can be intercepted by placing a jump at $0001. – peter ferrie Feb 12 '17 at 3:02

As mentioned by @NickWestgate, there's an error in processing line numbers in Applesoft BASIC that causes a crash for line numbers between 437760 and 440319 (a 16-bit number with high byte $AB, followed by a decimal digit 0-9). This is supposed to cause a SYNTAX ERROR. Because the 6502 has a limited-range relative branch, the actual syntax-error routine is out of range. What's typically done is to find a nearby absolute JMP, and use a relative branch to that instead. Unfortunately, there wasn't a nearby absolute JMP to the syntax error routine, and the developer took a shortcut: there happens to be a branch to the syntax error routine that's within range, but it's a CMP followed by a BNE which will only branch if the accumulator is not equal to #TOKEN.GOTO, which happens to be $AB.

Here's the relevant code section from Jamtronix's Applesoft disassembly; as @NickWestgate pointed out, this is based on Bob Sander-Cederlof's disassembly, consolidated into one convenient file. The routine to look at is LINGET at $DA0C. A branch to SYNERR is needed at $DA1E. SYNERR.2 jumps to SYNERR; it's close but not quite close enough -- it's still out of range for a branch at $DA1E. So they branched instead to ON.1 (this is the shortcut), which only results in a branch to SYNERR.2 if the accumulator happens not to equal TOKEN.GOTO (which is $AB). The accumulator at that point is the high byte of the parsed prospective line number, not including the extra digit that you're trying to include (which is supposed to be invalid because it would make the line number too large). So if the high byte of the part of the line number that has been processed already is $AB and if there's at least one additional digit, the code doesn't branch on the BNE SYNERR.2; it just marches on instead of jumping to SYNERR.2, and it will take some completely unintended action.

AB-             TOKEN.GOTO       .EQ $AB
.
.
.
D981- 4C C9 DE  SYNERR.2 JMP SYNERR
.
.
.
D9F4- C9 AB     ON.1   CMP #TOKEN.GOTO
D9F6- D0 89            BNE SYNERR.2
D9F8- C6 A1     ON.2   DEC FAC+4    COUNTED TO RIGHT ONE YET?
D9FA- D0 04            BNE .3       NO, KEEP LOOKING
D9FC- 68               PLA          YES, RETRIEVE CMD
D9FD- 4C 2A D8         JMP EXECUTE.STATEMENT.1   AND GO.
DA00- 20 B1 00  .3     JSR CHRGET   PRIME CONVERT SUBROUTINE
DA03- 20 0C DA         JSR LINGET   CONVERT LINE #
DA06- C9 2C            CMP #','     TERMINATE WITH COMMA?
DA08- F0 EE            BEQ ON.2     YES
DA0A- 68               PLA          NO, END OF LIST, SO IGNORE
DA0B- 60        RTS.7  RTS
                *--------------------------------
                *      CONVERT LINE NUMBER
                *--------------------------------
DA0C- A2 00     LINGET LDX #0       ASC # TO HEX ADDRESS
DA0E- 86 50            STX LINNUM   IN LINNUM.
DA10- 86 51            STX LINNUM+1
DA12- B0 F7     .1     BCS RTS.7    NOT A DIGIT
DA14- E9 2F            SBC #'0'-1   CONVERT DIGIT TO BINARY
DA16- 85 0D            STA CHARAC   SAVE THE DIGIT
DA18- A5 51            LDA LINNUM+1 CHECK RANGE
DA1A- 85 5E            STA INDEX
DA1C- C9 19            CMP /6400    LINE # TOO LARGE?
DA1E- B0 D4            BCS ON.1     YES, > 63999, GO INDIRECTLY TO 
                *                   "SYNTAX ERROR".
                *<<<<<DANGEROUS CODE>>>>>
                *      NOTE THAT IF (A) = $AB ON THE LINE ABOVE,
                *      ON.1 WILL COMPARE = AND CAUSE A CATASTROPHIC
                *      JUMP TO $22D9 (FOR GOTO), OR OTHER LOCATIONS
                *      FOR OTHER CALLS TO LINGET.
                *
                *      YOU CAN SEE THIS IS YOU FIRST PUT "BRK" IN $22D9,
                *      THEN TYPE "GO TO 437761".
                *
                *      ANY VALUE FROM 437760 THROUGH 440319 WILL CAUSE
                *      THE PROBLEM.  ($AB00 - $ABFF)
                *<<<<<DANGEROUS CODE>>>>>
DA20- A5 50            LDA LINNUM   MULTIPLY BY TEN
DA22- 0A               ASL
DA23- 26 5E            ROL INDEX
DA25- 0A               ASL
DA26- 26 5E            ROL INDEX
DA28- 65 50            ADC LINNUM
DA2A- 85 50            STA LINNUM
DA2C- A5 5E            LDA INDEX
DA2E- 65 51            ADC LINNUM+1
DA30- 85 51            STA LINNUM+1
DA32- 06 50            ASL LINNUM
DA34- 26 51            ROL LINNUM+1
DA36- A5 50            LDA LINNUM
DA38- 65 0D            ADC CHARAC   ADD DIGIT
DA3A- 85 50            STA LINNUM
DA3C- 90 02            BCC .2
DA3E- E6 51            INC LINNUM+1
DA40- 20 B1 00  .2     JSR CHRGET   GET NEXT CHAR
DA43- 4C 12 DA         JMP .1       MORE CONVERTING
                *--------------------------------
  • Thanks for explaining the glitch pointed out in Nick Westgate's answer. Understanding such well "optimised" code (and its shortcomings) is hard, and answers like this make it that little bit easier. – wizzwizz4 Dec 15 '16 at 21:11
  • Nice. Just a note: Jonno's listing is based on Bob Sander-Cederlof's. – Nick Westgate Dec 15 '16 at 22:17
  • @NickWestgate -- You're right -- I'll add that to the answer. Thanks for pointing it out. – Mitchell Spector Dec 15 '16 at 22:19
  • and the same issue exists on the Commodore 64, but the line number range is different because the compared token has a different value. – peter ferrie Feb 12 '17 at 2:37

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.