26. Mutator context

26.1. Introduction

.intro: This is the design of the mutator context module.

.readership: Any MPS developer; anyone porting the MPS to a new platform.

.overview: The mutator context module decodes the context of a mutator thread at the point when it caused a protection fault, so that access to a protected region of memory can be handled, or when it was suspended by the thread manager, so that its registers and control stack can be scanned.

.def.context: The context of a thread (also called its continuation) is an abstract representation of the control state of the thread at a point in time, including enough information to continue the thread from that point.

.status: The mutator context module does not currently present a clean interface to the rest of the MPS: source files are inconsistently named, and the implementation is (necessarily) mixed up with the implementation of the memory protection module (design.mps.prot) and the thread manager (design.mps.thread-manager).

26.2. Requirements

.req.fault.addr: Must determine the address that the mutator was trying to access when it caused a protection fault. (Without this address the MPS can’t handle the fault. See ArenaAccess().)

.req.fault.access: Should determine whether the mutator was trying to read or write the address when it caused a protection fault. (This enables a performance improvement in the case of a write fault. A read fault must be handled by ensuring the address pointed to has been fixed, which may require scanning the segment, whereas a write fault merely requires that the segment’s summary be discarded. See TraceSegAccess().)

.req.fault.step: Should be able to emulate the access that caused the fault. (This enables a significant performance improvement for weak hash tables. See request.dylan.160044.)

.req.suspend.scan: Must capture enough information to ambiguously scan all roots in the context of a thread that has been suspended by the thread manager. (This is necessary for conservative garbage collection to work. See design.mps.thread-manager.if.scan.)

26.3. Interface

typedef unsigned MutatorContextVar

.if.var: The type MutatorContextVar is the type of the discriminator for the union within MutatorContextStruct:

Value

Description

MutatorContextFAULT

Context of thread stopped by a protection fault.

MutatorContextTHREAD

Context of thread stopped by the thread manager.

typedef MutatorContextStruct *MutatorContext

.if.context: A structure representing the context of the mutator at the point when a protection fault occurred, or when it was suspended by the thread manager. This structure should be declared in a header so that it can be inlined in the Thread structure if necessary. See design.mps.thread-manager.if.thread.

Bool MutatorContextCheck(MutatorContext context)

.if.check: The check function for mutator contexts. See design.mps.check.

Res MutatorContextInitFault(MutatorContext context, ...)

.if.init.fault: Initialize with the context of the mutator at the point where it was stopped by a protection fault. The arguments are platform-specific and the return may be void instead of Res if this always succeeds.

Res MutatorContextInitThread(MutatorContext context, ...)

.if.init.thread: Initialize with the context of the mutator at the point where it was suspended by the thread manager. The arguments are platform-specific and the return may be void instead of Res if this always succeeds.

Bool MutatorContextCanStepInstruction(MutatorContext context)

.if.canstep: Examine the context to determine whether the protection module can single-step the instruction which is causing the fault. Return TRUE if MutatorContextStepInstruction() is capable of single-stepping the instruction, or FALSE if not.

Res MutatorContextStepInstruction(MutatorContext context)

.if.step: Single-step the instruction which is causing the fault. Update the mutator context according to the emulation or execution of the instruction, so that resuming the mutator will not cause the instruction which was caused the fault to be re-executed. Return ResOK if the instruction was single-stepped successfully, or ResUNIMPL if the instruction cannot be single-stepped.

This function is only called if MutatorContextCanStepInstruction(context) returned TRUE.

Res MutatorContextScan(ScanState ss, MutatorContext context, mps_area_scan_t scan, void *closure)

.if.context.scan: Scan all roots found in context using the given scan state by calling scan, and return the result code from the scanner.

Addr MutatorContextSP(MutatorContext context)

.if.context.sp: Return the pointer to the “top” of the thread’s stack at the point given by context. In the common case, where the stack grows downwards, this is actually the lowest stack address.

26.4. Implementations

26.4.1. Generic implementation

.impl.an: In prmcan.c and prmcanan.c.

.impl.an.context: There is no definition of MutatorContextStruct and so the mutator context cannot be decoded.

.impl.an.fault: Compatible only with the generic memory protection module (design.mps.prot.impl.an) where there are no protection faults.

.impl.an.suspend: Compatible only with the generic thread manager module (design.mps.thread-manager.impl.an) where there is only one thread, and so no threads are suspended.

26.4.2. Posix implementation

.impl.ix: In prmcix.c and protsgix.c, with processor-specific parts in prmci3.c and prmci6.c, and other platform-specific parts in prmcfri3.c, prmcfri6.c, prmclia6.c, prmclii3.c, and prmclii6.c.

.impl.ix.context: The context consists of the siginfo_t and ucontext_t structures. POSIX specifies some of the fields in siginfo_t, but says nothing about the contents of ucontext_t. This is decoded on a platform-by-platform basis.

.impl.ix.fault.signal: POSIX specifies that “Invalid permissions for mapped object” (a protection fault) causes a SEGV signal.

.impl.ix.fault.code: POSIX specifies that “Invalid permissions for mapped object” (a protection fault) causes siginfo_t.si_code to be set to SEGV_ACCERR.

.impl.ix.fault.addr: POSIX specifies that siginfo_t.si_addr is the address that the faulting instruction was attempting to access.

.impl.ix.fault.mode: This implementation does not attempt to determine whether the fault was a read or write.

.impl.ix.fault.step: This is implemented only on IA-32, and only for “simple MOV” instructions.

.impl.ix.suspend: PThreadextSuspend() records the context of each suspended thread, and ThreadRingSuspend() stores this in the Thread structure.

.impl.ix.context.scan: The context’s root registers are found in the ucontext_t.uc_mcontext structure.

.impl.ix.context.sp: The stack pointer is obtained from ucontext_t.uc_mcontext.mc_esp (FreeBSD on IA-32), uc_mcontext.gregs[REG_ESP] (Linux on IA-32), ucontext_t.uc_mcontext.mc_rsp (FreeBSD on x86-64), or uc_mcontext.gregs[REG_RSP] (Linux on x86-64).

26.4.3. Windows implementation

.impl.w3: In prmcw3.c, with processor-specific parts in prmci3.c, prmci6.c, and other platform-specific parts in prmcw3i3.c and prmcw3i6.c.

.impl.w3.context: The context of a thread that hit a protection fault is given by the EXCEPTION_POINTERS structure passed to a vectored exception handler, which points to EXCEPTION_RECORD and CONTEXT structures.

.impl.w3.fault.addr: EXCEPTION_RECORD.ExceptionAddress is the address that the faulting instruction was trying to access.

.impl.w3.fault.mode: EXCEPTION_RECORD.ExceptionInformation[0] is 0 for a read fault, 1 for a write fault, and 8 for an execute fault (which we handle as a read fault).

.impl.w3.fault.step: This is implemented only on IA-32, and only for “simple MOV” instructions.

.impl.w3.suspend: The context of a suspended thread is returned by GetThreadContext().

.impl.w3.context.scan: The context’s root registers are found in the CONTEXT structure.

.impl.w3.context.sp: The stack pointer is obtained from CONTEXT.Esp (on IA-32) or CONTEXT.Rsp (on x86-64).

26.4.4. macOS implementation

.impl.xc: In prmcix.c and prmcxc.c, with processor-specific parts in prmci3.c and prmci6.c, and other platform-specific parts in prmcxca6.c, prmcxci3.c and prmcxci6.c.

.impl.xc.context: The context consists of the __Request__mach_exception_raise_state_identity_t and arm_thread_state_t, x86_thread_state32_t or x86_thread_state64_t structures. There doesn’t seem to be any documentation for these structures, but they are defined in the Mach headers.

.impl.xc.fault.addr: __Request__mach_exception_raise_state_identity_t.code[1] is the address that the faulting instruction was trying to access.

.impl.xc.fault.mode: This implementation does not attempt to determine whether the fault was a read or write.

.impl.xc.fault.step: This is implemented only on IA-32, and only for “simple MOV” instructions.

.impl.xc.suspend: The context of a suspended thread is obtained by calling thread_get_state().

.impl.xc.context.scan: The thread’s registers are found in the arm_thread_state64_t, x86_thread_state32_t or x86_thread_state64_t structure.

.impl.xc.context.sp: The stack pointer is obtained using the arm_thread_state64_get_sp() macro (on ARM64), or from x86_thread_state32_t.__esp (on IA-32) or x86_thread_state64_t.__rsp (on x86-64).