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