// // signal.cpp // // Copyright (c) Microsoft Corporation. All rights reserved. // // signal(), raise(), and related functions // #include #include #include #include #include #include #include #include // Variables holding action codes (and code pointers) for SIGINT, SIGBRK, // SIGABRT and SIGTERM. // // note that the disposition (i.e., action to be taken upon receipt) of // these signals is defined on a per-process basis (not per-thread)!! static __crt_state_management::dual_state_global<__crt_signal_handler_t> ctrlc_action; // SIGINT static __crt_state_management::dual_state_global<__crt_signal_handler_t> ctrlbreak_action; // SIGBREAK static __crt_state_management::dual_state_global<__crt_signal_handler_t> abort_action; // SIGABRT static __crt_state_management::dual_state_global<__crt_signal_handler_t> term_action; // SIGTERM /* * flag indicated whether or not a handler has been installed to capture * ^C and ^Break events. */ static bool console_ctrl_handler_installed = false; #define _SIGHUP_IGNORE 1 #define _SIGQUIT_IGNORE 3 #define _SIGPIPE_IGNORE 13 #define _SIGIOINT_IGNORE 16 #define _SIGSTOP_IGNORE 17 // Initializes the signal handler pointers to the encoded nullptr value. extern "C" void __cdecl __acrt_initialize_signal_handlers(void* const encoded_nullptr) { // The encoded nullptr is SIG_DFL ctrlc_action.initialize (reinterpret_cast<__crt_signal_handler_t>(encoded_nullptr)); ctrlbreak_action.initialize(reinterpret_cast<__crt_signal_handler_t>(encoded_nullptr)); abort_action.initialize (reinterpret_cast<__crt_signal_handler_t>(encoded_nullptr)); term_action.initialize (reinterpret_cast<__crt_signal_handler_t>(encoded_nullptr)); } // Gets the address of the global action for one of the signals that has a // global action. Returns nullptr if the given signal does not have a global // action. static __crt_signal_handler_t* __cdecl get_global_action_nolock(int const signum) throw() { switch (signum) { // CRT_REFACTOR TODO: PERFORMANCE: for OS mode, these might be able to be in the const data section, instead of in writeable page data. case SIGINT: return &ctrlc_action.value(); case SIGBREAK: return &ctrlbreak_action.value(); case SIGABRT: return &abort_action.value(); case SIGABRT_COMPAT: return &abort_action.value(); case SIGTERM: return &term_action.value(); } return nullptr; } // Looks up the exception-action table entry for a signal. This function finds // the first entry in the 'action_table' whose _signal_number field is 'signum'. // If no such entry is found, nullptr is returned. static __crt_signal_action_t* __cdecl siglookup( int const signum, __crt_signal_action_t* const action_table ) throw() { // Walk through the table looking for the proper entry. Note that in the // case where more than one exception corresponds to the same signal, the // first such instance in the table is the one returned. for (__crt_signal_action_t* p = action_table; p != action_table + __acrt_signal_action_table_count; ++p) if (p->_signal_number == signum) return p; // If we reached the end of the table, return nullptr: return nullptr; } // Handles failures for signal(). static __crt_signal_handler_t __cdecl signal_failed(int const signum) throw() { switch (signum) { case _SIGHUP_IGNORE: case _SIGQUIT_IGNORE: case _SIGPIPE_IGNORE: case _SIGIOINT_IGNORE: case _SIGSTOP_IGNORE: return SIG_ERR; default: errno = EINVAL; return SIG_ERR; } } // Enclaves have no console, thus also no signals specific to consoles. #ifdef _UCRT_ENCLAVE_BUILD __inline static BOOL is_unsupported_signal(int const signum, __crt_signal_handler_t const sigact) { return (sigact == SIG_ACK || sigact == SIG_SGE || signum == SIGINT || signum == SIGBREAK); } __inline static BOOL is_console_signal(int const) { return FALSE; } #else /* ^^^ _UCRT_ENCLAVE_BUILD ^^^ // vvv !_UCRT_ENCLAVE_BUILD vvv */ __inline static BOOL is_unsupported_signal(int const, __crt_signal_handler_t const sigact) { return (sigact == SIG_ACK || sigact == SIG_SGE); } __inline static BOOL is_console_signal(int const signum) { return (signum == SIGINT || signum == SIGBREAK); } #endif /* _UCRT_ENCLAVE_BUILD */ /*** *static BOOL WINAPI ctrlevent_capture(DWORD ctrl_type) - capture ^C and ^Break events * *Purpose: * Capture ^C and ^Break events from the console and dispose of them * according the values in ctrlc_action and ctrlbreak_action, resp. * This is the routine that evokes the user-defined action for SIGINT * (^C) or SIGBREAK (^Break) installed by a call to signal(). * *Entry: * DWORD ctrl_type - indicates type of event, two values: * CTRL_C_EVENT * CTRL_BREAK_EVENT * *Exit: * Returns TRUE to indicate the event (signal) has been handled. * Otherwise, returns FALSE. * *Exceptions: * *******************************************************************************/ static BOOL WINAPI ctrlevent_capture(DWORD const ctrl_type) throw() { __crt_signal_handler_t ctrl_action = nullptr; int signal_code = 0; __acrt_lock(__acrt_signal_lock); __try { __crt_signal_handler_t* pctrl_action; // Identify the type of event and fetch the corresponding action // description: if (ctrl_type == CTRL_C_EVENT) { pctrl_action = &ctrlc_action.value(); ctrl_action = __crt_fast_decode_pointer(*pctrl_action); signal_code = SIGINT; } else { pctrl_action = &ctrlbreak_action.value(); ctrl_action = __crt_fast_decode_pointer(*pctrl_action); signal_code = SIGBREAK; } if (ctrl_action != SIG_DFL && ctrl_action != SIG_IGN) { // Reset the action to be SIG_DFL: *pctrl_action = __crt_fast_encode_pointer(nullptr); } } __finally { __acrt_unlock(__acrt_signal_lock); } __endtry // The default signal action leaves the event unhandled, so return false to // indicate such: if (ctrl_action == SIG_DFL) return FALSE; // If the action is not to ignore the signal, then invoke the action: if (ctrl_action != SIG_IGN) (*ctrl_action)(signal_code); // Then return TRUE to indicate the event has been handled (this may mean // that the even is being ignored): return TRUE; } /*** *__crt_signal_handler_t signal(signum, sigact) - Define a signal handler * *Purpose: * The signal routine allows the user to define what action should * be taken when various signals occur. The Win32/Dosx32 implementation * supports seven signals, divided up into three general groups * * 1. Signals corresponding to OS exceptions. These are: * SIGFPE * SIGILL * SIGSEGV * Signal actions for these signals are installed by altering the * _action and SigAction fields for the appropriate entry in the * exception-action table (XcptActTab[]). * * 2. Signals corresponding to ^C and ^Break. These are: * SIGINT * SIGBREAK * Signal actions for these signals are installed by altering the * _ctrlc_action and _ctrlbreak_action variables. * * 3. Signals which are implemented only in the runtime. That is, they * occur only as the result of a call to raise(). * SIGABRT * SIGTERM * * *Entry: * int signum signal type. recognized signal types are: * * SIGABRT (ANSI) * SIGBREAK * SIGFPE (ANSI) * SIGILL (ANSI) * SIGINT (ANSI) * SIGSEGV (ANSI) * SIGTERM (ANSI) * SIGABRT_COMPAT * * __crt_signal_handler_t sigact signal handling function or action code. the action * codes are: * * SIG_DFL - take the default action, whatever that may * be, upon receipt of this type type of signal. * * SIG_DIE - *** ILLEGAL *** * special code used in the _action field of an * XcptActTab[] entry to indicate that the runtime is * to terminate the process upon receipt of the exception. * not accepted as a value for sigact. * * SIG_IGN - ignore this type of signal * * [function address] - transfer control to this address * when a signal of this type occurs. * *Exit: * Good return: * Signal returns the previous value of the signal handling function * (e.g., SIG_DFL, SIG_IGN, etc., or [function address]). This value is * returned in DX:AX. * * Error return: * Signal returns -1 and errno is set to EINVAL. The error return is * generally taken if the user submits bogus input values. * *Exceptions: * None. * *******************************************************************************/ extern "C" __crt_signal_handler_t __cdecl signal(int signum, __crt_signal_handler_t sigact) { // Check for signal actions that are supported on other platforms but not on // this one, and make sure the action is not SIG_DIE: if (is_unsupported_signal(signum, sigact)) return signal_failed(signum); // First, handle the case where the signal does not correspond to an // exception in the host OS: if (signum == SIGINT || signum == SIGBREAK || signum == SIGABRT || signum == SIGABRT_COMPAT || signum == SIGTERM) { bool set_console_ctrl_error = false; __crt_signal_handler_t old_action = nullptr; __acrt_lock(__acrt_signal_lock); __try { // If the signal is SIGINT or SIGBREAK make sure the handler is // installed to capture ^C and ^Break events: // C4127: conditional expression is constant #pragma warning( suppress: 4127 ) if (is_console_signal(signum) && !console_ctrl_handler_installed) { if (SetConsoleCtrlHandler(ctrlevent_capture, TRUE)) { console_ctrl_handler_installed = true; } else { _doserrno = GetLastError(); set_console_ctrl_error = true; } } __crt_signal_handler_t* const action_pointer = get_global_action_nolock(signum); if (action_pointer != nullptr) { old_action = __crt_fast_decode_pointer(*action_pointer); if (sigact != SIG_GET) *action_pointer = __crt_fast_encode_pointer(sigact); } } __finally { __acrt_unlock(__acrt_signal_lock); } __endtry if (set_console_ctrl_error) return signal_failed(signum); return old_action; } // If we reach here, signum is supposed to be one of the signals which // correspond to exceptions on the host OS. If it's not one of these, // fail and return immediately: if (signum != SIGFPE && signum != SIGILL && signum != SIGSEGV) return signal_failed(signum); __acrt_ptd* const ptd = __acrt_getptd_noexit(); if (ptd == nullptr) return signal_failed(signum); // Check that there is a per-thread instance of the exception-action table // for this thread. If there isn't, create one: if (ptd->_pxcptacttab == __acrt_exception_action_table) { // Allocate space for an exception-action table: ptd->_pxcptacttab = static_cast<__crt_signal_action_t*>(_malloc_crt(__acrt_signal_action_table_size)); if (ptd->_pxcptacttab == nullptr) return signal_failed(signum); // Initialize the table by copying the contents of __acrt_exception_action_table: memcpy(ptd->_pxcptacttab, __acrt_exception_action_table, __acrt_signal_action_table_size); } // Look up the proper entry in the exception-action table. Note that if // several exceptions are mapped to the same signal, this returns the // pointer to first such entry in the exception action table. It is assumed // that the other entries immediately follow this one. __crt_signal_action_t* const xcpt_action = siglookup(signum, ptd->_pxcptacttab); if (xcpt_action == nullptr) return signal_failed(signum); // SIGSEGV, SIGILL and SIGFPE all have more than one exception mapped to // them. The code below depends on the exceptions corresponding to the same // signal being grouped together in the exception-action table. __crt_signal_handler_t const old_action = xcpt_action->_action; // If we are not just getting the currently installed action, loop through // all the entries corresponding to the given signal and update them as // appropriate: if (sigact != SIG_GET) { __crt_signal_action_t* const last = ptd->_pxcptacttab + __acrt_signal_action_table_count; // Iterate until we reach the end of the table or we reach the end of // the range of actions for this signal, whichever comes first: for (__crt_signal_action_t* p = xcpt_action; p != last && p->_signal_number == signum; ++p) { p->_action = sigact; } } return old_action; } /*** *int raise(signum) - Raise a signal * *Purpose: * This routine raises a signal (i.e., performs the action currently * defined for this signal). The action associated with the signal is * evoked directly without going through intermediate dispatching or * handling. * *Entry: * int signum - signal type (e.g., SIGINT) * *Exit: * returns 0 on good return, -1 on bad return. * *Exceptions: * May not return. Raise has no control over the action * routines defined for the various signals. Those routines may * abort, terminate, etc. In particular, the default actions for * certain signals will terminate the program. * *******************************************************************************/ extern "C" int __cdecl raise(int const signum) { __acrt_ptd* ptd = nullptr; int old_fpecode = 0; __crt_signal_handler_t* action_pointer = nullptr; bool action_is_global = true; switch (signum) { case SIGINT: case SIGBREAK: case SIGABRT: case SIGABRT_COMPAT: case SIGTERM: action_pointer = get_global_action_nolock(signum);; break; case SIGFPE: case SIGILL: case SIGSEGV: { ptd = __acrt_getptd_noexit(); if (ptd == nullptr) return -1; __crt_signal_action_t* const local_action = siglookup(signum, ptd->_pxcptacttab); _VALIDATE_RETURN(local_action != nullptr, EINVAL, -1); action_pointer = &local_action->_action; action_is_global = false; break; } default: // unsupported signal, return an error _VALIDATE_RETURN(("Invalid signal or error", 0), EINVAL, -1); } PEXCEPTION_POINTERS old_pxcptinfoptrs = nullptr; // If the action is global, we must acquire the lock before accessing it: if (action_is_global) __acrt_lock(__acrt_signal_lock); __crt_signal_handler_t action = nullptr; bool return0 = false; __try { // Global function pointers are encoded; per-thread pointers are not: action = action_is_global ? __crt_fast_decode_pointer(*action_pointer) : *action_pointer; // If the current action is SIG_IGN, just return: return0 = action == SIG_IGN; if (return0) __leave; // If the current action is SIG_DFL, take the default action. The current // default action for all of the supported signals is to terminate with an // exit code of 3: if (action == SIG_DFL) { // Be sure to unlock before entering the exit code. The exit function // does not return. if (action_is_global) __acrt_unlock(__acrt_signal_lock); _exit(3); } // For signals that correspond to exceptions, set the pointer to the // EXCEPTION_POINTERS structure to nullptr: if (signum == SIGFPE || signum == SIGSEGV || signum == SIGILL) { old_pxcptinfoptrs = ptd->_tpxcptinfoptrs; ptd->_tpxcptinfoptrs = nullptr; // If the signal is SIGFPE, also set _fpecode to _FPE_EXPLICITGEN: if ( signum == SIGFPE ) { old_fpecode = _fpecode; _fpecode = _FPE_EXPLICITGEN; } } // Reset the action to SIG_DFL before calling the user-specified handler. // For SIGFPE, we must reset the action for all of the FP exceptions: if (signum == SIGFPE) { __crt_signal_action_t* const first = ptd->_pxcptacttab + __acrt_signal_action_first_fpe_index; __crt_signal_action_t* const last = first + __acrt_signal_action_fpe_count; for (__crt_signal_action_t* p = first; p != last; ++p) { p->_action = SIG_DFL; } } else { *action_pointer = __crt_fast_encode_pointer(nullptr); } } __finally { if (action_is_global) __acrt_unlock(__acrt_signal_lock); } __endtry if (return0) return 0; // Call the user-specified handler routine. For SIGFPE, we have special // code to support old handlers which expect the value of _fpecode as the // second argument: if (signum == SIGFPE) { reinterpret_cast(action)(SIGFPE, _fpecode); } else { action(signum); } // For signals that correspond to exceptions, restore the pointer to the // EXCEPTION_POINTERS structure: if (signum == SIGFPE || signum == SIGSEGV || signum == SIGILL) { ptd->_tpxcptinfoptrs = old_pxcptinfoptrs; // If signum is SIGFPE, also restore _fpecode if (signum == SIGFPE) _fpecode = old_fpecode; } return 0; } // Gets the SIGABRT signal handling function extern "C" __crt_signal_handler_t __cdecl __acrt_get_sigabrt_handler() { return __acrt_lock_and_call(__acrt_signal_lock, [] { return __crt_fast_decode_pointer(abort_action.value()); }); } // Gets the FPE code for the current thread extern "C" int* __cdecl __fpecode() { return &__acrt_getptd()->_tfpecode; } // Returns a pointer to the signal handlers for the current thread extern "C" void** __cdecl __pxcptinfoptrs() { return reinterpret_cast(&__acrt_getptd()->_tpxcptinfoptrs); }