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 /*! RtlLookupFunctionEntry 111 * \brief Locates the RUNTIME_FUNCTION entry corresponding to a code address. 112 * \ref http://msdn.microsoft.com/en-us/library/ms680597(VS.85).aspx 113 * \todo Implement HistoryTable 114 */ 115 PRUNTIME_FUNCTION 116 NTAPI 117 RtlLookupFunctionEntry( 118 IN DWORD64 ControlPc, 119 OUT PDWORD64 ImageBase, 120 OUT PUNWIND_HISTORY_TABLE HistoryTable) 121 { 122 PRUNTIME_FUNCTION FunctionTable, FunctionEntry; 123 ULONG TableLength; 124 ULONG IndexLo, IndexHi, IndexMid; 125 126 /* Find the corresponding table */ 127 FunctionTable = RtlLookupFunctionTable(ControlPc, ImageBase, &TableLength); 128 129 /* Fail, if no table is found */ 130 if (!FunctionTable) 131 { 132 return NULL; 133 } 134 135 /* Use relative virtual address */ 136 ControlPc -= *ImageBase; 137 138 /* Do a binary search */ 139 IndexLo = 0; 140 IndexHi = TableLength; 141 while (IndexHi > IndexLo) 142 { 143 IndexMid = (IndexLo + IndexHi) / 2; 144 FunctionEntry = &FunctionTable[IndexMid]; 145 146 if (ControlPc < FunctionEntry->BeginAddress) 147 { 148 /* Continue search in lower half */ 149 IndexHi = IndexMid; 150 } 151 else if (ControlPc >= FunctionEntry->EndAddress) 152 { 153 /* Continue search in upper half */ 154 IndexLo = IndexMid + 1; 155 } 156 else 157 { 158 /* ControlPc is within limits, return entry */ 159 return FunctionEntry; 160 } 161 } 162 163 /* Nothing found, return NULL */ 164 return NULL; 165 } 166 167 BOOLEAN 168 NTAPI 169 RtlAddFunctionTable( 170 IN PRUNTIME_FUNCTION FunctionTable, 171 IN DWORD EntryCount, 172 IN DWORD64 BaseAddress) 173 { 174 UNIMPLEMENTED; 175 return FALSE; 176 } 177 178 BOOLEAN 179 NTAPI 180 RtlDeleteFunctionTable( 181 IN PRUNTIME_FUNCTION FunctionTable) 182 { 183 UNIMPLEMENTED; 184 return FALSE; 185 } 186 187 BOOLEAN 188 NTAPI 189 RtlInstallFunctionTableCallback( 190 IN DWORD64 TableIdentifier, 191 IN DWORD64 BaseAddress, 192 IN DWORD Length, 193 IN PGET_RUNTIME_FUNCTION_CALLBACK Callback, 194 IN PVOID Context, 195 IN PCWSTR OutOfProcessCallbackDll) 196 { 197 UNIMPLEMENTED; 198 return FALSE; 199 } 200 201 static 202 __inline 203 ULONG 204 UnwindOpSlots( 205 _In_ UNWIND_CODE UnwindCode) 206 { 207 static const UCHAR UnwindOpExtraSlotTable[] = 208 { 209 0, // UWOP_PUSH_NONVOL 210 1, // UWOP_ALLOC_LARGE (or 3, special cased in lookup code) 211 0, // UWOP_ALLOC_SMALL 212 0, // UWOP_SET_FPREG 213 1, // UWOP_SAVE_NONVOL 214 2, // UWOP_SAVE_NONVOL_FAR 215 1, // UWOP_EPILOG // previously UWOP_SAVE_XMM 216 2, // UWOP_SPARE_CODE // previously UWOP_SAVE_XMM_FAR 217 1, // UWOP_SAVE_XMM128 218 2, // UWOP_SAVE_XMM128_FAR 219 0, // UWOP_PUSH_MACHFRAME 220 2, // UWOP_SET_FPREG_LARGE 221 }; 222 223 if ((UnwindCode.UnwindOp == UWOP_ALLOC_LARGE) && 224 (UnwindCode.OpInfo != 0)) 225 { 226 return 3; 227 } 228 else 229 { 230 return UnwindOpExtraSlotTable[UnwindCode.UnwindOp] + 1; 231 } 232 } 233 234 static 235 __inline 236 void 237 SetReg( 238 _Inout_ PCONTEXT Context, 239 _In_ BYTE Reg, 240 _In_ DWORD64 Value) 241 { 242 ((DWORD64*)(&Context->Rax))[Reg] = Value; 243 } 244 245 static 246 __inline 247 void 248 SetRegFromStackValue( 249 _Inout_ PCONTEXT Context, 250 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, 251 _In_ BYTE Reg, 252 _In_ PDWORD64 ValuePointer) 253 { 254 SetReg(Context, Reg, *ValuePointer); 255 if (ContextPointers != NULL) 256 { 257 ContextPointers->IntegerContext[Reg] = ValuePointer; 258 } 259 } 260 261 static 262 __inline 263 DWORD64 264 GetReg( 265 _In_ PCONTEXT Context, 266 _In_ BYTE Reg) 267 { 268 return ((DWORD64*)(&Context->Rax))[Reg]; 269 } 270 271 static 272 __inline 273 void 274 PopReg( 275 _Inout_ PCONTEXT Context, 276 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, 277 _In_ BYTE Reg) 278 { 279 SetRegFromStackValue(Context, ContextPointers, Reg, (PDWORD64)Context->Rsp); 280 Context->Rsp += sizeof(DWORD64); 281 } 282 283 static 284 __inline 285 void 286 SetXmmReg( 287 _Inout_ PCONTEXT Context, 288 _In_ BYTE Reg, 289 _In_ M128A Value) 290 { 291 ((M128A*)(&Context->Xmm0))[Reg] = Value; 292 } 293 294 static 295 __inline 296 void 297 SetXmmRegFromStackValue( 298 _Out_ PCONTEXT Context, 299 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, 300 _In_ BYTE Reg, 301 _In_ M128A *ValuePointer) 302 { 303 SetXmmReg(Context, Reg, *ValuePointer); 304 if (ContextPointers != NULL) 305 { 306 ContextPointers->FloatingContext[Reg] = ValuePointer; 307 } 308 } 309 310 static 311 __inline 312 M128A 313 GetXmmReg(PCONTEXT Context, BYTE Reg) 314 { 315 return ((M128A*)(&Context->Xmm0))[Reg]; 316 } 317 318 /*! RtlpTryToUnwindEpilog 319 * \brief Helper function that tries to unwind epilog instructions. 320 * \return TRUE if we have been in an epilog and it could be unwound. 321 * FALSE if the instructions were not allowed for an epilog. 322 * \ref 323 * https://docs.microsoft.com/en-us/cpp/build/unwind-procedure 324 * https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog 325 * \todo 326 * - Test and compare with Windows behaviour 327 */ 328 static 329 __inline 330 BOOLEAN 331 RtlpTryToUnwindEpilog( 332 _Inout_ PCONTEXT Context, 333 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, 334 _In_ ULONG64 ImageBase, 335 _In_ PRUNTIME_FUNCTION FunctionEntry) 336 { 337 CONTEXT LocalContext; 338 BYTE *InstrPtr; 339 DWORD Instr; 340 BYTE Reg, Mod; 341 ULONG64 EndAddress; 342 343 /* Make a local copy of the context */ 344 LocalContext = *Context; 345 346 InstrPtr = (BYTE*)LocalContext.Rip; 347 348 /* Check if first instruction of epilog is "add rsp, x" */ 349 Instr = *(DWORD*)InstrPtr; 350 if ( (Instr & 0x00fffdff) == 0x00c48148 ) 351 { 352 if ( (Instr & 0x0000ff00) == 0x8300 ) 353 { 354 /* This is "add rsp, 0x??" */ 355 LocalContext.Rsp += Instr >> 24; 356 InstrPtr += 4; 357 } 358 else 359 { 360 /* This is "add rsp, 0x???????? */ 361 LocalContext.Rsp += *(DWORD*)(InstrPtr + 3); 362 InstrPtr += 7; 363 } 364 } 365 /* Check if first instruction of epilog is "lea rsp, ..." */ 366 else if ( (Instr & 0x38fffe) == 0x208d48 ) 367 { 368 /* Get the register */ 369 Reg = ((Instr << 8) | (Instr >> 16)) & 0x7; 370 371 LocalContext.Rsp = GetReg(&LocalContext, Reg); 372 373 /* Get adressing mode */ 374 Mod = (Instr >> 22) & 0x3; 375 if (Mod == 0) 376 { 377 /* No displacement */ 378 InstrPtr += 3; 379 } 380 else if (Mod == 1) 381 { 382 /* 1 byte displacement */ 383 LocalContext.Rsp += Instr >> 24; 384 InstrPtr += 4; 385 } 386 else if (Mod == 2) 387 { 388 /* 4 bytes displacement */ 389 LocalContext.Rsp += *(DWORD*)(InstrPtr + 3); 390 InstrPtr += 7; 391 } 392 } 393 394 /* Loop the following instructions before the ret */ 395 EndAddress = FunctionEntry->EndAddress + ImageBase - 1; 396 while ((DWORD64)InstrPtr < EndAddress) 397 { 398 Instr = *(DWORD*)InstrPtr; 399 400 /* Check for a simple pop */ 401 if ( (Instr & 0xf8) == 0x58 ) 402 { 403 /* Opcode pops a basic register from stack */ 404 Reg = Instr & 0x7; 405 PopReg(&LocalContext, ContextPointers, Reg); 406 InstrPtr++; 407 continue; 408 } 409 410 /* Check for REX + pop */ 411 if ( (Instr & 0xf8fb) == 0x5841 ) 412 { 413 /* Opcode is pop r8 .. r15 */ 414 Reg = ((Instr >> 8) & 0x7) + 8; 415 PopReg(&LocalContext, ContextPointers, Reg); 416 InstrPtr += 2; 417 continue; 418 } 419 420 /* Opcode not allowed for Epilog */ 421 return FALSE; 422 } 423 424 // check for popfq 425 426 // also allow end with jmp imm, jmp [target], iretq 427 428 /* Check if we are at the ret instruction */ 429 if ((DWORD64)InstrPtr != EndAddress) 430 { 431 /* If we went past the end of the function, something is broken! */ 432 ASSERT((DWORD64)InstrPtr <= EndAddress); 433 return FALSE; 434 } 435 436 /* Make sure this is really a ret instruction */ 437 if (*InstrPtr != 0xc3) 438 { 439 ASSERT(FALSE); 440 return FALSE; 441 } 442 443 /* Unwind is finished, pop new Rip from Stack */ 444 LocalContext.Rip = *(DWORD64*)LocalContext.Rsp; 445 LocalContext.Rsp += sizeof(DWORD64); 446 447 *Context = LocalContext; 448 return TRUE; 449 } 450 451 /*! 452 453 \ref https://docs.microsoft.com/en-us/cpp/build/unwind-data-definitions-in-c 454 */ 455 static 456 ULONG64 457 GetEstablisherFrame( 458 _In_ PCONTEXT Context, 459 _In_ PUNWIND_INFO UnwindInfo, 460 _In_ ULONG_PTR CodeOffset) 461 { 462 ULONG i; 463 464 /* Check if we have a frame register */ 465 if (UnwindInfo->FrameRegister == 0) 466 { 467 /* No frame register means we use Rsp */ 468 return Context->Rsp; 469 } 470 471 if ((CodeOffset >= UnwindInfo->SizeOfProlog) || 472 ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) != 0)) 473 { 474 return GetReg(Context, UnwindInfo->FrameRegister) - 475 UnwindInfo->FrameOffset * 16; 476 } 477 478 /* Loop all unwind ops */ 479 for (i = 0; 480 i < UnwindInfo->CountOfCodes; 481 i += UnwindOpSlots(UnwindInfo->UnwindCode[i])) 482 { 483 /* Check for SET_FPREG */ 484 if (UnwindInfo->UnwindCode[i].UnwindOp == UWOP_SET_FPREG) 485 { 486 return GetReg(Context, UnwindInfo->FrameRegister) - 487 UnwindInfo->FrameOffset * 16; 488 } 489 } 490 491 return Context->Rsp; 492 } 493 494 PEXCEPTION_ROUTINE 495 NTAPI 496 RtlVirtualUnwind( 497 _In_ ULONG HandlerType, 498 _In_ ULONG64 ImageBase, 499 _In_ ULONG64 ControlPc, 500 _In_ PRUNTIME_FUNCTION FunctionEntry, 501 _Inout_ PCONTEXT Context, 502 _Outptr_ PVOID *HandlerData, 503 _Out_ PULONG64 EstablisherFrame, 504 _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers) 505 { 506 PUNWIND_INFO UnwindInfo; 507 ULONG_PTR CodeOffset; 508 ULONG i, Offset; 509 UNWIND_CODE UnwindCode; 510 BYTE Reg; 511 PULONG LanguageHandler; 512 513 /* Use relative virtual address */ 514 ControlPc -= ImageBase; 515 516 /* Sanity checks */ 517 if ( (ControlPc < FunctionEntry->BeginAddress) || 518 (ControlPc >= FunctionEntry->EndAddress) ) 519 { 520 return NULL; 521 } 522 523 /* Get a pointer to the unwind info */ 524 UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData); 525 526 /* Check for chained info */ 527 if (UnwindInfo->Flags & UNW_FLAG_CHAININFO) 528 { 529 UNIMPLEMENTED_DBGBREAK(); 530 531 /* See https://docs.microsoft.com/en-us/cpp/build/chained-unwind-info-structures */ 532 FunctionEntry = (PRUNTIME_FUNCTION)&(UnwindInfo->UnwindCode[(UnwindInfo->CountOfCodes + 1) & ~1]); 533 UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData); 534 } 535 536 /* The language specific handler data follows the unwind info */ 537 LanguageHandler = ALIGN_UP_POINTER_BY(&UnwindInfo->UnwindCode[UnwindInfo->CountOfCodes], sizeof(ULONG)); 538 *HandlerData = (LanguageHandler + 1); 539 540 /* Calculate relative offset to function start */ 541 CodeOffset = ControlPc - FunctionEntry->BeginAddress; 542 543 *EstablisherFrame = GetEstablisherFrame(Context, UnwindInfo, CodeOffset); 544 545 /* Check if we are in the function epilog and try to finish it */ 546 if (CodeOffset > UnwindInfo->SizeOfProlog) 547 { 548 if (RtlpTryToUnwindEpilog(Context, ContextPointers, ImageBase, FunctionEntry)) 549 { 550 /* There's no exception routine */ 551 return NULL; 552 } 553 } 554 555 /* Skip all Ops with an offset greater than the current Offset */ 556 i = 0; 557 while ((i < UnwindInfo->CountOfCodes) && 558 (UnwindInfo->UnwindCode[i].CodeOffset > CodeOffset)) 559 { 560 i += UnwindOpSlots(UnwindInfo->UnwindCode[i]); 561 } 562 563 /* Process the remaining unwind ops */ 564 while (i < UnwindInfo->CountOfCodes) 565 { 566 UnwindCode = UnwindInfo->UnwindCode[i]; 567 switch (UnwindCode.UnwindOp) 568 { 569 case UWOP_PUSH_NONVOL: 570 Reg = UnwindCode.OpInfo; 571 PopReg(Context, ContextPointers, Reg); 572 i++; 573 break; 574 575 case UWOP_ALLOC_LARGE: 576 if (UnwindCode.OpInfo) 577 { 578 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]); 579 Context->Rsp += Offset; 580 i += 3; 581 } 582 else 583 { 584 Offset = UnwindInfo->UnwindCode[i+1].FrameOffset; 585 Context->Rsp += Offset * 8; 586 i += 2; 587 } 588 break; 589 590 case UWOP_ALLOC_SMALL: 591 Context->Rsp += (UnwindCode.OpInfo + 1) * 8; 592 i++; 593 break; 594 595 case UWOP_SET_FPREG: 596 Reg = UnwindInfo->FrameRegister; 597 Context->Rsp = GetReg(Context, Reg) - UnwindInfo->FrameOffset * 16; 598 i++; 599 break; 600 601 case UWOP_SAVE_NONVOL: 602 Reg = UnwindCode.OpInfo; 603 Offset = *(USHORT*)(&UnwindInfo->UnwindCode[i + 1]); 604 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset); 605 i += 2; 606 break; 607 608 case UWOP_SAVE_NONVOL_FAR: 609 Reg = UnwindCode.OpInfo; 610 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]); 611 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset); 612 i += 3; 613 break; 614 615 case UWOP_EPILOG: 616 i += 1; 617 break; 618 619 case UWOP_SPARE_CODE: 620 ASSERT(FALSE); 621 i += 2; 622 break; 623 624 case UWOP_SAVE_XMM128: 625 Reg = UnwindCode.OpInfo; 626 Offset = *(USHORT*)(&UnwindInfo->UnwindCode[i + 1]); 627 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)(Context->Rsp + Offset)); 628 i += 2; 629 break; 630 631 case UWOP_SAVE_XMM128_FAR: 632 Reg = UnwindCode.OpInfo; 633 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]); 634 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)(Context->Rsp + Offset)); 635 i += 3; 636 break; 637 638 case UWOP_PUSH_MACHFRAME: 639 /* OpInfo is 1, when an error code was pushed, otherwise 0. */ 640 Context->Rsp += UnwindCode.OpInfo * sizeof(DWORD64); 641 642 /* Now pop the MACHINE_FRAME (Yes, "magic numbers", deal with it) */ 643 Context->Rip = *(PDWORD64)(Context->Rsp + 0x00); 644 Context->SegCs = *(PDWORD64)(Context->Rsp + 0x08); 645 Context->EFlags = *(PDWORD64)(Context->Rsp + 0x10); 646 Context->SegSs = *(PDWORD64)(Context->Rsp + 0x20); 647 Context->Rsp = *(PDWORD64)(Context->Rsp + 0x18); 648 ASSERT((i + 1) == UnwindInfo->CountOfCodes); 649 goto Exit; 650 } 651 } 652 653 /* Unwind is finished, pop new Rip from Stack */ 654 if (Context->Rsp != 0) 655 { 656 Context->Rip = *(DWORD64*)Context->Rsp; 657 Context->Rsp += sizeof(DWORD64); 658 } 659 660 Exit: 661 662 /* Check if we have a handler and return it */ 663 if (UnwindInfo->Flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER)) 664 { 665 return RVA(ImageBase, *LanguageHandler); 666 } 667 668 return NULL; 669 } 670 671 /*! 672 \remark The implementation is based on the description in this blog: http://www.nynaeve.net/?p=106 673 674 Differences to the desciption: 675 - Instead of using 2 pointers to the unwind context and previous context, 676 that are being swapped and the context copied, the unwind context is 677 kept in the local context and copied back into the context passed in 678 by the caller. 679 680 \see http://www.nynaeve.net/?p=106 681 */ 682 BOOLEAN 683 NTAPI 684 RtplUnwindInternal( 685 _In_opt_ PVOID TargetFrame, 686 _In_opt_ PVOID TargetIp, 687 _In_ PEXCEPTION_RECORD ExceptionRecord, 688 _In_ PVOID ReturnValue, 689 _In_ PCONTEXT ContextRecord, 690 _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable, 691 _In_ ULONG HandlerType) 692 { 693 DISPATCHER_CONTEXT DispatcherContext; 694 PEXCEPTION_ROUTINE ExceptionRoutine; 695 EXCEPTION_DISPOSITION Disposition; 696 PRUNTIME_FUNCTION FunctionEntry; 697 ULONG_PTR StackLow, StackHigh; 698 ULONG64 ImageBase, EstablisherFrame; 699 CONTEXT UnwindContext; 700 701 /* Get the current stack limits and registration frame */ 702 RtlpGetStackLimits(&StackLow, &StackHigh); 703 704 /* If we have a target frame, then this is our high limit */ 705 if (TargetFrame != NULL) 706 { 707 StackHigh = (ULONG64)TargetFrame + 1; 708 } 709 710 /* Copy the context */ 711 UnwindContext = *ContextRecord; 712 713 /* Set up the constant fields of the dispatcher context */ 714 DispatcherContext.ContextRecord = ContextRecord; 715 DispatcherContext.HistoryTable = HistoryTable; 716 DispatcherContext.TargetIp = (ULONG64)TargetIp; 717 718 /* Start looping */ 719 while (TRUE) 720 { 721 /* Lookup the FunctionEntry for the current RIP */ 722 FunctionEntry = RtlLookupFunctionEntry(UnwindContext.Rip, &ImageBase, NULL); 723 if (FunctionEntry == NULL) 724 { 725 /* No function entry, so this must be a leaf function. Pop the return address from the stack. 726 Note: this can happen after the first frame as the result of an exception */ 727 UnwindContext.Rip = *(DWORD64*)UnwindContext.Rsp; 728 UnwindContext.Rsp += sizeof(DWORD64); 729 continue; 730 } 731 732 /* Do a virtual unwind to get the next frame */ 733 ExceptionRoutine = RtlVirtualUnwind(HandlerType, 734 ImageBase, 735 UnwindContext.Rip, 736 FunctionEntry, 737 &UnwindContext, 738 &DispatcherContext.HandlerData, 739 &EstablisherFrame, 740 NULL); 741 742 /* Check, if we are still within the stack boundaries */ 743 if ((EstablisherFrame < StackLow) || 744 (EstablisherFrame >= StackHigh) || 745 (EstablisherFrame & 7)) 746 { 747 /// TODO: Handle DPC stack 748 749 /* If we are handling an exception, we are done here. */ 750 if (HandlerType == UNW_FLAG_EHANDLER) 751 { 752 ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID; 753 return FALSE; 754 } 755 756 __debugbreak(); 757 RtlRaiseStatus(STATUS_BAD_STACK); 758 } 759 760 /* Check if we have an exception routine */ 761 if (ExceptionRoutine != NULL) 762 { 763 /* Check if this is the target frame */ 764 if (EstablisherFrame == (ULONG64)TargetFrame) 765 { 766 /* Set flag to inform the language handler */ 767 ExceptionRecord->ExceptionFlags |= EXCEPTION_TARGET_UNWIND; 768 } 769 770 /* Log the exception if it's enabled */ 771 RtlpCheckLogException(ExceptionRecord, 772 ContextRecord, 773 &DispatcherContext, 774 sizeof(DispatcherContext)); 775 776 /* Set up the variable fields of the dispatcher context */ 777 DispatcherContext.ControlPc = ContextRecord->Rip; 778 DispatcherContext.ImageBase = ImageBase; 779 DispatcherContext.FunctionEntry = FunctionEntry; 780 DispatcherContext.LanguageHandler = ExceptionRoutine; 781 DispatcherContext.EstablisherFrame = EstablisherFrame; 782 DispatcherContext.ScopeIndex = 0; 783 784 /* Store the return value in the unwind context */ 785 UnwindContext.Rax = (ULONG64)ReturnValue; 786 787 /* Loop all nested handlers */ 788 do 789 { 790 /// TODO: call RtlpExecuteHandlerForUnwind instead 791 /* Call the language specific handler */ 792 Disposition = ExceptionRoutine(ExceptionRecord, 793 (PVOID)EstablisherFrame, 794 &UnwindContext, 795 &DispatcherContext); 796 797 /* Clear exception flags for the next iteration */ 798 ExceptionRecord->ExceptionFlags &= ~(EXCEPTION_TARGET_UNWIND | 799 EXCEPTION_COLLIDED_UNWIND); 800 801 /* Check if we do exception handling */ 802 if (HandlerType == UNW_FLAG_EHANDLER) 803 { 804 if (Disposition == ExceptionContinueExecution) 805 { 806 /* Check if it was non-continuable */ 807 if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) 808 { 809 __debugbreak(); 810 RtlRaiseStatus(EXCEPTION_NONCONTINUABLE_EXCEPTION); 811 } 812 813 /* Execution continues */ 814 return TRUE; 815 } 816 else if (Disposition == ExceptionNestedException) 817 { 818 /// TODO 819 __debugbreak(); 820 } 821 } 822 823 if (Disposition == ExceptionCollidedUnwind) 824 { 825 /// TODO 826 __debugbreak(); 827 } 828 829 /* This must be ExceptionContinueSearch now */ 830 if (Disposition != ExceptionContinueSearch) 831 { 832 __debugbreak(); 833 RtlRaiseStatus(STATUS_INVALID_DISPOSITION); 834 } 835 } while (ExceptionRecord->ExceptionFlags & EXCEPTION_COLLIDED_UNWIND); 836 } 837 838 /* Check, if we have left our stack (8.) */ 839 if ((EstablisherFrame < StackLow) || 840 (EstablisherFrame > StackHigh) || 841 (EstablisherFrame & 7)) 842 { 843 /// TODO: Check for DPC stack 844 __debugbreak(); 845 846 if (UnwindContext.Rip == ContextRecord->Rip) 847 { 848 RtlRaiseStatus(STATUS_BAD_FUNCTION_TABLE); 849 } 850 else 851 { 852 ZwRaiseException(ExceptionRecord, ContextRecord, FALSE); 853 } 854 } 855 856 if (EstablisherFrame == (ULONG64)TargetFrame) 857 { 858 break; 859 } 860 861 /* We have successfully unwound a frame. Copy the unwind context back. */ 862 *ContextRecord = UnwindContext; 863 } 864 865 if (ExceptionRecord->ExceptionCode != STATUS_UNWIND_CONSOLIDATE) 866 { 867 ContextRecord->Rip = (ULONG64)TargetIp; 868 } 869 870 /* Set the return value */ 871 ContextRecord->Rax = (ULONG64)ReturnValue; 872 873 /* Restore the context */ 874 RtlRestoreContext(ContextRecord, ExceptionRecord); 875 876 /* Should never get here! */ 877 ASSERT(FALSE); 878 return FALSE; 879 } 880 881 VOID 882 NTAPI 883 RtlUnwindEx( 884 _In_opt_ PVOID TargetFrame, 885 _In_opt_ PVOID TargetIp, 886 _In_opt_ PEXCEPTION_RECORD ExceptionRecord, 887 _In_ PVOID ReturnValue, 888 _In_ PCONTEXT ContextRecord, 889 _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable) 890 { 891 EXCEPTION_RECORD LocalExceptionRecord; 892 893 /* Capture the current context */ 894 RtlCaptureContext(ContextRecord); 895 896 /* Check if we have an exception record */ 897 if (ExceptionRecord == NULL) 898 { 899 /* No exception record was passed, so set up a local one */ 900 LocalExceptionRecord.ExceptionCode = STATUS_UNWIND; 901 LocalExceptionRecord.ExceptionAddress = (PVOID)ContextRecord->Rip; 902 LocalExceptionRecord.ExceptionRecord = NULL; 903 LocalExceptionRecord.NumberParameters = 0; 904 ExceptionRecord = &LocalExceptionRecord; 905 } 906 907 /* Call the internal function */ 908 RtplUnwindInternal(TargetFrame, 909 TargetIp, 910 ExceptionRecord, 911 ReturnValue, 912 ContextRecord, 913 HistoryTable, 914 UNW_FLAG_UHANDLER); 915 } 916 917 VOID 918 NTAPI 919 RtlUnwind( 920 IN PVOID TargetFrame, 921 IN PVOID TargetIp, 922 IN PEXCEPTION_RECORD ExceptionRecord, 923 IN PVOID ReturnValue) 924 { 925 UNIMPLEMENTED; 926 return; 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 /* Look up the function entry */ 1077 FunctionEntry = RtlLookupFunctionEntry(Context.Rip, &ImageBase, NULL); 1078 ASSERT(FunctionEntry != NULL); 1079 1080 /* Do a virtual unwind to the caller and capture saved non-volatiles */ 1081 RtlVirtualUnwind(UNW_FLAG_EHANDLER, 1082 ImageBase, 1083 Context.Rip, 1084 FunctionEntry, 1085 &Context, 1086 &HandlerData, 1087 &EstablisherFrame, 1088 NonvolatileContextPointers); 1089 1090 /* Make sure nothing fishy is going on. Currently this is for kernel mode only. */ 1091 ASSERT(EstablisherFrame != 0); 1092 ASSERT((LONG64)Context.Rip < 0); 1093 1094 /* Continue until we reached the target frame or user mode */ 1095 } while (EstablisherFrame < TargetFrame); 1096 1097 /* If the caller did the right thing, we should get exactly the target frame */ 1098 ASSERT(EstablisherFrame == TargetFrame); 1099 } 1100 1101 VOID 1102 RtlSetUnwindContext( 1103 _In_ PCONTEXT Context, 1104 _In_ DWORD64 TargetFrame) 1105 { 1106 KNONVOLATILE_CONTEXT_POINTERS ContextPointers; 1107 1108 /* Capture pointers to the non-volatiles up to the target frame */ 1109 RtlpCaptureNonVolatileContextPointers(&ContextPointers, TargetFrame); 1110 1111 /* Copy the nonvolatiles to the captured locations */ 1112 *ContextPointers.R12 = Context->R12; 1113 *ContextPointers.R13 = Context->R13; 1114 *ContextPointers.R14 = Context->R14; 1115 *ContextPointers.R15 = Context->R15; 1116 *ContextPointers.Xmm6 = Context->Xmm6; 1117 *ContextPointers.Xmm7 = Context->Xmm7; 1118 *ContextPointers.Xmm8 = Context->Xmm8; 1119 *ContextPointers.Xmm9 = Context->Xmm9; 1120 *ContextPointers.Xmm10 = Context->Xmm10; 1121 *ContextPointers.Xmm11 = Context->Xmm11; 1122 *ContextPointers.Xmm12 = Context->Xmm12; 1123 *ContextPointers.Xmm13 = Context->Xmm13; 1124 *ContextPointers.Xmm14 = Context->Xmm14; 1125 *ContextPointers.Xmm15 = Context->Xmm15; 1126 } 1127