OK great.

Here is the patch for --enable-mutex-debug.

make check will have to come a bit later.  I have many scripts that do
more or less that, but they are mostly Python, and focused on the
journal's failure modes; I will need to consolidate them and convert
them to shell.

As for this patch: a system built from current master with
--enable-mutex-debug does not boot.  The ext2fs bootstrap filesystem
dies on the first lookup through a translator, at the double unlock in
diskfs_S_dir_lookup.  Patch 1 of the v2 series ("libdiskfs: Unlock the
directory once a lookup has found its node") fixes it.  With that
series applied, it boots, and a large apt upgrade ran through with
every mutex checked.

Thanks again,
Milos


On Mon, Nov 2, 2026 at 12:23 AM Samuel Thibault <[email protected]>
wrote:

> Milos Nikic, le dim. 01 nov. 2026 22:29:45 -0800, a ecrit:
> > To be sure we mean the same thing: --enable-mutex-debug only changes
> > the compiler flags, so it works for a native build on the Hurd as well
> > as for a cross build.
>
> Yes
>
> >  make check always runs inside a Hurd system,
> > since it mounts the image with settrans; in a cross build it would only
> > say so and skip.
>
> Yes.
>
> >   The two are independent, but used together, a lock
> > bug that the operations reach fails the check.
>
> The first one can still be used on a live system (could be a sub-hurd)
> to easily run packages' testsuites etc.
>
> > Where would you like the new header to live?  Maybe
> > libshouldbeinlibc/, so that it can apply to the whole tree rather than
> > only libdiskfs and the filesystems: libports, libpager and the other
> > translators may have the same kind of bugs.
>
> Yes.
>
> Samuel
>
From da36c151f95148092ab70b1e1f374f3ac3159e71 Mon Sep 17 00:00:00 2001
From: Milos Nikic <[email protected]>
Date: Mon, 2 Nov 2026 08:50:50 -0800
Subject: [PATCH] Add --enable-mutex-debug, for error-checking mutexes

A default mutex records no owner, so an unlock by a thread that does
not hold it, or of one nobody holds, passes silently and takes the lock
from its holder: such a bug only shows when another thread races for
that lock, far from the faulty unlock.

With --enable-mutex-debug, every C file is compiled with
libshouldbeinlibc/mutex-debug.h included first.  It makes the mutexes
that are initialized with no attributes, or with
PTHREAD_MUTEX_INITIALIZER, error-checking ones, and stops the program
with a backtrace when a lock or unlock fails.  It only changes the
compiler flags, so it works for a native build and for a cross build,
and a system or a sub-Hurd built with it can run packages' test suites
with every unbalanced lock caught where it happens.
---
 Makeconf                        |  6 +++
 config.make.in                  |  1 +
 configure.ac                    |  6 +++
 libshouldbeinlibc/mutex-debug.h | 94 +++++++++++++++++++++++++++++++++
 4 files changed, 107 insertions(+)
 create mode 100644 libshouldbeinlibc/mutex-debug.h

diff --git a/Makeconf b/Makeconf
index 288e7daae..4d18ebade 100644
--- a/Makeconf
+++ b/Makeconf
@@ -90,6 +90,12 @@ include $(..)config.make
 configured = yes
 endif
 
+# --enable-mutex-debug, once config.make has set mutex-debug.  In CFLAGS,
+# not CPPFLAGS, which also preprocesses the MIG definitions.
+ifeq ($(mutex-debug),yes)
+CFLAGS += -include $(top_srcdir)/libshouldbeinlibc/mutex-debug.h
+endif
+
 # If we're not configured, don't do deps; then `make TAGS' and such can work.
 ifndef configured
 no_deps = t
diff --git a/config.make.in b/config.make.in
index 1a30626b8..b4521dd58 100644
--- a/config.make.in
+++ b/config.make.in
@@ -11,6 +11,7 @@ asm_syntax = @asm_syntax@
 
 # Build options.
 build-profiled = @enable_profile@
+mutex-debug = @enable_mutex_debug@
 build-static = @enable_static_progs@
 boot-store-types = @boot_store_types@
 
diff --git a/configure.ac b/configure.ac
index 4ddb1fe9c..1e63f84f5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -58,6 +58,12 @@ AC_ARG_ENABLE(profile,
 [  --disable-profile	  do not build profiled libraries and programs])
 AC_SUBST(enable_profile)
 
+AC_ARG_ENABLE(mutex-debug,
+[  --enable-mutex-debug	  make mutexes error-checking, and stop a program
+			  with a backtrace when a lock or unlock fails],
+[], [enable_mutex_debug=no])
+AC_SUBST(enable_mutex_debug)
+
 define([default_static],['ext2fs,iso9660fs,rumpdisk,pci-arbiter,acpi'])dnl
 AC_ARG_ENABLE(static-progs,
 [  --enable-static-progs=PROGRAMS...
diff --git a/libshouldbeinlibc/mutex-debug.h b/libshouldbeinlibc/mutex-debug.h
new file mode 100644
index 000000000..d5bbe59df
--- /dev/null
+++ b/libshouldbeinlibc/mutex-debug.h
@@ -0,0 +1,94 @@
+/* Error-checking mutexes, for --enable-mutex-debug.
+
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   Written by Milos Nikic.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2, or (at
+   your option) any later version.
+
+   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., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* A default mutex records no owner: its unlock clears the lock whoever
+   calls it, so an unlock by a thread that does not hold the mutex, or of
+   one nobody holds, passes silently, and takes the lock from a thread
+   that holds it.  An error-checking mutex records its owner: its unlock
+   by another thread fails with EPERM, and its relock by the owner with
+   EDEADLK.
+
+   With --enable-mutex-debug, every C file of the tree is compiled with
+   this header included first (Makeconf).  Every mutex initialized with
+   no attributes, or with PTHREAD_MUTEX_INITIALIZER, is then an
+   error-checking one, and a lock or unlock that fails stops the program
+   with a backtrace.  */
+
+#ifndef _HURD_MUTEX_DEBUG_H
+#define _HURD_MUTEX_DEBUG_H
+
+/* Before the macros below, so that its declarations are not expanded.  */
+#include <pthread.h>
+#include <execinfo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Print the failure and a backtrace through stderr, and abort.  Not
+   through descriptor 2, as backtrace_symbols_fd and assert_backtrace
+   do: a bootstrap filesystem's stderr goes to the Mach console.  The
+   addresses resolve with addr2line against the unstripped program.  */
+static void __attribute__ ((noreturn, unused))
+mutex_debug_fail (const char *what, int err, const char *file, int line)
+{
+  void *bt[64];
+  int n = backtrace (bt, 64);
+  char **names = backtrace_symbols (bt, n);
+
+  fprintf (stderr, "mutex-debug: %s:%d: pthread_mutex_%s: %s\n",
+	   file, line, what, strerror (err));
+  for (int i = 1; i < n; i++)
+    fprintf (stderr, "  #%d %p %s\n", i, bt[i], names ? names[i] : "");
+  fflush (stderr);
+  abort ();
+}
+
+static inline int
+mutex_debug_init (pthread_mutex_t *m, const pthread_mutexattr_t *attr)
+{
+  pthread_mutexattr_t a;
+  int err;
+
+  if (attr)
+    return (pthread_mutex_init) (m, attr);
+  pthread_mutexattr_init (&a);
+  pthread_mutexattr_settype (&a, PTHREAD_MUTEX_ERRORCHECK);
+  err = (pthread_mutex_init) (m, &a);
+  pthread_mutexattr_destroy (&a);
+  return err;
+}
+
+#define pthread_mutex_init(m, attr) mutex_debug_init ((m), (attr))
+
+#undef PTHREAD_MUTEX_INITIALIZER
+#define PTHREAD_MUTEX_INITIALIZER __PTHREAD_ERRORCHECK_MUTEX_INITIALIZER
+
+#define pthread_mutex_lock(m)						\
+  ({ int __mutex_debug_err = (pthread_mutex_lock) (m);			\
+     if (__mutex_debug_err)						\
+       mutex_debug_fail ("lock", __mutex_debug_err, __FILE__, __LINE__);	\
+     __mutex_debug_err; })
+
+#define pthread_mutex_unlock(m)						\
+  ({ int __mutex_debug_err = (pthread_mutex_unlock) (m);		\
+     if (__mutex_debug_err)						\
+       mutex_debug_fail ("unlock", __mutex_debug_err, __FILE__, __LINE__); \
+     __mutex_debug_err; })
+
+#endif /* _HURD_MUTEX_DEBUG_H */
-- 
2.56.0

Reply via email to