diff options
-rw-r--r-- | libbutl/backtrace.cxx | 101 | ||||
-rw-r--r-- | libbutl/backtrace.mxx | 43 | ||||
-rw-r--r-- | libbutl/buildfile | 28 | ||||
-rw-r--r-- | tests/backtrace/buildfile | 12 | ||||
-rw-r--r-- | tests/backtrace/driver.cxx | 97 | ||||
-rw-r--r-- | tests/backtrace/testscript | 20 |
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 +} |