| 
 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 drivecommand.  
            
               | >>> 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 MacsBugdrvrcommand 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 atcontains 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-indrvrtemplate,
         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, anddrvrStatusfields. 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
         brcommand.  
            
               | >>> 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 theCntrlParamBlockRectemplate.  
            
               | >>> 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,
 cpbandiopb, which
                  expand toCntrlParamBlockRec(for
                  Control and Status requests) andIOParamBlockRec(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 akDriveIcon(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 csCodefield
         (offset $1A, field size of 2 bytes denoted by ".w") of theCntrlParamBlockRecis 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 theioCRefNumfield. You might be amazed that DTS engineers can instantly
         calculate the offset of the csCodefield in theCntrlParamBlockRecstructure, but in reality we
         cheat and use the MacsBughelpcommand.  
            
               | >>> 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
         fragscommand to dump information about the
         fragment containing the driver. The following example shows
         how to display all information (the-aoption)
         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 thedrvrcommand (in the column
         labeledDCE 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 theDCtlEntrytemplate.  
            
               | >>> 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
         dCtlWindowfield 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 dCtlStoragefield. In the above example, this closure ID is$00000385.
         You can find the corresponding code fragment usingfrags. Whilefragsdoes 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
         1argument tofrags) 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 $00000385is named "^FFE22770" (a unique name generated by the USB
         Manager). Once you know the name, you can usefragsto 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
         DoDriverIOentry point. In this case, the entry
         point is a
         transition
         vector, so you must use thetvbcommand.  
            
               | >>> 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
         DoDriverIOroutine. The prototype for theDoDriverIOentry 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 csCodeset tokDriverGestaltCode(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 helpin 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. |