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 }