Handle ADD/SUB relocations
[platform/upstream/elfutils.git] / src / strip.c
index aefea93..1f7b3ca 100644 (file)
@@ -1,16 +1,20 @@
 /* Discard section not used at runtime from object files.
-   Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005 Red Hat, Inc.
+   Copyright (C) 2000-2012, 2014, 2015, 2016, 2017 Red Hat, Inc.
+   This file is part of elfutils.
    Written by Ulrich Drepper <drepper@redhat.com>, 2000.
 
-   This program is Open Source software; you can redistribute it and/or
-   modify it under the terms of the Open Software License version 1.0 as
-   published by the Open Source Initiative.
+   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.
 
-   You should have received a copy of the Open Software License along
-   with this program; if not, you may obtain a copy of the Open Software
-   License version 1.0 from http://www.opensource.org/licenses/osl.php or
-   by writing the Open Source Initiative c/o Lawrence Rosen, Esq.,
-   3001 King Ranch Road, Ukiah, CA 95482.   */
+   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/>.  */
 
 #ifdef HAVE_CONFIG_H
 # include <config.h>
 #include <assert.h>
 #include <byteswap.h>
 #include <endian.h>
-#include <error.h>
 #include <fcntl.h>
+#include <fnmatch.h>
 #include <gelf.h>
 #include <libelf.h>
 #include <libintl.h>
 #include <locale.h>
-#include <mcheck.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include <sys/param.h>
+#include <sys/stat.h>
 #include <sys/time.h>
 
 #include <elf-knowledge.h>
 #include <libebl.h>
+#include "libdwelf.h"
+#include <libeu.h>
 #include <system.h>
+#include <printversion.h>
 
+typedef uint8_t GElf_Byte;
 
 /* Name and version of program.  */
-static void print_version (FILE *stream, struct argp_state *state);
-void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
 
 /* Bug report address.  */
-const char *argp_program_bug_address = PACKAGE_BUGREPORT;
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
 
 
 /* Values for the parameters which have no short form.  */
 #define OPT_REMOVE_COMMENT     0x100
 #define OPT_PERMISSIVE         0x101
+#define OPT_STRIP_SECTIONS     0x102
+#define OPT_RELOC_DEBUG        0x103
+#define OPT_KEEP_SECTION       0x104
 
 
 /* Definitions of arguments for argp functions.  */
 static const struct argp_option options[] =
 {
   { NULL, 0, NULL, 0, N_("Output selection:"), 0 },
-  { NULL, 'o', "FILE", 0, N_("Place stripped output into FILE"), 0 },
+  { "output", 'o', "FILE", 0, N_("Place stripped output into FILE"), 0 },
   { NULL, 'f', "FILE", 0, N_("Extract the removed sections into FILE"), 0 },
   { NULL, 'F', "FILE", 0, N_("Embed name FILE instead of -f argument"), 0 },
 
@@ -67,11 +76,16 @@ static const struct argp_option options[] =
   { "strip-debug", 'g', NULL, 0, N_("Remove all debugging symbols"), 0 },
   { NULL, 'd', NULL, OPTION_ALIAS, NULL, 0 },
   { NULL, 'S', NULL, OPTION_ALIAS, NULL, 0 },
+  { "strip-sections", OPT_STRIP_SECTIONS, NULL, 0,
+    N_("Remove section headers (not recommended)"), 0 },
   { "preserve-dates", 'p', NULL, 0,
     N_("Copy modified/access timestamps to the output"), 0 },
+  { "reloc-debug-sections", OPT_RELOC_DEBUG, NULL, 0,
+    N_("Resolve all trivial relocations between debug sections if the removed sections are placed in a debug file (only relevant for ET_REL files, operation is not reversable, needs -f)"), 0 },
   { "remove-comment", OPT_REMOVE_COMMENT, NULL, 0,
     N_("Remove .comment section"), 0 },
-  { "remove-section", 'R', "SECTION", OPTION_HIDDEN, NULL, 0 },
+  { "remove-section", 'R', "SECTION", 0, N_("Remove the named section.  SECTION is an extended wildcard pattern.  May be given more than once.  Only non-allocated sections can be removed."), 0 },
+  { "keep-section", OPT_KEEP_SECTION, "SECTION", 0, N_("Keep the named section.  SECTION is an extended wildcard pattern.  May be given more than once."), 0 },
   { "permissive", OPT_PERMISSIVE, NULL, 0,
     N_("Relax a few rules to handle slightly broken ELF files"), 0 },
   { NULL, 0, NULL, 0, NULL, 0 }
@@ -98,15 +112,24 @@ static int process_file (const char *fname);
 
 /* Handle one ELF file.  */
 static int handle_elf (int fd, Elf *elf, const char *prefix,
-                      const char *fname, mode_t mode, struct timeval tvp[2]);
+                      const char *fname, mode_t mode, struct timespec tvp[2]);
 
 /* Handle all files contained in the archive.  */
 static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname,
-                     struct timeval tvp[2]);
+                     struct timespec tvp[2]) __attribute__ ((unused));
+
+static int debug_fd = -1;
+static char *tmp_debug_fname = NULL;
+
+/* Close debug file descriptor, if opened. And remove temporary debug file.  */
+static void cleanup_debug (void);
 
 #define INTERNAL_ERROR(fname) \
-  error (EXIT_FAILURE, 0, gettext ("%s: INTERNAL ERROR %d (%s-%s): %s"),      \
-        fname, __LINE__, VERSION, __DATE__, elf_errmsg (-1))
+  do { \
+    cleanup_debug (); \
+    error (EXIT_FAILURE, 0, gettext ("%s: INTERNAL ERROR %d (%s): %s"),      \
+          fname, __LINE__, PACKAGE_VERSION, elf_errmsg (-1)); \
+  } while (0)
 
 
 /* Name of the output file.  */
@@ -127,9 +150,67 @@ static bool remove_comment;
 /* If true remove all debug sections.  */
 static bool remove_debug;
 
+/* If true remove all section headers.  */
+static bool remove_shdrs;
+
 /* If true relax some ELF rules for input files.  */
 static bool permissive;
 
+/* If true perform relocations between debug sections.  */
+static bool reloc_debug;
+
+/* Sections the user explicitly wants to keep or remove.  */
+struct section_pattern
+{
+  char *pattern;
+  struct section_pattern *next;
+};
+
+static struct section_pattern *keep_secs = NULL;
+static struct section_pattern *remove_secs = NULL;
+
+static void
+add_pattern (struct section_pattern **patterns, const char *pattern)
+{
+  struct section_pattern *p = xmalloc (sizeof *p);
+  p->pattern = xstrdup (pattern);
+  p->next = *patterns;
+  *patterns = p;
+}
+
+static void
+free_sec_patterns (struct section_pattern *patterns)
+{
+  struct section_pattern *pattern = patterns;
+  while (pattern != NULL)
+    {
+      struct section_pattern *p = pattern;
+      pattern = p->next;
+      free (p->pattern);
+      free (p);
+    }
+}
+
+static void
+free_patterns (void)
+{
+  free_sec_patterns (keep_secs);
+  free_sec_patterns (remove_secs);
+}
+
+static bool
+section_name_matches (struct section_pattern *patterns, const char *name)
+{
+  struct section_pattern *pattern = patterns;
+  while (pattern != NULL)
+    {
+      if (fnmatch (pattern->pattern, name, FNM_EXTMATCH) == 0)
+       return true;
+      pattern = pattern->next;
+    }
+  return false;
+}
+
 
 int
 main (int argc, char *argv[])
@@ -137,9 +218,6 @@ main (int argc, char *argv[])
   int remaining;
   int result = 0;
 
-  /* Make memory leak detection possible.  */
-  mtrace ();
-
   /* We use no threads here which can interfere with handling a stream.  */
   __fsetlocking (stdin, FSETLOCKING_BYCALLER);
   __fsetlocking (stdout, FSETLOCKING_BYCALLER);
@@ -149,15 +227,19 @@ main (int argc, char *argv[])
   setlocale (LC_ALL, "");
 
   /* Make sure the message catalog can be found.  */
-  bindtextdomain (PACKAGE, LOCALEDIR);
+  bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
 
   /* Initialize the message catalog.  */
-  textdomain (PACKAGE);
+  textdomain (PACKAGE_TARNAME);
 
   /* Parse and process arguments.  */
   if (argp_parse (&argp, argc, argv, 0, &remaining, NULL) != 0)
     return EXIT_FAILURE;
 
+  if (reloc_debug && debug_fname == NULL)
+    error (EXIT_FAILURE, 0,
+          gettext ("--reloc-debug-sections used without -f"));
+
   /* Tell the library which version we are expecting.  */
   elf_version (EV_CURRENT);
 
@@ -179,24 +261,11 @@ Only one input file allowed together with '-o' and '-f'"));
       while (++remaining < argc);
     }
 
+  free_patterns ();
   return result;
 }
 
 
-/* Print the version information.  */
-static void
-print_version (FILE *stream, struct argp_state *state __attribute__ ((unused)))
-{
-  fprintf (stream, "strip (%s) %s\n", PACKAGE_NAME, VERSION);
-  fprintf (stream, gettext ("\
-Copyright (C) %s Red Hat, Inc.\n\
-This is free software; see the source for copying conditions.  There is NO\n\
-warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
-"), "2005");
-  fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
-}
-
-
 /* Handle program arguments.  */
 static error_t
 parse_opt (int key, char *arg, struct argp_state *state)
@@ -234,19 +303,22 @@ parse_opt (int key, char *arg, struct argp_state *state)
       preserve_dates = true;
       break;
 
+    case OPT_RELOC_DEBUG:
+      reloc_debug = true;
+      break;
+
     case OPT_REMOVE_COMMENT:
       remove_comment = true;
       break;
 
     case 'R':
-      if (!strcmp (arg, ".comment"))
+      if (fnmatch (arg, ".comment", FNM_EXTMATCH) == 0)
        remove_comment = true;
-      else
-       {
-         argp_error (state,
-                     gettext ("-R option supports only .comment section"));
-         return EINVAL;
-       }
+      add_pattern (&remove_secs, arg);
+      break;
+
+    case OPT_KEEP_SECTION:
+      add_pattern (&keep_secs, arg);
       break;
 
     case 'g':
@@ -255,6 +327,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       remove_debug = true;
       break;
 
+    case OPT_STRIP_SECTIONS:
+      remove_shdrs = true;
+      break;
+
     case OPT_PERMISSIVE:
       permissive = true;
       break;
@@ -262,6 +338,16 @@ parse_opt (int key, char *arg, struct argp_state *state)
     case 's':                  /* Ignored for compatibility.  */
       break;
 
+    case ARGP_KEY_SUCCESS:
+      if (remove_comment == true
+         && section_name_matches (keep_secs, ".comment"))
+       {
+         argp_error (state,
+                     gettext ("cannot both keep and remove .comment section"));
+         return EINVAL;
+       }
+      break;
+
     default:
       return ARGP_ERR_UNKNOWN;
     }
@@ -275,38 +361,38 @@ process_file (const char *fname)
   /* If we have to preserve the modify and access timestamps get them
      now.  We cannot use fstat() after opening the file since the open
      would change the access time.  */
-  struct stat64 pre_st;
-  struct timeval tv[2];
+  struct stat pre_st;
+  struct timespec tv[2];
  again:
   if (preserve_dates)
     {
-      if (stat64 (fname, &pre_st) != 0)
+      if (stat (fname, &pre_st) != 0)
        {
-         error (0, errno, gettext ("cannot stat input file \"%s\""), fname);
+         error (0, errno, gettext ("cannot stat input file '%s'"), fname);
          return 1;
        }
 
       /* If we have to preserve the timestamp, we need it in the
         format utimes() understands.  */
-      TIMESPEC_TO_TIMEVAL (&tv[0], &pre_st.st_atim);
-      TIMESPEC_TO_TIMEVAL (&tv[1], &pre_st.st_mtim);
+      tv[0] = pre_st.st_atim;
+      tv[1] = pre_st.st_mtim;
     }
 
   /* Open the file.  */
   int fd = open (fname, output_fname == NULL ? O_RDWR : O_RDONLY);
   if (fd == -1)
     {
-      error (0, errno, gettext ("while opening \"%s\""), fname);
+      error (0, errno, gettext ("while opening '%s'"), fname);
       return 1;
     }
 
   /* We always use fstat() even if we called stat() before.  This is
      done to make sure the information returned by stat() is for the
      same file.  */
-  struct stat64 st;
-  if (fstat64 (fd, &st) != 0)
+  struct stat st;
+  if (fstat (fd, &st) != 0)
     {
-      error (0, errno, gettext ("cannot stat input file \"%s\""), fname);
+      error (0, errno, gettext ("cannot stat input file '%s'"), fname);
       return 1;
     }
   /* Paranoid mode on.  */
@@ -332,14 +418,24 @@ process_file (const char *fname)
     case ELF_K_AR:
       /* It is not possible to strip the content of an archive direct
         the output to a specific file.  */
-      if (unlikely (output_fname != NULL))
+      if (unlikely (output_fname != NULL || debug_fname != NULL))
        {
-         error (0, 0, gettext ("%s: cannot use -o when stripping archive"),
+         error (0, 0, gettext ("%s: cannot use -o or -f when stripping archive"),
                 fname);
          result = 1;
        }
       else
-       result = handle_ar (fd, elf, NULL, fname, preserve_dates ? tv : NULL);
+       {
+         /* We would like to support ar archives, but currently it just
+            doesn't work at all since we call elf_clone on the members
+            which doesn't really support ar members.
+            result = handle_ar (fd, elf, NULL, fname,
+                                preserve_dates ? tv : NULL);
+          */
+         error (0, 0, gettext ("%s: no support for stripping archive"),
+                fname);
+         result = 1;
+       }
       break;
 
     default:
@@ -362,25 +458,23 @@ process_file (const char *fname)
 
 static int
 handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
-           mode_t mode, struct timeval tvp[2])
+           mode_t mode, struct timespec tvp[2])
 {
   size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
   size_t fname_len = strlen (fname) + 1;
   char *fullname = alloca (prefix_len + 1 + fname_len);
   char *cp = fullname;
-  Elf *newelf;
   Elf *debugelf = NULL;
-  char *tmp_debug_fname = NULL;
+  tmp_debug_fname = NULL;
   int result = 0;
-  GElf_Ehdr ehdr_mem;
-  GElf_Ehdr *ehdr;
+  size_t shdridx = 0;
   size_t shstrndx;
-  size_t shnum;
   struct shdr_info
   {
     Elf_Scn *scn;
     GElf_Shdr shdr;
     Elf_Data *data;
+    Elf_Data *debug_data;
     const char *name;
     Elf32_Word idx;            /* Index in new file.  */
     Elf32_Word old_sh_link;    /* Original value of shdr.sh_link.  */
@@ -389,7 +483,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
     Elf32_Word group_idx;
     Elf32_Word group_cnt;
     Elf_Scn *newscn;
-    struct Ebl_Strent *se;
+    Dwelf_Strent *se;
     Elf32_Word *newsymidx;
   } *shdr_info = NULL;
   Elf_Scn *scn;
@@ -400,10 +494,11 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
   GElf_Ehdr *newehdr;
   GElf_Ehdr debugehdr_mem;
   GElf_Ehdr *debugehdr;
-  struct Ebl_Strtab *shst = NULL;
+  Dwelf_Strtab *shst = NULL;
   Elf_Data debuglink_crc_data;
   bool any_symtab_changes = false;
   Elf_Data *shstrtab_data = NULL;
+  void *debuglink_buf = NULL;
 
   /* Create the full name of the file.  */
   if (prefix != NULL)
@@ -424,12 +519,14 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
        }
     }
 
-  int debug_fd = -1;
+  debug_fd = -1;
 
-  /* Get the EBL handling.  The -g option is currently the only reason
-     we need EBL so dont open the backend unless necessary.  */
+  /* Get the EBL handling.  Removing all debugging symbols with the -g
+     option or resolving all relocations between debug sections with
+     the --reloc-debug-sections option are currently the only reasons
+     we need EBL so don't open the backend unless necessary.  */
   Ebl *ebl = NULL;
-  if (remove_debug)
+  if (remove_debug || reloc_debug)
     {
       ebl = ebl_openbackend (elf);
       if (ebl == NULL)
@@ -447,7 +544,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
         the debug file if the file would not contain any
         information.  */
       size_t debug_fname_len = strlen (debug_fname);
-      tmp_debug_fname = (char *) alloca (debug_fname_len + sizeof (".XXXXXX"));
+      tmp_debug_fname = (char *) xmalloc (debug_fname_len + sizeof (".XXXXXX"));
       strcpy (mempcpy (tmp_debug_fname, debug_fname, debug_fname_len),
              ".XXXXXX");
 
@@ -461,18 +558,31 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
     }
 
   /* Get the information from the old file.  */
-  ehdr = gelf_getehdr (elf, &ehdr_mem);
+  GElf_Ehdr ehdr_mem;
+  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
   if (ehdr == NULL)
     INTERNAL_ERROR (fname);
 
   /* Get the section header string table index.  */
-  if (unlikely (elf_getshstrndx (elf, &shstrndx) < 0))
-    error (EXIT_FAILURE, 0,
-          gettext ("cannot get section header string table index"));
+  if (unlikely (elf_getshdrstrndx (elf, &shstrndx) < 0))
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, 0,
+            gettext ("cannot get section header string table index"));
+    }
+
+  /* Get the number of phdrs in the old file.  */
+  size_t phnum;
+  if (elf_getphdrnum (elf, &phnum) != 0)
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, 0, gettext ("cannot get number of phdrs"));
+    }
 
   /* We now create a new ELF descriptor for the same file.  We
      construct it almost exactly in the same way with some information
      dropped.  */
+  Elf *newelf;
   if (output_fname != NULL)
     newelf = elf_begin (fd, ELF_C_WRITE_MMAP, NULL);
   else
@@ -480,21 +590,19 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
 
   if (unlikely (gelf_newehdr (newelf, gelf_getclass (elf)) == 0)
       || (ehdr->e_type != ET_REL
-         && unlikely (gelf_newphdr (newelf, ehdr->e_phnum) == 0)))
+         && unlikely (gelf_newphdr (newelf, phnum) == 0)))
     {
       error (0, 0, gettext ("cannot create new file '%s': %s"),
-            output_fname, elf_errmsg (-1));
+            output_fname ?: fname, elf_errmsg (-1));
       goto fail;
     }
 
   /* Copy over the old program header if needed.  */
   if (ehdr->e_type != ET_REL)
-    for (cnt = 0; cnt < ehdr->e_phnum; ++cnt)
+    for (cnt = 0; cnt < phnum; ++cnt)
       {
        GElf_Phdr phdr_mem;
-       GElf_Phdr *phdr;
-
-       phdr = gelf_getphdr (elf, cnt, &phdr_mem);
+       GElf_Phdr *phdr = gelf_getphdr (elf, cnt, &phdr_mem);
        if (phdr == NULL
            || unlikely (gelf_update_phdr (newelf, cnt, phdr) == 0))
          INTERNAL_ERROR (fname);
@@ -506,7 +614,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
       debugelf = elf_begin (debug_fd, ELF_C_WRITE_MMAP, NULL);
       if (unlikely (gelf_newehdr (debugelf, gelf_getclass (elf)) == 0)
          || (ehdr->e_type != ET_REL
-             && unlikely (gelf_newphdr (debugelf, ehdr->e_phnum) == 0)))
+             && unlikely (gelf_newphdr (debugelf, phnum) == 0)))
        {
          error (0, 0, gettext ("cannot create new file '%s': %s"),
                 debug_fname, elf_errmsg (-1));
@@ -515,12 +623,10 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
 
       /* Copy over the old program header if needed.  */
       if (ehdr->e_type != ET_REL)
-       for (cnt = 0; cnt < ehdr->e_phnum; ++cnt)
+       for (cnt = 0; cnt < phnum; ++cnt)
          {
            GElf_Phdr phdr_mem;
-           GElf_Phdr *phdr;
-
-           phdr = gelf_getphdr (elf, cnt, &phdr_mem);
+           GElf_Phdr *phdr = gelf_getphdr (elf, cnt, &phdr_mem);
            if (phdr == NULL
                || unlikely (gelf_update_phdr (debugelf, cnt, phdr) == 0))
              INTERNAL_ERROR (fname);
@@ -528,13 +634,19 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
     }
 
   /* Number of sections.  */
-  if (unlikely (elf_getshnum (elf, &shnum) < 0))
+  size_t shnum;
+  if (unlikely (elf_getshdrnum (elf, &shnum) < 0))
     {
       error (0, 0, gettext ("cannot determine number of sections: %s"),
             elf_errmsg (-1));
       goto fail_close;
     }
 
+  if (shstrndx >= shnum)
+    goto illformed;
+
+#define elf_assert(test) do { if (!(test)) goto illformed; } while (0)
+
   /* Storage for section information.  We leave room for two more
      entries since we unconditionally create a section header string
      table.  Maybe some weird tool created an ELF file without one.
@@ -549,6 +661,11 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
       memset (shdr_info, '\0', (shnum + 2) * sizeof (struct shdr_info));
     }
 
+  /* Track whether allocated sections all come before non-allocated ones.  */
+  bool seen_allocated = false;
+  bool seen_unallocated = false;
+  bool mixed_allocated_unallocated = false;
+
   /* Prepare section information data structure.  */
   scn = NULL;
   cnt = 1;
@@ -556,7 +673,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
     {
       /* This should always be true (i.e., there should not be any
         holes in the numbering).  */
-      assert (elf_ndxscn (scn) == cnt);
+      elf_assert (elf_ndxscn (scn) == cnt);
 
       shdr_info[cnt].scn = scn;
 
@@ -564,24 +681,63 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
       if (gelf_getshdr (scn, &shdr_info[cnt].shdr) == NULL)
        INTERNAL_ERROR (fname);
 
+      /* Normally (in non-ET_REL files) we see all allocated sections first,
+        then all non-allocated.  */
+      if ((shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) == 0)
+       seen_unallocated = true;
+      else
+       {
+         if (seen_unallocated && seen_allocated)
+           mixed_allocated_unallocated = true;
+         seen_allocated = true;
+       }
+
       /* Get the name of the section.  */
       shdr_info[cnt].name = elf_strptr (elf, shstrndx,
                                        shdr_info[cnt].shdr.sh_name);
       if (shdr_info[cnt].name == NULL)
        {
+       illformed:
          error (0, 0, gettext ("illformed file '%s'"), fname);
          goto fail_close;
        }
 
+      /* Sanity check the user.  */
+      if (section_name_matches (remove_secs, shdr_info[cnt].name))
+       {
+         if ((shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) != 0)
+           {
+             error (0, 0,
+                    gettext ("Cannot remove allocated section '%s'"),
+                    shdr_info[cnt].name);
+             result = 1;
+             goto fail_close;
+           }
+
+         if (section_name_matches (keep_secs, shdr_info[cnt].name))
+           {
+             error (0, 0,
+                    gettext ("Cannot both keep and remove section '%s'"),
+                    shdr_info[cnt].name);
+             result = 1;
+             goto fail_close;
+           }
+       }
+
       /* Mark them as present but not yet investigated.  */
       shdr_info[cnt].idx = 1;
 
       /* Remember the shdr.sh_link value.  */
       shdr_info[cnt].old_sh_link = shdr_info[cnt].shdr.sh_link;
+      if (shdr_info[cnt].old_sh_link >= shnum)
+       goto illformed;
 
       /* Sections in files other than relocatable object files which
-        are not loaded can be freely moved by us.  In relocatable
-        object files everything can be moved.  */
+        not loaded can be freely moved by us.  In theory we can also
+        freely move around allocated nobits sections.  But we don't
+        to keep the layout of all allocated sections as similar as
+        possible to the original file.  In relocatable object files
+        everything can be moved.  */
       if (ehdr->e_type == ET_REL
          || (shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) == 0)
        shdr_info[cnt].shdr.sh_offset = 0;
@@ -590,26 +746,30 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
         appropriate reference.  */
       if (unlikely (shdr_info[cnt].shdr.sh_type == SHT_SYMTAB_SHNDX))
        {
-         assert (shdr_info[shdr_info[cnt].shdr.sh_link].symtab_idx == 0);
+         elf_assert (shdr_info[shdr_info[cnt].shdr.sh_link].symtab_idx == 0);
          shdr_info[shdr_info[cnt].shdr.sh_link].symtab_idx = cnt;
        }
       else if (unlikely (shdr_info[cnt].shdr.sh_type == SHT_GROUP))
        {
-         Elf32_Word *grpref;
-         size_t inner;
-
          /* Cross-reference the sections contained in the section
             group.  */
          shdr_info[cnt].data = elf_getdata (shdr_info[cnt].scn, NULL);
-         if (shdr_info[cnt].data == NULL)
+         if (shdr_info[cnt].data == NULL
+             || shdr_info[cnt].data->d_size < sizeof (Elf32_Word))
            INTERNAL_ERROR (fname);
 
          /* XXX Fix for unaligned access.  */
-         grpref = (Elf32_Word *) shdr_info[cnt].data->d_buf;
+         Elf32_Word *grpref = (Elf32_Word *) shdr_info[cnt].data->d_buf;
+         size_t inner;
          for (inner = 1;
               inner < shdr_info[cnt].data->d_size / sizeof (Elf32_Word);
               ++inner)
-           shdr_info[grpref[inner]].group_idx = cnt;
+           {
+             if (grpref[inner] < shnum)
+               shdr_info[grpref[inner]].group_idx = cnt;
+             else
+               goto illformed;
+           }
 
          if (inner == 1 || (inner == 2 && (grpref[0] & GRP_COMDAT) == 0))
            /* If the section group contains only one element and this
@@ -620,7 +780,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
        }
       else if (unlikely (shdr_info[cnt].shdr.sh_type == SHT_GNU_versym))
        {
-         assert (shdr_info[shdr_info[cnt].shdr.sh_link].version_idx == 0);
+         elf_assert (shdr_info[shdr_info[cnt].shdr.sh_link].version_idx == 0);
          shdr_info[shdr_info[cnt].shdr.sh_link].version_idx = cnt;
        }
 
@@ -628,7 +788,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
         discarded right away.  */
       if ((shdr_info[cnt].shdr.sh_flags & SHF_GROUP) != 0)
        {
-         assert (shdr_info[cnt].group_idx != 0);
+         elf_assert (shdr_info[cnt].group_idx != 0);
 
          if (shdr_info[shdr_info[cnt].group_idx].idx == 0)
            {
@@ -651,29 +811,38 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
        know how to handle them
      - if a section is referred to from a section which is not removed
        in the sh_link or sh_info element it cannot be removed either
+     - the user might have explicitly said to remove or keep a section
   */
   for (cnt = 1; cnt < shnum; ++cnt)
-    /* Check whether the section can be removed.  */
-    if (ebl_section_strip_p (ebl, ehdr, &shdr_info[cnt].shdr,
-                            shdr_info[cnt].name, remove_comment,
-                            remove_debug))
+    /* Check whether the section can be removed.  Since we will create
+       a new .shstrtab assume it will be removed too.  */
+    if (remove_shdrs ? !(shdr_info[cnt].shdr.sh_flags & SHF_ALLOC)
+       : (ebl_section_strip_p (ebl, &shdr_info[cnt].shdr,
+                               shdr_info[cnt].name, remove_comment,
+                               remove_debug)
+          || cnt == shstrndx
+          || section_name_matches (remove_secs, shdr_info[cnt].name)))
       {
+       /* The user might want to explicitly keep this one.  */
+       if (section_name_matches (keep_secs, shdr_info[cnt].name))
+         continue;
+
        /* For now assume this section will be removed.  */
        shdr_info[cnt].idx = 0;
 
        idx = shdr_info[cnt].group_idx;
        while (idx != 0)
          {
+           /* The section group data is already loaded.  */
+           elf_assert (shdr_info[idx].data != NULL
+                       && shdr_info[idx].data->d_buf != NULL
+                       && shdr_info[idx].data->d_size >= sizeof (Elf32_Word));
+
            /* If the references section group is a normal section
               group and has one element remaining, or if it is an
               empty COMDAT section group it is removed.  */
-           bool is_comdat;
-
-           /* The section group data is already loaded.  */
-           assert (shdr_info[idx].data != NULL);
-
-           is_comdat = (((Elf32_Word *) shdr_info[idx].data->d_buf)[0]
-                        & GRP_COMDAT) != 0;
+           bool is_comdat = (((Elf32_Word *) shdr_info[idx].data->d_buf)[0]
+                             & GRP_COMDAT) != 0;
 
            --shdr_info[idx].group_cnt;
            if ((!is_comdat && shdr_info[idx].group_cnt == 1)
@@ -705,10 +874,35 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
            {
              /* If a relocation section is marked as being removed make
                 sure the section it is relocating is removed, too.  */
-             if ((shdr_info[cnt].shdr.sh_type == SHT_REL
+             if (shdr_info[cnt].shdr.sh_type == SHT_REL
                   || shdr_info[cnt].shdr.sh_type == SHT_RELA)
-                 && shdr_info[shdr_info[cnt].shdr.sh_info].idx != 0)
-               shdr_info[cnt].idx = 1;
+               {
+                 if (shdr_info[cnt].shdr.sh_info >= shnum)
+                   goto illformed;
+                 else if (shdr_info[shdr_info[cnt].shdr.sh_info].idx != 0)
+                   shdr_info[cnt].idx = 1;
+               }
+
+             /* If a group section is marked as being removed make
+                sure all the sections it contains are being removed, too.  */
+             if (shdr_info[cnt].shdr.sh_type == SHT_GROUP)
+               {
+                 Elf32_Word *grpref;
+                 grpref = (Elf32_Word *) shdr_info[cnt].data->d_buf;
+                 for (size_t in = 1;
+                      in < shdr_info[cnt].data->d_size / sizeof (Elf32_Word);
+                      ++in)
+                   if (grpref[in] < shnum)
+                     {
+                       if (shdr_info[grpref[in]].idx != 0)
+                         {
+                           shdr_info[cnt].idx = 1;
+                           break;
+                         }
+                     }
+                   else
+                     goto illformed;
+               }
            }
 
          if (shdr_info[cnt].idx == 1)
@@ -716,13 +910,11 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
              /* The content of symbol tables we don't remove must not
                 reference any section which we do remove.  Otherwise
                 we cannot remove the section.  */
-             if (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM
-                 || shdr_info[cnt].shdr.sh_type == SHT_SYMTAB)
+             if (debug_fname != NULL
+                 && shdr_info[cnt].debug_data == NULL
+                 && (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM
+                     || shdr_info[cnt].shdr.sh_type == SHT_SYMTAB))
                {
-                 Elf_Data *symdata;
-                 Elf_Data *xndxdata;
-                 size_t elsize;
-
                  /* Make sure the data is loaded.  */
                  if (shdr_info[cnt].data == NULL)
                    {
@@ -731,14 +923,14 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
                      if (shdr_info[cnt].data == NULL)
                        INTERNAL_ERROR (fname);
                    }
-                 symdata = shdr_info[cnt].data;
+                 Elf_Data *symdata = shdr_info[cnt].data;
 
                  /* If there is an extended section index table load it
                     as well.  */
                  if (shdr_info[cnt].symtab_idx != 0
                      && shdr_info[shdr_info[cnt].symtab_idx].data == NULL)
                    {
-                     assert (shdr_info[cnt].shdr.sh_type == SHT_SYMTAB);
+                     elf_assert (shdr_info[cnt].shdr.sh_type == SHT_SYMTAB);
 
                      shdr_info[shdr_info[cnt].symtab_idx].data
                        = elf_getdata (shdr_info[shdr_info[cnt].symtab_idx].scn,
@@ -746,11 +938,12 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
                      if (shdr_info[shdr_info[cnt].symtab_idx].data == NULL)
                        INTERNAL_ERROR (fname);
                    }
-                 xndxdata = shdr_info[shdr_info[cnt].symtab_idx].data;
+                 Elf_Data *xndxdata
+                   = shdr_info[shdr_info[cnt].symtab_idx].data;
 
                  /* Go through all symbols and make sure the section they
                     reference is not removed.  */
-                 elsize = gelf_fsize (elf, ELF_T_SYM, 1, ehdr->e_version);
+                 size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
 
                  for (size_t inner = 0;
                       inner < shdr_info[cnt].data->d_size / elsize;
@@ -758,15 +951,13 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
                    {
                      GElf_Sym sym_mem;
                      Elf32_Word xndx;
-                     GElf_Sym *sym;
-                     size_t scnidx;
-
-                     sym = gelf_getsymshndx (symdata, xndxdata, inner,
-                                             &sym_mem, &xndx);
+                     GElf_Sym *sym = gelf_getsymshndx (symdata, xndxdata,
+                                                       inner, &sym_mem,
+                                                       &xndx);
                      if (sym == NULL)
                        INTERNAL_ERROR (fname);
 
-                     scnidx = sym->st_shndx;
+                     size_t scnidx = sym->st_shndx;
                      if (scnidx == SHN_UNDEF || scnidx >= shnum
                          || (scnidx >= SHN_LORESERVE
                              && scnidx <= SHN_HIRESERVE
@@ -778,12 +969,25 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
                      else if (scnidx == SHN_XINDEX)
                        scnidx = xndx;
 
+                     if (scnidx >= shnum)
+                       goto illformed;
+
                      if (shdr_info[scnidx].idx == 0)
-                       {
-                         /* Mark this section as used.  */
-                         shdr_info[scnidx].idx = 1;
-                         changes |= scnidx < cnt;
-                       }
+                       /* This symbol table has a real symbol in
+                          a discarded section.  So preserve the
+                          original table in the debug file.  Unless
+                          it is a redundant data marker to a debug
+                          (data only) section.  */
+                       if (! (ebl_section_strip_p (ebl,
+                                                   &shdr_info[scnidx].shdr,
+                                                   shdr_info[scnidx].name,
+                                                   remove_comment,
+                                                   remove_debug)
+                              && ebl_data_marker_symbol (ebl, sym,
+                                       elf_strptr (elf,
+                                                   shdr_info[cnt].shdr.sh_link,
+                                                   sym->st_name))))
+                         shdr_info[cnt].debug_data = symdata;
                    }
                }
 
@@ -809,16 +1013,51 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
                }
 
              /* Handle references through sh_info.  */
-             if (SH_INFO_LINK_P (&shdr_info[cnt].shdr)
-                 && shdr_info[shdr_info[cnt].shdr.sh_info].idx == 0)
+             if (SH_INFO_LINK_P (&shdr_info[cnt].shdr))
                {
-                 shdr_info[shdr_info[cnt].shdr.sh_info].idx = 1;
-                 changes |= shdr_info[cnt].shdr.sh_info < cnt;
+                 if (shdr_info[cnt].shdr.sh_info >= shnum)
+                   goto illformed;
+                 else if ( shdr_info[shdr_info[cnt].shdr.sh_info].idx == 0)
+                   {
+                     shdr_info[shdr_info[cnt].shdr.sh_info].idx = 1;
+                     changes |= shdr_info[cnt].shdr.sh_info < cnt;
+                   }
                }
 
              /* Mark the section as investigated.  */
              shdr_info[cnt].idx = 2;
            }
+
+         if (debug_fname != NULL
+             && (shdr_info[cnt].idx == 0 || shdr_info[cnt].debug_data != NULL))
+           {
+             /* This section is being preserved in the debug file.
+                Sections it refers to must be preserved there too.
+
+                In this pass we mark sections to be preserved in both
+                files by setting the .debug_data pointer to the original
+                file's .data pointer.  Below, we'll copy the section
+                contents.  */
+
+             inline void check_preserved (size_t i)
+             {
+               if (i != 0 && i < shnum + 2 && shdr_info[i].idx != 0
+                   && shdr_info[i].debug_data == NULL)
+                 {
+                   if (shdr_info[i].data == NULL)
+                     shdr_info[i].data = elf_getdata (shdr_info[i].scn, NULL);
+                   if (shdr_info[i].data == NULL)
+                     INTERNAL_ERROR (fname);
+
+                   shdr_info[i].debug_data = shdr_info[i].data;
+                   changes |= i < cnt;
+                 }
+             }
+
+             check_preserved (shdr_info[cnt].shdr.sh_link);
+             if (SH_INFO_LINK_P (&shdr_info[cnt].shdr))
+               check_preserved (shdr_info[cnt].shdr.sh_info);
+           }
        }
     }
   while (changes);
@@ -829,24 +1068,27 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
     {
       for (cnt = 1; cnt < shnum; ++cnt)
        {
-         Elf_Data *debugdata;
-         GElf_Shdr debugshdr;
-         bool discard_section;
-
          scn = elf_newscn (debugelf);
          if (scn == NULL)
-           error (EXIT_FAILURE, 0,
-                  gettext ("while generating output file: %s"),
-                  elf_errmsg (-1));
+           {
+             cleanup_debug ();
+             error (EXIT_FAILURE, 0,
+                    gettext ("while generating output file: %s"),
+                    elf_errmsg (-1));
+           }
 
-         discard_section = shdr_info[cnt].idx > 0 && cnt != ehdr->e_shstrndx;
+         bool discard_section = (shdr_info[cnt].idx > 0
+                                 && shdr_info[cnt].debug_data == NULL
+                                 && shdr_info[cnt].shdr.sh_type != SHT_NOTE
+                                 && shdr_info[cnt].shdr.sh_type != SHT_GROUP
+                                 && cnt != shstrndx);
 
          /* Set the section header in the new file.  */
-         debugshdr = shdr_info[cnt].shdr;
+         GElf_Shdr debugshdr = shdr_info[cnt].shdr;
          if (discard_section)
            debugshdr.sh_type = SHT_NOBITS;
 
-         if (unlikely (gelf_update_shdr (scn, &debugshdr)) == 0)
+         if (unlikely (gelf_update_shdr (scn, &debugshdr) == 0))
            /* There cannot be any overflows.  */
            INTERNAL_ERROR (fname);
 
@@ -859,7 +1101,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
            }
 
          /* Set the data.  This is done by copying from the old file.  */
-         debugdata = elf_newdata (scn);
+         Elf_Data *debugdata = elf_newdata (scn);
          if (debugdata == NULL)
            INTERNAL_ERROR (fname);
 
@@ -868,6 +1110,16 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
          *debugdata = *shdr_info[cnt].data;
          if (discard_section)
            debugdata->d_buf = NULL;
+         else if (shdr_info[cnt].debug_data != NULL
+                  || shdr_info[cnt].shdr.sh_type == SHT_GROUP)
+           {
+             /* Copy the original data before it gets modified.  */
+             shdr_info[cnt].debug_data = debugdata;
+             if (debugdata->d_buf == NULL)
+               INTERNAL_ERROR (fname);
+             debugdata->d_buf = memcpy (xmalloc (debugdata->d_size),
+                                        debugdata->d_buf, debugdata->d_size);
+           }
        }
 
       /* Finish the ELF header.  Fill in the fields not handled by
@@ -882,9 +1134,44 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
       debugehdr->e_version = ehdr->e_version;
       debugehdr->e_entry = ehdr->e_entry;
       debugehdr->e_flags = ehdr->e_flags;
-      debugehdr->e_shstrndx = ehdr->e_shstrndx;
 
-      if (unlikely (gelf_update_ehdr (debugelf, debugehdr)) == 0)
+      size_t shdrstrndx;
+      if (elf_getshdrstrndx (elf, &shdrstrndx) < 0)
+       {
+         error (0, 0, gettext ("%s: error while getting shdrstrndx: %s"),
+                fname, elf_errmsg (-1));
+         result = 1;
+         goto fail_close;
+       }
+
+      if (shstrndx < SHN_LORESERVE)
+       debugehdr->e_shstrndx = shdrstrndx;
+      else
+       {
+         debugehdr->e_shstrndx = SHN_XINDEX;
+         Elf_Scn *scn0 = elf_getscn (debugelf, 0);
+         GElf_Shdr shdr0_mem;
+         GElf_Shdr *shdr0 = gelf_getshdr (scn0, &shdr0_mem);
+         if (shdr0 == NULL)
+           {
+             error (0, 0, gettext ("%s: error getting zero section: %s"),
+                    debug_fname, elf_errmsg (-1));
+             result = 1;
+             goto fail_close;
+           }
+
+         shdr0->sh_link = shdrstrndx;
+         if (gelf_update_shdr (scn0, shdr0) == 0)
+           {
+             error (0, 0, gettext ("%s: error while updating zero section: %s"),
+                    debug_fname, elf_errmsg (-1));
+             result = 1;
+             goto fail_close;
+           }
+
+       }
+
+      if (unlikely (gelf_update_ehdr (debugelf, debugehdr) == 0))
        {
          error (0, 0, gettext ("%s: error while creating ELF header: %s"),
                 debug_fname, elf_errmsg (-1));
@@ -893,15 +1180,19 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
        }
     }
 
-  /* Mark the section header string table as unused, we will create
-     a new one.  */
-  shdr_info[shstrndx].idx = 0;
+  /* Although we always create a new section header string table we
+     don't explicitly mark the existing one as unused.  It can still
+     be used through a symbol table section we are keeping.  If not it
+     will already be marked as unused.  */
 
   /* We need a string table for the section headers.  */
-  shst = ebl_strtabinit (true);
+  shst = dwelf_strtab_init (true);
   if (shst == NULL)
-    error (EXIT_FAILURE, errno, gettext ("while preparing output for '%s'"),
-          output_fname ?: fname);
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, errno, gettext ("while preparing output for '%s'"),
+            output_fname ?: fname);
+    }
 
   /* Assign new section numbers.  */
   shdr_info[0].idx = 0;
@@ -913,28 +1204,33 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
        /* Create a new section.  */
        shdr_info[cnt].newscn = elf_newscn (newelf);
        if (shdr_info[cnt].newscn == NULL)
-         error (EXIT_FAILURE, 0, gettext ("while generating output file: %s"),
-                elf_errmsg (-1));
+         {
+           cleanup_debug ();
+           error (EXIT_FAILURE, 0,
+                  gettext ("while generating output file: %s"),
+                  elf_errmsg (-1));
+         }
 
-       assert (elf_ndxscn (shdr_info[cnt].newscn) == shdr_info[cnt].idx);
+       elf_assert (elf_ndxscn (shdr_info[cnt].newscn) == shdr_info[cnt].idx);
 
        /* Add this name to the section header string table.  */
-       shdr_info[cnt].se = ebl_strtabadd (shst, shdr_info[cnt].name, 0);
+       shdr_info[cnt].se = dwelf_strtab_add (shst, shdr_info[cnt].name);
       }
 
-  /* Test whether we are doing anything at all.  */
-  if (cnt == idx)
-    /* Nope, all removable sections are already gone.  */
-    goto fail_close;
+  /* Test whether we are doing anything at all.  Either all removable
+     sections are already gone.  Or the only section we would remove is
+     the .shstrtab section which we would add again.  */
+  bool removing_sections = !(cnt == idx
+                            || (cnt == idx + 1
+                                && shdr_info[shstrndx].idx == 0));
+  if (output_fname == NULL && !removing_sections)
+      goto fail_close;
 
-  /* Create the reference to the file with the debug info.  */
-  if (debug_fname != NULL)
+  /* Create the reference to the file with the debug info (if any).  */
+  if (debug_fname != NULL && !remove_shdrs && removing_sections)
     {
-      char *debug_basename;
-      off_t crc_offset;
-
       /* Add the section header string table section name.  */
-      shdr_info[cnt].se = ebl_strtabadd (shst, ".gnu_debuglink", 15);
+      shdr_info[cnt].se = dwelf_strtab_add_len (shst, ".gnu_debuglink", 15);
       shdr_info[cnt].idx = idx++;
 
       /* Create the section header.  */
@@ -953,25 +1249,32 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
       /* Create the section.  */
       shdr_info[cnt].newscn = elf_newscn (newelf);
       if (shdr_info[cnt].newscn == NULL)
-       error (EXIT_FAILURE, 0,
-              gettext ("while create section header section: %s"),
-              elf_errmsg (-1));
-      assert (elf_ndxscn (shdr_info[cnt].newscn) == shdr_info[cnt].idx);
+       {
+         cleanup_debug ();
+         error (EXIT_FAILURE, 0,
+                gettext ("while create section header section: %s"),
+                elf_errmsg (-1));
+       }
+      elf_assert (elf_ndxscn (shdr_info[cnt].newscn) == shdr_info[cnt].idx);
 
       shdr_info[cnt].data = elf_newdata (shdr_info[cnt].newscn);
       if (shdr_info[cnt].data == NULL)
-       error (EXIT_FAILURE, 0, gettext ("cannot allocate section data: %s"),
-              elf_errmsg (-1));
+       {
+         cleanup_debug ();
+         error (EXIT_FAILURE, 0, gettext ("cannot allocate section data: %s"),
+                elf_errmsg (-1));
+       }
 
-      debug_basename = basename (debug_fname_embed ?: debug_fname);
-      crc_offset = strlen (debug_basename) + 1;
+      char *debug_basename = basename (debug_fname_embed ?: debug_fname);
+      off_t crc_offset = strlen (debug_basename) + 1;
       /* Align to 4 byte boundary */
       crc_offset = ((crc_offset - 1) & ~3) + 4;
 
       shdr_info[cnt].data->d_align = 4;
       shdr_info[cnt].shdr.sh_size = shdr_info[cnt].data->d_size
        = crc_offset + 4;
-      shdr_info[cnt].data->d_buf = xcalloc (1, shdr_info[cnt].data->d_size);
+      debuglink_buf = xcalloc (1, shdr_info[cnt].data->d_size);
+      shdr_info[cnt].data->d_buf = debuglink_buf;
 
       strcpy (shdr_info[cnt].data->d_buf, debug_basename);
 
@@ -987,10 +1290,10 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
     }
 
   /* Index of the section header table in the shdr_info array.  */
-  size_t shdridx = cnt;
+  shdridx = cnt;
 
   /* Add the section header string table section name.  */
-  shdr_info[cnt].se = ebl_strtabadd (shst, ".shstrtab", 10);
+  shdr_info[cnt].se = dwelf_strtab_add_len (shst, ".shstrtab", 10);
   shdr_info[cnt].idx = idx;
 
   /* Create the section header.  */
@@ -1009,19 +1312,30 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
   /* Create the section.  */
   shdr_info[cnt].newscn = elf_newscn (newelf);
   if (shdr_info[cnt].newscn == NULL)
-    error (EXIT_FAILURE, 0,
-          gettext ("while create section header section: %s"),
-          elf_errmsg (-1));
-  assert (elf_ndxscn (shdr_info[cnt].newscn) == idx);
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, 0,
+            gettext ("while create section header section: %s"),
+            elf_errmsg (-1));
+    }
+  elf_assert (elf_ndxscn (shdr_info[cnt].newscn) == idx);
 
   /* Finalize the string table and fill in the correct indices in the
      section headers.  */
   shstrtab_data = elf_newdata (shdr_info[cnt].newscn);
   if (shstrtab_data == NULL)
-    error (EXIT_FAILURE, 0,
-          gettext ("while create section header string table: %s"),
-          elf_errmsg (-1));
-  ebl_strtabfinalize (shst, shstrtab_data);
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, 0,
+            gettext ("while create section header string table: %s"),
+            elf_errmsg (-1));
+    }
+  if (dwelf_strtab_finalize (shst, shstrtab_data) == NULL)
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, 0,
+            gettext ("no memory to create section header string table"));
+    }
 
   /* We have to set the section size.  */
   shdr_info[cnt].shdr.sh_size = shstrtab_data->d_size;
@@ -1034,26 +1348,33 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
        Elf_Data *newdata;
 
        scn = elf_getscn (newelf, shdr_info[cnt].idx);
-       assert (scn != NULL);
+       elf_assert (scn != NULL);
 
        /* Update the name.  */
-       shdr_info[cnt].shdr.sh_name = ebl_strtaboffset (shdr_info[cnt].se);
+       shdr_info[cnt].shdr.sh_name = dwelf_strent_off (shdr_info[cnt].se);
 
        /* Update the section header from the input file.  Some fields
-          might be section indeces which now have to be adjusted.  */
+          might be section indeces which now have to be adjusted.  Keep
+          the index to the "current" sh_link in case we need it to lookup
+          symbol table names.  */
+       size_t sh_link = shdr_info[cnt].shdr.sh_link;
        if (shdr_info[cnt].shdr.sh_link != 0)
          shdr_info[cnt].shdr.sh_link =
            shdr_info[shdr_info[cnt].shdr.sh_link].idx;
 
        if (shdr_info[cnt].shdr.sh_type == SHT_GROUP)
          {
-           assert (shdr_info[cnt].data != NULL);
+           elf_assert (shdr_info[cnt].data != NULL
+                       && shdr_info[cnt].data->d_buf != NULL);
 
            Elf32_Word *grpref = (Elf32_Word *) shdr_info[cnt].data->d_buf;
            for (size_t inner = 0;
                 inner < shdr_info[cnt].data->d_size / sizeof (Elf32_Word);
                 ++inner)
-             grpref[inner] = shdr_info[grpref[inner]].idx;
+             if (grpref[inner] < shnum)
+               grpref[inner] = shdr_info[grpref[inner]].idx;
+             else
+               goto illformed;
          }
 
        /* Handle the SHT_REL, SHT_RELA, and SHF_INFO_LINK flag.  */
@@ -1062,7 +1383,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
            shdr_info[shdr_info[cnt].shdr.sh_info].idx;
 
        /* Get the data from the old file if necessary.  We already
-           created the data for the section header string table.  */
+          created the data for the section header string table.  */
        if (cnt < shnum)
          {
            if (shdr_info[cnt].data == NULL)
@@ -1083,7 +1404,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
            /* We know the size.  */
            shdr_info[cnt].shdr.sh_size = shdr_info[cnt].data->d_size;
 
-           /* We have to adjust symtol tables.  The st_shndx member might
+           /* We have to adjust symbol tables.  The st_shndx member might
               have to be updated.  */
            if (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM
                || shdr_info[cnt].shdr.sh_type == SHT_SYMTAB)
@@ -1091,32 +1412,35 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
                Elf_Data *versiondata = NULL;
                Elf_Data *shndxdata = NULL;
 
-               size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1,
-                                           ehdr->e_version);
+               size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
 
                if (shdr_info[cnt].symtab_idx != 0)
                  {
-                   assert (shdr_info[cnt].shdr.sh_type == SHT_SYMTAB_SHNDX);
+                   elf_assert (shdr_info[cnt].shdr.sh_type == SHT_SYMTAB_SHNDX);
                    /* This section has extended section information.
                       We have to modify that information, too.  */
                    shndxdata = elf_getdata (shdr_info[shdr_info[cnt].symtab_idx].scn,
                                             NULL);
 
-                   assert ((versiondata->d_size / sizeof (Elf32_Word))
-                           >= shdr_info[cnt].data->d_size / elsize);
+                   elf_assert (shndxdata != NULL
+                               && shndxdata->d_buf != NULL
+                               && ((shndxdata->d_size / sizeof (Elf32_Word))
+                                   >= shdr_info[cnt].data->d_size / elsize));
                  }
 
                if (shdr_info[cnt].version_idx != 0)
                  {
-                   assert (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM);
+                   elf_assert (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM);
                    /* This section has associated version
                       information.  We have to modify that
                       information, too.  */
                    versiondata = elf_getdata (shdr_info[shdr_info[cnt].version_idx].scn,
                                               NULL);
 
-                   assert ((versiondata->d_size / sizeof (GElf_Versym))
-                           >= shdr_info[cnt].data->d_size / elsize);
+                   elf_assert (versiondata != NULL
+                               && versiondata->d_buf != NULL
+                               && ((versiondata->d_size / sizeof (GElf_Versym))
+                                   >= shdr_info[cnt].data->d_size / elsize));
                  }
 
                shdr_info[cnt].newsymidx
@@ -1166,14 +1490,12 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
 
                    /* Get the full section index, if necessary from the
                       XINDEX table.  */
-                   if (sym->st_shndx != SHN_XINDEX)
-                     sec = shdr_info[sym->st_shndx].idx;
-                   else
-                     {
-                       assert (shndxdata != NULL);
-
-                       sec = shdr_info[xshndx].idx;
-                     }
+                   if (sym->st_shndx == SHN_XINDEX)
+                     elf_assert (shndxdata != NULL
+                                 && shndxdata->d_buf != NULL);
+                   size_t sidx = (sym->st_shndx != SHN_XINDEX
+                                  ? sym->st_shndx : xshndx);
+                   sec = shdr_info[sidx].idx;
 
                    if (sec != 0)
                      {
@@ -1191,7 +1513,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
                            nxshndx = sec;
                          }
 
-                       assert (sec < SHN_LORESERVE || shndxdata != NULL);
+                       elf_assert (sec < SHN_LORESERVE || shndxdata != NULL);
 
                        if ((inner != destidx || nshndx != sym->st_shndx
                             || (shndxdata != NULL && nxshndx != xshndx))
@@ -1211,10 +1533,41 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
                            shdr_info[cnt].shdr.sh_info = destidx - 1;
                          }
                      }
-                   else
-                     /* This is a section symbol for a section which has
-                        been removed.  */
-                     assert (GELF_ST_TYPE (sym->st_info) == STT_SECTION);
+                   else if ((shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) != 0
+                            && GELF_ST_TYPE (sym->st_info) != STT_SECTION
+                            && shdr_info[sidx].shdr.sh_type != SHT_GROUP)
+                     {
+                       /* Removing a real symbol from an allocated
+                          symbol table is hard and probably a
+                          mistake.  Really removing it means
+                          rewriting the dynamic segment and hash
+                          sections.  Just warn and set the symbol
+                          section to UNDEF.  */
+                       error (0, 0,
+                              gettext ("Cannot remove symbol [%zd] from allocated symbol table [%zd]"), inner, cnt);
+                       sym->st_shndx = SHN_UNDEF;
+                       if (gelf_update_sym (shdr_info[cnt].data, destidx,
+                                            sym) == 0)
+                         INTERNAL_ERROR (fname);
+                       shdr_info[cnt].newsymidx[inner] = destidx++;
+                     }
+                   else if (debug_fname != NULL
+                            && shdr_info[cnt].debug_data == NULL)
+                     /* The symbol points to a section that is discarded
+                        but isn't preserved in the debug file. Check that
+                        this is a section or group signature symbol
+                        for a section which has been removed.  Or a special
+                        data marker symbol to a debug section.  */
+                     {
+                       elf_assert (GELF_ST_TYPE (sym->st_info) == STT_SECTION
+                                   || ((shdr_info[sidx].shdr.sh_type
+                                        == SHT_GROUP)
+                                       && (shdr_info[sidx].shdr.sh_info
+                                           == inner))
+                                   || ebl_data_marker_symbol (ebl, sym,
+                                               elf_strptr (elf, sh_link,
+                                                           sym->st_name)));
+                     }
                  }
 
                if (destidx != inner)
@@ -1233,320 +1586,637 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
              }
          }
 
-       /* If we have to, compute the offset of the section.  */
-       if (shdr_info[cnt].shdr.sh_offset == 0)
-         shdr_info[cnt].shdr.sh_offset
-           = ((lastoffset + shdr_info[cnt].shdr.sh_addralign - 1)
-              & ~((GElf_Off) (shdr_info[cnt].shdr.sh_addralign - 1)));
-
-       /* Set the section header in the new file.  */
-       if (unlikely (gelf_update_shdr (scn, &shdr_info[cnt].shdr) == 0))
-         /* There cannot be any overflows.  */
-         INTERNAL_ERROR (fname);
+       /* If we have to, compute the offset of the section.
+          If allocate and unallocated sections are mixed, we only update
+          the allocated ones now.  The unallocated ones come second.  */
+       if (! mixed_allocated_unallocated
+           || (shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) != 0)
+         {
+           if (shdr_info[cnt].shdr.sh_offset == 0)
+             shdr_info[cnt].shdr.sh_offset
+               = ((lastoffset + shdr_info[cnt].shdr.sh_addralign - 1)
+                  & ~((GElf_Off) (shdr_info[cnt].shdr.sh_addralign - 1)));
+
+           /* Set the section header in the new file.  */
+           if (unlikely (gelf_update_shdr (scn, &shdr_info[cnt].shdr) == 0))
+             /* There cannot be any overflows.  */
+             INTERNAL_ERROR (fname);
 
-       /* Remember the last section written so far.  */
-       GElf_Off filesz = (shdr_info[cnt].shdr.sh_type != SHT_NOBITS
-                          ? shdr_info[cnt].shdr.sh_size : 0);
-       if (lastoffset < shdr_info[cnt].shdr.sh_offset + filesz)
-         lastoffset = shdr_info[cnt].shdr.sh_offset + filesz;
+           /* Remember the last section written so far.  */
+           GElf_Off filesz = (shdr_info[cnt].shdr.sh_type != SHT_NOBITS
+                              ? shdr_info[cnt].shdr.sh_size : 0);
+           if (lastoffset < shdr_info[cnt].shdr.sh_offset + filesz)
+             lastoffset = shdr_info[cnt].shdr.sh_offset + filesz;
+         }
       }
 
+  /* We might have to update the unallocated sections after we done the
+     allocated ones.  lastoffset is set to right after the last allocated
+     section.  */
+  if (mixed_allocated_unallocated)
+    for (cnt = 1; cnt <= shdridx; ++cnt)
+      if (shdr_info[cnt].idx > 0)
+       {
+         scn = elf_getscn (newelf, shdr_info[cnt].idx);
+         if ((shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) == 0)
+           {
+             if (shdr_info[cnt].shdr.sh_offset == 0)
+               shdr_info[cnt].shdr.sh_offset
+                 = ((lastoffset + shdr_info[cnt].shdr.sh_addralign - 1)
+                    & ~((GElf_Off) (shdr_info[cnt].shdr.sh_addralign - 1)));
+
+             /* Set the section header in the new file.  */
+             if (unlikely (gelf_update_shdr (scn, &shdr_info[cnt].shdr) == 0))
+               /* There cannot be any overflows.  */
+               INTERNAL_ERROR (fname);
+
+             /* Remember the last section written so far.  */
+             GElf_Off filesz = (shdr_info[cnt].shdr.sh_type != SHT_NOBITS
+                                ? shdr_info[cnt].shdr.sh_size : 0);
+             if (lastoffset < shdr_info[cnt].shdr.sh_offset + filesz)
+               lastoffset = shdr_info[cnt].shdr.sh_offset + filesz;
+           }
+       }
+
   /* Adjust symbol references if symbol tables changed.  */
   if (any_symtab_changes)
-    {
-      /* Find all relocation sections which use this
-        symbol table.  */
-      for (cnt = 1; cnt <= shdridx; ++cnt)
+    /* Find all relocation sections which use this symbol table.  */
+    for (cnt = 1; cnt <= shdridx; ++cnt)
+      {
+       /* Update section headers when the data size has changed.
+          We also update the SHT_NOBITS section in the debug
+          file so that the section headers match in sh_size.  */
+       inline void update_section_size (const Elf_Data *newdata)
        {
-         if (shdr_info[cnt].idx == 0 && debug_fname == NULL)
-           /* Ignore sections which are discarded.  When we are saving a
-              relocation section in a separate debug file, we must fix up
-              the symbol table references.  */
-           continue;
-
-         if (shdr_info[cnt].shdr.sh_type == SHT_REL
-             || shdr_info[cnt].shdr.sh_type == SHT_RELA)
+         GElf_Shdr shdr_mem;
+         GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+         shdr->sh_size = newdata->d_size;
+         (void) gelf_update_shdr (scn, shdr);
+         if (debugelf != NULL)
+           {
+             /* libelf will use d_size to set sh_size.  */
+             Elf_Data *debugdata = elf_getdata (elf_getscn (debugelf,
+                                                            cnt), NULL);
+             if (debugdata == NULL)
+               INTERNAL_ERROR (fname);
+             debugdata->d_size = newdata->d_size;
+           }
+       }
+
+       if (shdr_info[cnt].idx == 0 && debug_fname == NULL)
+         /* Ignore sections which are discarded.  When we are saving a
+            relocation section in a separate debug file, we must fix up
+            the symbol table references.  */
+         continue;
+
+       const Elf32_Word symtabidx = shdr_info[cnt].old_sh_link;
+       elf_assert (symtabidx < shnum + 2);
+       const Elf32_Word *const newsymidx = shdr_info[symtabidx].newsymidx;
+       switch (shdr_info[cnt].shdr.sh_type)
+         {
+           inline bool no_symtab_updates (void)
            {
              /* If the symbol table hasn't changed, do not do anything.  */
-             if (shdr_info[shdr_info[cnt].old_sh_link].newsymidx == NULL)
-               continue;
+             if (shdr_info[symtabidx].newsymidx == NULL)
+               return true;
 
-             Elf32_Word *newsymidx
-               = shdr_info[shdr_info[cnt].old_sh_link].newsymidx;
-             Elf_Data *d = elf_getdata (shdr_info[cnt].idx == 0
-                                        ? elf_getscn (debugelf, cnt)
-                                        : elf_getscn (newelf,
-                                                      shdr_info[cnt].idx),
-                                        NULL);
-             assert (d != NULL);
-             size_t nrels = (shdr_info[cnt].shdr.sh_size
-                             / shdr_info[cnt].shdr.sh_entsize);
-
-             if (shdr_info[cnt].shdr.sh_type == SHT_REL)
-               for (size_t relidx = 0; relidx < nrels; ++relidx)
+             /* If the symbol table is not discarded, but additionally
+                duplicated in the separate debug file and this section
+                is discarded, don't adjust anything.  */
+             return (shdr_info[cnt].idx == 0
+                     && shdr_info[symtabidx].debug_data != NULL);
+           }
+
+         case SHT_REL:
+         case SHT_RELA:
+           if (no_symtab_updates ())
+             break;
+
+           Elf_Data *d = elf_getdata (shdr_info[cnt].idx == 0
+                                      ? elf_getscn (debugelf, cnt)
+                                      : elf_getscn (newelf,
+                                                    shdr_info[cnt].idx),
+                                      NULL);
+           elf_assert (d != NULL && d->d_buf != NULL
+                       && shdr_info[cnt].shdr.sh_entsize != 0);
+           size_t nrels = (shdr_info[cnt].shdr.sh_size
+                           / shdr_info[cnt].shdr.sh_entsize);
+
+           size_t symsize = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
+           const Elf32_Word symidxn = (shdr_info[symtabidx].data->d_size
+                                       / symsize);
+           if (shdr_info[cnt].shdr.sh_type == SHT_REL)
+             for (size_t relidx = 0; relidx < nrels; ++relidx)
+               {
+                 GElf_Rel rel_mem;
+                 if (gelf_getrel (d, relidx, &rel_mem) == NULL)
+                   INTERNAL_ERROR (fname);
+
+                 size_t symidx = GELF_R_SYM (rel_mem.r_info);
+                 elf_assert (symidx < symidxn);
+                 if (newsymidx[symidx] != symidx)
+                   {
+                     rel_mem.r_info
+                       = GELF_R_INFO (newsymidx[symidx],
+                                      GELF_R_TYPE (rel_mem.r_info));
+
+                     if (gelf_update_rel (d, relidx, &rel_mem) == 0)
+                       INTERNAL_ERROR (fname);
+                   }
+               }
+           else
+             for (size_t relidx = 0; relidx < nrels; ++relidx)
+               {
+                 GElf_Rela rel_mem;
+                 if (gelf_getrela (d, relidx, &rel_mem) == NULL)
+                   INTERNAL_ERROR (fname);
+
+                 size_t symidx = GELF_R_SYM (rel_mem.r_info);
+                 elf_assert (symidx < symidxn);
+                 if (newsymidx[symidx] != symidx)
+                   {
+                     rel_mem.r_info
+                       = GELF_R_INFO (newsymidx[symidx],
+                                      GELF_R_TYPE (rel_mem.r_info));
+
+                     if (gelf_update_rela (d, relidx, &rel_mem) == 0)
+                       INTERNAL_ERROR (fname);
+                   }
+               }
+           break;
+
+         case SHT_HASH:
+           if (no_symtab_updates ())
+             break;
+
+           /* We have to recompute the hash table.  */
+
+           elf_assert (shdr_info[cnt].idx > 0);
+
+           /* The hash section in the new file.  */
+           scn = elf_getscn (newelf, shdr_info[cnt].idx);
+
+           /* The symbol table data.  */
+           Elf_Data *symd = elf_getdata (elf_getscn (newelf,
+                                                     shdr_info[symtabidx].idx),
+                                         NULL);
+           elf_assert (symd != NULL && symd->d_buf != NULL);
+
+           /* The hash table data.  */
+           Elf_Data *hashd = elf_getdata (scn, NULL);
+           elf_assert (hashd != NULL && hashd->d_buf != NULL);
+
+           if (shdr_info[cnt].shdr.sh_entsize == sizeof (Elf32_Word))
+             {
+               /* Sane arches first.  */
+               elf_assert (hashd->d_size >= 2 * sizeof (Elf32_Word));
+               Elf32_Word *bucket = (Elf32_Word *) hashd->d_buf;
+
+               size_t strshndx = shdr_info[symtabidx].old_sh_link;
+               size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
+
+               Elf32_Word nchain = bucket[1];
+               Elf32_Word nbucket = bucket[0];
+               uint64_t used_buf = ((2ULL + nchain + nbucket)
+                                    * sizeof (Elf32_Word));
+               elf_assert (used_buf <= hashd->d_size);
+
+               /* Adjust the nchain value.  The symbol table size
+                  changed.  We keep the same size for the bucket array.  */
+               bucket[1] = symd->d_size / elsize;
+               bucket += 2;
+               Elf32_Word *chain = bucket + nbucket;
+
+               /* New size of the section.  */
+               size_t n_size = ((2 + symd->d_size / elsize + nbucket)
+                                * sizeof (Elf32_Word));
+               elf_assert (n_size <= hashd->d_size);
+               hashd->d_size = n_size;
+               update_section_size (hashd);
+
+               /* Clear the arrays.  */
+               memset (bucket, '\0',
+                       (symd->d_size / elsize + nbucket)
+                       * sizeof (Elf32_Word));
+
+               for (size_t inner = shdr_info[symtabidx].shdr.sh_info;
+                    inner < symd->d_size / elsize; ++inner)
                  {
-                   GElf_Rel rel_mem;
-                   if (gelf_getrel (d, relidx, &rel_mem) == NULL)
-                     INTERNAL_ERROR (fname);
+                   GElf_Sym sym_mem;
+                   GElf_Sym *sym = gelf_getsym (symd, inner, &sym_mem);
+                   elf_assert (sym != NULL);
 
-                   size_t symidx = GELF_R_SYM (rel_mem.r_info);
-                   if (newsymidx[symidx] != symidx)
+                   const char *name = elf_strptr (elf, strshndx,
+                                                  sym->st_name);
+                   elf_assert (name != NULL && nbucket != 0);
+                   size_t hidx = elf_hash (name) % nbucket;
+
+                   if (bucket[hidx] == 0)
+                     bucket[hidx] = inner;
+                   else
                      {
-                       rel_mem.r_info
-                         = GELF_R_INFO (newsymidx[symidx],
-                                        GELF_R_TYPE (rel_mem.r_info));
+                       hidx = bucket[hidx];
 
-                       if (gelf_update_rel (d, relidx, &rel_mem) == 0)
-                         INTERNAL_ERROR (fname);
+                       while (chain[hidx] != 0 && chain[hidx] < nchain)
+                         hidx = chain[hidx];
+
+                       chain[hidx] = inner;
                      }
                  }
-             else
-               for (size_t relidx = 0; relidx < nrels; ++relidx)
+             }
+           else
+             {
+               /* Alpha and S390 64-bit use 64-bit SHT_HASH entries.  */
+               elf_assert (shdr_info[cnt].shdr.sh_entsize
+                           == sizeof (Elf64_Xword));
+
+               Elf64_Xword *bucket = (Elf64_Xword *) hashd->d_buf;
+
+               size_t strshndx = shdr_info[symtabidx].old_sh_link;
+               size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
+
+               elf_assert (symd->d_size >= 2 * sizeof (Elf64_Xword));
+               Elf64_Xword nbucket = bucket[0];
+               Elf64_Xword nchain = bucket[1];
+               uint64_t maxwords = hashd->d_size / sizeof (Elf64_Xword);
+               elf_assert (maxwords >= 2
+                           && maxwords - 2 >= nbucket
+                           && maxwords - 2 - nbucket >= nchain);
+
+               /* Adjust the nchain value.  The symbol table size
+                  changed.  We keep the same size for the bucket array.  */
+               bucket[1] = symd->d_size / elsize;
+               bucket += 2;
+               Elf64_Xword *chain = bucket + nbucket;
+
+               /* New size of the section.  */
+               size_t n_size = ((2 + symd->d_size / elsize + nbucket)
+                                * sizeof (Elf64_Xword));
+               elf_assert (n_size <= hashd->d_size);
+               hashd->d_size = n_size;
+               update_section_size (hashd);
+
+               /* Clear the arrays.  */
+               memset (bucket, '\0',
+                       (symd->d_size / elsize + nbucket)
+                       * sizeof (Elf64_Xword));
+
+               for (size_t inner = shdr_info[symtabidx].shdr.sh_info;
+                    inner < symd->d_size / elsize; ++inner)
                  {
-                   GElf_Rela rel_mem;
-                   if (gelf_getrela (d, relidx, &rel_mem) == NULL)
-                     INTERNAL_ERROR (fname);
+                   GElf_Sym sym_mem;
+                   GElf_Sym *sym = gelf_getsym (symd, inner, &sym_mem);
+                   elf_assert (sym != NULL);
+
+                   const char *name = elf_strptr (elf, strshndx,
+                                                  sym->st_name);
+                   elf_assert (name != NULL && nbucket != 0);
+                   size_t hidx = elf_hash (name) % nbucket;
 
-                   size_t symidx = GELF_R_SYM (rel_mem.r_info);
-                   if (newsymidx[symidx] != symidx)
+                   if (bucket[hidx] == 0)
+                     bucket[hidx] = inner;
+                   else
                      {
-                       rel_mem.r_info
-                         = GELF_R_INFO (newsymidx[symidx],
-                                        GELF_R_TYPE (rel_mem.r_info));
+                       hidx = bucket[hidx];
 
-                       if (gelf_update_rela (d, relidx, &rel_mem) == 0)
-                         INTERNAL_ERROR (fname);
+                       while (chain[hidx] != 0 && chain[hidx] < nchain)
+                         hidx = chain[hidx];
+
+                       chain[hidx] = inner;
                      }
                  }
-           }
-         else if (shdr_info[cnt].shdr.sh_type == SHT_HASH)
-           {
-             /* We have to recompute the hash table.  */
-             Elf32_Word symtabidx = shdr_info[cnt].old_sh_link;
+             }
+           break;
 
-             /* We do not have to do anything if the symbol table was
-                not changed.  */
-             if (shdr_info[symtabidx].newsymidx == NULL)
-               continue;
+         case SHT_GNU_versym:
+           /* If the symbol table changed we have to adjust the entries.  */
+           if (no_symtab_updates ())
+             break;
 
-             assert (shdr_info[cnt].idx > 0);
+           elf_assert (shdr_info[cnt].idx > 0);
+
+           /* The symbol version section in the new file.  */
+           scn = elf_getscn (newelf, shdr_info[cnt].idx);
+
+           /* The symbol table data.  */
+           symd = elf_getdata (elf_getscn (newelf, shdr_info[symtabidx].idx),
+                               NULL);
+           elf_assert (symd != NULL && symd->d_buf != NULL);
+           size_t symz = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
+           const Elf32_Word syms = (shdr_info[symtabidx].data->d_size / symz);
+
+           /* The version symbol data.  */
+           Elf_Data *verd = elf_getdata (scn, NULL);
+           elf_assert (verd != NULL && verd->d_buf != NULL);
+
+           /* The symbol version array.  */
+           GElf_Half *verstab = (GElf_Half *) verd->d_buf;
+
+           /* Walk through the list and */
+           size_t elsize = gelf_fsize (elf, verd->d_type, 1, EV_CURRENT);
+           Elf32_Word vers = verd->d_size / elsize;
+           for (size_t inner = 1; inner < vers && inner < syms; ++inner)
+             if (newsymidx[inner] != 0 && newsymidx[inner] < vers)
+               /* Overwriting the same array works since the
+                  reordering can only move entries to lower indices
+                  in the array.  */
+               verstab[newsymidx[inner]] = verstab[inner];
+
+           /* New size of the section.  */
+           verd->d_size = gelf_fsize (newelf, verd->d_type,
+                                      symd->d_size
+                                      / gelf_fsize (elf, symd->d_type, 1,
+                                                    EV_CURRENT),
+                                      EV_CURRENT);
+           update_section_size (verd);
+           break;
+
+         case SHT_GROUP:
+           if (no_symtab_updates ())
+             break;
+
+           /* Yes, the symbol table changed.
+              Update the section header of the section group.  */
+           scn = elf_getscn (newelf, shdr_info[cnt].idx);
+           GElf_Shdr shdr_mem;
+           GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+           elf_assert (shdr != NULL);
+
+           size_t symsz = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
+           const Elf32_Word symn = (shdr_info[symtabidx].data->d_size
+                                    / symsz);
+           elf_assert (shdr->sh_info < symn);
+           shdr->sh_info = newsymidx[shdr->sh_info];
+
+           (void) gelf_update_shdr (scn, shdr);
+           break;
+         }
+      }
 
-             /* The hash section in the new file.  */
-             scn = elf_getscn (newelf, shdr_info[cnt].idx);
+  /* Remove any relocations between debug sections in ET_REL
+     for the debug file when requested.  These relocations are always
+     zero based between the unallocated sections.  */
+  if (debug_fname != NULL && removing_sections
+      && reloc_debug && ehdr->e_type == ET_REL)
+    {
+      scn = NULL;
+      cnt = 0;
+      while ((scn = elf_nextscn (debugelf, scn)) != NULL)
+       {
+         cnt++;
+         /* We need the actual section and header from the debugelf
+            not just the cached original in shdr_info because we
+            might want to change the size.  */
+         GElf_Shdr shdr_mem;
+         GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+         if (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA)
+           {
+             /* Make sure that this relocation section points to a
+                section to relocate with contents, that isn't
+                allocated and that is a debug section.  */
+             Elf_Scn *tscn = elf_getscn (debugelf, shdr->sh_info);
+             GElf_Shdr tshdr_mem;
+             GElf_Shdr *tshdr = gelf_getshdr (tscn, &tshdr_mem);
+             if (tshdr->sh_type == SHT_NOBITS
+                 || tshdr->sh_size == 0
+                 || (tshdr->sh_flags & SHF_ALLOC) != 0)
+               continue;
 
-             /* The symbol table data.  */
-             Elf_Data *symd = elf_getdata (elf_getscn (newelf,
-                                                       shdr_info[symtabidx].idx),
-                                           NULL);
-             assert (symd != NULL);
+             const char *tname =  elf_strptr (debugelf, shstrndx,
+                                              tshdr->sh_name);
+             if (! tname || ! ebl_debugscn_p (ebl, tname))
+               continue;
 
-             /* The hash table data.  */
-             Elf_Data *hashd = elf_getdata (scn, NULL);
-             assert (hashd != NULL);
+             /* OK, lets relocate all trivial cross debug section
+                relocations. */
+             Elf_Data *reldata = elf_getdata (scn, NULL);
+             if (reldata == NULL || reldata->d_buf == NULL)
+               INTERNAL_ERROR (fname);
 
-             if (shdr_info[cnt].shdr.sh_entsize == sizeof (Elf32_Word))
+             /* Make sure we adjust the uncompressed debug data
+                (and recompress if necessary at the end).  */
+             GElf_Chdr tchdr;
+             int tcompress_type = 0;
+             if (gelf_getchdr (tscn, &tchdr) != NULL)
                {
-                 /* Sane arches first.  */
-                 Elf32_Word *bucket = (Elf32_Word *) hashd->d_buf;
-
-                 size_t strshndx = shdr_info[symtabidx].old_sh_link;
-                 size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1,
-                                             ehdr->e_version);
-
-                 /* Adjust the nchain value.  The symbol table size
-                    changed.  We keep the same size for the bucket array.  */
-                 bucket[1] = symd->d_size / elsize;
-                 Elf32_Word nbucket = bucket[0];
-                 bucket += 2;
-                 Elf32_Word *chain = bucket + nbucket;
-
-                 /* New size of the section.  */
-                 GElf_Shdr shdr_mem;
-                 GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
-                 shdr->sh_size = hashd->d_size
-                   = (2 + symd->d_size / elsize + nbucket)
-                     * sizeof (Elf32_Word);
-                 (void) gelf_update_shdr (scn, shdr);
-
-                 /* Clear the arrays.  */
-                 memset (bucket, '\0',
-                         (symd->d_size / elsize + nbucket)
-                         * sizeof (Elf32_Word));
-
-                 for (size_t inner = shdr_info[symtabidx].shdr.sh_info;
-                      inner < symd->d_size / elsize; ++inner)
-                   {
-                     GElf_Sym sym_mem;
-                     GElf_Sym *sym = gelf_getsym (symd, inner, &sym_mem);
-                     assert (sym != NULL);
+                 tcompress_type = tchdr.ch_type;
+                 if (elf_compress (tscn, 0, 0) != 1)
+                   INTERNAL_ERROR (fname);
+               }
 
-                     const char *name = elf_strptr (elf, strshndx,
-                                                    sym->st_name);
-                     assert (name != NULL);
-                     size_t hidx = elf_hash (name) % nbucket;
+             Elf_Data *tdata = elf_getdata (tscn, NULL);
+             if (tdata == NULL || tdata->d_buf == NULL
+                 || tdata->d_type != ELF_T_BYTE)
+               INTERNAL_ERROR (fname);
 
-                     if (bucket[hidx] == 0)
-                       bucket[hidx] = inner;
-                     else
-                       {
-                         hidx = bucket[hidx];
+             /* Pick up the symbol table and shndx table to
+                resolve relocation symbol indexes.  */
+             Elf64_Word symt = shdr->sh_link;
+             Elf_Data *symdata, *xndxdata;
+             elf_assert (symt < shnum + 2);
+             elf_assert (shdr_info[symt].symtab_idx < shnum + 2);
+             symdata = (shdr_info[symt].debug_data
+                        ?: shdr_info[symt].data);
+             xndxdata = (shdr_info[shdr_info[symt].symtab_idx].debug_data
+                         ?: shdr_info[shdr_info[symt].symtab_idx].data);
+
+             /* Apply one relocation.  Returns true when trivial
+                relocation actually done.  */
+             bool relocate (GElf_Addr offset, const GElf_Sxword addend,
+                            bool is_rela, int rtype, int symndx)
+             {
+               /* R_*_NONE relocs can always just be removed.  */
+               if (rtype == 0)
+                 return true;
+
+               /* We only do simple absolute relocations.  */
+               int addsub = 0;
+               Elf_Type type = ebl_reloc_simple_type (ebl, rtype, &addsub);
+               if (type == ELF_T_NUM)
+                 return false;
+
+               /* These are the types we can relocate.  */
+#define TYPES   DO_TYPE (BYTE, Byte); DO_TYPE (HALF, Half);            \
+               DO_TYPE (WORD, Word); DO_TYPE (SWORD, Sword);           \
+               DO_TYPE (XWORD, Xword); DO_TYPE (SXWORD, Sxword)
+
+               /* And only for relocations against other debug sections.  */
+               GElf_Sym sym_mem;
+               Elf32_Word xndx;
+               GElf_Sym *sym = gelf_getsymshndx (symdata, xndxdata,
+                                                 symndx, &sym_mem,
+                                                 &xndx);
+               Elf32_Word sec = (sym->st_shndx == SHN_XINDEX
+                                 ? xndx : sym->st_shndx);
+               if (sec >= shnum + 2)
+                 INTERNAL_ERROR (fname);
 
-                         while (chain[hidx] != 0)
-                           hidx = chain[hidx];
+               if (ebl_debugscn_p (ebl, shdr_info[sec].name))
+                 {
+                   size_t size;
 
-                         chain[hidx] = inner;
-                       }
-                   }
-               }
-             else
-               {
-                 /* Alpha and S390 64-bit use 64-bit SHT_HASH entries.  */
-                 assert (shdr_info[cnt].shdr.sh_entsize
-                         == sizeof (Elf64_Xword));
-
-                 Elf64_Xword *bucket = (Elf64_Xword *) hashd->d_buf;
-
-                 size_t strshndx = shdr_info[symtabidx].old_sh_link;
-                 size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1,
-                                             ehdr->e_version);
-
-                 /* Adjust the nchain value.  The symbol table size
-                    changed.  We keep the same size for the bucket array.  */
-                 bucket[1] = symd->d_size / elsize;
-                 Elf64_Xword nbucket = bucket[0];
-                 bucket += 2;
-                 Elf64_Xword *chain = bucket + nbucket;
-
-                 /* New size of the section.  */
-                 GElf_Shdr shdr_mem;
-                 GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
-                 shdr->sh_size = hashd->d_size
-                   = (2 + symd->d_size / elsize + nbucket)
-                     * sizeof (Elf64_Xword);
-                 (void) gelf_update_shdr (scn, shdr);
-
-                 /* Clear the arrays.  */
-                 memset (bucket, '\0',
-                         (symd->d_size / elsize + nbucket)
-                         * sizeof (Elf64_Xword));
-
-                 for (size_t inner = shdr_info[symtabidx].shdr.sh_info;
-                      inner < symd->d_size / elsize; ++inner)
-                   {
-                     GElf_Sym sym_mem;
-                     GElf_Sym *sym = gelf_getsym (symd, inner, &sym_mem);
-                     assert (sym != NULL);
+#define DO_TYPE(NAME, Name) GElf_##Name Name;
+                   union { TYPES; } tmpbuf;
+#undef DO_TYPE
+
+                   switch (type)
+                     {
+#define DO_TYPE(NAME, Name)                            \
+                       case ELF_T_##NAME:              \
+                         size = sizeof (GElf_##Name);  \
+                         tmpbuf.Name = 0;              \
+                         break;
+                       TYPES;
+#undef DO_TYPE
+                     default:
+                       return false;
+                     }
 
-                     const char *name = elf_strptr (elf, strshndx,
-                                                    sym->st_name);
-                     assert (name != NULL);
-                     size_t hidx = elf_hash (name) % nbucket;
+                   if (offset > tdata->d_size
+                       || tdata->d_size - offset < size)
+                     {
+                       cleanup_debug ();
+                       error (EXIT_FAILURE, 0, gettext ("bad relocation"));
+                     }
 
-                     if (bucket[hidx] == 0)
-                       bucket[hidx] = inner;
-                     else
-                       {
-                         hidx = bucket[hidx];
+                   /* When the symbol value is zero then for SHT_REL
+                      sections this is all that needs to be checked.
+                      The addend is contained in the original data at
+                      the offset already.  So if the (section) symbol
+                      address is zero and the given addend is zero
+                      just remove the relocation, it isn't needed
+                      anymore.  */
+                   if (addend == 0 && sym->st_value == 0)
+                     return true;
+
+                   Elf_Data tmpdata =
+                     {
+                       .d_type = type,
+                       .d_buf = &tmpbuf,
+                       .d_size = size,
+                       .d_version = EV_CURRENT,
+                     };
+                   Elf_Data rdata =
+                     {
+                       .d_type = type,
+                       .d_buf = tdata->d_buf + offset,
+                       .d_size = size,
+                       .d_version = EV_CURRENT,
+                     };
+
+                   GElf_Addr value = sym->st_value;
+                   if (is_rela)
+                     {
+                       /* For SHT_RELA sections we just take the
+                          given addend and add it to the value.  */
+                       value += addend;
+                       /* For ADD/SUB relocations we need to fetch the
+                          current section contents.  */
+                       if (addsub != 0)
+                         {
+                           Elf_Data *d = gelf_xlatetom (debugelf, &tmpdata,
+                                                        &rdata,
+                                                        ehdr->e_ident[EI_DATA]);
+                           if (d == NULL)
+                             INTERNAL_ERROR (fname);
+                           assert (d == &tmpdata);
+                         }
+                     }
+                   else
+                     {
+                       /* For SHT_REL sections we have to peek at
+                          what is already in the section at the given
+                          offset to get the addend.  */
+                       Elf_Data *d = gelf_xlatetom (debugelf, &tmpdata,
+                                                    &rdata,
+                                                    ehdr->e_ident[EI_DATA]);
+                       if (d == NULL)
+                         INTERNAL_ERROR (fname);
+                       assert (d == &tmpdata);
+                     }
 
-                         while (chain[hidx] != 0)
-                           hidx = chain[hidx];
+                   switch (type)
+                     {
+#define DO_TYPE(NAME, Name)                                     \
+                       case ELF_T_##NAME:                       \
+                         if (addsub < 0)                        \
+                           tmpbuf.Name -= (GElf_##Name) value; \
+                         else                                   \
+                           tmpbuf.Name += (GElf_##Name) value; \
+                         break;
+                       TYPES;
+#undef DO_TYPE
+                     default:
+                       abort ();
+                     }
 
-                         chain[hidx] = inner;
-                       }
-                   }
-               }
-           }
-         else if (shdr_info[cnt].shdr.sh_type == SHT_GNU_versym)
-           {
-             /* If the symbol table changed we have to adjust the
-                entries.  */
-             Elf32_Word symtabidx = shdr_info[cnt].old_sh_link;
+                   /* Now finally put in the new value.  */
+                   Elf_Data *s = gelf_xlatetof (debugelf, &rdata,
+                                                &tmpdata,
+                                                ehdr->e_ident[EI_DATA]);
+                   if (s == NULL)
+                     INTERNAL_ERROR (fname);
+                   assert (s == &rdata);
 
-             /* We do not have to do anything if the symbol table was
-                not changed.  */
-             if (shdr_info[symtabidx].newsymidx == NULL)
-               continue;
+                   return true;
+                 }
+               return false;
+             }
 
-             assert (shdr_info[cnt].idx > 0);
-
-             /* The symbol version section in the new file.  */
-             scn = elf_getscn (newelf, shdr_info[cnt].idx);
-
-             /* The symbol table data.  */
-             Elf_Data *symd = elf_getdata (elf_getscn (newelf,
-                                                       shdr_info[symtabidx].idx),
-                                           NULL);
-             assert (symd != NULL);
-
-             /* The version symbol data.  */
-             Elf_Data *verd = elf_getdata (scn, NULL);
-             assert (verd != NULL);
-
-             /* The symbol version array.  */
-             GElf_Half *verstab = (GElf_Half *) verd->d_buf;
-
-             /* New indices of the symbols.  */
-             Elf32_Word *newsymidx = shdr_info[symtabidx].newsymidx;
-
-             /* Walk through the list and */
-             size_t elsize = gelf_fsize (elf, verd->d_type, 1,
-                                         ehdr->e_version);
-             for (size_t inner = 1; inner < verd->d_size / elsize; ++inner)
-               if (newsymidx[inner] != 0)
-                 /* Overwriting the same array works since the
-                    reordering can only move entries to lower indices
-                    in the array.  */
-                 verstab[newsymidx[inner]] = verstab[inner];
-
-             /* New size of the section.  */
-             GElf_Shdr shdr_mem;
-             GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
-             shdr->sh_size = verd->d_size
-               = gelf_fsize (newelf, verd->d_type,
-                             symd->d_size / gelf_fsize (elf, symd->d_type, 1,
-                                                        ehdr->e_version),
-                             ehdr->e_version);
-             (void) gelf_update_shdr (scn, shdr);
-           }
-         else if (shdr_info[cnt].shdr.sh_type == SHT_GROUP)
-           {
-             /* Check whether the associated symbol table changed.  */
-             if (shdr_info[shdr_info[cnt].old_sh_link].newsymidx != NULL)
-               {
-                 /* Yes the symbol table changed.  Update the section
-                    header of the section group.  */
-                 scn = elf_getscn (newelf, shdr_info[cnt].idx);
-                 GElf_Shdr shdr_mem;
-                 GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
-                 assert (shdr != NULL);
+             if (shdr->sh_entsize == 0)
+               INTERNAL_ERROR (fname);
+
+             size_t nrels = shdr->sh_size / shdr->sh_entsize;
+             size_t next = 0;
+             if (shdr->sh_type == SHT_REL)
+               for (size_t relidx = 0; relidx < nrels; ++relidx)
+                 {
+                   GElf_Rel rel_mem;
+                   GElf_Rel *r = gelf_getrel (reldata, relidx, &rel_mem);
+                   if (! relocate (r->r_offset, 0, false,
+                                   GELF_R_TYPE (r->r_info),
+                                   GELF_R_SYM (r->r_info)))
+                     {
+                       if (relidx != next)
+                         gelf_update_rel (reldata, next, r);
+                       ++next;
+                     }
+                 }
+             else
+               for (size_t relidx = 0; relidx < nrels; ++relidx)
+                 {
+                   GElf_Rela rela_mem;
+                   GElf_Rela *r = gelf_getrela (reldata, relidx, &rela_mem);
+                   if (! relocate (r->r_offset, r->r_addend, true,
+                                   GELF_R_TYPE (r->r_info),
+                                   GELF_R_SYM (r->r_info)))
+                     {
+                       if (relidx != next)
+                         gelf_update_rela (reldata, next, r);
+                       ++next;
+                     }
+                 }
 
-                 size_t stabidx = shdr_info[cnt].old_sh_link;
-                 shdr->sh_info = shdr_info[stabidx].newsymidx[shdr->sh_info];
+             nrels = next;
+             shdr->sh_size = reldata->d_size = nrels * shdr->sh_entsize;
+             gelf_update_shdr (scn, shdr);
 
-                 (void) gelf_update_shdr (scn, shdr);
-               }
+             if (tcompress_type != 0)
+               if (elf_compress (tscn, tcompress_type, ELF_CHF_FORCE) != 1)
+                 INTERNAL_ERROR (fname);
            }
        }
     }
 
   /* Now that we have done all adjustments to the data,
      we can actually write out the debug file.  */
-  if (debug_fname != NULL)
+  if (debug_fname != NULL && removing_sections)
     {
-      uint32_t debug_crc;
-      Elf_Data debug_crc_data =
-       {
-         .d_type = ELF_T_WORD,
-         .d_buf = &debug_crc,
-         .d_size = sizeof (debug_crc),
-         .d_version = EV_CURRENT
-       };
-
       /* Finally write the file.  */
-      if (unlikely (elf_update (debugelf, ELF_C_WRITE)) == -1)
+      if (unlikely (elf_update (debugelf, ELF_C_WRITE) == -1))
        {
          error (0, 0, gettext ("while writing '%s': %s"),
-                debug_fname, elf_errmsg (-1));
+                tmp_debug_fname, elf_errmsg (-1));
          result = 1;
          goto fail_close;
        }
@@ -1562,23 +2232,36 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
        }
 
       /* The temporary file does not exist anymore.  */
+      free (tmp_debug_fname);
       tmp_debug_fname = NULL;
 
-      /* Compute the checksum which we will add to the executable.  */
-      if (crc32_file (debug_fd, &debug_crc) != 0)
+      if (!remove_shdrs)
        {
-         error (0, errno,
-                gettext ("while computing checksum for debug information"));
-         unlink (debug_fname);
-         result = 1;
-         goto fail_close;
-       }
+         uint32_t debug_crc;
+         Elf_Data debug_crc_data =
+           {
+             .d_type = ELF_T_WORD,
+             .d_buf = &debug_crc,
+             .d_size = sizeof (debug_crc),
+             .d_version = EV_CURRENT
+           };
+
+         /* Compute the checksum which we will add to the executable.  */
+         if (crc32_file (debug_fd, &debug_crc) != 0)
+           {
+             error (0, errno, gettext ("\
+while computing checksum for debug information"));
+             unlink (debug_fname);
+             result = 1;
+             goto fail_close;
+           }
 
-      /* Store it in the debuglink section data.  */
-      if (unlikely (gelf_xlatetof (newelf, &debuglink_crc_data,
-                                  &debug_crc_data, ehdr->e_ident[EI_DATA])
-                   != &debuglink_crc_data))
-       INTERNAL_ERROR (fname);
+         /* Store it in the debuglink section data.  */
+         if (unlikely (gelf_xlatetof (newelf, &debuglink_crc_data,
+                                      &debug_crc_data, ehdr->e_ident[EI_DATA])
+                       != &debuglink_crc_data))
+           INTERNAL_ERROR (fname);
+       }
     }
 
   /* Finally finish the ELF header.  Fill in the fields not handled by
@@ -1594,6 +2277,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
   newehdr->e_entry = ehdr->e_entry;
   newehdr->e_flags = ehdr->e_flags;
   newehdr->e_phoff = ehdr->e_phoff;
+
   /* We need to position the section header table.  */
   const size_t offsize = gelf_fsize (elf, ELF_T_OFF, 1, EV_CURRENT);
   newehdr->e_shoff = ((shdr_info[shdridx].shdr.sh_offset
@@ -1621,7 +2305,8 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
   if (gelf_update_ehdr (newelf, newehdr) == 0)
     {
       error (0, 0, gettext ("%s: error while creating ELF header: %s"),
-            fname, elf_errmsg (-1));
+            output_fname ?: fname, elf_errmsg (-1));
+      cleanup_debug ();
       return 1;
     }
 
@@ -1630,6 +2315,7 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
     {
       error (0, 0, gettext ("%s: error while reading the file: %s"),
             fname, elf_errmsg (-1));
+      cleanup_debug ();
       return 1;
     }
 
@@ -1643,18 +2329,72 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
   if (elf_update (newelf, ELF_C_WRITE) == -1)
     {
       error (0, 0, gettext ("while writing '%s': %s"),
-            fname, elf_errmsg (-1));
+            output_fname ?: fname, elf_errmsg (-1));
       result = 1;
     }
 
+  if (remove_shdrs)
+    {
+      /* libelf can't cope without the section headers being properly intact.
+        So we just let it write them normally, and then we nuke them later.  */
+
+      if (newehdr->e_ident[EI_CLASS] == ELFCLASS32)
+       {
+         assert (offsetof (Elf32_Ehdr, e_shentsize) + sizeof (Elf32_Half)
+                 == offsetof (Elf32_Ehdr, e_shnum));
+         assert (offsetof (Elf32_Ehdr, e_shnum) + sizeof (Elf32_Half)
+                 == offsetof (Elf32_Ehdr, e_shstrndx));
+         const Elf32_Off zero_off = 0;
+         const Elf32_Half zero[3] = { 0, 0, SHN_UNDEF };
+         if (pwrite_retry (fd, &zero_off, sizeof zero_off,
+                           offsetof (Elf32_Ehdr, e_shoff)) != sizeof zero_off
+             || (pwrite_retry (fd, zero, sizeof zero,
+                               offsetof (Elf32_Ehdr, e_shentsize))
+                 != sizeof zero)
+             || ftruncate (fd, shdr_info[shdridx].shdr.sh_offset) < 0)
+           {
+             error (0, errno, gettext ("while writing '%s'"),
+                    output_fname ?: fname);
+             result = 1;
+           }
+       }
+      else
+       {
+         assert (offsetof (Elf64_Ehdr, e_shentsize) + sizeof (Elf64_Half)
+                 == offsetof (Elf64_Ehdr, e_shnum));
+         assert (offsetof (Elf64_Ehdr, e_shnum) + sizeof (Elf64_Half)
+                 == offsetof (Elf64_Ehdr, e_shstrndx));
+         const Elf64_Off zero_off = 0;
+         const Elf64_Half zero[3] = { 0, 0, SHN_UNDEF };
+         if (pwrite_retry (fd, &zero_off, sizeof zero_off,
+                           offsetof (Elf64_Ehdr, e_shoff)) != sizeof zero_off
+             || (pwrite_retry (fd, zero, sizeof zero,
+                               offsetof (Elf64_Ehdr, e_shentsize))
+                 != sizeof zero)
+             || ftruncate (fd, shdr_info[shdridx].shdr.sh_offset) < 0)
+           {
+             error (0, errno, gettext ("while writing '%s'"),
+                    output_fname ?: fname);
+             result = 1;
+           }
+       }
+    }
+
  fail_close:
   if (shdr_info != NULL)
     {
       /* For some sections we might have created an table to map symbol
-        table indices.  */
-      if (any_symtab_changes)
-       for (cnt = 1; cnt <= shdridx; ++cnt)
+        table indices.  Or we might kept (original) data around to put
+        into the .debug file.  */
+      for (cnt = 1; cnt <= shdridx; ++cnt)
+       {
          free (shdr_info[cnt].newsymidx);
+         if (shdr_info[cnt].debug_data != NULL)
+           free (shdr_info[cnt].debug_data->d_buf);
+       }
+
+      /* Free data we allocated for the .gnu_debuglink section. */
+      free (debuglink_buf);
 
       /* Free the memory.  */
       if ((shnum + 2) * sizeof (struct shdr_info) > MAX_STACK_ALLOC)
@@ -1665,13 +2405,13 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
   if (shstrtab_data != NULL)
     free (shstrtab_data->d_buf);
   if (shst != NULL)
-    ebl_strtabfree (shst);
+    dwelf_strtab_free (shst);
 
   /* That was it.  Close the descriptors.  */
   if (elf_end (newelf) != 0)
     {
-      error (0, 0, gettext ("error while finishing '%s': %s"), fname,
-            elf_errmsg (-1));
+      error (0, 0, gettext ("error while finishing '%s': %s"),
+            output_fname ?: fname, elf_errmsg (-1));
       result = 1;
     }
 
@@ -1687,18 +2427,12 @@ handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
   if (ebl != NULL)
     ebl_closebackend (ebl);
 
-  /* Close debug file descriptor, if opened */
-  if (debug_fd >= 0)
-    {
-      if (tmp_debug_fname != NULL)
-       unlink (tmp_debug_fname);
-      close (debug_fd);
-    }
+  cleanup_debug ();
 
   /* If requested, preserve the timestamp.  */
   if (tvp != NULL)
     {
-      if (futimes (fd, tvp) != 0)
+      if (futimens (fd, tvp) != 0)
        {
          error (0, errno, gettext ("\
 cannot set access and modification date of '%s'"),
@@ -1709,15 +2443,34 @@ cannot set access and modification date of '%s'"),
 
   /* Close the file descriptor if we created a new file.  */
   if (output_fname != NULL)
-    close (fd);
+    {
+      close (fd);
+      if (result != 0)
+       unlink (output_fname);
+    }
 
   return result;
 }
 
+static void
+cleanup_debug (void)
+{
+  if (debug_fd >= 0)
+    {
+      if (tmp_debug_fname != NULL)
+       {
+         unlink (tmp_debug_fname);
+         free (tmp_debug_fname);
+         tmp_debug_fname = NULL;
+       }
+      close (debug_fd);
+      debug_fd = -1;
+    }
+}
 
 static int
 handle_ar (int fd, Elf *elf, const char *prefix, const char *fname,
-          struct timeval tvp[2])
+          struct timespec tvp[2])
 {
   size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
   size_t fname_len = strlen (fname) + 1;
@@ -1755,7 +2508,7 @@ handle_ar (int fd, Elf *elf, const char *prefix, const char *fname,
 
   if (tvp != NULL)
     {
-      if (unlikely (futimes (fd, tvp) != 0))
+      if (unlikely (futimens (fd, tvp) != 0))
        {
          error (0, errno, gettext ("\
 cannot set access and modification date of '%s'"), fname);
@@ -1768,3 +2521,6 @@ cannot set access and modification date of '%s'"), fname);
 
   return result;
 }
+
+
+#include "debugpred.h"