]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
libdw: Add Dwarf_Type (plain, dwo, gnu_lto) support for dwarf_begin
authorMark Wielaard <mark@klomp.org>
Wed, 1 Jul 2026 11:03:07 +0000 (13:03 +0200)
committerMark Wielaard <mark@klomp.org>
Wed, 1 Jul 2026 12:00:31 +0000 (14:00 +0200)
This introduces three new libdw functions dwarf_begin_type,
dwarf_begin_elf_type and dwarf_type.

When a file contained multiple types of DWARF, plain DWARF, DWO or
GNU_LTO, dwarf_begin and dwarf_begin_elf would use the plain DWARF
when available, otherwise use the DWO sections and finally the GNU_LTO
sections to construct a Dwarf handle. This meant that if a file
contained multiple types you would only be able to get one. And you
couldn't explicitly say you only wanted a Dwarf handle for a specific
type. Also you had no clear way of determining which type of Dwarf
handle you got.

The new dwarf_begin_type and dwarf_begin_elf_type allow explicitly
specifying a specific type of DWARF you want. And the dwarf_type
function will return the actual type of DWARF you got.

* libdw/libdw.h (Dwarf_Type): New enum.
(dwarf_begin_type): New function declaration.
(dwarf_begin_elf_type): Likewise.
(dwarf_get_type): Likewise.
* libdw/libdw.map (ELFUTILS_0.196): New section with new
function symbols based on ELFUTILS_0.194_EXPERIMENTAL.
* libdw/libdwP.h (enum dwarf_type): Removed.
(struct Dwarf): Make Dwarf_Type type.
(INTDECL): dwarf_begin_elf_type, dwarf_begin_type,
dwarf_get_type.
* libdw/dwarf_begin.c (dwarf_begin): Rename to and make a
wrapper around...
(dwarf_begin_type): ... this new function.
* libdw/dwarf_begin_elf.c (scn_dwarf_type): Return a
Dwarf_Type.
(check_section): Use Dwarf_Type instead of enum dwarf_type.
(global_read): Add requested_type argument. Split in
DWARF_T_AUTO case and specific type case.
(scngrp_read): Likewise.
(dwarf_begin_elf): Rename to and make a wrapper around..,
(dwarf_begin_elf_type): ... this new function.
(dwarf_get_type): New function.
* tests/Makefile.am (check_PROGRAMS): Add dwarf-type.
(TESTS): Add run-dwarf-type.sh.
(EXTRA_DIST): Likewise.
(dwarf_type_LDADD): New.
* tests/dwarf-type.c: New test.
* tests/run-dwarf-type.sh: New test wrapper.

https://sourceware.org/bugzilla/show_bug.cgi?id=28573

Signed-off-by: Mark Wielaard <mark@klomp.org>
Two remaining issues:

- Maybe we want a dedicated DWP type? Currently DWARF_T_DWO covers
  both single DWO and DWP/multi-DWO types. But this might not be a
  detail "real" users care about (only possibly useful for eu-dwp).

- Currently seeing a .debug_dwp section always assumes it is a dwo/dwp
  file, but it could be a plain DWARF file with a DWP reference. Need
  to actually inspect the .debug_dwp section to see which it is.

libdw/dwarf_begin.c
libdw/dwarf_begin_elf.c
libdw/libdw.h
libdw/libdw.map
libdw/libdwP.h
tests/Makefile.am
tests/dwarf-type.c [new file with mode: 0644]
tests/run-dwarf-type.sh [new file with mode: 0755]

index 19d16e5c511c6c28a98121e7e58ee7e53962e838..fbf1b0f05c900dfcf814974afa7fb9cf47bdf9aa 100644 (file)
@@ -1,5 +1,6 @@
 /* Create descriptor from file descriptor for processing file.
    Copyright (C) 2002, 2003, 2004, 2005 Red Hat, Inc.
+   Copyright (C) 2026 Mark J. Wielaard <mark@klomp.org>
    This file is part of elfutils.
    Written by Ulrich Drepper <drepper@redhat.com>, 2002.
 
@@ -39,7 +40,7 @@
 
 
 Dwarf *
-dwarf_begin (int fd, Dwarf_Cmd cmd)
+dwarf_begin_type (int fd, Dwarf_Cmd cmd, Dwarf_Type type)
 {
   Elf *elf;
   Elf_Cmd elfcmd;
@@ -85,7 +86,7 @@ dwarf_begin (int fd, Dwarf_Cmd cmd)
   else
     {
       /* Do the real work now that we have an ELF descriptor.  */
-      result = INTUSE(dwarf_begin_elf) (elf, cmd, NULL);
+      result = INTUSE(dwarf_begin_elf_type) (elf, cmd, type, NULL);
 
       /* If this failed, free the resources.  */
       if (result == NULL)
@@ -96,4 +97,11 @@ dwarf_begin (int fd, Dwarf_Cmd cmd)
 
   return result;
 }
+INTDEF(dwarf_begin_type)
+
+Dwarf *
+dwarf_begin (int fd, Dwarf_Cmd cmd)
+{
+  return INTUSE(dwarf_begin_type) (fd, cmd, DWARF_T_AUTO);
+}
 INTDEF(dwarf_begin)
index 7b58ad2e221828d885e52b7a2beafe55b2d2fc2f..5586ed66ec1908f73f1b76db79750cfd242e20cd 100644 (file)
@@ -1,6 +1,6 @@
 /* Create descriptor from ELF descriptor for processing file.
    Copyright (C) 2002-2011, 2014, 2015, 2017, 2018 Red Hat, Inc.
-   Copyright (C) 2023, Mark J. Wielaard <mark@klomp.org>
+   Copyright (C) 2023, 2026 Mark J. Wielaard <mark@klomp.org>
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -99,37 +99,37 @@ static const enum string_section_index scn_to_string_section_idx[IDX_last] =
   [IDX_gnu_debugaltlink] = STR_SCN_IDX_last
 };
 
-static enum dwarf_type
+static Dwarf_Type
 scn_dwarf_type (Dwarf *result, size_t shstrndx, Elf_Scn *scn)
 {
   GElf_Shdr shdr_mem;
   GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
   if (shdr == NULL)
-    return TYPE_UNKNOWN;
+    return DWARF_T_AUTO;
 
   const char *scnname = elf_strptr (result->elf, shstrndx,
                                    shdr->sh_name);
   if (scnname != NULL)
     {
       if (startswith (scnname, ".gnu.debuglto_.debug"))
-       return TYPE_GNU_LTO;
+       return DWARF_T_GNU_LTO;
       else if (strcmp (scnname, ".debug_cu_index") == 0
               || strcmp (scnname, ".debug_tu_index") == 0
               || strcmp (scnname, ".debug_dwp") == 0
               || strcmp (scnname, ".zdebug_cu_index") == 0
               || strcmp (scnname, ".zdebug_tu_index") == 0
               || strcmp (scnname, ".zdebug_dwp") == 0)
-       return TYPE_DWO;
+       return DWARF_T_DWO;
       else if (startswith (scnname, ".debug_") || startswith (scnname, ".zdebug_"))
        {
          size_t len = strlen (scnname);
          if (strcmp (scnname + len - 4, ".dwo") == 0)
-           return TYPE_DWO;
+           return DWARF_T_DWO;
          else
-           return TYPE_PLAIN;
+           return DWARF_T_PLAIN;
        }
     }
-  return TYPE_UNKNOWN;
+  return DWARF_T_AUTO;
 }
 static Dwarf *
 check_section (Dwarf *result, size_t shstrndx, Elf_Scn *scn, bool inscngrp)
@@ -186,11 +186,11 @@ check_section (Dwarf *result, size_t shstrndx, Elf_Scn *scn, bool inscngrp)
     {
       /* .debug_cu_index and .debug_tu_index don't have a .dwo suffix,
         but they are for DWO.  */
-      if (result->type != TYPE_DWO
+      if (result->type != DWARF_T_DWO
          && (cnt == IDX_debug_cu_index || cnt == IDX_debug_tu_index))
        continue;
       bool need_dot_dwo =
-       (result->type == TYPE_DWO
+       (result->type == DWARF_T_DWO
         && cnt != IDX_debug_cu_index
         && cnt != IDX_debug_tu_index);
       size_t dbglen = strlen (dwarf_scnnames[cnt]);
@@ -217,7 +217,7 @@ check_section (Dwarf *result, size_t shstrndx, Elf_Scn *scn, bool inscngrp)
               && startswith (scnname, ".gnu.debuglto_")
               && strcmp (&scnname[14], dwarf_scnnames[cnt]) == 0)
        {
-         if (result->type == TYPE_GNU_LTO)
+         if (result->type == DWARF_T_GNU_LTO)
            break;
        }
     }
@@ -482,17 +482,44 @@ valid_p (Dwarf *result)
 
 
 static Dwarf *
-global_read (Dwarf *result, Elf *elf, size_t shstrndx)
+global_read (Dwarf *result, Elf *elf, size_t shstrndx,
+            Dwarf_Type requested_type)
 {
   Elf_Scn *scn = NULL;
 
-  /* First check the type (PLAIN, DWO, LTO) we are looking for.  We
-     prefer PLAIN if available over DWO, over LTO.  */
-  while ((scn = elf_nextscn (elf, scn)) != NULL && result->type != TYPE_PLAIN)
+  if (requested_type == DWARF_T_AUTO)
     {
-      enum dwarf_type type = scn_dwarf_type (result, shstrndx, scn);
-      if (type > result->type)
-       result->type = type;
+      /* First check the type (PLAIN, DWO, LTO) we are looking for.  We
+        prefer PLAIN if available over DWO, over LTO.  */
+      while ((scn = elf_nextscn (elf, scn)) != NULL
+            && result->type != DWARF_T_PLAIN)
+       {
+         Dwarf_Type type = scn_dwarf_type (result, shstrndx, scn);
+         if (type > result->type)
+           result->type = type;
+       }
+    }
+  else
+    {
+      /* Check there is at least one section of the requested type.  */
+      bool found = false;
+      while ((scn = elf_nextscn (elf, scn)) != NULL && !found)
+       {
+         Dwarf_Type type = scn_dwarf_type (result, shstrndx, scn);
+         if (type == requested_type)
+           found = true;
+       }
+
+      if (!found)
+       {
+         /* Requested type not available.  */
+         Dwarf_Sig8_Hash_free (&result->sig8_hash);
+         __libdw_seterrno (DWARF_E_NO_DWARF);
+         free (result);
+         return NULL;
+       }
+
+      result->type = requested_type;
     }
 
   scn = NULL;
@@ -504,7 +531,8 @@ global_read (Dwarf *result, Elf *elf, size_t shstrndx)
 
 
 static Dwarf *
-scngrp_read (Dwarf *result, Elf *elf, size_t shstrndx, Elf_Scn *scngrp)
+scngrp_read (Dwarf *result, Elf *elf, size_t shstrndx,
+            Dwarf_Type requested_type, Elf_Scn *scngrp)
 {
   GElf_Shdr shdr_mem;
   GElf_Shdr *shdr = gelf_getshdr (scngrp, &shdr_mem);
@@ -541,24 +569,58 @@ scngrp_read (Dwarf *result, Elf *elf, size_t shstrndx, Elf_Scn *scngrp)
   Elf32_Word *scnidx = (Elf32_Word *) data->d_buf;
   size_t cnt;
 
-  /* First check the type (PLAIN, DWO, LTO) we are looking for.  We
-     prefer PLAIN if available over DWO, over LTO.  */
-  for (cnt = 1; cnt * sizeof (Elf32_Word) <= data->d_size; ++cnt)
+  if (requested_type == DWARF_T_AUTO)
     {
-      Elf_Scn *scn = elf_getscn (elf, scnidx[cnt]);
-      if (scn == NULL)
+      /* First check the type (PLAIN, DWO, LTO) we are looking for.  We
+        prefer PLAIN if available over DWO, over LTO.  */
+      for (cnt = 1; cnt * sizeof (Elf32_Word) <= data->d_size; ++cnt)
        {
-         /* A section group refers to a non-existing section.  Should
-            never happen.  */
+         Elf_Scn *scn = elf_getscn (elf, scnidx[cnt]);
+         if (scn == NULL)
+           {
+             /* A section group refers to a non-existing section.  Should
+                never happen.  */
+             Dwarf_Sig8_Hash_free (&result->sig8_hash);
+             __libdw_seterrno (DWARF_E_INVALID_ELF);
+             free (result);
+             return NULL;
+           }
+
+         Dwarf_Type type = scn_dwarf_type (result, shstrndx, scn);
+         if (type > result->type)
+           result->type = type;
+       }
+    }
+  else
+    {
+      /* Check there is at least one section of the requested type.  */
+      bool found = false;
+      for (cnt = 1; cnt * sizeof (Elf32_Word) <= data->d_size && !found; ++cnt)
+       {
+         Elf_Scn *scn = elf_getscn (elf, scnidx[cnt]);
+         if (scn == NULL)
+           {
+             Dwarf_Sig8_Hash_free (&result->sig8_hash);
+             __libdw_seterrno (DWARF_E_INVALID_ELF);
+             free (result);
+             return NULL;
+           }
+
+         Dwarf_Type type = scn_dwarf_type (result, shstrndx, scn);
+         if (type == requested_type)
+           found = true;
+       }
+
+      if (!found)
+       {
+         /* Requested type not available.  */
          Dwarf_Sig8_Hash_free (&result->sig8_hash);
-         __libdw_seterrno (DWARF_E_INVALID_ELF);
+         __libdw_seterrno (DWARF_E_NO_DWARF);
          free (result);
          return NULL;
        }
 
-      enum dwarf_type type = scn_dwarf_type (result, shstrndx, scn);
-      if (type > result->type)
-       result->type = type;
+      result->type = requested_type;
     }
 
   for (cnt = 1; cnt * sizeof (Elf32_Word) <= data->d_size && result != NULL; ++cnt)
@@ -575,7 +637,8 @@ scngrp_read (Dwarf *result, Elf *elf, size_t shstrndx, Elf_Scn *scngrp)
 
 
 Dwarf *
-dwarf_begin_elf (Elf *elf, Dwarf_Cmd cmd, Elf_Scn *scngrp)
+dwarf_begin_elf_type (Elf *elf, Dwarf_Cmd cmd, Dwarf_Type type,
+                     Elf_Scn *scngrp)
 {
   GElf_Ehdr *ehdr;
   GElf_Ehdr ehdr_mem;
@@ -644,9 +707,9 @@ dwarf_begin_elf (Elf *elf, Dwarf_Cmd cmd, Elf_Scn *scngrp)
         sections with the name are ignored.  The DWARF specification
         does not really say this is allowed.  */
       if (scngrp == NULL)
-       return global_read (result, elf, shstrndx);
+       return global_read (result, elf, shstrndx, type);
       else
-       return scngrp_read (result, elf, shstrndx, scngrp);
+       return scngrp_read (result, elf, shstrndx, type, scngrp);
     }
   else if (cmd == DWARF_C_WRITE)
     {
@@ -661,4 +724,22 @@ dwarf_begin_elf (Elf *elf, Dwarf_Cmd cmd, Elf_Scn *scngrp)
   free (result);
   return NULL;
 }
+INTDEF(dwarf_begin_elf_type)
+
+Dwarf *
+dwarf_begin_elf (Elf *elf, Dwarf_Cmd cmd, Elf_Scn *scngrp)
+{
+  return dwarf_begin_elf_type (elf, cmd, DWARF_T_AUTO, scngrp);
+}
 INTDEF(dwarf_begin_elf)
+
+Dwarf_Type
+dwarf_get_type (Dwarf *dwarf)
+{
+  if (dwarf == NULL)
+    return DWARF_T_AUTO;
+
+  return dwarf->type;
+}
+INTDEF(dwarf_get_type)
+
index 9096f301f6e363f4f0981e1393f504a35b51e2a5..bcd76138d71700d25f212b5401b6b5712fa2c5b0 100644 (file)
@@ -1,5 +1,6 @@
 /* Interfaces for libdw.
    Copyright (C) 2002-2010, 2013, 2014, 2016, 2018 Red Hat, Inc.
+   Copyright (C) 2026 Mark J. Wielaard <mark@klomp.org>
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -43,6 +44,19 @@ typedef enum
   }
 Dwarf_Cmd;
 
+/* DWARF type. Either plain DWARF, split DWARF DWOs or GNU LTO.  When
+   used with dwarf_begin_type or dwarf_begin_elf_type DWARF_T_AUTO
+   tries to get PLAIN, DWO, GNU_LTO in that order.  Enum values have a
+   numeric value indicating their priority in auto mode.  */
+typedef enum
+  {
+    DWARF_T_AUTO    = 0,  /* Automatic selection (PLAIN > DWO > GNU_LTO).  */
+    DWARF_T_GNU_LTO = 16, /* GNU LTO (.gnu.debuglto_.debug_*).  */
+    DWARF_T_DWO     = 32, /* Split DWARF (.debug_*.dwo).  */
+    DWARF_T_PLAIN   = 64, /* Standard DWARF (.debug_*).  */
+  }
+Dwarf_Type;
+
 
 /* Callback results.  */
 enum
@@ -234,12 +248,32 @@ typedef void (*__noreturn_attribute__ Dwarf_OOM) (void);
 extern "C" {
 #endif
 
-/* Create a handle for a new debug session.  */
+/* Create a handle for a new debug session.
+   Calls dwarf_begin_type (fildes, cmd, DWARF_T_AUTO).  */
 extern Dwarf *dwarf_begin (int fildes, Dwarf_Cmd cmd);
 
-/* Create a handle for a new debug session for an ELF file.  */
+/* Create a handle for a new debug session given a specific type.
+   When type is DWARF_T_AUTO will try to get PLAIN, DWO, GNU_LTO in
+   that order. Will return NULL if the requested type is not available
+   in the file. */
+extern Dwarf *dwarf_begin_type (int fildes, Dwarf_Cmd cmd, Dwarf_Type type);
+
+/* Create a handle for a new debug session for an ELF file.
+   Calls dwarf_begin_type (elf, cmd, DWARF_T_AUTO, scngrp).  */
 extern Dwarf *dwarf_begin_elf (Elf *elf, Dwarf_Cmd cmd, Elf_Scn *scngrp);
 
+/* Create a handle for a new debug session given a specific type.
+   When type is DWARF_T_AUTO will try to get PLAIN, DWO, GNU_LTO in
+   that order. Will return NULL if the requested type is not available
+   in the file. If scngrp isn't NULL it must be a SHT_GROUP section
+   and only sections in this group will be used.  */
+extern Dwarf *dwarf_begin_elf_type (Elf *elf, Dwarf_Cmd cmd, Dwarf_Type type,
+                                    Elf_Scn *scngrp);
+
+/* Returns the DWARF type of the givn Dwarf descriptor.  Return
+   DWARF_T_AUTO (zero) if the descriptor is NULL.  */
+extern Dwarf_Type dwarf_get_type (Dwarf *dwarf);
+
 /* Retrieve ELF descriptor used for DWARF access.  */
 extern Elf *dwarf_getelf (Dwarf *dwarf);
 
index b45647e6fa4c499762c0180da8f9337ceeaf764f..404b4c1056a9a263ef520d9f453a0c17aea5332b 100644 (file)
@@ -411,3 +411,10 @@ ELFUTILS_0.194_EXPERIMENTAL {
   global:
     dwflst_sample_getframes;
 } ELFUTILS_0.193_EXPERIMENTAL;
+
+ELFUTILS_0.196 {
+  global:
+    dwarf_begin_elf_type;
+    dwarf_begin_type;
+    dwarf_get_type;
+} ELFUTILS_0.194_EXPERIMENTAL;
index 628777b069da4b68bc032460c5928fc62ef68e30..8501337f49905c68829b787e16f8ba01b6fe2a78 100644 (file)
@@ -154,15 +154,6 @@ enum
 
 #include "dwarf_sig8_hash.h"
 
-/* The type of Dwarf object, sorted by preference
-   (if there is a higher order type, we pick that one over the others).  */
-enum dwarf_type
-  {
-    TYPE_UNKNOWN = 0,
-    TYPE_GNU_LTO = 16,
-    TYPE_DWO = 32,
-    TYPE_PLAIN = 64,
-  };
 
 /* This is the structure representing the debugging state.  */
 struct Dwarf
@@ -257,7 +248,7 @@ struct Dwarf
   /* Similar for addrx/constx, which will come from .debug_addr section.  */
   struct Dwarf_CU *fake_addr_cu;
 
-  enum dwarf_type type;
+  Dwarf_Type type;
 
   /* Supporting lock for internal memory handling.  Ensures threads that have
      an entry in the mem_tails array are not disturbed by new threads doing
@@ -486,6 +477,8 @@ INTDECL (dwarf_attr)
 INTDECL (dwarf_attr_integrate)
 INTDECL (dwarf_begin)
 INTDECL (dwarf_begin_elf)
+INTDECL (dwarf_begin_elf_type)
+INTDECL (dwarf_begin_type)
 INTDECL (dwarf_child)
 INTDECL (dwarf_cu_dwp_section_info)
 INTDECL (dwarf_default_lower_bound)
@@ -508,6 +501,7 @@ INTDECL (dwarf_getaranges)
 INTDECL (dwarf_getlocation_die)
 INTDECL (dwarf_getsrcfiles)
 INTDECL (dwarf_getsrclines)
+INTDECL (dwarf_get_type)
 INTDECL (dwarf_get_units)
 INTDECL (dwarf_hasattr)
 INTDECL (dwarf_haschildren)
index d3ecd35bbf42cdb89f17e45bb9d73635fd6cbd0a..4493f895ce101a640c87988622bf7435721253ca 100644 (file)
@@ -58,6 +58,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \
                  elfgetzdata elfputzdata zstrptr emptyfile vendorelf \
                  fillfile dwarf_default_lower_bound \
                  dwarf_language_lower_bound dwarf-die-addr-die \
+                 dwarf-type \
                  get-units-invalid get-units-split attr-integrate-skel \
                  all-dwarf-ranges unit-info next_cfi \
                  elfcopy addsections xlate_notes elfrdwrnop \
@@ -211,6 +212,7 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \
        emptyfile vendorelf fillfile dwarf_default_lower_bound \
        dwarf_language_lower_bound \
        run-dwarf-die-addr-die.sh \
+       run-dwarf-type.sh \
        run-get-units-invalid.sh run-get-units-split.sh \
        run-attr-integrate-skel.sh \
        run-all-dwarf-ranges.sh run-unit-info.sh \
@@ -584,6 +586,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
             testfile-bpf-reloc.expect.bz2 testfile-bpf-reloc.o.bz2 \
             testfile-m68k-core.bz2 testfile-m68k.bz2 testfile-m68k-s.bz2 \
             run-dwarf-die-addr-die.sh \
+            run-dwarf-type.sh \
             run-get-units-invalid.sh run-get-units-split.sh \
             testfile-hello4.dwo.bz2 testfile-hello5.dwo.bz2 \
             testfile-splitdwarf-4.bz2 testfile-splitdwarf-5.bz2 \
@@ -893,6 +896,7 @@ fillfile_LDADD = $(libelf)
 dwarf_default_lower_bound_LDADD = $(libdw)
 dwarf_language_lower_bound_LDADD = $(libdw)
 dwarf_die_addr_die_LDADD = $(libdw)
+dwarf_type_LDADD = $(libdw)
 get_units_invalid_LDADD = $(libdw)
 get_units_split_LDADD = $(libdw)
 attr_integrate_skel_LDADD = $(libdw)
diff --git a/tests/dwarf-type.c b/tests/dwarf-type.c
new file mode 100644 (file)
index 0000000..9fff663
--- /dev/null
@@ -0,0 +1,84 @@
+/* Test dwarf_begin_type and dwarf_get_type functionality.
+   Copyright (C) 2024 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file 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.
+
+   elfutils 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 <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <dwarf.h>
+#include <libdw.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static const char *
+type_name (Dwarf_Type type)
+{
+  switch (type)
+    {
+    case DWARF_T_AUTO: return "AUTO";
+    case DWARF_T_PLAIN: return "PLAIN";
+    case DWARF_T_DWO: return "DWO";
+    case DWARF_T_GNU_LTO: return "GNU_LTO";
+    default: return "UNKNOWN";
+    }
+}
+
+Dwarf_Type
+name_type (const char *name)
+{
+  if (strcmp (name, "AUTO") == 0)
+    return DWARF_T_AUTO;
+  else if (strcmp (name, "PLAIN") == 0)
+    return DWARF_T_PLAIN;
+  else if (strcmp (name, "DWO") == 0)
+    return DWARF_T_DWO;
+  else if (strcmp (name, "GNU_LTO") == 0)
+    return DWARF_T_GNU_LTO;
+  else
+    {
+      printf ("Unknown type name: %s\n", name);
+      exit (1);
+    }
+}
+
+int
+main (int argc, char *argv[])
+{
+  if (argc < 2)
+    {
+      fprintf (stderr, "Need <type> and <testfile>\n");
+      return 1;
+    }
+
+  const char *tname = argv[1];
+  const char *file = argv[2];
+  Dwarf_Type type = name_type (tname);
+
+  int fd = open (file, O_RDONLY);
+  if (fd < 0)
+    {
+      printf ("Cannot open %s\n", file);
+      return 1;
+    }
+
+  Dwarf *dbg = dwarf_begin_type (fd, DWARF_C_READ, type);
+  Dwarf_Type dwarf_type = dwarf_get_type (dbg);
+  printf ("%s: %s -> %s\n", file, type_name (type), type_name (dwarf_type));
+  dwarf_end (dbg);
+  close (fd);
+  return 0;
+}
diff --git a/tests/run-dwarf-type.sh b/tests/run-dwarf-type.sh
new file mode 100755 (executable)
index 0000000..954d785
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+# Copyright (C) 2026 Mark J. Wielaard <mark@klomp.org>
+# This file is part of elfutils.
+#
+# This file 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.
+#
+# elfutils 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 <http://www.gnu.org/licenses/>.
+
+. $srcdir/test-subr.sh
+
+# Test dwarf_begin_type and dwarf_get_type with fat LTO file
+# (file with both PLAIN and GNU_LTO sections)
+testfiles testfile-dwarf5-fat-lto.o
+
+testrun_compare ${abs_builddir}/dwarf-type AUTO testfile-dwarf5-fat-lto.o <<\EOF
+testfile-dwarf5-fat-lto.o: AUTO -> PLAIN
+EOF
+testrun_compare ${abs_builddir}/dwarf-type PLAIN testfile-dwarf5-fat-lto.o <<\EOF
+testfile-dwarf5-fat-lto.o: PLAIN -> PLAIN
+EOF
+testrun_compare ${abs_builddir}/dwarf-type DWO testfile-dwarf5-fat-lto.o <<\EOF
+testfile-dwarf5-fat-lto.o: DWO -> AUTO
+EOF
+testrun_compare ${abs_builddir}/dwarf-type GNU_LTO testfile-dwarf5-fat-lto.o <<\EOF
+testfile-dwarf5-fat-lto.o: GNU_LTO -> GNU_LTO
+EOF
+
+# Test dwarf_begin_type and dwarf_get_type with DWO (only) file
+testfiles testfile-hello5.dwo
+
+testrun_compare ${abs_builddir}/dwarf-type AUTO testfile-hello5.dwo <<\EOF
+testfile-hello5.dwo: AUTO -> DWO
+EOF
+testrun_compare ${abs_builddir}/dwarf-type PLAIN testfile-hello5.dwo <<\EOF
+testfile-hello5.dwo: PLAIN -> AUTO
+EOF
+testrun_compare ${abs_builddir}/dwarf-type DWO testfile-hello5.dwo <<\EOF
+testfile-hello5.dwo: DWO -> DWO
+EOF
+testrun_compare ${abs_builddir}/dwarf-type GNU_LTO testfile-hello5.dwo <<\EOF
+testfile-hello5.dwo: GNU_LTO -> AUTO
+EOF
+
+exit 0