From: Jeff Trawick Date: Fri, 20 Feb 2004 11:41:05 +0000 (+0000) Subject: Add mod_whatkilledus and mod_backtrace (experimental) for X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c3d12f1843b89869c13e0e26a3c7dc1fe8e0e179;p=thirdparty%2Fapache%2Fhttpd.git Add mod_whatkilledus and mod_backtrace (experimental) for reporting diagnostic information after a child process crash. See source code for documentation. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x@102694 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/CHANGES b/src/CHANGES index 851ef23ed52..906c6397e40 100644 --- a/src/CHANGES +++ b/src/CHANGES @@ -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 ] diff --git a/src/modules/experimental/mod_backtrace.c b/src/modules/experimental/mod_backtrace.c new file mode 100644 index 00000000000..b8ae77cbbb0 --- /dev/null +++ b/src/modules/experimental/mod_backtrace.c @@ -0,0 +1,203 @@ +/* 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 +#include +#include + +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 */ +}; diff --git a/src/modules/experimental/mod_whatkilledus.c b/src/modules/experimental/mod_whatkilledus.c new file mode 100644 index 00000000000..9c9e864874e --- /dev/null +++ b/src/modules/experimental/mod_whatkilledus.c @@ -0,0 +1,334 @@ +/* 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 */ +}; diff --git a/src/modules/experimental/mod_whatkilledus.exp b/src/modules/experimental/mod_whatkilledus.exp new file mode 100644 index 00000000000..f17ce676643 --- /dev/null +++ b/src/modules/experimental/mod_whatkilledus.exp @@ -0,0 +1 @@ +whatkilledus_module