MacsBug results
Volume Number: | | 7
|
Issue Number: | | 9
|
Column Tag: | | Developer's Forum
|
Getting Results With Macsbug
By Jeff Turnbull, Livingston Manor, NY
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
Getting Results with Macsbug: A Checklist of Techniques
[Jeff Turnbull is an independent systems consultant and programmer. He has worked on OCR, XCMDs, text patterns, and data acquisition. A graduate of Maharishi International University, he practices the Transcendental Mediation (TM) technique, which he considers a tremendous aid to good programming. He resides in the Catskill mountain region in Livingston Manor, N.Y. He can be reached at (914) 439-4310 (afternoons).]
Introduction
This article presents several checklists of Macsbug commands and techniques to get fast results in any language. Anyone can use Macsbug, so long as they have such a resource. The intent of the article is to give a list of practical techniques to try, without a lot of extra theory. With these checklists you can usually accomplish the majority of your debugging with only a small amount of effort. The checklists are designed for Macsbug, but can be used with other debuggers. The article assumes you have a working knowledge of the Mac and a little familiarity with debugging. The article has an introduction, sections containing the checklists of techniques, and some useful reference information. Also, Macsbug allows certain types of advanced debugging, but most programmers do not want to have to go through extensive tutorials -- they just want to know the techniques they can try to get results. Having a reference of techniques to try for various situations will not only save time, it will produce valuable results where they could not otherwise be obtained.
One of the main needs is to find out where your program is when it crashes, and a checklist is presented for this purpose. There are additional checklists on general techniques, stepping and breakpoints, going past an error, heap and memory, displaying messages, variable inspection, objects, miscellaneous tips, and hints on disassembly of your code. The reference information contains sections on invoking Macsbug, how to turn on symbol names, utility routines for using Macsbug from your source code, a list of Macsbug commands, and a list of system errors. The article is arranged so you can easily refer back to it when using Macsbug, to look up commands and techniques. Within each section the techniques are arranged from the simplest commands (which are often the most powerful) to more advanced techniques which are needed less often.
Macsbug is an Apple product available on the Developers CD, from APDA, and as part of MPW and some other development systems. Macsbug has certain advantages compared with other debuggers such as SADE. Macsbug is fast, uses little memory, shows the screen at the time of the crash, no special compiling is necessary, you can log messages to printer or file as you go, and Macsbug provides several powerful low-level commands. You can try these checklists of techniques before having to deal with other debuggers.
The Low-Level Mac
This paragraph describes a few of the low-level basics you need to know to help interpret the information in Macsbug. When a routine executes, space is reserved on the Stack, in first-in-last-out order (or last-in-first-out order). The more routines which are running, the larger the stack grows. Each routine is said to have a stack frame for its storage of local variables, parameters, etc. The stack starts out in (fairly) high memory and grows downward. The heap is another area of memory where storage for pointers and handles is allocated. Much debugging on the Mac concerns the heap and moving objects, and you are well advised to become very familiar with its theory and operation. See the references at the end of the article and the chapters in Inside Macintosh for more information on the heap. The 68000 microprocessor has eight address registers (internal memory) which hold pointers, and are denoted A0 - A7. Several of these registers are important. Register A5 usually holds a pointer to the start of the global variables. Register A6 usually points to the current stack frame. Register A7 (also called SP or stack pointer) holds a pointer to the bottom of the stack. However, many of the ROM routines use these registers for other purposes and restore their correct values only at the end of the routine. The 68000 also has eight data registers. There are some hints on getting some basic information from disassembly of code in the section on Just Enough Assembly.
Finding Where an Exception Has Occurred
Your program crashes and you wind up in Macsbug. Here are some techniques you should try to find out where the program crashed, and gather other useful information. I suggest you always run through these in order, even if you dont understand them all. Later you can look back at the file produced.
Check the screen by hitting the esc or tilde (~) key, for indications of what the program was doing when it crashed. Note everything which is on the screen: it may help as you look over your source code.
When entering Macsbug check the bottom line for the location of the program counter.
Now start by logging everything to a file: LOG Filename or LOG printer {for ImageWriter}.
Repeat initial message: HOW.
Find out where you are: WH. If no name is given, the routine may be a method call, library routine, or glue for a Mac toolbox call, and there are techniques to find out the name below.
Stack crawl: show the calling chain of routines on the stack. First try SC6, which shows the routines which are currently executing. This command lists the addresses of the stack frames of the calling chain and the callers address. Note that the last stack frame is not listed: see the next command. (If you are executing a ROM routine A6 may not point to a stack frame.)
Finish the stack crawl. There is one more routine on the stack which is not shown by the stack crawl command (for some reason known only to Apple). The routine name is usually given by the WH command (above) and the stack frame by A6. It is good to see what was called by doing ID lastCaller (get lastCaller from the stack crawl as above). This will disassemble the code of the last routine shown by the stack crawl command, and will usually give you the name of the final routine. (See the section on disassembly for more information.) This technique can be done on any of the routines on the stack. There are other techniques below if no name is given.
If the stack crawl using SC6 does not work (due to being in a ROM routine or other problem) then you can use SC7, and get a frame address to try SC6 again. SC7 will show more routines, some of which are not really on the stack (they are left over from previous calls). Then use the lowest valid Frame Addr with the SC6 command to get the calling chain, as in SC6 FrameAddr. This is only necessary if SC6 does not work, and it may not produce valid results.
Look at the code which was running when you crashed. IP PC. (The first instruction or two shown may not be valid until Macsbug gets in sync). If you need to, then disassemble closer to the start of the routine or go further. Try IP PC-100 (or more than 100 if necessary, then keep hitting return. Look for calls to (ROM) routines near the crash and other indicators of where the program was inside the routine. Look at the hints on assembly language for more information.
Check the heap. HT. See the section on memory if there is a problem.
Check for a memory error. DW memErr. It should give zero.
Check for a resource error. DW resErr. It should give zero.
You can sometimes get past your error by using the techniques described in the section on getting past an error.
Check the parameters to the routine. If you suspect bad parameters, then see the section on inspecting parameters and variables. Most ROM routines crash because of the parameters passed to them.
If no name is given. The routine may be an aborted method call, an unnamed library routine (such as a Pascal string function), in glue code for a ROM routine, an unloaded segment, a bad INIT may have taken over, or the PC may have become invalid. None of these have names. You can try the following techniques, but if it proves difficult it may become easier to see the section on stepping and the section on putting messages into your source. First try to reconstruct the stack calling chain using the techniques above. There may be trouble. Check your source and compare it to the calling chain (do you have any new code or routines you suspect?). The problem is not always the last routine on the stack, and it may be enough to know that you crashed after calling a dialog or other ROM routine. Disassemble the caller (as above in the point on finish the stack crawl) and see the section on disassembly for other techniques. Use the WH command on any code address (especially the caller from the stack calling chain, and any JSR statements you find in disassembling code) which will at least show you the heap and code segment. Glue code, and library routines are usually linked in the main code segment. Method jumps are often in there own segment (%_SelProcs in MPW Pascal). INITs and certain Inside Macintosh routines are often in the system heap. Method calls are usually disassembled as JSR in CODE %_SelProcs. You can disassemble the addresses of the JSR (and JMP and BSR) statements and see if it is glue code (there is usually an Atrap fairly soon in the code), or if you hit other JSR and/or JMP statements then you are probably going through a jump table.
What to try next. If the error indicates a specific problem, such as a memory problem, then see the section of this article dealing with the problem. You can try the list of general techniques. See the section on inspecting variables to inspect the parameters passed into the routine which crashed. There are many other techniques you can use to narrow down the problem before recompiling. You can rerun the program using EA {exit to application} and use the techniques in the sections below. These techniques will help you step through the error, record other information such as the toolbox routines called before the crash, and check the heap before calls. Sometimes it is helpful just to know if the problem repeats in the same way each time. (If it doesnt then you may have a bad pointer doing damage to memory somewhere.) See the section on putting messages into your source code.
Turn off logging to file if you are finished debugging: LOG.
Exit Macsbug. The system may have been corrupted so the safest way to leave Macsbug is with RB {reboot} or RS {restart}. You can also try EA {exit to application} or ES {exit to shell}. Also see the techniques for going on past the error.
General Techniques
Here is a list of general purpose techniques to try out.
Scramble the heap: HS. All programs should do this at least once to check for problems with moving handles. Use the command, then run your menu commands.
If you crash in a ROM routine, check the parameters to the routine. See the section on inspecting parameters.
Catch use of NIL pointers: SL 0 NIL!. This is a good defensive technique to try. After doing this command Macsbug will be invoked if your program attempts to dereference a pointer which is NIL (or zero). This command puts NIL! (or $4E494C21, an odd large value) into memory at location zero. An address or bus error will result if this is dereferenced. This technique doesnt slow you down one bit. You can also set memory location zero from your source code.
Record all the calls to toolbox routines. ATRA. This is very useful when running code for the first time, or at any time you think the code may crash. You will have a record of recent calls to toolbox routines. You can play back the last calls using ATPA. Note that most of the ATrap commands take an A on the end to indicate that the command should only apply to ATraps which are called from the application (not from Atraps which are called from ROM). If you wish to record all calls, even calls made by toolbox routines to other toolbox routines, then use ATR and ATP.
Check the heap before every toolbox call: ATHCA. Macsbug will break if the heap is bad. ATHC (without the A on the end) may incorrectly inform you that the heap is bad during calls made by memory manager routines.
Check a portion of memory at every toolbox call. First go into Macsbug and do a checksum of a memory range: CS address address+offset. Then do an ATrap checksum command: ATSSA. Macsbug will break if the memory changes. This technique is useful to see when a chunk of memory is getting used or inadvertently stepped on. This can help you find bad pointers or other wrong use of a variable or memory. If you know that data is going bad, but dont know why or when, then use this technique. Then use the techniques above to find out where the program was when the change occurred.
Put the Mac into slow motion: SS RomBase^ (RomBase^+40). This will preform a checksum on the given memory range before each 68000 instruction, which will really slow things down. You can check a longer or shorter range of memory to the extent you want by using SS RomBase^ (RomBase^+offset). RomBase is memory that will not change. You can also use SS memErr memErr+2. This will preform a checksum on the global variable memErr, which also checks for memory errors. You can check for resource errors by using SS resErr resErr+2. These techniques will really slow things down so you can see what happens on the screen in slow motion.
The above techniques can be used before you recompile, as can some of the techniques in the next few sections.
Stepping, Going and Breakpoints
This section describes some techniques you can try before checking your source code and recompiling, and also some things to try if other techniques dont work. If you crash, you can rerun the program using EA and try some of these techniques without having to recompile. First, some common stepping commands to use over and over.
To go back to your program: G.
To step one assembly language instruction: S.
To step several assembly language instructions. S 20 or S numberOfSteps. The S command will step into any called routines.
To step over routines by letting them run. SO or SO numberOfSteps. This will run any called routines at full speed as one step.
To let a routine finish running at full speed (when you are in the routine, to get done with it) do a magic return: MR A6. (When doing this command on a ROM routine, it is best to do this command toward the beginning of the routine, because some ROM routines use A6 for other purposes and the command may not work.)
To go till a certain point: GT Address. This is the equivalent of setting a breakpoint and using the go command. Use this command if you are getting bored with small steps and you want to get to a certain point, or you just want to get past where you are now. You can use routine names in place of Address. Also see the point below on breakpoints. The above commands are the basic commands for stepping and going.
Using breakpoints in your code. BR Address. The easiest place to break is at the start of a routine by using its name: BR RoutineName. If you set a breakpoint in the middle of a routine you should first disassemble your code (IP command) and start on an instruction boundary, or else an error may occur, as in BR RoutineName+offset. Hint: to set a breakpoint at a later point in a routine you can use the colon, which tells Macsbug to use the current name: BR :+offset.
Using breakpoints with commands: BR Address ;HC;G. This command will check the heap (HC) at the break and then go on (G). You can use any Macsbug commands (preceded by semicolons), such as TD (total display of registers), SC6 (stack crawl), IP (to disassemble the current code), DM Address (display memory), CS (checksum), etc.
Using Atrap breaks: ATBA. This will break into Macsbug on all ATraps called from your program. You will often want to narrow it down, for example: ATBA SetPort.
Finding strange behavior using Atraps. If your program is not behaving as it should, and you think that ROM routines are getting called (when they shouldnt be getting called), then set breakpoints on the suspected ROM routines. ATB RoutineName. Then follow the instructions for finding out where the program is. You can also just record the calls to the routine(s) without breaking into Macsbug by using ATR RoutineName, and play them back using ATP.
Breaking to check parameters. See the section on inspecting parameters.
Check for damage to heap. ATHCA or ATHC. Checks the heap before each ATrap and breaks if there is an inconsistency. (ATHC (without the A on the end) may incorrectly inform you that the heap is bad during calls made by memory manager routines.)
Check for memory changing. ATSS Address Address+offset (or ATSSA). Preforms a checksum on the specified address range before each ATrap and breaks if there is an inconsistency.
To stop before an error, and step through it. For example, suppose you know that an error occurs after a dialog box is dismissed, but not exactly when. There are several techniques to try. Find a way to get into Macsbug before the crash. For example, set an ATrap break on a ROM routine before the crash: ATB ROMRoutineName, and then use the stepping techniques above to follow the program to the error. You can also go into slow motion, SS RomBase^ and watch the screen (see the section on general techniques above).
To break on a routine which is not yet loaded into memory. There are several commands to try: Set an ATrap break on a ROM routine you know will be called after your code is loaded: ATB RoutineName. Put the Mac into slow motion: SS RomBase^, and try to catch it before it crashes, if youre fast enough. Set a break on another routine you know will be called: BR routineName. The routine could be one of your common utility routines which is already in memory. Set a breakpoint on LoadSeg: ATB LoadSeg. To avoid having to step through LoadSeg you can set a byte of memory to tell LoadSeg to go back into Macsbug when it is finished loading. There are several commands to do this. First set the byte: SB LoadTrap 1. Then go: G, and when you are back in Macsbug then reset the byte: SB LoadTrap 0. Now you can step twice: S 2, and you will be at the entry point of the newly loaded segment. Then step using the instructions above. If all else fails use DebugStr in the routine in your source and recompile. You can also load all your segments at the start of the program, which is useful when in the debugging stage.
Command-key short cuts: command G (for go), command S (for step), and command T (for trace or SO step over)
Clear breakpoints before leaving Macsbug: ATC and BRC.
Getting Past an Error
Sometimes the need arises to go past an error and continue executing the program. You may wish to save data or just see where you are in the program by seeing what comes next. Usually however, it is easier to restart the program, but you can try the techniques below if you really need to.
You can sometimes step past an error as follows: Do an IP PC to disasemble the code and to see what and where the next instruction is. If you think it is possible, then do G, G PC+2 or PC+4 or wherever the next instruction is. Statements which use A7 as a parameter with a plus or minus sign should not be skipped over or else the stack may get out of whack.
Another way to get past an error and keep going is to jump to the end of the routine. However, it is usually easier to start over, so this method is mainly useful if you really need to see what happens next. You will have do some detective work and restore the stack pointer (A7) and then jump to the end. First do IL on the beginning of the routine. You will see statements such as LINK A6,$FF00 and MOVEM.L A2-A5/D3-D7,-(SP). This statement saves the registers to the stack. Count the number of registers. (Dont just subtract A5-A2 = 3, count them A5, A4, A3, A2 = 4.) Next do A7 = numberOfRegisters*4+A6+FF00.w (we got the FF00 from the LINK statement above, so you should substitute whatever value is in the link statement). Do an IR PC and keep hitting return until you get to the end of the routine (you should see an UNLK statement). You want to go to the statement before the UNLK, to the address of the MOVE or MOVEM statement. Type G MOVEMAddress.
Heap and Memory Problems
You can get information about the heap, have Macsbug check the heap and memory at various times automatically for you, and do commands to catch errors.
Check the heap: HC.
Check the heap at each ATrap, break if bad: ATHC[A]. (ATHC (without the A on the end) may incorrectly inform you that the heap is bad during calls made by memory manager routines.)
Check the heap at points in your program: BR routineName ;HC.
Check the heap from points in your source code: DebugStr(;HC)
Summary of the heap: HT.
Display everything in the heap: HD.
Display CODE resources in the heap: HD CODE.
Display resources in the heap: HD RS.
Display some resources in the heap: HD, HD name, HD ICON, HD MENU, HD nVIR.
Check for fragmentation of the heap: HD. All the heap objects with a centered dot () next to them are nonrelocatable and should be located near the start of the heap or at the end of the heap. If there are dotted objects scattered through the heap then your heap is fragmented. Look carefully at the objects to determine their type.
Scramble the heap: HS. All programs should do this at least once to check for problems with moving handles. The command will move all relocatable blocks (the handles storage) at every memory manager call which might move memory. Run your program through its stuff and see if you crash or get strange results.
Catch use of NIL pointers: SL 0 NIL!. This is a good defensive technique to try. After doing this command Macsbug will be invoked if your program attempts to dereference a pointer which is NIL (or zero). This command puts NIL! (or $4E494C21, an odd large value) into memory at location zero. An address or bus error will result if this is dereferneced. This technique doesnt slow you down one bit.
Check a portion of memory to see if it changes: CS address address+offset. Use this technique to catch inadvertent use of a variable or find out when memory is getting stepped on.
Check a portion of memory at every toolbox call: ATSS[A] , Address. Macsbug will break if the memory changes. This technique is useful to see when a chunk of memory is getting used or inadvertently stepped on. This can help you find bad pointers or other wrong use of a variable or memory. If you know that data is going bad, but dont know why or when, then use this technique. Then use the techniques above to find out where the program was when the change occurred.
Check a portion of memory at points in your program: BR routineName ;CS Address Address+offset;G;. Skip the G if you want to stop.
Check a Mac system (global) variable to see if it has changed: CS variable variable+length. If the variable is an integer then use 2 for length, etc. Macsbug knows most variables by name.
Check a variable to see when it changes. There are several ways to get the address of the variable to use in the command. Pass the address by putting a statement in your source: DebugStr(concat(address of variable x is ,LongtoHexString(ord(@x)))); then use this address in the above commands (e.g., ATSS[A] address). You can also get the address by disassembling your code and looking for the statements which reference the variable, such as MOVE.L $0022(A6). Global variables are offsets from A5, and local variables are offsets from A6. See the section on inspecting variables for more information.
Check for memory errors of memory manager routines: ATB[A] (memerr^.W <> 0), this will check before each ATrap to see if an error has occurred since the last ATrap. The same thing can be accomplished by ATSS[A] memerr memErr+2, which will preform a checksum on the global variable memErr.
Check for resource errors. Use the above command with resErr instead of memErr.
Some useful addresses: a one meg Mac ranges from 0 - FFFFF, 2 meg Mac from 0 to 1FFFFF, 4 meg Mac from 0 to 3FFFFF. The heap goes from ApplZone^ to ApplLimit^.
Displaying Messages from Your Source Code
Macsbug can function like a writeln window which can be turned on and off (at run time), and can redirect messages to an Imagewriter. You can also send command strings to Macsbug from your program, to check the heap at different parts of your program for example. You can add messages to track down program flow and look at the values of variables by putting statements into your source code.
Simple message from your source: DebugStr(your message);. Use this like Writeln for printing messages.
Getting more information: use the Concat function and the routines in the Utility Routines section, for example: DebugStr(concat(before call to myFunc x = , LongToString(x)));.
Turn off breaking into Macsbug for messages: DX. This is very convenient to turn off breaking for messages and let your program run. The messages still print in the Macsbug window for later review, but your program will not stop running.
Force breaking on and off from your program: DebugStr(DX ON); and DebugStr(DX OFF);. Use this to turn breaking off while recording many messages, and then turn it back on and invoke Macsbug to view all the messages.
Log messages to a file or Imagewriter: LOG filename or LOG printer. All your messages will be sent to the file or printer for later review.
Debug procedure. Create a procedure(s) to display the values of certain variables you may need to see, such as a particular set of variables you are working with or a particular record your program uses. The procedure can also check for unreasonable values and give messages to this effect. These procedures can be used throughout program development and later modification.
Create a debug method of an object. It is often convenient in object oriented programming to have a method to invoke Macsbug and give all the values of the fields of the object. The code can also check if any of the fields appear to have unreasonable values and give additional error messages to this effect.
Check the heap from your program: DebugStr(;HC;G). This is useful to check the heap at specific points from your source, but it can also be done without recompiling by using the techniques in the section on memory and the section on breakpoints.
Check the heap from your program part II: insert DebugStr(;ATHC;G) and later turn it off with DebugStr(;ATC). This is useful to check the heap for some time, and later turn it off.
Go into Macsbug conditionally: if button then DebugStr(message); or if debugFlag then DebugStr(message);
See other points on stepping -- it may not be necessary to recompile.
Other techniques to give yourself messages and/or stop your program: insert repeat until button; . Click the mouse to continue or push the interrupt switch to go into Macsbug. You can also use: repeat until not button. You will have to hold the mouse down to catch this. Also: SysBeep(1) {gives some information of a sort}, and of course you can use an alert with a message (but why not use Macsbug which can be turned off and on and can be saved in a file or printed).
Variable and Parameter Inspection
You can use Macsbug to inspect parameters passed to routines, local variables, fields of an object, etc. Local variables, passed parameters and object handles are located on the stack and are arranged in memory as shown below. However, many compilers also use the registers of the microprocessor to hold some of the variables, especially loop variables, and of course, register variables in C. To find out if you have any register usage look at beginning of routine to see if you have any MOVE or MOVEM (move many) instructions which save registers to the stack.
The following is a map of the stack during a Pascal procedure or ROM routine (after the LINK statement). Recall that the stack grows downward in memory:
high memory
--------------------------------------------------------------------
- previous contents on stack from other stack frames
(many bytes)
--------------------------------------------------------------------
- [function result] (same rules as parameters below)
--------------------------------------------------------------------
- [first parameter] (if type > 4 bytes, then pointer passed)
--------------------------------------------------------------------
- [next parameters] (if VAR, then pointer passed)
--------------------------------------------------------------------
- [last parameter] (if 1 byte or less, then 2 bytes used)
--------------------------------------------------------------------
- [static link], pointer to local variables of enclosing
procedure if any
--------------------------------------------------------------------
- [self of object], in object Pascal the handle to objects
data (4 bytes)
--------------------------------------------------------------------
- return address, (4 bytes, located 4 bytes above A6 or
stack frame)
--------------------------------------------------------------------
- previous A6 saved (4 bytes, located at A6 or stack frame)
--Note: A6 or stack frame points here ^, below A6 are the following:
--------------------------------------------------------------------
- [copies of passed value parameters] (if non-variable parameters are larger than 4 bytes then they are copied here at the beginning of a Pascal procedure so they can be used as local variables.)
--------------------------------------------------------------------
- last local variable (number of bytes of type)
--------------------------------------------------------------------
- middle local variables
--------------------------------------------------------------------
- first local variable
--------------------------------------------------------------------
- saved registers
--------------------------------------------------------------------
- next stack frame or additional stack usage for this frame
Breaking to check parameters. You can break on an ATrap or on one of your own routines and then check the parameters. BR RoutineName or ATB ROMRoutineName, then do DM A7. The parameters will be displayed in the following order: [SELF] (in object Pascal the handle to the objects data, 4 bytes), [static link] (pointer to local variables of enclosing procedure if any), and then the last parameter through the first parameter. You may also wish to disassemble the code and see the statements that push the parameters on the stack: IP.
Check parameters inside a routine. If you are in the middle of a routine (perhaps you have crashed) and want to check parameters do DM A6. You can also inspect the parameters of any routine which is currently executing on the stack by doing a stack crawl (see section on finding where an exception has occurred) and then DM A6Frame. (Some ROM routines use A6 for special purposes, and you will have to use techniques such as the one above or work through the stack to find the parameters.) The order of the display (after the routine is called and the first instruction is done) is: saved A6 (4 bytes), return address (4 bytes), [SELF] (in object Pascal the handle to the objects data, 4 bytes), [static link] (pointer to local variables of enclosing procedure if any), and then the last parameter through the first parameter.
Local variables. These are not as easy to find on the stack, and different compilers use different conventions for ordering the locals. Some compilers use registers for local variables. For these reasons it is often easier to print the values of the variables in DebugStr statements as noted in the section on displaying messages. In Pascal the locals are arranged from first to last in the order in which they were declared in the Pascal source. You must first do some detective work to find the exact start of the local variables. Do the following two commands: ID (or IL) routineName. You should see LINK A6,$FF00 (or other number in place of $FF00). Then to see the local variables do DM stackFrameAddress+FF00.W (or other number, dont forget the .w). Get the stackFrameAddress from the stack crawl command (see the section on finding where an exception has occured). Hit return if necessary.
Inspecting locals at the end of a routine. One trick to make it easier to inspect local variables is to go to the end of the routine when the stack pointer will point to them. You can get to the end of a short routine using S, and keep hitting return until you get to the UNLK statement. For longer routines do IR and keep hitting return until you get to the end of the routine. You will see an UNLK statement. Then do GT UNLKaddress to go to the end. Then do DM A7 to see the locals (hit return if necessary).
Inspecting the Fields of an Object or Handle. First you need to get the objects SELF, i.e., the handle to the objects storage. There are several ways to do this. The SELF of an object is actually a parameter to any method, so try the techniques above for inspecting parameters. SELF is located at the stack frame (or A6 for the current stack frame) plus eight bytes, for any method on the stack. To see the fields of the object do DM @@(A6Frame+8) or DM (A6Frame+8)^^. You can also sometimes get the handle if the object is a local variable or parameter to another routine, as noted above. The other way of getting the handle is to inspect the heap using HD R (R for relocatable blocks) and try to guess and figure out which handle is your object. Once you have the handle do a display memory command: DM objectsHandle^^.
Checking the function result. Immediately after a routine returns (when you are in the calling routine at the statement just after the call) the stack pointer (A7) points to the function result, which can be displayed with DM A7. If you are stepping through your code and execute a magic return (MR A6) then you can inspect the function result using DM A7.
Miscellaneous Tips
Restore the previous command to the command line: command V (like Paste)
The return key repeats the last command.
The help command: HELP. This displays information on the commands.
Use the arrow keys to scroll up to material that has scrolled off the screen.
To get a menu of routine names: command : (colon). Then use the arrow keys to move up and down the menu and hit return to paste the name onto the command line.
You can type in names instead of addresses for most Mac global variables, ROM routines, and your own procedures.
Short cut for addresses. Macsbug uses the dot or period (.) as a special character to indicate the current address. Macsbug will automatically set the dot character after many commands so that in the next command you can just type in the dot instead of a long hex address, for example after the find command you can display memory with: DM .
Short cut for current routine name: the colon expands to the name of the current procedure, for example: :+22 means current+22.
Log everything to file or an Imagewriter. LOG filename or LOG printer.
To leave Macsbug. Go: G. Restart the program: EA. Exit to shell: ES. Restart or reboot: RS or RB. Also see the section on going past errors and stepping for advanced techniques.
Turn off breaking into Macsbug for messages: DX. This is very convenient to turn off breaking for messages and let your program run. The messages will still be recorded for the next time you go into Macsbug.
Hex calculator. Macsbug can function as a hex calculator and automatically converts from hex, decimal, and strings. Simply type the expression, number, or string and Macsbug will calculate and display the result in hex, decimal, and as a character string. Hex numbers are used as a default: use a pound sign before decimal numbers. For example: #1256 {return} results in $000004E8 #1256 #1256 , and (#20 * #50) results in $000003E8 #1000 #1000 , and NIL! results in $4E494C21 #1313426465 #1313426465 NIL!
When Macsbug is invoked, you are often on the next instruction after the bomb.
Command-key short cuts: command G (for go), command S (for step), and command T (for trace or SO step over).
Change registers. Use the equal sign: A7 = 2.
Miscellaneous no-nos. Calling a method of an object which does not exist. Dereferencing a field of a handle or object, or passing it as a parameter to a routine when memory can move. Using uninitialized, NIL, or disposed pointers. Disposing a handle twice. Passing a Pascal subprocedure as a ProcPtr.
You can specify multiple commands on the same line separated by semicolons, for example: ;HC;G.
Macros. Define a macro, for example: MC J DM 200;G then just type J and return to do these commands. Macros can be defined for any command or string or address.
Do commands everytime Macsbug is invoked. Define commands for the the special everytime macro, for example: DM everytime HC which will check the heap everytime Macsbug is invoked.
Permanent macros. You can use ResEdit and the files supplied with Macsbug to create permanent macros.
Display the version of Macsbug: DV.
Just Enough Assembly (or disassembly)
Disassembling your code will help you find out where you are, especially in case of a crash. It will also help you to set breakpoints and step through your code, all of which is very simple. You dont need to know every assembly statement or all about how your source is put into assembly, but a few simple hints will tell you the gist of what your code is doing. For example, it may be enough to disassemble your code and look for the names of ROM routines you call. The paragraphs later in this section will give you a few hints to tell you what your code is doing in other situations such as calling routines, passing parameters, stack frames, and some hints about some seemingly bizarre things in your code. You may wish to review the section toward the beginning of the article on The Low-Level Mac.
Disassembly commands: IL address (regular disassembly), IP address (which disassembles around the address), ID (which disassembles one line), and IR address (which disassembles to the end of the routine. If you omit address then Macsbug will use the PC (program counter) which is the current address that the microprocessor is executing. Hit return to continue with the disassembly. The first instructions listed may not be valid until Macsbug gets in sync. If you are in a ROM routine then it is possible that the name will not be valid, since the ROM routines jump around to little snippets of unnamed code.
Look for calls to other routines: look for JSR (jump subroutine), JMP (jump), and/or BSR (branch subroutine). Macsbug will usually list the names of the routines next to one of these statements. This will also help you relate the disassembly to your source code. If names are not listed for these then use the WH command on any code address which will at least show you the heap and code segment. Glue code, method jumps, and library routines are usually linked in the main code segment. MPW Pascal puts method jumps are in segment %_SelProcs. INITs and certain Inside Macintosh routines are often in the system heap. Method calls are usually disassembled as JSR CODE %_SelProcs. You can disassemble the addresses of the JSR (and JMP and BSR) statements and see if it is glue code (there is usually an Atrap fairly soon in the code), or if you hit other JSR and/or JMP statements then you are probably going through a jump table.
Check the relative location within the routine. Note the offset from the start and end of the routine (disassemble the routine to the end to see how long it is), as well as the offsets from calls to other routines. This will give you the approximate offset in your source.
If routine names are not listed. Some routines can be readily understood by certain clues. In MPW object Pascal, method calls are usually accomplished by JSR to an address in CODE %_SelProcs. Similarly, calls to certain toolbox routines (usually the ones that say [NOT IN ROM] in Inside Macintosh) have glue code, usually linked into your main CODE resource, and are indicated by JSR addressInMain. You can use the WH Address command to find out more about where the code is going to jump to. Certain library routines also sometimes do not have a name. For example, Pascals string functions are often linked in to your code but do not have Macsbug names. These are usually located in your main code segment. The above commands are usually all you need to know to look through your code and find out where you are and to set breakpoints.
Parameters. Just before a JSR (or JMP or BSR), there is usually code which puts parameters on the stack with statements such as PEA (push effective address) and MOVE usually with A6 or A7 as the second parameter. Pointers are often pushed instead of values. See the section on inspecting parameters for a map of the stack and for techniques on how to inspect parameters. The order of the statements is: space is reserved for the function result (if any), the parameters, the static link (a pointer to the local variables of any enclosing procedure), and then SELF (if object method call). After the call there are often statements which handle the result of the function.
The function result. Before calling a routine space is reserved for the function result (see previous point). Immediately after the routines returns (when you are at the statement just after the call) the stack pointer (A7) points to the function result, which can be displayed with DM A7. If you are stepping through your code and execute a magic return (MR A6) then you can inspect the function result using DM A7.
The start and end of routines. A routine usually starts with a LINK statement. Before executing the LINK statement, the stack is set up as in the previous point. The link statement sets up a stack frame for the routine by saving the current value of A6 and reserving space for the routines local variables.
Common statements: assignment statements (such as x:=2;) are usually handled by MOVE statements. If-then-else will usually have a CMP followed by a branch statement (such as BRA, BNE, BGT, etc.). These branches are usually opposite to the source code: when the source says equal, the assembly will say BNE (branch not equal). Loops are also usually accomplished with branch statements, although they also can be implemented with decrement and branch statements (such as DBRA).
General points on registers and addresses. Most assembly language statements take an address or register as a parameter. A registers are used for addresses. A5 usually points to the start of global variables. A6 usually points to the start of the stack frame. A7 is usually the same as SP, the stack pointer. Dereferencing the address is indicated by putting the register in parenthesis. Offsets from addresses are indicated by putting a number before the address. D registers are used for data.
General points on disassembly. ORI instructions usually indicate skewed disassembly, which happens until Macsbug gets back in sync. This happens when you ask Macsbug to disassemble starting in the middle of an assembly language instruction. The names of ROM routines may be incorrect because the ROM routines jump around to little snippets of code which do not have names.
The start and end of a routine. Routines usually start with a LINK statement which helps to set up the stack. There are often a lot of instructions at the start to set things up for the routine. This includes an instruction (usually MOVE or MOVEM) to save registers to the stack so that they can be used during the routine and later restored. In Pascal, some parameters are copied to create local variables. The routine will usually end with an UNLK (unlink) to restore the stack, and a RTS (return to sender) or JMP statement.
Where to Go from Here
There are several books, software products and other things you can do. Macsbug comes with a command reference manual. Scott Knasters book (How to Write Macintosh Software, The Debugging Reference for Macintosh)2 contains a tutorial on assembly language implementations of high level languages and using Macsbug and TMON for debugging. Macsbug also allows the use of Discipline (available from APDA) for automatically checking the parameters to ROM routines for reasonableness. Macsbug macros and templates for displaying memory in different formats can be created using ResEdit and the supplied resources. You can also write your own external commands or dcmds. The two main features I would like to see for the next version of Macsbug are automatic breakpoints/tracing commands for all subroutine calls and better facilities for external commands to call built-in commands (which would allow a menu driven interface to be set up).
Other Debuggers
You can upgrade to several other debuggers, including TMON, TMON Professional, The Debugger, THINK Pascal and THINK Cs built-in debugger, THINKs LightsBug advanced debugger, MPWs SADE, and a new free debugger called ABZmon. All of these debuggers use the mouse and multiple windows to organize and display information and keep it on the screen. TMON also has trap discipline (checks the reasonableness of parameters to ROM routines), loading of resources, and breakpoint/tracing commands for all subroutines (not just ATrap traces). The Debugger adds trap discipline, a backdoor to run the MPW shell, Incremental Build System for fast rebuilding of your application, MMU memory protection to help detect bad pointers, breakpoints/tracing through subroutines, and MacNozy (for disassembling and getting information on programs). SADE, the THINK debuggers, and The Debugger all allow you to step through your source line by line, and view variables, etc. ABZmon is similar to Macsbug, except that it is mouse-based, and can be downloaded for free from Applelink.
Invoking Macsbug
Push the programmers interrupt switch.
Crash.
Put the following statements in your code:
{1}
PROCEDURE debugger; INLINE $A9FF;
PROCEDURE DebugStr(str: str255); INLINE $ABFF;
Example calls in Pascal:
{2}
Debugger;
DebugStr(before call to newPtr);
DebugStr(concat(x= ,LongToString(x),
y= ,booleanToString(y)));
How to Turn on Symbol Names
In MPW Pascal. You usually use the -mbg full option on the command line, e.g. Pascal myCode.p -mbg full. You can also use the {$D+} compiler option in your source code.
In MPW C. You usually use the -mbg full option on the command line, e.g. C myCode.c -mbg full.
In THINK C. Macsbug symbols are on by default. Use the Options... command in the Edit menu and click on Macsbug Symbols.
In THINK Pascal. Choose Compile Options... from the project menu and click on Macsbug Names in the dialog box, or use the {$N+} compiler option in your source code.
Utility Routines
This section has some utility routines to help calling DebugStr to send messages to Macsbug. It is much easier to use functions when invoking DebugStr, since no variables need to be declared and assigned values. For example:
DebugStr(concat(before NewPtr x= , LongToString(x),y=,booleanToString(y)));
FUNCTION LongToString(theLong:longint):str255;
{simple glue routine takes integer or longint and returns string containing
value}
var
theString: str255;
begin
NumToString(theLong,theString);
LongToString := theString;
end;
FUNCTION LongToHexString (number: UNIV
longint): str255;
{takes longint and returns a string containing value in hex}
CONST
digits = 0123456789ABCDEFX;
VAR
hex: Str255;
hexIndex: INTEGER;
negative: BOOLEAN;
BEGIN
hex[0]:=chr(9);{set length of hex to 9}
hex[1] := $;
IF number < 0 THEN BEGIN
number:=bitAnd(number,maxLongint);
{ mask off leading negative bit}
negative := true;
END
ELSE negative := false;
FOR hexIndex := 9 DOWNTO 3 DO BEGIN
hex[hexIndex]:=digits[1+(numberMOD 16)];
{put digit into hex string}
number:= number DIV longint(16);
END;
IF negative THEN
hex[2]:=digits[1+(number MOD 16) + 8]
ELSE hex[2]:=digits[1+(number MOD 16)];
LongToHexString := hex;
END;
FUNCTION PtrToHexString( aPtr: UNIV ptr): str255;
{takes Ptr and returns string containing value in hex. StripAddress is
called on the Ptr.}
CONST
digits = 0123456789ABCDEF;
VAR
hex: string[10];
hexIndex: INTEGER;
number: longint;
BEGIN
hex[0] := chr(9); {set length of hex to 9}
hex[1] := $;
number := longint(StripAddress(aPtr));
FOR hexIndex := 9 DOWNTO 2 DO
BEGIN
hex[hexIndex]:=digits[1+(number MOD16)];
{put digit into hex string}
number := number DIV longint(16);
END;
PtrToHexString := hex;
END;
FUNCTION BooleanToString(theBoolean:Boolean): str255;
{ glue routine takes boolean and returns
string containing value}
begin
if theBoolean
then BooleanToString := true
else BooleanToString := false;
end;
List of Macsbug Commands
The following list of commands was taken from the Macsbug help command. Commands are listed in categories.
Editing
Type a command and then press Return or Enter to execute it. Typing return without entering a command repeats the last command. Multiple commands can be executed by separating them with ;.
Editing commands
Command-V Restore previous command lines for editing.
Command-Left Arrow Move cursor left one word.
Command-Right Arrow Move cursor right one word.
Command-Delete Delete the word to the left of the cursor.
Selecting procedure names
Procedure names are selected by typing Command-: while entering a command line. A menu of all procedures is shown. Up and down arrows are used to select a specific procedure. Typing Return copies the selected procedure to the current insertion point of the command line. Typing Escape leaves the command line unchanged. The list of procedure names can be qualified by typing the first letters of the name. Delete undoes the qualification one letter at a time. Typing Command-: undoes the qualification and shows all procedures.
Expressions
The general form of an expression is: value1 [operator value2]. Parentheses may be used to control the order of evaluation. Expressions always evaluate to a 32 bit value unless .W or .B follows the value. Expressions evaluate to either a numeric or a boolean value based on the operators used. The action of some commands change based on this result. For instance, BR addr expr will break after n times if expression is numeric or it will break when expr is true if expression is boolean.
Values
Registers
All 68000 family registers use their Motorola names. MMU 64 bit registers and floating point registers are not allowed in expressions.
Numbers
Numbers are hex by default but can be preceded by a $ in the case of conflicts with registers An and Dn. Numbers are decimal if they are preceded by a #.
Symbols
Symbols are found by searching the heap and evaluate to an address.
Traps
Trap number in the range A000 to ABFF or a trap name. Trap names can be preceded by a Ý in the case of conflicts with symbol names.
. The last address referenced by certain commands. For instance SM sets dot to the first address that was changed and sets the last command to DM. Typing return will display the memory that was changed.
: The address of the start of the proc shown in the PC window. Not valid if no proc name exists for PC.
Operators
Arithmetic + - * / MOD
Boolean AND or & OR or | NOT or ! XOR
Equality = or == <> or != < > <= >=
Indirection @ (prefix) or ^ (postfix)
Flow control
G [addr]. Resume execution at addr or PC if no addr. Command-G is the same as G, Return.
GT addr [;cmds]. Go till addr is reached and optionally execute one or more commands. The addr can be in ROM but will be much slower.
S [n | expr]. Step n instructions or until expr is true. Command-S is the same as S, Return.
SO [n | expr]. Step n instructions or until expr is true. JSRs, BSRs and Traps are treated as one instruction. For historical reasons, T (for Trace) is allowed as an alias for SO. Command-T is the same as T, Return.
SS addr [addr]. Step until checksum of addr range changes.
MR [offset | addr]. Break after the current procedure returns by replacing its return address. If the parameter is less than A6 then the return address is at A7+offset. If the parameter is greater than or equal to A6 then the return address is at addr+4. If no parameter then the return address is at A7.
Breakpoints
BR addr [n | expr] [;cmds]. Break at addr after n times or when expr is true and optionally execute one or more commands. If no n or expr then break always. The addr can be in ROM but will be much slower.
BRM string Set breakpoints at all procedure names that contain string. String can occur anywhere in the name, not just at the beginning.
BRC [addr]. Clear breakpoint at addr or all breakpoints if no addr.
BRD. Display breakpoint table.
A-Traps
Appending A to an A-Trap command name restricts calls to only those from the application heap. Entering two traps as parameters defines a trap range. Entering no traps defines the range A000 to ABFF.
ATB[A] [trap [trap]] [n | expr] [;cmds]. Break at traps after n times or when expr is true and optionally execute one or more commands. If no n or expr then break always.
ATT[A] [trap [trap]] [n | expr]. Display trap information each time a specified trap is called.
ATHC[A] [trap [trap]] [n | expr]. Check the heap each time a specified trap is called.
ATSS[A] [trap [trap]] [n | expr], addr [addr]. Checksum addr range each time a specified trap is called. If the checksum has changed then break into MacsBug. If no second addr then checksum the long word at addr.
ATC [trap [trap]]. Clear trap range or all traps if no parameters.
ATD. Display the A-Trap tables.
ATR[A] [ON | OFF]. Turns trap recording on or off. Toggle if no parameters. Information about the most recent trap calls is recorded.
ATP. Plays back the information recorded while ATR is on.
DSC [ON | OFF | str]. Turns Discipline on or off. Toggle if no parameter. Any parameters other than ON or OFF are passed to Discipline for interpretation. Discipline examines parameters before trap calls and examines results after trap calls. Any errors break into MacsBug.
Disassembly
All commands assume the PC if no addr is specified.
IL [addr [n]]. Disassemble n lines from addr. If no n then display half page.
IP [addr]. Disassemble half page centered around addr.
ID [addr]. Disassemble 1 line starting at addr.
IR [addr]. Disassemble till the end of the routine addr is in.
DH expr ... Disassemble one or more exprs as a sequence of 16 bit opcodes.
Heaps
HX [addr]. Set the current heap to the heap at addr. If no parameter then toggle between the Application, System and user heaps.
HZ. List all known heap zones.
HD [F | N | R | L | P | RS | TYPE]. Display specific blocks in the current heap or all blocks if no parameter. The possible qualifiers are:
F: Free blocks
N: Nonrelocatable blocks
R: Relocatable blocks
L: Locked blocks
P: Purgeable blocks
RS: Resource blocks
TYPE: Resource blocks of this type
HT. Display a summary of the current heap.
HC. Check the current heap for inconsistencies.
HS [addr]. Turn on scrambling of the heap at addr or ApplZone if no addr. Calling NewPtr, NewHandle, ReallocHandle, SetPtrSize or SetHandleSize checks the heap before the call. If good then the heap is scrambled. If bad then a MacsBug break is forced. Scrambling continues until next HS or a bad heap is detected.
Symbols
RN [expr]. Set the resource file ref num qualifier to expr. If no expr then set it to curMap. Once set, all subsequent symbol references must be from a heap block with a matching file ref num. If expr is 0 then all symbols match.
SD. Command-: is now used to select symbol names.
SX [ON | OFF]. Turn symbols in disassembly on or off. Toggle if no parameter.
Stack
SC6 [addr]. Show the calling chain based on A6 links. If no addr then the chain starts with with A6. If addr then the chain starts at addr.
SC7. Show possible return addresses on the stack. A return address is an even address that points after a JSR, BSR or A-Trap.
Memory
All commands assume the dot address if no addr is specified.
DM [addr [n | template | basic type]]. Display memory from addr for n bytes or as defined by a template or a basic type. The basic types are Byte, Word, Long, SignedByte, SignedWord, SignedLong, UnsignedByte, UnsignedWord, UnsignedLong, PString and CString.
TMP [name]. List templates names that match name. If no name then list all template names.
DP [addr]. Display memory from addr for 128 bytes.
DB [addr]. Display the byte at addr.
DW [addr]. Display the word at addr.
DL [addr]. Display the long at addr.
SM addr expr | string ... Assign values to memory starting at addr. Each value determines the assignment size. Specific sizes can be set using SB, SW or SL.
SB addr expr | string ... Assign values to bytes starting at addr.
SW addr expr | string ... Assign values to words starting at addr.
SL addr expr | string ... Assign values to longs starting at addr.
Registers
Values can be assigned to registers with commands of the form: RegisterName := expression or RegisterName = expression.
TD. Display CPU registers.
TF. Display 68881 floating point registers.
TM. Display 68851 MMU registers.
RAD. Toggle between registers as An and Dn or as RAn and RDn.
Macros
MC name expr | expr. Define a macro called name that expands to expr or to the current value of expr.
MCC [name]. Clear named macro or all macros if no name.
MCD [name]. List macros that match name. If no name then list all macros.
Miscellaneous
Escape or tilde. Toggle between the user screen and the MacsBug screen.
RB. Unmount the boot volume and reboot.
RS. Unmount all volumes except server volumes and reboot.
ES. Exit the current application.
EA. Restart the current application.
WH [addr | trap#]. Find the name and addr of the parameter. If no parameter then assume WH PC.
HOW. Display the reason MacsBug was entered this time.
F addr n expr | string. Search from addr to addr+n-1 for the pattern. If pattern is an expr then the width of the pattern is the smallest unit (byte, word or long) that will contain the value.
CS [addr [addr]]. Checksum addr range and remember the value. CS without parameters checksums the last addr range and compares it to the last value. If no second addr then checksum the long word at addr.
LOG [pathname | Printer]. Log all MacsBug output to a file or to an ImageWriter printer. LOG without parameters turns logging off.
SHOW [addr | addr [ L | LA | W | A ]]. Display memory at addr in status region. Quoting addr causes it to be evaluated each time the display is updated. SHOW without parameters cycles thru the display formats. The formats are:
L: Long words
LA: Combination of Longs and ASCII
W: Words
A: ASCII Text
SWAP. If you have a single screen then SWAP toggles between: Drawing step and A-Trap trace info without swapping. Draw step and A-Trap trace info and swap each time. If you have multiple screens then SWAP toggles between: MacsBug screen is always visible.
MacsBug screen is visible only at breaks.
DX [ON | OFF]. Turn user breaks (A9FF, ABFF) on or off. Toggle if no parameter.
DV. Display MacsBug version.
HELP [cmd | section]. Display info about a specific command, a section. If no parameter then display all sections.
List of System Errors
1 Bus error. Reference to invalid memory space.
2 Address error
3 Illegal instruction
4 Zero divide
5 Check exception
6 TrapV exception
7 Privilege violation
8 Trace exception
9 Line 1010 exception
10 Line 1111 exception
11 Miscellaneous exception
12 Unimplemented system routine
13 Spurious interrupt
14 I/O system error
15 Segment Loader error
16 Floating point error
17-24 Cant load package
25 Cant allocate heap block
26 Segment loader error
27 File error
28 Stack has overflowed into the heap
33 Bad heap block header
84 Menu resource was purged
Bibliography
1. Macsbug 6.2 Reference
2. Knaster, Scott. How to Write Macintosh Software, Second Edition, 1988. Hayden Books.