Reviewed-by: Jeremy Huddleston <[email protected]> On Sep 29, 2011, at 03:21, Barry Scott wrote:
> It is useful to be able to run an external program to analyse > a crashed server process. The server will run a user supplied > program in a subprocess of the server. > > The subprocess is created when the server starts up so that all > resources needed to create the subprocess are available. It is not > always possible to start up a process from within a crashed server. > For example if the malloc heap is corrupt fork will not work. > > If the server crashes a "crash" message is sent to the handler on > its stdin. The handler can then respond in whatever way it wishes. > If the handler wants the server to continue, return from > xorg_backtrace, it must write a line to its stdout. > > When then server exits normally the handler is sent the "exit" > message just before the server exits. > > Signed-off-by: Barry Scott <[email protected]> > Reviewed-by: Simon Farnsworth <[email protected]> > --- > change from V3: > * close backtrace_handler_stdin in child process > > change from V2: > * restore trailing NL in main.c > * beef up comments to make it clea how loops are exited > when the child process dies > * Use _X_HIDDEN for new symbols that are not to be exported > > change from V1: > > * check for EAGAIN and EINTR on calls to read(), write() and waitpid() > * put -backtrace into lexical position in the command parser and usage > * fix coding style white space and condition ordering > * update man page with more details > * call xorg_cleanup_backtrace() from FatalError() > > dix/main.c | 5 ++ > include/os.h | 3 + > man/Xserver.man | 20 +++++++++ > os/backtrace.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++- > os/log.c | 3 + > os/utils.c | 8 ++++ > 6 files changed, 159 insertions(+), 3 deletions(-) > > diff --git a/dix/main.c b/dix/main.c > index 16575ce..3326e78 100644 > --- a/dix/main.c > +++ b/dix/main.c > @@ -147,6 +147,8 @@ int main(int argc, char *argv[], char *envp[]) > > ProcessCommandLine(argc, argv); > > + xorg_init_backtrace(); > + > alwaysCheckForInput[0] = 0; > alwaysCheckForInput[1] = 1; > while(1) > @@ -354,6 +356,9 @@ int main(int argc, char *argv[], char *envp[]) > free(ConnectionInfo); > ConnectionInfo = NULL; > } > + > + xorg_cleanup_backtrace(); > + > return 0; > } > > diff --git a/include/os.h b/include/os.h > index a553f57..00336e5 100644 > --- a/include/os.h > +++ b/include/os.h > @@ -541,6 +541,9 @@ extern _X_EXPORT void ErrorF(const char *f, ...) > _X_ATTRIBUTE_PRINTF(1,2); > extern _X_EXPORT void Error(const char *str); > extern _X_EXPORT void LogPrintMarkers(void); > > +extern _X_HIDDEN void xorg_init_backtrace(void); > extern _X_EXPORT void xorg_backtrace(void); > +extern _X_HIDDEN void xorg_cleanup_backtrace(void); > +extern _X_HIDDEN const char *BacktraceHandlerPath; > > #endif /* OS_H */ > diff --git a/man/Xserver.man b/man/Xserver.man > index f743912..9def022 100644 > --- a/man/Xserver.man > +++ b/man/Xserver.man > @@ -100,6 +100,9 @@ specifies a file which contains a collection of > authorization records used > to authenticate access. See also the \fIxdm\fP(1) and > \fIXsecurity\fP(__miscmansuffix__) manual pages. > .TP 8 > +.B \-backtrace \fIbacktrace-handler-path\fP > +specifies the path to the optional backtrace handler program. See > \fBBACKTRACE HANDLER\fP below. > +.TP 8 > .B \-br > sets the default root window to solid black instead of the standard root weave > pattern. This is the default unless -retro or -wr is specified. > @@ -530,6 +533,23 @@ the following font path: > /usr/share/fonts/default/ghostscript > .fi > > +.SH BACKTRACE HANDLER > + > +The optional backtrace handler program will be run in a subprocess when > +the server starts. The handler is invoked with argv[1] set to the PID > +of the server. > +.PP > +The handler is expected to read a line from stdin that will tell the > +handler what has happened to the server. > +.PP > +When the server is about to exit normally "exit\\n" is sent to the handler. > +.PP > +If the server crashes "crash\n" is sent to the handler and the server will > +wait for a line to be written out on the handler's stdout. This causes > +the server to return from the xorg_backtrace function. Typically after > calling > +xorg_backtrace additional error messages are logged and the server shuts > down. > +If you do not want the server to continue then kill it. > + > .SH FILES > .TP 30 > .I /etc/X\fBn\fP.hosts > diff --git a/os/backtrace.c b/os/backtrace.c > index 7ca6dab..bcdcbf6 100644 > --- a/os/backtrace.c > +++ b/os/backtrace.c > @@ -28,6 +28,11 @@ > #include "os.h" > #include "misc.h" > > +#include <unistd.h> > +#include <errno.h> > +#include <sys/types.h> > +#include <sys/wait.h> > + > #ifdef HAVE_BACKTRACE > #ifndef _GNU_SOURCE > #define _GNU_SOURCE > @@ -35,7 +40,7 @@ > #include <dlfcn.h> > #include <execinfo.h> > > -void xorg_backtrace(void) > +void xorg_builtin_backtrace(void) > { > void *array[64]; > const char *mod; > @@ -180,7 +185,7 @@ static int xorg_backtrace_pstack(void) { > > # if defined(HAVE_PSTACK) || defined(HAVE_WALKCONTEXT) > > -void xorg_backtrace(void) { > +void xorg_builtin_backtrace(void) { > > ErrorF("\nBacktrace:\n"); > > @@ -207,7 +212,119 @@ void xorg_backtrace(void) { > # else > > /* Default fallback if we can't find any way to get a backtrace */ > -void xorg_backtrace(void) { return; } > +void xorg_builtin_backtrace(void) { return; } > > # endif > #endif > + > +const char *BacktraceHandlerPath = NULL; > + > +static int backtrace_handler_stdin; > +static int backtrace_handler_stdout; > +static pid_t child_pid; > + > +static const char cmd_crash[] = "crash\n"; > +static const char cmd_exit[] = "exit\n"; > + > +static void report_init_error(const char *msg) { > + /* prevent any further use of the handler */ > + BacktraceHandlerPath = NULL; > + FatalError("%s - errno %d", msg, errno); > +} > + > +void xorg_init_backtrace(void) { > + char pid_buffer[32]; > + int pipe_fds[2]; > + int child_stdin; > + int child_stdout; > + > + if (!BacktraceHandlerPath) > + return; > + > + if (pipe(pipe_fds) != 0) > + report_init_error("failed to create pipe"); > + backtrace_handler_stdin = pipe_fds[1]; > + child_stdin = pipe_fds[0]; > + > + if (pipe(pipe_fds) != 0) > + report_init_error("failed to create pipe"); > + backtrace_handler_stdout = pipe_fds[0]; > + child_stdout = pipe_fds[1]; > + > + /* server pid will be passed in argv[1] */ > + snprintf( pid_buffer, sizeof(pid_buffer), "%d", getpid()); > + > + child_pid = fork(); > + if (child_pid < 0) { > + report_init_error("failed to fork"); > + } else { > + if (child_pid == 0) { > + /* setup stdin, stdout to point to the pipes */ > + dup2(child_stdin, STDIN_FILENO); > + dup2(child_stdout, STDOUT_FILENO); > + > + /* close all other fds to the pipes */ > + close(child_stdin); > + close(child_stdout); > + close(backtrace_handler_stdin); > + close(backtrace_handler_stdout); > + > + execl(BacktraceHandlerPath, BacktraceHandlerPath, pid_buffer, > NULL); > + exit(1); > + } else { > + if (close(child_stdin) != 0) > + report_init_error("failed to close pipe fd"); > + if (close(child_stdout) != 0) > + report_init_error("failed to close pipe fd"); > + } > + } > +} > + > +void xorg_backtrace(void) { > + if (BacktraceHandlerPath) { > + int status; > + > + /* check that the handler process is still running */ > + if (waitpid(child_pid, &status, WNOHANG) == 0) { > + char buffer[1]; > + > + /* tell backtrace handler xorg has crashed */ > + while (write(backtrace_handler_stdin, cmd_crash, > sizeof(cmd_crash ) - 1) < 0) > + if (errno != EAGAIN && errno != EINTR) > + break; > + > + /* exit to caller after backtrace handler writes anything to > stdout */ > + while (read(backtrace_handler_stdout, buffer, sizeof(buffer)) < > 0) > + if (errno != EAGAIN && errno != EINTR) > + break; > + > + } else { > + /* handler process has quit? run builtin handler */ > + xorg_builtin_backtrace(); > + } > + } else { > + xorg_builtin_backtrace(); > + } > +} > + > +void xorg_cleanup_backtrace(void) { > + if (BacktraceHandlerPath) { > + int status; > + > + BacktraceHandlerPath = NULL; > + > + /* tell backtrace handler xorg is about to exit cleanly > + EPIPE is returned if the child process closes the pipe > + (which happens when the process exits) and the loop will exit */ > + while (write(backtrace_handler_stdin, cmd_exit, sizeof(cmd_exit ) - > 1) < 0) > + if (errno != EAGAIN && errno != EINTR) > + break; > + > + /* If the child has already exited waitpid return with ECHILD > + and the loop will exit */ > + while (waitpid(child_pid, &status, 0) < 0) > + if (errno != EINTR) > + break; > + > + } > +} > diff --git a/os/log.c b/os/log.c > index f519762..7b3409a 100644 > --- a/os/log.c > +++ b/os/log.c > @@ -523,6 +523,9 @@ FatalError(const char *f, ...) > va_list args; > static Bool beenhere = FALSE; > > + /* make sure any backtrace handler process is told to exit */ > + xorg_cleanup_backtrace(); > + > if (beenhere) > ErrorF("\nFatalError re-entered, aborting\n"); > else > diff --git a/os/utils.c b/os/utils.c > index 36cb46f..78c39e6 100644 > --- a/os/utils.c > +++ b/os/utils.c > @@ -470,6 +470,7 @@ void UseMsg(void) > ErrorF("-ac disable access control restrictions\n"); > ErrorF("-audit int set audit trail level\n"); > ErrorF("-auth file select authorization file\n"); > + ErrorF("-backtrace string path of program to run to handle a server > crash\n"); > ErrorF("-br create root window with black > background\n"); > ErrorF("+bs enable any backing store support\n"); > ErrorF("-bs disable any backing store support\n"); > @@ -616,6 +617,13 @@ ProcessCommandLine(int argc, char *argv[]) > else > UseMsg(); > } > + else if ( strcmp( argv[i], "-backtrace") == 0) > + { > + if(++i < argc) > + BacktraceHandlerPath = argv[i]; > + else > + UseMsg(); > + } > else if ( strcmp( argv[i], "-br") == 0) ; /* default */ > else if ( strcmp( argv[i], "+bs") == 0) > enableBackingStore = TRUE; > -- > 1.7.6 > _______________________________________________ [email protected]: X.Org development Archives: http://lists.x.org/archives/xorg-devel Info: http://lists.x.org/mailman/listinfo/xorg-devel
