1 /**
2  * Support for NT exception handling
3  *
4  * Compiler implementation of the
5  * $(LINK2 https://www.dlang.org, D programming language).
6  *
7  * Copyright:   Copyright (C) 1994-1998 by Symantec
8  *              Copyright (C) 2000-2023 by The D Language Foundation, All Rights Reserved
9  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
10  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
11  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/backend/nteh.d, backend/nteh.d)
12  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/backend/nteh.d
13  */
14 
15 module dmd.backend.nteh;
16 
17 import core.stdc.stdio;
18 import core.stdc.string;
19 
20 import dmd.backend.cc;
21 import dmd.backend.cdef;
22 import dmd.backend.code;
23 import dmd.backend.code_x86;
24 import dmd.backend.codebuilder : CodeBuilder;
25 import dmd.backend.dt;
26 import dmd.backend.el;
27 import dmd.backend.global;
28 import dmd.backend.oper;
29 import dmd.backend.rtlsym;
30 import dmd.backend.symtab;
31 import dmd.backend.ty;
32 import dmd.backend.type;
33 
34 static if (NTEXCEPTIONS)
35 {
36 
37 
38 nothrow:
39 @safe:
40 
41 import dmd.backend.eh : except_fillInEHTable;
42 
43 private __gshared
44 {
45     Symbol *s_table;
46     //Symbol *s_context;
47 }
48 
49 private
50 {
51     //immutable string s_name_context_tag = "__nt_context";
52     immutable string s_name_context = "__context";
53 }
54 
55 // member stable is not used for MARS or C++
56 
57 int nteh_EBPoffset_sindex()     { return -4; }
58 int nteh_EBPoffset_prev()       { return -nteh_contextsym_size() + 8; }
59 int nteh_EBPoffset_info()       { return -nteh_contextsym_size() + 4; }
60 int nteh_EBPoffset_esp()        { return -nteh_contextsym_size() + 0; }
61 
62 int nteh_offset_sindex()        { return 16; }
63 int nteh_offset_sindex_seh()    { return 20; }
64 int nteh_offset_info()          { return 4; }
65 
66 /***********************************
67  */
68 
69 @trusted
70 ubyte *nteh_context_string()
71 {
72     if (config.exe == EX_WIN32)
73     {
74         immutable string text_nt =
75             "struct __nt_context {" ~
76                 "int esp; int info; int prev; int handler; int stable; int sindex; int ebp;" ~
77              "};\n";
78 
79         return cast(ubyte *)text_nt.ptr;
80     }
81     else
82         return null;
83 }
84 
85 /*******************************
86  * Get symbol for scope table for current function.
87  * Returns:
88  *      symbol of table
89  */
90 
91 @trusted
92 private Symbol *nteh_scopetable()
93 {
94     Symbol *s;
95     type *t;
96 
97     if (!s_table)
98     {
99         t = type_alloc(TYint);
100         s = symbol_generate(SC.static_,t);
101         s.Sseg = UNKNOWN;
102         symbol_keep(s);
103         s_table = s;
104     }
105     return s_table;
106 }
107 
108 /*************************************
109  */
110 
111 @trusted
112 void nteh_filltables()
113 {
114     Symbol *s = s_table;
115     symbol_debug(s);
116     except_fillInEHTable(s);
117 }
118 
119 /****************************
120  * Generate and output scope table.
121  * Not called for NTEH C++ exceptions
122  */
123 
124 @trusted
125 void nteh_gentables(Symbol *sfunc)
126 {
127     Symbol *s = s_table;
128     symbol_debug(s);
129     //except_fillInEHTable(s);
130 
131     outdata(s);                 // output the scope table
132     nteh_framehandler(sfunc, s);
133     s_table = null;
134 }
135 
136 /**************************
137  * Declare frame variables.
138  */
139 
140 @trusted
141 void nteh_declarvars(Blockx *bx)
142 {
143     //printf("nteh_declarvars()\n");
144     if (!(bx.funcsym.Sfunc.Fflags3 & Fnteh)) // if haven't already done it
145     {   bx.funcsym.Sfunc.Fflags3 |= Fnteh;
146         Symbol* s = symbol_name(s_name_context,SC.bprel,tstypes[TYint]);
147         s.Soffset = -5 * 4;            // -6 * 4 for C __try, __except, __finally
148         s.Sflags |= SFLfree | SFLnodebug;
149         type_setty(&s.Stype,mTYvolatile | TYint);
150         symbol_add(s);
151         bx.context = s;
152     }
153 }
154 
155 /**************************************
156  * Generate elem that sets the context index into the scope table.
157  */
158 elem *nteh_setScopeTableIndex(Blockx *blx, int scope_index)
159 {
160     Symbol* s = blx.context;
161     symbol_debug(s);
162     elem* e = el_var(s);
163     e.EV.Voffset = nteh_offset_sindex();
164     return el_bin(OPeq, TYint, e, el_long(TYint, scope_index));
165 }
166 
167 
168 /**********************************
169  * Returns: pointer to context symbol.
170  */
171 
172 @trusted
173 Symbol *nteh_contextsym()
174 {
175     foreach (Symbol* sp; globsym)
176     {
177         symbol_debug(sp);
178         if (strcmp(sp.Sident.ptr,s_name_context.ptr) == 0)
179             return sp;
180     }
181     assert(0);
182 }
183 
184 /**********************************
185  * Returns: size of context symbol on stack.
186  */
187 @trusted
188 uint nteh_contextsym_size()
189 {
190     int sz;
191 
192     if (usednteh & NTEH_try)
193     {
194         sz = 5 * 4;
195     }
196     else if (usednteh & NTEHcpp)
197     {
198         sz = 5 * 4;                     // C++ context record
199     }
200     else if (usednteh & NTEHpassthru)
201     {
202         sz = 1 * 4;
203     }
204     else
205         sz = 0;                         // no context record
206     return sz;
207 }
208 
209 /**********************************
210  * Return: pointer to ecode symbol.
211  */
212 
213 @trusted
214 Symbol *nteh_ecodesym()
215 {
216     foreach (Symbol* sp; globsym)
217     {
218         if (strcmp(sp.Sident.ptr, "__ecode") == 0)
219             return sp;
220     }
221     assert(0);
222 }
223 
224 /*********************************
225  * Mark EH variables as used so that they don't get optimized away.
226  */
227 
228 void nteh_usevars()
229 {
230     // Turn off SFLdead and SFLunambig in Sflags
231     nteh_contextsym().Sflags &= ~SFLdead;
232     nteh_contextsym().Sflags |= SFLread;
233 }
234 
235 /*********************************
236  * Generate NT exception handling function prolog.
237  */
238 
239 @trusted
240 void nteh_prolog(ref CodeBuilder cdb)
241 {
242     code cs;
243 
244     if (usednteh & NTEHpassthru)
245     {
246         /* An sindex value of -2 is a magic value that tells the
247          * stack unwinder to skip this frame.
248          */
249         assert(config.exe & EX_posix);
250         cs.Iop = 0x68;
251         cs.Iflags = 0;
252         cs.Irex = 0;
253         cs.IFL2 = FLconst;
254         cs.IEV2.Vint = -2;
255         cdb.gen(&cs);                           // PUSH -2
256         return;
257     }
258 
259     /* Generate instance of struct __nt_context on stack frame:
260         [  ]                                    // previous ebp already there
261         push    -1                              // sindex
262         mov     EDX,FS:__except_list
263         push    offset FLAT:scope_table         // stable (not for MARS or C++)
264         push    offset FLAT:__except_handler3   // handler
265         push    EDX                             // prev
266         mov     FS:__except_list,ESP
267         sub     ESP,8                           // info, esp for __except support
268      */
269 
270 //    useregs(mAX);                     // What is this for?
271 
272     cs.Iop = 0x68;
273     cs.Iflags = 0;
274     cs.Irex = 0;
275     cs.IFL2 = FLconst;
276     cs.IEV2.Vint = -1;
277     cdb.gen(&cs);                 // PUSH -1
278 
279     // PUSH &framehandler
280     cs.IFL2 = FLframehandler;
281     nteh_scopetable();
282 
283 
284     CodeBuilder cdb2;
285     cdb2.ctor();
286     cdb2.gen(&cs);                          // PUSH &__except_handler3
287 
288     if (config.exe == EX_WIN32)
289     {
290         makeitextern(getRtlsym(RTLSYM.EXCEPT_LIST));
291     static if (0)
292     {
293         cs.Iop = 0xFF;
294         cs.Irm = modregrm(0,6,BPRM);
295         cs.Iflags = CFfs;
296         cs.Irex = 0;
297         cs.IFL1 = FLextern;
298         cs.IEV1.Vsym = getRtlsym(RTLSYM.EXCEPT_LIST);
299         cs.IEV1.Voffset = 0;
300         cdb2.gen(&cs);                             // PUSH FS:__except_list
301     }
302     else
303     {
304         useregs(mDX);
305         cs.Iop = 0x8B;
306         cs.Irm = modregrm(0,DX,BPRM);
307         cs.Iflags = CFfs;
308         cs.Irex = 0;
309         cs.IFL1 = FLextern;
310         cs.IEV1.Vsym = getRtlsym(RTLSYM.EXCEPT_LIST);
311         cs.IEV1.Voffset = 0;
312         cdb.gen(&cs);                            // MOV EDX,FS:__except_list
313 
314         cdb2.gen1(0x50 + DX);                      // PUSH EDX
315     }
316         cs.Iop = 0x89;
317         NEWREG(cs.Irm,SP);
318         cdb2.gen(&cs);                             // MOV FS:__except_list,ESP
319     }
320 
321     cdb.append(cdb2);
322     cod3_stackadj(cdb, 8);
323 }
324 
325 /*********************************
326  * Generate NT exception handling function epilog.
327  */
328 
329 @trusted
330 void nteh_epilog(ref CodeBuilder cdb)
331 {
332     if (config.exe != EX_WIN32)
333         return;
334 
335     /* Generate:
336         mov     ECX,__context[EBP].prev
337         mov     FS:__except_list,ECX
338      */
339     reg_t reg = CX;
340     useregs(1 << reg);
341 
342     code cs;
343     cs.Iop = 0x8B;
344     cs.Irm = modregrm(2,reg,BPRM);
345     cs.Iflags = 0;
346     cs.Irex = 0;
347     cs.IFL1 = FLconst;
348     // EBP offset of __context.prev
349     cs.IEV1.Vint = nteh_EBPoffset_prev();
350     cdb.gen(&cs);
351 
352     cs.Iop = 0x89;
353     cs.Irm = modregrm(0,reg,BPRM);
354     cs.Iflags |= CFfs;
355     cs.IFL1 = FLextern;
356     cs.IEV1.Vsym = getRtlsym(RTLSYM.EXCEPT_LIST);
357     cs.IEV1.Voffset = 0;
358     cdb.gen(&cs);
359 }
360 
361 /**************************
362  * Set/Reset ESP from context.
363  */
364 
365 @trusted
366 void nteh_setsp(ref CodeBuilder cdb, opcode_t op)
367 {
368     code cs;
369     cs.Iop = op;
370     cs.Irm = modregrm(2,SP,BPRM);
371     cs.Iflags = 0;
372     cs.Irex = 0;
373     cs.IFL1 = FLconst;
374     // EBP offset of __context.esp
375     cs.IEV1.Vint = nteh_EBPoffset_esp();
376     cdb.gen(&cs);               // MOV ESP,__context[EBP].esp
377 }
378 
379 /****************************
380  * Put out prolog for BC_filter block.
381  */
382 
383 @trusted
384 void nteh_filter(ref CodeBuilder cdb, block *b)
385 {
386     assert(b.BC == BC_filter);
387     if (b.Bflags & BFLehcode)          // if referenced __ecode
388     {
389         /* Generate:
390                 mov     EAX,__context[EBP].info
391                 mov     EAX,[EAX]
392                 mov     EAX,[EAX]
393                 mov     __ecode[EBP],EAX
394          */
395 
396         getregs(cdb,mAX);
397 
398         code cs;
399         cs.Iop = 0x8B;
400         cs.Irm = modregrm(2,AX,BPRM);
401         cs.Iflags = 0;
402         cs.Irex = 0;
403         cs.IFL1 = FLconst;
404         // EBP offset of __context.info
405         cs.IEV1.Vint = nteh_EBPoffset_info();
406         cdb.gen(&cs);                 // MOV EAX,__context[EBP].info
407 
408         cs.Irm = modregrm(0,AX,0);
409         cdb.gen(&cs);                     // MOV EAX,[EAX]
410         cdb.gen(&cs);                     // MOV EAX,[EAX]
411 
412         cs.Iop = 0x89;
413         cs.Irm = modregrm(2,AX,BPRM);
414         cs.IFL1 = FLauto;
415         cs.IEV1.Vsym = nteh_ecodesym();
416         cs.IEV1.Voffset = 0;
417         cdb.gen(&cs);                     // MOV __ecode[EBP],EAX
418     }
419 }
420 
421 /*******************************
422  * Generate C++ or D frame handler.
423  */
424 
425 void nteh_framehandler(Symbol *sfunc, Symbol *scopetable)
426 {
427     // Generate:
428     //  MOV     EAX,&scope_table
429     //  JMP     __cpp_framehandler
430 
431     if (scopetable)
432     {
433         symbol_debug(scopetable);
434         CodeBuilder cdb;
435         cdb.ctor();
436         cdb.gencs(0xB8+AX,0,FLextern,scopetable);  // MOV EAX,&scope_table
437 
438         cdb.gencs(0xE9,0,FLfunc,getRtlsym(RTLSYM.D_HANDLER));      // JMP _d_framehandler
439 
440         code *c = cdb.finish();
441         pinholeopt(c,null);
442         codout(sfunc.Sseg,c,null);
443         code_free(c);
444     }
445 }
446 
447 /*********************************
448  * Generate code to set scope index.
449  */
450 
451 code *nteh_patchindex(code* c, int sindex)
452 {
453     c.IEV2.Vsize_t = sindex;
454     return c;
455 }
456 
457 @trusted
458 void nteh_gensindex(ref CodeBuilder cdb, int sindex)
459 {
460     if (!(config.ehmethod == EHmethod.EH_WIN32 || config.ehmethod == EHmethod.EH_SEH) || funcsym_p.Sfunc.Fflags3 & Feh_none)
461         return;
462     // Generate:
463     //  MOV     -4[EBP],sindex
464 
465     cdb.genc(0xC7,modregrm(1,0,BP),FLconst,cast(targ_uns)nteh_EBPoffset_sindex(),FLconst,sindex); // 7 bytes long
466     cdb.last().Iflags |= CFvolatile;
467 
468     //assert(GENSINDEXSIZE == calccodsize(c));
469 }
470 
471 /*********************************
472  * Generate code for setjmp().
473  */
474 
475 @trusted
476 void cdsetjmp(ref CodeBuilder cdb, elem *e,regm_t *pretregs)
477 {
478     code cs;
479     regm_t retregs;
480     uint flag;
481 
482     const stackpushsave = stackpush;
483     if (funcsym_p.Sfunc.Fflags3 & Fnteh)
484     {
485         /*  If in NT SEH try block
486             If the frame that is calling setjmp has a try, except block
487             then the call to setjmp3 is as follows:
488               __setjmp3(environment,2,__seh_longjmp_unwind,trylevel);
489             __seth_longjmp_unwind is supplied by the RTL and is a stdcall
490             function. It is the name that MSOFT uses, we should
491             probably use the same one.
492             trylevel is the value that you increment at each try and
493             decrement at the close of the try.  This corresponds to the
494             index field of the ehrec.
495          */
496 
497         int sindex_off = 20;                // offset of __context.sindex
498         cs.Iop = 0xFF;
499         cs.Irm = modregrm(2,6,BPRM);
500         cs.Iflags = 0;
501         cs.Irex = 0;
502         cs.IFL1 = FLbprel;
503         cs.IEV1.Vsym = nteh_contextsym();
504         cs.IEV1.Voffset = sindex_off;
505         cdb.gen(&cs);                 // PUSH scope_index
506         stackpush += 4;
507         cdb.genadjesp(4);
508 
509         cs.Iop = 0x68;
510         cs.Iflags = CFoff;
511         cs.Irex = 0;
512         cs.IFL2 = FLextern;
513         cs.IEV2.Vsym = getRtlsym(RTLSYM.LONGJMP);
514         cs.IEV2.Voffset = 0;
515         cdb.gen(&cs);                 // PUSH &_seh_longjmp_unwind
516         stackpush += 4;
517         cdb.genadjesp(4);
518 
519         flag = 2;
520     }
521     else
522     {
523         /*  If the frame calling setjmp has neither a try..except, nor a
524             try..catch, then call setjmp3 as follows:
525             _setjmp3(environment,0)
526          */
527         flag = 0;
528     }
529     cs.Iop = 0x68;
530     cs.Iflags = 0;
531     cs.Irex = 0;
532     cs.IFL2 = FLconst;
533     cs.IEV2.Vint = flag;
534     cdb.gen(&cs);                     // PUSH flag
535     stackpush += 4;
536     cdb.genadjesp(4);
537 
538     pushParams(cdb,e.EV.E1,REGSIZE, TYnfunc);
539 
540     getregs(cdb,~getRtlsym(RTLSYM.SETJMP3).Sregsaved & (ALLREGS | mES));
541     cdb.gencs(0xE8,0,FLfunc,getRtlsym(RTLSYM.SETJMP3));      // CALL __setjmp3
542 
543     cod3_stackadj(cdb, -(stackpush - stackpushsave));
544     cdb.genadjesp(-(stackpush - stackpushsave));
545 
546     stackpush = stackpushsave;
547     retregs = regmask(e.Ety, TYnfunc);
548     fixresult(cdb,e,retregs,pretregs);
549 }
550 
551 /****************************************
552  * Call _local_unwind(), which means call the __finally blocks until
553  * stop_index is reached.
554  * Params:
555  *      cdb = append generated code to
556  *      saveregs = registers to save across the generated code
557  *      stop_index = index to stop at
558  */
559 
560 @trusted
561 void nteh_unwind(ref CodeBuilder cdb,regm_t saveregs,uint stop_index)
562 {
563     // Shouldn't this always be CX?
564     const reg_t reg = CX;
565 
566     // https://github.com/dlang/dmd/blob/cdfadf8a18f474e6a1b8352af2541efe3e3467cc/druntime/src/rt/deh_win32.d#L934
567     const local_unwind = RTLSYM.D_LOCAL_UNWIND2;    // __d_local_unwind2()
568 
569     const regm_t desregs = (~getRtlsym(local_unwind).Sregsaved & (ALLREGS)) | (1 << reg);
570     CodeBuilder cdbs;
571     cdbs.ctor();
572     CodeBuilder cdbr;
573     cdbr.ctor();
574     gensaverestore(saveregs & desregs,cdbs,cdbr);
575 
576     CodeBuilder cdbx;
577     cdbx.ctor();
578     getregs(cdbx,desregs);
579 
580     code cs;
581     cs.Iop = LEA;
582     cs.Irm = modregrm(2,reg,BPRM);
583     cs.Iflags = 0;
584     cs.Irex = 0;
585     cs.IFL1 = FLconst;
586     // EBP offset of __context.prev
587     cs.IEV1.Vint = nteh_EBPoffset_prev();
588     cdbx.gen(&cs);                             // LEA  ECX,contextsym
589 
590     int nargs = 0;
591 
592     cdbx.genc2(0x68,0,stop_index);                 // PUSH stop_index
593     cdbx.gen1(0x50 + reg);                         // PUSH ECX            ; DEstablisherFrame
594     nargs += 2;
595     cdbx.gencs(0x68,0,FLextern,nteh_scopetable());      // PUSH &scope_table    ; DHandlerTable
596     ++nargs;
597 
598     cdbx.gencs(0xE8,0,FLfunc,getRtlsym(local_unwind));  // CALL _local_unwind()
599     cod3_stackadj(cdbx, -nargs * 4);
600 
601     cdb.append(cdbs);
602     cdb.append(cdbx);
603     cdb.append(cdbr);
604 }
605 
606 /*************************************************
607  * Set monitor, hook monitor exception handler.
608  */
609 
610 @trusted
611 void nteh_monitor_prolog(ref CodeBuilder cdb, Symbol *shandle)
612 {
613     /*
614      *  PUSH    handle
615      *  PUSH    offset _d_monitor_handler
616      *  PUSH    FS:__except_list
617      *  MOV     FS:__except_list,ESP
618      *  CALL    _d_monitor_prolog
619      */
620     CodeBuilder cdbx;
621     cdbx.ctor();
622 
623     assert(config.exe == EX_WIN32);    // BUG: figure out how to implement for other EX's
624 
625     if (shandle.Sclass == SC.fastpar)
626     {   assert(shandle.Spreg != DX);
627         assert(shandle.Spreg2 == NOREG);
628         cdbx.gen1(0x50 + shandle.Spreg);   // PUSH shandle
629     }
630     else
631     {
632         // PUSH shandle
633         useregs(mCX);
634         cdbx.genc1(0x8B,modregrm(2,CX,4),FLconst,4 * (1 + needframe) + shandle.Soffset + localsize);
635         cdbx.last().Isib = modregrm(0,4,SP);
636         cdbx.gen1(0x50 + CX);                      // PUSH ECX
637     }
638 
639     Symbol *smh = getRtlsym(RTLSYM.MONITOR_HANDLER);
640     cdbx.gencs(0x68,0,FLextern,smh);             // PUSH offset _d_monitor_handler
641     makeitextern(smh);
642 
643     code cs;
644     useregs(mDX);
645     cs.Iop = 0x8B;
646     cs.Irm = modregrm(0,DX,BPRM);
647     cs.Iflags = CFfs;
648     cs.Irex = 0;
649     cs.IFL1 = FLextern;
650     cs.IEV1.Vsym = getRtlsym(RTLSYM.EXCEPT_LIST);
651     cs.IEV1.Voffset = 0;
652     cdb.gen(&cs);                   // MOV EDX,FS:__except_list
653 
654     cdbx.gen1(0x50 + DX);                  // PUSH EDX
655 
656     Symbol *s = getRtlsym(RTLSYM.MONITOR_PROLOG);
657     regm_t desregs = ~s.Sregsaved & ALLREGS;
658     getregs(cdbx,desregs);
659     cdbx.gencs(0xE8,0,FLfunc,s);       // CALL _d_monitor_prolog
660 
661     cs.Iop = 0x89;
662     NEWREG(cs.Irm,SP);
663     cdbx.gen(&cs);                         // MOV FS:__except_list,ESP
664 
665     cdb.append(cdbx);
666 }
667 
668 /*************************************************
669  * Release monitor, unhook monitor exception handler.
670  * Input:
671  *      retregs         registers to not destroy
672  */
673 
674 @trusted
675 void nteh_monitor_epilog(ref CodeBuilder cdb,regm_t retregs)
676 {
677     /*
678      *  CALL    _d_monitor_epilog
679      *  POP     FS:__except_list
680      */
681 
682     assert(config.exe == EX_WIN32);    // BUG: figure out how to implement for other EX's
683 
684     Symbol *s = getRtlsym(RTLSYM.MONITOR_EPILOG);
685     //desregs = ~s.Sregsaved & ALLREGS;
686     regm_t desregs = 0;
687     CodeBuilder cdbs;
688     cdbs.ctor();
689     CodeBuilder cdbr;
690     cdbr.ctor();
691     gensaverestore(retregs& desregs,cdbs,cdbr);
692     cdb.append(cdbs);
693 
694     getregs(cdb,desregs);
695     cdb.gencs(0xE8,0,FLfunc,s);               // CALL __d_monitor_epilog
696 
697     cdb.append(cdbr);
698 
699     code cs;
700     cs.Iop = 0x8F;
701     cs.Irm = modregrm(0,0,BPRM);
702     cs.Iflags = CFfs;
703     cs.Irex = 0;
704     cs.IFL1 = FLextern;
705     cs.IEV1.Vsym = getRtlsym(RTLSYM.EXCEPT_LIST);
706     cs.IEV1.Voffset = 0;
707     cdb.gen(&cs);                       // POP FS:__except_list
708 }
709 
710 }