home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / TSRSRC29.ZIP / MAPMEM.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1993-01-04  |  30.2 KB  |  953 lines

  1. {$R-,S-,I+}
  2.  
  3. {**************************************************************************
  4. *   Maps system memory blocks for MS/PCDOS 2.0 and higher.                *
  5. *   Also maps expanded memory allocation blocks                           *
  6. *   Copyright (c) 1986 Kim Kokkonen, TurboPower Software.                 *
  7. *   Released to the public domain for personal, non-commercial use only.  *
  8. ***************************************************************************
  9. *   version 1.0 1/2/86                                                    *
  10. *   version 1.1 1/10/86                                                   *
  11. *     running under DOS 2.X, where block owner names are unknown          *
  12. *   version 1.2 1/22/86                                                   *
  13. *     a bug in parsing the owner name of the block                        *
  14. *     a quirk in the way that the DOS PRINT buffer installs itself        *
  15. *     minor cosmetic changes                                              *
  16. *   version 1.3 2/6/86                                                    *
  17. *     smarter filtering for processes that deallocate their environment   *
  18. *   version 1.4 2/23/86                                                   *
  19. *     add a map of Expanded memory (EMS) as well                          *
  20. *   version 1.5 2/26/86                                                   *
  21. *     change format of last memory block                                  *
  22. *     change to more reliable scheme of finding first block               *
  23. *       (thanks to Chris Dunford for pointing out a useful                *
  24. *        undocumented DOS function).                                      *
  25. *     support environment lengths up to 32K                               *
  26. *   version 1.6 3/8/86                                                    *
  27. *     support "verbose" output mode                                       *
  28. *       display open file handles                                         *
  29. *       show command line of each block                                   *
  30. *   version 1.7 3/24/86                                                   *
  31. *     work around Turbo 3.00B bug with Delete procedure and length 255    *
  32. *     filter out command lines of programs which relocate over their      *
  33. *       command line at PSP:$80                                           *
  34. *     fix treatment of handle counts from PSP                             *
  35. *     add display of number of memory blocks per PSP to verbose mode      *
  36. *     accept V, -V, or /V for the verbose switch                          *
  37. *   version 1.8 4/20/86                                                   *
  38. *     change verbose mode to show each block individually                 *
  39. *   version 1.9 5/22/86                                                   *
  40. *     synchronize with RELEASE                                            *
  41. *   version 2.0 6/17/86                                                   *
  42. *     synchronize with RELEASE                                            *
  43. *   version 2.1 7/18/86                                                   *
  44. *     wrap long vector lists                                              *
  45. *   version 2.2 3/4/87                                                    *
  46. *     add support for WATCH files                                         *
  47. *   version 2.3 5/1/87                                                    *
  48. *     use in-memory WATCH data                                            *
  49. *     display disabled status of TSRs                                     *
  50. *   version 2.4 5/17/87                                                   *
  51. *     avoid use of EMS call $4B, which doesn't work in many EMS           *
  52. *       implementations                                                   *
  53. *   version 2.5 5/26/87                                                   *
  54. *     correct problem with MAPMEM run in batch file with WATCH            *                                    *
  55. *   version 2.6 1/15/89                                                   *
  56. *     make changes to deal with 386-to-the-Max                            *
  57. *     convert to Turbo Pascal 5.0                                         *
  58. *   version 2.7                                                           *
  59. *     skipped                                                             *
  60. *   version 2.8 3/10/89                                                   *
  61. *     clean up a few Turbo 3 leftovers                                    *
  62. *     add total extended memory report                                    *
  63. *   version 2.9 5/4/89                                                    *
  64. *     fix bug when no EMS blocks allocated                                *
  65. ***************************************************************************
  66. *   telephone: 408-438-8608, CompuServe: 72457,2131.                      *
  67. *   requires Turbo version 5 to compile.                                  *
  68. ***************************************************************************}
  69.  
  70. program MapMem;
  71.   {-look at the system memory map using DOS memory control blocks}
  72. uses
  73.   dos;
  74.  
  75. const
  76.   Version = '2.9';
  77.   MaxBlocks = 100;            {max number of DOS memory blocks checked}
  78.   MaxVector = $FF;            {highest interrupt vector checked for trapping}
  79.  
  80.   WatchID = 'TSR WATCHER';    {marking string for WATCH}
  81.  
  82.   {offsets into resident copy of WATCH.COM for data storage}
  83.   WatchOffset = $81;
  84.   NextChange = $104;
  85.   ChangeVectors = $220;
  86.   OrigVectors = $620;
  87.   CurrVectors = $A20;
  88.  
  89.   ATclass = $FC;              {machine ID bytes}
  90.   Model80 = $F8;
  91.  
  92. type
  93.   Pathname = string[64];
  94.   AllStrings = string[255];
  95.  
  96.   BlockType = 0..MaxBlocks;
  97.   Block =
  98.   record                      {store info about each memory block as it is found}
  99.     idbyte : Byte;
  100.     mcb : Word;
  101.     psp : Word;
  102.     len : Word;
  103.     psplen : Word;
  104.     env : Word;
  105.     cnt : Word;
  106.   end;
  107.   BlockArray = array[BlockType] of Block;
  108.  
  109. var
  110.   Blocks : BlockArray;
  111.   WatchBlock, BlockNum : BlockType;
  112.   WroteHeader, UseHook, Verbose, UseWatch : Boolean;
  113.   MachineId : Byte absolute $FFFF : $000E;
  114.  
  115.   procedure Abort(msg : AllStrings);
  116.     {-halt in case of error}
  117.   begin
  118.     WriteLn(msg);
  119.     Halt(1);
  120.   end;
  121.  
  122.   function StUpcase(s : Pathname) : Pathname;
  123.     {-return the upper case of a string}
  124.   var
  125.     i : Byte;
  126.   begin
  127.     for i := 1 to Length(s) do
  128.       s[i] := UpCase(s[i]);
  129.     StUpcase := s;
  130.   end;
  131.  
  132.   procedure FindTheBlocks;
  133.     {-scan memory for the allocated memory blocks}
  134.   const
  135.     MidBlockID = $4D;         {byte DOS uses to identify part of MCB chain}
  136.     EndBlockID = $5A;         {byte DOS uses to identify last block of MCB chain}
  137.   var
  138.     mcbSeg : Word;         {segment address of current MCB}
  139.     nextSeg : Word;        {computed segment address for the next MCB}
  140.     gotFirst : Boolean;       {true after first MCB is found}
  141.     gotLast : Boolean;        {true after last MCB is found}
  142.     idbyte : Byte;            {byte that DOS uses to identify an MCB}
  143.  
  144.     function GetStartMCB : Word;
  145.       {-return the first MCB segment}
  146.     var
  147.       reg : registers;
  148.     begin
  149.       reg.ah := $52;
  150.       MsDos(reg);
  151.       GetStartMCB := MemW[reg.es:(reg.bx-2)];
  152.     end;
  153.  
  154.     procedure StoreTheBlock(var mcbSeg, nextSeg : Word;
  155.                             var gotFirst, gotLast : Boolean);
  156.       {-store information regarding the memory block}
  157.     var
  158.       nextID : Byte;
  159.       pspAdd : Word;       {segment address of the current PSP}
  160.       mcbLen : Word;       {size of the current memory block in paragraphs}
  161.  
  162.     begin
  163.  
  164.       mcbLen := MemW[mcbSeg:3]; {size of the MCB in paragraphs}
  165.       nextSeg := Succ(mcbSeg+mcbLen); {where the next MCB should be}
  166.       pspAdd := MemW[mcbSeg:1]; {address of program segment prefix for MCB}
  167.       nextID := Mem[nextSeg:0];
  168.  
  169.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  170.         BlockNum := Succ(BlockNum);
  171.         gotFirst := True;
  172.         with Blocks[BlockNum] do begin
  173.           idbyte := Mem[mcbSeg:0];
  174.           mcb := mcbSeg;
  175.           psp := pspAdd;
  176.           env := MemW[pspAdd:$2C];
  177.           len := mcbLen;
  178.           psplen := 0;
  179.           cnt := 1;
  180.         end;
  181.       end;
  182.  
  183.     end;
  184.  
  185.   begin
  186.  
  187.     {initialize}
  188.     mcbSeg := GetStartMCB;
  189.     gotFirst := False;
  190.     gotLast := False;
  191.     BlockNum := 0;
  192.  
  193.     {scan all memory until the last block is found}
  194.     repeat
  195.       idbyte := Mem[mcbSeg:0];
  196.       if idbyte = MidBlockID then begin
  197.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  198.         if gotFirst then mcbSeg := nextSeg else mcbSeg := Succ(mcbSeg);
  199.       end else if gotFirst and (idbyte = EndBlockID) then begin
  200.         gotLast := True;
  201.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  202.       end else
  203.         {start block was invalid}
  204.         Abort('corrupted allocation chain or program error');
  205.     until gotLast;
  206.  
  207.   end;
  208.  
  209.   function FindMark(markName : AllStrings; markOffset : Word) : Word;
  210.     {-find the last memory block matching idstring at offset idoffset}
  211.   var
  212.     b : BlockType;
  213.     MemMark : Boolean;
  214.  
  215.     function HasIDstring(segment : Word;
  216.                          idString : AllStrings;
  217.                          idOffset : Word) : Boolean;
  218.       {-return true if idstring is found at segment:idoffset}
  219.     var
  220.       len : Byte absolute idString;
  221.       tString : AllStrings;
  222.       tlen : Byte absolute tString;
  223.  
  224.     begin
  225.       tlen := len;
  226.       Move(Mem[segment:idOffset], tString[1], len);
  227.       HasIDstring := (tString = idString);
  228.     end;
  229.  
  230.   begin
  231.     {scan from the last block down to find the last MARK TSR}
  232.     b := BlockNum;
  233.     MemMark := False;
  234.     repeat
  235.       if Blocks[b].psp = PrefixSeg then
  236.         {assure this program's command line is not matched}
  237.         b := Pred(b)
  238.       else if HasIDstring(Blocks[b].psp, markName, markOffset) then
  239.         {Mark found}
  240.         MemMark := True
  241.       else
  242.         {Keep looking}
  243.         b := Pred(b);
  244.     until (b < 1) or MemMark;
  245.  
  246.     UseWatch := MemMark;
  247.     FindMark := b;
  248.  
  249.   end;
  250.  
  251.   procedure StripNonAscii(var t : Pathname);
  252.     {-return an empty string if t contains any non-printable characters}
  253.   var
  254.     ipos : Byte;
  255.     goodname : Boolean;
  256.   begin
  257.     goodname := True;
  258.     for ipos := 1 to Length(t) do
  259.       if (t[ipos] <> #0) and ((t[ipos] < ' ') or (t[ipos] > '}')) then
  260.         goodname := False;
  261.     if not(goodname) then t := '';
  262.   end;
  263.  
  264.   function DOSversion : Byte;
  265.     {-return the major version number of DOS}
  266.   var
  267.     reg : registers;
  268.   begin
  269.     reg.ah := $30;
  270.     MsDos(Dos.Registers(reg));
  271.     DOSversion := reg.al;
  272.   end;
  273.  
  274.   procedure ShowTheBlocks;
  275.     {-analyze and display the blocks found}
  276.   const
  277.     hookst : string[14] = 'hooked vectors';
  278.     chainst : string[15] = 'chained vectors';
  279.   type
  280.     HexString = string[4];
  281.     Address = record
  282.                 offset, segment : Word;
  283.               end;
  284.     VectorType = 0..MaxVector;
  285.   var
  286.     st, cline : Pathname;
  287.     b : BlockType;
  288.     StLen, DOSv : Byte;
  289.     CommandPSP, WatchPSP : Word;
  290.     Vectors : array[VectorType] of Address absolute 0 : 0;
  291.     Vtable : array[VectorType] of LongInt;
  292.     SumNum : BlockType;
  293.     Sum : BlockArray;
  294.  
  295.     function HexB(b : Byte) : HexString;
  296.       {-return hex representation of byte}
  297.     const
  298.       hc : array[0..15] of Char = '0123456789ABCDEF';
  299.     begin
  300.       HexB := hc[b shr 4]+hc[b and $F];
  301.     end;
  302.  
  303.     function HexW(i : Word) : HexString;
  304.       {-return hex representation of Word}
  305.     begin
  306.       HexW := HexB(Hi(i))+HexB(Lo(i));
  307.     end;
  308.  
  309.     function Owner(startadd : Word) : Pathname;
  310.       {-return the name of the owner program of an MCB}
  311.     type
  312.       chararray = array[0..32767] of Char;
  313.     var
  314.       e : ^chararray;
  315.       i : Word;
  316.       t : Pathname;
  317.  
  318.       function LongPos(m : Pathname; var s : chararray) : Word;
  319.         {-return the position number of m in s, or 0 if not found}
  320.       var
  321.         mc : Char;
  322.         ss : Pathname;
  323.         i, maxindex : Word;
  324.         found : Boolean;
  325.       begin
  326.         i := 0;
  327.         maxindex := SizeOf(s)-Length(m);
  328.         ss[0] := m[0];
  329.         if Length(m) > 0 then begin
  330.           mc := m[1];
  331.           repeat
  332.             while (s[i] <> mc) and (i <= maxindex) do
  333.               i := Succ(i);
  334.             if s[i] = mc then begin
  335.               Move(s[i], ss[1], Length(m));
  336.               found := (ss = m);
  337.               if not(found) then i := Succ(i);
  338.             end;
  339.           until found or (i > maxindex);
  340.           if not(found) then i := 0;
  341.         end;
  342.         LongPos := i;
  343.       end;
  344.  
  345.       procedure StripPathname(var pname : Pathname);
  346.         {-remove leading drive or path name from the input}
  347.       var
  348.         spos, cpos, rpos : Byte;
  349.       begin
  350.         spos := Pos('\', pname);
  351.         cpos := Pos(':', pname);
  352.         if spos+cpos = 0 then Exit;
  353.         if spos <> 0 then begin
  354.           {find the last slash in the pathname}
  355.           rpos := Length(pname);
  356.           while (rpos > 0) and (pname[rpos] <> '\') do rpos := Pred(rpos);
  357.         end else
  358.           rpos := cpos;
  359.         Delete(pname, 1, rpos);
  360.       end;
  361.  
  362.       procedure StripExtension(var pname : Pathname);
  363.         {-remove the file extension}
  364.       var
  365.         dotpos : Byte;
  366.       begin
  367.         dotpos := Pos('.', pname);
  368.         if dotpos <> 0 then
  369.           Delete(pname, dotpos, 64); {<255 needed for Turbo version 3.00B bug}
  370.       end;
  371.  
  372.     begin
  373.       {point to the environment string}
  374.       e := Ptr(startadd, 0);
  375.  
  376.       {find end of the standard environment}
  377.       i := LongPos(#0#0, e^);
  378.       if i = 0 then begin
  379.         {something's wrong, exit gracefully}
  380.         Owner := '';
  381.         Exit;
  382.       end;
  383.  
  384.       {end of environment found, get the program name that follows it}
  385.       t := '';
  386.       i := i+4;               {skip over #0#0#args}
  387.       repeat
  388.         t := t+e^[i];
  389.         i := Succ(i);
  390.       until (Length(t) > 63) or (e^[i] = #0);
  391.  
  392.       StripNonAscii(t);
  393.       if t = '' then
  394.         Owner := 'N/A'
  395.       else begin
  396.         StripPathname(t);
  397.         StripExtension(t);
  398.         if t = '' then t := 'N/A';
  399.         Owner := StUpcase(t);
  400.       end;
  401.  
  402.     end;
  403.  
  404.     procedure InitVectorTable;
  405.       {-build real equivalent of vector addresses}
  406.     var
  407.       v : VectorType;
  408.  
  409.       function RealAdd(a : Address) : LongInt;
  410.         {-return the real equivalent of an address (pointer)}
  411.       begin
  412.         with a do
  413.           RealAdd := (LongInt(segment) shl 4)+offset;
  414.       end;
  415.  
  416.     begin
  417.       for v := 0 to MaxVector do
  418.         Vtable[v] := RealAdd(Vectors[v]);
  419.     end;
  420.  
  421.     procedure WriteVecs(start, stop, startcol, wrapcol : Word);
  422.       {-Show either trapped or chained interrupt vectors}
  423.  
  424.       procedure WriteHooks(start, stop, startcol, wrapcol : Word);
  425.         {-show the trapped interrupt vectors}
  426.       var
  427.         v : VectorType;
  428.         sadd, eadd : LongInt;
  429.         col : Word;
  430.       begin
  431.         sadd := LongInt(start) shl 4;
  432.         eadd := LongInt(stop) shl 4;
  433.         col := startcol;
  434.         for v := 0 to MaxVector do
  435.           if (Vtable[v] >= sadd) and (Vtable[v] <= eadd) then begin
  436.             if col+3 > wrapcol then begin
  437.               {wrap to next line}
  438.               WriteLn;
  439.               Write('':Pred(startcol));
  440.               col := startcol;
  441.             end;
  442.             Write(HexB(v), ' ');
  443.             col := col+3;
  444.           end;
  445.       end;
  446.  
  447.       procedure WriteChained(pspA, startcol, wrapcol : Word);
  448.         {-Write Chained interrupts as determined from watch data}
  449.       type
  450.         ChangeBlock =
  451.         record                {Store info about each vector takeover}
  452.           VecNum : Byte;
  453.           case ID : Byte of
  454.             0, 1 : (VecOfs, VecSeg : Word);
  455.             2 : (SaveCode : array[1..6] of Byte);
  456.             $FF : (pspAdd : Word);
  457.         end;
  458.         {
  459.         ID is interpreted as follows:
  460.         00 = ChangeBlock holds the new pointer for vector vecnum
  461.         01 = ChangeBlock holds pointer for vecnum but the block is disabled
  462.         02 = ChangeBlock holds the code underneath the vector patch
  463.         FF = ChangeBlock holds the segment of a new PSP
  464.         }
  465.       var
  466.         p : ^ChangeBlock;
  467.         i, maxchg, col : Word;
  468.         found : Boolean;
  469.       begin
  470.         {Initialize}
  471.         maxchg := MemW[WatchPSP:NextChange];
  472.         col := startcol;
  473.         found := False;
  474.         i := 0;
  475.  
  476.         while i < maxchg do begin
  477.           p := Ptr(WatchPSP, ChangeVectors+i);
  478.           with p^ do
  479.             case ID of
  480.               $FF :           {ChangeBlock starts a new PSP}
  481.                 found := (pspA = pspAdd);
  482.               $00 :           {ChangeBlock describes an active vector takeover}
  483.                 if found then begin
  484.                   {ChangeBlock specifies a vector taken over}
  485.                   if col >= wrapcol then begin
  486.                     Write(^M^J, '':Pred(startcol));
  487.                     col := startcol;
  488.                   end;
  489.                   Write(HexB(Lo(VecNum)), ' ');
  490.                   col := col+3;
  491.                 end;
  492.               $01 :           {ChangeBlock specifies a disabled takeover}
  493.                 if found then begin
  494.                   Write('disabled');
  495.                   {Don't write this more than once}
  496.                   Exit;
  497.                 end;
  498.             end;
  499.           i := i+SizeOf(ChangeBlock);
  500.         end;
  501.       end;
  502.  
  503.     begin
  504.       if start <> stop then
  505.         if UseWatch then
  506.           WriteChained(start, startcol, wrapcol)
  507.         else
  508.           WriteHooks(start, stop, startcol, wrapcol);
  509.     end;
  510.  
  511.     procedure SortByPSP(var Blocks : BlockArray; BlockNum : BlockType);
  512.       {-sort in order of ascending PSP}
  513.     var
  514.       i, j : BlockType;
  515.       temp : Block;
  516.     begin
  517.       for i := 1 to Pred(BlockNum) do
  518.         for j := BlockNum downto Succ(i) do
  519.           if Blocks[j].psp < Blocks[Pred(j)].psp then begin
  520.             temp := Blocks[j];
  521.             Blocks[j] := Blocks[Pred(j)];
  522.             Blocks[Pred(j)] := temp;
  523.           end;
  524.     end;
  525.  
  526.     procedure SumTheBlocks(var Blocks : BlockArray;
  527.                            BlockNum : BlockType;
  528.                            var Sum : BlockArray;
  529.                            var SumNum : BlockType);
  530.       {-combine the blocks with equivalent PSPs}
  531.     var
  532.       prevPSP : Word;
  533.       b : BlockType;
  534.     begin
  535.       SumNum := 0;
  536.       prevPSP := 0;
  537.       for b := 1 to BlockNum do begin
  538.         if Blocks[b].psp <> prevPSP then begin
  539.           SumNum := Succ(SumNum);
  540.           Sum[SumNum] := Blocks[b];
  541.           prevPSP := Blocks[b].psp;
  542.           if prevPSP = PrefixSeg then
  543.             {don't include the environment as part of free block's length}
  544.             Sum[SumNum].len := 0;
  545.         end else
  546.           with Sum[SumNum] do begin
  547.             cnt := Succ(cnt);
  548.             len := len+Blocks[b].len;
  549.           end;
  550.         {get length of the block which owns the executable program}
  551.         {for checking vector trapping next}
  552.         if Succ(Blocks[b].mcb) = Blocks[b].psp then
  553.           Sum[SumNum].psplen := Blocks[b].len;
  554.       end;
  555.     end;
  556.  
  557.     procedure TransferTheBlocks(var Blocks : BlockArray;
  558.                                 BlockNum : BlockType;
  559.                                 var Sum : BlockArray;
  560.                                 var SumNum : BlockType);
  561.       {-fill in the Sum array with a little initialization}
  562.     var
  563.       b : BlockType;
  564.     begin
  565.       for b := 1 to BlockNum do begin
  566.         Sum[b] := Blocks[b];
  567.         with Sum[b] do begin
  568.           cnt := 1;
  569.           if (Succ(mcb) = psp) and (psp <> 0) then
  570.             psplen := len
  571.           else
  572.             psplen := 0;
  573.         end;
  574.       end;
  575.       SumNum := BlockNum;
  576.     end;
  577.  
  578.     function OpenHandles(psp : Word) : Word;
  579.       {-return the number of open handles owned by a process}
  580.     var
  581.       h, o : Word;
  582.       b : Byte;
  583.     begin
  584.       h := 0;
  585.       if (psp <> 8) and (cline <> 'N/A') then
  586.         for o := 0 to 19 do begin
  587.           b := Mem[psp:$18+o];
  588.           if not(b in [$FF, 0..2]) then
  589.             h := Succ(h);
  590.         end;
  591.       OpenHandles := h;
  592.     end;
  593.  
  594.     function CommandLine(psp : Word) : Pathname;
  595.       {-return the command line of the PSP}
  596.     var
  597.       t, s : Pathname;
  598.     begin
  599.       if (psp <> 8) then begin
  600.         Move(Mem[psp:$80], t, 65);
  601.         if t[0] > #64 then t[0] := #64;
  602.         s := t;
  603.         StripNonAscii(t);
  604.         if s <> t then
  605.           {command line has been written over}
  606.           t := 'N/A'
  607.         else
  608.           {strip leading blanks}
  609.           while (Length(t) > 0) and (t[1] = #32) do Delete(t, 1, 1);
  610.       end else
  611.         {psp=8 is a special block owned by DOS containing the CONFIG.SYS drivers}
  612.         t := '';
  613.       CommandLine := t;
  614.     end;
  615.  
  616.     function PrevBlock(b : BlockType; psp : Word) : BlockType;
  617.       {-return highest block with number less than b having a PSP matching psp}
  618.       {-return 0 if none}
  619.     var
  620.       t : BlockType;
  621.       found : Boolean;
  622.     begin
  623.       found := False;
  624.       t := Pred(b);
  625.       while (t > 0) and not(found) do begin
  626.         found := (Sum[t].psp = psp);
  627.         if not(found) then t := Pred(t);
  628.       end;
  629.       PrevBlock := t;
  630.     end;
  631.  
  632.     procedure WriteTitle;
  633.     begin
  634.       Write('Allocated Memory Map - by TurboPower Software - Version ', Version);
  635.  
  636.       if Verbose then begin
  637.         WriteLn('  (verbose)');
  638.         WriteLn;
  639.         Write(' PSP  MCB files bytes owner    command line  ');
  640.         if UseWatch then
  641.           WriteLn(chainst)
  642.         else
  643.           WriteLn(hookst);
  644.         WriteLn('---- ---- ----- ----- -------- ------------- -----------------------------');
  645.       end else begin
  646.         WriteLn;
  647.         WriteLn;
  648.         Write(' PSP  blks bytes owner    command line        ');
  649.         if UseWatch then
  650.           WriteLn(chainst)
  651.         else
  652.           WriteLn(hookst);
  653.         WriteLn('----- ---- ----- -------- ------------------- ------------------------------');
  654.       end;
  655.     end;
  656.  
  657.   begin
  658.  
  659.     WriteTitle;
  660.  
  661.     {Get critical PSP addresses before sorting blocks}
  662.     CommandPSP := Blocks[2].psp;
  663.     if UseWatch then
  664.       WatchPSP := Blocks[WatchBlock].psp
  665.     else
  666.       InitVectorTable;
  667.  
  668.     {Rearrange the blocks for presentation}
  669.     if Verbose then
  670.       TransferTheBlocks(Blocks, BlockNum, Sum, SumNum)
  671.     else begin
  672.       SortByPSP(Blocks, BlockNum);
  673.       SumTheBlocks(Blocks, BlockNum, Sum, SumNum);
  674.     end;
  675.  
  676.     {Get DOS version number to see whether environment has program names}
  677.     DOSv := DOSversion;
  678.  
  679.     for b := 1 to SumNum do
  680.     with Sum[b] do begin
  681.  
  682.       {get the command line which invoked the program}
  683.       if b = SumNum then
  684.         cline := ''
  685.       else
  686.         cline := CommandLine(psp);
  687.  
  688.       {write out numerical information}
  689.       Write(HexW(psp), ' ');   {PSP address}
  690.       if Verbose then begin
  691.         Write(HexW(mcb), '  ', {MCB address}
  692.         OpenHandles(psp):2, '  '); {number of open file handles}
  693.       end else
  694.         Write(cnt:3, '  ');   {number of blocks}
  695.  
  696.       Write((LongInt(len) shl 4):6, ' '); {size of block in bytes}
  697.  
  698.       {get the program owning this block by scanning the environment}
  699.       if psp = PrefixSeg then
  700.         st := 'free'
  701.       else if psp = CommandPSP then
  702.         st := 'command'
  703.       else if psp = Sum[1].psp then
  704.         st := 'config'
  705.       else if (DOSv >= 3) then begin
  706.         if Verbose then begin
  707.           if Succ(mcb) = env then
  708.             {this is the environment block}
  709.             st := Owner(env)
  710.           else if PrevBlock(b, psp) <> 0 then
  711.             {this is the block that goes with the environment}
  712.             st := Owner(Sum[PrevBlock(b, psp)].env)
  713.           else
  714.             st := 'N/A';
  715.         end else if cnt > 1 then
  716.           st := Owner(env)
  717.         else
  718.           st := 'N/A';
  719.       end else
  720.         st := 'N/A';
  721.       while Length(st) < 9 do
  722.         st := st+' ';
  723.       Write(st);
  724.  
  725.       {write the command line that invoked the program}
  726.       if Verbose then
  727.         StLen := 13
  728.       else
  729.         StLen := 19;
  730.       if Length(cline) > StLen-3 then
  731.         cline := Copy(cline, 1, StLen-3)+'...'
  732.       else
  733.         while Length(cline) < StLen do cline := cline+' ';
  734.       Write(cline, ' ');
  735.  
  736.       {write the trapped interrupt vectors}
  737.       if Verbose then
  738.         WriteVecs(psp, psp+psplen, 46, 75)
  739.       else if (psp <> PrefixSeg) then
  740.         WriteVecs(psp, psp+psplen, 47, 75);
  741.  
  742.       WriteLn;
  743.     end;
  744.  
  745.   end;
  746.  
  747.   procedure ShowTheEMSblocks;
  748.     {-map out expanded memory, if present}
  749.   const
  750.     EMSinterrupt = $67;       {the vector used by the expanded memory manager}
  751.     MaxHandles = 255;
  752.  
  753.   type
  754.     HandlePageRecord =
  755.     record
  756.       handle : Word;
  757.       numpages : Word;
  758.     end;
  759.  
  760.     PageArray = array[0..MaxHandles] of HandlePageRecord;
  761.     PageArrayPtr = ^PageArray;
  762.     Pathname = string[64];
  763.  
  764.   var
  765.     EMSregs : registers;
  766.     EMShandles : Word;
  767.     Map : PageArrayPtr;
  768.     TotalPages : Word;
  769.  
  770.     function EMSpresent : Boolean;
  771.       {-Return true if EMS memory manager is present}
  772.     var
  773.       f : file;
  774.     begin
  775.       {"file handle" defined by the expanded memory manager at installation}
  776.       Assign(f, 'EMMXXXX0');
  777.       {$I-}
  778.       Reset(f);
  779.       {$I+}
  780.       if IOResult = 0 then begin
  781.         EMSpresent := True;
  782.         Close(f);
  783.       end else
  784.         EMSpresent := False;
  785.     end;
  786.  
  787.     function EMSpagesAvailable(var TotalPages : Word) : Word;
  788.       {-return the number of 16K expanded memory pages available and unallocated}
  789.     begin
  790.       EMSregs.ah := $42;
  791.       Intr(EMSinterrupt, Dos.Registers(EMSregs));
  792.       if EMSregs.ah <> 0 then begin
  793.         WriteLn('EMS device not responding');
  794.         EMSpagesAvailable := 0;
  795.         Exit;
  796.       end;
  797.       EMSpagesAvailable := EMSregs.bx;
  798.       TotalPages := EMSregs.dx;
  799.     end;
  800.  
  801.     procedure EMSpageMap(var PageMap : PageArray; var EMShandles : Word);
  802.       {-return an array of the allocated memory blocks}
  803.     begin
  804.       EMSregs.ah := $4D;
  805.       EMSregs.es := Seg(PageMap);
  806.       EMSregs.di := Ofs(PageMap);
  807.       EMSregs.bx := 0;
  808.       Intr(EMSinterrupt, Dos.Registers(EMSregs));
  809.       if EMSregs.ah <> 0 then begin
  810.         WriteLn('EMS device not responding');
  811.         EMShandles := 0;
  812.       end else
  813.         EMShandles := EMSregs.bx;
  814.     end;
  815.  
  816.     procedure WriteEMSmap(PageMap : PageArray; handles : Word);
  817.       {-write out the EMS page map}
  818.     var
  819.       h : Word;
  820.     begin
  821.       WroteHeader := True;
  822.       WriteLn('block   bytes   (Expanded Memory)');
  823.       WriteLn('-----   ------');
  824.       if Handles > 0 then
  825.         for h := 0 to Pred(handles) do
  826.           if PageMap[h].numpages <> 0 then
  827.             WriteLn(h:5, '  ', (LongInt(16384)*PageMap[h].numpages):7);
  828.     end;
  829.  
  830.   begin
  831.     if not(EMSpresent) then
  832.       Exit;
  833.     WriteLn;
  834.     {Get space for the largest possible page map}
  835.     GetMem(Map, 2048);
  836.     EMSpageMap(Map^, EMShandles);
  837.     WriteEMSmap(Map^, EMShandles);
  838.     WriteLn(' free  ', (16384*LongInt(EMSpagesAvailable(TotalPages))):7);
  839.     WriteLn('total  ', (16384*LongInt(TotalPages)):7);
  840.   end;
  841.  
  842.   function ExtMemTotalPrim : LongInt;
  843.     {-Returns total number of bytes of extended memory in the system.}
  844.   inline(
  845.     $B4/$88/                 {mov ah,$88     ;get extended memory function}
  846.     $CD/$15/                 {int $15        ;returns KB avail in AX}
  847.     $B9/$00/$04/             {mov cx,1024    ;CX = 1024 = bytes per KB}
  848.     $F7/$E1);                {mul cx         ;DX:AX has result}
  849.  
  850.   function ExtMemPossible : Boolean;
  851.     {-Return true if extended memory is possible}
  852.   begin
  853.     {don't allow allocation if running PC or XT, or under DOS 2.x or OS/2}
  854.     ExtMemPossible := False;
  855.     case DosVersion of
  856.       3..4 :
  857.         case MachineId of
  858.           ATclass, Model80 : ExtMemPossible := True;
  859.         end;
  860.     end;
  861.   end;
  862.  
  863.   procedure ShowTheExtendedMemory;
  864.     {-Show extended memory, if any}
  865.   var
  866.     Total : LongInt;
  867.   begin
  868.     if ExtMemPossible then
  869.       Total := ExtMemTotalPrim
  870.     else
  871.       Total := 0;
  872.     if Total = 0 then
  873.       Exit;
  874.     if WroteHeader then
  875.       WriteLn('                (Extended Memory)')
  876.     else begin
  877.       WroteHeader := True;
  878.       WriteLn;
  879.       WriteLn('block   bytes   (Extended Memory)');
  880.       WriteLn('-----   ------');
  881.     end;
  882.     WriteLn('total  ', Total:7);
  883.   end;
  884.  
  885.   procedure GetOptions;
  886.     {-Analyze command line for options}
  887.   const
  888.     unknop : string[24] = 'Unknown command option: ';
  889.   var
  890.     arg : AllStrings;
  891.     arglen : Byte absolute arg;
  892.     i : Word;
  893.  
  894.     procedure WriteHelp;
  895.       {-Show the options}
  896.     begin
  897.       WriteLn('MAPMEM ', Version, ', by TurboPower Software');
  898.       WriteLn;
  899.       WriteLn('MAPMEM produces a report showing what memory resident programs are installed,');
  900.       WriteLn('how much memory each uses, and what interrupt vectors are taken over.');
  901.       WriteLn;
  902.       WriteLn('MAPMEM accepts the following command line syntax:');
  903.       WriteLn;
  904.       WriteLn('  MAPMEM [Options]');
  905.       WriteLn;
  906.       WriteLn('Options may be preceded by either / or -. Valid options are as follows:');
  907.       WriteLn('     /H     do not use WATCH information.');
  908.       WriteLn('     /V     verbose report.');
  909.       WriteLn('     /?     write this help screen.');
  910.       Halt(1);
  911.     end;
  912.  
  913.   begin
  914.     {Initialize defaults}
  915.     Verbose := False;
  916.     UseHook := False;
  917.     WroteHeader := False;
  918.  
  919.     i := 1;
  920.     while i <= ParamCount do begin
  921.       arg := ParamStr(i);
  922.       if (arg[1] = '?') then
  923.         WriteHelp
  924.       else if (arg[1] = '-') or (arg[1] = '/') then
  925.         case arglen of
  926.           1 : Abort('Missing command option following '+arg);
  927.           2 : case UpCase(arg[2]) of
  928.                 '?' : WriteHelp;
  929.                 'H' : UseHook := True;
  930.                 'V' : Verbose := True;
  931.               else
  932.                 Abort(unknop+arg);
  933.               end;
  934.         else
  935.           Abort(unknop+arg);
  936.         end
  937.       else
  938.         Abort(unknop+arg);
  939.       i := Succ(i);
  940.     end;
  941.  
  942.   end;
  943.  
  944. begin
  945.   GetOptions;
  946.   FindTheBlocks;
  947.   WatchBlock := FindMark(WatchID, WatchOffset);
  948.   UseWatch := UseWatch and not(UseHook);
  949.   ShowTheBlocks;
  950.   ShowTheEMSblocks;
  951.   ShowTheExtendedMemory;
  952. end.
  953.