xref: /freebsd/contrib/kyua/utils/stacktrace.cpp (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
1 // Copyright 2012 The Kyua Authors.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "utils/stacktrace.hpp"
30 
31 extern "C" {
32 #include <sys/param.h>
33 #include <sys/resource.h>
34 
35 #include <unistd.h>
36 }
37 
38 #include <cerrno>
39 #include <cstdlib>
40 #include <cstring>
41 #include <fstream>
42 #include <iostream>
43 #include <stdexcept>
44 #include <string>
45 #include <vector>
46 
47 #include "utils/datetime.hpp"
48 #include "utils/env.hpp"
49 #include "utils/format/macros.hpp"
50 #include "utils/fs/operations.hpp"
51 #include "utils/fs/path.hpp"
52 #include "utils/logging/macros.hpp"
53 #include "utils/optional.ipp"
54 #include "utils/process/executor.ipp"
55 #include "utils/process/operations.hpp"
56 #include "utils/process/status.hpp"
57 #include "utils/sanity.hpp"
58 
59 namespace datetime = utils::datetime;
60 namespace executor = utils::process::executor;
61 namespace fs = utils::fs;
62 namespace process = utils::process;
63 
64 using utils::none;
65 using utils::optional;
66 
67 
68 /// Built-in path to GDB.
69 ///
70 /// This is the value that should be passed to the find_gdb() function.  If this
71 /// is an absolute path, then we use the binary specified by the variable; if it
72 /// is a relative path, we look for the binary in the path.
73 ///
74 /// Test cases can override the value of this built-in constant to unit-test the
75 /// behavior of the functions below.
76 const char* utils::builtin_gdb = GDB;
77 
78 
79 /// Maximum time the external GDB process is allowed to run for.
80 datetime::delta utils::gdb_timeout(60, 0);
81 
82 
83 namespace {
84 
85 
86 /// Maximum length of the core file name, if known.
87 ///
88 /// Some operating systems impose a maximum length on the basename of the core
89 /// file.  If MAXCOMLEN is defined, then we need to truncate the program name to
90 /// this length before searching for the core file.  If no such limit is known,
91 /// this is infinite.
92 static const std::string::size_type max_core_name_length =
93 #if defined(MAXCOMLEN)
94     MAXCOMLEN
95 #else
96     std::string::npos
97 #endif
98     ;
99 
100 
101 /// Functor to execute GDB in a subprocess.
102 class run_gdb {
103     /// Path to the GDB binary to use.
104     const fs::path& _gdb;
105 
106     /// Path to the program being debugged.
107     const fs::path& _program;
108 
109     /// Path to the dumped core.
110     const fs::path& _core_name;
111 
112 public:
113     /// Constructs the functor.
114     ///
115     /// \param gdb_ Path to the GDB binary to use.
116     /// \param program_ Path to the program being debugged.  Can be relative to
117     ///     the given work directory.
118     /// \param core_name_ Path to the dumped core.  Use find_core() to deduce
119     ///     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_)120     run_gdb(const fs::path& gdb_, const fs::path& program_,
121             const fs::path& core_name_) :
122         _gdb(gdb_), _program(program_), _core_name(core_name_)
123     {
124     }
125 
126     /// Executes GDB.
127     ///
128     /// \param control_directory Directory where we can store control files to
129     ///     not clobber any files created by the program being debugged.
130     void
operator ()(const fs::path & control_directory)131     operator()(const fs::path& control_directory)
132     {
133         const fs::path gdb_script_path = control_directory / "gdb.script";
134 
135         // Old versions of GDB, such as the one shipped by FreeBSD as of
136         // 11.0-CURRENT on 2014-11-26, do not support scripts on the command
137         // line via the '-ex' flag.  Instead, we have to create a script file
138         // and use that instead.
139         std::ofstream gdb_script(gdb_script_path.c_str());
140         if (!gdb_script) {
141             std::cerr << "Cannot create GDB script\n";
142             ::_exit(EXIT_FAILURE);
143         }
144         gdb_script << "backtrace\n";
145         gdb_script.close();
146 
147         utils::unsetenv("TERM");
148 
149         std::vector< std::string > args;
150         args.push_back("-batch");
151         args.push_back("-q");
152         args.push_back("-x");
153         args.push_back(gdb_script_path.str());
154         args.push_back(_program.str());
155         args.push_back(_core_name.str());
156 
157         // Force all GDB output to go to stderr.  We print messages to stderr
158         // when grabbing the stacktrace and we do not want GDB's output to end
159         // up split in two different files.
160         if (::dup2(STDERR_FILENO, STDOUT_FILENO) == -1) {
161             std::cerr << "Cannot redirect stdout to stderr\n";
162             ::_exit(EXIT_FAILURE);
163         }
164 
165         process::exec(_gdb, args);
166     }
167 };
168 
169 
170 }  // anonymous namespace
171 
172 
173 /// Looks for the path to the GDB binary.
174 ///
175 /// \return The absolute path to the GDB binary if any, otherwise none.  Note
176 /// that the returned path may or may not be valid: there is no guarantee that
177 /// the path exists and is executable.
178 optional< fs::path >
find_gdb(void)179 utils::find_gdb(void)
180 {
181     if (std::strlen(builtin_gdb) == 0) {
182         LW("The builtin path to GDB is bogus, which probably indicates a bug "
183            "in the build system; cannot gather stack traces");
184         return none;
185     }
186 
187     const fs::path gdb(builtin_gdb);
188     if (gdb.is_absolute())
189         return utils::make_optional(gdb);
190     else
191         return fs::find_in_path(gdb.c_str());
192 }
193 
194 
195 /// Looks for a core file for the given program.
196 ///
197 /// \param program The name of the binary that generated the core file.  Can be
198 ///     either absolute or relative.
199 /// \param status The exit status of the program.  This is necessary to gather
200 ///     the PID.
201 /// \param work_directory The directory from which the program was run.
202 ///
203 /// \return The path to the core file, if found; otherwise none.
204 optional< fs::path >
find_core(const fs::path & program,const process::status & status,const fs::path & work_directory)205 utils::find_core(const fs::path& program, const process::status& status,
206                  const fs::path& work_directory)
207 {
208     std::vector< fs::path > candidates;
209 
210     candidates.push_back(work_directory /
211         (program.leaf_name().substr(0, max_core_name_length) + ".core"));
212     if (program.is_absolute()) {
213         candidates.push_back(program.branch_path() /
214             (program.leaf_name().substr(0, max_core_name_length) + ".core"));
215     }
216     candidates.push_back(work_directory / (F("core.%s") % status.dead_pid()));
217     candidates.push_back(fs::path("/cores") /
218                          (F("core.%s") % status.dead_pid()));
219 
220     for (std::vector< fs::path >::const_iterator iter = candidates.begin();
221          iter != candidates.end(); ++iter) {
222         if (fs::exists(*iter)) {
223             LD(F("Attempting core file candidate %s: found") % *iter);
224             return utils::make_optional(*iter);
225         } else {
226             LD(F("Attempting core file candidate %s: not found") % *iter);
227         }
228     }
229     return none;
230 }
231 
232 
233 /// Raises core size limit to its possible maximum.
234 ///
235 /// This is a best-effort operation.  There is no guarantee that the operation
236 /// will yield a large-enough limit to generate any possible core file.
237 ///
238 /// \return True if the core size could be unlimited; false otherwise.
239 bool
unlimit_core_size(void)240 utils::unlimit_core_size(void)
241 {
242     bool ok;
243 
244     struct ::rlimit rl;
245     if (::getrlimit(RLIMIT_CORE, &rl) == -1) {
246         const int original_errno = errno;
247         LW(F("getrlimit should not have failed but got: %s") %
248            std::strerror(original_errno));
249         ok = false;
250     } else {
251         if (rl.rlim_max == 0) {
252             LW("getrlimit returned 0 for RLIMIT_CORE rlim_max; cannot raise "
253                "soft core limit");
254             ok = false;
255         } else {
256             rl.rlim_cur = rl.rlim_max;
257             LD(F("Raising soft core size limit to %s (hard value)") %
258                rl.rlim_cur);
259             if (::setrlimit(RLIMIT_CORE, &rl) == -1) {
260                 const int original_errno = errno;
261                 LW(F("setrlimit should not have failed but got: %s") %
262                    std::strerror(original_errno));
263                 ok = false;
264             } else {
265                 ok = true;
266             }
267         }
268     }
269 
270     return ok;
271 }
272 
273 
274 /// Gathers a stacktrace of a crashed program.
275 ///
276 /// \param program The name of the binary that crashed and dumped a core file.
277 ///     Can be either absolute or relative.
278 /// \param executor_handle The executor handler to get the status from and
279 ///     gdb handler from.
280 /// \param exit_handle The exit handler to stream additional diagnostic
281 ///     information from (stderr) and for redirecting to additional
282 ///     information to gdb from.
283 ///
284 /// \post If anything goes wrong, the diagnostic messages are written to the
285 /// output.  This function should not throw.
286 void
dump_stacktrace(const fs::path & program,executor::executor_handle & executor_handle,const executor::exit_handle & exit_handle)287 utils::dump_stacktrace(const fs::path& program,
288                        executor::executor_handle& executor_handle,
289                        const executor::exit_handle& exit_handle)
290 {
291     PRE(exit_handle.status());
292     const process::status& status = exit_handle.status().get();
293     PRE(status.signaled() && status.coredump());
294 
295     std::ofstream gdb_err(exit_handle.stderr_file().c_str(), std::ios::app);
296     if (!gdb_err) {
297         LW(F("Failed to open %s to append GDB's output") %
298            exit_handle.stderr_file());
299         return;
300     }
301 
302     gdb_err << F("Process with PID %s exited with signal %s and dumped core; "
303                  "attempting to gather stack trace\n") %
304         status.dead_pid() % status.termsig();
305 
306     const optional< fs::path > gdb = utils::find_gdb();
307     if (!gdb) {
308         gdb_err << F("Cannot find GDB binary; builtin was '%s'\n") %
309             builtin_gdb;
310         return;
311     }
312 
313     const optional< fs::path > core_file = find_core(
314         program, status, exit_handle.work_directory());
315     if (!core_file) {
316         gdb_err << F("Cannot find any core file\n");
317         return;
318     }
319 
320     gdb_err.flush();
321     const executor::exec_handle exec_handle =
322         executor_handle.spawn_followup(
323             run_gdb(gdb.get(), program, core_file.get()),
324             exit_handle, gdb_timeout);
325     const executor::exit_handle gdb_exit_handle =
326         executor_handle.wait(exec_handle);
327 
328     const optional< process::status >& gdb_status = gdb_exit_handle.status();
329     if (!gdb_status) {
330         gdb_err << "GDB timed out\n";
331     } else {
332         if (gdb_status.get().exited() &&
333             gdb_status.get().exitstatus() == EXIT_SUCCESS) {
334             gdb_err << "GDB exited successfully\n";
335         } else {
336             gdb_err << "GDB failed; see output above for details\n";
337         }
338     }
339 }
340 
341 
342 /// Gathers a stacktrace of a program if it crashed.
343 ///
344 /// This is just a convenience function to allow appending the stacktrace to an
345 /// existing file and to permit reusing the status as returned by auxiliary
346 /// process-spawning functions.
347 ///
348 /// \param program The name of the binary that crashed and dumped a core file.
349 ///     Can be either absolute or relative.
350 /// \param executor_handle The executor handler to get the status from and
351 ///     gdb handler from.
352 /// \param exit_handle The exit handler to stream additional diagnostic
353 ///     information from (stderr) and for redirecting to additional
354 ///     information to gdb from.
355 ///
356 /// \throw std::runtime_error If the output file cannot be opened.
357 ///
358 /// \post If anything goes wrong with the stack gatheringq, the diagnostic
359 /// messages are written to the output.
360 void
dump_stacktrace_if_available(const fs::path & program,executor::executor_handle & executor_handle,const executor::exit_handle & exit_handle)361 utils::dump_stacktrace_if_available(const fs::path& program,
362                                     executor::executor_handle& executor_handle,
363                                     const executor::exit_handle& exit_handle)
364 {
365     const optional< process::status >& status = exit_handle.status();
366     if (!status || !status.get().signaled() || !status.get().coredump())
367         return;
368 
369     dump_stacktrace(program, executor_handle, exit_handle);
370 }
371