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 }