Q: I'm trying to debug a native disk driver
('ndrv' ) and I want to set a MacsBug breakpoint
on the driver's main entry point (DoDriverIO ).
For 68K disk drivers ('DRVR' s) this was easy,
but native drivers all export the same symbol
(DoDriverIO ) so it's hard to tell what's what.
How do I find my driver's main entry point?
A: This can be done, but it requires some new tricks. For
the benefit of those with limited 'DRVR'
experience, I'll start by going over the technique used for
68K drivers. I'll then show how you can extend the technique
for native drivers.
Note:
This technique is most useful in situations where
you are debugging without source code, or where
recompiling the source to include the appropriate
DebugStr 's is not practical. If you
have the source and can easily load a new version
of your driver to include the required debugger
breaks, there's no point messing around in MacsBug. |
Note:
You can apply this technique to other types of
drivers (for example, serial drivers); the only
requirement is that the driver appears in the unit
table.
|
Note:
These examples were generated using MacsBug 6.6 on
Mac OS 9.
|
Finding a 68K Driver's Entry Points
To start off, you need to find your driver's reference
number. For disk drivers, this is a simple application of
the MacsBug drive command.
>>> drive
Displaying Drive Queue
Drive Volume Flags dRef Driver Name FSID Size QElem at
0009 The Count leiS FFCA .ATADisk 0000 013044C6 001D2896
0008 <none> lEIS FFCC .AppleUSBMassStorage 0000 0003C300 00553F4E
0001 <none> lEiD FFCC .AppleUSBMassStorage 0000 0000FFFF 00553F9A
|
The column labeled dRef (in all listings in
this Q&A, the interesting data is highlighted in red) yields the driver
reference number. In this example, we'll look at the main
hard disk, whose driver is ".ATADisk" and driver reference
number is $FFCA . You can pass this driver reference number
to the MacsBug drvr command to get more
information about the driver.
>>> drvr ffca
Displaying Driver Control Entries
dRef dNum Driver Flg Ver qHead Stor/Ver Dely Drvr at DCE at
FFCA 0035 .ATADisk bPO #0 00000000 001D285C 0065 001D650E 0003F3B0 |
For a 68K driver, the column labeled Drvr at
contains the address of the beginning of the driver header.
In this example, the address is $001D650E . You can dump the
driver header using the built-in drvr template,
which produces the nicely formatted output shown below.
>>> dm 1d650e drvr
Displaying DRVR at 001D650E
001D650E drvrFlags 6F00
001D6510 drvrDelay 0065
001D6512 drvrEMask 0000
001D6514 drvrMenu 0000
001D6516 drvrOpen 0020
001D6518 drvrPrime 00A2
001D651A drvrCtl 0044
001D651C drvrStatus 0054
001D651E drvrClose 0034
001D6520 drvrName ".ATADisk"
|
The import fields are the drvrOpen ,
drvrPrime , drvrCtl , and
drvrStatus fields. Each contains the offset (in
bytes) from the beginning of the driver header to the
corresponding entry point. You can use the offset to
calculate the address of the entry point. For example, to
disassemble the beginning of the driver's Control entry
point, you might do the following.
>>> il 1d650e+44
Disassembling from 1d650e+44
No procedure name
001D6552 MOVE.L A5,-(A7)
001D6554 MOVEA.L $0014(A1),A5
001D6558 MOVE.L A1,-(A7)
001D655A MOVE.L A0,-(A7)
001D655C JSR *+$22A8
001D6560 BRA.S *+$005E
|
To set a breakpoint on that entry point, use the MacsBug
br command.
>>> br 1d650e+44
Break at 001D6552 every time
|
This will break every time the driver's Control entry
point is called. Register A0 contains the address of the
parameter block (in the case of the Control entry point,
this will be a CntrlParamBlockRec ) passed to
your driver. You can dump this using the
CntrlParamBlockRec template.
>>> dm a0 cpb
Displaying CntrlParamBlockRec at 03D1FC4C
03D1FC4C qLink NIL
03D1FC50 qType 0002 = ioQType
03D1FC52 ioTrap A004 _Control
03D1FC54 ioCmdAddr 00010001 ->
03D1FC58 ioCompletion NIL
03D1FC5C ioResult 0001
03D1FC5E ioNamePtr NIL
03D1FC62 ioVRefNum 0009 = The Count
03D1FC64 ioCRefNum FFCA
03D1FC66 csCode 0015
03D1FC68 csParam 0000 0001 0000 0009 03D1 FD10 03D1 FCA0...
|
Note:
MacsBug includes two built-in macros,
cpb and iopb , which
expand to CntrlParamBlockRec (for
Control and Status requests) and
IOParamBlockRec (for Prime requests)
respectively.
|
This template nicely displays:
ioTrap -- This field encodes both the
type of the request (Control, Status, and so on) and the
mode of execution (synchronous, asynchronous, immediate).
Modern versions of MacsBug show the appropriate symbolic
name (in this case, _Control ).
ioVRefNum -- For a Control request to a
disk driver, this typically contains the drive number of
the drive targeted by the request.
ioCRefNum -- The driver reference number
of the target driver. We know this will be our driver
reference number (established above) because otherwise
the Device Manager wouldn't have called the entry point.
csCode -- The type of request, in this
case a kDriveIcon (15) request. For a full
list of request codes for disk drivers, see the MoreDisks
module of the DTS sample
MoreIsBetter.
csParam -- Many requests contain
request-specific information in this field.
If you want to watch for a specific request to your
driver, you can use a MacsBug conditional breakpoint. For
example, if you want to watch for
kRegisterPartition (50) requests, you might use
the following command.
>>> br 1d650e+44 (a0+1a)^.w=#50.w
Break at 001D6522 when (a0+1a)^.w=#50.w
|
This command tells MacsBug to break at the driver's
Control entry point where the csCode field
(offset $1A, field size of 2 bytes denoted by ".w") of the
CntrlParamBlockRec is 50 (# denotes decimal in
MacsBug). The ".w" notation on the constant is not strictly
necessary in this case, but I use it habitually because it
avoids a common sign extension "gotcha" when breaking on the
ioCRefNum field.
You might be amazed that DTS engineers can instantly
calculate the offset of the csCode field in the
CntrlParamBlockRec structure, but in reality we
cheat and use the MacsBug help command.
>>> help CntrlParamBlockRec
CNTRLPARAMBLOCKREC is a template (size = $0032 bytes):
+0000 qLink ^CntrlParamBlockRec
+0004 qType E2_QType
+0006 ioTrap IOTrapWord
+0008 ioCmdAddr Pointer
+000C ioCompletion Pointer
+0010 ioResult Word
+0012 ioNamePtr ^pString
+0016 ioVRefNum VRefNum
+0018 ioCRefNum Word
+001A csCode Word
+001C csParam Word 000B
|
The output shows all the fields in the template along
with their offsets. Very handy!
Finding a Native Driver's Entry Point
The following example shows what happens when you try to
use the above technique for a native disk driver, in this
case the ".AppleUSBMassStorage" unit table (UT) shim driver.
>>> drive
Displaying Drive Queue
Drive Volume Flags dRef Driver Name FSID Size QElem at
0009 The Count leiS FFCA .ATADisk 0000 013044C6 001D2896
0008 <none> lEIS FFCC .AppleUSBMassStorage 0000 0003C300 00553F4E
0001 <none> lEiD FFCC .AppleUSBMassStorage 0000 0000FFFF 00553F9A
>>> drvr ffcc
Displaying Driver Control Entries
dRef dNum Driver Flg Ver qHead Stor/Ver Dely Drvr at DCE at
FFCC 0033 .AppleUSBMass... bPO #0 00000000 02.02f00 0000 00196778 00196740
>>> dm 196778 drvr
Displaying DRVR at 00196778
00196778 drvrFlags 0000
0019677A drvrDelay 0000
0019677C drvrEMask 0000
0019677E drvrMenu 0000
00196780 drvrOpen 0000
00196782 drvrPrime 0000
00196784 drvrCtl 0000
00196786 drvrStatus 0000
00196788 drvrClose 0000
0019678A drvrName ".AppleUSBMassStorage"
|
Notice how the entry point offsets in the driver header
are all zero -- the old 68K driver technique is obviously
not going to work here! Instead, we must use the MacsBug
frags command to dump information about the
fragment containing the driver. The following example shows
how to display all information (the -a option)
about a driver whose fragment name is
"MyDriverFragmentName".
>>> frags -a "MyDriverFragmentName"
CFM Info for fragments in all contexts.
Finder heap zone @ 03AFE920, contextID = 01C30001
<System Context> heap zone @ 00002800, contextID = 00000001
^FFE22770 connID = 00000386 (closureID = 00000385)
pef @ FFE22770, flags = 00, symbols #2
sect #0: @ FFE227F0-FFE2B244 exec, use = #1, length = #35412
sect #1: @ 0054FEC0-00555708 data, use = #1, length = #22600
DoDriverIO tvec 005500E8
TheDriverDescription data 00553A30
Import Libraries:
DriverLoaderLib vers=00000000 oldest=00000000
DriverServicesLib vers=00000000 oldest=00000000
InterfaceLib vers=00000000 oldest=00000000
NameRegistryLib vers=00000000 oldest=00000000
|
This assumes that your driver has a well known fragment
name, which you supply as a parameter to frags .
However, in many cases, such as the USB driver in this
example, the driver's fragment name is not well known. To
find the fragment you must use dump out the driver's device
control entry (DCE). You can find the address of the DCE in
the output of the drvr command (in the column
labeled DCE at ).
>>> drvr ffcc
Displaying Driver Control Entries
dRef dNum Driver Flg Ver qHead Stor/Ver Dely Drvr at DCE at
FFCC 0033 .AppleUSBMass... bPO #0 00000000 02.02f00 0000 00196778 00196740
|
In this case, the DCE is at address $00196740 . You can
dump the DCE using the DCtlEntry template.
>>> dm 196740 dctlentry
Displaying DCtlEntry at 00196740
00196740 dCtlDriver 00196778
00196744 dCtlFlags #7980
00196746 dCtlQHdr
00196746 qFlags #0
00196748 qHead 00000000
0019674C qTail 00000000
00196750 dCtlPosition #1024
00196754 dCtlStorage 00000385
00196758 dCtlRefNum #-52
0019675A dCtlCurTicks #1664992
0019675E dCtlWindow 001967B0
00196762 dCtlDelay #0
00196764 dCtlEMask #0
00196766 dCtlMenu #0
|
For those of you familiar with 68K drivers, this DCE will
look somewhat strange. For example, the
dCtlWindow field is not nil, which is fine for
a desk accessory but very weird for a disk driver. These
inconsistencies exist because this is a native driver's DCE.
For native drivers, the Device Manager reuses certain DCE
fields to store its internal state.
IMPORTANT:
The format of the DCE for native drivers is
officially undocumented and may change in future
system releases. While this Q&A discusses some
details of this, this discussion is for debugging
purposes only. Do not put dependencies on the
format of a native driver's DCE in your code.
|
One item stored by the Device Manager in the native
driver's DCE is the CFM closure ID for the driver's code
fragment, which is stored in the dCtlStorage
field. In the above example, this closure ID is $00000385 .
You can find the corresponding code fragment using
frags . While frags does not allow
you to look up a fragment by closure ID, you can display all
of the fragments and then search the output for the fragment
with the correct ID. You can make your life easier by only
dumping the fragments in the system context (the -c
1 argument to frags ) and by noticing
that the displayed connection and closure IDs are typically
sorted by ID number.
>>> frags -c 1
CFM Info for fragments in all contexts.
<System Context> heap zone @ 00002800, contextID = 00000001
...
OTKernelUtilLib connID = 000003D4 (closureID = 000003D3)
OTKernelContextLib connID = 000003D5
OTNtvUtilLib connID = 000003CF (closureID = 00000424)
OTUtilityLib connID = 000003D0
OTStackSwapLib connID = 000003CD (closureID = 000003CC)
OTGlobalLib connID = 000003C8 (closureID = 000003CA)
RootLibrary connID = 000003C4 (closureID = 000003C3)
SLMGlobal connID = 000003BE (closureID = 000003BD)
USBHIDKeyboardModule connID = 00000390 (closureID = 0000038F)
^FFE22770 connID = 00000386 (closureID = 00000385)
USBImationSuperDiskClass connID = 0000036E (closureID = 0000036D)
USB Software Locator connID = 00000331 (closureID = 00000330)
USBHIDAppleMouseModule connID = 00000307 (closureID = 00000306)
CursorDevicesLib connID = 00000308
...
|
In this case, the fragment with a closure ID of $00000385
is named "^FFE22770" (a unique name generated by the USB
Manager). Once you know the name, you can use
frags to display information about the
fragment.
>>> frags -a "^FFE22770"
CFM Info for fragments in all contexts.
Finder heap zone @ 03AFE920, contextID = 01C30001
<System Context> heap zone @ 00002800, contextID = 00000001
^FFE22770 connID = 00000386 (closureID = 00000385)
pef @ FFE22770, flags = 00, symbols #2
sect #0: @ FFE227F0-FFE2B244 exec, use = #1, length = #35412
sect #1: @ 0054FEC0-00555708 data, use = #1, length = #22600
DoDriverIO tvec 005500E8
TheDriverDescription data 00553A30
Import Libraries:
DriverLoaderLib vers=00000000 oldest=00000000
DriverServicesLib vers=00000000 oldest=00000000
InterfaceLib vers=00000000 oldest=00000000
NameRegistryLib vers=00000000 oldest=00000000
|
You can now set a breakpoint on the
DoDriverIO entry point. In this case, the entry
point is a
transition
vector, so you must use the tvb command.
>>> tvb 5500e8
TVector Break at "DoDriverIO" (TVector at 005500E8) every time
|
Once you stop at the transition vector break, you can
look in the PowerPC registers for the parameters to the
DoDriverIO routine. The prototype for the
DoDriverIO entry point is shown below.
typedef extern OSErr (*DriverEntryPointPtr)(AddressSpaceID SpaceID,
IOCommandID CommandID,
IOCommandContents Contents,
IOCommandCode Code,
IOCommandKind Kind);
|
In PowerPC calling conventions, the first parameter is
place in register R3, the next in R4, and so on. The
contents of the registers are summarized by the following
"cheat sheet."
Register
|
Param Name
|
Description
|
R3
|
SpaceID
|
The target address space, always -1
(kCurrentAddressSpace ) on current
systems.
|
R4
|
CommandID
|
An opaque value allocated by the system.
|
R5
|
Contents
|
For typical requests (Read, Write, Control,
Status) this is basically a
ParmBlkPtr .
|
R6
|
Code
|
The type of request, 0 for Open, 1 for Close, 2
for Read, 3 for Write, 4 for Control, and 5 for
Status. Other request types are described in
"Devices.h".
|
R7
|
Kind
|
The mode of the request, 1 for synchronous, 2
for asynchronous, 3 for immediate.
|
You can use this information to break on a specific set
of requests to the driver. For example, the following breaks
on all Control requests.
>>> tvb 5500e8 r6=4
TVector Break at "DoDriverIO" (TVector at 005500E8) when r6=4 |
You can even look at the content of the parameter block.
For example the following breaks on all Driver Gestalt
requests (a Status request with csCode set to
kDriverGestaltCode (43)).
>>> tvb 5500e8 ((r6=5)&((r5+1a)^.w=#43.w))
TVector Break at "DoDriverIO" (TVector at 005500E8) when ((r6=5)&((r5+1a)^.w=#43.w)) |
References
- The
MacsBug
Reference and Debugging Guide is an excellent
reference for 68K debugging in MacsBug. Unfortunately it
hasn't been updated for PowerPC debugging.
- For PowerPC MacsBug debugging, the best place to
start is the MacsBug built-in help; to get that, simply
type
help in MacsBug.
- To truly understand assembly language debugging, you
need to be familiar with the Mac OS 68K and PowerPC
runtime architectures, both of which as well described in
Mac
OS Runtime Architectures.
- For general information about writing disk drivers,
the best place to start is Technote 1189
The
Monster Disk Driver Technote.
- For a full list of request codes for disk drivers,
see the MoreDisks module of the DTS sample
MoreIsBetter.
|