xref: /src/contrib/kyua/utils/stacktrace.cpp (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
108334c51SBrooks Davis // Copyright 2012 The Kyua Authors.
208334c51SBrooks Davis // All rights reserved.
308334c51SBrooks Davis //
408334c51SBrooks Davis // Redistribution and use in source and binary forms, with or without
508334c51SBrooks Davis // modification, are permitted provided that the following conditions are
608334c51SBrooks Davis // met:
708334c51SBrooks Davis //
808334c51SBrooks Davis // * Redistributions of source code must retain the above copyright
908334c51SBrooks Davis //   notice, this list of conditions and the following disclaimer.
1008334c51SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
1108334c51SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
1208334c51SBrooks Davis //   documentation and/or other materials provided with the distribution.
1308334c51SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
1408334c51SBrooks Davis //   may be used to endorse or promote products derived from this software
1508334c51SBrooks Davis //   without specific prior written permission.
1608334c51SBrooks Davis //
1708334c51SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1808334c51SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1908334c51SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2008334c51SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2108334c51SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2208334c51SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2308334c51SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2408334c51SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2508334c51SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2608334c51SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2708334c51SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2808334c51SBrooks Davis 
2908334c51SBrooks Davis #include "utils/stacktrace.hpp"
3008334c51SBrooks Davis 
3108334c51SBrooks Davis extern "C" {
3208334c51SBrooks Davis #include <sys/param.h>
3308334c51SBrooks Davis #include <sys/resource.h>
3408334c51SBrooks Davis 
3508334c51SBrooks Davis #include <unistd.h>
3608334c51SBrooks Davis }
3708334c51SBrooks Davis 
3808334c51SBrooks Davis #include <cerrno>
3908334c51SBrooks Davis #include <cstdlib>
4008334c51SBrooks Davis #include <cstring>
4108334c51SBrooks Davis #include <fstream>
4208334c51SBrooks Davis #include <iostream>
4308334c51SBrooks Davis #include <stdexcept>
4408334c51SBrooks Davis #include <string>
4508334c51SBrooks Davis #include <vector>
4608334c51SBrooks Davis 
4708334c51SBrooks Davis #include "utils/datetime.hpp"
4808334c51SBrooks Davis #include "utils/env.hpp"
4908334c51SBrooks Davis #include "utils/format/macros.hpp"
5008334c51SBrooks Davis #include "utils/fs/operations.hpp"
5108334c51SBrooks Davis #include "utils/fs/path.hpp"
5208334c51SBrooks Davis #include "utils/logging/macros.hpp"
5308334c51SBrooks Davis #include "utils/optional.ipp"
5408334c51SBrooks Davis #include "utils/process/executor.ipp"
5508334c51SBrooks Davis #include "utils/process/operations.hpp"
5608334c51SBrooks Davis #include "utils/process/status.hpp"
5708334c51SBrooks Davis #include "utils/sanity.hpp"
5808334c51SBrooks Davis 
5908334c51SBrooks Davis namespace datetime = utils::datetime;
6008334c51SBrooks Davis namespace executor = utils::process::executor;
6108334c51SBrooks Davis namespace fs = utils::fs;
6208334c51SBrooks Davis namespace process = utils::process;
6308334c51SBrooks Davis 
6408334c51SBrooks Davis using utils::none;
6508334c51SBrooks Davis using utils::optional;
6608334c51SBrooks Davis 
6708334c51SBrooks Davis 
6808334c51SBrooks Davis /// Built-in path to GDB.
6908334c51SBrooks Davis ///
7008334c51SBrooks Davis /// This is the value that should be passed to the find_gdb() function.  If this
7108334c51SBrooks Davis /// is an absolute path, then we use the binary specified by the variable; if it
7208334c51SBrooks Davis /// is a relative path, we look for the binary in the path.
7308334c51SBrooks Davis ///
7408334c51SBrooks Davis /// Test cases can override the value of this built-in constant to unit-test the
7508334c51SBrooks Davis /// behavior of the functions below.
7608334c51SBrooks Davis const char* utils::builtin_gdb = GDB;
7708334c51SBrooks Davis 
7808334c51SBrooks Davis 
7908334c51SBrooks Davis /// Maximum time the external GDB process is allowed to run for.
8008334c51SBrooks Davis datetime::delta utils::gdb_timeout(60, 0);
8108334c51SBrooks Davis 
8208334c51SBrooks Davis 
8308334c51SBrooks Davis namespace {
8408334c51SBrooks Davis 
8508334c51SBrooks Davis 
8608334c51SBrooks Davis /// Maximum length of the core file name, if known.
8708334c51SBrooks Davis ///
8808334c51SBrooks Davis /// Some operating systems impose a maximum length on the basename of the core
8908334c51SBrooks Davis /// file.  If MAXCOMLEN is defined, then we need to truncate the program name to
9008334c51SBrooks Davis /// this length before searching for the core file.  If no such limit is known,
9108334c51SBrooks Davis /// this is infinite.
9208334c51SBrooks Davis static const std::string::size_type max_core_name_length =
9308334c51SBrooks Davis #if defined(MAXCOMLEN)
9408334c51SBrooks Davis     MAXCOMLEN
9508334c51SBrooks Davis #else
9608334c51SBrooks Davis     std::string::npos
9708334c51SBrooks Davis #endif
9808334c51SBrooks Davis     ;
9908334c51SBrooks Davis 
10008334c51SBrooks Davis 
10108334c51SBrooks Davis /// Functor to execute GDB in a subprocess.
10208334c51SBrooks Davis class run_gdb {
10308334c51SBrooks Davis     /// Path to the GDB binary to use.
10408334c51SBrooks Davis     const fs::path& _gdb;
10508334c51SBrooks Davis 
10608334c51SBrooks Davis     /// Path to the program being debugged.
10708334c51SBrooks Davis     const fs::path& _program;
10808334c51SBrooks Davis 
10908334c51SBrooks Davis     /// Path to the dumped core.
11008334c51SBrooks Davis     const fs::path& _core_name;
11108334c51SBrooks Davis 
11208334c51SBrooks Davis public:
11308334c51SBrooks Davis     /// Constructs the functor.
11408334c51SBrooks Davis     ///
11508334c51SBrooks Davis     /// \param gdb_ Path to the GDB binary to use.
11608334c51SBrooks Davis     /// \param program_ Path to the program being debugged.  Can be relative to
11708334c51SBrooks Davis     ///     the given work directory.
11808334c51SBrooks Davis     /// \param core_name_ Path to the dumped core.  Use find_core() to deduce
11908334c51SBrooks Davis     ///     a valid candidate.  Can be relative to the given work directory.
run_gdb(const fs::path & gdb_,const fs::path & program_,const fs::path & core_name_)12008334c51SBrooks Davis     run_gdb(const fs::path& gdb_, const fs::path& program_,
12108334c51SBrooks Davis             const fs::path& core_name_) :
12208334c51SBrooks Davis         _gdb(gdb_), _program(program_), _core_name(core_name_)
12308334c51SBrooks Davis     {
12408334c51SBrooks Davis     }
12508334c51SBrooks Davis 
12608334c51SBrooks Davis     /// Executes GDB.
12708334c51SBrooks Davis     ///
12808334c51SBrooks Davis     /// \param control_directory Directory where we can store control files to
12908334c51SBrooks Davis     ///     not clobber any files created by the program being debugged.
13008334c51SBrooks Davis     void
operator ()(const fs::path & control_directory)13108334c51SBrooks Davis     operator()(const fs::path& control_directory)
13208334c51SBrooks Davis     {
13308334c51SBrooks Davis         const fs::path gdb_script_path = control_directory / "gdb.script";
13408334c51SBrooks Davis 
13508334c51SBrooks Davis         // Old versions of GDB, such as the one shipped by FreeBSD as of
13608334c51SBrooks Davis         // 11.0-CURRENT on 2014-11-26, do not support scripts on the command
13708334c51SBrooks Davis         // line via the '-ex' flag.  Instead, we have to create a script file
13808334c51SBrooks Davis         // and use that instead.
13908334c51SBrooks Davis         std::ofstream gdb_script(gdb_script_path.c_str());
14008334c51SBrooks Davis         if (!gdb_script) {
14108334c51SBrooks Davis             std::cerr << "Cannot create GDB script\n";
14208334c51SBrooks Davis             ::_exit(EXIT_FAILURE);
14308334c51SBrooks Davis         }
14408334c51SBrooks Davis         gdb_script << "backtrace\n";
14508334c51SBrooks Davis         gdb_script.close();
14608334c51SBrooks Davis 
14708334c51SBrooks Davis         utils::unsetenv("TERM");
14808334c51SBrooks Davis 
14908334c51SBrooks Davis         std::vector< std::string > args;
15008334c51SBrooks Davis         args.push_back("-batch");
15108334c51SBrooks Davis         args.push_back("-q");
15208334c51SBrooks Davis         args.push_back("-x");
15308334c51SBrooks Davis         args.push_back(gdb_script_path.str());
15408334c51SBrooks Davis         args.push_back(_program.str());
15508334c51SBrooks Davis         args.push_back(_core_name.str());
15608334c51SBrooks Davis 
15708334c51SBrooks Davis         // Force all GDB output to go to stderr.  We print messages to stderr
15808334c51SBrooks Davis         // when grabbing the stacktrace and we do not want GDB's output to end
15908334c51SBrooks Davis         // up split in two different files.
16008334c51SBrooks Davis         if (::dup2(STDERR_FILENO, STDOUT_FILENO) == -1) {
16108334c51SBrooks Davis             std::cerr << "Cannot redirect stdout to stderr\n";
16208334c51SBrooks Davis             ::_exit(EXIT_FAILURE);
16308334c51SBrooks Davis         }
16408334c51SBrooks Davis 
16508334c51SBrooks Davis         process::exec(_gdb, args);
16608334c51SBrooks Davis     }
16708334c51SBrooks Davis };
16808334c51SBrooks Davis 
16908334c51SBrooks Davis 
17008334c51SBrooks Davis }  // anonymous namespace
17108334c51SBrooks Davis 
17208334c51SBrooks Davis 
17308334c51SBrooks Davis /// Looks for the path to the GDB binary.
17408334c51SBrooks Davis ///
17508334c51SBrooks Davis /// \return The absolute path to the GDB binary if any, otherwise none.  Note
17608334c51SBrooks Davis /// that the returned path may or may not be valid: there is no guarantee that
17708334c51SBrooks Davis /// the path exists and is executable.
17808334c51SBrooks Davis optional< fs::path >
find_gdb(void)17908334c51SBrooks Davis utils::find_gdb(void)
18008334c51SBrooks Davis {
18108334c51SBrooks Davis     if (std::strlen(builtin_gdb) == 0) {
18208334c51SBrooks Davis         LW("The builtin path to GDB is bogus, which probably indicates a bug "
18308334c51SBrooks Davis            "in the build system; cannot gather stack traces");
18408334c51SBrooks Davis         return none;
18508334c51SBrooks Davis     }
18608334c51SBrooks Davis 
18708334c51SBrooks Davis     const fs::path gdb(builtin_gdb);
18808334c51SBrooks Davis     if (gdb.is_absolute())
18908334c51SBrooks Davis         return utils::make_optional(gdb);
19008334c51SBrooks Davis     else
19108334c51SBrooks Davis         return fs::find_in_path(gdb.c_str());
19208334c51SBrooks Davis }
19308334c51SBrooks Davis 
19408334c51SBrooks Davis 
19508334c51SBrooks Davis /// Looks for a core file for the given program.
19608334c51SBrooks Davis ///
19708334c51SBrooks Davis /// \param program The name of the binary that generated the core file.  Can be
19808334c51SBrooks Davis ///     either absolute or relative.
19908334c51SBrooks Davis /// \param status The exit status of the program.  This is necessary to gather
20008334c51SBrooks Davis ///     the PID.
20108334c51SBrooks Davis /// \param work_directory The directory from which the program was run.
20208334c51SBrooks Davis ///
20308334c51SBrooks Davis /// \return The path to the core file, if found; otherwise none.
20408334c51SBrooks Davis optional< fs::path >
find_core(const fs::path & program,const process::status & status,const fs::path & work_directory)20508334c51SBrooks Davis utils::find_core(const fs::path& program, const process::status& status,
20608334c51SBrooks Davis                  const fs::path& work_directory)
20708334c51SBrooks Davis {
20808334c51SBrooks Davis     std::vector< fs::path > candidates;
20908334c51SBrooks Davis 
21008334c51SBrooks Davis     candidates.push_back(work_directory /
21108334c51SBrooks Davis         (program.leaf_name().substr(0, max_core_name_length) + ".core"));
21208334c51SBrooks Davis     if (program.is_absolute()) {
21308334c51SBrooks Davis         candidates.push_back(program.branch_path() /
21408334c51SBrooks Davis             (program.leaf_name().substr(0, max_core_name_length) + ".core"));
21508334c51SBrooks Davis     }
21608334c51SBrooks Davis     candidates.push_back(work_directory / (F("core.%s") % status.dead_pid()));
21708334c51SBrooks Davis     candidates.push_back(fs::path("/cores") /
21808334c51SBrooks Davis                          (F("core.%s") % status.dead_pid()));
21908334c51SBrooks Davis 
22008334c51SBrooks Davis     for (std::vector< fs::path >::const_iterator iter = candidates.begin();
22108334c51SBrooks Davis          iter != candidates.end(); ++iter) {
22208334c51SBrooks Davis         if (fs::exists(*iter)) {
22308334c51SBrooks Davis             LD(F("Attempting core file candidate %s: found") % *iter);
22408334c51SBrooks Davis             return utils::make_optional(*iter);
22508334c51SBrooks Davis         } else {
22608334c51SBrooks Davis             LD(F("Attempting core file candidate %s: not found") % *iter);
22708334c51SBrooks Davis         }
22808334c51SBrooks Davis     }
22908334c51SBrooks Davis     return none;
23008334c51SBrooks Davis }
23108334c51SBrooks Davis 
23208334c51SBrooks Davis 
23308334c51SBrooks Davis /// Raises core size limit to its possible maximum.
23408334c51SBrooks Davis ///
23508334c51SBrooks Davis /// This is a best-effort operation.  There is no guarantee that the operation
23608334c51SBrooks Davis /// will yield a large-enough limit to generate any possible core file.
23708334c51SBrooks Davis ///
23808334c51SBrooks Davis /// \return True if the core size could be unlimited; false otherwise.
23908334c51SBrooks Davis bool
unlimit_core_size(void)24008334c51SBrooks Davis utils::unlimit_core_size(void)
24108334c51SBrooks Davis {
24208334c51SBrooks Davis     bool ok;
24308334c51SBrooks Davis 
24408334c51SBrooks Davis     struct ::rlimit rl;
24508334c51SBrooks Davis     if (::getrlimit(RLIMIT_CORE, &rl) == -1) {
24608334c51SBrooks Davis         const int original_errno = errno;
24708334c51SBrooks Davis         LW(F("getrlimit should not have failed but got: %s") %
24808334c51SBrooks Davis            std::strerror(original_errno));
24908334c51SBrooks Davis         ok = false;
25008334c51SBrooks Davis     } else {
25108334c51SBrooks Davis         if (rl.rlim_max == 0) {
25208334c51SBrooks Davis             LW("getrlimit returned 0 for RLIMIT_CORE rlim_max; cannot raise "
25308334c51SBrooks Davis                "soft core limit");
25408334c51SBrooks Davis             ok = false;
25508334c51SBrooks Davis         } else {
25608334c51SBrooks Davis             rl.rlim_cur = rl.rlim_max;
25708334c51SBrooks Davis             LD(F("Raising soft core size limit to %s (hard value)") %
25808334c51SBrooks Davis                rl.rlim_cur);
25908334c51SBrooks Davis             if (::setrlimit(RLIMIT_CORE, &rl) == -1) {
26008334c51SBrooks Davis                 const int original_errno = errno;
26108334c51SBrooks Davis                 LW(F("setrlimit should not have failed but got: %s") %
26208334c51SBrooks Davis                    std::strerror(original_errno));
26308334c51SBrooks Davis                 ok = false;
26408334c51SBrooks Davis             } else {
26508334c51SBrooks Davis                 ok = true;
26608334c51SBrooks Davis             }
26708334c51SBrooks Davis         }
26808334c51SBrooks Davis     }
26908334c51SBrooks Davis 
27008334c51SBrooks Davis     return ok;
27108334c51SBrooks Davis }
27208334c51SBrooks Davis 
27308334c51SBrooks Davis 
27408334c51SBrooks Davis /// Gathers a stacktrace of a crashed program.
27508334c51SBrooks Davis ///
27608334c51SBrooks Davis /// \param program The name of the binary that crashed and dumped a core file.
27708334c51SBrooks Davis ///     Can be either absolute or relative.
27808334c51SBrooks Davis /// \param executor_handle The executor handler to get the status from and
27908334c51SBrooks Davis ///     gdb handler from.
28008334c51SBrooks Davis /// \param exit_handle The exit handler to stream additional diagnostic
28108334c51SBrooks Davis ///     information from (stderr) and for redirecting to additional
28208334c51SBrooks Davis ///     information to gdb from.
28308334c51SBrooks Davis ///
28408334c51SBrooks Davis /// \post If anything goes wrong, the diagnostic messages are written to the
28508334c51SBrooks Davis /// output.  This function should not throw.
28608334c51SBrooks Davis void
dump_stacktrace(const fs::path & program,executor::executor_handle & executor_handle,const executor::exit_handle & exit_handle)28708334c51SBrooks Davis utils::dump_stacktrace(const fs::path& program,
28808334c51SBrooks Davis                        executor::executor_handle& executor_handle,
28908334c51SBrooks Davis                        const executor::exit_handle& exit_handle)
29008334c51SBrooks Davis {
29108334c51SBrooks Davis     PRE(exit_handle.status());
29208334c51SBrooks Davis     const process::status& status = exit_handle.status().get();
29308334c51SBrooks Davis     PRE(status.signaled() && status.coredump());
29408334c51SBrooks Davis 
29508334c51SBrooks Davis     std::ofstream gdb_err(exit_handle.stderr_file().c_str(), std::ios::app);
29608334c51SBrooks Davis     if (!gdb_err) {
29708334c51SBrooks Davis         LW(F("Failed to open %s to append GDB's output") %
29808334c51SBrooks Davis            exit_handle.stderr_file());
29908334c51SBrooks Davis         return;
30008334c51SBrooks Davis     }
30108334c51SBrooks Davis 
30208334c51SBrooks Davis     gdb_err << F("Process with PID %s exited with signal %s and dumped core; "
30308334c51SBrooks Davis                  "attempting to gather stack trace\n") %
30408334c51SBrooks Davis         status.dead_pid() % status.termsig();
30508334c51SBrooks Davis 
30608334c51SBrooks Davis     const optional< fs::path > gdb = utils::find_gdb();
30708334c51SBrooks Davis     if (!gdb) {
30808334c51SBrooks Davis         gdb_err << F("Cannot find GDB binary; builtin was '%s'\n") %
30908334c51SBrooks Davis             builtin_gdb;
31008334c51SBrooks Davis         return;
31108334c51SBrooks Davis     }
31208334c51SBrooks Davis 
31308334c51SBrooks Davis     const optional< fs::path > core_file = find_core(
31408334c51SBrooks Davis         program, status, exit_handle.work_directory());
31508334c51SBrooks Davis     if (!core_file) {
31608334c51SBrooks Davis         gdb_err << F("Cannot find any core file\n");
31708334c51SBrooks Davis         return;
31808334c51SBrooks Davis     }
31908334c51SBrooks Davis 
32008334c51SBrooks Davis     gdb_err.flush();
32108334c51SBrooks Davis     const executor::exec_handle exec_handle =
32208334c51SBrooks Davis         executor_handle.spawn_followup(
32308334c51SBrooks Davis             run_gdb(gdb.get(), program, core_file.get()),
32408334c51SBrooks Davis             exit_handle, gdb_timeout);
32508334c51SBrooks Davis     const executor::exit_handle gdb_exit_handle =
32608334c51SBrooks Davis         executor_handle.wait(exec_handle);
32708334c51SBrooks Davis 
32808334c51SBrooks Davis     const optional< process::status >& gdb_status = gdb_exit_handle.status();
32908334c51SBrooks Davis     if (!gdb_status) {
33008334c51SBrooks Davis         gdb_err << "GDB timed out\n";
33108334c51SBrooks Davis     } else {
33208334c51SBrooks Davis         if (gdb_status.get().exited() &&
33308334c51SBrooks Davis             gdb_status.get().exitstatus() == EXIT_SUCCESS) {
33408334c51SBrooks Davis             gdb_err << "GDB exited successfully\n";
33508334c51SBrooks Davis         } else {
33608334c51SBrooks Davis             gdb_err << "GDB failed; see output above for details\n";
33708334c51SBrooks Davis         }
33808334c51SBrooks Davis     }
33908334c51SBrooks Davis }
34008334c51SBrooks Davis 
34108334c51SBrooks Davis 
34208334c51SBrooks Davis /// Gathers a stacktrace of a program if it crashed.
34308334c51SBrooks Davis ///
34408334c51SBrooks Davis /// This is just a convenience function to allow appending the stacktrace to an
34508334c51SBrooks Davis /// existing file and to permit reusing the status as returned by auxiliary
34608334c51SBrooks Davis /// process-spawning functions.
34708334c51SBrooks Davis ///
34808334c51SBrooks Davis /// \param program The name of the binary that crashed and dumped a core file.
34908334c51SBrooks Davis ///     Can be either absolute or relative.
35008334c51SBrooks Davis /// \param executor_handle The executor handler to get the status from and
35108334c51SBrooks Davis ///     gdb handler from.
35208334c51SBrooks Davis /// \param exit_handle The exit handler to stream additional diagnostic
35308334c51SBrooks Davis ///     information from (stderr) and for redirecting to additional
35408334c51SBrooks Davis ///     information to gdb from.
35508334c51SBrooks Davis ///
35608334c51SBrooks Davis /// \throw std::runtime_error If the output file cannot be opened.
35708334c51SBrooks Davis ///
35808334c51SBrooks Davis /// \post If anything goes wrong with the stack gatheringq, the diagnostic
35908334c51SBrooks Davis /// messages are written to the output.
36008334c51SBrooks Davis void
dump_stacktrace_if_available(const fs::path & program,executor::executor_handle & executor_handle,const executor::exit_handle & exit_handle)36108334c51SBrooks Davis utils::dump_stacktrace_if_available(const fs::path& program,
36208334c51SBrooks Davis                                     executor::executor_handle& executor_handle,
36308334c51SBrooks Davis                                     const executor::exit_handle& exit_handle)
36408334c51SBrooks Davis {
36508334c51SBrooks Davis     const optional< process::status >& status = exit_handle.status();
36608334c51SBrooks Davis     if (!status || !status.get().signaled() || !status.get().coredump())
36708334c51SBrooks Davis         return;
36808334c51SBrooks Davis 
36908334c51SBrooks Davis     dump_stacktrace(program, executor_handle, exit_handle);
37008334c51SBrooks Davis }
371