259 lines
8.5 KiB
C
259 lines
8.5 KiB
C
// Native user-mode exception runtime. This file owns process-local native handler dispatch.
|
|
|
|
#include <ntlite/atomic.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <intrin.h>
|
|
#include <ntlite/exception.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
#define NTL_EXCEPTION_MAX_HANDLERS 16u
|
|
|
|
typedef struct NTL_EXCEPTION_HANDLER_ENTRY {
|
|
bool Active;
|
|
uint32_t Previous;
|
|
uint32_t Next;
|
|
NTL_EXCEPTION_HANDLER Handler;
|
|
void* Context;
|
|
} NTL_EXCEPTION_HANDLER_ENTRY;
|
|
|
|
typedef struct NTL_EXCEPTION_HANDLER_SNAPSHOT {
|
|
NTL_EXCEPTION_HANDLER Handler;
|
|
void* Context;
|
|
} NTL_EXCEPTION_HANDLER_SNAPSHOT;
|
|
|
|
static volatile int32_t gNtlExceptionLock;
|
|
static uint32_t gNtlExceptionListHead = UINT32_MAX;
|
|
static uint32_t gNtlExceptionListTail = UINT32_MAX;
|
|
static NTL_EXCEPTION_HANDLER_ENTRY gNtlExceptionHandlers[NTL_EXCEPTION_MAX_HANDLERS];
|
|
|
|
// Acquires the process-local exception handler lock.
|
|
static void NtlpExceptionLock(void) {
|
|
while (NtlAtomicCompareExchange32(&gNtlExceptionLock, 1, 0) != 0)
|
|
_mm_pause();
|
|
_ReadWriteBarrier();
|
|
}
|
|
|
|
// Releases the process-local exception handler lock.
|
|
static void NtlpExceptionUnlock(void) {
|
|
_ReadWriteBarrier();
|
|
NtlAtomicExchange32(&gNtlExceptionLock, 0);
|
|
}
|
|
|
|
// Returns a generic status after a failed exception-continue syscall.
|
|
static KSTATUS NtlpStatusOrFailure(KSTATUS status) {
|
|
return status == NTLSTATUS_SUCCESS ? NTLSTATUS_FAILURE : status;
|
|
}
|
|
|
|
// Asks the kernel to terminate the active exception dispatch.
|
|
static void NtlpTerminateExceptionDispatch(void) {
|
|
SYS_CONTINUE_EXCEPTION_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Disposition = SYS_EXCEPTION_DISPOSITION_TERMINATE_PROCESS;
|
|
|
|
KSTATUS status = SysContinueException(&request);
|
|
while (true) SysExitProcess((int32_t)NtlpStatusOrFailure(status));
|
|
}
|
|
|
|
// Asks the kernel to continue searching from the top-level native dispatcher.
|
|
static void NtlpContinueExceptionSearch(void) {
|
|
SYS_CONTINUE_EXCEPTION_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Disposition = SYS_EXCEPTION_DISPOSITION_CONTINUE_SEARCH;
|
|
|
|
KSTATUS status = SysContinueException(&request);
|
|
while (true) SysExitProcess((int32_t)NtlpStatusOrFailure(status));
|
|
}
|
|
|
|
// Asks the kernel to resume execution with a handler-edited user context.
|
|
static void NtlpContinueExecution(SYS_USER_CONTEXT* context) {
|
|
SYS_CONTINUE_EXCEPTION_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Disposition = SYS_EXCEPTION_DISPOSITION_CONTINUE_EXECUTION;
|
|
request.Context = context;
|
|
|
|
KSTATUS status = SysContinueException(&request);
|
|
while (true) SysExitProcess((int32_t)NtlpStatusOrFailure(status));
|
|
}
|
|
|
|
// Returns whether one kernel-provided exception frame has the expected ABI shape.
|
|
static bool NtlpFrameIsValid(SYS_EXCEPTION_FRAME* frame) {
|
|
if (!frame)
|
|
return false;
|
|
if (frame->Size != sizeof(*frame) || frame->Version != SYS_EXCEPTION_ABI_VERSION)
|
|
return false;
|
|
if (frame->ExceptionRecord.Size != sizeof(frame->ExceptionRecord) ||
|
|
frame->ExceptionRecord.Version != SYS_EXCEPTION_ABI_VERSION)
|
|
return false;
|
|
if (frame->ContextRecord.Size != sizeof(frame->ContextRecord) ||
|
|
frame->ContextRecord.Version != SYS_EXCEPTION_ABI_VERSION)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Copies the current handler list so callbacks can run without holding the list lock.
|
|
static size_t NtlpSnapshotExceptionHandlers(NTL_EXCEPTION_HANDLER_SNAPSHOT* snapshot, size_t capacity) {
|
|
size_t count = 0;
|
|
|
|
NtlpExceptionLock();
|
|
for (uint32_t index = gNtlExceptionListHead;
|
|
index != UINT32_MAX && count < capacity;
|
|
index = gNtlExceptionHandlers[index].Next) {
|
|
NTL_EXCEPTION_HANDLER_ENTRY* entry = &gNtlExceptionHandlers[index];
|
|
if (!entry->Active || !entry->Handler)
|
|
continue;
|
|
snapshot[count].Handler = entry->Handler;
|
|
snapshot[count].Context = entry->Context;
|
|
count++;
|
|
}
|
|
NtlpExceptionUnlock();
|
|
|
|
return count;
|
|
}
|
|
|
|
// Dispatches one kernel-delivered exception through the native handler list.
|
|
static void NtlpUserExceptionDispatcher(SYS_EXCEPTION_FRAME* frame) {
|
|
if (!NtlpFrameIsValid(frame))
|
|
NtlpTerminateExceptionDispatch();
|
|
|
|
NTL_EXCEPTION_HANDLER_SNAPSHOT snapshot[NTL_EXCEPTION_MAX_HANDLERS];
|
|
size_t count = NtlpSnapshotExceptionHandlers(snapshot, NTL_EXCEPTION_MAX_HANDLERS);
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
int disposition = snapshot[i].Handler(&frame->ExceptionRecord,
|
|
&frame->ContextRecord,
|
|
snapshot[i].Context);
|
|
if (disposition == NTL_EXCEPTION_CONTINUE_SEARCH)
|
|
continue;
|
|
if (disposition == NTL_EXCEPTION_CONTINUE_EXECUTION)
|
|
NtlpContinueExecution(&frame->ContextRecord);
|
|
if (disposition == NTL_EXCEPTION_TERMINATE_PROCESS)
|
|
NtlpTerminateExceptionDispatch();
|
|
NtlpTerminateExceptionDispatch();
|
|
}
|
|
|
|
NtlpContinueExceptionSearch();
|
|
}
|
|
|
|
// Registers the native dispatcher for the current process when no other dispatcher owns the process.
|
|
NTL_EXCEPTION_API KSTATUS NtlInitializeExceptionRuntime(void) {
|
|
NtlpExceptionLock();
|
|
|
|
SYS_EXCEPTION_DISPATCHER_REQUEST current;
|
|
memset(¤t, 0, sizeof(current));
|
|
current.Size = sizeof(current);
|
|
KSTATUS status = SysGetExceptionDispatcher(¤t);
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
NtlpExceptionUnlock();
|
|
return status;
|
|
}
|
|
|
|
if (current.DispatcherRoutine == NtlpUserExceptionDispatcher) {
|
|
NtlpExceptionUnlock();
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
if (current.DispatcherRoutine) {
|
|
NtlpExceptionUnlock();
|
|
return NTLSTATUS_ALREADY_EXISTS;
|
|
}
|
|
|
|
SYS_EXCEPTION_DISPATCHER_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.DispatcherRoutine = NtlpUserExceptionDispatcher;
|
|
|
|
status = SysSetExceptionDispatcher(&request);
|
|
NtlpExceptionUnlock();
|
|
return status;
|
|
}
|
|
|
|
// Adds one process-local native exception handler.
|
|
NTL_EXCEPTION_API void* NtlAddExceptionHandler(bool first, NTL_EXCEPTION_HANDLER handler, void* context) {
|
|
if (!handler)
|
|
return 0;
|
|
if (NtlInitializeExceptionRuntime() != NTLSTATUS_SUCCESS)
|
|
return 0;
|
|
|
|
NtlpExceptionLock();
|
|
|
|
uint32_t slot = UINT32_MAX;
|
|
for (uint32_t i = 0; i < NTL_EXCEPTION_MAX_HANDLERS; i++) {
|
|
if (!gNtlExceptionHandlers[i].Active) {
|
|
slot = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (slot == UINT32_MAX) {
|
|
NtlpExceptionUnlock();
|
|
return 0;
|
|
}
|
|
|
|
NTL_EXCEPTION_HANDLER_ENTRY* entry = &gNtlExceptionHandlers[slot];
|
|
entry->Active = 1;
|
|
entry->Handler = handler;
|
|
entry->Context = context;
|
|
entry->Previous = UINT32_MAX;
|
|
entry->Next = UINT32_MAX;
|
|
|
|
if (gNtlExceptionListHead == UINT32_MAX) {
|
|
gNtlExceptionListHead = slot;
|
|
gNtlExceptionListTail = slot;
|
|
} else if (first) {
|
|
entry->Next = gNtlExceptionListHead;
|
|
gNtlExceptionHandlers[gNtlExceptionListHead].Previous = slot;
|
|
gNtlExceptionListHead = slot;
|
|
} else {
|
|
entry->Previous = gNtlExceptionListTail;
|
|
gNtlExceptionHandlers[gNtlExceptionListTail].Next = slot;
|
|
gNtlExceptionListTail = slot;
|
|
}
|
|
|
|
NtlpExceptionUnlock();
|
|
return entry;
|
|
}
|
|
|
|
// Removes one process-local native exception handler.
|
|
NTL_EXCEPTION_API KSTATUS NtlRemoveExceptionHandler(void* handle) {
|
|
if (!handle)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
NtlpExceptionLock();
|
|
|
|
uint32_t slot = UINT32_MAX;
|
|
for (uint32_t i = 0; i < NTL_EXCEPTION_MAX_HANDLERS; i++) {
|
|
if (handle == &gNtlExceptionHandlers[i]) {
|
|
slot = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (slot == UINT32_MAX || !gNtlExceptionHandlers[slot].Active) {
|
|
NtlpExceptionUnlock();
|
|
return NTLSTATUS_NOT_FOUND;
|
|
}
|
|
|
|
NTL_EXCEPTION_HANDLER_ENTRY* entry = &gNtlExceptionHandlers[slot];
|
|
if (entry->Previous != UINT32_MAX)
|
|
gNtlExceptionHandlers[entry->Previous].Next = entry->Next;
|
|
else
|
|
gNtlExceptionListHead = entry->Next;
|
|
|
|
if (entry->Next != UINT32_MAX)
|
|
gNtlExceptionHandlers[entry->Next].Previous = entry->Previous;
|
|
else
|
|
gNtlExceptionListTail = entry->Previous;
|
|
|
|
memset(entry, 0, sizeof(*entry));
|
|
entry->Previous = UINT32_MAX;
|
|
entry->Next = UINT32_MAX;
|
|
|
|
NtlpExceptionUnlock();
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|