[REACTOS] Fix 64 bit build (#465)
[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
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