Hi Luca,

I did some testing of your first 2 patches of this series and found a couple
of test errors on various platforms:


CentOS 7

gcc -std=gnu99 -std=gnu11 -DHAVE_CONFIG_H -I. -I../../gltests -I..  
-DGNULIB_STRICT_CHECKING=1 -DIN_GNULIB_TESTS=1 -I. -I../../gltests -I.. 
-I../../gltests/.. -I../gllib -I../../gltests/../gllib -Wall 
-DCONTINUE_AFTER_ASSERT -Wno-error -Wno-error -g -O2 -MT test-sd-dlopen.o -MD 
-MP -MF .deps/test-sd-dlopen.Tpo -c -o test-sd-dlopen.o 
../../gltests/test-sd-dlopen.c
/tmp/ccsYRr90.s: Assembler messages:
/tmp/ccsYRr90.s:7: Fatal error: bad .section directive: want a,l,w,x,M,S,G,T in 
string
make[4]: *** [test-sd-dlopen.o] Error 1


AlmaLinux 9

gcc -std=gnu99 -std=gnu11 -DHAVE_CONFIG_H -I. -I../../gltests -I..  
-DGNULIB_STRICT_CHECKING=1 -DIN_GNULIB_TESTS=1 -I. -I../../gltests -I.. 
-I../../gltests/.. -I../gllib -I../../gltests/../gllib -Wall 
-DCONTINUE_AFTER_ASSERT -Wno-error -Wno-error -g -O2 -MT test-sd-dlopen.o -MD 
-MP -MF .deps/test-sd-dlopen.Tpo -c -o test-sd-dlopen.o 
../../gltests/test-sd-dlopen.c
/tmp/ccTZtxDv.s: Assembler messages:
/tmp/ccTZtxDv.s:8: Fatal error: bad .section directive: want a,l,w,x,M,S,G,T in 
string
make[4]: *** [Makefile:1494: test-sd-dlopen.o] Error 1


NetBSD 10.0

gcc -DHAVE_CONFIG_H -I. -I../../gltests -I..  -DGNULIB_STRICT_CHECKING=1  
-DIN_GNULIB_TESTS=1  -I. -I../../gltests  -I.. -I../../gltests/..  -I../gllib 
-I../../gltests/../gllib  -I/usr/pkg/include -Wall -DCONTINUE_AFTER_ASSERT 
-Wno-error -Wno-error -g -O2 -MT test-sd-dlopen.o -MD -MP -MF 
.deps/test-sd-dlopen.Tpo -c -o test-sd-dlopen.o ../../gltests/test-sd-dlopen.c
/tmp//ccObcS4r.s: Assembler messages:
/tmp//ccObcS4r.s:7: Fatal error: bad .section directive: want a,l,w,x,M,S,G,T 
in string
*** Error code 1


Solaris 11.4

gcc -m64 -Wno-error -Wno-error -g -O2  -L/root/lib -o test-sd-dlopen 
test-sd-dlopen.o libtests.a ../gllib/libgnu.a libtests.a ../gllib/libgnu.a 
libtests.a  
ld: fatal: file test-sd-dlopen.o: wrong ELF OSABI: ELFOSABI_GNU
collect2: error: ld returned 1 exit status
*** Error code 1


Therefore, once the copyright assignment paperwork is complete, here is an
augmented set of patches that I plan to commit: the two commits from you
(with GNU-style ChangeLog and a correction in m4/libdl.m4) and 4 commits
(1 for clarity, 3 to avoid the errors above).

>From ba276ea0689777fa1496e4ef20ba56e1fb7a62a6 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <[email protected]>
Date: Sun, 19 Jul 2026 18:29:18 +0100
Subject: [PATCH 2/6] sd-dlopen: Add tests.

* tests/test-sd-dlopen.c: New file.
* tests/test-sd-dlopen-disabled.c: New file.
* tests/test-sd-dlopen.sh: New file.
* modules/sd-dlopen-tests: New file.
---
 ChangeLog                       |  6 +++++
 modules/sd-dlopen-tests         | 19 +++++++++++++++
 tests/test-sd-dlopen-disabled.c | 41 +++++++++++++++++++++++++++++++++
 tests/test-sd-dlopen.c          | 36 +++++++++++++++++++++++++++++
 tests/test-sd-dlopen.sh         | 21 +++++++++++++++++
 5 files changed, 123 insertions(+)
 create mode 100644 modules/sd-dlopen-tests
 create mode 100644 tests/test-sd-dlopen-disabled.c
 create mode 100644 tests/test-sd-dlopen.c
 create mode 100755 tests/test-sd-dlopen.sh

diff --git a/ChangeLog b/ChangeLog
index 31c871b53c..115446119d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2026-07-21  Luca Boccassi  <[email protected]>
 
+	sd-dlopen: Add tests.
+	* tests/test-sd-dlopen.c: New file.
+	* tests/test-sd-dlopen-disabled.c: New file.
+	* tests/test-sd-dlopen.sh: New file.
+	* modules/sd-dlopen-tests: New file.
+
 	sd-dlopen: New module.
 	* lib/sd-dlopen.h: New file, based on systemd/src/systemd/sd-dlopen.h.
 	* m4/libdl.m4 (gl_LIBDL): Also set HAVE_DLOPEN and define HAVE_DLOPEN.
diff --git a/modules/sd-dlopen-tests b/modules/sd-dlopen-tests
new file mode 100644
index 0000000000..fdbd652755
--- /dev/null
+++ b/modules/sd-dlopen-tests
@@ -0,0 +1,19 @@
+Files:
+tests/test-sd-dlopen.c
+tests/test-sd-dlopen-disabled.c
+tests/test-sd-dlopen.sh
+
+Depends-on:
+test-framework-sh
+
+configure.ac:
+AC_CHECK_TOOL([OBJDUMP], [objdump], [false])
+SD_DLOPEN_SUPPORTED=$gl_cv_asm_ifndef_endif_balign
+AC_SUBST([SD_DLOPEN_SUPPORTED])
+
+Makefile.am:
+TESTS_ENVIRONMENT += \
+	OBJDUMP='$(OBJDUMP)' \
+	SD_DLOPEN_SUPPORTED='$(SD_DLOPEN_SUPPORTED)'
+TESTS += test-sd-dlopen test-sd-dlopen-disabled test-sd-dlopen.sh
+check_PROGRAMS += test-sd-dlopen test-sd-dlopen-disabled
diff --git a/tests/test-sd-dlopen-disabled.c b/tests/test-sd-dlopen-disabled.c
new file mode 100644
index 0000000000..a23d3cd6d6
--- /dev/null
+++ b/tests/test-sd-dlopen-disabled.c
@@ -0,0 +1,41 @@
+/* Test the disabled sd-dlopen macros.
+   Copyright 2026 Free Software Foundation, Inc.
+
+   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 3 of the License, 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, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#undef HAVE_ASM_IFNDEF_ENDIF_BALIGN
+
+#include "sd-dlopen.h"
+
+#ifdef _SD_ELF_NOTE_DLOPEN
+# error "sd-dlopen implementation enabled without assembler support"
+#endif
+
+SD_ELF_NOTE_DLOPEN ("test",
+                    "Test optional dependency",
+                    SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+                    "libtest.so.1");
+
+int
+main (void)
+{
+  SD_ELF_NOTE_DLOPEN_ANCHORED (test_required,
+                               "test",
+                               "Test required dependency",
+                               SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED,
+                               "libtest.so.1");
+  return 0;
+}
diff --git a/tests/test-sd-dlopen.c b/tests/test-sd-dlopen.c
new file mode 100644
index 0000000000..1faaa36c2c
--- /dev/null
+++ b/tests/test-sd-dlopen.c
@@ -0,0 +1,36 @@
+/* Test of sd-dlopen module.
+   Copyright 2026 Free Software Foundation, Inc.
+
+   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 3 of the License, 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, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include "sd-dlopen.h"
+
+SD_ELF_NOTE_DLOPEN ("test",
+                    "Test optional dependency",
+                    SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+                    "libtest.so.1");
+
+int
+main (void)
+{
+  SD_ELF_NOTE_DLOPEN_ANCHORED (test_required,
+                               "test",
+                               "Test required dependency",
+                               SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED,
+                               "libtest.so.1");
+
+  return SD_ELF_NOTE_DLOPEN_TYPE != 0x407c0c0a;
+}
diff --git a/tests/test-sd-dlopen.sh b/tests/test-sd-dlopen.sh
new file mode 100755
index 0000000000..36dd3baae2
--- /dev/null
+++ b/tests/test-sd-dlopen.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"
+
+test "$SD_DLOPEN_SUPPORTED" = yes \
+  || skip_ ".note.dlopen is not supported"
+test "$OBJDUMP" != false \
+  || skip_ "objdump is not available"
+
+program="$initial_cwd_/test-sd-dlopen${EXEEXT}"
+LC_ALL=C "$OBJDUMP" -h "$program" > sections \
+  || skip_ "objdump cannot read the test executable"
+grep '[.]note[.]dlopen' sections > /dev/null \
+  || fail_ ".note.dlopen section is missing"
+
+LC_ALL=C "$OBJDUMP" -s -j .note.dlopen "$program" > contents \
+  || fail_ "cannot dump the .note.dlopen section"
+vendor_count=$(grep -c '46444f00' contents)
+test "$vendor_count" = 2 \
+  || fail_ "expected two FDO dlopen notes, found $vendor_count"
+
+Exit 0
-- 
2.53.0

>From 5183e0053df3ffd84b94eb501d117b209fc08cd6 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Tue, 21 Jul 2026 22:29:23 +0200
Subject: [PATCH 4/6] sd-dlopen: Fix test compilation error on AlmaLinux 9 and
 CentOS 7.

* m4/sd-dlopen.m4 (gl_SD_DLOPEN): Test whether the assembler supports
the section flag R. Define _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS.
---
 ChangeLog       |  4 ++++
 m4/sd-dlopen.m4 | 20 +++++++++++++++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index a85e110501..b04ff54e95 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2026-07-21  Bruno Haible  <[email protected]>
 
+	sd-dlopen: Fix test compilation error on AlmaLinux 9 and CentOS 7.
+	* m4/sd-dlopen.m4 (gl_SD_DLOPEN): Test whether the assembler supports
+	the section flag R. Define _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS.
+
 	sd-dlopen: Reference the specification.
 	* modules/sd-dlopen (Description): Add reference to the specification.
 	* lib/sd-dlopen.h: Improve comments.
diff --git a/m4/sd-dlopen.m4 b/m4/sd-dlopen.m4
index 1511548dfb..3f0e2cea13 100644
--- a/m4/sd-dlopen.m4
+++ b/m4/sd-dlopen.m4
@@ -1,5 +1,5 @@
 # sd-dlopen.m4
-# serial 1
+# serial 2
 dnl Copyright (C) 2026 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -26,5 +26,23 @@ AC_DEFUN([gl_SD_DLOPEN]
   if test $gl_cv_asm_ifndef_endif_balign = yes; then
     AC_DEFINE([HAVE_ASM_IFNDEF_ENDIF_BALIGN], [1],
       [Define to 1 if the assembler supports .ifndef, .endif, and .balign.])
+
+    AC_CACHE_CHECK([whether the assembler supports the section flag R],
+      [gl_cv_asm_section_R],
+      [dnl This flag is only supported by binutils >= 2.36 or clang >= 13.
+       AC_COMPILE_IFELSE(
+         [AC_LANG_SOURCE([[
+          __asm__ (".section .foo,\"aR\"");
+         ]])],
+         [gl_cv_asm_section_R=yes],
+         [gl_cv_asm_section_R=no])])
+    if test $gl_cv_asm_section_R = yes; then
+      gl_sd_dlopen_section_flags='"aGR"'
+    else
+      gl_sd_dlopen_section_flags='"aG"'
+    fi
+    AC_DEFINE_UNQUOTED([_SD_ELF_NOTE_DLOPEN_SECTION_FLAGS],
+      [$gl_sd_dlopen_section_flags],
+      [Define to the section flags for sd-dlopen.h.])
   fi
 ])
-- 
2.53.0

>From 593badd99399c4f0940598b537fb5711bf12efbf Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Tue, 21 Jul 2026 23:51:05 +0200
Subject: [PATCH 5/6] sd-dlopen: Fix test compilation error on NetBSD 10.

* m4/sd-dlopen.m4 (gl_SD_DLOPEN): Test whether the assembler supports
the section flag o. Define _SD_ELF_NOTE_SUPPORTS_REFERENCES.
* lib/sd-dlopen.h (SD_ELF_NOTE_DLOPEN_ANCHORED) [!__clang__]: Use
_SD_ELF_NOTE_SUPPORTS_REFERENCES from the autoconfiguration.
---
 ChangeLog       |  6 ++++++
 lib/sd-dlopen.h |  6 +++---
 m4/sd-dlopen.m4 | 16 +++++++++++++++-
 3 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b04ff54e95..d23b321025 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2026-07-21  Bruno Haible  <[email protected]>
 
+	sd-dlopen: Fix test compilation error on NetBSD 10.
+	* m4/sd-dlopen.m4 (gl_SD_DLOPEN): Test whether the assembler supports
+	the section flag o. Define _SD_ELF_NOTE_SUPPORTS_REFERENCES.
+	* lib/sd-dlopen.h (SD_ELF_NOTE_DLOPEN_ANCHORED) [!__clang__]: Use
+	_SD_ELF_NOTE_SUPPORTS_REFERENCES from the autoconfiguration.
+
 	sd-dlopen: Fix test compilation error on AlmaLinux 9 and CentOS 7.
 	* m4/sd-dlopen.m4 (gl_SD_DLOPEN): Test whether the assembler supports
 	the section flag R. Define _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS.
diff --git a/lib/sd-dlopen.h b/lib/sd-dlopen.h
index 87760ada7a..b3e36ccd77 100644
--- a/lib/sd-dlopen.h
+++ b/lib/sd-dlopen.h
@@ -182,12 +182,12 @@ extern "C" {
         _SD_ELF_NOTE_DLOPEN(_SD_DLOPEN_JSON(feature, description, priority, __VA_ARGS__))
 
 /* The anchored note requires LLVM >= 18 (see above). Fall back to the non-anchored note on older clang. */
-#if defined(__clang__) && !defined(__apple_build_version__) && __clang_major__ < 18
+#if (defined(__clang__) ? defined(__apple_build_version__) || __clang_major__ >= 18 : _SD_ELF_NOTE_SUPPORTS_REFERENCES)
 #  define SD_ELF_NOTE_DLOPEN_ANCHORED(tag, feature, description, priority, ...) \
-        SD_ELF_NOTE_DLOPEN(feature, description, priority, __VA_ARGS__)
+        _SD_ELF_NOTE_DLOPEN_ANCHORED(tag, _SD_DLOPEN_JSON(feature, description, priority, __VA_ARGS__))
 #else
 #  define SD_ELF_NOTE_DLOPEN_ANCHORED(tag, feature, description, priority, ...) \
-        _SD_ELF_NOTE_DLOPEN_ANCHORED(tag, _SD_DLOPEN_JSON(feature, description, priority, __VA_ARGS__))
+        SD_ELF_NOTE_DLOPEN(feature, description, priority, __VA_ARGS__)
 #endif
 
 #else
diff --git a/m4/sd-dlopen.m4 b/m4/sd-dlopen.m4
index 3f0e2cea13..d9214b08bb 100644
--- a/m4/sd-dlopen.m4
+++ b/m4/sd-dlopen.m4
@@ -1,5 +1,5 @@
 # sd-dlopen.m4
-# serial 2
+# serial 3
 dnl Copyright (C) 2026 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -27,6 +27,20 @@ AC_DEFUN([gl_SD_DLOPEN]
     AC_DEFINE([HAVE_ASM_IFNDEF_ENDIF_BALIGN], [1],
       [Define to 1 if the assembler supports .ifndef, .endif, and .balign.])
 
+    AC_CACHE_CHECK([whether the assembler supports the section flag o],
+      [gl_cv_asm_section_o],
+      [dnl This flag is only supported by binutils >= 2.35 or clang >= 5.
+       AC_COMPILE_IFELSE(
+         [AC_LANG_SOURCE([[
+          __asm__ (".section .def; foobar:; .section .foo,\"ao\",%note,foobar");
+         ]])],
+         [gl_cv_asm_section_o=yes],
+         [gl_cv_asm_section_o=no])])
+    if test $gl_cv_asm_section_o = yes; then
+      AC_DEFINE([_SD_ELF_NOTE_SUPPORTS_REFERENCES], [1],
+        [Define to 1 if the ELF .section command supports the o flag.])
+    fi
+
     AC_CACHE_CHECK([whether the assembler supports the section flag R],
       [gl_cv_asm_section_R],
       [dnl This flag is only supported by binutils >= 2.36 or clang >= 13.
-- 
2.53.0

From a876f16e2fa532d6a801e8f8c4c2d22745065a99 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <[email protected]>
Date: Sun, 19 Jul 2026 18:29:17 +0100
Subject: [PATCH 1/6] sd-dlopen: New module.

* lib/sd-dlopen.h: New file, based on systemd/src/systemd/sd-dlopen.h.
* m4/libdl.m4 (gl_LIBDL): Also set HAVE_DLOPEN and define HAVE_DLOPEN.
* m4/sd-dlopen.m4: New file.
* modules/sd-dlopen: New file.
---
 ChangeLog         |   8 ++
 lib/sd-dlopen.h   | 203 ++++++++++++++++++++++++++++++++++++++++++++++
 m4/libdl.m4       |  21 +++--
 m4/sd-dlopen.m4   |  30 +++++++
 modules/sd-dlopen |  27 ++++++
 5 files changed, 283 insertions(+), 6 deletions(-)
 create mode 100644 lib/sd-dlopen.h
 create mode 100644 m4/sd-dlopen.m4
 create mode 100644 modules/sd-dlopen

diff --git a/ChangeLog b/ChangeLog
index bd8952adf7..31c871b53c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2026-07-21  Luca Boccassi  <[email protected]>
+
+	sd-dlopen: New module.
+	* lib/sd-dlopen.h: New file, based on systemd/src/systemd/sd-dlopen.h.
+	* m4/libdl.m4 (gl_LIBDL): Also set HAVE_DLOPEN and define HAVE_DLOPEN.
+	* m4/sd-dlopen.m4: New file.
+	* modules/sd-dlopen: New file.
+
 2026-07-21  Bruno Haible  <[email protected]>
 
 	mbiter-aux: Avoid gcc -Wcomment warning.
diff --git a/lib/sd-dlopen.h b/lib/sd-dlopen.h
new file mode 100644
index 0000000000..3f3b275e10
--- /dev/null
+++ b/lib/sd-dlopen.h
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: MIT-0 */
+#ifndef foosddlopenhfoo
+#define foosddlopenhfoo
+
+/***
+  Copyright © 2026 The systemd Project
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+***/
+
+/* Note: do not include other internal headers from here, to keep it MIT-0 and self-contained */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ELF note macros implementing the FDO .note.dlopen standard.
+ *
+ * These macros embed metadata in an ELF binary's .note.dlopen section,
+ * declaring optional shared library dependencies that are loaded via
+ * dlopen() at runtime. Package managers and build systems can read
+ * these notes to discover runtime dependencies not visible in ELF
+ * DT_NEEDED entries.
+ *
+ * Usage:
+ *
+ *   SD_ELF_NOTE_DLOPEN("myfeature", "Feature description",
+ *                       SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED,
+ *                       "libfoo.so.1");
+ *
+ * See SD_ELF_NOTE_DLOPEN(3) for details.
+ */
+
+#define SD_ELF_NOTE_DLOPEN_VENDOR "FDO"
+#define SD_ELF_NOTE_DLOPEN_TYPE 0x407c0c0a
+#define SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED    "required"
+#define SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended"
+#define SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED   "suggested"
+
+#if defined __ELF__ && (defined __GNUC__ || defined __clang__) \
+        && defined HAVE_ASM_IFNDEF_ENDIF_BALIGN && HAVE_ASM_IFNDEF_ENDIF_BALIGN
+
+/* The "R" (SHF_GNU_RETAIN) flag is only understood by binutils >= 2.36, so it can be disabled by defining
+ * _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS as 'aG' manually, but note that if --gc-sections is used when linking,
+ * the note will be dropped. */
+#ifndef _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS
+#  define _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS "aGR"
+#endif
+
+/* This guard tag ensures the assembler folds identical notes when compiling. Unfortunately hppa redefines
+ * '.equ' so it cannot be used. '.equiv' works on all arches, but it breaks with binutils 2.35 which is used
+ * on CentOS 9, so we need an ifdef here until the binutils baseline is updated, as above. */
+#if defined(__hppa__) || defined(__hppa64__)
+#  define _SD_ELF_NOTE_DLOPEN_GUARD ".set"
+#else
+#  define _SD_ELF_NOTE_DLOPEN_GUARD ".equ"
+#endif
+
+/* The json argument is a C string containing already backslash-escaped double quotes (i.e. the bytes
+ * [{\"feature\"...), so that once it is pasted into the assembler's .asciz directive the assembler decodes
+ * them back to plain quotes. The note is emitted into a COMDAT group keyed on the payload itself, which
+ * makes byte-identical notes fold to a single copy: the assembler folds duplicates within a translation
+ * unit (via the .ifndef guard) and the linker folds them across translation units (via the COMDAT group).
+ * This way a binary that triggers the same optional dependency from multiple call sites ends up with just
+ * one note for it. The n_type value below must match SD_ELF_NOTE_DLOPEN_TYPE (it is spelled out as a
+ * literal here as it cannot be expanded inside the assembler string). */
+#define _SD_ELF_NOTE_DLOPEN(json)                                                                                               \
+        __asm__ (                                                                                                               \
+                ".ifndef \"sd_dlopen:" json "\"\n"                                                                              \
+                _SD_ELF_NOTE_DLOPEN_GUARD " \"sd_dlopen:" json "\", 1\n" /* guard tag, to fold identical notes */               \
+                ".pushsection .note.dlopen, \"" _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS "\", %note, \"sd_dlopen:" json "\", comdat\n" \
+                ".balign 4\n"         /* notes require 4-byte alignment for the header and fields */                            \
+                ".long 884f - 883f\n" /* n_namesz: byte length of the vendor name (label 883->884) */                           \
+                ".long 882f - 881f\n" /* n_descsz: byte length of the JSON payload (label 881->882) */                          \
+                ".long 0x407c0c0a\n"  /* n_type: must match SD_ELF_NOTE_DLOPEN_TYPE */                                          \
+                "883:\n"              /* start of vendor name */                                                                \
+                ".asciz \"" SD_ELF_NOTE_DLOPEN_VENDOR "\"\n"                                                                    \
+                "884:\n"              /* end of vendor name */                                                                  \
+                ".balign 4\n"                                                                                                   \
+                "881:\n"              /* start of JSON payload */                                                               \
+                ".asciz \"" json "\"\n"                                                                                         \
+                "882:\n"              /* end of JSON payload */                                                                 \
+                ".balign 4\n"                                                                                                   \
+                ".popsection\n"                                                                                                 \
+                ".endif\n")
+
+/*
+ * SD_ELF_NOTE_DLOPEN_ANCHORED() emits a .note.dlopen ELF note that is "anchored" to a dummy symbol via the
+ * SHF_LINK_ORDER ('o') section flag, in addition to SHF_GROUP ('G') for folding identical notes together.
+ *
+ * Unlike the plain SD_ELF_NOTE_DLOPEN() macro, this variant ties the note's lifetime to a dummy symbol named
+ * with the specified tag: if the linker's --gc-sections removes the function that calls the macro (e.g.
+ * because the function is never referenced), the associated .note.dlopen entry is garbage-collected along
+ * with it.
+ *
+ * USAGE NOTE:
+ * - The 'tag' argument must be a unique symbol name to characterize the dlopen note (e.g. generated from
+ *   feature and its priority).
+ *
+ *   Example:
+ *       void dlopen_libfoo(int log_level) {
+ *               SD_ELF_NOTE_DLOPEN_ANCHORED(foo_suggested, "foo", "description of foo", "suggested", "libfoo.so.0");
+ *               ...
+ *       }
+ *
+ * TOOLCHAIN REQUIREMENTS:
+ * - GNU as (binutils) >= 2.35: this is when support for specifying the SHF_LINK_ORDER ('o') associated
+ *   symbol as a direct argument to .section/.pushsection was added.
+ * - LLVM (clang -integrated-as) >= 18: matching support for the 'o' flag argument combined with 'G'
+ *   (SHF_GROUP) landed around LLVM 18.
+ * - Do NOT add 'R' (SHF_GNU_RETAIN) to these flags: combined with SHF_LINK_ORDER, it would prevent the
+ *   linker from ever garbage-collecting the note, defeating the whole purpose of anchoring it to a symbol in
+ *   the first place.
+ */
+#define _SD_ELF_NOTE_DLOPEN_ANCHORED(tag, json)                                                                                 \
+        _Pragma("GCC diagnostic push")                                                                                          \
+        _Pragma("GCC diagnostic ignored \"-Wnested-externs\"")                                                                  \
+        _Pragma("GCC diagnostic ignored \"-Wredundant-decls\"")                                                                 \
+        __attribute__((visibility("hidden")))                                                                                   \
+        extern volatile const char __sd_dlopen_anchor_##tag __asm__("__sd_dlopen_anchor_" #tag);                                \
+        _Pragma("GCC diagnostic pop")                                                                                           \
+        __asm__ (                                                                                                               \
+                ".ifndef \"sd_dlopen:emitted:" #tag "\"\n"                                                                      \
+                _SD_ELF_NOTE_DLOPEN_GUARD " \"sd_dlopen:emitted:" #tag "\", 1\n"                                                \
+                ".pushsection .data.sd_dlopen_dummy." #tag ", \"awG\", %progbits, sd_dlopen_group_" #tag ", comdat\n"           \
+                ".weak __sd_dlopen_anchor_" #tag "\n"                                                                           \
+                ".hidden __sd_dlopen_anchor_" #tag "\n"                                                                         \
+                ".type __sd_dlopen_anchor_" #tag ", %object\n"                                                                  \
+                ".balign 8\n"                                                                                                   \
+                "__sd_dlopen_anchor_" #tag ":\n"                                                                                \
+                ".byte 0\n"                                                                                                     \
+                ".popsection\n"                                                                                                 \
+                ".pushsection .note.dlopen, \"aGo\", %note, __sd_dlopen_anchor_" #tag ", sd_dlopen_group_" #tag ", comdat\n"    \
+                ".balign 4\n"                                                                                                   \
+                ".long 884f - 883f\n"                                                                                           \
+                ".long 882f - 881f\n"                                                                                           \
+                ".long 0x407c0c0a\n"                                                                                            \
+                "883:\n"                                                                                                        \
+                ".asciz \"" SD_ELF_NOTE_DLOPEN_VENDOR "\"\n"                                                                    \
+                "884:\n"                                                                                                        \
+                ".balign 4\n"                                                                                                   \
+                "881:\n"                                                                                                        \
+                ".asciz \"" json "\"\n"                                                                                         \
+                "882:\n"                                                                                                        \
+                ".balign 4\n"                                                                                                   \
+                ".popsection\n"                                                                                                 \
+                ".endif\n"                                                                                                      \
+        );                                                                                                                      \
+        __asm__ volatile ("" : : "r" (&__sd_dlopen_anchor_##tag))
+
+#define _SD_SONAME_ARRAY1(a) "[\\\"" a "\\\"]"
+#define _SD_SONAME_ARRAY2(a, b) "[\\\"" a "\\\",\\\"" b "\\\"]"
+#define _SD_SONAME_ARRAY3(a, b, c) "[\\\"" a "\\\",\\\"" b "\\\",\\\"" c "\\\"]"
+#define _SD_SONAME_ARRAY4(a, b, c, d) "[\\\"" a "\\\",\\\"" b "\\\",\\\"" c "\\\",\\\"" d "\\\"]"
+#define _SD_SONAME_ARRAY5(a, b, c, d, e) "[\\\"" a "\\\",\\\"" b "\\\",\\\"" c "\\\",\\\"" d "\\\",\\\"" e "\\\"]"
+#define _SD_SONAME_ARRAY_GET(_1,_2,_3,_4,_5,NAME,...) NAME
+#define _SD_SONAME_ARRAY(...) _SD_SONAME_ARRAY_GET(__VA_ARGS__, _SD_SONAME_ARRAY5, _SD_SONAME_ARRAY4, _SD_SONAME_ARRAY3, _SD_SONAME_ARRAY2, _SD_SONAME_ARRAY1)(__VA_ARGS__)
+
+#define _SD_DLOPEN_JSON(feature, description, priority, ...)    \
+        "[{"                                                    \
+        "\\\"feature\\\":\\\"" feature "\\\","                  \
+        "\\\"description\\\":\\\"" description "\\\","          \
+        "\\\"priority\\\":\\\"" priority "\\\","                \
+        "\\\"soname\\\":" _SD_SONAME_ARRAY(__VA_ARGS__)         \
+        "}]"
+
+#define SD_ELF_NOTE_DLOPEN(feature, description, priority, ...) \
+        _SD_ELF_NOTE_DLOPEN(_SD_DLOPEN_JSON(feature, description, priority, __VA_ARGS__))
+
+/* The anchored note requires LLVM >= 18 (see above). Fall back to the non-anchored note on older clang. */
+#if defined(__clang__) && !defined(__apple_build_version__) && __clang_major__ < 18
+#  define SD_ELF_NOTE_DLOPEN_ANCHORED(tag, feature, description, priority, ...) \
+        SD_ELF_NOTE_DLOPEN(feature, description, priority, __VA_ARGS__)
+#else
+#  define SD_ELF_NOTE_DLOPEN_ANCHORED(tag, feature, description, priority, ...) \
+        _SD_ELF_NOTE_DLOPEN_ANCHORED(tag, _SD_DLOPEN_JSON(feature, description, priority, __VA_ARGS__))
+#endif
+
+#else
+
+#define SD_ELF_NOTE_DLOPEN(feature, description, priority, ...)
+#define SD_ELF_NOTE_DLOPEN_ANCHORED(tag, feature, description, priority, ...)
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/m4/libdl.m4 b/m4/libdl.m4
index 79d6906363..09e3b13131 100644
--- a/m4/libdl.m4
+++ b/m4/libdl.m4
@@ -1,5 +1,5 @@
 # libdl.m4
-# serial 3
+# serial 4
 dnl Copyright (C) 2024-2026 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -10,6 +10,7 @@
 
 dnl Set LIBDL to '-ldl' if it is needed to use the functions declared
 dnl in <dlfcn.h> (dlopen, dlsym, etc.), or to empty otherwise.
+dnl Set HAVE_DLOPEN to 1 if dlopen and dlsym are available, or to 0 otherwise.
 AC_DEFUN([gl_LIBDL],
 [
   dnl dlopen, dlsym are
@@ -21,8 +22,10 @@ AC_DEFUN([gl_LIBDL]
     [AC_LINK_IFELSE(
        [AC_LANG_PROGRAM(
           [[#include <dlfcn.h>
+            #include <stddef.h>
           ]],
-          [[return ! dlsym (RTLD_DEFAULT, "main");]])],
+          [[void *handle = dlopen (NULL, RTLD_LAZY);
+            return ! (handle && dlsym (handle, "main"));]])],
        [gl_cv_lib_dl=none],
        [gl_cv_lib_dl=maybe])
      if test $gl_cv_lib_dl = maybe; then
@@ -31,16 +34,22 @@ AC_DEFUN([gl_LIBDL]
        AC_LINK_IFELSE(
          [AC_LANG_PROGRAM(
             [[#include <dlfcn.h>
+              #include <stddef.h>
             ]],
-            [[return ! dlsym (RTLD_DEFAULT, "main");]])],
+            [[void *handle = dlopen (NULL, RTLD_LAZY);
+              return ! (handle && dlsym (handle, "main"));]])],
          [gl_cv_lib_dl='-ldl'],
-         [gl_cv_lib_dl=none])
+         [gl_cv_lib_dl=no])
        LIBS="$saved_LIBS"
      fi
     ])
   case "$gl_cv_lib_dl" in
-    none) LIBDL='' ;;
-    *)    LIBDL="$gl_cv_lib_dl" ;;
+    none) LIBDL=''; HAVE_DLOPEN=1 ;;
+    no)   LIBDL=''; HAVE_DLOPEN=0 ;;
+    *)    LIBDL="$gl_cv_lib_dl"; HAVE_DLOPEN=1 ;;
   esac
   AC_SUBST([LIBDL])
+  AC_SUBST([HAVE_DLOPEN])
+  AC_DEFINE_UNQUOTED([HAVE_DLOPEN], [$HAVE_DLOPEN],
+    [Define to 1 if dlopen and dlsym are available.])
 ])
diff --git a/m4/sd-dlopen.m4 b/m4/sd-dlopen.m4
new file mode 100644
index 0000000000..1511548dfb
--- /dev/null
+++ b/m4/sd-dlopen.m4
@@ -0,0 +1,30 @@
+# sd-dlopen.m4
+# serial 1
+dnl Copyright (C) 2026 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.
+dnl This file is offered as-is, without any warranty.
+
+AC_DEFUN([gl_SD_DLOPEN],
+[
+  AC_REQUIRE([gl_LIBDL])
+
+  AC_CACHE_CHECK([whether the assembler supports .ifndef, .endif, and .balign],
+    [gl_cv_asm_ifndef_endif_balign],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_SOURCE([[
+#if !defined __ELF__ || !(defined __GNUC__ || defined __clang__)
+# error "not an ELF target with GNU-style inline assembly"
+#endif
+__asm__ (".ifndef \"gl_sd_dlopen_test\"\n"
+         ".balign 4\n"
+         ".endif\n");
+]])],
+       [gl_cv_asm_ifndef_endif_balign=yes],
+       [gl_cv_asm_ifndef_endif_balign=no])])
+  if test $gl_cv_asm_ifndef_endif_balign = yes; then
+    AC_DEFINE([HAVE_ASM_IFNDEF_ENDIF_BALIGN], [1],
+      [Define to 1 if the assembler supports .ifndef, .endif, and .balign.])
+  fi
+])
diff --git a/modules/sd-dlopen b/modules/sd-dlopen
new file mode 100644
index 0000000000..179a1f1ef5
--- /dev/null
+++ b/modules/sd-dlopen
@@ -0,0 +1,27 @@
+Description:
+Emit ELF notes for package dependencies loaded with dlopen.
+
+Files:
+lib/sd-dlopen.h
+m4/sd-dlopen.m4
+m4/libdl.m4
+
+Depends-on:
+stdint-h
+
+configure.ac:
+gl_SD_DLOPEN
+
+Makefile.am:
+
+Include:
+"sd-dlopen.h"
+
+Link:
+$(LIBDL)
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.53.0

>From 6183bf1d55d7581f5b8ad8297f788cb4c4fd0155 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Tue, 21 Jul 2026 21:51:31 +0200
Subject: [PATCH 3/6] sd-dlopen: Reference the specification.

* modules/sd-dlopen (Description): Add reference to the specification.
* lib/sd-dlopen.h: Improve comments.
---
 ChangeLog         | 6 ++++++
 lib/sd-dlopen.h   | 5 +++--
 modules/sd-dlopen | 3 ++-
 3 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 115446119d..a85e110501 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2026-07-21  Bruno Haible  <[email protected]>
+
+	sd-dlopen: Reference the specification.
+	* modules/sd-dlopen (Description): Add reference to the specification.
+	* lib/sd-dlopen.h: Improve comments.
+
 2026-07-21  Luca Boccassi  <[email protected]>
 
 	sd-dlopen: Add tests.
diff --git a/lib/sd-dlopen.h b/lib/sd-dlopen.h
index 3f3b275e10..87760ada7a 100644
--- a/lib/sd-dlopen.h
+++ b/lib/sd-dlopen.h
@@ -27,7 +27,8 @@
 extern "C" {
 #endif
 
-/* ELF note macros implementing the FDO .note.dlopen standard.
+/* ELF note macros implementing the UAPI Group's .note.dlopen specification
+ * https://uapi-group.org/specifications/specs/elf_dlopen_metadata/
  *
  * These macros embed metadata in an ELF binary's .note.dlopen section,
  * declaring optional shared library dependencies that are loaded via
@@ -44,7 +45,7 @@ extern "C" {
  * See SD_ELF_NOTE_DLOPEN(3) for details.
  */
 
-#define SD_ELF_NOTE_DLOPEN_VENDOR "FDO"
+#define SD_ELF_NOTE_DLOPEN_VENDOR "FDO" /* freedesktop.org */
 #define SD_ELF_NOTE_DLOPEN_TYPE 0x407c0c0a
 #define SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED    "required"
 #define SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended"
diff --git a/modules/sd-dlopen b/modules/sd-dlopen
index 179a1f1ef5..452ef2ecc0 100644
--- a/modules/sd-dlopen
+++ b/modules/sd-dlopen
@@ -1,5 +1,6 @@
 Description:
-Emit ELF notes for package dependencies loaded with dlopen.
+Emit ELF notes for package dependencies loaded with dlopen, according to
+https://uapi-group.org/specifications/specs/elf_dlopen_metadata/
 
 Files:
 lib/sd-dlopen.h
-- 
2.53.0

>From 88757c1aa233fb65075d07959173bde6740fea97 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Wed, 22 Jul 2026 00:14:55 +0200
Subject: [PATCH 6/6] sd-dlopen: Fix test compilation error on Solaris 11.4.

* lib/sd-dlopen.h: Disable the asm macros on Solaris.
---
 ChangeLog       | 3 +++
 lib/sd-dlopen.h | 3 ++-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index d23b321025..08b3ce1552 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
 2026-07-21  Bruno Haible  <[email protected]>
 
+	sd-dlopen: Fix test compilation error on Solaris 11.4.
+	* lib/sd-dlopen.h: Disable the asm macros on Solaris.
+
 	sd-dlopen: Fix test compilation error on NetBSD 10.
 	* m4/sd-dlopen.m4 (gl_SD_DLOPEN): Test whether the assembler supports
 	the section flag o. Define _SD_ELF_NOTE_SUPPORTS_REFERENCES.
diff --git a/lib/sd-dlopen.h b/lib/sd-dlopen.h
index b3e36ccd77..57b7554754 100644
--- a/lib/sd-dlopen.h
+++ b/lib/sd-dlopen.h
@@ -52,7 +52,8 @@ extern "C" {
 #define SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED   "suggested"
 
 #if defined __ELF__ && (defined __GNUC__ || defined __clang__) \
-        && defined HAVE_ASM_IFNDEF_ENDIF_BALIGN && HAVE_ASM_IFNDEF_ENDIF_BALIGN
+        && defined HAVE_ASM_IFNDEF_ENDIF_BALIGN && HAVE_ASM_IFNDEF_ENDIF_BALIGN \
+        && !defined __sun /* Avoid linker error "wrong ELF OSABI: ELFOSABI_GNU" */
 
 /* The "R" (SHF_GNU_RETAIN) flag is only understood by binutils >= 2.36, so it can be disabled by defining
  * _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS as 'aG' manually, but note that if --gc-sections is used when linking,
-- 
2.53.0

Reply via email to