397 lines
13 KiB
NASM
397 lines
13 KiB
NASM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;
|
|
;; Microsoft Research Singularity
|
|
;;
|
|
;; Copyright (c) Microsoft Corporation. All rights reserved.
|
|
;;
|
|
;; File: halstack.asm
|
|
;;
|
|
;; Note:
|
|
;;
|
|
|
|
|
|
include hal.inc
|
|
|
|
;; The code in this file runs as part of the priveleged runtime. It is responsible for
|
|
;; calling into the ABI to allocate and free new stack segments, and to do all the low
|
|
;; level mucking to stitch the stacks together.
|
|
|
|
|
|
;; LinkStack is a glue routine to extend the stack. Its job is to:
|
|
;;
|
|
;; 1 - preserve the calling registers of the function it is being called from
|
|
;; 2 - set up arguments and call the appropriate ABI call to grow the stack
|
|
;; 3 - copy args and stack frame and switch execution to the resulting new stack segment
|
|
;; 4 - tail patch the function return so that Free is called on return
|
|
;;
|
|
;; Its entry point calling conventions are delicate; essentially
|
|
;; - rax contains the amount of new stack needed
|
|
;; - all argument registers must be preserved
|
|
;; - two values - (# of bytes) and (Unlink) are passed on the stack (from the stubs below)
|
|
;;
|
|
;; BUGBUG: This code doesn't maintain the required 16 byte alignment when running on 64-bit platforms
|
|
;; This is not being fixed at the moment, since we're expecting to deprecate this code.
|
|
|
|
LinkFrame struct
|
|
ifdef ISA_IX64
|
|
_r9 dp ? ; saved in LinkStack prolog
|
|
_r8 dp ? ; saved in LinkStack prolog
|
|
endif
|
|
_dx dp ? ; saved in LinkStack prolog
|
|
_cx dp ? ; saved in LinkStack prolog
|
|
_alloc dp ? ; saved in LinkStack prolog from ax (set by grower prolog)
|
|
_UnlinkFn dp ? ; pushed by LinkStackN stub
|
|
_argStack dp ? ; pushed by LinkStackN stub
|
|
;; Continue address
|
|
_continue dp ? ; pushed by call of LinkStack function
|
|
;; EBP frame
|
|
_oldBp dp ? ; pushed by grower prolog
|
|
;; Return address
|
|
_return dp ? ; pushed by call of grower
|
|
;; Stack Arguments (argStack*sizeof PWORD)
|
|
LinkFrame ends
|
|
|
|
;
|
|
; __checkStackOverflow
|
|
;
|
|
; This function takes the bottom of the current frame in eax. If we
|
|
; detect a stack overflow, we make ourselves a real stack frame (for
|
|
; debugging convenience) and then break.
|
|
;
|
|
; BUGBUG: This code doesn't maintain the required 16 byte alignment when running on 64-bit platforms
|
|
; This is not being fixed at the moment, since we're expecting to deprecate this code.
|
|
|
|
align 16
|
|
__checkStackOverflow proc
|
|
push PDX
|
|
GET_THREAD_RECORD_FIELD PDX, _activeStackLimit
|
|
cmp PAX, PDX
|
|
jb overflowHandler
|
|
pop PDX
|
|
ret
|
|
overflowHandler:
|
|
int 3
|
|
pop PDX
|
|
ret
|
|
__checkStackOverflow endp
|
|
|
|
|
|
?c_LinkStackFunctionsBegin@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0
|
|
|
|
?c_LinkStackBegin@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0
|
|
|
|
;;; NOTE: If you change this function, please ensure that the necessary
|
|
;;; changes are made to CallStack.SkipLinkStackFrame
|
|
|
|
align 16
|
|
|
|
LinkStack proc
|
|
;; Save allocation amount
|
|
push PAX
|
|
|
|
;; Save argument registers
|
|
push PCX
|
|
push PDX
|
|
ifdef ISA_IX64
|
|
push r8
|
|
push r9
|
|
endif
|
|
|
|
;; Call stack allocation ABI - this will update the current thread's stackBegin/Limit
|
|
mov PCX, [PSP].LinkFrame._alloc
|
|
call SYMFIX(?g_AllocateStackSegment@Struct_Microsoft_Singularity_V1_Services_StackService@@SIPAUuintPtr@@PAU2@@Z)
|
|
|
|
;; Switch over to new stack (keep old stack in PDX)
|
|
mov PDX, PSP
|
|
mov PSP, PAX
|
|
|
|
GET_THREAD_CONTEXT_FIELD PAX, _stackLimit
|
|
SET_THREAD_RECORD_FIELD _activeStackLimit, PAX
|
|
|
|
;; Now build up the copied/hijacked stack frame for the grower to continue on
|
|
|
|
;; Copy arguments to new stack
|
|
mov PCX, [PDX].LinkFrame._argStack
|
|
jecxz noargs
|
|
lea PAX, [PDX] + SIZEOF LinkFrame - SIZEOF PWORD
|
|
next:
|
|
push [PAX+PCX*4]
|
|
loop next
|
|
noargs:
|
|
|
|
;; Push hijacked return address. This will make the function return to our UnlinkStack routine
|
|
;; rather than its real return address.
|
|
push [PDX].LinkFrame._UnlinkFn
|
|
|
|
;; Push old ebp frame
|
|
;; Note that the exception unwind code expects this ebp frame to point to grower's original ebp frame.
|
|
push PBP
|
|
|
|
;; Reset ebp to the new stack segment.
|
|
mov PBP, PSP
|
|
|
|
;; Get continue address
|
|
mov PAX, [PDX].LinkFrame._continue
|
|
|
|
;; Restore arg registers
|
|
ifdef ISA_IX64
|
|
mov r8, [PDX].LinkFrame._r8
|
|
mov r9, [PDX].LinkFrame._r9
|
|
endif
|
|
mov PCX, [PDX].LinkFrame._cx
|
|
mov PDX, [PDX].LinkFrame._dx
|
|
|
|
;; Return. This will resume execution in the prolog of the function which called us. However
|
|
;; we are on the new stack segment, and the function has been tail patched so it will call back to
|
|
;; UnlinkStack when it returns.
|
|
jmp PAX
|
|
|
|
LinkStack endp
|
|
|
|
?c_LinkStackLimit@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0
|
|
|
|
?c_UnlinkStackBegin@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0
|
|
|
|
;; UnlinkStack is essentially a continuation of LinkStack; it is called from the hijacked return
|
|
;; pushed by LinkStack. Because of the unlink stub, we are on the old stack; however we have
|
|
;; some cleanup to do.
|
|
;; 1. We must call Free on the stack we allocated before, and restore the thread's stack limits.
|
|
;; 2. We must update the activeStackLimit on the thread.
|
|
|
|
;; The stack state on entry to UnlinkStack is:
|
|
;; 1. We are back on the old stack
|
|
;; 2. Everything from the grower has been popped except its return address & args
|
|
;; 3. We have been called by the UnlinkStackN stub; it will execute the return for us
|
|
|
|
;;; NOTE: If you change this function, please ensure that the necessary
|
|
;;; changes are made to CallStack.SkipUnlinkStackFrame
|
|
|
|
align 16
|
|
|
|
UnlinkStack proc
|
|
|
|
;; Save return value registers
|
|
push PDX
|
|
push PAX
|
|
|
|
;; HACK: set limit to zero to temporarily disable stack growth
|
|
;; We can get rid of this if we set activeStackLimit in asm code:
|
|
;; ThreadContext *context = Isa.GetCurrentThread();
|
|
;; StackHead *head = (StackHead *) (context->stackBegin - sizeof(StackHead));
|
|
;; Isa.StackLimit = head->prevLimit;
|
|
xor PAX, PAX
|
|
SET_THREAD_RECORD_FIELD _activeStackLimit, PAX
|
|
|
|
;; Call Free routine
|
|
call SYMFIX(?g_FreeStackSegment@Struct_Microsoft_Singularity_V1_Services_StackService@@SIXXZ)
|
|
|
|
;; Set active stack limit
|
|
GET_THREAD_CONTEXT_FIELD PAX, _stackLimit
|
|
SET_THREAD_RECORD_FIELD _activeStackLimit, PAX
|
|
|
|
POP PAX
|
|
POP PDX
|
|
|
|
;; Return to UnlinkStackN stub, which will execute ret N
|
|
ret
|
|
|
|
UnlinkStack endp
|
|
|
|
?c_UnlinkStackLimit@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0
|
|
|
|
LINKNAME MACRO SIZE:REQ
|
|
EXITM SYMFIX(?g_LinkStack&SIZE&@Class_Microsoft_Singularity_Memory_Stacks@@SIXXZ)
|
|
ENDM
|
|
|
|
UNLINKNAME MACRO SIZE:REQ
|
|
EXITM SYMFIX(?g_UnlinkStack&SIZE&@Class_Microsoft_Singularity_Memory_Stacks@@SIXXZ)
|
|
ENDM
|
|
|
|
;; Implementations of LinkStackNn: push the appropriate number
|
|
;; and call the general-purpose LinkStack.
|
|
|
|
ifdef ISA_IX64
|
|
|
|
; x64 does not have a PUSH OFFSET64 instruction, so there are two possible solutions.
|
|
;
|
|
; (1) LEA into a register and then push it
|
|
; (2) Use PUSH reg/mem64 (i.e. FF /6)
|
|
;
|
|
; Since stack link routines cannot polute any registers, (1) would require save/restore
|
|
; overhead as well, making (2) the more desirable solution.
|
|
|
|
;;; NOTE: If you change these function, please ensure that the necessary
|
|
;;; changes are made to CallStack.SkipLinkStackStubFrame
|
|
|
|
LINKSTACKSTUB MACRO SLOTS:REQ
|
|
LOCAL unlink, uaddr
|
|
align 16
|
|
LINKNAME(%SLOTS*8):
|
|
push SLOTS
|
|
push [uaddr]
|
|
jmp LinkStack
|
|
uaddr dq unlink
|
|
unlink:
|
|
mov rsp, rbp
|
|
pop rbp
|
|
call UnlinkStack
|
|
ret SLOTS*8
|
|
ENDM
|
|
|
|
elseifdef ISA_IX86
|
|
|
|
LINKSTACKSTUB MACRO SLOTS:REQ
|
|
LOCAL unlink
|
|
align 8
|
|
LINKNAME(%SLOTS*4):
|
|
push SLOTS
|
|
push unlink
|
|
jmp LinkStack
|
|
unlink:
|
|
mov esp, ebp
|
|
pop ebp
|
|
call UnlinkStack
|
|
ret SLOTS*4
|
|
ENDM
|
|
|
|
endif
|
|
|
|
?c_LinkStackStubsBegin@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0
|
|
|
|
LINKSTACKSTUB 0
|
|
LINKSTACKSTUB 1
|
|
LINKSTACKSTUB 2
|
|
LINKSTACKSTUB 3
|
|
LINKSTACKSTUB 4
|
|
LINKSTACKSTUB 5
|
|
LINKSTACKSTUB 6
|
|
LINKSTACKSTUB 7
|
|
LINKSTACKSTUB 8
|
|
LINKSTACKSTUB 9
|
|
LINKSTACKSTUB 10
|
|
LINKSTACKSTUB 11
|
|
LINKSTACKSTUB 12
|
|
LINKSTACKSTUB 13
|
|
LINKSTACKSTUB 14
|
|
LINKSTACKSTUB 15
|
|
LINKSTACKSTUB 16
|
|
LINKSTACKSTUB 17
|
|
LINKSTACKSTUB 18
|
|
LINKSTACKSTUB 19
|
|
LINKSTACKSTUB 20
|
|
LINKSTACKSTUB 21
|
|
LINKSTACKSTUB 22
|
|
LINKSTACKSTUB 23
|
|
LINKSTACKSTUB 24
|
|
LINKSTACKSTUB 25
|
|
LINKSTACKSTUB 26
|
|
LINKSTACKSTUB 27
|
|
LINKSTACKSTUB 28
|
|
LINKSTACKSTUB 29
|
|
LINKSTACKSTUB 30
|
|
LINKSTACKSTUB 31
|
|
LINKSTACKSTUB 32
|
|
|
|
?c_LinkStackStubsLimit@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0
|
|
|
|
;; CheckStackLimit is the routine that Bartok-generated code calls to check for
|
|
;; stack growth
|
|
|
|
align 16
|
|
PUBLIC __checkStackLimit
|
|
__checkStackLimit PROC
|
|
;; Note: expected stack growth limit is in PAX
|
|
|
|
;; Bartok doesn't allow quite enough overhead for the stack switch
|
|
;; now that it occurs in managed code. So, we will pretend bartok
|
|
;; asked for more stack until it can be updated. Note that this
|
|
;; is not quite sufficient; there are cases where bartok will not
|
|
;; even probe which we cannot alter.
|
|
sub eax, 32*sizeof(PWORD)
|
|
|
|
;; Free up a register
|
|
push PDX
|
|
|
|
ifdef PARANOID_CHECKS
|
|
|
|
;; Free up another register
|
|
push PCX
|
|
|
|
;; See if we are on the interrupt stack
|
|
GET_THREAD_RECORD_FIELD PDX, _activeStackLimit
|
|
GET_CPU_RECORD_FIELD PCX, _interruptStackLimit
|
|
cmp PCX, PDX
|
|
jne not_interrupt_stack
|
|
|
|
;; Check for stack underflow
|
|
GET_CPU_RECORD_FIELD PCX, _interruptStackBegin
|
|
cmp PCX, 0 ; kernel startup?
|
|
je stack_ok
|
|
cmp PSP, PCX ; underflow?
|
|
jl stack_ok
|
|
int 3
|
|
|
|
;; (Note: we will check for overflow below in the normal code)
|
|
|
|
not_interrupt_stack:
|
|
GET_THREAD_CONTEXT_FIELD PCX, _stackLimit
|
|
cmp PCX, PDX
|
|
jne not_thread_stack
|
|
|
|
;; Check for stack underflow
|
|
GET_THREAD_CONTEXT_FIELD PCX, _stackBegin
|
|
cmp PCX, 0 ; kernel startup?
|
|
je stack_ok
|
|
cmp PSP, PCX ; underflow?
|
|
jl stack_ok
|
|
int 3
|
|
|
|
not_thread_stack:
|
|
;; Check for kernel startup case
|
|
cmp PCX, 0
|
|
je stack_ok
|
|
;; Check for disabled stack limit checks case
|
|
cmp PDX, 0
|
|
je stack_ok
|
|
;; activeStackLimit appears to be bogus
|
|
int 3
|
|
|
|
stack_ok:
|
|
|
|
pop PCX
|
|
|
|
endif ; PARANOID_CHECKS
|
|
|
|
GET_THREAD_RECORD_FIELD PDX, _activeStackLimit
|
|
cmp PAX, PDX
|
|
jb grow
|
|
pop PDX
|
|
ret
|
|
|
|
grow:
|
|
;; Since this is a slow path, always do some extra checks here.
|
|
|
|
;; Check for stack overflow
|
|
cmp PSP, PDX
|
|
jg no_overflow
|
|
int 3
|
|
|
|
no_overflow:
|
|
;; Now check to see if we are on the interrupt stack; we shouldn't be growing.
|
|
GET_CPU_RECORD_FIELD PAX, _interruptStackLimit
|
|
cmp PDX, PAX
|
|
jne not_interrupt
|
|
int 3
|
|
|
|
not_interrupt:
|
|
pop PDX
|
|
;; this sets the status bit checked by "jb" above; our caller will be checking this
|
|
stc
|
|
ret
|
|
|
|
__checkStackLimit ENDP
|
|
|
|
?c_LinkStackFunctionsLimit@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0
|
|
|
|
end
|