| /* Detect stack overflow (when getrlimit and sigaction or sigvec are available) |
| Copyright (C) 1993, 1994, 2006, 2007 Free Software Foundation, Inc. |
| Jim Avera <jima@netcom.com>, October 1993. |
| |
| This file is part of GNU M4. |
| |
| GNU M4 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 of the License, or |
| (at your option) any later version. |
| |
| GNU M4 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. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| /* Compiled only when USE_STACKOVF is defined, which itself requires |
| getrlimit with the RLIMIT_STACK option, and support for alternate |
| signal stacks using either SVR4 or BSD interfaces. |
| |
| This should compile on ANY system which supports either sigaltstack() |
| or sigstack(), with or without <siginfo.h> or another way to determine |
| the fault address. |
| |
| There is no completely portable way to determine if a SIGSEGV signal |
| indicates a stack overflow. The fault address can be used to infer |
| this. However, the fault address is passed to the signal handler in |
| different ways on various systems. One of three methods are used to |
| determine the fault address: |
| |
| 1. The siginfo parameter (with siginfo.h, i.e., SVR4). |
| |
| 2. 4th "addr" parameter (assumed if struct sigcontext is defined, |
| i.e., SunOS 4.x/BSD). |
| |
| 3. None (if no method is available). This case just prints a |
| message before aborting with a core dump. That way the user at |
| least knows that it *might* be a recursion problem. |
| |
| Jim Avera <jima@netcom.com> writes, on Tue, 5 Oct 93 19:27 PDT: |
| |
| "I got interested finding out how a program could catch and |
| diagnose its own stack overflow, and ended up modifying m4 to do |
| this. Now it prints a nice error message and exits. |
| |
| How it works: SIGSEGV is caught using a separate signal stack. The |
| signal handler declares a stack overflow if the fault address is |
| near the end of the stack region, or if the maximum VM address |
| space limit has been reached. Otherwise, it returns to re-execute |
| the instruction with SIG_DFL set, so that any real bugs cause a |
| core dump as usual." |
| |
| Jim Avera <jima@netcom.com> writes, on Fri, 24 Jun 94 12:14 PDT: |
| |
| "The stack-overflow detection code would still be needed to avoid a |
| SIGSEGV abort if swap space was exhausted at the moment the stack |
| tried to grow. This is probably unlikely to occur with the |
| explicit nesting limit option of GNU m4." |
| |
| Jim Avera <jima@netcom.com> writes, on Wed, 6 Jul 1994 14:41 PDT: |
| |
| "When a stack overflow occurs, a SIGSEGV signal is sent, which by |
| default aborts the process with a core dump. |
| |
| The code in stackovf.c catches SIGSEGV using a separate signal |
| stack. The signal handler determines whether or not the SIGSEGV |
| arose from a stack overflow. If it is a stack overflow, an |
| external function is called (which, in m4, prints a message an |
| exits). Otherwise the SIGSEGV represents an m4 bug, and the signal |
| is re-raised with SIG_DFL set, which results in an abort and core |
| dump in the usual way. It seems important (to me) that internal m4 |
| bugs not be reported as user recursion errors, or vice-versa." */ |
| |
| /* Define this to see runtime debug info. Implied by DEBUG. */ |
| /*#define DEBUG_STKOVF */ |
| |
| #include <config.h> |
| |
| #include "m4.h" |
| |
| #ifdef USE_STACKOVF |
| |
| #include <sys/time.h> |
| #include <sys/resource.h> |
| |
| #if HAVE_SIGINFO_H |
| # include <siginfo.h> |
| #endif |
| |
| #ifndef SA_RESETHAND |
| # define SA_RESETHAND 0 |
| #endif |
| #ifndef SA_SIGINFO |
| # define SA_SIGINFO 0 |
| #endif |
| |
| #ifndef SIGSTKSZ |
| # define SIGSTKSZ 8192 |
| #endif |
| |
| /* If the trap address is within STACKOVF_DETECT bytes of the calculated |
| stack limit, we diagnose a stack overflow. This must be large enough |
| to cover errors in our estimatation of the limit address, and to |
| account for the maximum size of local variables (the amount the |
| trapping reference might exceed the stack limit). Also, some machines |
| may report an arbitrary address within the same page frame. |
| If the value is too large, we might call some other SIGSEGV a stack |
| overflow, masking a bug. */ |
| |
| #ifndef STACKOVF_DETECT |
| # define STACKOVF_DETECT 16384 |
| #endif |
| |
| typedef void (*handler_t) (void); |
| |
| #if defined(__ultrix) && defined(__vax) |
| extern char *sbrk (int); |
| extern int getrlimit (int, struct rlimit *); |
| extern int sigstack (struct sigstack *, struct sigstack *); |
| extern int sigvec (int, struct sigvec *, struct sigvec *); |
| #endif |
| |
| static void *stackbuf; |
| static const char *stackbot; |
| static const char *stackend; |
| static const char *arg0; |
| static handler_t stackovf_handler; |
| |
| /* The following OS-independent procedure is called from the SIGSEGV |
| signal handler. The signal handler obtains information about the trap |
| in an OS-dependent manner, and passes a parameter with the meanings as |
| explained below. |
| |
| If the OS explicitly identifies a stack overflow trap, either pass |
| PARAM_STACKOVF if a stack overflow, or pass PARAM_NOSTACKOVF if not |
| (id est, it is a random bounds violation). Otherwise, if the fault |
| address is available, pass the fault address. Otherwise (if no |
| information is available), pass NULL. |
| |
| Not given an explicit indication, we compare the fault address with |
| the estimated stack limit, and test to see if overall VM space is |
| exhausted. |
| |
| If a stack overflow is identified, then the external *stackovf_handler |
| function is called, which should print an error message and exit. If |
| it is NOT a stack overflow, then we silently abort with a core dump by |
| returning to re-raise the SIGSEGV with SIG_DFL set. If indeterminate, |
| then we do not call *stackovf_handler, but instead print an ambiguous |
| message and abort with a core dump. This only occurs on systems which |
| provide no information, but is better than nothing. */ |
| |
| #define PARAM_STACKOVF ((const char *) (1 + STACKOVF_DETECT)) |
| #define PARAM_NOSTACKOVF ((const char *) (2 + STACKOVF_DETECT)) |
| |
| static void |
| process_sigsegv (int signo, const char *p) |
| { |
| ptrdiff_t diff; |
| diff = (p - stackend); |
| |
| #ifdef DEBUG_STKOVF |
| { |
| char buf[200]; |
| |
| sprintf (buf, |
| "process_sigsegv: p=%p stackend=%p diff=%" PRIdPTR "bot=%p\n", |
| p, stackend, diff, stackbot); |
| write (2, buf, strlen (buf)); |
| } |
| #endif |
| |
| if (p != PARAM_NOSTACKOVF) |
| { |
| if ((long) sbrk (8192) == (long) -1) |
| { |
| const char *cp; |
| |
| /* sbrk failed. Assume the RLIMIT_VMEM prevents expansion even |
| if the stack limit has not been reached. */ |
| |
| /* FIXME - calling gettext inside a signal handler is |
| dangerous, since it can call malloc, which is not signal |
| safe. We can sort of justify it by the fact that this |
| handler is designed to exit() the program, but it could |
| really use a better fix. */ |
| cp = _("VMEM limit exceeded?\n"); |
| write (2, cp, strlen (cp)); |
| p = PARAM_STACKOVF; |
| } |
| if (diff >= -STACKOVF_DETECT && diff <= STACKOVF_DETECT) |
| { |
| |
| /* The fault address is "sufficiently close" to the stack lim. */ |
| |
| p = PARAM_STACKOVF; |
| } |
| if (p == PARAM_STACKOVF) |
| { |
| |
| /* We have determined that this is indeed a stack overflow. */ |
| |
| (*stackovf_handler) (); /* should call exit() */ |
| } |
| } |
| if (p == NULL) |
| { |
| const char *cp; |
| |
| /* FIXME - calling gettext inside a signal handler is dangerous, |
| since it can call malloc, which is not signal safe. */ |
| cp = _("\ |
| Memory bounds violation detected (SIGSEGV). Either a stack overflow\n\ |
| occurred, or there is a bug in "); |
| write (2, cp, strlen (cp)); |
| write (2, arg0, strlen (arg0)); |
| cp = _(". Check for possible infinite recursion.\n"); |
| write (2, cp, strlen (cp)); |
| } |
| |
| /* Return to re-execute the instruction which caused the trap with |
| SIGSEGV set to SIG_DFL. An abort with core dump should occur. */ |
| |
| signal (signo, SIG_DFL); |
| } |
| |
| #if HAVE_STRUCT_SIGACTION_SA_SIGACTION |
| |
| /* POSIX. */ |
| |
| static void |
| sigsegv_handler (int signo, siginfo_t *ip, void *context) |
| { |
| process_sigsegv |
| (signo, (ip != NULL |
| && ip->si_signo == SIGSEGV ? (char *) ip->si_addr : NULL)); |
| } |
| |
| #elif HAVE_SIGINFO_T |
| |
| /* SVR4. */ |
| |
| static void |
| sigsegv_handler (int signo, siginfo_t *ip) |
| { |
| process_sigsegv |
| (signo, (ip != NULL |
| && ip->si_signo == SIGSEGV ? (char *) ip->si_addr : NULL)); |
| } |
| |
| #elif HAVE_SIGCONTEXT |
| |
| /* SunOS 4.x (and BSD?). (not tested) */ |
| |
| static void |
| sigsegv_handler (int signo, int code, struct sigcontext *scp, char *addr) |
| { |
| process_sigsegv (signo, addr); |
| } |
| |
| #else /* not HAVE_SIGCONTEXT */ |
| |
| /* OS provides no information. */ |
| |
| static void |
| sigsegv_handler (int signo) |
| { |
| process_sigsegv (signo, NULL); |
| } |
| |
| #endif /* not HAVE_SIGCONTEXT */ |
| |
| /* Arrange to trap a stack-overflow and call a specified handler. The |
| call is on a dedicated signal stack. |
| |
| argv and envp are as passed to main(). |
| |
| If a stack overflow is not detected, then the SIGSEGV is re-raised |
| with action set to SIG_DFL, causing an abort and coredump in the usual |
| way. |
| |
| Detection of a stack overflow depends on the trap address being near |
| the stack limit address. The stack limit can not be directly |
| determined in a portable way, but we make an estimate based on the |
| address of the argv and environment vectors, their contents, and the |
| maximum stack size obtained using getrlimit. */ |
| |
| void |
| setup_stackovf_trap (char *const *argv, char *const *envp, handler_t handler) |
| { |
| struct rlimit rl; |
| rlim_t stack_len; |
| int grows_upward; |
| register char *const *v; |
| register char *p; |
| #if HAVE_SIGACTION && defined SA_ONSTACK |
| struct sigaction act; |
| #elif HAVE_SIGVEC && defined SV_ONSTACK |
| struct sigvec vec; |
| #else |
| |
| Error - Do not know how to set up stack-ovf trap handler... |
| |
| #endif |
| |
| arg0 = argv[0]; |
| stackovf_handler = handler; |
| |
| /* Calculate the approximate expected addr for a stack-ovf trap. */ |
| |
| if (getrlimit (RLIMIT_STACK, &rl) < 0) |
| error (EXIT_FAILURE, errno, "getrlimit"); |
| stack_len = (rl.rlim_cur < rl.rlim_max ? rl.rlim_cur : rl.rlim_max); |
| stackbot = (char *) argv; |
| grows_upward = ((char *) &stack_len > stackbot); |
| if (grows_upward) |
| { |
| |
| /* Grows toward increasing addresses. */ |
| |
| for (v = argv; (p = (char *) *v) != NULL; v++) |
| { |
| if (p < stackbot) |
| stackbot = p; |
| } |
| if ((char *) envp < stackbot) |
| stackbot = (char *) envp; |
| for (v = envp; (p = (char *) *v) != NULL; v++) |
| { |
| if (p < stackbot) |
| stackbot = p; |
| } |
| stackend = stackbot + stack_len; |
| } |
| else |
| { |
| |
| /* The stack grows "downward" (toward decreasing addresses). */ |
| |
| for (v = argv; (p = (char *) *v) != NULL; v++) |
| { |
| if (p > stackbot) |
| stackbot = p; |
| } |
| if ((char *) envp > stackbot) |
| stackbot = (char *) envp; |
| for (v = envp; (p = (char *) *v) != NULL; v++) |
| { |
| if (p > stackbot) |
| stackbot = p; |
| } |
| stackend = stackbot - stack_len; |
| } |
| |
| /* Allocate a separate signal-handler stack. */ |
| |
| #if HAVE_SIGALTSTACK && (HAVE_SIGINFO_T || ! HAVE_SIGSTACK) |
| |
| /* Use sigaltstack only if siginfo_t is available, unless there is no |
| choice. */ |
| |
| { |
| stack_t ss; |
| # ifndef HAVE_STACK_T_SS_SP |
| /* This workaround is for BSD/OS 4.0.1: |
| http://lists.gnu.org/archive/html/bug-m4/2006-12/msg00004.html */ |
| # define ss_sp ss_base |
| # endif /* ! HAVE_STACK_T_SS_SP */ |
| |
| stackbuf = xmalloc (SIGSTKSZ); |
| |
| ss.ss_size = SIGSTKSZ; |
| ss.ss_sp = stackbuf; |
| ss.ss_flags = 0; |
| if (sigaltstack (&ss, NULL) < 0) |
| { |
| /* Oops - sigstack exists but doesn't work. We can't install |
| the overflow detector, but should gracefully treat it as |
| though sigstack doesn't exist. For example, this happens |
| when compiled with Linux 2.1 headers but run against Linux |
| 2.0 kernel. */ |
| free (stackbuf); |
| if (errno == ENOSYS) |
| return; |
| error (EXIT_FAILURE, errno, "sigaltstack"); |
| } |
| } |
| |
| #elif HAVE_SIGSTACK |
| |
| { |
| struct sigstack ss; |
| stackbuf = xmalloc (2 * SIGSTKSZ); |
| |
| ss.ss_sp = stackbuf + SIGSTKSZ; |
| ss.ss_onstack = 0; |
| if (sigstack (&ss, NULL) < 0) |
| { |
| /* Oops - sigstack exists but doesn't work. We can't install |
| the overflow detector, but should gracefully treat it as |
| though sigstack doesn't exist. For example, this happens |
| when compiled with Linux 2.1 headers but run against Linux |
| 2.0 kernel. */ |
| free (stackbuf); |
| if (errno == ENOSYS) |
| return; |
| error (EXIT_FAILURE, errno, "sigstack"); |
| } |
| } |
| |
| #else /* not HAVE_SIGSTACK */ |
| |
| Error - Do not know how to set up stack-ovf trap handler... |
| |
| #endif /* not HAVE_SIGSTACK */ |
| |
| /* Arm the SIGSEGV signal handler. */ |
| |
| #if HAVE_SIGACTION && defined SA_ONSTACK |
| |
| sigaction (SIGSEGV, NULL, &act); |
| # if HAVE_STRUCT_SIGACTION_SA_SIGACTION |
| act.sa_sigaction = sigsegv_handler; |
| # else /* ! HAVE_STRUCT_SIGACTION_SA_SIGACTION */ |
| act.sa_handler = (RETSIGTYPE (*) (int)) sigsegv_handler; |
| # endif /* ! HAVE_STRUCT_SIGACTION_SA_SIGACTION */ |
| sigemptyset (&act.sa_mask); |
| act.sa_flags = (SA_ONSTACK | SA_RESETHAND | SA_SIGINFO); |
| if (sigaction (SIGSEGV, &act, NULL) < 0) |
| error (EXIT_FAILURE, errno, "sigaction"); |
| |
| #else /* ! HAVE_SIGACTION */ |
| |
| vec.sv_handler = (RETSIGTYPE (*) (int)) sigsegv_handler; |
| vec.sv_mask = 0; |
| vec.sv_flags = (SV_ONSTACK | SV_RESETHAND); |
| if (sigvec (SIGSEGV, &vec, NULL) < 0) |
| error (EXIT_FAILURE, errno, "sigvec"); |
| |
| #endif /* ! HAVE_SIGACTION */ |
| |
| } |
| |
| void |
| stackovf_exit (void) |
| { |
| DELETE (stackbuf); |
| } |
| |
| #endif /* USE_STACKOVF */ |