1 /**
2  * Implementation of exception handling support routines for Win64.
3  *
4  * Note that this code also support POSIX, however since v2.070.0,
5  * DWARF exception handling is used instead when possible,
6  * as it provides better compatibility with C++.
7  *
8  * Copyright: Copyright Digital Mars 2000 - 2013.
9  * License: Distributed under the
10  *      $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
11  *    (See accompanying file LICENSE)
12  * Authors:   Walter Bright, Sean Kelly
13  * Source: $(DRUNTIMESRC rt/deh_win64_posix.d)
14  * See_Also: https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=vs-2019
15  */
16 
17 module rt.deh_win64_posix;
18 
19 version (Win64)
20     version = Win64_Posix;
21 version (Posix)
22     version = Win64_Posix;
23 
24 version (Win64_Posix):
25 
26 version (OSX)
27     version = Darwin;
28 else version (iOS)
29     version = Darwin;
30 else version (TVOS)
31     version = Darwin;
32 else version (WatchOS)
33     version = Darwin;
34 
35 //debug=PRINTF;
36 debug(PRINTF) import core.stdc.stdio : printf;
37 
38 extern (C)
39 {
40     int _d_isbaseof(ClassInfo oc, ClassInfo c);
41     void _d_createTrace(Throwable o, void* context);
42 }
43 
44 alias int function() fp_t;   // function pointer in ambient memory model
45 
46 // DHandlerInfo table is generated by except_gentables() in eh.c
47 
48 struct DHandlerInfo
49 {
50     uint offset;                // offset from function address to start of guarded section
51     uint endoffset;             // offset of end of guarded section
52     int prev_index;             // previous table index
53     uint cioffset;              // offset to DCatchInfo data from start of table (!=0 if try-catch)
54     size_t finally_offset;      // offset to finally code to execute
55                                 // (!=0 if try-finally)
56 }
57 
58 // Address of DHandlerTable, searched for by eh_finddata()
59 
60 struct DHandlerTable
61 {
62     uint espoffset;             // offset of ESP from EBP
63     uint retoffset;             // offset from start of function to return code
64     size_t nhandlers;           // dimension of handler_info[] (use size_t to set alignment of handler_info[])
65     DHandlerInfo[1] handler_info;
66 }
67 
68 struct DCatchBlock
69 {
70     ClassInfo type;             // catch type
71     size_t bpoffset;            // EBP offset of catch var
72     size_t codeoffset;          // catch handler offset
73 }
74 
75 // Create one of these for each try-catch
76 struct DCatchInfo
77 {
78     size_t ncatches;                    // number of catch blocks
79     DCatchBlock[1] catch_block;         // data for each catch block
80 }
81 
82 // One of these is generated for each function with try-catch or try-finally
83 
84 struct FuncTable
85 {
86     void *fptr;                 // pointer to start of function
87     DHandlerTable *handlertable; // eh data for this function
88     uint fsize;         // size of function in bytes
89 }
90 
91 private
92 {
93     struct InFlight
94     {
95         InFlight*   next;
96         void*       addr;
97         Throwable   t;
98     }
99 
100     InFlight* __inflight = null;
101 
102     /// __inflight is per-stack, not per-thread, and as such needs to be
103     /// swapped out on fiber context switches.
104     extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc
105     {
106         auto old = __inflight;
107         __inflight = cast(InFlight*)newContext;
108         return old;
109     }
110 }
111 
112 void terminate()
113 {
114     asm
115     {
116         hlt ;
117     }
118 }
119 
120 /*******************************************
121  * Given address that is inside a function,
122  * figure out which function it is in.
123  * Return DHandlerTable if there is one, NULL if not.
124  */
125 
126 immutable(FuncTable)* __eh_finddata(void *address)
127 {
128     import rt.sections;
129     foreach (ref sg; SectionGroup)
130     {
131         auto pstart = sg.ehTables.ptr;
132         auto pend = pstart + sg.ehTables.length;
133         if (auto ft = __eh_finddata(address, pstart, pend))
134             return ft;
135     }
136     return null;
137 }
138 
139 immutable(FuncTable)* __eh_finddata(void *address, immutable(FuncTable)* pstart, immutable(FuncTable)* pend)
140 {
141     debug(PRINTF) printf("FuncTable.sizeof = %p\n", FuncTable.sizeof);
142     debug(PRINTF) printf("__eh_finddata(address = %p)\n", address);
143     debug(PRINTF) printf("_deh_beg = %p, _deh_end = %p\n", pstart, pend);
144 
145     for (auto ft = pstart; 1; ft++)
146     {
147      Lagain:
148         if (ft >= pend)
149             break;
150 
151         version (Win64)
152         {
153             /* The MS Linker has an inexplicable and erratic tendency to insert
154              * 8 zero bytes between sections generated from different .obj
155              * files. This kludge tries to skip over them.
156              */
157             if (ft.fptr == null)
158             {
159                 ft = cast(immutable(FuncTable)*)(cast(void**)ft + 1);
160                 goto Lagain;
161             }
162         }
163 
164         debug(PRINTF) printf("  ft = %p, fptr = %p, handlertable = %p, fsize = x%03x\n",
165               ft, ft.fptr, ft.handlertable, ft.fsize);
166 
167         immutable(void)* fptr = ft.fptr;
168         version (Win64)
169         {
170             /* If linked with /DEBUG, the linker rewrites it so the function pointer points
171              * to a JMP to the actual code. The address will be in the actual code, so we
172              * need to follow the JMP.
173              */
174             if ((cast(ubyte*)fptr)[0] == 0xE9)
175             {   // JMP target = RIP of next instruction + signed 32 bit displacement
176                 fptr = fptr + 5 + *cast(int*)(fptr + 1);
177             }
178         }
179 
180         if (fptr <= address &&
181             address < cast(void *)(cast(char *)fptr + ft.fsize))
182         {
183             debug(PRINTF) printf("\tfound handler table\n");
184             return ft;
185         }
186     }
187     debug(PRINTF) printf("\tnot found\n");
188     return null;
189 }
190 
191 
192 /******************************
193  * Given EBP, find return address to caller, and caller's EBP.
194  * Input:
195  *   regbp       Value of EBP for current function
196  *   *pretaddr   Return address
197  * Output:
198  *   *pretaddr   return address to caller
199  * Returns:
200  *   caller's EBP
201  */
202 
203 size_t __eh_find_caller(size_t regbp, size_t *pretaddr)
204 {
205     size_t bp = *cast(size_t *)regbp;
206 
207     if (bp)         // if not end of call chain
208     {
209         // Perform sanity checks on new EBP.
210         // If it is screwed up, terminate() hopefully before we do more damage.
211         if (bp <= regbp)
212             // stack should grow to smaller values
213             terminate();
214 
215         *pretaddr = *cast(size_t *)(regbp + size_t.sizeof);
216     }
217     return bp;
218 }
219 
220 
221 /***********************************
222  * Throw a D object.
223  */
224 
225 extern (C) void _d_throwc(Throwable h)
226 {
227     size_t regebp;
228 
229     debug(PRINTF)
230     {
231         printf("_d_throw(h = %p, &h = %p)\n", h, &h);
232         printf("\tvptr = %p\n", *cast(void **)h);
233     }
234 
235     version (D_InlineAsm_X86)
236         asm
237         {
238             mov regebp,EBP  ;
239         }
240     else version (D_InlineAsm_X86_64)
241         asm
242         {
243             mov regebp,RBP  ;
244         }
245     else
246         static assert(0);
247 
248     /* Increment reference count if `h` is a refcounted Throwable
249      */
250     auto refcount = h.refcount();
251     if (refcount)       // non-zero means it's refcounted
252         h.refcount() = refcount + 1;
253 
254     _d_createTrace(h, null);
255 
256 //static uint abc;
257 //if (++abc == 2) *(char *)0=0;
258 
259 //int count = 0;
260     while (1)           // for each function on the stack
261     {
262         size_t retaddr;
263 
264         regebp = __eh_find_caller(regebp,&retaddr);
265         if (!regebp)
266         {   // if end of call chain
267             debug(PRINTF) printf("end of call chain\n");
268             break;
269         }
270 
271         debug(PRINTF) printf("found caller, EBP = %p, retaddr = %p\n", regebp, retaddr);
272 //if (++count == 12) *(char*)0=0;
273         auto func_table = __eh_finddata(cast(void *)retaddr);   // find static data associated with function
274         auto handler_table = func_table ? func_table.handlertable : null;
275         if (!handler_table)         // if no static data
276         {
277             debug(PRINTF) printf("no handler table\n");
278             continue;
279         }
280         auto funcoffset = cast(size_t)func_table.fptr;
281         version (Win64)
282         {
283             /* If linked with /DEBUG, the linker rewrites it so the function pointer points
284              * to a JMP to the actual code. The address will be in the actual code, so we
285              * need to follow the JMP.
286              */
287             if ((cast(ubyte*)funcoffset)[0] == 0xE9)
288             {   // JMP target = RIP of next instruction + signed 32 bit displacement
289                 funcoffset = funcoffset + 5 + *cast(int*)(funcoffset + 1);
290             }
291         }
292         auto spoff = handler_table.espoffset;
293         auto retoffset = handler_table.retoffset;
294 
295         debug(PRINTF)
296         {
297             printf("retaddr = %p\n", retaddr);
298             printf("regebp=%p, funcoffset=%p, spoff=x%x, retoffset=x%x\n",
299             regebp,funcoffset,spoff,retoffset);
300         }
301 
302         // Find start index for retaddr in static data
303         auto dim = handler_table.nhandlers;
304 
305         debug(PRINTF)
306         {
307             printf("handler_info[%d]:\n", dim);
308             for (uint i = 0; i < dim; i++)
309             {
310                 auto phi = &handler_table.handler_info.ptr[i];
311                 printf("\t[%d]: offset = x%04x, endoffset = x%04x, prev_index = %d, cioffset = x%04x, finally_offset = %x\n",
312                         i, phi.offset, phi.endoffset, phi.prev_index, phi.cioffset, phi.finally_offset);
313             }
314         }
315 
316         auto index = -1;
317         for (uint i = 0; i < dim; i++)
318         {
319             auto phi = &handler_table.handler_info.ptr[i];
320 
321             debug(PRINTF) printf("i = %d, phi.offset = %04x\n", i, funcoffset + phi.offset);
322             if (retaddr > funcoffset + phi.offset &&
323                 retaddr <= funcoffset + phi.endoffset)
324                 index = i;
325         }
326         debug(PRINTF) printf("index = %d\n", index);
327 
328         if (dim)
329         {
330             auto phi = &handler_table.handler_info.ptr[index+1];
331             debug(PRINTF) printf("next finally_offset %p\n", phi.finally_offset);
332             auto prev = cast(InFlight*) &__inflight;
333             auto curr = prev.next;
334 
335             if (curr !is null && curr.addr == cast(void*)(funcoffset + phi.finally_offset))
336             {
337                 auto e = cast(Error)(cast(Throwable) h);
338                 if (e !is null && (cast(Error) curr.t) is null)
339                 {
340                     debug(PRINTF) printf("new error %p bypassing inflight %p\n", h, curr.t);
341 
342                     e.bypassedException = curr.t;
343                     prev.next = curr.next;
344                     //h = cast(Object*) t;
345                 }
346                 else
347                 {
348                     debug(PRINTF) printf("replacing thrown %p with inflight %p\n", h, __inflight.t);
349 
350                     h = Throwable.chainTogether(curr.t, cast(Throwable) h);
351                     prev.next = curr.next;
352                 }
353             }
354         }
355 
356         // walk through handler table, checking each handler
357         // with an index smaller than the current table_index
358         int prev_ndx;
359         for (auto ndx = index; ndx != -1; ndx = prev_ndx)
360         {
361             auto phi = &handler_table.handler_info.ptr[ndx];
362             prev_ndx = phi.prev_index;
363             if (phi.cioffset)
364             {
365                 // this is a catch handler (no finally)
366 
367                 auto pci = cast(DCatchInfo *)(cast(char *)handler_table + phi.cioffset);
368                 auto ncatches = pci.ncatches;
369                 for (uint i = 0; i < ncatches; i++)
370                 {
371                     auto ci = **cast(ClassInfo **)h;
372 
373                     auto pcb = &pci.catch_block.ptr[i];
374 
375                     if (_d_isbaseof(ci, pcb.type))
376                     {
377                         // Matched the catch type, so we've found the handler.
378 
379                         // Initialize catch variable
380                         *cast(void **)(regebp + (pcb.bpoffset)) = cast(void*)h;
381 
382                         // Jump to catch block. Does not return.
383                         {
384                             size_t catch_esp;
385                             fp_t catch_addr;
386 
387                             catch_addr = cast(fp_t)(funcoffset + pcb.codeoffset);
388                             catch_esp = regebp - handler_table.espoffset - fp_t.sizeof;
389                             version (D_InlineAsm_X86)
390                                 asm
391                                 {
392                                     mov     EAX,catch_esp   ;
393                                     mov     ECX,catch_addr  ;
394                                     mov     [EAX],ECX       ;
395                                     mov     EBP,regebp      ;
396                                     mov     ESP,EAX         ; // reset stack
397                                     ret                     ; // jump to catch block
398                                 }
399                             else version (D_InlineAsm_X86_64)
400                                 asm
401                                 {
402                                     mov     RAX,catch_esp   ;
403                                     mov     RCX,catch_esp   ;
404                                     mov     RCX,catch_addr  ;
405                                     mov     [RAX],RCX       ;
406                                     mov     RBP,regebp      ;
407                                     mov     RSP,RAX         ; // reset stack
408                                     ret                     ; // jump to catch block
409                                 }
410                             else
411                                 static assert(0);
412                         }
413                     }
414                 }
415             }
416             else if (phi.finally_offset)
417             {
418                 // Call finally block
419                 // Note that it is unnecessary to adjust the ESP, as the finally block
420                 // accesses all items on the stack as relative to EBP.
421                 debug(PRINTF) printf("calling finally_offset %p\n", phi.finally_offset);
422 
423                 auto     blockaddr = cast(void*)(funcoffset + phi.finally_offset);
424                 InFlight inflight;
425 
426                 inflight.addr = blockaddr;
427                 inflight.next = __inflight;
428                 inflight.t    = cast(Throwable) h;
429                 __inflight    = &inflight;
430 
431                 version (Darwin)
432                 {
433                     version (D_InlineAsm_X86)
434                         asm
435                         {
436                             sub     ESP,4           ;
437                             push    EBX             ;
438                             mov     EBX,blockaddr   ;
439                             push    EBP             ;
440                             mov     EBP,regebp      ;
441                             call    EBX             ;
442                             pop     EBP             ;
443                             pop     EBX             ;
444                             add     ESP,4           ;
445                         }
446                     else version (D_InlineAsm_X86_64)
447                         asm
448                         {
449                             sub     RSP,8           ;
450                             push    RBX             ;
451                             mov     RBX,blockaddr   ;
452                             push    RBP             ;
453                             mov     RBP,regebp      ;
454                             call    RBX             ;
455                             pop     RBP             ;
456                             pop     RBX             ;
457                             add     RSP,8           ;
458                         }
459                     else
460                         static assert(0);
461                 }
462                 else
463                 {
464                     version (D_InlineAsm_X86)
465                         asm
466                         {
467                             push    EBX             ;
468                             mov     EBX,blockaddr   ;
469                             push    EBP             ;
470                             mov     EBP,regebp      ;
471                             call    EBX             ;
472                             pop     EBP             ;
473                             pop     EBX             ;
474                         }
475                     else version (D_InlineAsm_X86_64)
476                         asm
477                         {
478                             sub     RSP,8           ;
479                             push    RBX             ;
480                             mov     RBX,blockaddr   ;
481                             push    RBP             ;
482                             mov     RBP,regebp      ;
483                             call    RBX             ;
484                             pop     RBP             ;
485                             pop     RBX             ;
486                             add     RSP,8           ;
487                         }
488                     else
489                         static assert(0);
490                 }
491 
492                 if (__inflight is &inflight)
493                     __inflight = __inflight.next;
494             }
495         }
496     }
497     terminate();
498 }