1452 lines
35 KiB
PHP
1452 lines
35 KiB
PHP
|
|
|
|
; Copyright (c) 1989 Microsoft Corporation
|
|
|
|
; Module Name:
|
|
|
|
; kimacro.inc
|
|
|
|
; Abstract:
|
|
|
|
; This module contains the macros used by kernel assembler code.
|
|
; It includes macros to manipulate interrupts, support system
|
|
; entry and exit for syscalls, faults, and interrupts, and
|
|
; manipulate floating point state.
|
|
|
|
; Author:
|
|
|
|
; Shie-Lin (shielint) 24-Jan-1990
|
|
|
|
; Revision History:
|
|
|
|
; BryanWi 17-Aug-90
|
|
; Replace GENERATE_MACHINE... and RESTORE... with ENTER_...
|
|
; and EXIT_ALL macros.
|
|
|
|
|
|
|
|
|
|
|
|
; These constants are used by the fpo directives in this file.
|
|
; This directive causes the assembler to output a .debug$f segment
|
|
; in the obj file. The segment will contain 1 fpo record for each
|
|
; directive present during assembly.
|
|
|
|
; Although the assembler will accept all valid values, the value of 7
|
|
; in the FPO_REGS field indicates to the debugger that a trap frame is
|
|
; generated by the function. The value of 7 can be used because the
|
|
; C/C++ compiler puts a maximum value of 3 in the field.
|
|
|
|
FPO_LOCALS equ 0 ; 32 bits, size of locals in dwords
|
|
FPO_PARAMS equ 0 ; 32 bits, size of parameters in dwords
|
|
FPO_PROLOG equ 0 ; 12 bits, 0-4095, # of bytes in prolog
|
|
FPO_REGS equ 0 ; 3 bits, 0-7, # regs saved in prolog
|
|
FPO_USE_EBP equ 0 ; 1 bit, 0-1, is ebp used?
|
|
FPO_TRAPFRAME equ 1 ; 2 bits, 0=fpo, 1=trap frame, 2=tss
|
|
|
|
|
|
|
|
|
|
|
|
|
|
; POLL_DEBUGGER
|
|
|
|
; Macro Description:
|
|
|
|
; Call the debugger so it can check for control-c. If it finds
|
|
; it, it will report our iret address as address of break-in.
|
|
|
|
; N.B. This macro should be used when all the caller's registers
|
|
; have been restored. (Otherwise, the kernel debugger register
|
|
; dump will not have correct state.) The only exception is
|
|
; fs. This is because Kd may need to access PCR or PRCB.
|
|
|
|
; Arguments:
|
|
|
|
; There MUST be an iret frame on the stack when this macro
|
|
; is invoked.
|
|
|
|
; Exit:
|
|
|
|
; Debugger will iret for us, so we don't usually return from
|
|
; this macro, but remember that it generates nothing for non-DEVL
|
|
; kernels.
|
|
|
|
|
|
POLL_DEBUGGER macro
|
|
local a, b, c_
|
|
|
|
if DEVL
|
|
EXTRNP _DbgBreakPointWithStatus,1
|
|
stdCall _KdPollBreakIn
|
|
or al,al
|
|
jz short c_
|
|
stdCall _DbgBreakPointWithStatus,<DBG_STATUS_CONTROL_C>
|
|
c_:
|
|
endif ; DEVL
|
|
endm
|
|
|
|
|
|
|
|
; ASSERT_FS
|
|
|
|
; Try to catch funky condition wherein we get FS=r3 value while
|
|
; running in kernel mode.
|
|
|
|
|
|
|
|
ASSERT_FS macro
|
|
local a,b
|
|
|
|
if DBG
|
|
EXTRNP _KeBugCheck,1
|
|
|
|
mov bx,fs
|
|
cmp bx,KGDT_R0_PCR
|
|
jnz short a
|
|
|
|
cmp dword ptr fs:[0], 0
|
|
jne short b
|
|
|
|
a:
|
|
stdCall _KeBugCheck,<-1>
|
|
align 4
|
|
b:
|
|
endif
|
|
endm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
; Copy data from various places into base of TrapFrame, net effect
|
|
; is to allow dbg KB command to trace accross trap frame, and to
|
|
; allow user to find arguments to system calls.
|
|
|
|
; USE ebx and edi.
|
|
|
|
|
|
SET_DEBUG_DATA macro
|
|
|
|
ife FPO
|
|
|
|
|
|
; This macro is used by ENTER_SYSTEM_CALL, ENTER_TRAP and ENTER_INTERRUPT
|
|
; and is used at the end of above macros. It is safe to destroy ebx, edi.
|
|
|
|
|
|
mov ebx,[ebp]+TsEbp
|
|
mov edi,[ebp]+TsEip
|
|
mov [ebp]+TsDbgArgPointer,edx
|
|
mov [ebp]+TsDbgArgMark,0BADB0D00h
|
|
mov [ebp]+TsDbgEbp,ebx
|
|
mov [ebp]+TsDbgEip,edi
|
|
endif
|
|
|
|
endm
|
|
|
|
|
|
|
|
|
|
; ENTER_DR_ASSIST EnterLabel, ExitLabel, NoAbiosAssist, NoV86Assist
|
|
|
|
; Macro Description:
|
|
|
|
; Jumped to by ENTER_ macros to deal with DR register work,
|
|
; abios work and v86 work. The main purpose of this macro is
|
|
; that interrupt/trap/systemCall EnterMacros can jump here to
|
|
; deal with some special cases such that most of the times the
|
|
; main ENTER_ execution flow can proceed without being branched.
|
|
|
|
; If (previousmode == usermode) {
|
|
; save DR* in trapframe
|
|
; load DR* from Prcb
|
|
; }
|
|
|
|
; Arguments:
|
|
; EnterLabel - label to emit
|
|
; ExitLabel - label to branch to when done
|
|
|
|
; Entry-conditions:
|
|
; Dr work:
|
|
; DebugActive == TRUE
|
|
; (esi)->Thread object
|
|
; (esp)->base of trap frame
|
|
; (ebp)->base of trap frame
|
|
|
|
; Abios work:
|
|
; v86 work:
|
|
|
|
; Exit-conditions:
|
|
; Dr work:
|
|
; Interrupts match input state (this routine doesn't change IEF)
|
|
; (esp)->base of trap frame
|
|
; (ebp)->base of trap frame
|
|
; Preserves entry eax, edx
|
|
; Abios work:
|
|
; v86 work:
|
|
|
|
|
|
|
|
ENTER_DR_ASSIST macro EnterLabel, ExitLabel, NoAbiosAssist, NoV86Assist, V86R
|
|
local a,b
|
|
|
|
public Dr_&EnterLabel
|
|
align 4
|
|
Dr_&EnterLabel:
|
|
|
|
|
|
; Test if we came from user-mode. If not, do nothing.
|
|
|
|
|
|
test dword ptr [ebp]+TsEFlags,EFLAGS_V86_MASK
|
|
jnz short a
|
|
|
|
test dword ptr [ebp]+TsSegCs,MODE_MASK
|
|
jz Dr_&ExitLabel ; called from kmode, go continue
|
|
|
|
|
|
|
|
; Save user-mode Dr* regs in TrapFrame
|
|
|
|
; We are safe to destroy ebx, ecx, edi because in ENTER_INTERRUPT and
|
|
; ENTER_TRAP these registers are saved already. In ENTER_SYSTEMCALL
|
|
; ebx, edi is saved and ecx is don't-care.
|
|
|
|
|
|
a: mov ebx,dr0
|
|
mov ecx,dr1
|
|
mov edi,dr2
|
|
mov [ebp]+TsDr0,ebx
|
|
mov [ebp]+TsDr1,ecx
|
|
mov [ebp]+TsDr2,edi
|
|
mov ebx,dr3
|
|
mov ecx,dr6
|
|
mov edi,dr7
|
|
mov [ebp]+TsDr3,ebx
|
|
mov [ebp]+TsDr6,ecx
|
|
mov ebx,0
|
|
mov [ebp]+TsDr7,edi
|
|
|
|
|
|
; Make Dr7 safe before loading junk from save area
|
|
|
|
mov dr7,ebx
|
|
|
|
|
|
; Load KernelDr* into processor
|
|
|
|
|
|
mov edi,dword ptr fs:[PcPrcb]
|
|
mov ebx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr0
|
|
mov ecx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr1
|
|
mov dr0,ebx
|
|
mov dr1,ecx
|
|
mov ebx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr2
|
|
mov ecx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr3
|
|
mov dr2,ebx
|
|
mov dr3,ecx
|
|
mov ebx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr6
|
|
mov ecx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr7
|
|
mov dr6,ebx
|
|
mov dr7,ecx
|
|
|
|
ifnb <V86R>
|
|
test dword ptr [ebp]+TsEFlags,EFLAGS_V86_MASK
|
|
jz short b
|
|
jmp Dr_&V86R
|
|
endif
|
|
b:
|
|
jmp Dr_&ExitLabel
|
|
|
|
|
|
ifb <NoAbiosAssist>
|
|
|
|
public Abios_&EnterLabel
|
|
align 4
|
|
Abios_&EnterLabel:
|
|
|
|
|
|
; INTERRUPT_STACK16_TO_STACK32
|
|
|
|
; This macro remaps current 32bit stack to 16bit stack at interrupt
|
|
; time.
|
|
|
|
; Arguments:
|
|
|
|
; (esp)->trap frame.
|
|
; (eax)->Entry Esp.
|
|
|
|
|
|
mov eax, [esp].TsErrCode ; (eax) = Entry Esp
|
|
mov ecx, KGDT_R0_DATA
|
|
mov edx, esp
|
|
shl eax, 16
|
|
add edx, fs:[PcstackLimit]
|
|
mov [esp].TsErrCode, eax
|
|
mov ss, cx
|
|
mov esp, edx ; Interrupts are off
|
|
mov ebp, edx
|
|
jmp Abios_&ExitLabel
|
|
|
|
endif ; NoAbiosAssist
|
|
|
|
ifb <NoV86Assist>
|
|
|
|
public V86_&EnterLabel
|
|
align 4
|
|
V86_&EnterLabel:
|
|
|
|
|
|
; Move the V86 segment registers to the correct place in the frame
|
|
|
|
mov eax,dword ptr [ebp].TsV86Fs
|
|
mov ebx,dword ptr [ebp].TsV86Gs
|
|
mov ecx,dword ptr [ebp].TsV86Es
|
|
mov edx,dword ptr [ebp].TsV86Ds
|
|
mov [ebp].TsSegFs,ax
|
|
mov [ebp].TsSegGs,bx
|
|
mov [ebp].TsSegEs,cx
|
|
mov [ebp].TsSegDs,dx
|
|
jmp V86_&ExitLabel
|
|
|
|
endif ; NoV86Assist
|
|
|
|
endm
|
|
|
|
|
|
|
|
; ENTER_SYSCALL AssistLabel, TagetLabel, NoFSLoad
|
|
|
|
; Macro Description:
|
|
|
|
; Build the frame and set registers needed by a system call.
|
|
|
|
; Save:
|
|
; Errorpad,
|
|
; Non-volatile regs,
|
|
; FS,
|
|
; ExceptionList,
|
|
; PreviousMode
|
|
|
|
; Don't Save:
|
|
; Volatile regs
|
|
; Seg regs
|
|
; Floating point state
|
|
|
|
; Set:
|
|
; FS,
|
|
; ExceptionList,
|
|
; PreviousMode,
|
|
; Direction
|
|
|
|
; Arguments:
|
|
; AssistLabel - label ENTER_ASSIST macro is at
|
|
; TargetLabel - label to emit for ENTER_ASSIST to jump to
|
|
; NoFSLoad - Don't set FS(it is already set to KGDT_R0_PCR at entry).
|
|
|
|
; Exit-conditions:
|
|
; Interrupts match input state (this routine doesn't change IEF)
|
|
; (esp)->base of trap frame
|
|
; (ebp)->base of trap frame
|
|
; Preserves entry eax, edx
|
|
|
|
; Note:
|
|
; The DS: reference to PreviousMode is *required* for correct
|
|
; functioning of lazy selector loads. If you remove this use
|
|
; of DS:, put a DS: override on something.
|
|
|
|
|
|
|
|
ENTER_SYSCALL macro AssistLabel, TargetLabel, NoFSLoad
|
|
|
|
|
|
.FPO ( FPO_LOCALS, FPO_PARAMS, FPO_PROLOG, FPO_REGS, FPO_USE_EBP, FPO_TRAPFRAME )
|
|
|
|
ifdef KERNELONLY
|
|
|
|
|
|
; Construct trap frame.
|
|
|
|
; N.B. The initial part of the trap frame is constructed by pushing values
|
|
; on the stack. If the format of the trap frame is changed, then the
|
|
; following code must alos be changed.
|
|
|
|
|
|
push 0 ; put pad dword for error on stack
|
|
push ebp ; save the non-volatile registers
|
|
push ebx ;
|
|
push esi ;
|
|
push edi ;
|
|
ifb <NoFSLoad>
|
|
push fs ; save and set FS to PCR.
|
|
mov ebx,KGDT_R0_PCR ; set PCR segment number
|
|
mov fs,bx ;
|
|
else
|
|
; FS already contains KGDT_R0_PCR(entry via PentiumPro fast system call)
|
|
push KGDT_R3_TEB OR RPL_MASK
|
|
endif ; NoFSLoad
|
|
|
|
|
|
; Save the old exception list in trap frame and initialize a new empty
|
|
; exception list.
|
|
|
|
|
|
push PCR[PcExceptionList] ; save old exception list
|
|
mov PCR[PcExceptionList],EXCEPTION_CHAIN_END ; set new empty list
|
|
|
|
|
|
; Save the old previous mode in trap frame, allocate remainder of trap frame,
|
|
; and set the new previous mode.
|
|
|
|
|
|
mov esi,PCR[PcPrcbData+PbCurrentThread] ; get current thread address
|
|
push [esi]+ThPreviousMode ; save old previous mode
|
|
sub esp,TsPreviousPreviousMode ; allocate remainder of trap frame
|
|
mov ebx,[esp+TsSegCS] ; compute new previous mode
|
|
and ebx,MODE_MASK ;
|
|
mov [esi]+ThPreviousMode,bl ; set new previous mode
|
|
|
|
|
|
; Save the old trap frame address and set the new trap frame address.
|
|
|
|
|
|
mov ebp,esp ; set trap frame address
|
|
mov ebx,[esi].ThTrapFrame ; save current trap frame address
|
|
mov [ebp].TsEdx,ebx ;
|
|
mov [esi].ThTrapFrame,ebp ; set new trap frame address
|
|
cld ; make sure direction is forward
|
|
|
|
SET_DEBUG_DATA ; Note this destroys edi
|
|
|
|
test byte ptr [esi]+ThDebugActive,-1 ; test if debugging active
|
|
jnz Dr_&AssistLabel ; if nz, debugging is active on thread
|
|
|
|
Dr_&TargetLabel: ;
|
|
sti ; enable interrupts
|
|
|
|
else
|
|
%out ENTER_SYSCAL outside of kernel
|
|
.err
|
|
endif
|
|
endm
|
|
|
|
|
|
|
|
; ENTER_INTERRUPT AssistLabel, TargetLabel
|
|
|
|
; Macro Description:
|
|
|
|
; Build the frame and set registers needed by an interrupt.
|
|
|
|
; Save:
|
|
; Errorpad,
|
|
; Non-volatile regs,
|
|
; FS,
|
|
; ExceptionList,
|
|
; PreviousMode
|
|
; Volatile regs
|
|
; Seg regs from V86 mode
|
|
; DS, ES, GS
|
|
|
|
; Don't Save:
|
|
; Floating point state
|
|
|
|
; Set:
|
|
; FS,
|
|
; ExceptionList,
|
|
; Direction,
|
|
; DS, ES
|
|
|
|
; Don't Set:
|
|
; PreviousMode
|
|
|
|
; Arguments:
|
|
; AssistLabel - label ENTER_ASSIST macro is at
|
|
; TargetLabel - label to emit for ENTER_ASSIST to jump to
|
|
|
|
; Exit-conditions:
|
|
; Interrupts match input state (this routine doesn't change IEF)
|
|
; (esp)->base of trap frame
|
|
; (ebp)->base of trap frame
|
|
; Preserves entry eax, ecx, edx
|
|
|
|
|
|
|
|
ENTER_INTERRUPT macro AssistLabel, TargetLabel, PassParm
|
|
local b
|
|
|
|
.FPO ( FPO_LOCALS+2, FPO_PARAMS, FPO_PROLOG, FPO_REGS, FPO_USE_EBP, FPO_TRAPFRAME )
|
|
|
|
|
|
; Fill in parts of frame we care about
|
|
|
|
|
|
ifb <PassParm>
|
|
push esp ; Use Error code field to save 16bit esp
|
|
endif
|
|
push ebp ; Save the non-volatile registers
|
|
push ebx
|
|
push esi
|
|
push edi
|
|
|
|
sub esp, TsEdi
|
|
mov ebp,esp
|
|
|
|
mov [esp]+TsEax, eax ; Save volatile registers
|
|
mov [esp]+TsEcx, ecx
|
|
mov [esp]+TsEdx, edx
|
|
if DBG
|
|
mov dword ptr [esp]+TsPreviousPreviousMode, -1 ; ThPreviousMode not pushed on interrupt
|
|
endif
|
|
|
|
test dword ptr [esp].TsEflags,EFLAGS_V86_MASK
|
|
jnz V86_&AssistLabel
|
|
|
|
cmp word ptr [esp]+TsSegCs, KGDT_R0_CODE
|
|
jz short @f
|
|
|
|
mov [esp]+TsSegFs, fs ; Save and set FS to PCR.
|
|
mov [esp]+TsSegDs, ds
|
|
mov [esp]+TsSegEs, es
|
|
mov [esp]+TsSegGs, gs
|
|
|
|
V86_&TargetLabel:
|
|
mov ebx,KGDT_R0_PCR
|
|
mov eax,KGDT_R3_DATA OR RPL_MASK
|
|
mov fs, bx
|
|
mov ds, ax
|
|
mov es, ax
|
|
@@:
|
|
mov ebx, fs:[PcExceptionList] ;Save, set ExceptionList
|
|
mov fs:[PcExceptionList],EXCEPTION_CHAIN_END
|
|
mov [esp]+TsExceptionList, ebx
|
|
|
|
ifnb <PassParm>
|
|
lea eax, [esp].TsErrCode
|
|
lea ecx, [esp].TsEip ; Move eax to EIP field
|
|
mov ebx, ss:[eax] ; (ebx) = parameter to pass
|
|
mov ss:[eax], ecx ; save 16bit esp
|
|
endif
|
|
|
|
|
|
; Remap ABIOS 16 bit stack to 32 bit stack, if necessary.
|
|
|
|
|
|
cmp esp, 10000h
|
|
jb Abios_&AssistLabel
|
|
|
|
mov dword ptr [esp].TsErrCode, 0 ; Indicate no remapping.
|
|
Abios_&TargetLabel:
|
|
|
|
|
|
; end of Abios stack checking
|
|
|
|
|
|
cld
|
|
|
|
ifnb <PassParm>
|
|
push ebx ; push parameter as argument
|
|
endif
|
|
|
|
|
|
SET_DEBUG_DATA
|
|
|
|
test byte ptr PCR[PcDebugActive], -1
|
|
jnz Dr_&AssistLabel
|
|
|
|
Dr_&TargetLabel:
|
|
|
|
endm
|
|
|
|
|
|
|
|
; ENTER_INTERRUPT_FORCE_STATE AssistLabel, TargetLabel
|
|
|
|
; Macro Description:
|
|
|
|
; Build the frame and set registers needed by an interrupt.
|
|
|
|
; This macro is the same as ENTER_INTERRUPT except that it forces the
|
|
; needed state and does not save previous state.
|
|
|
|
; This macro is currently only used by HalpApicRebootService which does not
|
|
; return;
|
|
|
|
; Save:
|
|
; Errorpad,
|
|
; Non-volatile regs,
|
|
; ExceptionList,
|
|
; PreviousMode
|
|
; Volatile regs
|
|
; Seg regs from V86 mode
|
|
|
|
; Don't Save:
|
|
; FS,
|
|
; DS, ES, GS
|
|
; Floating point state
|
|
|
|
; Set:
|
|
; FS,
|
|
; ExceptionList,
|
|
; Direction,
|
|
; DS, ES
|
|
|
|
; Don't Set:
|
|
; PreviousMode
|
|
|
|
; Arguments:
|
|
; AssistLabel - label ENTER_ASSIST macro is at
|
|
; TargetLabel - label to emit for ENTER_ASSIST to jump to
|
|
|
|
; Exit-conditions:
|
|
; Interrupts match input state (this routine doesn't change IEF)
|
|
; (esp)->base of trap frame
|
|
; (ebp)->base of trap frame
|
|
; Preserves entry eax, ecx, edx
|
|
|
|
|
|
|
|
ENTER_INTERRUPT_FORCE_STATE macro AssistLabel, TargetLabel, PassParm
|
|
local b
|
|
|
|
.FPO ( FPO_LOCALS+2, FPO_PARAMS, FPO_PROLOG, FPO_REGS, FPO_USE_EBP, FPO_TRAPFRAME )
|
|
|
|
|
|
; Fill in parts of frame we care about
|
|
|
|
|
|
ifb <PassParm>
|
|
push esp ; Use Error code field to save 16bit esp
|
|
endif
|
|
push ebp ; Save the non-volatile registers
|
|
push ebx
|
|
push esi
|
|
push edi
|
|
|
|
sub esp, TsEdi
|
|
mov ebp,esp
|
|
|
|
mov [esp]+TsEax, eax ; Save volatile registers
|
|
mov [esp]+TsEcx, ecx
|
|
mov [esp]+TsEdx, edx
|
|
if DBG
|
|
mov dword ptr [esp]+TsPreviousPreviousMode, -1 ; ThPreviousMode not pushed on interrupt
|
|
endif
|
|
|
|
test dword ptr [esp].TsEflags,EFLAGS_V86_MASK
|
|
jnz V86_&AssistLabel
|
|
|
|
V86_&TargetLabel:
|
|
mov ebx,KGDT_R0_PCR
|
|
mov eax,KGDT_R3_DATA OR RPL_MASK
|
|
mov fs, bx
|
|
mov ds, ax
|
|
mov es, ax
|
|
@@:
|
|
mov ebx, fs:[PcExceptionList] ;Save, set ExceptionList
|
|
mov fs:[PcExceptionList],EXCEPTION_CHAIN_END
|
|
mov [esp]+TsExceptionList, ebx
|
|
|
|
ifnb <PassParm>
|
|
lea eax, [esp].TsErrCode
|
|
lea ecx, [esp].TsEip ; Move eax to EIP field
|
|
mov ebx, ss:[eax] ; (ebx) = parameter to pass
|
|
mov ss:[eax], ecx ; save 16bit esp
|
|
endif
|
|
|
|
|
|
; Remap ABIOS 16 bit stack to 32 bit stack, if necessary.
|
|
|
|
|
|
cmp esp, 10000h
|
|
jb Abios_&AssistLabel
|
|
|
|
mov dword ptr [esp].TsErrCode, 0 ; Indicate no remapping.
|
|
Abios_&TargetLabel:
|
|
|
|
|
|
; end of Abios stack checking
|
|
|
|
|
|
cld
|
|
|
|
ifnb <PassParm>
|
|
push ebx ; push parameter as argument
|
|
endif
|
|
|
|
|
|
SET_DEBUG_DATA
|
|
|
|
test byte ptr PCR[PcDebugActive], -1
|
|
jnz Dr_&AssistLabel
|
|
|
|
Dr_&TargetLabel:
|
|
|
|
endm
|
|
|
|
|
|
|
|
; ENTER_TRAP AssistLabel, TargetLabel
|
|
|
|
; Macro Description:
|
|
|
|
; Build the frame and set registers needed by a trap or exception.
|
|
|
|
; Save:
|
|
; Non-volatile regs,
|
|
; FS,
|
|
; ExceptionList,
|
|
; PreviousMode,
|
|
; Volatile regs
|
|
; Seg Regs from V86 mode
|
|
; DS, ES, GS
|
|
|
|
; Don't Save:
|
|
; Floating point state
|
|
|
|
; Set:
|
|
; FS,
|
|
; Direction,
|
|
; DS, ES
|
|
|
|
; Don't Set:
|
|
; PreviousMode,
|
|
; ExceptionList
|
|
|
|
; Arguments:
|
|
; AssistLabel - label ENTER_ASSIST macro is at
|
|
; TargetLabel - label to emit for ENTER_ASSIST to jump to
|
|
|
|
; Exit-conditions:
|
|
; Interrupts match input state (this routine doesn't change IEF)
|
|
; (esp)->base of trap frame
|
|
; (ebp)->base of trap frame
|
|
; Preserves entry eax
|
|
|
|
|
|
|
|
ENTER_TRAP macro AssistLabel, TargetLabel
|
|
local b
|
|
|
|
.FPO ( FPO_LOCALS, FPO_PARAMS, FPO_PROLOG, FPO_REGS, FPO_USE_EBP, FPO_TRAPFRAME )
|
|
|
|
|
|
; Fill in parts of frame we care about
|
|
|
|
|
|
if DBG
|
|
ifndef _Ki16BitStackException
|
|
EXTRNP _Ki16BitStackException
|
|
endif
|
|
endif ; DBG
|
|
|
|
mov word ptr [esp+2], 0 ; Clear upper word of ErrorCode
|
|
|
|
push ebp ; Save the non-volatile registers
|
|
push ebx
|
|
push esi
|
|
push edi
|
|
|
|
push fs ; Save and set FS to PCR.
|
|
mov ebx,KGDT_R0_PCR
|
|
mov fs,bx
|
|
mov ebx, fs:[PcExceptionList] ;Save ExceptionList
|
|
push ebx
|
|
if DBG
|
|
push -1 ; Don't need to save ThPreviousMode from trap
|
|
else
|
|
sub esp, 4 ; pad dword
|
|
endif
|
|
push eax ; Save the volatile registers
|
|
push ecx
|
|
push edx
|
|
|
|
push ds ; Save segments
|
|
push es
|
|
push gs
|
|
|
|
|
|
; Skip allocate reset of trap frame and Set up DS/ES, they may be trash
|
|
|
|
|
|
mov ax,KGDT_R3_DATA OR RPL_MASK
|
|
sub esp,TsSegGs
|
|
mov ds,ax
|
|
mov es,ax
|
|
|
|
if DBG
|
|
|
|
; The code here check if the exception occurred in ring 0
|
|
; ABIOS code. If yes, this is a fatal condition. We will
|
|
; put out message and bugcheck.
|
|
|
|
|
|
cmp esp, 10000h ; Is the trap in abios?
|
|
jb _Ki16BitStackException ; if b, yes, switch stack and bugcheck.
|
|
|
|
endif ; DBG
|
|
|
|
mov ebp,esp
|
|
test dword ptr [esp].TsEflags,EFLAGS_V86_MASK
|
|
jnz V86_&AssistLabel
|
|
|
|
V86_&TargetLabel:
|
|
|
|
cld
|
|
SET_DEBUG_DATA
|
|
|
|
test byte ptr PCR[PcDebugActive], -1
|
|
jnz Dr_&AssistLabel
|
|
|
|
Dr_&TargetLabel:
|
|
|
|
endm
|
|
|
|
|
|
; EXIT_ALL NoRestoreSegs, NoRestoreVolatiles, NoPreviousMode
|
|
|
|
; Macro Description:
|
|
|
|
; Load a syscall frame back into the machine.
|
|
|
|
; Restore:
|
|
; Volatile regs, IF NoRestoreVolatiles blank
|
|
; NoPreviousMode,
|
|
; ExceptionList,
|
|
; FS,
|
|
; Non-volatile regs
|
|
|
|
; If the frame is a kernel mode frame, AND esp has been edited,
|
|
; then TsSegCs will have a special value. Test for that value
|
|
; and execute special code for that case.
|
|
|
|
; N.B. This macro generates an IRET! (i.e. It exits!)
|
|
|
|
; Arguments:
|
|
|
|
; NoRestoreSegs - non-blank if DS, ES, GS are NOT to be restored
|
|
|
|
; NoRestoreVolatiles - non-blank if Volatile regs are NOT to be restored
|
|
|
|
; NoPreviousMode - if nb pop ThPreviousMode
|
|
|
|
; Entry-conditions:
|
|
|
|
; (esp)->base of trap frame
|
|
; (ebp)->Base of trap frame
|
|
|
|
; Exit-conditions:
|
|
|
|
; Does not exit, returns.
|
|
; Preserves eax, ecx, edx, IFF NoRestoreVolatiles is set
|
|
|
|
|
|
|
|
?adjesp = 0
|
|
?RestoreAll = 1
|
|
|
|
EXIT_ALL macro NoRestoreSegs, NoRestoreVolatiles, NoPreviousMode
|
|
local a, b, f, x
|
|
local Abios_ExitHelp, Abios_ExitHelp_Target1, Abios_ExitHelp_Target2
|
|
local Dr_ExitHelp, Dr_ExitHelp_Target, V86_ExitHelp, V86_ExitHelp_Target
|
|
local Db_NotATrapFrame, Db_A, Db_NotValidEntry, NonFlatPm_Target
|
|
|
|
|
|
; Sanity check some values and setup globals for macro
|
|
|
|
|
|
?adjesp = TsSegGs
|
|
?RestoreAll = 1
|
|
|
|
ifnb <NoRestoreSegs>
|
|
?RestoreAll = 0
|
|
?adjesp = ?adjesp + 12
|
|
endif
|
|
|
|
ifnb <NoRestoreVolatiles>
|
|
if ?RestoreAll eq 1
|
|
%out "EXIT_ALL NoRestoreVolatiles requires NoRestoreSegs"
|
|
.err
|
|
endif
|
|
?adjesp = ?adjesp + 12
|
|
endif
|
|
|
|
ifb <NoPreviousMode>
|
|
ifndef KERNELONLY
|
|
%out EXIT_ALL can not restore previousmode outside kernel
|
|
.err
|
|
endif
|
|
endif
|
|
|
|
; All callers are responsible for getting here with interrupts disabled.
|
|
|
|
if DBG
|
|
pushfd
|
|
pop edx
|
|
|
|
test edx, EFLAGS_INTERRUPT_MASK
|
|
jnz Db_NotValidEntry
|
|
|
|
cmp esp, ebp ; make sure esp = ebp
|
|
jne Db_NotValidEntry
|
|
|
|
; Make sure BADB0D00 sig is present. If not this isn't a trap frame!
|
|
Db_A: sub [esp]+TsDbgArgMark,0BADB0D00h
|
|
jne Db_NotATrapFrame
|
|
endif
|
|
|
|
ASSERT_FS
|
|
|
|
mov edx, [esp]+TsExceptionList
|
|
if DBG
|
|
or edx, edx
|
|
jnz short @f
|
|
int 3
|
|
@@:
|
|
endif
|
|
mov ebx, fs:[PcDebugActive] ; (ebx) = DebugActive flag
|
|
mov fs:[PcExceptionList], edx ; Restore ExceptionList
|
|
|
|
ifb <NoPreviousMode>
|
|
mov ecx, [esp]+TsPreviousPreviousMode ; Restore PreviousMode
|
|
if DBG
|
|
cmp ecx, -1 ; temporary debugging code
|
|
jne @f ; to make sure no one tries to pop ThPreviousMode
|
|
int 3 ; when it wasn't saved
|
|
@@:
|
|
endif
|
|
mov esi,fs:[PcPrcbData+PbCurrentThread]
|
|
mov [esi]+ThPreviousMode,cl
|
|
else
|
|
if DBG
|
|
mov ecx, [esp]+TsPreviousPreviousMode
|
|
cmp ecx, -1 ; temporary debugging code
|
|
je @f ; to make sure no one pushed ThPreviousMode and
|
|
int 3 ; is now exiting without restoreing it
|
|
@@:
|
|
endif
|
|
endif
|
|
|
|
test ebx, 0fh
|
|
jnz Dr_ExitHelp
|
|
|
|
Dr_ExitHelp_Target:
|
|
|
|
test dword ptr [esp].TsEflags,EFLAGS_V86_MASK
|
|
jnz V86_ExitHelp
|
|
|
|
test word ptr [esp]+TsSegCs,FRAME_EDITED
|
|
jz b ; Edited frame pop out.
|
|
|
|
|
|
if ?RestoreAll eq 0
|
|
.errnz MODE_MASK-1
|
|
cmp word ptr [esp]+TsSegCs,KGDT_R3_CODE OR RPL_MASK ; set/clear ZF
|
|
bt word ptr [esp]+TsSegCs,0 ; test MODE_MASK set/clear CF
|
|
cmc ; (CF=1 and ZF=0)
|
|
ja f ; jmp if CF=0 and ZF=0
|
|
endif
|
|
ifb <NoRestoreVolatiles>
|
|
ifb <NoRestoreSegs> ; must restore eax before any
|
|
mov eax, [esp].TsEax ; selectors! (see trap0e handler)
|
|
endif
|
|
endif
|
|
|
|
ifb <NoRestoreVolatiles>
|
|
mov edx, [ebp]+TsEdx ; Restore volitales
|
|
mov ecx, [ebp]+TsEcx
|
|
ifb <NoRestoreSegs>
|
|
else
|
|
mov eax, [ebp]+TsEax
|
|
endif
|
|
endif ; NoRestoreVolatiles
|
|
|
|
cmp word ptr [ebp]+TsSegCs, KGDT_R0_CODE
|
|
jz short @f
|
|
|
|
ifb <NoRestoreSegs>
|
|
lea esp, [ebp]+TsSegGs
|
|
pop gs ; Restore Segs
|
|
pop es
|
|
pop ds
|
|
endif
|
|
NonFlatPm_Target:
|
|
lea esp, [ebp]+TsSegFs
|
|
pop fs
|
|
@@:
|
|
V86_ExitHelp_Target:
|
|
|
|
lea esp, [ebp]+TsEdi ; Skip PreMode, ExceptList and fs
|
|
|
|
pop edi ; restore non-volatiles
|
|
pop esi
|
|
pop ebx
|
|
pop ebp
|
|
|
|
|
|
; Esp MUST point to the Error Code on the stack. Because we use it to
|
|
; store the entering esp.
|
|
|
|
|
|
cmp word ptr [esp+8], 80h ; check for abios code segment?
|
|
ja Abios_ExitHelp
|
|
|
|
Abios_ExitHelp_Target1:
|
|
|
|
add esp, 4 ; remove error code from trap frame
|
|
|
|
Abios_ExitHelp_Target2:
|
|
|
|
|
|
; End of ABIOS stack check
|
|
|
|
|
|
ifnb <NoRestoreVolatiles>
|
|
|
|
test _KeFeatureBits, KF_FAST_SYSCALL
|
|
jz @f
|
|
|
|
;; If returning to kernel mode, use iretd
|
|
test dword ptr [esp+4], MODE_MASK
|
|
jz @f ; Return to kmode. use iretd
|
|
|
|
;; If returning to V86 mode, use iretd
|
|
test dword ptr [esp+8], EFLAGS_V86_MASK
|
|
jnz @f ; Return to user V86 mode
|
|
|
|
pop edx ; pop EIP
|
|
add esp, 8 ; Remove CS & Eflags
|
|
pop ecx ; pop ESP
|
|
|
|
sti ; sysexit does not reload flags
|
|
|
|
|
|
SYSEXIT_INSTR
|
|
|
|
@@:
|
|
endif ;; <NoRestoreVolatiles>
|
|
|
|
iretd ; return
|
|
|
|
if DBG
|
|
Db_NotATrapFrame:
|
|
add [esp]+TsDbgArgMark,0BADB0D00h ; put back the orig value
|
|
Db_NotValidEntry:
|
|
int 3
|
|
jmp Db_A
|
|
endif
|
|
|
|
|
|
; EXIT_HELPER
|
|
|
|
; if (PreviousMode == UserMode) {
|
|
; DR* regs = TF.Dr* regs
|
|
; }
|
|
|
|
; Entry-Conditions:
|
|
|
|
; DebugActive == TRUE
|
|
; (ebp)->TrapFrame
|
|
|
|
|
|
|
|
align dword
|
|
Dr_ExitHelp:
|
|
|
|
test dword ptr [ebp]+TsEFlags,EFLAGS_V86_MASK
|
|
jnz short x
|
|
|
|
test dword ptr [ebp]+TsSegCs,MODE_MASK
|
|
jz Dr_ExitHelp_Target
|
|
|
|
x: mov ebx,0
|
|
mov esi,[ebp]+TsDr0
|
|
mov edi,[ebp]+TsDr1
|
|
mov dr7,ebx
|
|
mov dr0,esi
|
|
mov ebx,[ebp]+TsDr2
|
|
mov dr1,edi
|
|
mov dr2,ebx
|
|
mov esi,[ebp]+TsDr3
|
|
mov edi,[ebp]+TsDr6
|
|
mov ebx,[ebp]+TsDr7
|
|
mov dr3,esi
|
|
mov dr6,edi
|
|
mov dr7,ebx
|
|
|
|
jmp Dr_ExitHelp_Target
|
|
|
|
align dword
|
|
Abios_ExitHelp:
|
|
|
|
|
|
; INTERRUPT_STACK32_TO_STACK16
|
|
|
|
; This macro remaps current 32bit stack to 16bit stack at interrupt
|
|
; time.
|
|
|
|
; Arguments:
|
|
|
|
; (esp)->TsEip.
|
|
|
|
|
|
; PERFNOTE shielint We should check if there is any other H/W interrupt
|
|
; pending. If yes, don't switch back to 16 bit stack. This way
|
|
; we can get better performance.
|
|
|
|
|
|
cmp word ptr [esp+2], 0 ; (esp+2) = Low word of error code
|
|
jz Abios_ExitHelp_Target1
|
|
cmp word ptr [esp], 0 ; (esp) = High word of error code
|
|
jnz Abios_ExitHelp_Target1
|
|
|
|
shr dword ptr [esp], 16
|
|
mov word ptr [esp + 2], KGDT_STACK16
|
|
lss sp, dword ptr [esp]
|
|
movzx esp, sp
|
|
jmp Abios_ExitHelp_Target2
|
|
|
|
|
|
; Restore volatiles for V86 mode, and move seg regs
|
|
|
|
|
|
align dword
|
|
V86_ExitHelp:
|
|
|
|
add esp,TsEdx
|
|
pop edx
|
|
pop ecx
|
|
pop eax
|
|
jmp V86_ExitHelp_Target
|
|
|
|
|
|
if ?RestoreAll eq 0
|
|
|
|
; Restore segs and volatiles for non-flat R3 PM (VDM in PM)
|
|
|
|
|
|
f: mov eax,[esp].TsEax ; restore eax before any selectors
|
|
; (see trap0e handler)
|
|
add esp,TsSegGs
|
|
|
|
pop gs
|
|
pop es
|
|
pop ds
|
|
|
|
pop edx
|
|
pop ecx
|
|
jmp NonFlatPm_Target
|
|
|
|
endif ; not ?RestoreAll
|
|
|
|
|
|
|
|
; TsSegCs contains the special value that means the frame was edited
|
|
; in a way that affected esp, AND it's a kernel mode frame.
|
|
; (Special value is null selector except for RPL.)
|
|
|
|
; Put back the real CS.
|
|
; push eflags, eip onto target stack
|
|
; restore
|
|
; switch to target stack
|
|
; iret
|
|
|
|
|
|
b: mov ebx,[esp]+TsTempSegCs
|
|
mov [esp]+TsSegCs,ebx
|
|
|
|
|
|
; There is no instruction that will load esp with an arbitrary value
|
|
; (i.e. one out of a frame) and do a return, if no privledge transition
|
|
; is occuring. Therefore, if we are returning to kernel mode, and
|
|
; esp has been edited, we must "emulate" a kind of iretd.
|
|
|
|
; We do this by logically pushing the eip,cs,eflags onto the new
|
|
; logical stack, loading that stack, and doing an iretd. This
|
|
; requires that the new logical stack is at least 1 dword higher
|
|
; than the unedited esp would have been. (i.e. It is not legal
|
|
; to edit esp to have a new value < the old value.)
|
|
|
|
; KeContextToKframes enforces this rule.
|
|
|
|
|
|
|
|
; Compute new logical stack address
|
|
|
|
|
|
mov ebx,[esp]+TsTempEsp
|
|
sub ebx,12
|
|
mov [esp]+TsErrCode,ebx
|
|
|
|
|
|
; Copy eip,cs,eflags to new stack. note we do this high to low
|
|
|
|
|
|
mov esi,[esp]+TsEflags
|
|
mov [ebx+8],esi
|
|
mov esi,[esp]+TsSegCs
|
|
mov [ebx+4],esi
|
|
mov esi,[esp]+TsEip
|
|
mov [ebx],esi
|
|
|
|
|
|
; Do a standard restore sequence.
|
|
|
|
; Observe that RestoreVolatiles is honored. Editing a volatile
|
|
; register has no effect when returning from a system call.
|
|
|
|
ifb <NoRestoreVolatiles>
|
|
mov eax,[esp].TsEax
|
|
endif
|
|
; add esp,TsSegGs
|
|
|
|
;ifb <NoRestoreSegs>
|
|
; pop gs
|
|
; pop es
|
|
; pop ds
|
|
;else
|
|
; add esp,12
|
|
;endif
|
|
|
|
ifb <NoRestoreVolatiles>
|
|
mov edx, [esp]+TsEdx
|
|
mov ecx, [esp]+TsEcx
|
|
endif
|
|
|
|
;ifnb <NoPreviousMode>
|
|
; add esp, 4 ; Skip previous mode
|
|
;else
|
|
; pop ebx ; Restore PreviousMode
|
|
; mov esi,fs:[PcPrcbData+PbCurrentThread]
|
|
; mov ss:[esi]+ThPreviousMode,bl
|
|
;endif
|
|
|
|
; pop ebx
|
|
|
|
; mov fs:[PcExceptionList], ebx ;Restore ExceptionList
|
|
; pop fs
|
|
|
|
add esp, TsEdi
|
|
pop edi ; restore non-volatiles
|
|
pop esi
|
|
pop ebx
|
|
pop ebp
|
|
|
|
|
|
; (esp)->TsErrCode, where we saved the new esp
|
|
|
|
|
|
mov esp,[esp] ; Do move not push to avoid increment
|
|
iretd
|
|
|
|
endm
|
|
|
|
|
|
|
|
|
|
; INTERRUPT_EXIT
|
|
|
|
; Macro Description:
|
|
|
|
; This macro is executed on return from an interrupt vector service
|
|
; service routine. Its function is to restore privileged processor
|
|
; state, and continue thread execution. If control is returning to
|
|
; user mode and there is a user APC pending, then APC level interupt
|
|
; will be requested and control is transfered to the user APC delivery
|
|
; routine, if no higher level interrupt pending.
|
|
|
|
; Arguments:
|
|
|
|
; (TOS) = previous irql
|
|
; (TOS+4) = irq vector to eoi
|
|
; (TOS+8 ...) = machine_state frame
|
|
; (ebp)-> machine state frame (trap frame)
|
|
|
|
|
|
|
|
INTERRUPT_EXIT macro DebugCheck
|
|
local a
|
|
|
|
ifnb <DebugCheck>
|
|
POLL_DEBUGGER
|
|
endif
|
|
if DBG ; save current eip for
|
|
a: mov esi, offset a ; debugging bad trap frames
|
|
endif
|
|
|
|
ifdef __imp_Kei386EoiHelper@0
|
|
cli
|
|
call _HalEndSystemInterrupt@8
|
|
jmp dword ptr [__imp_Kei386EoiHelper@0]
|
|
|
|
else
|
|
cli
|
|
call dword ptr [__imp__HalEndSystemInterrupt@8]
|
|
jmp Kei386EoiHelper@0
|
|
endif
|
|
endm
|
|
|
|
|
|
|
|
|
|
; SPURIOUS_INTERRUPT_EXIT
|
|
|
|
; Macro Description:
|
|
|
|
; To exit an interrupt without performing the EOI.
|
|
|
|
; Arguments:
|
|
|
|
; (TOS) = machine_state frame
|
|
; (ebp)-> machine state frame (trap frame)
|
|
|
|
|
|
|
|
SPURIOUS_INTERRUPT_EXIT macro
|
|
local a
|
|
if DBG ; save current eip for
|
|
a: mov esi, offset a ; debugging bad trap frames
|
|
endif
|
|
ifdef __imp_Kei386EoiHelper@0
|
|
jmp dword ptr [__imp_Kei386EoiHelper@0]
|
|
else
|
|
jmp Kei386EoiHelper@0
|
|
endif
|
|
endm
|
|
|
|
|
|
|
|
; ENTER_TRAPV86
|
|
|
|
; Macro Description:
|
|
|
|
; Construct trap frame for v86 mode traps.
|
|
|
|
|
|
|
|
ENTER_TRAPV86 macro DRENTER,V86ENTER,LOADES
|
|
sub esp, TsErrCode
|
|
mov word ptr [esp].TsErrCode + 2, 0
|
|
mov [esp].TsEbx, ebx
|
|
mov [esp].TsEax, eax
|
|
mov [esp].TsEbp, ebp
|
|
mov [esp].TsEsi, esi
|
|
mov [esp].TsEdi, edi
|
|
mov ebx, KGDT_R0_PCR
|
|
mov eax, KGDT_R3_DATA OR RPL_MASK
|
|
mov [esp].TsEcx, ecx
|
|
mov [esp].TsEdx, edx
|
|
if DBG
|
|
mov [esp].TsPreviousPreviousMode, -1
|
|
mov [esp]+TsDbgArgMark, 0BADB0D00h
|
|
endif
|
|
mov fs, bx
|
|
mov ds, ax
|
|
ifnb <LOADES>
|
|
mov es, ax
|
|
endif
|
|
mov ebp, esp
|
|
cld ; CHECKIT_SUDEEP ; do we really need it
|
|
test byte ptr PCR[PcDebugActive], -1
|
|
jnz Dr_&DRENTER
|
|
|
|
Dr_&V86ENTER:
|
|
endm
|
|
|
|
|
|
|
|
; Taken from ntos\vdm\i386\vdmtb.inc
|
|
|
|
|
|
FIXED_NTVDMSTATE_LINEAR_PC_AT equ 0714H
|
|
FIXED_NTVDMSTATE_LINEAR_PC_98 equ 0614H
|
|
MACHINE_TYPE_MASK equ 0ff00H
|
|
VDM_VIRTUAL_INTERRUPTS equ 0200H
|
|
|
|
|
|
|
|
; EXIT_TRAPV86
|
|
|
|
; Macro Description:
|
|
|
|
; if UserApc is pending deliver it
|
|
; if User Context is v86 mode
|
|
; Exit from kernel (does not return)
|
|
; else
|
|
; return (expected to execute EXIT_ALL)
|
|
|
|
|
|
EXIT_TRAPV86 macro
|
|
local w, x, y, z
|
|
|
|
z: mov ebx, PCR[PcPrcbData+PbCurrentThread]
|
|
mov byte ptr [ebx]+ThAlerted, 0
|
|
cmp byte ptr [ebx]+ThApcState.AsUserApcPending, 0
|
|
jne short w
|
|
|
|
|
|
; Kernel exit to V86 mode
|
|
|
|
|
|
add esp,TsEdx
|
|
pop edx
|
|
pop ecx
|
|
pop eax
|
|
test byte ptr PCR[PcDebugActive], -1
|
|
jnz short x
|
|
y:
|
|
add esp,12 ; unused fields
|
|
pop edi
|
|
pop esi
|
|
pop ebx
|
|
pop ebp
|
|
add esp,4 ; clear error code
|
|
iretd
|
|
|
|
x: mov esi,[ebp]+TsDr0
|
|
mov edi,[ebp]+TsDr1
|
|
mov ebx,[ebp]+TsDr2
|
|
mov dr0,esi
|
|
mov dr1,edi
|
|
mov dr2,ebx
|
|
mov esi,[ebp]+TsDr3
|
|
mov edi,[ebp]+TsDr6
|
|
mov ebx,[ebp]+TsDr7
|
|
mov dr3,esi
|
|
mov dr6,edi
|
|
mov dr7,ebx
|
|
jmp short y
|
|
|
|
w:
|
|
|
|
; Dispatch user mode APC
|
|
; The APC routine runs with interrupts on and at APC level
|
|
|
|
|
|
mov ecx, APC_LEVEL
|
|
fstCall KfRaiseIrql
|
|
push eax ; Save OldIrql
|
|
sti
|
|
|
|
stdCall _KiDeliverApc, <1, 0, ebp> ; ebp - Trap frame
|
|
; 0 - Null exception frame
|
|
; 1 - Previous mode
|
|
|
|
pop ecx ; (TOS) = OldIrql
|
|
fstCall KfLowerIrql
|
|
|
|
cli
|
|
|
|
|
|
; UserApc may have changed to vdm Monitor context (user flat 32)
|
|
; If it has cannot use the v86 only kernel exit
|
|
|
|
|
|
test dword ptr [ebp]+TsEFlags,EFLAGS_V86_MASK
|
|
jnz short z
|
|
|
|
; Exit to do EXIT_ALL
|
|
endm
|