ADC Home > Reference Library > Technical Q&As > Legacy Documents > Hardware & Drivers >

Legacy Documentclose button

Important: This document is part of the Legacy section of the ADC Reference Library. This information should not be used for new development.

Current information on this Reference Library topic can be found here:

Native Disk Driver Debugging


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.

[Feb 28 2000]


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.