lmi-commits
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]