1 /* 2 * COPYRIGHT: See COPYING in the top level directory 3 * PROJECT: ReactOS system libraries 4 * PURPOSE: Unwinding related functions 5 * PROGRAMMER: Timo Kreuzer (timo.kreuzer@reactos.org) 6 */ 7 8 /* INCLUDES *****************************************************************/ 9 10 #include <rtl.h> 11 12 #define NDEBUG 13 #include <debug.h> 14 15 #define UNWIND_HISTORY_TABLE_NONE 0 16 #define UNWIND_HISTORY_TABLE_GLOBAL 1 17 #define UNWIND_HISTORY_TABLE_LOCAL 2 18 19 #define UWOP_PUSH_NONVOL 0 20 #define UWOP_ALLOC_LARGE 1 21 #define UWOP_ALLOC_SMALL 2 22 #define UWOP_SET_FPREG 3 23 #define UWOP_SAVE_NONVOL 4 24 #define UWOP_SAVE_NONVOL_FAR 5 25 #if 0 // These are deprecated / not for x64 26 #define UWOP_SAVE_XMM 6 27 #define UWOP_SAVE_XMM_FAR 7 28 #else 29 #define UWOP_EPILOG 6 30 #define UWOP_SPARE_CODE 7 31 #endif 32 #define UWOP_SAVE_XMM128 8 33 #define UWOP_SAVE_XMM128_FAR 9 34 #define UWOP_PUSH_MACHFRAME 10 35 36 37 typedef unsigned char UBYTE; 38 39 typedef union _UNWIND_CODE 40 { 41 struct 42 { 43 UBYTE CodeOffset; 44 UBYTE UnwindOp:4; 45 UBYTE OpInfo:4; 46 }; 47 USHORT FrameOffset; 48 } UNWIND_CODE, *PUNWIND_CODE; 49 50 typedef struct _UNWIND_INFO 51 { 52 UBYTE Version:3; 53 UBYTE Flags:5; 54 UBYTE SizeOfProlog; 55 UBYTE CountOfCodes; 56 UBYTE FrameRegister:4; 57 UBYTE FrameOffset:4; 58 UNWIND_CODE UnwindCode[1]; 59 /* union { 60 OPTIONAL ULONG ExceptionHandler; 61 OPTIONAL ULONG FunctionEntry; 62 }; 63 OPTIONAL ULONG ExceptionData[]; 64 */ 65 } UNWIND_INFO, *PUNWIND_INFO; 66 67 /* FUNCTIONS *****************************************************************/ 68 69 /*! RtlLookupFunctionTable 70 * \brief Locates the table of RUNTIME_FUNCTION entries for a code address. 71 * \param ControlPc 72 * Address of the code, for which the table should be searched. 73 * \param ImageBase 74 * Pointer to a DWORD64 that receives the base address of the 75 * corresponding executable image. 76 * \param Length 77 * Pointer to an ULONG that receives the number of table entries 78 * present in the table. 79 */ 80 PRUNTIME_FUNCTION 81 NTAPI 82 RtlLookupFunctionTable( 83 IN DWORD64 ControlPc, 84 OUT PDWORD64 ImageBase, 85 OUT PULONG Length) 86 { 87 PVOID Table; 88 ULONG Size; 89 90 /* Find corresponding file header from code address */ 91 if (!RtlPcToFileHeader((PVOID)ControlPc, (PVOID*)ImageBase)) 92 { 93 /* Nothing found */ 94 return NULL; 95 } 96 97 /* Locate the exception directory */ 98 Table = RtlImageDirectoryEntryToData((PVOID)*ImageBase, 99 TRUE, 100 IMAGE_DIRECTORY_ENTRY_EXCEPTION, 101 &Size); 102 103 /* Return the number of entries */ 104 *Length = Size / sizeof(RUNTIME_FUNCTION); 105 106 /* Return the address of the table */ 107 return Table; 108 } 109 110 PRUNTIME_FUNCTION 111 NTAPI 112 RtlpLookupDynamicFunctionEntry( 113 _In_ DWORD64 ControlPc, 114 _Out_ PDWORD64 ImageBase, 115 _In_ PUNWIND_HISTORY_TABLE HistoryTable); 116 117 /*! RtlLookupFunctionEntry 118 * \brief Locates the RUNTIME_FUNCTION entry corresponding to a code address. 119 * \ref http://msdn.microsoft.com/en-us/library/ms680597(VS.85).aspx 120 * \todo Implement HistoryTable 121 */ 122 PRUNTIME_FUNCTION 123 NTAPI 124 RtlLookupFunctionEntry( 125 IN DWORD64 ControlPc, 126 OUT PDWORD64 ImageBase, 127 OUT PUNWIND_HISTORY_TABLE HistoryTable) 128 { 129 PRUNTIME_FUNCTION FunctionTable, FunctionEntry; 130 ULONG TableLength; 131 ULONG IndexLo, IndexHi, IndexMid; 132 133 /* Find the corresponding table */ 134 FunctionTable = RtlLookupFunctionTable(ControlPc, ImageBase, &TableLength); 135 136 /* If no table is found, try dynamic function tables */ 137 if (!FunctionTable) 138 { 139 return RtlpLookupDynamicFunctionEntry(ControlPc, ImageBase, HistoryTable); 140 } 141 142 /* Use relative virtual address */ 143 ControlPc -= *ImageBase; 144 145 /* Do a binary search */ 146 IndexLo = 0; 147 IndexHi = TableLength; 148 while (IndexHi > IndexLo) 149 { 150 IndexMid = (IndexLo + IndexHi) / 2; 151 FunctionEntry = &FunctionTable[IndexMid]; 152 153 if (ControlPc < FunctionEntry->BeginAddress) 154 { 155 /* Continue search in lower half */ 156 IndexHi = IndexMid; 157 } 158 else if (ControlPc >= FunctionEntry->EndAddress) 159 { 160 /* Continue search in upper half */ 161 IndexLo = IndexMid + 1; 162 } 163 else 164 { 165 /* ControlPc is within limits, return entry */ 166 return FunctionEntry; 167 } 168 } 169 170 /* Nothing found, return NULL */ 171 return NULL; 172 } 173 174 static 175 __inline 176 ULONG 177 UnwindOpSlots( 178 _In_ UNWIND_CODE UnwindCode) 179 { 180 static const UCHAR UnwindOpExtraSlotTable[] = 181 { 182 0, // UWOP_PUSH_NONVOL 183 1, // UWOP_ALLOC_LARGE (or 3, special cased in lookup code) 184 0, // UWOP_ALLOC_SMALL 185 0, // UWOP_SET_FPREG 186 1, // UWOP_SAVE_NONVOL 187 2, // UWOP_SAVE_NONVOL_FAR 188 1, // UWOP_EPILOG // previously UWOP_SAVE_XMM 189 2, // UWOP_SPARE_CODE // previously UWOP_SAVE_XMM_FAR 190 1, // UWOP_SAVE_XMM128 191 2, // UWOP_SAVE_XMM128_FAR 192 0, // UWOP_PUSH_MACHFRAME 193 2, // UWOP_SET_FPREG_LARGE 194 }; 195 196 if ((UnwindCode.UnwindOp == UWOP_ALLOC_LARGE) && 197 (UnwindCode.OpInfo != 0)) 198 { 199 return 3; 200 } 201 else 202 { 203 return UnwindOpExtraSlotTable[UnwindCode.UnwindOp] + 1; 204 } 205 } 206 207 static 208 __inline 209 void 210 SetReg( 211 _Inout_ PCONTEXT Context, 212 _In_ BYTE Reg, 213 _In_ DWORD64 Value) 214 { 215 ((DWORD64*)(&Context->Rax))[Reg] = Value; 216 } 217 218 static 219 __inline 220 void 221 SetRegFromStackValue( 222 _Inout_ PCONTEXT Context, 223 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, 224 _In_ BYTE Reg, 225 _In_ PDWORD64 ValuePointer) 226 { 227 SetReg(Context, Reg, *ValuePointer); 228 if (ContextPointers != NULL) 229 { 230 ContextPointers->IntegerContext[Reg] = ValuePointer; 231 } 232 } 233 234 static 235 __inline 236 DWORD64 237 GetReg( 238 _In_ PCONTEXT Context, 239 _In_ BYTE Reg) 240 { 241 return ((DWORD64*)(&Context->Rax))[Reg]; 242 } 243 244 static 245 __inline 246 void 247 PopReg( 248 _Inout_ PCONTEXT Context, 249 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, 250 _In_ BYTE Reg) 251 { 252 SetRegFromStackValue(Context, ContextPointers, Reg, (PDWORD64)Context->Rsp); 253 Context->Rsp += sizeof(DWORD64); 254 } 255 256 static 257 __inline 258 void 259 SetXmmReg( 260 _Inout_ PCONTEXT Context, 261 _In_ BYTE Reg, 262 _In_ M128A Value) 263 { 264 ((M128A*)(&Context->Xmm0))[Reg] = Value; 265 } 266 267 static 268 __inline 269 void 270 SetXmmRegFromStackValue( 271 _Out_ PCONTEXT Context, 272 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, 273 _In_ BYTE Reg, 274 _In_ M128A *ValuePointer) 275 { 276 SetXmmReg(Context, Reg, *ValuePointer); 277 if (ContextPointers != NULL) 278 { 279 ContextPointers->FloatingContext[Reg] = ValuePointer; 280 } 281 } 282 283 static 284 __inline 285 M128A 286 GetXmmReg(PCONTEXT Context, BYTE Reg) 287 { 288 return ((M128A*)(&Context->Xmm0))[Reg]; 289 } 290 291 /*! RtlpTryToUnwindEpilog 292 * \brief Helper function that tries to unwind epilog instructions. 293 * \return TRUE if we have been in an epilog and it could be unwound. 294 * FALSE if the instructions were not allowed for an epilog. 295 * \ref 296 * https://docs.microsoft.com/en-us/cpp/build/unwind-procedure 297 * https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog 298 * \todo 299 * - Test and compare with Windows behaviour 300 */ 301 static 302 __inline 303 BOOLEAN 304 RtlpTryToUnwindEpilog( 305 _Inout_ PCONTEXT Context, 306 _In_ ULONG64 ControlPc, 307 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, 308 _In_ ULONG64 ImageBase, 309 _In_ PRUNTIME_FUNCTION FunctionEntry) 310 { 311 CONTEXT LocalContext; 312 BYTE *InstrPtr; 313 DWORD Instr; 314 BYTE Reg, Mod; 315 ULONG64 EndAddress; 316 317 /* Make a local copy of the context */ 318 LocalContext = *Context; 319 320 InstrPtr = (BYTE*)ControlPc; 321 322 /* Check if first instruction of epilog is "add rsp, x" */ 323 Instr = *(DWORD*)InstrPtr; 324 if ( (Instr & 0x00fffdff) == 0x00c48148 ) 325 { 326 if ( (Instr & 0x0000ff00) == 0x8300 ) 327 { 328 /* This is "add rsp, 0x??" */ 329 LocalContext.Rsp += Instr >> 24; 330 InstrPtr += 4; 331 } 332 else 333 { 334 /* This is "add rsp, 0x???????? */ 335 LocalContext.Rsp += *(DWORD*)(InstrPtr + 3); 336 InstrPtr += 7; 337 } 338 } 339 /* Check if first instruction of epilog is "lea rsp, ..." */ 340 else if ( (Instr & 0x38fffe) == 0x208d48 ) 341 { 342 /* Get the register */ 343 Reg = (Instr >> 16) & 0x7; 344 345 /* REX.R */ 346 Reg += (Instr & 1) * 8; 347 348 LocalContext.Rsp = GetReg(&LocalContext, Reg); 349 350 /* Get addressing mode */ 351 Mod = (Instr >> 22) & 0x3; 352 if (Mod == 0) 353 { 354 /* No displacement */ 355 InstrPtr += 3; 356 } 357 else if (Mod == 1) 358 { 359 /* 1 byte displacement */ 360 LocalContext.Rsp += (LONG)(CHAR)(Instr >> 24); 361 InstrPtr += 4; 362 } 363 else if (Mod == 2) 364 { 365 /* 4 bytes displacement */ 366 LocalContext.Rsp += *(LONG*)(InstrPtr + 3); 367 InstrPtr += 7; 368 } 369 } 370 371 /* Loop the following instructions before the ret */ 372 EndAddress = FunctionEntry->EndAddress + ImageBase - 1; 373 while ((DWORD64)InstrPtr < EndAddress) 374 { 375 Instr = *(DWORD*)InstrPtr; 376 377 /* Check for a simple pop */ 378 if ( (Instr & 0xf8) == 0x58 ) 379 { 380 /* Opcode pops a basic register from stack */ 381 Reg = Instr & 0x7; 382 PopReg(&LocalContext, ContextPointers, Reg); 383 InstrPtr++; 384 continue; 385 } 386 387 /* Check for REX + pop */ 388 if ( (Instr & 0xf8fb) == 0x5841 ) 389 { 390 /* Opcode is pop r8 .. r15 */ 391 Reg = ((Instr >> 8) & 0x7) + 8; 392 PopReg(&LocalContext, ContextPointers, Reg); 393 InstrPtr += 2; 394 continue; 395 } 396 397 /* Opcode not allowed for Epilog */ 398 return FALSE; 399 } 400 401 // check for popfq 402 403 // also allow end with jmp imm, jmp [target], iretq 404 405 /* Check if we are at the ret instruction */ 406 if ((DWORD64)InstrPtr != EndAddress) 407 { 408 /* If we went past the end of the function, something is broken! */ 409 ASSERT((DWORD64)InstrPtr <= EndAddress); 410 return FALSE; 411 } 412 413 /* Make sure this is really a ret instruction */ 414 if (*InstrPtr != 0xc3) 415 { 416 return FALSE; 417 } 418 419 /* Unwind is finished, pop new Rip from Stack */ 420 LocalContext.Rip = *(DWORD64*)LocalContext.Rsp; 421 LocalContext.Rsp += sizeof(DWORD64); 422 423 *Context = LocalContext; 424 return TRUE; 425 } 426 427 /*! 428 429 \ref https://docs.microsoft.com/en-us/cpp/build/unwind-data-definitions-in-c 430 */ 431 static 432 ULONG64 433 GetEstablisherFrame( 434 _In_ PCONTEXT Context, 435 _In_ PUNWIND_INFO UnwindInfo, 436 _In_ ULONG_PTR CodeOffset) 437 { 438 ULONG i; 439 440 /* Check if we have a frame register */ 441 if (UnwindInfo->FrameRegister == 0) 442 { 443 /* No frame register means we use Rsp */ 444 return Context->Rsp; 445 } 446 447 if ((CodeOffset >= UnwindInfo->SizeOfProlog) || 448 ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) != 0)) 449 { 450 return GetReg(Context, UnwindInfo->FrameRegister) - 451 UnwindInfo->FrameOffset * 16; 452 } 453 454 /* Loop all unwind ops */ 455 for (i = 0; 456 i < UnwindInfo->CountOfCodes; 457 i += UnwindOpSlots(UnwindInfo->UnwindCode[i])) 458 { 459 /* Skip codes past our code offset */ 460 if (UnwindInfo->UnwindCode[i].CodeOffset > CodeOffset) 461 { 462 continue; 463 } 464 465 /* Check for SET_FPREG */ 466 if (UnwindInfo->UnwindCode[i].UnwindOp == UWOP_SET_FPREG) 467 { 468 return GetReg(Context, UnwindInfo->FrameRegister) - 469 UnwindInfo->FrameOffset * 16; 470 } 471 } 472 473 return Context->Rsp; 474 } 475 476 PEXCEPTION_ROUTINE 477 NTAPI 478 RtlVirtualUnwind( 479 _In_ ULONG HandlerType, 480 _In_ ULONG64 ImageBase, 481 _In_ ULONG64 ControlPc, 482 _In_ PRUNTIME_FUNCTION FunctionEntry, 483 _Inout_ PCONTEXT Context, 484 _Outptr_ PVOID *HandlerData, 485 _Out_ PULONG64 EstablisherFrame, 486 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers) 487 { 488 PUNWIND_INFO UnwindInfo; 489 ULONG_PTR ControlRva, CodeOffset; 490 ULONG i, Offset; 491 UNWIND_CODE UnwindCode; 492 BYTE Reg; 493 PULONG LanguageHandler; 494 495 /* Get relative virtual address */ 496 ControlRva = ControlPc - ImageBase; 497 498 /* Sanity checks */ 499 if ( (ControlRva < FunctionEntry->BeginAddress) || 500 (ControlRva >= FunctionEntry->EndAddress) ) 501 { 502 return NULL; 503 } 504 505 /* Get a pointer to the unwind info */ 506 UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData); 507 508 /* The language specific handler data follows the unwind info */ 509 LanguageHandler = ALIGN_UP_POINTER_BY(&UnwindInfo->UnwindCode[UnwindInfo->CountOfCodes], sizeof(ULONG)); 510 511 /* Calculate relative offset to function start */ 512 CodeOffset = ControlRva - FunctionEntry->BeginAddress; 513 514 *EstablisherFrame = GetEstablisherFrame(Context, UnwindInfo, CodeOffset); 515 516 /* Check if we are in the function epilog and try to finish it */ 517 if ((CodeOffset > UnwindInfo->SizeOfProlog) && (UnwindInfo->CountOfCodes > 0)) 518 { 519 if (RtlpTryToUnwindEpilog(Context, ControlPc, ContextPointers, ImageBase, FunctionEntry)) 520 { 521 /* There's no exception routine */ 522 return NULL; 523 } 524 } 525 526 /* Skip all Ops with an offset greater than the current Offset */ 527 i = 0; 528 while ((i < UnwindInfo->CountOfCodes) && 529 (UnwindInfo->UnwindCode[i].CodeOffset > CodeOffset)) 530 { 531 i += UnwindOpSlots(UnwindInfo->UnwindCode[i]); 532 } 533 534 RepeatChainedInfo: 535 536 /* Process the remaining unwind ops */ 537 while (i < UnwindInfo->CountOfCodes) 538 { 539 UnwindCode = UnwindInfo->UnwindCode[i]; 540 switch (UnwindCode.UnwindOp) 541 { 542 case UWOP_PUSH_NONVOL: 543 Reg = UnwindCode.OpInfo; 544 PopReg(Context, ContextPointers, Reg); 545 i++; 546 break; 547 548 case UWOP_ALLOC_LARGE: 549 if (UnwindCode.OpInfo) 550 { 551 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]); 552 Context->Rsp += Offset; 553 i += 3; 554 } 555 else 556 { 557 Offset = UnwindInfo->UnwindCode[i+1].FrameOffset; 558 Context->Rsp += Offset * 8; 559 i += 2; 560 } 561 break; 562 563 case UWOP_ALLOC_SMALL: 564 Context->Rsp += (UnwindCode.OpInfo + 1) * 8; 565 i++; 566 break; 567 568 case UWOP_SET_FPREG: 569 Reg = UnwindInfo->FrameRegister; 570 Context->Rsp = GetReg(Context, Reg) - UnwindInfo->FrameOffset * 16; 571 i++; 572 break; 573 574 case UWOP_SAVE_NONVOL: 575 Reg = UnwindCode.OpInfo; 576 Offset = UnwindInfo->UnwindCode[i + 1].FrameOffset; 577 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset); 578 i += 2; 579 break; 580 581 case UWOP_SAVE_NONVOL_FAR: 582 Reg = UnwindCode.OpInfo; 583 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]); 584 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset); 585 i += 3; 586 break; 587 588 case UWOP_EPILOG: 589 i += 1; 590 break; 591 592 case UWOP_SPARE_CODE: 593 ASSERT(FALSE); 594 i += 2; 595 break; 596 597 case UWOP_SAVE_XMM128: 598 Reg = UnwindCode.OpInfo; 599 Offset = UnwindInfo->UnwindCode[i + 1].FrameOffset; 600 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)Context->Rsp + Offset); 601 i += 2; 602 break; 603 604 case UWOP_SAVE_XMM128_FAR: 605 Reg = UnwindCode.OpInfo; 606 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]); 607 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)Context->Rsp + Offset); 608 i += 3; 609 break; 610 611 case UWOP_PUSH_MACHFRAME: 612 /* OpInfo is 1, when an error code was pushed, otherwise 0. */ 613 Context->Rsp += UnwindCode.OpInfo * sizeof(DWORD64); 614 615 /* Now pop the MACHINE_FRAME (RIP/RSP only. And yes, "magic numbers", deal with it) */ 616 Context->Rip = *(PDWORD64)(Context->Rsp + 0x00); 617 Context->Rsp = *(PDWORD64)(Context->Rsp + 0x18); 618 ASSERT((i + 1) == UnwindInfo->CountOfCodes); 619 goto Exit; 620 } 621 } 622 623 /* Check for chained info */ 624 if (UnwindInfo->Flags & UNW_FLAG_CHAININFO) 625 { 626 /* See https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-160#chained-unwind-info-structures */ 627 FunctionEntry = (PRUNTIME_FUNCTION)&(UnwindInfo->UnwindCode[(UnwindInfo->CountOfCodes + 1) & ~1]); 628 UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData); 629 i = 0; 630 goto RepeatChainedInfo; 631 } 632 633 /* Unwind is finished, pop new Rip from Stack */ 634 if (Context->Rsp != 0) 635 { 636 Context->Rip = *(DWORD64*)Context->Rsp; 637 Context->Rsp += sizeof(DWORD64); 638 } 639 640 Exit: 641 642 /* Check if we have a handler and return it */ 643 if (UnwindInfo->Flags & (HandlerType & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER))) 644 { 645 *HandlerData = (LanguageHandler + 1); 646 return RVA(ImageBase, *LanguageHandler); 647 } 648 649 return NULL; 650 } 651 652 /*! 653 \remark The implementation is based on the description in this blog: http://www.nynaeve.net/?p=106 654 655 Differences to the desciption: 656 - Instead of using 2 pointers to the unwind context and previous context, 657 that are being swapped and the context copied, the unwind context is 658 kept in the local context and copied back into the context passed in 659 by the caller. 660 661 \see http://www.nynaeve.net/?p=106 662 */ 663 BOOLEAN 664 NTAPI 665 RtlpUnwindInternal( 666 _In_opt_ PVOID TargetFrame, 667 _In_opt_ PVOID TargetIp, 668 _In_ PEXCEPTION_RECORD ExceptionRecord, 669 _In_ PVOID ReturnValue, 670 _In_ PCONTEXT ContextRecord, 671 _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable, 672 _In_ ULONG HandlerType) 673 { 674 DISPATCHER_CONTEXT DispatcherContext; 675 PEXCEPTION_ROUTINE ExceptionRoutine; 676 EXCEPTION_DISPOSITION Disposition; 677 PRUNTIME_FUNCTION FunctionEntry; 678 ULONG_PTR StackLow, StackHigh; 679 ULONG64 ImageBase, EstablisherFrame; 680 CONTEXT UnwindContext; 681 682 /* Get the current stack limits and registration frame */ 683 RtlpGetStackLimits(&StackLow, &StackHigh); 684 685 /* If we have a target frame, then this is our high limit */ 686 if (TargetFrame != NULL) 687 { 688 StackHigh = (ULONG64)TargetFrame + 1; 689 } 690 691 /* Copy the context */ 692 UnwindContext = *ContextRecord; 693 694 /* Set up the constant fields of the dispatcher context */ 695 DispatcherContext.ContextRecord = &UnwindContext; 696 DispatcherContext.HistoryTable = HistoryTable; 697 DispatcherContext.TargetIp = (ULONG64)TargetIp; 698 699 /* Start looping */ 700 while (TRUE) 701 { 702 /* Lookup the FunctionEntry for the current RIP */ 703 FunctionEntry = RtlLookupFunctionEntry(UnwindContext.Rip, &ImageBase, NULL); 704 if (FunctionEntry == NULL) 705 { 706 /* No function entry, so this must be a leaf function. Pop the return address from the stack. 707 Note: this can happen after the first frame as the result of an exception */ 708 UnwindContext.Rip = *(DWORD64*)UnwindContext.Rsp; 709 UnwindContext.Rsp += sizeof(DWORD64); 710 711 /* Copy the context back for the next iteration */ 712 *ContextRecord = UnwindContext; 713 continue; 714 } 715 716 /* Save Rip before the virtual unwind */ 717 DispatcherContext.ControlPc = UnwindContext.Rip; 718 719 /* Do a virtual unwind to get the next frame */ 720 ExceptionRoutine = RtlVirtualUnwind(HandlerType, 721 ImageBase, 722 UnwindContext.Rip, 723 FunctionEntry, 724 &UnwindContext, 725 &DispatcherContext.HandlerData, 726 &EstablisherFrame, 727 NULL); 728 729 /* Check, if we are still within the stack boundaries */ 730 if ((EstablisherFrame < StackLow) || 731 (EstablisherFrame >= StackHigh) || 732 (EstablisherFrame & 7)) 733 { 734 /// TODO: Handle DPC stack 735 736 /* If we are handling an exception, we are done here. */ 737 if (HandlerType == UNW_FLAG_EHANDLER) 738 { 739 ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID; 740 return FALSE; 741 } 742 743 __debugbreak(); 744 RtlRaiseStatus(STATUS_BAD_STACK); 745 } 746 747 /* Check if we have an exception routine */ 748 if (ExceptionRoutine != NULL) 749 { 750 /* Check if this is the target frame */ 751 if (EstablisherFrame == (ULONG64)TargetFrame) 752 { 753 /* Set flag to inform the language handler */ 754 ExceptionRecord->ExceptionFlags |= EXCEPTION_TARGET_UNWIND; 755 } 756 757 /* Log the exception if it's enabled */ 758 RtlpCheckLogException(ExceptionRecord, 759 ContextRecord, 760 &DispatcherContext, 761 sizeof(DispatcherContext)); 762 763 /* Set up the variable fields of the dispatcher context */ 764 DispatcherContext.ImageBase = ImageBase; 765 DispatcherContext.FunctionEntry = FunctionEntry; 766 DispatcherContext.LanguageHandler = ExceptionRoutine; 767 DispatcherContext.EstablisherFrame = EstablisherFrame; 768 DispatcherContext.ScopeIndex = 0; 769 770 /* Store the return value in the unwind context */ 771 UnwindContext.Rax = (ULONG64)ReturnValue; 772 773 /* Loop all nested handlers */ 774 do 775 { 776 /// TODO: call RtlpExecuteHandlerForUnwind instead 777 /* Call the language specific handler */ 778 Disposition = ExceptionRoutine(ExceptionRecord, 779 (PVOID)EstablisherFrame, 780 ContextRecord, 781 &DispatcherContext); 782 783 /* Clear exception flags for the next iteration */ 784 ExceptionRecord->ExceptionFlags &= ~(EXCEPTION_TARGET_UNWIND | 785 EXCEPTION_COLLIDED_UNWIND); 786 787 /* Check if we do exception handling */ 788 if (HandlerType == UNW_FLAG_EHANDLER) 789 { 790 if (Disposition == ExceptionContinueExecution) 791 { 792 /* Check if it was non-continuable */ 793 if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) 794 { 795 __debugbreak(); 796 RtlRaiseStatus(EXCEPTION_NONCONTINUABLE_EXCEPTION); 797 } 798 799 /* Execution continues */ 800 return TRUE; 801 } 802 else if (Disposition == ExceptionNestedException) 803 { 804 /// TODO 805 __debugbreak(); 806 } 807 } 808 809 if (Disposition == ExceptionCollidedUnwind) 810 { 811 /// TODO 812 __debugbreak(); 813 } 814 815 /* This must be ExceptionContinueSearch now */ 816 if (Disposition != ExceptionContinueSearch) 817 { 818 __debugbreak(); 819 RtlRaiseStatus(STATUS_INVALID_DISPOSITION); 820 } 821 } while (ExceptionRecord->ExceptionFlags & EXCEPTION_COLLIDED_UNWIND); 822 } 823 824 /* Check, if we have left our stack (8.) */ 825 if ((EstablisherFrame < StackLow) || 826 (EstablisherFrame > StackHigh) || 827 (EstablisherFrame & 7)) 828 { 829 /// TODO: Check for DPC stack 830 __debugbreak(); 831 832 if (UnwindContext.Rip == ContextRecord->Rip) 833 { 834 RtlRaiseStatus(STATUS_BAD_FUNCTION_TABLE); 835 } 836 else 837 { 838 ZwRaiseException(ExceptionRecord, ContextRecord, FALSE); 839 } 840 } 841 842 if (EstablisherFrame == (ULONG64)TargetFrame) 843 { 844 break; 845 } 846 847 /* We have successfully unwound a frame. Copy the unwind context back. */ 848 *ContextRecord = UnwindContext; 849 } 850 851 if (ExceptionRecord->ExceptionCode != STATUS_UNWIND_CONSOLIDATE) 852 { 853 ContextRecord->Rip = (ULONG64)TargetIp; 854 } 855 856 /* Set the return value */ 857 ContextRecord->Rax = (ULONG64)ReturnValue; 858 859 /* Restore the context */ 860 RtlRestoreContext(ContextRecord, ExceptionRecord); 861 862 /* Should never get here! */ 863 ASSERT(FALSE); 864 return FALSE; 865 } 866 867 VOID 868 NTAPI 869 RtlUnwindEx( 870 _In_opt_ PVOID TargetFrame, 871 _In_opt_ PVOID TargetIp, 872 _In_opt_ PEXCEPTION_RECORD ExceptionRecord, 873 _In_ PVOID ReturnValue, 874 _In_ PCONTEXT ContextRecord, 875 _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable) 876 { 877 EXCEPTION_RECORD LocalExceptionRecord; 878 879 /* Capture the current context */ 880 RtlCaptureContext(ContextRecord); 881 882 /* Check if we have an exception record */ 883 if (ExceptionRecord == NULL) 884 { 885 /* No exception record was passed, so set up a local one */ 886 LocalExceptionRecord.ExceptionCode = STATUS_UNWIND; 887 LocalExceptionRecord.ExceptionAddress = (PVOID)ContextRecord->Rip; 888 LocalExceptionRecord.ExceptionRecord = NULL; 889 LocalExceptionRecord.NumberParameters = 0; 890 ExceptionRecord = &LocalExceptionRecord; 891 } 892 893 /* Set unwind flags */ 894 ExceptionRecord->ExceptionFlags = EXCEPTION_UNWINDING; 895 if (TargetFrame == NULL) 896 { 897 ExceptionRecord->ExceptionFlags |= EXCEPTION_EXIT_UNWIND; 898 } 899 900 /* Call the internal function */ 901 RtlpUnwindInternal(TargetFrame, 902 TargetIp, 903 ExceptionRecord, 904 ReturnValue, 905 ContextRecord, 906 HistoryTable, 907 UNW_FLAG_UHANDLER); 908 } 909 910 VOID 911 NTAPI 912 RtlUnwind( 913 _In_opt_ PVOID TargetFrame, 914 _In_opt_ PVOID TargetIp, 915 _In_opt_ PEXCEPTION_RECORD ExceptionRecord, 916 _In_ PVOID ReturnValue) 917 { 918 CONTEXT Context; 919 920 RtlUnwindEx(TargetFrame, 921 TargetIp, 922 ExceptionRecord, 923 ReturnValue, 924 &Context, 925 NULL); 926 } 927 928 ULONG 929 NTAPI 930 RtlWalkFrameChain(OUT PVOID *Callers, 931 IN ULONG Count, 932 IN ULONG Flags) 933 { 934 CONTEXT Context; 935 ULONG64 ControlPc, ImageBase, EstablisherFrame; 936 ULONG64 StackLow, StackHigh; 937 PVOID HandlerData; 938 ULONG i, FramesToSkip; 939 PRUNTIME_FUNCTION FunctionEntry; 940 941 DPRINT("Enter RtlWalkFrameChain\n"); 942 943 /* The upper bits in Flags define how many frames to skip */ 944 FramesToSkip = Flags >> 8; 945 946 /* Capture the current Context */ 947 RtlCaptureContext(&Context); 948 ControlPc = Context.Rip; 949 950 /* Get the stack limits */ 951 RtlpGetStackLimits(&StackLow, &StackHigh); 952 953 /* Check if we want the user-mode stack frame */ 954 if (Flags & 1) 955 { 956 } 957 958 _SEH2_TRY 959 { 960 /* Loop the frames */ 961 for (i = 0; i < FramesToSkip + Count; i++) 962 { 963 /* Lookup the FunctionEntry for the current ControlPc */ 964 FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL); 965 966 /* Is this a leaf function? */ 967 if (!FunctionEntry) 968 { 969 Context.Rip = *(DWORD64*)Context.Rsp; 970 Context.Rsp += sizeof(DWORD64); 971 DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp); 972 } 973 else 974 { 975 RtlVirtualUnwind(UNW_FLAG_NHANDLER, 976 ImageBase, 977 ControlPc, 978 FunctionEntry, 979 &Context, 980 &HandlerData, 981 &EstablisherFrame, 982 NULL); 983 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp); 984 } 985 986 /* Check if we are in kernel mode */ 987 if (RtlpGetMode() == KernelMode) 988 { 989 /* Check if we left the kernel range */ 990 if (!(Flags & 1) && (Context.Rip < 0xFFFF800000000000ULL)) 991 { 992 break; 993 } 994 } 995 else 996 { 997 /* Check if we left the user range */ 998 if ((Context.Rip < 0x10000) || 999 (Context.Rip > 0x000007FFFFFEFFFFULL)) 1000 { 1001 break; 1002 } 1003 } 1004 1005 /* Check, if we have left our stack */ 1006 if ((Context.Rsp <= StackLow) || (Context.Rsp >= StackHigh)) 1007 { 1008 break; 1009 } 1010 1011 /* Continue with new Rip */ 1012 ControlPc = Context.Rip; 1013 1014 /* Save value, if we are past the frames to skip */ 1015 if (i >= FramesToSkip) 1016 { 1017 Callers[i - FramesToSkip] = (PVOID)ControlPc; 1018 } 1019 } 1020 } 1021 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 1022 { 1023 DPRINT1("Exception while getting callers!\n"); 1024 i = 0; 1025 } 1026 _SEH2_END; 1027 1028 DPRINT("RtlWalkFrameChain returns %ld\n", i); 1029 return i; 1030 } 1031 1032 /*! RtlGetCallersAddress 1033 * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html 1034 */ 1035 #undef RtlGetCallersAddress 1036 VOID 1037 NTAPI 1038 RtlGetCallersAddress( 1039 OUT PVOID *CallersAddress, 1040 OUT PVOID *CallersCaller ) 1041 { 1042 PVOID Callers[4]; 1043 ULONG Number; 1044 1045 /* Get callers: 1046 * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */ 1047 Number = RtlWalkFrameChain(Callers, 4, 0); 1048 1049 *CallersAddress = (Number >= 3) ? Callers[2] : NULL; 1050 *CallersCaller = (Number == 4) ? Callers[3] : NULL; 1051 1052 return; 1053 } 1054 1055 static 1056 VOID 1057 RtlpCaptureNonVolatileContextPointers( 1058 _Out_ PKNONVOLATILE_CONTEXT_POINTERS NonvolatileContextPointers, 1059 _In_ ULONG64 TargetFrame) 1060 { 1061 CONTEXT Context; 1062 PRUNTIME_FUNCTION FunctionEntry; 1063 ULONG64 ImageBase; 1064 PVOID HandlerData; 1065 ULONG64 EstablisherFrame; 1066 1067 /* Zero out the nonvolatile context pointers */ 1068 RtlZeroMemory(NonvolatileContextPointers, sizeof(*NonvolatileContextPointers)); 1069 1070 /* Capture the current context */ 1071 RtlCaptureContext(&Context); 1072 1073 do 1074 { 1075 /* Make sure nothing fishy is going on. Currently this is for kernel mode only. */ 1076 ASSERT((LONG64)Context.Rip < 0); 1077 ASSERT((LONG64)Context.Rsp < 0); 1078 1079 /* Look up the function entry */ 1080 FunctionEntry = RtlLookupFunctionEntry(Context.Rip, &ImageBase, NULL); 1081 if (FunctionEntry != NULL) 1082 { 1083 /* Do a virtual unwind to the caller and capture saved non-volatiles */ 1084 RtlVirtualUnwind(UNW_FLAG_EHANDLER, 1085 ImageBase, 1086 Context.Rip, 1087 FunctionEntry, 1088 &Context, 1089 &HandlerData, 1090 &EstablisherFrame, 1091 NonvolatileContextPointers); 1092 1093 ASSERT(EstablisherFrame != 0); 1094 } 1095 else 1096 { 1097 Context.Rip = *(PULONG64)Context.Rsp; 1098 Context.Rsp += 8; 1099 } 1100 1101 /* Continue until we reach user mode */ 1102 } while ((LONG64)Context.Rip < 0); 1103 1104 /* If the caller did the right thing, we should get past the target frame */ 1105 ASSERT(EstablisherFrame >= TargetFrame); 1106 } 1107 1108 VOID 1109 RtlSetUnwindContext( 1110 _In_ PCONTEXT Context, 1111 _In_ DWORD64 TargetFrame) 1112 { 1113 KNONVOLATILE_CONTEXT_POINTERS ContextPointers; 1114 1115 /* Capture pointers to the non-volatiles up to the target frame */ 1116 RtlpCaptureNonVolatileContextPointers(&ContextPointers, TargetFrame); 1117 1118 /* Copy the nonvolatiles to the captured locations */ 1119 *ContextPointers.R12 = Context->R12; 1120 *ContextPointers.R13 = Context->R13; 1121 *ContextPointers.R14 = Context->R14; 1122 *ContextPointers.R15 = Context->R15; 1123 *ContextPointers.Xmm6 = Context->Xmm6; 1124 *ContextPointers.Xmm7 = Context->Xmm7; 1125 *ContextPointers.Xmm8 = Context->Xmm8; 1126 *ContextPointers.Xmm9 = Context->Xmm9; 1127 *ContextPointers.Xmm10 = Context->Xmm10; 1128 *ContextPointers.Xmm11 = Context->Xmm11; 1129 *ContextPointers.Xmm12 = Context->Xmm12; 1130 *ContextPointers.Xmm13 = Context->Xmm13; 1131 *ContextPointers.Xmm14 = Context->Xmm14; 1132 *ContextPointers.Xmm15 = Context->Xmm15; 1133 } 1134 1135 VOID 1136 RtlpRestoreContextInternal( 1137 _In_ PCONTEXT ContextRecord); 1138 1139 VOID 1140 RtlRestoreContext( 1141 _In_ PCONTEXT ContextRecord, 1142 _In_ PEXCEPTION_RECORD ExceptionRecord) 1143 { 1144 if (ExceptionRecord != NULL) 1145 { 1146 if ((ExceptionRecord->ExceptionCode == STATUS_UNWIND_CONSOLIDATE) && 1147 (ExceptionRecord->NumberParameters >= 1)) 1148 { 1149 PVOID (*Consolidate)(EXCEPTION_RECORD*) = (PVOID)ExceptionRecord->ExceptionInformation[0]; 1150 // FIXME: This should be called through an asm wrapper to allow handling recursive unwinding 1151 ContextRecord->Rip = (ULONG64)Consolidate(ExceptionRecord); 1152 } 1153 } 1154 1155 RtlpRestoreContextInternal(ContextRecord); 1156 } 1157