	PAGE 80
	.Z80
	.COMMENT #

PROFR.M,
is the relocatable portion of the runtime profiler PROF.C.
It is called from an ECO-C program as:

	void profr(prog_name,com_tail,clock,start_addr,end_addr,printer)

	char *prog_name;	/*  .COM file name of the user program	*/
	char *com_tail;		/*  command tail for the user program	*/
	int  clock;		/*  clock intervals in 1/100 sec	*/
	char *start_addr;	/*  start address of profile		*/
	char *end_addr;		/*  end address of profile		*/
	int  printer;		/*  TRUE iff output to printer		*/

According to the ECO-C parameter passing scheme described on manual 
pages pp.63, the parameters are passed on the stack as 16-bit numbers:
	|		|
	|---------------|
	|  *prog_name	|  | increasing address
	|---------------|  V
	|   *com_tail	|
	|---------------|
	|    clock	|
	|---------------|
	|  start_addr	|
	|---------------|
	|   end_addr	|
	|---------------|
	|   printer	|
	|---------------|
	|   *old_SP	|  ^ growth of stack
	|---------------|

The last entry is the old stack pointer.
On entry, HL contains the length of the stack portion used for this 
function call, i.e., 2*6 + 2 = 14. In a regular C-function, HL is 
used to save the address of the old stack pointer on the stack,
which would be recovered on exit by the "Return" code segment at $RTNV.
However, we don't need all this because this program redefines the SP anyway.

PROFR
- creates a File Control Block (FCB) for the user program;
- parses the command tail and puts program arguments in the 
  base page just like the CCP would have done it;
- relocates the resident runtime portion below the BDOS;
- loads the user program;
- calculates the memory addresses of the profile "bins";
- redirects the warmboot jump address to the display subroutine;
- sets the system clock timer to the selected time interval;
- directs the system clock interrupt to the runtime routine;
- executes the user program;
- during program execution, assesses PC (program counter) at each
  clock interrupt and increments counter of corresponding bin;
- intercepts the warmboot jump on exit of user program; 
- restores the system clock and redirected jump addresses;
- displays the profile bins with their counters (optionally
  with printer output);
- performs a warmboot.

24 Nov 1987	A.Herrmann
17 Mar 1988	revisited
21 Mar 1988	bin initialization
22 Mar 1988	command tail parsing into base page
24 Mar 1988	system clock interval setting
25 Mar 1988	printer output
04 Apr 1988	output of total count (=elapsed time)
#

;Program constants
NBIN	EQU 16		;number of profile bins
NBIN2	EQU 4		;dual log of NBIN

;CP/M addresses
REBOOT	EQU     0H	;jump to reboot
BDOS	EQU     5H	;jump to BDOS
FCB1	EQU    5CH	;1st FCB in base page
FCB2	EQU    6CH	;2nd FCB
COMTAIL	EQU    80H	;command tail
TPA	EQU   100H	;start of user TPA
CLKSVC	EQU 0EB92H	;address of clock interrupt service
CLKVEC	EQU 0FFDAH	;address of clock interrupt vector

;Port addresses
CLOCK	EQU    21H	;CTC Ch1 port, used as clock counter
CLKSEC	EQU   125	;counter constant for 1 second interrupt

;========== Program entry =========================================
PROFR::
	POP HL		;Discard return address

;Copy program name in local string --------------------------------
	POP HL
	LD A,(HL)
	OR A
	JP NZ,CPNAME
	LD DE,CPNMSG
	LD C,9
	CALL BDOS
	JP PR$EXT
CPNMSG:	DB 'No program name ???'
	DB '$'
CPNAME:
	LD DE,PNAME
CPNAM1:	LD A,(HL)
	LD (DE),A
	INC HL
	INC DE
	CP ' '
	JP Z,CPNAM2
	OR A
	JR NZ,CPNAM1
CPNAM2:
	XOR A
	LD (DE),A

;Build FCB from program name --------------------------------------
	PUSH DE
	LD HL,PNAME
	LD DE,FCB
	CALL BF
	POP HL		;terminate program name string with '$'
	LD (HL),'$'

;Copy command tail into buffer, prefix with char count ------------
	POP HL
	LD DE,CTAIL+1
	PUSH DE
	LD C,0		;char count
CPTAI1:	LD A,(HL)
	LD (DE),A
	INC HL
	INC DE
	OR A
	JR Z,CPTAI2
	INC C
	JR CPTAI1
CPTAI2:
	POP HL
	DEC HL
	LD (HL),C	;Prefix with count

;Clear FCB portion in base page ------------------------------------
	LD HL,TPA
	LD DE,FCB1
	INC DE
	SBC HL,DE
	LD B,H
	LD C,L
	LD HL,FCB1
	LD (HL),0
	LDIR

;Parse first 2 program arguments into FCBs in base page --------------
CTPARS:
	LD HL,CTAIL
	LD A,(HL)
	OR A
	JP Z,CTPAR9	;Done if no args
	LD B,A		;Replace all blanks in command tail with zeros
	LD A,' '
CTPAR1:
	INC HL
	CP (HL)
	JR NZ,CTPAR2
	LD (HL),0
CTPAR2:	DJNZ CTPAR1

	LD HL,CTAIL	;Look for 1st string
	LD B,(HL)
CTPAR3:
	DEC B
	INC HL
	LD A,(HL)
	OR A
	JR Z,CTPAR3
	PUSH BC
	PUSH HL
	LD DE,FCB1	;DE -> 1st FCB, HL -> 1st string
	CALL BF		;Build FCB
	POP HL
	POP BC
CTPAR4:
	DEC B
	JP Z,CTPAR7	;Done?
	INC HL
	LD A,(HL)
	OR A
	JR NZ,CTPAR4
CTPAR5:
	DEC B		;Look for 2nd string
	JP Z,CTPAR6
	INC HL
	LD A,(HL)
	OR A
	JR Z,CTPAR5
	LD DE,FCB2	;HL -> 2nd string, DE -> 2nd FCB
	CALL BF		;Build FCB
CTPAR6:
	LD HL,CTAIL
	LD B,(HL)
	XOR A		;Replace all zeros with blanks again
CTPAR7:
	INC HL
	CP (HL)
	JR NZ,CTPAR8
	LD (HL),' '
CTPAR8:	DJNZ CTPAR7
CTPAR9:

;Copy command tail from buffer into base page --------------------------
	LD DE,COMTAIL
	LD HL,CTAIL
	LD C,(HL)
	LD B,0
	INC BC
	INC BC
	LDIR

;Get clock interval value and transform into 1/125 secs -----------------
PR$CLK:
	POP HL		;c * 1/100 sec equals roughly
	LD A,L		;t = c + [c/4] * 1/125 sec
	OR A
	JR Z,PR$CL1
	LD (CSEC),A
	SRL A
	SRL A
	ADD A,L
	JR PR$CL2
PR$CL1:
	LD A,10		;default = 1/10 sec
	LD (CSEC),A
	LD A,12		;t=12 for 1/10 sec
PR$CL2:
	LD (CLINT),A

;Get start address of profile ---------------------------------------
PR$GST:
	POP DE
	LD A,D		;If 0, set start address to begin of program
	OR E
	JR NZ,PR$GS1
	LD DE,TPA
PR$GS1:	LD (S$ADDR),DE

;Get end address of profile
	POP HL
	LD (E$ADDR),HL	;If 0, end address will be set to end of program
	LD A,H		; after program is loaded
	OR L
	JR Z,PR$GS2
	SBC HL,DE	;Otherwise check order
	JR NC,PR$GS2
	LD DE,ERRMSG
	LD C,9
	CALL BDOS
	JP PR$EXT
ERRMSG:	DB 'Start > End ???'
	DB 0DH,0AH
	DB '$'
PR$GS2:

;Get print flag ---------------------------------------------------------
	POP HL
	LD A,L
	LD (PRTFLG),A

;-----------------------------------------------------------------------
;Calculate the relocation offset so that the resident portion 
;of the runtime routine will begin on a page boundary just 
;beneath the BDOS
PR$REL:
	XOR A
	LD HL,PR$END
	LD DE,PR$TOP
	SBC HL,DE
	LD A,L
	CPL
	LD L,A
	LD H,0
	LD (PAGOFF),HL	; L = offset to page boundary
	PUSH HL
	LD HL,(BDOS+1)
	LD DE,PR$END
	SCF
	SBC HL,DE
	POP DE
	SBC HL,DE
	LD (RELOFF),HL	;relocation offset

;-----------------------------------------------------------------------
;Calculate and substitute each relocated address in the runtime portion 
;of the profiler as given by relocation table RELTAB below
	LD B,H
	LD C,L		;BC = offset
	LD HL,RELTAB
PR$RE1:	LD E,(HL)
	INC HL
	LD D,(HL)	;DE = next table entry
	LD A,D
	OR E
	JP Z,PR$RE2
	INC HL
	PUSH HL		;Save table pointer
	PUSH DE
	EX DE,HL
	LD A,(HL)
	INC HL
	LD H,(HL)
	LD L,A
	ADD HL,BC	;HL = new address
	POP DE
	EX DE,HL
	LD (HL),E
	INC HL
	LD (HL),D
	POP HL		;Restore table pointer
	JP PR$RE1
PR$RE2:

;Relocate the runtime portion to the end of the TPA -----------------
	LD HL,PR$END
	LD DE,PR$BEG
	XOR A
	SBC HL,DE
	INC HL
	LD B,H
	LD C,L		;BC = length of relocated program
	LD HL,(BDOS+1)
	LD DE,(PAGOFF)
	SCF		;set carry
	SBC HL,DE
	EX DE,HL	;DE = new end address
	LD HL,PR$END	;HL = old end address
	LDDR		;Copy the runtime portion

;Reset stack pointer below relocated program --------------------------
	EX DE,HL
	INC HL
	LD SP,HL

;Continue in relocated program ----------------------------------------
JPHL::	JP (HL)

;======== Subroutine to build an FCB for a file name ==================
;HL -> File name, zero-terminated; DE -> FCB
;For comments, see Andy Johnson-Laird, The Programmer's CP/M Handbook,
;p. 100.
BF:
	INC HL
	LD A,(HL)
	DEC HL
	CP ':'
	JP NZ,BF$ND
	LD A,(HL)
	AND 00011111B
	INC HL
	INC HL
	JR BF$SD
BF$ND:
	XOR A
BF$SD:
	LD (DE),A
	INC DE
	LD C,8
	CALL BF$GT
	CP '.'
	JR NZ,BF$NT
	INC HL
BF$NT:
	LD C,3
	CALL BF$GT
	LD B,0
	LD C,24
	CALL BF$FT
	RET
BF$GT:
	LD A,(HL)
	OR A
	JR Z,BF$SFT
	CP '*'
	JR Z,BF$QFT
	CP '.'
	JR Z,BF$SFT
	LD (DE),A
	INC DE
	INC HL
	DEC C
	JR NZ,BF$GT
BF$SKIP:
	LD A,(HL)
	OR A
	RET Z
	CP '.'
	RET Z
	INC HL
	JR BF$SKIP
BF$SFT:
	LD B,' '
	JR BF$FT
BF$QFT:
	LD B,'?'
	CALL BF$FT
	JR BF$SKIP
BF$FT:
	PUSH AF
	LD A,B
BF$FTL:
	LD (DE),A
	INC DE
	DEC C
	JR NZ, BF$FTL
	POP AF
	RET

;========= Relocation table ==============================================
PAGOFF:	DEFW 0		;offset to page boundary
RELOFF:	DEFW 0		;relocation offset
RELTAB:			;relocation table
	IRPC X,123456789
	DEFW $&X+1
	ENDM
	IRPC X,0123456789
	DEFW $1&X+1
	ENDM
	IRPC X,0123456789
	DEFW $2&X+1
	ENDM
	IRPC X,0123456789
	DEFW $3&X+1
	ENDM
	IRPC X,0123456789
	DEFW $4&X+1
	ENDM
	IRPC X,0123456789
	DEFW $5&X+1
	ENDM
	IRPC X,0123456
	DEFW $6&X+1
	ENDM
	DEFW 0		;end of table

CTAIL:			;command tail buffer
	DEFB 0		;char count
	DEFS 127

;====== Relocated portion of the profiler ===============================
;Execution of relocated profiler starts here

;Load user program ------------------------------------------------------
PR$BEG::
$25:	LD DE,FCB		;FBC of program file
$26:	CALL OPENF		;Open file
	JR NC,PR$LOD
$32:	LD DE,FOPMSG
$30:	JP PR$MSG		;If file not opened, abort
FOPMSG:	DB 'Cannot find program file'
	DB '$'

PR$LOD:				;Top address of runtime portion
$33:	LD DE,FCB		; = end of TPA during loading 
	LD HL,TPA		; of user program
$34:	CALL READF		;Read file into TPA
	JR NZ,PR$LO1
$35:	LD DE,OVFMSG
$36:	JP PR$MSG		;If program too long, abort
OVFMSG:	DB 'Not enough memory'
	DB '$'
PR$LO1:
	PUSH HL			;Save end of program address
$37:	LD DE,FCB
	LD C,16			;Close file
	CALL BDOS
	POP HL
	LD B,H			;BC = end of program address
	LD C,L
	LD DE,TPA
	XOR A
	SBC HL,DE
	JR NZ,PR$CAL
$38:	LD DE,EMPMSG
PR$MSG:
	LD C,9
	CALL BDOS
$31:	JP PR$EXT		;If program file was empty, abort
EMPMSG:	DB 'Program file empty???'
	DB '$'

;Calculate addresses of bins between start and end of profile ----------
PR$CAL:
$46:	LD HL,(E$ADDR)
	LD A,H
	OR L
	JR NZ,PR$CA1
	LD H,B			;end of profile = end of program
	LD L,C
$27:	LD (E$ADDR),HL
PR$CA1:
	EX DE,HL		;Calculate length of profile
$24:	LD HL,(S$ADDR)
	EX DE,HL
	SBC HL,DE
	LD B,NBIN2		;and divide by number of bins
PR$CA2:	SRL L			; by shifting (works only because
	SRL H			; number of bins is power of 2)
	JR NC,PR$CA3
	SET 7,L
PR$CA3:	DJNZ PR$CA2
	EX DE,HL		;DE = address increment
				;HL = start address
	PUSH HL
$28:	LD HL,MEM		;Underflow bin always gets 
	PUSH HL			; address zero
	POP IX
	LD (IX+0),0
	LD (IX+1),0
	LD B,NBIN		;B = number of bins
	POP HL			;1st bin gets start address
PR$CA4:
	INC IX
	INC IX
	LD (IX+0),L
	LD (IX+1),H
	ADD HL,DE
	DJNZ PR$CA4
$29:	LD HL,(E$ADDR)		;Overflow bin always
	LD (IX+2),L		; gets the end address
	LD (IX+3),H
	LD (IX+4),0FFH		;Sentinel address goes
	LD (IX+5),0FFH		; behind last table entry

;Set bin counters to zero ---------------------------------------------
	LD A,NBIN
	INC A
	INC A
	ADD A,A
	LD B,A
$2:	LD HL,BIN
PR$ZER:	LD (HL),0
	INC HL
	DJNZ PR$ZER

;Save and replace warmboot jump address ---------------------------------
	LD HL,(REBOOT+1)
$22:	LD (REBADR),HL
$23:	LD HL,PR$OUT		;Reboot is now redirected to 
	LD (REBOOT+1),HL	; output routine of profiler

;Save and replace BDOS jump address --------------------------------------
; The BDOS jump is funneled through the top of the resident portion of the 
; profiler because some programs determine TPA from the start address of BDOS.
	LD HL,(BDOS+1)
$40:	LD (PR$TOP+1),HL
$41:	LD HL,PR$TOP
	LD (BDOS+1),HL

;Save and replace clock interrupt vector and set system clock interval --------
	LD HL,(CLKVEC)
$43:	LD (INTVEC),HL
$44:	LD HL,PR$INT	;new interrupt service for clock
$48:	LD A,(CLINT)	;new counter value
$49:	CALL SETCLK

;Execute user program and hope for it to behave well --------------------------
PR$EXE::
	EI
	JP TPA

;========== Resident runtime portion of profiler ==============================
;This is where a jump to BDOS will be redirected to
PR$TOP::
	JP BDOS		;Address will be overwritten at runtime
			; with actual BDOS address

;Set the clock interrupt vector and counter ------------------------------
;HL = interrupt service address; A = counter constant
SETCLK:
	DI		;Disable interrupts while loading
	PUSH AF
	LD (CLKVEC),HL
	LD A,3		;Reset clock counter in CTC Ch1
	OUT (CLOCK),A	
	LD A,0C5H	;Constant will be loaded
	OUT (CLOCK),A
	POP AF		;Load counter constant
	OUT (CLOCK),A
	RET

;Interrupt service routine --------------------------------------------------
PR$INT::
	PUSH HL
	LD HL,2		;Recover PC (=current return address on stack)
	ADD HL,SP
	EI		;Re-enable interrupts as quickly as possible
	PUSH DE
	PUSH BC
	PUSH AF
	LD C,(HL)
	INC HL
	LD B,(HL)	;BC = PC
$5:	LD HL,MEM+2	;Skip underflow bin
	PUSH HL		;Save begin of table address
	SCF		;Set carry
PR$IN1:
	LD E,(HL)	;Do a linear search of table for bin
	INC HL		; address corresponding to PC
	LD D,(HL)
	INC HL
	EX DE,HL	;HL = memory segment start address of bin
	SBC HL,BC	;If HL-BC-1 < 0, carry remains set
	EX DE,HL
	JR C,PR$IN1	;Repeat until address > PC
			;(Table must have FFFFH after last entry!)
	POP DE		;Calculate offset to begin of table
	SBC HL,DE
$6:	LD DE,BIN	;Add offset to bin table 
	ADD HL,DE	; to find corresponding bin
	DEC HL
	LD D,(HL)
	DEC HL
	LD E,(HL)
	INC DE		;Increment bin counter
	LD (HL),E
	INC HL
	LD (HL),D
	POP AF
	POP BC
	POP DE
	POP HL
	RETI		;Return from interrupt service

;This is where a jump to warmboot will be redirected to ------------------
;Restore original CP/M settings
PR$OUT::
$45:	LD HL,(INTVEC)	;clock interrupt vector
	LD A,CLKSEC	;1 second interval
$47:	CALL SETCLK
	EI
$11:	LD HL,(REBADR)	;warmboot jump
	LD (REBOOT+1),HL
$42:	LD HL,(PR$TOP+1)
	LD (BDOS+1),HL	;BDOS jump

;Output profiler results -------------------------------------------------
$1:	CALL PRTCR	;Print CRLF
$53:	LD DE,PR$STR	;Print program name
$54:	CALL PRTSTR
$55:	LD DE,CL$STR	;Print clock interval
$56:	CALL PRTSTR
$57:	LD A,(CSEC)
	LD L,A
	LD H,0
$58:	CALL PRTHL3
$59:	LD DE,CS$STR
$60:	CALL PRTSTR

	LD A,NBIN	;Print bin counters
	INC A		;2 extra bins for underflow and overflow
	INC A
	LD B,A		;B = bin down counter
	XOR A
	LD HL,0
$61:	LD (TOTAL),HL	;bin counter total
	
$3:	LD HL,BIN
$4:	LD DE,MEM

PR$1:			;Loop thru tables
	PUSH BC		;Save counter
	PUSH HL		;Save pointer to bin entry
	EX DE,HL
	LD E,(HL)
	INC HL
	LD D,(HL)
	INC HL
	EX DE,HL
	PUSH DE		;Save pointer to next memory segment entry
$7:	CALL PRTHLH	;Print memory segment address in HL as hex
$8:	CALL PRTDSH	;Print "-"
	POP BC
	POP HL
	LD E,(HL)
	INC HL
	LD D,(HL)
	INC HL
	PUSH HL
$62:	LD HL,(TOTAL)
	ADD HL,DE
$63:	LD (TOTAL),HL
	POP HL
	EX DE,HL
	PUSH DE
	PUSH BC
$9:	CALL PRTHL5	;Print bin count in HL as decimal
$10:	CALL PRTCR	;Print CRLF
	POP DE
	POP HL
	POP BC
	DJNZ PR$1	;Repeat until done

$64:	CALL PR$TOT	;Print counter total
$65:	LD HL,(TOTAL)
$66:	CALL PRTHL5

;Warmboot ----------------------------------------------------------------
PR$EXT:
	JP REBOOT

;========== Print subroutines ============================================
PRTCR:			;Print CR and LF
	LD A,0DH
$12:	CALL PRTACC
	LD A,0AH
	JR PRTACC

PRTSP:			;Print SPACE
	LD A,' '
	JR PRTACC

PRTDSH:			;Print "-"
	LD A,'-'
	JR PRTACC

PRTHL3:			;Print HL as 3-digit decimal
	LD BC,3
	JR PRTDEC
PRTHL5:			;Print HL as 5-digit decimal
	LD BC,5		;Convert integer in HL
PRTDEC:
$13:	CALL CONVRT	; into 5-char string at STRING

PRTST:			;Print $-terminated string STRING
$14:	LD DE,STRING
	JR PRTSTR
PRTHLH:			;Print HL as 4-digit hex address
	PUSH HL
	LD A,H
$15:	CALL PRTHEX
	POP HL
	LD A,L
PRTHEX:			;Print Accu as 2-byte hex
	PUSH AF
	SRL A
	SRL A
	SRL A
	SRL A
$16:	CALL PRTHE1
	POP AF
	AND 0FH
PRTHE1:
	ADD A,30H
	CP 3AH
	JR C,PRTACC
	ADD A,7H
PRTACC:
	LD E,A
	LD C,6
	PUSH DE
	CALL BDOS
	POP DE
$50:	LD A,(PRTFLG)
	OR A
	RET Z
PRTLST:
	LD C,5
	CALL BDOS
	RET
PRTSTR:
	LD C,9
	PUSH DE
	CALL BDOS
	POP DE
$51:	LD A,(PRTFLG)
	OR A
	RET Z
	EX DE,HL
PRTST1:
	LD A,(HL)
	CP '$'
	RET Z
	PUSH HL
	LD E,A
$52:	CALL PRTLST
	POP HL
	INC HL
	JR PRTST1

STRING:	DS 5		; max.5 chars
	DB '$'

;=========== Auxiliary Subroutines ===================================

CONVRT:			;converts integer in HL
	PUSH BC		; into string with BC chars
$17:	LD DE,STRING	; suppressing leading zeros
	EX DE,HL
	ADD HL,BC
	LD A,'$'	;Append string terminator
	LD (HL),A
	DEC HL
	EX DE,HL
	LD B,C

	PUSH DE		;HL >= 0A000H ?
	EX DE,HL
	LD HL,0A000H
	SCF
	SBC HL,DE
	JR NC,CONVR0

	EX DE,HL	;Yes, subtract 0A000H = 40960 
	LD DE,0A000H
	CCF
	SBC HL,DE
$18:	CALL DIV10	;and divide the difference
	POP DE
$19:	CALL RCODE	;Encode remainder
	DEC B		;less 1 digit
	PUSH DE
	LD DE,1000H	;Add 1000H = 4096 back to quotient
	ADD HL,DE
	POP DE
	JR CONVR1	;and continue as normal

CONVR0:	EX DE,HL	;No,
	POP DE
CONVR1:
$20:	CALL DIV10	;Divide HL by 10
$21:	CALL RCODE	;Encode remainder
	DJNZ CONVR1	;all digits generated?
	POP BC
	LD B,C		;Suppress leading zeros, if any
	DEC B
	RET Z
	LD H,D
	LD L,E
CONVR2:	
	INC HL
	LD A,(HL)
	CP '0'
	RET NZ
	LD (HL),' '
	DJNZ CONVR2
	RET

DIV10:	;divides integer in HL by 10 -----------------------------------
	PUSH BC
	PUSH DE
	LD B,12		;Dividend has 12+4 bits
	LD D,0A0H	;Divisor (=10) is in upper halfbyte of D
	LD E,0
DIVBIT:	XOR A		;Clear carry
	ADD HL,HL	;HL 1 bit to the left
	PUSH AF		;Save carry
	XOR A		;Clear carry
	SBC HL,DE	;"Is DE in HL"?
	INC HL		;Let's assume, yes.
	JR NC,DIVCNT	;If not 
	POP AF		;and if no carry
	JR C,DIVNXT
	ADD HL,DE	;subtract and
	DEC HL		;decrement quotient.
	JR DIVNXT
DIVCNT:	POP AF
DIVNXT:	DJNZ DIVBIT
	POP DE
	POP BC
	RET

RCODE:	;encodes remainder as ASCII char. ------------------------------
	LD A,H		;Shift remainder from upper halfbyte
	RR A		;of H into lower halfbyte,
	RR A
	RR A
	RR A
	AND 0FH		;mask it,
	OR 30H		;encode it as ASCII character,
	LD (DE),A	;and store it away.
	LD A,H		;Quotient is now in L and
	AND 0FH		;lower halfbyte of H.
	LD H,A
	DEC DE
	RET

OPENF:	;opens the file described by an FCB -------------------------------
	;DE -> FCB

	LD C,15
	CALL BDOS
	RL A		;if error (A=0FFH), carry set
	RET

READF:	;reads the file described by an FCB into memory starting at HL --------
	;DE -> FCB address
	;HL -> DMA address
	PUSH HL
	PUSH DE
	EX DE,HL
	LD C,26		;Set DMA address
	CALL BDOS
	POP DE
	POP HL
	PUSH HL
	PUSH DE
	LD C,20		;Read sequential
	CALL BDOS
	POP DE
	POP HL
	OR A
	RET NZ		;Return with A<>0, if end of file
	PUSH DE		;Save FCB address
	LD DE,80H
	ADD HL,DE
	PUSH HL		;Save new DMA address
	ADD HL,DE	;DMA+80H > end of TPA?
	XOR A
$39:	LD DE,PR$LOD
	SBC HL,DE
	POP HL
	POP DE		;Restore FCB address
	JR C,READF
	RET		;Return with A=0, if file too big

;========= Profiler data ====================================================
PR$STR:	DEFB 'Profile of program '
PNAME:	DEFS 15		;program name string
	DEFB '$'
CL$STR:	DEFB '    Clock interval '
	DEFB '$'
CS$STR:	DEFB '/100 sec'
	DEFB 0DH,0AH,0AH,'$'
PR$TOT:	DEFB 'Total$'
FCB:	DEFS 36		;local File Control Block
CSEC:	DEFB 0		;clock interval in 1/100 sec
CLINT:	DEFB 0		;clock interval in counter units
TOTAL:	DEFW 0		;bin counter total
REBADR:	DEFW 0		;saved reboot jump address
INTVEC:	DEFW 0		;saved clock interrupt vector
S$ADDR:	DEFW 0		;start address of profile
E$ADDR:	DEFW 0		;end address of profile
PRTFLG:	DEFB 0		;1 = printer output

MEM::
	DEFW 0		;underflow segment
	DEFS 2*NBIN	;start addresses of memory segments
	DEFW 0		;overflow segment
	DEFW 0FFFFH	;safety sentinel
BIN::
	DEFS 2*(NBIN+2)	;bin counts for memory segments

PR$END	EQU $-1
;================ Program end ========================================
	END
