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 #define UWOP_SAVE_XMM 6 26 #define UWOP_SAVE_XMM_FAR 7 27 #define UWOP_SAVE_XMM128 8 28 #define UWOP_SAVE_XMM128_FAR 9 29 #define UWOP_PUSH_MACHFRAME 10 30 31 32 typedef unsigned char UBYTE; 33 34 typedef union _UNWIND_CODE 35 { 36 struct 37 { 38 UBYTE CodeOffset; 39 UBYTE UnwindOp:4; 40 UBYTE OpInfo:4; 41 }; 42 USHORT FrameOffset; 43 } UNWIND_CODE, *PUNWIND_CODE; 44 45 typedef struct _UNWIND_INFO 46 { 47 UBYTE Version:3; 48 UBYTE Flags:5; 49 UBYTE SizeOfProlog; 50 UBYTE CountOfCodes; 51 UBYTE FrameRegister:4; 52 UBYTE FrameOffset:4; 53 UNWIND_CODE UnwindCode[1]; 54 /* union { 55 OPTIONAL ULONG ExceptionHandler; 56 OPTIONAL ULONG FunctionEntry; 57 }; 58 OPTIONAL ULONG ExceptionData[]; 59 */ 60 } UNWIND_INFO, *PUNWIND_INFO; 61 62 /* FUNCTIONS *****************************************************************/ 63 64 /*! RtlLookupFunctionTable 65 * \brief Locates the table of RUNTIME_FUNCTION entries for a code address. 66 * \param ControlPc 67 * Address of the code, for which the table should be searched. 68 * \param ImageBase 69 * Pointer to a DWORD64 that receives the base address of the 70 * corresponding executable image. 71 * \param Length 72 * Pointer to an ULONG that receives the number of table entries 73 * present in the table. 74 */ 75 PRUNTIME_FUNCTION 76 NTAPI 77 RtlLookupFunctionTable( 78 IN DWORD64 ControlPc, 79 OUT PDWORD64 ImageBase, 80 OUT PULONG Length) 81 { 82 PVOID Table; 83 ULONG Size; 84 85 /* Find corresponding file header from code address */ 86 if (!RtlPcToFileHeader((PVOID)ControlPc, (PVOID*)ImageBase)) 87 { 88 /* Nothing found */ 89 return NULL; 90 } 91 92 /* Locate the exception directory */ 93 Table = RtlImageDirectoryEntryToData((PVOID)*ImageBase, 94 TRUE, 95 IMAGE_DIRECTORY_ENTRY_EXCEPTION, 96 &Size); 97 98 /* Return the number of entries */ 99 *Length = Size / sizeof(RUNTIME_FUNCTION); 100 101 /* Return the address of the table */ 102 return Table; 103 } 104 105 /*! RtlLookupFunctionEntry 106 * \brief Locates the RUNTIME_FUNCTION entry corresponding to a code address. 107 * \ref http://msdn.microsoft.com/en-us/library/ms680597(VS.85).aspx 108 * \todo Implement HistoryTable 109 */ 110 PRUNTIME_FUNCTION 111 NTAPI 112 RtlLookupFunctionEntry( 113 IN DWORD64 ControlPc, 114 OUT PDWORD64 ImageBase, 115 OUT PUNWIND_HISTORY_TABLE HistoryTable) 116 { 117 PRUNTIME_FUNCTION FunctionTable, FunctionEntry; 118 ULONG TableLength; 119 ULONG IndexLo, IndexHi, IndexMid; 120 121 /* Find the corresponding table */ 122 FunctionTable = RtlLookupFunctionTable(ControlPc, ImageBase, &TableLength); 123 124 /* Fail, if no table is found */ 125 if (!FunctionTable) 126 { 127 return NULL; 128 } 129 130 /* Use relative virtual address */ 131 ControlPc -= *ImageBase; 132 133 /* Do a binary search */ 134 IndexLo = 0; 135 IndexHi = TableLength; 136 while (IndexHi > IndexLo) 137 { 138 IndexMid = (IndexLo + IndexHi) / 2; 139 FunctionEntry = &FunctionTable[IndexMid]; 140 141 if (ControlPc < FunctionEntry->BeginAddress) 142 { 143 /* Continue search in lower half */ 144 IndexHi = IndexMid; 145 } 146 else if (ControlPc >= FunctionEntry->EndAddress) 147 { 148 /* Continue search in upper half */ 149 IndexLo = IndexMid + 1; 150 } 151 else 152 { 153 /* ControlPc is within limits, return entry */ 154 return FunctionEntry; 155 } 156 } 157 158 /* Nothing found, return NULL */ 159 return NULL; 160 } 161 162 BOOLEAN 163 NTAPI 164 RtlAddFunctionTable( 165 IN PRUNTIME_FUNCTION FunctionTable, 166 IN DWORD EntryCount, 167 IN DWORD64 BaseAddress) 168 { 169 UNIMPLEMENTED; 170 return FALSE; 171 } 172 173 BOOLEAN 174 NTAPI 175 RtlDeleteFunctionTable( 176 IN PRUNTIME_FUNCTION FunctionTable) 177 { 178 UNIMPLEMENTED; 179 return FALSE; 180 } 181 182 BOOLEAN 183 NTAPI 184 RtlInstallFunctionTableCallback( 185 IN DWORD64 TableIdentifier, 186 IN DWORD64 BaseAddress, 187 IN DWORD Length, 188 IN PGET_RUNTIME_FUNCTION_CALLBACK Callback, 189 IN PVOID Context, 190 IN PCWSTR OutOfProcessCallbackDll) 191 { 192 UNIMPLEMENTED; 193 return FALSE; 194 } 195 196 void 197 FORCEINLINE 198 SetReg(PCONTEXT Context, BYTE Reg, DWORD64 Value) 199 { 200 ((DWORD64*)(&Context->Rax))[Reg] = Value; 201 } 202 203 DWORD64 204 FORCEINLINE 205 GetReg(PCONTEXT Context, BYTE Reg) 206 { 207 return ((DWORD64*)(&Context->Rax))[Reg]; 208 } 209 210 void 211 FORCEINLINE 212 PopReg(PCONTEXT Context, BYTE Reg) 213 { 214 DWORD64 Value = *(DWORD64*)Context->Rsp; 215 Context->Rsp += 8; 216 SetReg(Context, Reg, Value); 217 } 218 219 /*! RtlpTryToUnwindEpilog 220 * \brief Helper function that tries to unwind epilog instructions. 221 * \return TRUE if we have been in an epilog and it could be unwound. 222 * FALSE if the instructions were not allowed for an epilog. 223 * \ref 224 * http://msdn.microsoft.com/en-us/library/8ydc79k6(VS.80).aspx 225 * http://msdn.microsoft.com/en-us/library/tawsa7cb.aspx 226 * \todo 227 * - Test and compare with Windows behaviour 228 */ 229 BOOLEAN 230 static 231 __inline 232 RtlpTryToUnwindEpilog( 233 PCONTEXT Context, 234 ULONG64 ImageBase, 235 PRUNTIME_FUNCTION FunctionEntry) 236 { 237 CONTEXT LocalContext; 238 BYTE *InstrPtr; 239 DWORD Instr; 240 BYTE Reg, Mod; 241 ULONG64 EndAddress; 242 243 /* Make a local copy of the context */ 244 LocalContext = *Context; 245 246 InstrPtr = (BYTE*)LocalContext.Rip; 247 248 /* Check if first instruction of epilog is "add rsp, x" */ 249 Instr = *(DWORD*)InstrPtr; 250 if ( (Instr & 0x00fffdff) == 0x00c48148 ) 251 { 252 if ( (Instr & 0x0000ff00) == 0x8300 ) 253 { 254 /* This is "add rsp, 0x??" */ 255 LocalContext.Rsp += Instr >> 24; 256 InstrPtr += 4; 257 } 258 else 259 { 260 /* This is "add rsp, 0x???????? */ 261 LocalContext.Rsp += *(DWORD*)(InstrPtr + 3); 262 InstrPtr += 7; 263 } 264 } 265 /* Check if first instruction of epilog is "lea rsp, ..." */ 266 else if ( (Instr & 0x38fffe) == 0x208d48 ) 267 { 268 /* Get the register */ 269 Reg = ((Instr << 8) | (Instr >> 16)) & 0x7; 270 271 LocalContext.Rsp = GetReg(&LocalContext, Reg); 272 273 /* Get adressing mode */ 274 Mod = (Instr >> 22) & 0x3; 275 if (Mod == 0) 276 { 277 /* No displacement */ 278 InstrPtr += 3; 279 } 280 else if (Mod == 1) 281 { 282 /* 1 byte displacement */ 283 LocalContext.Rsp += Instr >> 24; 284 InstrPtr += 4; 285 } 286 else if (Mod == 2) 287 { 288 /* 4 bytes displacement */ 289 LocalContext.Rsp += *(DWORD*)(InstrPtr + 3); 290 InstrPtr += 7; 291 } 292 } 293 294 /* Loop the following instructions before the ret */ 295 EndAddress = FunctionEntry->EndAddress + ImageBase - 1; 296 while ((DWORD64)InstrPtr < EndAddress) 297 { 298 Instr = *(DWORD*)InstrPtr; 299 300 /* Check for a simple pop */ 301 if ( (Instr & 0xf8) == 0x58 ) 302 { 303 /* Opcode pops a basic register from stack */ 304 Reg = Instr & 0x7; 305 PopReg(&LocalContext, Reg); 306 InstrPtr++; 307 continue; 308 } 309 310 /* Check for REX + pop */ 311 if ( (Instr & 0xf8fb) == 0x5841 ) 312 { 313 /* Opcode is pop r8 .. r15 */ 314 Reg = ((Instr >> 8) & 0x7) + 8; 315 PopReg(&LocalContext, Reg); 316 InstrPtr += 2; 317 continue; 318 } 319 320 /* Opcode not allowed for Epilog */ 321 return FALSE; 322 } 323 324 /* Check if we are at the ret instruction */ 325 if ((DWORD64)InstrPtr != EndAddress) 326 { 327 /* If we went past the end of the function, something is broken! */ 328 ASSERT((DWORD64)InstrPtr <= EndAddress); 329 return FALSE; 330 } 331 332 /* Make sure this is really a ret instruction */ 333 if (*InstrPtr != 0xc3) 334 { 335 ASSERT(FALSE); 336 return FALSE; 337 } 338 339 /* Unwind is finished, pop new Rip from Stack */ 340 LocalContext.Rip = *(DWORD64*)LocalContext.Rsp; 341 LocalContext.Rsp += sizeof(DWORD64); 342 343 *Context = LocalContext; 344 return TRUE; 345 } 346 347 PEXCEPTION_ROUTINE 348 NTAPI 349 RtlVirtualUnwind( 350 _In_ ULONG HandlerType, 351 _In_ ULONG64 ImageBase, 352 _In_ ULONG64 ControlPc, 353 _In_ PRUNTIME_FUNCTION FunctionEntry, 354 _Inout_ PCONTEXT Context, 355 _Outptr_ PVOID *HandlerData, 356 _Out_ PULONG64 EstablisherFrame, 357 _Inout_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers) 358 { 359 PUNWIND_INFO UnwindInfo; 360 ULONG_PTR CodeOffset; 361 ULONG i; 362 UNWIND_CODE UnwindCode; 363 BYTE Reg; 364 365 /* Use relative virtual address */ 366 ControlPc -= ImageBase; 367 368 /* Sanity checks */ 369 if ( (ControlPc < FunctionEntry->BeginAddress) || 370 (ControlPc >= FunctionEntry->EndAddress) ) 371 { 372 return NULL; 373 } 374 375 /* Get a pointer to the unwind info */ 376 UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData); 377 378 /* Calculate relative offset to function start */ 379 CodeOffset = ControlPc - FunctionEntry->BeginAddress; 380 381 /* Check if we are in the function epilog and try to finish it */ 382 if (CodeOffset > UnwindInfo->SizeOfProlog) 383 { 384 if (RtlpTryToUnwindEpilog(Context, ImageBase, FunctionEntry)) 385 { 386 /* There's no exception routine */ 387 return NULL; 388 } 389 } 390 391 /* Skip all Ops with an offset greater than the current Offset */ 392 i = 0; 393 while (i < UnwindInfo->CountOfCodes && 394 CodeOffset < UnwindInfo->UnwindCode[i].CodeOffset) 395 { 396 UnwindCode = UnwindInfo->UnwindCode[i]; 397 switch (UnwindCode.UnwindOp) 398 { 399 case UWOP_SAVE_NONVOL: 400 case UWOP_SAVE_XMM: 401 case UWOP_SAVE_XMM128: 402 i += 2; 403 break; 404 405 case UWOP_SAVE_NONVOL_FAR: 406 case UWOP_SAVE_XMM_FAR: 407 case UWOP_SAVE_XMM128_FAR: 408 i += 3; 409 break; 410 411 case UWOP_ALLOC_LARGE: 412 i += UnwindCode.OpInfo ? 3 : 2; 413 break; 414 415 default: 416 i++; 417 } 418 } 419 420 /* Process the remaining unwind ops */ 421 while (i < UnwindInfo->CountOfCodes) 422 { 423 UnwindCode = UnwindInfo->UnwindCode[i]; 424 switch (UnwindCode.UnwindOp) 425 { 426 case UWOP_PUSH_NONVOL: 427 Reg = UnwindCode.OpInfo; 428 SetReg(Context, Reg, *(DWORD64*)Context->Rsp); 429 Context->Rsp += sizeof(DWORD64); 430 i++; 431 break; 432 433 case UWOP_ALLOC_LARGE: 434 if (UnwindCode.OpInfo) 435 { 436 ULONG Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]); 437 Context->Rsp += Offset; 438 i += 3; 439 } 440 else 441 { 442 USHORT Offset = UnwindInfo->UnwindCode[i+1].FrameOffset; 443 Context->Rsp += Offset * 8; 444 i += 2; 445 } 446 break; 447 448 case UWOP_ALLOC_SMALL: 449 Context->Rsp += (UnwindCode.OpInfo + 1) * 8; 450 i++; 451 break; 452 453 case UWOP_SET_FPREG: 454 i++; 455 break; 456 457 case UWOP_SAVE_NONVOL: 458 i += 2; 459 break; 460 461 case UWOP_SAVE_NONVOL_FAR: 462 i += 3; 463 break; 464 465 case UWOP_SAVE_XMM: 466 i += 2; 467 break; 468 469 case UWOP_SAVE_XMM_FAR: 470 i += 3; 471 break; 472 473 case UWOP_SAVE_XMM128: 474 i += 2; 475 break; 476 477 case UWOP_SAVE_XMM128_FAR: 478 i += 3; 479 break; 480 481 case UWOP_PUSH_MACHFRAME: 482 i += 1; 483 break; 484 } 485 } 486 487 /* Unwind is finished, pop new Rip from Stack */ 488 Context->Rip = *(DWORD64*)Context->Rsp; 489 Context->Rsp += sizeof(DWORD64); 490 491 return 0; 492 } 493 494 VOID 495 NTAPI 496 RtlUnwindEx( 497 _In_opt_ PVOID TargetFrame, 498 _In_opt_ PVOID TargetIp, 499 _In_opt_ PEXCEPTION_RECORD ExceptionRecord, 500 _In_ PVOID ReturnValue, 501 _In_ PCONTEXT ContextRecord, 502 _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable) 503 { 504 __debugbreak(); 505 return; 506 } 507 508 VOID 509 NTAPI 510 RtlUnwind( 511 IN PVOID TargetFrame, 512 IN PVOID TargetIp, 513 IN PEXCEPTION_RECORD ExceptionRecord, 514 IN PVOID ReturnValue) 515 { 516 UNIMPLEMENTED; 517 return; 518 } 519 520 ULONG 521 NTAPI 522 RtlWalkFrameChain(OUT PVOID *Callers, 523 IN ULONG Count, 524 IN ULONG Flags) 525 { 526 CONTEXT Context; 527 ULONG64 ControlPc, ImageBase, EstablisherFrame; 528 ULONG64 StackLow, StackHigh; 529 PVOID HandlerData; 530 ULONG i, FramesToSkip; 531 PRUNTIME_FUNCTION FunctionEntry; 532 533 DPRINT("Enter RtlWalkFrameChain\n"); 534 535 /* The upper bits in Flags define how many frames to skip */ 536 FramesToSkip = Flags >> 8; 537 538 /* Capture the current Context */ 539 RtlCaptureContext(&Context); 540 ControlPc = Context.Rip; 541 542 /* Get the stack limits */ 543 RtlpGetStackLimits(&StackLow, &StackHigh); 544 545 /* Check if we want the user-mode stack frame */ 546 if (Flags & 1) 547 { 548 } 549 550 /* Loop the frames */ 551 for (i = 0; i < FramesToSkip + Count; i++) 552 { 553 /* Lookup the FunctionEntry for the current ControlPc */ 554 FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL); 555 556 /* Is this a leaf function? */ 557 if (!FunctionEntry) 558 { 559 Context.Rip = *(DWORD64*)Context.Rsp; 560 Context.Rsp += sizeof(DWORD64); 561 DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp); 562 } 563 else 564 { 565 RtlVirtualUnwind(0, 566 ImageBase, 567 ControlPc, 568 FunctionEntry, 569 &Context, 570 &HandlerData, 571 &EstablisherFrame, 572 NULL); 573 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp); 574 } 575 576 /* Check if new Rip is valid */ 577 if (!Context.Rip) 578 { 579 break; 580 } 581 582 /* Check, if we have left our stack */ 583 if ((Context.Rsp < StackLow) || (Context.Rsp > StackHigh)) 584 { 585 break; 586 } 587 588 /* Continue with new Rip */ 589 ControlPc = Context.Rip; 590 591 /* Save value, if we are past the frames to skip */ 592 if (i >= FramesToSkip) 593 { 594 Callers[i - FramesToSkip] = (PVOID)ControlPc; 595 } 596 } 597 598 DPRINT("RtlWalkFrameChain returns %ld\n", i); 599 return i; 600 } 601 602 /*! RtlGetCallersAddress 603 * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html 604 */ 605 #undef RtlGetCallersAddress 606 VOID 607 NTAPI 608 RtlGetCallersAddress( 609 OUT PVOID *CallersAddress, 610 OUT PVOID *CallersCaller ) 611 { 612 PVOID Callers[4]; 613 ULONG Number; 614 615 /* Get callers: 616 * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */ 617 Number = RtlWalkFrameChain(Callers, 4, 0); 618 619 *CallersAddress = (Number >= 3) ? Callers[2] : NULL; 620 *CallersCaller = (Number == 4) ? Callers[3] : NULL; 621 622 return; 623 } 624 625 // FIXME: move to different file 626 VOID 627 NTAPI 628 RtlRaiseException(IN PEXCEPTION_RECORD ExceptionRecord) 629 { 630 CONTEXT Context; 631 NTSTATUS Status = STATUS_INVALID_DISPOSITION; 632 ULONG64 ImageBase; 633 PRUNTIME_FUNCTION FunctionEntry; 634 PVOID HandlerData; 635 ULONG64 EstablisherFrame; 636 637 /* Capture the context */ 638 RtlCaptureContext(&Context); 639 640 /* Get the function entry for this function */ 641 FunctionEntry = RtlLookupFunctionEntry(Context.Rip, 642 &ImageBase, 643 NULL); 644 645 /* Check if we found it */ 646 if (FunctionEntry) 647 { 648 /* Unwind to the caller of this function */ 649 RtlVirtualUnwind(UNW_FLAG_NHANDLER, 650 ImageBase, 651 Context.Rip, 652 FunctionEntry, 653 &Context, 654 &HandlerData, 655 &EstablisherFrame, 656 NULL); 657 658 /* Save the exception address */ 659 ExceptionRecord->ExceptionAddress = (PVOID)Context.Rip; 660 661 /* Write the context flag */ 662 Context.ContextFlags = CONTEXT_FULL; 663 664 /* Check if user mode debugger is active */ 665 if (RtlpCheckForActiveDebugger()) 666 { 667 /* Raise an exception immediately */ 668 Status = ZwRaiseException(ExceptionRecord, &Context, TRUE); 669 } 670 else 671 { 672 /* Dispatch the exception and check if we should continue */ 673 if (!RtlDispatchException(ExceptionRecord, &Context)) 674 { 675 /* Raise the exception */ 676 Status = ZwRaiseException(ExceptionRecord, &Context, FALSE); 677 } 678 else 679 { 680 /* Continue, go back to previous context */ 681 Status = ZwContinue(&Context, FALSE); 682 } 683 } 684 } 685 686 /* If we returned, raise a status */ 687 RtlRaiseStatus(Status); 688 } 689 690