Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260205201906.GD3520958@port70.net>
Date: Thu, 5 Feb 2026 21:19:06 +0100
From: Szabolcs Nagy <nsz@...t70.net>
To: Luciano Lo Giudice <lmlogiudice@...il.com>
Cc: musl@...ts.openwall.com
Subject: Re: [RFC] Implement the _dl_find_object interface

* Luciano Lo Giudice <lmlogiudice@...il.com> [2026-02-04 20:23:04 -0300]:
> If we look at  the unwinder logic in libgcc, we will notice that outside of
> glibc, it uses `dl_iterate_phdr` to locate the .eh_frame section for a
> specific program counter value [1].
> 
> The issue with their approach is that they use a global cache [2], which is
> not protected by any locks. This is presumably because they assume that
> `dl_iterate_phdr` provides mutual exclusion by itself. In musl, however,
> the dynamic linker doesn't exactly do that, and as such, it appears that
> C++ exceptions (and anything that uses the `_Unwind_Find_FDE` interface)
> are unsafe in a multi-threaded scenario.

glibc has a (recursive) lock around the loop in dl_iterate_phdr,
this is a bug as it exposes internal lock to the user callback
(may cause deadlock) and since the loop can run for a long time
it can be a scalability issue for multithread code doing unwinds
concurrently. however libgcc relies on the lock so not fixed.
cant find bugzilla entry only
https://sourceware.org/pipermail/libc-help/2020-May/005276.html

> 
> The obvious fix to this would be to file a bug for libgcc and get it fixed
> there, but there's another option that also doesn't involve changing the
> locking protocol in the dynamic linker: Some time ago, glibc implemented a
> new interface called `_dl_find_object` [3], whose main purpose was to
> improve the performance of the unwinder used in C++ programs. As such, musl
> itself can do the same thing and in addition to fixing this bug, also
> obtain a slight performance boost for the described scenario, since this
> new interface would only acquire the dynamic linker's read lock once
> instead of multiple times.

i think it makes sense to report the libgcc bug.
(i'd expect the libgcc maintainers to require the defacto lock
in libc, however this is not documented currently)

note that glibc uses a version check + retry lock-free algorithm
in _dl_find_object for performane, musl could do this simpler
as it does not have to deal with dlclose. (e.g. dladdr could be
lock-free)

one issue with the _dl_find_object solution is deployment:
it will take time to enable its use in libgcc_s by default
since we want to allow users to build old musl code with a
new compiler so a new symbol in libgcc is problematic.

> 
> I'm attaching the patch inline in this message as it's a short one. Any
> questions, comments or critiques are welcome.
> 
> [1]:
> https://github.com/gcc-mirror/gcc/blob/master/libgcc/unwind-dw2-fde-dip.c#L569
> [2]:
> https://github.com/gcc-mirror/gcc/blob/master/libgcc/unwind-dw2-fde-dip.c#L156
> [3]:
> https://git.zx2c4.com/glibc/commit/elf?id=5d28a8962dcb6ec056b81d730e3c6fb57185a210
> 
> --------------------------------------
> diff --git a/include/dlfcn.h b/include/dlfcn.h
> index 13ab71dd..4d794cbd 100644
> --- a/include/dlfcn.h
> +++ b/include/dlfcn.h
> @@ -31,8 +31,35 @@ typedef struct {
>   const char *dli_sname;
>   void *dli_saddr;
>  } Dl_info;
> +
> +#ifdef __x86_64__
> +# define DLFO_STRUCT_HAS_EH_DBASE 0
> +#else
> +# define DLFO_STRUCT_HAS_EH_DBASE 1
> +#endif

this should be in a bits/ header, not generic

> +
> +#include <bits/alltypes.h>
> +
> +#define DLFO_FLAG_SFRAME   1
> +
> +struct dl_find_object {
> +    unsigned long long dlfo_flags;
> +    void *dlfo_map_start;
> +    void *dlfo_map_end;
> +    struct link_map *dlfo_link_map;
> +    void *dlfo_eh_frame;
> +#if DLFO_STRUCT_HAS_EH_DBASE
> +    void *dlfo_eh_dbase;
> +#  if __LONG_MAX == 0x7fffffffL
> +    unsigned int __pad1;
> +#  endif
> +#endif
> +    unsigned long long __extra[7];

this does not match the glibc abi/api on arm.

needs DLFO_STRUCT_HAS_EH_COUNT

> +};
> +
>  int dladdr(const void *, Dl_info *);
>  int dlinfo(void *, int, void *);
> +int _dl_find_object(void *, struct dl_find_object *);
>  #endif
> 
>  #if _REDIR_TIME64
> diff --git a/include/elf.h b/include/elf.h
> index d6ae539a..ff196ffa 100644
> --- a/include/elf.h
> +++ b/include/elf.h
> @@ -612,6 +612,7 @@ typedef struct {
>  #define PT_GNU_STACK 0x6474e551
>  #define PT_GNU_RELRO 0x6474e552
>  #define PT_GNU_PROPERTY 0x6474e553
> +#define PT_GNU_SFRAME 0x6474e554
>  #define PT_LOSUNW 0x6ffffffa
>  #define PT_SUNWBSS 0x6ffffffa
>  #define PT_SUNWSTACK 0x6ffffffb
> diff --git a/ldso/dynlink.c b/ldso/dynlink.c
> index 715948f4..2195017c 100644
> --- a/ldso/dynlink.c
> +++ b/ldso/dynlink.c
> @@ -2437,3 +2437,44 @@ static void error_impl(const char *fmt, ...)
>  static void error_noop(const char *fmt, ...)
>  {
>  }
> +
> +int _dl_find_object(void *addr, struct dl_find_object *obj)
> +{
> + struct dso *p;
> + Phdr *phdr;
> + size_t cnt, esize;
> + unsigned int read;
> +
> + pthread_rwlock_rdlock(&lock);
> + p = addr2dso((size_t)addr);
> + pthread_rwlock_unlock(&lock);
> +
> + if (!p) return -1;
> +
> + memset(obj, 0, sizeof(*obj));
> + obj->dlfo_map_start = p->map;
> + obj->dlfo_map_end = p->map + p->map_len;
> + obj->dlfo_link_map = (struct link_map *)p;
> +#if DLFO_STRUCT_HAS_EH_DBASE
> + obj->dlfo_eh_dbase = p->got;
> +#endif
> +
> + phdr = p->phdr;
> + cnt = p->phnum;
> + esize = p->phentsize;
> + read = 0;
> +
> + for (; cnt--; phdr = (void *)((char *)phdr + esize)) {
> + if (phdr->p_type == PT_GNU_EH_FRAME) {
> + obj->dlfo_eh_frame = laddr(p, phdr->p_vaddr);
> + read |= 1;
> + } else if (phdr->p_type == PT_GNU_SFRAME) {
> + obj->dlfo_flags |= DLFO_FLAG_SFRAME;
> + read |= 2;
> + }
> +
> + if (read == 3) break;
> + }
> +
> + return 0;
> +}
> diff --git a/src/dl_find_object.c b/src/dl_find_object.c
> new file mode 100644
> index 00000000..918d76a7
> --- /dev/null
> +++ b/src/dl_find_object.c
> @@ -0,0 +1,10 @@
> +#define _GNU_SOURCE
> +#include <dlfcn.h>
> +
> +static int
> +stub_dl_find_object (void *a, struct dl_find_object *o)
> +{
> +  return (-1);
> +}
> +
> +weak_alias (stub_dl_find_object, _dl_find_object);

this api must work with static linking

if the symbol is present it will be used instead
of dl_iterate_phdr

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.