|
28 | 28 | #include "gcconfig.h"
|
29 | 29 |
|
30 | 30 | #include "thread.h"
|
| 31 | +#include "threadstore.h" |
31 | 32 |
|
32 | 33 | #define REDHAWK_PALEXPORT extern "C"
|
33 | 34 | #define REDHAWK_PALAPI __stdcall
|
@@ -322,10 +323,120 @@ REDHAWK_PALEXPORT HANDLE REDHAWK_PALAPI PalCreateEventW(_In_opt_ LPSECURITY_ATTR
|
322 | 323 | return CreateEventW(pEventAttributes, manualReset, initialState, pName);
|
323 | 324 | }
|
324 | 325 |
|
| 326 | +#ifdef TARGET_X86 |
| 327 | + |
| 328 | +#define EXCEPTION_HIJACK 0xe0434f4e // 0xe0000000 | 'COM'+1 |
| 329 | + |
| 330 | +PEXCEPTION_REGISTRATION_RECORD GetCurrentSEHRecord() |
| 331 | +{ |
| 332 | + return (PEXCEPTION_REGISTRATION_RECORD)__readfsdword(0); |
| 333 | +} |
| 334 | + |
| 335 | +VOID SetCurrentSEHRecord(EXCEPTION_REGISTRATION_RECORD *pSEH) |
| 336 | +{ |
| 337 | + __writefsdword(0, (DWORD)pSEH); |
| 338 | +} |
| 339 | + |
| 340 | +VOID PopSEHRecords(LPVOID pTargetSP) |
| 341 | +{ |
| 342 | + PEXCEPTION_REGISTRATION_RECORD currentContext = GetCurrentSEHRecord(); |
| 343 | + // The last record in the chain is EXCEPTION_CHAIN_END which is defined as maxiumum |
| 344 | + // pointer value so it cannot satisfy the loop condition. |
| 345 | + while (currentContext < pTargetSP) |
| 346 | + { |
| 347 | + currentContext = currentContext->Next; |
| 348 | + } |
| 349 | + SetCurrentSEHRecord(currentContext); |
| 350 | +} |
| 351 | + |
| 352 | +// This will check who caused the exception. If it was caused by the redirect function, |
| 353 | +// the reason is to resume the thread back at the point it was redirected in the first |
| 354 | +// place. If the exception was not caused by the function, then it was caused by the call |
| 355 | +// out to the I[GC|Debugger]ThreadControl client and we need to determine if it's an |
| 356 | +// exception that we can just eat and let the runtime resume the thread, or if it's an |
| 357 | +// uncatchable exception that we need to pass on to the runtime. |
| 358 | +int RtlRestoreContextFallbackExceptionFilter(PEXCEPTION_POINTERS pExcepPtrs, CONTEXT *pCtx, Thread *pThread) |
| 359 | +{ |
| 360 | + if (pExcepPtrs->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) |
| 361 | + { |
| 362 | + return EXCEPTION_CONTINUE_SEARCH; |
| 363 | + } |
| 364 | + |
| 365 | + // Get the thread handle |
| 366 | + _ASSERTE(pExcepPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_HIJACK); |
| 367 | + |
| 368 | + // Copy everything in the saved context record into the EH context. |
| 369 | + // Historically the EH context has enough space for every enabled context feature. |
| 370 | + // That may not hold for the future features beyond AVX, but this codepath is |
| 371 | + // supposed to be used only on OSes that do not have RtlRestoreContext. |
| 372 | + CONTEXT* pTarget = pExcepPtrs->ContextRecord; |
| 373 | + if (!CopyContext(pTarget, pCtx->ContextFlags, pCtx)) |
| 374 | + { |
| 375 | + PalPrintFatalError("Could not set context record.\n"); |
| 376 | + RhFailFast(); |
| 377 | + } |
| 378 | + |
| 379 | + DWORD espValue = pCtx->Esp; |
| 380 | + |
| 381 | + // NOTE: Ugly, ugly workaround. |
| 382 | + // We need to resume the thread into the managed code where it was redirected, |
| 383 | + // and the corresponding ESP is below the current one. But C++ expects that |
| 384 | + // on an EXCEPTION_CONTINUE_EXECUTION that the ESP will be above where it has |
| 385 | + // installed the SEH handler. To solve this, we need to remove all handlers |
| 386 | + // that reside above the resumed ESP, but we must leave the OS-installed |
| 387 | + // handler at the top, so we grab the top SEH handler, call |
| 388 | + // PopSEHRecords which will remove all SEH handlers above the target ESP and |
| 389 | + // then link the OS handler back in with SetCurrentSEHRecord. |
| 390 | + |
| 391 | + // Get the special OS handler and save it until PopSEHRecords is done |
| 392 | + EXCEPTION_REGISTRATION_RECORD *pCurSEH = GetCurrentSEHRecord(); |
| 393 | + |
| 394 | + // Unlink all records above the target resume ESP |
| 395 | + PopSEHRecords((LPVOID)(size_t)espValue); |
| 396 | + |
| 397 | + // Link the special OS handler back in to the top |
| 398 | + pCurSEH->Next = GetCurrentSEHRecord(); |
| 399 | + |
| 400 | + // Register the special OS handler as the top handler with the OS |
| 401 | + SetCurrentSEHRecord(pCurSEH); |
| 402 | + |
| 403 | + // Resume execution at point where thread was originally redirected |
| 404 | + return EXCEPTION_CONTINUE_EXECUTION; |
| 405 | +} |
| 406 | + |
| 407 | +EXTERN_C VOID __cdecl RtlRestoreContextFallback(PCONTEXT ContextRecord, struct _EXCEPTION_RECORD* ExceptionRecord) |
| 408 | +{ |
| 409 | + Thread *pThread = ThreadStore::GetCurrentThread(); |
| 410 | + |
| 411 | + // A counter to avoid a nasty case where an |
| 412 | + // up-stack filter throws another exception |
| 413 | + // causing our filter to be run again for |
| 414 | + // some unrelated exception. |
| 415 | + int filter_count = 0; |
| 416 | + |
| 417 | + __try |
| 418 | + { |
| 419 | + // Save the instruction pointer where we redirected last. This does not race with the check |
| 420 | + // against this variable because the GC will not attempt to redirect the thread until the |
| 421 | + // instruction pointer of this thread is back in managed code. |
| 422 | + pThread->SetPendingRedirect(ContextRecord->Eip); |
| 423 | + RaiseException(EXCEPTION_HIJACK, 0, 0, NULL); |
| 424 | + } |
| 425 | + __except (++filter_count == 1 |
| 426 | + ? RtlRestoreContextFallbackExceptionFilter(GetExceptionInformation(), ContextRecord, pThread) |
| 427 | + : EXCEPTION_CONTINUE_SEARCH) |
| 428 | + { |
| 429 | + _ASSERTE(!"Reached body of __except in RtlRestoreContextFallback"); |
| 430 | + } |
| 431 | +} |
| 432 | + |
| 433 | +#endif // TARGET_X86 |
| 434 | + |
325 | 435 | typedef BOOL(WINAPI* PINITIALIZECONTEXT2)(PVOID Buffer, DWORD ContextFlags, PCONTEXT* Context, PDWORD ContextLength, ULONG64 XStateCompactionMask);
|
326 | 436 | PINITIALIZECONTEXT2 pfnInitializeContext2 = NULL;
|
327 | 437 |
|
328 | 438 | #ifdef TARGET_X86
|
| 439 | +EXTERN_C VOID __cdecl RtlRestoreContextFallback(PCONTEXT ContextRecord, struct _EXCEPTION_RECORD* ExceptionRecord); |
329 | 440 | typedef VOID(__cdecl* PRTLRESTORECONTEXT)(PCONTEXT ContextRecord, struct _EXCEPTION_RECORD* ExceptionRecord);
|
330 | 441 | PRTLRESTORECONTEXT pfnRtlRestoreContext = NULL;
|
331 | 442 |
|
@@ -356,6 +467,11 @@ REDHAWK_PALEXPORT CONTEXT* PalAllocateCompleteOSContext(_Out_ uint8_t** contextB
|
356 | 467 | {
|
357 | 468 | HMODULE hm = GetModuleHandleW(_T("ntdll.dll"));
|
358 | 469 | pfnRtlRestoreContext = (PRTLRESTORECONTEXT)GetProcAddress(hm, "RtlRestoreContext");
|
| 470 | + if (pfnRtlRestoreContext == NULL) |
| 471 | + { |
| 472 | + // Fallback to the internal implementation if OS doesn't provide one. |
| 473 | + pfnRtlRestoreContext = RtlRestoreContextFallback; |
| 474 | + } |
359 | 475 | }
|
360 | 476 | #endif //TARGET_X86
|
361 | 477 |
|
@@ -438,7 +554,12 @@ REDHAWK_PALEXPORT _Success_(return) bool REDHAWK_PALAPI PalSetThreadContext(HAND
|
438 | 554 | REDHAWK_PALEXPORT void REDHAWK_PALAPI PalRestoreContext(CONTEXT * pCtx)
|
439 | 555 | {
|
440 | 556 | __asan_handle_no_return();
|
| 557 | +#ifdef TARGET_X86 |
| 558 | + _ASSERTE(pfnRtlRestoreContext != NULL); |
| 559 | + pfnRtlRestoreContext(pCtx, NULL); |
| 560 | +#else |
441 | 561 | RtlRestoreContext(pCtx, NULL);
|
| 562 | +#endif //TARGET_X86 |
442 | 563 | }
|
443 | 564 |
|
444 | 565 | REDHAWK_PALIMPORT void REDHAWK_PALAPI PopulateControlSegmentRegisters(CONTEXT* pContext)
|
@@ -568,16 +689,41 @@ REDHAWK_PALEXPORT void REDHAWK_PALAPI PalHijack(HANDLE hThread, _In_opt_ void* p
|
568 | 689 |
|
569 | 690 | if (GetThreadContext(hThread, &win32ctx))
|
570 | 691 | {
|
| 692 | + bool isSafeToRedirect = true; |
| 693 | + |
| 694 | +#ifdef TARGET_X86 |
| 695 | + // Workaround around WOW64 problems. Only do this workaround if a) this is x86, and b) the OS does |
| 696 | + // not support trap frame reporting. |
| 697 | + if ((win32ctx.ContextFlags & CONTEXT_EXCEPTION_REPORTING) == 0) |
| 698 | + { |
| 699 | + // This code fixes a race between GetThreadContext and NtContinue. If we redirect managed code |
| 700 | + // at the same place twice in a row, we run the risk of reading a bogus CONTEXT when we redirect |
| 701 | + // the second time. This leads to access violations on x86 machines. To fix the problem, we |
| 702 | + // never redirect at the same instruction pointer that we redirected at on the previous GC. |
| 703 | + if (((Thread*)pThreadToHijack)->CheckPendingRedirect(win32ctx.Eip)) |
| 704 | + { |
| 705 | + isSafeToRedirect = false; |
| 706 | + } |
| 707 | + } |
| 708 | +#else |
| 709 | + // In some cases Windows will not set the CONTEXT_EXCEPTION_REPORTING flag if the thread is executing |
| 710 | + // in kernel mode (i.e. in the middle of a syscall or exception handling). Therefore, we should treat |
| 711 | + // the absence of the CONTEXT_EXCEPTION_REPORTING flag as an indication that it is not safe to |
| 712 | + // manipulate with the current state of the thread context. |
| 713 | + isSafeToRedirect = (win32ctx.ContextFlags & CONTEXT_EXCEPTION_REPORTING) != 0; |
| 714 | +#endif |
| 715 | + |
571 | 716 | // The CONTEXT_SERVICE_ACTIVE and CONTEXT_EXCEPTION_ACTIVE output flags indicate we suspended the thread
|
572 | 717 | // at a point where the kernel cannot guarantee a completely accurate context. We'll fail the request in
|
573 | 718 | // this case (which should force our caller to resume the thread and try again -- since this is a fairly
|
574 | 719 | // narrow window we're highly likely to succeed next time).
|
575 |
| - // Note: in some cases (x86 WOW64, ARM32 on ARM64) the OS will not set the CONTEXT_EXCEPTION_REPORTING flag |
576 |
| - // if the thread is executing in kernel mode (i.e. in the middle of a syscall or exception handling). |
577 |
| - // Therefore, we should treat the absence of the CONTEXT_EXCEPTION_REPORTING flag as an indication that |
578 |
| - // it is not safe to manipulate with the current state of the thread context. |
579 | 720 | if ((win32ctx.ContextFlags & CONTEXT_EXCEPTION_REPORTING) != 0 &&
|
580 |
| - ((win32ctx.ContextFlags & (CONTEXT_SERVICE_ACTIVE | CONTEXT_EXCEPTION_ACTIVE)) == 0)) |
| 721 | + ((win32ctx.ContextFlags & (CONTEXT_SERVICE_ACTIVE | CONTEXT_EXCEPTION_ACTIVE)) != 0)) |
| 722 | + { |
| 723 | + isSafeToRedirect = false; |
| 724 | + } |
| 725 | + |
| 726 | + if (isSafeToRedirect) |
581 | 727 | {
|
582 | 728 | g_pHijackCallback(&win32ctx, pThreadToHijack);
|
583 | 729 | }
|
|
0 commit comments