|
This technote shows how to
translate your experience with MacsBug into a
working knowledge of the GNU source-level debugger
(GDB) as used on Mac OS X. The focus is on using
GDB as an assembly-level tool for debugging
programs for which you don't have source (for
example, debugging crashes inside Carbon itself) or
for which the source is inaccessible (for example,
debugging a crash that only occurs at a customer's
site). However, many of the tricks you learn here
will also be useful for more mundane source-level
debugging.
This technote is primarily directed
at application developers (and other developers of
user space code) who are familiar with MacsBug and
want a quick introduction to GDB. If you're not
already familiar with MacsBug, you probably should
skip this technote and go straight to the GDB
manual. If you're developing kernel code (for
example, an IOKit driver) you will find most of
this technote useful but you must also learn the
two-machine debugging techniques described in
other
Apple documentation.
[Oct 18 2001]
|
Introduction
The first step in a successful transition from MacsBug to
GDB is...
If you're used to MacsBug, learning GDB won't be that
hard. And while you'll miss some of MacsBug's nicer features
(the showpath 'dcmd' for example) you'll be
impressed by some of the cool things that GDB can do for you
(future break is one of my favorites). So, let's get
started.
About GDB
The GNU source-level debugger (GDB) is the primary
debugging tool on Mac OS X. Whenever you debug on Mac OS X,
you are using GDB in one form or another.
- GDB is available as a standard command-line tool
(
gdb ) that you can run from Terminal.
- Project Builder provides a graphical front end to
GDB's commonly-used debugging facilities. However, there
are cases where you might want to enter GDB commands
directly, either by starting GDB from the command line or
by entering commands at Project Builder's GDB
prompt.
- The CodeWarrior source-level debugger is currently
layered on top of GDB (although there is no direct access
to GDB from within CodeWarrior).
This technote concentrates on using GDB from the command
line. This is in line with the overall goal of exploring GDB
as an assembly-level debugging tool.
While it is possible to define macros to make GDB behave
like MacsBug (for an amazing example, check out the
MacsBug GDB plug-in), that's
not a good long-term choice in my opinion. Mac OS X is the
long-term direction of Apple, and GDB will be the core
debugger on Mac OS X for the foreseeable future. It behooves
you to learn GDB now rather than later.
GDB, as its full name suggests, is primarily a
source-level debugger. This has its pros and cons. There's
no denying that source-level debugging is easier and its
absence in MacsBug has always been sorely missed. However,
GDB's focus on source-level debugging means that it isn't a
great assembly-level debugger. It is, however, good enough
to get the job done.
Conventions Used In This Technote
Throughout this technote the following styles have
special meaning.
Monospace text indicates a GDB or
MacsBug command, or text output by the computer.
Bold monospace text indicates
commands entered by the user.
Italic monospace text indicates
comments.
- A backslash (\) at the end of a line indicates that
the line is continued on the next line.
^X indicates that you should enter a
control character (for example, the notation ^C means you
should hold down the control key and press the C
key).
- In debugger command descriptions, meta variables are
shown in angle brackets. For example,
DM
<addr> indicates that you should substitute
an address for <addr> .
GDB Basics
When you break into MacsBug on traditional Mac OS you
stop the entire computer. This is not the case with GDB on
Macs OS X. When you break into GDB you stop only the process
that you're debugging. This allows you to look up source
code and documentation while you're debugging.
There are two basic ways of targeting the process you
want to debug.
- Attach to the process after it's started running
- Run your program from within GDB.
The first approach has the advantage that you can start
debugging a program after it's been executing for some time.
You don't need to know in advance that this is a debugging
session. You can start debugging when you notice the program
misbehaving. The disadvantage of this approach is that you
have to jump through some hoops to see the program's output
to stdout and stderr . The
technique for doing this is explained later
in this document.
The second approach lets you see your program's output to
stdout and stderr easily. You
should use it when you're actively debugging a problem.
Attaching to a Running
Process
Listing 1 shows how to attach GDB to a running process.
You start by launching Terminal and running GDB from the
command line (gdb ). Once you've started
GDB you can either attach by name or by BSD process ID.
First you should try attaching by name (attach
BBEdit ). This can fail for a variety of
reasons--in this example it failed because the application
is a CFM binary--in which case you should attach by BSD
process ID. You can find the BSD process ID using GDB's
shell command to execute the ps
command line tool. This example uses grep to
search the output (which would be rather large otherwise).
The first column of numbers is the BSD process ID. You can
use that BSD process ID to attach to the process
(attach 795 ).
Listing 1. Attaching to a running
process
[localhost:~] quinn% gdb
[... output omitted ...]
(gdb) attach BBEdit
Unable to locate process named "BBEdit".
(gdb) shell ps auxww | grep BBEdit
quinn 823 [...] -csh -c ps auxww | grep BBEdit (tcsh)
quinn 795 [...] /Volumes/YipYip/MyApplications/BBEdit [...]
(gdb) attach 795
[... output omitted ...]
(gdb)
|
|
IMPORTANT:
A BSD process ID (PID) is the BSD way of
identifying a process. It is not the same as a
Carbon Process Serial Number (PSN), although there
are routines to map between the two in
"Processes.h".
|
Note:
To attach to a running application either it must
by owned by you or you must have root
privileges.
|
Note:
The attach command supports Tab
completion. If you type a partial process name and
press Tab, GDB will complete the full name.
|
Running Your Program From
Within GDB
Running a program from within GDB requires different
actions depending on the program's binary format. For Mach-O
programs you can simply enter the path to the executable on
the command line as you launch GDB. This is shown in Listing
2. GDB will stop after reading the program's symbols. You
can then set breakpoints, or just run the program
(r ).
Listing 2. Running a Mach-O program
under GDB
[localhost:~] quinn% gdb /Applications/TextEdit.app/\
Contents/MacOS/TextEdit
[... output omitted ...]
(gdb) r
|
|
IMPORTANT:
You must enter the path to the executable, not the
package. For example, enter
"/Applications/TextEdit.app/Contents/MacOS/TextEdit"
and not "/Applications/TextEdit.app".
|
If your program is built as a CFM binary you must do a
little more work. Rather than pointing GDB at the
application itself, you must point it at the CFM loader
program (LaunchCFMApp) that is responsible for loading and
running the CFM application. Listing 3 shows how this is
done. You must enter the path to the CFM binary (again, this
is the executable, not the package) as an argument to the
run (r ) command. This tells LaunchCFMApp which
CFM application you want to launch.
Listing 3. Running a CFM program
under GDB
[localhost:~] quinn% gdb /System/Library/Frameworks/\
Carbon.framework/Versions/A/Support/LaunchCFMApp
[... output omitted ...]
(gdb) r /Applications/Internet\ Explorer/\
Internet\ Explorer.app/Contents/MacOS/Internet\ Explorer
|
|
Breaking into GDB and Entering Commands
To stop your program in the debugger, just switch to the
appropriate Terminal window and enter ^C. All the threads in
your program will stop and GDB will present you with the
(gdb) prompt. Your program will immediately
stop responding to events (if you trying to click on one of
its windows you will get the spinning rainbow beach ball
cursor of death). You can continue executing your
program with the continue command (c ).
Listing 4 shows this entire process.
Listing 4. Stopping and continuing
a program
^C
Program received signal SIGINT, Interrupt.
0x700009a8 in mach_msg_overwrite_trap ()
(gdb) c
Continuing.
|
|
When you see the GDB prompt you can enter GDB commands.
GDB accepts commands in much the same way as MacsBug. You
type the name of the command followed by any command
arguments and then press Return to execute the command. Like
MacsBug, GDB supports command line editing
and history. You can also press Return to re-execute the
previous command. However, there are two significant
differences from the basic MacsBug command loop.
- MacsBug commands tend to be short and cryptic. GDB
commands, on the other hand, are long and explanatory.
Fortunately, you don't have to type the entire command.
Any unambiguous abbreviation of the command will do. For
example, the full command to see the registers is
info registers . A more succinct abbreviation
is info reg . However, if you really hate
typing you can abbreviate this all the way down to
i r . GDB has special logic that allows one
letter commands to work properly even if they are
ambiguous. For example, even though c is the
first letter of many commands, it is always interpreted
as (the very commonly used) continue
command. Finally, pressing Tab will autocomplete an
unambiguous command and pressing Tab twice will list
alternatives for an ambiguous command.
- In MacsBug you can type an expression on the command
line and MacsBug will evaluate it and display the result.
GDB requires you to do something with the expression. If
you want to see its value, simply print it using the
print command (or p for short).
Unlike MacsBug, the print command only displays the
results in one format at a time. To control that format,
append a '/' and a format specifier ('o' for octal, 'x'
for hex, 'd' for decimal, 'u' for unsigned decimal, 't'
for binary, 'f' for float, 'a' for address, 'c' for
char). You can also append a size qualifier ('b' for 1
byte, 'h' for 2 bytes, 'w' for 4 bytes, 'g' for 8
bytes).
MacsBug
|
GDB
|
command-power
|
^C [1]
|
G or command-G
|
c [2]
|
HELP <command>
|
help <command>
|
|
apropos <regex>
[3]
|
<expr>
|
p/x <expr> [4]
|
Notes:
- You must enter
^C in the Terminal window
in which you're running GDB.
c is short for
continue .
apropos will print all GDB help entries
that match the regular expression. This is really useful
if you're totally lost. For example, lets say you want
change the default radix for numbers input and printed by
GDB. help radix is no help at all, but
apropos radix is very useful.
p is short for print , while
/x specifies that the number should be
printed in hex. You can use any of the format specifiers
listed above.
Stopping Debugging
You can stop debugging in a variety of ways.
- Quit your program -- GDB continues to run but is no
longer attached to your program
- Detach from your program (using GDB's
detach command) -- GDB will keep running but
the connection between it and your program is
broken.
- Force quit your program (using GDB's
kill command) -- This is akin to the MacsBug
ES command. Your program dies but GDB keeps
running.
- Quit GDB (using GDB's
quit command) --
If you quit GDB while it's still attached to your
program, GDB will either force quit it (if you ran the
program from within GDB) or detach from it (if you
attached to it) before quitting.
Back to top
MacsBug To GDB Reference
The following sections describe the GDB equivalent for
various MacsBug constructs. The sections are structured
along similar lines to the MacsBug online help.
Editing
GDB supports command-line editing in much the same way as
MacsBug, but of course all the key bindings are different.
You can configure the GDB key bindings--see the GDB
manual for more information on this--but the default
configuration is described below.
MacsBug
|
GDB
|
left arrow
|
left arrow or ^B
|
right arrow
|
right arrow or ^F
|
option-left arrow
|
escape B
|
option-right arrow
|
escape F
|
option-delete
|
escape delete
|
command-left arrow
|
^A
|
command-right arrow
|
^E
|
command-delete
|
^U
|
command-V
|
up arrow
|
command-B
|
down arrow
|
Selecting Procedure Names
Like MacsBug, GDB is smart enough to find some symbols
even if the program wasn't compiled with debugger symbols.
You can learn more about accessing symbols in the Symbols
section later in this document.
MacsBug
|
GDB
|
command-D or command-;
|
Tab Tab [1]
|
|
info func <regex> [2]
|
Notes:
- The first Tab tries to complete the symbol; if there
is no unambiguous completion, the second Tab lists the
alternatives.
- This lists all the functions whose names match the
<regex>. Using an overly general regular expression
(for example,
info func .* ) can take a very
long time if your program uses large system frameworks
(for example, the Carbon framework).
Values
For an experienced MacsBug user GDB values are hard to
get used to. You must remember to use $ in front of any
register names you use. By default a numeric constant is in
decimal (not hex), and you must prefix hex numbers with 0x
and not $ ($ is used for registers, the value
history and GDB convenience
variables). There's also no support for unit suffixes
(K, M, G, and ß). Finally, $_ behaves much the same as
". " in MacsBug (it's the last address examined
by "x "), while $__ is like
".^ ".
One nice feature of GDB is the
value history. Every time you print an expression GDB prints
the value of the expression along with a dollars sign ($)
and a number. You can use this dollars sign and number
combination to recall the value in subsequent expressions.
You can see an example of this in Listing 5.
GDB also supports convenience variables, which are
discussed in more detail later in this
technote.
MacsBug
|
GDB
|
<register>
|
$<register>
|
$<hexnum>
|
0x<hexnum>
|
#<decnum>
|
<decnum>
|
K , M , G
and ß (postfix)
|
not supported
|
.
|
$_
|
(option-;)
|
not supported
|
:
|
not supported
|
Note:
GDB does let you change the default input and
output radix. Type apropos radix for
details.
|
Expressions and Operators
GDB uses a C-like syntax for its expressions, so it
shouldn't be hard for you to pick up. One big difference
between this and MacsBug is that each GDB expression has an
associated type. For example, GDB knows that the type of
argv is a pointer to a pointer to a character.
If you use it in an expression, GDB can work out the type of
the result--GDB knows that **argv is a
character. Thus, MacsBug's naive implementation of the
indirection operation (@ or ^) is replaced by the GDB's
C-like operator (*) which carries type information with it.
This means that you usually don't need MacsBug's size
postfix operators (.B , .W , and
.L ) because GDB already knows the right size of
an indirection. If necessary you can override GDB's type
information by using a cast. Some examples of this are shown
in Listing 5.
Listing 5. Using casts in
expressions.
$r3 is an integer, but you can cast it.
(gdb) p/x *(char *)$r3
$6 = 0x0
(gdb) p/x *(short *)$r3
$7 = 0x3
(gdb) p/x *(int *)$r3
$8 = 0x3e360
You can also use the brace notation when printing.
(gdb) p/x {char}$r3
$9 = 0x0
(gdb) p/x {short}$r3
$10 = 0x3
(gdb) p/x {long}$r3
$11 = 0x3e360
Accessing the value history.
(gdb) p/x $11
$12 = 0x3e360
|
|
Note:
GDB's indirection operation (*) is more lenient
that an ANSI C compiler in that it will let you
treat an integer value as a pointer. So an
expression like *0x01000 works in GDB, but wouldn't
work in C without a cast.
|
Note:
GDB's expression syntax is actually dependent on
the language you're debugging. However, when you're
debugging without source the default C-like syntax
applies.
|
MacsBug
|
GDB
|
+
|
+
|
-
|
-
|
*
|
*
|
/ or ÷
|
/
|
MOD
|
%
|
AND or &
|
& or &&
[1]
|
OR or |
|
| or ||
[1]
|
NOT or !
|
~ or ! [1]
|
XOR
|
^
|
<<
|
<<
|
>>
|
>>
|
= or ==
|
==
|
<> or != or
≠
|
!=
|
<
|
<
|
>
|
>
|
<=
|
<=
|
>=
|
>=
|
@ (prefix) or ^
(postfix)
|
* (prefix)
|
.B
|
not supported [2]
|
.W
|
not supported [2]
|
.L
|
not supported [2]
|
Notes:
- MacsBug makes no distinction between bitwise and
logical operators (AND and & are identical). GDB
supports both types, with the standard C syntax.
- You can achieve the same effect with a cast, as shown
in Listing 5.
Flow Control
GDB has much the same flow control facilities as MacsBug.
Note that the commands with a trailing 'i' (the 'i' stands
for instruction) work at the assembly level and step one
instruction at a time. You must use these variants when
debugging without source. When stepping one instruction at a
time, you might want to use the display command
(as shown in Listing 6) to display the current instruction
after each command. Alternatively, activate the MacsBug
GDB plug-in.
Listing 6. Using
display when stepping through
instructions
Breakpoint 1, 0x70268f8c in HLock ()
(gdb) display/i $pc
2: x/i $pc 0x70268f8c <HLock>: mflr r0
(gdb) si
0x70268f90 in HLock ()
2: x/i $pc 0x70268f90 <HLock+4>: stw r0,8(r1)
(gdb) si
0x70268f94 in HLock ()
2: x/i $pc 0x70268f94 <HLock+8>: stwu r1,-64(r1)
(gdb)
|
|
MacsBug
|
GDB
|
G or command-G
|
continue or c
|
G <addr>
|
jump *<addr> [1]
|
GG
|
delete [2]
continue
|
GTP <addr>
|
tbreak *<addr>
continue
|
S or command-S
|
step or s
|
|
stepi or si
[3] [4]
|
SO or T or
command-T
|
next or n
|
|
nexti or ni
[3] [4]
|
MRP
|
finish [5]
|
|
tbreak *$lr
continue [6]
|
Notes:
- Because GDB's primary
focus is source-level debugging, the arguments to
commands like
jump are normally a function
name, or a line number within a source file. If you want
to use an address, you have to prefix it with '*'. This
convention is used by a number of commands.
delete removes all breakpoints. You have
to confirm this action unless you turn off confirmation
using set confirm off .
- Commands ending with 'i' operate one instruction at a
time.
- You should use
si for stepping through
normal instructions. Only use ni for
instructions that specifically call a function (typically
the bl instruction). Using ni
for other instructions can have weird side effects; for
example, if the current PC is at a mflr
instruction, ni mistakenly works like a
magic return [2787251].
finish works properly only if you have
source for the current routine.
- This assumes that the return address is still in
LR.
Breakpoints, A-Trap Breaks, and TVector Breaks
GDB has comprehensive support for breakpoint. In addition
to the MacsBug equivalent commands listed below, you also
have the fb (future break) command which allows
you to set a breakpoint on a symbol that isn't yet loaded.
The breakpoint will be set when the code containing the
symbol is loaded. This is a very useful technique for
setting a breakpoint in a dynamically loaded plug-in.
You can create thread-specific breakpoints. These
breakpoints only trigger if a particular thread executes the
specified code. See the section on threads
later in this technote for an example of how to do this.
Another cool GDB feature is that you can enable and
disable breakpoints without actually deleting them (using
the enable and disable commands).
This lets you run your program for a time without disrupting
your breakpoint state.
A final cool feature of GDB breakpoints is the ability to
save breakpoints to a file (using the
save-breakpoints command) and restore them
later (using the source command). This makes it
easy to retain your breakpoint state between GDB
sessions.
GDB has no support for A-traps because there's no trap
dispatcher in Mac OS X. Furthermore, there's no support for
TVector breaks because TVectors are a CFM construct and GDB
knows very little about CFM. This isn't a problem though:
you can use normal breakpoints instead of A-trap and TVector
breaks.
MacsBug
|
GDB
|
BRP <function>
|
b <function> [1]
|
BRP <addr>
|
b *<addr> [2]
|
BRP <addr>
<condition>
|
b *<addr> if
<condition> [3]
|
BRP <addr> <count>
|
b *<addr>
ignore $bpnum <count> [4]
|
BRP <addr> ;
<statement>
|
b *<addr>
command $bpnum
> <statement>
> end
|
BRC <addr>
|
clear *<addr> [5]
|
BRC
|
delete
|
BRD
|
info break
|
Notes:
- If you break at the beginning of a function you can
use
tbreak $lr followed by c to
step out of the function.
- As described in the previous
section, if you want to set a breakpoint on a
specific address, you have to use the "*" syntax. For
example, to break on the address $12345678, you use the
command
b *0x12345678 .
- You can use the
condition command to
change the condition associated with a breakpoint at any
time.
$bpnum is a convenience variable that
returns the number of the last breakpoint created.
Convenience variables have no direct analogue in MacsBug.
They are explained in detail in the GDB
manual. Convenience variables can be helpful in
places where you'd use MacsBug's playmem .
- It's normally easier to clear a breakpoint by number
(using
delete <x> , where
<x> is the breakpoint number) than by
address. If you can't remember the breakpoint number, the
info break command prints it for each
breakpoint.
Watch Points
MacsBug
|
GDB
|
WP
|
not supported [1]
|
WPC
|
not supported [1]
|
WPD
|
not supported [1]
|
Notes:
- GDB does support watchpoints (use
apropos
watch to learn about the commands) but this
support does not currently work on Mac OS X
[2762873]. Then again, my experience with
watchpoints under MacsBug is that they were kind of flaky.
Until GDB's watchpoint support is fixed you have to find
random memory corruption the hard way (by applying
conditional breakpoints to get a rough idea of who's
responsible and then stepping through code).
Disassembly
For a MacsBug user, GDB's disassemble
command is hard to get used to: if you only supply one
argument, it disassembles the entire function containing the
address specified by the argument. To disassemble something
smaller than a function you have to use two arguments to
specify a range of addresses. Alternatively, if you just
want to disassemble a few instructions, the examine
(x ) command with the instruction format
modifier (/i ) is easier.
MacsBug
|
GDB
|
ILP <addr>
|
x/8i <addr>
|
ILP <addr> <len>
|
disas <addr>
<addr>+<len>
|
IPP PC
|
disas $pc-20 $pc+20
|
|
x/20i $pc-40
|
IRP <function>
|
disas <function>
|
DHP <addr>
|
not supported; you can use playmem
to work around this
|
Emulator
What emulator?
Heaps
There is no direct support for debugging heap zones in
GDB. However, you can call heap checking functions in
various memory management libraries. For example, Listing 7
shows how to call some of the debug functions in the System
framework memory library.
Listing 7. Using call
for heap debugging
Prints the list of heap zones, ala MacsBug "HZ".
(gdb) call (void) malloc_zone_print(0, 0)
Scalable zone 0x5010: inUse=10056(2815KB) small=10027(1227KB) [...]
5 regions:
Region [0x5000-0x45000, 256KB] In_use=2472
Region [0x1387000-0x13c7000, 256KB] In_use=4110
Region [0x13f8000-0x1438000, 256KB] In_use=1647
Region [0x154a000-0x158a000, 256KB] In_use=1064
Region [0x16a3000-0x16e3000, 256KB] In_use=734
Free in last zone 26898
Scalable zone 0xf4f010: inUse=645(85KB) small=645(85KB) [...]
1 regions:
Region [0xf4f000-0xf8f000, 256KB] In_use=645
Free in last zone 158402
Prints a detailed dump of each zone, ala MacsBug "HD". If you
just want to dump one zone, supply it at the first parameter.
(gdb) call (void) malloc_zone_print(0, 1)
Scalable zone 0x5010: inUse=10056(2815KB) small=10027(1227KB) [...]
5 regions:
Region [0x5000-0x45000, 256KB] In_use=2472
Sizes in use: 16[282] 32[1136] 48[344] 64[183] 80[130] [...]
Region [0x1387000-0x13c7000, 256KB] In_use=4110
Sizes in use: 16[360] 32[2041] 48[1088] 64[172] 80[228] [...]
Region [0x13f8000-0x1438000, 256KB] In_use=1647
Sizes in use: 16[316] 32[590] 48[211] 64[108] 80[75] [...]
Region [0x154a000-0x158a000, 256KB] In_use=1064
Sizes in use: 16[281] 32[330] 48[127] 64[65] 80[51] [...]
Region [0x16a3000-0x16e3000, 256KB] In_use=734
Sizes in use: 16[46] 32[225] 48[72] 64[26] 80[17] [...]
Free Sizes: >=1024[5] 592[1] 576[1] 400[1] 32[2] 16[8]
Free in last zone 26898
Scalable zone 0xf4f010: inUse=645(85KB) small=645(85KB) [...]
1 regions:
Region [0xf4f000-0xf8f000, 256KB] In_use=645
Sizes in use: 16[1] 32[373] 48[122] 64[47] 80[34] [...]
Free Sizes: >=1024[3] 768[1] 160[1] 128[1] 80[4] 64[2] 16[23]
Free in last zone 158402
Determines which zone a pointer is in.
(gdb) call (void) malloc_zone_print_ptr_info(0x16a3000)
ptr 0x16a3000 in registered zone 0x5010
Checks all heap zones (or a specific zone if you supply
it as a parameter), ala MacsBug "HC ALL".
(gdb) call (int) malloc_zone_check(0)
$18 = 1
Deliberately destroy our heap.
(gdb) call (void) memset(0x16a3000, 0, 256)
Recheck the heap. Houston control, we have a problem.
(gdb) call (int) malloc_zone_check(0)
*** malloc[988]: invariant broken at region end: ptr=0x16a3010 [...]
*** malloc[988]: Region 4 incorrect szone_check_all() counter=3
*** malloc[988]: error: Check: region incorrect
$19 = 0
|
|
Note:
Remember that GDB might not be the right tool for
debugging heap problems. Mac OS X has a number of
really cool heap debugging tools that are
independent of GDB. Check out Inside
Mac OS X: Performance for details.
|
Note:
The zones shown in Listing 7 are not Memory Manager
zones but a different concept (with the same name)
supported by the System framework memory library.
Memory Manager zones are not supported under
Carbon.
|
Symbols
GDB, being a source-level debugger, has a gazillion
commands to control how symbols are handled. Type
apropos symbol to see them all. Their
description is beyond the scope of this document. The
GDB manual has all the gory
details.
Stack
MacsBug
|
GDB
|
SC or SC6
|
bt [1] [2]
[3]
|
Notes:
bt is short for
backtrace .
- GDB has no equivalent for SC7 but you rarely need it
because virtually all Mac OS X code is written in a
high-level language and thus
bt works
(almost) always.
- By default
bt crawls the current
thread's stack. There are commands to work with different
threads within your process. For example, you can use
thread apply all bt to get a backtrace of
all threads. See the discussion on threads
later in this document for more details.
Memory
GDB's x (examine) command is the direct
equivalent of MacsBug's DM command.
x works much like print in that
you can append a '/' and then a format specifier ('o' for
octal, 'x' for hex, 'd' for decimal, 'u' for unsigned
decimal, 't' for binary, 'f' for float, 'a' for address, 'c'
for char) and optionally a size qualifier ('b' for 1 byte,
'h' for 2 bytes, 'w' for 4 bytes, 'g' for 8 bytes). The x
command supports two additional format qualifiers that
aren't supported by print ('s' for string, 'i'
for instruction). It also supports a repeat counter. Listing
8 shows some examples of using the x
command.
Listing 8. Some examples of using
the x command.
Stop at SetMenuItemText. $r5 is the itemString parameter.
Breakpoint 1, 0x738087cc in SetMenuItemText ()
Print itemString as a C string. This usually works pretty well.
(gdb) x/s $r5
0xbffff1e8: "\005Close"
Print itemString as a sequence of characters. Note the use of
'c' as a format qualifier and 5 as a repeat count.
(gdb) x/5c $r5+1
0xbffff1e9: 67 'C' 108 'l' 111 'o' 115 's' 101 'e'
Print the first byte of itemString as binary.
(gdb) x/tb $r5
0xbffff1e8: 00000101
And the first half word.
(gdb) x/th $r5
0xbffff1e8: 0000010101000011
And the first word.
(gdb) x/tw $r5
0xbffff1e8: 00000101010000110110110001101111
Note how both the format specifier and size qualifier
are 'sticky'.
(gdb) x/t $r5
0xbffff1e8: 00000101010000110110110001101111
(gdb) x $r5
0xbffff1e8: 00000101010000110110110001101111
Print the first word as hex.
(gdb) x/xw $r5
0xbffff1e8: 0x05436c6f
Hitting return on the blank line repeats the previous command.
Like MacsBug this continues dumping the next memory location.
(gdb)
0xbffff1ec: 0x73650000
You can use the 'i' format specifier to disassemble.
The repeat count is especially useful in this case.
(gdb) x/i $pc
0x738087cc <SetMenuItemText+20>: stw r3,120(r30)
(gdb) x/4i $pc
0x738087cc <SetMenuItemText+20>: stw r3,120(r30)
0x738087d0 <SetMenuItemText+24>: mr r0,r4
0x738087d4 <SetMenuItemText+28>: stw r5,128(r30)
0x738087d8 <SetMenuItemText+32>: sth r0,124(r30)
|
|
MacsBug
|
GDB
|
DM <addr>
|
x/8xb <addr>
|
DMA <addr>
|
x/4c <addr> [1]
|
DM <addr> <type>
|
p {<type>}<addr>
|
DM <addr> pstring
|
x/s <addr> [2]
|
TMP <template>
|
info type <regex>
|
SB <addr> <val>
|
set *((char *) <addr>) =
<val>
|
SW <addr> <val>
|
set *((short *) <addr>) =
<val>
|
SL <addr> <val>
|
set *((int *) <addr>) =
<val>
|
Notes:
- This is actually a pretty lame substitute for DMA.
See the MacsBug GDB plug-in for a user-defined command
that implements this properly.
- This actually prints a C string, but it works pretty
well most of the time for pstrings.
x/8c
<addr> is not a good substitute because it's
hard to read the separate characters as a string.
Probably the best way print a pstring is with a
user-defined command.
Registers
MacsBug
|
GDB
|
<register>=<value>
|
set $<register> =
<value>
|
TD
|
info reg
|
TF or TV
|
info all-reg
|
Macros
GDB supports user-defined commands that are much more
powerful than MacsBug macros. User-defined commands can span
multiple lines, and can include control flow statements like
if and while (see the GDB
manual for details on these). They can't, however, be
used in expressions, which is sometimes inconvenient.
You can use the source command to run a file
containing GDB commands as if you'd typed them in at the
prompt. GDB automatically sources the ".gdbinit" file in
your home directory and the current directory when you start
it. This allows you to define persistent user-defined
commands, much like adding a macro to your MacsBug "Debugger
Prefs" file.
MacsBug
|
GDB
|
MC <name>
<expansion>
|
define <name>
> <expansion>
> end
|
®1 through
®9
|
$arg0 through $arg9 ,
$argc
|
MCC
|
no equivalent
|
MCD <name>
|
help user-defined [1]
|
|
show user
|
|
show user <name>
|
Notes:
- You can provide help for your user-defined commands
using the
document command. This is
especially useful if you need help remember how to use
some complex command you put in your ".gdbinit" file six
months ago. See the GDB manual
for details.
Miscellaneous
MacsBug
|
GDB
|
ES
|
kill
|
WH <addr>
|
p/a <addr>
|
|
info sym <addr>
|
FILL <addr> <num> \
<val>
|
call (void) memset(<addr>,
<val>, <num>) [1]
|
SHOW
|
you can probably achieve the results you need
with display
|
DV
|
show version
|
STOPIF
|
GDB supports real if and
while statements, see the GDB
manual
|
HELP <command>
|
help <command>
|
|
apropos <regex>
|
HELP <template>
|
ptype <type>
|
Notes:
- This technique is not without its perils. See
The Dangers of Calling Functions
for details.
DCMDs
IMPORTANT:
This section makes heavy use of GDB's ability to
call arbitrary functions within a process. Many of
these functions were specifically designed to
support debugging. The list of functions changes
over time. To see the latest list, search for all
functions containing "DebugPrint" or "GDB" (use
info func DebugPrint and info
func GDB ).
|
MacsBug
|
GDB
|
AEDesc
|
call (void)
GDBPrintHelpDebuggingAppleEvents()
[1]
|
Evt
|
call (void)
GDBPrintEventQueue()
|
Zap
|
see discussion of MallocScribble
environment variable in Inside
Mac OS X: Performance
|
Leaks
|
see discussion of MallocDebug application and
leaks command line tool in Inside
Mac OS X: Performance
|
Layers
|
call (void)
DebugPrintAllWindowGroups()
call (void)
DebugPrintWindowGroup(<group>)
[2]
|
THING
|
call (void) GDBComponentList()
|
FRAGS
|
info target
|
|
info cfm
|
|
info dyld
|
|
info sharedlibrary
|
ECHO or PRINTF
|
echo
|
|
output
|
|
print
|
|
printf
|
EBBE
|
not need because $0 through $FFF are not mapped
for normal applications
|
MLIST
|
call (void) DebugPrintMenuList()
call (void) DebugPrintMenu(<menu>)
[3]
call (void) DebugPrintMenuItem(<menu>,
<item>) [3]
|
SCREAM
|
see the discussion on threads
later in this document
|
Notes:
- This prints out information about how to display
Apple event descriptors. The instructions vary depending
on the system version.
- You can determine
<group> by
looking through the list of groups displayed by
DebugPrintAllWindowGroups .
- You can determine
<menu> by
looking through the list of menus displayed by
DebugPrintMenuList .
Other Cool Stuff
MacsBug
|
GDB
|
WINDLIST
|
call (void) DebugPrintWindowList()
call (void)
DebugPrintPlatformWindowList()
|
DM <port> GrafPort
|
call (void)
QDDebugPrintPortInfo(<port>)
call (void)
QDDebugPrintCGSInfo(<port>)
[1]
|
DM <window> WindowRecord
|
call (void)
DebugPrintWindow(<window>)
|
DM <control>^
ControlRecord
|
call (void)
GDBShowControlInfo(<control>)
|
|
call (void)
GDBShowControlHierarchy(<window>)
|
PLAYMEM
|
see below
|
©<func>(<param>...)
|
call (<resulttype>)
<func>(<param>...)
|
Notes:
- You can determine the current port by calling
GetQDGlobalsThePort .
Back to top
Hints and Tips
This section contains miscellaneous hints and tips for
using GDB as an assembly-level debugger.
Seeing stdout and stderr After
Attaching
If you attach GDB to a process
(as opposed to starting the process from within GDB), you
won't be able to see anything that the process prints to
stdout or stderr . Programs
launched by the Finder typically have stdout
and stderr connected to "/dev/console", so the
information they print goes to the console. You can view
this by launching the Console application (in the Utilities
folder), however, it's inconvenient to have to look in a
separate window. Another alternative is to connect the
process's stdout or stderr to the
terminal device for GDB's Terminal window. Listing 9 shows
how to do this.
Listing 9. Connecting
stdout and stderr to
GDB's terminal device.
(gdb) attach 795
[... output omitted ...]
(gdb) call (void) DebugPrintMenuList()
No output )-:
Close the stdout and stderr file descriptors.
(gdb) call (void) close(1)
(gdb) call (void) close(2)
Determine the name of the terminal device for GDB itself.
(gdb) shell tty
/dev/ttyp1
Reopen stdout and stderr, but connected to GDB's terminal.
The function results should be 1 and 2; if not, something
is horribly wrong.
(gdb) call (int) open("/dev/ttyp1", 2, 0)
$1 = 1
(gdb) call (int) open("/dev/ttyp1", 2, 0)
$2 = 2
Try the DebugPrintMenuList again.
(gdb) call (void) DebugPrintMenuList()
Yay output!
Index MenuRef ID Title
----- ---------- ---- -----
<regular menus>
00001 0x767725D3 -21629 Ed
00002 0x76772627 1128 <Apple>
00003 0x767726CF 1129 File
00004 0x76772567 1130 Edit
[... remaining output omitted ...]
|
|
Remote Debugging
One of the nice features of GDB is that you can debug
your application across any TCP/IP network (including the
public Internet). You do this by logging into the remote
machine, running GDB on that machine, and then attaching to
the application you want to debug. This has two key
advantages.
- You can debug programs at remote sites; it no longer
matters if you can't reproduce the problem in your
office!
- You can debug things that are disrupted by the
debugger's user interface. Two interesting examples of
this are application suspend/resuming handling and Drag
Manager callbacks.
Remote debugging has some caveats of which you should be
aware. Firstly, you must enable remote login on the remote
machine. The easiest way to do this is to check the Allow
Remote Login checkbox in the Sharing panel of System
Preferences. You can then log in to that machine by
launching Terminal on your local machine and using the
ssh command line program to log into the remote
machine. Obviously you have to have the user name and
password of an account on the remote machine. Once you have
logged into the remote machine you can use GDB in much the
same way as you would normally. Listing 10 shows an example
of this.
Note:
Mac OS X 10.0 did not support ssh ; you
must use telnet instead. For more
information on these commands, type man
ssh and man telnet at the
command line.
|
Listing 10. Using ssh
to log in to a remote machine.
[localhost:~] quinn% ssh puppy.apple.com
quinn@puppy.apple.com's password:
Welcome to Darwin!
puppy> gdb
GNU gdb 5.0-20001113 (Apple version gdb-200) (Mon Sep 3 02:43:52 GMT 2001) (UI_OUT)
Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "powerpc-apple-macos10".
(gdb)
|
|
Don't run a GUI application from within GDB when logged
in remotely. A GUI application needs to connect to system
services which aren't available if you run it from a remote
login session. If you need to debug a GUI application, have
the remote user launch the application from the Finder, then
start GDB in your ssh session and attach
to the running application.
DebugStr
Mac OS X supports the standard DebugStr and
Debugger system calls. However, their default
behavior is to just print a message to stderr
and continue. You must take special steps if you want these
calls to stop program execution. Specifically, these
routines only stop in the debugger if you have set the
USERBREAK environment variable to 1. You can
set this environment variable in a number of ways.
- Command line -- Type the following shell command
before running GDB:
setenv USERBREAK 1 . You
must run your application
from within GDB for this to be effective.
- Within GDB -- Enter the following GDB command before
running your application:
set env USERBREAK
1 . You must run your
application from within GDB for this to be
effective.
- Programmatically -- You can set the
USERBREAK environment variable from within
your own program using the following C statement:
setenv("USERBREAK", "1", true) . You must do
this before any other part of your program calls
DebugStr or Debugger . Also,
this only works if you call the standard C library
setenv routine (in System framework); for
MSL CFM applications this means you should call the
Mach-O version of this function via CFBundle.
Finally, the underlying mechanism used to stop your
program in the debugger when you call DebugStr
is the raise system call (part of System
framework). You can explicitly cause your program to stop
regardless of the setting of USERBREAK by
calling raise(SIGINT) .
The Dangers of Calling
Functions
GDB, like MacsBug, lets you call functions within the
application you're debugging. Also like MacsBug, this
technique has its dangers. You should be aware of the
following things.
- The function executes within the context of your
program. If your program has crashed, there's no
guarantee that it will be in a fit state to execute the
function.
- You have to be very careful if you call a function
that has intricate dependencies. For example, if your
program has a mutex that protects a critical data
structure and you break into GDB while that mutex is
held, don't try to call a function that needs to acquire
the mutex.
- The function you call executes in the context of the
current thread (the thread displayed by
info
thread ). Other threads are not blocked while the
function executes. You can block them explicitly using
the thread suspend and thread
resume commands, but this may cause more problems
than it solves.
- You should be especially careful calling functions
that are not preemptive safe. For example, calling
NewWindow would be a mistake because the
call will execute in the context of a preemptive thread
and will access data structures that aren't preemptive
safe.
MacsBug GDB Plug-in
No technote on GDB for MacsBug users would be complete
without mentioning the MacsBug GDB plug-in. This plug-in
extends GDB to support a subset of the MacsBug commands and
a MacsBug-like user interface. The plug-in is installed as
part of the Mac OS X 10.1 developer tools in the
"/usr/libexec/gdb/plugins/MacsBug/" directory. You can learn
more about the plug-in from the documentation in that
directory.
To activate the plug-in, take the following steps.
- Start GDB from the command line.
- Enter the command
source
/usr/libexec/gdb/plugins/MacsBug/gdbinit-MacsBug .
You will find that most MacsBug commands are available
(in addition to the standard GDB commands). Type
help MacsBug for more online help
on these commands.
- To start the user interface, enter the command
mb on . The user interface will display "Not
Running" until you attach to the application you wish to
debug. Use normal GDB commands to attach.
IMPORTANT:
The MacsBug-like user interface requires a terminal
window of at least 44 rows and 80 columns. You may
need to resize your Terminal window for the user
interface to start. This and other terminal
considerations are discussed in the plug-in's
documentation.
|
Note:
If you want to always activate the plug-in, just
add the above commands to the ".gdbinit" file in
your home directory.
|
The plug-in is especially useful when doing assembly
language debugger because it maintains a lot of useful state
on the screen (the current registers, a disassembly of the
instructions around the current PC, and so on).
Playmem
One nice feature of MacsBug is that it allocates a small
region of memory (512 bytes) and sets the symbol
playmem to point to it. You can use this memory
for all sorts of nasty debugging hacks. GDB has no dedicated
playmem , but you can easily create your own, as
shown in Listing 11. These commands create a 512 byte block
of zeroed memory and set the convenience variable
$playmem to point to it.
Listing 11. Creating
playmem .
(gdb) call (void *) calloc(1, 512)
$1 = (void *) 0x1ed11b0
(gdb) set $playmem = $
(gdb) p/a $playmem
$2 = 0x1ed11b0
|
|
Once you have a block of play memory you can use it for a
variety of tricky things. For example, you can use it to
simulate the MacsBug DHP command, as shown in
Listing 11.
Listing 12. Simulating MacsBug's
DHP command using
playmem .
Create a mydhp user-defined command.
(gdb) define mydhp
Type commands for definition of "mydhp".
End with a line saying just "end".
>set *(int *)$playmem = $arg0
>x/i $playmem
>end
Use the newly created command.
(gdb) mydhp 0x4e800020
0x1ed11b0: blr
|
|
This is just one example of the use of convenience
variables. You can do many other cools things with them. To
create a convenience variable, just name it (don't forget to
prefix it with $) on the left of a set expression (as shown
in Listing 11). To see the value of a convenience variable,
just print it. To see all convenience
variables, use the show convenience command.
You can learn more about convenience variables in the
GDB manual.
Threads
GDB provides a number of commands to support debugging
threads. Listing 13 shows an example of their use.
Listing 13. Thread debugging
commands.
Show information about the current thread.
(gdb) info thread
Thread 0x1903 has current state "WAITING"
Thread 0x1903 has a suspend count of 0.
Show a list of all threads.
(gdb) info threads
3 process 665 thread 0x1b03 0x700009a8 in mach_msg_overwrite_trap ()
2 process 665 thread 0x1a03 0x70059d58 in semaphore_wait_signal_trap ()
* 1 process 665 thread 0x1903 0x700009a8 in mach_msg_overwrite_trap ()
Get information about a specific thread.
(gdb) info thread 3
Thread 0x1b03 has current state "WAITING"
Thread 0x1b03 has a suspend count of 1.
Make another thread the current thread
and then backtrace that thread, and then
switch back to the previous thread.
(gdb) thread 3
[Switching to thread 3 (process 665 thread 0x1b03)]
#0 0x700009a8 in mach_msg_overwrite_trap ()
(gdb) bt
#0 0x700009a8 in mach_msg_overwrite_trap ()
#1 0x700058d4 in mach_msg_overwrite ()
#2 0x700279a0 in thread_suspend ()
#3 0x70027934 in _pthread_become_available ()
#4 0x70027658 in pthread_exit ()
#5 0x700150f8 in _pthread_body ()
#6 0x00000000 in ?? ()
(gdb) thread 1
[Switching to thread 1 (process 665 thread 0x1903)]
#0 0x700009a8 in mach_msg_overwrite_trap ()
Apply a command to all threads. In this example,
we "backtrace" every thread.
(gdb) thread apply all bt
Thread 3 (process 665 thread 0x1b03):
#0 0x700009a8 in mach_msg_overwrite_trap ()
#1 0x700058d4 in mach_msg_overwrite ()
#2 0x700279a0 in thread_suspend ()
[... remaining output omitted ...]
Thread 2 (process 665 thread 0x1a03):
#0 0x70059d58 in semaphore_wait_signal_trap ()
#1 0x70016300 in semaphore_wait_signal ()
#2 0x70016168 in _pthread_cond_wait ()
[... remaining output omitted ...]
Thread 1 (process 665 thread 0x1903):
#0 0x700009a8 in mach_msg_overwrite_trap ()
#1 0x70006e34 in mach_msg ()
#2 0x7017ab20 in __CFRunLoopRun ()
[... remaining output omitted ...]
Create a thread-specific breakpoint.
(gdb) break HandToHand thread 1
Breakpoint 1 at 0x7025e844
|
|
Back to top
Summary
GDB is your friend. Learn to love GDB. Soon you will
forget your infatuation with MacsBug.
Back to top
References
Richard Stallman et al, Debugging
with GDB, Free Software Foundation, March 2000
Apple Computer, MacsBug
Reference and Debugging Guide, Addison-Wesley, 1990
Inside
Mac OS X: Performance
projectbuilder-users
Mailing List -- The Apple GDB team hang out on this
mailing list.
Back to top
Downloadables
|
Acrobat version of this Note (120K)
|
Download
|
Back to top
|