;stgmap - routine will show the memory map allocated to each MCB
;in the system
;for version 3.x and later the program name will be provided as well
;
title stgmap.asm - show storage chain
name  ikjstg
page    80,132
;
;------- m a c r o s     -------------
pusha    macro
         pushr   ax
         pushr  bx
         pushr  cx
         pushr  dx
         pushr  si
         pushr  di
         pushr  es
         pushr  ds
         pushr  bp
         endm
;
pushr    macro   reg
         push    reg
         endm
;
popa     macro
         popr    bp
         popr   ds
         popr   es
         popr   di
         popr   si
         popr   dx
         popr   cx
         popr   bx
         popr   ax
         endm
;
popr     macro   reg
         pop     reg
         endm
;
;------- e q u a t e s   -------------
;
cr      equ     0dh     ;common symbols
lf      equ     0ah     ;
tab     equ     09h
;
code     segment para public 'code'
         assume cs:code,ds:code,es:code,ss:code
;
id       segment byte public 'data'
author   db     'Steve Leoce - Public Domain'
stamp    db     'STGMAP 29 Aug 1987'
release  db     'V3 R1 SvcLvl 7'
terminal db     1ah
id       ends
;
         org    100h
entry:   jmp    map     ;do the map thing...
;
heading  db     13,10,'Addr   Para    Bytes    Member Name'
         db     8 dup (' '),'Hooked Vectors '
crlf     db     cr,lf,'$'
commsg   db     'COMMAND.COM '
unkmsg   db     '    N/A     '
fremsg   db     'UNALLOCATED '
space    db     3 dup (' '),'$'
n_blk    db     0       ;count table entries
ver3     db     0       ;=1 if version 3.x
;
fcount   db     0       ;free space count
mcount   db     1       ;member count
;
pgmsize  dw     ?       ;region size
;
map      proc   near

;
; release any other storage not used by the map program above here
; allow 512 bytes to remain on board for stack and table ...
;
         push   es
         mov    ax,ds:[002ch]   ;release the env
         mov    es,ax
         mov    ah,49h
         int    21h     ;no need to keep it here
         pop    es      ;restore es segments
         mov    bx,512+offset eom       ;allow 1K stk, Plus pgm
         mov    sp,bx   ;reset stack here ...
         add    bx,15   ;round up 1k
         mov    cl,04   ;divide by 16 for paragraphs
         shr    bx,cl
         mov    pgmsize,bx      ;store new region size
         mov    ah,4ah  ;free allocated memory fcn
         int    21h     ;do it now
         jnc    relsd           ;couldn't release
         mov    dx,offset memmsg
         mov    ah,09h
         int    21h     ;write the warning
;
relsd:  mov     ah,30h  ;check cp version
         int    21h
         cmp    al,3    ;if not 3.x?
         jb     not3
         inc    ver3
not3:
        call    parse   ;determine flags
        call    intro   ;intro function here ...
;
;--- find the MCB by scanning through memory, on exit ES=MCB address
;--- AX=Block address, BX=ES, CX=OWNER, DX=TOP of memory
;
         xor    bx,bx
search:  mov    es,bx   ;point segment to 0
         assume es:nothing      ;tell masm nothing
         cmp    byte ptr es:[0],'M'     ;is this an MCB signature?
         je     check   ;might be ... let's look
crawl:   inc    bx      ;point to next segment
         jmp    search  ;continue the search
check:   mov    ax,bx   ;MCB seg should belong to next
         inc    ax
         mov    cx,word ptr es:[1]      ;if owner of this block is the
         cmp    ax,cx   ;is the same as the block
         jne    crawl   ;is the legit first block
foundf:  mov    dx,word ptr ds:[2]      ;top of memory is saved here
;
; if the owner is cx=0, then this block is unallocated <FREE>
; AX=mem addr, BX=ES, CX=OWN, DX=TOP, look up owner in table
;
worm:   cld             ;string moves forward in memory
         mov    di,offset table ;where to search for the names
         mov    cx,word ptr es:[1]      ;get owner of this block
         mov    al,n_blk        ;number to look at
srchtab:        or      al,al   ;if no entries are left
         jz     create  ;then create a new one
         dec    al      ;adjust valid entry table count
         or     cx,cx   ;if unallocated
         jz     skip    ;then skip to the end
         mov    si,word ptr [di]        ;get pid of owner
         cmp    cx,si   ;if owner matches
         je     addlen  ;add more length bytes here
skip:    add    di,17   ;point to next entry
         jmp    srchtab ;and continue the scan
;
; assume di points to 1st empty spot in the table
;
create:  inc    n_blk   ;adding a new entry to count
         mov    ax,cx   ;recover the id word here
         or     cx,cx   ;if block is not allocated now
         jnz    notfree ;don't count it
         mov    ax,bx   ;then put memory as owner
         inc    ax      ;marked next segment
notfree:        mov     word ptr [di],ax        ;put owner id in table
         mov    word ptr [di][2],0      ;zero initial length
addlen:  mov    si,word ptr es:[3]      ;block length is here
         inc    bx      ;use length to point bx...
         mov    ax,bx   ;point ax to memory segment
         add    bx,si   ;and MCB zone
         add    word ptr [di][2],si     ;increase paragraph count
         mov    si,offset fremsg        ;the memory free message
         or     cx,cx   ;if owner = 0
         jnz    owned   ;
         or     memchk,cl       ;if doing fast scan, mark free
         jz     scanall ;don't mark
         mov    word ptr [di],cx        ;otherwise, PID is zero
scanall: mov    cx,12   ;length of string
         inc    fcount  ;free space counter ...
         jmp    copy
owned:
;
; is this block a primary block (a program)?
;
         cmp    ax,cx   ;is mem=owner
         jne    next    ;no
;
; this is a program block - word at offset 2ch into the block contains
; environment segment address (offset from current es by 1 block)
;
         mov    si,word ptr es:[2ch+10h]
         cmp    n_blk,1 ;if not first block
         jne    chkenv  ;look for an envrionment
         mov    si,offset commsg        ;the commander is owner
         mov    cx,12   ;bytes to output as name
copy:    mov    bp,12   ;fixup
         sub    bp,cx   ;for later
         add    di,4    ;string destination
         push   cs      ;point es to this segment
         pop    es
         rep    movsb   ;copy the name here
         mov    al,' '
         mov    cx,bp   ;remainder to fill w/spaces
         rep    stosb   ;store the bytes here
         mov    al,'$'  ;dos string terminator
         stosb          ;save it in stg
         push   cs      ;restore ds to segment
         pop    ds
         jmp    short next      ;get next block
;
; is the envrionment still allocated to the owner?
;
chkenv:  cmp    ver3,0  ;if not 3.x
         je     noenv   ;skip this section - no name
         dec    si
         push   si      ;point to env MCB
         pop    ds      ;segment is DS for name find
         cmp    cx,word ptr ds:[1]      ;compare owners
         jnz    noenv   ;not our property
;
; point ds:si to the env and scan for dbl zero entry (00)
;
         inc    si      ;point si back to env
         push   si
         pop    ds      ;data segment here
         xor    si,si   ;offset = 0
         inc    si
scan:    dec    si      ;backup one byte
         lodsw           ;look at the word there
         or     ax,ax   ;if not double 0 then
         jnz    scan    ;continue to look
;
; now at the end of the path name and program name
;
         lodsw           ;skip a word (# strings)
         mov    bp,si   ;point to first char
         dec    bp      ;
path:    lodsb           ;read char at si
         or     al,al   ;if 0, eos
         jnz    path    ;else continue reading
;
; si now points to the terminator (0), scan backward for \ in memory
;
         dec    si
         mov    cx,si   ;point cx past last char
name:    dec    si      ;point to char
         cmp    si,bp   ;is it the 1st char?
         je     start   ;string start here ...
         cmp    byte ptr [si],'\'       ;the backslash?
         jne    name    ;no, continue looking
start:   inc    si      ;point to start char
         sub    cx,si   ;length of string
         jmp    copy    ;copy the name ...
noenv:   push   cs      ;restore ds
         pop    ds
         mov    cx,12   ;number of chars
         mov    si,offset unkmsg        ;unknown message
         jmp    copy    ;write that name
;
; point es to next MCB and continue searching, stop at top of memory
;
next:    mov    es,bx   ;set es to next paragraph
         cmp    bx,dx   ;now at top of memory?
         je     nomore  ;nothing else to do now
         jmp    worm    ;otherwise, continue scan
nomore:  cmp    memchk,0        ;see if table should be written
         je     short showtab
         call   summary ;just write summary , exit
showtab:        mov     dx,offset heading       ;display the table now
         mov    ah,09h
         int    21h     ;write the heading
         mov    si,offset table
         mov    cl,n_blk
         xor    ch,ch
print:   inc    mcount  ;increment owner
         call   prtword
         mov    siz,0ffffh      ;load -1 indicator - size reqd
         call   prtword ;print size
         call   ascii           ;output the size in bytes
         mov    dx,si   ;name of owner
         mov    ah,09h  ;function = output string
         int    21h
         add    si,13   ;next entry is here
         cmp    fast,1  ;true for fast?
         je     nxt     ;get next
         call   vlist   ;show the vector list
nxt:     lea    dx,crlf ;the cr/lf string
         mov    ah,09h
         int    21h     ;output newline
         mov    pid,0   ;assume no owner right now
         mov    siz,0   ;sizing is required
         loop   print   ;continue until done
         lea    dx,members      ;member string
         mov    ah,09
         int    21h
         mov    al,mcount
         sub    al,fcount       ;remove the # free
         call   number
;
         lea    dx,freespc
         mov    ah,09
         int    21h
;
         mov    al,fcount
         call   number
         mov    dx,offset crlf
         mov    ah,09h
;
         mov    al,vctchk              ; check vectors?
         or     al,al                  ; if not, quit
         jz     mxit                   ; exit map routine
;
         call   vectors                ; otherwise, show free vectors
;
mxit:
         mov    ax,4c00h        ;terminate with CP
         int    21h
map      endp
;
spacer   db      5 dup (' ')     ;5 blanks to sep name list ...
         db      '$' ;end marker
;
vlist    proc   near    ;output the list of vectors
         push   cx      ;save block count here
         push   si
;
; ** compare to see if an active process was found
;
         push   ds
         pop    bp
         cmp    pid,bp
         jne    short v0        ;no - start the search
         lea    dx,amsg
         dec    mcount  ;keep track properly
         mov    ah,09h
         int    21h     ;output the msg line
         jmp    short vexit     ;blow out when this pgm
;
; ** output the spacer characters now - and search interrupts
;
v0:      lea    dx,spacer       ;write the spacer here
         mov    ah,09h
         int    21h     ;output the spaces
;
; now start the loop here - read the vector table and see if owner
; matches requested value - move bp,pid and see
;
         mov    bp,pid
         mov    di,bp
         add    di,10h  ;correction required
         mov    cx,00ffh        ;load the number to do
         xor    ax,ax   ;the place to start
         xor    si,si
v1:      push   cx      ;save the loop control for now
         push   ds
         push   ax
         xor    bx,bx
         mov    ds,bx
         lodsw
         lodsw          ;get the segment
         mov    bx,ax
         pop    ax
         pop    ds      ;restore ds
         mov    dx,bx   ;for later
         cmp    bx,bp   ;compare the segments here
         je     v2      ;do nothing, continue
         cmp    bx,di   ;maybe this one?
         jne    v3      ;not a match, continue search
v2:      push   ax
         call   hex2    ;output 2 chars now ...
         mov    dl,' '
         mov    ah,02
         int    21h     ;output a blank space
         int    21h     ;output the space twice
         pop    ax
v3:      pop    cx      ;restore the lcr
         inc    al      ;point to next interrupt
         loop   v1      ;continue until done
;
vexit:   pop    si
         pop    cx      ;restore block counter
         mov    pid,0   ;store a zero here for later
         ret
vlist   endp
;
pid     dw      0       ;process id word here
siz     dw      0       ;process size requirement
;
prtword  proc    near
         lodsw           ;get owner
         cmp    pid,0
         jne    prt2    ;don't load second time
         mov    pid,ax  ;otherwise, save the value
prt2:    cmp    siz,0ffffh      ;insert the size yet?
         jne    prt3    ;just continue - not required
         mov    siz,ax  ;store the value now
prt3:    call   hex4    ;write 4 digits
         mov    dx,offset space ;the spacer ...
         mov    ah,09h
         int    21h
         ret            ;back to caller
prtword endp
;
; hex4 - write AX as 4 hex digits onto console
; hex2 - write AL as 2 hex digits onto console
;
hex4     proc   near
         push   ax
         mov    al,ah   ;show high digits first
         call   hex2    ;display al
         pop    ax      ;restore low digits to al
;
hex2     proc    near    ;display al
         push   ax      ;save register used
         push   cx      ;save cx across shift
         mov    cl,4
         shr    al,cl   ;get high 4 bits
         pop    cx      ;restore cx
         call   h2c     ;display upper al digits
         pop    ax      ;restore lower
         and    al,0fh  ;mask and display
h2c:     add    al,90h  ;convert to ascii char in al
         daa             ;bcd convert
         adc    al,40h
         daa
         push   dx
         mov    dl,al
         mov    ah,02
         int    21h
         pop    dx
         ret
hex2     endp
hex4     endp
;
output   db      7 dup ('0')
         db      3 dup (' ')
         db     '$'
;
ascii    proc    near
         pusha           ;save all registers
         mov    cx,7    ;bytes to zero
         lea    di,output
         mov    dl,' '
rp0:     mov     [di],dl
         inc     di
         loop   rp0     ;store spaces
         mov    ax,siz  ;store the size
         xor    dx,dx   ;multiply by 16 now
         mov    bx,16   ;value in a paragraph
         mul    bx      ;do it now, dx:ax contains result
         dec    di      ;addr to start
         xchg   bp,dx   ;stash temp hiword
         mov    bx,0ah
         mov    cl,30h  ;convert factor weight
rp1:     cmp     bp,0    ;hi part exhausted yet?
         jz      short rp2       ;continue when done
         xchg   ax,bp
         xor    dx,dx   ;continue with top
         div    bx
         xchg   bp,ax
         div    bx
         or     dl,cl
         mov    [di],dl ;store converted char
         dec    di      ;make room for next
         jmp    short rp1       ;continue
rp2:     xor     dx,dx   ;hi word is gone
         div     bx      ;remainder in ax used
         or     dl,cl   ;adjust weight for ascii
         mov    [di],dl ;store the char
         dec    di
         cmp    ax,0    ;done?
         jnz    short rp2
         lea    dx,output
         mov    ah,09h
         int    21h     ;output the string
         popa           ;restore registers
         ret            ;return now
ascii    endp            ;all done
;
summary proc    near    ;only show totals
         call   newline
;
         mov    si,offset table
         mov    cl,n_blk
         xor    ch,ch
         xor    bx,bx   ;temp total
         mov    bp,bx
         mov    fcount,bl
         mov    mcount,bl
;
smrylp:  lodsw           ;read pid field for free mark
         or     ax,ax
         jnz    alloc   ;already allocated
;
         inc    fcount
         lodsw          ;now have size
         add    bp,ax   ;free space in bp
         jmp    short smryelp
;
alloc:   lodsw
         add    bx,ax
         inc    mcount
;
smryelp: add    si,13   ;skip away filename
         loop   smrylp  ;and continue
;
; write details for summary here
;
         push   bp      ;save free space total
         sub    bx,pgmsize      ;remove from allocated space
         mov    siz,bx  ;resident bytes here
         call   ascii
         call   smryhdr
         lea    dx,smrymsg2
         mov    ah,09h
         int    21h
;
         lea    dx,smrymem
         mov    ah,09h
         int    21h
         xor    ax,ax
         mov    al,mcount
         dec    al      ;remove count of self

         call   number
         call   newline
;
; process unallocated segments here
;
         pop    bx
         add    bx,pgmsize      ;add program length to free
         mov    siz,bx
         call   ascii
         call   smryhdr
         lea    dx,smryfre2
         mov    ah,09h
         int    21h     ;write total
;
         mov    ah,09h
         lea    dx,smryfre
         int    21h
         xor    ax,ax
         mov    al,fcount
;
; adjustment to al not required here -- the free pool will be
; linked with the large free pool at the bottom when execution
; is terminated
;
         call   number
         call   newline
;
;
; obtain total memory size
;
         call    newline ;double space required
         call    memsize
         mov    siz,ax
         call   ascii
         lea    dx,smrytot
         mov    ah,09h
         int    21h
         call   newline
;
; terminate execution here
;
         mov     ax,4c00h
         int    21h     ;condition code is 00
;
smryhdr  proc    near
         lea     dx,smrymsg      ;write header
         mov    ah,09h
         int    21h     ;and return to internal call
         ret
smryhdr  endp
;
newline  proc    near
         lea     dx,crlf
         mov    ah,09h
         int    21h
         ret
newline  endp
;
summary  endp
;
memsize  proc    near    ;determine memory size
         xor     dx,dx
         mov    bx,1024 ;bytes / k
         mov    cx,16   ;bytes / para
         int    12h     ;memsize request
         mul    bx
         div    cx
         ret            ;ax = size in para
memsize  endp
;
         subttl VECTORS - Show free user interrupts
         page
;
maxvect  equ       67H                 ; last user vector number
first    equ       60H                 ; first user vector
userofs  equ       0180H               ; first vector offset
;
vctr     db     0                      ; current processed vector
curv     db     first                  ; first one to check
;
vectors  proc   near
         lea    dx,freevct             ; free message
         mov    ah,09h
         int    21H                    ; write intro message
;
         xor    ax,ax                  ; segment zero
         mov    es,ax
         mov    di,userofs             ; user offset = 180H
;
v01:     push   ds
         lds    bx,dword ptr es:[di]
         test   bx,0FFFFH              ; see if free, null segment
         jnz    v02                    ; not sure, check 1st instr
;
         mov    ax,ds
         test   ax,0FFFFH              ; reset flags
         jz     v03                    ; certainly free now
;
; not sure- examine first instruction
;
v02:     cmp    byte ptr [bx],0CFH     ; valid iret does nothing...
         jz     v03                    ; certainly not used
;
         pop    ds                     ; restore for next search
         jmp    short v04              ; see if complete
;
v03:     pop    ds
         inc    byte ptr [vctr]        ; current vector number
;
         xor    bx,bx
         mov    al,byte ptr [curv]     ; current conversion
         call   hex2                   ; convert byte to HEX
;
         lea    dx,spaces              ; two spaces
         mov    ah,09H
         int    21H                    ; write separator
;
v04:     cmp    byte ptr [curv],maxvect
         jz     v05                    ; all complete...
;
         cmp    byte ptr [curv],0FFH
         jz     v06                    ; increment free count, continue
;
         test   byte ptr [curv],0FFH   ; last vector in list?
         jz     v07                    ; nothing free
;
v06:     inc    byte ptr [curv]        ; next one
         add    di,04                  ; next vector in table
         jmp    short v01              ; continue scan
;
v05:     mov    ah,09H                 ; write final cr/lf
         lea    dx,crlf
         int    21H
;
         ret                           ; back to caller
;
v07:     lea    dx,nonefree            ; nothing free
         mov    ah,09H
         int    21H
         jmp    short v05              ; output cr/lf, exit
;
vectors  endp
;
amsg     db      ' * Map facility not resident *$'
;
smrymsg  db      'Bytes in $'
smrymsg2 db      'resident allocation - $'
smryfre2 db      'free storage pool   - $'
smrytot  db      'Bytes total memory$'
;
smrymem  db      ' Member(s): $'
smryfre  db      'Segment(s): $'
members  db      13,10,4 dup(' '),'Active Load Members - $'
freespc  db      13,10,4 dup(' '),'Blocks Available    - $'
;
freevct  db      13, 10, 10
         db      4 dup(' '), 'Available interrupt vectors:', 13, 10
         db      4 dup(' '), '$'
;
spaces   db      2 dup (' '), '$'
;
nonefree db      4 dup(' '), '* None available', '$'
;
memmsg   db      '!STGMAP: (F21,S4A) Failed to release; all allocated'
         db      13,10,'$'
intmsg   db      'MCB Storage Map Facility - Version 3, Release 2, Service Level 8'
         db      13,10,'Load Member names $'
notm     db      'not $'
arem     db      'are $'
avail    db      'available',13 dup (' '),'Control Program $'
period   db      '.'
;
intro    proc    near
         mov     ah,09h
         lea    dx,intmsg
         int    21h
         lea    dx,notm
         cmp    ver3,0
         je     short i2
i1:      lea    dx,arem
i2:      mov    ah,09h
         int    21h     ;output the message
         lea    dx,avail
         int    21h     ;finish msg
         mov    ah,30h  ;get the version
         int    21h     ;ask cp for release number
         push   ax
         xor    ah,ah   ;blow out the top for now
         call   number
         mov    dl,period
         mov    ah,02
         int    21h     ;output the dot ..
         pop    ax
         mov    al,ah
         xor    ah,ah
         call   number
         lea    dx,crlf
         mov    ah,09h
         int    21h     ;write the crlf ...
         ret             ;back from intro routine
;
number   proc    near
         aam
         add    ax,3030h
         cmp    ah,30h
         je     p1
         push   ax
         mov    dl,ah
         mov    ah,02
         int    21h
         pop    ax
p1:      mov     ah,02
         mov     dl,al
         int    21h     ;output the char
         ret
number   endp
intro    endp            ;end of proc
;
parse    proc    near
         mov     si,80h
         lodsb
         or     al,al   ;if nothing, done
         jz     pend    ;quit now
         mov    cl,al   ; save count of characters
         xor    ch,ch   ; don't go forever though
;
pa1:     lodsb
         cmp    al,0dh  ;the cr char?
         je     pend    ;done - nothing
;
         cmp    al,20H  ; if a space or tab, do nothing
         je     short pnxt
;
         cmp    al,09H
         je     short pnxt
;
         cmp    al,'/'  ;complete?
         je     short nxch
;
         lea    dx,help
         mov    ah,09H
         int    21H
;
         mov    ax,4C16H
         int    21H
;
nxch:

         lodsb          ;get next one in
         cmp    al,'?'  ;issue helps
         jne    pa1a    ;continue
;
         lea    dx,help
         mov    ah,09h
         int    21h
;
         mov    ax,4c04h
         int    21h
;
pa1a:    and    al,5fh  ;convert to uppercase
         cmp    al,'F'
         jne    pa2     ;invalid char
;
         inc    [fast]
         jmp     short pnxt
;
pa2:     cmp     al,'M'  ;mem size only
         jne     pa3
;
         inc    [memchk]
         jmp    short pnxt
;
pa3:     cmp    al,'V'                 ; show vectors?
         jne    pa4                    ; no, bogus switch
;
         inc    [vctchk]
         jmp    short pnxt
;
pnxt:
         loop   pa1                    ; continue until done
;
pend:    ret
;
pa4:
         lea     dx,pmsg1
         mov     ah,09h
         int    21h
;
         ret
;
parse    endp
;
pmsg1    db      'Invalid parameter has been ignored',13,10,'$'
help     db      13,10,'Usage: STGMAP [/F /M /V]',13,10,10
         db      '  /F - Fast memory scan, Suppress vector display',13,10
         db      '  /M - Active memory scan only, Show memory usage',13,10
         db      '  /V - Show free vectors in the user region',13,10
         db      'NONE - Full memory scan, Show everything available'
         db      13,10,'$'
;
fast     db      0       ;=1 when selected def= no fast scan
memchk   db      0       ;=1 when selected def= no mem check only
vctchk   db      0       ;=1 when selected def= no show vectors
;
; format is table db 20 dup (0,0,0,0,'            ')
;
table    label   byte
eom      equ     $       ;last byte here
;
code     ends
         end     entry   ;entry point to linker
