#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <argp.h>
#include <assert.h>
#include <byteswap.h>
#include <endian.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <gelf.h>
#include <libelf.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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;
ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
#define OPT_REMOVE_COMMENT 0x100
#define OPT_PERMISSIVE 0x101
#define OPT_STRIP_SECTIONS 0x102
#define OPT_RELOC_DEBUG 0x103
#define OPT_KEEP_SECTION 0x104
#define OPT_RELOC_DEBUG_ONLY 0x105
static const struct argp_option options[] =
{
{ NULL, 0, NULL, 0, N_("Output selection:"), 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 },
{ NULL, 0, NULL, 0, N_("Output options:"), 0 },
{ "strip-all", 's', NULL, OPTION_HIDDEN, NULL, 0 },
{ "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 reversible, needs -f)"), 0 },
{ "reloc-debug-sections-only", OPT_RELOC_DEBUG_ONLY, NULL, 0,
N_("Similar to --reloc-debug-sections, but resolve all trivial relocations between debug sections in place. No other stripping is performed (operation is not reversible, incompatible with -f, -g, --remove-comment and --remove-section)"), 0 },
{ "remove-comment", OPT_REMOVE_COMMENT, NULL, 0,
N_("Remove .comment section"), 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 }
};
static const char doc[] = N_("Discard symbols from object files.");
static const char args_doc[] = N_("[FILE...]");
static error_t parse_opt (int key, char *arg, struct argp_state *state);
static struct argp argp =
{
options, parse_opt, args_doc, doc, NULL, NULL, NULL
};
static int process_file (const char *fname);
static int handle_elf (int fd, Elf *elf, const char *prefix,
const char *fname, mode_t mode, struct timespec tvp[2]);
static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname,
struct timespec tvp[2]) __attribute__ ((unused));
static int debug_fd = -1;
static char *tmp_debug_fname = NULL;
static void cleanup_debug (void);
#define INTERNAL_ERROR(fname) \
do { \
cleanup_debug (); \
error_exit (0, _("%s: INTERNAL ERROR %d (%s): %s"), \
fname, __LINE__, PACKAGE_VERSION, elf_errmsg (-1)); \
} while (0)
static const char *output_fname;
static const char *debug_fname;
static const char *debug_fname_embed;
static bool preserve_dates;
static bool remove_comment;
static bool remove_debug;
static bool remove_shdrs;
static bool permissive;
static bool reloc_debug;
static bool reloc_debug_only;
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[])
{
int remaining;
int result = 0;
__fsetlocking (stdin, FSETLOCKING_BYCALLER);
__fsetlocking (stdout, FSETLOCKING_BYCALLER);
__fsetlocking (stderr, FSETLOCKING_BYCALLER);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
textdomain (PACKAGE_TARNAME);
if (argp_parse (&argp, argc, argv, 0, &remaining, NULL) != 0)
return EXIT_FAILURE;
if (reloc_debug && debug_fname == NULL)
error_exit (0, _("--reloc-debug-sections used without -f"));
if (reloc_debug_only &&
(debug_fname != NULL || remove_secs != NULL
|| remove_comment == true || remove_debug == true))
error_exit (0,
_("--reloc-debug-sections-only incompatible with -f, -g, --remove-comment and --remove-section"));
elf_version (EV_CURRENT);
if (remaining == argc)
result = process_file ("a.out");
else
{
if ((output_fname != NULL || debug_fname != NULL)
&& remaining + 1 < argc)
error_exit (0, _("Only one input file allowed together with '-o' and '-f'"));
do
result |= process_file (argv[remaining]);
while (++remaining < argc);
}
free_patterns ();
return result;
}
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
switch (key)
{
case 'f':
if (debug_fname != NULL)
{
error (0, 0, _("-f option specified twice"));
return EINVAL;
}
debug_fname = arg;
break;
case 'F':
if (debug_fname_embed != NULL)
{
error (0, 0, _("-F option specified twice"));
return EINVAL;
}
debug_fname_embed = arg;
break;
case 'o':
if (output_fname != NULL)
{
error (0, 0, _("-o option specified twice"));
return EINVAL;
}
output_fname = arg;
break;
case 'p':
preserve_dates = true;
break;
case OPT_RELOC_DEBUG:
reloc_debug = true;
break;
case OPT_RELOC_DEBUG_ONLY:
reloc_debug_only = true;
break;
case OPT_REMOVE_COMMENT:
remove_comment = true;
break;
case 'R':
if (fnmatch (arg, ".comment", FNM_EXTMATCH) == 0)
remove_comment = true;
add_pattern (&remove_secs, arg);
break;
case OPT_KEEP_SECTION:
add_pattern (&keep_secs, arg);
break;
case 'g':
case 'd':
case 'S':
remove_debug = true;
break;
case OPT_STRIP_SECTIONS:
remove_shdrs = true;
break;
case OPT_PERMISSIVE:
permissive = true;
break;
case 's':
break;
case ARGP_KEY_SUCCESS:
if (remove_comment == true
&& section_name_matches (keep_secs, ".comment"))
{
argp_error (state,
_("cannot both keep and remove .comment section"));
return EINVAL;
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static const char *
secndx_name (Elf *elf, size_t ndx)
{
size_t shstrndx;
GElf_Shdr mem;
Elf_Scn *sec = elf_getscn (elf, ndx);
GElf_Shdr *shdr = gelf_getshdr (sec, &mem);
if (shdr == NULL || elf_getshdrstrndx (elf, &shstrndx) < 0)
return "???";
return elf_strptr (elf, shstrndx, shdr->sh_name) ?: "???";
}
static Elf_Data *
get_xndxdata (Elf *elf, Elf_Scn *symscn)
{
Elf_Data *xndxdata = NULL;
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr (symscn, &shdr_mem);
if (shdr != NULL && shdr->sh_type == SHT_SYMTAB)
{
size_t scnndx = elf_ndxscn (symscn);
Elf_Scn *xndxscn = NULL;
while ((xndxscn = elf_nextscn (elf, xndxscn)) != NULL)
{
GElf_Shdr xndxshdr_mem;
GElf_Shdr *xndxshdr = gelf_getshdr (xndxscn, &xndxshdr_mem);
if (xndxshdr != NULL
&& xndxshdr->sh_type == SHT_SYMTAB_SHNDX
&& xndxshdr->sh_link == scnndx)
{
xndxdata = elf_getdata (xndxscn, NULL);
break;
}
}
}
return xndxdata;
}
static int
update_shdrstrndx (Elf *elf, size_t shdrstrndx)
{
GElf_Ehdr ehdr;
if (gelf_getehdr (elf, &ehdr) == 0)
return 1;
if (shdrstrndx < SHN_LORESERVE)
ehdr.e_shstrndx = shdrstrndx;
else
{
ehdr.e_shstrndx = SHN_XINDEX;
Elf_Scn *scn0 = elf_getscn (elf, 0);
GElf_Shdr shdr0_mem;
GElf_Shdr *shdr0 = gelf_getshdr (scn0, &shdr0_mem);
if (shdr0 == NULL)
return 1;
shdr0->sh_link = shdrstrndx;
if (gelf_update_shdr (scn0, shdr0) == 0)
return 1;
}
if (unlikely (gelf_update_ehdr (elf, &ehdr) == 0))
return 1;
return 0;
}
static bool
relocate (Elf *elf, GElf_Addr offset, const GElf_Sxword addend,
Elf_Data *tdata, unsigned int ei_data, const char *fname,
bool is_rela, GElf_Sym *sym, int addsub, Elf_Type type)
{
#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)
size_t size;
#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;
}
if (offset > tdata->d_size
|| tdata->d_size - offset < size)
{
cleanup_debug ();
error_exit (0, _("bad relocation"));
}
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)
{
value += addend;
if (addsub != 0)
{
Elf_Data *d = gelf_xlatetom (elf, &tmpdata,
&rdata,
ei_data);
if (d == NULL)
INTERNAL_ERROR (fname);
assert (d == &tmpdata);
}
}
else
{
Elf_Data *d = gelf_xlatetom (elf, &tmpdata,
&rdata,
ei_data);
if (d == NULL)
INTERNAL_ERROR (fname);
assert (d == &tmpdata);
}
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 ();
}
Elf_Data *s = gelf_xlatetof (elf, &rdata,
&tmpdata,
ei_data);
if (s == NULL)
INTERNAL_ERROR (fname);
assert (s == &rdata);
return true;
}
static void
remove_debug_relocations (Ebl *ebl, Elf *elf, GElf_Ehdr *ehdr,
const char *fname, size_t shstrndx)
{
Elf_Scn *scn = NULL;
while ((scn = elf_nextscn (elf, scn)) != NULL)
{
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
if (shdr != NULL
&& (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA))
{
Elf_Scn *tscn = elf_getscn (elf, shdr->sh_info);
GElf_Shdr tshdr_mem;
GElf_Shdr *tshdr = gelf_getshdr (tscn, &tshdr_mem);
if (tshdr == NULL
|| tshdr->sh_type == SHT_NOBITS
|| tshdr->sh_size == 0
|| (tshdr->sh_flags & SHF_ALLOC) != 0)
continue;
const char *tname = elf_strptr (elf, shstrndx,
tshdr->sh_name);
if (! tname || ! ebl_debugscn_p (ebl, tname))
continue;
Elf_Data *reldata = elf_getdata (scn, NULL);
if (reldata == NULL || reldata->d_buf == NULL)
INTERNAL_ERROR (fname);
GElf_Chdr tchdr;
int tcompress_type = 0;
bool is_gnu_compressed = false;
if (startswith (tname, ".zdebug"))
{
is_gnu_compressed = true;
if (elf_compress_gnu (tscn, 0, 0) != 1)
INTERNAL_ERROR (fname);
}
else
{
if (gelf_getchdr (tscn, &tchdr) != NULL)
{
tcompress_type = tchdr.ch_type;
if (elf_compress (tscn, 0, 0) != 1)
INTERNAL_ERROR (fname);
}
}
Elf_Data *tdata = elf_getdata (tscn, NULL);
if (tdata == NULL || tdata->d_buf == NULL
|| tdata->d_type != ELF_T_BYTE)
INTERNAL_ERROR (fname);
Elf64_Word symt = shdr->sh_link;
Elf_Data *symdata, *xndxdata;
Elf_Scn * symscn = elf_getscn (elf, symt);
symdata = elf_getdata (symscn, NULL);
xndxdata = get_xndxdata (elf, symscn);
if (symdata == NULL)
INTERNAL_ERROR (fname);
if (shdr->sh_entsize == 0)
INTERNAL_ERROR (fname);
size_t nrels = shdr->sh_size / shdr->sh_entsize;
size_t next = 0;
const bool is_rela = (shdr->sh_type == SHT_RELA);
const unsigned int ei_data = ehdr->e_ident[EI_DATA];
for (size_t relidx = 0; relidx < nrels; ++relidx)
{
int rtype, symndx, offset, addend;
union { GElf_Rela rela; GElf_Rel rel; } mem;
void *rel_p;
if (is_rela)
{
GElf_Rela *r = gelf_getrela (reldata, relidx, &mem.rela);
if (r == NULL)
INTERNAL_ERROR (fname);
offset = r->r_offset;
addend = r->r_addend;
rtype = GELF_R_TYPE (r->r_info);
symndx = GELF_R_SYM (r->r_info);
rel_p = r;
}
else
{
GElf_Rel *r = gelf_getrel (reldata, relidx, &mem.rel);
if (r == NULL)
INTERNAL_ERROR (fname);
offset = r->r_offset;
addend = 0;
rtype = GELF_R_TYPE (r->r_info);
symndx = GELF_R_SYM (r->r_info);
rel_p = r;
}
if (rtype == 0)
continue;
int addsub = 0;
Elf_Type type = ebl_reloc_simple_type (ebl, rtype, &addsub);
if (type == ELF_T_NUM)
goto relocate_failed;
GElf_Sym sym_mem;
Elf32_Word xndx;
GElf_Sym *sym = gelf_getsymshndx (symdata, xndxdata,
symndx, &sym_mem,
&xndx);
if (sym == NULL)
INTERNAL_ERROR (fname);
Elf32_Word sec = (sym->st_shndx == SHN_XINDEX
? xndx : sym->st_shndx);
bool dbg_scn = ebl_debugscn_p (ebl, secndx_name (elf, sec));
if (!dbg_scn)
goto relocate_failed;
if (! relocate (elf, offset, addend,
tdata, ei_data, fname, is_rela,
sym, addsub, type))
goto relocate_failed;
continue;
relocate_failed:
if (relidx != next)
{
int updated;
if (is_rela)
updated = gelf_update_rela (reldata, next, rel_p);
else
updated = gelf_update_rel (reldata, next, rel_p);
if (updated == 0)
INTERNAL_ERROR (fname);
}
++next;
}
nrels = next;
shdr->sh_size = reldata->d_size = nrels * shdr->sh_entsize;
if (gelf_update_shdr (scn, shdr) == 0)
INTERNAL_ERROR (fname);
if (is_gnu_compressed)
{
if (elf_compress_gnu (tscn, 1, ELF_CHF_FORCE) != 1)
INTERNAL_ERROR (fname);
}
else if (tcompress_type != 0)
{
if (elf_compress (tscn, tcompress_type, ELF_CHF_FORCE) != 1)
INTERNAL_ERROR (fname);
}
}
}
}
static int
process_file (const char *fname)
{
struct stat pre_st;
struct timespec tv[2];
again:
if (preserve_dates)
{
if (stat (fname, &pre_st) != 0)
{
error (0, errno, _("cannot stat input file '%s'"), fname);
return 1;
}
tv[0] = pre_st.st_atim;
tv[1] = pre_st.st_mtim;
}
int fd = open (fname, output_fname == NULL ? O_RDWR : O_RDONLY);
if (fd == -1)
{
error (0, errno, _("while opening '%s'"), fname);
return 1;
}
struct stat st;
if (fstat (fd, &st) != 0)
{
error (0, errno, _("cannot stat input file '%s'"), fname);
return 1;
}
if (preserve_dates
&& (st.st_ino != pre_st.st_ino || st.st_dev != pre_st.st_dev))
{
close (fd);
goto again;
}
Elf *elf = elf_begin (fd, output_fname == NULL ? ELF_C_RDWR : ELF_C_READ,
NULL);
int result;
switch (elf_kind (elf))
{
case ELF_K_ELF:
result = handle_elf (fd, elf, NULL, fname, st.st_mode & ACCESSPERMS,
preserve_dates ? tv : NULL);
break;
case ELF_K_AR:
if (unlikely (output_fname != NULL || debug_fname != NULL))
{
error (0, 0, _("%s: cannot use -o or -f when stripping archive"),
fname);
result = 1;
}
else
{
error (0, 0, _("%s: no support for stripping archive"),
fname);
result = 1;
}
break;
default:
error (0, 0, _("%s: File format not recognized"), fname);
result = 1;
break;
}
if (unlikely (elf_end (elf) != 0))
INTERNAL_ERROR (fname);
close (fd);
return result;
}
static int
handle_debug_relocs (Elf *elf, Ebl *ebl, Elf *new_elf,
GElf_Ehdr *ehdr, const char *fname, size_t shstrndx,
GElf_Off *last_offset, GElf_Xword *last_size)
{
if (gelf_update_ehdr (new_elf, ehdr) == 0)
{
error (0, 0, "couldn't update new ehdr: %s", elf_errmsg (-1));
return 1;
}
GElf_Off lastoffset = 0;
Elf_Scn *scn = NULL;
while ((scn = elf_nextscn (elf, scn)) != NULL)
{
GElf_Shdr shdr;
if (gelf_getshdr (scn, &shdr) == NULL)
{
error (0, 0, "couldn't get shdr: %s", elf_errmsg (-1));
return 1;
}
Elf_Scn *new_scn = elf_newscn (new_elf);
if (new_scn == NULL)
{
error (0, 0, "couldn't create new section: %s", elf_errmsg (-1));
return 1;
}
if (gelf_update_shdr (new_scn, &shdr) == 0)
{
error (0, 0, "couldn't update shdr: %s", elf_errmsg (-1));
return 1;
}
Elf_Data *data = NULL;
while ((data = elf_getdata (scn, data)) != NULL)
{
Elf_Data *new_data = elf_newdata (new_scn);
if (new_data == NULL)
{
error (0, 0, "couldn't create new section data: %s",
elf_errmsg (-1));
return 1;
}
*new_data = *data;
}
if ((shdr.sh_flags & SHF_ALLOC) != 0)
{
GElf_Off filesz = (shdr.sh_type != SHT_NOBITS
? shdr.sh_size : 0);
if (lastoffset < shdr.sh_offset + filesz)
lastoffset = shdr.sh_offset + filesz;
}
}
if (update_shdrstrndx (new_elf, shstrndx) != 0)
{
error (0, 0, "error updating shdrstrndx: %s", elf_errmsg (-1));
return 1;
}
remove_debug_relocations (ebl, new_elf, ehdr, fname, shstrndx);
scn = NULL;
while ((scn = elf_nextscn (new_elf, scn)) != NULL)
{
GElf_Shdr shdr;
if (gelf_getshdr (scn, &shdr) == NULL)
{
error (0, 0, "couldn't get shdr: %s", elf_errmsg (-1));
return 1;
}
if ((shdr.sh_flags & SHF_ALLOC) == 0)
{
shdr.sh_offset = ((lastoffset + shdr.sh_addralign - 1)
& ~((GElf_Off) (shdr.sh_addralign - 1)));
if (gelf_update_shdr (scn, &shdr) == 0)
{
error (0, 0, "couldn't update shdr: %s", elf_errmsg (-1));
return 1;
}
GElf_Off filesz = (shdr.sh_type != SHT_NOBITS
? shdr.sh_size : 0);
lastoffset = shdr.sh_offset + filesz;
*last_offset = shdr.sh_offset;
*last_size = filesz;
}
}
return 0;
}
static inline void
update_section_size (Elf_Scn *scn,
const Elf_Data *newdata,
Elf *debugelf,
size_t cnt,
const char *fname)
{
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
shdr->sh_size = newdata->d_size;
if (gelf_update_shdr (scn, shdr) == 0)
INTERNAL_ERROR (fname);
if (debugelf != NULL)
{
Elf_Data *debugdata = elf_getdata (elf_getscn (debugelf,
cnt), NULL);
if (debugdata == NULL)
INTERNAL_ERROR (fname);
debugdata->d_size = newdata->d_size;
}
}
#define MAX_STACK_ALLOC (400 * 1024)
static int
handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
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 *debugelf = NULL;
tmp_debug_fname = NULL;
int result = 0;
size_t shdridx = 0;
GElf_Off lastsec_offset = 0;
Elf64_Xword lastsec_size = 0;
size_t shstrndx;
struct shdr_info
{
Elf_Scn *scn;
GElf_Shdr shdr;
Elf_Data *data;
Elf_Data *debug_data;
const char *name;
Elf32_Word idx;
Elf32_Word old_sh_link;
Elf32_Word symtab_idx;
Elf32_Word version_idx;
Elf32_Word group_idx;
Elf32_Word group_cnt;
Elf_Scn *newscn;
Dwelf_Strent *se;
Elf32_Word *newsymidx;
} *shdr_info = NULL;
Elf_Scn *scn;
size_t cnt;
size_t idx;
bool changes;
GElf_Ehdr newehdr_mem;
GElf_Ehdr *newehdr;
GElf_Ehdr debugehdr_mem;
GElf_Ehdr *debugehdr;
Dwelf_Strtab *shst = NULL;
Elf_Data debuglink_crc_data;
bool any_symtab_changes = false;
Elf_Data *shstrtab_data = NULL;
void *debuglink_buf = NULL;
if (prefix != NULL)
{
cp = mempcpy (cp, prefix, prefix_len);
*cp++ = ':';
}
memcpy (cp, fname, fname_len);
if (output_fname != NULL)
{
fd = open (output_fname, O_RDWR | O_CREAT, mode);
if (unlikely (fd == -1))
{
error (0, errno, _("cannot open '%s'"), output_fname);
return 1;
}
}
debug_fd = -1;
Ebl *ebl = NULL;
if (remove_debug || reloc_debug || reloc_debug_only)
{
ebl = ebl_openbackend (elf);
if (ebl == NULL)
{
error (0, errno, _("cannot open EBL backend"));
result = 1;
goto fail;
}
}
if (debug_fname != NULL)
{
size_t debug_fname_len = strlen (debug_fname);
tmp_debug_fname = xmalloc (debug_fname_len + sizeof (".XXXXXX"));
strcpy (mempcpy (tmp_debug_fname, debug_fname, debug_fname_len),
".XXXXXX");
debug_fd = mkstemp (tmp_debug_fname);
if (unlikely (debug_fd == -1))
{
error (0, errno, _("cannot open '%s'"), debug_fname);
result = 1;
goto fail;
}
}
GElf_Ehdr ehdr_mem;
GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
if (ehdr == NULL)
INTERNAL_ERROR (fname);
if (unlikely (elf_getshdrstrndx (elf, &shstrndx) < 0))
{
cleanup_debug ();
error_exit (0, _("cannot get section header string table index"));
}
size_t phnum;
if (elf_getphdrnum (elf, &phnum) != 0)
{
cleanup_debug ();
error_exit (0, _("cannot get number of phdrs"));
}
Elf *newelf;
if (output_fname != NULL)
newelf = elf_begin (fd, ELF_C_WRITE_MMAP, NULL);
else
newelf = elf_clone (elf, ELF_C_EMPTY);
if (unlikely (gelf_newehdr (newelf, gelf_getclass (elf)) == 0))
{
error (0, 0, _("cannot create new ehdr for file '%s': %s"),
output_fname ?: fname, elf_errmsg (-1));
goto fail;
}
if (phnum > 0)
{
if (unlikely (gelf_newphdr (newelf, phnum) == 0))
{
error (0, 0, _("cannot create new phdr for file '%s': %s"),
output_fname ?: fname, elf_errmsg (-1));
goto fail;
}
for (cnt = 0; cnt < phnum; ++cnt)
{
GElf_Phdr 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);
}
}
if (reloc_debug_only)
{
if (handle_debug_relocs (elf, ebl, newelf, ehdr, fname, shstrndx,
&lastsec_offset, &lastsec_size) != 0)
{
result = 1;
goto fail_close;
}
idx = shstrndx;
goto done;
}
if (debug_fname != NULL)
{
debugelf = elf_begin (debug_fd, ELF_C_WRITE, NULL);
if (unlikely (gelf_newehdr (debugelf, gelf_getclass (elf)) == 0))
{
error (0, 0, _("cannot create new ehdr for file '%s': %s"),
debug_fname, elf_errmsg (-1));
goto fail_close;
}
if (phnum > 0)
{
if (unlikely (gelf_newphdr (debugelf, phnum) == 0))
{
error (0, 0, _("cannot create new phdr for file '%s': %s"),
debug_fname, elf_errmsg (-1));
goto fail_close;
}
for (cnt = 0; cnt < phnum; ++cnt)
{
GElf_Phdr 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);
}
}
}
size_t shnum;
if (unlikely (elf_getshdrnum (elf, &shnum) < 0))
{
error (0, 0, _("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)
if ((shnum + 2) * sizeof (struct shdr_info) > MAX_STACK_ALLOC)
shdr_info = xcalloc (shnum + 2, sizeof (struct shdr_info));
else
{
shdr_info = (struct shdr_info *) alloca ((shnum + 2)
* sizeof (struct shdr_info));
memset (shdr_info, '\0', (shnum + 2) * sizeof (struct shdr_info));
}
bool seen_allocated = false;
bool seen_unallocated = false;
bool mixed_allocated_unallocated = false;
scn = NULL;
cnt = 1;
while ((scn = elf_nextscn (elf, scn)) != NULL)
{
elf_assert (elf_ndxscn (scn) == cnt);
shdr_info[cnt].scn = scn;
if (gelf_getshdr (scn, &shdr_info[cnt].shdr) == NULL)
INTERNAL_ERROR (fname);
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;
}
shdr_info[cnt].name = elf_strptr (elf, shstrndx,
shdr_info[cnt].shdr.sh_name);
if (shdr_info[cnt].name == NULL)
{
illformed:
error (0, 0, _("illformed file '%s'"), fname);
goto fail_close;
}
if (section_name_matches (remove_secs, shdr_info[cnt].name))
{
if ((shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) != 0)
{
error (0, 0,
_("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,
_("Cannot both keep and remove section '%s'"),
shdr_info[cnt].name);
result = 1;
goto fail_close;
}
}
shdr_info[cnt].idx = 1;
shdr_info[cnt].old_sh_link = shdr_info[cnt].shdr.sh_link;
if (shdr_info[cnt].old_sh_link >= shnum)
goto illformed;
if (phnum == 0
|| (shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) == 0)
shdr_info[cnt].shdr.sh_offset = 0;
if (unlikely (shdr_info[cnt].shdr.sh_type == SHT_SYMTAB_SHNDX))
{
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))
{
shdr_info[cnt].data = elf_getdata (shdr_info[cnt].scn, NULL);
if (shdr_info[cnt].data == NULL
|| shdr_info[cnt].data->d_size < sizeof (Elf32_Word))
INTERNAL_ERROR (fname);
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)
{
if (grpref[inner] < shnum)
shdr_info[grpref[inner]].group_idx = cnt;
else
goto illformed;
}
if (inner == 1 || (inner == 2 && (grpref[0] & GRP_COMDAT) == 0))
shdr_info[cnt].idx = 0;
else
shdr_info[cnt].group_cnt = inner - 1;
}
else if (unlikely (shdr_info[cnt].shdr.sh_type == SHT_GNU_versym))
{
elf_assert (shdr_info[shdr_info[cnt].shdr.sh_link].version_idx == 0);
shdr_info[shdr_info[cnt].shdr.sh_link].version_idx = cnt;
}
if ((shdr_info[cnt].shdr.sh_flags & SHF_GROUP) != 0)
{
elf_assert (shdr_info[cnt].group_idx != 0);
if (shdr_info[shdr_info[cnt].group_idx].idx == 0)
{
shdr_info[cnt].group_idx = 0;
}
}
++cnt;
}
for (cnt = 1; cnt < shnum; ++cnt)
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)))
{
if (section_name_matches (keep_secs, shdr_info[cnt].name))
continue;
shdr_info[cnt].idx = 0;
idx = shdr_info[cnt].group_idx;
while (idx != 0)
{
elf_assert (shdr_info[idx].data != NULL
&& shdr_info[idx].data->d_buf != NULL
&& shdr_info[idx].data->d_size >= sizeof (Elf32_Word));
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)
|| (is_comdat && shdr_info[idx].group_cnt == 0))
{
shdr_info[idx].idx = 0;
idx = shdr_info[idx].group_idx;
}
else
break;
}
}
shdr_info[0].idx = 2;
do
{
changes = false;
for (cnt = 1; cnt < shnum; ++cnt)
{
if (shdr_info[cnt].idx == 0)
{
if (shdr_info[cnt].shdr.sh_type == SHT_REL
|| shdr_info[cnt].shdr.sh_type == SHT_RELA)
{
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 (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)
{
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))
{
if (shdr_info[cnt].data == NULL)
{
shdr_info[cnt].data
= elf_getdata (shdr_info[cnt].scn, NULL);
if (shdr_info[cnt].data == NULL)
INTERNAL_ERROR (fname);
}
Elf_Data *symdata = shdr_info[cnt].data;
if (shdr_info[cnt].symtab_idx != 0
&& shdr_info[shdr_info[cnt].symtab_idx].data == NULL)
{
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,
NULL);
if (shdr_info[shdr_info[cnt].symtab_idx].data == NULL)
INTERNAL_ERROR (fname);
}
Elf_Data *xndxdata
= shdr_info[shdr_info[cnt].symtab_idx].data;
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;
++inner)
{
GElf_Sym sym_mem;
Elf32_Word xndx;
GElf_Sym *sym = gelf_getsymshndx (symdata, xndxdata,
inner, &sym_mem,
&xndx);
if (sym == NULL)
INTERNAL_ERROR (fname);
size_t scnidx = sym->st_shndx;
if (scnidx == SHN_UNDEF || scnidx >= shnum
|| (scnidx >= SHN_LORESERVE
&& scnidx <= SHN_HIRESERVE
&& scnidx != SHN_XINDEX)
|| GELF_ST_TYPE (sym->st_info) == STT_SECTION)
continue;
else if (scnidx == SHN_XINDEX)
scnidx = xndx;
if (scnidx >= shnum)
goto illformed;
if (shdr_info[scnidx].idx == 0)
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;
}
}
if (shdr_info[shdr_info[cnt].shdr.sh_link].idx == 0)
{
shdr_info[shdr_info[cnt].shdr.sh_link].idx = 1;
changes |= shdr_info[cnt].shdr.sh_link < cnt;
}
if (SH_INFO_LINK_P (&shdr_info[cnt].shdr))
{
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;
}
}
shdr_info[cnt].idx = 2;
}
if (debug_fname != NULL
&& (shdr_info[cnt].idx == 0 || shdr_info[cnt].debug_data != NULL))
{
size_t shdr_indices[2] = { shdr_info[cnt].shdr.sh_link, 0 };
int n = 1;
if (SH_INFO_LINK_P (&shdr_info[cnt].shdr))
{
shdr_indices[1] = shdr_info[cnt].shdr.sh_info;
n++;
}
for (int j = 0; j < n; j++)
{
size_t i = shdr_indices[j];
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;
}
}
}
}
}
while (changes);
if (debug_fname != NULL)
{
for (cnt = 1; cnt < shnum; ++cnt)
{
scn = elf_newscn (debugelf);
if (scn == NULL)
{
cleanup_debug ();
error_exit (0, _("while generating output file: %s"),
elf_errmsg (-1));
}
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);
GElf_Shdr debugshdr = shdr_info[cnt].shdr;
if (discard_section)
debugshdr.sh_type = SHT_NOBITS;
if (unlikely (gelf_update_shdr (scn, &debugshdr) == 0))
INTERNAL_ERROR (fname);
if (shdr_info[cnt].data == NULL)
{
shdr_info[cnt].data = elf_getdata (shdr_info[cnt].scn, NULL);
if (shdr_info[cnt].data == NULL)
INTERNAL_ERROR (fname);
}
Elf_Data *debugdata = elf_newdata (scn);
if (debugdata == NULL)
INTERNAL_ERROR (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)
{
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);
}
}
debugehdr = gelf_getehdr (debugelf, &debugehdr_mem);
if (debugehdr == NULL)
INTERNAL_ERROR (fname);
memcpy (debugehdr->e_ident, ehdr->e_ident, EI_NIDENT);
debugehdr->e_type = ehdr->e_type;
debugehdr->e_machine = ehdr->e_machine;
debugehdr->e_version = ehdr->e_version;
debugehdr->e_entry = ehdr->e_entry;
debugehdr->e_flags = ehdr->e_flags;
if (unlikely (gelf_update_ehdr (debugelf, debugehdr) == 0))
{
error (0, 0, _("%s: error while updating ELF header: %s"),
debug_fname, elf_errmsg (-1));
result = 1;
goto fail_close;
}
size_t shdrstrndx;
if (elf_getshdrstrndx (elf, &shdrstrndx) < 0)
{
error (0, 0, _("%s: error while getting shdrstrndx: %s"),
fname, elf_errmsg (-1));
result = 1;
goto fail_close;
}
if (update_shdrstrndx (debugelf, shdrstrndx) != 0)
{
error (0, 0, _("%s: error updating shdrstrndx: %s"),
debug_fname, elf_errmsg (-1));
result = 1;
goto fail_close;
}
}
shst = dwelf_strtab_init (true);
if (shst == NULL)
{
cleanup_debug ();
error_exit (errno, _("while preparing output for '%s'"),
output_fname ?: fname);
}
shdr_info[0].idx = 0;
for (cnt = idx = 1; cnt < shnum; ++cnt)
if (shdr_info[cnt].idx > 0)
{
shdr_info[cnt].idx = idx++;
shdr_info[cnt].newscn = elf_newscn (newelf);
if (shdr_info[cnt].newscn == NULL)
{
cleanup_debug ();
error_exit (0,
_("while generating output file: %s"),
elf_errmsg (-1));
}
elf_assert (elf_ndxscn (shdr_info[cnt].newscn) == shdr_info[cnt].idx);
shdr_info[cnt].se = dwelf_strtab_add (shst, shdr_info[cnt].name);
}
bool removing_sections = !(cnt == idx
|| (cnt == idx + 1
&& shdr_info[shstrndx].idx == 0));
if (output_fname == NULL && !removing_sections)
goto fail_close;
if (debug_fname != NULL && !remove_shdrs && removing_sections)
{
shdr_info[cnt].se = dwelf_strtab_add_len (shst, ".gnu_debuglink", 15);
shdr_info[cnt].idx = idx++;
shdr_info[cnt].shdr.sh_type = SHT_PROGBITS;
shdr_info[cnt].shdr.sh_flags = 0;
shdr_info[cnt].shdr.sh_addr = 0;
shdr_info[cnt].shdr.sh_link = SHN_UNDEF;
shdr_info[cnt].shdr.sh_info = SHN_UNDEF;
shdr_info[cnt].shdr.sh_entsize = 0;
shdr_info[cnt].shdr.sh_addralign = 4;
shdr_info[cnt].shdr.sh_offset = 0;
shdr_info[cnt].newscn = elf_newscn (newelf);
if (shdr_info[cnt].newscn == NULL)
{
cleanup_debug ();
error_exit (0, _("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)
{
cleanup_debug ();
error_exit (0, _("cannot allocate section data: %s"),
elf_errmsg (-1));
}
const char *debug_basename = xbasename (debug_fname_embed ?: debug_fname);
off_t crc_offset = strlen (debug_basename) + 1;
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;
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);
debuglink_crc_data = *shdr_info[cnt].data;
debuglink_crc_data.d_buf = ((char *) debuglink_crc_data.d_buf
+ crc_offset);
debuglink_crc_data.d_size = 4;
++cnt;
}
shdridx = cnt;
shdr_info[cnt].se = dwelf_strtab_add_len (shst, ".shstrtab", 10);
shdr_info[cnt].idx = idx;
shdr_info[cnt].shdr.sh_type = SHT_STRTAB;
shdr_info[cnt].shdr.sh_flags = 0;
shdr_info[cnt].shdr.sh_addr = 0;
shdr_info[cnt].shdr.sh_link = SHN_UNDEF;
shdr_info[cnt].shdr.sh_info = SHN_UNDEF;
shdr_info[cnt].shdr.sh_entsize = 0;
shdr_info[cnt].shdr.sh_offset = 0;
shdr_info[cnt].shdr.sh_addralign = 1;
shdr_info[cnt].newscn = elf_newscn (newelf);
if (shdr_info[cnt].newscn == NULL)
{
cleanup_debug ();
error_exit (0, _("while create section header section: %s"),
elf_errmsg (-1));
}
elf_assert (elf_ndxscn (shdr_info[cnt].newscn) == idx);
shstrtab_data = elf_newdata (shdr_info[cnt].newscn);
if (shstrtab_data == NULL)
{
cleanup_debug ();
error_exit (0, _("while create section header string table: %s"),
elf_errmsg (-1));
}
if (dwelf_strtab_finalize (shst, shstrtab_data) == NULL)
{
cleanup_debug ();
error_exit (0, _("no memory to create section header string table"));
}
shdr_info[cnt].shdr.sh_size = shstrtab_data->d_size;
GElf_Off lastoffset = 0;
for (cnt = 1; cnt <= shdridx; ++cnt)
if (shdr_info[cnt].idx > 0)
{
Elf_Data *newdata;
scn = elf_getscn (newelf, shdr_info[cnt].idx);
elf_assert (scn != NULL);
shdr_info[cnt].shdr.sh_name = dwelf_strent_off (shdr_info[cnt].se);
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)
{
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 = 1;
inner < shdr_info[cnt].data->d_size / sizeof (Elf32_Word);
++inner)
if (grpref[inner] < shnum)
grpref[inner] = shdr_info[grpref[inner]].idx;
else
goto illformed;
}
if (SH_INFO_LINK_P (&shdr_info[cnt].shdr))
shdr_info[cnt].shdr.sh_info =
shdr_info[shdr_info[cnt].shdr.sh_info].idx;
if (cnt < shnum)
{
if (shdr_info[cnt].data == NULL)
{
shdr_info[cnt].data = elf_getdata (shdr_info[cnt].scn, NULL);
if (shdr_info[cnt].data == NULL)
INTERNAL_ERROR (fname);
}
newdata = elf_newdata (scn);
if (newdata == NULL)
INTERNAL_ERROR (fname);
*newdata = *shdr_info[cnt].data;
shdr_info[cnt].shdr.sh_size = shdr_info[cnt].data->d_size;
if (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM
|| shdr_info[cnt].shdr.sh_type == SHT_SYMTAB)
{
Elf_Data *versiondata = NULL;
Elf_Data *shndxdata = NULL;
size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
if (shdr_info[cnt].symtab_idx != 0)
{
elf_assert (shdr_info[cnt].shdr.sh_type == SHT_SYMTAB_SHNDX);
shndxdata = elf_getdata (shdr_info[shdr_info[cnt].symtab_idx].scn,
NULL);
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)
{
elf_assert (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM);
versiondata = elf_getdata (shdr_info[shdr_info[cnt].version_idx].scn,
NULL);
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
= xcalloc (shdr_info[cnt].data->d_size / elsize,
sizeof (Elf32_Word));
bool last_was_local = true;
size_t destidx;
size_t inner;
for (destidx = inner = 1;
inner < shdr_info[cnt].data->d_size / elsize;
++inner)
{
Elf32_Word sec;
GElf_Sym sym_mem;
Elf32_Word xshndx;
GElf_Sym *sym = gelf_getsymshndx (shdr_info[cnt].data,
shndxdata, inner,
&sym_mem, &xshndx);
if (sym == NULL)
INTERNAL_ERROR (fname);
if (sym->st_shndx == SHN_UNDEF
|| (sym->st_shndx >= SHN_LORESERVE
&& sym->st_shndx != SHN_XINDEX))
{
if (destidx != inner
&& gelf_update_symshndx (shdr_info[cnt].data,
shndxdata,
destidx, sym,
xshndx) == 0)
INTERNAL_ERROR (fname);
shdr_info[cnt].newsymidx[inner] = destidx++;
if (last_was_local
&& GELF_ST_BIND (sym->st_info) != STB_LOCAL)
{
last_was_local = false;
shdr_info[cnt].shdr.sh_info = destidx - 1;
}
continue;
}
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);
elf_assert (sidx < shnum);
sec = shdr_info[sidx].idx;
if (sec != 0)
{
GElf_Section nshndx;
Elf32_Word nxshndx;
if (sec < SHN_LORESERVE)
{
nshndx = sec;
nxshndx = 0;
}
else
{
nshndx = SHN_XINDEX;
nxshndx = sec;
}
elf_assert (sec < SHN_LORESERVE || shndxdata != NULL);
if ((inner != destidx || nshndx != sym->st_shndx
|| (shndxdata != NULL && nxshndx != xshndx))
&& (sym->st_shndx = nshndx,
gelf_update_symshndx (shdr_info[cnt].data,
shndxdata,
destidx, sym,
nxshndx) == 0))
INTERNAL_ERROR (fname);
shdr_info[cnt].newsymidx[inner] = destidx++;
if (last_was_local
&& GELF_ST_BIND (sym->st_info) != STB_LOCAL)
{
last_was_local = false;
shdr_info[cnt].shdr.sh_info = destidx - 1;
}
}
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)
{
error (0, 0,
_("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)
{
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)
{
shdr_info[cnt].shdr.sh_size = newdata->d_size
= destidx * elsize;
any_symtab_changes = true;
}
else
{
free (shdr_info[cnt].newsymidx);
shdr_info[cnt].newsymidx = NULL;
}
}
}
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)));
if (unlikely (gelf_update_shdr (scn, &shdr_info[cnt].shdr) == 0))
INTERNAL_ERROR (fname);
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;
}
}
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)));
if (unlikely (gelf_update_shdr (scn, &shdr_info[cnt].shdr) == 0))
INTERNAL_ERROR (fname);
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;
}
}
if (any_symtab_changes)
for (cnt = 1; cnt <= shdridx; ++cnt)
{
struct shdr_info *info = &shdr_info[cnt];
if (info->idx == 0 && debug_fname == NULL)
continue;
const Elf32_Word symtabidx = info->old_sh_link;
elf_assert (symtabidx < shnum + 2);
const Elf32_Word *const newsymidx = shdr_info[symtabidx].newsymidx;
if (newsymidx == NULL)
continue;
if (info->idx == 0 && shdr_info[symtabidx].debug_data != NULL)
continue;
switch (info->shdr.sh_type)
{
case SHT_REL:
case SHT_RELA:
scn = (info->idx == 0
? elf_getscn (debugelf, cnt)
: elf_getscn (newelf, info->idx));
Elf_Data *d = elf_getdata (scn, NULL);
elf_assert (d != NULL && d->d_buf != NULL
&& info->shdr.sh_entsize != 0);
size_t nrels = (info->shdr.sh_size / info->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 (info->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:
elf_assert (info->idx > 0);
scn = elf_getscn (newelf, info->idx);
Elf_Data *symd = elf_getdata (elf_getscn (newelf,
shdr_info[symtabidx].idx),
NULL);
elf_assert (symd != NULL && symd->d_buf != NULL);
Elf_Data *hashd = elf_getdata (scn, NULL);
elf_assert (hashd != NULL && hashd->d_buf != NULL);
if (info->shdr.sh_entsize == sizeof (Elf32_Word))
{
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);
bucket[1] = symd->d_size / elsize;
bucket += 2;
Elf32_Word *chain = bucket + nbucket;
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 (scn, hashd, debugelf, cnt, fname);
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);
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;
if (bucket[hidx] == 0)
bucket[hidx] = inner;
else
{
hidx = bucket[hidx];
while (chain[hidx] != 0 && chain[hidx] < nchain)
hidx = chain[hidx];
chain[hidx] = inner;
}
}
}
else
{
elf_assert (info->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);
bucket[1] = symd->d_size / elsize;
bucket += 2;
Elf64_Xword *chain = bucket + nbucket;
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 (scn, hashd, debugelf, cnt, fname);
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);
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;
if (bucket[hidx] == 0)
bucket[hidx] = inner;
else
{
hidx = bucket[hidx];
while (chain[hidx] != 0 && chain[hidx] < nchain)
hidx = chain[hidx];
chain[hidx] = inner;
}
}
}
break;
case SHT_GNU_versym:
elf_assert (info->idx > 0);
scn = elf_getscn (newelf, info->idx);
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);
Elf_Data *verd = elf_getdata (scn, NULL);
elf_assert (verd != NULL && verd->d_buf != NULL);
GElf_Half *verstab = (GElf_Half *) verd->d_buf;
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)
verstab[newsymidx[inner]] = verstab[inner];
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 (scn, verd, debugelf, cnt, fname);
break;
case SHT_GROUP:
scn = elf_getscn (newelf, info->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;
}
}
if (debug_fname != NULL && removing_sections
&& reloc_debug && ehdr->e_type == ET_REL)
remove_debug_relocations (ebl, debugelf, ehdr, fname, shstrndx);
if (debug_fname != NULL && removing_sections)
{
if (unlikely (elf_update (debugelf, ELF_C_WRITE) == -1))
{
error (0, 0, _("while writing '%s': %s"),
tmp_debug_fname, elf_errmsg (-1));
result = 1;
goto fail_close;
}
if (rename (tmp_debug_fname, debug_fname) != 0
|| fchmod (debug_fd, mode) != 0)
{
error (0, errno, _("while creating '%s'"), debug_fname);
result = 1;
goto fail_close;
}
free (tmp_debug_fname);
tmp_debug_fname = NULL;
if (!remove_shdrs)
{
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
};
if (crc32_file (debug_fd, &debug_crc) != 0)
{
error (0, errno, _("\
while computing checksum for debug information"));
unlink (debug_fname);
result = 1;
goto fail_close;
}
if (unlikely (gelf_xlatetof (newelf, &debuglink_crc_data,
&debug_crc_data, ehdr->e_ident[EI_DATA])
!= &debuglink_crc_data))
INTERNAL_ERROR (fname);
}
}
lastsec_offset = shdr_info[shdridx].shdr.sh_offset;
lastsec_size = shdr_info[shdridx].shdr.sh_size;
done:
newehdr = gelf_getehdr (newelf, &newehdr_mem);
if (newehdr == NULL)
INTERNAL_ERROR (fname);
memcpy (newehdr->e_ident, ehdr->e_ident, EI_NIDENT);
newehdr->e_type = ehdr->e_type;
newehdr->e_machine = ehdr->e_machine;
newehdr->e_version = ehdr->e_version;
newehdr->e_entry = ehdr->e_entry;
newehdr->e_flags = ehdr->e_flags;
newehdr->e_phoff = ehdr->e_phoff;
const size_t offsize = gelf_fsize (elf, ELF_T_OFF, 1, EV_CURRENT);
newehdr->e_shoff = ((lastsec_offset + lastsec_size + offsize - 1)
& ~((GElf_Off) (offsize - 1)));
newehdr->e_shentsize = gelf_fsize (elf, ELF_T_SHDR, 1, EV_CURRENT);
if (gelf_update_ehdr (newelf, newehdr) == 0)
{
error (0, 0, _("%s: error while creating ELF header: %s"),
output_fname ?: fname, elf_errmsg (-1));
result = 1;
}
if (update_shdrstrndx (newelf, idx) != 0)
{
error (0, 0, _("%s: error updating shdrstrndx: %s"),
output_fname ?: fname, elf_errmsg (-1));
result = 1;
}
if (elf_cntl (elf, ELF_C_FDDONE) != 0)
{
error (0, 0, _("%s: error while reading the file: %s"),
fname, elf_errmsg (-1));
result = 1;
}
elf_flagelf (newelf, ELF_C_SET,
(phnum > 0 ? ELF_F_LAYOUT : 0)
| (permissive ? ELF_F_PERMISSIVE : 0));
if (elf_update (newelf, ELF_C_WRITE) == -1)
{
error (0, 0, _("while writing '%s': %s"),
output_fname ?: fname, elf_errmsg (-1));
result = 1;
}
if (remove_shdrs)
{
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, lastsec_offset) < 0)
{
error (0, errno, _("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, lastsec_offset) < 0)
{
error (0, errno, _("while writing '%s'"),
output_fname ?: fname);
result = 1;
}
}
}
fail_close:
if (shdr_info != NULL)
{
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 (debuglink_buf);
if ((shnum + 2) * sizeof (struct shdr_info) > MAX_STACK_ALLOC)
free (shdr_info);
}
if (shstrtab_data != NULL)
free (shstrtab_data->d_buf);
if (shst != NULL)
dwelf_strtab_free (shst);
if (elf_end (newelf) != 0)
{
error (0, 0, _("error while finishing '%s': %s"),
output_fname ?: fname, elf_errmsg (-1));
result = 1;
}
if (debugelf != NULL && elf_end (debugelf) != 0)
{
error (0, 0, _("error while finishing '%s': %s"), debug_fname,
elf_errmsg (-1));
result = 1;
}
fail:
if (ebl != NULL)
ebl_closebackend (ebl);
cleanup_debug ();
if (tvp != NULL)
{
if (futimens (fd, tvp) != 0)
{
error (0, errno, _("\
cannot set access and modification date of '%s'"),
output_fname ?: fname);
result = 1;
}
}
if (output_fname != NULL)
{
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 timespec tvp[2])
{
size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
size_t fname_len = strlen (fname) + 1;
char new_prefix[prefix_len + 1 + fname_len];
char *cp = new_prefix;
if (prefix != NULL)
{
cp = mempcpy (cp, prefix, prefix_len);
*cp++ = ':';
}
memcpy (cp, fname, fname_len);
Elf *subelf;
Elf_Cmd cmd = ELF_C_RDWR;
int result = 0;
while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
{
Elf_Arhdr *arhdr = elf_getarhdr (subelf);
if (elf_kind (subelf) == ELF_K_ELF)
result |= handle_elf (fd, subelf, new_prefix, arhdr->ar_name, 0, NULL);
else if (elf_kind (subelf) == ELF_K_AR)
result |= handle_ar (fd, subelf, new_prefix, arhdr->ar_name, NULL);
cmd = elf_next (subelf);
if (unlikely (elf_end (subelf) != 0))
INTERNAL_ERROR (fname);
}
if (tvp != NULL)
{
if (unlikely (futimens (fd, tvp) != 0))
{
error (0, errno, _("\
cannot set access and modification date of '%s'"), fname);
result = 1;
}
}
if (unlikely (close (fd) != 0))
error_exit (errno, _("while closing '%s'"), fname);
return result;
}
#include "debugpred.h"