/* Definitions for Dwarf2 EH unwind support for Windows32 targets Copyright (C) 2007-2024 Free Software Foundation, Inc. Contributed by Pascal Obry This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Under Section 7 of GPL version 3, you are granted additional permissions described in the GCC Runtime Library Exception, version 3.1, as published by the Free Software Foundation. You should have received a copy of the GNU General Public License and a copy of the GCC Runtime Library Exception along with this program; see the files COPYING3 and COPYING.RUNTIME respectively. If not, see . */ /* This file implements the md_fallback_frame_state_for routine for Windows, triggered when the GCC table based unwinding process hits a frame for which no unwind info has been registered. This typically occurs when raising an exception from a signal handler, because the handler is actually called from the OS kernel. The basic idea is to detect that we are indeed trying to unwind past a signal handler and to fill out the GCC internal unwinding structures for the OS kernel frame as if it had been directly called from the interrupted context. This is all assuming that the code to set the handler asked the kernel to pass a pointer to such context information. There is three main parts. 1) The first thing to do is to check if we are in a signal context. If not we can just return as there is nothing to do. We are probably on some foreign code for which no unwind frame can be found. If this is a call from the Windows signal handler, then: 2) We must get the signal context information. * With the standard exception filter: This is on Windows pointed to by an EXCEPTION_POINTERS. We know that the signal handle will call an UnhandledExceptionFilter with this parameter. The spec for this routine is: LONG WINAPI UnhandledExceptionFilter(struct _EXCEPTION_POINTERS*); So the pointer to struct _EXCEPTION_POINTERS must be somewhere on the stack. This was found experimentally to always be at offset 0 of the context frame in all cases handled by this implementation. * With the SEH exception handler: In this case the signal context is directly on the stack as the SEH exception handler has the following prototype: DWORD SEH_error_handler (PEXCEPTION_RECORD ExceptionRecord, PVOID EstablisherFrame, PCONTEXT ContextRecord, PVOID DispatcherContext) This was found experimentally to always be at offset 56 of the context frame in all cases handled by this implementation. 3) When we have the signal context we just have to save some registers and set the return address based on the program counter (Eip). Note that this implementation follows closely the same principles as the GNU/Linux and OSF ones. */ #ifndef __MINGW64__ #define WIN32_MEAN_AND_LEAN #include /* Patterns found experimentally to be on a Windows signal handler */ /* In a standard exception filter */ #define SIG_PAT1 \ (pc_[-2] == 0xff && pc_[-1] == 0xd0 /* call %eax */ \ && pc_[0] == 0x83 && pc_[1] == 0xf8) /* cmp 0xdepl,%eax */ #define SIG_PAT2 \ (pc_[-5] == 0xe8 && pc_[-4] == 0x68 /* call (depl16) */ \ && pc_[0] == 0xc3) /* ret */ /* In a Win32 SEH handler */ #define SIG_SEH1 \ (pc_[-5] == 0xe8 /* call addr */ \ && pc_[0] == 0x83 && pc_[1] == 0xc4 /* add 0xval,%esp */ \ && pc_[3] == 0xb8) /* mov 0xval,%eax */ #define SIG_SEH2 \ (pc_[-5] == 0x8b && pc_[-4] == 0x4d /* mov depl(%ebp),%ecx */ \ && pc_[0] == 0x64 && pc_[1] == 0x8b) /* mov %fs:(0), */ \ /* In the GCC alloca (stack probing) */ #define SIG_ALLOCA \ (pc_[-1] == 0x83 /* orl $0x0,(%ecx) */ \ && pc_[0] == 0x9 && pc_[1] == 0 \ && pc_[2] == 0x2d && pc_[3] == 0 /* subl $0x1000,%eax */ \ && pc_[4] == 0x10 && pc_[5] == 0) #define MD_FALLBACK_FRAME_STATE_FOR i386_w32_fallback_frame_state static _Unwind_Reason_Code i386_w32_fallback_frame_state (struct _Unwind_Context *context, _Unwind_FrameState *fs) { void * ctx_ra_ = (void *)(context->ra); /* return address */ void * ctx_cfa_ = (void *)(context->cfa); /* context frame address */ unsigned char * pc_ = (unsigned char *) ctx_ra_; /* In the test below we look for two specific patterns found experimentally to be in the Windows signal handler. */ if (SIG_PAT1 || SIG_PAT2 || SIG_SEH1 || SIG_SEH2) { PEXCEPTION_POINTERS weinfo_; PCONTEXT proc_ctx_; long new_cfa_; if (SIG_SEH1) proc_ctx_ = (PCONTEXT) (*(int*)(ctx_cfa_ + 56)); else if (SIG_SEH2) proc_ctx_ = (PCONTEXT) (*(int*)(ctx_cfa_ + 8)); else { weinfo_ = (PEXCEPTION_POINTERS) (*(int*)ctx_cfa_); proc_ctx_ = weinfo_->ContextRecord; } /* The new context frame address is the stack pointer. */ new_cfa_ = proc_ctx_->Esp; fs->regs.cfa_how = CFA_REG_OFFSET; fs->regs.cfa_reg = __builtin_dwarf_sp_column(); fs->regs.cfa_offset = new_cfa_ - (long) ctx_cfa_; /* Restore registers. */ fs->regs.how[0] = REG_SAVED_OFFSET; fs->regs.reg[0].loc.offset = (long)&proc_ctx_->Eax - new_cfa_; fs->regs.how[3] = REG_SAVED_OFFSET; fs->regs.reg[3].loc.offset = (long)&proc_ctx_->Ebx - new_cfa_; fs->regs.how[1] = REG_SAVED_OFFSET; fs->regs.reg[1].loc.offset = (long)&proc_ctx_->Ecx - new_cfa_; fs->regs.how[2] = REG_SAVED_OFFSET; fs->regs.reg[2].loc.offset = (long)&proc_ctx_->Edx - new_cfa_; fs->regs.how[6] = REG_SAVED_OFFSET; fs->regs.reg[6].loc.offset = (long)&proc_ctx_->Esi - new_cfa_; fs->regs.how[7] = REG_SAVED_OFFSET; fs->regs.reg[7].loc.offset = (long)&proc_ctx_->Edi - new_cfa_; fs->regs.how[5] = REG_SAVED_OFFSET; fs->regs.reg[5].loc.offset = (long)&proc_ctx_->Ebp - new_cfa_; fs->regs.how[8] = REG_SAVED_OFFSET; fs->regs.reg[8].loc.offset = (long)&proc_ctx_->Eip - new_cfa_; fs->retaddr_column = 8; fs->signal_frame = 1; return _URC_NO_REASON; } /* Unwinding through _alloca, propagating from a trap triggered by one of it's probes prior to the real SP adjustment. The only operations of interest performed is "pushl %ecx", followed by ecx clobbering. */ else if (SIG_ALLOCA) { /* Only one push between entry in _alloca and the probe trap. */ long new_cfa_ = (long) ctx_cfa_ + 4; fs->regs.cfa_how = CFA_REG_OFFSET; fs->regs.cfa_reg = __builtin_dwarf_sp_column(); fs->regs.cfa_offset = new_cfa_ - (long) ctx_cfa_; /* The saved value of %ecx is at CFA - 4 */ fs->regs.how[1] = REG_SAVED_OFFSET; fs->regs.reg[1].loc.offset = -4; /* and what is stored at the CFA is the return address. */ fs->retaddr_column = 8; fs->regs.how[8] = REG_SAVED_OFFSET; fs->regs.reg[8].loc.offset = 0; fs->signal_frame = 1; return _URC_NO_REASON; } else return _URC_END_OF_STACK; } #endif /* !__MINGW64__ */