598 lines
16 KiB
ArmAsm
598 lines
16 KiB
ArmAsm
|
//++
|
|||
|
//
|
|||
|
// Module Name:
|
|||
|
//
|
|||
|
// trampoln.s
|
|||
|
//
|
|||
|
// Abstract:
|
|||
|
//
|
|||
|
// This module implements the trampoline code necessary to dispatch user
|
|||
|
// mode APCs.
|
|||
|
//
|
|||
|
// Author:
|
|||
|
//
|
|||
|
// William K. Cheung 25-Oct-1995
|
|||
|
//
|
|||
|
// Environment:
|
|||
|
//
|
|||
|
// User mode only.
|
|||
|
//
|
|||
|
// Revision History:
|
|||
|
//
|
|||
|
// 08-Feb-1996 Updated to EAS 2.1
|
|||
|
//
|
|||
|
//--
|
|||
|
|
|||
|
#include "ksia64.h"
|
|||
|
|
|||
|
.file "trampoln.s"
|
|||
|
|
|||
|
PublicFunction(RtlpCaptureRnats)
|
|||
|
PublicFunction(RtlDispatchException)
|
|||
|
PublicFunction(RtlRaiseException)
|
|||
|
PublicFunction(RtlRaiseStatus)
|
|||
|
PublicFunction(ZwContinue)
|
|||
|
PublicFunction(ZwCallbackReturn)
|
|||
|
PublicFunction(ZwRaiseException)
|
|||
|
PublicFunction(ZwTestAlert)
|
|||
|
.global Wow64PrepareForException
|
|||
|
|
|||
|
|
|||
|
//++
|
|||
|
//
|
|||
|
// EXCEPTION_DISPOSITION
|
|||
|
// KiUserApcHandler (
|
|||
|
// IN PEXCEPTION_RECORD ExceptionRecord,
|
|||
|
// IN ULONG EstablisherFrame,
|
|||
|
// IN OUT PCONTEXT ContextRecord,
|
|||
|
// IN OUT PDISPATCHER_CONTEXT DispatcherContext
|
|||
|
//
|
|||
|
// Routine Description:
|
|||
|
//
|
|||
|
// This function is called when an exception occurs in an APC routine
|
|||
|
// or one of its dynamic descendents and when an unwind through the
|
|||
|
// APC dispatcher is in progress. If an unwind is in progress, then test
|
|||
|
// alert is called to ensure that all currently queued APCs are executed.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
//
|
|||
|
// ExceptionRecord (a0) - Supplies a pointer to an exception record.
|
|||
|
//
|
|||
|
// EstablisherFrame (a1) - Supplies the frame pointer of the establisher
|
|||
|
// of this exception handler.
|
|||
|
//
|
|||
|
// ContextRecord (a2) - Supplies a pointer to a context record.
|
|||
|
//
|
|||
|
// DispatcherContext (a3) - Supplies a pointer to the dispatcher context
|
|||
|
// record.
|
|||
|
//
|
|||
|
// Return Value:
|
|||
|
//
|
|||
|
// ExceptionContinueSearch is returned as the function value.
|
|||
|
//
|
|||
|
//--
|
|||
|
|
|||
|
NESTED_ENTRY (KiUserApcHandler)
|
|||
|
|
|||
|
//
|
|||
|
// register aliases
|
|||
|
//
|
|||
|
|
|||
|
pUwnd = pt1
|
|||
|
pNot = pt2
|
|||
|
|
|||
|
|
|||
|
NESTED_SETUP(1, 2, 0, 0)
|
|||
|
add t0 = ErExceptionFlags, a0
|
|||
|
;;
|
|||
|
|
|||
|
PROLOGUE_END
|
|||
|
|
|||
|
ld4 t2 = [t0] // get exception flags
|
|||
|
;;
|
|||
|
and t2 = EXCEPTION_UNWIND, t2 // check if unwind in progress
|
|||
|
;;
|
|||
|
|
|||
|
cmp4.ne pUwnd, pNot = zero, t2
|
|||
|
;;
|
|||
|
|
|||
|
(pNot) add v0 = ExceptionContinueSearch, zero
|
|||
|
(pNot) br.ret.sptk.clr brp // return
|
|||
|
(pUwnd) br.call.spnt.many brp = ZwTestAlert
|
|||
|
|
|||
|
//
|
|||
|
// restore preserved states and set the disposition value to continue search
|
|||
|
//
|
|||
|
|
|||
|
add v0 = ExceptionContinueSearch, zero
|
|||
|
mov brp = savedbrp // restore return link
|
|||
|
nop.b 0
|
|||
|
|
|||
|
nop.m 0
|
|||
|
mov ar.pfs = savedpfs // restore pfs
|
|||
|
br.ret.sptk.clr brp // return
|
|||
|
|
|||
|
NESTED_EXIT (KiUserApcHandler)
|
|||
|
|
|||
|
//++
|
|||
|
//
|
|||
|
// VOID
|
|||
|
// KiUserApcDispatcher (
|
|||
|
// IN PVOID NormalContext,
|
|||
|
// IN PVOID SystemArgument1,
|
|||
|
// IN PVOID SystemArgument2,
|
|||
|
// IN PKNORMAL_ROUTINE NormalRoutine
|
|||
|
// )
|
|||
|
//
|
|||
|
// Routine Description:
|
|||
|
//
|
|||
|
// This routine is entered on return from kernel mode to deliver an APC
|
|||
|
// in user mode. The stack frame for this routine was built when the
|
|||
|
// APC interrupt was processed and contains the entire machine state of
|
|||
|
// the current thread. The specified APC routine is called and then the
|
|||
|
// machine state is restored and execution is continued.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
//
|
|||
|
// t0 - Supplies the normal context parameter that was specified when the
|
|||
|
// APC was initialized.
|
|||
|
//
|
|||
|
// t1 - Supplies the first argument that was provied by the executive when
|
|||
|
// the APC was queued.
|
|||
|
//
|
|||
|
// t2 - Supplies the second argument that was provided by the executive
|
|||
|
// when the APC was queued.
|
|||
|
//
|
|||
|
// t3 - Supplies the address of the plabel for the function
|
|||
|
// that is to be called.
|
|||
|
//
|
|||
|
// Return Value:
|
|||
|
//
|
|||
|
// None.
|
|||
|
//
|
|||
|
//
|
|||
|
// N.B. On entry, sp points to the stack scratch area at the top of the
|
|||
|
// memory stack.
|
|||
|
//
|
|||
|
// N.B. The input arguments of this function are passed from the kernel
|
|||
|
// in scratch registers t0, t1, t2, and t3.
|
|||
|
//
|
|||
|
//--
|
|||
|
|
|||
|
NESTED_ENTRY_EX (KiUserApcDispatch, KiUserApcHandler)
|
|||
|
ALTERNATE_ENTRY (KiUserApcDispatcher)
|
|||
|
|
|||
|
.prologue
|
|||
|
.unwabi @nt, CONTEXT_FRAME
|
|||
|
|
|||
|
.regstk 0, 0, 3, 0
|
|||
|
|
|||
|
rBsp = t10 // BspStore
|
|||
|
rpCr = t11 // pointer to context record
|
|||
|
rpT1 = t12 // temporary pointer
|
|||
|
|
|||
|
alloc t22 = ar.pfs, 0, 0, 3, 0 // 3 outputs
|
|||
|
add t10 = STACK_SCRATCH_AREA+ContextFrameLength+24, sp
|
|||
|
add t11 = STACK_SCRATCH_AREA+ContextFrameLength, sp
|
|||
|
;;
|
|||
|
|
|||
|
PROLOGUE_END
|
|||
|
|
|||
|
ld8.nta t12 = [t10], -8
|
|||
|
movl s1 = _gp
|
|||
|
;;
|
|||
|
|
|||
|
ld8.nta out0 = [t11], 8
|
|||
|
ld8.nta t13 = [t12], PlGlobalPointer-PlEntryPoint
|
|||
|
;;
|
|||
|
|
|||
|
ld8.nta out1 = [t11], 8
|
|||
|
ld8.nta out2 = [t10]
|
|||
|
mov bt0 = t13
|
|||
|
|
|||
|
ld8.nta gp = [t12]
|
|||
|
br.call.sptk.many brp = bt0 // call APC routine
|
|||
|
;;
|
|||
|
|
|||
|
//
|
|||
|
// On return, setup global pointer and branch register to call ZwContinue.
|
|||
|
// Also, flush the RSE to sync up the bsp and bspstore pointers. The
|
|||
|
// corresponding field in the context record is updated too.
|
|||
|
//
|
|||
|
|
|||
|
flushrs
|
|||
|
mov out1 = 1 // set TestAlert to TRUE
|
|||
|
;;
|
|||
|
|
|||
|
add out0 = STACK_SCRATCH_AREA, sp // context record address
|
|||
|
mov gp = s1 // restore gp
|
|||
|
br.call.sptk.many brp = ZwContinue
|
|||
|
;;
|
|||
|
|
|||
|
//
|
|||
|
// if successful, ZwContinue does not return here;
|
|||
|
// otherwise, error happened.
|
|||
|
//
|
|||
|
|
|||
|
mov gp = s1 // restore gp
|
|||
|
mov s0 = v0 // save the return status
|
|||
|
;;
|
|||
|
|
|||
|
Kuad10:
|
|||
|
mov out0 = s0 // setup 1st argument
|
|||
|
br.call.sptk.many brp = RtlRaiseStatus
|
|||
|
;;
|
|||
|
|
|||
|
nop.m 0
|
|||
|
nop.i 0
|
|||
|
br Kuad10 // loop on return
|
|||
|
|
|||
|
NESTED_EXIT(KiUserApcDispatch)
|
|||
|
|
|||
|
|
|||
|
//++
|
|||
|
//
|
|||
|
// VOID
|
|||
|
// KiUserCallbackDispatcher (
|
|||
|
// VOID
|
|||
|
// )
|
|||
|
//
|
|||
|
// Routine Description:
|
|||
|
//
|
|||
|
// This routine is entered on a callout from kernel mode to execute a
|
|||
|
// user mode callback function. All arguments for this function have
|
|||
|
// been placed on the stack.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
//
|
|||
|
// (sp + 32 + CkApiNumber) - Supplies the API number of the callback
|
|||
|
// function that is to be executed.
|
|||
|
//
|
|||
|
// (sp + 32 + CkBuffer) - Supplies a pointer to the input buffer.
|
|||
|
//
|
|||
|
// (sp + 32 + CkLength) - Supplies the input buffer length.
|
|||
|
//
|
|||
|
// Return Value:
|
|||
|
//
|
|||
|
// This function returns to kernel mode.
|
|||
|
//
|
|||
|
// N.B. Preserved register s1 is used to save ZwCallbackReturn plabel address.
|
|||
|
// On entry, gp is set to the global pointer value of NTDLL
|
|||
|
//
|
|||
|
//--
|
|||
|
|
|||
|
NESTED_ENTRY(KiUserCallbackDispatch)
|
|||
|
|
|||
|
.prologue
|
|||
|
.savesp rp, STACK_SCRATCH_AREA+CkBrRp
|
|||
|
.savesp ar.pfs, STACK_SCRATCH_AREA+CkRsPFS
|
|||
|
.vframesp STACK_SCRATCH_AREA+CkIntSp
|
|||
|
|
|||
|
nop.m 0
|
|||
|
nop.m 0
|
|||
|
nop.i 0
|
|||
|
;;
|
|||
|
|
|||
|
PROLOGUE_END
|
|||
|
|
|||
|
ALTERNATE_ENTRY(KiUserCallbackDispatcher)
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// register aliases
|
|||
|
//
|
|||
|
|
|||
|
rpT0 = t0 // temporary pointer
|
|||
|
rpT1 = t1 // temporary pointer
|
|||
|
rT0 = t2 // temporary value
|
|||
|
rFunc = t3 // callback function entry
|
|||
|
rApi = t4
|
|||
|
|
|||
|
|
|||
|
alloc t22 = ar.pfs, 0, 0, 3, 0 // 3 outputs max.
|
|||
|
mov teb = kteb // sanitize teb
|
|||
|
add rpT0 = STACK_SCRATCH_AREA + CkApiNumber, sp
|
|||
|
movl gp = _gp
|
|||
|
;;
|
|||
|
|
|||
|
ld4 rApi = [rpT0], CkBuffer - CkApiNumber // get API number
|
|||
|
add rpT1 = TePeb, teb
|
|||
|
mov s0 = gp
|
|||
|
;;
|
|||
|
|
|||
|
//
|
|||
|
// load both input buffer address and length into scratch register t2
|
|||
|
// and then deposit them into registers out0 & out1 respectively.
|
|||
|
//
|
|||
|
// N.B. t0 is 8-byte aligned.
|
|||
|
//
|
|||
|
|
|||
|
LDPTRINC(out0, rpT0, CkLength-CkBuffer) // input buffer address
|
|||
|
LDPTR(t11, rpT1) // get address of PEB
|
|||
|
#if defined(_WIN64)
|
|||
|
shl rApi = rApi, 3 // compute offset to table entry
|
|||
|
#else
|
|||
|
shl rApi = rApi, 2 // compute offset to table entry
|
|||
|
#endif
|
|||
|
;;
|
|||
|
|
|||
|
ld4 out1 = [rpT0] // get input buffer length
|
|||
|
add t5 = PeKernelCallbackTable, t11
|
|||
|
;;
|
|||
|
LDPTR(rFunc, t5) // address of callback table
|
|||
|
;;
|
|||
|
|
|||
|
add rFunc = rApi, rFunc // compute addr of table entry
|
|||
|
;;
|
|||
|
LDPTR(t6, rFunc) // get plabel's address
|
|||
|
;;
|
|||
|
|
|||
|
ld8.nt1 t9 = [t6], PlGlobalPointer-PlEntryPoint // load entry point address
|
|||
|
;;
|
|||
|
|
|||
|
ld8.nt1 gp = [t6] // load callee's GP
|
|||
|
mov bt0 = t9
|
|||
|
br.call.sptk.many brp = bt0 // invoke the callback func
|
|||
|
|
|||
|
//
|
|||
|
// If a return from the callback function occurs, then the output buffer
|
|||
|
// address and length are returned as NULL.
|
|||
|
//
|
|||
|
|
|||
|
mov out0 = zero // NULL output buffer addr
|
|||
|
mov out1 = zero // zero output buffer len
|
|||
|
|
|||
|
mov out2 = v0 // set completion status
|
|||
|
mov gp = s0
|
|||
|
br.call.sptk.many brp = ZwCallbackReturn
|
|||
|
|
|||
|
//
|
|||
|
// Unsuccessful completion after attempting to return to kernel mode. Use
|
|||
|
// the return status as the exception code, set noncontinuable exception and
|
|||
|
// attempt to raise another exception. Note there is no return from raise
|
|||
|
// status.
|
|||
|
//
|
|||
|
|
|||
|
nop.m 0
|
|||
|
mov gp = s0 // restore our own GP
|
|||
|
mov s0 = v0 // save status value
|
|||
|
;;
|
|||
|
|
|||
|
Kucd10:
|
|||
|
mov out0 = s0 // set status value
|
|||
|
br.call.sptk.many brp = RtlRaiseStatus
|
|||
|
|
|||
|
nop.m 0
|
|||
|
nop.m 0
|
|||
|
br Kucd10 // jump back to Kucd10
|
|||
|
|
|||
|
NESTED_EXIT(KiUserCallbackDispatch)
|
|||
|
|
|||
|
//++
|
|||
|
//
|
|||
|
// VOID
|
|||
|
// KiUserExceptionDispatcher (
|
|||
|
// IN PEXCEPTION_RECORD ExceptionRecord,
|
|||
|
// IN PCONTEXT ContextRecord
|
|||
|
// )
|
|||
|
//
|
|||
|
// Routine Description:
|
|||
|
//
|
|||
|
// This routine is entered on return from kernel mode to dispatch a user
|
|||
|
// mode exception. If a frame based handler handles the exception, then
|
|||
|
// the execution is continued. Else last chance processing is performed.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
//
|
|||
|
// s0 - Supplies a pointer to an exception record.
|
|||
|
//
|
|||
|
// s1 - Supplies a pointer to a context record.
|
|||
|
//
|
|||
|
// Return Value:
|
|||
|
//
|
|||
|
// None.
|
|||
|
//
|
|||
|
// N.B. preserved register s3 is used to save the current global pointer.
|
|||
|
//
|
|||
|
//--
|
|||
|
|
|||
|
NESTED_ENTRY (KiUserExceptionDispatch)
|
|||
|
ALTERNATE_ENTRY(KiUserExceptionDispatcher)
|
|||
|
|
|||
|
.prologue
|
|||
|
.unwabi @nt, CONTEXT_FRAME
|
|||
|
|
|||
|
alloc t0 = ar.pfs, 0, 1, 3, 0
|
|||
|
mov teb = kteb // sanitize teb
|
|||
|
mov s3 = gp // save global pointer
|
|||
|
;;
|
|||
|
|
|||
|
PROLOGUE_END
|
|||
|
|
|||
|
flushrs // flush the RSE
|
|||
|
;;
|
|||
|
mov out0 = s1
|
|||
|
br.call.sptk.many brp = RtlpCaptureRnats
|
|||
|
;;
|
|||
|
|
|||
|
add t1 = @gprel(Wow64PrepareForException), gp
|
|||
|
;;
|
|||
|
ld8 t1 = [t1]
|
|||
|
;;
|
|||
|
cmp.ne pt1, pt0 = zero, t1 // Wow64PrepareForException != NULL?
|
|||
|
;;
|
|||
|
(pt1) ld8 t2 = [t1], PlGlobalPointer - PlEntryPoint
|
|||
|
;;
|
|||
|
(pt1) ld8 gp = [t1]
|
|||
|
(pt1) mov bt0 = t2
|
|||
|
(pt1) br.call.spnt.few brp = bt0
|
|||
|
;;
|
|||
|
|
|||
|
mov gp = s3
|
|||
|
|
|||
|
mov out0 = s0
|
|||
|
mov out1 = s1
|
|||
|
br.call.sptk.many brp = RtlDispatchException
|
|||
|
|
|||
|
cmp4.eq pt1, pt0 = zero, v0 // result is FALSE ?
|
|||
|
;;
|
|||
|
(pt1) mov out2 = zero
|
|||
|
mov gp = s3
|
|||
|
|
|||
|
(pt0) add out0 = 0, s1
|
|||
|
(pt0) mov out1 = zero // set test alert to FALSE.
|
|||
|
(pt0) br.call.sptk.many brp = ZwContinue
|
|||
|
;;
|
|||
|
|
|||
|
(pt1) add out0 = 0, s0
|
|||
|
(pt1) mov out1 = s1
|
|||
|
(pt1) br.call.sptk.many brp = ZwRaiseException
|
|||
|
;;
|
|||
|
|
|||
|
//
|
|||
|
// Common code for nonsuccessful completion of the continue or last chance
|
|||
|
// processing service. Use the return status as the exception code, set
|
|||
|
// noncontinuable exception and attempt to raise another exception. Note
|
|||
|
// that the stack grows and eventually this loop will end.
|
|||
|
//
|
|||
|
|
|||
|
Kued10:
|
|||
|
|
|||
|
//
|
|||
|
// allocate space for exception record
|
|||
|
//
|
|||
|
|
|||
|
nop.m 0
|
|||
|
movl s2 = EXCEPTION_NONCONTINUABLE // set noncontinuable flag.
|
|||
|
|
|||
|
add sp = -ExceptionRecordLength, sp
|
|||
|
nop.f 0
|
|||
|
mov gp = s3 // restore gp
|
|||
|
;;
|
|||
|
|
|||
|
add out0 = STACK_SCRATCH_AREA, sp // get except record addr
|
|||
|
add t2 = ErExceptionFlags+STACK_SCRATCH_AREA, sp
|
|||
|
add t3 = ErExceptionCode+STACK_SCRATCH_AREA, sp
|
|||
|
;;
|
|||
|
|
|||
|
//
|
|||
|
// Set exception flags and exception code.
|
|||
|
//
|
|||
|
|
|||
|
st4 [t2] = s2, ErExceptionRecord - ErExceptionFlags
|
|||
|
st4 [t3] = v0, ErNumberParameters - ErExceptionCode
|
|||
|
;;
|
|||
|
|
|||
|
//
|
|||
|
// Set exception record and number of parameters.
|
|||
|
// Then call RtlRaiseException
|
|||
|
//
|
|||
|
|
|||
|
st4 [t2] = s0
|
|||
|
st4 [t3] = zero
|
|||
|
br.call.sptk.many brp = RtlRaiseException
|
|||
|
|
|||
|
nop.m 0
|
|||
|
nop.m 0
|
|||
|
br Kued10 // loop on return
|
|||
|
|
|||
|
NESTED_EXIT(KiUserExceptionDispatch)
|
|||
|
|
|||
|
|
|||
|
//++
|
|||
|
//
|
|||
|
// NTSTATUS
|
|||
|
// KiRaiseUserExceptionDispatcher (
|
|||
|
// IN NTSTATUS ExceptionCode
|
|||
|
// )
|
|||
|
//
|
|||
|
// Routine Description:
|
|||
|
//
|
|||
|
// This routine is entered on return from kernel mode to raise a user
|
|||
|
// mode exception.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
//
|
|||
|
// v0 - Supplies the status code to be raised.
|
|||
|
//
|
|||
|
// Return Value:
|
|||
|
//
|
|||
|
// ExceptionCode
|
|||
|
//
|
|||
|
//--
|
|||
|
|
|||
|
//
|
|||
|
// N.B. This function is not called in the typical way. Instead of a normal
|
|||
|
// subroutine call to the nested entry point above, the alternate entry point
|
|||
|
// address below is stuffed into the Fir address of the trap frame. Thus when
|
|||
|
// the kernel returns from the trap, the following code is executed directly.
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
NESTED_ENTRY(KiRaiseUserExceptionDispatch)
|
|||
|
|
|||
|
.prologue
|
|||
|
.savepsp ar.pfs, -8
|
|||
|
nop.m 0
|
|||
|
.savepsp rp, 0
|
|||
|
nop.m 0
|
|||
|
nop.i 0
|
|||
|
;;
|
|||
|
|
|||
|
ALTERNATE_ENTRY(KiRaiseUserExceptionDispatcher)
|
|||
|
|
|||
|
//
|
|||
|
// ar.pfs and brp have been saved on the stack in the scratch area.
|
|||
|
//
|
|||
|
|
|||
|
alloc t22 = ar.pfs, 8, 1, 1, 0
|
|||
|
ld8.nta t3 = [sp]
|
|||
|
.fframe ExceptionRecordLength+STACK_SCRATCH_AREA, tg10
|
|||
|
[tg10:] add sp = -ExceptionRecordLength-STACK_SCRATCH_AREA, sp
|
|||
|
;;
|
|||
|
|
|||
|
PROLOGUE_END
|
|||
|
|
|||
|
add t1 = STACK_SCRATCH_AREA+ErExceptionCode, sp
|
|||
|
add t2 = STACK_SCRATCH_AREA+ErExceptionFlags, sp
|
|||
|
mov loc0 = v0
|
|||
|
;;
|
|||
|
|
|||
|
//
|
|||
|
// set exception code and exception flags
|
|||
|
//
|
|||
|
|
|||
|
st4 [t1] = v0, ErExceptionRecord - ErExceptionCode
|
|||
|
movl gp = _gp // setup gp to ntdll's
|
|||
|
|
|||
|
st4 [t2] = zero, ErExceptionAddress - ErExceptionFlags
|
|||
|
;;
|
|||
|
st4 [t1] = zero, ErNumberParameters - ErExceptionRecord
|
|||
|
add out0 = STACK_SCRATCH_AREA, sp
|
|||
|
;;
|
|||
|
|
|||
|
//
|
|||
|
// set exception record and exception address
|
|||
|
//
|
|||
|
|
|||
|
st4 [t1] = zero // set number of parameters
|
|||
|
STPTR(t2, t3)
|
|||
|
br.call.sptk.many brp = RtlRaiseException
|
|||
|
|
|||
|
add t1 = ExceptionRecordLength+STACK_SCRATCH_AREA, sp
|
|||
|
add t2 = ExceptionRecordLength+STACK_SCRATCH_AREA+8, sp
|
|||
|
mov v0 = loc0
|
|||
|
;;
|
|||
|
|
|||
|
ld8.nta t3 = [t1]
|
|||
|
ld8.nta t4 = [t2]
|
|||
|
;;
|
|||
|
mov brp = t3
|
|||
|
|
|||
|
.restore tg20
|
|||
|
[tg20:] add sp = ExceptionRecordLength, sp
|
|||
|
mov ar.pfs = t4
|
|||
|
br.ret.sptk.clr brp
|
|||
|
|
|||
|
NESTED_EXIT(KiRaiseUserExceptionDispatcher)
|