Goingware logo

Secrets of the Debug Meisters: Tips and Tricks for MacsBug

A grab bag of tips and tricks for debugging Macintosh software with the MacsBug assembly code debugger.

Michael D. Crawford

Michael D. Crawford
GoingWare Inc.
crawford@goingware.com

All of us who program on the Macintosh have lots of little tricks that we use to get our job done. Most of these are passed around via e-mail and the Usenet News, if they are passed on at all. In an effort to collect all these in one place for the benefit of all, I've created this page. Please submit your tips to crawford@goingware.com and I will add them to this page - with proper attribution of course.

Contents

Where to get MacsBug

[Top]

MacsBug is available via anonymous FTP from Apple Computer from here.

It is included with the two books below, but you will want to get the latest version, especially if you are using a PowerPC.

Are there books that teach how to use MacsBug?

[Top]

Yes, among them are:

  • Othmer and Strauss, Debugging Mac Software with MacsBug, Addison-Wesley 1991, ISBN 0201570491. MacsBug included on disk.
  • Apple Computer Inc., MacsBug Reference and Debugging Guide version 6.2, Addison-Wesley 1991, ISBN 0201567687. MacsBug 6.2 included on disk.

While you don't need to know how to write assembly code to use MacsBug effectively, you will need to know how to read and understand it. Thus you will also need to get assembly code reference manuals, such as:

  • Kacmarcik, Optimizing PowerPC Code, Addison-Wesley 1995, ISBN 0201408392.
  • Motorola, M68000UM/AD MC68000 8/16/32-Bit MPU User Manual, Motorola Literature Distribution, 1991.

All of these books may be ordered from the Computer Literacy Bookstore.

How to get MacsBug help for free

[Top]

Contributed by Bill Coderre, bc@wetware.com

  1. Install MacsBug and programmer's key
  2. Reboot the machine. Don't start any apps yet.
  3. Hit CMD-POWER
  4. type "log MacsBug Help <RETURN>"
  5. type "help<RETURN>"
  6. Push space until done.
  7. type "log<RETURN>"
  8. type "g<RETURN>"
  9. Find the file called MacsBug Help on your desktop. Open it with a text editor. Read it.

Mike sez - also see the MacsBug 6.5.2 HTML Help

Breaking while a particular application is executing

[Top]

Most applications call WaitNextEvent. While an application is executing, a Pascal string with the name of the application is placed at location 0x910. Thus the first four characters of the name itself begin at location 0x911. Suppose you want to break while SimpleText is active. Enter MacsBug and give the command:

atb WaitNextEvent 911^ = 'Simp'

then continue. You will shortly drop into MacsBug at SimpleText's WaitNextEvent call. This is particularly useful when debugging faceless background applications. If the application does not call WaitNextEvent, try GetNextEvent instead.

Capturing a Log File After a Crash

[Top]

Programmers should instruct their users and testers about the "stdlog" command in MacsBug.

Stdlog opens a file named "stdlog" on the Desktop and records some valuable information in it.

When you crash, type stdlog and then restart. Find the file named stdlog on the Desktop and mail it to the programmer or technical support when you report a bug in a program.

Clearing Debugger Instructions While a Program is Executing

[Top]

Contributed by Jim Luther, JumpLong@aol.com

My favorite MacsBug command line this week is:

sw pc-2 4e71

It replaces the _Debugger instructions I've sprinkled through my code with NOPs after I've stepped through the code a few times to make sure it works.

Logging Program Execution

[Top]

Contributed by Darren Giles, Terran Interactive, mars@netcom.com

Always on the lookout for useful debugging tools & tips, I'd love to share ideas with others on the topic. I'll start out by offering a snippet I've found very useful -- hopefully others will do the same.

One thing that's really bugged me about MacsBug on PPC is that the stack crawl has become a much less useful tool. The snippet below gives you the ability to keep track of a list of the last significant points your program has visited. It's a list, not a stack, so you can also see patterns of execution.

Hardly rocket science, but useful & easy to add. Just call DEBUG_STUFF_INIT at startup, then insert a DEBUG_ENTRY wherever you want. To see what's up, especially during a bad hang, just dm [the address that DEBUG_STUFF_INIT dumped out at startup.]

The conditional compilation means that if you turn of debugging in your final build, the release version of the program won't have any of this code in it.

[debugstuff.h] #define DB_ROUTINES_NBR_ENTRIES 40 #define DB_ROUTINES_CHARS 16 typedef char db_routine_entry[DB_ROUTINES_CHARS]; #if DEBUGGING void DEBUG_ENTRY (char *txt); void DEBUG_STUFF_INIT (char *title); #else #define DEBUG_ENTRY #define DEBUG_STUFF_INIT #endif [debugstuff.c] /////////////////////////////////////////////////////////////////////// //// #if DEBUGGING void DEBUG_STUFF_INIT (char *title) { OSErr myErr; char txt[256]; long response; if (!MacsBugInstalled()) { hi_notify ("MacsBug is not installedä the debugging log will be inaccessible."); } g_debug_entries= (db_routine_entry*) NewPtrClear ((DB_ROUTINES_NBR_ENTRIES+2) * DB_ROUTINES_CHARS); memset (&g_debug_entries[0], '=', DB_ROUTINES_CHARS); BlockMoveData (title, &g_debug_entries[0], strlen(title)); memset (&g_debug_entries[DB_ROUTINES_NBR_ENTRIES+1], '=', DB_ROUTINES_CHARS); sprintf (txt, "Debugging routine list is at 0x%lx;g", (long) g_debug_entries); c2pstr (txt); DebugStr (txt); } #endif /////////////////////////////////////////////////////////////////////// //// // This leaves a line in the debugging entry log. // For example, important enter/exit points of routines /////////////////////////////////////////////////////////////////////// //// #if DEBUGGING void DEBUG_ENTRY (char *txt) { short len; // Move the previous entries down one BlockMoveData (&g_debug_entries[1], &g_debug_entries[2], (DB_ROUTINES_NBR_ENTRIES-1) * DB_ROUTINES_CHARS); // Clear the new space memset (&g_debug_entries[1], 0, DB_ROUTINES_CHARS); // Copy in the new entry len= strlen (txt); if (len > DB_ROUTINES_CHARS) { len= DB_ROUTINES_CHARS; } BlockMoveData (txt, &g_debug_entries[1], len); } #endif Hope this does someone some good. - Darren ========================================================================== Darren Giles, Technical Director Terran Interactive For info on Cleaner QuickTime compression, visit http://www.terran-int.com

Logging Data in Real Time

[Top]

Contributed by Dave Stone, dstone@chem.utoronto.ca

I've also used conditional compilation to debug serial communications stuff being processed at interrupt time - something like

#ifdef DEBUG_MY_ROUTINE #define MAX_BUFFER 10000 char bufffer[MAX_BUFFER]; // or NewPtr it or something long bufCount = 0L; #endif . . . #ifdef DEBUG_MY_ROUTINE if(bufCount < MAX_BUFFER) { bufCount ++; buffer[bufCount] = ch; // ch is a character read/written // through serial port } #endif

etc. Handy, because you can let it rip for a while to see if there is a consistent pattern in the errors in ch - in my case, a stream of Midi data through a very basic freeware Midi Driver.

Using Cursors to Trace Program Execution

[Top]

Contributed by Tom Kimpton, Jostens Learning Corporation, tom@jlc.com

One technique that I have used in the past where dropping into the debugger wasn't an option, and logging wasn't getting flushed in time/took too long, was to create a bunch of cursors numbering 00 - 99, and made a call to set the cursor and return the number of the previous cursor:

routine1() { short oldCursor = setDebugCursor(15); ... (void) setDebugCursor(oldCursor); }

This way when the machine froze, the cursor would tell me what routine it had frozen in.

Using Touch and Go Breakpoints with Two Monitors

[Top]

I had a bug in which the Mac would occasionally freeze during shutdown without the ability to get into MacsBug. It would only occur about once in twenty reboots.

The way I dealt with this was to borrow a display card and hook two monitors up to the Macintosh. You can use the Monitors control panel to select which monitor will be used for MacsBug (hold the option key and drag the happy Mac around).

I wrote a small application that just called ShutDownRestart(), and placed it in the Startup Items folder. Thus, when the Mac came up into the Finder it would immediately reboot. About every twenty minutes it would freeze.

If you define a macro named FirstTime in the Debugger Prefs file, MacsBug will execute it when it loads. I used a macro that was something like:

swap; atr; atb shutdownrestart ";atb Newhandle ";g";g";g

or some such. The swap command caused MacsBug to be permanently left on the second screen. That way when the crash occurred you could still see the last few things MacsBug did. The ";g" following the a-trap break commands tells MacsBug to continue after the break - this is a "Touch and Go" breakpoint.

One thing you can also do inside a touch and go breakpoint is set new breakpoints. I would take guesses on what traps might be called in the neighborhood of the crash, and have breakpoints set on them when ShutDownRestart was called.

Then I could leave the Mac rebooting on its own in the lab, and pop in every half an hour to check the log, adjust the breakpoints and start it up again.

The actual bug took about five months to find and fix.

Twiddling Pixels in the Menu Bar

[Top]

Contributed by Dave Fleck, Wacom Technology Corp., dfleck@wacom.com

Here's my debugging tip.

I do drivers, and you just plain can't set a breakpoint in ADB completion routines (freezes the keyboard so MacsBug is worthless!).

So I throw one of the routines below into the routine to see when a piece of code gets executed.

What does it do? It "lights up" a bar (length dependant on screen resolution) in the menu bar. So if you DotToggle(300); you get a flashing short line in the menu bar.

void DotOn(long where) { long *dot; dot = (long *)(LMGetScrnBase() + where); *dot |= -1; } void DotOff(long where) { long *dot; dot = (long *)(LMGetScrnBase() + where); *dot &= 0; } void DotToggle(long where) { long *dot; dot = (long *)(LMGetScrnBase() + where); *dot ^= -1; } dave ----------------------------------------------------------------- Dave Fleck email:dfleck@wacom.com phone:360-750-8882x154 Wacom Technology Corp. sales@wacom.com 501 S.E. Columbia Shores Blvd, #300 support@wacom.com Vancouver, WA 98661 WWW/FTP:wacom.com ------------------------------------------------------------------

Forcing Testers to Use MacsBug During Beta Testing

[Top]

Contributed by Harold Ekstrom, the ag group, inc., ekstrom@aggroup.com.

Don't you just hate it when beta testers say "it crashes" but don't give you any more information? First, tell them to use the "stdlog" command in MacsBug, then force them to install MacsBug by checking for it during your program's initialization:

--- DebugUtils.h --- #pragma once // Debugger types. typedef enum DebuggerType { kNoDebugger, kMacsBug, kTMON, kOtherDebugger } DebuggerType; Boolean GetDebuggerInfo( DebuggerType *outDebuggerType, UInt16 *outDebuggerSignature ); --- DebugUtils.c --- // Private defines for some low memory globals. #define MacJmp ((Ptr *)0x0120) // MacsBug jumptable [pointer]. #define MacJmpByte ((UInt8 *)0x0120) // MacsBug flags in 24 bit mode [byte]. #define MacJmpFlag ((UInt8 *)0x0BFF) // MacsBug flag [byte]. // Debugger flag bits. #define kDebuggerInstalledBit 5 // --------------------------------------------------------------------------------- // // --------------------------------------------------------------------------------- Boolean GetDebuggerInfo( DebuggerType * outDebuggerType, UInt16 * outDebuggerSignature ) { Boolean theResult = false; SInt32 theResponse; // Initialize return values to defaults. *outDebuggerType = kNoDebugger; *outDebuggerSignature = ' '; if ( Gestalt( gestaltAddressingModeAttr, &theResponse ) == noErr ) { UInt16 theDebugFlags; // As documented in the "MacsBug Reference & Debugging Guide", page 412 // if we have a 32 bit capable Memory Manager, debugger flags are at 0x0BFF // if we have a 24 bit capable Memory Manager, debugger flags are at 0x0120 if ( (theResponse & (1L << gestalt32BitCapable)) != 0 ) { theDebugFlags = *MacJmpFlag; } else { theDebugFlags = *MacJmpByte; } if ( (theDebugFlags & (1L << kDebuggerInstalledBit)) != 0 ) { Ptr theDebuggerEntry; Ptr theROMBaseWorld; // There is a debugger installed. theResult = true; // Get the debugger entry. theDebuggerEntry = StripAddress( *MacJmp ); // Get the ROM base. theROMBaseWorld = StripAddress( LMGetROMBase() ); // Compare the debugger entry to the ROM base. if ( theDebuggerEntry < theROMBaseWorld ) { UInt16 **theDebuggerWorld; // It's not a ROM based debugger. // Get the debugger world. theDebuggerWorld = (UInt16 **) StripAddress( theDebuggerEntry - sizeof(Ptr) ); // Get the debugger signature. *outDebuggerSignature = **theDebuggerWorld; // Get the debugger type. switch ( *outDebuggerSignature ) { case 'MT': *outDebuggerType = kMacsBug; break; case 'WH': *outDebuggerType = kTMON; break; default: *outDebuggerType = kOtherDebugger; break; } } } } return theResult; } Check for a low level debugger like this: #if BETA_VERSION DebuggerType theDebuggerType; UInt16 theDebuggerSig; if ( !GetDebuggerInfo( &theDebuggerType, &theDebuggerSig ) ) { HaltRotateCursor( gRotateCrsr ); StopAlert( go_get_macsbug_alrt, nil ); ExitToShell(); } #endif

The Developer University Debugging Class

[Top]

Contributed by Malcolm Teas, Blaze Technology, mhteas@btech.com

As the instructor and developer of Apple's Developer University class called "Macintosh Debugging Tips and Techniques" I would like to make sure your tips page references this class.

This class is centered around MacsBug as the easiest to learn low-level debugger. It also covers a multitude of low-level topics like memory maps, subroutine calling protocols, code segments and code fragments, reading (and understanding) assembler for 68K and PPC, and many more areas. One key area is how to avoid bugs in the first place. All the information you need to be able to debug software at the low-level.

The class is available from Apple's Developer University.

Another thing I want to mention is the version number of the most current MacsBug is 6.5.3 (as of this writing). This version includes the PPC commands and features.

[I have taken this class and recommend it highly - Mike]

The above was written years ago. I'm afraid Developer University doesn't exist anymore. I'm not sure whether Apple offers anything really like it now. I took several DU classes, and both learned a lot of valuable skills and enjoyed myself. -- Mike

Add A-Traps as Markers for A-Trap Recording

[Top]

Contributed by Moi GoingWare, Inc.

If you compile your program in 68k (even if you are running on a PowerPC) you can use a-trap recording to see the last few trap calls your program made before a crash. This is immensely helpful if your program runs for a while and then crashes.

To turn on a-trap recording, break into the debugger and type "atr". Then cause the crash, and type "atp" (for "a-trap playback") to see the last few trap calls.

Be sure to compile with MacsBug symbols and A6 stack frames turned on.

A-trap recording might not be useful, though, if your program runs for a long ways without calling any traps. It might be engaged in a lengthy calculation, or accessing only library routines - a good reason for using NewPtr instead of malloc.

You can add markers to your code by calling a-traps that have only benign side effects. For example, I like to call InitCursor.

I was debugging some crashy code the other day that processed each word of a text file in a long loop and then crashed. To narrow down where in the loop it crashed I added a bunch of calls to InitCursor at different points in the code. After causing the crash and playing back the a-traps I was able to narrow it down further, which I did by adding more InitCursor calls. Since the a-trap recorder records the function name and offset where the trap call was made, you can see where you have been in the code. You can tell which trap is which by disassembling the function and counting the trap calls from the beginning or end.

A-trap recording works on the 68k because 68k system calls are "exceptions". That is, they are a type of illegal instruction that causes the processor to break from the linear flow of program code and branch to an exception handler, saving the registers and program counter on the stack. The Mac OS programmers cleverly installed an exception handler that figures out which particular illegal instruction code you called and branches to a subroutine that does the desired function and returns.

When you turn on a-trap recording, MacsBug installs its own "a-line" exception handler (so called because it handles illegal instructions that start with hex A) that records the trap word on a circular buffer, along with the program counter, and then branches to the original exception handler. When you play back the traps, MacsBug looks up the symbol name and offset by searching in your program's executable code.

A-trap recording does not work in PowerPC native code (including native code called from a 68k program) because system calls made from PowerPC code is made via a normal subroutine call to a shared library. When your program is loaded into memory the Code Fragment Manager places the addresses of each of the system functions into the transition vectors in your native code. That's why, as much as possible, you should always maintain both native PowerPC and 68k versions of your code. (There is a MacsBug function called "atvb" that does system call breaks from native code but it's not nearly so nice to use as the 68k "atb".)

Use Conditional A-Trap Breaks

[Top]

Contributed by Moi GoingWare, Inc.

In a similar vein to the above, you can use a trap call that takes a parameter to break when a particular condition is true. For example, you can break when a resource of type 'FOO#' is loaded with the following command:

atb GetResource (sp+2)^ = 'FOO#'

Even more interestingly, suppose you want to break when a loop counter reaches a particular value. You might want to only occasionally enable the breakpoint. You could add this code to your program's source:

for ( i = 0; i < kMaxLoop; i++ ){ if ( i == 27 ) Debugger; ... }

but you might not know ahead of time what iteration to break on, or you might want to change which iteration you break on each time you run the program. One solution is to call a trap such as SysBeep that ignores its parameter (being sure to turn off the sound first):

for ( i = 0; i < kMaxLoop; i++ ){ SysBeep(i); ... }

then you can use a command like

atb SysBeep sp^ = 27

(If you look carefully at the compiled code you could set a conditional breakpoint that looks at the register or stack location that the counter is kept in, but this is a little easier on the brain).

Jasik's Debugger: for Power Programmers

[Top]

A high-powered alternative to MacsBug is The Debugger from Jasik Designs. ( macnosy@jasik.com)

Jasik's Debugger will do all kinds of things that MacsBug cannot. There are advantages and disadvantes to each debugger. The major difference is that MacsBug is "lightweight" - you can install it by just dropping it in the System Folder, and it has such a small impact on the system that you can leave it installed on regular user or test machines.

While Jasik's Debugger is more powerful, it is harder to install and to use. There is also a difference in price - MacsBug is free, while The Debugger costs a couple hundred dollars - but the price is well worth it. You really should have both debuggers, and should consider the cost of The Debugger as a valuable and basic part of your programmer's toolkit. I personally prefer to keep both debuggers around, using MacsBug most of the time but hauling out Jasik's Debugger when the going gets rough. I know lots of people that use The Debugger as their main debugger.

Among the features of The Debugger are source code debugging of almost any code (not just application code), "training wheels" for PowerPC assembly (the names of the opcodes are spelled out in the disassemblies), sensible debugging of OpenDoc parts, MMU protection, a graphical user interface (windows and menus, as opposed to MacsBug's command line) and many, many more. A significant advantage of The Debugger is the personal technical support provided by Steve Jasik himself.

Also from Jasik Designs are MacNosy, a powerful interactive disassembler, and CoverTest, a code coverage test tool.

Links to Other MacsBug Pages

[Top]

Dead Links

[Top]

Clicking here will get you nowhere. Have you any idea where these pages went? A lot of links have disappeared since I last updated the page.

Vote for Us at the Programming Pages Voting for GoingWare at The Programming Pages will encourage more people to read these articles.

Reciprocal Links

[Top]

Please see the explanation in the Reciprocal Links page.

Paguito.com
Online computer sales Compaq, Hewlett Packard and SONY.

Call GoingWare: +1 (831) 401-3790, info@goingware.com

Web Design by Bonita

Valid XHTML 1.0! Valid CSS