!pr1
ProDOS Snooper.............................Bob Sander-Cederlof

This past week I have been working on a project which involved creating a new device driver for a disk-like device.  In the process of debugging my driver, I had to write a "snooper" program.

By "snooper", I mean a program which will make a list of all calls to the driver, recording the origin of the call and the parameters of the call.

ProDOS keeps a table of the addresses of the device drivers assigned to each slot and drive between $BF10 and $BF2F.  There are two bytes for each slot and drive.  $BF10-1F is for drive 1, and $BF20-2F is for drive 2.  For example, the address of the device driver for slot 6 drive 1 is at $BF1C,1D.  (Normally this address is $D000.)

I have a Sider drive in slot 7.  The device driver address for the Sider is $C753, and is kept at $BF1E,1F and $BF2E,2F.

By patching the device driver address to point to my own code, I can get control whenever ProDOS tries to read or write or whatever.  If I save and restore all the registers, and jump to the REAL device driver after I am finished, ProDOS will never be the wiser.  But I will!

While my program has control, I can capture all the information I am interested in.  Unfortunately I cannot print it out at this time, because if I try to ProDOS will get stuck in a loop.  Instead I will save the data in a buffer so I can look at it later.

The program which follows has three distinct parts.  Lines 1140-1290 are an installation and removal tool.  If the program has just been BLOADed or LOADed and ASMed, running INSTALL.SNOOPER will (you guessed it!) install the snooper.  The actual device driver address for the slot (which you specified in line 1060 before assembling the program) will be saved in my two-byte variable DRIVER.  The previous contents of DRIVER, which is the address of my snoop routine, will be copied into ProDOS's table.  The value of DRIVES, which you specified before assembling the program at line 1070, will determine whether SNOOPER is connected to drive 2 or not.  It will always be connected to drive 1.

If SNOOPER has already been installed, running INSTALL.SNOOPER will reverse the installation process, returning ProDOS to its original state.  INSTALL.SNOOPER also resets the buffer I use to keep the captured information.  To make it easy to run INSTALL.SNOOPER, I put a JMP to it at $300.  After assembly you can type "$300G" to install the snooper, and type the same again to dis-install it.

The JMP at $303 (line 1120) goes to the display program.  After SNOOPER has been installed, all disk accesses on the installed slot will cause information to be accumulated in BUFFER.  Typing "$303G" will cause the contents of BUFFER to be displayed in an easy-to-read format.

I set up SNOOPER to capture eight bytes of information each time it is activated.  You might decide to save more or less.  I save the return address from the stack, to get some idea of which routine inside ProDOS is trying to access the disk.  I also save the six bytes at $42-47, which are the calling parameters for the device driver.  Page 6-8 of Beneath Apple ProDOS describes these parameters; you can also find out about them in Apple's ProDOS Technical Reference Manual and in Gary Little's "Apple ProDOS--Advanced Features".

$42 contains the command code:  00=status, 01=read, 02=write, and 03=format.  $43 contains the unit number, in the format DSSS0000 (where SSS=slot and D=0 for drive 1, D=1 for drive 2).  $44-45 contain the address of the memory buffer, lo-byte first; the buffer is 512 bytes long.  $46-47 contain the block number to be read or written.

My DISPLAY program displays each group of eight bytes on a separate line, in the following format:

     hhll:cc.uu.buff.blok

where hhll is the return address from the stack, hi-byte first; cc is the command code; uu is the unit number; buff is the buffer address, hi-byte first; blok is the block number, hi-byte first.

If you get into figuring out more of what ProDOS is doing, you might want to save more information from the stack.  You can look behind the immediate return address to get more return addresses and other data which have been saved on the stack before calling the device driver.

A word of explanation about lines 1040, 1360, 1370, 1490, and 1500.  Line 1040 tells the S-C Macro Assembler that it is OK to assemble opcodes legal in the 65C02.  The PHX, PHY, PLX and PLY opcodes are in the 65C02, 65802, and 65816; however, they are not in the 6502.  If you have only the 6502 in your Apple, you will need to substitute the longer code shown in the comments.  Leave out line 1040, and use the following:

       1360    TYA
       1365    PHA
       1370    TXA
       1375    PHA
       .
       .
       .
       1490    PLA
       1495    TAX
       1500    PLA
       1505    TAY

In the process of "snooping" I was able to debug my new device drivers for the project I was developing.  I also discovered what appear to be some gross in-efficiencies in ProDOS.  In the course of even simple CATALOGs, LOADs, and SAVEs the same blocks are read into the same buffers over and over, at times when it would appear to be totally unnecessary.  If there was some mechanism inside MLI to keep track of the fact that a complete un-spoiled copy of a particular block was already in RAM, it could save a lot of time.  On the other hand, it could be that the current approach is safer.  I think it is a potentially fruitful area for further investigation.  Any takers?
!np
DOS 3.3 RWTS Snooper.......................Bob Sander-Cederlof

Of course if I want to look around in ProDOS the same curiosity certainly applies to DOS 3.3.  The fact of the matter is, I started snooping in DOS first; nevertheless, the ProDOS article took precedence in these pages.

There are several nice places to patch a snooper into DOS 3.3.  One is right at the beginning of RWTS, $BD00.  This position is usually taken by hard disks, however.  For example, Sider and Corvus use $BD00.  I could skip down below $BD00, but Sider for one expects several bytes after $BD00 to be normal DOS code.  Looking backward, $BD00 is normally called only from a subroutine which starts at $B7B5.  This subroutine, in turn, is normally only called from $B090.  Your own programs may call RWTS differently, but DOS itself almost always goes through $B090.  (The exceptions are the reading and writing of the DOS image during boot or INITialization.)

Therefore...I patched my SNOOPER program in at $B090.  The INSTALL.SNOOPER code in lines 1060-1160 is very similar to that in the ProDOS snooper.  It swaps the address currently in my variable DRIVER with the address at $B091,2.  Typing "$800G" will install SNOOPER, and typing it again will dis-install SNOOPER.

The DOS snooper prints out each line of information as it goes along, without storing the data.  Each line contains the two most recent return address from the stack, so you can trace who is calling RWTS.  I also print out the RWTS command, the track and sector, and the buffer address.

Here is an example of the printout, in this case during a SAVE operation:

!lm+5
:LOAD S.RWTS.SNOOPER
:ASM                     Assembler SNOOPER

0000 ERRORS IN ASSEMBLY
:$800G                   install SNOOPER
:SAVE S.RWTS.SNOOPER     sample DOS command
AB24.AD45.01.11.00.B3BB  read VTOC
AB45.B1E6.01.11.0F.B4BB  read Catalog sector
A6AA.AB24.01.1F.0F.9700  T/S list
C3E9.ACDD.01.1F.0E.9600  read 1st data sector
ACDD.B0C8.02.1F.0E.9600  write 1st data sector
D349.ACDD.01.1F.0D.9600  read 2nd data sector
ACDD.B0C8.02.1F.0D.9600  write 2nd data sector
D328.ACDD.01.1F.0C.9600  read 3rd data sector
ACDD.B0C8.02.1F.0C.9600  write 3rd data sector
D352.ACDD.01.1F.0B.9600  read 4th data sector
ACDD.B0C8.02.1F.0B.9600  write 4th data sector
A2F8.A6AA.01.11.00.B3BB  read VTOC
A6AA.AC1E.01.11.0F.B4BB  read catalog sector
A2F8.A6AA.02.11.0F.B4BB  write catalog sector
AD1A.AB45.01.11.00.B3BB  read VTOC
AB45.B1E6.01.11.0F.B4BB  read catalog sector
A6AA.AD1A.01.1F.0F.9700  read T/S list
A6AA.AD1D.01.1F.0E.9600  read 4 data sectors
A6AA.AD1D.01.1F.0D.9600    to VERIFY the file
A6AA.AD1D.01.1F.0C.9600
A6AA.AD1D.01.1F.0B.9600
:$800G                   dis-install SNOOPER
!lm-5
