1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
#include "stdafx.h"
#include "Seh64.h"
#include "Seh.h"
#include "Code/X64/Asm.h"
#if defined(WINDOWS) && defined(X64)
#include "Code/FnState.h"
#include "Gc/Gc.h"
#include "Gc/CodeTable.h"
namespace code {
namespace eh {
RUNTIME_FUNCTION *exceptionCallback(void *pc, void *base) {
storm::CodeTable &table = storm::codeTable();
void *found = table.find(pc);
if (!found)
return null;
byte *code = (byte *)found;
size_t size = storm::Gc::codeSize(code);
Nat startOffset = Nat(size_t(code) - size_t(base));
// Note: The format that is manipulated here matches what WindowsOutput creates for us
// in its constructor. What is done here probably makes more sense in light of the code
// there.
// EH offset is stored at the end of the allocation:
Nat ehOffset = *(Nat *)(code + size - sizeof(Nat));
Nat dataStart = ehOffset + Nat(sizeof(RUNTIME_FUNCTION));
// If pc is after where we will place the "EndAddress", then we simply return null. This
// is to not accidentally confuse the unwinding logic if we get an exception during the
// small shim in the end of the allocation.
if (size_t(pc) > size_t(code) + ehOffset)
return null;
// Find and update the RUNTIME_FUNCTION in the function. Note: we need to update the
// offsets inside it, since it might have moved since we used it last.
RUNTIME_FUNCTION *fn = (RUNTIME_FUNCTION *)(code + ehOffset);
fn->BeginAddress = startOffset;
// Note: a bit too late, but it is cumbersome to figure out exactly where.
fn->EndAddress = startOffset + ehOffset;
fn->UnwindData = startOffset + dataStart;
// Find and update the address of the handler function as well:
UnwindInfo *uwInfo = (UnwindInfo *)&code[dataStart];
Nat exCodeCount = roundUp(uwInfo->unwindCount, byte(2));
*(DWORD *)(code + dataStart + sizeof(UnwindInfo) + exCodeCount*2)
= startOffset + Nat(size) - 10;
// For debugging:
// PVAR(base);
// PVAR(found);
// PVAR((void *)(size_t(pc) - size_t(found)));
// PVAR(fn);
// for (Nat i = 0; i < size - ehOffset; i++) {
// if (i % 8 == 0)
// PNN(L"\n" << i << L":");
// PNN(L" " << toHex(((byte *)fn)[i]));
// }
// PLN(L"");
// PLN(L"");
// PVAR((void *)(code + size - 10));
return fn;
}
// From the Windows documentation:
// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170
struct DispatchContext {
size_t pc;
size_t base;
RUNTIME_FUNCTION *fnEntry;
size_t establisherFrame;
size_t targetIp;
CONTEXT *contextRecord;
void *languageHandler;
void *handlerData;
// In later SDKs
void *historyTable;
};
/**
* Description of a single entry in the block table.
*/
struct FnBlock {
// At what offset do we start?
Nat offset;
// What block?
Nat block;
};
SehFrame extractFrame(_EXCEPTION_RECORD *er, void *frame, _CONTEXT *ctx, void *dispatch) {
DispatchContext *dispatchContext = (DispatchContext *)dispatch;
size_t endOfContext = (size_t)dispatchContext->handlerData;
// Now, endOfContext is a pointer to the byte after we stored the pointer to this
// function. Based on that, we can find the start of the GcCode since we know how much
// is stored after that point in the code. See Code/X64/WindowsOutput.cpp for details.
// Note: 6 is the size of the jump operation we use as a shim to call the EH function.
size_t endOfCode = endOfContext + 6 + sizeof(Nat);
endOfCode = roundUp(endOfCode, sizeof(void *));
// Now we can retrieve the GcCode! Due to the object layout, it is right after the code
// portion (when properly aligned):
GcCode *refs = (GcCode *)endOfCode;
SehFrame result;
result.stackPtr = frame;
result.binary = code::codeBinaryImpl(refs);
result.frameOffset = -result.binary->stackOffset();
// We can also find the metadata table at the end of the binary:
// The last Nat is the size of the actual code.
Nat *ehOffset = ((Nat *)endOfCode) - 1;
// The binary also contains the start of the code:
size_t codeStart = size_t(result.binary->address());
// Now, we can compute the start of the EH data and extract the block table:
size_t startOfEhData = codeStart + *ehOffset;
// The block table is just before the end, so we can call the generic code from there:
Nat active = findFunctionStateFromEnd((void *)startOfEhData, dispatchContext->pc - codeStart);
code::decodeFnState(active, result.part, result.activation);
return result;
}
// Note: This is not always present in the Windows headers.
extern "C"
NTSYSAPI VOID RtlUnwindEx(PVOID targetFrame, PVOID targetIp, PEXCEPTION_RECORD er,
PVOID returnValue, PCONTEXT context, void *history);
void resumeFrame(SehFrame &frame, Binary::Resume &resume, storm::RootObject *object,
_CONTEXT *ctx, _EXCEPTION_RECORD *er, void *dispatch) {
DispatchContext *dispatchContext = (DispatchContext *)dispatch;
er->ExceptionFlags |= EXCEPTION_UNWINDING;
// Store the target block in the exception parameters!
// Most likely, we can entirely trash the exception parameters at this stage, since
// other code should not care during the unwind step. However, to be a bit on the safe
// side, we just add it as the last parameter to the exception record.
er->ExceptionInformation[er->NumberParameters++] = resume.cleanUntil;
RtlUnwindEx(frame.stackPtr, resume.ip, er, object, ctx, dispatchContext->historyTable);
// Expected to not return...
dbg_assert(false, L"Failed to unwind the stack!");
}
void cleanupPartialFrame(SehFrame &frame, _EXCEPTION_RECORD *er) {
size_t cleanupTo = er->ExceptionInformation[er->NumberParameters - 1];
cleanupPartialFrame(frame, Nat(cleanupTo));
}
}
}
#endif
namespace code {
namespace eh {
using namespace code::x64;
static const Reg order[] = {
rax, rcx, rdx, rbx, ptrStack, ptrFrame, rsi, rdi,
r8, r9, r10, r11, r12, r13, r14, r15
};
Nat win64Register(Reg reg) {
for (Nat i = 0; i < ARRAY_COUNT(order); i++) {
if (same(reg, order[i]))
return i;
}
assert(false, L"Register not supported!");
return 0;
}
Reg fromWin64Register(Nat id) {
if (id < ARRAY_COUNT(order)) {
return asSize(order[id], Size::sPtr);
} else {
return noReg;
}
}
}
}
|