; ; histogram utility ; 1/14/83 ; cr equ 0dh lf equ 0ah cseg public conout public systemreset public printm public crlf public blank public collecthist public histds extrn parse:near extrn parse2:near extrn conin:near extrn inittimer:near extrn starttimer:near extrn stoptimer:near extrn printdword:near extrn printword:near hist: mov ax,ds mov ss,ax ;set up histogram stack mov sp,offset stacktop mov dx,offset signon call printm ;print signon message call getcommandline call loadcmdfile call setupcommandtail call setuprange call gettimerstartaddr call setupbdosint call executeuserprogram call printhistogram call systemreset ; ; get command line into buffer at 80h (unless it's already there) ; and put a 0 at the end of it ; getcommandline: cmp byte ptr .5dh,' ' jnz putzero ;already entered command line in 'hist' command mov dx,offset comlinemessage call printm mov byte ptr .7fh, 7eh ;store max count mov dx,7fh ;start of buffer for rdconbuff call rdconbuff call crlf putzero: sub bh,bh mov bl,byte ptr .80h ;get actual count mov byte ptr 81h[bx],0 ;store 0 at end of command line ret ; ; load cmd file parsed from command line ; set up dma address to default to users default buffer at 80h ; set user cs, ds and es registers from base page of loaded program ; loadcmdfile: mov di,5ch call parse mov bx,65h cmp byte ptr [bx],' ' jnz load1 mov byte ptr [bx],'C' mov byte ptr 1[bx],'M' mov byte ptr 2[bx],'D' load1: mov dx,5ch call openfile inc al jz nofile mov dx,5ch call loadfile mov userds,bx mov dx,5ch call closefile inc al jz closeerr mov dx,userds call setdmabase ;set up dma address for loaded program mov dx,80h call setdma mov ax,userds mov usercs,ax mov useres,ax mov es,ax ;point to user's base page test es: byte ptr .5,1 ;check for 8080 model jnz is8080 ;if so - cs, es = ds mov ax,es:.3 ;get code base mov usercs,ax mov ax,es:.15 ;get extra base or ax,ax ;see if there is an extra segment jz is8080 mov useres,ax is8080: ret ; nofile: mov dx,offset nofilemessage call printm call systemreset closeerr: mov dx,offset closemessage call printm call systemreset ; ; copy command tail to buffer at 80h in user's base page ; parse 2 file names into 5ch and 6ch ; setupcommandtail: dec conptr ;point to delimiter push conptr ;save conptr for parsing later mov al,byte ptr .80h ;get count of command line sub al,byte ptr conptr ;subtract chars already parsed mov es,userds mov di,80h ;point to user's default buffer stos al ;store count inc al ;to include delimiter mov cl,al sub ch,ch ;count in cx s1: call conin stos al dec cx jnz s1 pop conptr ;restore conptr mov di,5ch call parse ;parse first filename cmp al,0 ;check for end of command tail jnz s2 dec conptr ;to rescan eol when parsing second filename s2: mov di,6ch call parse2 ;parse second filename mov cx,36 ;36 bytes to move in fcb mov si,5ch mov di,si mov es,userds rep movsb ;move fcb into user's base page ret ; ; set up default range (code segment of loaded program) ; see if user wants to change to another range ; setuprange: mov es,userds sub si,si ;point to base page lods es:ax ;get low 16 bits of code segment length mov maxoffset,ax ;store it lods es:al ;get high 4 bits of code segment length mov cl,12 sub ah,ah shl ax,cl ;convert to pp's mov maxbase,ax ;store it lods es:ax ;get base of code segment mov minbase,ax ;store it add maxbase,ax ;to form total maxbase call getrange mov ax,minoffset and ax,0fff0h ;mask to even paragraph mov minoffset,ax mov cl,4 shr ax,cl ;convert to pp's add ax,minbase mov minpara,ax ;store low end of range mov ax,maxoffset add ax,0fh ;round up jnc sr1 add maxbase,1000h ;if wraparound occurred sr1: mov cl,4 shr ax,cl ;convert to pp's add ax,maxbase mov maxpara,ax ;store high end of range sub ax,minpara ;range = max - min mov bl,nbucket dec bl sub bh,bh add ax,bx ;to cause round up if not even division jnc sr15 mov ax,0ffffh ;if add caused wrap, use max range value sr15: sub dx,dx ;for division mov cx,dx mov cl,nbucket div cx or ax,ax jnz sr2 inc ax ;make sure npara > 0 sr2: mov npara,ax ;# paragraphs per bucket ; see if user wants to change range ret getrange: mov dx,offset lowmessage mov si,offset minbase call getrangevalue mov dx,offset highmessage mov si,offset maxbase call getrangevalue ret getrangevalue: push si call printm pop si push si mov es,[si] mov di,2[si] call printdword mov al,')' ! call conout mov al,':' ! call conout call blank mov dx,offset consolebuff call rdconbuff mov conbuffptr,0 mov bl,conbufflen sub bh,bh mov conbuff[bx],0 ;insert 0 terminator call getnum pop si or ah,ah jz noinput cmp al,':' ;base value entered? jnz newoffset push si mov [si],bx call getnum pop si newoffset: cmp al,0 jnz badinput mov 2[si],bx noinput: call crlf ret badinput: mov dx,offset badinputmessage call printm call systemreset gettimerstartaddr: mov ax,usercs mov startbase,ax mov dx,offset timeronmessage mov si,offset startbase call getrangevalue test startoffset,0ffffh jz nostart mov startflag,1 mov es,startbase mov di,startoffset mov ax,es:[di] ;get instruction at start address mov startinstr,ax ;save it ; mov es: byte ptr [di],0cch ;interrupt 3 instruction mov es: word ptr [di],04cdh ;interrupt 4 instruction nostart: ret ; ; save current break interrupt and replace it ; with pointer to local break interrupt handler ; setupbreakint: sub ax,ax mov es,ax mov si,010h mov di,offset break_ip mov ax,es:[si] ;get real break ip mov [di],ax ;save it mov ax,offset break_int_handler mov es:[si],ax ;store break ip in interrupt vector mov ax,es:2[si] ;get real break cs mov 2[di],ax ;save it mov ax,cs mov es:2[si],ax ;store hist cs in interrupt vector ret break_int_handler: push ds mov ds,histds mov sssave,ss mov spsave,sp mov ss,histss mov sp,offset break_tos push ax ! push bx ! push cx ! push dx push bp ! push si ! push di ! push es mov es,sssave mov si,spsave ;point to user's stack sub es:word ptr 2[si],2 ;dec user ip to account for INT 4 ; dec es:word ptr 2[si] ;dec user ip to account for INT 3 or es:word ptr 6[si],200h ;make sure IF is on test startflag,1 jz notstart ;jump if there was no start timer addr mov di,es:2[si] ;get user ip cmp di,startoffset ;see if we are at the start timer addr jnz notstart mov ax,es:4[si] ;get user cs cmp ax,startbase jnz notstart mov es,ax mov ax,startinstr ;get instr replaced by INT 3 mov es:[di],ax ;restore it call starttimer notstart: pop es ! pop di ! pop si ! pop bp ! pop dx ! pop cx ! pop bx ! pop ax mov ss,sssave mov sp,spsave pop ds iret ; ; save current bdos interrupt and replace it ; with pointer to local bdos interrupt handler ; setupbdosint: sub ax,ax mov es,ax mov si,380h mov di,offset bdos_ip mov ax,es:[si] ;get real bdos ip mov cs:[di],ax ;save it mov ax,offset bdos_int_handler mov es:[si],ax ;store hist ip in interrupt vector mov ax,es:2[si] ;get real bdos cs mov cs:2[di],ax ;save it mov ax,cs mov es:2[si],ax ;store hist cs in interrupt vector ret bdos_int_handler: cmp cl,0 jz userdone ;done on function 0 call jmpf dword ptr bdos_ip ;transfer control to the bdos executeuserprogram: call inittimer mov histss,ss mov histsp,sp mov histds,ds call setupbreakint test startflag,1 jnz ex0 call starttimer ex0: mov es,useres mov ds,userds callf dword ptr userip ; ; arrive here upon completion of user program ; (function 0 bdos call intercepted - later add user supplied ; terminate address or timeout) ; userdone: call stoptimer sub ax,ax mov es,ax mov di,380h mov si,offset bdos_ip movs ax,cs:ax movs ax,cs:ax ;restore bdos interrupt mov ax,histds mov ds,ax ;restore histogram ds mov ax,histss mov ss,ax mov sp,histsp ;restore histogram ss, sp mov dx,offset donemessage call printm ret ;to main histogram program bdos_ip rw 1 bdos_cs rw 1 ; ; collect histogram data ; called with user ip in ax; user cs in bx ; collecthist: add ax,0fh ;round up to next paragraph jnc col1 ;check for wraparound add bx,1000h ;allow for wraparound col1: mov cl,4 shr ax,cl ;change ip to paragraph number add ax,bx ;ax now has actual paragraph # of interrupt cmp ax,minpara ;check against low end of range jc outofrange cmp maxpara,ax ;check against high end of range jc outofrange sub ax,minpara ;normalize within range sub dx,dx ;for division div npara ;divide by pp's per bucket to get bucket # add ax,ax ;to index into word array mov bx,ax inc bucket [bx] ;increment appropriate count outofrange: ret printhistogram: call printcommandline call findmax mov cl,nbucket mov bptr,0 phloop: push cx ;save loop count call printaddress mov bx,bptr add bx,bx ;index into word array mov ax,bucket[bx] or ax,ax jz empty call printbar empty: call crlf pop cx ;restore loop count dec cl jz phdone inc bptr jmps phloop phdone: call printmax ret printbar: ;called with value in ax mov bx,maxdots dec bx mul bx div maxvalue inc ax ;so anything above 0 gets at least one dot mov cx,ax ;number of dots in cx pb0: mov al,'*' push cx ;save count call conout pop cx dec cx jnz pb0 ret printcommandline: mov dx,offset histmessage call printm mov bx,81h pcl0: mov al,[bx] or al,al jz pcl1 push bx call conout pop bx inc bx jmps pcl0 pcl1: call crlf call crlf ret printaddress: mov ax,bptr mul npara ;get # paragraphs to add to min mov bx,minoffset mov cl,4 shr bx,cl ;convert min offset to paragraphs add ax,bx ;actual address of this bucket mov di,ax and ax,0f000h ;mask off 64K overflow add ax,minbase mov es,ax shl di,cl ;make offset bytes from paragraphs call printdword call blank ret findmax: mov dx,0 ;max in dx mov bx,0 ;index into bucket array mov cl,nbucket ;count in cl maxloop: cmp dx,bucket[bx] jnc max1 mov dx,bucket[bx] max1: inc bx inc bx dec cl ;decrement count jnz maxloop mov maxvalue,dx ret printmax: mov dx,offset maxmessage call printm mov ax,maxvalue call printword call crlf call crlf ret ; ; return 16-bit value in bx ; delimiter in al; ah = 0 if no input ; getnum: sub bx,bx mov ah,bh get0: call getchar call delim jz getret mov cl,4 shl bx,cl call hexcon jc geterr add bl,al mov ah,1 jmps get0 getret: ret geterr: mov dx,offset baddigitmessage call printm call systemreset hexcon: sub al,'0' cmp al,10 jb hexret add al,('0' - 'A' + 10) and 0ffh cmp al,16 jnb hexerr cmp al,10 jc hexerr hexret: clc ret hexerr: stc ret delim: cmp al,':' jz d0 or al,al d0: ret getchar: ;return next char from consolebuff mov di,conbuffptr mov al,conbuff[di] inc conbuffptr upper: cmp al,'a' jb upret cmp al,'z' ja upret and al,5fh upret: ret crlf: mov al,cr call conout mov al,lf call conout ret blank: mov al,' ' call conout ret ; ; OS interface routines ; bdos: int 224 ret systemreset: mov cl,0 mov dl,0 jmps bdos conout: mov dl,al mov cl,2 jmps bdos printm: mov cl,9 jmps bdos rdconbuff: mov cl,10 jmps bdos openfile: mov cl,15 jmps bdos closefile: mov cl,16 jmps bdos setdma: mov cl,26 jmps bdos setdmabase: mov cl,51 jmps bdos loadfile: mov cl,59 jmps bdos ; ; histogram data area ; cseg histds rw 1 userip dw 0 usercs rw 1 dseg public userds extrn conptr:word signon db 'Histogram Utility Version 0.2 1/14/83',cr,lf,cr,lf,'$' comlinemessage db 'Enter command line: $' nofilemessage db 'No file$' closemessage db 'Cannot close$' histmessage db 'Histogram for: $' donemessage db cr,lf,'User program complete',cr,lf,cr,lf,'$' maxmessage db cr,lf,'Maximum = $' lowmessage db 'Low address of range (default = $' highmessage db 'High address of range (default = $' badinputmessage db cr,lf,'Bad input$' baddigitmessage db cr,lf,'Bad hex digit$' timeronmessage db 'Start timer address (default = $' bufflen equ 80 consolebuff db bufflen ;max length conbufflen rb 1 ;actual length conbuff rb bufflen ;buffer conbuffptr rw 1 ;console buffer pointer stack rw 128 stacktop rw 0 userds rw 1 useres rw 1 histss rw 1 histsp rw 1 maxnbucket equ 255 ;seems like this should be enough bucket rw maxnbucket nbucket db 50 ;default (for now) npara rw 1 ;# paragraphs per bucket minbase rw 1 minoffset rw 1 minpara rw 1 ;low end of range (pp #) maxbase rw 1 maxoffset rw 1 maxpara rw 1 ;high end of range (pp #) maxvalue rw 1 ;highest value in buckets maxdots dw 64 ;# dots in largest bucket bptr rw 1 break_ip rw 1 ;save what was there break_cs rw 1 ; for now, reserve word for INT 4 startinstr rw 1 ;instruction overwritten by INT 3 startflag db 0 ;set when user enters start timer addr startbase dw 0 startoffset dw 0 spsave rw 1 sssave rw 1 rw 128 ;break int local stack break_tos rw 0 end