Sync to trunk head(r38096)
[reactos.git] / reactos / lib / rtl / amd64 / unwind.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * PURPOSE: Exception 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 typedef unsigned char UBYTE;
32
33 typedef union _UNWIND_CODE
34 {
35 struct
36 {
37 UBYTE CodeOffset;
38 UBYTE UnwindOp:4;
39 UBYTE OpInfo:4;
40 };
41 USHORT FrameOffset;
42 } UNWIND_CODE, *PUNWIND_CODE;
43
44 typedef struct _UNWIND_INFO
45 {
46 UBYTE Version:3;
47 UBYTE Flags:5;
48 UBYTE SizeOfProlog;
49 UBYTE CountOfCodes;
50 UBYTE FrameRegister:4;
51 UBYTE FrameOffset:4;
52 UNWIND_CODE UnwindCode[1];
53 /* union {
54 OPTIONAL ULONG ExceptionHandler;
55 OPTIONAL ULONG FunctionEntry;
56 };
57 OPTIONAL ULONG ExceptionData[];
58 */
59 } UNWIND_INFO, *PUNWIND_INFO;
60
61 PVOID
62 NTAPI
63 RtlpLookupModuleBase(
64 PVOID Address);
65
66 /* FUNCTIONS *****************************************************************/
67
68 PRUNTIME_FUNCTION
69 NTAPI
70 RtlLookupFunctionTable(
71 IN DWORD64 ControlPc,
72 OUT PDWORD64 ImageBase,
73 OUT PULONG Length)
74 {
75 PIMAGE_DOS_HEADER DosHeader;
76 PIMAGE_NT_HEADERS NtHeader;
77 PIMAGE_DATA_DIRECTORY Directory;
78
79 /* Find ModuleBase */
80 DosHeader = RtlpLookupModuleBase((PVOID)ControlPc);
81 if (!DosHeader)
82 {
83 return NULL;
84 }
85
86 /* Locate NT header and check number of directories */
87 NtHeader = (PVOID)((ULONG64)DosHeader + DosHeader->e_lfanew);
88 if (NtHeader->OptionalHeader.NumberOfRvaAndSizes
89 < IMAGE_DIRECTORY_ENTRY_EXCEPTION)
90 {
91 return NULL;
92 }
93
94 /* Locate the exception directory */
95 Directory = &NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];
96 *Length = Directory->Size / sizeof(RUNTIME_FUNCTION);
97 *ImageBase = (ULONG64)DosHeader;
98 if (!Directory->VirtualAddress)
99 {
100 return NULL;
101 }
102
103 return (PVOID)((ULONG64)DosHeader + Directory->VirtualAddress);
104 }
105
106
107 // http://msdn.microsoft.com/en-us/library/ms680597(VS.85).aspx
108 PRUNTIME_FUNCTION
109 NTAPI
110 RtlLookupFunctionEntry(
111 IN DWORD64 ControlPc,
112 OUT PDWORD64 ImageBase,
113 OUT PUNWIND_HISTORY_TABLE HistoryTable)
114 {
115 PRUNTIME_FUNCTION FunctionTable, FunctionEntry;
116 ULONG TableLength;
117 ULONG IndexLo, IndexHi, IndexMid;
118
119 /* Find the corresponding table */
120 FunctionTable = RtlLookupFunctionTable(ControlPc, ImageBase, &TableLength);
121
122 /* Fail, if no table is found */
123 if (!FunctionTable)
124 {
125 return (PVOID)1;
126 }
127
128 /* Use relative virtual address */
129 ControlPc -= *ImageBase;
130
131 /* Do a binary search */
132 IndexLo = 0;
133 IndexHi = TableLength;
134 while (IndexHi > IndexLo)
135 {
136 IndexMid = (IndexLo + IndexHi) / 2;
137 FunctionEntry = &FunctionTable[IndexMid];
138
139 if ( (ControlPc >= FunctionEntry->BeginAddress) &&
140 (ControlPc < FunctionEntry->EndAddress) )
141 {
142 /* ControlPc is within limits, return entry */
143 return FunctionEntry;
144 }
145
146 if (ControlPc < FunctionEntry->BeginAddress)
147 {
148 /* Continue search in lower half */
149 IndexHi = IndexMid;
150 }
151 else
152 {
153 /* Continue search in upper half */
154 IndexLo = IndexMid + 1;
155 }
156 }
157
158 /* Nothing found, return NULL */
159 return NULL;
160 }
161
162 void
163 FORCEINLINE
164 SetReg(PCONTEXT Context, BYTE Reg, DWORD64 Value)
165 {
166 ((DWORD64*)(&Context->Rax))[Reg] = Value;
167 }
168
169 DWORD64
170 FORCEINLINE
171 GetReg(PCONTEXT Context, BYTE Reg)
172 {
173 return ((DWORD64*)(&Context->Rax))[Reg];
174 }
175
176 void
177 FORCEINLINE
178 PopReg(PCONTEXT Context, BYTE Reg)
179 {
180 DWORD64 Value = *(DWORD64*)Context->Rsp;
181 Context->Rsp += 8;
182 SetReg(Context, Reg, Value);
183 }
184
185 /* Helper function that tries to unwind epilog instructions.
186 * Returns TRUE we have been in an epilog and it could be unwound.
187 * FALSE if the instructions were not allowed for an epilog.
188 * References:
189 * http://msdn.microsoft.com/en-us/library/8ydc79k6(VS.80).aspx
190 * http://msdn.microsoft.com/en-us/library/tawsa7cb.aspx
191 * TODO:
192 * - Test and compare with Windows behaviour
193 */
194 BOOLEAN
195 static
196 inline
197 RtlpTryToUnwindEpilog(
198 PCONTEXT Context,
199 ULONG64 ImageBase,
200 PRUNTIME_FUNCTION FunctionEntry)
201 {
202 CONTEXT LocalContext;
203 BYTE *InstrPtr;
204 DWORD Instr;
205 BYTE Reg, Mod;
206 ULONG64 EndAddress;
207
208 /* Make a local copy of the context */
209 LocalContext = *Context;
210
211 InstrPtr = (BYTE*)LocalContext.Rip;
212
213 /* Check if first instruction of epilog is "add rsp, x" */
214 Instr = *(DWORD*)InstrPtr;
215 if ( (Instr & 0x00fffdff) == 0x00c48148 )
216 {
217 if ( (Instr & 0x0000ff00) == 0x8300 )
218 {
219 /* This is "add rsp, 0x??" */
220 LocalContext.Rsp += Instr >> 24;
221 InstrPtr += 4;
222 }
223 else
224 {
225 /* This is "add rsp, 0x???????? */
226 LocalContext.Rsp += *(DWORD*)(InstrPtr + 3);
227 InstrPtr += 7;
228 }
229 }
230 /* Check if first instruction of epilog is "lea rsp, ..." */
231 else if ( (Instr & 0x38fffe) == 0x208d48 )
232 {
233 /* Get the register */
234 Reg = ((Instr << 8) | (Instr >> 16)) & 0x7;
235
236 LocalContext.Rsp = GetReg(&LocalContext, Reg);
237
238 /* Get adressing mode */
239 Mod = (Instr >> 22) & 0x3;
240 if (Mod == 0)
241 {
242 /* No displacement */
243 InstrPtr += 3;
244 }
245 else if (Mod == 1)
246 {
247 /* 1 byte displacement */
248 LocalContext.Rsp += Instr >> 24;
249 InstrPtr += 4;
250 }
251 else if (Mod == 2)
252 {
253 /* 4 bytes displacement */
254 LocalContext.Rsp += *(DWORD*)(InstrPtr + 3);
255 InstrPtr += 7;
256 }
257 }
258
259 /* Loop the following instructions */
260 EndAddress = FunctionEntry->EndAddress + ImageBase;
261 while((DWORD64)InstrPtr < EndAddress)
262 {
263 Instr = *(DWORD*)InstrPtr;
264
265 /* Check for a simple pop */
266 if ( (Instr & 0xf8) == 0x58 )
267 {
268 /* Opcode pops a basic register from stack */
269 Reg = Instr & 0x7;
270 PopReg(&LocalContext, Reg);
271 InstrPtr++;
272 continue;
273 }
274
275 /* Check for REX + pop */
276 if ( (Instr & 0xf8fb) == 0x5841 )
277 {
278 /* Opcode is pop r8 .. r15 */
279 Reg = (Instr & 0x7) + 8;
280 PopReg(&LocalContext, Reg);
281 InstrPtr += 2;
282 continue;
283 }
284
285 /* Check for retn / retf */
286 if ( (Instr & 0xf7) == 0xc3 )
287 {
288 /* We are finished */
289 break;
290 }
291
292 /* Opcode not allowed for Epilog */
293 return FALSE;
294 }
295
296 /* Unwind is finished, pop new Rip from Stack */
297 LocalContext.Rip = *(DWORD64*)LocalContext.Rsp;
298 LocalContext.Rsp += sizeof(DWORD64);
299
300 *Context = LocalContext;
301 return TRUE;
302 }
303
304
305 PEXCEPTION_ROUTINE
306 NTAPI
307 RtlVirtualUnwind(
308 IN ULONG HandlerType,
309 IN ULONG64 ImageBase,
310 IN ULONG64 ControlPc,
311 IN PRUNTIME_FUNCTION FunctionEntry,
312 IN OUT PCONTEXT Context,
313 OUT PVOID *HandlerData,
314 OUT PULONG64 EstablisherFrame,
315 IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers)
316 {
317 PUNWIND_INFO UnwindInfo;
318 ULONG CodeOffset;
319 ULONG i;
320 UNWIND_CODE UnwindCode;
321 BYTE Reg;
322
323 /* Use relative virtual address */
324 ControlPc -= ImageBase;
325
326 /* Sanity checks */
327 if ( (ControlPc < FunctionEntry->BeginAddress) ||
328 (ControlPc >= FunctionEntry->EndAddress) )
329 {
330 return NULL;
331 }
332
333 /* Get a pointer to the unwind info */
334 UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
335
336 /* Calculate relative offset to function start */
337 CodeOffset = ControlPc - FunctionEntry->BeginAddress;
338
339 /* Check if we are in the function epilog and try to finish it */
340 if (CodeOffset > UnwindInfo->SizeOfProlog)
341 {
342 if (RtlpTryToUnwindEpilog(Context, ImageBase, FunctionEntry))
343 {
344 /* There's no exception routine */
345 return NULL;
346 }
347 }
348
349 /* Skip all Ops with an offset greater than the current Offset */
350 i = 0;
351 while (i < UnwindInfo->CountOfCodes &&
352 CodeOffset < UnwindInfo->UnwindCode[i].CodeOffset)
353 {
354 UnwindCode = UnwindInfo->UnwindCode[i];
355 switch (UnwindCode.UnwindOp)
356 {
357 case UWOP_SAVE_NONVOL:
358 case UWOP_SAVE_XMM:
359 case UWOP_SAVE_XMM128:
360 i += 2;
361 break;
362
363 case UWOP_SAVE_NONVOL_FAR:
364 case UWOP_SAVE_XMM_FAR:
365 case UWOP_SAVE_XMM128_FAR:
366 i += 3;
367 break;
368
369 case UWOP_ALLOC_LARGE:
370 i += UnwindCode.OpInfo ? 3 : 2;
371 break;
372
373 default:
374 i++;
375 }
376 }
377
378 /* Process the left Ops */
379 while (i < UnwindInfo->CountOfCodes)
380 {
381 UnwindCode = UnwindInfo->UnwindCode[i];
382 switch (UnwindCode.UnwindOp)
383 {
384 case UWOP_PUSH_NONVOL:
385 Reg = UnwindCode.OpInfo;
386 SetReg(Context, Reg, *(DWORD64*)Context->Rsp);
387 Context->Rsp += sizeof(DWORD64);
388 i++;
389 break;
390
391 case UWOP_ALLOC_LARGE:
392 if (UnwindCode.OpInfo)
393 {
394 ULONG Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]);
395 Context->Rsp += Offset;
396 i += 3;
397 }
398 else
399 {
400 USHORT Offset = UnwindInfo->UnwindCode[i+1].FrameOffset;
401 Context->Rsp += Offset * 8;
402 i += 2;
403 }
404 break;
405
406 case UWOP_ALLOC_SMALL:
407 Context->Rsp += (UnwindCode.OpInfo + 1) * 8;
408 i++;
409 break;
410
411 case UWOP_SET_FPREG:
412 i++;
413 break;
414
415 case UWOP_SAVE_NONVOL:
416 i += 2;
417 break;
418
419 case UWOP_SAVE_NONVOL_FAR:
420 i += 3;
421 break;
422
423 case UWOP_SAVE_XMM:
424 i += 2;
425 break;
426
427 case UWOP_SAVE_XMM_FAR:
428 i += 3;
429 case UWOP_SAVE_XMM128:
430 i += 2;
431 case UWOP_SAVE_XMM128_FAR:
432 i += 3;
433 case UWOP_PUSH_MACHFRAME:
434 i += 1;
435 }
436 }
437
438 /* Unwind is finished, pop new Rip from Stack */
439 Context->Rip = *(DWORD64*)Context->Rsp;
440 Context->Rsp += sizeof(DWORD64);
441
442 return 0;
443 }
444
445 VOID
446 NTAPI
447 RtlUnwindEx(
448 IN ULONG64 TargetFrame,
449 IN ULONG64 TargetIp,
450 IN PEXCEPTION_RECORD ExceptionRecord,
451 IN PVOID ReturnValue,
452 OUT PCONTEXT OriginalContext,
453 IN PUNWIND_HISTORY_TABLE HistoryTable)
454 {
455 UNIMPLEMENTED;
456 return;
457 }
458
459 VOID
460 NTAPI
461 RtlUnwind(
462 IN PVOID TargetFrame,
463 IN PVOID TargetIp,
464 IN PEXCEPTION_RECORD ExceptionRecord,
465 IN PVOID ReturnValue)
466 {
467 UNIMPLEMENTED;
468 return;
469 }
470
471 ULONG
472 NTAPI
473 RtlWalkFrameChain(OUT PVOID *Callers,
474 IN ULONG Count,
475 IN ULONG Flags)
476 {
477 CONTEXT Context;
478 ULONG64 ControlPc, ImageBase, EstablisherFrame;
479 ULONG64 StackLow, StackHigh;
480 PVOID HandlerData;
481 INT i;
482 PRUNTIME_FUNCTION FunctionEntry;
483
484 DPRINT("Enter RtlWalkFrameChain\n");
485
486 /* Capture the current Context */
487 RtlCaptureContext(&Context);
488 ControlPc = Context.Rip;
489
490 /* Get the stack limits */
491 RtlpGetStackLimits(&StackLow, &StackHigh);
492
493 /* Check if we want the user-mode stack frame */
494 if (Flags == 1)
495 {
496 }
497
498 /* Loop the frames */
499 for (i = 0; i < Count; i++)
500 {
501 /* Lookup the FunctionEntry for the current ControlPc */
502 FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
503
504 /* Is this a leaf function? */
505 if (!FunctionEntry)
506 {
507 Context.Rip = *(DWORD64*)Context.Rsp;
508 Context.Rsp += sizeof(DWORD64);
509 DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
510 }
511 else
512 {
513 RtlVirtualUnwind(0,
514 ImageBase,
515 ControlPc,
516 FunctionEntry,
517 &Context,
518 &HandlerData,
519 &EstablisherFrame,
520 NULL);
521 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
522 }
523
524 /* Check if new Rip is valid */
525 if (!Context.Rip)
526 {
527 break;
528 }
529
530 /* Check, if we have left our stack */
531 if ((Context.Rsp < StackLow) || (Context.Rsp > StackHigh))
532 {
533 break;
534 }
535
536 /* Save this frame and continue with new Rip */
537 ControlPc = Context.Rip;
538 Callers[i] = (PVOID)ControlPc;
539 }
540
541 DPRINT("RtlWalkFrameChain returns %ld\n", i);
542 return i;
543 }
544
545 // http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html
546 #undef RtlGetCallersAddress
547 VOID
548 NTAPI
549 RtlGetCallersAddress(
550 OUT PVOID *CallersAddress,
551 OUT PVOID *CallersCaller )
552 {
553 PVOID Callers[4];
554 ULONG Number;
555
556 /* Get callers:
557 * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */
558 Number = RtlWalkFrameChain(Callers, 4, 0);
559
560 if (CallersAddress)
561 {
562 *CallersAddress = (Number >= 3) ? Callers[2] : NULL;
563 }
564 if (CallersCaller)
565 {
566 *CallersCaller = (Number == 4) ? Callers[3] : NULL;
567 }
568
569 return;
570 }