Macsbug Template
Volume Number: 8
Issue Number: 7
Column Tag: Debugging
Related Info: Time Manager Vert. Retrace Mgr
Macsbug Template Use
Accessing your globals in MacsBug
By David T. Roach, Austin, Texas
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
Introduction
When writing an application in C, high-level debuggers such as Think C’s
Debugger and SADE eliminate quite a bit of pain from the development process since
they are able to display references to variables by name.
Developers of lower-level code, such as INITs, drivers, control panel devices,
code resources, plug-ins, XCMDs, etc., must use either MacsBug or TMON, neither of
which accesses variables directly by name. This is because the global variables are
referenced from the A5 register, or in some cases the A4 register, and the temporary
variables are accessed via the A6 register.
On the other hand, MacsBug and TMON have no problem finding your routines by
name, because each has a method for searching the current ( target) heap zone for the
presence of the function name immediately following the associated function. This
facility has recently been updated to include function names longer than 8 characters,
with support for this from the most popular compilers.
It is possible to trick MacsBug 6.2 (or higher) into believing that what it
perceives as the first instruction in a named function is really a pointer or handle to a
place in memory that contains your globals, which you can access by name. In order to
do so, you must do several things you might not do otherwise:
Instead of using A5 or A4 to point to your globals under compiler control, you
must allocate a Pointer or Handle and fill it in using a data structure which defines your
globals explicitly.
You must store this pointer or handle in your main code segment. Technically
this is self-modifying code. However you only modify the code once during
initialization, and it remains unmodified after that. This is essentially the same method
that a VBL or Time Manager (version 1) task uses to store a pointer to it’s globals, so
this method should remain valid as long as Apple maintains support for programs
written under the past and current versions of the Vertical Retrace Manager. This
method has not varied since the introduction of the 128K Mac, which is a fairly good
reason to believe that it will remain the same in the future (although not infallible).
Create a separate ‘.r’ text file which contains ‘mxbm’ (macro) and ‘mxwt’
(template) resource types which define your very own globals structure.
Use the MPW Rez tool or SARez (a precompiled version of the Rez tool included
with Think C 5.0) to compile and merge these resources into your Debugger Prefs file
in the system folder. MPW includes the Rez resource compiler tool, which is activated
from command line input in MPW. Also included is a Commando interface for Rez,
which is a dialog used to build a command line specific to Rez. SARez is a utility included
with Think C 5 which is a double-clickable version of MPW’s commando interface to
Rez. It also creates a command line, then parses it and executes it immediately. Unless
stated otherwise, all further references to Rez in this article also refer to SARez.
While it’s possible to use ResEdit to perform this and the previous step, it’s
slow and painful. Additionally, ResEdit cannot deal with really large templates.
In order for MacsBug to find it at startup time, the Debugger Prefs file must
exist at the main level of your system folder, and not in any other folders such as
Control Panels or Extensions. It would make sense for future versions of MacsBug to
look in the Prefs Folder as well.
Reboot and enjoy debugging your variables by name. Each time you add a data
element to your structure(s), you must also update the corresponding ‘.r’ file and
recompile it using Rez. While this sounds like a pain (it is the first few times), the
payback on the stability and integrity of your project is tremendous.
What It Takes
MacsBug 6.2 and Think C 5 or MPW C package v3.2, are required for
implementing the symbolic display of variables under MacsBug, using the techniques
described in this article. Other development environments and debuggers will have
slightly different requirements, but shouldn’t present any serious obstacles to
implementation. The techniques for dealing with this feature under MPW are somewhat
different than those required by Think C 5, but the differences have been isolated into
separate source files so that the remaining files can be run under either compiler.
Why It Works
MacsBug has been able to reference functions by name almost since the release of
the first Macintoshes. More recently the ability to include user templates and macros,
as well as logging to an output file, have been included. MacsBug names (officially
known as Procedure Definitions) are documented on page 408 of the "MacsBug
Reference and Debugging Guide for MacsBug version 6.2", which you’ll find on
bookstore shelves next to the Inside Macintosh volumes if you don’t already have it. It’s
the official documentation for MacsBug. MacsBug names can have a fixed size of 8 or 16
characters, or they can be of a variable length. MPW C or Think C 5 will generate these
names optionally. In the case of the MPW Assembler, you are required to build the
names into your code (which isn’t really so bad once you figure out how to do it).
What we want to do is to use a MacsBug name to represent a pointer to all of our
data rather than the entry point of a routine. To do this, we have to structure the
program under development to always reference this pointer, rather than A5 or A4. In
some cases it’s also useful to include values that would normally be stack-based and
temporary in the globals allocation, so we can observe their value the last time that
they were used. In doing so, we gain the advantage of being able to access more than 32k
worth of global data. All the globals are all defined in a single ‘.h’ or ‘.a’ file, with a
corresponding ‘.r’ containing exactly the same data structure(s) in MacsBug/Rez
format.
The pointer to our data is embedded into one of our code segments as a fake
function, with routines in the same segment which set or get the current value. It
should only be necessary to initialize this pointer once. Calling StripAddress on the
pointer before we set it and FlushInstructionCache after we set it will assure 32-bit
addressing and 040 caching compatibility. The fake function (our pointer) and the
routines which manipulate it are handled in an object-oriented style. The fake function
is the object’s data field, and the routines are methods to manipulate or access the
single data field. If Apple ever changes the way VBL tasks access their globals ( thereby
rendering this scheme useless), then these routines most probably can be modified to
use the new method.
The fake function simply consists of a 4-byte pointer, an assembly language
‘RTS’ instruction, and the MacsBug name. Think C 5 requires some additional
gymnastics to build this apparently simple piece of code. This is because Think C insists
on mapping all function entry points via the main jump table, which is in turn
referenced from A5 or A4. When referencing our pointer from the two routines which
access it, we need PC-relative addressing. We get around this by declaring three
different entry points into a single C function in Think C. Each of these individual
functions can then reference each of the other two with PC-relative addressing. Of
course, one of these three functions is the fake function. Any external references to this
function will still be referenced via the jump table, so when using Think C you must
use the two routines to manipulate the globals pointer. You will not be able to access it
directly by name from anywhere else in the program, even though it may look like it’s
compiling OK.
Under MPW, we create a ‘.a’ file in assembly language, which contains the
pointer and the two routines. It’s simply linked into the project. Since MPW doesn’t
insist on referencing each and every function through the jump table, the code will
appear to be much more straightforward, although it is functionally identical. In other
words, don’t look at the Think C code first if you want to understand how this works.
Once we’ve defined the data structure in C or MPW Assembler, then it’s time to
define an identical structure in Rez format. MPW 3.1 shipped with some
MacsBug-specific ‘.r’ files on the MacsBug distribution disk. For some mysterious
reason, these files are not part of MPW 3.2. These ‘.r’ files defined the ‘mxwt’ and
‘mxbm’ templates in both MacsBug and ResEdit formats. The ‘TMPL’ resources are the
ResEdit templates used in defining the ‘mxbm’ and ‘mxwt’ resources. The ‘mxwt’
resources are MacsBug data templates which are used by MacsBug to display various
data types.
Built-in templates already included in Debugger Prefs include classics such as
WindowPtr, GrafPtr, pstring, cstring, etc. An ‘mxwt’ template can reference a data type
defined in another ‘mxwt’ template, since MacsBug is essentially interpretive in this
regard. Templates can also be singly or doubly dereferenced, so if you have a handle or
a pointer to a data structure, you can display the entire structure. If it contains a
handle or a pointer to a structure, you can display that structure too, ad infinitum. The
ability to deal with linked lists of structures is also included.
Macros created by the ‘mxbm’ resources or created directly by the MacsBug
user can also reference any of these templates. So, if we define a template which
corresponds directly to our program’s data structures and we are able to access the
pointer to our data structures via name in MacsBug, then we have a mechanism to
display all the data by name and value. Furthermore, if MacsBug’s logging option is
enabled, everything gets stored to a disk file for later examination.
For instance, if we name our fake function ‘theGlobals’ and we have a template
named ‘theTemplate’, then anytime we’re in MacsBug and the target heap contains our
code we can type
dm theGlobals theTemplate
to view all of our data’s current values. We can even create a macro which types
out this line for us, maybe with a name like ‘globs’. Then if we’d like, in the middle of
any routine we can place a debugger call such as
/* 1 */
DebugStr("\pglobs;g");
which will disable all the interrupts, display all the globals, re-enable the
interrupts, and then go on running. Or we can break at a specific function and do the
same thing in MacsBug by typing
BR funcname ‘;globs;g’
Once over the learning hump, life becomes much easier. Various versions of the
main data template can be created which only display a subset of data. It’s also possible
to develop function-specific templates which display values on the stack when
MacsBug breaks at a function entry point. This is useful in object-oriented
programming where the pointer to the object is always at a certain position on the
stack, and you can then break on any method and display the object’s variables.
Sanity checking
It always helps to be sure that what you’re looking at really is your data, and
that your ‘mxwt’ template does in fact match your data structure. You can make life
easier on yourself by storing your application’s OSType signature at both the beginning
and end of your data structure. Fill in these fields immediately after initializing the
pointer in the fake function. If you don’t have a signature, you can use something like
‘dBug’. By having the same 4 bytes at both the start and end of your data structure, you
can easily see whether you’re looking at the right address (is the first OSType filled in
correctly?), and if you are, whether the template matches the structure (is the last
OSType filled in correctly?). Without these sanity checks in place, you can easily fall
back into a questioning mindset (Grumble, grumble, is this really my data, or does it
just look like it might be my data??).
Updating Debugger Prefs using SARez or the Rez Commando dialog
If you’re not familiar with Rez, then the prospect of compiling resources and
merging them into your Debugger Prefs file can be a bit daunting. Until you’re confident
of the methodology, it may be a good idea to drag a fresh copy of the Debugger Prefs file
into your System Folder each time you plan to add new template and macro resources.
Never work on your original of Debugger Prefs, only on a copy!
We need to specifically tell Rez where our source ‘.r’ file is, where Debugger
Prefs is, and that we want to merge our resources into Debugger Prefs rather than blow
the existing file away. Since all the templates and definitions we need to compile our
file are actually in the file already, there is no need for search paths or any additional
description files. If we ask for progress information, then we get a listing which
includes any errors made during compilation. This is one of the few places in this cycle
that you’ll get any kind of specific error messages, so make sure that you don’t have
any.
If you’re an MPW script monger, then you can probably sit down and type out the
command line. Otherwise, it’s probably easier to use the Commando interface the first
time and copy the command line into MPW for future use. SARez users will need to
remember to do the same things in the commando dialog each time you compile the file.
Referring to the dialog, note that ‘Progress Information’ is checked, and that the
‘Merge resources into resource file’ radio button is selected. Click on the ‘Description
Files’ push button to determine which file to compile, and click on the ‘Resource
Output File’ popup menu to select the destination file. Since we want to modify Debugger
Prefs, we ‘Select an existing output file’. Everything else in this dialog is unused.
If you’re using MPW, now’s the time to select the Command Line and copy it,
then Cancel Rez and paste the line into the MPW worksheet, select it, hit enter, and off
you go. You can now use this over and over again without calling up the Commando
interface. Of course, if you rename your hard disk or any of the folders, then it stops
working.