| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <sys/mman.h> |
| #include <limits.h> |
| #include <unistd.h> |
| #undef __USE_XOPEN |
| #include <signal.h> |
| |
| #include "open/platform_types.h" |
| #include "port_crash_handler.h" |
| #include "port_malloc.h" |
| #include "stack_dump.h" |
| #include "../linux/include/gdb_crash_handler.h" |
| #include "signals_internal.h" |
| #include "port_thread_internal.h" |
| |
| |
| #define FLAG_CORE ((port_crash_handler_get_flags() & PORT_CRASH_DUMP_PROCESS_CORE) != 0) |
| #define FLAG_DBG ((port_crash_handler_get_flags() & PORT_CRASH_CALL_DEBUGGER) != 0) |
| |
| |
| bool is_stack_overflow(port_tls_data_t* tlsdata, void* fault_addr) |
| { |
| if (!tlsdata || !fault_addr) |
| return false; |
| |
| if (tlsdata->guard_page_addr) |
| { |
| return (fault_addr >= tlsdata->guard_page_addr && |
| (size_t)fault_addr < (size_t)tlsdata->guard_page_addr |
| + tlsdata->guard_page_size); |
| } |
| |
| size_t stack_top = |
| (size_t)tlsdata->stack_addr - tlsdata->stack_size + tlsdata->guard_page_size; |
| |
| // Determine that fault is beyond stack top |
| return ((size_t)fault_addr < stack_top && |
| (size_t)fault_addr > stack_top - tlsdata->stack_size); |
| } |
| |
| static void c_handler(Registers* pregs, size_t signum, void* fault_addr) |
| { // this exception handler is executed *after* OS signal handler returned |
| int result; |
| port_tls_data_t* tlsdata = get_private_tls_data(); |
| |
| switch ((int)signum) |
| { |
| case SIGSEGV: |
| if (tlsdata->restore_guard_page) |
| { |
| // Now it's safe to disable alternative stack |
| set_alt_stack(tlsdata, FALSE); |
| result = port_process_signal(PORT_SIGNAL_STACK_OVERFLOW, pregs, fault_addr, FALSE); |
| } |
| else |
| result = port_process_signal(PORT_SIGNAL_GPF, pregs, fault_addr, FALSE); |
| break; |
| case SIGFPE: |
| result = port_process_signal(PORT_SIGNAL_ARITHMETIC, pregs, fault_addr, FALSE); |
| break; |
| case SIGTRAP: |
| // Correct return address |
| pregs->set_ip((void*)((POINTER_SIZE_INT)pregs->get_ip() - 1)); |
| result = port_process_signal(PORT_SIGNAL_BREAKPOINT, pregs, fault_addr, FALSE); |
| break; |
| case SIGINT: |
| result = port_process_signal(PORT_SIGNAL_CTRL_C, pregs, fault_addr, FALSE); |
| break; |
| case SIGQUIT: |
| result = port_process_signal(PORT_SIGNAL_QUIT, pregs, fault_addr, FALSE); |
| break; |
| case SIGABRT: |
| result = port_process_signal(PORT_SIGNAL_ABORT, NULL, fault_addr, FALSE); |
| break; |
| default: |
| result = port_process_signal(PORT_SIGNAL_UNKNOWN, pregs, fault_addr, TRUE); |
| } |
| |
| if (result == 0) |
| { |
| // Restore guard page if needed |
| if (tlsdata->restore_guard_page) |
| { |
| port_thread_restore_guard_page(); |
| tlsdata->restore_guard_page = FALSE; |
| |
| if (port_thread_detach_temporary() == 0) |
| STD_FREE(tlsdata); |
| } |
| return; |
| } |
| |
| // We've got a crash |
| if (signum == SIGSEGV) |
| { |
| port_thread_restore_guard_page(); // To catch SO again |
| tlsdata->restore_guard_page = FALSE; |
| } |
| |
| if (result > 0) // invoke debugger |
| { // Prepare second catch of signal to attach GDB from signal handler |
| //assert(tlsdata); // Should be attached - provided by general_signal_handler |
| tlsdata->debugger = TRUE; |
| return; // To produce signal again |
| } |
| |
| // result < 0 - exit process |
| if (FLAG_CORE) |
| { // Return to the same place to produce the same crash and generate core |
| signal(signum, SIG_DFL); // setup default handler |
| return; |
| } |
| |
| // No core needed - simply terminate |
| _exit(-1); |
| } |
| |
| static void general_signal_handler(int signum, siginfo_t* info, void* context) |
| { |
| Registers regs; |
| |
| if (!context) |
| return; |
| |
| // Convert OS context to Registers |
| port_thread_context_to_regs(®s, (ucontext_t*)context); |
| void* fault_addr = info ? info->si_addr : NULL; |
| |
| // Check if SIGSEGV is produced by port_read/write_memory |
| port_tls_data_t* tlsdata = get_private_tls_data(); |
| if (tlsdata && tlsdata->violation_flag) |
| { |
| tlsdata->violation_flag = 0; |
| regs.set_ip(tlsdata->restart_address); |
| return; |
| } |
| |
| if (!tlsdata) // Tread is not attached - attach thread temporarily |
| { |
| int res; |
| tlsdata = (port_tls_data_t*)STD_MALLOC(sizeof(port_tls_data_t)); |
| |
| if (tlsdata) // Try to attach the thread |
| res = port_thread_attach_local(tlsdata, TRUE, TRUE, 0); |
| |
| if (!tlsdata || res != 0) |
| { // Can't process correctly; perform default actions |
| if (FLAG_DBG) |
| { |
| bool result = gdb_crash_handler(®s); |
| _exit(-1); // Exit process if not sucessful... |
| } |
| |
| if (FLAG_CORE && |
| signum != SIGABRT) // SIGABRT can't be rethrown |
| { |
| signal(signum, SIG_DFL); // setup default handler |
| return; |
| } |
| |
| _exit(-1); |
| } |
| |
| // SIGSEGV can represent SO which can't be processed out of signal handler |
| if (signum == SIGSEGV && // This can occur only when a user set an alternative stack |
| is_stack_overflow(tlsdata, fault_addr)) |
| { |
| int result = port_process_signal(PORT_SIGNAL_STACK_OVERFLOW, ®s, fault_addr, FALSE); |
| |
| if (result == 0) |
| { |
| if (port_thread_detach_temporary() == 0) |
| STD_FREE(tlsdata); |
| return; |
| } |
| |
| if (result > 0) |
| tlsdata->debugger = TRUE; |
| else |
| { |
| if (FLAG_CORE) |
| { // Rethrow crash to generate core |
| signal(signum, SIG_DFL); // setup default handler |
| return; |
| } |
| _exit(-1); |
| } |
| } |
| } |
| |
| if (tlsdata->debugger) |
| { |
| bool result = gdb_crash_handler(®s); |
| _exit(-1); // Exit process if not sucessful... |
| } |
| |
| if (signum == SIGABRT && // SIGABRT can't be trown again from c_handler |
| FLAG_DBG) |
| { // So attaching GDB right here |
| bool result = gdb_crash_handler(®s); |
| _exit(-1); // Exit process if not sucessful... |
| } |
| |
| if (signum == SIGSEGV && |
| is_stack_overflow(tlsdata, fault_addr)) |
| { |
| // Second SO while previous SO is not processed yet - is GPF |
| if (tlsdata->restore_guard_page) |
| tlsdata->restore_guard_page = FALSE; |
| else |
| { // To process signal on protected stack area |
| port_thread_clear_guard_page(); |
| // Note: the call above does not disable alternative stack |
| // It can't be made while we are on alternative stack |
| // Alt stack will be disabled explicitly in c_handler() |
| tlsdata->restore_guard_page = TRUE; |
| } |
| } |
| |
| // Prepare registers for transfering control out of signal handler |
| void* callback = (void*)&c_handler; |
| |
| port_set_longjump_regs(callback, ®s, 3, |
| ®s, (void*)(size_t)signum, fault_addr); |
| |
| // Convert prepared Registers back to OS context |
| port_thread_regs_to_context((ucontext_t*)context, ®s); |
| // Return from signal handler to go to C handler |
| } |
| |
| |
| struct sig_reg |
| { |
| int signal; |
| port_sigtype port_sig; |
| int flags; |
| bool set_up; |
| }; |
| |
| static sig_reg signals_used[] = |
| { |
| { SIGTRAP, PORT_SIGNAL_BREAKPOINT, SA_SIGINFO, false }, |
| { SIGSEGV, PORT_SIGNAL_GPF, SA_SIGINFO | SA_ONSTACK, false }, |
| { SIGFPE, PORT_SIGNAL_ARITHMETIC, SA_SIGINFO, false }, |
| { SIGINT, PORT_SIGNAL_CTRL_C, SA_SIGINFO, false }, |
| { SIGQUIT, PORT_SIGNAL_QUIT, SA_SIGINFO, false }, |
| { SIGABRT, PORT_SIGNAL_ABORT, SA_SIGINFO, false } |
| }; |
| |
| static struct sigaction old_actions[sizeof(signals_used)/sizeof(signals_used[0])]; |
| |
| // For signals that must change their default behavior |
| struct sig_redef |
| { |
| int signal; |
| sig_t handler; |
| bool set_up; |
| }; |
| |
| static sig_redef signals_other[] = |
| { |
| { SIGPIPE, SIG_IGN, false } |
| }; |
| |
| static sig_t old_handlers[sizeof(signals_other)/sizeof(signals_other[0])]; |
| |
| |
| static void restore_signals() |
| { |
| for (size_t i = 0; i < sizeof(signals_used)/sizeof(signals_used[0]); i++) |
| { |
| if (!signals_used[i].set_up) |
| continue; |
| |
| signals_used[i].set_up = false; |
| sigaction(signals_used[i].signal, &old_actions[i], NULL); |
| } |
| for (size_t j = 0; j < sizeof(signals_other)/sizeof(signals_other[0]); j++) |
| { |
| if (!signals_other[j].set_up) |
| continue; |
| |
| signals_other[j].set_up = false; |
| signal(signals_other[j].signal, old_handlers[j]); |
| } |
| } |
| |
| int initialize_signals() |
| { |
| struct sigaction sa; |
| |
| for (size_t i = 0; i < sizeof(signals_used)/sizeof(signals_used[0]); i++) |
| { |
| if (!sd_is_handler_registered(signals_used[i].port_sig) && |
| signals_used[i].signal != SIGSEGV) // Sigsegv is needed for port_memaccess |
| continue; |
| |
| sigemptyset(&sa.sa_mask); |
| sa.sa_flags = signals_used[i].flags; |
| sa.sa_sigaction = &general_signal_handler; |
| |
| if (0 != sigaction(signals_used[i].signal, &sa, &old_actions[i])) |
| { |
| restore_signals(); |
| return -1; |
| } |
| |
| signals_used[i].set_up = true; |
| } |
| |
| for (size_t j = 0; j < sizeof(signals_other)/sizeof(signals_other[0]); j++) |
| { |
| old_handlers[j] = signal(signals_other[j].signal, signals_other[j].handler); |
| if (old_handlers[j] == SIG_ERR) |
| { |
| restore_signals(); |
| return -1; |
| } |
| signals_other[j].set_up = true; |
| } |
| |
| // Prepare gdb crash handler |
| if (!init_gdb_crash_handler()) |
| { |
| restore_signals(); |
| return -1; |
| } |
| |
| return 0; |
| |
| } //initialize_signals |
| |
| int shutdown_signals() |
| { |
| cleanup_gdb_crash_handler(); |
| restore_signals(); |
| return 0; |
| } //shutdown_signals |
| |
| void sig_process_crash_flags_change(unsigned added, unsigned removed) |
| { |
| // Still empty on Linux |
| } |