[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master 1227352 2/2: Add C++ exception unwinder for p
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master 1227352 2/2: Add C++ exception unwinder for pc-linux-gnu |
Date: |
Sun, 7 Feb 2021 18:41:19 -0500 (EST) |
branch: master
commit 12273527fa9d951e5ee891982ef562d0d4b3c24e
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>
Add C++ exception unwinder for pc-linux-gnu
* Makefile.am: Link unwinder into lmi and unit tests only.
* lmi_setup_20.sh: Install extra libraries.
* objects.make: Likewise.
* posix_fhs.make: Specify extra libraries.
* test_coding_rules.cpp: Allow certain reserved names.
* unwind.cpp: Unwind exceptions.
* unwind.hpp: Likewise.
* workhorse.make: Link extra libraries.
Obviously libdw should be used to format the stack trace nicely.
No doubt practical experience will guide future use. For example,
showing a backtrace on the console for exceptions that are already
caught and handled appropriately may not be ideal.
---
Makefile.am | 5 +-
lmi_setup_20.sh | 2 +
objects.make | 2 +
posix_fhs.make | 2 +
test_coding_rules.cpp | 6 +-
unwind.cpp | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++
unwind.hpp | 31 ++++++++++
workhorse.make | 1 +
8 files changed, 209 insertions(+), 2 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 00772d8..a9d0845 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -377,6 +377,7 @@ liblmi_common_sources = \
system_command.cpp \
timer.cpp \
tn_range_types.cpp \
+ unwind.cpp \
xml_lmi.cpp \
yare_input.cpp
@@ -574,7 +575,8 @@ common_test_objects = \
alert_cli.cpp \
fenv_lmi.cpp \
getopt.cpp \
- license.cpp
+ license.cpp \
+ unwind.cpp
test_account_value_SOURCES = \
$(common_test_objects) \
@@ -1485,6 +1487,7 @@ noinst_HEADERS = \
tn_range_type_trammels.hpp \
tn_range_types.hpp \
transferor.hpp \
+ unwind.hpp \
value_cast.hpp \
verify_products.hpp \
version.hpp \
diff --git a/lmi_setup_20.sh b/lmi_setup_20.sh
index 24a99ef..c6f1b9e 100755
--- a/lmi_setup_20.sh
+++ b/lmi_setup_20.sh
@@ -93,9 +93,11 @@ apt-get --assume-yes install \
jing \
libarchive-tools \
libc6-dbg \
+ libdw-dev \
libgtk-3-dev \
libtool \
libxml2-utils \
+ libunwind-dev \
libxslt1-dev \
make \
patch \
diff --git a/objects.make b/objects.make
index cb19e68..b505327 100644
--- a/objects.make
+++ b/objects.make
@@ -243,6 +243,7 @@ common_common_objects := \
system_command.o \
timer.o \
tn_range_types.o \
+ unwind.o \
xml_lmi.o \
yare_input.o \
@@ -488,6 +489,7 @@ common_test_objects := \
fenv_lmi.o \
getopt.o \
license.o \
+ unwind.o \
# List required object files explicitly for each test unless several
# dozen are required. List only object files, not libraries, to avoid
diff --git a/posix_fhs.make b/posix_fhs.make
index 928de26..8e3b84c 100644
--- a/posix_fhs.make
+++ b/posix_fhs.make
@@ -28,6 +28,8 @@ SHREXT := .so
PERFORM :=
+EXTRA_LIBS := -ldw -lunwind -ldl
+
platform_boost_libraries := \
-lboost_filesystem-gcc \
diff --git a/test_coding_rules.cpp b/test_coding_rules.cpp
index d026323..edf8171 100644
--- a/test_coding_rules.cpp
+++ b/test_coding_rules.cpp
@@ -928,7 +928,7 @@ bool check_reserved_name_exception(std::string const& s)
,"_snprintf"
,"_vsnprintf"
,"_wcsdup"
- // Compiler specific: gcc.
+ // Compiler specific: gcc, clang.
,"__FLOAT_WORD_ORDER__"
,"__GLIBCPP__"
,"__GNUC_MINOR__"
@@ -943,7 +943,11 @@ bool check_reserved_name_exception(std::string const& s)
,"__asm__"
,"__attribute__"
,"__clang__"
+ ,"__class_type_info"
,"__cxa_demangle"
+ ,"__cxa_rethrow"
+ ,"__cxa_throw"
+ ,"__dynamic_cast"
// Compiler specific: gcc, Cygwin.
,"__CYGWIN__"
// Compiler specific: gcc, MinGW.
diff --git a/unwind.cpp b/unwind.cpp
new file mode 100644
index 0000000..e4a8d8c
--- /dev/null
+++ b/unwind.cpp
@@ -0,0 +1,162 @@
+// C++ exception unwinder for pc-linux-gnu.
+//
+// Copyright (C) 2021 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// https://savannah.nongnu.org/projects/lmi
+// email: <gchicares@sbcglobal.net>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "unwind.hpp"
+
+#if defined LMI_X86_64 && defined LMI_POSIX
+
+#define UNW_LOCAL_ONLY
+
+#include <cstdio> // fprintf()
+#include <cstdlib> // free()
+#include <cxxabi.h>
+#include <dlfcn.h>
+#include <exception>
+#include <execinfo.h>
+#include <libunwind.h>
+#include <typeinfo> // type_info
+
+#if defined __GNUC__
+# pragma GCC diagnostic push
+ // Calls to low-level C functions may as well use "0" for
+ // terseness instead of "nullptr".
+# pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
+ // Casting dlsym objects to function pointers is allowed
+ // only as a conditional extension.
+# pragma GCC diagnostic ignored "-Wconditionally-supported"
+# pragma GCC diagnostic ignored "-Wold-style-cast"
+#endif // defined __GNUC__
+
+// ABI:
+extern "C" void __cxa_throw
+ (void* thrown_exception // exception address
+ ,struct std::type_info* tinfo // exception type
+ ,void (*dest)(void*) // exception destructor
+ );
+
+using cxa_throw_t = void (*)(void*, std::type_info*, void (*)(void*));
+using cxa_rethrow_t = void (*)();
+
+cxa_throw_t original_cxa_throw = (cxa_throw_t) dlsym(RTLD_NEXT,
"__cxa_throw");
+// Not yet used:
+cxa_rethrow_t original_cxa_rethrow = (cxa_rethrow_t) dlsym(RTLD_NEXT,
"__cxa_rethrow");
+
+// ABI:
+extern "C" char* __cxa_demangle
+ (char const* mangled_name // mangled name, NUL-terminated
+ ,char * output_buffer // just use 0
+ ,size_t * length // just use 0
+ ,int * status // zero --> success
+ );
+
+/// Print type of exception, and what() if it's a std::exception.
+///
+/// Use fprintf() rather than iostreams because the former can't
+/// throw C++ exceptions.
+
+void identify_exception(void* thrown_exception, std::type_info* tinfo)
+{
+ if(0 == tinfo)
+ {
+ std::fprintf(stderr, "Exception type_info is null.\n");
+ return;
+ }
+
+ char const* mangled_name = tinfo->name();
+ int status = 0;
+
+ char* demangled_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
+ char const* exception_name = (0 == status) ? demangled_name : mangled_name;
+ std::fprintf(stderr, "Exception type: '%s'\n", exception_name);
+ std::free(demangled_name);
+
+ using abi_ti = abi::__class_type_info;
+ abi_ti const* src = dynamic_cast<abi_ti const*>(&typeid(std::exception));
+ abi_ti const* dst = dynamic_cast<abi_ti*>(tinfo);
+
+ if(dst && src)
+ {
+ std::exception* x = reinterpret_cast<std::exception*>
+ (abi::__dynamic_cast
+ (thrown_exception
+ ,src // static type of exception object
+ ,dst // desired type of target
+ ,-1 // unspecified relationship between src and dst
+ )
+ );
+ if(x)
+ {
+ std::fprintf(stderr, "what(): '%s'\n", x->what());
+ }
+ }
+}
+
+void fail(char const* msg)
+{
+ std::fprintf(stderr, "%s\n", msg);
+}
+
+void print_backtrace()
+{
+ unw_context_t context;
+ if(0 != unw_getcontext(&context))
+ {fail("Failed to get machine state");}
+
+ unw_cursor_t cursor;
+ if(0 != unw_init_local(&cursor, &context))
+ {fail("Failed to initialize cursor");}
+
+ // IP is in this function, so first frame could be skipped.
+ while(0 < unw_step(&cursor))
+ {
+ unw_word_t pc;
+ if(0 != unw_get_reg(&cursor, UNW_REG_IP, &pc))
+ {fail("Failed to read IP");}
+ std::fprintf(stderr, "0x%lx: ", pc);
+
+ char symbol[4096]; // Should be plenty.
+ unw_word_t offset;
+ if(0 == unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset))
+ {
+ int status = 0;
+ char* demangled_name = abi::__cxa_demangle(symbol, 0, 0, &status);
+ char* name = (0 == status) ? demangled_name : symbol;
+ std::fprintf(stderr, "(%s+0x%lx)\n", name, offset);
+ std::free(demangled_name);
+ }
+ else
+ {
+ std::fprintf(stderr, "Failed to get symbol name.\n");
+ }
+ }
+}
+
+extern "C"
+void __cxa_throw(void* thrown_exception, std::type_info* tinfo, void
(*dest)(void*))
+{
+ identify_exception(thrown_exception, tinfo);
+ print_backtrace();
+ original_cxa_throw(thrown_exception, tinfo, dest);
+}
+
+#endif // defined LMI_X86_64 && defined LMI_POSIX
diff --git a/unwind.hpp b/unwind.hpp
new file mode 100644
index 0000000..91a6c52
--- /dev/null
+++ b/unwind.hpp
@@ -0,0 +1,31 @@
+// C++ exception unwinder for pc-linux-gnu.
+//
+// Copyright (C) 2021 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// https://savannah.nongnu.org/projects/lmi
+// email: <gchicares@sbcglobal.net>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#ifndef unwind_hpp
+#define unwind_hpp
+
+#include "config.hpp"
+
+// Deliberately empty. This header is just lmi's canonical place to
+// include "config.hpp". The exception unwinder exposes nothing to any
+// other lmi code.
+
+#endif // unwind_hpp
diff --git a/workhorse.make b/workhorse.make
index 9b1b5bf..87b20da 100644
--- a/workhorse.make
+++ b/workhorse.make
@@ -916,6 +916,7 @@ REQUIRED_LDFLAGS = \
$(addprefix -L , $(all_library_directories)) \
$(EXTRA_LDFLAGS) \
$(REQUIRED_LIBS) \
+ $(EXTRA_LIBS) \
# The '--use-temp-file' windres option seems to be often helpful and
# never harmful.