
%if 0

lDebug - libre 86-DOS debugger

Copyright (C) 1995-2003 Paul Vojta
Copyright (C) 2008-2025 E. C. Masloch

Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.

DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.

%endif

[list -]
%if 0

For lDebug build notes, consult the manual's chapter on building.

lDebug contributions

lDebug is based on DEBUG/X 1.13 to 1.18 as released by Japheth, whose work
on DEBUG/X has been released as Public Domain. (Some changes up to version
1.27 were picked up from DEBUG/X since.)

%endif

%include "debug.mac"
[list +]

%ifndef _MAP
%elifempty _MAP
%else	; defined non-empty, str or non-str
	[map all _MAP]
%endif

%if _ONLY386
	cpu 386
%else
	cpu 8086
%endif
	org 100h
	addsection lDEBUG_DATA_ENTRY, align=16 start=100h
data_entry_start:
%define DATASECTIONFIXUP -data_entry_start+100h
_CURRENT_SECTION %+ _start:
%xdefine %[_CURRENT_SECTION %+ FIXUP] - _CURRENT_SECTION %+ _start+100h

	addsection ASMTABLE1, align=16 follows=lDEBUG_DATA_ENTRY
	addsection ASMTABLE2, align=16 follows=ASMTABLE1
	addsection MESSAGESEGMENT, align=16 follows=ASMTABLE2 vstart=0
messagesegment_start:
	addsection lDEBUG_CODE, align=16 follows=MESSAGESEGMENT vstart=0
code_start:
%define CODESECTIONFIXUP -code_start+0
_CURRENT_SECTION %+ _start:
%xdefine %[_CURRENT_SECTION %+ FIXUP] - _CURRENT_SECTION %+ _start+0
	addsection lDEBUG_CODE2, align=16 follows=lDEBUG_CODE vstart=0
code2_start:
%define CODE2SECTIONFIXUP -code2_start+0
_CURRENT_SECTION %+ _start:
%xdefine %[_CURRENT_SECTION %+ FIXUP] - _CURRENT_SECTION %+ _start+0

	addsection DATASTACK, align=16 follows=ASMTABLE2 nobits
	addsection INIT, align=16 follows=lDEBUG_CODE2 vstart=0
%if _DEVICE
	addsection DEVICESHIM, align=16 follows=INIT vstart=0
%endif
	addsection RELOCATEDZERO, vstart=0 nobits
relocatedzero:


	usesection lDEBUG_CODE
section_of puts, trimputs, puts_ext_done, puts_ext_next
section_of puts_getline_ext_done
section_of puts_copyoutput_ext_done
section_of guard_auxbuff, guard_re
section_of handle_serial_flags_ctrl_c
section_of hexword_high
section_of call_int2D
section_of copy_single_counted_string
section_of intchk
section_of IsIISPEntry?
section_of dd_store
section_of dohack, getsegmented, hexbyte, hexnyb, iseol?.notsemicolon
section_of peekc, getc, prephack, readmem, setds2si, setds2bx, setes2dx
section_of silence_get_start, silence_get_start.have_di, silence_count_zeroes
section_of setrmlimit, skipcomm0, unhack
section_of skipequals
section_of to_errret_pop
section_of cmd3.have_cx_convenience
section_of verifysegm, verifysegm_or_error
section_of pp16, resetmode, resetmode_and_test_d_b_bit
section_of pp_fix32bitflags, proceedbreakpoint
section_of putsz, hexword, skipcomma, cmd3, cmd3_not_ext, cmd3_preprocessed
section_of hexdword
section_of cmd3_not_inject, cmd3_injected
section_of aa_not_inject, aa_injected, aa_before_getline, aa_after_getline
section_of transfer_ext_cx_nearcall_from_code2
section_of traceone, unexpectedinterrupt, dumpregs_no_disasm
section_of aa_imm_entry, ia_restore_a_addr, ia_restore_u_addr, disasm
section_of ia_restore_ds
%ifn _SYMBOLIC
section_of selector_to_segment
%endif
section_of disp_al, disp_al_hex, disp_al_nybble_hex, disp_ax_dec, disp_ax_hex
section_of disp_dxax_times_cx_width_bx_size
section_of disp_dxax_times_cx_width_bx_size.store
section_of disp_message, disp_message_length_cx
section_of ispm
section_of setrc, error
section_of _doscall, _doscall_return_es, _doscall_return_es_parameter_es_ds
section_of intcall_ext_return_es, close_ext, doscall_extseg
section_of yy_reset_buf

section_of InDOS, bootgetmemorysize
section_of chkeol, decword, decdword
section_of dec_dword_minwidth
section_of getexpression.lit_isdigit?, getexpression.lit_ishexdigit?
section_of getlinearaddr
section_of getlinear_d_b
section_of getlinear_high_limit
section_of getlinear_32bit
section_of getlinear_high_limit.do_not_use_test
section_of getlinear_common
section_of ifsep, iseol?, isseparator?, isstring?, movp
section_of putc, putsline, putsline_crlf, putsz_error
section_of setrmsegm
section_of skipwh0, skipwhite
section_of test_d_b_bit, test_high_limit, push_cxdx_or_edx
section_of uppercase
section_of setes2ax
section_of zz_copy_strings_to_str_buffer
section_of call_xms_move, zz_call_xms, zz_detect_xms
section_of code2_to_code_ret_to_ext
section_of yy_dos_parse_name, yy_open_file, yy_check_lfn
section_of yy_common_parse_name
section_of yy_common_parse_name_bx_buffer
section_of retry_open_scriptspath
section_of yy_boot_read.bx, yy_boot_seek_start.bx, yy_boot_seek_current.bx
section_of yy_boot_parse_name, yy_boot_open_file
section_of yy_boot_parse_name_bx_buffer
section_of near_transfer_ext_return, scan_dir_aux, read_sector
section_of yy_boot_init_dir, got_yyentry, scan_dir_entry
section_of ..@yy_filename_empty, boot_parse_fn
section_of count_store, count_store.maybe_0
section_of loader_get_resident_address, loader_prepare_resident


 %if _SYMBOLIC && _DUALCODE && _SYMBOLASMDUALCODE
	usesection lDEBUG_CODE2
 %else
	usesection lDEBUG_CODE
 %endif
section_of bxcx_to_cx_paragraphs
section_of zz_list_symbol.first, zz_list_symbol.subsequent
section_of zz_xms_to_86mm, zz_xms_try_free, zz_xms_try_free_handle
section_of zz_86mm_to_xms, zz_del_match.add_poison_entrypoint
section_of zz_free_nonxms, zz_free_xms, zz_free_reset, zz_free_dos
section_of zz_get_literal, zz_restore_strat, zz_save_strat
section_of zz_switch_s, zz_transfer_buffer


%if _SYMBOLIC
 %if _DUALCODE && _SYMBOLICDUALCODE
	usesection lDEBUG_CODE2
 %else
	usesection lDEBUG_CODE
 %endif
section_of shift_left_4_bxcx
section_of selector_to_segment

section_of anti_normalise_pointer_with_displacement_bxcx, displaystring
section_of getfarpointer.hash, getfarpointer.main, getfarpointer.str
section_of move_delete_farpointer.hash, move_delete_farpointer.hash.sidi
section_of move_delete_farpointer.main, move_delete_farpointer.str.sidi
section_of move_insert_farpointer.hash, move_insert_farpointer.hash.sidi
section_of move_insert_farpointer.main, move_insert_farpointer.main.sidi
section_of move_insert_farpointer.str.sidi
section_of normalise_pointer, normalise_pointer_with_displacement_bxcx
section_of pointer_to_linear
section_of save_slice_farpointer.hash, save_slice_farpointer.main
section_of save_slice_farpointer.str
section_of segment_to_selector
section_of zz_insert, zz_match_symbol, zz_match_symbol.continue, zz_relocate

section_of binsearchhash, binsearchmain
section_of check_second_slice
section_of disp_size_hash, disp_size_main, disp_size_str
section_of displayresult, getstring1, getstring2
section_of increment_ss_ref_count
section_of list_sym_storage_usage, str_index_to_pointer
section_of zz_commit_insert, zz_compact, zz_compact_expand_check_nonxms
section_of zz_compact_expand_check_xms
section_of zz_delete_hash, zz_delete_main, zz_delete_main_and_its_hash
section_of zz_delete_string, zz_expand, zz_expand_common
section_of zz_get_symstr_length_bytes
section_of zz_get_symstr_length_bytes.ssLength_in_cx
section_of zz_get_symstr_length_indices
section_of zz_get_symstr_length_indices.ssLength_in_cx
section_of zz_hash, zz_hash.bx_init
section_of zz_insert_main.linklist, zz_reloc_main, zz_reloc_str
section_of zz_store_pre_str, zz_store_string, zz_store_string.hash_bx
section_of zz_unlink_main_next
%endif

%if _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif
section_of FloatToStr


 %if _DUALCODE && _EXPRDUALCODE
	usesection lDEBUG_CODE2
 %else
	usesection lDEBUG_CODE
 %endif

section_of getofsforbx, checkpointer, getrangeX
section_of getrangeX_have_address_need_length
section_of getrange, getaddr, getofsforbx_remember_bitness
section_of remember_bitness, getaddr_taken
section_of getstr, get_length, get_length_keyword
section_of isbracketorunaryoperator?, isunaryoperator?, isoperator?
section_of ..@call_operator_dispatchers
section_of isseparator?, istype?, isunsignedtype?
section_of handlesegment, handle3byte, handlebyte, handleword
section_of handledword, handlepointer
section_of uoh_abs, uoh_minus, calculate_minus_bxdx, uoh_plus
section_of uoh_not_bitwise, uoh_not_boolean
section_of od_minus, od_plus, od_multiply, od_divide, od_modulo, od_retn
section_of od_above, od_below, od_equal, od_not, od_or, od_and, od_xor
section_of od_cond, od_o, od_a, od_x, od_c, od_string_common
section_of of_modulo, of_minus, of_plus, or_hhtype, of_multiply
section_of set_hhtype, of_divide, of_power
section_of of_compare_below_equal, of_compare_below, of_compare_not_equal
section_of of_compare_equal, of_compare_above_equal, of_compare_above
section_of toboolean, of_helper_compare_true, of_rightop, of_helper_compare
section_of of_shift_right, of_shift_right_signed, of_shift_left
section_of of_helper_getshiftdata, of_bit_mirror
section_of of_or_bitwise, or_hhtype_1, of_or_boolean, of_clr_bitwise
section_of of_and_bitwise, of_and_boolean, of_xor_bitwise, of_xor_boolean
section_of of_helper_retbool, of_helper_getbool

section_of var_cip_setup, var_csp_setup, var_lfsr_setup, var_reverselfsr_setup
section_of var_dpspsel_setup, var_seldbg_setup, var_mt_setup
section_of var_ioi_setup, var_psps_setup, var_ppr_setup, var_ppi_setup
section_of var_iok_setup, var_rhcount_setup
section_of var_get_psp_segment, var_get_psp_selector
section_of var_bootldpunit_setup, var_bootsdpunit_setup, var_bootydpunit_setup
section_of var_bootldppart_setup, var_bootsdppart_setup, var_bootydppart_setup
section_of var_bootunitflags_setup

section_of issymbol?, isvariable?, var_mm_setup, var_ri_setup, var_ysf_setup
section_of default_variable_setup
section_of var_ext_setup
section_of var_ext_setup_done

section_of getstmmxdigit
section_of getexpression.lit_ishexdigit?, getexpression.lit_isdigit?
section_of of_cond, count_unary_operators, count_unary_operators_restrict
section_of get3byte.checksignificantbits, getbyte, checksignificantbitscommon
section_of getnyb, get_value_range

section_of getaddrX, getword, getdword, getexpression

section_of getrangeX.lines, getrangeX.ecx_and_0_valid
section_of getrange.lines, getrange.ecx_and_0_valid, get_length.lines
section_of getrangeX.lines_and_uu, getrange.lines_and_uu
section_of isseparator?.except_L_or_dot, isvariable?.return_name
section_of getword.checksignificantbits, getbyte.checksignificantbits

section_of seg_bx_to_sel
section_of getexpression.countsignificantbits
section_of ..@rr_checksignificantbits, ..@rr_operatordispatchers
section_of ..@rr_operatorfunctions, ..@parsecm_getaddr
section_of stack_check.internal


	usesection INIT
section_of init_uppercase
section_of init_skipwhite, init_skipwh0
section_of init_skipcomma, init_skipcomm0
section_of init_skipequals


	usesection lDEBUG_DATA_ENTRY
section_of entry_retn


		; This is used to refer to the program image parts
		;  that are always to be placed at a fixed position
		;  directly behind the PSP. (These never need to be
		;  relocated in application mode, except if the /T
		;  switch is used.)
%define DATAENTRYTABLESIZE (ldebug_data_entry_size \
			+ asmtable1_size \
			+ asmtable2_size)

		; The following are offsets from the PSP segment,
		;  in application mode. They refer to the sources
		;  of these section images in the program image.
		; Device driver and bootloaded mode early on will
		;  set up ds so that ds:100h -> program image,
		;  mimicking the application mode with a pseudo-PSP
		;  segment address.
		; Device driver mode will relocate everything to end
		;  up with device shim, pseudo MCB (16 bytes), PSP
		;  (256 bytes), data/entry, asmtables, data/stack,
		;  message. After that the auxbuff, history buffer,
		;  code1, code2, environment, and ext segment can be
		;  placed. Finally a single paragraph is reserved
		;  for an SD container MCB.
		; For application mode, data/entry and asmtables
		;  are not usually relocated. The original PSP is
		;  used as is too. Behind the asmtables, the setup
		;  is similar to device mode. The exception is that
		;  the entire resident debugger, process and all,
		;  can be relocated late in init, by using the /T
		;  switch to init.
		; Boot loaded mode will relocate everything to end
		;  up at the top of the LMA, including the "NDEB"
		;  image identification paragraph (always on a KiB
		;  boundary), the pseudo PSP, and then all the
		;  resident sections. (To fill to a KiB-aligned
		;  size, some padding may follow at the end.)
%define MESSAGESECTIONOFFSET (100h + DATAENTRYTABLESIZE)
%define CODESECTIONOFFSET (MESSAGESECTIONOFFSET + messagesegment_size)
%define INITSECTIONOFFSET (CODESECTIONOFFSET+ldebug_code_size+ldebug_code2_size)

		; These targets indicate where to place the code
		;  code sections and auxbuff when boot loaded.
		;  This uses full segments (not truncated).
%define BOOTCODETARGET1 (MESSAGESECTIONOFFSET + messagesegment_nobits_size + datastack_size)
%define BOOTCODETARGET2 (BOOTCODETARGET1+auxbuff_size)
%define BOOTAUXTARGET1 (BOOTCODETARGET1+ldebug_code_size+ldebug_code2_size)
%define BOOTAUXTARGET2 BOOTCODETARGET1
%define BOOTAUXTARGET3 (BOOTAUXTARGET1+auxbuff_size)

%if _BOOTLDR_DISCARD || _BOOTLDR_DISCARD_HELP
		; If the message and/or code section is truncated
		;  then non-boot-loaded modes have to calculate
		;  their layouts with the truncated sizes. The
		;  CODETARGET1 is based on MESSAGESECTIONOFFSET
		;  because CODESECTIONOFFSET already implies the
		;  use of the untruncated message segment.
%define CODETARGET1 (MESSAGESECTIONOFFSET + messagesegment_nobits_truncated_size + datastack_size)
%define CODETARGET2 (CODETARGET1+auxbuff_size)
%define AUXTARGET1 (CODETARGET1+ldebug_code_bootldr_truncated_size+ldebug_code2_size)
%define AUXTARGET2 CODETARGET1
%define AUXTARGET3 (AUXTARGET1+auxbuff_size)
%else
		; If no section is truncated we can just directly
		;  re-use the layouts, same as things were before
		;  the sections were made to be truncated.
%xdefine CODETARGET1 BOOTCODETARGET1
%xdefine CODETARGET2 BOOTCODETARGET2
%xdefine AUXTARGET1 BOOTAUXTARGET1
%xdefine AUXTARGET2 BOOTAUXTARGET2
%xdefine AUXTARGET3 BOOTAUXTARGET3
%endif

		; This calculation allows for all the sections
		;  that are stored in the program image (the
		;  first term) plus the data/stack section,
		;  auxbuff twice (twice for if layout 3 needed),
		;  and the separate history segment. After init
		;  is relocated this high, all destinations for
		;  the to-be resident sections can be written to.
		; (Now also includes the environment block and
		;  the ext code segment.)
		; This is based on the application PSP segment.
		; Update: This may be not large enough for the
		;  aux and ext buffers if _INIT_MAX=0. In this
		;  case, init may have to relocate itself a
		;  second time later to make space for the full
		;  buffers.
%define APPINITTARGET (INITSECTIONOFFSET \
			+ datastack_size \
			+ ext_data_nonboot_add_size \
			+ auxbuff_size + auxbuff_init_size \
			+ historysegment_size \
			+ app_env_size \
			+ extsegment_size)
		; The early stack goes behind the init target.
%define APPINITSTACK_START (APPINITTARGET+init_size)
APPINITSTACK_SIZE equ 512	; must be even
		; This is the size of the application mode
		;  process memory block required while still
		;  using the init stack behind init target.
%define APPINITSTACK_END (APPINITSTACK_START+APPINITSTACK_SIZE)

BOOTINITSTACK_SIZE equ 512	; must be divisible by 16
		; Similar to APPINITTARGET this calculates
		;  the size needed for the maximum size resident
		;  image once installed. The auxbuff size twice,
		;  history segment, data/stack section, and the
		;  INITSECTIONOFFSET are the same here. The final
		;  +16 is for the NDEB image identification
		;  paragraph. The KiB macros are to round up the
		;  size to the next kibi byte boundary as we will
		;  install by modifying the int 12h return value,
		;  which has kibi byte granularity.
		; (Now also includes the environment block and
		;  the ext code segment.)
%define BOOTDELTA	(fromkib(kib(auxbuff_size * 2 \
				+ historysegment_boot_size \
				+ boot_env_size \
				+ extsegment_boot_size \
				+ datastack_size \
				+ INITSECTIONOFFSET + 16)))

%if _DEVICE
		; This is the size of the shim, the pseudo MCB,
		;  as well as the process we will create.
%define DEVICEADJUST	(deviceshim_size + 110h)
		; Like APPINITTARGET, except we need to add
		;  in the device adjust for the additional things
		;  to place before the resident image.
		; The final 10h is for the SD container MCB space
		;  reserved at the end of our resident allocation.
		; Question: Is the PSP size included in the define
		;  INITSECTIONOFFSET redundant with DEVICEADJUST?
		;  Even if this is true it has very little negative
		;  effects, we would simply need 256 more bytes
		;  than would be required.
		; Answer: No, it isn't redundant. The first PSP size
		;  is to translate from the entrypoint ds (where
		;  ds:100h -> program image) to the device header.
		;  The second PSP size is for the actual PSP that is
		;  allocated eventually.
%define DEVICEINITTARGET (INITSECTIONOFFSET \
			+ DEVICEADJUST \
			+ datastack_size \
			+ ext_data_nonboot_add_size \
			+ auxbuff_size + auxbuff_init_size \
			+ historysegment_size \
			+ dev_env_size \
			+ extsegment_size \
			+ 10h)
		; The minus 100h is because the target calculates
		;  from the pseudo PSP location on entry (cs - 10h).
		;  To check the size we can subtract this term so as
		;  to not use more than needed.
		; This adds the shim size as the shim exists in its
		;  own section behind the init section. It will be
		;  discarded at this spot after it was copied to the
		;  resident position of the device header.
		; No temporary stack in or behind the init section is
		;  used because the device installer provides a
		;  perfectly valid stack for us to use.
%define DEVICEINITSIZE (DEVICEINITTARGET - 100h \
			+ init_size \
			+ deviceshim_size)


		; The final resident copy of this device header
		;  is relocated from the shim to in front of our
		;  PSP. Therefore, this space after the PSP can
		;  be re-used for the newly expanded N buffer.
		;  (Refer to N_BUFFER_END.)

		; The device header is of a fixed format.
		;  For our purposes, the 4-byte code for
		;  each the strategy entry and the
		;  interrupt entry is part of this format.
		; (DOS may read the attributes or entrypoint
		;  offsets before calling either, so the
		;  inicomp stage needs to recreate in its
		;  entrypoints part exactly what we have here.)
%macro writedeviceheader 3
	hiddenusesection %1
%2:
.next:
%ifidni %1, DEVICESHIM
	dd -1
%else
	fill 2, -1, jmp strict short j_zero_entrypoint
	dw -1
%endif
.attributes:
	dw 8800h			; character device, open/close supported
		; Setting bit 800h means that opening the device
		;  will already cause a critical error. This
		;  discourages opening the character device.
		;  As we never use it we want to avoid opens.
.strategy:
	dw .strategy_entry %3		; -> strategy entry
.interrupt:
	dw .interrupt_entry %3		; -> interrupt entry
.name:
	fill 8, 32, db "LDEBUG$$"	; character device name
.strategy_entry:
	fill 4, 90h, jmp %2 %+ .device_entrypoint
.interrupt_entry:
	fill 4, 90h, retf
%endmacro

writedeviceheader lDEBUG_DATA_ENTRY, device_header, - 100h
.pass_lcfg:
	dd 0				; (not used yet)
%else
	jmp initcode_j
%endif

		; Startup codes can be discarded after one of
		;  them is used to enter the initialisation part.
		;  Therefore the N buffer is now extended past
		;  these codes, refer to N_BUFFER_END.
%if _BOOTLDR
	align 32, db 0
 %if ($ - $$) != 32
  %error Wrong kernel iniload entrypoint
 %endif
	mov bx, boot_initcode
%endif

%if _BOOTLDR || _DEVICE
		; INP:	cs:0 => program image
		;	INIT:bx -> init entrypoint
device_boot_common_entrypoint:
	mov ax, cs
	sub ax, 10h
	mov ds, ax	; => would-be PSP before program image
	jmp @F
%endif
	align 64, db 0
 %if ($ - $$) != 64
  %error Wrong application entrypoint
 %endif

		; INP:	cs = ds => PSP
j_zero_entrypoint:
initcode_j:
	mov ax, cs
	xor bx, bx

		; INP:	ds:100h = ax:100h -> program image
		;	INIT:bx -> init entrypoint
@@:
	add ax, paras(INITSECTIONOFFSET)
			; => original INIT section in program image
	push ax
	push bx
	retf		; dispatch to INIT code


%if _DEVICE
		; INP:	es:bx -> device request header
		;	ss:sp -> a DOS stack, far return address to DOS
		;	cs:0 -> our start image
		; OUT:	bx = offset of init function in INIT segment
		;	ss:sp -> bx, fl, ds, ax, far return address
device_header.device_entrypoint:
	cmp byte [es:bx + 2], 0		; command code 0 (init) ?
	je @F

	mov word [es:bx + 3], 0100h	; no error, done
	cmp byte [es:bx + 2], 0Eh	; command code 0Eh (close) ?
	je .retf			; yes, accept it -->
	mov word [es:bx + 3], 8103h	; error, done, code: unknown command
.retf:	retf

@@:
	push ax
	push ds
	pushf
	push bx
	mov bx, device_initcode
	jmp device_boot_common_entrypoint


writedeviceheader DEVICESHIM, shim_device_header, - 0
shim_device_header.device_entrypoint:
	mov word [es:bx + 3], 0100h	; no error, done
	cmp byte [es:bx + 2], 0Eh	; command code 0Eh (close) ?
	je .retf			; yes, accept it -->
	mov word [es:bx + 3], 8103h	; error, done, code: unknown command
.retf:	retf

	align 16
deviceshim_size	equ $ - section.DEVICESHIM.vstart
	endarea deviceshim, 1


	usesection lDEBUG_DATA_ENTRY
%else
deviceshim_size	equ 0
%endif


	align 2, db 0
N_BUFFER_END equ $		; end of N buffer (starts in PSP at 80h)

cmdlist:	repeatdw aa,bb,cc,ddd,ee,ff,gg,hh,ii,error,kk,ll,mm,nn,oo
		repeatdw pp,qq,rr,sss,tt,uu,vv,ww,xx,yy
%if _SYMBOLIC
		dw zz
%endif

%if _LOADER
aa equ error
cc equ error
kk equ error
ll equ error
nn equ error
pp equ error
tt equ error
uu equ error
ww equ error
%endif


%include "options.mac"

	align 4, db 0
				; options, startoptions and internalflags
				; have to be consecutive
options:	dd DEFAULTOPTIONS ; run-time options
options2:	dd DEFAULTOPTIONS2
options3:	dd DEFAULTOPTIONS3
options4:	dd DEFAULTOPTIONS4
options5:	dd 0
options6:	dd DEFAULTOPTIONS6
options7:	dd DEFAULTOPTIONS7
	; options, options2, options3, options4, options5, options6, options7
	;  are each assumed to be dwords
	;  and all consecutive in expr.asm isvariable?

startoptions:	dd DEFAULTOPTIONS ; options as determined during startup; read-only for user
startoptions2:	dd DEFAULTOPTIONS2
startoptions3:	dd DEFAULTOPTIONS3
startoptions4:	dd DEFAULTOPTIONS4
startoptions5:	dd 0
startoptions6:	dd DEFAULTOPTIONS6
startoptions7:	dd DEFAULTOPTIONS7
	; startoptions, startoptions2, startoptions3, startoptions4,
	;  startoptions5, startoptions6
	;  are each assumed
	;  to be dwords and all consecutive in expr.asm isvariable?

internalflags:	dd attachedterm|pagedcommand|notstdinput|inputfile|notstdoutput|outputfile|debuggeeA20|debuggerA20
				; flags only modified by DEBUG itself
internalflags2:
%if _SYMBOLIC
		dd dif2_sym_req_xms | dif2_sym_req_86mm
%else
		dd 0
%endif
internalflags3:	dd dif3_partition_changed | dif3_at_line_end
internalflags4:	dd 0
internalflags5:	dd 0
internalflags6:
%if _DEBUG && ! _DEBUG_COND
		dd dif6_debug_mode	; always indicate debuggable
%else
		dd 0
%endif
internalflags7:	dd 0
	; internalflags, internalflags2, internalflags3, internalflags4,
	;  internalflags5, internalflags6, internalflags7
	;  are each assumed
	;  to be dwords and all consecutive in expr.asm isvariable?

%if !_LOADER
asm_options:	dd DEFAULTASMOPTIONS
asm_startoptions:
		dd DEFAULTASMOPTIONS

gg_first_cseip_linear:	dd 0
gg_next_cseip_linear:	dd 0
tpg_possible_breakpoint:dd 0
gg_deferred_message:	dw msg.empty_message
bb_deferred_message_in_lineout_behind:
			dw 0
		align 4, db 0
tpg_proceed_bp:		times BPSIZE db 0
%if _DEBUG1
		align 2, db 0
test_records_Readmem:		times 6 * 16 db 0
test_records_Writemem:		times 6 * 16 db 0
test_records_getLinear:		times 6 * 16 db 0
test_records_getSegmented:	times 6 * 16 db 0

test_readmem_value:		db 0
%endif
pp_instruction:		db 0
%if _RH
skip_rh:	db 0
%endif
%if _RH || _SWHILEBUFFER || _RECMDWHILEBUFFER || _EXTENSIONS
in_re:		db 0
%endif
		align 4, db 0
pp_operand:		dd 0
%if _PM && _DEBUG_PM_ENTRY
debug_pm_entry_total:	dd 0
debug_pm_entry_esph:	dd 0
debug_pm_entry_b:	dd 0
%endif
%endif
%if _LOADER
currentposition:	db 2	; 0 = bottom, 1 = top, 2 = hidden
			db 0
%endif
		align 2, db 0
code_seg:	dw 0		; LOADER-relocate
%if _PM
code_sel:	dw 0
%endif
%if _DUALCODE
code2_seg:	dw 0
 %if _PM
code2_sel:	dw 0
 %endif
%endif
%if _IMMASM
immseg:		dw 0
%endif
%if _MESSAGESEGMENT
 %if _LINK
linkinfoaddress:dw linkinfo
 %endif
messageseg:	dw 0		; LOADER-relocate
 %if _PM
messagesel:	dw 0
 %endif
%endif
%if _EXTENSIONS
extseg:		dw 0		; LOADER-relocate
 %if _PM
extcssel:	dw 0
 %if extseg + 2 != extcssel
  %error code_ret_to_ext expects extcssel after extseg
 %endif
extdssel:	dw 0
 %else
extcssel equ extseg
extdssel equ extseg
 %endif
%endif
%if _APP_ENV_SIZE || _DEV_ENV_SIZE || _BOOT_ENV_SIZE
envseg:		dw 0		; (includes pseudo-MCB)	; LOADER-relocate
env_size:	dw 0
%endif

entryseg_size:		dw 100h + DATAENTRYTABLESIZE + datastack_size
code_size:		dw ldebug_code_size	; may truncate
%if _DUALCODE
code2_size:		dw ldebug_code2_size
%endif
%if _HISTORY_SEPARATE_FIXED && _HISTORY
historyseg_size:	dw historysegment_boot_size
%endif
%if _MESSAGESEGMENT
messageseg_size:	dw messagesegment_nobits_size	; may truncate
%endif
%if _HELP_COMPRESSED && _BOOTLDR_DISCARD_HELP
indirect_hshrink_message_buffer:
			dw nontruncated_hshrink_message_buffer
%endif
%if _EXTENSIONS
extseg_size:		dw extsegment_size
extseg_used:		dw 0
extdata:		dw ext_data_area
extdata_size:		dw ext_data_area.size
extdata_used:		dw 0
ext_command_handler:	dw 0
ext_preprocess_handler:	dw 0
ext_inject_handler:	dw 0
%if !_LOADER
ext_aa_before_getline_handler:	dw 0
ext_aa_inject_handler:		dw 0
ext_aa_after_getline_handler:	dw 0
%endif
ext_puts_handler:	dw 0
ext_puts_copyoutput_handler:	dw 0
%if _EXT_PUTS_GETLINE
ext_puts_getline_handler:	dw 0
%endif
%if _CATCHINT2D
ext_amis_handler:	dw 0
%endif
%if _EXT_PUTS_GETLINE
in_getinput:		db 0
			db 0	; reserved, zeroed in cmd3
%endif
pm_2_86m_0:		dw 0	; for fast, fl preserving dispatch

extcall_table:
.ret:
	dw entry_ret_to_ext
 %if _PM
	dw 0
 %endif
	dw code_ret_to_ext
 %if _PM
	dw 0
 %endif
 %if _DUALCODE
	dw code2_ret_to_ext
  %if _PM
	dw 0
  %endif
 %endif

.segsel:
	dw pspdbg
 %if _PM
	dw cssel
 %endif
	dw code_seg
 %if _PM
	dw code_sel
 %endif
 %if _DUALCODE
	dw code2_seg
  %if _PM
	dw code2_sel
  %endif
 %endif
%endif
alloc_size:		dw 0
alloc_seg:		dw 0	; LOADER-relocate
%if !_LOADER
next_reserved_segment:	dw 0
alloc_size_not_reserved:dw 0
reserved_address:	dw 0
%endif

		db 13
		align 16, db 13	; insure the cmdline_buffer is prefixed by CR
cmdline_buffer:
.size:		equ _RC_BUFFER_SIZE
		times .size db 0
.end:
.position:	dw cmdline_buffer

%if !_LOADER
		db 13
		align 2, db 13	; insure the re_buffer is prefixed by CR
re_buffer:
.size:		equ _RE_BUFFER_SIZE
		fill .size,0,db "@R"
.end:
.position:	dw re_buffer

		align 4, db 0
re_count:	dd 0
re_limit:	dd 256
%endif
		align 4, db 0
rc_count:	dd 0
rc_limit:	dd 4096

		align 2, db 0
cmd3_set_options:		dw 0
%if _PM
auxbuff_switchbuffer_size:	dw 0
%endif
auxbuff_segorsel:segonlyaddress		; LOADER-relocate
%if !_LOADER
auxbuff_behind_last_silent:
		dw 0		; -> behind last silent buffer entry
%if _RH
auxbuff_start_silent:
		dw 0
auxbuff_amount_silent:
		dw 0
%endif
%endif
auxbuff_current_size:
		dw auxbuff_size
%if _LINK || _AUXBUFFSIZE != _AUXBUFFMAXSIZE
auxbuff_current_size_minus_24:
		dw auxbuff_size - 24
%endif

%if !_LOADER
tt_silent_mode_number:
		dw 0		; if non-zero: maximum amount of dumps
				;  displayed after T/TP/P while silent
%endif
%if _SYMBOLIC
created_psp:	dw 0
created_size:	dw 0
%endif
%if _CONFIG
yy_boot_attempt_open_stack:	dw 0
yy_boot_attempt_open_pathname:	dw 0
%endif

%if _INPUT_FILE_HANDLES
		align INPUTFILEHANDLE_size
input_file_handles:
		times _INPUT_FILE_HANDLES * INPUTFILEHANDLE_size db -1
.active:	dw 0
.to_close:	dw 0
%endif
	align 2, db 0
indos_remember_seek_function:	dw 4201h
indos_remember_seek_handle:	dw -1
	align 4, db 0
indos_remember_seek_offset:	dd 0
%if _INPUT_FILE_BOOT
	align 4, db 0
boot_remember_seek_offset:	dd 0
	align 2, db 0
boot_remember_seek_handle:	dw -1
%endif
 %if _EXTENSIONS
	alignb 2
ext_handle:	dw -1
 %endif

charcounter:	db 0		; used by raw output to handle tab
linecounter:	db 0		; used by paging in puts
	align 4, db 0
savesp:		dw stack_end	; saved stack pointer
		dw 0		; 0 to set high word of esp
%if !_LOADER
re_sp:		dw 0
%endif
errret:		dw cmd3		; return here if error
throwret:	dw errhandler	; return here if error - priority, no display
throwsp:	dw stack_end	; stack pointer set before jumping to throwret
run_sp:		dw 0		; stack pointer when running
		dw 0		; (zero for esph)
run_sp_reserve:	dw 0		; additional space to reserve between the
				;  near return address of run and the run_sp
spadjust:	dw 40h 		; adjust sp by this amount for save
%if _SYMBOLIC
stack_low_address:
		dw str_buffer	; low end of stack, default = str_buffer
%endif

%if !_LOADER
pspdbe:		dw 0		; debuggee's PSP (86M segment)
				;  unless DIF&attachedterm or bootloaded
%endif
%ifn _MESSAGESEGMENT
 %if _LINK
linkinfoaddress:dw linkinfo
 %endif
%endif
pspdbg:		dw 0		; debugger's PSP (86M segment)	; LOADER-relocate
%if !_LOADER
	align 4, db 0
run2324:	dd 0,0		; debuggee's interrupt vectors 23h and 24h (both modes)
%if _PM
		dd 0
dbg2324:	dw i23pm, i24pm
%endif
%if _VDD
hVdd:		dw -1		; NTVDM VDD handle
%endif
	align 4, db 0
sav2324:	dd 0,0		; debugger's interrupt vectors 23h and 24h (real-mode only)
hakstat:	db 0		; whether we have hacked the vectors or not
%if _MS_0RANGE_COMPAT
getrange_is_uu:	db 0
%endif
	align 4, db 0
psp22:		dd 0		; original terminate address from our PSP
parent:		dw 0		; original parent process from our PSP (must follow psp22)
%if _MCB || _INT || _EXTENSIONS
firstmcb:	dw -1		; start of MCB chain (always segment)
firstumcb:	dw -1
%endif
pInDOS:		segofs16address	; far16 address of InDOS flag (bimodal)
%if _USESDA
pSDA:		segofs16address minusone
				; far16 address of SDA (bimodal)
%endif
%endif
machine:	db 0		; type of processor for assembler and disassembler (1..6)
has_87:		db 0		; if there is a math coprocessor present
mach_87:	db 0		; type of coprocessor present
encodedmach87:	db 0		; C0 = no coproceasor, C = coprocessor present,
				;  C2 = 287 present on a 386
%if _MMXSUPP
has_mmx:	db 0
%endif
%if _VXCHG
 %ifn _VXCHGBIOS
		align 4, db 0
xmsdrv:	dd 0		; XMM driver address, obtained thru int 2F, ax=4310h
xmsmove:istruc XMSM	; XMS block move struct, used to save/restore screens
	iend
 %endif
align 2, db 0
csrpos:	dw 0		; cursor position of currently inactive screen
vrows:	db 0		; current rows; to see if debuggee changed video mode
%endif
%if _ALTVID		; exchange some video BIOS data fields for option /2.
oldcsrpos:	dw 0	; cursor position
oldcrtp:	dw 0	; CRTC port
oldcols:	dw 80	; columns
oldmr:	; label word
oldmode:	db 0	; video mode
oldrows:	db 24	; rows - 1
%endif
bInDbg:		db 1		; 1=debugger is running
notatty:	db 10		; if standard input is from a file
				; this is also used for a linebreak processing hack
vpage:		db 0		; video page the debugger is to use for BIOS output
%if _MCLOPT
master_pic_base:db _MCLOPT_DEFAULT
%endif
switchar:	db 0		; switch character
swch1:		db ' '		; switch character if it's a slash
	align 2, db 0
dd_default_length:
		dw 80h
dd_default_lines:
		dw 0
%if !_LOADER
uu_default_length:
		dw 20h
uu_default_lines:
		dw 0
%endif
promptlen:	dw 0		; length of prompt
bufnext:	dw line_in+2	; address of next available character
bufend:		dw line_in+2	; address + 1 of last valid character
%if _EXTENSIONS
indirect_array_errfix:
		dw 0		; -> errfix.eld's array, 0 if none installed
%endif
rc:		dw 0
priorrc:	dw 0
erc:		dw 0
%if _HISTORY
history:
 %if _HISTORY_SEPARATE_FIXED
.segorsel:	segonlyaddress	; LOADER-relocate
.first:		dw historysegment_boot_size - 2
.last:		dw historysegment_boot_size - 2
 %else
.first:		dw historybuffer.end - 2
.last:		dw historybuffer.end - 2
 %endif
%endif

var_addr_entries:
%if !_LOADER
a_addr:		segmentedaddress; address for next A command
%endif
d_addr:		segmentedaddress; address for next D command; must follow a_addr
%if !_LOADER
behind_r_u_addr:segmentedaddress; address behind R's disassembly
u_addr:		segmentedaddress; address for next U command; must follow d_addr
%endif
e_addr:		segmentedaddress; address for current/next E command
%if _DSTRINGS
dz_addr:	segmentedaddress; address for next ASCIZ string
dcpm_addr:	segmentedaddress; address for next $-terminated string
dcount_addr:	segmentedaddress; address for next byte-counted string
dwcount_addr:	segmentedaddress; address for next word-counted string
%endif
var_addr_entries.amount equ ($ - var_addr_entries) / SEGADR_size
%if _DX
x_addr:		dd 0		; (phys) address for next DX command
%endif
%if _DSTRINGS
dstringtype:	db 0		; FFh byte-counted, FEh word-counted, else terminator byte
	align 2, db 0
dstringaddr:	dw dz_addr	; -> address of last string
%endif
%if _INT
	align 4, db 0
intaddress:	dd 0
lastint_is_86m_and_mcb:
		dw 0
lastint:	db 0
%endif
%if !_LOADER
	align 4, db 0
search_results_amount: equ 16
search_results:
%if _PM
		times 6 * search_results_amount db 0
%else
		times 4 * search_results_amount db 0
%endif
	align 4, db 0
sscounter:	dd 0
eqflag:		db 0		; flag indicating presence of `=' operand
	align 2, db 0
eqladdr:	dw 0,0,0	; address of `=' operand in G, P and T command
	align 2, db 0
run_int:	dw 0		; interrupt type that stopped the running
%endif
lastcmd:	dw dmycmd
%if _EXTENSIONS
lastcmd_transfer_ext_address:
		dw 0
near_transfer_ext_address:
		dw 0
%endif
%if !_LOADER
fileext:	db 0		; file extension (0 if no file name)
EXT_OTHER	equ 1
EXT_COM		equ 2
EXT_EXE		equ 4
EXT_HEX		equ 8
%endif

	align 4, db 0
mmxbuff:	dd 0		; buffer with a (read-only) part of MMX register
				; for access from within expressions
%if _CATCHINT08
intr8_counter:	dw 0
intr8_limit:	dw 18 * 5	; ca 5 seconds
%endif
%if !_LOADER
maxmachinetype:	db 0
%endif
serial_rows:
		db 24
serial_columns:
		db 80
serial_keep_timeout:
		db 15
%if _USE_TX_FIFO
serial_fifo_size:
		db _BI_TX_FIFO_SIZE
			; size of built-in TX fifo (1 is as if no FIFO)
%endif
serial_flags:
		db 0
sf_init_done:	equ 1
sf_ctrl_c:	equ 2
sf_double_ctrl_c:	equ 4
sf_built_in_fifo:	equ 8
sf_use_serial:		equ 10h

serial_var_intnum:	db _INTNUM
serial_var_params:	db _UART_PARAMS
serial_var_fifo:	db _UART_FIFO
	align 2, db 0
serial_var_baseport:	dw _UART_BASE
serial_var_dl:		dw _UART_RATE
serial_var_irqmask:	dw _OFFMASK

io_rows:	db 1
io_columns:	db 1
	align 2, db 0
%if _40COLUMNS
io_columns_getline:	dw 0			; byte variable zero-extended
%endif
io_start_buffer:	dw 0
io_end_buffer:		dw 0
io_levels:		dw 255
io_flags:		dw DEFAULTIOFLAGS
iof_extra_iol_for_tpg_re:	equ 1
iof_extra_iol_for_rc:		equ 2
DEFAULTIOFLAGS equ iof_extra_iol_for_tpg_re | iof_extra_iol_for_rc

	align 2, db 0
getline_timer_count:	dw 0
getline_timer_last:	dw 0
getline_timer_func:	dw dmycmd

%if _INPUT_FILE_HANDLES || _INPUT_FILE_BOOT
	align 2, db 0
if_exists_then_address:	dw 0
if_exists_si:		dw 0
if_exists_sp:		dw 0
if_exists_length:	dw 0
%endif

	align 2, db 0
terminator_in_line_in:
.offset:		dw 0
.value:			db 0

%if !_LOADER
qqtermcode:		db 0
	align 2, db 0
device_mcb_paragraphs:	dw 0
device_header_address:	dd 0
%endif

	align 2, db 0
inttab_optional:
.:
serial_installed_intnum: equ $
	dw 100h
	dw serial_interrupt_handler
	dw dif4_int_serial_hooked
%if _PM
	dw 12Fh
	dw debug2F
	dw dif4_int_2F_hooked
%endif
%if _CATCHINT08
	dw 108h
	dw intr8
	dw dif4_int_08_hooked
%endif
%if _CATCHINTFAULTCOND && _CATCHINT0D
	dw 10Dh
	dw intr0D
	dw dif4_int_0D_hooked
%endif
%if _CATCHINTFAULTCOND && _CATCHINT0C
	dw 10Ch
	dw intr0C
	dw dif4_int_0C_hooked
%endif
.amount: equ ($ - .) / 6
	dw 0


amisintr_offset:
	dw inttab

inttab_pre:
	times 3 * inttab_optional.amount db 0

inttab:
%if _CATCHINT00
	db 0
	dw intr0	; table of interrupt initialization stuff
%endif
%if _CATCHINT01
	db 1
	dw intr1
%endif
%if _CATCHINT03
	db 3
	dw intr3
%endif
%if _CATCHINT07
	db 7
	dw intr7
%endif
%if ! _CATCHINTFAULTCOND && _CATCHINT0C
	db 0Ch
	dw intr0C
%endif
%if ! _CATCHINTFAULTCOND && _CATCHINT0D
	db 0Dh
	dw intr0D
%endif
%if _CATCHINT18
	db 18h
	dw intr18
%endif
%if _CATCHINT19
	db 19h
	dw intr19
%endif
%if _CATCHSYSREQ
	db _SYSREQINT
	dw intr_sysreq
%endif
%if _CATCHINT06
.i06:	db 6
	dw intr6
%endif
	endarea inttab
	inttab_number equ inttab_size / 3
%if _CATCHINT2D
.i2D:	db 2Dh
	dw int2D
%endif


intforcetab:
%if _CATCHINT00
	db opt4_int_00_force >> 24
%endif
%if _CATCHINT01
	db opt4_int_01_force >> 24
%endif
%if _CATCHINT03
	db opt4_int_03_force >> 24
%endif
%if _CATCHINT07
	db opt4_int_07_force >> 24
%endif
%if _CATCHINT0C
	db opt4_int_0C_force >> 24
%endif
%if _CATCHINT0D
	db opt4_int_0D_force >> 24
%endif
%if _CATCHINT18
	db opt4_int_18_force >> 24
%endif
%if _CATCHINT19
	db opt4_int_19_force >> 24
%endif
%if _CATCHSYSREQ
 %if _SYSREQINT == 09h
	db opt4_int_09_force >> 24
 %elif _SYSREQINT == 15h
	db opt4_int_15_force >> 24
 %else
  %error Unknown SysReq interrupt
 %endif
%endif
%if _CATCHINT06
	db opt4_int_06_force >> 24
%endif

%if _CATCHINT06 && _DETECT95LX
	align 2, db 0
inttab_number_variable:
	dw inttab_number
%endif


%if !_LOADER
	align 2, db 0
		; Parameter block for EXEC call
execblk:
		dw 0		;(00) zero: copy the parent's environment
.cmdline:	dw 0,0		;(02) address of command tail to copy
.fcb1:		dw 5Ch,0	;(06) address of first FCB to copy
.fcb2:		dw 6Ch,0	;(10) address of second FCB to copy
.sssp:		dw 0,0		;(14) initial SS:SP
.csip:		dw 0,0		;(18) initial CS:IP
%endif


		; Register save area (32 words).
		; must be DWORD aligned, used as stack
	align 4, db 0
regs:
reg_eax:	dd 0	;+00 eax
reg_ebx:	dd 0	;+04 ebx
reg_ecx:	dd 0	;+08 ecx
reg_edx:	dd 0	;+12 edx
reg_esp:	dd 0	;+16 esp
reg_ebp:	dd 0	;+20 ebp
reg_esi:	dd 0	;+24 esi
reg_edi:	dd 0	;+28 edi
reg_ds:		dd 0	;+32  ds (high word unused)
reg_es:		dd 0	;+36  es (high word unused)
reg_ss:		dd 0	;+40  ss (high word unused)
reg_cs:		dd 0	;+44  cs (high word unused)
reg_fs:		dd 0	;+48  fs (high word unused)
reg_gs:		dd 0	;+52  gs (high word unused)
reg_eip:	dd 0	;+56 eip
reg_efl:	dd 0	;+60 efl(ags)
regs.end:
regs.size: equ regs.end - regs

%if _REGSHIGHLIGHT
regs_prior:
.:
	times 16 dd 0
.end:
.size: equ .end - .

%if .size != regs.size
 %error regs prior save area size mismatch
%endif
%endif

%if _DEVICE
device_quittable_regs:
.:
	times 16 dd 0
.end:
.size: equ .end - .

%if .size != regs.size
 %error regs prior save area size mismatch
%endif
%endif

%if _VARIABLES
vregs:
.amount: equ 256
	times .amount dd 0	; internal v0..vff
%endif

; possible byte encoding of lDebug variables for dynamic computations:
; xxxxyyyy
; 10: register
;   xx: size (0 = 1, 1 = 2, 2 = 4)
;     yyyy: 0..15: register as stored in the register save area
;                  as SIL, DIL, BPL, SPL aren't supported these map to xH
;                  xSL, IPL and FLL are invalid, ExS are invalid
; 1011: variable
;     yyyy: which variable. variables are always dword-sized
; 11000000: 32-bit compound, next byte stores: xxxxyyyy first, second 16-bit reg
; 11000001..11111111: available for encoding other compound regs, vars, indirection,
;                     symbols, types etc
; 0xxxxxxx: operators


%if !_LOADER
; Instruction set information needed for the 'p' command.
; ppbytes and ppinfo needs to be consecutive.
ppbytes:db 66h,67h,26h,2Eh,36h,3Eh,64h,65h,0F2h,0F3h,0F0h	; prefixes
PPLEN_ONLY_PREFIXES	equ $-ppbytes
.string:
	db 0ACh,0ADh,0AAh,0ABh,0A4h,0A5h	; lods,stos,movs
	db 6Ch,6Dh,6Eh,6Fh			; ins,outs
	db 0A6h,0A7h,0AEh,0AFh			; cmps,scas
PPLEN_ONLY_STRING	equ $-ppbytes
.string_amount:		equ $ - .string
	db 0CCh,0CDh				; int instructions
	db 0E0h,0E1h,0E2h			; loop instructions
	db 0E8h					; call rel16/32
	db 09Ah					; call far seg16:16/32
;	(This last one is done explicitly by the code.)
;	db 0FFh					; FF/2 or FF/3: indirect call

PPLEN	equ	$-ppbytes	; size of the above table

;	Info for the above, respectively. This MUST follow
;	 immediately after ppbytes, as we add + PPLEN - 1 to
;	 di after repne scasb to index into this (ppinfo).
;	80h = prefix; 82h = operand size prefix; 81h = address size prefix.
;	If the high bit is not set, the next highest bit (40h) indicates
;	 that the instruction size depends on whether there is an operand
;	 size prefix; if set, under o32 two bytes are added to the size.
;	 (This is only used for direct near and far call.)
;	If both the two highest bits are clear, then PP_STRDEST,
;	 PP_STRSRC, or PP_STRSRC2 may be set. This only happens for
;	 string instructions, which always are neither prefixes nor
;	 use additional bytes.
;	The remaining bits tell the number of additional bytes in the
;	 instruction. This is at most 4. It must be below-or-equal to
;	 7, or if PP_VARSIZ is used, 5 (so the sum stays below 8).

PP_ADRSIZ	equ 01h
PP_OPSIZ	equ 02h
PP_PREFIX	equ 80h
PP_VARSIZ	equ 40h
PP_STRDEST	equ 20h
PP_STRSRC	equ 10h
PP_STRSRC2	equ 08h
PP_SIZ_MASK	equ 07h

ppinfo:	db PP_PREFIX | PP_OPSIZ, PP_PREFIX | PP_ADRSIZ
	times 9 db PP_PREFIX		; prefixes
	db PP_STRSRC, PP_STRSRC		; lods
	db PP_STRDEST, PP_STRDEST	; stos
	db PP_STRDEST | PP_STRSRC, PP_STRDEST | PP_STRSRC
					; movs
	db PP_STRDEST, PP_STRDEST	; ins
	db PP_STRSRC, PP_STRSRC		; outs
	db PP_STRSRC2 | PP_STRSRC, PP_STRSRC2 | PP_STRSRC
					; cmps
	db PP_STRSRC2, PP_STRSRC2	; scas
	db 0,1				; int
	db 1,1,1			; loop
	db PP_VARSIZ | 2		; call rel16/32 with displacement
	db PP_VARSIZ | 4		; call far 16:16 or 16:32 immediate

%if PPLEN != $-ppinfo
 %error "ppinfo table has wrong size"
%endif


;	Equates for instruction operands.
;	First the sizes.

OP_ALL		equ 40h		; byte/word/dword operand (could be 30h but ...)
OP_1632		equ 50h		; word or dword operand
OP_8		equ 60h		; byte operand
OP_16		equ 70h		; word operand
OP_32		equ 80h		; dword operand
OP_64		equ 90h		; qword operand
OP_1632_DEFAULT	equ 0A0h	; word or dword or default opsize
OP_1632_DEFAULT_ASM	equ 0B0h; asm: word or dword or default opsize,
				;  disasm: word or dword

OP_SIZE	equ OP_ALL		; the lowest of these

;	These operand types need to be combined with a size.
;	Bits 0 to 3 give one of these types (maximum 15),
;	 and bits 4 to 7 specify the size. Table entries
;	 for these are identified by detecting that they
;	 are above-or-equal OP_SIZE.
;	The first parameter to the opsizeditem macro is the
;	 name of the item. It has to match the names used in
;	 the instr.key and debugtbl.inc files.
;	The second parameter is the entry for bittab that
;	 is used by aa.asm (the assembler).
;	The third parameter is the suffix used to create the
;	 entry for asmjmp (prefix aop_) and disjmp2 (dop_).

%macro opsizeditem 3.nolist
 %1 equ nextindex
 %xdefine BITTAB_OPSIZEDITEMS BITTAB_OPSIZEDITEMS,%2
 %xdefine ASMJMP_OPSIZEDITEMS ASMJMP_OPSIZEDITEMS,aop_%3
 %xdefine DISJMP2_OPSIZEDITEMS DISJMP2_OPSIZEDITEMS,dop_%3
 %assign nextindex nextindex + 1
%endmacro
%assign nextindex 0
%define BITTAB_OPSIZEDITEMS ""
%define ASMJMP_OPSIZEDITEMS ""
%define DISJMP2_OPSIZEDITEMS ""
opsizeditem OP_IMM,	ARG_IMMED,	imm	; immediate
opsizeditem OP_IMM_NOT_EXTEND,	ARG_IMMED,	imm_not_extend	; immediate
		; This one is for showing the WORD/DWORD size keyword if the
		;  value could fit into an imms8.
opsizeditem OP_RM,ARG_DEREF+ARG_JUSTREG,rm	; reg/mem
opsizeditem OP_M,	ARG_DEREF,	m	; mem (but not reg)
opsizeditem OP_R_MOD,	ARG_JUSTREG,	r_mod	; register, determined from MOD R/M part
opsizeditem OP_MOFFS,	ARG_DEREF,	moffs	; memory offset; e.g., [1234]
opsizeditem OP_R,	ARG_JUSTREG,	r	; reg part of reg/mem byte
opsizeditem OP_R_ADD,	ARG_JUSTREG,	r_add	; register, determined from instruction byte
opsizeditem OP_AX,	ARG_JUSTREG,	ax	; al or ax or eax
%if nextindex > 16
 %error Too many op sized items
%endif

;	These don't need a size.
;	Because the size needs to be clear to indicate
;	 that one of these is to be used, the maximum
;	 value for these is 63 (as 64 is OP_SIZE).
;	The minimum value for these is 1 because a 0
;	 without size means the end of an op list (OP_END).
;	The first parameter to the opitem macro is the name
;	 of the item. It has to match the names used in the
;	 instr.key and debugtbl.inc files.
;	The second parameter is the entry for bittab that
;	 is used by aa.asm (the assembler). The third
;	 parameter is the entry for asmjmp.
;	The fourth parameter is the entry for optab as used
;	 by uu.asm (the disassembler).
;
;	asm_siznum contains entries for OP_M64 to OP_MXX.
;	 (The order has to match their opitem order.)
;	asm_regnum contains entries for OP_DX to OP_GS.
;	 (The order has to match their opitem order.)

%macro opitem 4.nolist
 %1 equ nextindex
 %xdefine BITTAB_OPITEMS BITTAB_OPITEMS,%2
 %xdefine ASMJMP_OPITEMS ASMJMP_OPITEMS,%3
 %xdefine OPTAB_OPITEMS OPTAB_OPITEMS,%4
 %assign nextindex nextindex + 1
%endmacro
	OP_END	equ 0
%assign nextindex 1
%define BITTAB_OPITEMS ""
%define ASMJMP_OPITEMS ""
%define OPTAB_OPITEMS ""
	; order of the following (ao17 entries) must match asm_siznum in aa.asm
OP_FIRST_ASM_SIZNUM	equ nextindex	; corresponding to asm_siznum start
opitem OP_M64,		ARG_DEREF,	ao17,dop_m64	; qword memory (obsolete?)
opitem OP_MFLOAT,	ARG_DEREF,	ao17,dop_mfloat	; float memory
opitem OP_MDOUBLE,	ARG_DEREF,	ao17,dop_mdouble; double-precision floating memory
opitem OP_M80,		ARG_DEREF,	ao17,dop_m80	; tbyte memory
opitem OP_MXX,		ARG_DEREF,	ao17,dop_mxx	; memory (size unknown)
opitem OP_FARIMM,	ARG_FARADDR,	ao21,dop_farimm	; far16/far32 immediate
opitem OP_FAR_OFFSET,	ARG_IMMED,	aop_faroffset,da_internal_error	; far offset-only
opitem OP_REL8,		ARG_IMMED,	ao23,dop_rel8	; byte address relative to IP
opitem OP_REL1632,	ARG_IMMED,	ao25,dop_rel1632; word or dword address relative to IP
opitem OP_1CHK,		ARG_WEIRDREG,	ao29,dop49	; check for ST(1)
opitem OP_STI,		ARG_WEIRDREG,	aop_sti,dop_sti	; ST(I)
opitem OP_CR,		ARG_WEIRDREG,	aop_cr,dop_cr	; CRx
opitem OP_DR,		ARG_WEIRDREG,	ao34,dop_dr	; DRx
opitem OP_TR,		ARG_WEIRDREG,	ao35,dop_tr	; TRx
opitem OP_SEGREG,	ARG_WEIRDREG,	ao39,dop_segreg	; segment register
opitem OP_IMMS8,	ARG_IMMED,	ao41,dop_imms8 	; sign extended immediate byte
opitem OP_IMMS8_EXTEND,	ARG_IMMED,	ao41_extend,dop_imms8		; add etc word/dword r/m, imms8
opitem OP_IMM8,		ARG_IMMED,	ao42,dop_imm8	; immediate byte (other args may be (d)word)
opitem OP_IMM8_OPTIONAL,ARG_IMMED,	ao42,dop_imm8_optional
opitem OP_IMM8_INT,	ARG_IMMED,	ao42,dop_imm8_int		; immediate byte for int
opitem OP_IMM8_SHIFTROT,ARG_IMMED,	ao42,dop_imm8_shiftrot		; immediate byte for shift/rotate
opitem OP_MMX,		ARG_WEIRDREG,	aop_mmx,dop_mmx	; MMx
opitem OP_MMX_MOD,	ARG_WEIRDREG,	aop_mmx_mod,dop_mmx_mod	; MMx, but in ModR/M part
opitem OP_X,		0FFh,	ao_x,	dop_x		; OPX byte follows
opitem OP_SHOSIZ,	0FFh,	ao44,	dop_shosiz	; set flag to always show the size
opitem OP_ASM_MOVX,	0FFh,	ao_movx,dop60a		; enforce next operand size to be specified
opitem OP_SHORT,	0FFh,	ao_short,dop_short	; allow short keyword
opitem OP_NEAR,		0FFh,	ao_near,dop_near	; allow near keyword
opitem OP_FAR,		0FFh,	ao_far,	dop_far		; allow far keyword
opitem OP_FAR_REQUIRED,	0FFh,	ao_far_required,dop_far_required	; require far keyword
opitem OP_FAR_M,	0FFh,	ao_modifier_continue,dop_far_m		; les, lds, lss, lfs, lgs, or jmp/call far mem
opitem OP_DOUBLE_M,	0FFh,	ao_modifier_continue,dop_double_m	; bound
opitem OP_M_SRC,	0FFh,	ao_modifier_continue,dop_m_src
opitem OP_M_DST,	0FFh,	ao_modifier_continue,dop_m_dst
opitem OP_M_SRC_DST,	0FFh,	ao_modifier_continue,dop_m_src_dst
opitem OP_STACK_PUSH,	0FFh,	ac09_internal_error,dop_stack_push
opitem OP_STACK_POP,	0FFh,	ac09_internal_error,dop_stack_pop
opitem OP_STACK_SPECIAL,0FFh,	ac09_internal_error,dop_stack_special
opitem OP_M_ALWAYS_16,	0FFh,	ao_m_always_16,dop_m_always_16
opitem OP_E_CX,	ARG_JUSTREG,	aop_e_cx, da_e_cx			; (E)CX
OP_FIRST_STRING equ nextindex
opitem OP_1,	ARG_IMMED,	ao46, "1"	; 1 (simple "string" ops from here on)
opitem OP_3,	ARG_IMMED,	ao47, "3"	; 3
	; order of the following (ao48 entries) must match asm_regnum in aa.asm
OP_FIRST_ASM_REGNUM	equ nextindex	; corresponding to asm_regnum start
opitem OP_DX,	ARG_JUSTREG,	ao48, "DX"	; DX
opitem OP_CL,	ARG_JUSTREG,	ao48, "CL"	; CL
opitem OP_ST,	ARG_WEIRDREG,	ao48, "ST"	; ST (top of coprocessor stack)
opitem OP_CS,	ARG_WEIRDREG,	ao48, "CS"	; CS
opitem OP_DS,	ARG_WEIRDREG,	ao48, "DS"	; DS
opitem OP_ES,	ARG_WEIRDREG,	ao48, "ES"	; ES
opitem OP_FS,	ARG_WEIRDREG,	ao48, "FS"	; FS
opitem OP_GS,	ARG_WEIRDREG,	ao48, "GS"	; GS
opitem OP_SS,	ARG_WEIRDREG,	ao48, "SS"	; SS
OP_AFTER_LAST equ nextindex
%if nextindex > OP_SIZE
 %error Too many op items
%endif
OP_AMOUNT_TABLE	equ nextindex + 16 - 1
		; nextindex: amount sizeless types
		; 16: OP_SIZE combined types
		; -1: OP_END does not occur in tables

; The following are extensions to sizeless OP_ items.
;  After an OP_X operand, a byte giving an index into
;  the OPX tables follows in the list. This is never
;  used with a size so we can make use of the full
;  eight bits. First macro parameter is the name which
;  matches the use in instr.key. Second parameter is
;  the routine in the assembler to dispatch to. Third
;  parameter is the routine in the disassembler.
; Only used for some disassembler extensions for now.

%macro opxitem 3.nolist
 %1 equ nextindex
 %xdefine ASMJMP_OPXITEMS ASMJMP_OPXITEMS,%2
 %xdefine DISJMP_OPXITEMS DISJMP_OPXITEMS,%3
 %assign nextindex nextindex + 1
%endmacro
%assign nextindex 0
%define ASMJMP_OPXITEMS ""
%define DISJMP_OPXITEMS ""
opxitem OPX_NOTHING,	ao_modifier_continue, dop60a
opxitem OPX_MOD_IF_REG,	ao_modifier_continue, dop_mod_if_reg
opxitem OPX_MOD_IF_MEMOFFS_AND_OTHER_REG_AL_AX_EAX, ao_modifier_continue, \
	dop_mod_if_memoffs_and_other_acc
opxitem OPX_MOD_IF_OTHER_REG_AX_EAX, ao_modifier_continue, \
	dop_mod_if_other_acc_1632
opxitem OPX_MOD_IF_REG_1632, ao_modifier_continue, \
	dop_mod_if_reg_1632
opxitem OPX_MOD_IF_REG_AL_AX_EAX, ao_modifier_continue, \
	dop_mod_if_reg_acc
opxitem OPX_MOD_IF_REG_AX_EAX, ao_modifier_continue, \
	dop_mod_if_reg_acc_1632
opxitem OPX_MODALWAYS_IF_REG, ao_modifier_continue, \
	dop_modalways_if_reg
opxitem OPX_LOOP_OPTION, ac09_internal_error, dop_loop_option
%if nextindex > 256
 %error Too many opx items
%endif

	; Instructions that have an implicit operand subject to a segment prefix.
	; This means a prefixed segment is allowed by the strict assembler, and
	; the disassembler treats a segment prefix as part of the instruction and
	; displays it in front of the instruction's mnemonic.
	; (outs, movs, cmps, lods, xlat).
segprfxtab:
	db 06Eh,06Fh,0A4h,0A5h,0A6h,0A7h,0ACh,0ADh
a32prfxtab:
	db 0D7h				; xlat, last in segprfxtab, first in a32prfxtab
SEGP_LEN equ $-segprfxtab

		; Instructions that can be used with REPE/REPNE.
		; (ins, outs, movs, stos, lods; cmps, scas)
replist:db 06Ch,06Eh,0A4h,0AAh,0ACh	; REP (no difference)
REP_SAME_LEN equ $-replist		; number of indifferent replist entries
	db 0A6h,0AEh			; REPE/REPNE
REP_LEN equ $-replist
REP_DIFF_LEN equ REP_LEN-REP_SAME_LEN	; number of replist entries with difference

A32P_LEN equ $-a32prfxtab

; prfxtab P_LEN REP_LEN REPE_REPNE_LEN

		; All the instructions in replist also have an implicit operand
		; subject to ASIZE (similar to segprfxtab). Additionally, the
		; xlat instruction (0D7h) has such an implicit operand too.
		; maskmovq too.

	align 2, db 0
o32prfxtab:
	dw 0Eh, 1Eh, 06h, 16h, SPARSE_BASE + 0A0h, SPARSE_BASE + 0A8h
	; push cs, push ds, push es, push ss, push fs, push gs
	dw 1Fh, 07h, 17h, SPARSE_BASE + 0A1h, SPARSE_BASE + 0A9h
	; pop ds, pop es, pop ss, pop fs, pop gs
O32P_AMOUNT equ ($ - o32prfxtab) / 2


	%include "asmtabs.asm"
%endif


	usesection lDEBUG_DATA_ENTRY

msg_start:
	%include "msg.asm"

msg_end:

	numdef SHOWMSGSIZE, _DEFAULTSHOWSIZE
%if _SHOWMSGSIZE
%assign MSGSIZE msg_end - msg_start
%warning msg holds MSGSIZE bytes
%endif


	usesection lDEBUG_DATA_ENTRY

..@symhint_trace_caller_entry_to_code_segsel:
entry_to_code_segsel:
%if _PM
	push di
	mov di, word [cs:pm_2_86m_0]
	jmp near word [cs:.table + di]

		; REM:	Dispatch table in section lDEBUG_DATA_ENTRY
	align 2
.table:
	dw .86m
	dw .pm

.pm:
	pop di
..@symhint_trace_caller_entry_to_code_sel:
entry_to_code_sel: equ $
	push ax
	mov ax, word [cs:code_sel]
	jmp entry_to_code_common

.86m:
	pop di
%endif

		; INP:	word [cs:ip] = near address to jump to in other segment
..@symhint_trace_caller_entry_to_code_seg:
entry_to_code_seg:
	push ax			; word space for ?jumpaddress_ip, is ax
	mov ax, word [cs:code_seg]

entry_to_code_common:
	lframe 0
	lpar word, jumpaddress_cs_and_orig_ip
	lpar word, jumpaddress_ip
	lenter

	push si
	pushf
	cld

	xchg word [bp + ?jumpaddress_cs_and_orig_ip], ax	; fill function segment
	mov si, ax
	cs lodsw
%if _DEBUG
	cmp al, 0CCh		; debugger breakpoint ?
	jne @F			; no -->
	int3			; break to make it remove the breakpoint
	dec si
	dec si
	cs lodsw		; reload the word
	cmp al, 0CCh
	jne @F

.l:
	int3
	jmp .l

@@:
%endif
	xchg word [bp + ?jumpaddress_ip], ax		; fill function offset
		; (and restore ax)

	popf
	pop si

	lleave
	retf			; jump to dword [bp + ?jumpaddress]


%if !_LOADER
		; debug22 - Interrupt 22h handler
		;
		; This is for DEBUG itself: it's a catch-all for the various Int23
		; and Int24 calls that may occur unpredictably at any time. What we
		; do is pretend to be a command interpreter (which we are, in a sense,
		; just with different sort of commands) by setting our parent PSP
		; value equal to our own PSP so that DOS does not free our memory when
		; we quit. Therefore control ends up here when DOS detects Control-C
		; or an Abort in the critical error prompt is selected.
debug22:
	cli
.cleartraceflag:
	cld			; reestablish things
	mov ax, cs
	mov ds, ax
	mov ss, ax
	mov sp, word [ savesp ]	; restore stack
%if _PM
	mov byte [pm_2_86m_0], 0
	clropt [internalflags], protectedmode	; reset PM flag
%endif
	times 1 - (($ - $$) & 1) nop	; align in-code parameter
	call entry_to_code_seg
	dw cmd2_reset_re_maybe_pm
%endif


	; doscall is used by symbols.asm and run.asm and in
	;  cmd3_close_handle, so define it prior
%if _PM && _NOEXTENDER
%macro doscall 0
	nearcall _doscall
%endmacro
%else
		; When we don't support non-extended DPMI all Int21 calls
		; are either in Real Mode or extended (all are real Int21
		; instructions).
%macro doscall 0
	int 21h
%endmacro
%endif


%if _DUALCODE
	usesection lDEBUG_CODE2
 %if $ - $$
  %error cmd3_mirror is not at offset 0 in lDEBUG_CODE2
 %endif
cmd3_mirror:
	db 0B9h				; mov cx, imm16 (cx = nonzero)
	xor cx, cx			; cx = 0
	dualcall cmd3.have_cx_convenience
%endif


	usesection lDEBUG_CODE
%if $ - $$
 %error cmd3 is not at offset 0 in lDEBUG_CODE
%endif

	code_insure_low_byte_not_0CCh
		; Begin main command loop.
cmd3: section_of_function
		; A convenience entrypoint: Entering
		;  cmd3 at offset 1 instead of 0 will
		;  make the debugger additionally
		;  display a linebreak early on.
	db 0B9h				; mov cx, imm16 (cx = nonzero)
	xor cx, cx			; cx = 0
.have_cx_convenience:

	push ss
	pop ds
	_386_o32		; mov esp
	mov sp, word [ savesp ]		; restore stack
_386	and sp, ~3			; align stack
	_386_o32
	xor ax, ax
	_386_o32
	push ax
	_386_o32
	popf
_386	mov sp, word [ savesp ]		; restore stack
	cld
	sti
	mov word [ errret ], cmd3
	mov word [ throwret ], errhandler
	mov word [ throwsp ], sp
%if _SYMBOLIC
	mov word [ stack_low_address ], str_buffer
%endif
	xor ax, ax
%if _EXTENSIONS && _EXT_PUTS_GETLINE
	mov word [in_getinput], ax	; set this byte and reserved next byte
%endif
	xchg ax, word [cmd3_set_options]
	or word [options], ax

	push ds
	pop es
	jcxz @F
	jmp @FF
@@:
	mov dx, crlf
	call putsz
@@:

%if _SYMBOLIC
	clropt [internalflags2], dif2_xms_detection_done
	nearcall zz_save_strat
%endif

	xor di, di
	xchg di, word [terminator_in_line_in.offset]
	test di, di
	jz @F
	cmp byte [di], 0
	jne @F
	mov al, byte [terminator_in_line_in.value]
	stosb
@@:

	and word [run_sp_reserve], 0
%if !_LOADER
%if _RH || _SWHILEBUFFER || _RECMDWHILEBUFFER || _EXTENSIONS
	rol byte [in_re], 1
	jc @F
	clropt [internalflags], tt_while
@@:
%endif
%endif
%if _RH
	clropt [internalflags6], dif6_rh_mode
	mov byte [skip_rh], 0
%endif
%if _IMMASM
	clropt [internalflags6], dif6_immasm_no_output | dif6_immasm
%endif
	clropt [internalflags3], dif3_unquiet_error
%if _REGSHIGHLIGHT
	clropt [internalflags3], dif3_do_not_highlight
%endif
%if _INPUT_FILE_HANDLES || _INPUT_FILE_BOOT
	clropt [internalflags3], dif3_auxbuff_guarded_1 | dif3_in_if
%else
	clropt [internalflags3], dif3_auxbuff_guarded_1
%endif
	clropt [internalflags3], \
		dif3_input_serial_override | dif3_input_terminal_override
	clropt [internalflags2], dif2_in_silence_dump
%if _PM
 %if _DEBUG
	clropt [internalflags6], dif6_in_hook2F | dif6_in_amis_hook2F
 %else
	clropt [internalflags6], dif6_in_amis_hook2F
 %endif
	call resetmode
%endif

%ifn _VXCHG
	mov ah, 0Fh
	int 10h
	mov byte [vpage], bh			; update page
%endif

%if _TEST_HELP_FILE
cmd3_close_handle:
	call InDOS
	jnz @F
	mov ah, 3Eh
	mov bx, -1
	xchg bx, [handle]
	cmp bx, -1
	je @F
	doscall
@@:
%endif

%if _EXTENSIONS
cmd3_close_ext:
	call close_ext
%endif

%if _RH
cmd3_rh_init:
	testopt [options6], opt6_rh_mode
	jz .check_disable
.check_enable:
	testopt [internalflags6], dif6_rh_mode_was
	jnz .done
.enable:
		; guarded_1 is always reset by cmd3
		; guarded_2 is only set by RE indicating silent mode
		; so we never want to not enable RH mode
	setopt [internalflags3], dif3_auxbuff_guarded_3
	setopt [internalflags6], dif6_rh_mode_was

	call tpg_initialise_empty_auxbuff.reset

	mov dx, msg.rh_enabled
	jmp .done_putsz

.check_disable:
	testopt [internalflags6], dif6_rh_mode_was
	jz .done
.disable:
	clropt [internalflags3], dif3_auxbuff_guarded_3
	clropt [internalflags6], dif6_rh_mode_was
	mov dx, msg.rh_disabled

.done_putsz:
	call putsz
.done:
%endif

%if _DEBUG && _DEBUG_COND
cmd3_debug_mode_init:
	testopt [options6], opt6_debug_mode
	jz .check_disable
.check_enable:
	testopt [internalflags6], dif6_debug_mode
	jnz .done
	call reset_interrupts
	setopt [internalflags6], dif6_debug_mode
	jmp .done

.check_disable:
	testopt [internalflags6], dif6_debug_mode
	jz .done
	call set_interrupts
	clropt [internalflags6], dif6_debug_mode
.done:
 %endif


%if _PM
cmd3_int2F_init:
	mov al, 2Fh		; interrupt number
	mov si, debug2F		; -> IISP entry header

	testopt [options4], opt4_int_2F_hook
	jnz .done
.check_disable:
	mov dx, opt4_int_2F_force >> 16
 %if (opt4_int_2F_force >> 16) == dif4_int_2F_hooked
	call cmd3_int_disable.set_bx_to_dx
 %else
	mov bx, dif4_int_2F_hooked
	call cmd3_int_disable
 %endif
	jc .done
	clropt [internalflags], hooked2F

.done:
%endif


%if _CATCHINTFAULTCOND && _CATCHINT0D
cmd3_int0D_init:
	mov al, 0Dh		; interrupt number
	mov si, intr0D		; -> IISP entry header
	mov dx, opt4_int_0D_force >> 16
 %if (opt4_int_0D_force >> 16) == dif4_int_0D_hooked
	mov bx, dx
 %else
	mov bx, dif4_int_0D_hooked
 %endif

	testopt [options4], opt4_int_fault_hook
	jz .check_disable
.check_enable:
	call cmd3_int_enable
	jmp .done

.check_disable:
	call cmd3_int_disable

.done:
%endif


%if _CATCHINTFAULTCOND && _CATCHINT0C
cmd3_int0C_init:
	mov al, 0Ch		; interrupt number
	mov si, intr0C		; -> IISP entry header
	mov dx, opt4_int_0C_force >> 16
 %if (opt4_int_0C_force >> 16) == dif4_int_0C_hooked
	mov bx, dx
 %else
	mov bx, dif4_int_0C_hooked
 %endif

	testopt [options4], opt4_int_fault_hook
	jz .check_disable
.check_enable:
	call cmd3_int_enable
	jmp .done

.check_disable:
	call cmd3_int_disable

.done:
%endif


%if _CATCHINT08
cmd3_int08_init:
	mov al, 08h		; interrupt number
	mov si, intr8		; -> IISP entry header
	mov dx, opt4_int_08_force >> 16
 %if (opt4_int_08_force >> 16) == dif4_int_08_hooked
	mov bx, dx
 %else
	mov bx, dif4_int_08_hooked
 %endif

	testopt [options4], opt4_int_08_hook
	jz .check_disable
.check_enable:
	call cmd3_int_enable
	jmp .done

.check_disable:
	call cmd3_int_disable

.done:
%endif


%if _CATCHINT2D
cmd3_int2D_init:
	mov al, 2Dh		; interrupt number
	mov si, int2D		; -> IISP entry header
	mov bx, dif4_int_2D_hooked
	mov dx, opt4_int_2D_force >> 16

	testopt [options4], opt4_int_2D_hook
	jz .check_disable
.check_enable:
	test word [internalflags4], bx
	jnz .done

	call intchk			; ZR if offset = -1 or segment = 0
					; CHG: ax, dx, bx
	jz .fail

	mov ah, byte [try_amis_multiplex_number]
	mov al, 00h
		; function 0 changes dx, di, cx, al
%if _PM
	call call_int2D
%else
	int 2Dh				; enquire whether there's anyone
%endif
	test al, al
	jz .got

	xor ax, ax			; start with multiplex number 0
.loopplex:
	mov al, 00h			; AMIS installation check
		; function 0 changes dx, di, cx, al
%if _PM
	call call_int2D
%else
	int 2Dh				; enquire whether there's anyone
%endif
	test al, al			; free ?
	jz .got				; yes, put it to use -->
	inc ah
	jnz .loopplex			; try next multiplexer -->

	mov dx, msg.cannot_hook_2D.nofree
	jmp .fail_putsz

.got:
	mov byte [amis_multiplex_number], ah

	mov al, 2Dh		; interrupt number
	mov bx, dif4_int_2D_hooked
	call cmd3_int_enable.need
	jmp .done

.fail:
	mov dx, msg.cannot_hook_2D.invalid
.fail_putsz:
	call putsz
	clropt [options4], opt4_int_2D_hook
	jmp .done

.check_disable:
	call cmd3_int_disable

.done:
%endif


	testopt [internalflags3], dif3_input_re
	jnz cmd3_continue_1_re
	clropt [options2], opt2_re_cancel_tpg
%if !_LOADER
	call silence_dump
%endif


cmd3_serial_init:
	testopt [options], enable_serial
	jz .check_disable_serial
.check_enable_serial:
	testopt [serial_flags], sf_init_done
	jz .enable_serial
		; If we disabled the actual use flag prior somehow
		;  (sf_use_serial clear) but we get here with
		;  sf_init_done set then re-enable use of serial I/O.
	setopt [serial_flags], sf_use_serial
	jmp .done_serial

.enable_serial:

	mov al, byte [serial_var_intnum]
	mov byte [serial_use_intnum], al
	mov al, byte [serial_var_params]
	mov byte [serial_use_params], al
	mov al, byte [serial_var_fifo]
	mov byte [serial_use_fifo], al
	mov ax, word [serial_var_baseport]
	mov word [serial_use_baseport], ax
	mov ax, word [serial_var_dl]
	mov word [serial_use_dl], ax
	mov ax, word [serial_var_irqmask]
	mov word [serial_use_irqmask], ax
  call  serial_clear_fifos
  call  serial_install_interrupt_handler
	jnc @F
	mov di, msg.serial_cannot_hook.old_int
	mov al, byte [serial_installed_intnum]
	call hexbyte
	mov di, msg.serial_cannot_hook.new_int
	mov al, byte [serial_use_intnum]
	call hexbyte
	mov dx, msg.serial_cannot_hook
	jmp .no_keep

@@:
	mov byte [serial_interrupt_handler + ieEOI], 80h
  call  serial_init_UART

	setopt [serial_flags], sf_init_done | sf_use_serial

	mov dx, msg.serial_request_keep
	call putsz

	mov di, line_out
%if _DEBUG
 %if _DEBUG_COND
	testopt [internalflags6], dif6_debug_mode
	jz @F
 %endif
	mov al, '~'		; indicate instance is to be debugged
	stosb
@@:
%endif
	mov ax, "= "
	stosw

	xor ax, ax
	mov word [getline_timer_count], ax
	push es
	mov ax, 40h
	mov es, ax
	mov ax, word [es:6Ch]
	mov word [getline_timer_last], ax
	pop es
	mov word [getline_timer_func], .timer

		; if we're executing from the command line
		;  buffer or a Y file then we want to
		;  override input to be from serial for the
		;  KEEP confirmation prompt.
		; output is always to serial if we're here.
	setopt [internalflags3], dif3_input_serial_override
	call getline00
	clropt [internalflags3], dif3_input_serial_override

	call skipcomm0
	dec si
	mov dx, msg.keep
	call isstring?
	je .done_serial

	mov dx, msg.serial_no_keep_enter
.no_keep:
		; Immediately disable use of serial I/O.
	clropt [serial_flags], sf_use_serial
		; Set the flag for next cmd3 loop to properly disable
		;  and de-initialise the serial port.
	clropt [options], enable_serial
	call putsz
	jmp cmd3


.timer:
	push ax
	push dx
	push cx
	push bx
	push es

	mov dx, 40h
	mov es, dx

  mov al, 0Bh	; request In-Service Register (ISR)
  out 0A0h, al	; from secondary PIC
  in al, 0A0h	; read the ISR
  test al, byte [serial_use_irqmask + 1]
  jnz .timer_ours
  mov al, 0Bh	; request In-Service Register (ISR)
  out 20h, al	; from primary PIC
  in al, 20h	; read the ISR
  and al, ~100b
  test al, byte [serial_use_irqmask]
  jnz .timer_ours
.timer_ours_done:

  mov cx, word [serial_save_irq_mask]
  xor bx, bx		; all bits clear (= IRQ ON)
  call set_irq		; enable IRQs and get prior status
  test bx, bx		; IRQs were still enabled ?
  jz @F			; yes, fine -->
  clropt [options6], opt6_share_serial_irq
			; no, make sure not to chain any longer
@@:
	mov cx, word [getline_timer_count]
	mov dx, word [getline_timer_last]

%if _SLEEP_NEW
	mov ax, word [es:6Ch]
	cmp dx, ax
	je .timer_next
	neg dx			; minus prior tick
	add dx, ax		; new tick - prior tick

	cmp dx, word [sleep_delta_limit]
	jbe @F
	mov dx, word [sleep_delta_limit]
	test dx, dx
	jnz @F
	inc dx			; limit 0 would lead to stagnant sleep
@@:
	cmp dx, word [sleep_highest_delta]
	jbe @F
	mov word [sleep_highest_delta], dx
@@:
	add cx, dx
	jnc @F
	mov cx, -1
@@:
	mov dx, ax
%else
	cmp dx, word [es:6Ch]
	je .timer_next
	mov dx, word [es:6Ch]
	inc cx
%endif
	mov al, 18
	mul byte [serial_keep_timeout]
	test ax, ax
	jz .timer_next
	cmp cx, ax
	jb .timer_next

	pop es
	mov dx, msg.serial_no_keep_timer
	jmp .no_keep

.timer_ours:
	setopt [options6], opt6_serial_EOI_call
  call serial_eoi
	jmp .timer_ours_done

.timer_next:
	mov word [getline_timer_count], cx
	mov word [getline_timer_last], dx
	pop es
	pop bx
	pop cx
	pop dx
	pop ax
	retn


.check_disable_serial:
		; If serial is initialised, uninstall it.
	testopt [serial_flags], sf_init_done
	jnz .disable_serial
		; Not initialised. Is the interrupt still hooked?
	testopt [internalflags4], dif4_int_serial_hooked
	jz .done_serial
		; Try unhooking the interrupt handler.
	call serial_uninstall_interrupt_handler
	jc .done_serial			; if it failed again -->
	mov di, msg.serial_late_unhook.int
	mov al, byte [serial_installed_intnum]
	call hexbyte
	mov dx, msg.serial_late_unhook
	call putsz
	jmp .done_serial

.disable_serial:

  call serial_clean_up
	jnc @F
		; Immediately disable use of serial I/O,
		;  so that our error message goes to the
		;  local terminal rather than to serial I/O.
	clropt [serial_flags], sf_use_serial
	mov di, msg.serial_cannot_unhook.int
	mov al, byte [serial_installed_intnum]
	call hexbyte
	mov dx, msg.serial_cannot_unhook
	call putsz
	mov byte [serial_interrupt_handler + ieEOI], 0
					; we do not issue EOI any longer
@@:
	clropt [serial_flags], sf_init_done | sf_use_serial
.done_serial:


%if _VXCHG
cmd3_vv_set:
	call vv_set
%endif


%if _PM
cmd3_ss_init:
	call ispm
	jnz .done

subcpu 286
	mov bx, ss
	lar cx, bx
	jnz .done
	shr cx, 8

	testopt [options3], opt3_ss_b_bit_set
	jz .check_clear
.check_set:
	testopt [internalflags3], dif3_ss_b_bit_set
	jnz .done

	mov ch, 40h
	jmp @F

.check_clear:
	testopt [internalflags3], dif3_ss_b_bit_set
	jz .done

@@:
	mov ax, 0009h
	int 31h
	jc .done

	xoropt [internalflags3], dif3_ss_b_bit_set
subcpureset

.done:
%endif


%if _IMMASM
	call near [ ia_restore ]
%endif

	testopt [options7], opt7_no_ensure
	jnz .skip

%if _PM
	call ispm
	jz @F
%endif
%if !_LOADER
	call ensuredebuggeeloaded	; if no task is active, create a dummy one
%endif
%if _PM && 0
	jmp @FF
@@:
	testopt [internalflags], attachedterm
	jz @F
	mov dx, .message
	call putsz

	usesection lDEBUG_DATA_ENTRY
.message:	ascizline "Attached term is set in PM!"
	usesection lDEBUG_CODE
%endif
@@:
.skip:


cmd3_continue_1_re:
	mov di, line_out	; build prompt
%if _DEBUG
 %if _DEBUG_COND
	testopt [internalflags6], dif6_debug_mode
	jz @F
 %endif
	mov al, '~'		; indicate instance is to be debugged
	stosb
@@:
%endif
%if !_LOADER
%if _INDOS_PROMPT
	call InDOS
	jz @F
 %if _BOOTLDR
	testopt [internalflags], nodosloaded
				; boot mode ?
  %if _INDOS_PROMPT_NOBOOT
	jnz @F			; yes, do not show special prompt -->
  %elif _INDOS_PROMPT_NOFLAG
	jnz .indos_prompt	; yes, show special prompt -->
				;  (do not call .real_indos check)
  %endif
 %endif
 %if _INDOS_PROMPT_NOFLAG
  %if _APPLICATION || _DEVICE
		; Never branches to here if bootloaded,
		;  so no need to handle only bootloaded.
	call InDOS.real_indos	; real InDOS set ?
	jz @F			; no, do not show special prompt -->
  %endif
 %endif
.indos_prompt:
	mov al, '!'
	stosb
@@:
%endif
%endif
%if !_LOADER
	mov al, '-'		; main prompt
%else
	mov al, '*'
%endif
%if _PM
	call ispm
	jnz .realmode
	mov al, '#'		; PM main prompt
.realmode:
%endif
	testopt [internalflags3], dif3_input_cmdline
	jz @F
	mov al, '&'
@@:
	testopt [internalflags3], dif3_input_re
	jz @F
	mov al, '%'
@@:
	stosb

	mov byte [hhflag], 0
	and word [hh_depth], 0
	and word [hh_depth_of_single_term], 0
	mov word [getline_timer_func], dmycmd
	clropt [internalflags], usecharcounter	; reset this automatically

	testopt [internalflags3], dif3_input_re
	jnz cmd3_continue_2_re

	setopt [internalflags], pagedcommand	; 2009-02-21: default to page all commands
	clropt [internalflags], tt_silence | tt_silent_mode
				; reset, in case it's still set
	clropt [internalflags2], dif2_tpg_proceed_bp_set | \
			dif2_bp_failure | dif2_tpg_keep_proceed_bp, 1
%if _INPUT_FILE_HANDLES
	clropt [internalflags2], dif2_closed_input_file
%endif

cmd3_continue_2_re:
	call determine_quiet_output

	xor cx, cx
	xchg cx, word [rc]	; reset rc
	mov word [priorrc], cx	; make prior value available
	jcxz @F
	mov word [erc], cx	; update to last non-zero value
@@:

%if !_LOADER
cmd3_check_relimit:
	testopt [internalflags3], dif3_input_re
	jz cmd3_continue_not_re

	add word [re_count], 1
	adc word [re_count + 2], 0
	mov dx, word [re_limit + 2]
	mov ax, word [re_limit]
	cmp word [re_count + 2], dx
	jne @F
	cmp word [re_count], ax
@@:
		; This branch bypasses cmd3_check_rclimit
		;  because RE buffer commands should not
		;  count towards the RC limit.
	jbe cmd3_continue_relimit_not_reached

	mov dx, msg.re_limit_reached
	jmp cmd3_check_common

cmd3_continue_not_re:
%endif

cmd3_check_rclimit:

%if _INPUT_FILE_HANDLES || _INPUT_FILE_BOOT
		; If executing from a script file then
		;  command doesn't count for RC limit.
%if _INPUT_FILE_BOOT
	testopt [internalflags2], dif2_input_file_boot
	jnz .file
%endif
%if _INPUT_FILE_HANDLES
	call InDOS
	jnz .file_not
	testopt [internalflags2], dif2_input_file
	jnz .file
%endif
.file_not:

.file: equ cmd3_continue_rclimit_not_reached
%endif

	testopt [internalflags3], dif3_input_cmdline
	jz cmd3_continue_not_rc

	add word [rc_count], 1
	adc word [rc_count + 2], 0
	mov dx, word [rc_limit + 2]
	mov ax, word [rc_limit]
	cmp word [rc_count + 2], dx
	jne @F
	cmp word [rc_count], ax
@@:
	jbe cmd3_continue_rclimit_not_reached

	mov dx, msg.rc_limit_reached
cmd3_check_common:
	call putsz_error
	mov ax, 0104h
	call setrc
	setopt [internalflags3], dif3_at_line_end
	call getline_close_file
	jmp cmd3

cmd3_continue_not_rc:
cmd3_continue_rclimit_not_reached:
cmd3_continue_relimit_not_reached:

cmd3_check_line_out_overflow:
	cmp word [line_out_overflow], 2642h
	je @F
	mov word [line_out_overflow], 2642h
	mov dx, msg.line_out_overflow
	call putsz_error
@@:

cmd3_getline:
%if _EXTENSIONS
	xor cx, cx
	xchg cx, word [ext_inject_handler]
	jcxz cmd3_not_inject
	jmp transfer_ext_cx

cmd3_not_inject: section_of_function
	mov sp, word [ savesp ]		; restore stack
	call getline00		; prompted input, also resets linecounter
cmd3_injected: section_of_function
	mov sp, word [ savesp ]		; restore stack


	mov cx, word [ext_preprocess_handler]
	jcxz cmd3_preprocessed
	jmp transfer_ext_cx

cmd3_preprocessed: section_of_function
	push ss
	pop ds
	push ss
	pop es
	mov sp, word [ savesp ]		; restore stack


	mov cx, word [ext_command_handler]
	jcxz cmd3_not_ext
	jmp transfer_ext_cx

cmd3_not_ext: section_of_function
	push ss
	pop ds
	push ss
	pop es
	mov sp, word [ savesp ]		; restore stack
	dec si
	lodsb
%else
	call getline00		; prompted input, also resets linecounter
cmd3_injected: section_of_function
%endif

	call iseol?.notsemicolon
	jne cmd3_notblank
	testopt [options3], opt3_disable_autorepeat
	jnz @F
	mov dx, word [lastcmd]
	mov byte [si], al
	jmp short cmd4

@@:
	mov word [lastcmd], dmycmd
	jmp cmd3

cmd3_notblank:
%if _SYMBOLIC
	clropt [internalflags3], dif3_nosymbols_1
%endif
	mov word [lastcmd], dmycmd
	cmp al, ';'
	je cmd3_j1		; if comment -->
	cmp al, ':'
	je cmd3_j1		; if jump label -->
	cmp al, '?'
	je help			; if request for help -->
	cmp al, '.'
	je immasm		; if assembling/immediate execution -->
	cmp al, '-'
	jne @F			; if not no symbol prefix -->
%if _SYMBOLIC
	setopt [internalflags3], dif3_nosymbols_1
%endif
	call skipwhite		; skip to next command letter
		; Empty line (autorepeat) not valid.
		; Comment not valid. Goto label not valid.
		; Help request not valid.
@@:
	call uppercase
	sub al, 'A'
%if _SYMBOLIC
	cmp al, 'Z' - 'A'
	ja error		; if not recognised -->
	je @F			; if Z, do not commit -->
	nearcall zz_commit_insert	; if not Z, commit now
@@:
%else
	cmp al, 'Y' - 'A'
	ja error		; if not recognised -->
%endif
	cbw
	xchg bx, ax
	call skipcomma
	shl bx, 1
	mov dx, word [ cmdlist+bx ]
cmd4:
	mov di, line_out
%if _DEBUG || _LOADER
	db __TEST_IMM8		; (skip int3)
.int3:
	int3			; used by BU command
%endif
	call dx
cmd3_j1:
	jmp cmd3		; back to the top


%if !_LOADER
	code_insure_low_byte_not_0CCh
cmd2_reset_re_maybe_pm:

	_386_o32		; mov esp
	mov sp, word [ savesp ]		; restore stack
_386	and sp, ~3			; align stack
	_386_o32
	xor ax, ax
	_386_o32
	push ax
	_386_o32
	popf
_386	mov sp, word [ savesp ]		; restore stack
	cld
	sti

%if _PM
	call handle_mode_changed
%endif
%endif

	code_insure_low_byte_not_0CCh
cmd2_reset_re:
	mov bx, word [io_levels]
.entry_bx_levels:
	xor cx, cx
.entry_bx_levels_cx_cmdline:
	xor dx, dx
%if _INPUT_FILE_HANDLES
	testopt [internalflags2], dif2_input_file
	jz @F
	add cx, word [input_file_handles.active]
	inc cx
@@:
%endif
%if _INPUT_FILE_BOOT
	testopt [internalflags2], dif2_input_file_boot
	jz @F
	add cx, word [load_input_file.active]
	inc cx
@@:
%endif
%if !_LOADER
	testopt [internalflags3], dif3_input_re
	jz @F
	inc cx
	inc dx
		; Flag: If we abort anything (effective IOL >= 1)
		;  then we need to cancel RE buffer execution.
		;  This is so because RE execution is always the
		;  topmost command source, taking precedence over
		;  yy as well as RC buffer execution.
	testopt [io_flags], iof_extra_iol_for_tpg_re
	jz @F
	inc bx
	jnz @F
	dec bx
@@:
%endif
	testopt [internalflags3], dif3_input_cmdline
	jz @F
	inc cx
	testopt [io_flags], iof_extra_iol_for_rc
	jz @F
	inc bx
	jnz @F
	dec bx
@@:
	cmp cx, bx
	jbe @F
	mov cx, bx
@@:
	jcxz cmd3_j1			; IOL zero or nothing active -->
	push ds
	pop es
@@:
	push cx
	push dx
	call getline_close_file.resetstuff
	pop dx
	pop cx
	loop @B
%if !_LOADER
	test dx, dx			; first cancelled was RE ?
	jz cmd3_j1			; no, just proceed now -->
	setopt [options2], opt2_re_cancel_tpg
					; set to cancel command
	jmp dumpregs_extended.exit	; clean up RE state
%else
	jmp cmd3_j1
%endif

dmycmd:
	retn

%if _EXTENSIONS
lastcmd_transfer_ext_entry:
	mov cx, eld_linkcall.call_cmd3 - eld_linkcall
	push cx
	mov cx, word [lastcmd_transfer_ext_address]
	jmp transfer_ext_cx

near_transfer_ext_entry:
	push cx
	mov cx, word [near_transfer_ext_address]
	jmp transfer_ext_cx
		; INP:	cx = entrypoint ip
		;	ss:sp -> original cx value, near return address

		; INP:	ss:sp -> three words to discard, cx, near return
near_transfer_ext_return: section_of_function
	pop cx				; discard near return address
	pop cx
	pop cx				; discard far return address
	pop cx				; restore cx
	retn				; return
%endif

help:
	call skipcomma
	call uppercase
%if _EXTHELP
 %if _COND
	mov dx, msg.condhelp
	cmp al, 'C'
	je .spec
 %endif
 %if _OPTIONS
	cmp al, 'O'
	je .options		; option help -->
 %endif
 %if _EXPRESSIONS
	mov dx, msg.expressionhelp
	cmp al, 'E'
	je .spec
 %endif
%endif
%if _EMS
	mov dx, msg.xhelp
	cmp al, 'X'
	je .spec
%endif
	dec si
 %if _BOOTLDR && _EXTHELPBOOT
	mov dx, msg.boot
	call isstring?
	mov dx, msg.boothelp
	je .spec
 %endif
%if _PM
	mov dx, msg.desc
	call isstring?
	mov dx, msg.deschelp
	je .spec
%endif
%if _EXTHELP
	mov dx, msg.source
	call isstring?
	mov dx, msg.help_source
	je .spec
%endif
%if !_LOADER
	mov dx, msg.re
	call isstring?
	mov dx, msg.help_re
	je .spec
	mov dx, msg.run
	call isstring?
	mov dx, msg.help_run
	je .spec
%endif
	mov dx, msg.string_version
	call isstring?
	mov bx, msg.build_array
	mov cx, msg.build_version_amount
	je .spec_multi
	mov dx, msg.string_build
	call isstring?
	mov cl, msg.build_short_amount
	je .spec_multi
	lodsb
	call uppercase
	mov cl, msg.build_long_amount
	cmp al, 'B'
	je .spec_multi		; build info -->
%if _EXTHELP
	mov dx, msg.license
	cmp al, 'L'
	je .spec		; licence -->
	mov dx, msg.flaghelp
	cmp al, 'F'
	je .spec		; flag help -->
	mov dx, msg.reghelp
	cmp al, 'R'
	je .spec		; register help -->
 %if _VARIABLES || _OPTIONS || _PSPVARIABLES
	mov dx, msg.varhelp
	cmp al, 'V'
	je .spec		; variable help -->
 %endif
%endif
	mov bx, msg.help_array	; default help
	mov cl, msg.help_array_amount
	dec si
	jmp .spec_multi

.spec:
	lodsb
	call chkeol
%if _MESSAGESEGMENT || _HELP_COMPRESSED
	call putsz_exthelp
	jmp cmd3_j1a
%endif

prnquit:
	call putsz		; print string
cmd3_j1a:
	jmp cmd3_j1		; done

errorj1:jmp error

help.spec_multi:
	lodsb
	call chkeol
.loop:
	mov dx, word [bx]
	call putsz_exthelp
	inc bx
	inc bx
	loop .loop
	jmp short cmd3_j1a

%if _EXTHELP && _OPTIONS
help.options:
	mov bx, si
	call skipwhite
	call iseol?
	je .all
	call uppercase
	cmp al, 'A'
	mov dx, msg.asmoptions_1
	je .single
	cmp al, 'I'
	mov dx, msg.flags_1
	je .single
	mov di, msg.options_scan
	mov cx, msg.options_scan_amount
	repne scasb
	jne .pages
	sub di, msg.options_scan + 1
	shl di, 1
	mov di, word [msg.options_array + di]
	mov dx, di
%if _MESSAGESEGMENT
	push ds
 %if _PM
	call get_messagesegsel
 %else
 	mov ds, word [ss:messageseg]
 %endif
 %if _HELP_COMPRESSED
	cmp word [di], 2 + !!_LZEXEDAT * 3
 %else
	cmp byte [di], 0
 %endif
	pop ds
%else
 %if _HELP_COMPRESSED
	cmp word [di], 2 + !!_LZEXEDAT * 3
 %else
	cmp byte [di], 0
 %endif
%endif
	je errorj1
.single:
	jmp help.spec

.pages:
	lea si, [bx - 1]
	mov dx, msg.string_options
	call isstring?
	jne errorj1
	mov dx, msg.options_pages
	jmp .single

.all:
	mov bx, msg.options_array
	mov cx, msg.options_array_amount
.loop:
	mov di, word [bx]
	mov dx, word [bx]
	call putsz_exthelp
	inc bx
	inc bx
%if _MESSAGESEGMENT
	push ds
 %if _PM
	call get_messagesegsel
 %else
 	mov ds, word [ss:messageseg]
 %endif
 %if _HELP_COMPRESSED
	cmp word [di], 2 + !!_LZEXEDAT * 3
 %else
	cmp byte [di], 0
 %endif
	pop ds
%else
 %if _HELP_COMPRESSED
	cmp word [di], 2 + !!_LZEXEDAT * 3
 %else
	cmp byte [di], 0
 %endif
%endif
	je @F
	cmp cx, 1
	je @F
	mov dx, crlf
	call putsz
@@:
	loop .loop
	jmp cmd3
%endif


%if (_CATCHINTFAULTCOND && _CATCHINT0D) \
	|| (_CATCHINTFAULTCOND && _CATCHINT0C) \
	|| _CATCHINT2D || _CATCHINT08 || _LOADER
		; INP:	ds:si -> IISP entry header
		;	al = interrupt number
		;	bx = interrupt hooked status flag (dif4 low word)
		; OUT:	-
		; CHG:	ax, bx, cx, dx, di, si
cmd3_int_enable.set_bx_to_dx:
	mov bx, dx

cmd3_int_enable:
	test word [internalflags4], bx
	jnz .ret

.need:
	push bx
	call install_86m_interrupt_handler
	pop bx
	or word [internalflags4], bx
	call update_inttab_optional
.ret:
	retn
%endif


%if (_CATCHINTFAULTCOND && _CATCHINT0D) \
	|| (_CATCHINTFAULTCOND && _CATCHINT0C) \
	|| _CATCHINT2D || _CATCHINT08 || _PM || _LOADER
		; INP:	ds:si -> IISP entry header
		;	al = interrupt number
		;	dx = interrupt unhook force flag (dif4 high word)
		;	bx = interrupt hooked status flag (dif4 low word)
		; OUT:	NC if successfully unhooked
		;	 dif4 low word cleared
		;	CY if already unhooked or failed to unhook,
		;	 ZR if already unhooked
		;	  (dif4 low word is clear)
		;	 NZ if failed to unhook
		;	  (dif4 low word is still set)
		; CHG:	ax, bx, di, si
cmd3_int_disable.set_bx_to_dx:
	mov bx, dx

cmd3_int_disable:
	test word [internalflags4], bx
	jz .ret_CY		; --> (ZR)

	call UnhookInterruptForce
	jc .ret_CY_NZ

	not bx
	and word [internalflags4], bx
	call update_inttab_optional
	clc			; (NC)
	retn

.ret_CY_NZ:
	test sp, sp		; (NZ)
.ret_CY:
	stc			; (CY)
	retn
%endif


determine_quiet_output:
	clropt [internalflags3], dif3_quiet_output

	push di
	push ax
	testopt [internalflags3], dif3_input_re
	jnz .notquiet

%if _INPUT_FILE_BOOT
	testopt [internalflags2], dif2_input_file_boot
	jz @F
	mov ax, LOAD_INPUT_FILE_SIZE
	push dx
	mul word [load_input_file.active]
	pop dx
	mov di, ax
	testopt [load_input_file + di - LOADDATA3 + ldFATType], ifhfQuietOutput
	jmp .quiet_if_nz

@@:
%endif
%if _INPUT_FILE_HANDLES
	call InDOS
	jnz @F

	testopt [internalflags2], dif2_input_file
	jz @F
	mov di, word [input_file_handles.active]
	shl di, 1
	shl di, 1
	shl di, 1		; to qword array index
 %if INPUTFILEHANDLE_size != 8
  %error Unexpected structure size
 %endif
	testopt [input_file_handles + di + ifhFlags], ifhfQuietOutput
	jmp .quiet_if_nz

@@:
%endif
	testopt [internalflags3], dif3_input_cmdline
	jz @F
	testopt [options], opt_cmdline_quiet_output
	; jmp .quiet_if_nz

.quiet_if_nz:
	jz @F
.quiet:
	setopt [internalflags3], dif3_quiet_output
.notquiet:
@@:
	pop ax
	pop di
	retn


guard_auxbuff.1_or_2:
	testopt [internalflags3], dif3_auxbuff_guarded_1 \
		| dif3_auxbuff_guarded_2
	jmp @F

guard_auxbuff: section_of_function
	testopt [internalflags3], dif3_auxbuff_guarded_1 \
		| dif3_auxbuff_guarded_2 \
		| dif3_auxbuff_guarded_3
@@:
	jnz @F
	setopt [internalflags3], dif3_auxbuff_guarded_1
	retn

@@:
	mov ax, 0101h
	call setrc
	mov dx, msg.guard_auxbuff_error
.putsz_error:
	call putsz
	jmp cmd3


		; This is used to disallow commands
		;  while reading from the RE buffer.
guard_re: section_of_function
%if !_LOADER
	testopt [internalflags3], dif3_input_re
	jnz @F
	retn

@@:
	mov ax, 0102h
	call setrc
	mov dx, msg.guard_re_error
	jmp guard_auxbuff.putsz_error
%else
	retn
%endif

		; This is used to disallow commands
		;  while reading from the RC buffer.
guard_rc:
	testopt [internalflags3], dif3_input_cmdline
	jnz @F
	retn

@@:
	mov ax, 0102h
	call setrc
	mov dx, msg.guard_rc_error
	jmp guard_auxbuff.putsz_error


%if _EXTENSIONS
	usesection lDEBUG_DATA_ENTRY
	align 2, db 0
linkcall_table:
.init:	dw 0
	dw (.end - .) / 2
.:
	dw 0
.code1:	dw 0
 %if _DUALCODE
	dw 1
.code2:	dw 0
 %endif
	dw 2
.entry:	dw 0
.end:

		; INP:	_PM=0: ss:sp -> dword far return address
		;	_PM=1: ss:sp -> word near return address
		;		followed by an uninitialised placeholder word
		; CHG:	- (not even flags)
entry_ret_to_ext:
 %if _PM
	call entry_to_code_segsel
	dw code_ret_to_ext
 %else
	retf		; simple
 %endif


	usesection lDEBUG_CODE
 %if _DUALCODE && _PM
		; INP:	ss:sp -> dword dual call return address (to discard)
		;	ss:sp + 4 -> word near return address
		;		followed by an uninitialised placeholder word
		; CHG:	- (not even flags)
dualfunction
code2_to_code_ret_to_ext: section_of_function
	lframe 0
	lpar word, dualseg
	lpar word, dualofs
	lenter
	push word [bp + ?frame_bp]
	pop word [bp + ?dualseg]
	lleave ctx
	pop bp			; (discard)
	pop bp			; (discard)
	pop bp			; restore bp
		; fall through to code_ret_to_ext
 %endif

		; INP:	_PM=0: ss:sp -> dword far return address
		;	_PM=1: ss:sp -> word near return address
		;		followed by an uninitialised placeholder word
		; CHG:	- (not even flags)
code_ret_to_ext:
 %if _PM
		; ss:sp -> word near return address, placeholder word
	lframe 0
	lpar word, retseg
	lpar word, retofs
	lenter
	push di
	mov di, word [ss:pm_2_86m_0]
  %if extseg + 2 != extcssel
   %error code_ret_to_ext expects extcssel after extseg
  %endif
	push word [ss:extseg + di]
	pop word [bp + ?retseg]
	pop di
	lleave , optimiserestoresp
 %endif
	retf			; now return to ext

 %if _DUALCODE
	usesection lDEBUG_CODE2
code2_ret_to_ext:
  %if _PM
	dualcall code2_to_code_ret_to_ext
  %else
	retf			; simple case if _PM=0
  %endif
 %endif
%endif


%include "amis.asm"


	usesection lDEBUG_CODE

%if _DEBUG4 || _DEBUG5
%define _DEB_ASM_PREFIX
%include "deb.asm"
%endif


%if !_LOADER
%include "aa.asm"
%endif
%include "dd.asm"
%if _RN
 %imacro internaldatarelocation 0-*.nolist
 %endmacro
 %include "fptostr.asm"
 %unimacro internaldatarelocation 0-*.nolist
%endif
%include "run.asm"
%include "install.asm"
%if !_LOADER
%include "uu.asm"
%endif
%if _IMMASM
%include "immasm.asm"
%else
immasm:
	lodsb
	call chkeol
	jmp cmd3
%endif
%if _HELP_COMPRESSED
	overridedef STANDALONE, 0
%include "helpdepk.asm"
	resetdef STANDALONE
%endif


	usesection lDEBUG_DATA_ENTRY

%if _PM || _CATCHINT07 || _CATCHINT0C || _CATCHINT0D
	align 4, db 0
exception_csip:	dd 0	; 16:16 far 16-bit address of debugger exception
 %if _AREAS
exception_stack:times 4 dw 0
			; stack of debugger exception
 %endif
%endif

%if _PM
%include "pmdata.asm"
%include "pminit.asm"
%include "pmentry.asm"


	usesection lDEBUG_CODE

resetmode_and_test_d_b_bit: section_of_function
%if _PM
	call resetmode
%endif

		; Test if bx is a 32-bit selector
		;  (as opposed to a 16-bit selector or a segment)
		;
		; INP:	bx = selector (PM) or segment (86M)
		; OUT:	NZ = 32-bit
		;	ZR = 16-bit (always if 86M)
		;	NC
		; REM:	This checks whether a code segment's D bit or
		;	 a stack segment's B bit is set. This operation
		;	 is not meaningful otherwise.
test_d_b_bit: section_of_function
_386	call ispm
_386	jz .pm				; 386 and PM, check selector -->
		; not PM or no 386
.ZR:
	cmp al, al			; ZR, NC
	retn
.pm:
[cpu 386]
	push eax
	xor eax, eax			; use rights = 0 if inaccessible
	lar eax, ebx			; access rights
		; eax is unchanged if the access rights are inaccessible
		;  (and NZ is set in that case)
	test eax, 400000h		; test bit (NC)
	pop eax
	retn
__CPU__


		; Test if selector in bx has a limit beyond 64 KiB - 1 B
		;
		; INP:	bx = selector (PM) or segment (86M)
		; OUT:	NZ = limit above 64 KiB - 1 B
		;	ZR = limit below 64 KiB (always if 86M)
		;	NC
test_high_limit: section_of_function
_386	call ispm
_386	jz .pm				; 386 and PM, check selector -->
		; not PM or no 386
	jmp test_d_b_bit.ZR

.pm:
[cpu 386]
	push eax
	xor eax, eax			; use limit = 0 if inaccessible
	lsl eax, ebx			; segment limit
		; eax is unchanged if the segment limit is inaccessible
		;  (and NZ is set in that case)
	test eax, 0FFFF_0000h		; (NC) ZR if low limit, else NZ
	pop eax
	retn
__CPU__

subcpureset	; subcpu used in pminit.asm
%endif	; _PM

%if _NOEXTENDER
		; When we support non-extended DPMI, some calls to Int21
		; are (extended) Int21 calls and some are (not extended)
		; calls down to the real mode Int21. doscall is a macro
		; that will always call the non-extended Int21.

		; Execute a non-extended DOS call
_doscall: section_of_function
	push di
	mov di, word [ss:pm_2_86m_0]
	jmp near word [ss:.table + di]

	usesection lDEBUG_DATA_ENTRY
		; REM:	Dispatch table in section lDEBUG_CODE
	align 2, db 0
.table:
	dw .86m_pop
	dw .pm_pop

	usesection lDEBUG_CODE
.pm_pop:
	pop di
subcpu 286
		; Execute a non-extended DOS call from PM
.pm:
	push word [ss:pspdbg]
	push 21h
	call intcall
	retn
subcpureset
.86m_pop:
	pop di
	jmp _int21
%endif


%if _DUALCODE
%push
%assign %$counter 0

%rep 2

%if %$counter == 0
%define %$currentindex 0
%define %$currentname lDEBUG_CODE
%define %$othername lDEBUG_CODE2
%define %$othersegvar code2_seg
%define %$otherselvar code2_sel
%else
%define %$currentindex 1
%define %$currentname lDEBUG_CODE2
%define %$othername lDEBUG_CODE
%define %$othersegvar code_seg
%define %$otherselvar code_sel
%endif

	hiddenusesection %$currentname
%if _PM
%$currentname %+ _to_ %+ %$currentname %+ _dualcall_helper:
	push ax		; placeholder
	push ax		; chain
	lframe 0
	lpar word,	offset_segment
	lpar word,	placeholder
	lpar word,	chain
	lenter
	push bx
	mov bx, word [bp + ?offset_segment]
%if _DEBUG
	mov word [bp + ?placeholder], bx
	push word [cs:bx + 2]
%else
	push word [cs:bx]
	lea bx, [bx + 2]
	mov word [bp + ?placeholder], bx
%endif
	pop word [bp + ?chain]
	mov word [bp + ?offset_segment], %$$currentindex
	pop bx
	lleave
	retn

%$currentname %+ _to_ %+ %$othername %+ _dualcall_helper:
	push ax		; placeholder
	push ax		; chain
	push ax		; chain
	lframe 0
	lpar word,	offset_segment
	lpar word,	placeholder
	lpar dword,	chain
	lenter
	push bx
	mov bx, word [bp + ?offset_segment]
%if _DEBUG
	mov word [bp + ?placeholder], bx
	push word [cs:bx + 2]
%else
	push word [cs:bx]
	lea bx, [bx + 2]
	mov word [bp + ?placeholder], bx
%endif
	pop word [bp + ?chain]
	mov bx, word [ss:pm_2_86m_0]
%if %$$othersegvar + 2 != %$$otherselvar
 %error Expected sel variable after seg variable
%endif
	mov bx, word [ss:%$$othersegvar + bx]
	mov word [bp + ?chain + 2], bx
	mov word [bp + ?offset_segment], %$$currentindex
	pop bx
	lleave
	retf


%$currentname %+ _dualret_helper:
	lframe near
	lpar word,	index_segment
	lpar word,	offset
	lpar_return
	lenter
	push bx
	push di

%if code_seg + 2 != code_sel
 %error Unexpected layout
%endif
%if code2_seg + 2 != code2_sel
 %error Unexpected layout
%endif
	mov bx, word [bp + ?index_segment]
	mov bl, byte [ss:.table + bx]	; multiply without fl change
	mov di, word [ss:pm_2_86m_0]
	mov bx, word [ss:code_seg + bx + di]
	mov word [bp + ?index_segment], bx
	pop di
	pop bx
	lleave
	lret

	usesection lDEBUG_DATA_ENTRY
.table:
	db 0
	db code2_seg - code_seg

	hiddenusesection %$currentname
%endif


%ifn _DUALCODE && ! _PM && _DUALCODENEARDUAL
%$currentname %+ _to_ %+ %$othername %+ _nearcall_helper:
	push ax		; return_offset
	push ax		; placeholder
	push ax		; chain
	push ax		; chain
	lframe 0
	lpar word,	offset_segment
	lpar word,	return_offset	; far return
	lpar word,	placeholder	; near return
	lpar dword,	chain		; far target address
	lenter
	push bx
	mov bx, word [bp + ?offset_segment]
%if _DEBUG
	mov word [bp + ?return_offset], bx
	push word [cs:bx + 2]
%else
	push word [cs:bx]
	lea bx, [bx + 2]
	mov word [bp + ?return_offset], bx
%endif
	pop word [bp + ?chain]

%if _PM
	mov bx, word [ss:pm_2_86m_0]
%if %$$othersegvar + 2 != %$$otherselvar
 %error Expected sel variable after seg variable
%endif
	mov bx, word [ss:%$$othersegvar + bx]
%else
	mov bx, word [ss:%$$othersegvar]
%endif
	mov word [bp + ?chain + 2], bx
%if _PM
	mov word [bp + ?offset_segment], %$$currentindex
%else
	mov word [bp + ?offset_segment], cs
%endif
	mov word [bp + ?placeholder], %$$othername %+ _retf_from_dual
	pop bx
	lleave
	retf

%$currentname %+ _retf_from_dual:
	dualreturn
	retf
%endif


%assign %$counter %$counter + 1
%endrep
%pop
%endif


	usesection lDEBUG_CODE

%if _SYMBOLIC
%include "symbols.asm"
%else

%if _PM


	hiddenusesection SECTION_OF_ %+ selector_to_segment

	; For branches other than symbolic, here's selector_to_segment
	;  (as used by the puts in lineio.asm). Picked from symsnip
	;  binsrch.asm at revision 9c232415d568.
		; INP:	word [ss:sp] = selector to access
		; OUT:	word [ss:sp] = segment value to use for access
		; CHG:	-
dualfunction
selector_to_segment: section_of_function
	lframe dualdistance
	lpar word,	in_selector_out_segment
	lpar_return
	lenter

	call _CURRENT_SECTION %+ _ispm
				; is it PM ?
	jnz .ret		; no, 86M --> (selector == segment)

subcpu 286
	push ax
	push bx
	push cx
	push dx

	mov bx, word [bp + ?in_selector_out_segment]
	mov ax, 6
	int 31h			; get segment base to cx:dx
	shr dx, 4
	shl cx, 12
	or dx, cx
	mov word [bp + ?in_selector_out_segment], dx

	pop dx
	pop cx
	pop bx
	pop ax
subcpureset

.ret:
	lleave
	dualreturn
	lret
%endif

%endif


	; support functions for symbols.asm
	usesection lDEBUG_CODE
%if _PM
dualfunction
push_cxdx_or_edx: section_of_function
	lframe dualdistance
	lpar dword, return
	lpar_return
	lenter
_no386	push cx
	_386_o32
	push dx
	pop word [bp + ?return]
	pop word [bp + ?return + 2]
	lleave
	dualreturn
	lret
%endif

		; INP:	ds:dx -> message
		;	cx = length
		; CHG:	-
		; STT:	ds, es don't care
disp_message_length_cx: section_of_function
	push ax
	push bx
	push cx
	push dx
	push es
	push ds
	 push ds
	 pop es			; es:dx -> message, cx = length
	 push ss
	 pop ds			; ds = ss (required for puts)
	call puts
	pop ds
	pop es
	pop dx
	pop cx
	pop bx
	pop ax
	retn

		; INP:	ds:dx -> message, ASCIZ
		; CHG:	-
		; STT:	ds, es don't care
disp_message: section_of_function
	push es
	 push ds
	 pop es			; es:dx -> message
	call putsz		; (sets up ds = ss internally)
	pop es
	retn

		; INP:	al = character to display
		; CHG:	-
		; STT:	ds, es don't care
disp_al: equ putc


		; Display number in ax hexadecimal, always 4 digits
		;
		; INP:	ax = number
		; OUT:	displayed using disp_al
		; CHG:	none
disp_ax_hex: section_of_function
	xchg al, ah
	nearcall disp_al_hex
	xchg al, ah
disp_al_hex: section_of_function
	push cx
	mov cl, 4
	rol al, cl
	nearcall disp_al_nybble_hex
	rol al, cl
	pop cx
disp_al_nybble_hex: section_of_function
	push ax
	and al, 0Fh
	add al, '0'
	cmp al, '9'
	jbe @F
	add al, -'9' -1 +'A'
@@:
	nearcall disp_al
	pop ax
	retn


		; Display number in ax decimal
		;
		; INP:	ax = number
		; OUT:	displayed using disp_al
		; CHG:	none
disp_ax_dec: section_of_function			; ax (no leading zeros)
		push bx
		xor bx, bx
.pushax:
		push dx
		push ax
		or bl, bl
		jz .nobl
		sub bl, 5
		neg bl
.nobl:
		push cx
		mov cx, 10000
		call .divide_out
		mov cx, 1000
		call .divide_out
		mov cx, 100
		call .divide_out
		mov cl, 10
		call .divide_out
							; (Divisor 1 is useless)
		add al, '0'
		nearcall disp_al
		pop cx
		pop ax
		pop dx
		pop bx					; Caller's register
		retn


		; INP:	ax = number
		;	cx = divisor
		; OUT:	ax = remainder of operation
		;	result displayed
.divide_out:
		push dx
		xor dx, dx
		div cx				; 0:ax / cx
		push dx				; remainder
		dec bl
		jnz .nobl2
		or bh, 1
.nobl2:
		or bh, al
		jz .leadingzero
		add al, '0'
		nearcall disp_al			; display result
 .leadingzero:
		pop ax				; remainder
		pop dx
		retn


%if !_LOADER
%include "cc.asm"
%endif


%include "bb.asm"


%include "ee.asm"


%include "ff.asm"


%include "hh.asm"


%include "iioo.asm"


	usesection lDEBUG_CODE

%if _PM == 0 && _LINK
ispm: section_of_function
	test sp, sp			; NZ, NC
	retn

test_d_b_bit: section_of_function
test_high_limit: section_of_function
	cmp al, al			; ZR, NC
	retn

setes2dx: section_of_function
	mov es, dx
	retn

call_int2D: section_of_function
	int 2Dh
	retn

_doscall: section_of_function
doscall_extseg: section_of_function
	int 21h
	retn

	usesection lDEBUG_DATA_ENTRY
dpmi32:		db 0

	usesection lDEBUG_CODE
%endif

%if _PM

%if _DUALCODE
 %assign REPEAT 2
%else
 %assign REPEAT 1
%endif

	usesection lDEBUG_CODE
%rep REPEAT
		; OUT:	NC
		;	ZR if in protected mode
		;	NZ otherwise
		; STT:	-
		;	([internalflags] & nodosloaded, [internalflags] & protectedmode set up)
_CURRENT_SECTION %+ _ispm:
	push ax
%if protectedmode & ~0FF00h
 %error Internal flags re-ordered, adjust code here
%endif
	mov al, byte [ss:internalflags+1]	; get flag byte
	and al, protectedmode>>8		; separate PM flag
	xor al, protectedmode>>8		; ZR if in PM (NC)
	pop ax
	retn

	usesection lDEBUG_CODE2
%endrep
	usesection lDEBUG_CODE
ispm equ _CURRENT_SECTION %+ _ispm
check_section_of ispm

%endif


setpspdbg:
%if _PM
	mov bx, word [pspdbg]
%else
	mov bx, ss		; = word [pspdbg] (if _PM=0 or in 86M)
%endif

setpsp:
%if _APPLICATION || _DEVICE
%if _BOOTLDR
	testopt [internalflags], nodosloaded
	jnz .ret		; no PSPs -->
%endif

%if _USESDA
	cmp word [pSDA+0], byte -1
	je .int21

.86m:
	push ds
	push si
	mov si, pSDA + so16aSegSel
	call update_dosdata_segment
	lds si, [si - so16aSegSel]
	mov word [si+10h], bx	; set PSP segment
	pop si
	pop ds
	retn

.int21:
%endif
	mov ah, 50h
 %if _PM
	call ispm
	jnz .int21_86m
  %if _NOEXTENDER
	jmp _doscall.pm		; insure non-extended (set to bx = PSP segment)
  %else
	mov ax, 0002h
	int 31h			; segment to selector
	xchg bx, ax		; bx = selector
	mov ah, 50h		; reset to function number
  %endif
.int21_86m:
 %endif
	jmp _int21
%else
	retn
%endif


getpsp:
%if _APPLICATION || _DEVICE
%if _BOOTLDR
	xor bx, bx		; = placeholder value if no PSPs
	testopt [internalflags], nodosloaded
	jnz .ret		; no PSPs -->
%endif
%if _USESDA
	cmp word [pSDA+0], byte -1
	je .int21
	push ds
	push si
	mov si, pSDA + so16aSegSel
	call update_dosdata_segment
	lds si, [si - so16aSegSel]
	mov bx, word [si + 10h]	; bx = PSP segment
	pop si
	pop ds
	retn

.int21:
%endif
	mov ah, 51h
 %if _PM
	call ispm
	jnz .int21_86m
  %if _NOEXTENDER
	jmp _doscall.pm		; insure non-extended (bx = PSP segment)
  %else
	call _int21		; get PSP as a selector
	push bx
	dualcall selector_to_segment
	pop bx			; bx = PSP segment
	retn
  %endif
.int21_86m:
 %endif
	jmp _int21		; in 86 Mode call DOS the normal way
%else
	xor bx, bx		; = placeholder value if no PSPs
	retn
%endif


dual2function
_doscall_return_es: section_of_function
_doscall_return_es_parameter_es_ds: section_of_function
	lframe dual2distance
	lpar word, es_ds_value
	lpar_return
%if _PM
	lvar word, int_number
	lenter
	mov word [bp + ?int_number], 21h
	push di
	mov di, word [ss:pm_2_86m_0]
	jmp near word [ss:.table + di]

	usesection lDEBUG_DATA_ENTRY
		; REM:	Dispatch table in section lDEBUG_CODE
	align 2, db 0
.table:
	dw .86m
	dw .pm

	usesection lDEBUG_CODE
.pm:
	pop di
	push word [bp + ?es_ds_value]
	push word [bp + ?es_ds_value]
	push word [bp + ?int_number]
	push word [bp + ?frame_bp]
	call intcall_return_parameter_es_parameter_ds
	pop word [bp + ?es_ds_value]		; discard returned ds
	pop word [bp + ?es_ds_value]		; get es
	jmp .ret
.86m:
	pop di
%else
	lenter
%endif
	 push es
	 push ds
	mov ds, word [bp + ?es_ds_value]
	mov es, word [bp + ?es_ds_value]
	int 21h
	mov word [bp + ?es_ds_value], es
	 pop ds
	 pop es
.ret:
	lleave
	dual2return
	lret


		; Execute real Int21 instruction. If this is in PM it might get extended.
_int21:
%if _APPLICATION || _DEVICE
%if _BOOTLDR
	pushf
	testopt [ss:internalflags], nodosloaded
	jnz .reterr		; no Int21 --> (throw?)
	popf
%endif
	int 21h
setpsp.ret: equ $
getpsp.ret: equ $
	retn
%if _BOOTLDR
.reterr:
	popf
	mov ax, 1
	stc
	retn
%endif
%else
	mov ax, 1
	stc
	retn
%endif


%if _PM
intcall_return_parameter_es_parameter_ds:
	lframe near
	lpar word, es_value
	lpar word, ds_value
	lpar_return
	lpar word, int_number
	lpar word, bp_value
	lvar 32h, 86m_call_struc
	lenter
	push es
	mov word [bp + ?86m_call_struc +00h], di	; edi
	mov word [bp + ?86m_call_struc +04h], si	; esi
	mov word [bp + ?86m_call_struc +10h], bx	; ebx
	mov word [bp + ?86m_call_struc +14h], dx	; edx
	mov word [bp + ?86m_call_struc +18h], cx	; ecx
	mov word [bp + ?86m_call_struc +1Ch], ax	; eax
	mov ax, word [bp + ?bp_value]
	mov word [bp + ?86m_call_struc +08h], ax	; bp
	mov al, 0					; (preserve flags!)
	lahf
	xchg al, ah
	mov word [bp + ?86m_call_struc +20h], ax	; flags
	xor ax, ax
	mov word [bp + ?86m_call_struc +0Ch + 2], ax
	mov word [bp + ?86m_call_struc +0Ch], ax
	mov word [bp + ?86m_call_struc +2Eh], ax	; sp
	mov word [bp + ?86m_call_struc +30h], ax	; ss
	mov ax, word [bp + ?es_value]			; usually [pspdbg]
	mov word [bp + ?86m_call_struc +22h], ax	; es
	mov ax, word [bp + ?ds_value]			; usually [pspdbg]
	mov word [bp + ?86m_call_struc +24h], ax	; ds
	push ss
	pop es				; => stack
	lea di, [bp + ?86m_call_struc]	; -> 86-Mode call structure
_386	movzx edi, di			; (previously checked b[dpmi32] here)
	mov bx, word [bp + ?int_number]			; int#
	xor cx, cx
	mov ax, 0300h
	int 31h
	mov ah, byte [bp + ?86m_call_struc +20h]	; flags
	sahf
	mov di, word [bp + ?86m_call_struc +00h]	; edi
	mov si, word [bp + ?86m_call_struc +04h]	; esi
	mov bx, word [bp + ?86m_call_struc +10h]	; ebx
	mov dx, word [bp + ?86m_call_struc +14h]	; edx
	mov cx, word [bp + ?86m_call_struc +18h]	; ecx
	mov ax, word [bp + ?86m_call_struc +1Ch]	; eax
	push word [bp + ?86m_call_struc +22h]		; return es value
	pop word [bp + ?es_value]			;  in the parameter
	push word [bp + ?86m_call_struc +24h]		; return ds value
	pop word [bp + ?ds_value]			;  in the parameter
	pop es
	lleave
	lret

intcall:
	lframe near
	lpar word, es_ds_value
	lpar word, int_number
	lenter
	push word [bp + ?es_ds_value]			; es
	push word [bp + ?es_ds_value]			; ds
	push word [bp + ?int_number]			; int number
	push word [bp + ?frame_bp]			; bp
	call intcall_return_parameter_es_parameter_ds
		; (discard returned parameters ?es_value, ?ds_value, done by lleave)
	lleave , forcerestoresp
	lret


%if _EXTENSIONS
intcall_ext_return_es: section_of_function
	lframe 6
	lpar word, es_ds_value
	lpar word, int_number
	lpar_return
	lenter
	push word [bp + ?es_ds_value]			; es
	push word [bp + ?es_ds_value]			; ds
	push word [bp + ?int_number]			; int number
	push word [bp + ?frame_bp]			; bp
	call intcall_return_parameter_es_parameter_ds
	pop word [bp + ?es_ds_value]			; discard ds
	pop word [bp + ?es_ds_value]			; return es
	lleave , optimiserestoresp
	retn


doscall_extseg: section_of_function
	pushf
	call ispm
	jnz .rm
subcpu 286
	popf
.pm:
	push word [ss:extseg]
	push 21h
	call intcall
	retn
subcpureset
.rm:
	popf
	jmp _int21
%endif

call_int2D: section_of_function
	call ispm
	jnz short .rm
subcpu 286
	push word [ss:pspdbg]	; es ds value. generally unused
	push 2Dh		; interrupt 2Dh
	call intcall		; call it
	retn
subcpureset
.rm:
	int 2Dh			; directly call int 2Dh
	retn


		; Called in PM only, ds unknown.
		;
		; INP:	-
		; OUT:	CY if no DOS extender available ("MS-DOS" on Int2F.168A)
		;	NC if DOS extender available
		; CHG:	-
isextenderavailable:
subcpu 286
	push ds
	push es
	pusha
	push ss
	pop ds
	mov si, msg.msdos
_386	movzx esi, si
	mov ax, 168Ah
	int 2Fh
	cmp al, 1			; CY if al is zero
	cmc				; NC if al is zero, CY else
	popa
	pop es
	pop ds
	retn
subcpureset

nodosextinst:
	push ss
	pop ds
	mov dx, nodosext
	jmp putsz
%endif


%if !_LOADER
%include "ll.asm"
%endif


%include "mm.asm"


	usesection lDEBUG_CODE

%if !_LOADER
		; K command - enter Kommand line in new style
		; N command - change the Name of the program being debugged
		;
		; N command is subject to weird Microsoft compatibility if
		;  an options2 flag is set. In this mode, the entire line_in
		;  contents after the N command are used for the command line
		;  tail rather than only the tail after the program name.
		;  (Yes, really.) Also, the debuggee ds is assumed to point
		;  to a PSP and the FCBs and command line tail are written
		;  to this segment.
%if _MS_N_COMPAT
nn:
	setopt [internalflags2], dif2_nn_capitalise
				; prepare old style (MS Debug compatibility)
	testopt [options2], opt2_nn_capitalise
	jnz @F			; leave it set -->
	clropt [internalflags2], dif2_nn_capitalise
				; disable old style, use new style
@@:

	setopt [internalflags2], dif2_nn_compat
				; prepare old style (MS Debug compatibility)
	testopt [options2], opt2_nn_compat
	jnz @FF			; leave it set -->
	jmp @F
kk:
	clropt [internalflags2], dif2_nn_capitalise
				; also disable capitalisation
@@:
	clropt [internalflags2], dif2_nn_compat
				; disable old style, use new style
@@:
%else
kk:
nn:
%endif
	push ss
	pop es

%if _MS_N_COMPAT
	dec si			; -> first non-blank
@@:
	dec si			; -> before prior start of command
	cmp byte [si], 32	; blank ?
	je @B
	cmp byte [si], 9
	je @B			; yes, decrement -->
	cmp byte [si], ','
	je @B
	inc si			; restore -> start of command (first blank, if any)
	lodsb			; make al = first text byte, si -> after
%endif

%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [ss:internalflags], nodosloaded
	jz @F
 %endif
	mov dx, msg.nobootsupp
	jmp putsz
@@:
%endif
%if _APPLICATION || _DEVICE
 %if _MS_N_COMPAT
	testopt [internalflags2], dif2_nn_compat
	jz @F
	call nn_copy_to_line_out
	dec si
	lodsb			; restore al
@@:
 %endif
	mov	di, DTA		; destination address
%if _MS_N_COMPAT	; (not needed if not including the blank scan)
	call skipcomm0		; skip blanks before program load filename
%endif

		; Copy and canonicalize file name.
nn1:
	cmp di, N_BUFFER_END
	jae .toolong
	call ifsep		; check for separators CR, blank, tab, comma, ;, =
	je nn3			; if end of file name
	cmp al, byte [ss:swch1]
		; The use of ss here appears to be intended to
		;  allow loading from ds different from the
		;  data entry and PSP segment, However, the
		;  subsequent copy of the command tail around
		;  nn4 does not participate in this scheme.
		; So if this is used make sure to adjust that.
	je nn3			; if '/' (and '/' is the switch character)
	call uppercase
	stosb
	lodsb
	jmp short nn1		; back for more

.toolong:
nn4.toolong:
	push ss
	pop ds
	mov dx, msg.n_toolongname
	call putsz
	mov di, N_BUFFER_END - 3
	mov al, 0		; truncate the name
	stosb
	mov byte [fileext], al	; invalid / none
	mov word [execblk.cmdline], di
	mov ax, 13 << 8		; 0 in low byte (tail length), CR in high byte
	stosw
	retn


nn3:
	push ss
	pop ds
	mov al, 0		; null terminate the file name string
	stosb
	mov word [execblk.cmdline], di
				; save start of command tail

%if _DEBUG4
	push dx
	mov dx, DTA
	call d4disp_msg
	mov dx, crlf
	call d4disp_msg
	pop dx
%endif
		; Determine file extension
	cmp di, DTA+1
	je nn3d			; if no file name at all
	cmp di, DTA+5
	jb nn3c			; if no extension (name too short)
	mov al, EXT_HEX
	cmp word [di-5], ".H"
	jne nn3a		; if not .HEX
	cmp word [di-3], "EX"
	je nn3d			; if .HEX
nn3a:
	mov al, EXT_EXE
	cmp word [di-5], ".E"
	jne nn3b		; if not .EXE
	cmp word [di-3], "XE"
	je nn3d			; if .EXE
nn3b:
	mov al, EXT_COM
	cmp word [di-5], ".C"
	jne nn3c		; if not .COM
	cmp word [di-3], "OM"
	je nn3d			; if .COM
nn3c:
	mov al, EXT_OTHER
nn3d:
	mov byte [fileext], al

		; Finish the N command
	push di
%if _MS_N_COMPAT
	testopt [internalflags2], dif2_nn_compat
	jnz @F
	call nn_copy_to_line_out
	jmp @F

		; INP:	si - 1 -> command line tail
		; OUT:	line_out has tail, terminated by 0 or 13 (CR)
		; CHG:	di, al
nn_copy_to_line_out:
	push si
%endif
	mov di, line_out
	dec si
nn4:
	lodsb			; copy the remainder to line_out
	testopt [internalflags2], dif2_nn_capitalise
	jz .nocaps
	call uppercase
.nocaps:
	stosb
	call iseol?.notsemicolon
	jne nn4
%if _MS_N_COMPAT
	pop si
	retn

@@:
%endif
	call InDOS
	jz .fcb_setup
%if _DEVICE
	testopt [internalflags6], dif6_device_mode
	jnz @F
%endif
	and word [reg_eax], 0
@@:
	jmp .fcb_none

.fcb_setup:
		; Set up FCBs.
	mov si, line_out
	mov di, 5Ch
	call nn6		; do first FCB
%if _DEVICE
	testopt [internalflags6], dif6_device_mode
	jnz @F
%endif
	mov byte [reg_eax], al
@@:
	mov di, 6Ch
	call nn6		; second FCB
%if _DEVICE
	testopt [internalflags6], dif6_device_mode
	jnz @F
%endif
	mov byte [reg_eax+1], al
@@:
.fcb_none:

		; Copy command tail.
	mov si, line_out
	pop di
	cmp di, N_BUFFER_END - 2
	jae .toolong
	push di			; -> counter byte
	inc di			; -> first byte of buffer
	lea bx, [di + 127]	; -> behind last byte of PSP target buffer
				;  (within 128 bytes the CR must be written)
	mov ax, N_BUFFER_END	; -> behind last byte in our N buffer
	cmp bx, ax		; use whichever limit is smaller
	jb @F
	mov bx, ax		; N buffer is smaller, use it as limit
@@:
nn5:
	cmp di, bx		; can store one more (possibly CR) ?
	jae .toolong		; no -->
	lodsb
	stosb			; store byte
	call iseol?.notsemicolon; end of string ?
	jne nn5			; if not end of string -->
.toolong_terminate:	; jump destination from .toolong
	mov byte [di - 1], 13	; (just overwrite this unconditionally)
	push di
	mov cx, di
	sub cx, N_BUFFER_END
	neg cx
	xor ax, ax
	rep stosb
	pop di
	pop ax			; recover old DI
	xchg ax, di
	sub ax, di		; compute length of tail
%if _MS_N_COMPAT
	mov cx, ax
%endif
	dec ax
	dec ax
	stosb
%if _MS_N_COMPAT
	testopt [internalflags2], dif2_nn_compat
	jz @F
	mov es, word [reg_ds]	; modify current ds, assuming a PSP
	call ll_copy_cmdline_and_fcbs.have_cx
@@:
%endif
%if _DEBUG4
	mov dx, DTA
	call d4disp_msg
	mov dx, crlf
	call d4disp_msg
%endif
	retn			; done

.toolong:
	mov dx, msg.n_toolongtail
	call putsz
	mov di, bx
	jmp .toolong_terminate


		; Subroutine to process an FCB.
		;
		; INP:	di -> FCB
		;	si -> input
nn6:
	lodsb
	call iseol?.notsemicolon
	je nn7			; if end
	call ifsep
	je nn6			; if separator (other than CR)
	cmp al, byte [switchar]
	je nn10			; if switch character
nn7:
	dec si
	mov ax, 2901h		; parse filename
	doscall
	push ax			; save AL
nn8:
	lodsb			; skip till separator
	call ifsep
	je nn9			; if separator character (including CR)
	cmp al, byte [swch1]
	jne nn8			; if not switchar (sort of)
nn9:
	dec si
	pop ax			; recover AL
	cmp al, 1
	jne nn9a		; if not 1
	dec ax
nn9a:
	retn

		; Handle a switch (differently).
nn10:	lodsb
	call iseol?.notsemicolon
	je nn7			; if end of string
	call ifsep
	je nn10			; if another separator (other than CR)
	mov al, 0
	stosb
	dec si
	lodsb
	cmp al, 'a'
	jb nn11			; if not a lower case letter
	cmp al, 'z'
	ja nn11
	and al, TOUPPER		; convert to upper case
nn11:	stosb
	mov ax, 32<<8|32
	stosw
	stosw
	stosw
	stosw
	stosw
	xor ax, ax
	stosw
	stosw
	stosw
	stosw
	retn			; return with al = 0


		; Compare character with separators
		;
		; INP:	al = character
		; OUT:	ZR if al is CR, NUL, blank, tab, comma, semicolon, or equal sign
		;	NZ else
		; REM:	This is only used for parsing FCBs.
ifsep: section_of_function
	call iseol?		; semicolon or CR or NUL
	je .return
	cmp al, 32
	je .return
	cmp al, 9
	je .return
	cmp al, ','
	je .return
	cmp al, '='
.return:
	retn
%endif
%endif


		; Ensure segment in bx is writeable
		;
		; INP:	bx = selector/segment
		; OUT:	NC if in 86M, bx unchanged
		;	NC if in PM and bx not a code segment, bx unchanged
		;	NC if in PM and was a code segment,
		;	 bx = word [scratchsel], set up to mirror INP:bx selector
		;	CY if in PM and a failure occurred, segment not writeable
		; CHG:	bx
		; STT:	ss = debugger data selector
%if _PM
verifysegm_or_error: section_of_function
	push bx
	call verifysegm
	jc .ro
	add sp, 2		; (discard bx on stack)
	retn

.ro:
	push ss
	pop es
	call ee0a
	mov di, msg.readonly_verifysegm.selector
	pop ax			; get original selector
	call hexword
	mov dx, msg.readonly_verifysegm
	call putsz_error
	jmp cmd3


verifysegm: section_of_function
	call ispm
	jnz .rm			; (NC)
	push ax
	push es
	_386_o32	; push edi
	push di
	push bp
	mov bp, sp
	sub sp, 8
	 push ss
	 pop es
	mov di, sp
_386	movzx edi, di
	mov ax, 000Bh		; get descriptor
	int 31h
	jc @F
	test byte [di+5], 8	; code segment ?
	jz @F			; (NC) no -->
	and byte [di+5], 0F3h	; reset CODE+conforming attr
	or byte [di+5], 2	; set writable
	mov bx, word [scratchsel]
	mov ax, 000Ch
	int 31h
@@:
	mov sp, bp
	pop bp
	_386_o32	; pop edi
	pop di
	pop es
	pop ax
.rm:
.retn:
	retn

subcpu 286
		; INP:	dx = 86 Mode segment to access
		; OUT:	bx = scratch selector, addressing that segment,
		;	 limit set to 64 KiB (allow all 16-bit accesses)
		; CHG:	-
		; STT:	ss = lDEBUG_DATA_ENTRY selector, in PM
setrmsegm: section_of_function
	mov bx, word [ss:scratchsel]
	call setrmaddr

		; INP:	bx = selector
		; OUT:	limit set to 0FFFFh
		; STT:	in PM
setrmlimit: section_of_function
	push ax
	push cx
	push dx
	xor cx, cx
; _386	dec cx		; limit 0FFFF_FFFFh on 386+
		; We don't want that here. All users expect a 64 KiB segment.
	mov dx, -1	; limit 0FFFFh on 286
	mov ax, 8
	int 31h		; set limit
	pop dx		; restore base segment
	pop cx
	pop ax
	retn

setrmaddr:		;<--- set selector in BX to segment address in DX
.:
	push ax
	push cx
	push dx
	mov cx, dx
	shl dx, 4
	shr cx, 12	; cx:dx = base address
	mov ax, 7
	int 31h		; set base
	pop dx
	pop cx
	pop ax
	retn

subcpureset
%endif

		; Read a byte relative to cs:eip
		;
		; INP:	reg_cs, reg_eip
		;	cx = (signed) eip adjustment
		; OUT:	al = byte at that address
		;	(e)bx = new offset (eip+adjustment)
		; CHG:	-
getcseipbyte:
	push es
%if _PM
	mov bx, word [reg_cs]
	mov es, bx
	call test_d_b_bit
	jz .16
[cpu 386]
	mov ebx, dword [reg_eip]
	push edx
	movsx edx, cx
	add ebx, edx
..@getcseipbyte_fault_skip_6_near_call:
	mov al, byte [es:ebx]
	pop edx
	pop es
	retn
__CPU__
.16:
%else
	mov es, word [reg_cs]
%endif
	mov bx, word [reg_eip]
	add bx, cx
..@getcseipbyte_fault_skip_2_near_call:
	mov al, byte [es:bx]
	pop es
	retn

		; Write to a byte relative to cs:eip
		;
		; INP:	reg_cs, reg_eip
		;	cx = (signed) eip adjustment
		;	al = source byte to write
		; OUT:	NC if apparently written
		;	CY if failed to get a writeable selector
		; CHG:	(e)bx
setcseipbyte:
	push es
%if _PM
	mov bx, word [reg_cs]
	call verifysegm
	jc .ret
	mov es, bx
	call test_d_b_bit
	jz .16
[cpu 386]
	mov ebx, dword [reg_eip]
	push edx
	movsx edx, cx
..@setcseipbyte_fault_skip_6_near_call:
	mov byte [es:ebx+edx],al
	pop edx
	pop es
	clc
	retn
__CPU__
.16:
%else
	mov es, word [reg_cs]
%endif
	mov bx, word [reg_eip]
	add bx, cx
..@setcseipbyte_fault_skip_2_near_call:
	mov byte [es:bx], al
	clc
.ret:
	pop es
	retn

		; Exchange byte with memory
		;
		; INP:	bx:(e)dx-> destination byte
		;	al = source byte
		; REM:	Determines whether to use edx by the
		;	 segment limit of the selector.
		;	 (Uses in run.asm always pass a segmented
		;	 address obtained from getsegmented. This
		;	 will have edxh = 0 always so it doesn't
		;	 matter whether we use edx or dx.)
		; OUT:	CY if failed due to segment not writable
		;	NC if successful,
		;	 al = previous value of destination byte
		; CHG:	ah
writemem:
%if _DEBUG1
	push dx
	push ax

	call getlinear_high_limit.do_not_use_test	; NB do NOT resetmode
	jc @F			; already an error ?  then return --> (CY)
	push bx
	push cx
	mov bx, test_records_Writemem
	call handle_test_case_multiple_16
				; check whether this should testcase the error
				; CY to indicate error from this call
	pop cx
	pop bx
@@:
	pop ax
	pop dx
	jnc .do_not_use_test
	retn			; return CY here

%endif
.do_not_use_test:

	mov ah, al
%if _PM
	call ispm
	jnz .16			; (NC from ispm) -->
	call verifysegm		; make bx a writeable segment
	jc .ret
_386_PM	call test_high_limit	; 32-bit segment ?
	jz .16			; (NC from test_d_b_bit) -->
[cpu 386]
	push ds
	mov ds, bx
..@writemem_fault_skip_2_near_call_a:
	xchg al, byte [edx]
..@writemem_fault_skip_2_near_call_b:
	cmp ah, byte [edx]
	pop ds
__CPU__
	jmp short .cmp
.16:
%endif
	push ds
	mov ds, bx
	push bx
	mov bx, dx
..@writemem_fault_skip_4_near_call_a:
	xchg al, byte [bx]
..@writemem_fault_skip_4_near_call_b:
	cmp ah, byte [bx]
	pop bx
	pop ds
.cmp:
	je .ret			; (NC)
	stc			; Failed to compare (i.e. memory wasn't our byte after writing).
				; This check catches ROM that will silently fail to write.
.ret:
	retn


		; Read byte from memory
		;
		; INP:	bx:(e)dx-> destination byte
		; REM:	Determines whether to use edx by the
		;	 segment limit of the selector.
		;	 (Uses in run.asm always pass a segmented
		;	 address obtained from getsegmented. This
		;	 will have edxh = 0 always so it doesn't
		;	 matter whether we use edx or dx.)
		; OUT:	 al = value of byte read
readmem: section_of_function
%if _DEBUG1
	push dx
	push ax

	call getlinear_high_limit.do_not_use_test	; NB do NOT resetmode
	jc @F			; already an error ?  then return --> (CY)
	push bx
	push cx
	mov bx, test_records_Readmem
	call handle_test_case_multiple_16
				; check whether this should testcase the error
				; CY to indicate error from this call
	pop cx
	pop bx
@@:
	pop ax
	pop dx
	jnc .do_not_use_test
	mov al, byte [test_readmem_value]
				; return a most likely wrong value
	retn

%endif
.do_not_use_test:

%if _PM
_386_PM	call test_high_limit	; 32-bit segment ?
	jz .16
[cpu 386]
	push ds
	mov ds, bx
..@readmem_fault_skip_2_near_call:
	mov al, byte [edx]
	pop ds
	retn
__CPU__
.16:
%endif
	push ds
	push bx
	mov ds, bx
	mov bx, dx
..@readmem_fault_skip_4_near_call:
	mov al, byte [bx]
	pop bx
	pop ds
	retn


%if !_LOADER
		; Q command - quit.
qq:
	call guard_re
	xor cx, cx	; no qq mode selected
	dec si
.loop:
	lodsb
	call uppercase
	cmp al, 'A'
	je qq_a
	mov ch, qqmode_b; QB mode (breakpoint before terminate)
	cmp al, 'B'
	je .otherletter
	mov ch, qqmode_c; QC mode (terminate device in a container MCB)
	cmp al, 'C'
	je .otherletter
	mov ch, qqmode_d; QD mode (terminate device in initialisation)
	cmp al, 'D'
	je .otherletter
	mov byte [qq_mode], cl
	jmp qq_default

.otherletter:
	or cl, ch
	jmp .loop

	usesection lDEBUG_DATA_ENTRY
qq_mode:	db 0
qqmode_b:	equ 1
qqmode_c:	equ 2
qqmode_d:	equ 4
	usesection lDEBUG_CODE

qq_a:
	lodsb
	call chkeol
	call terminate_attached_process
	mov bx, msg.qq_a_unterminated
	jz .attached_unterminated
	mov bx, msg.qq_a_terminated
.attached_unterminated:
	call putrunint
	mov dx, bx
	jmp putsz


qq_default:
	call chkeol

%if _RH
	clropt [options6], opt6_rh_mode
	clropt [internalflags3], dif3_auxbuff_guarded_3
	clropt [internalflags6], dif6_rh_mode_was | dif6_rh_mode | dif6_rh_mode_2
%endif
	call guard_auxbuff

%if _DEVICE
 %if _APPLICATION || _BOOTLDR
	testopt [internalflags6], dif6_device_mode
	jz .nondevice
 %endif
	test cl, qqmode_c | qqmode_d
	jnz .deviceselected
	mov dx, msg.qq_device_none_selected
	jmp putsz

.deviceselected:
.nondevice:
%endif
 %if _BOOTLDR
		; Test whether we are in non-DOS mode, and were
		; currently entered in protected mode. Since
		; this will make the entire operation fail,
		; it has to be checked for before modifying
		; or releasing any of the resources.
		; (Does this ever occur? No?)
	testopt [internalflags], nodosloaded
	jz .notpmnodos
%if _PM
	call ispm
  %if _TSR	; same message, reuse code
	jz .cannotpmquit
  %else
	jnz .notpmnodos_nodos
	mov dx, msg.cannotpmquit
	jmp putsz
  %endif
%endif
.notpmnodos_nodos:
	call bootgetmemorysize		; dx => behind usable memory
	mov ax, word [ boot_new_memsizekib ]
	mov cl, 6
	shl ax, cl
	cmp ax, dx			; same?
	je @F
	mov dx, msg.cannotbootquit_memsizes
	jmp .putsz
%if !_TSR || !_PM
	.putsz equ putsz
%endif

@@:
.notpmnodos:
 %endif
%if _PM
 %if _TSR
		; Test whether we are in TSR mode, and were
		; currently entered in protected mode. Since
		; this will make the entire operation fail,
		; it has to be checked for before modifying
		; or releasing any of the resources.
	testopt [internalflags], tsrmode
	jz .notpmtsr
	call ispm
	jnz .notpmtsr

; This isn't yet implemented. Broken down:
; * Uses terminate_attached_process which returns in real mode.
;  * Exception vectors are implicitly restored/discarded by that.
; * (RM) Interrupt vectors are currently restored in real mode. Unnecessary.
; * The VDD is un-registered in real mode. Necessary?
; * Normal 21.4C is used to return to the real parent.
;  * We have to discard our DOS process resources. Any DPMI TSR resources?
;  * We must again gain control in debuggee's mode after discarding them.
;  * We must return to the debuggee and seemlessly discard our memory. The
;    stack trick possibly/probably does not work in protected mode.

.cannotpmquit:
	mov dx, msg.cannotpmquit
.putsz:
	jmp putsz

.notpmtsr:
 %endif

 %if (opt4_int_2F_hook)&~0FFh
  %fatal DCO4 re-ordered, adjust code here
 %endif
	mov ax, [options4]
 	mov ah, __TEST_IMM8
	xchg ah, [dpmidisable]		 	; disable DPMI hook
						; (SMC in section lDEBUG_DATA_ENTRY)
	push ax
	clropt [options4], opt4_int_2F_hook	; avoid a new hook while terminating
%endif
%else	; !_LOADER
qq:
	call chkeol
%endif

qq_restore_interrupts_simulated:
	xor bp, bp

%if _CATCHINT2D
.2D:
	mov al, 2Dh		; interrupt number
	mov si, int2D		; -> IISP entry header
	mov cx, "2D"
	mov dx, opt4_int_2D_force >> 16
 %if (opt4_int_2D_force >> 16) == dif4_int_2D_hooked
	call qq_int_unhook_sim.set_bx_to_dx
 %else
	mov bx, dif4_int_2D_hooked
	call qq_int_unhook_sim
 %endif
%endif


%if _CATCHINTFAULTCOND && _CATCHINT0D
.0D:
	mov al, 0Dh		; interrupt number
	mov si, intr0D		; -> IISP entry header
	mov cx, "0D"
	mov dx, opt4_int_0D_force >> 16
 %if (opt4_int_0D_force >> 16) == dif4_int_0D_hooked
	call qq_int_unhook_sim.set_bx_to_dx
 %else
	mov bx, dif4_int_0D_hooked
	call qq_int_unhook_sim
 %endif
%endif


%if _CATCHINTFAULTCOND && _CATCHINT0C
.0C:
	mov al, 0Ch		; interrupt number
	mov si, intr0C		; -> IISP entry header
	mov cx, "0C"
	mov dx, opt4_int_0C_force >> 16
 %if (opt4_int_0C_force >> 16) == dif4_int_0C_hooked
	call qq_int_unhook_sim.set_bx_to_dx
 %else
	mov bx, dif4_int_0C_hooked
	call qq_int_unhook_sim
 %endif
%endif


%if _CATCHINT08
.08:
	mov al, 08h		; interrupt number
	mov si, intr8		; -> IISP entry header
	mov cx, "08"
	mov dx, opt4_int_08_force >> 16
 %if (opt4_int_08_force >> 16) == dif4_int_08_hooked
	call qq_int_unhook_sim.set_bx_to_dx
 %else
	mov bx, dif4_int_08_hooked
	call qq_int_unhook_sim
 %endif
%endif


.serial:
	mov al, byte [serial_installed_intnum]
	mov si, serial_interrupt_handler
	push cx			; (make space)
	mov di, sp		; es:di -> word on stack
	call hexbyte		; write byte value as text
	pop cx			; cx = what to write into error message
	mov dx, opt4_int_serial_force >> 16
 %if (opt4_int_serial_force >> 16) == dif4_int_serial_hooked
	call qq_int_unhook_sim.set_bx_to_dx
 %else
	mov bx, dif4_int_serial_hooked
	call qq_int_unhook_sim
 %endif

%if _PM
.2F:
	mov al, 2Fh		; interrupt number
	mov si, debug2F		; -> IISP entry header
	mov cx, "2F"
	mov dx, opt4_int_2F_force >> 16
	 testopt [internalflags], hooked2F
	 jz .noint2F
	call qq_int_unhook_sim.need

.noint2F:
%endif


%if CATCHINTAMOUNT && ! (_DEBUG && ! _DEBUG_COND)
 %if _DEBUG
	testopt [internalflags6], dif6_debug_mode
	jnz .skipints
 %endif
		; Simulate to restore interrupt vectors.
	mov si, inttab
	mov di, intforcetab
%if _CATCHINT06 && _DETECT95LX
	mov cx, word [inttab_number_variable]
%else
	mov cx, inttab_number
%endif
	jcxz .intsimend
	xor dx, dx
.nextintsim:
	lodsb
	xchg ax, bx			; bl = number
	lodsw				; si -> list
	xchg ax, si			; si -> entry, ax -> list
	xchg ax, bx			; al = number, bx -> list
	push di
	mov dh, byte [di]
	call UnhookInterruptForceSim
	pop di
	push ss
	pop es
	jnc @F
	mov di, msg.serial_cannot_unhook.int
	call hexbyte
	mov dx, msg.serial_cannot_unhook.nowarn
	call putsz
	inc bp
@@:
	inc di
	xchg bx, si			; si -> list
	loop .nextintsim

.intsimend:
.skipints:
%endif

	mov dx, msg.empty_message
	test bp, bp
	jnz qq_attached_unterminated.common


%if _DEVICE
qq_device_prepare:
 %if _APPLICATION || _BOOTLDR
	testopt [internalflags6], dif6_device_mode
	jz qq_nondevice
 %endif

%if _PM
	mov dx, msg.qq_device_pm
	call ispm
	jz @F			; in PM -->
%endif

		; Try quitting early in device init ?
	testopt [qq_mode], qqmode_d
	jz .device_c		; no, must be container quit -->

.device_d:
	testopt [internalflags], tsrmode
	jz .check_device_c

	mov si, regs
	mov di, device_quittable_regs
	mov cx, words(regs.size)
	repe cmpsw		; can quit to device init ?
	jne .check_device_c
	les di, [device_header_address]
	mov al, -1
	mov cl, 4
	repe scasb		; is next device pointer still -1 ?
	push ss
	pop es
	je qq_device_got	; yes -->

.check_device_c:
		; Cannot quit to device init. Clear the flag
		;  so we know later on that we're trying QC.
	clropt [qq_mode], qqmode_d
	testopt [qq_mode], qqmode_c
				; actually want to try QC ?
	jnz .device_c		; yes -->

	mov dx, msg.qq_device_no_d
@@:
	jmp qq_attached_unterminated.common

.device_c:
	mov ax, 5802h
	int 21h
	mov ah, 0
	push ax			; preserve UMB link
	mov ax, 5803h
	mov bx, 1
	int 21h			; enable UMB link
				;  we want to support the case in which
				;  the first UMCB may have changed. so
				;  instead of searching for it again we
				;  just request the link enabled.

	mov bx, -1
	mov ah, 52h
	int 21h
	mov di, bx
	cmp bx, - (30h + 12h)
	ja .no_c
	cmp bx, 1
	je .no_c
	mov dx, word [es:bx - 2]
	mov cx, 30h

.nulloop:
	mov si, msg.NULblank
	cmpsw			; di += 2, si += 2. compare
	jne .nulnext
.nulcheck:
	push di
	push cx
	mov cl, 3		; 3 more words to go
	repe cmpsw		; match ?
	pop cx
	pop di
	je .nulfound
.nulnext:
	dec di			; di -= 1 so it ends up 1 higher than prior
	loop .nulloop
	jmp .no_c

.nulfound:
	sub di, 3 * 2 + 4 + 2	; (strategy, interrupt, flags are words,
				;  next device pointer is a dword.
				;  additional plus 2 for the cmpsw output.)

		; es:di -> NUL device header
.devloop:
	mov ax, word [device_header_address]
	cmp ax, word [es:di]
	jne .devnext
	mov ax, word [device_header_address + 2]
	cmp ax, word [es:di + 2]
	je .mcb
.devnext:
	inc cx
	js .no_c

	les di, [es:di]
	cmp di, -1
	jne .devloop
	jmp .no_c

.mcb:

	mov word [.device_reference], di
	mov word [.device_reference + 2], es

	and word [.counter], 0
	mov di, ax		; => start of memory allocated to us
	mov cx, word [device_mcb_paragraphs]
				; = amount paragraphs allocated to us
	add di, cx		; => behind memory allocated to us

		; dx => first MCB
.mcbloop:
	mov es, dx
	mov si, dx		; => MCB
	add si, word [es:3]
	inc si			; => next MCB (or behind current MCB)
	cmp byte [es:0], 'M'	; valid MCB ?
	je @F
	cmp byte [es:0], 'Z'
	jne .no_c		; no -->
@@:
	cmp dx, ax		; start of MCB < allocation ?
	jae .mcbnext		; no -->
	cmp si, di		; end of MCB > allocation ?
	jb .mcbnext		; no -->

	cmp word [es:1], 0	; free ?
	je .mcbnext		; do not match -->
	dec ax			; => our (sub) MCB
	cmp dx, ax		; matches (DEVLOAD style) ?
	jne .mcbcontainer	; no -->
	cmp word [es:3], cx	; size matches ?
	jne .mcbcontainer	; no -->
	and word [.container_segment], 0
	jmp .mcbdone		; found a non-container MCB

.mcbnext:
	inc word [.counter]	; safeguard against infinite loop
	jz .no_c
	mov dx, si		; => next MCB
	cmp byte [es:0], 'M'	; prior was 'M' ?
	je .mcbloop		; yes, so loop -->
	jmp .no_c

.mcbcontainer:
	inc ax			; => allocated block (device header)
	cmp word [es:1], 50h	; SD owner system ?
	jae .mcbnext
	cmp word [es:8], "SD"
	jne .mcbnext		; no -->

	push word [es:0]
	pop word [.container_is_z]
	mov word [.container_end], si
	mov word [.container_segment], dx
	inc dx			; => sub-MCB
.submcbloop:
	mov es, dx
	mov si, dx
	add si, word [es:3]
	inc si			; => next sub or MCB (or behind Z MCB)
	cmp dx, ax
	jae .submcbnext
	cmp si, di
	jb .submcbnext

	push ax
	dec ax			; => our (sub) MCB
	cmp dx, ax		; matched start of allocation ?
	pop ax
	jne .submcbnext
	cmp word [es:3], cx	; matches allocation size ?
	jne .submcbnext
	cmp word [es:1], 0	; is not free ?
	je .submcbnext
	jmp .mcbdone		; all yes, found it -->

.submcbnext:
	inc word [.counter]	; safeguard against infinite loop
	jz .no_c
	mov dx, si		; => next sub MCB or after container
	mov si, word [.container_end]
	cmp dx, si		; after container ?
	jb .submcbloop		; no -->
		; This jump could be a jne but generally
		;  we can assume that the container does
		;  not overflow across the 1 MiB limit.
		; And this is more hardened against errors.
	mov dx, si		; insure we use actual container end
	cmp byte [.container_is_z], 'Z'
				; container had a Z ?
	jne .mcbloop		; no -->
		; if here, loop now, dx already updated and
		;  furthermore es does not point at container!
	jmp .no_c

	usesection lDEBUG_DATA_ENTRY
	align 4, db 0
.device_reference:	dd 0
.container_segment:	dw 0
.container_end:		dw 0
.container_is_z:	dw 0
.counter:		dw 0
	usesection lDEBUG_CODE

.mcbdone:

	db __TEST_IMM8		; (skip stc, NC)
.no_c:
	stc
	pop bx
	pushf
	mov ax, 5803h
	int 21h			; restore UMB link
	popf
	push ss
	pop es
	jnc @F
	mov dx, msg.qq_device_no_c
	jmp qq_attached_unterminated.common

@@:

qq_device_got:
qq_nondevice:
%endif


qq_restore_interrupts:
%if _CATCHINT2D
.2D:
	mov al, 2Dh		; interrupt number
	mov si, int2D		; -> IISP entry header
	mov cx, "2D"
	mov dx, opt4_int_2D_force >> 16
 %if (opt4_int_2D_force >> 16) == dif4_int_2D_hooked
	call qq_int_unhook_real.set_bx_to_dx
 %else
	mov bx, dif4_int_2D_hooked
	call qq_int_unhook_real
 %endif
	jc qq_attached_unterminated.common
%endif


%if _CATCHINTFAULTCOND && _CATCHINT0D
.0D:
	mov al, 0Dh		; interrupt number
	mov si, intr0D		; -> IISP entry header
	mov cx, "0D"
	mov dx, opt4_int_0D_force >> 16
 %if (opt4_int_0D_force >> 16) == dif4_int_0D_hooked
	call qq_int_unhook_real.set_bx_to_dx
 %else
	mov bx, dif4_int_0D_hooked
	call qq_int_unhook_real
 %endif
	jc qq_attached_unterminated.common
%endif


%if _CATCHINTFAULTCOND && _CATCHINT0C
.0C:
	mov al, 0Ch		; interrupt number
	mov si, intr0C		; -> IISP entry header
	mov cx, "0C"
	mov dx, opt4_int_0C_force >> 16
 %if (opt4_int_0C_force >> 16) == dif4_int_0C_hooked
	call qq_int_unhook_real.set_bx_to_dx
 %else
	mov bx, dif4_int_0C_hooked
	call qq_int_unhook_real
 %endif
	jc qq_attached_unterminated.common
%endif


%if _CATCHINT08
.08:
	mov al, 08h		; interrupt number
	mov si, intr8		; -> IISP entry header
	mov cx, "08"
	mov dx, opt4_int_08_force >> 16
 %if (opt4_int_08_force >> 16) == dif4_int_08_hooked
	call qq_int_unhook_real.set_bx_to_dx
 %else
	mov bx, dif4_int_08_hooked
	call qq_int_unhook_real
 %endif
	jc qq_attached_unterminated.common
%endif


.serial:
	testopt [serial_flags], sf_init_done
	jz @F
	call serial_clean_up			; unhook interrupt
	clropt [serial_flags], sf_init_done | sf_use_serial
						; clear (in case return to cmd3)
	clropt [options], enable_serial		; do not output to serial any longer
@@:
	testopt [internalflags4], dif4_int_serial_hooked
	jz .done_serial
	call serial_uninstall_interrupt_handler
	jnc .done_serial			; if it succeeded -->

	mov di, msg.serial_cannot_unhook.int
	mov al, byte [serial_installed_intnum]
	call hexbyte
	mov dx, msg.serial_cannot_unhook.nowarn
	mov byte [serial_interrupt_handler + ieEOI], 0
						; we do not issue EOI any longer
	jmp qq_attached_unterminated.common


.done_serial:

%if _PM
.2F:
	mov al, 2Fh		; interrupt number
	mov si, debug2F		; -> IISP entry header
	mov cx, "2F"
	mov dx, opt4_int_2F_force >> 16
 %if (opt4_int_2F_force >> 16) == dif4_int_2F_hooked
	mov bx, dx
 %else
	mov bx, dif4_int_2F_hooked
 %endif
	 testopt [internalflags], hooked2F
	 jz .noint2F
	call qq_int_unhook_real.need
	jc qq_attached_unterminated.common

.got2F:
	clropt [internalflags], hooked2F

.noint2F:
%endif

%if _LOADER
	int 19h
%else ; _LOADER
%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [internalflags], nodosloaded
	jnz .areas
 %else
	jmp .areas
 %endif
%endif


%if _APPLICATION || _DEVICE
		; Cancel child's process if any.
		; This will drop to real mode if debuggee is in protected mode.
%if _TSR || _DEVICE
	testopt [internalflags], tsrmode
	jz .terminate_attached	; falls through for device or TSR application

%if _PM
	call ispm
	jz @F			; in PM -->
	testopt [internalflags], canswitchmode
	jz @FF			; in 86 Mode and cannot switch to PM -->

	setopt [internalflags], modeswitched	; set flag for resetmode
	mov al, 0
	call sr_state		; save state
	call switchmode 	; switch to PM
		; ! handle_mode_changed not called here !
		; do not call InDOS or other functions using seg/sels
@@:
	call pm_reset_handlers
		; ! this calls resetmode

		; remember that we cannot access Protected Mode any longer
	clropt [internalflags], canswitchmode | switchbuffer
@@:
%endif

	jmp .areas

.terminate_attached:
%endif

	call terminate_attached_process
	jz qq_attached_unterminated
%if _PM
	call ispm
	jnz @F

	mov dx, msg.qq_still_pm
	jmp qq_attached_unterminated.common
@@:
%endif
%endif


.areas:
%if _AREAS && _AREAS_HOOK_CLIENT
	call uninstall_areas.qq_entry
 %if _AREAS_HOOK_SERVER
	jc .areas_error
 %else
	jnc @F
	mov dx, msg.qqlate_areas_error
	jmp qq_attached_unterminated.common
 %endif
%endif

@@:
%if _AREAS_HOOK_SERVER
	mov dx, word [ddebugareas.next + 2]
	mov bx, word [ddebugareas.next]
	mov ax, ss
	cmp dx, ax
	je @F
	 push dx
	 push bx
	mov al, 0
	push cs
	call qqlate_86m_to_areastruc_entry
	push ss
	pop ds
	push ss
	pop es
	 pop bx
	 pop dx
	cmp dx, word [ddebugareas.next + 2]
	jne @B
	cmp bx, word [ddebugareas.next]
	jne @B
.areas_error:
	mov dx, msg.qqlate_areas_error
	jmp qq_attached_unterminated.common

@@:
%endif


.restoreints:
%if CATCHINTAMOUNT && ! (_DEBUG && ! _DEBUG_COND)
 %if _DEBUG
	testopt [internalflags6], dif6_debug_mode
	jnz .skiprestoreints
 %endif
		; Restore interrupt vectors.
	mov si, inttab
	mov di, intforcetab
%if _CATCHINT06 && _DETECT95LX
	mov cx, word [inttab_number_variable]
%else
	mov cx, inttab_number
%endif
	jcxz .intend
	xor dx, dx
.nextint:
	lodsb
	xchg ax, bx			; bl = number
	lodsw				; si -> list
	xchg ax, si			; si -> entry, ax -> list
	xchg ax, bx			; al = number, bx -> list
	push di
	mov dh, byte [di]
	call UnhookInterruptForce
	pop di
	inc di
	xchg bx, si			; si -> list
	loop .nextint

.intend:
.skiprestoreints:
%endif


%if _PM
	pop ax					; (discard)
%endif


qqlate:
%if _SYMBOLIC
		; Free XMS symbol table. 86 Mode memory backed symbol table
		;  is freed by our process's termination.
		; Update: QD device termination does not currently terminate
		;  our process. In the meantime, free things explicitly.
		;  Note that the calls need to be in this order because of
		;  how zz_free_xms calls zz_free_reset first.
	nearcall zz_free_dos
	nearcall zz_free_xms
%endif

		; Release the registered VDD.
%if _VDD
	testopt [internalflags], ntpacket
	jz .novdd
	mov ax, word [hVdd]
	UnRegisterModule
.novdd:
%endif

%if _VXCHG
	testopt [internalflags6], dif6_vv_mode
	jz @F

	call vv_disable
@@:
%endif

%if _ALTVID
	call setscreen
%endif

		; Restore termination address.
%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [internalflags], nodosloaded
	jnz .bootterminate	; terminate -->
 %else
	jmp .bootterminate
 %endif
%endif
%if _DEVICE
 %if _APPLICATION
	testopt [internalflags6], dif6_device_mode
	jnz .deviceterminate
 %else
	jmp .deviceterminate
 %endif
%endif

%if _APPLICATION || _DEVICE
%if _TSR || _DEVICE
.appterminate:
	 push ss
	 pop es
	testopt [internalflags], tsrmode
	jz .nontsrterminate

.tsrterminate:
	mov dx, qq.proceedtsrtermination

.terminate_to_shim_process:
	xor si, si
	mov es, word [auxbuff_segorsel]
	xor di, di
	xor ax, ax
	mov cx, 8
	rep stosw		; 10h MCB bytes
	mov cx, 40h
	rep movsw		; 80h PSP bytes
	mov ax, es
	inc ax
	mov word [es:1], ax	; fake MCB
	push ds
	mov ds, ax
	mov word [34h], 18h
	mov word [36h], ax	; insure default PHT and fix segment
	mov word [32h], 1	; only one PHT entry (zero might crash)
	mov byte [18h], -1	; PHT entry is closed
	mov word [2Ch], 0	; PSP clear
	call .setparent		; make it self-owned, just in case
	mov bx, ss		; => process segment
	dec bx			; span actual MCB
%if _DEVICE
 %if _APPLICATION
	testopt [ss:internalflags6], dif6_device_mode
	jz @F
 %endif
	sub bx, deviceshim_size_p + paras(10h)
				; span shim and pseudo MCB
@@:
%endif
	mov ds, bx		; => our (real) MCB
	cmp dx, qq.proceedtsrtermination
	jne @F
	mov word [1], ax	; owner = fake PSP
@@:
	pop ds
	call .setparent		; make the fake PSP our parent
	jmp short terminate_00	; see ya

.nontsrterminate:
%endif
	mov si, psp22		; restore termination address
	mov di, TPIV
	movsw
	movsw
	mov di, 16h		; restore PSP of parent
	movsw
		; Really done.

	testopt [qq_mode], qqmode_b
	jz @F
	int3

@@:
	mov ah, 4Ch		; quit
	mov al, byte [qqtermcode]
				; return code
	int 21h
%endif


terminate_00:	; used by terminate_attached_process
	mov ax, 4C00h		; quit
	int 21h


%if _AREAS_HOOK_SERVER
qqlate_86m_to_areastruc_entry:
	mov cx, areastruc_entry.qq_entry
	push ss
	push cx
	retf
%endif
%endif	; !_LOADER


qq_attached_unterminated:
%if !_LOADER
	call putrunint
%endif	; !_LOADER
	mov dx, msg.qq_unterm

.common:
		; Restore state:
%if _PM
 %if (opt4_int_2F_hook)&~0FFh
  %fatal DCO4 re-ordered, adjust code here
 %endif
 	pop ax
	mov [dpmidisable], ah	; (SMC in section lDEBUG_DATA_ENTRY)
	and al, opt4_int_2F_hook
	clropt [options4], opt4_int_2F_hook
	or [options4], al
%endif
	jmp putsz


%if !_LOADER
%if _DEVICE
qqlate.deviceterminate:
	testopt [qq_mode], qqmode_d
	jz .mode_c

.mode_d:
		; We modify the device request header
		;  only now, so in case of being unable
		;  to release something then the debugger
		;  will remain usable and stay resident.
	mov es, word [reg_es]
	mov bx, word [reg_ebx]
	mov ax, ds			; => process segment
	sub ax, paras(deviceshim_size + 10h)
					; span shim and pseudo MCB
	mov word [es:bx + 3], 8103h	; error, done, code: unknown command
	and word [es:bx + 0Eh], 0
	mov word [es:bx + 0Eh + 2], ax	; -> behind memory in use
		; es reset in run or qqlate.terminate_to_shim_process

%if 0		; old code to manually release process resources
	xor bx, bx		; = 0
	mov cx, word [32h]	; get amount of handles
.loop:
	mov ah, 3Eh
	int 21h			; close it
	inc bx			; next handle
	loop .loop		; loop for all process handles -->
%else		; new code re-using TSR terminate handling
	mov dx, .proceed
	jmp qqlate.terminate_to_shim_process


	usesection lDEBUG_DATA_ENTRY
.proceed:
	cli
	mov ax, cs
	mov ss, ax
	mov sp, stack_end
	mov ds, ax
	cld
	sti

	call entry_to_code_seg
	dw .proceedcode


	usesection lDEBUG_CODE
	code_insure_low_byte_not_0CCh
.proceedcode:
%endif

%if _DEBUG
		; avoid hooking interrupts again:
	mov byte [cs:..@patch_tsr_quit_run], __JMP_REL16
				; (SMC in section lDEBUG_CODE)
%endif
	testopt [qq_mode], qqmode_b
	jz @F
	mov word [reg_eip], entry_int3_retf
@@:
	jmp run			; run this


.mode_c:
qqlate_device_container:
	push es
	les di, [qq_device_prepare.device_reference]
	mov dx, es		; => device header pointing to ours
	mov ax, ss		; => process segment
	sub ax, deviceshim_size_p + paras(10h)
				; span shim and pseudo MCB
		; ! ax is re-used in .handlecontainer
	mov es, ax		; => our device header
	push word [es:0 + 2]
	push word [es:0]	; get our next link
	mov es, dx
	pop word [es:di]
	pop word [es:di + 2]	; update their next link
	mov es, ax
	or word [es:0], -1
	or word [es:0 + 2], -1	; de-initialise our next link
	pop es

	xor cx, cx		; flag: do not shrink our allocation
	mov bx, word [qq_device_prepare.container_segment]
	test bx, bx		; are we in a container ?
	jz .nocontainer		; no -->
.handlecontainer:
	add ax, word [device_mcb_paragraphs]
				; => behind our allocation
	mov dx, ax
	neg dx
	add dx, word [qq_device_prepare.container_end]
				; are we last in container ?
	push ds
	mov ds, bx		; => container

	je .notrail		; yes, easier -->

	dec ax			; => last paragraph allocated to us
				;  (buffer for trailer container MCB)
	mov es, ax
	xor si, si
	xor di, di
		; copy over MCB letter, owner, and name/type
	mov cx, words(16)
	rep movsw
	mov byte [ss:qq_device_prepare.container_is_z], 'M'
				; tell subsequent handler to use 'M'
	mov word [es:3], dx	; set new size
	inc cx			; flag: shrink our allocation

.notrail:
	call .setowner
	sub word [es:3], cx	; -= 1 in case we have trail
	mov al, byte [ss:qq_device_prepare.container_is_z]
	mov byte [es:0], al	; set our letter to M or Z
				;  (Z only if container had Z and also
				;  there is no trailing container created)

	sub dx, bx		; device mode MCB minus container MCB
	dec dx			; account for MCB paragraph to get MCB size
	mov word [3], dx	; adjust size
	mov byte [0], 'M'	; set M unconditionally
	test dx, dx		; size zero ?
	jnz @F
	mov word [1], dx	; yes, zero the owner too
@@:
	pop ds
	jmp qqlate.appterminate

.nocontainer:
	call .setowner
	jmp qqlate.appterminate


		; INP:	ss = debugger process/data segment
		; OUT:	es = dx => our device mode MCB
		;	owner of this MCB set to our process
		; STT:	R86M
		; CHG:	-
.setowner:
	mov dx, ss
	sub dx, deviceshim_size_p + paras(10h + 10h)
				; span shim, pseudo MCB, actual MCB
	mov es, dx		; => device mode MCB
	mov word [es:1], ss	; insure valid owner (must be our PSP)
	retn
%endif


%if (_APPLICATION && _TSR) || _DEVICE
	usesection lDEBUG_DATA_ENTRY

qq.proceedtsrtermination:
	cli
	mov ax, cs
	mov ss, ax
	mov sp, stack_end
	mov ds, ax
	cld
	sti
	sub word [reg_esp], 2+4+((qq.tsrfreecode_size+1)&~1)
	mov di, word [reg_esp]	; -> stack frame
	mov es, word [reg_ss]
	mov ax, word [reg_ds]
	stosw			; debuggee's ds
	mov ax, word [reg_eip]
	stosw
	mov ax, word [reg_cs]
	stosw			; debuggee's cs:ip
	push es
	push di
	mov si, qq.tsrfreecode
	mov cx, ((qq.tsrfreecode_size+1)>>1)
	rep movsw		; code on stack
	mov ax, cs		; => process segment
	dec ax			; span actual MCB
%if _DEVICE
 %if _APPLICATION
	testopt [internalflags6], dif6_device_mode
	jz @F
 %endif
	sub ax, deviceshim_size_p + paras(10h)
				; span shim + pseudo MCB
@@:
%endif
	mov word [reg_ds], ax	; = our MCB
	pop word [reg_eip]
	pop word [reg_cs]	; -> code on stack (at int3)
	testopt [qq_mode], qqmode_b
				; QB mode ?
	jnz @F			; yes, leave pointing cs:ip at int3
	inc word [reg_eip]	; point cs:ip past the int3
@@:
	testopt [options3], opt3_tsr_quit_leave_tf
	jnz @F
	clropt [reg_efl], 100h	; clear TF
@@:

	call entry_to_code_seg
	dw .proceedtsrcode


	usesection lDEBUG_CODE

	code_insure_low_byte_not_0CCh
.proceedtsrcode:
%if _DEBUG
		; avoid hooking interrupts again:
	mov byte [cs:..@patch_tsr_quit_run], __JMP_REL16
				; (SMC in section lDEBUG_CODE)
%endif
	jmp run			; run this


	usesection lDEBUG_DATA_ENTRY

	align 2, db 0
	; (Update: Explicitly clears TF now, except if the
	; option opt3_tsr_quit_leave_tf is set. See above.)
	;
	; Note that since we are in control of debuggee's TF and
	; reset it every time the debugger is entered, this code
	; will not be entered with TF set. It might be entered
	; with IF set and an interrupt might occur; the only harm
	; done then is that the interrupt handler has less stack
	; available. All flags must be preserved by this code.
qq.tsrfreecode:
	int3			; breakpoint for QB mode, 1 byte
	mov word [1], 0		; free the MCB
	pop ds			; restore debuggee's ds
	retf ((qq.tsrfreecode_size+1)&~1)	; jump
qq.tsrfreecode_size: equ $-qq.tsrfreecode


	usesection lDEBUG_CODE

		; INP:	ax => PSP segment to set as parent
		;	ss:dx -> entrypoint to set as PRA
qqlate.setparent:
	mov word [16h], ax
	mov word [0Ah], dx
	mov word [0Ah+2], ss
	retn
%endif


	usesection lDEBUG_CODE

%if _BOOTLDR
qqlate.bootterminate:
	sub word [reg_esp], 2*8+4+((qq.bootfreecode_size+1)&~1)
	mov di, word [reg_esp]	; -> stack frame
	mov es, word [reg_ss]
	mov ax, word [reg_ds]
	stosw
	mov ax, word [reg_es]
	stosw
	mov ax, word [reg_esi]
	stosw
	mov ax, word [reg_edi]
	stosw
	mov ax, word [reg_eax]
	stosw
	mov ax, word [reg_ecx]
	stosw
	mov ax, word [reg_ebx]
	stosw
	mov ax, word [reg_edx]
	stosw
	mov ax, word [reg_eip]
	stosw
	mov ax, word [reg_cs]
	stosw			; debuggee's cs:ip
	push es
	push di
	 push ds
	  push cs
	  pop ds		; => lDEBUG_CODE
	mov si, qq.bootfreecode
	mov cx, ((qq.bootfreecode_size+1)>>1)
	rep movsw		; code on stack
	 pop ds

	 push ss
	 pop es

	mov ax, word [ boot_new_memsizekib ]
	mov cl, 6
	shl ax, cl		; ax => source of EBDA (new position)
	mov dx, word [ boot_old_memsizekib ]
	shl dx, cl		; dx => destination of EBDA (old position)
	xor cx, cx		; size of EBDA to move (if none)
	push ds
	mov ds, cx
	mov bx, word [40Eh]	; new ref in word [0:40Eh] (if none)
	pop ds
	cmp byte [ boot_ebdaflag ], 0	; any EBDA ?
	jz .noebda

	push ds
	mov ds, ax		; => EBDA
	xor bx, bx
	mov bl, byte [ 0 ]	; EBDA size in KiB
	mov cl, 6
	shl bx, cl		; *64, to paragraphs
	mov cx, bx		; = size of EBDA to move (in paragraphs)
	mov bx, dx		; = new EBDA reference to put in word [0:40Eh]
	pop ds

.noebda:
	mov word [reg_eax], ax	; => relocated (new) EBDA position
				;  (in front of debugger image)
	mov word [reg_ebx], bx	; = what to put in word [0:40Eh],
				;  unchanged content of that word if no EBDA
	mov word [reg_ecx], cx	; = EBDA size, 0 if no EBDA
	mov word [reg_edx], dx	; = original (old) EBDA position
				; = original mem size (in paras)
				;  (behind/in debugger image)
	mov word [reg_ds], 0

	pop word [reg_eip]
	pop word [reg_cs]	; -> code on stack
	testopt [qq_mode], qqmode_b
				; QB mode ?
	jnz @F			; yes, leave pointing cs:ip at int3
	inc word [reg_eip]	; point cs:ip past the int3
@@:
	testopt [options3], opt3_tsr_quit_leave_tf
	jnz @F
	clropt [reg_efl], 100h	; clear TF
@@:
	; call dumpregs
%if _DEBUG
		; avoid hooking interrupts again:
	mov byte [cs:..@patch_tsr_quit_run], __JMP_REL16
				; (SMC in section lDEBUG_CODE)
%endif
	; jmp cmd3
	jmp run			; run this


	align 2, db 0
qq.bootfreecode:
	int3			; breakpoint for QB mode, 1 byte
	pushf
	cli
	call movp		; move EBDA back (if any)
	mov word [40Eh], bx	; back relocate EBDA (if any)
	mov cl, 6
	shr dx, cl		; = to KiB
	mov word [413h], dx	; back relocate mem size
	popf
	pop ds
	pop es
	pop si
	pop di
	pop ax
	pop cx
	pop bx
	pop dx
	retf ((qq.bootfreecode_size+1)&~1)
%endif
%endif	; !_LOADER


		; Move paragraphs
		;
		; INP:	ax:0-> source
		;	dx:0-> destination
		;	cx = number of paragraphs
		; CHG:	-
		; Note:	Doesn't work correctly on HMA; doesn't always wrap to LMA either.
		;	Do not provide a wrapped/HMA source or destination!
movp: section_of_function
	push cx
	push ds
	push si
	push es
	push di

	cmp ax, dx		; source above destination ?
	ja .up			; yes, move up (forwards) -->
	je .return		; same, no need to move -->
	push ax
	add ax, cx		; (expected not to carry)
	cmp ax, dx		; end of source is above destination ?
	pop ax
	ja .down		; yes, move from top down -->
	; Here, the end of source is below-or-equal the destination,
	;  so they do not overlap. In this case we prefer moving up.

.up:
	push ax
	push dx
.uploop:
	mov ds, ax
	mov es, dx
	xor di, di
	xor si, si		; -> start of segment
	sub cx, 1000h		; 64 KiB left ?
	jbe .uplast		; no -->
	push cx
	mov cx, 10000h /2
	rep movsw		; move 64 KiB
	pop cx
	add ax, 1000h
	add dx, 1000h		; -> next segment
	jmp short .uploop	; proceed for more -->
.uplast:
	add cx, 1000h		; restore counter
	shl cx, 1
	shl cx, 1
	shl cx, 1		; *8, paragraphs to words
	rep movsw		; move last part
	pop dx
	pop ax
	jmp short .return

.down:
	std			; _AMD_ERRATUM_109_WORKAROUND as below
.dnloop:
	sub cx, 1000h		; 64 KiB left ?
	jbe .dnlast		; no -->
	push ax
	push dx
	add ax, cx
	add dx, cx
	mov ds, ax		; -> 64 KiB not yet moved
	mov es, dx
	pop dx
	pop ax
	mov di, -2
	mov si, di		; moved from last word down
	push cx
	mov cx, 10000h /2
	rep movsw		; move 64 KiB
	pop cx
	jmp short .dnloop	; proceed for more -->
.dnlast:
	add cx, 1000h		; restore counter
	shl cx, 1
	shl cx, 1
	shl cx, 1		; *8, paragraphs to words
	mov di, cx
	dec di
	shl di, 1		; words to offset, -> last word
	mov si, di
	mov ds, ax
	mov es, dx		; first segment correct


	numdef AMD_ERRATUM_109_WORKAROUND, 1
		; Refer to comment in init.asm init_movp.

%if _AMD_ERRATUM_109_WORKAROUND
	jcxz @FF
	cmp cx, 20
	ja @FF
@@:
	movsw
	loop @B
@@:
%endif
	rep movsw		; move first part
	cld
.return:
	pop di
	pop es
	pop si
	pop ds
	pop cx
	retn
%if !_LOADER
qq.bootfreecode_size: equ $-qq.bootfreecode
%endif


%if (_CATCHINTFAULTCOND && _CATCHINT0D) \
	|| (_CATCHINTFAULTCOND && _CATCHINT0C) \
	|| _CATCHINT08 || _CATCHINT2D || _PM \
	|| 1	; (serial)
		; INP:	ds:si -> IISP entry
		;	al = interrupt number
		;	dx = interrupt unhook force flag (dif4 high word)
		;	bx = interrupt hook status flag (dif4 low word)
		;	cx = letters to insert into message
		;	bp = number of interrupts already unable to unhook
		; OUT:	NC if able to unhook or not currently hooked
		;	CY if currently hooked and unable to unhook,
		;	 error message displayed
		;	 bp incremented
		; CHG:	es, dx, ah, di
qq_int_unhook_sim.set_bx_to_dx:
	mov bx, dx

qq_int_unhook_sim:
	test word [internalflags4], bx
	jz .ret			; (NC)

.need:
	call UnhookInterruptForceSim
				; try unhooking it
	push ss
	pop es
	jnc .ret

	mov word [msg.serial_cannot_unhook.int], cx
	mov dx, msg.serial_cannot_unhook.nowarn
	call putsz
	inc bp
	stc

.ret:
	retn
%endif


%if (_CATCHINTFAULTCOND && _CATCHINT0D) \
	|| (_CATCHINTFAULTCOND && _CATCHINT0C) \
	|| _CATCHINT08 || _CATCHINT2D || _PM
		; INP:	al = interrupt number
		;	ds:si -> IISP entry
		;	dx = interrupt unhook force flag (dif4 high word)
		;	bx = interrupt hook status flag (dif4 low word)
		;	cx = text to insert into error message
		; OUT:	CY if is hooked and error unhooking,
		;	 dx -> error message
		;	NC if not hooked now (already wasn't or have unhooked),
		;	 ZR if was already unhooked
		;	 NZ if has been unhooked, was hooked
qq_int_unhook_real.set_bx_to_dx:
	mov bx, dx

qq_int_unhook_real:
	test word [internalflags4], bx
	jz .ret			; --> (NC, ZR)

.need:
	call UnhookInterruptForce
				; try unhooking it
	jnc .unhooked

	mov word [msg.serial_cannot_unhook.int], cx
	mov dx, msg.serial_cannot_unhook.nowarn
	stc
	retn

.unhooked:
	not bx
	and word [internalflags4], bx
	call update_inttab_optional
	test sp, sp		; (NC, NZ)
.ret:
	retn
%endif


%include "ss.asm"


	usesection lDEBUG_CODE

%if 0
getdebuggeebyte:
	push bp
	mov bp, sp
	sub sp, byte 4
	push bx
	push cx
%define _dedata -4
%define _bp 0
%define _ip 2
%define _adroffset 4
%define _adrsegment 8
	test byte [], memorydump
	jz .realmemory

	jmp short .return
.realmemory32:
.realmemory:
	mov ax, word [ bp + _adrsegment ]
	mov bx, word [ bp + _adroffset ]
	push ds
	mov ds, ax
	push word [ bx ]
	pop word [ bp + _dedata ]
	push word [ bx +2 ]
	pop word [ bp + _dedata +2 ]
	pop ds
;	test ax, ax
;	jnz .return
	mov dx, ax
	mov cl, 4
	shl ax, cl
	mov cl, 12
	shr dx, cl
	add ax, bx
	adc dx, byte 0
	jnz .return
	sub ax, 23h*4
	jb .return
	cmp ax, 2*4
	jae .return

	push ds
	xor bx, bx
	mov ds, bx
	push si
	push di
	mov si, 22h*4
	mov di, hackints.dummy22
	movsw
	movsw
	mov bl, 8
	add si, bx
	add di, bx
	movsw
	movsw

	mov cl, byte [ bx - 4 + hackints2324 ]
	mov byte [ bp + _dedata ], cl
.return:
	pop cx
	pop bx
	pop ax
	pop dx
	pop bp
	retn 6


		; Interrupt hack table
		;
		; This contains the Int23 and Int24 handler we want to show
		; the user. As we'll retrieve a dword per access,
	align 4, db 0
hackints:
.dummy22:	dd 0
.23:		dd 0
.24:		dd 0
.dummy25:	dd 0
%endif


%if !_LOADER
%include "ww.asm"
%endif


	usesection lDEBUG_CODE

%ifn _EMS
xx: equ error
%else
 %define extcall nearcall
 %define extcallcall nearcall
 %imacro internalcoderelocation 0-*.nolist
 %endmacro
 %imacro internaldatarelocation 0-*.nolist
 %endmacro
 %imacro linkdatarelocation 0-*.nolist
 %endmacro
 %define relocated(address) address
 %assign ELD 0

 %include "xxshared.asm"

 %undef extcall
 %undef extcallcall
 %unimacro internalcoderelocation 0-*.nolist
 %unimacro internaldatarelocation 0-*.nolist
 %unimacro linkdatarelocation 0-*.nolist
 %undef relocated
%endif	; _EMS

%if _DUALCODE
	usesection lDEBUG_CODE2
error_mirror:
	dualcall error

	usesection lDEBUG_CODE
%else
error_mirror equ error
%endif

		; Error handlers.
dualfunction
error: section_of_function
	push ss
	pop es
	push ss
	pop ds
%if _RH
	clropt [internalflags6], dif6_rh_mode_2 | dif6_rh_mode
%endif

%if _EXTENSIONS && _EXT_PUTS_GETLINE
	mov bl, 4
		; INP:	bl = 4 means want to get effective carat position,
		;	 si -> text with error
		;	bl = 5 means want to get carat position with
		;	 si = input value for counter
		; OUT:	bl == 4 if to run default handling,
		;	 si may be modified
		;	bl == 5 if to use si as number of spaces to indent,
		;	 si = value for counter
		;	bl must be 4 or 5
		; CHG:	ax, bh, cx, dx, di
		; STT:	ds = es = ss
		;	UP, EI
	call transfer_to_ext_puts_getline
	mov cx, si
	cmp bl, 4
	jne .gotcx
%else
	mov cx, si
%endif
	sub cx, line_in+3
	cmp cx, 256
	ja .invalid
	add cx, word [promptlen]; number of spaces to skip
	db __TEST_IMM16		; (skip xor)
.invalid:
	xor cx, cx		; if we're really messed up
.gotcx:
	mov sp, [throwsp]
	jmp near [throwret]
		; INP:	cx = number of spaces to indent

		; This is the default address in throwret.
		; Display the error, then jump to errret.
errhandler:
	call get_columns	; ax = columns
.:
	sub cx, ax
	jnc .
	add cx, ax
	jz err2
	mov al, 32
.loop:
	call putc
	loop .loop
err2:
	mov dx, errcarat
	call putsz		; print string
	mov ax, 01FFh
	call setrc
	mov word [lastcmd], dmycmd
				; cancel command repetition
	jmp near [errret]	; return to the prompt (cmd3, aa01)


setrc: section_of_function
	cmp word [rc], 0
	jne .ret
	mov word [rc], ax
.ret:
	retn


%if !_LOADER
		; Terminate the attached process, if any
		;
		; OUT:	NZ if now no process attached
		;	ZR if still a process attached,
		;	 ie we failed to terminate this one
terminate_attached_process:
	testopt [internalflags], attachedterm
	jnz @F
%if _TSR && (_APPLICATION || _DEVICE)
	testopt [internalflags], tsrmode
	jnz .tsr
%endif
	clropt [reg_efl], 300h	; clear TF and IF
	mov word [reg_cs], cs
	mov word [reg_eip], terminate_00
	 push ax		; (dummy to take space for return address)
	mov word [reg_ss], ss
	mov word [reg_esp], sp	; save current ss:sp
	 pop ax			; (discard)
	xor ax, ax
	mov word [reg_eip+2], ax
	mov word [reg_esp+2], ax
	mov word [reg_efl+2], ax
%if _PM
	mov word [reg_es], ax
	mov word [reg_ds], ax
	mov word [reg_fs], ax
	mov word [reg_gs], ax	; insure valid segregs in PM
%endif
	mov word [run_sp_reserve], 128
	call run
		; The dummy stack space above is to hold the return address
		; of this call. The debugger stack is used by this run.
	and word [run_sp_reserve], 0

%if _SYMBOLIC
	nearcall zz_detect_xms
	clropt [internalflags2], dif2_createdprocess
%endif
.testagain:
	testopt [internalflags], attachedterm
@@:
	retn

%if _TSR && (_APPLICATION || _DEVICE)
.tsr:
	mov word [run_int], msg.tsr_cannot_terminate_attached
	jmp .testagain
%endif
%endif


%include "vv.asm"


%if _DEVICE && _DEVICE_SET_2324
	usesection lDEBUG_DATA_ENTRY
devint23:
	stc
	retf

devint24:
	mov al, 3
	iret

	usesection lDEBUG_CODE
%endif


;--- this is called by "run"
;--- set debuggee's INT 23/24.
;--- don't use INT 21h here, DOS might be "in use".
;--- registers may be modified - will soon be set to debuggee's

%if !_LOADER
		; Low-level functions to reset to debuggee's interrupt vectors 23h/24h
		; INP:	-
		; OUT:	-
		; CHG:	bx, (e)dx, cx, ax
		; STT:	ds = our segment
		;	Do not use Int21, even if not in InDOS mode
setint2324:
%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [internalflags], nodosloaded
	jnz .ret		; don't touch int23/24 -->
 %else
	retn
 %endif
%endif
%if _APPLICATION || _DEVICE
%if _PM
	call ispm
	jz .pm
%endif
	push es
	push di
	push si

	xor di, di
	mov es, di
	mov di, 23h *4
	mov si, run2324
	movsw
	movsw
	movsw
	movsw

%if _PM
	call InDOS
	jnz @F
	call hook2F
@@:
%endif
	pop si
	pop di
	pop es
.ret:
	retn
%if _PM
.pm:
	push si
	mov si, run2324
	mov bx, 0223h
.loop:
	_386_o32		; mov edx, dword [si+0]
	mov dx, word [si+0]
	mov cx, word [si+4]
	mov ax, 0205h
	int 31h
	add si, 6
	inc bl
	dec bh
	jnz .loop
	pop si
	retn
%endif
%endif


		; Low-level functions to save debuggee's interrupt vectors 23h/24h
		;  and set our interrupt vectors instead
		; INP:	-
		; OUT:	-
		; CHG:	-
		; STT:	ds = our segment
		;	Do not use Int21, even if not in InDOS mode
getint2324:
%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [internalflags], nodosloaded
	jnz .ret		; don't touch int23/24 -->
 %else
	retn
 %endif
%endif
%if _APPLICATION || _DEVICE
%if _PM
	call ispm
	jz .pm
%endif
	push si
	push di
	push es

	push ds
	pop es
	xor di, di
	mov ds, di
	mov di, run2324
	mov si, 23h *4
	push si
	movsw			; save interrupt vector 23h
	movsw
	movsw			; save interrupt vector 24h
	movsw
	pop di
	push es
	pop ds
	xor si, si
	mov es, si
	mov si, CCIV
	movsw
	movsw
	movsw
	movsw

	pop es
	pop di
	pop si
.ret:
	retn
%if _PM
subcpu 286
.pm:
	_386_o32
	pusha
	mov di, run2324
	mov bx, 0223h
.loop:
	mov ax, 0204h
	int 31h
	_386_o32		; mov dword [di+0], edx
	mov word [di+0], dx
	mov word [di+4], cx
	add di, byte 6
	inc bl
	dec bh
	jnz .loop
%if _ONLYNON386
	db __TEST_IMM8		; (skip pusha)
%else
	db __TEST_IMM16		; (skip pushad)
%endif

restoredbgi2324:
setdbgi2324:
	_386_o32
	pusha
	mov si, dbg2324
	mov bx, 0223h
_386	xor edx, edx
.loop:
	lodsw
	mov dx, ax
	mov cx, word [cssel]
	mov ax, 0205h
	int 31h
	inc bl
	dec bh
	jnz .loop
	_386_o32
	popa
	retn
subcpureset
%endif
%endif

%if 0
The next three subroutines concern the handling of Int23 and 24.
These interrupt vectors are saved and restored when running the
child process, but are not active when DEBUG itself is running.
It is still useful for the programmer to be able to check where Int23
and 24 point, so these values are copied into the interrupt table
during parts of the C, D, (DX, DI,) E, M, and S commands, so that
they appear to be in effect. The E command also copies these values
back.

Between calls to dohack and unhack, there should be no calls to DOS,
so that there is no possibility of these vectors being used when
DEBUG itself is running.

; As long as no DOS is loaded anyway, Int23 and Int24 won't be touched
by us, so the whole hack is unnecessary and will be skipped.
%endif

		; PREPHACK - Set up for interrupt vector substitution.
		; Entry	es = cs
prephack: section_of_function
	cmp byte [hakstat], 0
	jne .err		; if hack status error -->
	push di
	mov di, sav2324		; debugger's Int2324
	call prehak1
	pop di
	retn

.err:
	push dx
	mov dx, ph_msg
	call putsz		; display error
	pop dx
	retn

		; INP:	di-> saved interrupt vectors
		; OUT:	-
		; CHG:	-
prehak1:
%if _PM
	call ispm
	jz .pm			; nothing to do
%endif
	push ds
	push si
	xor si, si
	mov ds, si
	mov si, 23h *4
	movsw
	movsw
	movsw
	movsw
	pop si
	pop ds
.pm:
	retn


		; DOHACK - Fake the interrupt vectors 23h and 24h to debuggee's
		; UNHACK - Restore interrupt vectors 23h and 24h to our values
		;	It's OK to do either of these twice in a row.
		;	In particular, the S command may do unhack twice in a row.
		; INP:	ds = our segment
		; OUT:	es = our segment
		; CHG:	-
		; STT:	Do not use Int21
dohack: section_of_function
	push ss
	pop es
%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [internalflags], nodosloaded
	jnz unhack.ret		; nothing to hack -->
 %else
	retn
 %endif
%endif
%if _APPLICATION || _DEVICE
	push si
	mov byte [hakstat], 1
	mov si, run2324		; debuggee's interrupt vectors
%if _PM
	call ispm
	jnz unhack.common
subcpu 286
	_386_o32
	pusha
	mov bx, 0223h
.pm_loop:
	_386_o32
	mov dx, word [si+0+0]
	mov cx, word [si+0+4]
	mov ax, 205h
	int 31h
	add si, byte 6
	inc bl
	dec bh
	jnz .pm_loop
	_386_o32
	popa
	pop si
	retn
subcpureset
%else
	jmp short unhack.common
%endif
%endif

unhack: section_of_function
	push ss
	pop es
%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [internalflags], nodosloaded
	jnz .ret		; nothing to hack -->
 %else
	retn
 %endif
%endif
%if _APPLICATION || _DEVICE
	mov byte [hakstat], 0
%if _PM
	call ispm
	jz restoredbgi2324
%endif
	push si
	mov si, sav2324		; debugger's interrupt vectors
.common:
	push di
	push es
	xor di, di
	mov es, di
	mov di, 23h *4
	movsw
	movsw
	movsw
	movsw
	pop es
	pop di
	pop si
.ret:
	retn
%endif
%endif

%if _LOADER
prephack: section_of_function
dohack: section_of_function
unhack: section_of_function
	retn
%endif


InDOS_or_BIOS_output:
	testopt [options6], opt6_bios_output
	jnz InDOS.return	; if should do output to ROM-BIOS -->

InDOS_or_BIOS_IO:
	testopt [options6], opt6_bios_io
	jnz InDOS.return	; if should do I/O from/to ROM-BIOS -->


		; OUT:	NZ if InDOS mode
		;	ZR if not
		; CHG:	-
		; STT:	ss = ds
InDOS: section_of_function
%if _BOOTLDR
	testopt [internalflags], nodosloaded
	jnz .return		; always "in DOS" -->
%endif
 %if _APPLICATION || _DEVICE
	testopt [options], fakeindos
	jnz .return		; faking InDOS on anyway -->
.real_indos:
	push ds
	push si
	mov si, pInDOS + so16aSegSel
	call update_dosdata_segment
	lds si, [si - so16aSegSel]
	cmp byte [si], 0
	pop si
	pop ds
 %endif
.return:
	retn


		; INP:	si -> word seg or sel, word segment, word selector
update_dosdata_segment:
%if _APPLICATION || _DEVICE
	testopt [internalflags2], dif2_int31_segment
	jz .ret
	push dx
	push ax
	push bx
	mov al, 31h
	call get_86m_interrupt_handler_no_dos
%if _PM
	cmp word [si + soaSegment], dx
	je @F

	call ispm
	jnz .realmode

	mov bx, dx
	mov ax, 0002h
	int 31h

	mov word [si + soaSegSel], ax
	mov word [si + soaSelector], ax
	jmp @F

.realmode:
	mov word [si + soaSegSel], dx
	and word [si + soaSelector], 0

@@:
	mov word [si + soaSegment], dx
	pop bx
	pop ax
%else
	pop bx
	pop ax
	mov word [si + soaSegSel], dx
%endif
	pop dx
%endif
.ret:
	retn


;	PARSECM - Parse command line for C and M commands.
;	Entry	AL		First nonwhite character of parameters
;		SI		Address of the character after that
;		DI		(If _PM) getaddr or getaddrX for second parameter
;	Exit	DS:ESI		Address from first parameter
;		ES:EDI		Address from second parameter
;		ECX		Length of address range minus one
;		[bAddr32]	Set if any high word non-zero

parsecm_have_address:
	nearcall getrangeX_have_address_need_length
	jmp @F

parsecm:
	call prephack
	mov bx, word [reg_ds]	; get source range
	xor cx, cx
	nearcall getrangeX	; get address range into bx:(e)dx bx:(e)cx
		; Bug fixed in Debug/X 2.00: This used the same scratch
		;  selector as the getaddr used for the second operand.
		; As we never write to the first operand of an C or M
		;  command the simple fix is to use getrangeX here.
@@:
	push bx			; save segment first address
	call skipcomm0
	mov bx, word [reg_ds]
	_386_PM_o32	; sub ecx, edx
	sub cx, dx		; number of bytes minus one
	_386_PM_o32	; push edx
	push dx
	_386_PM_o32	; push ecx
	push cx
%if _PM
	mov cl, byte [bAddr32]
	push cx
%if _DUALCODE && _EXPRDUALCODE
	nearcall ..@parsecm_getaddr

	usesection lDEBUG_CODE2
..@parsecm_getaddr: section_of_function
	jmp di

	usesection lDEBUG_CODE
%else
	call di			; get destination address into bx:edx
%endif
	pop cx
	or byte [bAddr32], cl	; if either is 32-bit, handle both as 32-bit
%else
	nearcall getaddr	; get destination address into bx:dx
%endif
	_386_PM_o32
	pop cx		; pop ecx
	_386_PM_o32	; mov edi, edx
	mov di, dx
	_386_PM_o32
	add dx, cx	; add edx, ecx
	jc short errorj7	; if it wrapped around
	call chkeol		; expect end of line
	mov es, bx
	_386_PM_o32	; pop esi
	pop si
	pop ds
	retn

errorj7:
	jmp error


%if _APPLICATION || _DEVICE
;	PARSELW - Parse command line for L and W commands.
;
;	Entry	AL	First nonwhite character of parameters
;		SI	Address of the character after that
;
;	Exit	If there is at most one argument (program load/write), then the
;		zero flag is set, and registers are set as follows:
;		bx:(e)dx	Transfer address
;
;		If there are more arguments (absolute disk read/write), then the
;		zero flag is clear, and registers are set as follows:
;
;		DOS versions prior to 3.31:
;		AL	Drive number
;		CX	Number of sectors to read
;		DX	Beginning logical sector number
;		DS:BX	Transfer address
;
;		Later DOS versions:
;		AL	Drive number
;		BX	Offset of packet
;		CX	0FFFFh

	usesection lDEBUG_DATA_ENTRY
	align 4, db 0
packet:	dd 0		; sector number
	dw 0		; number of sectors to read
	dd 0		; transfer address Segm:OOOO
%if _PM
	dw 0		; transfer address might be Segm:OOOOOOOO!
%endif

	usesection lDEBUG_CODE
parselw:
	mov bx, word [reg_cs]	; default segment
	_386_o32	; xor edx, edx
	xor dx, dx
	test byte [fileext], EXT_HEX
	jnz @F			; if .HEX file, default offset is 0 -->
	mov dh, 1		; default offset in dx = 100h
@@:
	call iseol?
	je plw2			; if no arguments
	nearcall getaddr	; get buffer address into bx:(e)dx
	call skipcomm0
	call iseol?
	je plw2			; if only one argument
	push bx			; save segment
	push dx			; save offset
	mov bx, 80h		; max number of sectors to read
	neg dx
	jz plw1			; if address is zero
	mov cl, 9
	shr dx, cl		; max number of sectors which can be read
	mov di, dx
plw1:
	cmp byte [si], ':'	; drive letter specification ?
	jne @F			; no -->

	push ax
	call uppercase
	sub al, 'A'
	cmp al, 32		; valid drive ?
	mov dl, al		; put drive number
	inc si			; -> past the colon
	pop ax
	jb @FF			; got it -->
	dec si			; -> at colon

@@:
	nearcall getbyte	; get drive number (DL)
	db __TEST_IMM8		; (skip lodsb)
@@:
	lodsb
	call skipcomm0
	push dx
	add dl, 'A'
	mov byte [driveno], dl
	nearcall getdword	; get relative sector number
	call skipcomm0
	push bx			; save sector number high
	push dx			; save sector number low
	push si			; in case we find an error
	nearcall getword	; get sector count
	dec dx
	cmp dx, di
	jae errorj7		; if too many sectors
	inc dx
	mov cx, dx
	call chkeol		; expect end of line
	testopt [internalflags], oldpacket| newpacket| ntpacket
	jnz plw3		; if using a packet -->
	pop si			; in case of error
	pop dx			; get LoWord starting logical sector number
	pop bx			; get HiWord
	test bx, bx		; just a 16-bit sector number possible
	jnz errorj7		; if too big
	pop ax			; drive number
	pop bx			; transfer buffer ofs
	pop ds			; transfer buffer seg
	test cx, cx		; NZ
plw2:
	retn

		; disk I/O packet for Int25/Int26, Int21.7305, VDD
plw3:
	pop bx			; discard si
	mov bx, packet
	pop word [bx+0]		; LoWord sector number
	pop word [bx+2]		; HiWord sector number
	mov word [bx+4], cx	; number of sectors
	pop ax			; drive number
	pop word [bx+6]		; transfer address ofs
	pop dx
	xor cx, cx
%if _PM
	call ispm
	jnz plw3_1
	cmp byte [dpmi32], 0
	jz plw3_1
[cpu 386]
	mov word [bx+10], dx	; save segment of transfer buffer
	movzx ebx, bx
	shr edx, 16		; get HiWord(offset)
	cmp byte [bAddr32], 1
	jz plw3_1
	xor dx, dx
__CPU__
plw3_1:
%endif
	mov word [bx+8], dx	; transfer address seg
	dec cx			; NZ and make cx = -1
	retn
%endif


%include "expr.asm"
%include "rr.asm"


%include "lineio.asm"


%include "ints.asm"


	usesection lDEBUG_CODE

%if _BOOTLDR
		; Determine the amount of actual memory
		;
		; This is important to call at the time we need the size,
		; not just save the size initially. Loading other pre-boot
		; installers or RPLs will change the size.
		;
		; INP:	-
		; OUT:	dx = segment behind usable memory (taking EBDAs & RPLs into account)
		;	ds = ss
		; CHG:	ax, cx, di, si, ds
bootgetmemorysize: section_of_function
	push es
	xor ax, ax
	mov ds, ax
	int 12h					; get memory size in KiB
	mov cl, 6
	shl ax, cl				; *64, convert to paragraphs
	push ax
	lds si, [ 2Fh *4 ]			; get current Int2F
	inc si					; pointer valid (not 0FFFFh) ? (left increased!)
	jz .norpl				; no -->
	mov ax, ds
	test ax, ax				; segment valid (not zero) ?
	jz .norpl				; no -->
	times 2 inc si				; +3 with above inc
	push cs
	pop es
	mov di, .rpl
	mov cx, .rpl_size
	repe cmpsb				; "RPL" signature ?
	jne .norpl				; no -->
	pop dx
	mov ax, 4A06h
	int 2Fh					; adjust usable memory size for RPL
	db __TEST_IMM8				; (skip pop)
.norpl:
	pop dx
		; dx = segment behind last available memory
	 push ss
	 pop ds
	pop es
	retn

.rpl:	db "RPL"
	endarea .rpl
%endif


%if !_LOADER
		; Ensure a debuggee process is loaded
		;
		; INP:	si:di = to preserve if have a process already
		; OUT:	NZ if have no process and unable to create process
		;	ZR if have a process or created empty process
		;	NC if had no process yet, created one or not
		;	CY if had a process already,
		;	 si:di = preserved input
		;	NC, ZR if had no process, created empty one,
		;	 si:di = debuggee cs:ip
		;	NC, NZ if int19 occurred (or bootloaded)
		; CHG:	si, di, cx
ensuredebuggeeloaded:
	push ax
	push bx
	push dx

	testopt [internalflags3], dif3_gotint19
	jz .notint19

	clropt [internalflags3], dif3_gotint19
%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [internalflags], nodosloaded
	jz .dosint19
 %endif

.bootint19:
	call zeroregs

	mov ax, 60h
	push ax
	mov di, reg_ds
	stosw
	scasw			; (skip dummy high word)
	stosw
	scasw
	stosw
	scasw
	stosw
	call adusetup
	call bootgetmemorysize
	sub dx, 60h
	cmp dx, 1000h
	jbe .bootbelow64kib	; if memory left <= 64 KiB
	xor dx, dx		; dx = 1000h (same thing, after shifting)
.bootbelow64kib:
	mov cl, 4
	shl dx, cl
	dec dx
	dec dx
	mov word [reg_esp], dx
	pop es
	xchg dx, di		; es:di = child stack pointer
	xor ax, ax
	stosw			; push 0 on client's stack

	mov word [es:0], 019CDh	; place opcode for int 19h at cs:ip
	jmp @F
%endif

 %if _APPLICATION || _DEVICE
.dosint19:
	mov dx, word [reg_esp]
	mov bx, word [reg_ss]

	call zeroregs

		; Upon receiving an int 19h in DOS
		;  just set up some shim that will
		;  lead to process termination.
		; Unlike before we do not longer try
		;  to create a new process then.
	mov word [reg_esp], dx
	mov word [reg_ss], bx	; preserve our stack
	push word [pspdbe]
	pop word [reg_cs]	; cs = PSP, ip = 0,
				;  cs:ip -> int 20h instruction

@@:
	testopt [internalflags], attachedterm
	jnz .noprocess		; if also process not loaded

	or dl, 1		; flags return NC, NZ
	jmp .return
 %endif

.notint19:
	testopt [internalflags], attachedterm
	jnz .noprocess		; not loaded, create -->
				; flags return ZR
	stc			; flags return CY
	jmp .return

.noprocess:
%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [internalflags], nodosloaded
	jnz .return		; flags return NC, NZ
 %else
	jmp .return
 %endif
%endif

 %if _APPLICATION || _DEVICE
.dosnoprocess:
	mov ah, 48h		; get size of largest free block
	mov bx, -1
	int 21h
	cmp bx, 20h		; enough for PSP + 256 Bytes for code/stack ?
	jb .return_no_clr	; no -->
	mov ah, 48h		; allocate it
	int 21h
	jc .return_no_clr	; (memory taken between the calls)

	push ax
	call zeroregs
	mov byte [reg_eip+1], 100h>>8
	pop ax

	push bx
%if _SYMBOLIC
	push bx
%endif
	mov di, reg_ds		; fill segment registers ds,es,ss,cs
	stosw
	scasw			; (skip dummy high word)
	stosw
	scasw
	stosw
	scasw
	stosw
	call adusetup
	mov bx, word [reg_cs]	; bx:dx = where to load program
	mov es, bx
	pop ax			; get size of memory block
	mov dx, ax
	add dx, bx
	mov word [es:ALASAP], dx
	push ax
	cmp ax, 1000h
	jbe .below64kib		; if memory left <= 64 KiB
	xor ax, ax		; ax = 1000h (same thing, after shifting)
.below64kib:
	mov cl, 4
	shl ax, cl
	dec ax
	dec ax
	mov word [reg_esp], ax
	xchg ax, di		; es:di = child stack pointer
	xor ax, ax
	stosw			; push 0 on client's stack

		; Create a PSP
	mov ah, 55h		; create child PSP
	mov dx, es
	mov si, word [es:ALASAP]
	clc			; works around OS/2 bug
	int 21h
	mov word [es:ALASAP], si
	call setpspdbg		; reset PSP to ours
	call ll_copy_cmdline_and_fcbs
	pop ax

	push ds
	 push es
	 pop ds
	cmp ax, 0FFFh
	jbe @F
	mov ax, 0FFFh		; 0FFFh for large blocks
@@:
	sub ax, 10h		; cannot underflow because block is >= 110h Bytes
				; at most 0FEFh
	mov bx, 0Ch
	sub bx, ax		; 000Ch - 00FEFh = 0F01Dh
	mov cl, 4
	shl ax, cl		; 0FEF0h
		; call far 0F01Dh:0FEF0h = 1000C0h
		; This is either the same as 0C0h if A20 masked, or in the HMA.
	mov byte [CPMCALL], 09Ah; call far imm:imm opcode
	mov word [CPMCALL + 1], ax
	mov word [CPMCALL + 3], bx	; adjusted CALL 5 dispatcher

	mov dx, 80h
	mov ah, 1Ah		; set DTA to default DTA
	int 21h
	pop ds

		; Finish up. Set termination address.
	mov ax, 2522h		; set interrupt vector 22h
	mov dx, int22
	int 21h
	mov word [es:TPIV], dx
	mov word [es:TPIV+2], ds

	mov byte [es:100h], 0C3h	; place opcode for retn at cs:ip

%if _SYMBOLIC
	pop bx				; size of memory block
%endif
	mov word [pspdbe], es
	mov ax, es
	dec ax
	mov es, ax
	inc ax
	mov word [es:8+0], "DE"
	mov word [es:8+2], "BU"
	mov word [es:8+4], "GG"
	mov word [es:8+6], "EE"	; set MCB name
	mov word [es:1], ax	; set MCB owner

%if _SYMBOLIC
	setopt [internalflags2], dif2_createdprocess
	mov word [created_psp], ax
	mov word [created_size], bx
%endif

	mov si, word [reg_cs]
	mov di, word [reg_eip]		; ? is this ever used ?

	clropt [internalflags], attachedterm
	cmp al, al			; flags return ZR, NC
 %endif

.return:
@@:
	push ss
	pop es

	pop dx
	pop bx
	pop ax
	retn

.return_no_clr:
	mov dx, msg.ensure_no_memory
	call putsz
	test dx, dx			; flags return NZ, NC
	jmp .return
%endif


zeroregs:
	; call set_efl_to_fl	; initialise EFL, and ax = 0
; set_efl_to_fl:
	xor ax, ax		; initialise ax = 0 and FL = ZR NC etc
_no386	push ax			; dummy high word
	_386_o32	; pushfd
	pushf
	pop word [reg_efl]	; set to FL
	pop word [reg_efl+2]	; set to high word of EFL, or zero
	; retn

	mov di, regs
	mov cx, 15 * 2		; (8 standard + 6 segregs + eip) * 2
	rep stosw		; initialise all registers
	retn


%if _PM
		; Hook Int2F if a DPMI host is found. However for Win9x and DosEmu
		; Int2F.1687 is not hooked because it doesn't work. Debugging in
		; protected mode may still work, but the initial switch must be
		; single-stepped.
		;
		; CHG:	ah, bx, cx, dx, di, es
		; OUT:	al = 0FFh if installed
		;	al = status value 0F0h..0FEh else
		; STT:	V86/RM
		;	ds = debugger data segment
		;	! might be called with unknown ss
		;	  if [internalflags6] & dif6_in_amis_hook2F
hook2F:
	call InDOS
	mov al, 0FEh
	jnz .return
%if _APPLICATION || _DEVICE
.not_in_dos:
	mov al, 0FDh
	testopt [internalflags], hooked2F
	jnz .return		; don't hook now -->
	dec ax			; 0FCh
 %if _DEBUG
	testopt [internalflags6], dif6_in_hook2F
	jnz .return
 %endif
.loop:
 %if _GUARD_86M_INT2F
	push es
	xor ax, ax
	mov es, ax		; (only used in 86 Mode)
	mov ax, [es:2Fh * 4]
	cmp ax, -1
	je @F			; --> (ZR)
	or ax, [es:2Fh * 4 + 2]
@@:
	pop es
	mov al, 0FBh
	jz .return
 %endif
 %if _DEBUG
	testopt [internalflags6], dif6_in_amis_hook2F
	jnz @F
	push si
	call findinstalleddebugger
				; CHG: si, di, es, ax, cx, dx
	pop si
	jc @F
	setopt [internalflags6], dif6_in_hook2F
				; avoid recursion
	inc ax			; al = 31h
	int 2Dh			; call out to debugger
	clropt [internalflags6], dif6_in_hook2F
	cmp al, 0FFh
	jne @F
  %if _DISPHOOK
	mov dx, dpmihookamis
	call putsz
  %endif
@@:
 %endif
	mov ax, 1687h		; DPMI host installed?
	int 2Fh
	test ax, ax
	mov al, 0FAh
	jnz .return
	mov word [dpmientry+0], di	; true host DPMI entry
	mov word [dpmientry+2], es
	mov word [dpmiwatch+0], di
	mov word [dpmiwatch+2], es
	dec ax			; 0F9h
		; Previously checked DIF nohook2F here.
	dec ax			; 0F8h
	testopt [options4], opt4_int_2F_hook
	jz .return		; requested to not hook -->
	mov ax, 352Fh
	int 21h
	mov word [oldi2F+0], bx
	mov word [oldi2F+2], es
	mov dx, debug2F		; ds => lDEBUG_DATA_ENTRY
	mov ax, 252Fh
	int 21h

		; Test whether we can hook the DPMI entrypoint call.
	mov ax, 1687h
	int 2Fh
	test ax, ax
	jnz .nohost
	cmp di, mydpmientry	; our entrypoint returned ?
	jne .nohook
	mov ax, es
	mov bx, ds		; bx => lDEBUG_DATA_ENTRY
	cmp ax, bx
	jne .nohook		; no -->

	mov word [dpmiwatch+0], mydpmientry
	mov word [dpmiwatch+2], ds	; => lDEBUG_DATA_ENTRY

	setopt [internalflags], hooked2F
	setopt [internalflags4], dif4_int_2F_hooked
	call update_inttab_optional
%if _DISPHOOK
	testopt [internalflags6], dif6_in_amis_hook2F
	jnz @F
	mov ax, ds		; ax => lDEBUG_DATA_ENTRY
	push ds
	pop es
	mov di, dpmihookcs
	call hexword
	mov dx, dpmihook
	call putsz
@@:
%endif
	mov al, 0FFh
%endif
.return:
	push ds
	pop es
	retn

%if _APPLICATION || _DEVICE
.nohost:
.nohook:
	push ds
	lds dx, [oldi2F]
	mov ax, 252Fh
	int 21h			; unhook
	pop ds
	push ds
	pop es			; restore segregs
	clropt [options4], opt4_int_2F_hook
				; disable hook next
	testopt [internalflags6], dif6_in_amis_hook2F
	jnz @F
	mov dx, msg.dpmi_no_hook
	call putsz		; display message about it
@@:
	call .loop
	mov al, 0F0h
	retn
%endif
%endif


		; Following bits need to be *before* boot.asm
		;  to support the _BOOTLDR_DISCARD option.
		;  But boot.asm must be included before init.asm
		;  so that init will get the nearcall/dualcall
		;  uses in boot.asm in its relocation tables.
		; So all code section contents originally from
		;  init.asm is now moved to here.
%if _SYMBOLIC && (_APPLICATION || _DEVICE)
		; Moved this here so the nearcall macro is used
		;  before we write the relocate_from_code table.
	usesection lDEBUG_CODE
..@switch_s_cont:
	nearcall zz_save_strat
	nearcall zz_switch_s
	dec si
	mov dx, si
	retf

..@switch_s_catch:
	mov sp, word [throwsp]	; restore stack
				;  (needed here if returned to errret)
	mov dx, errcarat
	call putsz
	xor dx, dx
	retf
%endif


	usesection lDEBUG_CODE
initcont:
%if _APPLICATION
	int 21h				; resize to required
;	jc ...				; (expected to work since it had to be larger. also we hooked ints)
%endif

%if _APPLICATION || _DEVICE
.device:
%if _VXCHG
	call vv_set
%endif

	push ds
	pop es
	call getint2324			; init run2324 to avoid using or displaying NUL vectors

	push ds
	pop es
	pop si
	lodsb
	call kk				; process the rest of the command line
%endif

.boot_entry:
	push ds
	pop es				; => lDEBUG_DATA_ENTRY

%if _ALTVID
;	mov al, ALTSCREENBITS
	call setscreen
%endif

	mov si, cmd3
%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [internalflags], nodosloaded
	jz @F
 %endif
 %if _LOADER
	push si
	jmp moveloader_hook_ints
 %else
	jmp si				; directly jump to cmd3 of the installed image
 %endif
@@:
%endif
%if _APPLICATION || _DEVICE
	push si
	jmp ll3				; load a program if one has been given at the command line
%endif


%if _BOOTLDR_DISCARD
	usesection lDEBUG_CODE
ldebug_code_bootldr_truncated_size: equ fromparas(paras($ - code_start))
%else
ldebug_code_bootldr_truncated_size: equ ldebug_code_size
%endif
	endarea ldebug_code_bootldr_truncated, 1

%if _BOOTLDR
 %include "boot.asm"
%endif


	usesection lDEBUG_CODE
	align 16, db 0
ldebug_code_size	equ $-section.lDEBUG_CODE.vstart
	endarea ldebug_code, 1


	usesection lDEBUG_CODE2
	align 16, db 0
ldebug_code2_size	equ $-section.lDEBUG_CODE2.vstart
	endarea ldebug_code2, 1


	usesection INIT
initstart:

%include "init.asm"

	usesection INIT
		align 16, db 0
init_size	equ $-section.INIT.vstart
	endarea init, 1


	usesection lDEBUG_DATA_ENTRY
	align 16, db 0
ldebug_data_entry_size	equ $-section.lDEBUG_DATA_ENTRY.vstart
	endarea ldebug_data_entry, 1

%if !_LOADER
	usesection ASMTABLE1
	align 16, db 0
asmtable1_size		equ $-section.ASMTABLE1.vstart
	endarea asmtable1, 1

	usesection ASMTABLE2
	align 16, db 0
asmtable2_size		equ $-section.ASMTABLE2.vstart
	endarea asmtable2, 1
%else
asmtable1_size		equ 0
	endarea asmtable1, 1
asmtable2_size		equ 0
	endarea asmtable2, 1
%endif

	usesection MESSAGESEGMENT
nontruncated_message_end:

	align 16, db 0
messagesegment_size	equ $-section.MESSAGESEGMENT.vstart
	endarea messagesegment, 1

	absolute nontruncated_message_end
%if _HELP_COMPRESSED
	alignb 16
nontruncated_hshrink_message_buffer:
		resb hshrink_message_buffer_size
%endif

	alignb 16
messagesegment_nobits_size	equ $-section.MESSAGESEGMENT.vstart
	endarea messagesegment_nobits, 1


	usesection DATASTACK
%define SECTIONFIXUP - $$ + 100h + DATAENTRYTABLESIZE

		; I/O buffers
	alignb 2
line_in:	resb 1			; maximal length of input line
		resb 1			; actual length (must be one less than previous byte)
		resb 255		; buffer for 13-terminated input line
.end:

%if _CONFIG
	alignb 2
configpath:	resb 128 + 256
.dir_end:	resw 1

	alignb 2
scriptspath:	resb 128 + 256
.dir_end:	resw 1

yy_try_scriptspath:
.nokeywordused:	resb 1
.didnotyettry:	resb 1
%endif
 %if _EXTENSIONS
	alignb 2
ext_cmdline:	resw 1
  %if _ELDTRAILER
eldheader:	resd 1
  %endif
 %endif
 %if _EXTENSIONS || _INPUT_FILE_HANDLES || _INPUT_FILE_BOOT
yy_is_script:	resb 1			; else extension
 %endif

	; zero-initialisation starts here
..@init_first:
		resb 1			; line_in overflow stop
			; This byte is zero-initialised. When line_in
			;  is scanned using skipcomma (in bb.asm) but
			;  al already had the CR, then the scan may
			;  overflow line_in. Including this byte here
			;  (_after_ line_in) insures that the scan will
			;  stop while still reading within the section.
				; b_bplist and g_bplist are expected in that order by initcont
%if _BREAKPOINTS
	alignb 2
b_bplist:
.used_mask:	resb (_NUM_B_BP + _NUM_SYM_BP + 7) >> 3
					; bitmask of used points
.disabled_mask:	resb (_NUM_B_BP + _NUM_SYM_BP + 7) >> 3
					; bitmask of disabled points
 %if _BREAKPOINTS_STICKY
.sticky_mask:	resb (_NUM_B_BP + _NUM_SYM_BP + 7) >> 3
					; bitmask of sticky points
					; desc: stay around during DEBUG's operation unless
					; explicitly removed/un-stickified. This allows
					; to keep breakpoints around while changing from PM.
					; Hits while in DEBUG are ignored though, use DDEBUG.
					; Disabling won't remove them, just ignores hits.
 %endif
	alignb 2
.bp:		resb (_NUM_B_BP + _NUM_SYM_BP) * BPSIZE
	alignb 2
.counter:	resw _NUM_B_BP
	alignb 2
.id:		resw _NUM_B_BP		; array of lengths/offsets, 0 = unused
			; low 10 bits = offset into .idbuffer (0..1023)
			; high 6 bits = length (0..63, 0 if unused)
	alignb 2
.when:		resw _NUM_B_BP		; array of pointers, 0 = unused

.idbuffer.length:	equ _NUM_B_ID_BYTES
.idbuffer.free:
		resw 1			; offset into .idbuffer of free space
					; (0..1024)

.whenbuffer.length:	equ _NUM_B_WHEN_BYTES
.whenbuffer.free:
		resw 1			; *offset* into .whenbuffer
					;  (not a pointer)

.idbuffer:
		resb .idbuffer.length	; buffer holding ID strings
.whenbuffer:
		resb .whenbuffer.length	; buffer holding condition strings
%endif
%if _NUM_G_BP
	resb 1 - (($-$$) % 2)		; make g_bplist.bp aligned
g_bplist:
.used_count:	resb 1			; for the byte counter of saved breakpoints
.bp:		resb _NUM_G_BP*BPSIZE
.end:
%endif
%if !_LOADER
sss_silent_count_used:	resb 1
drivenumber:		resb 1
%if _MCBPOSITION
mcbposition:		resb 1
%endif
%if _SDSUB
skipsd:			resb 1
%endif
%if _MCBLIMIT
	alignb 2
mcblimit:		resw 1
%endif
%endif
%if _HISTORY && ! _HISTORY_SEPARATE_FIXED
	alignb 2
historybuffer:	resb _HISTORY_SIZE
.end:
%endif

		; $ - $$	= offset into section
		; % 2		= 1 if odd offset, 0 if even
		; 2 -		= 1 if odd, 2 if even
		; % 2		= 1 if odd, 0 if even
	; resb (2 - (($-$$) % 2)) % 2
		; $ - $$	= offset into section
		; % 2		= 1 if odd offset, 0 if even
		; 1 -		= 0 if odd, 1 if even
	resb 1 - (($-$$) % 2)		; make line_out aligned
trim_overflow:	resb 1			; actually part of line_out to avoid overflow of trimputs loop
line_out:	resb 263
		resb 1			; reserved for terminating zero
line_out_end:
	alignb 2
line_out_overflow:	resw 1		; 2642h if line_out didn't overflow

%if !_LOADER
	alignb 4
sss_silent_count:	resd 1
%endif
	alignb 2
getrange_lines:		resw 1

serial_save_irq_mask:	resw 1
serial_save_irq_off:	resw 1
serial_save_dl:		resw 1
serial_save_ier:	resb 1
serial_save_lcr:	resb 1
serial_save_mcr:	resb 1
%if _USE_TX_FIFO
 serial_fcr_setting:	resb 1
%endif
serial_use_intnum:	resb 1
serial_use_params:	resb 1
serial_use_fifo:	resb 1
%if _RH
rh_display_with_count:	resb 1
	alignb 2
rh_count_number:	resw 1
%endif
	alignb 2
baseport:
serial_use_baseport:	resw 1
serial_use_dl:		resw 1
serial_use_irqmask:	resw 1

	alignb 2
rxhead:		resw 1
rxtail:		resw 1
txhead:		resw 1
txtail:		resw 1
	alignb 16
rxfifo:		resb _RXFIFOSIZE
	alignb 16
txfifo:		resb _TXFIFOSIZE

 %if _SYMBOLIC
%if _BUFFER_86MM_SLICE || _XMS_SYMBOL_TABLE
	alignb 16
access_slice_buffer:
.:
	resb ssString + 255
	alignb 2
.size:	equ $ - .
 %if _SECOND_SLICE
	alignb 16
second_access_slice_buffer:
.:
	resb ssString + 255
	alignb 2
.size:	equ $ - .
 %endif
%endif
 %endif

	alignb 2
while_buffer:
.length equ _WHILEBUFFSIZE
	resb .length
.end:

%if (_MMXSUPP && _RM && ! _RM_AUX) || (_RN && ! _RN_AUX)
	alignb 4
rn_rm_buffer:
 %if (_RN && ! _RN_AUX)
		resb 128
 %else
		resb 108
 %endif
%endif

%if _SYMBOLIC
	alignb 16
str_buffer:	resb 512		; long enough for smName1 + smName2 content
		; by placing this buffer below the stack, a stack overflow
		;  might be less harmful if the str_buffer isn't in use.
%endif

	; zero-initialisation ends here
..@init_behind:

		alignb 16		; stack might be re-used as GDT, so align it on a paragraph
stack:		resb _STACKSIZE
		alignb 2		; ensure stack aligned
stack_end:

%if _EXTENSIONS
	alignb 16
ext_data_area:
.:	resb _EXT_DATA_BOOT_SIZE
	alignb 16
.size equ $ - .

ext_data_max_size_equate equ 0FFF0h - (ext_data_area + SECTIONFIXUP)
 %if ((_EXT_DATA_BOOT_SIZE + 15) & ~15) <= ((_EXT_DATA_INIT_SIZE + 15) & ~15)
  ext_data_nonboot_add_size equ ((_EXT_DATA_INIT_SIZE + 15) & ~15) - ((_EXT_DATA_BOOT_SIZE + 15) & ~15)
 %else
  ext_data_nonboot_add_size equ 0
 %endif
%else
 ext_data_max_size_equate equ 0
 ext_data_nonboot_add_size equ 0
%endif

datastack_size	equ $-section.DATASTACK.vstart
	endarea datastack, 1


auxbuff_size: equ (_AUXBUFFSIZE+15) & ~15
	endarea auxbuff, 1

auxbuff_max_size: equ (_AUXBUFFMAXSIZE+15) & ~15
	endarea auxbuff_max, 1

%if _INIT_MAX
 auxbuff_init_size: equ (_AUXBUFFMAXSIZE+15) & ~15
%else
 auxbuff_init_size: equ (_AUXBUFFSIZE+15) & ~15
%endif
	endarea auxbuff_init, 1


pspsegment_size:	equ 100h + DATAENTRYTABLESIZE + datastack_size
	endarea pspsegment, 1			; size of PSP and image when installed


	numdef SHOWASMTABLESIZE, _DEFAULTSHOWSIZE
%if _SHOWASMTABLESIZE
%assign ASMTABLESIZE asmtable1_size + asmtable2_size
%warning asmtables hold ASMTABLESIZE bytes
%endif


%assign __INITSIZE init_size
%if __INITSIZE > (64 * 1024)
 %error init segment too large (%[__INITSIZE])
%endif

	numdef SHOWINITSIZE, _DEFAULTSHOWSIZE
%if _SHOWINITSIZE
%warning init segment holds __INITSIZE bytes
%endif


%assign __CODESIZE ldebug_code_size
%if __CODESIZE > (64 * 1024)
 %error code segment too large (%[__CODESIZE])
%endif

	numdef SHOWCODESIZE, _DEFAULTSHOWSIZE
%if _SHOWCODESIZE
%warning code segment holds __CODESIZE bytes
%endif


%assign __CODETRUNCATEDSIZE ldebug_code_bootldr_truncated_size
%assign __CODEDISCARDSIZE ldebug_code_size - ldebug_code_bootldr_truncated_size

%if _BOOTLDR_DISCARD
	numdef SHOWCODETRUNCATEDSIZE, _DEFAULTSHOWSIZE
%else
	numdef SHOWCODETRUNCATEDSIZE, 0
%endif
%if _SHOWCODETRUNCATEDSIZE
%warning code segment truncated holds __CODETRUNCATEDSIZE bytes (__CODEDISCARDSIZE bytes discarded)
%endif


%assign __CODE2SIZE ldebug_code2_size
%if __CODE2SIZE > (64 * 1024)
 %error code segment 2 too large (%[__CODE2SIZE])
%endif

%if _DUALCODE
	numdef SHOWCODE2SIZE, _DEFAULTSHOWSIZE
%else
	numdef SHOWCODE2SIZE, 0
%endif
%if _SHOWCODE2SIZE
%warning code segment 2 holds __CODE2SIZE bytes
%endif


%assign __MESSAGESEGMENTSIZE messagesegment_nobits_size
%if __MESSAGESEGMENTSIZE > (64 * 1024)
 %error message segment too large (%[__MESSAGESEGMENTSIZE])
%endif

%if _MESSAGESEGMENT
	numdef SHOWMESSAGESEGMENTSIZE, _DEFAULTSHOWSIZE
%else
	numdef SHOWMESSAGESEGMENTSIZE, 0
%endif
%if _SHOWMESSAGESEGMENTSIZE
%warning message segment holds __MESSAGESEGMENTSIZE bytes
%endif


%assign __MESSAGESEGMENTTRUNCATEDSIZE messagesegment_nobits_truncated_size
%assign __MESSAGESEGMENTDISCARDSIZE messagesegment_nobits_size - messagesegment_nobits_truncated_size

%if _BOOTLDR_DISCARD_HELP
	numdef SHOWMESSAGESEGMENTTRUNCATEDSIZE, _DEFAULTSHOWSIZE
%else
	numdef SHOWMESSAGESEGMENTTRUNCATEDSIZE, 0
%endif
%if _SHOWMESSAGESEGMENTTRUNCATEDSIZE
%warning message segment truncated holds __MESSAGESEGMENTTRUNCATEDSIZE bytes (__MESSAGESEGMENTDISCARDSIZE bytes discarded)
%endif


%assign __PSPSEGMENTSIZE pspsegment_size
%assign __PSPSEGMENTMAXSIZE pspsegment_size - _EXT_DATA_BOOT_SIZE + ext_data_max_size_equate
%if __PSPSEGMENTSIZE > (64 * 1024)
 %error resident size of PSP segment too large (%[__PSPSEGMENTSIZE])
%elif __PSPSEGMENTMAXSIZE > (64 * 1024 - 16)
 %error resident maximum size of PSP segment too large (%[__PSPSEGMENTMAXSIZE])
%endif

	numdef SHOWPSPSIZE, _DEFAULTSHOWSIZE
%if _SHOWPSPSIZE
%warning PSP segment holds __PSPSEGMENTSIZE bytes
 %if _EXTENSIONS
  %warning Maximum PSP segment holds __PSPSEGMENTMAXSIZE bytes
 %endif
%endif

%if CODE_INSURE_COUNT
 %warning code_insure_low_byte_not_0CCh needed CODE_INSURE_COUNT times
%endif
