• Updated 2023-07-12: Hello, Guest! Welcome back, and be sure to check out this follow-up post about our outage a week or so ago.

Weird crash under System 6 for my app

Mu0n

Well-known member
I'm developping a silly little game with fluid masked sprite animations and like I used to do back in the day in 2005, I'm regularly testing it under both System 7.5.3 and System 6.0.8. Right now, it works fine under 7, but crashes with an illegal instruction in 6.

I took the opportunity to finally dig in the excellent book ressource provided by @pcamen over here: (1991 MacsBug book) https://vintageapple.org/macbooks/pdf/Debugging_Macintosh_Software_with_MacsBug_1991.pdf

and got Macsbug 6.2.2 from here https://macgui.com/downloads/?file_id=17063&keywords=macsbug

Now, I understand the concept of Mac Traps, and that they have hex codes followed by a variable amount of them for their parameters, records, etc and that they can be properly formatted with a Macsbug template for better, albeit optional, viewing. I understand the concept of the program counter and have read the basic "how to get around" chapter of the book. But this is just scratching the surface of knowing my way around running time debugging.

My program attempts to set a fullscreen GrafPort. It uses qd.screenBits.bounds if it detects that the screen is 512x342 (as it should inside a mini-vMac environment running System 6). If it doesn't, it'll set up a plainDBox window centered on whatever resolution real estate it can find and disable the menu area. Both of these situations have been tested extensively 15 years ago and worked well, but here I am nowadays hitting this snag. I'm taking this opportunity to learn about MacsBug to pinpoint the exact location where the bug happens - especially since it's not happening at all in my dev environment (7.5.3 in Basilisk II) but only when I bring the app to System 6.0.8 in mini-vMac.

Here's a screencap of my MacsBug screen upon the first encountered Illegal Instruction error;





 I did list many lines after these and they're all the same, just a bunch of 00's.

So what an experienced programmer of the 68000 does from here? Set up multiple debug break points in THINK C and hope for the best? Check some specific memory locations? Trace from an earlier point in the code? (tracing from that point basically returns to this exact Macsbug screen endlessly)

 

Crutch

Well-known member
The opcode for ORI is 0000 and it’s getting passed 00 as an argument. So it looks like you jumped into zeroed-out memory and the 68000 is trying to execute it. Possible you are calling a function via a pointer that’s not initialized? Are you calling ModalDialog for your plainDBox with a filterProc pointer that’s set to an a valid but non-NULL value?

 

Mu0n

Well-known member
//Sets which rectangular area to pick gfx from inside the 'PICT' resources
void SetTheRects(){
SetRect(&batCarRect, 0, 0, 64, 40);
SetRect(&batCarMaskRect, 0, 0, 64, 40);
SetRect(&bigPictureRect, 0, 0, 512, 342);
SetRect(&batCarIsAtRect, 200, 200, 240, 240);
batCarWasAtRect = batCarIsAtRect;
batCarComboRect = batCarIsAtRect;

//Individual gfx faces and masks
SetRect(&batCarSprite[0].face, 0, 0, 32, 40);
SetRect(&batCarSprite[0].mask, 32, 0, 64, 40);
}


Here's the function where I think the crash (again, only under System 6) occurs.

Here's the full source code where main is located:

#include "Misc.h"
#include "Offscreen.h"
#include "Sprites.h"

/*
* SmoothAnim2019 - December 2nd 2019 by Michael Juneau
* Attempt at creating smooth animation with a sprite which does not destroy the background
* by using 4 grafPorts: the screen, a pristine background backup, a sprite and its mask.
* Based on Chapter 0 of "Tricks of the Mac Game Programming Gurus
*/

/********************* defines ***/

#define rBatCarID 128 //'PICT' ID# of the batcar
#define rBatCarMaskID 128 //'PICT' ID# of the batcar mask
#define rBackgroundID 129 //'PICT' ID# of the street background
#define kPutInFront (WindowPtr)-1L
#define kWaitTicks 3L // Sets the delay in Ticks between frames. Try 3. Try 2. Try 0.

/********************* typedef tSpriteType ***/

typedef struct{
Rect face;
Rect mask;
} tSpriteType;

/********************* Global Variables ***/

Rect bigPictureRect, //Rectangle of the refreshing window area
batCarRect, //batCar
batCarMaskRect, //batCar mask
batCarIsAtRect, //current location of batcar
batCarWasAtRect, //old location of batcar
batCarComboRect; //current and old locations, union of

GrafPtr workPort, //pointer to offscreen working gfx assembling area
facesPort, //copy of characters gfx
maskPort, //copy of masks gfx
backgroundPort, //copy of background (street)
mainWindow; //copy of main window

tSpriteType batCarSprite[1]; //struct containing the rects of the batcar and it mask,
//currently ONE frame

long targetTick;

int carSpeed = 10;

/********************* Function Prototypes ***/

void InitBorderlessAndPort(void);
void OpenMainWindow(void);
void SetTheRects(void);
void SetThePorts(void);
void CleanUpAndLeave(void);
void GameLoop(void);
void DriveBatCar(void);
void DoDelay(void);
void ShowBatCar(void);


/********************* Function definitions ***/
void InitBorderlessAndPort(){
InitMacStuff(); //Inits the &qd.thePort grafPort (ie screen bits) and a bunch of managers
HideMyMenuBar();
mainWindow = MacPlusArea(gray,white,false); //takes a full screen in 512x342 if on a compact mac; otherwise, centers it
}

void OpenMainWindow (void) {
mainWindow = GetNewCWindow(128, 0L, kPutInFront); // Load window from resource.
ShowWindow((GrafPtr)mainWindow); // Now display it.
SetPort((GrafPtr)mainWindow); // Make its port current.
ClipRect(&bigPictureRect); // Set its clip region.
CopyRgn(mainWindow->clipRgn, mainWindow->visRgn); // Set its visRgn.
ForeColor(blackColor); // Set its pen color to black.
BackColor(whiteColor); // Set background color white.
}

//Sets which rectangular area to pick gfx from inside the 'PICT' resources
void SetTheRects(){
SetRect(&batCarRect, 0, 0, 64, 40);
SetRect(&batCarMaskRect, 0, 0, 64, 40);
SetRect(&bigPictureRect, 0, 0, 512, 342);
SetRect(&batCarIsAtRect, 200, 200, 240, 240);
batCarWasAtRect = batCarIsAtRect;
batCarComboRect = batCarIsAtRect;

//Individual gfx faces and masks
SetRect(&batCarSprite[0].face, 0, 0, 32, 40);
SetRect(&batCarSprite[0].mask, 32, 0, 64, 40);
}

void SetThePorts(void){
//Create BitMap for batcar gfx
CreateOffScreenBitMapLite (&batCarRect, &facesPort);
LoadGraphicLite (rBatCarID);

//Create BitMap for batcar mask gfx
CreateOffScreenBitMapLite (&batCarMaskRect, &maskPort);
LoadGraphicLite (rBatCarMaskID);

// Create BitMap for background picture.
CreateOffScreenBitMapLite (&bigPictureRect, &backgroundPort);
LoadGraphicLite(rBackgroundID);

// Create BitMap for off-screen assembly of images.
CreateOffScreenBitMapLite (&bigPictureRect, &workPort);

//OpenMainWindow(); //doesn't seem to be necessary; cut.

//{This fills the main window with the background picture, so the user can see it.

CopyBits(&((GrafPtr)backgroundPort)->portBits,
&((GrafPtr)mainWindow)->portBits,
&bigPictureRect, &bigPictureRect, srcCopy, mainWindow->visRgn);

// This fills the workPort.portBits with the background picture, so updates can be done quickly.
CopyBits(&((GrafPtr)backgroundPort)->portBits,
&((GrafPtr)workPort)->portBits,
&bigPictureRect, &bigPictureRect, srcCopy, mainWindow->visRgn);
}

void CleanUpAndLeave(){
DisposeWindow(mainWindow);
ShowMyMenuBar();
}

void DriveBatCar(){
batCarWasAtRect = batCarIsAtRect;
OffsetRect(&batCarIsAtRect, 0, -carSpeed);
if(batCarIsAtRect.bottom < 0){
batCarIsAtRect.top = 342;
batCarIsAtRect.bottom = 382;
}
ShowBatCar();
DoDelay();
}

void ShowBatCar (void) // Do the animation and make it appear on the screen.
{
CopyMask(&((GrafPtr)facesPort)->portBits,
&((GrafPtr)maskPort)->portBits,
&((GrafPtr)workPort)->portBits,
&batCarSprite[0].face,
&batCarSprite[0].mask,
&batCarIsAtRect);


UnionRect(&batCarWasAtRect, &batCarIsAtRect, &batCarComboRect);


CopyBits(&((GrafPtr)workPort)->portBits,
&(((GrafPtr)mainWindow)->portBits),
&batCarComboRect, &batCarComboRect, srcCopy, mainWindow->visRgn);

CopyBits(&((GrafPtr)backgroundPort)->portBits,
&(((GrafPtr)workPort)->portBits),
&batCarIsAtRect, &batCarIsAtRect, srcCopy, mainWindow->visRgn);

}

void DoDelay (void)
{
do
{
}
while (TickCount() < targetTick); // Loop until TickCount() catches up.
targetTick = TickCount() + kWaitTicks;
}

void GameLoop(){
while(!Button()){
DriveBatCar();
}
while(Button()){
}
while(!Button()){
}
}

/********************** Main Function ****/

void main(void){
InitBorderlessAndPort();
SetTheRects();
//SetThePorts();
//GameLoop();
CleanUpAndLeave();
}






Here's how I force a 512x342 graphic area even with macs that are newer than a compact Mac:

Code:
/*------ Hide Menu bar
   1) HideMyMenuBar();  Explicit use
   2) ShowMyMenuBar();  Explicit use

                      ----------*/
void HideMyMenuBar(void)
 {
 Rect rcMBar;
 short *setMBarHeight;
 
 setMBarHeight=(short *)0x0BAA;
 
 if ( gs_hrgnMBar == 0L) {
 
    gs_dyMBar  =  GetMBarHeight();
    
    *setMBarHeight=0;
    
    rcMBar = qd.screenBits.bounds;
    rcMBar.bottom = rcMBar.top + gs_dyMBar;
    
    gs_hrgnMBar = NewRgn();
    RectRgn( gs_hrgnMBar, &rcMBar );
    UnionRgn( GetGrayRgn(), gs_hrgnMBar, GetGrayRgn() );
    PaintOne(nil,gs_hrgnMBar);
    }
 }

void ShowMyMenuBar(void)
 {
 short *setMBarHeight;
 setMBarHeight=(short *)0x0BAA;
 
 if( gs_hrgnMBar) {
 
   *setMBarHeight=gs_dyMBar;
   DiffRgn(GetGrayRgn(), gs_hrgnMBar, GetGrayRgn());
   DisposeRgn(gs_hrgnMBar);
   gs_hrgnMBar=0L;
   }
 }
 /*------- End of Hide Menu Bar -------*/
 
 
/*--------- Ranged Random -----------*/
unsigned short rangeRandom( unsigned short min, unsigned short max )
/* the function will calculate the correct max and min values */
{
  long range;
  long randomNumber;
  
  range = MAX(min,max) - MIN(min,max) + 1;
  randomNumber=Random();
  randomNumber=ABS(randomNumber);
  
  return( (randomNumber * range)/kRandomUpperLimit+min);
}
/*--------- End of Ranged Random -----------*/



/* Mac Plus Area - creates a 512x342 screen centered in the middle, with correct
Clip region. If the screen is larger than that, makes a border with borderPat as its
pattern. */

GrafPtr MacPlusArea(Pattern borderPat, Pattern middlePat,Boolean wantMenus)
{
WindowPtr window;
Rect r;

r=qd.screenBits.bounds;
r.top=kMenuBarHeight;

if(kScreenWidth <=512 && kScreenHeight <=342)
  {
  WholeScreen(middlePat,wantMenus);
  }
else 
 {
 
 if(wantMenus) window=NewWindow(nil,&r,nil,true,plainDBox,kMoveToFront,false,0);
 else window=NewWindow(nil,&(qd.screenBits.bounds),nil,true,plainDBox,kMoveToFront,false,0);
 
 SetPort(window);
 FillRect(&(window->portRect), borderPat);

 if(wantMenus) {
   PortSize(512,362);
   MovePortTo((kScreenWidth-512)/2,(kScreenHeight-342-kMenuBarHeight)/2); 
 }
 else {
 MovePortTo((kScreenWidth-512)/2,(kScreenHeight-342)/2);
  PortSize(512,342);
 }
 ClipRect(&(window->portRect));
 CLS(middlePat,wantMenus);
 }
 
 CopyRgn(window->clipRgn, window->visRgn);
 return (GrafPtr)window;
}


/*--------- CLS ---------------*/
void CLS(Pattern thePat, Boolean wantMenus)
{
Rect r;
WindowPtr window;

GetPort(&window);

r=window->portRect;
r.top=kMenuBarHeight;

if(wantMenus) FillRect(&r,thePat);
else FillRect(&(window->portRect),thePat);
}
/*--------- End of CLS --------*/

/* WholeScreen - Will create a screen-sized featureless window filled with the pattern
   of your choice. You are responsible to deal with any previous windows and ports 
   NOTE: use DisposeWindow to get rid of it */
   
void WholeScreen(Pattern thePat, Boolean wantMenus)
{
WindowPtr window;
Rect r;

r=qd.screenBits.bounds;
r.top=kMenuBarHeight;

if(wantMenus) window=NewWindow(nil,&r,nil,true,plainDBox,kMoveToFront,false,0);
else window=NewWindow(nil,&(qd.screenBits.bounds),nil,true,plainDBox,kMoveToFront,false,0);
SetPort(window);

FillRect(&(window->portRect), thePat);
}

short OldLoadFile(SFTypeList myTypes)
  {
  SFReply reply;      //GetFile reply
  short openResult=0; //trails file number
  long count;         //used to read the data
  Point where={50,50};//SFGetFile dialog location
  
  SFGetFile(where,"\pSelect a file",0L,1,myTypes,0L,&reply);
  
  if(reply.good) FSOpen(reply.fName,reply.vRefNum,&openResult);
  return openResult;
  }

int GetScreenWidth(void)
  {
  return qd.screenBits.bounds.right-qd.screenBits.bounds.left;
  }
  
int GetScreenHeight(void)
  {
  return qd.screenBits.bounds.bottom-qd.screenBits.bounds.top;
  }
 

Crutch

Well-known member
On casual late night read that all looks pretty reasonable.  Any idea where the program was before it jumped into this land of zeros?  A7 is your stack pointer.  It’s pointing to $003CC4CA.  That might be where you would go if you executed an RTS from here.  What happens if you do IL $3CC4CA (or a bit before) to see what instructions the 68000 was executing when it jumped to no-man’s land?

Also, you’re currently executing code at address $80something.  Dog_Cow would probably know for sure, but I’m pretty certain that’s a ROM address on these machines.  Did you call an unimplemented trap that landed you in ROM somehow?  Sorry like I said ... not a Macsbug expert but that’s weird.

 

Crutch

Well-known member
Wait ... I just saw you’re using Offscreen.h. Those are the calls dealing with Gworlds etc. documented in Inside Macintosh Volume VI. Those are not supported under System 6, I’m fairly certain. I don’t see them in the code you included, but are you calling those anywhere? If so that’s very likely your problem. 

 

Mu0n

Well-known member
Offscreen.c is a mixture of old utility functions I cobbled up together 15 years ago and new code from "Tricks of the Mac Game Programming Gurus". It might fail there possibly because there is some pointer work in there

Code:
/* OffScreen.c
 */
#include "OffScreen.h"

#ifndef FALSE
#define FALSE 0
#define TRUE 1
#endif


// Creates an offscreen bitmap, without any error checking.
// If this function fails to create the bitmap, the program crashes.
// DO NOT USE THIS FUNCTION in any real program.

void CreateOffScreenBitMapLite (Rect *theRect, GrafPtr *offScreen)
{
	GrafPtr		theBWPort;
	BitMap		theBitMap;	
	long		theRowBytes;
	
	theBWPort = (GrafPtr)(NewPtr(sizeof(GrafPort)));
	OpenPort(theBWPort);
	theRowBytes = (long)((theRect->right - theRect->left + 15L) / 16L) * 2L;
	theBitMap.rowBytes = (short)theRowBytes;
	theBitMap.baseAddr = NewPtr((long)theBitMap.rowBytes * 
		(theRect->bottom - theRect->top));	
//	ERROR CHECKING GOES HERE IN CreateOffScreenBitMap()--see MGWGraphics1.c
	theBitMap.bounds = *theRect;
//	ERROR CHECKING GOES HERE IN CreateOffScreenBitMap()--see MGWGraphics1.c
	SetPortBits(&theBitMap);
	ClipRect(theRect);
	RectRgn(theBWPort->visRgn, theRect);
	EraseRect(theRect);
	*offScreen = theBWPort;
}

// Handy function that loads a PICT graphic, get's its bounds and draws it.
// The port drawn to is assumed the current port.  No scaling is done.

void LoadGraphicLite (short thePictID)
{
	Rect		bounds;
	PicHandle	thePicture;
	
	thePicture = GetPicture(thePictID);			// Load graphic from resource fork.
												
//	ERROR CHECKING GOES HERE IN LoadGraphic()--see MGWGraphics1.c
	
	HLock((Handle)thePicture);					// If we made it this far, lock handle.
	bounds = (*thePicture)->picFrame;			// Get a copy of the picture's bounds.
	HUnlock((Handle)thePicture);				// We can unlock the picture now.
	OffsetRect(&bounds, -bounds.left, -bounds.top);	// Offset bounds rect to (0, 0).
	DrawPicture(thePicture, &bounds);			// Draw picture to current port.
	ReleaseResource((Handle)thePicture);		// Dispose of picture from heap.
}


Boolean InitBitMap(BitMap *loadBM)
      {
      loadBM->rowBytes = ((loadBM->bounds.right - loadBM->bounds.left +
                                  15) >> 4) << 1;
         loadBM->baseAddr =
                  NewPtrClear(loadBM->rowBytes * (long) (loadBM->bounds.bottom
                                      - loadBM->bounds.top));
      return loadBM->baseAddr != nil;
      } 
 
Boolean LoadPicResIntoBitMap(BitMap *loadBM,int picID)
      {
      PicHandle myPicH = NULL;
      GrafPtr oldG, newG;
      BitMap saveBits;
     
      if(NULL == loadBM) return false;   /* dpo */
      
      GetPort(&oldG); 
 
/* 5 following lines handle the PICT retrieval */                                                          
      if(NULL== (myPicH=GetPicture(picID))) 
       {
       return false;
       }
     
      loadBM->bounds=(*myPicH)->picFrame;
      if(!InitBitMap(loadBM))
        {
        DisposeHandle((Handle)myPicH);
        return false;
        }

/* Next line creates a new port with the correct sized Rect read from the PICT */

      if ( ! CreateOffscreenGrafPort(&newG, &(loadBM->bounds))) 
         {
         DisposeHandle((Handle)myPicH);
         return false;
         }
      SetPort(newG);
      saveBits = newG->portBits;
      SetPortBits(loadBM);
      EraseRect(&(loadBM->bounds));
        
/* Once the offscreen BitMap object is set as the port, draw onto it from the picHandle*/
      HLock((Handle)myPicH);
      DrawPicture(myPicH,&(loadBM->bounds));
      HUnlock((Handle)myPicH);

      
/* Last lines take care of the cleanup */     
      SetPortBits(&saveBits);
      SetPort(oldG);   
      
      DisposeHandle((Handle)myPicH);
      DestroyOffscreenGrafPort(newG); 
      
      return true;
      }

Boolean LoadPicHandleIntoBitMap(BitMap *loadBM, PicHandle myPicH)
      {
      GrafPtr oldG, newG;
      BitMap saveBits;
     
      if(NULL == loadBM) return false;   /* dpo */
      
      GetPort(&oldG); 
      
      loadBM->bounds=(*myPicH)->picFrame;
      if(!InitBitMap(loadBM))
        {
        return false;
        }

/* Next line creates a new port with the correct sized Rect read from the PICT */

      if ( ! CreateOffscreenGrafPort(&newG, &(loadBM->bounds))) 
         {
         return false;
         }
      SetPort(newG);
      saveBits = newG->portBits;
      SetPortBits(loadBM);
      EraseRect(&(loadBM->bounds));
        
/* Once the offscreen BitMap object is set as the port, draw onto it from the picHandle*/
      HLock((Handle)myPicH);
      DrawPicture(myPicH,&(loadBM->bounds));
      HUnlock((Handle)myPicH);

/* Last lines take care of the cleanup */     
      SetPortBits(&saveBits);
      SetPort(oldG);   
      
      DestroyOffscreenGrafPort(newG); 
      return true;
      }


Boolean CreateOffscreenGrafPort(GrafPtr *newOffscreen, Rect *inBounds)
      {
          GrafPtr savePort;
          GrafPtr newPort;
          
          GetPort(&savePort);    /* need this to restore thePort after OpenPort */

          /* since the bits are just memory, let's clear them before we start*/
          newPort = (GrafPtr) NewPtrClear(sizeof(GrafPort));    /* allocate the grafPort */
          if (MemError() != noErr)
              return FALSE;        /* failed to allocate the off-screen port */
          /*
          the call to OpenPort does the following . . .
              allocates space for visRgn (set to screenBits.bounds) and
              clipRgn (set wide open)
              sets portBits to screenBits
              sets portRect to screenBits.bounds
              (See Inside Mac: Imaging with QuickDraw,
              pages 2-38 to 2-39)
              side effect: does a SetPort(&offScreen)
          */
          OpenPort(newPort);
          /* make bitmap the size of the bounds that caller supplied */
          newPort->portRect = *inBounds;
          newPort->portBits.bounds = *inBounds;
          RectRgn(newPort->clipRgn, inBounds);    /* avoid wide-open clipRgn, be safe  */
          RectRgn(newPort->visRgn, inBounds);    /* in case newBounds is screen bounds */

          /* rowBytes is size of row, it must be rounded up to an even number of bytes */
          newPort->portBits.rowBytes = ((inBounds->right - inBounds->left +
                                  15) >> 4) << 1;

          /* number of bytes in BitMap is rowBytes * number of rows */
          /* see notes at end of Technote about using _NewHandle rather than _NewPtr*/
          newPort->portBits.baseAddr =
                  NewPtrClear(newPort->portBits.rowBytes * (long) (inBounds->bottom
                                      - inBounds->top));
          if (newPort->portBits.baseAddr == nil) { /* check to see if we had
                              enough room for the bits */
              SetPort(savePort);
              ClosePort(newPort);      /* dump the visRgn and clipRgn */
              DisposePtr((Ptr)newPort); /* dump the GrafPort */
              return FALSE;            /* tell caller we failed */
              }
          *newOffscreen = newPort;
          SetPort(savePort);
          
          return TRUE;               /* tell caller we succeeded! */
      }

void DestroyOffscreenGrafPort(GrafPtr oldOffscreen)
      {
          ClosePort(oldOffscreen);            /* dump the visRgn and clipRgn */
          DisposePtr(oldOffscreen->portBits.baseAddr);
          DisposePtr((Ptr)oldOffscreen);            /* dump the port */
      }
 

Crutch

Well-known member
Ah OK. That all looks pretty good. Yeah I think we need to know what routine we were running when your PC got dumped into that uninitialized memory. 

 

Mu0n

Well-known member
I think I'm onto something. 

The structure of the program I got from the book assumes I want a color window and loads it from a resource, which also expects to get a 'wctb' (window color table?) of the same ID, which would explain the crash in 6 but not in 7.

EDIT: scratch that, I'm not using it at all, it's commented out



 
Last edited by a moderator:

Crutch

Well-known member
Yeah, that wouldn’t explain it anyway because the wctb (you’re right - window color table) is entirely optional.  If present it allows funky custom colorization of windows down to (I think) System 5, but it’s entirely optional and very rarely used ever.

 
Last edited by a moderator:

Mu0n

Well-known member
I squashed the bug. When I branch out to detecting 512x342, I would only create the window filling the screen, but not keeping up a WindowPtr (then transfered as a GrafPtr to the main method). I made sure WholeScreen returned a WindowPtr so MacPlusArea got one in this specific case.

 

Crutch

Well-known member
Oh indeed!  MacPlusArea as written above returns an uninitialized pointer in the WholeScreen case above.  Good catch.

Still not clear to me why that would cause the 68000 to start executing zeroed-out instructions in ROM, but anyway ....

 

Dog Cow

Well-known member
Here's a screencap of my MacsBug screen upon the first encountered Illegal Instruction error;



Look at the value of address register A0. It is 80F80000. Probably there is a JMP (A0) instruction immediately preceding, and A0 is getting loaded with the wrong destination address.

On the 68000, the stack grows down toward memory location 0. I would check your SP to see which subroutines were called to get you to this crash. Check those first two locations in the left column, top, under "SP"

You can use the WH command in MacsBug to see if a location is in ROM or not, and if it is, what Toolbox or OS call it is.

 
Last edited by a moderator:
Top