dladdr(3)

πŸ’– dladdr, dladdr1 πŸ’–

okay so this is dladdr(3) from the Library Functions Manual and bestie, if you've ever stared at a raw pointer like "who even ARE you" β€” this is the page for you. 🌸


✨ NAME ✨

dladdr, dladdr1 β€” translate address to symbolic information

πŸ“š LIBRARY πŸ“š

Dynamic linking library (libdl, -ldl) πŸ¦‹

πŸŽ€ SYNOPSIS πŸŽ€

#define _GNU_SOURCE
#include <dlfcn.h>

int dladdr(const void *addr, Dl_info *info);
int dladdr1(const void *addr, Dl_info *info, void **extra_info,
            int flags);

πŸ’… DESCRIPTION πŸ’…

So dladdr() is literally the function that answers "hey, whose address is this??" πŸ•΅οΈβ™€οΈ You hand it an address in addr, and it checks whether that address lives inside one of the shared objects your program has loaded. If it does, you get back info about the shared object and the symbol that overlaps addr. All of that lands in a Dl_info structure, and it looks like this:

typedef struct {
    const char *dli_fname;  /* Pathname of shared object that
                               contains address */
    void       *dli_fbase;  /* Base address at which shared
                               object is loaded */
    const char *dli_sname;  /* Name of symbol whose definition
                               overlaps addr */
    void       *dli_saddr;  /* Exact address of symbol named
                               in dli_sname */
} Dl_info;

And if literally no symbol matches addr? Then dli_sname and dli_saddr get set to NULL. No symbol, no name, periodt. πŸ’€


Now dladdr1() is giving "same energy, but extra." It's like dladdr(), except it also hands you more info through the extra_info argument. What you actually get back depends on what you put in flags, and flags can be one of these:

  • RTLD_DL_LINKMAP β€” bestie you get a pointer to the link map for the matched file. The extra_info argument points to a pointer to a link_map structure (so like, struct link_map **), and that structure is defined in <link.h> like this:
struct link_map {
    ElfW(Addr) l_addr;  /* Difference between the
                           address in the ELF file and
                           the address in memory */
    char      *l_name;  /* Absolute pathname where
                           object was found */
    ElfW(Dyn) *l_ld;    /* Dynamic section of the
                           shared object */
    struct link_map *l_next, *l_prev;
                        /* Chain of loaded objects */

    /* Plus additional fields private to the
       implementation */
};
  • RTLD_DL_SYMENT β€” now you get a pointer to the ELF symbol table entry of the matching symbol. The extra_info argument here is a pointer to a symbol pointer: const ElfW(Sym) **. And the ElfW() macro? It turns its argument into the name of an ELF data type that's suitable for your hardware architecture. So on a 64-bit platform, ElfW(Sym) gives you the data type name Elf64_Sym, which is defined in <elf.h> like so:
typedef struct  {
    Elf64_Word    st_name;     /* Symbol name */
    unsigned char st_info;     /* Symbol type and binding */
    unsigned char st_other;    /* Symbol visibility */
    Elf64_Section st_shndx;    /* Section index */
    Elf64_Addr    st_value;    /* Symbol value */
    Elf64_Xword   st_size;     /* Symbol size */
} Elf64_Sym;

The st_name field is an index into the string table. 🧡

The st_info field encodes the symbol's type and binding. You pull the type out with the macro ELF64_ST_TYPE(st_info) (or ELF32_ST_TYPE() on 32-bit platforms), and it yields one of these:

  • STT_NOTYPE β€” Symbol type is unspecified
  • STT_OBJECT β€” Symbol is a data object
  • STT_FUNC β€” Symbol is a code object
  • STT_SECTION β€” Symbol associated with a section
  • STT_FILE β€” Symbol's name is filename
  • STT_COMMON β€” Symbol is a common data object
  • STT_TLS β€” Symbol is thread-local data object
  • STT_GNU_IFUNC β€” Symbol is indirect code object

The symbol binding comes out of that same st_info field using the macro ELF64_ST_BIND(st_info) (or ELF32_ST_BIND() on 32-bit platforms), and it yields one of these:

  • STB_LOCAL β€” Local symbol
  • STB_GLOBAL β€” Global symbol
  • STB_WEAK β€” Weak symbol
  • STB_GNU_UNIQUE β€” Unique symbol

And the st_other field holds the symbol's visibility, which you extract with the macro ELF64_ST_VISIBILITY(st_info) (or ELF32_ST_VISIBILITY() on 32-bit platforms), and it yields one of these:

  • STV_DEFAULT β€” Default symbol visibility rules
  • STV_INTERNAL β€” Processor-specific hidden class
  • STV_HIDDEN β€” Symbol unavailable in other modules
  • STV_PROTECTED β€” Not preemptible, not exported

🌈 RETURN VALUE 🌈

On success, these functions return a nonzero value. πŸ’« But listen: if the address in addr matched a shared object but not a symbol inside that shared object, then info->dli_sname and info->dli_saddr get set to NULL. Symbol found: no. Shared object found: yes.

If the address in addr couldn't be matched to a shared object at all, then these functions return 0. And in that case? An error message is not available via dlerror(3). So don't go asking dlerror(3) for the tea, it has none. πŸ™ˆ

⭐ ATTRIBUTES ⭐

For an explanation of the terms used in this section, see attributes(7).

  • Interface: dladdr(), dladdr1()
  • Attribute: Thread safety
  • Value: MT-Safe

(Yeah bestie, MT-Safe, we love a thread-safe icon. πŸ‘‘)

πŸ¦‹ STANDARDS πŸ¦‹

GNU.

πŸ•°οΈ HISTORY πŸ•°οΈ

  • dladdr() β€” glibc 2.0.
  • dladdr1() β€” glibc 2.3.3.
  • Solaris.

πŸ’€ BUGS πŸ’€

ngl, sometimes the function pointers you pass to dladdr() will surprise you. 😭 On some architectures (notably i386 and x86-64), dli_fname and dli_fbase may end up pointing back at the object you called dladdr() from β€” even if the function used as an argument is supposed to come from a dynamically linked library. Like, betrayal.

The problem is that the function pointer is still resolved at compile time, but it merely points to the plt (Procedure Linkage Table) section of the original object β€” and that section is the one that dispatches the call after asking the dynamic linker to resolve the symbol. So yeah. Main character pointer, wrong address. ✨

To work around this, you can try compiling the code to be position-independent: then the compiler can't prepare the pointer at compile time anymore, and gcc(1) generates code that just loads the final symbol address from the got (Global Offset Table) at run time before passing it to dladdr(). Slay. 🫢

πŸ‘€ SEE ALSO πŸ‘€

dl_iterate_phdr(3), dlinfo(3), dlopen(3), dlsym(3), ld.so(8)


and that's the whole page, bestie β€” this was Linux man-pages 6.20, dated 2026-02-08, for dladdr(3) πŸ’– go forth and translate those addresses, you absolute legend πŸŽ€βœ¨

πŸ“¦ where this page comes from
  • arch rolling: man-pages 6.20-1 Β· BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • πŸ“œ the page's license: Linux-man-pages-copyleft
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms