TITLE PROFIL - MS-DOS Profile program ;Profiler for MS-DOS 1.25 2.00 ; ; Lots of stuff stolen from debug. ; User provides # of paragraphs per bucket, program is cut up accordingly. ; User also specifies clock interval ;System calls PRINTBUF EQU 9 SETDMA EQU 26 CREATE EQU 22 OPEN EQU 15 CLOSE EQU 16 GETBUF EQU 10 BLKWRT EQU 40 BLKRD EQU 39 OUTCH EQU 2 SETBASE EQU 38 FCB EQU 5CH BUFLEN EQU 80 ; FCB offsets RR EQU 33 RECLEN EQU 14 FILELEN EQU 16 ;Segments in load order CODE SEGMENT PUBLIC CODE ENDS DATA SEGMENT BYTE DATA ENDS INIT SEGMENT BYTE INIT ENDS DG GROUP CODE,DATA,INIT ;The data segment DATA SEGMENT BYTE ORG 0 ENDMES DB 13,10,"Program terminated normally",13,10,"$" ABORTMES DB 13,10,"Program aborted",13,10,"$" TOOBIG DB "Program too big",13,10,"$" EXEBAD DB "EXE file bad",13,10,"$" OUT_FCB LABEL WORD DB 0 OUTNAME DB " PRF" DB 30 DUP(0) DB 80H DUP(?) STACK LABEL WORD BYTEBUF DB BUFLEN DUP(?) ;Processed input queue AXSAVE DW ? ;See interrupt routine BXSAVE DW ? ; " " " PROG_AREA DW ? ;Segment of program start ;EXE file header RUNVAR LABEL WORD RELPT DW ? LASTP LABEL WORD RELSEG DW ? PSIZE LABEL WORD PAGES DW ? RELCNT DW ? HEADSIZ DW ? DW ? LOADLOW DW ? PROG_SS LABEL WORD ;Program stack seg INITSS DW ? PROG_SP LABEL WORD ;Program SP INITSP DW ? DW ? PROG_ENTRY EQU THIS DWORD PROG_RA LABEL WORD ;Program start offset INITIP DW ? PROG_SA LABEL WORD ;Program start segment (may be different from PROG_AREA) INITCS DW ? RELTAB DW ? RUNVARSIZ EQU $-RUNVAR EXEFILE DB 0 ;Flag to indicate EXE file DRV_VALID DW ? ;Init for AX register OUTPUT_DATA LABEL WORD ;Start of the profile data CLOCK_GRAIN DW ? ;Clock interval micro-seconds BUCKET_NUM DW ? ;Number of buckets BUCKET_SIZE DW ? ;Paragraphs per bucket PROG_LOW_PA DW ? ;Start of program (PARA #) PROG_HIGH_PA DW ? ;End of program (PARA #) DOS_PA DW ? ;IO-DOS PARA boundry HIT_IO DW 0 ;IO bucket HIT_DOS DW 0 ;DOS bucket HIT_HIGH DW 0 ;Above Program bucket NUM_DATA_WORDS EQU ($-OUTPUT_DATA)/2 ;Number of word items BUCKET LABEL WORD ;Bucket count area ;The following data will be overwritten when the buckets are initialized LINEBUF DB BUFLEN,1,0DH ;Raw input buffer DB BUFLEN DUP(?) NOFILE DB "File not found",13,10,"$" OUTERR DB "Cannot open output file",13,10,"$" GRAIN_PROMPT DB "Sample time (micro-sec) >= 60 ? ","$" SIZE_PROMPT DB "Number of paragraphs (16 bytes) per bucket? ","$" PARAM_PROMPT DB "Parameters to program? ","$" DATA ENDS ;The resident code portion CODE SEGMENT PUBLIC ASSUME CS:DG,DS:DG,ES:DG,SS:DG ;The clock interrupt routine PUBLIC CLK_INTER ;Stuff provided by external clock handler routine EXTRN CLOCKON:NEAR,CLOCKOFF:NEAR,LEAVE_INT:NEAR ORG 100H START: CLD MOV SP,OFFSET DG:STACK ;Use internal stack CALL SETUP ;The following setup stuff cannot be done in SETUP because we're probably ; overwritting the INIT area MOV DX,[PROG_AREA] MOV AH,SETBASE INT 21H ;Set base for program MOV ES,[PROG_AREA] PUSH SI ;Points to BYTEBUF MOV DI,81H ;Set unformatted params COMTAIL: LODSB STOSB CMP AL,13 JNZ COMTAIL SUB DI,82H ;Figure length XCHG AX,DI MOV BYTE PTR ES:[80H],AL POP SI MOV DI,FCB ;First param MOV AX,2901H INT 21H MOV BYTE PTR [DRV_VALID],AL MOV AX,2901H MOV DI,6CH ;Second param INT 21H MOV BYTE PTR [DRV_VALID+1],AL MOV AX,ES ;Prog segment to AX MOV DX,[PROG_RA] ;Offset CMP [EXEFILE],1 JZ EXELOAD ;EXE file JMP BINFIL ;Regular file (.COM) EXELOAD: MOV AX,[HEADSIZ] ;Size of header in paragraphs ADD AX,31 MOV CL,4 ROL AX,CL ;Size in bytes MOV BX,AX AND AX,0FE00H AND BX,0FH MOV WORD PTR DS:[FCB+RR],AX ;Position in file of program MOV WORD PTR DS:[FCB+RR+2],BX ;Record size MOV DX,[PAGES] ;Size in 512 byte blocks DEC DX XCHG DH,DL ROL DX,1 MOV DI,DX MOV SI,DX AND DI,0FE00H AND SI,1FFH SUB DI,AX SBB SI,BX MOV AX,[LASTP] OR AX,AX JNZ PARTP MOV AX,200H PARTP: ADD DI,AX ADC SI,0 MOV AX,DI ADD AX,15 AND AL,0F0H OR AX,SI MOV CL,4 ROR AX,CL XCHG AX,CX MOV BX,[PROG_AREA] ADD BX,10H MOV AX,WORD PTR DS:[2] SUB AX,CX MOV DX,OFFSET DG:TOOBIG JB ERROR CMP BX,AX JA ERROR CMP [LOADLOW],-1 JNZ LOADEXE XCHG AX,BX LOADEXE: MOV BP,AX XOR DX,DX CALL READ JC HAVEXE BADEXE: MOV DX,OFFSET DG:EXEBAD ERROR: MOV AH,PRINTBUF ;Print the message in DX INT 21H INT 20H ;Exit HAVEXE: MOV AX,[RELTAB] ;Get position of relocation table MOV WORD PTR DS:[FCB+RR],AX MOV WORD PTR DS:[FCB+RR+2],0 MOV DX,OFFSET DG:RELPT ;Four byte buffer MOV AH,SETDMA INT 21H CMP [RELCNT],0 JZ NOREL RELOC: MOV AH,BLKRD MOV DX,FCB MOV CX,4 INT 21H ;Read in one relocation pointer OR AL,AL JNZ BADEXE MOV DI,[RELPT] ;Pointer offset MOV AX,[RELSEG] ;pointer segment ADD AX,BP ;Bias with actual load segment MOV ES,AX ADD ES:[DI],BP ;Relocate DEC [RELCNT] JNZ RELOC NOREL: ADD [INITSS],BP ADD [INITCS],BP JMP SHORT PROGGO BINFIL: MOV WORD PTR DS:[FCB+RECLEN],1 MOV SI,-1 MOV DI,SI CALL READ MOV ES,[PROG_SA] ;Prog segment to ES MOV AX,WORD PTR ES:[6] MOV [PROG_SP],AX ;Default SP for non EXE files DEC AH MOV WORD PTR ES:[6],AX ;Fix size PROGGO: PUSH DS MOV AX,[PROG_AREA] MOV DS,AX MOV DX,80H MOV AH,SETDMA INT 21H ;Set default disk transfer address POP DS MOV BX,[BUCKET_NUM] SHL BX,1 ;Mult by 2 to get #bytes in bucket area CLEAR: MOV BUCKET[BX],0 ;Zero counts SUB BX,2 JGE CLEAR MOV DX,[CLOCK_GRAIN] PUSH DS POP ES CLI ;Don't collect data yet CALL CLOCKON ;Set the interrupt MOV SI,[PROG_RA] MOV DI,[PROG_AREA] MOV BX,[PROG_SS] MOV CX,[PROG_SP] MOV AX,[DRV_VALID] MOV DX,[PROG_SA] MOV SS,BX MOV SP,CX XOR CX,CX PUSH CX ;0 on prog stack PUSH DX PUSH SI MOV DS,DI ;Set up segments MOV ES,DI STI ;Start collecting data XXX PROC FAR RET ;Hop to program XXX ENDP READ: ; AX:DX is disk transfer address (segment:offset) ; SI:DI is 32 bit length RDLOOP: MOV BX,DX AND DX,000FH MOV CL,4 SHR BX,CL ADD AX,BX PUSH AX PUSH DX PUSH DS MOV DS,AX MOV AH,SETDMA INT 21H POP DS MOV DX,FCB MOV CX,0FFF0H ;Keep request in segment OR SI,SI ;Need > 64K? JNZ BIGRD MOV CX,DI ;Limit to amount requested BIGRD: MOV AH,BLKRD INT 21H SUB DI,CX ;Subtract off amount done SBB SI,0 ;Ripple carry CMP AL,1 ;EOF? POP DX POP AX ;Restore transfer address JZ RET10 ADD DX,CX ;Bump transfer address by last read MOV BX,SI OR BX,DI ;Finished with request JNZ RDLOOP RET10: STC RET ;Return here on termination or abort TERMINATE: CLI ;Stop collecting data MOV DX,OFFSET DG:ENDMES JMP SHORT WRITEOUT ABORT: CLI ;Stop collecting data MOV DX,OFFSET DG:ABORTMES WRITEOUT: MOV AX,CS MOV DS,AX MOV SS,AX MOV SP,OFFSET DG:STACK ;Use internal stack PUSH DX CALL CLOCKOFF ;Restore original clock routine STI ;Back to normal clock POP DX MOV AH,PRINTBUF INT 21H ;Apropriate termination message MOV [OUT_FCB+14],2 ;Word size records MOV DX,OFFSET DG:OUTPUT_DATA MOV AH,SETDMA INT 21H ;Set the transfer address MOV CX,NUM_DATA_WORDS ADD CX,[BUCKET_NUM] MOV DX,OFFSET DG:OUT_FCB MOV AH,BLKWRT INT 21H ;Write out data MOV DX,OFFSET DG:OUT_FCB MOV AH,CLOSE INT 21H INT 20H ;Exit ;The clock interrupt routine CLK_INTER PROC NEAR CLI PUSH DS PUSH CS POP DS ;Get profile segment MOV [AXSAVE],AX MOV [BXSAVE],BX POP AX ;old DS MOV BX,OFFSET DG:LEAVE_INT PUSH BX PUSH AX PUSH ES PUSH [AXSAVE] PUSH [BXSAVE] PUSH CX PUSH DX ;Stack looks like this ; ; +18 OLDFLAGS ; +16 OLDCS ; +14 OLDIP ; +12 RETURN TO LEAVE_INT ; +10 OLDDS ; +8 OLDES ; +6 OLDAX ; +4 OLDBX ; +2 OLDCX ;SP-> OLDDX MOV BX,SP LES BX,DWORD PTR SS:[BX+14] ;Get CS:IP MOV AX,BX MOV CL,4 SHR AX,CL MOV CX,ES ADD AX,CX ;Paragraph of CS:IP CMP AX,[DOS_PA] ;Below DOS? JB IOHIT CMP AX,[PROG_LOW_PA] ;Below program? JB DOSHIT CMP AX,[PROG_HIGH_PA] ;Above program? JAE MISSH SUB AX,[PROG_LOW_PA] ;Paragraph offset XOR DX,DX DIV [BUCKET_SIZE] MOV BX,AX SHL BX,1 ;Mult by 2 to get byte offset INC BUCKET[BX] JMP SHORT DONE IOHIT: INC [HIT_IO] JMP SHORT DONE DOSHIT: INC [HIT_DOS] JMP SHORT DONE MISSH: INC [HIT_HIGH] DONE: POP DX POP CX POP BX POP AX POP ES POP DS STI RET ;To LEAVE_INT CLK_INTER ENDP CODE ENDS ;The init segment contains code to process input parameters ; It will be blasted as soon as the program to be run is read in ; And/or the bucket area is initialized INIT SEGMENT BYTE ORG 0 SETUP: MOV DX,FCB MOV AH,OPEN INT 21H ;Open program file AND AL,AL JZ OPENOK MOV DX,OFFSET DG:NOFILE JMP ERROR OPENOK: XOR BX,BX MOV WORD PTR DS:[FCB+RR],BX MOV WORD PTR DS:[FCB+RR+2],BX ;RR to 0 MOV SI,FCB MOV DI,OFFSET DG:OUT_FCB MOV CX,4 REP MOVSW MOVSB ;Transfer drive spec and file to output MOV DX,OFFSET DG:OUT_FCB MOV AH,CREATE INT 21H ;Try to create the output file AND AL,AL JZ GETSIZE MOV DX,OFFSET DG:OUTERR JMP ERROR GETSIZE: ;Get bucket size MOV DX,OFFSET DG:SIZE_PROMPT MOV AH,PRINTBUF INT 21H CALL INBUF CALL SCANB JZ GETSIZE ;SCANB went to CR XOR BX,BX INC BX ;Size >=1 CALL GETNUM JC GETSIZE ;Bad number MOV [BUCKET_SIZE],DX CMP WORD PTR DS:[FCB+9],5800H+"E" ;"EX" JNZ NOTEXE CMP BYTE PTR DS:[FCB+11],"E" JNZ NOTEXE LOADEXEHEAD: ;Load the EXE header MOV [EXEFILE],1 MOV DX,OFFSET DG:RUNVAR ;Read header in here MOV AH,SETDMA INT 21H MOV CX,RUNVARSIZ MOV DX,FCB MOV WORD PTR DS:[FCB+RECLEN],1 OR AL,AL MOV AH,BLKRD INT 21H CMP [RELPT],5A4DH ;Magic number JZ EXEOK JMP BADEXE EXEOK: MOV AX,[PAGES] ;Size of file in 512 byte blocks MOV CL,5 SHL AX,CL ;Size in paragraphs JMP SHORT SETBUCKET NOTEXE: MOV AX,WORD PTR DS:[FCB+FILELEN] MOV DX,WORD PTR DS:[FCB+FILELEN+2] ;Size of file in bytes DX:AX ADD AX,15 ADC DX,0 ;Round to PARA MOV CL,4 SHR AX,CL AND AX,0FFFH MOV CL,12 SHL DX,CL AND DX,0F000H OR AX,DX ;Size in paragraphs to AX MOV [PROG_RA],100H ;Default offset SETBUCKET: PUSH AX ;Save size XOR DX,DX DIV [BUCKET_SIZE] INC AX ;Round up MOV [BUCKET_NUM],AX MOV BX,OFFSET DG:BUCKET SHL AX,1 ;Number of bytes in bucket area ADD AX,BX ;Size of profil in bytes ADD AX,15 ;Round up to PARA boundry MOV CL,4 SHR AX,CL ;Number of paragraphs in profil INC AX ;Insurance MOV BX,CS ADD AX,BX MOV [PROG_AREA],AX CMP [EXEFILE],1 JZ SETBOUNDS MOV AX,[PROG_AREA] ;Set up .COM segments MOV [PROG_SS],AX MOV [PROG_SA],AX SETBOUNDS: ;Set the sample window MOV BX,10H ;Get start offset ADD BX,[PROG_AREA] ;PARA # of start MOV [PROG_LOW_PA],BX POP AX ;Recall size of PROG in paragraphs ADD BX,AX MOV [PROG_HIGH_PA],BX SETDOS: XOR DX,DX MOV ES,DX ;look in interrupt area MOV DX,WORD PTR ES:[82H] ;From int #20 MOV [DOS_PA],DX PUSH DS POP ES GETGRAIN: ;Get sample interval MOV DX,OFFSET DG:GRAIN_PROMPT MOV AH,PRINTBUF INT 21H CALL INBUF CALL SCANB JZ GETGRAIN ;SCANB went to CR MOV BX,60 ;Grain >=60 CALL GETNUM JC GETGRAIN ;Bad number MOV [CLOCK_GRAIN],DX MOV DX,OFFSET DG:PARAM_PROMPT MOV AH,PRINTBUF INT 21H CALL INBUF ;Get program parameters MOV AX,2522H ;Set vector 22H MOV DX,OFFSET DG:TERMINATE INT 21H MOV AL,23H ;Set vector 23H MOV DX,OFFSET DG:ABORT INT 21H RET ;Back to resident code GETNUM: ;Get a number, DS:SI points to buffer, carry set if bad XOR DX,DX MOV CL,0 LODSB NUMLP: SUB AL,"0" JB NUMCHK CMP AL,9 JA NUMCHK CMP DX,6553 JAE BADNUM MOV CL,1 PUSH BX MOV BX,DX SHL DX,1 SHL DX,1 ADD DX,BX SHL DX,1 CBW POP BX ADD DX,AX LODSB JMP NUMLP NUMCHK: CMP CL,0 JZ BADNUM CMP BX,DX JA BADNUM CLC RET BADNUM: STC RET INBUF: ;Read in from console, SI points to start on exit MOV AH,GETBUF MOV DX,OFFSET DG:LINEBUF INT 21H MOV SI,2 + OFFSET DG:LINEBUF MOV DI,OFFSET DG:BYTEBUF CASECHK: LODSB CMP AL,'a' JB NOCONV CMP AL,'z' JA NOCONV ADD AL,"A"-"a" ;Convert to upper case NOCONV: STOSB CMP AL,13 JZ INDONE CMP AL,'"' JNZ QUOTSCAN CMP AL,"'" JNZ CASECHK QUOTSCAN: MOV AH,AL KILLSTR: LODSB STOSB CMP AL,13 JZ INDONE CMP AL,AH JNZ KILLSTR JMP SHORT CASECHK INDONE: MOV SI,OFFSET DG:BYTEBUF ;Output CR/LF CRLF: MOV AL,13 CALL OUT MOV AL,10 OUT: PUSH AX PUSH DX AND AL,7FH XCHG AX,DX MOV AH,OUTCH INT 21H POP DX POP AX RET SCANB: ;Scan to first non-blank PUSH AX SCANNEXT: LODSB CMP AL," " JZ SCANNEXT CMP AL,9 JZ SCANNEXT DEC SI POP AX EOLCHK: CMP BYTE PTR[SI],13 RET INIT ENDS END START