You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by tr...@apache.org on 2004/02/20 12:41:05 UTC

cvs commit: apache-1.3/src/modules/experimental mod_backtrace.c mod_whatkilledus.c mod_whatkilledus.exp

trawick     2004/02/20 03:41:05

  Modified:    src      CHANGES
  Added:       src/modules/experimental mod_backtrace.c mod_whatkilledus.c
                        mod_whatkilledus.exp
  Log:
  Add mod_whatkilledus and mod_backtrace (experimental) for
  reporting diagnostic information after a child process crash.
  See source code for documentation.
  
  Revision  Changes    Path
  1.1928    +5 -0      apache-1.3/src/CHANGES
  
  Index: CHANGES
  ===================================================================
  RCS file: /home/cvs/apache-1.3/src/CHANGES,v
  retrieving revision 1.1927
  retrieving revision 1.1928
  diff -u -r1.1927 -r1.1928
  --- CHANGES	29 Jan 2004 21:46:50 -0000	1.1927
  +++ CHANGES	20 Feb 2004 11:41:04 -0000	1.1928
  @@ -1,5 +1,10 @@
   Changes with Apache 1.3.30
   
  +  *) Add mod_whatkilledus and mod_backtrace (experimental) for
  +     reporting diagnostic information after a child process crash.
  +     See source code for documentation.
  +     [Jeff Trawick, with help from mod_log_forensic]
  +
     *) mod_usertrack no longer inspects the Cookie2 header for
        the cookie name. PR 11475.  [Chris Darrochi <chrisd pearsoncmg.com>]
   
  
  
  
  1.1                  apache-1.3/src/modules/experimental/mod_backtrace.c
  
  Index: mod_backtrace.c
  ===================================================================
  /* Copyright 2004 The Apache Software Foundation
   *
   * Licensed 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.
   */
  
  #if !defined(__linux__) && !defined(__FreeBSD__)
  #error This module is currently only implemented for Linux and FreeBSD.
  #endif
  
  /*
   * Documentation:
   *
   * mod_backtrace is an experimental module for Apache httpd 1.3 which
   * collects backtraces when a child process crashes.  Currently it is
   * implemented only on Linux and FreeBSD, but other platforms could be
   * supported in the future.  You should verify that it works reasonably
   * on your system before putting it in production.
   *
   * It implements a fatal exception hook that will be called when a child
   * process crashes.  In the exception hook it uses system library routines
   * to obtain information about the call stack, and it writes the call
   * stack to a log file or the web server error log.  The backtrace is a
   * critical piece of information when determining the failing software
   * component that caused the crash.  Note that the backtrace written by
   * mod_backtrace may not have as much information as a debugger can
   * display from a core dump.
   *
   * Apache httpd requirements for mod_backtrace:
   *
   *   Apache httpd >= 1.3.30 must be built with the AP_ENABLE_EXCEPTION_HOOK
   *   symbol defined and mod_so enabled.  AP_ENABLE_EXCEPTION_HOOK is already
   *   defined in ap_config.h for some platforms, including AIX, Linux,
   *   Solaris, and HP-UX.  It can be enabled for other platforms by including
   *   -DAP_ENABLE_EXCEPTION_HOOK in CFLAGS when the configure script is
   *   invoked.
   *
   * Compiling mod_backtrace:
   *
   *   Linux:
   *     apxs -ci mod_backtrace.c
   *
   *   FreeBSD:
   *     install libexecinfo from the Ports system then
   *     apxs -ci -L/usr/local/lib -lexecinfo mod_backtrace.c
   *
   * Activating mod_backtrace:
   *
   *   1. Load it like any other DSO:
   *        LoadModule backtrace_module libexec/mod_backtrace.so
   *        ...
   *        AddModule mod_backtrace.c
   *
   *   2. Enable exception hooks for modules like mod_backtrace:
   *        EnableExceptionHook On
   *
   *   3. Choose where backtrace information should be written.
   *      If you want backtraces from crashes to be reported some place other
   *      than the error log, use the BacktraceLog directive to specify a
   *      fully-qualified filename for the log to which backtraces will be
   *      written.  Note that the web server user id (e.g., "nobody") must
   *      be able to create or append to this log file, as the log file is
   *      not opened until a crash occurs.
   */
  
  #include "httpd.h"
  #include "http_config.h"
  #include "http_log.h"
  
  #include <fcntl.h>
  #include <unistd.h>
  #include <execinfo.h>
  
  static char *log_fname;
  
  static void bt_show_backtrace(int sig)
  {
      char msgbuf[128];
      size_t size;
      void *array[20];
      extern int main();
      int logfd;
      time_t now;
      char msg_prefix[60];
      char *newline;
      int using_errorlog = 1;
  
      time(&now);
      ap_snprintf(msg_prefix, sizeof msg_prefix,
                  "[%s pid %ld mod_backtrace",
                  asctime(localtime(&now)),
                  (long)getpid());
      newline = strchr(msg_prefix, '\n'); /* dang asctime() */
      if (newline) {                      /* silly we are */
          *newline = ']';
      }
  
      if (log_fname) {
          logfd = open(log_fname, O_WRONLY|O_APPEND|O_CREAT, 0644);
          if (logfd == -1) {
              logfd = 2; /* unix, so fd 2 is the web server error log */
              ap_snprintf(msgbuf, sizeof msgbuf,
                          "%s error %d opening %s\n",
                          msg_prefix, errno, log_fname);
              write(logfd, msgbuf, strlen(msgbuf));
          }
          else {
              using_errorlog = 0;
          }
      }
      else {
          logfd = 2;
      }
      
      ap_snprintf(msgbuf, sizeof msgbuf,
                  "%s backtrace for signal %d\n",
                  msg_prefix, sig);
      write(logfd, msgbuf, strlen(msgbuf));
  
      /* the address of main() can be useful if we're on old 
       * glibc and get only addresses for stack frames... knowing
       * where main() is then is a useful clue
       */
      ap_snprintf(msgbuf, sizeof msgbuf,
                  "%s main() is at %pp\n",
                  msg_prefix,
                  main);/* don't you DARE put parens after "main" */
      write(logfd, msgbuf, strlen(msgbuf));
  
      size = backtrace(array, sizeof array / sizeof array[0]);
      backtrace_symbols_fd(array, size, logfd);
      ap_snprintf(msgbuf, sizeof msgbuf,
                  "%s end of report\n",
                  msg_prefix);
      write(logfd, msgbuf, strlen(msgbuf));
      if (!using_errorlog) {
          close(logfd);
      }
  }
  
  static void bt_exception_hook(ap_exception_info_t *ei)
  {
      bt_show_backtrace(ei->sig);
  }
  
  static void bt_init(server_rec *s, pool *p)
  {
      int rc = ap_add_fatal_exception_hook(bt_exception_hook);
      
      if (rc) {
          ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, s,
                       "fatal exception hooks are not enabled; please "
                       "enable them with the EnableExceptionHook directive "
                       "or disable mod_backtrace");
      }
  }
  
  static const char *bt_cmd_file(cmd_parms *cmd, void *dconf, char *fname)
  {
      log_fname = ap_pstrdup(cmd->pool, fname);
      return NULL;
  }
  
  static const command_rec bt_command_table[] = {
      {
          "BacktraceLog", bt_cmd_file, NULL, RSRC_CONF, TAKE1, "the fully-qualified filename of the mod_backtrace logfile"
      }
      ,
      {
          NULL
      }
  };
  
  module MODULE_VAR_EXPORT backtrace_module = {
      STANDARD_MODULE_STUFF,
      bt_init,                    /* initializer */
      NULL,                       /* dir config creater */
      NULL,                       /* dir merger --- default is to override */
      NULL,                       /* server config */
      NULL,                       /* merge server config */
      bt_command_table,           /* command table */
      NULL,                       /* handlers */
      NULL,                       /* filename translation */
      NULL,                       /* check_user_id */
      NULL,                       /* check auth */
      NULL,                       /* check access */
      NULL,                       /* type_checker */
      NULL,                       /* fixups */
      NULL,                       /* logger */
      NULL,                       /* header parser */
      NULL,                       /* child init */
      NULL,                       /* child exit */
      NULL                        /* post read request */
  };
  
  
  
  1.1                  apache-1.3/src/modules/experimental/mod_whatkilledus.c
  
  Index: mod_whatkilledus.c
  ===================================================================
  /* Copyright 2004 The Apache Software Foundation
   *
   * Licensed 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.
   */
  
  /*
   * Documentation:
   *
   * mod_whatkilledus is an experimental module for Apache httpd 1.3 which
   * tracks the current request and logs a report of the active request
   * when a child process crashes.  You should verify that it works reasonably
   * on your system before putting it in production.
   *
   * mod_whatkilledus is called during request processing to save information
   * about the current request.  It also implements a fatal exception hook
   * that will be called when a child process crashes.
   *
   * Apache httpd requirements for mod_whatkilledus:
   *
   *   Apache httpd >= 1.3.30 must be built with the AP_ENABLE_EXCEPTION_HOOK
   *   symbol defined and mod_so enabled.  AP_ENABLE_EXCEPTION_HOOK is already
   *   defined in ap_config.h for some platforms, including AIX, Linux,
   *   Solaris, and HP-UX.  It can be enabled for other platforms by including
   *   -DAP_ENABLE_EXCEPTION_HOOK in CFLAGS when the configure script is
   *   invoked.
   *
   * Compiling mod_whatkilledus:
   *
   *   AIX:
   *     apxs -ci -I/path/to/apache/src/main -Wl,-bE:mod_whatkilledus.exp mod_whatkilledus.c
   *
   *   other:
   *     apxs -ci -I/path/to/apache/src/main mod_whatkilledus.c
   *
   * Activating mod_whatkilledus:
   *
   *   1. Load it like any other DSO, but the AddModule should come
   *      last so that if another module causes a crash early in
   *      request processing mod_whatkilledus will have already
   *      had a chance to save information about the request.
   *
   *        LoadModule whatkilledus_module libexec/mod_whatkilledus.so
   *        ...
   *        AddModule mod_whatkilledus.c
   *
   *   2. Enable exception hooks for modules like mod_whatkilledus:
   *        EnableExceptionHook On
   *
   *   3. Choose where the report on current activity should be written.  If
   *      you want it reported to some place other than the error log, use the
   *      WhatKilledUsLog directive to specify a fully-qualified filename for
   *      the log.  Note that the web server user id (e.g., "nobody") must
   *      be able to create or append to this log file, as the log file is
   *      not opened until a crash occurs.
   */
  
  #include "httpd.h"
  #include "http_config.h"
  #include "http_log.h"
  
  #include "test_char.h" /* an odd one since it is not installed */
  
  /* this module is not thread-safe; it is intended for Apache 1.3 on
   * platforms that use single-threaded child processes for handling
   * client connections
   */
  static char *log_fname;
  static char *local_addr;
  static char *remote_addr;
  static char *request_plus_headers;
  static char buffer[2048];
  
  static void exception_hook(ap_exception_info_t *ei)
  {
      int msg_len;
      int logfd;
      char msg_prefix[60];
      time_t now;
      char *newline;
      int using_errorlog = 1;
  
      time(&now);
      ap_snprintf(msg_prefix, sizeof msg_prefix,
                  "[%s pid %ld mod_whatkilledus",
                  asctime(localtime(&now)),
                  (long)getpid());
      newline = strchr(msg_prefix, '\n'); /* dang asctime() */
      if (newline) {                      /* silly we are */
          *newline = ']';
      }
  
      if (log_fname) {
          logfd = open(log_fname, O_WRONLY|O_APPEND|O_CREAT, 0644);
          if (logfd == -1) {
              logfd = 2; /* unix, so fd 2 is the web server error log */
              ap_snprintf(buffer, sizeof buffer,
                          "%s error %d opening %s\n",
                          msg_prefix, errno, log_fname);
              write(logfd, buffer, strlen(buffer));
          }
          else {
              using_errorlog = 0;
          }
      }
      else {
          logfd = 2;
      }
  
      msg_len = ap_snprintf(buffer, sizeof buffer,
                            "%s sig %d crash\n",
                            msg_prefix, ei->sig);
      write(logfd, buffer, msg_len);
  
      if (local_addr) {
          msg_len = ap_snprintf(buffer, sizeof buffer,
                                "%s active connection: %s->%s\n",
                                msg_prefix, remote_addr, local_addr);
      }
      else {
          msg_len = ap_snprintf(buffer, sizeof buffer,
                                "%s no active connection at crash\n",
                                msg_prefix);
      }
  
      write(logfd, buffer, msg_len);
  
      if (request_plus_headers) {
          msg_len = ap_snprintf(buffer, sizeof buffer,
                                "%s active request:\n",
                                msg_prefix);
          write(logfd, buffer, msg_len);
          write(logfd, request_plus_headers, strlen(request_plus_headers));
      }
      else {
          msg_len = ap_snprintf(buffer, sizeof buffer,
                                "%s no request active at crash\n",
                                msg_prefix);
          write(logfd, buffer, msg_len);
      }
      msg_len = ap_snprintf(buffer, sizeof buffer,
                            "%s end of report\n",
                            msg_prefix);
      write(logfd, buffer, msg_len);
      if (!using_errorlog) {
          close(logfd);
      }
  }
  
  static void init(server_rec *s, pool *p)
  {
      int rc = ap_add_fatal_exception_hook(exception_hook);
  
      if (rc) {
          ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, s,
                       "fatal exception hooks are not enabled; please "
                       "enable them with the EnableExceptionHook directive "
                       "or disable mod_whatkilledus");
      }
  }
  
  static void clear_conn_info(void *ignored)
  {
      local_addr = remote_addr = NULL;
  }
  
  static void save_conn_info(request_rec *r)
  {
      conn_rec *c = r->connection;
      local_addr =  ap_psprintf(c->pool, "%pI", &c->local_addr);
      remote_addr = ap_psprintf(c->pool, "%pI", &c->remote_addr);
  
      ap_register_cleanup(c->pool, NULL, clear_conn_info, ap_null_cleanup);
  }
  
  static void clear_req_info(void *ignored)
  {
      request_plus_headers = NULL;
  }
  
  #define FIELD_SEPARATOR   "|"
  #define KEYVAL_SEPARATOR  ":"
  
  /* graciously lifted from mod_log_forensic */
  
  static int count_string(const char *p)
  {
      int n;
  
      for (n = 0; *p; ++p, ++n) {
          if (test_char_table[*(unsigned char *)p] & T_ESCAPE_FORENSIC) {
              n += 2;
          }
      }
      return n;
  }
  
  static int count_headers(void *in_len, const char *key, const char *value)
  {
      int *len = in_len;
  
      *len += strlen(FIELD_SEPARATOR);
      *len += count_string(key);
      *len += strlen(KEYVAL_SEPARATOR);
      *len += count_string(value);
  
      return 1;
  }
  
  static char *copy_and_escape(char *loc, const char *str) {
      /* mod_log_forensic will SIGABRT here if it messed up the count
       * and overflowed; mod_whatkilledus will segfault here or will
       * SIGABRT back in the caller if that happens
       */
      for ( ; *str; ++str) {
          if (test_char_table[*(unsigned char *)str] & T_ESCAPE_FORENSIC) {
              *loc++ = '%';
              sprintf(loc, "%02x", *(unsigned char *)str);
              loc += 2;
          }
          else {
              *loc++ = *str;
          }
      }
      *loc = '\0';
      return loc;
  }
  
  static int copy_headers(void *in_ch, const char *key, const char *value)
  {
      char **ch = in_ch;
  
      strcpy(*ch, FIELD_SEPARATOR);
      *ch += strlen(FIELD_SEPARATOR);
  
      *ch = copy_and_escape(*ch, key);
  
      strcpy(*ch, KEYVAL_SEPARATOR);
      *ch += strlen(KEYVAL_SEPARATOR);
  
      *ch = copy_and_escape(*ch, value);
  
      return 1;
  }
  
  static void save_req_info(request_rec *r)
  {
      /* to save for the request:
       * r->the_request + 
       * foreach header:
       *   '|' + header field
       */
      int len = strlen(r->the_request);
      char *ch;
      ap_table_do(count_headers, &len, r->headers_in, NULL);
  
      request_plus_headers = ap_palloc(r->pool, len + 2 /* 2 for the '\n' + '\0' at end */);
      ch = request_plus_headers;
      strcpy(ch, r->the_request);
      ch += strlen(ch);
  
      ap_table_do(copy_headers, &ch, r->headers_in, NULL);
      *ch = '\n';
      *(ch + 1) = '\0';
  
      ap_assert(ch == request_plus_headers + len);
  
      ap_register_cleanup(r->pool, NULL, clear_req_info, ap_null_cleanup);
  }
  
  static int post_read(request_rec *r)
  {
      if (r->prev) { /* we were already called for this internal redirect */
          return DECLINED;
      }
  
      /* save whatever info, like client, which vhost, which port
       * (to know SSL or not), etc.
       */
      if (!local_addr) { /* first request on this connection */
          save_conn_info(r);
      }
  
      save_req_info(r);
  
      return DECLINED;
  }
  
  static const char *cmd_file(cmd_parms *cmd, void *dconf, char *fname)
  {
      log_fname = ap_pstrdup(cmd->pool, fname);
      return NULL;
  }
  
  static const command_rec command_table[] = {
      {
          "WhatKilledUsLog", cmd_file, NULL, RSRC_CONF, TAKE1, "the fully-qualified filename of the mod_whatkilledus logfile"
      }
      ,
      {
          NULL
      }
  };
  
  module MODULE_VAR_EXPORT whatkilledus_module = {
      STANDARD_MODULE_STUFF,
      init,                       /* initializer */
      NULL,                       /* create per-dir config */
      NULL,                       /* merge per-dir config */
      NULL,                       /* server config */
      NULL,                       /* merge server config */
      command_table,              /* command table */
      NULL,                       /* handlers */
      NULL,                       /* filename translation */
      NULL,                       /* check_user_id */
      NULL,                       /* check auth */
      NULL,                       /* check access */
      NULL,                       /* type_checker */
      NULL,                       /* fixups */
      NULL,                       /* logger */
      NULL,                       /* header parser */
      NULL,                       /* child_init */
      NULL,                       /* child_exit */
      post_read                   /* post read-request */
  };
  
  
  
  1.1                  apache-1.3/src/modules/experimental/mod_whatkilledus.exp
  
  Index: mod_whatkilledus.exp
  ===================================================================
  whatkilledus_module