111be35a1SLionel Sambuc // Copyright 2012 Google Inc.
211be35a1SLionel Sambuc // All rights reserved.
311be35a1SLionel Sambuc //
411be35a1SLionel Sambuc // Redistribution and use in source and binary forms, with or without
511be35a1SLionel Sambuc // modification, are permitted provided that the following conditions are
611be35a1SLionel Sambuc // met:
711be35a1SLionel Sambuc //
811be35a1SLionel Sambuc // * Redistributions of source code must retain the above copyright
911be35a1SLionel Sambuc //   notice, this list of conditions and the following disclaimer.
1011be35a1SLionel Sambuc // * Redistributions in binary form must reproduce the above copyright
1111be35a1SLionel Sambuc //   notice, this list of conditions and the following disclaimer in the
1211be35a1SLionel Sambuc //   documentation and/or other materials provided with the distribution.
1311be35a1SLionel Sambuc // * Neither the name of Google Inc. nor the names of its contributors
1411be35a1SLionel Sambuc //   may be used to endorse or promote products derived from this software
1511be35a1SLionel Sambuc //   without specific prior written permission.
1611be35a1SLionel Sambuc //
1711be35a1SLionel Sambuc // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1811be35a1SLionel Sambuc // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1911be35a1SLionel Sambuc // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2011be35a1SLionel Sambuc // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2111be35a1SLionel Sambuc // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2211be35a1SLionel Sambuc // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2311be35a1SLionel Sambuc // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2411be35a1SLionel Sambuc // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2511be35a1SLionel Sambuc // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2611be35a1SLionel Sambuc // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2711be35a1SLionel Sambuc // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2811be35a1SLionel Sambuc 
2911be35a1SLionel Sambuc #include "stacktrace.h"
3011be35a1SLionel Sambuc 
3111be35a1SLionel Sambuc #include <sys/param.h>
3211be35a1SLionel Sambuc #include <sys/wait.h>
3311be35a1SLionel Sambuc 
3411be35a1SLionel Sambuc #include <assert.h>
3511be35a1SLionel Sambuc #include <fcntl.h>
3611be35a1SLionel Sambuc #include <stdarg.h>
3711be35a1SLionel Sambuc #include <stdio.h>
3811be35a1SLionel Sambuc #include <stdlib.h>
3911be35a1SLionel Sambuc #include <string.h>
4011be35a1SLionel Sambuc #include <unistd.h>
4111be35a1SLionel Sambuc 
4211be35a1SLionel Sambuc #include "defs.h"
4311be35a1SLionel Sambuc #include "fs.h"
4411be35a1SLionel Sambuc #include "env.h"
4511be35a1SLionel Sambuc #include "error.h"
4611be35a1SLionel Sambuc #include "run.h"
4711be35a1SLionel Sambuc #include "text.h"
4811be35a1SLionel Sambuc 
4911be35a1SLionel Sambuc 
5011be35a1SLionel Sambuc /// Built-in path to GDB.
5111be35a1SLionel Sambuc ///
5211be35a1SLionel Sambuc /// This should be an absolute path for deterministic behavior.  We also accept
5311be35a1SLionel Sambuc /// a basename to cope with any issues that might arise from an invalid
5411be35a1SLionel Sambuc /// configure check or a manual override of the GDB constant, in which case the
5511be35a1SLionel Sambuc /// exec call below will (try to) locate the binary in the path.
5611be35a1SLionel Sambuc ///
5711be35a1SLionel Sambuc /// Note that the program pointed to by this variable is not required to exist.
5811be35a1SLionel Sambuc /// If it does not, we fail gracefully.
5911be35a1SLionel Sambuc ///
6011be35a1SLionel Sambuc /// Test cases can override the value of this built-in constant to unit-test the
6111be35a1SLionel Sambuc /// behavior of the functions below.
6211be35a1SLionel Sambuc const char* kyua_stacktrace_gdb = GDB;
6311be35a1SLionel Sambuc #undef GDB  // We really want to use the variable, not the macro.
6411be35a1SLionel Sambuc 
6511be35a1SLionel Sambuc 
6611be35a1SLionel Sambuc /// Time to give to the external GDB process to produce a stack trace.
6711be35a1SLionel Sambuc ///
6811be35a1SLionel Sambuc /// Test cases can override the value of this built-in constant to unit-test the
6911be35a1SLionel Sambuc /// behavior of the functions below.
7011be35a1SLionel Sambuc unsigned long kyua_stacktrace_gdb_timeout = 300;
7111be35a1SLionel Sambuc 
7211be35a1SLionel Sambuc 
7311be35a1SLionel Sambuc /// Maximum length of the core file name, if known.
7411be35a1SLionel Sambuc ///
7511be35a1SLionel Sambuc /// Some operating systems impose a maximum length on the basename of the core
7611be35a1SLionel Sambuc /// file.  If MAXCOMLEN is defined, then we need to truncate the program name to
7711be35a1SLionel Sambuc /// this length before searching for the core file.  If we cannot figure out
7811be35a1SLionel Sambuc /// what this limit is, we set it to zero, which we consider later as
7911be35a1SLionel Sambuc /// "unlimited".
8011be35a1SLionel Sambuc #if !defined(MAXCOMLEN)
8111be35a1SLionel Sambuc #   define MAXCOMLEN 0
8211be35a1SLionel Sambuc #endif
8311be35a1SLionel Sambuc 
8411be35a1SLionel Sambuc 
8511be35a1SLionel Sambuc static void run_gdb(const char* program, const char* core_name, FILE* output)
8611be35a1SLionel Sambuc     KYUA_DEFS_NORETURN;
8711be35a1SLionel Sambuc 
8811be35a1SLionel Sambuc 
8911be35a1SLionel Sambuc /// Constructs the parameters to run GDB with.
9011be35a1SLionel Sambuc ///
9111be35a1SLionel Sambuc /// \param original_run_params Parameters used to run the binary that generated
9211be35a1SLionel Sambuc ///     the core dump.
9311be35a1SLionel Sambuc ///
9411be35a1SLionel Sambuc /// \return The run parameters with which to run GDB.
9511be35a1SLionel Sambuc static kyua_run_params_t
gdb_run_params(const kyua_run_params_t * original_run_params)9611be35a1SLionel Sambuc gdb_run_params(const kyua_run_params_t* original_run_params)
9711be35a1SLionel Sambuc {
9811be35a1SLionel Sambuc     kyua_run_params_t run_params = *original_run_params;
9911be35a1SLionel Sambuc     run_params.timeout_seconds = kyua_stacktrace_gdb_timeout;
10011be35a1SLionel Sambuc     return run_params;
10111be35a1SLionel Sambuc }
10211be35a1SLionel Sambuc 
10311be35a1SLionel Sambuc 
10411be35a1SLionel Sambuc /// Body of a subprocess to execute GDB.
10511be35a1SLionel Sambuc ///
10611be35a1SLionel Sambuc /// This should be called from the child created by a kyua_run_fork() call,
10711be35a1SLionel Sambuc /// which means that we do not have to take care of isolating the process.
10811be35a1SLionel Sambuc ///
10911be35a1SLionel Sambuc /// \pre The caller must have flushed stdout before spawning this process, to
11011be35a1SLionel Sambuc ///     prevent double-flushing and/or corruption of data.
11111be35a1SLionel Sambuc ///
11211be35a1SLionel Sambuc /// \param program Path to the program being debugged.  Can be relative to
11311be35a1SLionel Sambuc ///     the given work directory.
11411be35a1SLionel Sambuc /// \param core_name Path to the dumped core.  Use find_core() to deduce
11511be35a1SLionel Sambuc ///     a valid candidate.  Can be relative to the given work directory.
11611be35a1SLionel Sambuc /// \param output Stream to which to send the output of GDB.
11711be35a1SLionel Sambuc static void
run_gdb(const char * program,const char * core_name,FILE * output)11811be35a1SLionel Sambuc run_gdb(const char* program, const char* core_name, FILE* output)
11911be35a1SLionel Sambuc {
12011be35a1SLionel Sambuc     // TODO(jmmv): Should be done by kyua_run_fork(), but doing so would change
12111be35a1SLionel Sambuc     // the semantics of the ATF interface.  Need to evaluate this carefully.
12211be35a1SLionel Sambuc     const kyua_error_t error = kyua_env_unset("TERM");
12311be35a1SLionel Sambuc     if (kyua_error_is_set(error)) {
12411be35a1SLionel Sambuc         kyua_error_warn(error, "Failed to unset TERM; GDB may misbehave");
12511be35a1SLionel Sambuc         free(error);
12611be35a1SLionel Sambuc     }
12711be35a1SLionel Sambuc 
12811be35a1SLionel Sambuc     (void)close(STDIN_FILENO);
129*3260d16fSLionel Sambuc #if defined(__minix) && !defined(NDEBUG)
130*3260d16fSLionel Sambuc     const int input_fd =
131*3260d16fSLionel Sambuc #endif /* defined(__minix) && !defined(NDEBUG) */
132*3260d16fSLionel Sambuc     open("/dev/null", O_RDONLY);
13311be35a1SLionel Sambuc     assert(input_fd == STDIN_FILENO);
13411be35a1SLionel Sambuc 
13511be35a1SLionel Sambuc     const int output_fd = fileno(output);
13611be35a1SLionel Sambuc     assert(output_fd != -1);  // We expect a file-backed stream.
13711be35a1SLionel Sambuc     if (output_fd != STDOUT_FILENO) {
13811be35a1SLionel Sambuc         fflush(stdout);
13911be35a1SLionel Sambuc         (void)dup2(output_fd, STDOUT_FILENO);
14011be35a1SLionel Sambuc     }
14111be35a1SLionel Sambuc     if (output_fd != STDERR_FILENO) {
14211be35a1SLionel Sambuc         fflush(stderr);
14311be35a1SLionel Sambuc         (void)dup2(output_fd, STDERR_FILENO);
14411be35a1SLionel Sambuc     }
14511be35a1SLionel Sambuc     if (output_fd != STDOUT_FILENO && output_fd != STDERR_FILENO)
14611be35a1SLionel Sambuc         fclose(output);
14711be35a1SLionel Sambuc 
14811be35a1SLionel Sambuc     const char* const gdb_args[] = {
14911be35a1SLionel Sambuc         "gdb", "-batch", "-q", "-ex", "bt", program, core_name, NULL };
15011be35a1SLionel Sambuc     kyua_run_exec(kyua_stacktrace_gdb, gdb_args);
15111be35a1SLionel Sambuc }
15211be35a1SLionel Sambuc 
15311be35a1SLionel Sambuc 
15411be35a1SLionel Sambuc /// Truncates a string.
15511be35a1SLionel Sambuc ///
15611be35a1SLionel Sambuc /// \param source The string to truncate.
15711be35a1SLionel Sambuc /// \param [out] buffer Output buffer into which to store the truncated text.
15811be35a1SLionel Sambuc /// \param buffer_length Size of the buffer.
15911be35a1SLionel Sambuc ///
16011be35a1SLionel Sambuc /// \return A pointer to the buffer.
16111be35a1SLionel Sambuc static const char*
slice(const char * source,char * buffer,const size_t buffer_length)16211be35a1SLionel Sambuc slice(const char* source, char* buffer, const size_t buffer_length)
16311be35a1SLionel Sambuc {
16411be35a1SLionel Sambuc     const size_t source_length = strlen(source);
16511be35a1SLionel Sambuc     if (source_length < buffer_length) {
16611be35a1SLionel Sambuc         strcpy(buffer, source);
16711be35a1SLionel Sambuc     } else {
16811be35a1SLionel Sambuc         memcpy(buffer, source, buffer_length - 1);
16911be35a1SLionel Sambuc         buffer[buffer_length - 1] = '\0';
17011be35a1SLionel Sambuc     }
17111be35a1SLionel Sambuc     return buffer;
17211be35a1SLionel Sambuc }
17311be35a1SLionel Sambuc 
17411be35a1SLionel Sambuc 
17511be35a1SLionel Sambuc static char* try_core(const char* format, ...) KYUA_DEFS_FORMAT_PRINTF(1, 2);
17611be35a1SLionel Sambuc 
17711be35a1SLionel Sambuc 
17811be35a1SLionel Sambuc /// Generates a path and checks if it exists.
17911be35a1SLionel Sambuc ///
18011be35a1SLionel Sambuc /// \param format Formatting string for the path to generate.
18111be35a1SLionel Sambuc /// \param ... Arguments to the formatting string.
18211be35a1SLionel Sambuc ///
18311be35a1SLionel Sambuc /// \return A dynamically-allocated string containing the generated path if
18411be35a1SLionel Sambuc /// there were no errors and the file pointed to by such path exists; NULL
18511be35a1SLionel Sambuc /// otherwise.  The returned string must be relesed with free() by the caller.
18611be35a1SLionel Sambuc static char*
try_core(const char * format,...)18711be35a1SLionel Sambuc try_core(const char* format, ...)
18811be35a1SLionel Sambuc {
18911be35a1SLionel Sambuc     char* path;
19011be35a1SLionel Sambuc     va_list ap;
19111be35a1SLionel Sambuc 
19211be35a1SLionel Sambuc     va_start(ap, format);
19311be35a1SLionel Sambuc     kyua_error_t error = kyua_text_vprintf(&path, format, ap);
19411be35a1SLionel Sambuc     va_end(ap);
19511be35a1SLionel Sambuc     if (kyua_error_is_set(error)) {
19611be35a1SLionel Sambuc         // Something went really wrong (and should not have happened).  Ignore
19711be35a1SLionel Sambuc         // this core file candidate.
19811be35a1SLionel Sambuc         kyua_error_free(error);
19911be35a1SLionel Sambuc         return NULL;
20011be35a1SLionel Sambuc     }
20111be35a1SLionel Sambuc 
20211be35a1SLionel Sambuc     if (access(path, F_OK) == -1) {
20311be35a1SLionel Sambuc         free(path);
20411be35a1SLionel Sambuc         return NULL;
20511be35a1SLionel Sambuc     } else {
20611be35a1SLionel Sambuc         return path;
20711be35a1SLionel Sambuc     }
20811be35a1SLionel Sambuc }
20911be35a1SLionel Sambuc 
21011be35a1SLionel Sambuc 
21111be35a1SLionel Sambuc /// Simple version of basename() that operates on constant strings.
21211be35a1SLionel Sambuc ///
21311be35a1SLionel Sambuc /// This is not 100% compatible with basename() because it may return an
21411be35a1SLionel Sambuc /// unexpected string if the path ends with a slash.  For our purposes, this
21511be35a1SLionel Sambuc /// does not matter, so we can use this simplified trick.
21611be35a1SLionel Sambuc ///
21711be35a1SLionel Sambuc /// \param path Path from which to compute the basename.
21811be35a1SLionel Sambuc ///
21911be35a1SLionel Sambuc /// \return A pointer within the input path pointing at the last component.
22011be35a1SLionel Sambuc static const char*
const_basename(const char * path)22111be35a1SLionel Sambuc const_basename(const char* path)
22211be35a1SLionel Sambuc {
22311be35a1SLionel Sambuc     const char* last_slash = strrchr(path, '/');
22411be35a1SLionel Sambuc     return last_slash == NULL ? path : last_slash + 1;
22511be35a1SLionel Sambuc }
22611be35a1SLionel Sambuc 
22711be35a1SLionel Sambuc 
22811be35a1SLionel Sambuc /// Looks for a core file for the given program.
22911be35a1SLionel Sambuc ///
23011be35a1SLionel Sambuc /// \param name The basename of the binary that generated the core.
23111be35a1SLionel Sambuc /// \param directory The directory from which the program was run.  We expect to
23211be35a1SLionel Sambuc ///     find the core file in this directory.
23311be35a1SLionel Sambuc /// \param dead_pid PID of the process that generated the core.  This is needed
23411be35a1SLionel Sambuc ///     in some platforms.
23511be35a1SLionel Sambuc ///
23611be35a1SLionel Sambuc /// \return The path to the core file if found; otherwise none.
23711be35a1SLionel Sambuc char*
kyua_stacktrace_find_core(const char * name,const char * directory,const pid_t dead_pid)23811be35a1SLionel Sambuc kyua_stacktrace_find_core(const char* name, const char* directory,
23911be35a1SLionel Sambuc                           const pid_t dead_pid)
24011be35a1SLionel Sambuc {
24111be35a1SLionel Sambuc     char* candidate = NULL;
24211be35a1SLionel Sambuc 
24311be35a1SLionel Sambuc     // TODO(jmmv): Other than checking all these defaults, in NetBSD we should
24411be35a1SLionel Sambuc     // also inspect the value of the kern.defcorename sysctl(2) MIB and use that
24511be35a1SLionel Sambuc     // as the first candidate.
24611be35a1SLionel Sambuc     //
24711be35a1SLionel Sambuc     // In Linux, the way to determine the name is by looking at
24811be35a1SLionel Sambuc     // /proc/sys/kernel/core_{pattern,uses_pid} as described by core(5).
24911be35a1SLionel Sambuc     // Unfortunately, there does not seem to be a standard API to parse these
25011be35a1SLionel Sambuc     // files, which makes checking for core files quite difficult if the
25111be35a1SLionel Sambuc     // defaults have been modified.
25211be35a1SLionel Sambuc 
25311be35a1SLionel Sambuc     // Default NetBSD naming scheme.
25411be35a1SLionel Sambuc     if (candidate == NULL && MAXCOMLEN > 0) {
25511be35a1SLionel Sambuc         char truncated[MAXCOMLEN + 1];
25611be35a1SLionel Sambuc         candidate = try_core("%s/%s.core", directory,
25711be35a1SLionel Sambuc                              slice(name, truncated, sizeof(truncated)));
25811be35a1SLionel Sambuc     }
25911be35a1SLionel Sambuc 
26011be35a1SLionel Sambuc     // Common naming scheme without the MAXCOMLEN truncation.
26111be35a1SLionel Sambuc     if (candidate == NULL)
26211be35a1SLionel Sambuc         candidate = try_core("%s/%s.core", directory, name);
26311be35a1SLionel Sambuc 
26411be35a1SLionel Sambuc     // Common naming scheme found in Linux systems.
26511be35a1SLionel Sambuc     if (candidate == NULL)
26611be35a1SLionel Sambuc         candidate = try_core("%s/core.%d", directory, (int)dead_pid);
26711be35a1SLionel Sambuc 
26811be35a1SLionel Sambuc     // Default Mac OS X naming scheme.
26911be35a1SLionel Sambuc     if (candidate == NULL)
27011be35a1SLionel Sambuc         candidate = try_core("/cores/core.%d", (int)dead_pid);
27111be35a1SLionel Sambuc 
27211be35a1SLionel Sambuc     // Common naming scheme found in Linux systems.  Attempted last due to the
27311be35a1SLionel Sambuc     // genericity of the core file name.
27411be35a1SLionel Sambuc     if (candidate == NULL)
27511be35a1SLionel Sambuc         candidate = try_core("%s/core", directory);
27611be35a1SLionel Sambuc 
27711be35a1SLionel Sambuc     if (candidate != NULL) {
27811be35a1SLionel Sambuc         char* abs_candidate;
27911be35a1SLionel Sambuc         kyua_error_t error = kyua_fs_make_absolute(candidate, &abs_candidate);
28011be35a1SLionel Sambuc         if (kyua_error_is_set(error)) {
28111be35a1SLionel Sambuc             kyua_error_free(error);
28211be35a1SLionel Sambuc             return candidate;  // Return possibly-relative path as a best guess.
28311be35a1SLionel Sambuc         } else {
28411be35a1SLionel Sambuc             free(candidate);
28511be35a1SLionel Sambuc             return abs_candidate;
28611be35a1SLionel Sambuc         }
28711be35a1SLionel Sambuc     } else {
28811be35a1SLionel Sambuc         return candidate;
28911be35a1SLionel Sambuc     }
29011be35a1SLionel Sambuc }
29111be35a1SLionel Sambuc 
29211be35a1SLionel Sambuc 
29311be35a1SLionel Sambuc /// Gathers a stacktrace of a crashed program.
29411be35a1SLionel Sambuc ///
29511be35a1SLionel Sambuc /// \param program The name of the binary that crashed and dumped a core file.
29611be35a1SLionel Sambuc ///     Can be either absolute or relative.
29711be35a1SLionel Sambuc /// \param dead_pid The PID of the process that dumped core.
29811be35a1SLionel Sambuc /// \param original_run_params Parameters with which the original binary was
29911be35a1SLionel Sambuc ///     executed.  These are reused to run GDB, but adjusted with GDB-specific
30011be35a1SLionel Sambuc ///     settings.  Of special interest, the work directory is used to search for
30111be35a1SLionel Sambuc ///     the core file.
30211be35a1SLionel Sambuc /// \param output Stream into which to dump the stack trace and any additional
30311be35a1SLionel Sambuc ///     information.
30411be35a1SLionel Sambuc ///
30511be35a1SLionel Sambuc /// \post If anything goes wrong, the diagnostic messages are written to the
30611be35a1SLionel Sambuc /// output.  This function returns no errors.
30711be35a1SLionel Sambuc void
kyua_stacktrace_dump(const char * program,const pid_t dead_pid,const kyua_run_params_t * original_run_params,FILE * output)30811be35a1SLionel Sambuc kyua_stacktrace_dump(const char* program, const pid_t dead_pid,
30911be35a1SLionel Sambuc                      const kyua_run_params_t* original_run_params, FILE* output)
31011be35a1SLionel Sambuc {
31111be35a1SLionel Sambuc     fprintf(output, "Process with PID %d dumped core; attempting to gather "
31211be35a1SLionel Sambuc             "stack trace\n", dead_pid);
31311be35a1SLionel Sambuc 
31411be35a1SLionel Sambuc     const kyua_run_params_t run_params = gdb_run_params(original_run_params);
31511be35a1SLionel Sambuc 
31611be35a1SLionel Sambuc     kyua_error_t error = kyua_error_ok();
31711be35a1SLionel Sambuc 
31811be35a1SLionel Sambuc     char* core_file = kyua_stacktrace_find_core(const_basename(program),
31911be35a1SLionel Sambuc                                                 run_params.work_directory,
32011be35a1SLionel Sambuc                                                 dead_pid);
32111be35a1SLionel Sambuc     if (core_file == NULL) {
32211be35a1SLionel Sambuc         fprintf(output, "Cannot find any core file\n");
32311be35a1SLionel Sambuc         goto out;
32411be35a1SLionel Sambuc     }
32511be35a1SLionel Sambuc 
32611be35a1SLionel Sambuc     // We must flush the output stream right before invoking fork, so that the
32711be35a1SLionel Sambuc     // subprocess does not have any unflushed data.  Failure to do so results in
32811be35a1SLionel Sambuc     // the messages above being written twice to the output.
32911be35a1SLionel Sambuc     fflush(output);
33011be35a1SLionel Sambuc     pid_t pid;
33111be35a1SLionel Sambuc     error = kyua_run_fork(&run_params, &pid);
33211be35a1SLionel Sambuc     if (!kyua_error_is_set(error) && pid == 0) {
33311be35a1SLionel Sambuc         run_gdb(program, core_file, output);
33411be35a1SLionel Sambuc     }
33511be35a1SLionel Sambuc     assert(pid != -1 && pid != 0);
33611be35a1SLionel Sambuc     if (kyua_error_is_set(error))
33711be35a1SLionel Sambuc         goto out_core_file;
33811be35a1SLionel Sambuc 
33911be35a1SLionel Sambuc     int status; bool timed_out;
34011be35a1SLionel Sambuc     error = kyua_run_wait(pid, &status, &timed_out);
34111be35a1SLionel Sambuc     if (kyua_error_is_set(error))
34211be35a1SLionel Sambuc         goto out_core_file;
34311be35a1SLionel Sambuc 
34411be35a1SLionel Sambuc     if (timed_out) {
34511be35a1SLionel Sambuc         fprintf(output, "GDB failed; timed out\n");
34611be35a1SLionel Sambuc     } else {
34711be35a1SLionel Sambuc         if (WIFEXITED(status)) {
34811be35a1SLionel Sambuc             if (WEXITSTATUS(status) == EXIT_SUCCESS)
34911be35a1SLionel Sambuc                 fprintf(output, "GDB exited successfully\n");
35011be35a1SLionel Sambuc             else
35111be35a1SLionel Sambuc                 fprintf(output, "GDB failed with code %d; see output above for "
35211be35a1SLionel Sambuc                         "details\n", WEXITSTATUS(status));
35311be35a1SLionel Sambuc         } else {
35411be35a1SLionel Sambuc             assert(WIFSIGNALED(status));
35511be35a1SLionel Sambuc             fprintf(output, "GDB received signal %d; see output above for "
35611be35a1SLionel Sambuc                     "details\n", WTERMSIG(status));
35711be35a1SLionel Sambuc         }
35811be35a1SLionel Sambuc     }
35911be35a1SLionel Sambuc 
36011be35a1SLionel Sambuc out_core_file:
36111be35a1SLionel Sambuc     free(core_file);
36211be35a1SLionel Sambuc out:
36311be35a1SLionel Sambuc     if (kyua_error_is_set(error)) {
36411be35a1SLionel Sambuc         kyua_error_fprintf(output, error, "Failed to gather stacktrace");
36511be35a1SLionel Sambuc         free(error);
36611be35a1SLionel Sambuc     }
36711be35a1SLionel Sambuc }
368