	page	,132
	title	crt0 - C start up routine
;***
;crt0.asm - C start up routine
;
;	Copyright (c) 1985-1992, Microsoft Corporation.  All rights reserved.
;
;Purpose:
;	How startup works in a few words -
;
;	The startup and termination is performed by a few modules
;
;		crt0.asm	DOS 2.x/3.x specific init/term
;		crt0msg.asm	DOS 2.x/3.x error messages
;		(winstart.asm)	Windows specific init/term (not included)
;
;		crt0dat.asm	remainder of shared DOS 3.x init/term
;
;	*************  IMPORTANT  *****************************************
;
;	The "DOSSEG" directive in this module must not be removed or else
;	the user will have to link with the "/DOSSEG" linker switch in
;	order to get proper segment ordering.
;
;	See the C documentation for more information about the /DOSSEG switch.
;
;	All assembler modules must be assembled with the /Mx switch, i.e.
;
;		masm -Mx crt0,;
;
;	.COM Files:
;	(1) Start at location 0x100
;	(2) No stack segment
;	(3) All segments grouped together
;	(4) All seg registers the same at _astart.
;
;*******************************************************************************

;*******************************;*
	DOSSEG			;* specifies DOS SEGment ordering *
;*******************************;*

?DF=	1			; this is special for c startup

.xlist
include version.inc

include cmacros.inc
include msdos.inc
include defsegs.inc
include heap.inc
include rterr.inc
.list

	_DEBUGSCREEN equ 1	; debug screen swapping
	_FATALHOOK equ 1		; enable fatal hook code

ifdef FARSTACK
ife sizeD
    error <You cannot have a far stack in Small or Medium memory models.>
endif
endif

ifdef	_COM_
    if sizeC or sizeD
	error <Must use Small memory model for .COM files.>
    endif
endif	;_COM_

	page
;===========================================================================
;
;	Segment definitions
;
;	The segment order is essentially the same as in XENIX.
;	This module is edited after assembly to contain a dosseg comment
;	record for the linker.
;
;===========================================================================

CrtDefSegs <code,etext,data>
CrtDefSegs <xiqcseg>

ifndef _COM_
CrtDefSegs <stack>
else
DefGrpCom			; Group all segments in DGROUP
endif

codeOFFSET equ	offset _TEXT:
dataOFFSET equ	offset DGROUP:


page

public	__acrtused		; trick to force in startup
	__acrtused = 9876h	; funny value not easily matched in SYMDEB


extrn	__acrtmsg:abs		; trick to pull in startup messages


_STACK_SIZE = 2048		; Default stack size = 2K

ifndef _COM_
sBegin	stack
assumes ds,data
	db	_STACK_SIZE dup (?)    ; stack space
sEnd
endif	;_COM_

ifdef FARSTACK
; Set up the first entry in the near heap
; (see defsegs.inc definition of <stack>)
sBegin stubheap
      stubheap_size	dw	1		; 0-length free entry
      stubheap_next	dw	_HEAP_END	; mark end of heap
sEnd
endif ;FARSTACK

page

externP main			; C main program

externP exit			; exit ( code )

if	sizeC
extrn	__exit:far		; _exit ( code) (cmacros name conflict)
else
extrn	__exit:near
endif


ifdef _DEBUGSCREEN
CrtDefSegs <dbdata>
sBegin	dbdata			; Debugger Screen swap setup segment
assumes ds,data
globalW  __aDBswpflg,0			; Flag == __aDBswpyes if should attempt swapping
globalW  __aDBswpchk,___aDBretaddr	; By default points to a model dependent ret
globalW  __aDBrterr,___aDBretaddr	;
globalW  __aDBexit,___aDBretaddr	; (Report we are exiting)
staticD  __aDBcallbk,0			; Call back address to debugger
sEnd
	public	__aDBdoswp
__aDBdoswp	=	0d6d6h
endif	; _DEBUGSCREEN

sBegin	xiqcseg
externW	__qczrinit			;* QC -Zr initializer call address
sEnd

sBegin	data

extrn	__edata:byte		; end of data (start of bss)
extrn	__end:byte		; end of bss (start of stack)

externW _psp			; psp:0 (paragraph #)
externW __argc
externDP __argv
externDP _environ
externW _osversion		; byte-swapped OS version 
externW _osver			; OS version (usable byte order)

;	these are used by DOS C memory management (not used in Windows)

globalW _atopsp,0		; top of stack (heap bottom)
globalW _aexit_rtn,<codeoffset __exit> ; NEAR pointer

;*
;*	The following (_asizds and _nheap_desc) must be in this order!
;*

globalW _asizds,0		; DGROUP size - 1 (in bytes)

labelW	<PUBLIC,_nheap_desc>	; near heap descriptor
_heap_seg_desc <0,_HEAP_NEAR OR _HEAP_MODIFY, 0,0,0,0,0,0>
.ERRE	flags	eq	2	; flags better be second word

.ERRE	offset _asizds+2 EQ offset _nheap_desc	; make sure!

; Heap segment limits (for use with QC)

globalW _aseghi,0		; highest heap segment so far
globalW _aseglo,0		; lowest allowable heap segment

;
; DOS Extender Flag
;
globalW _fDosExt,0		; 0 = no DOS Extender




sEnd

	page


externP _cinit			; run-time initializers

externP _NMSG_WRITE		; pascal - write error message to stdout
externP _FF_MSGBANNER		; pascal - error message banner
				; (includes FORTRAN $DEBUG info)

externP _setargv		; process command line arguments
externP _setenvp		; process environment

ifdef _FATALHOOK
extrn	   __farstub:far		; alternative stub
extrn	   __fatalexit(__farstub):far	; FAR ptr to fatal exit routine
endif


sBegin	code
ifdef _COM_
assumes cs,DGROUP		; .com files are all in one segment
else
assumes cs,code
endif

assumes ds,nothing


page
;***
;_astart - start of all C programs
;
;Purpose:
;	Startup routine to initialize C run-time environment
;
;Entry:
;
;Exit:
;	Exits to DOS via exit().
;
;Uses:
;
;Exceptions:
;
;*******************************************************************************

ifdef _COM_
	org	0100h		; .COM files start at 0100h
endif

labelNP <PUBLIC,_astart>	; start address of all "C" programs


;	check MS-DOS version for 2.0 or later

	callos	VERSION 	; ax = DOS version
				; *** ax gets preserved for a long time ***
	cmp	al,2		; check for version 2 or later
	jae	setup		;   yes - continue with setup
ifdef _COM_			; DOS puts 0 on stack for .COM files
	retn			; DOS 1.0 exit program (int 20h at psp:0)
else	; _COM_
	xor	ax,ax
	push	es		; push warm start vector es:ax = psp:0
	push	ax
	retf			; DOS 1.0 exit program (int 20h at psp:0)
endif	; _COM_

setup:


ifdef _COM_
	mov	di,ds		; di = DGROUP
	mov	si,ds:[DOS_MAXPARA] ; get max. paragraph
else
	mov	di,DGROUP	; di = DGROUP
	mov	si,ds:[DOS_MAXPARA] ; get max. paragraph
endif	;_COM_

ifdef  _COM_
	mov	cs:[_dataseg],di ; save dgroup for future
endif

	sub	si,di		; si = # para in data area
	cmp	si,1000h	; if more than 64K
	jb	setSP

	mov	si,1000H	; use full 64K (-16)

setSP:

ifdef _COM_
	;
	; .COM files - allocate stack (-2 for _asizds limit)
	; [Do the calculation in a temp register in case it overflows.]
	;
	mov	bx,dataoffset __end ; go to end of DGROUP
	add	bx,(_STACK_SIZE-2) ; stack is beyond that
	jnc	SPok		; jump out if stack ok (ax = new sp)
	;fall thru		; error - not enough stack space

else	;!_COM_

ifdef FARSTACK
	jmp	short SPok	; stack can't be too big, if .EXE was loaded
else ;!FARSTACK
	cli			; turn off interrupts
	mov	ss,di		; SS = DGROUP
	add	sp,dataoffset __end-2 ; 2 for _asizds limit
	sti			; turn interrupts back on
	jnc	SPok

	push	ss		; establish DS=DGROUP for
	pop	ds		; _FF_MSGBANNER -> _NMSG_WRITE -> _NMSG_TEXT
endif ;FARSTACK

endif  ;_COM_

;
; Error - Not enough stack space
;

	callcrt _FF_MSGBANNER	; for "\r\nrun-time error " banner
	xor	ax,ax		; stack overflow error message (_RT_STACK=0)
	push	ax
	callcrt _NMSG_WRITE
	mov	ax,DOS_terminate shl 8 + 255
	callos			; terminate process with 255
	;*** NEVER RETURNS ***

;
; ax = os version
; es = psp
;

SPok:

ifdef _COM_
	; bx = new sp value
	mov	sp,bx		; set new sp value
endif

ifdef FARSTACK
	mov	ds, di		; reload dgroup
	assumes ds, data
else
	assumes ss,data
endif


	mov	[_osversion],ax ;* save byte-swapped OS version number
	xchg	ah,al
	mov	[_osver],ax	;* save OS version number (usable byte order)


; at this point:
;    COM files:  DS = DI = ES = SS = DGROUP
;    EXE files:  DI = SS = DGROUP, DS = ES = PSP
;    FARSTACK:	 DI = DS = DGROUP, ES = PSP, SS = STACK

;
; Save size of dgroup
;

stup10:
	mov	ax,si		; si = # paragraphs in dgroup
	mov	cl,4
	shl	ax,cl		; ax = size of dgroup (bytes)
	dec	ax		; ax = size DGROUP - 1
	mov	[_asizds],ax	; Size of Global Data Segment

;
; Carve an initial near heap out of the bottom of the stack
;

ifdef FARSTACK
	; for far stacks, we use the dummy subheap segment instead
	mov	bx,dataoffset _nheap_desc ; ds:bx = near heap descriptor
	mov	[bx].checksum,ds	  ; save dgroup in near heap desc
	mov	[bx].segsize, dataOFFSET(stubheap_next) + 2
	mov	[bx].last, dataOFFSET(stubheap_next)
	mov	[bx].start, dataOFFSET(stubheap_size)
	mov	[bx].rover, dataOFFSET(stubheap_size)

else
	mov	bx,dataoffset _nheap_desc ; ss:bx = near heap descriptor
	mov	ss:[bx].checksum,ss ; save dgroup in near heap desc

	and	sp,not 1	    ; make even (if not)
	mov	ss:[bx].segsize,sp  ; save as segment size

	.ERRE	_HEAP_END -2	; make sure they're equal
	mov	ax,_HEAP_END	; get end-of-heap flag
	push	ax		; into place
	mov	ss:[bx].last,sp ; pointer to end-of-heap
	not	ax		; ax = 1 (0-length free entry)
	push	ax		; first heap entry
	mov	ss:[bx].start,sp; init start/rover
	mov	ss:[bx].rover,sp
endif

;
; Now initialize the top of stack location
;

	mov	[_atopsp],sp	; Top of Stack Region

;	release extra space to DOS



	add	si,di		; si = DGROUP + # para in DGROUP
ifdef FARSTACK
	; at this point es still has the psp address
	mov	es:[DOS_MAXPARA],si ; fix psp:2
else
	mov	ds:[DOS_MAXPARA],si ; fix psp:2
endif

	mov	bx,es		; bx = PSP base
	sub	bx,si		; bx = - # para used
	neg	bx
	callos	setmem		; set memory block size
ifdef FARSTACK
	; for FARSTACK, es has the psp address, ds = DGROUP
stup20:
	mov	[_psp],es
else
	; no FARSTACK, ds = psp, ss = DGROUP
stup20:
	mov	[_psp],ds	; save psp:0
endif

;	zero data areas (_BSS and c_common)

ifdef FARSTACK
	push	ds
	pop	es		; ES = DGROUP
	assumes es, data
else

ifndef _COM_
	push	ss
	pop	es		; ES = DGROUP
endif	;_COM_
	assumes es,data

endif ;FARSTACK

; at this point:
;    COM files:  DS = ES = SS = DGROUP
;    EXE files:  SS = ES = DGROUP, DS = PSP
;    FARSTACK:	 DS = ES = DGROUP, SS = STACK

	cld			; set direction flag (up)
	mov	di,dataOFFSET __edata ; beginning of bss area
	mov	cx,dataOFFSET __end ; end of bss area
	sub	cx,di
	xor	ax,ax
	rep	stosb		; zero bss


;	C segmentation conventions set up here	(DS=SS and CLD)

ifndef _COM_
ifndef FARSTACK
	push	ss		; set up initial DS=ES=SS, CLD
	pop	ds
endif	;FARSTACK
endif	;_COM_
	assumes ds,data

; OK, we now have DS = ES = DGROUP for all models, and SS = DGROUP
; except for the far stack model.

;	Must run this initializer prior to any far heap allocations being
;	done.  This means that we have hack-ed this in here.  There should
;	be a better solution for C7

	mov	cx,__qczrinit		;* Get initializer addr
	jcxz	@F			;* Is it zero?
	call	cx			;* No -- call indirect through
@@:

;	process command line and environment

	call	_setenvp	; crack environment
	call	_setargv	; crack command line

;
; Zero bp for debugging backtrace support (i.e., mark top-of-stack).
;

	xor	bp,bp		; mark top stack frame for SYMDEB

;	do necessary initialization

	call	_cinit		; shared by DOS and Windows

ifndef	_COM_
ifdef FARSTACK
	mov	ax, DGROUP
	mov	ds, ax		; ds = DGROUP
else
	push	ss
	pop	ds		; ds = DGROUP
endif ;FARSTACK
endif ;_COM_
	assumes ds,data

;	call main and exit

if	sizeD
	push	word ptr [_environ+2] ; the environment is not always in DS
endif
	push	word ptr [_environ]

if	sizeD
	push	word ptr [__argv+2] ; the arguments are not always in DS
endif
	push	word ptr [__argv]

	push	[__argc]	; argument count

	call	main		; main ( argc , argv , envp )

; use whatever is in ax after returning here from the main program

	push	ax
	callcrt exit		; exit (AX)
				;   _exit will call terminators

ifdef _DEBUGSCREEN
___aDBretaddr:			; label to appropriate "RET"
	ret			; Default dbdata address
endif	; _DEBUGSCREEN


page
;***
;_amsg_exit, _cintDIV - Fast exit fatal errors
;
;Purpose:
;	Exit the program with error code of 255 and appropriate error
;	message.  cintDIV is used for integer divide by zero, amsg_exit
;	is for other run time errors.
;
ifdef _FATALHOOK
;	Weak extern call through _fatalexit was added so programs
;	may be notified of imminent termination (e.g., PWB).
endif
;
;Entry:
;	AX	= error message number (amsg_exit only).
;
;Exit:
;	calls exit() [cintDIV] or indirect through _aexit_rtn [amg_exit].
;
;Uses:
;
;Exceptions:
;
;*******************************************************************************

labelNP <PUBLIC,_cintDIV>

	assumes ds,nothing

	mov	ax,cs:[_dataseg]
	mov	ds, ax		; ds = DGROUP
	assumes ds, data

	mov	ax,_RT_INTDIV	; Integer divide by zero interrupt
	mov	[_aexit_rtn],codeoffset _exit ; call high-level exit()
				; to cause file buffer flushing

labelNP <PUBLIC,_amsg_exit>

	assumes ds,nothing

	push	ax		; message number for _NMSG_WRITE
				; save around call to __fatalexit
ifdef _FATALHOOK
	; weak extern call to fatal exit hook
	; ax = error code
	mov	bx,cs:[_dataseg]
	mov	ds, bx		; ds = DGROUP
	assumes ds, data
	call	__fatalexit
endif
				; error code already on stack
	callcrt _FF_MSGBANNER	; run-time error message banner
	callcrt _NMSG_WRITE	; write error message to stdout

ifdef _DEBUGSCREEN
	cmp	__aDBswpflg,__aDBdoswp	; Aware debugger as parent?
	jne	@F			; No -- skip
	pop	ax
	push	ax
	call	__aDBrterr		; Yes -- report a runtime error
@@:
endif	; _DEBUGSCREEN

	assumes ds,data

	mov	ax,255
	push	ax
if	sizeC
	push	cs				; _exit is compiled far
						;	but called near
endif
	call	word ptr [_aexit_rtn] ; _exit(255) ordinarily
				; (or exit(255) for div by 0)
				; NEAR routine pointer

; Location that holds DGROUP segment (necessary for .COM file support)

ifdef  _COM_
globalW _dataseg,0		; will hold DGROUP

else	;not _COM_
globalW _dataseg,DGROUP

endif	;not _COM_

sEnd
	end	_astart 	; start address
