aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libbutl/backtrace.cxx101
-rw-r--r--libbutl/backtrace.mxx43
-rw-r--r--libbutl/buildfile28
-rw-r--r--tests/backtrace/buildfile12
-rw-r--r--tests/backtrace/driver.cxx97
-rw-r--r--tests/backtrace/testscript20
6 files changed, 292 insertions, 9 deletions
diff --git a/libbutl/backtrace.cxx b/libbutl/backtrace.cxx
new file mode 100644
index 0000000..7a8d615
--- /dev/null
+++ b/libbutl/backtrace.cxx
@@ -0,0 +1,101 @@
+// file : libbutl/backtrace.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef __cpp_modules_ts
+#include <libbutl/backtrace.mxx>
+#endif
+
+// We only enable backtrace during bootstrap if we can do it without any
+// complications of the build scripts/makefiles.
+//
+// With glibc linking with -rdynamic gives (non-static) function names.
+// FreeBSD requires explicitly linking -lexecinfo.
+//
+#ifndef BUILD2_BOOTSTRAP
+# if defined(__linux__) || \
+ defined(__APPLE__) || \
+ defined(__FreeBSD__)
+# define LIBBUTL_BACKTRACE
+# endif
+#else
+# if defined(__linux__) || \
+ defined(__APPLE__)
+# define LIBBUTL_BACKTRACE
+# endif
+#endif
+
+#ifdef LIBBUTL_BACKTRACE
+# include <stdlib.h> // free()
+# include <execinfo.h>
+#endif
+
+#include <cassert>
+
+#ifndef __cpp_lib_modules_ts
+#include <string>
+
+#ifdef LIBBUTL_BACKTRACE
+# include <memory> // unique_ptr
+# include <cstddef> // size_t
+#endif
+
+#include <exception>
+#endif
+
+// Other includes.
+
+#ifdef __cpp_modules_ts
+module butl.backtrace;
+
+// Only imports additional to interface.
+#ifdef __clang__
+#ifdef __cpp_lib_modules_ts
+import std.core;
+#endif
+#endif
+
+#endif
+
+using namespace std;
+
+namespace butl
+{
+ string
+ backtrace () noexcept
+ try
+ {
+ string r;
+
+#ifdef LIBBUTL_BACKTRACE
+
+ // Note: backtrace() returns int on Linux and MacOS and size_t on FreeBSD.
+ //
+ void* buf[1024];
+ auto n (::backtrace (buf, 1024));
+
+ assert (n >= 0);
+
+ char** fs (backtrace_symbols (buf, n)); // Note: returns NULL on error.
+
+ if (fs != nullptr)
+ {
+ unique_ptr<char*, void (*)(char**)> deleter (
+ fs, [] (char** s) {::free (s);});
+
+ for (size_t i (0); i != static_cast<size_t> (n); ++i)
+ {
+ r += fs[i];
+ r += '\n';
+ }
+ }
+
+#endif
+
+ return r;
+ }
+ catch (const std::exception&)
+ {
+ return string ();
+ }
+}
diff --git a/libbutl/backtrace.mxx b/libbutl/backtrace.mxx
new file mode 100644
index 0000000..25d0dcd
--- /dev/null
+++ b/libbutl/backtrace.mxx
@@ -0,0 +1,43 @@
+// file : libbutl/backtrace.mxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef __cpp_modules_ts
+#pragma once
+#endif
+
+// C includes.
+
+#ifndef __cpp_lib_modules_ts
+#include <string>
+#endif
+
+// Other includes.
+
+#ifdef __cpp_modules_ts
+export module butl.backtrace;
+#ifdef __cpp_lib_modules_ts
+import std.core;
+#endif
+#endif
+
+#include <libbutl/export.hxx>
+
+LIBBUTL_MODEXPORT namespace butl
+{
+ // Return the calling thread's backtrace or empty string if this
+ // functionality is not supported or an error has occurred. The exact
+ // backtrace format is implementation-defined; it normally contains a line
+ // with the binary name, address in that binary, and, if available, the
+ // function name for each stack frame.
+ //
+ // Currently this functionality is only available on Linux, FreeBSD, and Mac
+ // OS. On the first two platforms the address can be mapped to the function
+ // name and, if built with debug info, to source location using the
+ // addr2line(1) utility:
+ //
+ // $ addr2line -f -C -e <binary> <addr>
+ //
+ LIBBUTL_SYMEXPORT std::string
+ backtrace () noexcept;
+}
diff --git a/libbutl/buildfile b/libbutl/buildfile
index 8e88402..feb2f96 100644
--- a/libbutl/buildfile
+++ b/libbutl/buildfile
@@ -16,7 +16,15 @@ lib{butl}: {hxx ixx txx cxx}{** -uuid-* +uuid-io -win32-utility} \
hxx{**.hxx -uuid-*.hxx +uuid-io.hxx -win32-utility.hxx \
-version.hxx} hxx{version}
-windows = ($cxx.target.class == 'windows')
+tclass = $cxx.target.class
+tsys = $cxx.target.system
+
+linux = ($tclass == 'linux')
+macos = ($tclass == 'macos')
+windows = ($tclass == 'windows')
+freebsd = ($tsys == 'freebsd')
+
+mingw = ($tsys == 'mingw32')
# Exclude these from compilation on non-Windows targets.
#
@@ -30,17 +38,19 @@ lib{butl}: file{*.c *.h}
# Platform-specific UUID implementations.
#
-lib{butl}: cxx{uuid-linux}: include = ($cxx.target.class == 'linux')
-lib{butl}: cxx{uuid-macos}: include = ($cxx.target.class == 'macos')
+lib{butl}: cxx{uuid-linux}: include = $linux
+lib{butl}: cxx{uuid-macos}: include = $macos
lib{butl}: cxx{uuid-windows}: include = $windows
-lib{butl}: cxx{uuid-freebsd}: include = ($cxx.target.system == 'freebsd')
+lib{butl}: cxx{uuid-freebsd}: include = $freebsd
-if ($cxx.target.class == 'linux')
+if $linux
cxx.libs += -ldl
-elif ($cxx.target.class == 'macos')
+elif $macos
cxx.libs += -framework CoreFoundation
-elif ($windows)
- cxx.libs += ($cxx.target.system == 'mingw32' ? -lrpcrt4 : rpcrt4.lib)
+elif $windows
+ cxx.libs += ($mingw ? -lrpcrt4 : rpcrt4.lib)
+elif $freebsd
+ cxx.libs += -lexecinfo
# Include the generated version header into the distribution (so that we don't
# pick up an installed one) and don't remove it when cleaning in src (so that
@@ -63,7 +73,7 @@ objs{*} bmis{*}: cxx.poptions += -DLIBBUTL_SHARED_BUILD
# Additional system libraries.
#
if $windows
- cxx.libs += ($cxx.target.system == 'mingw32' ? -limagehlp : imagehlp.lib)
+ cxx.libs += ($mingw ? -limagehlp : imagehlp.lib)
else
cxx.libs += -lpthread
diff --git a/tests/backtrace/buildfile b/tests/backtrace/buildfile
new file mode 100644
index 0000000..a4120df
--- /dev/null
+++ b/tests/backtrace/buildfile
@@ -0,0 +1,12 @@
+# file : tests/backtrace/buildfile
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+import libs = libbutl%lib{butl}
+
+exe{driver}: {hxx cxx}{*} $libs testscript
+
+# Make sure backtrace() includes function names.
+#
+if ($cxx.target.class == 'linux')
+ cxx.loptions += -rdynamic
diff --git a/tests/backtrace/driver.cxx b/tests/backtrace/driver.cxx
new file mode 100644
index 0000000..5fd9223
--- /dev/null
+++ b/tests/backtrace/driver.cxx
@@ -0,0 +1,97 @@
+// file : tests/backtrace/driver.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef _WIN32
+# include <sys/resource.h> // setrlimit()
+#endif
+
+#include <cassert>
+
+#ifndef __cpp_lib_modules_ts
+#include <string>
+#include <iostream>
+#include <exception> // set_terminate(), terminate_handler
+#include <system_error>
+#else
+import std.io;
+#endif
+
+// Other includes.
+
+#ifdef __cpp_modules_ts
+#ifdef __cpp_lib_modules_ts
+import std.core;
+#endif
+import butl.process;
+import butl.backtrace;
+#else
+#include <libbutl/process.mxx>
+#include <libbutl/backtrace.mxx>
+#endif
+
+using namespace std;
+using namespace butl;
+
+namespace test
+{
+ // Note: is not static to make sure the stack frame is not optimized out.
+ //
+ int
+ func ()
+ {
+ throw system_error (EINVAL, generic_category ());
+ }
+}
+
+static terminate_handler def_term_handler;
+
+// Usage: argv[0] [-c]
+//
+// Print the backtrace for an unhandled exception.
+//
+// -c
+// Run as a child process that sets up the backtrace-printing terminate
+// handler and throws unhandled exception.
+int
+main (int argc, const char* argv[])
+{
+ // Not to cause the testscript to fail due to the abnormal test driver
+ // termination, delegate the unhandled exception backtrace printing to the
+ // child process.
+ //
+ bool child (false);
+ for (int i (1); i != argc; ++i)
+ {
+ string o (argv[i]);
+
+ if (o == "-c")
+ child = true;
+ else
+ assert (false);
+ }
+
+ if (child)
+ {
+ // Disable dumping core file on POSIX.
+ //
+#ifndef _WIN32
+ struct rlimit rlim {0, 0};
+ assert (setrlimit (RLIMIT_CORE, &rlim) == 0);
+#endif
+
+ def_term_handler = set_terminate ([]()
+ {
+ cerr << backtrace ();
+
+ if (def_term_handler != nullptr)
+ def_term_handler ();
+ });
+
+ return test::func ();
+ }
+
+ // Report failure with non-zero code if child succeeds.
+ //
+ return process_run (0, 1, 2, argv[0], "-c") ? 1 : 0;
+}
diff --git a/tests/backtrace/testscript b/tests/backtrace/testscript
new file mode 100644
index 0000000..bdcd147
--- /dev/null
+++ b/tests/backtrace/testscript
@@ -0,0 +1,20 @@
+# file : tests/backtrace/testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+tclass = $cxx.target.class
+tsys = $cxx.target.system
+
+: basic
+:
+if ($tclass == 'linux' || $tclass == 'macos' || $tsys == 'freebsd')
+{
+ # The stack frame line format varies among OSes. The only common thing is
+ # the '0x' function address prefix.
+ #
+ $* 2>>~%EOE%
+ %.*
+ %.*0x.*%
+ %.*
+ EOE
+}