BRL-CAD
backtrace.c
Go to the documentation of this file.
1 /* B A C K T R A C E . C
2  * BRL-CAD
3  *
4  * Copyright (c) 2007-2014 United States Government as represented by
5  * the U.S. Army Research Laboratory.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * version 2.1 as published by the Free Software Foundation.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this file; see the file named COPYING for more
18  * information.
19  */
20 
21 #include "common.h"
22 
23 /* system headers */
24 #include <signal.h>
25 #include <stdarg.h>
26 #include <stdlib.h>
27 #include <time.h>
28 #include <string.h>
29 #ifdef HAVE_SYS_TIME_H
30 # include <sys/time.h>
31 #endif
32 #ifdef HAVE_SYS_TIMES_H
33 # include <sys/times.h>
34 #endif
35 #ifdef HAVE_SYS_TYPES_H
36 # include <sys/types.h>
37 #endif
38 #ifdef HAVE_PROCESS_H
39 # include <process.h>
40 #endif
41 #include "bsocket.h"
42 #include "bio.h"
43 
44 /* common headers */
45 #include "bu/debug.h"
46 #include "bu/file.h"
47 #include "bu/log.h"
48 #include "bu/malloc.h"
49 #include "bu/parallel.h"
50 #include "bu/str.h"
51 
52 /* strict c99 doesn't declare kill() (but POSIX does) */
53 #if defined(HAVE_KILL) && !defined(HAVE_DECL_KILL)
54 extern int kill(pid_t, int);
55 #endif
56 
57 /* fileno() may be a macro (e.g., Windows) or may not even be declared
58  * when compiling strict, but declare it as needed
59  */
60 #if defined(HAVE_FILENO) && !defined(HAVE_DECL_FILENO)
61 extern int fileno(FILE*);
62 #endif
63 
64 
65 /* so we don't have to worry as much about stack stomping */
66 #define BT_BUFSIZE 4096
67 static char buffer[BT_BUFSIZE] = {0};
68 
69 static pid_t pid = (pid_t)0;
70 static int backtrace_done = 0;
71 static int interrupt_wait = 0;
72 
73 /* avoid stack variables for backtrace() */
74 static int input[2] = {0, 0};
75 static int output[2] = {0, 0};
76 static fd_set fdset;
77 static fd_set readset;
78 static struct timeval tv;
79 static int result;
80 static int position;
81 static int processing_bt;
82 static char c = 0;
83 static int warned;
84 
85 /* avoid stack variables for bu_backtrace() */
86 static char *debugger_args[4] = { NULL, NULL, NULL, NULL };
87 static const char *locate_gdb = NULL;
88 
89 
90 /* SIGCHLD handler for backtrace() */
91 HIDDEN void
92 backtrace_sigchld(int signum)
93 {
94  if (LIKELY(signum)) {
95  backtrace_done = 1;
96  interrupt_wait = 1;
97  }
98 }
99 
100 
101 /* SIGINT handler for bu_backtrace() */
102 HIDDEN void
103 backtrace_sigint(int signum)
104 {
105  if (LIKELY(signum)) {
106  interrupt_wait = 1;
107  }
108 }
109 
110 
111 /* actual guts to bu_backtrace() used to invoke gdb and parse out the
112  * backtrace from gdb's output.
113  */
114 HIDDEN void
115 backtrace(char * const *args, int fd)
116 {
117  /* receiving a SIGCHLD signal indicates something happened to a
118  * child process, which should be this backtrace since it is
119  * invoked after a fork() call as the child.
120  */
121 #ifdef SIGCHLD
122  signal(SIGCHLD, backtrace_sigchld);
123 #endif
124 #ifdef SIGINT
125  signal(SIGINT, backtrace_sigint);
126 #endif
127 
128  if (UNLIKELY((pipe(input) == -1) || (pipe(output) == -1))) {
129  perror("unable to open pipe");
130  fflush(stderr);
131  /* can't call bu_bomb()/bu_exit(), recursive */
132  return;
133  }
134 
135  pid = fork();
136  if (pid == 0) {
137  int ret;
138 
139  close(0);
140  ret = dup(input[0]); /* set the stdin to the in pipe */
141  if (ret == -1)
142  perror("dup");
143 
144  close(1);
145  ret = dup(output[1]); /* set the stdout to the out pipe */
146  if (ret == -1)
147  perror("dup");
148 
149  close(2);
150  ret = dup(output[1]); /* set the stderr to the out pipe */
151  if (ret == -1)
152  perror("dup");
153 
154  execvp(args[0], args); /* invoke debugger */
155  perror("exec failed");
156  fflush(stderr);
157  /* can't call bu_bomb()/bu_exit(), recursive */
158  exit(1);
159  } else if (pid == (pid_t) -1) {
160  perror("unable to fork");
161  fflush(stderr);
162  /* can't call bu_bomb()/bu_exit(), recursive */
163  exit(1);
164  }
165 
166  FD_ZERO(&fdset);
167  FD_SET(output[0], &fdset);
168 
169  if (write(input[1], "set prompt\n", 12) != 12) {
170  perror("write [set prompt] failed");
171  } else if (write(input[1], "set confirm off\n", 16) != 16) {
172  perror("write [set confirm off] failed");
173  } else if (write(input[1], "set backtrace past-main on\n", 27) != 27) {
174  perror("write [set backtrace past-main on] failed");
175  } else if (write(input[1], "bt full\n", 8) != 8) {
176  perror("write [bt full] failed");
177  } else if (write(input[1], "thread apply all bt full\n", 25) != 25) {
178  perror("write [thread apply all bt full] failed");
179  }
180 
181  /* Can add additional gdb commands above here. Output will
182  * contain everything up to the "Detaching from process" statement
183  * from the quit command below.
184  */
185 
186  if (write(input[1], "quit\n", 5) != 5) {
187  perror("write [quit] failed");
188  }
189 
190  position = 0;
191  processing_bt = 0;
192  memset(buffer, 0, BT_BUFSIZE);
193 
194  /* get/print the trace */
195  warned = 0;
196  while (1) {
197  readset = fdset;
198 
199  tv.tv_sec = 0;
200  tv.tv_usec = 42;
201 
202  result = select(FD_SETSIZE, &readset, NULL, NULL, &tv);
203  if (result == -1) {
204  break;
205  }
206 
207  if ((result > 0) && (FD_ISSET(output[0], &readset))) {
208  if (read(output[0], &c, 1)) {
209  switch (c) {
210  case '\n':
212  bu_log("BACKTRACE DEBUG: [%s]\n", buffer);
213  }
214  if (position+1 < BT_BUFSIZE) {
215  buffer[position++] = c;
216  buffer[position] = '\0';
217  } else {
218  position++;
219  }
220  if (bu_strncmp(buffer, "No locals", 9) == 0) {
221  /* skip it */
222  } else if (bu_strncmp(buffer, "No symbol table", 15) == 0) {
223  /* skip it */
224  } else if (bu_strncmp(buffer, "Detaching", 9) == 0) {
225  /* done processing backtrace output */
226  processing_bt = 0;
227  } else if (processing_bt == 1) {
228  if ((size_t)write(fd, buffer, strlen(buffer)) != strlen(buffer)) {
229  perror("error writing stack to file");
230  break;
231  }
232  if (position > BT_BUFSIZE) {
233  if (write(fd, " [TRIMMED]\n", 11) != 11) {
234  perror("error writing trim message to file");
235  break;
236  }
237  }
238  }
239  position = 0;
240  continue;
241  case '#':
242  /* once we find a # on the beginning of a
243  * line, begin keeping track of the output.
244  * the first #0 backtrace frame (i.e. that for
245  * the bu_backtrace() call) is not included in
246  * the output intentionally (because of the
247  * gdb prompt).
248  */
249  if (position == 0) {
250  processing_bt = 1;
251  }
252  break;
253  default:
254  break;
255  }
256  if (position+1 < BT_BUFSIZE) {
257  buffer[position++] = c;
258  buffer[position] = '\0';
259  } else {
260  if (UNLIKELY(!warned && (bu_debug & BU_DEBUG_ATTACH))) {
261  bu_log("Warning: debugger output overflow\n");
262  warned = 1;
263  }
264  position++;
265  }
266  }
267  } else if (backtrace_done) {
268  break;
269  }
270  }
271 
272  fflush(stdout);
273  fflush(stderr);
274 
275  close(input[0]);
276  close(input[1]);
277  close(output[0]);
278  close(output[1]);
279 
281  bu_log("\nBacktrace complete.\nAttach debugger or interrupt to continue...\n");
282  } else {
283 # ifdef HAVE_KILL
284  /* not attaching, so let the parent continue */
285 # ifdef SIGINT
286  kill(getppid(), SIGINT);
287 # endif
288 # ifdef SIGCHLD
289  kill(getppid(), SIGCHLD);
290 # endif
291 # endif
292  sleep(2);
293  }
294 
295  exit(0);
296 }
297 
298 
299 int
300 bu_backtrace(FILE *fp)
301 {
302  if (!fp) {
303  fp = stdout;
304  }
305 
306  /* make sure the debugger exists */
307  if ((locate_gdb = bu_which("gdb"))) {
308  debugger_args[0] = bu_strdup(locate_gdb);
310  bu_log("Found gdb in USER path: %s\n", locate_gdb);
311  }
312  } else if ((locate_gdb = bu_whereis("gdb"))) {
313  debugger_args[0] = bu_strdup(locate_gdb);
315  bu_log("Found gdb in SYSTEM path: %s\n", locate_gdb);
316  } else {
317  if (UNLIKELY(bu_debug & BU_DEBUG_BACKTRACE)) {
318  bu_log("gdb was NOT found, no backtrace available\n");
319  }
320  return 0;
321  }
322  }
323  locate_gdb = NULL;
324 
325 #ifdef SIGINT
326  signal(SIGINT, backtrace_sigint);
327 #endif
328 
329  snprintf(buffer, BT_BUFSIZE, "%d", bu_process_id());
330 
331  debugger_args[1] = (char*) bu_argv0_full_path();
332  debugger_args[2] = buffer;
333 
335  bu_log("CALL STACK BACKTRACE REQUESTED\n");
336  bu_log("Invoking Debugger: %s %s %s\n\n", debugger_args[0], debugger_args[1], debugger_args[2]);
337  }
338 
339  /* fork so that trace symbols stop _here_ instead of in some libc
340  * routine (e.g., in wait(2)).
341  */
342  pid = fork();
343  if (pid == 0) {
344  /* child */
345  backtrace(debugger_args, fileno(fp));
346  bu_free(debugger_args[0], "gdb strdup");
347  debugger_args[0] = NULL;
348  exit(0);
349  } else if (pid == (pid_t) -1) {
350  /* failure */
351  bu_free(debugger_args[0], "gdb strdup");
352  debugger_args[0] = NULL;
353  perror("unable to fork for gdb");
354  return 0;
355  }
356  /* parent */
357  if (debugger_args[0]) {
358  bu_free(debugger_args[0], "gdb strdup");
359  debugger_args[0] = NULL;
360  }
361  fflush(fp);
362 
363  /* Could probably do something better than this to avoid hanging
364  * indefinitely. Keeps the trace clean, though, and allows for a
365  * debugger to be attached interactively if needed.
366  */
367  interrupt_wait = 0;
368 #ifdef HAVE_KILL
369  {
370  struct timeval start, end;
371  gettimeofday(&start, NULL);
372  gettimeofday(&end, NULL);
373  while ((interrupt_wait == 0) && (end.tv_sec - start.tv_sec < 45 /* seconds */)) {
374  /* do nothing, wait for debugger to attach but don't wait too long */;
375  gettimeofday(&end, NULL);
376  sleep(1);
377  }
378  }
379 #else
380  /* FIXME: need something better here for win32 */
381  sleep(10);
382 #endif
383 
384  if (UNLIKELY(bu_debug & BU_DEBUG_BACKTRACE)) {
385  bu_log("\nContinuing.\n");
386  }
387 
388 #ifdef SIGINT
389  signal(SIGINT, SIG_DFL);
390 #endif
391 #ifdef SIGCHLD
392  signal(SIGCHLD, SIG_DFL);
393 #endif
394 
395  fflush(fp);
396 
397  return 1;
398 }
399 
400 
401 #ifdef TEST_BACKTRACE
402 int bar(char **argv)
403 {
404  int moo = 5;
405  bu_backtrace(NULL);
406  return 0;
407 }
408 
409 
410 int foo(char **argv)
411 {
412  return bar(argv);
413 }
414 
415 
416 int
417 main(int argc, char *argv[])
418 {
419  if (argc > 1) {
420  bu_bomb("this is a test\n");
421  } else {
422  (void)foo(argv);
423  }
424  return 0;
425 }
426 #endif /* TEST_BACKTRACE */
427 
428 /*
429  * Local Variables:
430  * mode: C
431  * tab-width: 8
432  * indent-tabs-mode: t
433  * c-file-style: "stroustrup"
434  * End:
435  * ex: shiftwidth=4 tabstop=8
436  */
void bu_log(const char *,...) _BU_ATTR_PRINTF12
Definition: log.c:176
#define LIKELY(expression)
Definition: common.h:261
#define BU_DEBUG_BACKTRACE
Definition: debug.h:60
int bu_process_id(void)
Definition: process.c:31
Header file for the BRL-CAD common definitions.
#define HIDDEN
Definition: common.h:86
int main(int ac, char *av[])
Definition: bu_b64.c:63
int bu_strncmp(const char *string1, const char *string2, size_t n)
Definition: str.c:191
void * memset(void *s, int c, size_t n)
HIDDEN void backtrace(char *const *args, int fd)
Definition: backtrace.c:115
const char * bu_which(const char *cmd)
Definition: which.c:43
#define BU_DEBUG_ATTACH
Definition: debug.h:61
#define BT_BUFSIZE
Definition: backtrace.c:66
int bu_backtrace(FILE *fp)
Definition: backtrace.c:300
int bu_debug
Definition: globals.c:87
const char * bu_whereis(const char *cmd)
Definition: whereis.c:52
HIDDEN void backtrace_sigchld(int signum)
Definition: backtrace.c:92
void bu_free(void *ptr, const char *str)
Definition: malloc.c:328
HIDDEN void backtrace_sigint(int signum)
Definition: backtrace.c:103
void bu_bomb(const char *str) _BU_ATTR_NORETURN
Definition: bomb.c:91
const char * bu_argv0_full_path(void)
Definition: progname.c:48
#define bu_strdup(s)
Definition: str.h:71
#define UNLIKELY(expression)
Definition: common.h:282