[beast: 52/57] SFI: platform: add backtrace generation API (GLIBC specific)
- From: Tim Janik <timj src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [beast: 52/57] SFI: platform: add backtrace generation API (GLIBC specific)
- Date: Sun, 23 Jul 2017 10:02:09 +0000 (UTC)
commit 78605a1f95a08d2b144f8164dac29f56bfb82afb
Author: Tim Janik <timj gnu org>
Date: Wed Jul 19 03:00:51 2017 +0200
SFI: platform: add backtrace generation API (GLIBC specific)
Signed-off-by: Tim Janik <timj gnu org>
sfi/bcore.hh | 11 --
sfi/platform.cc | 414 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
sfi/platform.hh | 22 +++
3 files changed, 436 insertions(+), 11 deletions(-)
---
diff --git a/sfi/bcore.hh b/sfi/bcore.hh
index 3a77f7d..2780179 100644
--- a/sfi/bcore.hh
+++ b/sfi/bcore.hh
@@ -52,9 +52,6 @@ template<class ...Args> inline void dump (const char *conditiona
template<class ...Args> inline void debug (const char *conditional, const char *format, const
Args &...args) BSE_ALWAYS_INLINE;
inline bool debug_enabled (const char *conditional) BSE_ALWAYS_INLINE
BSE_PURE;
-// == Development Aids ==
-extern inline void breakpoint () BSE_ALWAYS_INLINE; ///< Cause a debugging breakpoint,
for development only.
-
// == Binary Lookups ==
template<typename RandIter, class Cmp, typename Arg, int case_lookup_or_sibling_or_insertion>
extern inline std::pair<RandIter,bool>
@@ -138,14 +135,6 @@ compare_greater (const Value &v1, const Value &v2)
}
// == Implementation Details ==
-#if (defined __i386__ || defined __x86_64__)
-inline void breakpoint() { __asm__ __volatile__ ("int $03"); }
-#elif defined __alpha__ && !defined __osf__
-inline void breakpoint() { __asm__ __volatile__ ("bpt"); }
-#else // !__i386__ && !__alpha__
-inline void breakpoint() { __builtin_trap(); }
-#endif
-
namespace Internal {
extern bool debug_any_enabled; //< Indicates if $BSE_DEBUG enables some debug
settings.
bool debug_key_enabled (const char *conditional) BSE_PURE;
diff --git a/sfi/platform.cc b/sfi/platform.cc
index d2255bc..4b28b3b 100644
--- a/sfi/platform.cc
+++ b/sfi/platform.cc
@@ -242,4 +242,418 @@ dummy_backtrace (void **buffer, int size)
int (*backtrace_pointers) (void **buffer, int size) = &dummy_backtrace;
#endif // !_EXECINFO_H
+// == Sub-Process ==
+struct PExec {
+ String data_in, data_out, data_err;
+ uint64 usec_timeout; // max child runtime
+ StringVector args;
+ StringVector evars;
+public:
+ inline int execute (); // returns -errno
+};
+
+static String
+exec_cmd (const String &cmd, bool witherr, StringVector fix_env, uint64 usec_timeout = 1000000 / 2)
+{
+ String btout;
+ PExec sub;
+ sub.data_in = "";
+ sub.usec_timeout = usec_timeout;
+ sub.args = string_split (cmd, " ");
+ if (sub.args.size() < 1)
+ sub.args.push_back ("");
+ sub.evars = fix_env;
+ int eret = sub.execute();
+ if (eret < 0)
+ warning ("executing '%s' failed: %s\n", sub.args[0].c_str(), strerror (-eret));
+ if (!witherr)
+ sub.data_err = "";
+ return sub.data_err + sub.data_out;
+}
+
+static int // 0 on success
+kill_child (int pid, int *status, int patience)
+{
+ int wr;
+ if (patience >= 3) // try graceful reap
+ {
+ if (waitpid (pid, status, WNOHANG) > 0)
+ return 0;
+ }
+ if (patience >= 2) // try SIGHUP
+ {
+ kill (pid, SIGHUP);
+ if (waitpid (pid, status, WNOHANG) > 0)
+ return 0;
+ usleep (20 * 1000); // give it some scheduling/shutdown time
+ if (waitpid (pid, status, WNOHANG) > 0)
+ return 0;
+ usleep (50 * 1000); // give it some scheduling/shutdown time
+ if (waitpid (pid, status, WNOHANG) > 0)
+ return 0;
+ usleep (100 * 1000); // give it some scheduling/shutdown time
+ if (waitpid (pid, status, WNOHANG) > 0)
+ return 0;
+ }
+ if (patience >= 1) // try SIGTERM
+ {
+ kill (pid, SIGTERM);
+ if (waitpid (pid, status, WNOHANG) > 0)
+ return 0;
+ usleep (200 * 1000); // give it some scheduling/shutdown time
+ if (waitpid (pid, status, WNOHANG) > 0)
+ return 0;
+ usleep (400 * 1000); // give it some scheduling/shutdown time
+ if (waitpid (pid, status, WNOHANG) > 0)
+ return 0;
+ }
+ // finish it off
+ kill (pid, SIGKILL);
+ do
+ wr = waitpid (pid, status, 0);
+ while (wr < 0 && errno == EINTR);
+ return wr;
+}
+
+static inline ssize_t
+string_must_read (String &string, int fd)
+{
+ ssize_t n_bytes;
+ do
+ {
+ char buf[4096];
+ n_bytes = read (fd, buf, sizeof (buf));
+ if (n_bytes == 0)
+ return 0; // EOF, calling this function assumes data is available
+ else if (n_bytes > 0)
+ {
+ string.append (buf, n_bytes);
+ return n_bytes;
+ }
+ }
+ while (n_bytes < 0 && errno == EINTR);
+ // n_bytes < 0
+ warning ("failed to read() from child process: %s", strerror (errno));
+ return -1;
+}
+
+#define RETRY_ON_EINTR(expr) ({ ssize_t __r; do __r = ssize_t (expr); while (__r < 0 && errno == EINTR);
__r; })
+
+static inline size_t
+string_must_write (const String &string, int outfd, size_t *stringpos)
+{
+ if (*stringpos < string.size())
+ {
+ ssize_t n = RETRY_ON_EINTR (write (outfd, string.data() + *stringpos, string.size() - *stringpos));
+ *stringpos += MAX (n, 0);
+ }
+ return *stringpos < string.size(); // remainings
+}
+
+int
+PExec::execute ()
+{
+ int fork_pid = -1;
+ int stdin_pipe[2] = { -1, -1 };
+ int stdout_pipe[2] = { -1, -1 };
+ int stderr_pipe[2] = { -1, -1 };
+ ssize_t i;
+ const int parent_pid = getpid();
+ if (pipe (stdin_pipe) < 0 || pipe (stdout_pipe) < 0 || pipe (stderr_pipe) < 0)
+ goto return_errno;
+ if (signal (SIGCHLD, SIG_DFL) == SIG_ERR)
+ goto return_errno;
+ char parent_pid_str[64];
+ if (snprintf (parent_pid_str, sizeof (parent_pid_str), "%u", parent_pid) < 0)
+ goto return_errno;
+ char self_exe[1024]; // PATH_MAX
+ i = readlink ("/proc/self/exe", self_exe, sizeof (self_exe));
+ if (i < 0)
+ goto return_errno;
+ errno = ENOMEM;
+ if (size_t (i) >= sizeof (self_exe))
+ goto return_errno;
+ fork_pid = fork ();
+ if (fork_pid < 0)
+ goto return_errno;
+ if (fork_pid == 0) // child
+ {
+ for (const String &evar : evars)
+ {
+ const char *var = evar.c_str(), *eq = strchr (var, '=');
+ if (!eq)
+ {
+ unsetenv (var);
+ continue;
+ }
+ setenv (evar.substr (0, eq - var).c_str(), eq + 1, true);
+ }
+ close (stdin_pipe[1]);
+ close (stdout_pipe[0]);
+ close (stderr_pipe[0]);
+ if (RETRY_ON_EINTR (dup2 (stdin_pipe[0], 0)) < 0 ||
+ RETRY_ON_EINTR (dup2 (stdout_pipe[1], 1)) < 0 ||
+ RETRY_ON_EINTR (dup2 (stderr_pipe[1], 2)) < 0)
+ {
+ kill (getpid(), SIGSYS);
+ while (1)
+ _exit (-128);
+ }
+ if (stdin_pipe[0] >= 3)
+ close (stdin_pipe[0]);
+ if (stdout_pipe[1] >= 3)
+ close (stdout_pipe[1]);
+ if (stderr_pipe[1] >= 3)
+ close (stderr_pipe[1]);
+ const ssize_t exec_nargs = args.size() + 1;
+ const char *exec_args[exec_nargs];
+ for (i = 0; i < exec_nargs - 1; i++)
+ if (args[i] == "\uFFFDexe\u001A")
+ exec_args[i] = self_exe;
+ else if (args[i] == "\uFFFDpid\u001A")
+ exec_args[i] = parent_pid_str;
+ else
+ exec_args[i] = args[i].c_str();
+ exec_args[exec_nargs - 1] = NULL;
+ execvp (exec_args[0], (char**) exec_args);
+ kill (getpid(), SIGSYS);
+ while (1)
+ _exit (-128);
+ }
+ else // parent
+ {
+ size_t data_in_pos = 0, need_wait = true;
+ close (stdin_pipe[0]);
+ close (stdout_pipe[1]);
+ close (stderr_pipe[1]);
+ uint last_status = 0;
+ uint64 sstamp = timestamp_realtime();
+ // read data until we get EOF on all pipes
+ while (stdout_pipe[0] >= 0 || stderr_pipe[0] >= 0)
+ {
+ fd_set readfds, writefds;
+ FD_ZERO (&readfds);
+ FD_ZERO (&writefds);
+ if (stdin_pipe[1] >= 0)
+ FD_SET (stdin_pipe[1], &writefds);
+ if (stdout_pipe[0] >= 0)
+ FD_SET (stdout_pipe[0], &readfds);
+ if (stderr_pipe[0] >= 0)
+ FD_SET (stderr_pipe[0], &readfds);
+ int maxfd = MAX (stdin_pipe[1], MAX (stdout_pipe[0], stderr_pipe[0]));
+ struct timeval tv;
+ tv.tv_sec = 0; // sleep at most 0.5 seconds to catch clock skews, etc.
+ tv.tv_usec = MIN (usec_timeout ? usec_timeout : 1000000, 100 * 1000);
+ int ret = select (maxfd + 1, &readfds, &writefds, NULL, &tv);
+ if (ret < 0 && errno != EINTR)
+ goto return_errno;
+ if (stdin_pipe[1] >= 0 &&
+ FD_ISSET (stdin_pipe[1], &writefds) &&
+ string_must_write (data_in, stdin_pipe[1], &data_in_pos) == 0)
+ {
+ close (stdin_pipe[1]);
+ stdin_pipe[1] = -1;
+ }
+ if (stdout_pipe[0] >= 0 && FD_ISSET (stdout_pipe[0], &readfds) && string_must_read (data_out,
stdout_pipe[0]) == 0)
+ {
+ close (stdout_pipe[0]);
+ stdout_pipe[0] = -1;
+ }
+ if (stderr_pipe[0] >= 0 && FD_ISSET (stderr_pipe[0], &readfds) && string_must_read (data_err,
stderr_pipe[0]) == 0)
+ {
+ close (stderr_pipe[0]);
+ stderr_pipe[0] = -1;
+ }
+ if (usec_timeout)
+ {
+ uint64 nstamp = timestamp_realtime();
+ int status = 0;
+ sstamp = MIN (sstamp, nstamp); // guard against backwards clock skews
+ if (usec_timeout < nstamp - sstamp)
+ {
+ // timeout reached, need to abort the child now
+ kill_child (fork_pid, &status, 3);
+ last_status = 1024; // timeout
+ if (WIFSIGNALED (status))
+ ; // debug_msg ("%s: child timed out and received: %s\n", __func__, strsignal (WTERMSIG
(status)));
+ need_wait = false;
+ break;
+ }
+ }
+ }
+ close (stdout_pipe[0]);
+ close (stderr_pipe[0]);
+ if (need_wait)
+ {
+ int status = 0;
+ pid_t wr;
+ do
+ wr = waitpid (fork_pid, &status, 0);
+ while (wr < 0 && errno == EINTR);
+ if (WIFEXITED (status)) // normal exit
+ last_status = WEXITSTATUS (status); // 0..255
+ else if (WIFSIGNALED (status))
+ last_status = (WTERMSIG (status) << 12); // signalled
+ else // WCOREDUMP (status)
+ last_status = 512; // coredump
+ }
+ return last_status;
+ }
+ return_errno:
+ const int error_errno = errno;
+ if (fork_pid > 0) // child still alive?
+ {
+ int status = 0;
+ kill_child (fork_pid, &status, 1);
+ }
+ close (stdin_pipe[0]);
+ close (stdin_pipe[1]);
+ close (stdout_pipe[0]);
+ close (stdout_pipe[1]);
+ close (stderr_pipe[0]);
+ close (stderr_pipe[1]);
+ errno = error_errno;
+ return -error_errno;
+}
+
+// == Backtrace ==
+struct Mapping { size_t addr, end; String exe; };
+typedef std::vector<Mapping> MappingVector;
+
+static MappingVector
+read_maps ()
+{
+ MappingVector mv;
+ FILE *fmaps = fopen ("/proc/self/maps", "r");
+ if (!fmaps)
+ return mv;
+ int count = 0;
+ while (!feof (fmaps))
+ {
+ size_t addr = 0, end = 0, offset, inode = 0;
+ char perms[9 + 1], filename[1234 + 1] = "", device[9 + 1] = "";
+ count = fscanf (fmaps, "%zx-%zx %9s %zx %9s %zu", &addr, &end, perms, &offset, device, &inode);
+ if (count < 1)
+ break;
+ char c = fgetc (fmaps);
+ while (c == ' ')
+ c = fgetc (fmaps);
+ if (c != '\n')
+ {
+ ungetc (c, fmaps);
+ count = fscanf (fmaps, "%1234s\n", filename);
+ }
+ if (filename[0])
+ mv.push_back (Mapping { addr, end, filename });
+ }
+ fclose (fmaps);
+ return mv;
+}
+
+/// Generate a list of strings describing backtrace frames from the given frame pointers using addr2line(1).
+StringVector
+pretty_backtrace_symbols (void **pointers, const int nptrs)
+{
+ // fetch process maps to correlate pointers
+ MappingVector maps = read_maps();
+ // reduce mappings to DSOs
+ char self_exe[1024 + 1] = { 0, }; // PATH_MAX
+ ssize_t l = readlink ("/proc/self/exe", self_exe, sizeof (self_exe));
+ if (l > 0)
+ {
+ // filter out mapping entry for no-PIE binary which addr2line expects absolute addresses for
+ for (size_t i = 0; i < maps.size(); i++)
+ if (maps[i].exe.size() < 2 || maps[i].exe[0] != '/' || // erase "[heap]" and other non-dso
+ (maps[i].exe == self_exe && // find /proc/self/exe
+ (maps[i].addr == 0x08048000 || // statically linked elf_i386 binary
+ maps[i].addr == 0x00400000))) // statically linked elf_x86_64 binary
+ {
+ maps.erase (maps.begin() + i);
+ i--;
+ }
+ }
+ // resolve pointers to function, file and line
+ StringVector symbols;
+ const char *addr2line = "/usr/bin/addr2line";
+ const bool have_addr2line = access (addr2line, X_OK) == 0;
+ for (ssize_t i = 0; i < nptrs; i++)
+ {
+ const size_t addr = size_t (pointers[i]);
+ String dso;
+ size_t dso_offset = 0;
+ // find DSO for pointer
+ for (size_t j = 0; j < maps.size(); j++)
+ if (maps[j].addr < addr && addr < maps[j].end)
+ {
+ dso = maps[j].exe;
+ dso_offset = addr - maps[j].addr;
+ break;
+ }
+ if (dso.empty())
+ {
+ // no DSO, resort to executable
+ dso = self_exe;
+ dso_offset = addr;
+ }
+ // resolve through addr2line
+ String entry;
+ if (have_addr2line)
+ {
+ // resolve to code location *before* return address
+ const size_t caller_addr = dso_offset - 1;
+ entry = exec_cmd (string_format ("%s -C -f -s -i -p -e %s 0x%zx", addr2line, dso.c_str(),
caller_addr), true,
+ cstrings_to_vector ("LC_ALL=C", "LANGUAGE", NULL));
+ }
+ // polish entry
+ while (entry.size() && strchr ("\n \t\r", entry[entry.size() - 1]))
+ entry.resize (entry.size() - 1);
+ if (string_endswith (entry, ":?"))
+ entry.resize (entry.size() - 2);
+ if (string_endswith (entry, " at ??"))
+ entry.resize (entry.size() - 6);
+ if (entry.compare (0, 2, "??") == 0)
+ entry = "";
+ if (entry.empty())
+ entry = string_format ("%s@0x%zx", dso.c_str(), dso_offset);
+ // format backtrace symbol output
+ String symbol = string_format ("0x%016zx", addr);
+ if (!entry.empty())
+ symbol += " in " + entry;
+ for (auto sym : string_split (symbol, "\n"))
+ {
+ if (sym.compare (0, 14, " (inlined by) ") == 0)
+ sym = " inlined by " + sym.substr (14);
+ symbols.push_back (sym);
+ }
+ }
+ return symbols;
+}
+
+/// Generate a pretty backtrace string, given backtrace pointers and using pretty_backtrace_symbols().
+String
+pretty_backtrace (void **ptrs, ssize_t nptrs, const char *file, int line, const char *func)
+{
+ void *fallback = __builtin_return_address (0);
+ if (nptrs < 0)
+ {
+ ptrs = &fallback;
+ nptrs = 1;
+ }
+ StringVector symbols = pretty_backtrace_symbols (ptrs, nptrs);
+ String where;
+ if (file && file[0])
+ where += file;
+ if (!where.empty() && line > 0)
+ where += string_format (":%u", line);
+ if (func && func[0])
+ {
+ if (!where.empty())
+ where += ":";
+ where += func;
+ }
+ if (!where.empty())
+ where = " (from " + where + ")";
+ return string_format ("Backtrace at 0x%016x%s:\n %s\n", ptrs[0], where, string_join ("\n ", symbols));
+}
+
} // Bse
diff --git a/sfi/platform.hh b/sfi/platform.hh
index a0e7998..245e945 100644
--- a/sfi/platform.hh
+++ b/sfi/platform.hh
@@ -23,6 +23,28 @@ String program_cwd (); ///< The current working
std::string executable_name (); ///< Retrieve the name part of executable_path().
std::string executable_path (); ///< Retrieve the path to the currently running
executable.
+// == Debugging Aids ==
+extern inline void breakpoint () BSE_ALWAYS_INLINE; ///< Cause a debugging breakpoint,
for development only.
+extern int (*backtrace_pointers) (void **buffer, int size); ///< Capture stack frames for a
backtrace (on __GLIBC__).
+String pretty_backtrace (void **ptrs, ssize_t nptrs, const char *file, int line, const
char *func);
+StringVector pretty_backtrace_symbols (void **pointers, const int nptrs);
+#define BSE_BACKTRACE_MAXDEPTH 1024 ///< Maximum depth for runtime backtrace generation.
+/// Print backtrace of the current line to stderr.
+#define BSE_BACKTRACE() ({ ::Bse::printerr ("%s", BSE_BACKTRACE_STRING()); })
+/// Generate a string that contains a backtrace of the current line.
+#define BSE_BACKTRACE_STRING() ({ void *__p_[BSE_BACKTRACE_MAXDEPTH]; \
+ const String __s_ = ::Bse::pretty_backtrace (__p_, ::Bse::backtrace_pointers (__p_, sizeof (__p_) /
sizeof (__p_[0])), \
+ __FILE__, __LINE__, __func__); __s_; })
+
+// == Implementation Details ==
+#if (defined __i386__ || defined __x86_64__)
+inline void breakpoint() { __asm__ __volatile__ ("int $03"); }
+#elif defined __alpha__ && !defined __osf__
+inline void breakpoint() { __asm__ __volatile__ ("bpt"); }
+#else // !__i386__ && !__alpha__
+inline void breakpoint() { __builtin_trap(); }
+#endif
+
} // Bse
#endif // __BSE_PLATFORM_HH__
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]