diff options
Diffstat (limited to 'signal.c')
| -rw-r--r-- | signal.c | 327 |
1 files changed, 142 insertions, 185 deletions
@@ -36,6 +36,7 @@ #include "debug_counter.h" #include "eval_intern.h" #include "internal.h" +#include "internal/error.h" #include "internal/eval.h" #include "internal/sanitizers.h" #include "internal/signal.h" @@ -44,6 +45,7 @@ #include "ruby_atomic.h" #include "vm_core.h" #include "ractor_core.h" +#include "ruby/internal/attr/nonstring.h" #ifdef NEED_RUBY_ATOMIC_OPS rb_atomic_t @@ -132,7 +134,7 @@ static const struct signals { #ifdef SIGCONT {"CONT", SIGCONT}, #endif -#if RUBY_SIGCHLD +#ifdef RUBY_SIGCHLD {"CHLD", RUBY_SIGCHLD }, {"CLD", RUBY_SIGCHLD }, #endif @@ -273,7 +275,7 @@ signm2signo(VALUE *sig_ptr, int negative, int exit, int *prefix_ptr) vsig = rb_str_subseq(vsig, prefix, len); prefix = signame_prefix_len; } - rb_raise(rb_eArgError, "unsupported signal `%.*s%"PRIsVALUE"'", + rb_raise(rb_eArgError, "unsupported signal '%.*s%"PRIsVALUE"'", prefix, signame_prefix, vsig); UNREACHABLE_RETURN(0); } @@ -401,7 +403,9 @@ interrupt_init(int argc, VALUE *argv, VALUE self) return rb_call_super(2, args); } -void rb_malloc_info_show_results(void); /* gc.c */ +#if defined(USE_SIGALTSTACK) || defined(_WIN32) +static void reset_sigmask(int sig); +#endif void ruby_default_signal(int sig) @@ -409,9 +413,11 @@ ruby_default_signal(int sig) #if USE_DEBUG_COUNTER rb_debug_counter_show_results("killed by signal."); #endif - rb_malloc_info_show_results(); signal(sig, SIG_DFL); +#if defined(USE_SIGALTSTACK) || defined(_WIN32) + reset_sigmask(sig); +#endif raise(sig); } @@ -508,9 +514,6 @@ static struct { rb_atomic_t cnt[RUBY_NSIG]; rb_atomic_t size; } signal_buff; -#if RUBY_SIGCHLD -volatile unsigned int ruby_nocldwait; -#endif #define sighandler_t ruby_sighandler_t @@ -593,7 +596,7 @@ ruby_signal(int signum, sighandler_t handler) #endif sigemptyset(&sigact.sa_mask); -#ifdef USE_SIGALTSTACK +#if defined(USE_SIGALTSTACK) && !defined(__wasm__) if (handler == SIG_IGN || handler == SIG_DFL) { sigact.sa_handler = handler; sigact.sa_flags = 0; @@ -608,27 +611,6 @@ ruby_signal(int signum, sighandler_t handler) #endif switch (signum) { -#if RUBY_SIGCHLD - case RUBY_SIGCHLD: - if (handler == SIG_IGN) { - ruby_nocldwait = 1; -# ifdef USE_SIGALTSTACK - if (sigact.sa_flags & SA_SIGINFO) { - sigact.sa_sigaction = (ruby_sigaction_t*)sighandler; - } - else { - sigact.sa_handler = sighandler; - } -# else - sigact.sa_handler = handler; - sigact.sa_flags = 0; -# endif - } - else { - ruby_nocldwait = 0; - } - break; -#endif #if defined(SA_ONSTACK) && defined(USE_SIGALTSTACK) case SIGSEGV: #ifdef SIGBUS @@ -651,7 +633,7 @@ ruby_signal(int signum, sighandler_t handler) } sighandler_t -posix_signal(int signum, sighandler_t handler) +ruby_posix_signal(int signum, sighandler_t handler) { return ruby_signal(signum, handler); } @@ -682,6 +664,10 @@ ruby_nativethread_signal(int signum, sighandler_t handler) #endif #endif +#if !defined(POSIX_SIGNAL) && !defined(SIG_GET) +static rb_nativethread_lock_t sig_check_lock; +#endif + static int signal_ignored(int sig) { @@ -691,9 +677,16 @@ signal_ignored(int sig) (void)VALGRIND_MAKE_MEM_DEFINED(&old, sizeof(old)); if (sigaction(sig, NULL, &old) < 0) return FALSE; func = old.sa_handler; +#elif defined SIG_GET + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/signal-action-constants + // SIG_GET: Returns the current value of the signal. + func = signal(sig, SIG_GET); #else - sighandler_t old = signal(sig, SIG_DFL); + sighandler_t old; + rb_native_mutex_lock(&sig_check_lock); + old = signal(sig, SIG_DFL); signal(sig, old); + rb_native_mutex_unlock(&sig_check_lock); func = old; #endif if (func == SIG_IGN) return 1; @@ -707,35 +700,14 @@ signal_enque(int sig) ATOMIC_INC(signal_buff.size); } -#if RUBY_SIGCHLD -static rb_atomic_t sigchld_hit; -/* destructive getter than simple predicate */ -# define GET_SIGCHLD_HIT() ATOMIC_EXCHANGE(sigchld_hit, 0) -#else -# define GET_SIGCHLD_HIT() 0 -#endif - static void sighandler(int sig) { int old_errnum = errno; - /* the VM always needs to handle SIGCHLD for rb_waitpid */ - if (sig == RUBY_SIGCHLD) { -#if RUBY_SIGCHLD - rb_vm_t *vm = GET_VM(); - ATOMIC_EXCHANGE(sigchld_hit, 1); - - /* avoid spurious wakeup in main thread if and only if nobody uses trap(:CHLD) */ - if (vm && ACCESS_ONCE(VALUE, vm->trap_list.cmd[sig])) { - signal_enque(sig); - } -#endif - } - else { - signal_enque(sig); - } + signal_enque(sig); rb_thread_wakeup_timer_thread(sig); + #if !defined(BSD_SIGNAL) && !defined(POSIX_SIGNAL) ruby_signal(sig, sighandler); #endif @@ -746,7 +718,7 @@ sighandler(int sig) int rb_signal_buff_size(void) { - return signal_buff.size; + return RUBY_ATOMIC_LOAD(signal_buff.size); } static void @@ -774,7 +746,7 @@ rb_get_next_signal(void) { int i, sig = 0; - if (signal_buff.size != 0) { + if (rb_signal_buff_size() != 0) { for (i=1; i<RUBY_NSIG; i++) { if (signal_buff.cnt[i] > 0) { ATOMIC_DEC(signal_buff.cnt[i]); @@ -789,13 +761,15 @@ rb_get_next_signal(void) #if defined SIGSEGV || defined SIGBUS || defined SIGILL || defined SIGFPE static const char *received_signal; -# define clear_received_signal() (void)(ruby_disable_gc = 0, received_signal = 0) +# define clear_received_signal() do { \ + if (GET_VM() != NULL) rb_gc_enable(); \ + received_signal = 0; \ +} while (0) #else # define clear_received_signal() ((void)0) #endif #if defined(USE_SIGALTSTACK) || defined(_WIN32) -NORETURN(void rb_ec_stack_overflow(rb_execution_context_t *ec, int crit)); # if defined __HAIKU__ # define USE_UCONTEXT_REG 1 # elif !(defined(HAVE_UCONTEXT_H) && (defined __i386__ || defined __x86_64__ || defined __amd64__)) @@ -841,7 +815,8 @@ check_stack_overflow(int sig, const uintptr_t addr, const ucontext_t *ctx) const greg_t bp = mctx->gregs[REG_EBP]; # endif # elif defined __APPLE__ -# if __DARWIN_UNIX03 +# include <AvailabilityMacros.h> +# if defined(MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 # define MCTX_SS_REG(reg) __ss.__##reg # else # define MCTX_SS_REG(reg) ss.reg @@ -880,13 +855,17 @@ check_stack_overflow(int sig, const uintptr_t addr, const ucontext_t *ctx) if (sp_page == fault_page || sp_page == fault_page + 1 || (sp_page <= fault_page && fault_page <= bp_page)) { rb_execution_context_t *ec = GET_EC(); - int crit = FALSE; + ruby_stack_overflow_critical_level crit = rb_stack_overflow_signal; int uplevel = roomof(pagesize, sizeof(*ec->tag)) / 2; /* XXX: heuristic */ while ((uintptr_t)ec->tag->buf / pagesize <= fault_page + 1) { /* drop the last tag if it is close to the fault, * otherwise it can cause stack overflow again at the same * place. */ - if ((crit = (!ec->tag->prev || !--uplevel)) != FALSE) break; + if (!ec->tag->prev || !--uplevel) { + crit = rb_stack_overflow_fatal; + break; + } + rb_vm_tag_jmpbuf_deinit(&ec->tag->buf); ec->tag = ec->tag->prev; } reset_sigmask(sig); @@ -898,23 +877,28 @@ static void check_stack_overflow(int sig, const void *addr) { int ruby_stack_overflowed_p(const rb_thread_t *, const void *); - rb_thread_t *th = GET_THREAD(); + rb_execution_context_t *ec = rb_current_execution_context(false); + if (!ec) return; + rb_thread_t *th = rb_ec_thread_ptr(ec); if (ruby_stack_overflowed_p(th, addr)) { reset_sigmask(sig); - rb_ec_stack_overflow(th->ec, FALSE); + rb_ec_stack_overflow(th->ec, 1); } } # endif + # ifdef _WIN32 # define CHECK_STACK_OVERFLOW() check_stack_overflow(sig, 0) # else # define FAULT_ADDRESS info->si_addr # ifdef USE_UCONTEXT_REG -# define CHECK_STACK_OVERFLOW() check_stack_overflow(sig, (uintptr_t)FAULT_ADDRESS, ctx) +# define CHECK_STACK_OVERFLOW_() check_stack_overflow(sig, (uintptr_t)FAULT_ADDRESS, ctx) # else -# define CHECK_STACK_OVERFLOW() check_stack_overflow(sig, FAULT_ADDRESS) +# define CHECK_STACK_OVERFLOW_() check_stack_overflow(sig, FAULT_ADDRESS) # endif # define MESSAGE_FAULT_ADDRESS " at %p", FAULT_ADDRESS +# define SIGNAL_FROM_USER_P() ((info)->si_code == SI_USER) +# define CHECK_STACK_OVERFLOW() (SIGNAL_FROM_USER_P() ? (void)0 : CHECK_STACK_OVERFLOW_()) # endif #else # define CHECK_STACK_OVERFLOW() (void)0 @@ -924,10 +908,10 @@ check_stack_overflow(int sig, const void *addr) #endif #if defined SIGSEGV || defined SIGBUS || defined SIGILL || defined SIGFPE -NOINLINE(static void check_reserved_signal_(const char *name, size_t name_len)); +NOINLINE(static void check_reserved_signal_(const char *name, size_t name_len, int signo)); /* noinine to reduce stack usage in signal handers */ -#define check_reserved_signal(name) check_reserved_signal_(name, sizeof(name)-1) +#define check_reserved_signal(name) check_reserved_signal_(name, sizeof(name)-1, sig) #ifdef SIGBUS @@ -965,6 +949,20 @@ sigsegv(int sig SIGINFO_ARG) } #endif +#ifdef SIGABRT + +static sighandler_t default_sigabrt_handler; +NORETURN(static ruby_sigaction_t sigabrt); + +static void +sigabrt(int sig SIGINFO_ARG) +{ + check_reserved_signal("ABRT"); + CHECK_STACK_OVERFLOW(); + rb_bug_for_fatal_signal(default_sigabrt_handler, sig, SIGINFO_CTX, "Aborted" MESSAGE_FAULT_ADDRESS); +} +#endif + #ifdef SIGILL static sighandler_t default_sigill_handler; @@ -999,38 +997,57 @@ ruby_abort(void) } static void -check_reserved_signal_(const char *name, size_t name_len) +check_reserved_signal_(const char *name, size_t name_len, int signo) { const char *prev = ATOMIC_PTR_EXCHANGE(received_signal, name); if (prev) { ssize_t RB_UNUSED_VAR(err); -#define NOZ(name, str) name[sizeof(str)-1] = str + static const int stderr_fd = 2; +#define NOZ(name, str) RBIMPL_ATTR_NONSTRING() name[sizeof(str)-1] = str static const char NOZ(msg1, " received in "); static const char NOZ(msg2, " handler\n"); #ifdef HAVE_WRITEV struct iovec iov[4]; + int i = 0; +# define W(str, len) \ + iov[i++] = (struct iovec){.iov_base = (void *)(str), .iov_len = (len)} +#else +# define W(str, len) err = write(stderr_fd, (str), (len)) +#endif + +#if __has_feature(address_sanitizer) || \ + __has_feature(memory_sanitizer) || \ + defined(HAVE_VALGRIND_MEMCHECK_H) +# define SANITIZING true +#else +# define SANITIZING false +#endif - iov[0].iov_base = (void *)name; - iov[0].iov_len = name_len; - iov[1].iov_base = (void *)msg1; - iov[1].iov_len = sizeof(msg1); - iov[2].iov_base = (void *)prev; - iov[2].iov_len = strlen(prev); - iov[3].iov_base = (void *)msg2; - iov[3].iov_len = sizeof(msg2); - err = writev(2, iov, 4); +#ifdef SIGABRT +// Avoid infinite loop when already aborting +# define RECURSIVE (signo == SIGABRT) #else - err = write(2, name, name_len); - err = write(2, msg1, sizeof(msg1)); - err = write(2, prev, strlen(prev)); - err = write(2, msg2, sizeof(msg2)); +# define RECURSIVE false +#endif + if (SANITIZING || RECURSIVE) ruby_signal(signo, SIG_DFL); +# undef SANITIZING +# undef RECURSIVE + W(name, name_len); + W(msg1, sizeof(msg1)); + W(prev, strlen(prev)); + W(msg2, sizeof(msg2)); +# undef W +#ifdef HAVE_WRITEV + err = writev(stderr_fd, iov, i); #endif ruby_abort(); } - ruby_disable_gc = 1; + if (GET_VM() != NULL) { + rb_gc_disable_no_rest(); + } } #endif @@ -1061,7 +1078,7 @@ signal_exec(VALUE cmd, int sig) EC_PUSH_TAG(ec); if ((state = EC_EXEC_TAG()) == TAG_NONE) { VALUE signum = INT2NUM(sig); - rb_eval_cmd_kw(cmd, rb_ary_new3(1, signum), RB_NO_KEYWORDS); + rb_eval_cmd_call_kw(cmd, 1, &signum, RB_NO_KEYWORDS); } EC_POP_TAG(); ec = GET_EC(); @@ -1085,16 +1102,6 @@ rb_vm_trap_exit(rb_vm_t *vm) } } -void ruby_waitpid_all(rb_vm_t *); /* process.c */ - -void -ruby_sigchld_handler(rb_vm_t *vm) -{ - if (SIGCHLD_LOSSY || GET_SIGCHLD_HIT()) { - ruby_waitpid_all(vm); - } -} - /* returns true if a trap handler was run, false otherwise */ int rb_signal_exec(rb_thread_t *th, int sig) @@ -1162,7 +1169,7 @@ default_handler(int sig) #ifdef SIGUSR2 case SIGUSR2: #endif -#if RUBY_SIGCHLD +#ifdef RUBY_SIGCHLD case RUBY_SIGCHLD: #endif func = sighandler; @@ -1230,9 +1237,6 @@ trap_handler(VALUE *cmd, int sig) break; case 14: if (memcmp(cptr, "SYSTEM_DEFAULT", 14) == 0) { - if (sig == RUBY_SIGCHLD) { - goto sig_dfl; - } func = SIG_DFL; *cmd = 0; } @@ -1260,11 +1264,6 @@ trap_handler(VALUE *cmd, int sig) break; } } - else { - rb_proc_t *proc; - GetProcPtr(*cmd, proc); - (void)proc; - } } return func; @@ -1359,31 +1358,36 @@ reserved_signal_p(int signo) /* * call-seq: - * Signal.trap( signal, command ) -> obj - * Signal.trap( signal ) {| | block } -> obj + * Signal.trap(signal, command) -> obj + * Signal.trap(signal) { ... } -> obj + * + * Specifies the handling of signals. Returns the previous handler for + * the given signal. + * + * Argument +signal+ is a signal name (a string or symbol such + * as +SIGALRM+ or +SIGUSR1+) or an integer signal number. When +signal+ + * is a string or symbol, the leading characters +SIG+ may be omitted. * - * Specifies the handling of signals. The first parameter is a signal - * name (a string such as ``SIGALRM'', ``SIGUSR1'', and so on) or a - * signal number. The characters ``SIG'' may be omitted from the - * signal name. The command or block specifies code to be run when the + * Argument +command+ or block provided specifies code to be run when the * signal is raised. - * If the command is the string ``IGNORE'' or ``SIG_IGN'', the signal - * will be ignored. - * If the command is ``DEFAULT'' or ``SIG_DFL'', the Ruby's default handler - * will be invoked. - * If the command is ``EXIT'', the script will be terminated by the signal. - * If the command is ``SYSTEM_DEFAULT'', the operating system's default - * handler will be invoked. - * Otherwise, the given command or block will be run. - * The special signal name ``EXIT'' or signal number zero will be - * invoked just prior to program termination. - * trap returns the previous handler for the given signal. + * + * Argument +command+ may also be a string or symbol with the following special + * values: + * + * - +IGNORE+, +SIG_IGN+: the signal will be ignored. + * - +DEFAULT+, +SIG_DFL+: Ruby's default handler will be invoked. + * - +EXIT+: the process will be terminated by the signal. + * - +SYSTEM_DEFAULT+: the operating system's default handler will be invoked. + * + * The special signal name +EXIT+ or signal number zero will be + * invoked just prior to program termination: * * Signal.trap(0, proc { puts "Terminating: #{$$}" }) * Signal.trap("CLD") { puts "Child died" } * fork && Process.wait * - * <em>produces:</em> + * Outputs: + * * Terminating: 27461 * Child died * Terminating: 27460 @@ -1450,6 +1454,7 @@ sig_list(VALUE _) if (reserved_signal_p(signum)) rb_bug(failed); \ perror(failed); \ } while (0) + static int install_sighandler_core(int signum, sighandler_t handler, sighandler_t *old_handler) { @@ -1474,25 +1479,6 @@ install_sighandler_core(int signum, sighandler_t handler, sighandler_t *old_hand # define force_install_sighandler(signum, handler, old_handler) \ INSTALL_SIGHANDLER(install_sighandler_core(signum, handler, old_handler), #signum, signum) -#if RUBY_SIGCHLD -static int -init_sigchld(int sig) -{ - sighandler_t oldfunc; - sighandler_t func = sighandler; - - oldfunc = ruby_signal(sig, SIG_DFL); - if (oldfunc == SIG_ERR) return -1; - ruby_signal(sig, func); - ACCESS_ONCE(VALUE, GET_VM()->trap_list.cmd[sig]) = 0; - - return 0; -} - -# define init_sigchld(signum) \ - INSTALL_SIGHANDLER(init_sigchld(signum), #signum, signum) -#endif - void ruby_sig_finalize(void) { @@ -1504,7 +1490,6 @@ ruby_sig_finalize(void) } } - int ruby_enable_coredump = 0; /* @@ -1558,6 +1543,12 @@ Init_signal(void) rb_define_method(rb_eSignal, "signo", esignal_signo, 0); rb_alias(rb_eSignal, rb_intern_const("signm"), rb_intern_const("message")); rb_define_method(rb_eInterrupt, "initialize", interrupt_init, -1); +#if !defined(POSIX_SIGNAL) && !defined(SIG_GET) + rb_native_mutex_initialize(&sig_check_lock); +#endif + + // It should be ready to call rb_signal_exec() + VM_ASSERT(GET_THREAD()->pending_interrupt_queue); /* At this time, there is no subthread. Then sigmask guarantee atomics. */ rb_disable_interrupt(); @@ -1593,6 +1584,10 @@ Init_signal(void) RB_ALTSTACK_INIT(GET_VM()->main_altstack, rb_allocate_sigaltstack()); force_install_sighandler(SIGSEGV, (sighandler_t)sigsegv, &default_sigsegv_handler); #endif + +#ifdef SIGABRT + force_install_sighandler(SIGABRT, (sighandler_t)sigabrt, &default_sigabrt_handler); +#endif } #ifdef SIGPIPE install_sighandler(SIGPIPE, sig_do_nothing); @@ -1601,55 +1596,17 @@ Init_signal(void) install_sighandler(SIGSYS, sig_do_nothing); #endif -#if RUBY_SIGCHLD - init_sigchld(RUBY_SIGCHLD); +#ifdef RUBY_SIGCHLD + install_sighandler(RUBY_SIGCHLD, sighandler); #endif rb_enable_interrupt(); } -#if defined(HAVE_GRANTPT) -extern int grantpt(int); -#else -static int -fake_grantfd(int masterfd) +void +rb_signal_atfork(void) { - errno = ENOSYS; - return -1; -} -#define grantpt(fd) fake_grantfd(fd) +#if defined(HAVE_WORKING_FORK) && !defined(POSIX_SIGNAL) && !defined(SIG_GET) + rb_native_mutex_initialize(&sig_check_lock); #endif - -int -rb_grantpt(int masterfd) -{ - if (RUBY_SIGCHLD) { - rb_vm_t *vm = GET_VM(); - int ret, e; - - /* - * Prevent waitpid calls from Ruby by taking waitpid_lock. - * Pedantically, grantpt(3) is undefined if a non-default - * SIGCHLD handler is defined, but preventing conflicting - * waitpid calls ought to be sufficient. - * - * We could install the default sighandler temporarily, but that - * could cause SIGCHLD to be missed by other threads. Blocking - * SIGCHLD won't work here, either, unless we stop and restart - * timer-thread (as only timer-thread sees SIGCHLD), but that - * seems like overkill. - */ - rb_nativethread_lock_lock(&vm->waitpid_lock); - { - ret = grantpt(masterfd); /* may spawn `pt_chown' and wait on it */ - if (ret < 0) e = errno; - } - rb_nativethread_lock_unlock(&vm->waitpid_lock); - - if (ret < 0) errno = e; - return ret; - } - else { - return grantpt(masterfd); - } } |
