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 adressing 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 ASSERT(FALSE); 417 return FALSE; 418 } 419 420 /* Unwind is finished, pop new Rip from Stack */ 421 LocalContext.Rip = *(DWORD64*)LocalContext.Rsp; 422 LocalContext.Rsp += sizeof(DWORD64); 423 424 *Context = LocalContext; 425 return TRUE; 426 } 427 428 /*! 429 430 \ref https://docs.microsoft.com/en-us/cpp/build/unwind-data-definitions-in-c 431 */ 432 static 433 ULONG64 434 GetEstablisherFrame( 435 _In_ PCONTEXT Context, 436 _In_ PUNWIND_INFO UnwindInfo, 437 _In_ ULONG_PTR CodeOffset) 438 { 439 ULONG i; 440 441 /* Check if we have a frame register */ 442 if (UnwindInfo->FrameRegister == 0) 443 { 444 /* No frame register means we use Rsp */ 445 return Context->Rsp; 446 } 447 448 if ((CodeOffset >= UnwindInfo->SizeOfProlog) || 449 ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) != 0)) 450 { 451 return GetReg(Context, UnwindInfo->FrameRegister) - 452 UnwindInfo->FrameOffset * 16; 453 } 454 455 /* Loop all unwind ops */ 456 for (i = 0; 457 i < UnwindInfo->CountOfCodes; 458 i += UnwindOpSlots(UnwindInfo->UnwindCode[i])) 459 { 460 /* Skip codes past our code offset */ 461 if (UnwindInfo->UnwindCode[i].CodeOffset > CodeOffset) 462 { 463 continue; 464 } 465 466 /* Check for SET_FPREG */ 467 if (UnwindInfo->UnwindCode[i].UnwindOp == UWOP_SET_FPREG) 468 { 469 return GetReg(Context, UnwindInfo->FrameRegister) - 470 UnwindInfo->FrameOffset * 16; 471 } 472 } 473 474 return Context->Rsp; 475 } 476 477 PEXCEPTION_ROUTINE 478 NTAPI 479 RtlVirtualUnwind( 480 _In_ ULONG HandlerType, 481 _In_ ULONG64 ImageBase, 482 _In_ ULONG64 ControlPc, 483 _In_ PRUNTIME_FUNCTION FunctionEntry, 484 _Inout_ PCONTEXT Context, 485 _Outptr_ PVOID *HandlerData, 486 _Out_ PULONG64 EstablisherFrame, 487 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers) 488 { 489 PUNWIND_INFO UnwindInfo; 490 ULONG_PTR ControlRva, CodeOffset; 491 ULONG i, Offset; 492 UNWIND_CODE UnwindCode; 493 BYTE Reg; 494 PULONG LanguageHandler; 495 496 /* Get relative virtual address */ 497 ControlRva = ControlPc - ImageBase; 498 499 /* Sanity checks */ 500 if ( (ControlRva < FunctionEntry->BeginAddress) || 501 (ControlRva >= FunctionEntry->EndAddress) ) 502 { 503 return NULL; 504 } 505 506 /* Get a pointer to the unwind info */ 507 UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData); 508 509 /* The language specific handler data follows the unwind info */ 510 LanguageHandler = ALIGN_UP_POINTER_BY(&UnwindInfo->UnwindCode[UnwindInfo->CountOfCodes], sizeof(ULONG)); 511 512 /* Calculate relative offset to function start */ 513 CodeOffset = ControlRva - FunctionEntry->BeginAddress; 514 515 *EstablisherFrame = GetEstablisherFrame(Context, UnwindInfo, CodeOffset); 516 517 /* Check if we are in the function epilog and try to finish it */ 518 if ((CodeOffset > UnwindInfo->SizeOfProlog) && (UnwindInfo->CountOfCodes > 0)) 519 { 520 if (RtlpTryToUnwindEpilog(Context, ControlPc, ContextPointers, ImageBase, FunctionEntry)) 521 { 522 /* There's no exception routine */ 523 return NULL; 524 } 525 } 526 527 /* Skip all Ops with an offset greater than the current Offset */ 528 i = 0; 529 while ((i < UnwindInfo->CountOfCodes) && 530 (UnwindInfo->UnwindCode[i].CodeOffset > CodeOffset)) 531 { 532 i += UnwindOpSlots(UnwindInfo->UnwindCode[i]); 533 } 534 535 RepeatChainedInfo: 536 537 /* Process the remaining unwind ops */ 538 while (i < UnwindInfo->CountOfCodes) 539 { 540 UnwindCode = UnwindInfo->UnwindCode[i]; 541 switch (UnwindCode.UnwindOp) 542 { 543 case UWOP_PUSH_NONVOL: 544 Reg = UnwindCode.OpInfo; 545 PopReg(Context, ContextPointers, Reg); 546 i++; 547 break; 548 549 case UWOP_ALLOC_LARGE: 550 if (UnwindCode.OpInfo) 551 { 552 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]); 553 Context->Rsp += Offset; 554 i += 3; 555 } 556 else 557 { 558 Offset = UnwindInfo->UnwindCode[i+1].FrameOffset; 559 Context->Rsp += Offset * 8; 560 i += 2; 561 } 562 break; 563 564 case UWOP_ALLOC_SMALL: 565 Context->Rsp += (UnwindCode.OpInfo + 1) * 8; 566 i++; 567 break; 568 569 case UWOP_SET_FPREG: 570 Reg = UnwindInfo->FrameRegister; 571 Context->Rsp = GetReg(Context, Reg) - UnwindInfo->FrameOffset * 16; 572 i++; 573 break; 574 575 case UWOP_SAVE_NONVOL: 576 Reg = UnwindCode.OpInfo; 577 Offset = UnwindInfo->UnwindCode[i + 1].FrameOffset; 578 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset); 579 i += 2; 580 break; 581 582 case UWOP_SAVE_NONVOL_FAR: 583 Reg = UnwindCode.OpInfo; 584 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]); 585 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset); 586 i += 3; 587 break; 588 589 case UWOP_EPILOG: 590 i += 1; 591 break; 592 593 case UWOP_SPARE_CODE: 594 ASSERT(FALSE); 595 i += 2; 596 break; 597 598 case UWOP_SAVE_XMM128: 599 Reg = UnwindCode.OpInfo; 600 Offset = UnwindInfo->UnwindCode[i + 1].FrameOffset; 601 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)Context->Rsp + Offset); 602 i += 2; 603 break; 604 605 case UWOP_SAVE_XMM128_FAR: 606 Reg = UnwindCode.OpInfo; 607 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]); 608 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)Context->Rsp + Offset); 609 i += 3; 610 break; 611 612 case UWOP_PUSH_MACHFRAME: 613 /* OpInfo is 1, when an error code was pushed, otherwise 0. */ 614 Context->Rsp += UnwindCode.OpInfo * sizeof(DWORD64); 615 616 /* Now pop the MACHINE_FRAME (RIP/RSP only. And yes, "magic numbers", deal with it) */ 617 Context->Rip = *(PDWORD64)(Context->Rsp + 0x00); 618 Context->Rsp = *(PDWORD64)(Context->Rsp + 0x18); 619 ASSERT((i + 1) == UnwindInfo->CountOfCodes); 620 goto Exit; 621 } 622 } 623 624 /* Check for chained info */ 625 if (UnwindInfo->Flags & UNW_FLAG_CHAININFO) 626 { 627 /* See https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-160#chained-unwind-info-structures */ 628 FunctionEntry = (PRUNTIME_FUNCTION)&(UnwindInfo->UnwindCode[(UnwindInfo->CountOfCodes + 1) & ~1]); 629 UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData); 630 i = 0; 631 goto RepeatChainedInfo; 632 } 633 634 /* Unwind is finished, pop new Rip from Stack */ 635 if (Context->Rsp != 0) 636 { 637 Context->Rip = *(DWORD64*)Context->Rsp; 638 Context->Rsp += sizeof(DWORD64); 639 } 640 641 Exit: 642 643 /* Check if we have a handler and return it */ 644 if (UnwindInfo->Flags & (HandlerType & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER))) 645 { 646 *HandlerData = (LanguageHandler + 1); 647 return RVA(ImageBase, *LanguageHandler); 648 } 649 650 return NULL; 651 } 652 653 /*! 654 \remark The implementation is based on the description in this blog: http://www.nynaeve.net/?p=106 655 656 Differences to the desciption: 657 - Instead of using 2 pointers to the unwind context and previous context, 658 that are being swapped and the context copied, the unwind context is 659 kept in the local context and copied back into the context passed in 660 by the caller. 661 662 \see http://www.nynaeve.net/?p=106 663 */ 664 BOOLEAN 665 NTAPI 666 RtlpUnwindInternal( 667 _In_opt_ PVOID TargetFrame, 668 _In_opt_ PVOID TargetIp, 669 _In_ PEXCEPTION_RECORD ExceptionRecord, 670 _In_ PVOID ReturnValue, 671 _In_ PCONTEXT ContextRecord, 672 _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable, 673 _In_ ULONG HandlerType) 674 { 675 DISPATCHER_CONTEXT DispatcherContext; 676 PEXCEPTION_ROUTINE ExceptionRoutine; 677 EXCEPTION_DISPOSITION Disposition; 678 PRUNTIME_FUNCTION FunctionEntry; 679 ULONG_PTR StackLow, StackHigh; 680 ULONG64 ImageBase, EstablisherFrame; 681 CONTEXT UnwindContext; 682 683 /* Get the current stack limits and registration frame */ 684 RtlpGetStackLimits(&StackLow, &StackHigh); 685 686 /* If we have a target frame, then this is our high limit */ 687 if (TargetFrame != NULL) 688 { 689 StackHigh = (ULONG64)TargetFrame + 1; 690 } 691 692 /* Copy the context */ 693 UnwindContext = *ContextRecord; 694 695 /* Set up the constant fields of the dispatcher context */ 696 DispatcherContext.ContextRecord = &UnwindContext; 697 DispatcherContext.HistoryTable = HistoryTable; 698 DispatcherContext.TargetIp = (ULONG64)TargetIp; 699 700 /* Start looping */ 701 while (TRUE) 702 { 703 /* Lookup the FunctionEntry for the current RIP */ 704 FunctionEntry = RtlLookupFunctionEntry(UnwindContext.Rip, &ImageBase, NULL); 705 if (FunctionEntry == NULL) 706 { 707 /* No function entry, so this must be a leaf function. Pop the return address from the stack. 708 Note: this can happen after the first frame as the result of an exception */ 709 UnwindContext.Rip = *(DWORD64*)UnwindContext.Rsp; 710 UnwindContext.Rsp += sizeof(DWORD64); 711 712 /* Copy the context back for the next iteration */ 713 *ContextRecord = UnwindContext; 714 continue; 715 } 716 717 /* Save Rip before the virtual unwind */ 718 DispatcherContext.ControlPc = UnwindContext.Rip; 719 720 /* Do a virtual unwind to get the next frame */ 721 ExceptionRoutine = RtlVirtualUnwind(HandlerType, 722 ImageBase, 723 UnwindContext.Rip, 724 FunctionEntry, 725 &UnwindContext, 726 &DispatcherContext.HandlerData, 727 &EstablisherFrame, 728 NULL); 729 730 /* Check, if we are still within the stack boundaries */ 731 if ((EstablisherFrame < StackLow) || 732 (EstablisherFrame >= StackHigh) || 733 (EstablisherFrame & 7)) 734 { 735 /// TODO: Handle DPC stack 736 737 /* If we are handling an exception, we are done here. */ 738 if (HandlerType == UNW_FLAG_EHANDLER) 739 { 740 ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID; 741 return FALSE; 742 } 743 744 __debugbreak(); 745 RtlRaiseStatus(STATUS_BAD_STACK); 746 } 747 748 /* Check if we have an exception routine */ 749 if (ExceptionRoutine != NULL) 750 { 751 /* Check if this is the target frame */ 752 if (EstablisherFrame == (ULONG64)TargetFrame) 753 { 754 /* Set flag to inform the language handler */ 755 ExceptionRecord->ExceptionFlags |= EXCEPTION_TARGET_UNWIND; 756 } 757 758 /* Log the exception if it's enabled */ 759 RtlpCheckLogException(ExceptionRecord, 760 ContextRecord, 761 &DispatcherContext, 762 sizeof(DispatcherContext)); 763 764 /* Set up the variable fields of the dispatcher context */ 765 DispatcherContext.ImageBase = ImageBase; 766 DispatcherContext.FunctionEntry = FunctionEntry; 767 DispatcherContext.LanguageHandler = ExceptionRoutine; 768 DispatcherContext.EstablisherFrame = EstablisherFrame; 769 DispatcherContext.ScopeIndex = 0; 770 771 /* Store the return value in the unwind context */ 772 UnwindContext.Rax = (ULONG64)ReturnValue; 773 774 /* Loop all nested handlers */ 775 do 776 { 777 /// TODO: call RtlpExecuteHandlerForUnwind instead 778 /* Call the language specific handler */ 779 Disposition = ExceptionRoutine(ExceptionRecord, 780 (PVOID)EstablisherFrame, 781 ContextRecord, 782 &DispatcherContext); 783 784 /* Clear exception flags for the next iteration */ 785 ExceptionRecord->ExceptionFlags &= ~(EXCEPTION_TARGET_UNWIND | 786 EXCEPTION_COLLIDED_UNWIND); 787 788 /* Check if we do exception handling */ 789 if (HandlerType == UNW_FLAG_EHANDLER) 790 { 791 if (Disposition == ExceptionContinueExecution) 792 { 793 /* Check if it was non-continuable */ 794 if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) 795 { 796 __debugbreak(); 797 RtlRaiseStatus(EXCEPTION_NONCONTINUABLE_EXCEPTION); 798 } 799 800 /* Execution continues */ 801 return TRUE; 802 } 803 else if (Disposition == ExceptionNestedException) 804 { 805 /// TODO 806 __debugbreak(); 807 } 808 } 809 810 if (Disposition == ExceptionCollidedUnwind) 811 { 812 /// TODO 813 __debugbreak(); 814 } 815 816 /* This must be ExceptionContinueSearch now */ 817 if (Disposition != ExceptionContinueSearch) 818 { 819 __debugbreak(); 820 RtlRaiseStatus(STATUS_INVALID_DISPOSITION); 821 } 822 } while (ExceptionRecord->ExceptionFlags & EXCEPTION_COLLIDED_UNWIND); 823 } 824 825 /* Check, if we have left our stack (8.) */ 826 if ((EstablisherFrame < StackLow) || 827 (EstablisherFrame > StackHigh) || 828 (EstablisherFrame & 7)) 829 { 830 /// TODO: Check for DPC stack 831 __debugbreak(); 832 833 if (UnwindContext.Rip == ContextRecord->Rip) 834 { 835 RtlRaiseStatus(STATUS_BAD_FUNCTION_TABLE); 836 } 837 else 838 { 839 ZwRaiseException(ExceptionRecord, ContextRecord, FALSE); 840 } 841 } 842 843 if (EstablisherFrame == (ULONG64)TargetFrame) 844 { 845 break; 846 } 847 848 /* We have successfully unwound a frame. Copy the unwind context back. */ 849 *ContextRecord = UnwindContext; 850 } 851 852 if (ExceptionRecord->ExceptionCode != STATUS_UNWIND_CONSOLIDATE) 853 { 854 ContextRecord->Rip = (ULONG64)TargetIp; 855 } 856 857 /* Set the return value */ 858 ContextRecord->Rax = (ULONG64)ReturnValue; 859 860 /* Restore the context */ 861 RtlRestoreContext(ContextRecord, ExceptionRecord); 862 863 /* Should never get here! */ 864 ASSERT(FALSE); 865 return FALSE; 866 } 867 868 VOID 869 NTAPI 870 RtlUnwindEx( 871 _In_opt_ PVOID TargetFrame, 872 _In_opt_ PVOID TargetIp, 873 _In_opt_ PEXCEPTION_RECORD ExceptionRecord, 874 _In_ PVOID ReturnValue, 875 _In_ PCONTEXT ContextRecord, 876 _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable) 877 { 878 EXCEPTION_RECORD LocalExceptionRecord; 879 880 /* Capture the current context */ 881 RtlCaptureContext(ContextRecord); 882 883 /* Check if we have an exception record */ 884 if (ExceptionRecord == NULL) 885 { 886 /* No exception record was passed, so set up a local one */ 887 LocalExceptionRecord.ExceptionCode = STATUS_UNWIND; 888 LocalExceptionRecord.ExceptionAddress = (PVOID)ContextRecord->Rip; 889 LocalExceptionRecord.ExceptionRecord = NULL; 890 LocalExceptionRecord.NumberParameters = 0; 891 ExceptionRecord = &LocalExceptionRecord; 892 } 893 894 /* Set unwind flags */ 895 ExceptionRecord->ExceptionFlags = EXCEPTION_UNWINDING; 896 if (TargetFrame == NULL) 897 { 898 ExceptionRecord->ExceptionFlags |= EXCEPTION_EXIT_UNWIND; 899 } 900 901 /* Call the internal function */ 902 RtlpUnwindInternal(TargetFrame, 903 TargetIp, 904 ExceptionRecord, 905 ReturnValue, 906 ContextRecord, 907 HistoryTable, 908 UNW_FLAG_UHANDLER); 909 } 910 911 VOID 912 NTAPI 913 RtlUnwind( 914 _In_opt_ PVOID TargetFrame, 915 _In_opt_ PVOID TargetIp, 916 _In_opt_ PEXCEPTION_RECORD ExceptionRecord, 917 _In_ PVOID ReturnValue) 918 { 919 CONTEXT Context; 920 921 RtlUnwindEx(TargetFrame, 922 TargetIp, 923 ExceptionRecord, 924 ReturnValue, 925 &Context, 926 NULL); 927 } 928 929 ULONG 930 NTAPI 931 RtlWalkFrameChain(OUT PVOID *Callers, 932 IN ULONG Count, 933 IN ULONG Flags) 934 { 935 CONTEXT Context; 936 ULONG64 ControlPc, ImageBase, EstablisherFrame; 937 ULONG64 StackLow, StackHigh; 938 PVOID HandlerData; 939 ULONG i, FramesToSkip; 940 PRUNTIME_FUNCTION FunctionEntry; 941 942 DPRINT("Enter RtlWalkFrameChain\n"); 943 944 /* The upper bits in Flags define how many frames to skip */ 945 FramesToSkip = Flags >> 8; 946 947 /* Capture the current Context */ 948 RtlCaptureContext(&Context); 949 ControlPc = Context.Rip; 950 951 /* Get the stack limits */ 952 RtlpGetStackLimits(&StackLow, &StackHigh); 953 954 /* Check if we want the user-mode stack frame */ 955 if (Flags & 1) 956 { 957 } 958 959 _SEH2_TRY 960 { 961 /* Loop the frames */ 962 for (i = 0; i < FramesToSkip + Count; i++) 963 { 964 /* Lookup the FunctionEntry for the current ControlPc */ 965 FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL); 966 967 /* Is this a leaf function? */ 968 if (!FunctionEntry) 969 { 970 Context.Rip = *(DWORD64*)Context.Rsp; 971 Context.Rsp += sizeof(DWORD64); 972 DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp); 973 } 974 else 975 { 976 RtlVirtualUnwind(UNW_FLAG_NHANDLER, 977 ImageBase, 978 ControlPc, 979 FunctionEntry, 980 &Context, 981 &HandlerData, 982 &EstablisherFrame, 983 NULL); 984 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp); 985 } 986 987 /* Check if we are in kernel mode */ 988 if (RtlpGetMode() == KernelMode) 989 { 990 /* Check if we left the kernel range */ 991 if (!(Flags & 1) && (Context.Rip < 0xFFFF800000000000ULL)) 992 { 993 break; 994 } 995 } 996 else 997 { 998 /* Check if we left the user range */ 999 if ((Context.Rip < 0x10000) || 1000 (Context.Rip > 0x000007FFFFFEFFFFULL)) 1001 { 1002 break; 1003 } 1004 } 1005 1006 /* Check, if we have left our stack */ 1007 if ((Context.Rsp <= StackLow) || (Context.Rsp >= StackHigh)) 1008 { 1009 break; 1010 } 1011 1012 /* Continue with new Rip */ 1013 ControlPc = Context.Rip; 1014 1015 /* Save value, if we are past the frames to skip */ 1016 if (i >= FramesToSkip) 1017 { 1018 Callers[i - FramesToSkip] = (PVOID)ControlPc; 1019 } 1020 } 1021 } 1022 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 1023 { 1024 DPRINT1("Exception while getting callers!\n"); 1025 i = 0; 1026 } 1027 _SEH2_END; 1028 1029 DPRINT("RtlWalkFrameChain returns %ld\n", i); 1030 return i; 1031 } 1032 1033 /*! RtlGetCallersAddress 1034 * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html 1035 */ 1036 #undef RtlGetCallersAddress 1037 VOID 1038 NTAPI 1039 RtlGetCallersAddress( 1040 OUT PVOID *CallersAddress, 1041 OUT PVOID *CallersCaller ) 1042 { 1043 PVOID Callers[4]; 1044 ULONG Number; 1045 1046 /* Get callers: 1047 * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */ 1048 Number = RtlWalkFrameChain(Callers, 4, 0); 1049 1050 *CallersAddress = (Number >= 3) ? Callers[2] : NULL; 1051 *CallersCaller = (Number == 4) ? Callers[3] : NULL; 1052 1053 return; 1054 } 1055 1056 static 1057 VOID 1058 RtlpCaptureNonVolatileContextPointers( 1059 _Out_ PKNONVOLATILE_CONTEXT_POINTERS NonvolatileContextPointers, 1060 _In_ ULONG64 TargetFrame) 1061 { 1062 CONTEXT Context; 1063 PRUNTIME_FUNCTION FunctionEntry; 1064 ULONG64 ImageBase; 1065 PVOID HandlerData; 1066 ULONG64 EstablisherFrame; 1067 1068 /* Zero out the nonvolatile context pointers */ 1069 RtlZeroMemory(NonvolatileContextPointers, sizeof(*NonvolatileContextPointers)); 1070 1071 /* Capture the current context */ 1072 RtlCaptureContext(&Context); 1073 1074 do 1075 { 1076 /* Make sure nothing fishy is going on. Currently this is for kernel mode only. */ 1077 ASSERT((LONG64)Context.Rip < 0); 1078 ASSERT((LONG64)Context.Rsp < 0); 1079 1080 /* Look up the function entry */ 1081 FunctionEntry = RtlLookupFunctionEntry(Context.Rip, &ImageBase, NULL); 1082 if (FunctionEntry != NULL) 1083 { 1084 /* Do a virtual unwind to the caller and capture saved non-volatiles */ 1085 RtlVirtualUnwind(UNW_FLAG_EHANDLER, 1086 ImageBase, 1087 Context.Rip, 1088 FunctionEntry, 1089 &Context, 1090 &HandlerData, 1091 &EstablisherFrame, 1092 NonvolatileContextPointers); 1093 1094 ASSERT(EstablisherFrame != 0); 1095 } 1096 else 1097 { 1098 Context.Rip = *(PULONG64)Context.Rsp; 1099 Context.Rsp += 8; 1100 } 1101 1102 /* Continue until we reach user mode */ 1103 } while ((LONG64)Context.Rip < 0); 1104 1105 /* If the caller did the right thing, we should get past the target frame */ 1106 ASSERT(EstablisherFrame >= TargetFrame); 1107 } 1108 1109 VOID 1110 RtlSetUnwindContext( 1111 _In_ PCONTEXT Context, 1112 _In_ DWORD64 TargetFrame) 1113 { 1114 KNONVOLATILE_CONTEXT_POINTERS ContextPointers; 1115 1116 /* Capture pointers to the non-volatiles up to the target frame */ 1117 RtlpCaptureNonVolatileContextPointers(&ContextPointers, TargetFrame); 1118 1119 /* Copy the nonvolatiles to the captured locations */ 1120 *ContextPointers.R12 = Context->R12; 1121 *ContextPointers.R13 = Context->R13; 1122 *ContextPointers.R14 = Context->R14; 1123 *ContextPointers.R15 = Context->R15; 1124 *ContextPointers.Xmm6 = Context->Xmm6; 1125 *ContextPointers.Xmm7 = Context->Xmm7; 1126 *ContextPointers.Xmm8 = Context->Xmm8; 1127 *ContextPointers.Xmm9 = Context->Xmm9; 1128 *ContextPointers.Xmm10 = Context->Xmm10; 1129 *ContextPointers.Xmm11 = Context->Xmm11; 1130 *ContextPointers.Xmm12 = Context->Xmm12; 1131 *ContextPointers.Xmm13 = Context->Xmm13; 1132 *ContextPointers.Xmm14 = Context->Xmm14; 1133 *ContextPointers.Xmm15 = Context->Xmm15; 1134 } 1135