When a unit test fails, we call abort(), because
  - this allows finding the failure spot a posteriori with a core dump,
  - it works fine in the debugger, even if the debugger cannot put a
    breakpoint on, say, the function exit().

However, when running a unit test in a continuous integration environment,
abort() is not ideal because it does not print the failure spot, and after
the CI run is done, the VM with the core dump is gone. Debugging with gdb
is practically impossible in such environments.

Today I have a failure:

  FAIL: test-vasnprintf-posix
  ===========================

  FAIL test-vasnprintf-posix (exit status: 134)

on a macOS 12 VM for CI. Which I cannot reproduce on the macOS 12 machine
in the GCC compilefarm. So, what to do? Adding printf statements before
each call to abort() is tedious.

I'm adding a module 'abort-debug' that overrides the abort() function so
that it prints a stack trace. For a unit test that printed

$ ./test-floor1 
../../gltests/test-floor1.c:36: assertion 'floor (0.0) == 3.0' failed
Abort trap: 6

with this module it prints

(with -O0)
$ ./test-floor1 
../../gltests/test-floor1.c:36: assertion 'floor (0.0) == 3.0' failed
Stack trace:
0   test-floor1                         0x000000010064fb54 rpl_abort + 68
1   test-floor1                         0x000000010064f190 main + 136
2   dyld                                0x00000001008bd08c start + 520
Abort trap: 6

or (with -O2)
$ ./test-floor1 
../../gltests/test-floor1.c:36: assertion 'floor (0.0) == 3.0' failed
Stack trace:
0   test-floor1                         0x0000000102a97e40 rpl_abort + 64
1   test-floor1                         0x0000000102a97e00 rpl_abort + 0
2   dyld                                0x0000000102ac508c start + 520
Abort trap: 6

Note that on glibc systems, sometimes I don't get function names in the
stack trace, despite using '-rdynamic' or '-Wl,-export-dynamic'.


2024-05-17  Bruno Haible  <br...@clisp.org>

        New module 'abort-debug'.
        * lib/stdlib.in.h (abort): New declaration.
        * lib/abort-debug.c: New file.
        * m4/abort-debug.m4: New file.
        * modules/abort-debug: New file.
        * m4/stdlib_h.m4 (gl_STDLIB_H_REQUIRE_DEFAULTS): Initialize
        GNULIB_ABORT_DEBUG.
        (gl_STDLIB_H_DEFAULTS): Initialize REPLACE_ABORT.
        * modules/stdlib (Makefile.am): Substitute GNULIB_ABORT_DEBUG,
        REPLACE_ABORT.

>From cf692362ba3ac21a9553e88b37fa9f0b4d73f7af Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 17 May 2024 22:05:46 +0200
Subject: [PATCH] New module 'abort-debug'.

* lib/stdlib.in.h (abort): New declaration.
* lib/abort-debug.c: New file.
* m4/abort-debug.m4: New file.
* modules/abort-debug: New file.
* m4/stdlib_h.m4 (gl_STDLIB_H_REQUIRE_DEFAULTS): Initialize
GNULIB_ABORT_DEBUG.
(gl_STDLIB_H_DEFAULTS): Initialize REPLACE_ABORT.
* modules/stdlib (Makefile.am): Substitute GNULIB_ABORT_DEBUG,
REPLACE_ABORT.
---
 ChangeLog           | 13 +++++++++
 lib/abort-debug.c   | 65 +++++++++++++++++++++++++++++++++++++++++++++
 lib/stdlib.in.h     | 17 ++++++++++++
 m4/abort-debug.m4   | 52 ++++++++++++++++++++++++++++++++++++
 m4/stdlib_h.m4      |  4 ++-
 modules/abort-debug | 33 +++++++++++++++++++++++
 modules/stdlib      |  2 ++
 7 files changed, 185 insertions(+), 1 deletion(-)
 create mode 100644 lib/abort-debug.c
 create mode 100644 m4/abort-debug.m4
 create mode 100644 modules/abort-debug

diff --git a/ChangeLog b/ChangeLog
index 40c8667ea4..25c95d2aba 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2024-05-17  Bruno Haible  <br...@clisp.org>
+
+	New module 'abort-debug'.
+	* lib/stdlib.in.h (abort): New declaration.
+	* lib/abort-debug.c: New file.
+	* m4/abort-debug.m4: New file.
+	* modules/abort-debug: New file.
+	* m4/stdlib_h.m4 (gl_STDLIB_H_REQUIRE_DEFAULTS): Initialize
+	GNULIB_ABORT_DEBUG.
+	(gl_STDLIB_H_DEFAULTS): Initialize REPLACE_ABORT.
+	* modules/stdlib (Makefile.am): Substitute GNULIB_ABORT_DEBUG,
+	REPLACE_ABORT.
+
 2024-05-17  Bruno Haible  <br...@clisp.org>
 
 	execinfo: Update doc.
diff --git a/lib/abort-debug.c b/lib/abort-debug.c
new file mode 100644
index 0000000000..2ca8f81169
--- /dev/null
+++ b/lib/abort-debug.c
@@ -0,0 +1,65 @@
+/* abort() function that prints a stack trace before aborting.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <stdlib.h>
+
+#include <signal.h>
+
+#if HAVE_EXECINFO_H
+
+# include <stdio.h>
+
+# include "execinfo.h"
+
+static inline void
+# if (__GNUC__ >= 3) || (__clang_major__ >= 4)
+__attribute__ ((always_inline))
+# endif
+print_stack_trace (FILE *stream)
+{
+  void *buffer[100];
+  int max_size = sizeof (buffer) / sizeof (buffer[0]);
+  int size = backtrace (buffer, max_size);
+  if (size > 0)
+    {
+      char **symbols = backtrace_symbols (buffer, size);
+      if (symbols != NULL)
+        {
+          int i;
+
+          fprintf (stream, "Stack trace:\n");
+          for (i = 0; i < size; i++)
+            fprintf (stream, "%s\n", symbols[i]);
+          fflush (stream);
+
+          free (symbols);
+        }
+    }
+}
+
+#endif
+
+void
+rpl_abort (void)
+{
+#if HAVE_EXECINFO_H
+  print_stack_trace (stderr);
+#endif
+  raise (SIGABRT);
+}
diff --git a/lib/stdlib.in.h b/lib/stdlib.in.h
index e74e7c18d1..1888d3ee31 100644
--- a/lib/stdlib.in.h
+++ b/lib/stdlib.in.h
@@ -216,6 +216,23 @@ _GL_WARN_ON_USE (_Exit, "_Exit is unportable - "
 #endif
 
 
+#if @GNULIB_ABORT_DEBUG@
+# if @REPLACE_ABORT@
+#  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+#   undef abort
+#   define abort rpl_abort
+#  endif
+_GL_FUNCDECL_RPL (abort, _Noreturn void, (void));
+_GL_CXXALIAS_RPL (abort, void, (void));
+# else
+_GL_CXXALIAS_SYS (abort, void, (void));
+# endif
+# if __GLIBC__ >= 2
+_GL_CXXALIASWARN (abort);
+# endif
+#endif
+
+
 #if @GNULIB_FREE_POSIX@
 # if @REPLACE_FREE@
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
diff --git a/m4/abort-debug.m4 b/m4/abort-debug.m4
new file mode 100644
index 0000000000..e819d58dcb
--- /dev/null
+++ b/m4/abort-debug.m4
@@ -0,0 +1,52 @@
+# abort-debug.m4
+# serial 1
+dnl Copyright (C) 2024 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_ABORT_DEBUG_EARLY],
+[
+  AC_MSG_CHECKING([whether to enable debugging facilities on abort])
+  AC_ARG_ENABLE([debug-abort],
+    [AS_HELP_STRING([[--disable-debug-abort]],
+       [turn off debugging facilities])],
+    [case "$enableval" in
+       yes | no) ;;
+       *) AC_MSG_WARN([invalid argument supplied to --enable-debug-abort])
+          enable_debug_abort=yes
+          ;;
+     esac
+    ],
+    [enable_debug_abort=yes])
+  AC_MSG_RESULT([$enable_debug_abort])
+
+  AC_REQUIRE([gl_STDLIB_H_DEFAULTS])
+  if test $enable_debug_abort = yes; then
+    AC_REQUIRE([AC_CANONICAL_HOST])
+    case "$host_os" in
+      *-gnu* | gnu* | darwin* | freebsd* | dragonfly* | netbsd* | openbsd* | solaris*)
+        dnl execinfo might be implemented on this platform.
+        REPLACE_ABORT=1
+        dnl On *BSD system, link all programs with -lexecinfo. Cf. m4/execinfo.m4.
+        case "$host_os" in
+          freebsd* | dragonfly* | netbsd* | openbsd*)
+            LDFLAGS="$LDFLAGS -lexecinfo"
+            ;;
+        esac
+        dnl Link all programs in such a way that the stack trace includes the
+        dnl function names. '-rdynamic' is equivalent to '-Wl,-export-dynamic'.
+        case "$host_os" in
+          *-gnu* | gnu* | openbsd*)
+            LDFLAGS="$LDFLAGS -rdynamic"
+            ;;
+        esac
+        ;;
+    esac
+  fi
+])
+
+AC_DEFUN([gl_ABORT_DEBUG],
+[
+  AC_REQUIRE([AC_C_INLINE])
+])
diff --git a/m4/stdlib_h.m4 b/m4/stdlib_h.m4
index a4662f2995..bb5a646041 100644
--- a/m4/stdlib_h.m4
+++ b/m4/stdlib_h.m4
@@ -1,5 +1,5 @@
 # stdlib_h.m4
-# serial 77
+# serial 78
 dnl Copyright (C) 2007-2024 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -110,6 +110,7 @@ AC_DEFUN([gl_STDLIB_H_REQUIRE_DEFAULTS]
 [
   m4_defun(GL_MODULE_INDICATOR_PREFIX[_STDLIB_H_MODULE_INDICATOR_DEFAULTS], [
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB__EXIT])
+    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_ABORT_DEBUG])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_ALIGNED_ALLOC])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_ATOLL])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_CALLOC_GNU])
@@ -218,6 +219,7 @@ AC_DEFUN([gl_STDLIB_H_DEFAULTS]
   HAVE_UNLOCKPT=1;           AC_SUBST([HAVE_UNLOCKPT])
   HAVE_DECL_UNSETENV=1;      AC_SUBST([HAVE_DECL_UNSETENV])
   REPLACE__EXIT=0;           AC_SUBST([REPLACE__EXIT])
+  REPLACE_ABORT=0;           AC_SUBST([REPLACE_ABORT])
   REPLACE_ALIGNED_ALLOC=0;   AC_SUBST([REPLACE_ALIGNED_ALLOC])
   REPLACE_CALLOC_FOR_CALLOC_GNU=0;    AC_SUBST([REPLACE_CALLOC_FOR_CALLOC_GNU])
   REPLACE_CALLOC_FOR_CALLOC_POSIX=0;  AC_SUBST([REPLACE_CALLOC_FOR_CALLOC_POSIX])
diff --git a/modules/abort-debug b/modules/abort-debug
new file mode 100644
index 0000000000..56cbdac5be
--- /dev/null
+++ b/modules/abort-debug
@@ -0,0 +1,33 @@
+Description:
+abort() function that prints a stack trace before aborting.
+
+Files:
+lib/abort-debug.c
+m4/abort-debug.m4
+
+Depends-on:
+stdlib
+execinfo
+
+configure.ac-early:
+AC_REQUIRE([gl_ABORT_DEBUG_EARLY])
+export LDFLAGS
+
+configure.ac:
+gl_ABORT_DEBUG
+gl_CONDITIONAL([GL_COND_OBJ_ABORT_DEBUG], [test $REPLACE_ABORT = 1])
+gl_STDLIB_MODULE_INDICATOR([abort-debug])
+
+Makefile.am:
+if GL_COND_OBJ_ABORT_DEBUG
+lib_SOURCES += abort-debug.c
+endif
+
+Include:
+<stdlib.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/modules/stdlib b/modules/stdlib
index 7b0194db41..4e2b0ff32a 100644
--- a/modules/stdlib
+++ b/modules/stdlib
@@ -37,6 +37,7 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status $(CXXDEFS_H) \
 	      -e 's|@''PRAGMA_COLUMNS''@|@PRAGMA_COLUMNS@|g' \
 	      -e 's|@''NEXT_STDLIB_H''@|$(NEXT_STDLIB_H)|g' \
 	      -e 's/@''GNULIB__EXIT''@/$(GNULIB__EXIT)/g' \
+	      -e 's/@''GNULIB_ABORT_DEBUG''@/$(GNULIB_ABORT_DEBUG)/g' \
 	      -e 's/@''GNULIB_ALIGNED_ALLOC''@/$(GNULIB_ALIGNED_ALLOC)/g' \
 	      -e 's/@''GNULIB_ATOLL''@/$(GNULIB_ATOLL)/g' \
 	      -e 's/@''GNULIB_CALLOC_GNU''@/$(GNULIB_CALLOC_GNU)/g' \
@@ -139,6 +140,7 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status $(CXXDEFS_H) \
 	      < $@-t1 > $@-t2
 	$(AM_V_at)sed \
 	      -e 's|@''REPLACE__EXIT''@|$(REPLACE__EXIT)|g' \
+	      -e 's|@''REPLACE_ABORT''@|$(REPLACE_ABORT)|g' \
 	      -e 's|@''REPLACE_ALIGNED_ALLOC''@|$(REPLACE_ALIGNED_ALLOC)|g' \
 	      -e 's|@''REPLACE_CALLOC_FOR_CALLOC_GNU''@|$(REPLACE_CALLOC_FOR_CALLOC_GNU)|g' \
 	      -e 's|@''REPLACE_CALLOC_FOR_CALLOC_POSIX''@|$(REPLACE_CALLOC_FOR_CALLOC_POSIX)|g' \
-- 
2.34.1

Reply via email to