Squashed 'third_party/elfutils/' content from commit 555e15e
Change-Id: I61cde98949e47e5c8c09c33260de17f30921be79
git-subtree-dir: third_party/elfutils
git-subtree-split: 555e15ebe8bf1eb33d00747173cfc80cc65648a4
diff --git a/src/unstrip.c b/src/unstrip.c
new file mode 100644
index 0000000..f368e69
--- /dev/null
+++ b/src/unstrip.c
@@ -0,0 +1,2456 @@
+/* Combine stripped files with separate symbols and debug information.
+ Copyright (C) 2007-2012, 2014, 2015 Red Hat, Inc.
+ This file is part of elfutils.
+ Written by Roland McGrath <roland@redhat.com>, 2007.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* TODO:
+
+ * SHX_XINDEX
+
+ * prelink vs .debug_* linked addresses
+
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <assert.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <libintl.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <gelf.h>
+#include <libebl.h>
+#include <libdwfl.h>
+#include "libdwelf.h"
+#include "libeu.h"
+#include "printversion.h"
+
+#ifndef _
+# define _(str) gettext (str)
+#endif
+
+/* Name and version of program. */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address. */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+/* Definitions of arguments for argp functions. */
+static const struct argp_option options[] =
+{
+ /* Group 2 will follow group 1 from dwfl_standard_argp. */
+ { "match-file-names", 'f', NULL, 0,
+ N_("Match MODULE against file names, not module names"), 2 },
+ { "ignore-missing", 'i', NULL, 0, N_("Silently skip unfindable files"), 0 },
+
+ { NULL, 0, NULL, 0, N_("Output options:"), 0 },
+ { "output", 'o', "FILE", 0, N_("Place output into FILE"), 0 },
+ { "output-directory", 'd', "DIRECTORY",
+ 0, N_("Create multiple output files under DIRECTORY"), 0 },
+ { "module-names", 'm', NULL, 0, N_("Use module rather than file names"), 0 },
+ { "all", 'a', NULL, 0,
+ N_("Create output for modules that have no separate debug information"),
+ 0 },
+ { "relocate", 'R', NULL, 0,
+ N_("Apply relocations to section contents in ET_REL files"), 0 },
+ { "list-only", 'n', NULL, 0,
+ N_("Only list module and file names, build IDs"), 0 },
+ { "force", 'F', NULL, 0,
+ N_("Force combining files even if some ELF headers don't seem to match"),
+ 0 },
+ { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+struct arg_info
+{
+ const char *output_file;
+ const char *output_dir;
+ Dwfl *dwfl;
+ char **args;
+ bool list;
+ bool all;
+ bool ignore;
+ bool modnames;
+ bool match_files;
+ bool relocate;
+ bool force;
+};
+
+/* Handle program arguments. */
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ struct arg_info *info = state->input;
+
+ switch (key)
+ {
+ case ARGP_KEY_INIT:
+ state->child_inputs[0] = &info->dwfl;
+ break;
+
+ case 'o':
+ if (info->output_file != NULL)
+ {
+ argp_error (state, _("-o option specified twice"));
+ return EINVAL;
+ }
+ info->output_file = arg;
+ break;
+
+ case 'd':
+ if (info->output_dir != NULL)
+ {
+ argp_error (state, _("-d option specified twice"));
+ return EINVAL;
+ }
+ info->output_dir = arg;
+ break;
+
+ case 'm':
+ info->modnames = true;
+ break;
+ case 'f':
+ info->match_files = true;
+ break;
+ case 'a':
+ info->all = true;
+ break;
+ case 'i':
+ info->ignore = true;
+ break;
+ case 'n':
+ info->list = true;
+ break;
+ case 'R':
+ info->relocate = true;
+ break;
+ case 'F':
+ info->force = true;
+ break;
+
+ case ARGP_KEY_ARGS:
+ case ARGP_KEY_NO_ARGS:
+ /* We "consume" all the arguments here. */
+ info->args = &state->argv[state->next];
+
+ if (info->output_file != NULL && info->output_dir != NULL)
+ {
+ argp_error (state, _("only one of -o or -d allowed"));
+ return EINVAL;
+ }
+
+ if (info->list && (info->dwfl == NULL
+ || info->output_dir != NULL
+ || info->output_file != NULL))
+ {
+ argp_error (state,
+ _("-n cannot be used with explicit files or -o or -d"));
+ return EINVAL;
+ }
+
+ if (info->output_dir != NULL)
+ {
+ struct stat st;
+ error_t fail = 0;
+ if (stat (info->output_dir, &st) < 0)
+ fail = errno;
+ else if (!S_ISDIR (st.st_mode))
+ fail = ENOTDIR;
+ if (fail)
+ {
+ argp_failure (state, EXIT_FAILURE, fail,
+ _("output directory '%s'"), info->output_dir);
+ return fail;
+ }
+ }
+
+ if (info->dwfl == NULL)
+ {
+ if (state->next + 2 != state->argc)
+ {
+ argp_error (state, _("exactly two file arguments are required"));
+ return EINVAL;
+ }
+
+ if (info->ignore || info->all || info->modnames || info->relocate)
+ {
+ argp_error (state, _("\
+-m, -a, -R, and -i options not allowed with explicit files"));
+ return EINVAL;
+ }
+
+ /* Bail out immediately to prevent dwfl_standard_argp's parser
+ from defaulting to "-e a.out". */
+ return ENOSYS;
+ }
+ else if (info->output_file == NULL && info->output_dir == NULL
+ && !info->list)
+ {
+ argp_error (state,
+ _("-o or -d is required when using implicit files"));
+ return EINVAL;
+ }
+ break;
+
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
+
+#define ELF_CHECK(call, msg) \
+ do \
+ { \
+ if (unlikely (!(call))) \
+ error (EXIT_FAILURE, 0, msg, elf_errmsg (-1)); \
+ } while (0)
+
+/* Copy INELF to newly-created OUTELF, exit via error for any problems. */
+static void
+copy_elf (Elf *outelf, Elf *inelf)
+{
+ ELF_CHECK (gelf_newehdr (outelf, gelf_getclass (inelf)),
+ _("cannot create ELF header: %s"));
+
+ GElf_Ehdr ehdr_mem;
+ GElf_Ehdr *ehdr = gelf_getehdr (inelf, &ehdr_mem);
+ ELF_CHECK (gelf_update_ehdr (outelf, ehdr),
+ _("cannot copy ELF header: %s"));
+
+ size_t phnum;
+ ELF_CHECK (elf_getphdrnum (inelf, &phnum) == 0,
+ _("cannot get number of program headers: %s"));
+
+ if (phnum > 0)
+ {
+ ELF_CHECK (gelf_newphdr (outelf, phnum),
+ _("cannot create program headers: %s"));
+
+ GElf_Phdr phdr_mem;
+ for (size_t i = 0; i < phnum; ++i)
+ ELF_CHECK (gelf_update_phdr (outelf, i,
+ gelf_getphdr (inelf, i, &phdr_mem)),
+ _("cannot copy program header: %s"));
+ }
+
+ Elf_Scn *scn = NULL;
+ while ((scn = elf_nextscn (inelf, scn)) != NULL)
+ {
+ Elf_Scn *newscn = elf_newscn (outelf);
+
+ GElf_Shdr shdr_mem;
+ ELF_CHECK (gelf_update_shdr (newscn, gelf_getshdr (scn, &shdr_mem)),
+ _("cannot copy section header: %s"));
+
+ Elf_Data *data = elf_getdata (scn, NULL);
+ ELF_CHECK (data != NULL, _("cannot get section data: %s"));
+ Elf_Data *newdata = elf_newdata (newscn);
+ ELF_CHECK (newdata != NULL, _("cannot copy section data: %s"));
+ *newdata = *data;
+ elf_flagdata (newdata, ELF_C_SET, ELF_F_DIRTY);
+ }
+}
+
+/* Create directories containing PATH. */
+static void
+make_directories (const char *path)
+{
+ const char *lastslash = strrchr (path, '/');
+ if (lastslash == NULL)
+ return;
+
+ while (lastslash > path && lastslash[-1] == '/')
+ --lastslash;
+ if (lastslash == path)
+ return;
+
+ char *dir = strndupa (path, lastslash - path);
+ while (mkdir (dir, 0777) < 0 && errno != EEXIST)
+ if (errno == ENOENT)
+ make_directories (dir);
+ else
+ error (EXIT_FAILURE, errno, _("cannot create directory '%s'"), dir);
+}
+
+/* Keep track of new section data we are creating, so we can free it
+ when done. */
+struct data_list
+{
+ void *data;
+ struct data_list *next;
+};
+
+struct data_list *new_data_list;
+
+static void
+record_new_data (void *data)
+{
+ struct data_list *next = new_data_list;
+ new_data_list = xmalloc (sizeof (struct data_list));
+ new_data_list->data = data;
+ new_data_list->next = next;
+}
+
+static void
+free_new_data (void)
+{
+ struct data_list *list = new_data_list;
+ while (list != NULL)
+ {
+ struct data_list *next = list->next;
+ free (list->data);
+ free (list);
+ list = next;
+ }
+ new_data_list = NULL;
+}
+
+/* The binutils linker leaves gratuitous section symbols in .symtab
+ that strip has to remove. Older linkers likewise include a
+ symbol for every section, even unallocated ones, in .dynsym.
+ Because of this, the related sections can shrink in the stripped
+ file from their original size. Older versions of strip do not
+ adjust the sh_size field in the debuginfo file's SHT_NOBITS
+ version of the section header, so it can appear larger. */
+static bool
+section_can_shrink (const GElf_Shdr *shdr)
+{
+ switch (shdr->sh_type)
+ {
+ case SHT_SYMTAB:
+ case SHT_DYNSYM:
+ case SHT_HASH:
+ case SHT_GNU_versym:
+ return true;
+ }
+ return false;
+}
+
+/* See if this symbol table has a leading section symbol for every single
+ section, in order. The binutils linker produces this. While we're here,
+ update each section symbol's st_value. */
+static size_t
+symtab_count_leading_section_symbols (Elf *elf, Elf_Scn *scn, size_t shnum,
+ Elf_Data *newsymdata)
+{
+ Elf_Data *data = elf_getdata (scn, NULL);
+ Elf_Data *shndxdata = NULL; /* XXX */
+
+ for (size_t i = 1; i < shnum; ++i)
+ {
+ GElf_Sym sym_mem;
+ GElf_Word shndx = SHN_UNDEF;
+ GElf_Sym *sym = gelf_getsymshndx (data, shndxdata, i, &sym_mem, &shndx);
+ ELF_CHECK (sym != NULL, _("cannot get symbol table entry: %s"));
+
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (elf_getscn (elf, i), &shdr_mem);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+ if (sym->st_shndx != SHN_XINDEX)
+ shndx = sym->st_shndx;
+
+ if (shndx != i || GELF_ST_TYPE (sym->st_info) != STT_SECTION)
+ return i;
+
+ sym->st_value = shdr->sh_addr;
+ if (sym->st_shndx != SHN_XINDEX)
+ shndx = SHN_UNDEF;
+ ELF_CHECK (gelf_update_symshndx (newsymdata, shndxdata, i, sym, shndx),
+ _("cannot update symbol table: %s"));
+ }
+
+ return shnum;
+}
+
+static void
+update_shdr (Elf_Scn *outscn, GElf_Shdr *newshdr)
+{
+ ELF_CHECK (gelf_update_shdr (outscn, newshdr),
+ _("cannot update section header: %s"));
+}
+
+/* We expanded the output section, so update its header. */
+static void
+update_sh_size (Elf_Scn *outscn, const Elf_Data *data)
+{
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *newshdr = gelf_getshdr (outscn, &shdr_mem);
+ ELF_CHECK (newshdr != NULL, _("cannot get section header: %s"));
+
+ newshdr->sh_size = data->d_size;
+
+ update_shdr (outscn, newshdr);
+}
+
+/* Update relocation sections using the symbol table. */
+static void
+adjust_relocs (Elf_Scn *outscn, Elf_Scn *inscn, const GElf_Shdr *shdr,
+ size_t map[], const GElf_Shdr *symshdr)
+{
+ Elf_Data *data = elf_getdata (outscn, NULL);
+
+ inline void adjust_reloc (GElf_Xword *info)
+ {
+ size_t ndx = GELF_R_SYM (*info);
+ if (ndx != STN_UNDEF)
+ *info = GELF_R_INFO (map[ndx - 1], GELF_R_TYPE (*info));
+ }
+
+ switch (shdr->sh_type)
+ {
+ case SHT_REL:
+ for (size_t i = 0; i < shdr->sh_size / shdr->sh_entsize; ++i)
+ {
+ GElf_Rel rel_mem;
+ GElf_Rel *rel = gelf_getrel (data, i, &rel_mem);
+ adjust_reloc (&rel->r_info);
+ ELF_CHECK (gelf_update_rel (data, i, rel),
+ _("cannot update relocation: %s"));
+ }
+ break;
+
+ case SHT_RELA:
+ for (size_t i = 0; i < shdr->sh_size / shdr->sh_entsize; ++i)
+ {
+ GElf_Rela rela_mem;
+ GElf_Rela *rela = gelf_getrela (data, i, &rela_mem);
+ adjust_reloc (&rela->r_info);
+ ELF_CHECK (gelf_update_rela (data, i, rela),
+ _("cannot update relocation: %s"));
+ }
+ break;
+
+ case SHT_GROUP:
+ {
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *newshdr = gelf_getshdr (outscn, &shdr_mem);
+ ELF_CHECK (newshdr != NULL, _("cannot get section header: %s"));
+ if (newshdr->sh_info != STN_UNDEF)
+ {
+ newshdr->sh_info = map[newshdr->sh_info - 1];
+ update_shdr (outscn, newshdr);
+ }
+ break;
+ }
+
+ case SHT_HASH:
+ /* We must expand the table and rejigger its contents. */
+ {
+ const size_t nsym = symshdr->sh_size / symshdr->sh_entsize;
+ const size_t onent = shdr->sh_size / shdr->sh_entsize;
+ assert (data->d_size == shdr->sh_size);
+
+#define CONVERT_HASH(Hash_Word) \
+ { \
+ const Hash_Word *const old_hash = data->d_buf; \
+ const size_t nbucket = old_hash[0]; \
+ const size_t nchain = old_hash[1]; \
+ const Hash_Word *const old_bucket = &old_hash[2]; \
+ const Hash_Word *const old_chain = &old_bucket[nbucket]; \
+ assert (onent == 2 + nbucket + nchain); \
+ \
+ const size_t nent = 2 + nbucket + nsym; \
+ Hash_Word *const new_hash = xcalloc (nent, sizeof new_hash[0]); \
+ Hash_Word *const new_bucket = &new_hash[2]; \
+ Hash_Word *const new_chain = &new_bucket[nbucket]; \
+ \
+ new_hash[0] = nbucket; \
+ new_hash[1] = nsym; \
+ for (size_t i = 0; i < nbucket; ++i) \
+ if (old_bucket[i] != STN_UNDEF) \
+ new_bucket[i] = map[old_bucket[i] - 1]; \
+ \
+ for (size_t i = 1; i < nchain; ++i) \
+ if (old_chain[i] != STN_UNDEF) \
+ new_chain[map[i - 1]] = map[old_chain[i] - 1]; \
+ \
+ record_new_data (new_hash); \
+ data->d_buf = new_hash; \
+ data->d_size = nent * sizeof new_hash[0]; \
+ }
+
+ switch (shdr->sh_entsize)
+ {
+ case 4:
+ CONVERT_HASH (Elf32_Word);
+ break;
+ case 8:
+ CONVERT_HASH (Elf64_Xword);
+ break;
+ default:
+ abort ();
+ }
+
+ elf_flagdata (data, ELF_C_SET, ELF_F_DIRTY);
+ update_sh_size (outscn, data);
+
+#undef CONVERT_HASH
+ }
+ break;
+
+ case SHT_GNU_versym:
+ /* We must expand the table and move its elements around. */
+ {
+ const size_t nent = symshdr->sh_size / symshdr->sh_entsize;
+ const size_t onent = shdr->sh_size / shdr->sh_entsize;
+ assert (nent >= onent);
+
+ /* We don't bother using gelf_update_versym because there is
+ really no conversion to be done. */
+ assert (sizeof (Elf32_Versym) == sizeof (GElf_Versym));
+ assert (sizeof (Elf64_Versym) == sizeof (GElf_Versym));
+ GElf_Versym *versym = xcalloc (nent, sizeof versym[0]);
+
+ for (size_t i = 1; i < onent; ++i)
+ {
+ GElf_Versym *v = gelf_getversym (data, i, &versym[map[i - 1]]);
+ ELF_CHECK (v != NULL, _("cannot get symbol version: %s"));
+ }
+
+ record_new_data (versym);
+ data->d_buf = versym;
+ data->d_size = nent * shdr->sh_entsize;
+ elf_flagdata (data, ELF_C_SET, ELF_F_DIRTY);
+ update_sh_size (outscn, data);
+ }
+ break;
+
+ default:
+ error (EXIT_FAILURE, 0,
+ _("unexpected section type in [%zu] with sh_link to symtab"),
+ elf_ndxscn (inscn));
+ }
+}
+
+/* Adjust all the relocation sections in the file. */
+static void
+adjust_all_relocs (Elf *elf, Elf_Scn *symtab, const GElf_Shdr *symshdr,
+ size_t map[])
+{
+ size_t new_sh_link = elf_ndxscn (symtab);
+ Elf_Scn *scn = NULL;
+ while ((scn = elf_nextscn (elf, scn)) != NULL)
+ if (scn != symtab)
+ {
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+ if (shdr->sh_type != SHT_NOBITS && shdr->sh_link == new_sh_link)
+ adjust_relocs (scn, scn, shdr, map, symshdr);
+ }
+}
+
+/* The original file probably had section symbols for all of its
+ sections, even the unallocated ones. To match it as closely as
+ possible, add in section symbols for the added sections. */
+static Elf_Data *
+add_new_section_symbols (Elf_Scn *old_symscn, size_t old_shnum,
+ Elf *elf, bool rel, Elf_Scn *symscn, size_t shnum)
+{
+ const size_t added = shnum - old_shnum;
+
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (symscn, &shdr_mem);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+ const size_t nsym = shdr->sh_size / shdr->sh_entsize;
+ size_t symndx_map[nsym - 1];
+
+ shdr->sh_info += added;
+ shdr->sh_size += added * shdr->sh_entsize;
+ update_shdr (symscn, shdr);
+
+ Elf_Data *symdata = elf_getdata (symscn, NULL);
+ Elf_Data *shndxdata = NULL; /* XXX */
+
+ symdata->d_size = shdr->sh_size;
+ symdata->d_buf = xmalloc (symdata->d_size);
+ record_new_data (symdata->d_buf);
+
+ /* Copy the existing section symbols. */
+ Elf_Data *old_symdata = elf_getdata (old_symscn, NULL);
+ for (size_t i = 0; i < old_shnum; ++i)
+ {
+ GElf_Sym sym_mem;
+ GElf_Word shndx = SHN_UNDEF;
+ GElf_Sym *sym = gelf_getsymshndx (old_symdata, shndxdata,
+ i, &sym_mem, &shndx);
+ ELF_CHECK (gelf_update_symshndx (symdata, shndxdata, i,
+ sym, shndx),
+ _("cannot update symbol table: %s"));
+
+ if (i > 0)
+ symndx_map[i - 1] = i;
+ }
+
+ /* Add in the new section symbols. */
+ for (size_t i = old_shnum; i < shnum; ++i)
+ {
+ GElf_Shdr i_shdr_mem;
+ GElf_Shdr *i_shdr = gelf_getshdr (elf_getscn (elf, i), &i_shdr_mem);
+ ELF_CHECK (i_shdr != NULL, _("cannot get section header: %s"));
+ GElf_Sym sym =
+ {
+ .st_value = rel ? 0 : i_shdr->sh_addr,
+ .st_info = GELF_ST_INFO (STB_LOCAL, STT_SECTION),
+ .st_shndx = i < SHN_LORESERVE ? i : SHN_XINDEX
+ };
+ GElf_Word shndx = i < SHN_LORESERVE ? SHN_UNDEF : i;
+ ELF_CHECK (gelf_update_symshndx (symdata, shndxdata, i,
+ &sym, shndx),
+ _("cannot update symbol table: %s"));
+ }
+
+ /* Now copy the rest of the existing symbols. */
+ for (size_t i = old_shnum; i < nsym; ++i)
+ {
+ GElf_Sym sym_mem;
+ GElf_Word shndx = SHN_UNDEF;
+ GElf_Sym *sym = gelf_getsymshndx (old_symdata, shndxdata,
+ i, &sym_mem, &shndx);
+ ELF_CHECK (gelf_update_symshndx (symdata, shndxdata,
+ i + added, sym, shndx),
+ _("cannot update symbol table: %s"));
+
+ symndx_map[i - 1] = i + added;
+ }
+
+ /* Adjust any relocations referring to the old symbol table. */
+ adjust_all_relocs (elf, symscn, shdr, symndx_map);
+
+ return symdata;
+}
+
+/* This has the side effect of updating STT_SECTION symbols' values,
+ in case of prelink adjustments. */
+static Elf_Data *
+check_symtab_section_symbols (Elf *elf, bool rel, Elf_Scn *scn,
+ size_t shnum, size_t shstrndx,
+ Elf_Scn *oscn, size_t oshnum, size_t oshstrndx,
+ size_t debuglink)
+{
+ size_t n = symtab_count_leading_section_symbols (elf, oscn, oshnum,
+ elf_getdata (scn, NULL));
+
+ if (n == oshnum)
+ return add_new_section_symbols (oscn, n, elf, rel, scn, shnum);
+
+ if (n == oshstrndx || (n == debuglink && n == oshstrndx - 1))
+ return add_new_section_symbols (oscn, n, elf, rel, scn, shstrndx);
+
+ return NULL;
+}
+
+struct section
+{
+ Elf_Scn *scn;
+ const char *name;
+ Elf_Scn *outscn;
+ Dwelf_Strent *strent;
+ GElf_Shdr shdr;
+};
+
+static int
+compare_alloc_sections (const struct section *s1, const struct section *s2,
+ bool rel)
+{
+ if (!rel)
+ {
+ /* Sort by address. */
+ if (s1->shdr.sh_addr < s2->shdr.sh_addr)
+ return -1;
+ if (s1->shdr.sh_addr > s2->shdr.sh_addr)
+ return 1;
+ }
+
+ /* At the same address, preserve original section order. */
+ return (ssize_t) elf_ndxscn (s1->scn) - (ssize_t) elf_ndxscn (s2->scn);
+}
+
+static int
+compare_unalloc_sections (const GElf_Shdr *shdr1, const GElf_Shdr *shdr2,
+ const char *name1, const char *name2)
+{
+ /* Sort by sh_flags as an arbitrary ordering. */
+ if (shdr1->sh_flags < shdr2->sh_flags)
+ return -1;
+ if (shdr1->sh_flags > shdr2->sh_flags)
+ return 1;
+
+ /* Sort by name as last resort. */
+ return strcmp (name1, name2);
+}
+
+static int
+compare_sections (const void *a, const void *b, bool rel)
+{
+ const struct section *s1 = a;
+ const struct section *s2 = b;
+
+ /* Sort all non-allocated sections last. */
+ if ((s1->shdr.sh_flags ^ s2->shdr.sh_flags) & SHF_ALLOC)
+ return (s1->shdr.sh_flags & SHF_ALLOC) ? -1 : 1;
+
+ return ((s1->shdr.sh_flags & SHF_ALLOC)
+ ? compare_alloc_sections (s1, s2, rel)
+ : compare_unalloc_sections (&s1->shdr, &s2->shdr,
+ s1->name, s2->name));
+}
+
+static int
+compare_sections_rel (const void *a, const void *b)
+{
+ return compare_sections (a, b, true);
+}
+
+static int
+compare_sections_nonrel (const void *a, const void *b)
+{
+ return compare_sections (a, b, false);
+}
+
+
+struct symbol
+{
+ size_t *map;
+
+ union
+ {
+ const char *name;
+ Dwelf_Strent *strent;
+ };
+ union
+ {
+ struct
+ {
+ GElf_Addr value;
+ GElf_Xword size;
+ GElf_Word shndx;
+ union
+ {
+ struct
+ {
+ uint8_t info;
+ uint8_t other;
+ } info;
+ int16_t compare;
+ };
+ };
+
+ /* For a symbol discarded after first sort, this matches its better's
+ map pointer. */
+ size_t *duplicate;
+ };
+};
+
+/* Collect input symbols into our internal form. */
+static void
+collect_symbols (Elf *outelf, bool rel, Elf_Scn *symscn, Elf_Scn *strscn,
+ const size_t nent, const GElf_Addr bias,
+ const size_t scnmap[], struct symbol *table, size_t *map,
+ struct section *split_bss)
+{
+ Elf_Data *symdata = elf_getdata (symscn, NULL);
+ Elf_Data *strdata = elf_getdata (strscn, NULL);
+ Elf_Data *shndxdata = NULL; /* XXX */
+
+ for (size_t i = 1; i < nent; ++i)
+ {
+ GElf_Sym sym_mem;
+ GElf_Word shndx = SHN_UNDEF;
+ GElf_Sym *sym = gelf_getsymshndx (symdata, shndxdata, i,
+ &sym_mem, &shndx);
+ ELF_CHECK (sym != NULL, _("cannot get symbol table entry: %s"));
+ if (sym->st_shndx != SHN_XINDEX)
+ shndx = sym->st_shndx;
+
+ if (sym->st_name >= strdata->d_size)
+ error (EXIT_FAILURE, 0,
+ _("invalid string offset in symbol [%zu]"), i);
+
+ struct symbol *s = &table[i - 1];
+ s->map = &map[i - 1];
+ s->name = strdata->d_buf + sym->st_name;
+ s->value = sym->st_value + bias;
+ s->size = sym->st_size;
+ s->shndx = shndx;
+ s->info.info = sym->st_info;
+ s->info.other = sym->st_other;
+
+ if (scnmap != NULL && shndx != SHN_UNDEF && shndx < SHN_LORESERVE)
+ s->shndx = scnmap[shndx - 1];
+
+ if (GELF_ST_TYPE (s->info.info) == STT_SECTION && !rel)
+ {
+ /* Update the value to match the output section. */
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (elf_getscn (outelf, s->shndx),
+ &shdr_mem);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+ s->value = shdr->sh_addr;
+ }
+ else if (split_bss != NULL
+ && s->value < split_bss->shdr.sh_addr
+ && s->value >= split_bss[-1].shdr.sh_addr
+ && shndx == elf_ndxscn (split_bss->outscn))
+ /* This symbol was in .bss and was split into .dynbss. */
+ s->shndx = elf_ndxscn (split_bss[-1].outscn);
+ }
+}
+
+
+#define CMP(value) \
+ if (s1->value < s2->value) \
+ return -1; \
+ if (s1->value > s2->value) \
+ return 1
+
+/* Compare symbols with a consistent ordering,
+ but one only meaningful for equality. */
+static int
+compare_symbols (const void *a, const void *b)
+{
+ const struct symbol *s1 = a;
+ const struct symbol *s2 = b;
+
+ CMP (value);
+ CMP (size);
+ CMP (shndx);
+
+ return (s1->compare - s2->compare) ?: strcmp (s1->name, s2->name);
+}
+
+/* Compare symbols for output order after slots have been assigned. */
+static int
+compare_symbols_output (const void *a, const void *b)
+{
+ const struct symbol *s1 = a;
+ const struct symbol *s2 = b;
+ int cmp;
+
+ /* Sort discarded symbols last. */
+ cmp = (s1->name == NULL) - (s2->name == NULL);
+
+ if (cmp == 0)
+ /* Local symbols must come first. */
+ cmp = ((GELF_ST_BIND (s2->info.info) == STB_LOCAL)
+ - (GELF_ST_BIND (s1->info.info) == STB_LOCAL));
+
+ if (cmp == 0)
+ /* binutils always puts section symbols first. */
+ cmp = ((GELF_ST_TYPE (s2->info.info) == STT_SECTION)
+ - (GELF_ST_TYPE (s1->info.info) == STT_SECTION));
+
+ if (cmp == 0)
+ {
+ if (GELF_ST_TYPE (s1->info.info) == STT_SECTION)
+ {
+ /* binutils always puts section symbols in section index order. */
+ CMP (shndx);
+ else
+ assert (s1 == s2);
+ }
+
+ /* Nothing really matters, so preserve the original order. */
+ CMP (map);
+ else
+ assert (s1 == s2);
+ }
+
+ return cmp;
+}
+
+#undef CMP
+
+/* Return true if the flags of the sections match, ignoring the SHF_INFO_LINK
+ flag if the section contains relocation information. */
+static bool
+sections_flags_match (Elf64_Xword sh_flags1, Elf64_Xword sh_flags2,
+ Elf64_Word sh_type)
+{
+ if (sh_type == SHT_REL || sh_type == SHT_RELA)
+ {
+ sh_flags1 &= ~SHF_INFO_LINK;
+ sh_flags2 &= ~SHF_INFO_LINK;
+ }
+
+ return sh_flags1 == sh_flags2;
+}
+
+/* Return true iff the flags, size, and name match. */
+static bool
+sections_match (const struct section *sections, size_t i,
+ const GElf_Shdr *shdr, const char *name)
+{
+ return (sections_flags_match (sections[i].shdr.sh_flags, shdr->sh_flags,
+ sections[i].shdr.sh_type)
+ && (sections[i].shdr.sh_size == shdr->sh_size
+ || (sections[i].shdr.sh_size < shdr->sh_size
+ && section_can_shrink (§ions[i].shdr)))
+ && !strcmp (sections[i].name, name));
+}
+
+/* Locate a matching allocated section in SECTIONS. */
+static struct section *
+find_alloc_section (const GElf_Shdr *shdr, GElf_Addr bias, const char *name,
+ struct section sections[], size_t nalloc)
+{
+ const GElf_Addr addr = shdr->sh_addr + bias;
+ size_t l = 0, u = nalloc;
+ while (l < u)
+ {
+ size_t i = (l + u) / 2;
+ if (addr < sections[i].shdr.sh_addr)
+ u = i;
+ else if (addr > sections[i].shdr.sh_addr)
+ l = i + 1;
+ else
+ {
+ /* We've found allocated sections with this address.
+ Find one with matching size, flags, and name. */
+ while (i > 0 && sections[i - 1].shdr.sh_addr == addr)
+ --i;
+ for (; i < nalloc && sections[i].shdr.sh_addr == addr;
+ ++i)
+ if (sections_match (sections, i, shdr, name))
+ return §ions[i];
+ break;
+ }
+ }
+ return NULL;
+}
+
+static inline const char *
+get_section_name (size_t ndx, const GElf_Shdr *shdr, const Elf_Data *shstrtab)
+{
+ if (shdr->sh_name >= shstrtab->d_size)
+ error (EXIT_FAILURE, 0, _("cannot read section [%zu] name: %s"),
+ ndx, elf_errmsg (-1));
+ return shstrtab->d_buf + shdr->sh_name;
+}
+
+/* Fix things up when prelink has moved some allocated sections around
+ and the debuginfo file's section headers no longer match up.
+ This fills in SECTIONS[0..NALLOC-1].outscn or exits.
+ If there was a .bss section that was split into two sections
+ with the new one preceding it in sh_addr, we return that pointer. */
+static struct section *
+find_alloc_sections_prelink (Elf *debug, Elf_Data *debug_shstrtab,
+ Elf *main, const GElf_Ehdr *main_ehdr,
+ Elf_Data *main_shstrtab, GElf_Addr bias,
+ struct section *sections,
+ size_t nalloc, size_t nsections)
+{
+ Elf_Scn *undo = NULL;
+ for (size_t i = nalloc; i < nsections; ++i)
+ {
+ const struct section *sec = §ions[i];
+ if (sec->shdr.sh_type == SHT_PROGBITS
+ && !(sec->shdr.sh_flags & SHF_ALLOC)
+ && !strcmp (sec->name, ".gnu.prelink_undo"))
+ {
+ undo = sec->scn;
+ break;
+ }
+ }
+
+ /* Find the original allocated sections before prelinking. */
+ struct section *undo_sections = NULL;
+ size_t undo_nalloc = 0;
+ if (undo != NULL)
+ {
+ /* Clear assignments that might have been bogus. */
+ for (size_t i = 0; i < nalloc; ++i)
+ sections[i].outscn = NULL;
+
+ Elf_Data *undodata = elf_rawdata (undo, NULL);
+ ELF_CHECK (undodata != NULL,
+ _("cannot read '.gnu.prelink_undo' section: %s"));
+
+ union
+ {
+ Elf32_Ehdr e32;
+ Elf64_Ehdr e64;
+ } ehdr;
+ Elf_Data dst =
+ {
+ .d_buf = &ehdr,
+ .d_size = sizeof ehdr,
+ .d_type = ELF_T_EHDR,
+ .d_version = EV_CURRENT
+ };
+ Elf_Data src = *undodata;
+ src.d_size = gelf_fsize (main, ELF_T_EHDR, 1, EV_CURRENT);
+ src.d_type = ELF_T_EHDR;
+ ELF_CHECK (gelf_xlatetom (main, &dst, &src,
+ main_ehdr->e_ident[EI_DATA]) != NULL,
+ _("cannot read '.gnu.prelink_undo' section: %s"));
+
+ uint_fast16_t phnum;
+ uint_fast16_t shnum;
+ if (ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32)
+ {
+ phnum = ehdr.e32.e_phnum;
+ shnum = ehdr.e32.e_shnum;
+ }
+ else
+ {
+ phnum = ehdr.e64.e_phnum;
+ shnum = ehdr.e64.e_shnum;
+ }
+
+ bool class32 = ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32;
+ size_t shsize = class32 ? sizeof (Elf32_Shdr) : sizeof (Elf64_Shdr);
+ if (unlikely (shnum == 0 || shnum > SIZE_MAX / shsize + 1))
+ error (EXIT_FAILURE, 0, _("overflow with shnum = %zu in '%s' section"),
+ (size_t) shnum, ".gnu.prelink_undo");
+
+ --shnum;
+
+ size_t phsize = gelf_fsize (main, ELF_T_PHDR, phnum, EV_CURRENT);
+ src.d_buf += src.d_size + phsize;
+ src.d_size = gelf_fsize (main, ELF_T_SHDR, shnum, EV_CURRENT);
+ src.d_type = ELF_T_SHDR;
+ if ((size_t) (src.d_buf - undodata->d_buf) > undodata->d_size
+ || undodata->d_size - (src.d_buf - undodata->d_buf) != src.d_size)
+ error (EXIT_FAILURE, 0, _("invalid contents in '%s' section"),
+ ".gnu.prelink_undo");
+
+ const size_t shdr_bytes = shnum * shsize;
+ void *shdr = xmalloc (shdr_bytes);
+ dst.d_buf = shdr;
+ dst.d_size = shdr_bytes;
+ ELF_CHECK (gelf_xlatetom (main, &dst, &src,
+ main_ehdr->e_ident[EI_DATA]) != NULL,
+ _("cannot read '.gnu.prelink_undo' section: %s"));
+
+ undo_sections = xmalloc (shnum * sizeof undo_sections[0]);
+ for (size_t i = 0; i < shnum; ++i)
+ {
+ struct section *sec = &undo_sections[undo_nalloc];
+ Elf32_Shdr (*s32)[shnum] = shdr;
+ Elf64_Shdr (*s64)[shnum] = shdr;
+ if (class32)
+ {
+#define COPY(field) sec->shdr.field = (*s32)[i].field
+ COPY (sh_name);
+ COPY (sh_type);
+ COPY (sh_flags);
+ COPY (sh_addr);
+ COPY (sh_offset);
+ COPY (sh_size);
+ COPY (sh_link);
+ COPY (sh_info);
+ COPY (sh_addralign);
+ COPY (sh_entsize);
+#undef COPY
+ }
+ else
+ sec->shdr = (*s64)[i];
+ if (sec->shdr.sh_flags & SHF_ALLOC)
+ {
+ sec->shdr.sh_addr += bias;
+ sec->name = get_section_name (i + 1, &sec->shdr, main_shstrtab);
+ sec->scn = elf_getscn (main, i + 1); /* Really just for ndx. */
+ sec->outscn = NULL;
+ sec->strent = NULL;
+ ++undo_nalloc;
+ }
+ }
+ qsort (undo_sections, undo_nalloc,
+ sizeof undo_sections[0], compare_sections_nonrel);
+ free (shdr);
+ }
+
+ bool fail = false;
+ inline void check_match (bool match, Elf_Scn *scn, const char *name)
+ {
+ if (!match)
+ {
+ fail = true;
+ error (0, 0, _("cannot find matching section for [%zu] '%s'"),
+ elf_ndxscn (scn), name);
+ }
+ }
+
+ Elf_Scn *scn = NULL;
+ while ((scn = elf_nextscn (debug, scn)) != NULL)
+ {
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+ if (!(shdr->sh_flags & SHF_ALLOC))
+ continue;
+
+ const char *name = get_section_name (elf_ndxscn (scn), shdr,
+ debug_shstrtab);
+
+ if (undo_sections != NULL)
+ {
+ struct section *sec = find_alloc_section (shdr, 0, name,
+ undo_sections,
+ undo_nalloc);
+ if (sec != NULL)
+ {
+ sec->outscn = scn;
+ continue;
+ }
+ }
+
+ /* If there is no prelink info, we are just here to find
+ the sections to give error messages about. */
+ for (size_t i = 0; shdr != NULL && i < nalloc; ++i)
+ if (sections[i].outscn == scn)
+ shdr = NULL;
+ check_match (shdr == NULL, scn, name);
+ }
+
+ if (fail)
+ exit (EXIT_FAILURE);
+
+ /* Now we have lined up output sections for each of the original sections
+ before prelinking. Translate those to the prelinked sections.
+ This matches what prelink's undo_sections does. */
+ struct section *split_bss = NULL;
+ for (size_t i = 0; i < undo_nalloc; ++i)
+ {
+ const struct section *undo_sec = &undo_sections[i];
+
+ const char *name = undo_sec->name;
+ scn = undo_sec->scn; /* This is just for elf_ndxscn. */
+
+ for (size_t j = 0; j < nalloc; ++j)
+ {
+ struct section *sec = §ions[j];
+#define RELA_SCALED(field) \
+ (2 * sec->shdr.field == 3 * undo_sec->shdr.field)
+ if (sec->outscn == NULL
+ && sec->shdr.sh_name == undo_sec->shdr.sh_name
+ && sec->shdr.sh_flags == undo_sec->shdr.sh_flags
+ && sec->shdr.sh_addralign == undo_sec->shdr.sh_addralign
+ && (((sec->shdr.sh_type == undo_sec->shdr.sh_type
+ && sec->shdr.sh_entsize == undo_sec->shdr.sh_entsize
+ && (sec->shdr.sh_size == undo_sec->shdr.sh_size
+ || (sec->shdr.sh_size > undo_sec->shdr.sh_size
+ && main_ehdr->e_type == ET_EXEC
+ && !strcmp (sec->name, ".dynstr"))))
+ || (sec->shdr.sh_size == undo_sec->shdr.sh_size
+ && ((sec->shdr.sh_entsize == undo_sec->shdr.sh_entsize
+ && undo_sec->shdr.sh_type == SHT_NOBITS)
+ || undo_sec->shdr.sh_type == SHT_PROGBITS)
+ && !strcmp (sec->name, ".plt")))
+ || (sec->shdr.sh_type == SHT_RELA
+ && undo_sec->shdr.sh_type == SHT_REL
+ && RELA_SCALED (sh_entsize) && RELA_SCALED (sh_size))
+ || (sec->shdr.sh_entsize == undo_sec->shdr.sh_entsize
+ && (sec->shdr.sh_type == undo_sec->shdr.sh_type
+ || (sec->shdr.sh_type == SHT_PROGBITS
+ && undo_sec->shdr.sh_type == SHT_NOBITS))
+ && sec->shdr.sh_size <= undo_sec->shdr.sh_size
+ && (!strcmp (sec->name, ".bss")
+ || !strcmp (sec->name, ".sbss"))
+ && (sec->shdr.sh_size == undo_sec->shdr.sh_size
+ || (split_bss = sec) > sections))))
+ {
+ sec->outscn = undo_sec->outscn;
+ undo_sec = NULL;
+ break;
+ }
+ }
+
+ check_match (undo_sec == NULL, scn, name);
+ }
+
+ free (undo_sections);
+
+ if (fail)
+ exit (EXIT_FAILURE);
+
+ return split_bss;
+}
+
+/* Create new .shstrtab contents, subroutine of copy_elided_sections.
+ This can't be open coded there and still use variable-length auto arrays,
+ since the end of our block would free other VLAs too. */
+static Elf_Data *
+new_shstrtab (Elf *unstripped, size_t unstripped_shnum,
+ Elf_Data *shstrtab, size_t unstripped_shstrndx,
+ struct section *sections, size_t stripped_shnum,
+ Dwelf_Strtab *strtab)
+{
+ if (strtab == NULL)
+ return NULL;
+
+ Dwelf_Strent *unstripped_strent[unstripped_shnum - 1];
+ memset (unstripped_strent, 0, sizeof unstripped_strent);
+ for (struct section *sec = sections;
+ sec < §ions[stripped_shnum - 1];
+ ++sec)
+ if (sec->outscn != NULL)
+ {
+ if (sec->strent == NULL)
+ {
+ sec->strent = dwelf_strtab_add (strtab, sec->name);
+ ELF_CHECK (sec->strent != NULL,
+ _("cannot add section name to string table: %s"));
+ }
+ unstripped_strent[elf_ndxscn (sec->outscn) - 1] = sec->strent;
+ }
+
+ /* Add names of sections we aren't touching. */
+ for (size_t i = 0; i < unstripped_shnum - 1; ++i)
+ if (unstripped_strent[i] == NULL)
+ {
+ Elf_Scn *scn = elf_getscn (unstripped, i + 1);
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+ const char *name = get_section_name (i + 1, shdr, shstrtab);
+ unstripped_strent[i] = dwelf_strtab_add (strtab, name);
+ ELF_CHECK (unstripped_strent[i] != NULL,
+ _("cannot add section name to string table: %s"));
+ }
+ else
+ unstripped_strent[i] = NULL;
+
+ /* Now finalize the string table so we can get offsets. */
+ Elf_Data *strtab_data = elf_getdata (elf_getscn (unstripped,
+ unstripped_shstrndx), NULL);
+ ELF_CHECK (elf_flagdata (strtab_data, ELF_C_SET, ELF_F_DIRTY),
+ _("cannot update section header string table data: %s"));
+ if (dwelf_strtab_finalize (strtab, strtab_data) == NULL)
+ error (EXIT_FAILURE, 0, "Not enough memory to create string table");
+
+ /* Update the sh_name fields of sections we aren't modifying later. */
+ for (size_t i = 0; i < unstripped_shnum - 1; ++i)
+ if (unstripped_strent[i] != NULL)
+ {
+ Elf_Scn *scn = elf_getscn (unstripped, i + 1);
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+ shdr->sh_name = dwelf_strent_off (unstripped_strent[i]);
+ if (i + 1 == unstripped_shstrndx)
+ shdr->sh_size = strtab_data->d_size;
+ update_shdr (scn, shdr);
+ }
+
+ return strtab_data;
+}
+
+/* Fill in any SHT_NOBITS sections in UNSTRIPPED by
+ copying their contents and sh_type from STRIPPED. */
+static void
+copy_elided_sections (Elf *unstripped, Elf *stripped,
+ const GElf_Ehdr *stripped_ehdr, GElf_Addr bias)
+{
+ size_t unstripped_shstrndx;
+ ELF_CHECK (elf_getshdrstrndx (unstripped, &unstripped_shstrndx) == 0,
+ _("cannot get section header string table section index: %s"));
+
+ size_t stripped_shstrndx;
+ ELF_CHECK (elf_getshdrstrndx (stripped, &stripped_shstrndx) == 0,
+ _("cannot get section header string table section index: %s"));
+
+ size_t unstripped_shnum;
+ ELF_CHECK (elf_getshdrnum (unstripped, &unstripped_shnum) == 0,
+ _("cannot get section count: %s"));
+
+ size_t stripped_shnum;
+ ELF_CHECK (elf_getshdrnum (stripped, &stripped_shnum) == 0,
+ _("cannot get section count: %s"));
+
+ if (unlikely (stripped_shnum > unstripped_shnum))
+ error (EXIT_FAILURE, 0, _("\
+more sections in stripped file than debug file -- arguments reversed?"));
+
+ /* Cache the stripped file's section details. */
+ struct section sections[stripped_shnum - 1];
+ Elf_Scn *scn = NULL;
+ while ((scn = elf_nextscn (stripped, scn)) != NULL)
+ {
+ size_t i = elf_ndxscn (scn) - 1;
+ GElf_Shdr *shdr = gelf_getshdr (scn, §ions[i].shdr);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+ sections[i].name = elf_strptr (stripped, stripped_shstrndx,
+ shdr->sh_name);
+ if (sections[i].name == NULL)
+ error (EXIT_FAILURE, 0, _("cannot read section [%zu] name: %s"),
+ elf_ndxscn (scn), elf_errmsg (-1));
+ sections[i].scn = scn;
+ sections[i].outscn = NULL;
+ sections[i].strent = NULL;
+ }
+
+ const struct section *stripped_symtab = NULL;
+
+ /* Sort the sections, allocated by address and others after. */
+ qsort (sections, stripped_shnum - 1, sizeof sections[0],
+ stripped_ehdr->e_type == ET_REL
+ ? compare_sections_rel : compare_sections_nonrel);
+ size_t nalloc = stripped_shnum - 1;
+ while (nalloc > 0 && !(sections[nalloc - 1].shdr.sh_flags & SHF_ALLOC))
+ {
+ --nalloc;
+ if (sections[nalloc].shdr.sh_type == SHT_SYMTAB)
+ stripped_symtab = §ions[nalloc];
+ }
+
+ /* Locate a matching unallocated section in SECTIONS. */
+ inline struct section *find_unalloc_section (const GElf_Shdr *shdr,
+ const char *name)
+ {
+ size_t l = nalloc, u = stripped_shnum - 1;
+ while (l < u)
+ {
+ size_t i = (l + u) / 2;
+ struct section *sec = §ions[i];
+ int cmp = compare_unalloc_sections (shdr, &sec->shdr,
+ name, sec->name);
+ if (cmp < 0)
+ u = i;
+ else if (cmp > 0)
+ l = i + 1;
+ else
+ return sec;
+ }
+ return NULL;
+ }
+
+ Elf_Data *shstrtab = elf_getdata (elf_getscn (unstripped,
+ unstripped_shstrndx), NULL);
+ ELF_CHECK (shstrtab != NULL,
+ _("cannot read section header string table: %s"));
+
+ /* Match each debuginfo section with its corresponding stripped section. */
+ bool check_prelink = false;
+ Elf_Scn *unstripped_symtab = NULL;
+ size_t unstripped_strndx = 0;
+ size_t alloc_avail = 0;
+ scn = NULL;
+ while ((scn = elf_nextscn (unstripped, scn)) != NULL)
+ {
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+ if (shdr->sh_type == SHT_SYMTAB)
+ {
+ unstripped_symtab = scn;
+ unstripped_strndx = shdr->sh_link;
+ continue;
+ }
+
+ const size_t ndx = elf_ndxscn (scn);
+ if (ndx == unstripped_shstrndx || ndx == unstripped_strndx)
+ continue;
+
+ const char *name = get_section_name (ndx, shdr, shstrtab);
+
+ struct section *sec = NULL;
+ if (shdr->sh_flags & SHF_ALLOC)
+ {
+ if (stripped_ehdr->e_type != ET_REL)
+ {
+ /* Look for the section that matches. */
+ sec = find_alloc_section (shdr, bias, name, sections, nalloc);
+ if (sec == NULL)
+ {
+ /* We couldn't figure it out. It may be a prelink issue. */
+ check_prelink = true;
+ continue;
+ }
+ }
+ else
+ {
+ /* The sh_addr of allocated sections does not help us,
+ but the order usually matches. */
+ if (likely (sections_match (sections, alloc_avail, shdr, name)))
+ sec = §ions[alloc_avail++];
+ else
+ for (size_t i = alloc_avail + 1; i < nalloc; ++i)
+ if (sections_match (sections, i, shdr, name))
+ {
+ sec = §ions[i];
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* Look for the section that matches. */
+ sec = find_unalloc_section (shdr, name);
+ if (sec == NULL)
+ {
+ /* An additional unallocated section is fine if not SHT_NOBITS.
+ We looked it up anyway in case it's an unallocated section
+ copied in both files (e.g. SHT_NOTE), and don't keep both. */
+ if (shdr->sh_type != SHT_NOBITS)
+ continue;
+
+ /* Somehow some old .debug files wound up with SHT_NOBITS
+ .comment sections, so let those pass. */
+ if (!strcmp (name, ".comment"))
+ continue;
+ }
+ }
+
+ if (sec == NULL)
+ error (EXIT_FAILURE, 0,
+ _("cannot find matching section for [%zu] '%s'"),
+ elf_ndxscn (scn), name);
+
+ sec->outscn = scn;
+ }
+
+ /* If that failed due to changes made by prelink, we take another tack.
+ We keep track of a .bss section that was partly split into .dynbss
+ so that collect_symbols can update symbols' st_shndx fields. */
+ struct section *split_bss = NULL;
+ if (check_prelink)
+ {
+ Elf_Data *data = elf_getdata (elf_getscn (stripped, stripped_shstrndx),
+ NULL);
+ ELF_CHECK (data != NULL,
+ _("cannot read section header string table: %s"));
+ split_bss = find_alloc_sections_prelink (unstripped, shstrtab,
+ stripped, stripped_ehdr,
+ data, bias, sections,
+ nalloc, stripped_shnum - 1);
+ }
+
+ /* Make sure each main file section has a place to go. */
+ const struct section *stripped_dynsym = NULL;
+ size_t debuglink = SHN_UNDEF;
+ size_t ndx_section[stripped_shnum - 1];
+ Dwelf_Strtab *strtab = NULL;
+ for (struct section *sec = sections;
+ sec < §ions[stripped_shnum - 1];
+ ++sec)
+ {
+ size_t secndx = elf_ndxscn (sec->scn);
+
+ if (sec->outscn == NULL)
+ {
+ /* We didn't find any corresponding section for this. */
+
+ if (secndx == stripped_shstrndx)
+ {
+ /* We only need one .shstrtab. */
+ ndx_section[secndx - 1] = unstripped_shstrndx;
+ continue;
+ }
+
+ if (unstripped_symtab != NULL && sec == stripped_symtab)
+ {
+ /* We don't need a second symbol table. */
+ ndx_section[secndx - 1] = elf_ndxscn (unstripped_symtab);
+ continue;
+ }
+
+ if (unstripped_symtab != NULL && stripped_symtab != NULL
+ && secndx == stripped_symtab->shdr.sh_link
+ && unstripped_strndx != 0)
+ {
+ /* ... nor its string table. */
+ ndx_section[secndx - 1] = unstripped_strndx;
+ continue;
+ }
+
+ if (!(sec->shdr.sh_flags & SHF_ALLOC)
+ && !strcmp (sec->name, ".gnu_debuglink"))
+ {
+ /* This was created by stripping. We don't want it. */
+ debuglink = secndx;
+ ndx_section[secndx - 1] = SHN_UNDEF;
+ continue;
+ }
+
+ sec->outscn = elf_newscn (unstripped);
+ Elf_Data *newdata = elf_newdata (sec->outscn);
+ ELF_CHECK (newdata != NULL && gelf_update_shdr (sec->outscn,
+ &sec->shdr),
+ _("cannot add new section: %s"));
+
+ if (strtab == NULL)
+ strtab = dwelf_strtab_init (true);
+ sec->strent = dwelf_strtab_add (strtab, sec->name);
+ ELF_CHECK (sec->strent != NULL,
+ _("cannot add section name to string table: %s"));
+ }
+
+ /* Cache the mapping of original section indices to output sections. */
+ ndx_section[secndx - 1] = elf_ndxscn (sec->outscn);
+ }
+
+ /* We added some sections, so we need a new shstrtab. */
+ Elf_Data *strtab_data = new_shstrtab (unstripped, unstripped_shnum,
+ shstrtab, unstripped_shstrndx,
+ sections, stripped_shnum,
+ strtab);
+
+ /* Get the updated section count. */
+ ELF_CHECK (elf_getshdrnum (unstripped, &unstripped_shnum) == 0,
+ _("cannot get section count: %s"));
+
+ bool placed[unstripped_shnum - 1];
+ memset (placed, 0, sizeof placed);
+
+ /* Now update the output sections and copy in their data. */
+ GElf_Off offset = 0;
+ for (const struct section *sec = sections;
+ sec < §ions[stripped_shnum - 1];
+ ++sec)
+ if (sec->outscn != NULL)
+ {
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (sec->outscn, &shdr_mem);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+ /* In an ET_REL file under --relocate, the sh_addr of SHF_ALLOC
+ sections will have been set nonzero by relocation. This
+ touched the shdrs of whichever file had the symtab. sh_addr
+ is still zero in the corresponding shdr. The relocated
+ address is what we want to use. */
+ if (stripped_ehdr->e_type != ET_REL
+ || !(shdr_mem.sh_flags & SHF_ALLOC)
+ || shdr_mem.sh_addr == 0)
+ shdr_mem.sh_addr = sec->shdr.sh_addr;
+
+ shdr_mem.sh_type = sec->shdr.sh_type;
+ shdr_mem.sh_size = sec->shdr.sh_size;
+ shdr_mem.sh_info = sec->shdr.sh_info;
+ shdr_mem.sh_link = sec->shdr.sh_link;
+
+ /* Buggy binutils objdump might have stripped the SHF_INFO_LINK
+ put it back if necessary. */
+ if ((sec->shdr.sh_type == SHT_REL || sec->shdr.sh_type == SHT_RELA)
+ && sec->shdr.sh_flags != shdr_mem.sh_flags
+ && (sec->shdr.sh_flags & SHF_INFO_LINK) != 0)
+ shdr_mem.sh_flags |= SHF_INFO_LINK;
+
+ if (sec->shdr.sh_link != SHN_UNDEF)
+ shdr_mem.sh_link = ndx_section[sec->shdr.sh_link - 1];
+ if (SH_INFO_LINK_P (&sec->shdr) && sec->shdr.sh_info != 0)
+ shdr_mem.sh_info = ndx_section[sec->shdr.sh_info - 1];
+
+ if (strtab != NULL)
+ shdr_mem.sh_name = dwelf_strent_off (sec->strent);
+
+ Elf_Data *indata = elf_getdata (sec->scn, NULL);
+ ELF_CHECK (indata != NULL, _("cannot get section data: %s"));
+ Elf_Data *outdata = elf_getdata (sec->outscn, NULL);
+ ELF_CHECK (outdata != NULL, _("cannot copy section data: %s"));
+ *outdata = *indata;
+ elf_flagdata (outdata, ELF_C_SET, ELF_F_DIRTY);
+
+ /* Preserve the file layout of the allocated sections. */
+ if (stripped_ehdr->e_type != ET_REL && (shdr_mem.sh_flags & SHF_ALLOC))
+ {
+ shdr_mem.sh_offset = sec->shdr.sh_offset;
+ placed[elf_ndxscn (sec->outscn) - 1] = true;
+
+ const GElf_Off end_offset = (shdr_mem.sh_offset
+ + (shdr_mem.sh_type == SHT_NOBITS
+ ? 0 : shdr_mem.sh_size));
+ if (end_offset > offset)
+ offset = end_offset;
+ }
+
+ update_shdr (sec->outscn, &shdr_mem);
+
+ if (shdr_mem.sh_type == SHT_SYMTAB || shdr_mem.sh_type == SHT_DYNSYM)
+ {
+ /* We must adjust all the section indices in the symbol table. */
+
+ Elf_Data *shndxdata = NULL; /* XXX */
+
+ for (size_t i = 1; i < shdr_mem.sh_size / shdr_mem.sh_entsize; ++i)
+ {
+ GElf_Sym sym_mem;
+ GElf_Word shndx = SHN_UNDEF;
+ GElf_Sym *sym = gelf_getsymshndx (outdata, shndxdata,
+ i, &sym_mem, &shndx);
+ ELF_CHECK (sym != NULL,
+ _("cannot get symbol table entry: %s"));
+ if (sym->st_shndx != SHN_XINDEX)
+ shndx = sym->st_shndx;
+
+ if (shndx != SHN_UNDEF && shndx < SHN_LORESERVE)
+ {
+ if (shndx >= stripped_shnum)
+ error (EXIT_FAILURE, 0,
+ _("symbol [%zu] has invalid section index"), i);
+
+ shndx = ndx_section[shndx - 1];
+ if (shndx < SHN_LORESERVE)
+ {
+ sym->st_shndx = shndx;
+ shndx = SHN_UNDEF;
+ }
+ else
+ sym->st_shndx = SHN_XINDEX;
+
+ ELF_CHECK (gelf_update_symshndx (outdata, shndxdata,
+ i, sym, shndx),
+ _("cannot update symbol table: %s"));
+ }
+ }
+
+ if (shdr_mem.sh_type == SHT_SYMTAB)
+ stripped_symtab = sec;
+ if (shdr_mem.sh_type == SHT_DYNSYM)
+ stripped_dynsym = sec;
+ }
+ }
+
+ /* We may need to update the symbol table. */
+ Elf_Data *symdata = NULL;
+ Dwelf_Strtab *symstrtab = NULL;
+ Elf_Data *symstrdata = NULL;
+ if (unstripped_symtab != NULL && (stripped_symtab != NULL
+ || check_prelink /* Section adjustments. */
+ || (stripped_ehdr->e_type != ET_REL
+ && bias != 0)))
+ {
+ /* Merge the stripped file's symbol table into the unstripped one. */
+ const size_t stripped_nsym = (stripped_symtab == NULL ? 1
+ : (stripped_symtab->shdr.sh_size
+ / stripped_symtab->shdr.sh_entsize));
+
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (unstripped_symtab, &shdr_mem);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+ const size_t unstripped_nsym = shdr->sh_size / shdr->sh_entsize;
+
+ /* First collect all the symbols from both tables. */
+
+ const size_t total_syms = stripped_nsym - 1 + unstripped_nsym - 1;
+ struct symbol symbols[total_syms];
+ size_t symndx_map[total_syms];
+
+ if (stripped_symtab != NULL)
+ collect_symbols (unstripped, stripped_ehdr->e_type == ET_REL,
+ stripped_symtab->scn,
+ elf_getscn (stripped, stripped_symtab->shdr.sh_link),
+ stripped_nsym, 0, ndx_section,
+ symbols, symndx_map, NULL);
+
+ Elf_Scn *unstripped_strtab = elf_getscn (unstripped, shdr->sh_link);
+ collect_symbols (unstripped, stripped_ehdr->e_type == ET_REL,
+ unstripped_symtab, unstripped_strtab, unstripped_nsym,
+ stripped_ehdr->e_type == ET_REL ? 0 : bias, NULL,
+ &symbols[stripped_nsym - 1],
+ &symndx_map[stripped_nsym - 1], split_bss);
+
+ /* Next, sort our array of all symbols. */
+ qsort (symbols, total_syms, sizeof symbols[0], compare_symbols);
+
+ /* Now we can weed out the duplicates. Assign remaining symbols
+ new slots, collecting a map from old indices to new. */
+ size_t nsym = 0;
+ for (struct symbol *s = symbols; s < &symbols[total_syms]; ++s)
+ {
+ /* Skip a section symbol for a removed section. */
+ if (s->shndx == SHN_UNDEF
+ && GELF_ST_TYPE (s->info.info) == STT_SECTION)
+ {
+ s->name = NULL; /* Mark as discarded. */
+ *s->map = STN_UNDEF;
+ s->duplicate = NULL;
+ continue;
+ }
+
+ struct symbol *n = s;
+ while (n + 1 < &symbols[total_syms] && !compare_symbols (s, n + 1))
+ ++n;
+
+ while (s < n)
+ {
+ /* This is a duplicate. Its twin will get the next slot. */
+ s->name = NULL; /* Mark as discarded. */
+ s->duplicate = n->map;
+ ++s;
+ }
+
+ /* Allocate the next slot. */
+ *s->map = ++nsym;
+ }
+
+ /* Now we sort again, to determine the order in the output. */
+ qsort (symbols, total_syms, sizeof symbols[0], compare_symbols_output);
+
+ if (nsym < total_syms)
+ /* The discarded symbols are now at the end of the table. */
+ assert (symbols[nsym].name == NULL);
+
+ /* Now a final pass updates the map with the final order,
+ and builds up the new string table. */
+ symstrtab = dwelf_strtab_init (true);
+ for (size_t i = 0; i < nsym; ++i)
+ {
+ assert (symbols[i].name != NULL);
+ assert (*symbols[i].map != 0);
+ *symbols[i].map = 1 + i;
+ symbols[i].strent = dwelf_strtab_add (symstrtab, symbols[i].name);
+ }
+
+ /* Scan the discarded symbols too, just to update their slots
+ in SYMNDX_MAP to refer to their live duplicates. */
+ for (size_t i = nsym; i < total_syms; ++i)
+ {
+ assert (symbols[i].name == NULL);
+ if (symbols[i].duplicate == NULL)
+ assert (*symbols[i].map == STN_UNDEF);
+ else
+ {
+ assert (*symbols[i].duplicate != STN_UNDEF);
+ *symbols[i].map = *symbols[i].duplicate;
+ }
+ }
+
+ /* Now we are ready to write the new symbol table. */
+ symdata = elf_getdata (unstripped_symtab, NULL);
+ symstrdata = elf_getdata (unstripped_strtab, NULL);
+ Elf_Data *shndxdata = NULL; /* XXX */
+
+ /* If symtab and the section header table share the string table
+ add the section names to the strtab and then (after finalizing)
+ fixup the section header sh_names. Also dispose of the old data. */
+ Dwelf_Strent *unstripped_strent[unstripped_shnum - 1];
+ if (unstripped_shstrndx == elf_ndxscn (unstripped_strtab))
+ {
+ for (size_t i = 0; i < unstripped_shnum - 1; ++i)
+ {
+ Elf_Scn *sec = elf_getscn (unstripped, i + 1);
+ GElf_Shdr mem;
+ GElf_Shdr *hdr = gelf_getshdr (sec, &mem);
+ const char *name = get_section_name (i + 1, hdr, shstrtab);
+ unstripped_strent[i] = dwelf_strtab_add (symstrtab, name);
+ ELF_CHECK (unstripped_strent[i] != NULL,
+ _("cannot add section name to string table: %s"));
+ }
+
+ if (strtab != NULL)
+ {
+ dwelf_strtab_free (strtab);
+ free (strtab_data->d_buf);
+ strtab = NULL;
+ }
+ }
+
+ if (dwelf_strtab_finalize (symstrtab, symstrdata) == NULL)
+ error (EXIT_FAILURE, 0, "Not enough memory to create symbol table");
+
+ elf_flagdata (symstrdata, ELF_C_SET, ELF_F_DIRTY);
+
+ /* And update the section header names if necessary. */
+ if (unstripped_shstrndx == elf_ndxscn (unstripped_strtab))
+ {
+ for (size_t i = 0; i < unstripped_shnum - 1; ++i)
+ {
+ Elf_Scn *sec = elf_getscn (unstripped, i + 1);
+ GElf_Shdr mem;
+ GElf_Shdr *hdr = gelf_getshdr (sec, &mem);
+ shdr->sh_name = dwelf_strent_off (unstripped_strent[i]);
+ update_shdr (sec, hdr);
+ }
+ }
+
+ /* Now update the symtab shdr. Reload symtab shdr because sh_name
+ might have changed above. */
+ shdr = gelf_getshdr (unstripped_symtab, &shdr_mem);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+ shdr->sh_size = symdata->d_size = (1 + nsym) * shdr->sh_entsize;
+ symdata->d_buf = xmalloc (symdata->d_size);
+ record_new_data (symdata->d_buf);
+
+ GElf_Sym sym;
+ memset (&sym, 0, sizeof sym);
+ ELF_CHECK (gelf_update_symshndx (symdata, shndxdata, 0, &sym, SHN_UNDEF),
+ _("cannot update symbol table: %s"));
+
+ shdr->sh_info = 1;
+ for (size_t i = 0; i < nsym; ++i)
+ {
+ struct symbol *s = &symbols[i];
+
+ /* Fill in the symbol details. */
+ sym.st_name = dwelf_strent_off (s->strent);
+ sym.st_value = s->value; /* Already biased to output address. */
+ sym.st_size = s->size;
+ sym.st_shndx = s->shndx; /* Already mapped to output index. */
+ sym.st_info = s->info.info;
+ sym.st_other = s->info.other;
+
+ /* Keep track of the number of leading local symbols. */
+ if (GELF_ST_BIND (sym.st_info) == STB_LOCAL)
+ {
+ assert (shdr->sh_info == 1 + i);
+ shdr->sh_info = 1 + i + 1;
+ }
+
+ ELF_CHECK (gelf_update_symshndx (symdata, shndxdata, 1 + i,
+ &sym, SHN_UNDEF),
+ _("cannot update symbol table: %s"));
+
+ }
+ elf_flagdata (symdata, ELF_C_SET, ELF_F_DIRTY);
+ update_shdr (unstripped_symtab, shdr);
+
+ if (stripped_symtab != NULL)
+ {
+ /* Adjust any relocations referring to the old symbol table. */
+ const size_t old_sh_link = elf_ndxscn (stripped_symtab->scn);
+ for (const struct section *sec = sections;
+ sec < §ions[stripped_shnum - 1];
+ ++sec)
+ if (sec->outscn != NULL && sec->shdr.sh_link == old_sh_link)
+ adjust_relocs (sec->outscn, sec->scn, &sec->shdr,
+ symndx_map, shdr);
+ }
+
+ /* Also adjust references to the other old symbol table. */
+ adjust_all_relocs (unstripped, unstripped_symtab, shdr,
+ &symndx_map[stripped_nsym - 1]);
+ }
+ else if (stripped_symtab != NULL && stripped_shnum != unstripped_shnum)
+ check_symtab_section_symbols (unstripped,
+ stripped_ehdr->e_type == ET_REL,
+ stripped_symtab->scn,
+ unstripped_shnum, unstripped_shstrndx,
+ stripped_symtab->outscn,
+ stripped_shnum, stripped_shstrndx,
+ debuglink);
+
+ if (stripped_dynsym != NULL)
+ (void) check_symtab_section_symbols (unstripped,
+ stripped_ehdr->e_type == ET_REL,
+ stripped_dynsym->outscn,
+ unstripped_shnum,
+ unstripped_shstrndx,
+ stripped_dynsym->scn, stripped_shnum,
+ stripped_shstrndx, debuglink);
+
+ /* We need to preserve the layout of the stripped file so the
+ phdrs will match up. This requires us to do our own layout of
+ the added sections. We do manual layout even for ET_REL just
+ so we can try to match what the original probably had. */
+
+ elf_flagelf (unstripped, ELF_C_SET, ELF_F_LAYOUT);
+
+ if (offset == 0)
+ /* For ET_REL we are starting the layout from scratch. */
+ offset = gelf_fsize (unstripped, ELF_T_EHDR, 1, EV_CURRENT);
+
+ bool skip_reloc = false;
+ do
+ {
+ skip_reloc = !skip_reloc;
+ for (size_t i = 0; i < unstripped_shnum - 1; ++i)
+ if (!placed[i])
+ {
+ scn = elf_getscn (unstripped, 1 + i);
+
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+ ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+ /* We must make sure we have read in the data of all sections
+ beforehand and marked them to be written out. When we're
+ modifying the existing file in place, we might overwrite
+ this part of the file before we get to handling the section. */
+
+ ELF_CHECK (elf_flagdata (elf_getdata (scn, NULL),
+ ELF_C_SET, ELF_F_DIRTY),
+ _("cannot read section data: %s"));
+
+ if (skip_reloc
+ && (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA))
+ continue;
+
+ GElf_Off align = shdr->sh_addralign ?: 1;
+ offset = (offset + align - 1) & -align;
+ shdr->sh_offset = offset;
+ if (shdr->sh_type != SHT_NOBITS)
+ offset += shdr->sh_size;
+
+ update_shdr (scn, shdr);
+
+ if (unstripped_shstrndx == 1 + i)
+ {
+ /* Place the section headers immediately after
+ .shstrtab, and update the ELF header. */
+
+ GElf_Ehdr ehdr_mem;
+ GElf_Ehdr *ehdr = gelf_getehdr (unstripped, &ehdr_mem);
+ ELF_CHECK (ehdr != NULL, _("cannot get ELF header: %s"));
+
+ GElf_Off sh_align = gelf_getclass (unstripped) * 4;
+ offset = (offset + sh_align - 1) & -sh_align;
+ ehdr->e_shnum = unstripped_shnum;
+ ehdr->e_shoff = offset;
+ offset += unstripped_shnum * ehdr->e_shentsize;
+ ELF_CHECK (gelf_update_ehdr (unstripped, ehdr),
+ _("cannot update ELF header: %s"));
+ }
+
+ placed[i] = true;
+ }
+ }
+ while (skip_reloc);
+
+ size_t phnum;
+ ELF_CHECK (elf_getphdrnum (stripped, &phnum) == 0,
+ _("cannot get number of program headers: %s"));
+
+ if (phnum > 0)
+ ELF_CHECK (gelf_newphdr (unstripped, phnum),
+ _("cannot create program headers: %s"));
+
+ /* Copy each program header from the stripped file. */
+ for (size_t i = 0; i < phnum; ++i)
+ {
+ GElf_Phdr phdr_mem;
+ GElf_Phdr *phdr = gelf_getphdr (stripped, i, &phdr_mem);
+ ELF_CHECK (phdr != NULL, _("cannot get program header: %s"));
+
+ ELF_CHECK (gelf_update_phdr (unstripped, i, phdr),
+ _("cannot update program header: %s"));
+ }
+
+ /* Finally, write out the file. */
+ ELF_CHECK (elf_update (unstripped, ELF_C_WRITE) > 0,
+ _("cannot write output file: %s"));
+
+ if (strtab != NULL)
+ {
+ dwelf_strtab_free (strtab);
+ free (strtab_data->d_buf);
+ }
+
+ if (symstrtab != NULL)
+ {
+ dwelf_strtab_free (symstrtab);
+ free (symstrdata->d_buf);
+ }
+ free_new_data ();
+}
+
+/* Process one pair of files, already opened. */
+static void
+handle_file (const char *output_file, bool create_dirs,
+ Elf *stripped, const GElf_Ehdr *stripped_ehdr,
+ Elf *unstripped)
+{
+ size_t phnum;
+ ELF_CHECK (elf_getphdrnum (stripped, &phnum) == 0,
+ _("cannot get number of program headers: %s"));
+
+ /* Determine the address bias between the debuginfo file and the main
+ file, which may have been modified by prelinking. */
+ GElf_Addr bias = 0;
+ if (unstripped != NULL)
+ for (size_t i = 0; i < phnum; ++i)
+ {
+ GElf_Phdr phdr_mem;
+ GElf_Phdr *phdr = gelf_getphdr (stripped, i, &phdr_mem);
+ ELF_CHECK (phdr != NULL, _("cannot get program header: %s"));
+ if (phdr->p_type == PT_LOAD)
+ {
+ GElf_Phdr unstripped_phdr_mem;
+ GElf_Phdr *unstripped_phdr = gelf_getphdr (unstripped, i,
+ &unstripped_phdr_mem);
+ ELF_CHECK (unstripped_phdr != NULL,
+ _("cannot get program header: %s"));
+ bias = phdr->p_vaddr - unstripped_phdr->p_vaddr;
+ break;
+ }
+ }
+
+ /* One day we could adjust all the DWARF data (like prelink itself does). */
+ if (bias != 0)
+ {
+ if (output_file == NULL)
+ error (0, 0, _("\
+DWARF data not adjusted for prelinking bias; consider prelink -u"));
+ else
+ error (0, 0, _("\
+DWARF data in '%s' not adjusted for prelinking bias; consider prelink -u"),
+ output_file);
+ }
+
+ if (output_file == NULL)
+ /* Modify the unstripped file in place. */
+ copy_elided_sections (unstripped, stripped, stripped_ehdr, bias);
+ else
+ {
+ if (create_dirs)
+ make_directories (output_file);
+
+ /* Copy the unstripped file and then modify it. */
+ int outfd = open (output_file, O_RDWR | O_CREAT,
+ stripped_ehdr->e_type == ET_REL ? 0666 : 0777);
+ if (outfd < 0)
+ error (EXIT_FAILURE, errno, _("cannot open '%s'"), output_file);
+ Elf *outelf = elf_begin (outfd, ELF_C_WRITE, NULL);
+ ELF_CHECK (outelf != NULL, _("cannot create ELF descriptor: %s"));
+
+ if (unstripped == NULL)
+ {
+ /* Actually, we are just copying out the main file as it is. */
+ copy_elf (outelf, stripped);
+ if (stripped_ehdr->e_type != ET_REL)
+ elf_flagelf (outelf, ELF_C_SET, ELF_F_LAYOUT);
+ ELF_CHECK (elf_update (outelf, ELF_C_WRITE) > 0,
+ _("cannot write output file: %s"));
+ }
+ else
+ {
+ copy_elf (outelf, unstripped);
+ copy_elided_sections (outelf, stripped, stripped_ehdr, bias);
+ }
+
+ elf_end (outelf);
+ close (outfd);
+ }
+}
+
+static int
+open_file (const char *file, bool writable)
+{
+ int fd = open (file, writable ? O_RDWR : O_RDONLY);
+ if (fd < 0)
+ error (EXIT_FAILURE, errno, _("cannot open '%s'"), file);
+ return fd;
+}
+
+/* Handle a pair of files we need to open by name. */
+static void
+handle_explicit_files (const char *output_file, bool create_dirs, bool force,
+ const char *stripped_file, const char *unstripped_file)
+{
+
+ /* Warn, and exit if not forced to continue, if some ELF header
+ sanity check for the stripped and unstripped files failed. */
+ void warn (const char *msg)
+ {
+ error (force ? 0 : EXIT_FAILURE, 0, "%s'%s' and '%s' %s%s.",
+ force ? _("WARNING: ") : "",
+ stripped_file, unstripped_file, msg,
+ force ? "" : _(", use --force"));
+ }
+
+ int stripped_fd = open_file (stripped_file, false);
+ Elf *stripped = elf_begin (stripped_fd, ELF_C_READ, NULL);
+ GElf_Ehdr stripped_ehdr;
+ ELF_CHECK (gelf_getehdr (stripped, &stripped_ehdr),
+ _("cannot create ELF descriptor: %s"));
+
+ int unstripped_fd = -1;
+ Elf *unstripped = NULL;
+ if (unstripped_file != NULL)
+ {
+ unstripped_fd = open_file (unstripped_file, output_file == NULL);
+ unstripped = elf_begin (unstripped_fd,
+ (output_file == NULL ? ELF_C_RDWR : ELF_C_READ),
+ NULL);
+ GElf_Ehdr unstripped_ehdr;
+ ELF_CHECK (gelf_getehdr (unstripped, &unstripped_ehdr),
+ _("cannot create ELF descriptor: %s"));
+
+ if (memcmp (stripped_ehdr.e_ident,
+ unstripped_ehdr.e_ident, EI_NIDENT) != 0)
+ warn (_("ELF header identification (e_ident) different"));
+
+ if (stripped_ehdr.e_type != unstripped_ehdr.e_type)
+ warn (_("ELF header type (e_type) different"));
+
+ if (stripped_ehdr.e_machine != unstripped_ehdr.e_machine)
+ warn (_("ELF header machine type (e_machine) different"));
+
+ if (stripped_ehdr.e_phnum < unstripped_ehdr.e_phnum)
+ warn (_("stripped program header (e_phnum) smaller than unstripped"));
+ }
+
+ handle_file (output_file, create_dirs, stripped, &stripped_ehdr, unstripped);
+
+ elf_end (stripped);
+ close (stripped_fd);
+
+ elf_end (unstripped);
+ close (unstripped_fd);
+}
+
+
+/* Handle a pair of files opened implicitly by libdwfl for one module. */
+static void
+handle_dwfl_module (const char *output_file, bool create_dirs, bool force,
+ Dwfl_Module *mod, bool all, bool ignore, bool relocate)
+{
+ GElf_Addr bias;
+ Elf *stripped = dwfl_module_getelf (mod, &bias);
+ if (stripped == NULL)
+ {
+ if (ignore)
+ return;
+
+ const char *file;
+ const char *modname = dwfl_module_info (mod, NULL, NULL, NULL,
+ NULL, NULL, &file, NULL);
+ if (file == NULL)
+ error (EXIT_FAILURE, 0,
+ _("cannot find stripped file for module '%s': %s"),
+ modname, dwfl_errmsg (-1));
+ else
+ error (EXIT_FAILURE, 0,
+ _("cannot open stripped file '%s' for module '%s': %s"),
+ modname, file, dwfl_errmsg (-1));
+ }
+
+ Elf *debug = dwarf_getelf (dwfl_module_getdwarf (mod, &bias));
+ if (debug == NULL && !all)
+ {
+ if (ignore)
+ return;
+
+ const char *file;
+ const char *modname = dwfl_module_info (mod, NULL, NULL, NULL,
+ NULL, NULL, NULL, &file);
+ if (file == NULL)
+ error (EXIT_FAILURE, 0,
+ _("cannot find debug file for module '%s': %s"),
+ modname, dwfl_errmsg (-1));
+ else
+ error (EXIT_FAILURE, 0,
+ _("cannot open debug file '%s' for module '%s': %s"),
+ modname, file, dwfl_errmsg (-1));
+ }
+
+ if (debug == stripped)
+ {
+ if (all)
+ debug = NULL;
+ else
+ {
+ const char *file;
+ const char *modname = dwfl_module_info (mod, NULL, NULL, NULL,
+ NULL, NULL, &file, NULL);
+ error (EXIT_FAILURE, 0, _("module '%s' file '%s' is not stripped"),
+ modname, file);
+ }
+ }
+
+ GElf_Ehdr stripped_ehdr;
+ ELF_CHECK (gelf_getehdr (stripped, &stripped_ehdr),
+ _("cannot create ELF descriptor: %s"));
+
+ if (stripped_ehdr.e_type == ET_REL)
+ {
+ if (!relocate)
+ {
+ /* We can't use the Elf handles already open,
+ because the DWARF sections have been relocated. */
+
+ const char *stripped_file = NULL;
+ const char *unstripped_file = NULL;
+ (void) dwfl_module_info (mod, NULL, NULL, NULL, NULL, NULL,
+ &stripped_file, &unstripped_file);
+
+ handle_explicit_files (output_file, create_dirs, force,
+ stripped_file, unstripped_file);
+ return;
+ }
+
+ /* Relocation is what we want! This ensures that all sections that can
+ get sh_addr values assigned have them, even ones not used in DWARF.
+ They might still be used in the symbol table. */
+ if (dwfl_module_relocations (mod) < 0)
+ error (EXIT_FAILURE, 0,
+ _("cannot cache section addresses for module '%s': %s"),
+ dwfl_module_info (mod, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
+ dwfl_errmsg (-1));
+ }
+
+ handle_file (output_file, create_dirs, stripped, &stripped_ehdr, debug);
+}
+
+/* Handle one module being written to the output directory. */
+static void
+handle_output_dir_module (const char *output_dir, Dwfl_Module *mod, bool force,
+ bool all, bool ignore, bool modnames, bool relocate)
+{
+ if (! modnames)
+ {
+ /* Make sure we've searched for the ELF file. */
+ GElf_Addr bias;
+ (void) dwfl_module_getelf (mod, &bias);
+ }
+
+ const char *file;
+ const char *name = dwfl_module_info (mod, NULL, NULL, NULL,
+ NULL, NULL, &file, NULL);
+
+ if (file == NULL && ignore)
+ return;
+
+ char *output_file;
+ if (asprintf (&output_file, "%s/%s", output_dir, modnames ? name : file) < 0)
+ error (EXIT_FAILURE, 0, _("memory exhausted"));
+
+ handle_dwfl_module (output_file, true, force, mod, all, ignore, relocate);
+}
+
+
+static void
+list_module (Dwfl_Module *mod)
+{
+ /* Make sure we have searched for the files. */
+ GElf_Addr bias;
+ bool have_elf = dwfl_module_getelf (mod, &bias) != NULL;
+ bool have_dwarf = dwfl_module_getdwarf (mod, &bias) != NULL;
+
+ const char *file;
+ const char *debug;
+ Dwarf_Addr start;
+ Dwarf_Addr end;
+ const char *name = dwfl_module_info (mod, NULL, &start, &end,
+ NULL, NULL, &file, &debug);
+ if (file != NULL && debug != NULL && (debug == file || !strcmp (debug, file)))
+ debug = ".";
+
+ const unsigned char *id;
+ GElf_Addr id_vaddr;
+ int id_len = dwfl_module_build_id (mod, &id, &id_vaddr);
+
+ printf ("%#" PRIx64 "+%#" PRIx64 " ", start, end - start);
+
+ if (id_len > 0)
+ {
+ do
+ printf ("%02" PRIx8, *id++);
+ while (--id_len > 0);
+ if (id_vaddr != 0)
+ printf ("@%#" PRIx64, id_vaddr);
+ }
+ else
+ putchar ('-');
+
+ printf (" %s %s %s\n",
+ file ?: have_elf ? "." : "-",
+ debug ?: have_dwarf ? "." : "-",
+ name);
+}
+
+
+struct match_module_info
+{
+ char **patterns;
+ Dwfl_Module *found;
+ bool match_files;
+};
+
+static int
+match_module (Dwfl_Module *mod,
+ void **userdata __attribute__ ((unused)),
+ const char *name,
+ Dwarf_Addr start __attribute__ ((unused)),
+ void *arg)
+{
+ struct match_module_info *info = arg;
+
+ if (info->patterns[0] == NULL) /* Match all. */
+ {
+ match:
+ info->found = mod;
+ return DWARF_CB_ABORT;
+ }
+
+ if (info->match_files)
+ {
+ /* Make sure we've searched for the ELF file. */
+ GElf_Addr bias;
+ (void) dwfl_module_getelf (mod, &bias);
+
+ const char *file;
+ const char *check = dwfl_module_info (mod, NULL, NULL, NULL,
+ NULL, NULL, &file, NULL);
+ assert (check == name);
+ if (file == NULL)
+ return DWARF_CB_OK;
+
+ name = file;
+ }
+
+ for (char **p = info->patterns; *p != NULL; ++p)
+ if (fnmatch (*p, name, 0) == 0)
+ goto match;
+
+ return DWARF_CB_OK;
+}
+
+/* Handle files opened implicitly via libdwfl. */
+static void
+handle_implicit_modules (const struct arg_info *info)
+{
+ struct match_module_info mmi = { info->args, NULL, info->match_files };
+ inline ptrdiff_t next (ptrdiff_t offset)
+ {
+ return dwfl_getmodules (info->dwfl, &match_module, &mmi, offset);
+ }
+ ptrdiff_t offset = next (0);
+ if (offset == 0)
+ error (EXIT_FAILURE, 0, _("no matching modules found"));
+
+ if (info->list)
+ do
+ list_module (mmi.found);
+ while ((offset = next (offset)) > 0);
+ else if (info->output_dir == NULL)
+ {
+ if (next (offset) != 0)
+ error (EXIT_FAILURE, 0, _("matched more than one module"));
+ handle_dwfl_module (info->output_file, false, info->force, mmi.found,
+ info->all, info->ignore, info->relocate);
+ }
+ else
+ do
+ handle_output_dir_module (info->output_dir, mmi.found, info->force,
+ info->all, info->ignore,
+ info->modnames, info->relocate);
+ while ((offset = next (offset)) > 0);
+}
+
+int
+main (int argc, char **argv)
+{
+ /* We use no threads here which can interfere with handling a stream. */
+ __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+ __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+ __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+ /* Set locale. */
+ setlocale (LC_ALL, "");
+
+ /* Make sure the message catalog can be found. */
+ bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+ /* Initialize the message catalog. */
+ textdomain (PACKAGE_TARNAME);
+
+ /* Parse and process arguments. */
+ const struct argp_child argp_children[] =
+ {
+ {
+ .argp = dwfl_standard_argp (),
+ .header = N_("Input selection options:"),
+ .group = 1,
+ },
+ { .argp = NULL },
+ };
+ const struct argp argp =
+ {
+ .options = options,
+ .parser = parse_opt,
+ .children = argp_children,
+ .args_doc = N_("STRIPPED-FILE DEBUG-FILE\n[MODULE...]"),
+ .doc = N_("\
+Combine stripped files with separate symbols and debug information.\n\
+\n\
+The first form puts the result in DEBUG-FILE if -o was not given.\n\
+\n\
+MODULE arguments give file name patterns matching modules to process.\n\
+With -f these match the file name of the main (stripped) file \
+(slashes are never special), otherwise they match the simple module names. \
+With no arguments, process all modules found.\n\
+\n\
+Multiple modules are written to files under OUTPUT-DIRECTORY, \
+creating subdirectories as needed. \
+With -m these files have simple module names, otherwise they have the \
+name of the main file complete with directory underneath OUTPUT-DIRECTORY.\n\
+\n\
+With -n no files are written, but one line to standard output for each module:\
+\n\tSTART+SIZE BUILDID FILE DEBUGFILE MODULENAME\n\
+START and SIZE are hexadecimal giving the address bounds of the module. \
+BUILDID is hexadecimal for the build ID bits, or - if no ID is known; \
+the hexadecimal may be followed by @0xADDR giving the address where the \
+ID resides if that is known. \
+FILE is the file name found for the module, or - if none was found, \
+or . if an ELF image is available but not from any named file. \
+DEBUGFILE is the separate debuginfo file name, \
+or - if no debuginfo was found, or . if FILE contains the debug information.\
+")
+ };
+
+ int remaining;
+ struct arg_info info = { .args = NULL };
+ error_t result = argp_parse (&argp, argc, argv, 0, &remaining, &info);
+ if (result == ENOSYS)
+ assert (info.dwfl == NULL);
+ else if (result)
+ return EXIT_FAILURE;
+ assert (info.args != NULL);
+
+ /* Tell the library which version we are expecting. */
+ elf_version (EV_CURRENT);
+
+ if (info.dwfl == NULL)
+ {
+ assert (result == ENOSYS);
+
+ if (info.output_dir != NULL)
+ {
+ char *file;
+ if (asprintf (&file, "%s/%s", info.output_dir, info.args[0]) < 0)
+ error (EXIT_FAILURE, 0, _("memory exhausted"));
+ handle_explicit_files (file, true, info.force,
+ info.args[0], info.args[1]);
+ free (file);
+ }
+ else
+ handle_explicit_files (info.output_file, false, info.force,
+ info.args[0], info.args[1]);
+ }
+ else
+ {
+ /* parse_opt checked this. */
+ assert (info.output_file != NULL || info.output_dir != NULL || info.list);
+
+ handle_implicit_modules (&info);
+
+ dwfl_end (info.dwfl);
+ }
+
+ return 0;
+}
+
+
+#include "debugpred.h"