summaryrefslogtreecommitdiff
path: root/v2.0/source/PROFIL.ASM
blob: 08a5ada3b3bb7182d8902fdf6053cdc2da706fa6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
        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