68000 exception processing in megadrive games

Ask anything your want about Megadrive/Genesis programming.

Moderator: BigEvilCorporation

Post Reply
powerofrecall
Very interested
Posts: 237
Joined: Fri Apr 17, 2009 7:35 pm
Location: USA

68000 exception processing in megadrive games

Post by powerofrecall » Sat Dec 18, 2010 2:51 am

I've been working on a game engine (in C, with Stef's kit and some of his code to guide me) for about a month and a half now and as it's growing in complexity it would become useful to have some basic exception processing in the code that would be able to display at runtime, especially as I expand testing to the real hardware.

Most commercial MD games will vector to a simple routine that will either RTE out of the exception, or in some circumstances do a STOP, or in others do a loop to hang the game up. This is really trivial to do, of course, but isn't all that useful when you're adding lots of new code or making large changes to the game you're working on. To that effect my engine so far has a very simple bit of code that will print onscreen when an exception occurs, but it's lacking detailed info. Since I rebuild a lot, it's been easy to spot mistakes I've made so far, but I think that will change.

Some commercial MD games have some very in-depth output including a reason for the exception, particularly Flashback and Mortal Kombat 3/UMK 3. These games will pop up with the exception description and a register dump before hanging up. MK3 also seems to show (part of?) the stack. I have been able to take a peek at these routines via IDA and, again, they're not horribly complicated, (other than MK3's clever use of the upper byte of the vector address to store the reason for the exception) but I'm lacking some understanding of how some of these exceptions/interrupts actually happen. Traveler's Tales MD games tend to have VERY detailed exception processing that can actually determine what caused the fault to begin with:

http://tcrf.net/File:MickeyManiaCrash.png

Note that I don't particularly care about fancy scrolling backgrounds or fonts from Puggsy or anything in my error handler. :) A description of the fault, register and stack dumps would be great.

Obviously, this level of detail of output would be a great thing to have as my code grows in complexity. So the question is, are there any resources as to how to code this? I'm not a really a 68000 coder, but I'm not completely new to it either, so I'm not against doing this myself. I just need some tips and that's why I come to you geniuses. :)

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Sat Dec 18, 2010 5:06 am

Well, unless you want to handle everything with assembly, you need to call a C function. That means pushing the volatile registers on the stack, pushing any arguments on the stack, then calling the function. Look at this interrupt handler I wrote for the SEGA CD:

Code: Select all

; Sub-CPU ASIC Done (INT01) Service Handler

asic_int:
        tst.l   ASICHandler
        bne.s   +
        rte
+
        movem.l d0-d1/a0-a1,-(sp)
        move.l  ASICParam,d0
        move.l  d0,-(sp)
        movea.l ASICHandler,a0
        jsr     (a0)
        addq.l  #4,sp
        movem.l (sp)+,d0-d1/a0-a1
        rte
ASICHandler is a pointer to the C function to call from the exception handler (the exception in this case being interrupt level 1). If there's no function set, it merely exits via rte. If there is a C function set as the handler, the code pushes d0, d1, a0, and a1 on the stack. C functions can freely change those registers without saving them, so we need to save them. Then you push any arguments needed, from right to left (so the left-most arguments are pushed last). In this case, I only push one argument, whatever is in the ASICParam variable. I then call ASICHandler by loading it in a0 and using jsr. The C function would be written like normal; if it had a return value, it would be in d0 when it returns back from the jsr. We then clean the arguments off the stack - I pushed one long, so I add 4 to the stack. By the way, all arguments on the stack are longs, no matter what. If the argument is a character, it's still a long on the stack. Then we restore d0, d1, a0, and a1 from the stack and return via rte.

powerofrecall
Very interested
Posts: 237
Joined: Fri Apr 17, 2009 7:35 pm
Location: USA

Post by powerofrecall » Sat Dec 18, 2010 2:30 pm

Thanks for the info, Chilly. Think I'll definitely code it in C. I'm starting to form an idea.

Like I mentioned in my too-long post, could I use the top byte of the vector table entries to store the reason for the exception (so my handler knows why it's being called)? I suppose I could maybe layout the vector table something like this:

Code: Select all

dc.l     0, 0x200
dc.l    (0x1000000+EXCEPTION)        /* bus error */
dc.l    (0x2000000+EXCEPTION)        /* addr error */
dc.l    (0x3000000+EXCEPTION)        /* illegal */
dc.l    (0x4000000+EXCEPTION)        /* zero divide */
dc.l    (0x5000000+EXCEPTION)        /* chk */
dc.l    (0x6000000+EXCEPTION)        /* trapv */
dc.l    (0x7000000+EXCEPTION)        /* privilege */

etc...
Which, if I'm right, would jump to EXCEPTION stored in the bottom 24bits and leave the top 8 bits intact.
Then, I could check the top byte and the C language error handler function could do whatever printout or handling it needs to based on the reason. Which leaves another question, how would I get the address of the vector that got called? The PC? Are registers/PC accessible from C?

I know this seems like a silly thing to worry about implementing but the way I figure is if I can put a good debugging-oriented handler together and release it to the community, all the better. :)

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Sat Dec 18, 2010 7:16 pm

Yes, the top byte on the 68000 can be used for flags and such. Some OSes (cough - Mac - cough) did that in all pointers, leading to issues when they needed to move beyond 16MB of addressable space. If you wish to pass some info to the C code, store it in a structure before calling the C code, passing a pointer as an arg. Maybe like this

Code: Select all

ExceptEntry:
    movem.l d0-d7/a0-a6,-(sp)
    pea     ExceptEntry(pc)
    pea     (sp)
    lea     ExceptHandler,a0
    jsr     (a0)
    addq.l  #8,sp
    movem.l (sp)+,d0-d7/a0-a6
    rte
The argument is a pointer to the stack where the PC for the exception followed by all the registers are stored (on the stack as well). Note that ExceptEntry is pushed as PC relative - that preserves the upper byte you want. You can push other info there as well - whatever you wish to use in the handler. The struct for above would look like

Code: Select all

    struct ExceptData {
        unsigned int entry;
        unsigned int reg[15];
    };
The C func would look like

Code: Select all

void ExceptHandler(struct ExceptData *except)
{
    int exc_vec = except->entry >> 24;

    yada yada yada

}

powerofrecall
Very interested
Posts: 237
Joined: Fri Apr 17, 2009 7:35 pm
Location: USA

Post by powerofrecall » Sat Dec 18, 2010 9:43 pm

Chilly, you need a PayPal donate button as a post signature. :)

I had started working on it but kind of hit the wall right where you picked up. I think I have it now, though 8)

Post Reply