diff -ru elfutils-0.121/src/Makefile.am elfutils-0.121-debugpack/src/Makefile.am --- elfutils-0.121/src/Makefile.am 2006-05-30 07:13:07.000000000 +0200 +++ elfutils-0.121-debugpack/src/Makefile.am 2006-06-26 15:17:00.000000000 +0200 @@ -52,7 +52,7 @@ base_cpu = @base_cpu@ bin_PROGRAMS = readelf nm size strip ld elflint findtextrel addr2line \ - elfcmp objdump ranlib strings + elfcmp objdump ranlib strings debugpack ld_dsos = libld_elf_i386_pic.a @@ -112,6 +112,7 @@ objdump_LDADD = $(libebl) $(libelf) $(libeu) $(libmudflap) -ldl ranlib_LDADD = $(libelf) $(libeu) $(libmudflap) strings_LDADD = $(libelf) $(libeu) $(libmudflap) +debugpack_LDADD = $(libdw) $(libebl) $(libelf) $(libeu) $(libmudflap) -ldl ldlex.o: ldscript.c ldlex_no_Werror = yes diff -ru elfutils-0.121/src/debugpack.c elfutils-0.121-debugpack/src/debugpack.c --- elfutils-0.121/src/debugpack.c 2006-06-26 15:16:32.000000000 +0200 +++ elfutils-0.121-debugpack/src/debugpack.c 2006-06-26 15:12:38.000000000 +0200 @@ -0,0 +1,2662 @@ +/* Unify debuginfo items found in multiple compile units in the same file. + Copyright (C) 2006 Jan Kratochvil; based on code Copyright (C) 2000, 2001, + 2002, 2003, 2004, 2005, 2006 Red Hat, Inc. + This file is part of Red Hat elfutils. + Written by Jan Kratochvil , 2006. + + Red Hat elfutils 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; version 2 of the License. + + Red Hat 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 Red Hat elfutils; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA. + + Red Hat elfutils is an included package of the Open Invention Network. + An included package of the Open Invention Network is a package for which + Open Invention Network licensees cross-license their patents. No patent + license is granted, either expressly or impliedly, by designation as an + included package. Should you wish to participate in the Open Invention + Network licensing program, please visit www.openinventionnetwork.com + . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +/* Name and version of program. */ +static void print_version (FILE *stream, struct argp_state *state); +void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; + +/* Bug report address. */ +const char *argp_program_bug_address = PACKAGE_BUGREPORT; + + +/* Values for the parameters which have no short form. */ +#define OPT_REMOVE_COMMENT 0x100 +#define OPT_PERMISSIVE 0x101 + + +/* Definitions of arguments for argp functions. */ +static const struct argp_option options[] = +{ + { NULL, 0, NULL, 0, N_("Output selection:"), 0 }, + { NULL, 'o', "FILE", 0, N_("Place packed output into FILE"), 0 }, + + { NULL, 0, NULL, 0, N_("Output options:"), 0 }, + { "preserve-dates", 'p', NULL, 0, + N_("Copy modified/access timestamps to the output"), 0 }, + { "permissive", OPT_PERMISSIVE, NULL, 0, + N_("Relax a few rules to handle slightly broken ELF files"), 0 }, + { NULL, 0, NULL, 0, NULL, 0 } +}; + +/* Short description of program. */ +static const char doc[] = N_("Unify duplicite debuginfo in object file."); + +/* Strings for arguments in help texts. */ +static const char args_doc[] = N_("[FILE...]"); + +/* Prototype for option handler. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state); + +/* Data structure to communicate with argp functions. */ +static struct argp argp = +{ + options, parse_opt, args_doc, doc, NULL, NULL, NULL +}; + + +/* Print symbols in file named FNAME. */ +static int process_file (const char *fname); + +/* Handle one ELF file. */ +static int handle_elf (int fd, Elf *elf, const char *prefix, + const char *fname, mode_t mode, struct timeval tvp[2]); + +/* Handle all files contained in the archive. */ +static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname, + struct timeval tvp[2]); + +#define INTERNAL_ERROR(fname) \ + error (EXIT_FAILURE, 0, gettext ("%s: INTERNAL ERROR %d (%s-%s): %s"), \ + fname, __LINE__, VERSION, __DATE__, elf_errmsg (-1)) + + +/* Name of the output file. */ +static const char *output_fname; + +/* If true output files shall have same date as the input file. */ +static bool preserve_dates; + +/* If true relax some ELF rules for input files. */ +static bool permissive; + + +int +main (int argc, char *argv[]) +{ + int remaining; + int result = 0; + + /* Make memory leak detection possible. */ + mtrace (); + + /* We use no threads here which can interfere with handling a stream. */ + __fsetlocking (stdin, FSETLOCKING_BYCALLER); + __fsetlocking (stdout, FSETLOCKING_BYCALLER); + __fsetlocking (stderr, FSETLOCKING_BYCALLER); + + /* Set locale. */ + setlocale (LC_ALL, ""); + + /* Make sure the message catalog can be found. */ + bindtextdomain (PACKAGE, LOCALEDIR); + + /* Initialize the message catalog. */ + textdomain (PACKAGE); + + /* Parse and process arguments. */ + if (argp_parse (&argp, argc, argv, 0, &remaining, NULL) != 0) + return EXIT_FAILURE; + + /* Tell the library which version we are expecting. */ + elf_version (EV_CURRENT); + + if (remaining == argc) + /* The user didn't specify a name so we use a.out. */ + result = process_file ("a.out"); + else + { + /* If we have seen the '-o' or '-f' option there must be exactly one + input file. */ + if (output_fname != NULL + && remaining + 1 < argc) + error (EXIT_FAILURE, 0, gettext ("\ +Only one input file allowed together with '-o'")); + + /* Process all the remaining files. */ + do + result |= process_file (argv[remaining]); + while (++remaining < argc); + } + + return result; +} + + +/* Print the version information. */ +static void +print_version (FILE *stream, struct argp_state *state __attribute__ ((unused))) +{ + fprintf (stream, "debugpack(%s) %s\n", PACKAGE_NAME, VERSION); + fprintf (stream, gettext ("\ +Copyright (C) %s Jan Kratochvil\n\ +This is free software; see the source for copying conditions. There is NO\n\ +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ +"), "2006"); + fprintf (stream, gettext ("Written by %s.\n"), "Jan Kratochvil"); +} + + +/* Handle program arguments. */ +static error_t +parse_opt (int key, char *arg, struct argp_state *state __attribute__ ((unused))) +{ + switch (key) + { + case 'o': + if (output_fname != NULL) + { + error (0, 0, gettext ("-o option specified twice")); + return EINVAL; + } + output_fname = arg; + break; + + case 'p': + preserve_dates = true; + break; + + case OPT_PERMISSIVE: + permissive = true; + break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + + +static int +process_file (const char *fname) +{ + /* If we have to preserve the modify and access timestamps get them + now. We cannot use fstat() after opening the file since the open + would change the access time. */ + struct stat64 pre_st; + struct timeval tv[2]; + again: + if (preserve_dates) + { + if (stat64 (fname, &pre_st) != 0) + { + error (0, errno, gettext ("cannot stat input file '%s'"), fname); + return 1; + } + + /* If we have to preserve the timestamp, we need it in the + format utimes() understands. */ + TIMESPEC_TO_TIMEVAL (&tv[0], &pre_st.st_atim); + TIMESPEC_TO_TIMEVAL (&tv[1], &pre_st.st_mtim); + } + + /* Open the file. */ + int fd = open (fname, output_fname == NULL ? O_RDWR : O_RDONLY); + if (fd == -1) + { + error (0, errno, gettext ("while opening '%s'"), fname); + return 1; + } + + /* We always use fstat() even if we called stat() before. This is + done to make sure the information returned by stat() is for the + same file. */ + struct stat64 st; + if (fstat64 (fd, &st) != 0) + { + error (0, errno, gettext ("cannot stat input file '%s'"), fname); + return 1; + } + /* Paranoid mode on. */ + if (preserve_dates + && (st.st_ino != pre_st.st_ino || st.st_dev != pre_st.st_dev)) + { + /* We detected a race. Try again. */ + close (fd); + goto again; + } + + /* Now get the ELF descriptor. */ + 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: + /* It is not possible to strip the content of an archive direct + the output to a specific file. */ + if (unlikely (output_fname != NULL)) + { + error (0, 0, gettext ("%s: cannot use -o when stripping archive"), + fname); + result = 1; + } + else + result = handle_ar (fd, elf, NULL, fname, preserve_dates ? tv : NULL); + break; + + default: + error (0, 0, gettext ("%s: File format not recognized"), fname); + result = 1; + break; + } + + if (unlikely (elf_end (elf) != 0)) + INTERNAL_ERROR (fname); + + close (fd); + + return result; +} + + +static struct reference +{ + struct reference *next; + /* List: packed_chunk->reference */ + struct reference *next_packed_chunk,**next_packed_chunk_prevp; + uintmax_t ref; + Dwarf_Off originator_cu_offset; + unsigned int form; + bool dead; + void *valp; + /* Inside: packed_chunk->data */ + void *compare_copy; +} *reference; + +static void +reference_register (uintmax_t ref, Dwarf_Off originator_cu_offset, unsigned int form, void *valp) +{ + struct reference *reference_new; + + reference_new = xmalloc (sizeof (*reference_new)); + reference_new->next = reference; + reference_new->ref = ref; + reference_new->originator_cu_offset = originator_cu_offset; + reference_new->form = form; + reference_new->dead = false; + reference_new->valp = valp; + reference_new->next_packed_chunk = NULL; + reference_new->next_packed_chunk_prevp = NULL; + reference = reference_new; +} + +struct attrcb_args +{ + Dwarf *dbg; + int level; + unsigned int addrsize; + Dwarf_Off cu_offset; + struct decl_val + { + uintmax_t val; + unsigned int form; + void *valp; + } decl_file; +}; + +static void +decl_val_clear (void *p, unsigned int form) +{ + switch (form) + { + case DW_FORM_data8: memset(p, 0, 8); break; + case DW_FORM_data4: memset(p, 0, 4); break; + case DW_FORM_data2: memset(p, 0, 2); break; + case DW_FORM_data1: memset(p, 0, 1); break; + default: + error (0, 0, gettext ("Error clearing form %u"), form); + } +} + +static intmax_t stmt_list = INTMAX_C(-1); + +static int +attr_callback (Dwarf_Attribute *attrp, void *arg) +{ + struct attrcb_args *cbargs = (struct attrcb_args *) arg; + + unsigned int attr = dwarf_whatattr (attrp); + if (unlikely (attr == 0)) + { + error (0, 0, gettext ("cannot get attribute code: %s"), + dwarf_errmsg (-1)); + return DWARF_CB_ABORT; + } + + unsigned int form = dwarf_whatform (attrp); + if (unlikely (form == 0)) + { + error (0, 0, gettext ("cannot get attribute form: %s"), + dwarf_errmsg (-1)); + return DWARF_CB_ABORT; + } + + switch (form) + { + case DW_FORM_addr:; + case DW_FORM_indirect: + case DW_FORM_strp: + case DW_FORM_string:; + break; + + case DW_FORM_ref_addr: + case DW_FORM_ref_udata: + case DW_FORM_ref8: + case DW_FORM_ref4: + case DW_FORM_ref2: + case DW_FORM_ref1:; + Dwarf_Off ref; + if (unlikely (dwarf_formref (attrp, &ref) != 0)) + { + attrval_out: + error (0, 0, gettext ("cannot get attribute value: %s"), + dwarf_errmsg (-1)); + return DWARF_CB_ABORT; + } + reference_register ((uintmax_t) (ref + cbargs->cu_offset), cbargs->cu_offset, form, attrp->valp); + break; + + case DW_FORM_udata: + case DW_FORM_sdata: + case DW_FORM_data8: + case DW_FORM_data4: + case DW_FORM_data2: + case DW_FORM_data1:; + Dwarf_Word num; + if (unlikely (dwarf_formudata (attrp, &num) != 0)) + goto attrval_out; + + struct decl_val *decl_val = NULL; + + if (attr == DW_AT_decl_file) + decl_val = &cbargs->decl_file; + if (decl_val) + { + if (decl_val->val) + error (0, 0, gettext ("%s value already defined; was %" PRIuMAX ", now %" PRIuMAX), + "DW_AT_decl_file", decl_val->val, (uintmax_t) num); + decl_val->val = num; + decl_val->form = form; + decl_val->valp = attrp->valp; + } + if (attr == DW_AT_stmt_list) + stmt_list = num; + break; + + case DW_FORM_flag:; + break; + + case DW_FORM_block4: + case DW_FORM_block2: + case DW_FORM_block1: + case DW_FORM_block:; + break; + + default: + break; + } + + return DWARF_CB_OK; +} + + +struct debug_line_entry +{ + const char *directory; + size_t directory_length; + const char *file; + size_t file_length; +}; + +static struct debug_line_table +{ + struct debug_line_table *next; + size_t offset; + int entries; /* entry[0] == NULL */ + struct debug_line_entry entry[0]; +} *debug_line_table; + +static struct debug_line_entry *debug_line_find (size_t offset, int fileidx) +{ + struct debug_line_table *debug_line_table_iter; + + for (debug_line_table_iter = debug_line_table; debug_line_table_iter; + debug_line_table_iter = debug_line_table_iter->next) + if (debug_line_table_iter->offset == offset) + { + if (fileidx < 0 || fileidx >= debug_line_table_iter->entries) + return NULL; + return &debug_line_table_iter->entry[fileidx]; + } + return NULL; +} + +static void +print_debug_line_section (Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg) +{ + if (shdr->sh_size == 0) + return; + + /* There is no functionality in libdw to read the information in the + way it is represented here. Hardcode the decoder. */ + Elf_Data *data = elf_getdata (scn, NULL); + if (data == NULL || data->d_buf == NULL) + { + error (0, 0, gettext ("cannot get line data section data: %s"), + elf_errmsg (-1)); + return; + } + + const unsigned char *linep = (const unsigned char *) data->d_buf; + const unsigned char *lineendp; + + while (linep + < (lineendp = (const unsigned char *) data->d_buf + data->d_size)) + { + size_t start_offset = linep - (const unsigned char *) data->d_buf; + + Dwarf_Word unit_length = read_4ubyte_unaligned_inc (dbg, linep); + unsigned int length = 4; + if (unlikely (unit_length == 0xffffffff)) + { + if (unlikely (linep + 8 > lineendp)) + { + invalid_data: + error (0, 0, gettext ("invalid data in section [%zu] '%s'"), + elf_ndxscn (scn), ".debug_line"); + return; + } + unit_length = read_8ubyte_unaligned_inc (dbg, linep); + length = 8; + } + + /* Check whether we have enough room in the section. */ + if (unit_length < 2 + length + 5 * 1 + || unlikely (linep + unit_length > lineendp)) + goto invalid_data; + lineendp = linep + unit_length; + + /* The next element of the header is the version identifier. */ + read_2ubyte_unaligned_inc (dbg, linep); + + /* Next comes the header length. */ + Dwarf_Word header_length; + if (length == 4) + header_length = read_4ubyte_unaligned_inc (dbg, linep); + else + header_length = read_8ubyte_unaligned_inc (dbg, linep); + //const unsigned char *header_start = linep; + + /* Next the minimum instruction length. */ + linep++; + + /* Then the flag determining the default value of the is_stmt + register. */ + linep++; + + /* Now the line base. */ + ++linep; + + /* And the line range. */ + linep++; + + /* The opcode base. */ + uint_fast8_t opcode_base = *linep++; + + if (unlikely (linep + opcode_base - 1 >= lineendp)) + goto invalid_data; + int opcode_base_l10 = 1; + unsigned int tmp = opcode_base; + while (tmp > 10) + { + tmp /= 10; + ++opcode_base_l10; + } + linep += opcode_base - 1; + if (unlikely (linep >= lineendp)) + goto invalid_data; + + unsigned int directories = 0; + const unsigned char *linep_restore; + linep_restore = linep; + while (*linep != 0) + { + unsigned char *endp = memchr (linep, '\0', lineendp - linep); + if (endp == NULL) + goto invalid_data; + + directories++; + linep = endp + 1; + } + /* directory[0] == NULL */ + const char **directory; + directory = xmalloc (sizeof (*directory) * (directories + 1)); + directory[0] = NULL; + linep = linep_restore; + unsigned int directory_i = 1; + while (*linep != 0) + { + unsigned char *endp = memchr (linep, '\0', lineendp - linep); + if (endp == NULL) + goto invalid_data; + + directory[directory_i++] = (const char *)linep; + + linep = endp + 1; + } + assert (directory_i == directories + 1); + /* Skip the final NUL byte. */ + ++linep; + + if (unlikely (linep >= lineendp)) + goto invalid_data; + linep_restore = linep; + int files = 0; + for (unsigned int cnt = 1; *linep != 0; ++cnt) + { + /* First comes the file name. */ + char *fname = (char *) linep; + unsigned char *endp = memchr (fname, '\0', lineendp - linep); + if (endp == NULL) + goto invalid_data; + linep = endp + 1; + + /* Then the index. */ + unsigned int diridx; + get_uleb128 (diridx, linep); + + if (diridx > directories) + goto invalid_data; + + /* Next comes the modification time. */ + unsigned int mtime; + get_uleb128 (mtime, linep); + + /* Finally the length of the file. */ + unsigned int fsize; + get_uleb128 (fsize, linep); + + files++; + } + linep = linep_restore; + struct debug_line_table *debug_line_table_new = NULL; + if (files) + { + debug_line_table_new = xmalloc (sizeof (*debug_line_table_new) + + (files + 1) * sizeof (*debug_line_table_new->entry)); + debug_line_table_new->next = debug_line_table; + debug_line_table_new->offset = start_offset; + debug_line_table_new->entries = files + 1; + debug_line_table_new->entry[0].directory = NULL; + debug_line_table_new->entry[0].file = NULL; + debug_line_table = debug_line_table_new; + } + for (unsigned int cnt = 1; *linep != 0; ++cnt) + { + /* First comes the file name. */ + char *fname = (char *) linep; + unsigned char *endp = memchr (fname, '\0', lineendp - linep); + if (endp == NULL) + goto invalid_data; + linep = endp + 1; + + /* Then the index. */ + unsigned int diridx; + get_uleb128 (diridx, linep); + + if (diridx > directories) + goto invalid_data; + + /* Next comes the modification time. */ + unsigned int mtime; + get_uleb128 (mtime, linep); + + /* Finally the length of the file. */ + unsigned int fsize; + get_uleb128 (fsize, linep); + + debug_line_table_new->entry[cnt].directory = directory[diridx]; + if (debug_line_table_new->entry[cnt].directory) + debug_line_table_new->entry[cnt].directory_length = + strlen(debug_line_table_new->entry[cnt].directory); + else + debug_line_table_new->entry[cnt].directory_length = 0; + debug_line_table_new->entry[cnt].file = fname; + debug_line_table_new->entry[cnt].file_length = strlen(fname); + } + /* Skip the final NUL byte. */ + ++linep; + free(directory); + + /* We are not interested in the 'opcodes'. */ + linep = lineendp; + } + + /* There must only be one data block. */ + assert (elf_getdata (scn, data) == NULL); +} + + +struct packed_chunk +{ + /* List: packed_chunk_list */ + struct packed_chunk *next; + Dwarf_Off offset; + Dwarf_Off offset_end; + Dwarf_Off offset_new; + /* Linked by: reference->next_packed_chuk */ + struct reference *reference; + /* "data" starts with some filename opaque stuff. + * Point in the middle is marked by "info_after_code". + * Follows .debug_info data after its ULEB128 code. + * Point in the middle is marked by "abbrev_after_code". + * Follows .debug_abbrev data after its ULEB128 code. */ + uint8_t *info_after_code; + size_t info_length; /* Without the (missing) ULEB128 code. */ + uint8_t *abbrev_after_code; + size_t abbrev_length; /* Without the (missing) ULEB128 code. */ + size_t length; + uint8_t data[0]; +}; +static struct packed_chunk *packed_chunk_list; +static struct packed_chunk **packed_chunk_array; +static int packed_chunk_count; + +static void * +find_abbrev_end (const unsigned char *abbrevp, + const unsigned char *abbrev_buffer_end, + void **abbrev_after_codep) +{ + assert (abbrevp + sizeof (uint8_t) <= abbrev_buffer_end); + assert (*(uint8_t *) abbrevp != 0); + /* Get the abbreviation code. */ + unsigned int code; + get_uleb128 (code, abbrevp); + if (abbrev_after_codep) + *abbrev_after_codep = (void *) abbrevp; + assert (abbrevp + sizeof (uint8_t) <= abbrev_buffer_end); + /* Get the abbreviation tag. */ + unsigned int tag; + get_uleb128 (tag, abbrevp); + assert (abbrevp + sizeof (uint8_t) <= abbrev_buffer_end); + /* has_children? */ + abbrevp++; + + unsigned int attrname; + unsigned int attrform; + do + { + get_uleb128 (attrname, abbrevp); + get_uleb128 (attrform, abbrevp); + } + while (attrname != 0 && attrform != 0); + assert (abbrevp <= abbrev_buffer_end); + + return (void *) abbrevp; +} + +#warning "FIXME: Fixed size array!" +static Dwarf_Off cu_offset[0x100]; +static int cu_offset_count; + +static void +print_debug_info_section (GElf_Shdr *shdr, Dwarf *dbg) +{ + /* If the section is empty we don't have to do anything. */ + if (shdr->sh_size == 0) + return; + + int maxdies = 20; + Dwarf_Die *dies = (Dwarf_Die *) xmalloc (maxdies * sizeof (Dwarf_Die)); + + Dwarf_Off offset = 0; + + /* New compilation unit. */ + size_t cuhl; + //Dwarf_Half version; + Dwarf_Off abbroffset; + uint8_t addrsize; + uint8_t offsize; + Dwarf_Off nextcu; + next_cu: + if (dwarf_nextcu (dbg, offset, &nextcu, &cuhl, &abbroffset, &addrsize, + &offsize) != 0) + goto do_return; + cu_offset[cu_offset_count++] = offset; + + struct attrcb_args args; + args.dbg = dbg; + args.addrsize = addrsize; + args.cu_offset = offset; + + offset += cuhl; + + int level = 0; + + uint8_t *pack_chunk = NULL; + size_t pack_chunk_used = 0; + size_t pack_chunk_allocated = 0; + + if (unlikely (dwarf_offdie (dbg, offset, &dies[level]) == NULL)) + { + error (0, 0, gettext ("cannot get DIE at offset %" PRIu64 + " in section '%s': %s"), + (uint64_t) offset, ".debug_info", dwarf_errmsg (-1)); + goto do_return; + } + + struct reference *reference_last_for_compare_copy; + void *addr_for_compare_copy; + do + { + offset = dwarf_dieoffset (&dies[level]); + if (offset == ~0ul) + { + error (0, 0, gettext ("cannot get DIE offset: %s"), + dwarf_errmsg (-1)); + goto do_return; + } + + int tag = dwarf_tag (&dies[level]); + if (tag == DW_TAG_invalid) + { + error (0, 0, gettext ("cannot get tag of DIE at offset %" PRIu64 + " in section '%s': %s"), + (uint64_t) offset, ".debug_info", dwarf_errmsg (-1)); + goto do_return; + } + + if (tag == DW_TAG_compile_unit) + stmt_list = INTMAX_C(-1); + void *addr = dies[level].addr; + struct reference *reference_last = reference; + if (level <= 1) + { + pack_chunk_used = 0; + reference_last_for_compare_copy = reference; + addr_for_compare_copy = addr; + } + assert (dies[level].abbrev != NULL); + void *abbrev = dbg->sectiondata[IDX_debug_abbrev]->d_buf + + dies[level].abbrev->offset; + void *abbrev_buffer_end = dbg->sectiondata[IDX_debug_abbrev]->d_buf + + dbg->sectiondata[IDX_debug_abbrev]->d_size; + assert (abbrev < abbrev_buffer_end); + void *abbrev_after_code; + void *abbrev_end = find_abbrev_end (abbrev, abbrev_buffer_end, + &abbrev_after_code); + + /* Print the attribute values. */ + args.level = level; + args.decl_file.val = 0; + (void) dwarf_getattrs (&dies[level], attr_callback, &args, 0); + + /* Make room for the next level's DIE. */ + if (level + 1 == maxdies) + dies = (Dwarf_Die *) xrealloc (dies, + (maxdies += 10) + * sizeof (Dwarf_Die)); + + int res = dwarf_child (&dies[level], &dies[level + 1]); + if (res > 0) + { + while ((res = dwarf_siblingof (&dies[level], &dies[level])) == 1) + if (level-- == 0) + break; + + if (res == -1) + { + error (0, 0, gettext ("cannot get next DIE: %s\n"), + dwarf_errmsg (-1)); + goto do_return; + } + } + else if (unlikely (res < 0)) + { + error (0, 0, gettext ("cannot get next DIE: %s"), + dwarf_errmsg (-1)); + goto do_return; + } + else + ++level; + + Dwarf_Off offset_end; + if (level >= 0) + offset_end = dwarf_dieoffset (&dies[level]); + else if (nextcu != (Dwarf_Off) -1l && nextcu != 0) +#warning "FIXME: Too terrible hack to catch the end of the last DIE in a section." + offset_end = nextcu - 1; + else + break; + assert (offset_end > offset); + + const unsigned char *addr_after_code = addr; + /* Skip the abbreviation code. */ + unsigned int u128; + get_uleb128 (u128, addr_after_code); + Dwarf_Off offset_after_code = offset + ((void *) addr_after_code - addr); + + size_t need_mem = 0; + need_mem += offset_end - offset_after_code; /* .debug_info */ + need_mem += abbrev_end - abbrev_after_code; /* .debug_abbrev */ + + struct debug_line_entry *debug_line = NULL; + if (args.decl_file.val && stmt_list != INTMAX_C(-1)) + debug_line = debug_line_find (stmt_list, args.decl_file.val); + if (debug_line && debug_line->directory) + need_mem += strlen(debug_line->directory); + else + need_mem++; + if (debug_line) + need_mem += strlen(debug_line->file); + else + need_mem++; + + if (pack_chunk_used + need_mem > pack_chunk_allocated) + { + pack_chunk_allocated = pack_chunk_used + need_mem + 0x100; + pack_chunk = xrealloc (pack_chunk, pack_chunk_allocated); + } + if (debug_line && debug_line->directory) + { + memcpy(&pack_chunk[pack_chunk_used], debug_line->directory, + debug_line->directory_length); + pack_chunk_used += debug_line->directory_length; + } + pack_chunk[pack_chunk_used++] = 0; + if (debug_line) + { + memcpy(&pack_chunk[pack_chunk_used], debug_line->file, + debug_line->file_length); + pack_chunk_used += debug_line->file_length; + } + memcpy (&pack_chunk[pack_chunk_used], addr_after_code, + offset_end - offset_after_code); + if (args.decl_file.val) + decl_val_clear (&pack_chunk[pack_chunk_used] + + (args.decl_file.valp - addr), + args.decl_file.form); + struct reference *reference_iter; + for (reference_iter = reference; reference_iter != reference_last; + reference_iter = reference_iter->next) + { + assert (reference_iter->valp >= addr); + assert (reference_iter->valp < addr + + (offset_end - offset_after_code)); + assert (reference_iter->compare_copy == NULL); + } + pack_chunk_used += offset_end - offset_after_code; + memcpy (&pack_chunk[pack_chunk_used], abbrev_after_code, + abbrev_end - abbrev_after_code); + pack_chunk_used += abbrev_end - abbrev_after_code; + + if (level == 1 || (args.level == 1 && level == -1)) + { + struct packed_chunk *packed_chunk_new; + + packed_chunk_new = xmalloc (sizeof (*packed_chunk_new) + + pack_chunk_used); + packed_chunk_new->next = packed_chunk_list; + packed_chunk_new->offset = offset; + packed_chunk_new->offset_end = offset_end; + packed_chunk_new->offset_new = 0; + packed_chunk_new->reference = NULL; + packed_chunk_new->abbrev_length = abbrev_end - abbrev_after_code; + packed_chunk_new->abbrev_after_code = packed_chunk_new->data + + pack_chunk_used - packed_chunk_new->abbrev_length; + packed_chunk_new->info_length = offset_end - offset_after_code; + packed_chunk_new->info_after_code = + packed_chunk_new->abbrev_after_code + - packed_chunk_new->info_length; + packed_chunk_new->length = pack_chunk_used; + memcpy (packed_chunk_new->data, pack_chunk, pack_chunk_used); + packed_chunk_list = packed_chunk_new; + packed_chunk_count++; + + for (reference_iter = reference; + reference_iter != reference_last_for_compare_copy; + reference_iter = reference_iter->next) + { + assert (reference_iter->valp >= addr_for_compare_copy); + assert (reference_iter->valp < addr + (offset_end - offset)); + assert (reference_iter->compare_copy == NULL); + + reference_iter->compare_copy = packed_chunk_new->data + + (reference_iter->valp - addr_for_compare_copy); + + assert (reference_iter->next_packed_chunk == NULL); + assert (reference_iter->next_packed_chunk_prevp == NULL); + reference_iter->next_packed_chunk = packed_chunk_new->reference; + reference_iter->next_packed_chunk_prevp = &packed_chunk_new->reference; + if (packed_chunk_new->reference) + packed_chunk_new->reference->next_packed_chunk_prevp = &reference_iter->next_packed_chunk; + packed_chunk_new->reference = reference_iter; + } + } + } + while (level >= 0); + + offset = nextcu; + if (offset != 0) + goto next_cu; + + do_return: + free (dies); +} + + +static int packed_chunk_compar (struct packed_chunk **ap, + struct packed_chunk **bp) +{ + int result; + + result = ((*bp)->length > (*ap)->length) - ((*bp)->length < (*ap)->length); + if (result) + return result; + + result = ((*bp)->info_length > (*ap)->info_length) - ((*bp)->info_length < (*ap)->info_length); + if (result) + return result; + + result = ((*bp)->abbrev_length > (*ap)->abbrev_length) - ((*bp)->abbrev_length < (*ap)->abbrev_length); + if (result) + return result; + + return memcmp ((*ap)->data, (*bp)->data, (*ap)->length); +} + +static void +packed_chunk_repack (void) +{ + packed_chunk_array = xcalloc (packed_chunk_count, + sizeof (*packed_chunk_array)); + struct packed_chunk **packed_chunk_array_iter = packed_chunk_array; + struct packed_chunk *packed_chunk_iter; + for (packed_chunk_iter = packed_chunk_list; + packed_chunk_iter; + packed_chunk_iter = packed_chunk_iter->next) + { + *packed_chunk_array_iter++ = packed_chunk_iter; + } + assert (packed_chunk_array_iter == packed_chunk_array + packed_chunk_count); +} + + +static void * +write_uint32_t (Dwarf *dbg, void *d, uint32_t data) +{ + uint32_t *d_typed = d; + + if (dbg->other_byte_order) + data = bswap_32 (data); + *d_typed++ = data; + return d_typed; +} + +static void * +write_uint16_t (Dwarf *dbg, void *d, uint16_t data) +{ + uint16_t *d_typed = d; + + if (dbg->other_byte_order) + data = bswap_16 (data); + *d_typed++ = data; + return d_typed; +} + +static void * +write_uint8_t (Dwarf *dbg __attribute__ ((unused)), void *d, uint8_t data) +{ + uint8_t *d_typed = d; + + *d_typed++ = data; + return d_typed; +} + +static void * +write_uleb128 (void *d, uint64_t data) +{ + uint8_t *d_typed = d; + + do + { + *d_typed = data & 0x7F; + data >>= 7U; + if (data) + *d_typed |= 0x80; + d_typed++; + } + while (data); + + return d_typed; +} + +static void * +write_string (void *d, const char *string) +{ + size_t len = strlen (string) + 1; + + memcpy(d, string, len); + d += len; + return d; +} + +static void * +write_data (void *d, const void *data, size_t len) +{ + memcpy(d, data, len); + d += len; + return d; +} + +static void +write_formref (Dwarf *dbg, void *datap, unsigned int form, Dwarf_Off offset) +{ + Dwarf_Off offset_written; + + assert (datap != NULL); + + switch (form) + { + case DW_FORM_ref1: + write_uint8_t (dbg, datap, offset); + offset_written = (uint8_t) offset; + break; + + case DW_FORM_ref2: + write_uint16_t (dbg, datap, offset); + offset_written = (uint16_t) offset; + break; + + case DW_FORM_ref4: + write_uint32_t (dbg, datap, offset); + offset_written = (uint32_t) offset; + break; + + case DW_FORM_ref8: + case DW_FORM_ref_udata: + default: + error (0, 0, gettext ("offset writing is not supported for form %u: %" PRIuMAX), + form, (uintmax_t) offset); + return; + } + if (offset != offset_written) + error (0, 0, gettext ("offset does not fit into DW_FORM_???" + "; written %" PRIuMAX ": %" PRIuMAX), + (uintmax_t) offset_written, (uintmax_t) offset); +} + + +static struct deleted_info_range +{ + struct deleted_info_range *next; + uintmax_t start; + uintmax_t end; /* "end" is 1 byte after the range. */ +} *deleted_info_range_list; +static int deleted_info_range_count; + +static struct deleted_info_range **deleted_info_range_array; + +/* "end" is 1 byte after the range. */ +static void +delete_info_range (uintmax_t start, uintmax_t end) +{ + struct deleted_info_range *deleted_info_range_iter; + struct deleted_info_range *deleted_info_range_new; + + assert (start <= end); + + for (deleted_info_range_iter = deleted_info_range_list; + deleted_info_range_iter; + deleted_info_range_iter = deleted_info_range_iter->next) + assert (end <= deleted_info_range_iter->start + || start >= deleted_info_range_iter->end); + + deleted_info_range_new = xmalloc (sizeof (*deleted_info_range_new)); + deleted_info_range_new->next = deleted_info_range_list; + deleted_info_range_new->start = start; + deleted_info_range_new->end = end; + deleted_info_range_list = deleted_info_range_new; + deleted_info_range_count++; +} + +static size_t +deleted_info_ranges_map (uintmax_t addr) +{ + struct deleted_info_range *deleted_info_range_iter; + uintmax_t shift_down = 0; + + for (deleted_info_range_iter = deleted_info_range_list; + deleted_info_range_iter; + deleted_info_range_iter = deleted_info_range_iter->next) + { + if (addr < deleted_info_range_iter->start) + continue; + assert (addr >= deleted_info_range_iter->end); + shift_down += deleted_info_range_iter->end + - deleted_info_range_iter->start; + } + return addr - shift_down; +} + +static int +deleted_info_ranges_compar (const struct deleted_info_range **ap, + const struct deleted_info_range **bp) +{ + return ((*bp)->start < (*ap)->start) - ((*bp)->start > (*ap)->start); +} + +static void +deleted_info_ranges_sort (void) +{ + deleted_info_range_array = xcalloc (deleted_info_range_count, + sizeof (*deleted_info_range_array)); + struct deleted_info_range **deleted_info_range_array_iter; + deleted_info_range_array_iter = deleted_info_range_array; + while (deleted_info_range_list) + { + *deleted_info_range_array_iter++ = deleted_info_range_list; + deleted_info_range_list = deleted_info_range_list->next; + } + assert (deleted_info_range_array + deleted_info_range_count + == deleted_info_range_array_iter); + qsort (deleted_info_range_array, deleted_info_range_count, + sizeof (*deleted_info_range_array), + (int (*)(const void *, const void *))deleted_info_ranges_compar); +} + + +static void +pack_debug_info_section (Elf *elf, GElf_Shdr *shdr_info, Elf_Data *data_info, + Elf_Data *data_abbrev, Elf_Scn *scn_line, + GElf_Shdr *shdr_line, Elf_Data *data_line) +{ + Dwarf *dbg; + /* Before we start the real work get a debug context descriptor. */ + dbg = dwarf_begin_elf (elf, DWARF_C_READ, NULL); + if (dbg == NULL) + { + error (0, 0, gettext ("cannot get debug context descriptor: %s"), + dwarf_errmsg (-1)); + return; + } + + print_debug_line_section (scn_line, shdr_line, dbg); + + print_debug_info_section (shdr_info, dbg); + + packed_chunk_repack (); + +#warning "Provide dynamic memory allocation!" +#define FIXME_MEMORY_RESERVE 0x1000000 +{ + void *buf_info, *d_info, *d_info_length; + void *buf_abbrev, *d_abbrev; + void *buf_line, *d_line; + + buf_info = xmalloc (data_info->d_size + FIXME_MEMORY_RESERVE); + memcpy(buf_info, data_info->d_buf, data_info->d_size); + d_info = buf_info + data_info->d_size; + + buf_abbrev = xmalloc (data_abbrev->d_size + FIXME_MEMORY_RESERVE); + memcpy(buf_abbrev, data_abbrev->d_buf, data_abbrev->d_size); + d_abbrev = buf_abbrev + data_abbrev->d_size; + + buf_line = xmalloc (data_line->d_size + FIXME_MEMORY_RESERVE); + memcpy(buf_line, data_line->d_buf, data_line->d_size); + d_line = buf_line + data_line->d_size; + + struct reference *reference_iter; + for (reference_iter = reference; reference_iter; + reference_iter = reference_iter->next) + { + assert (reference_iter->valp >= data_info->d_buf); + assert (reference_iter->valp < data_info->d_buf + data_info->d_size); + + reference_iter->valp = buf_info + + (reference_iter->valp - data_info->d_buf); + } + + d_info_length = d_info; + d_info = write_uint32_t (dbg, d_info, 0); /* 4-byte length; filled later */ + d_info = write_uint16_t (dbg, d_info, 2); /* 2-byte DWARF version */ + d_info = write_uint32_t (dbg, d_info, d_abbrev - buf_abbrev); /* 4-byte .debug_abbrev section offset */ + d_info = write_uint8_t (dbg, d_info, 4 /* FIXME */); /* 1-byte address size */ + + d_info = write_uleb128 (d_info, 1); /* abbrev id */ + d_info = write_uint32_t (dbg, d_info, d_line - buf_line); /* DW_AT_stmt_list */ + d_info = write_string (d_info, PACKAGE_STRING); /* DW_AT_producer */ + /* FIXME: Autodetect the value. */ + d_info = write_uint8_t (dbg, d_info, DW_LANG_C89); /* DW_AT_language */ + + uint64_t abbrev_used = 1; + d_abbrev = write_uleb128 (d_abbrev, abbrev_used++); /* abbrev id */ + d_abbrev = write_uint8_t (dbg, d_abbrev, DW_TAG_compile_unit); + d_abbrev = write_uint8_t (dbg, d_abbrev, DW_CHILDREN_yes); + d_abbrev = write_uint8_t (dbg, d_abbrev, DW_AT_stmt_list); + d_abbrev = write_uint8_t (dbg, d_abbrev, DW_FORM_data4); + d_abbrev = write_uint8_t (dbg, d_abbrev, DW_AT_producer); + d_abbrev = write_uint8_t (dbg, d_abbrev, DW_FORM_string); + d_abbrev = write_uint8_t (dbg, d_abbrev, DW_AT_language); + d_abbrev = write_uint8_t (dbg, d_abbrev, DW_FORM_data1); + d_abbrev = write_uint8_t (dbg, d_abbrev, 0); /* terminator 1/2 */ + d_abbrev = write_uint8_t (dbg, d_abbrev, 0); /* terminator 2/2 */ + + + bool optimized; + do + { + optimized = false; + + qsort (packed_chunk_array, packed_chunk_count, sizeof (*packed_chunk_array), + (int (*)(const void *, const void *)) packed_chunk_compar); + + struct packed_chunk **packed_chunk_array_iter; + for (packed_chunk_array_iter = packed_chunk_array; + packed_chunk_array_iter < packed_chunk_array + packed_chunk_count - 1; + packed_chunk_array_iter++) + { + /* packed_chunk_array_iter[0]->offset_new may be set */ + assert (packed_chunk_array_iter[1]->offset_new == 0); + if (packed_chunk_compar (&packed_chunk_array_iter[0], + &packed_chunk_array_iter[1])) + continue; + + if (packed_chunk_array_iter[0]->offset_new) + packed_chunk_array_iter[1]->offset_new = + packed_chunk_array_iter[0]->offset_new; + else + { + packed_chunk_array_iter[0]->offset_new = + packed_chunk_array_iter[1]->offset_new = d_info - buf_info; + + /* Choose any one you want. */ + struct packed_chunk *packed_chunk_iter = packed_chunk_array_iter[0]; + + uint64_t abbrev_this = abbrev_used++; + d_abbrev = write_uleb128 (d_abbrev, abbrev_this); /* abbrev id */ + d_abbrev = write_data (d_abbrev, packed_chunk_iter->abbrev_after_code, + packed_chunk_iter->abbrev_length); + + d_info = write_uleb128 (d_info, abbrev_this); /* abbrev id */ + d_info = write_data (d_info, packed_chunk_iter->info_after_code, + packed_chunk_iter->info_length); + } + } + for (packed_chunk_array_iter = packed_chunk_array; + packed_chunk_array_iter < packed_chunk_array + packed_chunk_count;) + { + struct packed_chunk *packed_chunk_iter = *packed_chunk_array_iter; + + if (packed_chunk_iter->offset_new == 0) + { + packed_chunk_array_iter++; + continue; + } + + /* Drop references to deleted DIEs as each DIE can be moved only once. */ + struct reference **reference_iterp; + for (reference_iterp = &reference; (reference_iter = *reference_iterp);) + { + if (reference_iter->dead || reference_iter->ref != packed_chunk_iter->offset) + { + reference_iterp = &reference_iter->next; + continue; + } + reference_iter->ref = packed_chunk_iter->offset_new; + size_t shift_offset; + if (reference_iter->valp >= buf_info + data_info->d_size) + shift_offset = data_info->d_size; + else + shift_offset = reference_iter->originator_cu_offset; + if (reference_iter->compare_copy) + write_formref (dbg, reference_iter->compare_copy, + reference_iter->form, reference_iter->ref - shift_offset); + write_formref (dbg, reference_iter->valp, + reference_iter->form, reference_iter->ref - shift_offset); + reference_iterp = &reference_iter->next; + reference_iter->dead = true; + } + + /* Update reference items for references from the moved DIEs. */ + for (reference_iter = packed_chunk_iter->reference; reference_iter; + reference_iter = reference_iter->next_packed_chunk) + { + if (reference_iter->dead) + continue; + Dwarf_Off reference_at_offset = reference_iter->valp - buf_info; + assert (reference_at_offset >= packed_chunk_iter->offset + && reference_at_offset < packed_chunk_iter->offset_end); + assert (reference_iter->compare_copy != NULL); + reference_iter->valp += packed_chunk_iter->offset_new - packed_chunk_iter->offset; + reference_iter->compare_copy = NULL; + } + + delete_info_range (packed_chunk_iter->offset, packed_chunk_iter->offset_end); + /* Delete the current item due to its: ->offset_new != 0 */ + *packed_chunk_array_iter = packed_chunk_array[--packed_chunk_count]; + optimized = true; + } + } + while (optimized); + + d_abbrev = write_uleb128 (d_abbrev, 0); /* abbrev id - terminator */ + + d_info = write_uleb128 (d_info, 0); /* abbrev id - child terminator */ + + /* (- sizeof(uint32_t)) to exclude the length field itself. */ + write_uint32_t (dbg, d_info_length, d_info - d_info_length - sizeof(uint32_t)); + + for (reference_iter = reference; reference_iter; + reference_iter = reference_iter->next) + { + size_t shift_offset; + if (reference_iter->valp >= buf_info + data_info->d_size) + shift_offset = data_info->d_size; + else + shift_offset = reference_iter->originator_cu_offset; + shift_offset = deleted_info_ranges_map (shift_offset); + + reference_iter->ref = deleted_info_ranges_map (reference_iter->ref); + write_formref (dbg, reference_iter->valp, + reference_iter->form, reference_iter->ref - shift_offset); + } + + delete_info_range (d_info - buf_info, d_info - buf_info); + deleted_info_ranges_sort(); + struct deleted_info_range **deleted_info_range_array_iter; + void *src_info = buf_info; + int cu_offset_i = 0; + deleted_info_range_array_iter = deleted_info_range_array; + while (deleted_info_range_array_iter < deleted_info_range_array + deleted_info_range_count) + { + struct deleted_info_range *deleted_info_range_iter = *deleted_info_range_array_iter; + + ssize_t copy = (buf_info + deleted_info_range_iter->start) - src_info; + assert (copy >= 0); + src_info += copy; + + size_t skip = deleted_info_range_iter->end + - deleted_info_range_iter->start; + src_info += skip; + + while (cu_offset_i < cu_offset_count) + { + Dwarf_Off offset = cu_offset[cu_offset_i]; + /* Already in our generated zone? There are no deletions. */ + if (src_info >= buf_info + data_info->d_size) + break; + if (cu_offset_i + 1 < cu_offset_count + && src_info >= buf_info + cu_offset[cu_offset_i + 1]) + { + cu_offset_i++; + continue; + } + + void *addr = buf_info + offset; + uint32_t length = read_4ubyte_unaligned_inc (dbg, addr); + /* FIXME: Not yet supported. */ + assert (length != 0xFFFFFFFF); + length -= skip; + write_uint32_t (dbg, buf_info + offset, length); + break; + } + + deleted_info_range_array_iter++; + } + void *dst_info = buf_info; + src_info = buf_info; + deleted_info_range_array_iter = deleted_info_range_array; + while (deleted_info_range_array_iter < deleted_info_range_array + deleted_info_range_count) + { + struct deleted_info_range *deleted_info_range_iter = *deleted_info_range_array_iter; + + ssize_t copy = (buf_info + deleted_info_range_iter->start) - src_info; + assert (copy >= 0); + memmove (dst_info, src_info, copy); + dst_info += copy; + src_info += copy; + + size_t skip = deleted_info_range_iter->end + - deleted_info_range_iter->start; + src_info += skip; + d_info -= skip; + + deleted_info_range_array_iter++; + } + assert (dst_info == d_info); + + data_info->d_buf = buf_info; + data_info->d_size = d_info - buf_info; + + data_abbrev->d_buf = buf_abbrev; + data_abbrev->d_size = d_abbrev - buf_abbrev; + + data_line->d_buf = buf_line; + data_line->d_size = d_line - buf_line; +} + + /* We are done with the DWARF handling. */ + dwarf_end (dbg); +} + + +/* Maximum size of array allocated on stack. */ +#define MAX_STACK_ALLOC (400 * 1024) + +static int +handle_elf (int fd, Elf *elf, const char *prefix, const char *fname, + mode_t mode, struct timeval 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; + int result = 0; + size_t shstrndx; + struct shdr_info + { + Elf_Scn *scn; + GElf_Shdr shdr; + Elf_Data *data; + const char *name; + Elf32_Word idx; /* Index in new file. */ + Elf32_Word old_sh_link; /* Original value of shdr.sh_link. */ + Elf32_Word symtab_idx; + Elf32_Word version_idx; + Elf32_Word group_idx; + Elf32_Word group_cnt; + Elf_Scn *newscn; + struct Ebl_Strent *se; + Elf32_Word *newsymidx; + } *shdr_info = NULL; + Elf_Scn *scn; + size_t cnt; + size_t old_debug_info_cnt = 0, new_debug_info_cnt = 0; + size_t old_debug_abbrev_cnt = 0, new_debug_abbrev_cnt = 0; + size_t old_debug_line_cnt = 0, new_debug_line_cnt = 0; + size_t idx; + bool changes; + GElf_Ehdr newehdr_mem; + GElf_Ehdr *newehdr; + struct Ebl_Strtab *shst = NULL; + bool any_symtab_changes = false; + Elf_Data *shstrtab_data = NULL; + size_t shdridx = 0; + + /* Create the full name of the file. */ + if (prefix != NULL) + { + cp = mempcpy (cp, prefix, prefix_len); + *cp++ = ':'; + } + memcpy (cp, fname, fname_len); + + /* If we are not replacing the input file open a new file here. */ + if (output_fname != NULL) + { + fd = open (output_fname, O_RDWR | O_CREAT, mode); + if (unlikely (fd == -1)) + { + error (0, errno, gettext ("cannot open '%s'"), output_fname); + return 1; + } + } + + /* Get the information from the old file. */ + GElf_Ehdr ehdr_mem; + GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (ehdr == NULL) + INTERNAL_ERROR (fname); + + /* Get the section header string table index. */ + if (unlikely (elf_getshstrndx (elf, &shstrndx) < 0)) + error (EXIT_FAILURE, 0, + gettext ("cannot get section header string table index")); + + /* We now create a new ELF descriptor for the same file. We + construct it almost exactly in the same way with some information + dropped. */ + Elf *newelf; + if (output_fname != NULL) + newelf = elf_begin (fd, ELF_C_WRITE_MMAP, NULL); + else + newelf = elf_clone (elf, ELF_C_EMPTY); + + if (unlikely (gelf_newehdr (newelf, gelf_getclass (elf)) == 0) + || (ehdr->e_type != ET_REL + && unlikely (gelf_newphdr (newelf, ehdr->e_phnum) == 0))) + { + error (0, 0, gettext ("cannot create new file '%s': %s"), + output_fname, elf_errmsg (-1)); + goto fail; + } + + /* Copy over the old program header if needed. */ + if (ehdr->e_type != ET_REL) + for (cnt = 0; cnt < ehdr->e_phnum; ++cnt) + { + 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); + } + + /* Number of sections. */ + size_t shnum; + if (unlikely (elf_getshnum (elf, &shnum) < 0)) + { + error (0, 0, gettext ("cannot determine number of sections: %s"), + elf_errmsg (-1)); + goto fail_close; + } + + if (shstrndx >= shnum) + goto illformed; + +#define elf_assert(test) do { if (!(test)) goto illformed; } while (0) + + /* Storage for section information. We leave room for five more + entries since we unconditionally create a section header string + table. Maybe some weird tool created an ELF file without one. + The second one is used for the debug link section. + The third one is for the rebuilt ".debug_info" section. + The fourth one is for the rebuilt ".debug_abbrev" section. + The fifth one is for the rebuilt ".debug_line" section. */ + if ((shnum + 5) * sizeof (struct shdr_info) > MAX_STACK_ALLOC) + shdr_info = (struct shdr_info *) xcalloc (shnum + 5, + sizeof (struct shdr_info)); + else + { + shdr_info = (struct shdr_info *) alloca ((shnum + 5) + * sizeof (struct shdr_info)); + memset (shdr_info, '\0', (shnum + 5) * sizeof (struct shdr_info)); + } + + /* Prepare section information data structure. */ + scn = NULL; + cnt = 1; + while ((scn = elf_nextscn (elf, scn)) != NULL) + { + /* This should always be true (i.e., there should not be any + holes in the numbering). */ + elf_assert (elf_ndxscn (scn) == cnt); + + shdr_info[cnt].scn = scn; + + /* Get the header. */ + if (gelf_getshdr (scn, &shdr_info[cnt].shdr) == NULL) + INTERNAL_ERROR (fname); + + /* Get the name of the section. */ + shdr_info[cnt].name = elf_strptr (elf, shstrndx, + shdr_info[cnt].shdr.sh_name); + if (shdr_info[cnt].name == NULL) + { + illformed: + error (0, 0, gettext ("illformed file '%s'"), fname); + goto fail_close; + } + + if (!strcmp(".debug_info", shdr_info[cnt].name)) + old_debug_info_cnt = cnt; + if (!strcmp(".debug_abbrev", shdr_info[cnt].name)) + old_debug_abbrev_cnt = cnt; + if (!strcmp(".debug_line", shdr_info[cnt].name)) + old_debug_line_cnt = cnt; + + /* Mark them as present but not yet investigated. */ + shdr_info[cnt].idx = 1; + + /* Remember the shdr.sh_link value. */ + shdr_info[cnt].old_sh_link = shdr_info[cnt].shdr.sh_link; + if (shdr_info[cnt].old_sh_link >= shnum) + goto illformed; + + /* Sections in files other than relocatable object files which + are not loaded can be freely moved by us. In relocatable + object files everything can be moved. */ + if (ehdr->e_type == ET_REL + || (shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) == 0) + shdr_info[cnt].shdr.sh_offset = 0; + + /* If this is an extended section index table store an + appropriate reference. */ + 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)) + { + /* Cross-reference the sections contained in the section + group. */ + shdr_info[cnt].data = elf_getdata (shdr_info[cnt].scn, NULL); + if (shdr_info[cnt].data == NULL) + INTERNAL_ERROR (fname); + + /* XXX Fix for unaligned access. */ + 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)) + /* If the section group contains only one element and this + is n COMDAT section we can drop it right away. */ + /* 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 this section is part of a group make sure it is not + discarded right away. */ + 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) + { + /* The section group section will be removed. */ + shdr_info[cnt].group_idx = 0; + shdr_info[cnt].shdr.sh_flags &= ~SHF_GROUP; + } + } + + /* Increment the counter. */ + ++cnt; + } + + /* Mark the SHT_NULL section as handled. */ + shdr_info[0].idx = 2; + + + /* Handle exceptions: section groups and cross-references. We might + have to repeat this a few times since the resetting of the flag + might propagate. */ + do + { + changes = false; + + for (cnt = 1; cnt < shnum; ++cnt) + { + if (shdr_info[cnt].idx == 1) + { + /* The content of symbol tables we don't remove must not + reference any section which we do remove. Otherwise + we cannot remove the section. */ + if (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM + || shdr_info[cnt].shdr.sh_type == SHT_SYMTAB) + { + /* Make sure the data is loaded. */ + 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 there is an extended section index table load it + as well. */ + if (shdr_info[cnt].symtab_idx != 0 + && shdr_info[shdr_info[cnt].symtab_idx].data == NULL) + { + 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; + + /* Go through all symbols and make sure the section they + reference is not removed. */ + size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, + ehdr->e_version); + + 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) + /* Don't count in the section symbols. */ + || GELF_ST_TYPE (sym->st_info) == STT_SECTION) + /* This is no section index, leave it alone. */ + continue; + else if (scnidx == SHN_XINDEX) + scnidx = xndx; + + if (scnidx >= shnum) + goto illformed; + + if (shdr_info[scnidx].idx == 0) + { + /* Mark this section as used. */ + shdr_info[scnidx].idx = 1; + changes |= scnidx < cnt; + } + } + } + + /* Cross referencing happens: + - for the cases the ELF specification says. That are + + SHT_DYNAMIC in sh_link to string table + + SHT_HASH in sh_link to symbol table + + SHT_REL and SHT_RELA in sh_link to symbol table + + SHT_SYMTAB and SHT_DYNSYM in sh_link to string table + + SHT_GROUP in sh_link to symbol table + + SHT_SYMTAB_SHNDX in sh_link to symbol table + Other (OS or architecture-specific) sections might as + well use this field so we process it unconditionally. + - references inside section groups + - specially marked references in sh_info if the SHF_INFO_LINK + flag is set + */ + + 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; + } + + /* Handle references through sh_info. */ + 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; + } + } + + /* Mark the section as investigated. */ + shdr_info[cnt].idx = 2; + } + } + } + while (changes); + + /* Mark the section header string table as unused, we will create + a new one. */ + shdr_info[shstrndx].idx = 0; + + if (old_debug_info_cnt && old_debug_abbrev_cnt && old_debug_line_cnt) + { + new_debug_info_cnt = shnum++; + + shdr_info[new_debug_info_cnt].shdr = shdr_info[old_debug_info_cnt].shdr; + shdr_info[new_debug_info_cnt].idx = shdr_info[old_debug_info_cnt].idx; + shdr_info[old_debug_info_cnt].idx = 0; + shdr_info[new_debug_info_cnt].name = shdr_info[old_debug_info_cnt].name; + + shdr_info[new_debug_info_cnt].data = + elf_getdata (shdr_info[old_debug_info_cnt].scn, NULL); + if (shdr_info[new_debug_info_cnt].data == NULL) + INTERNAL_ERROR (fname); + + new_debug_abbrev_cnt = shnum++; + + shdr_info[new_debug_abbrev_cnt].shdr = + shdr_info[old_debug_abbrev_cnt].shdr; + shdr_info[new_debug_abbrev_cnt].idx = + shdr_info[old_debug_abbrev_cnt].idx; + shdr_info[old_debug_abbrev_cnt].idx = 0; + shdr_info[new_debug_abbrev_cnt].name = + shdr_info[old_debug_abbrev_cnt].name; + + shdr_info[new_debug_abbrev_cnt].data = + elf_getdata (shdr_info[old_debug_abbrev_cnt].scn, NULL); + if (shdr_info[new_debug_abbrev_cnt].data == NULL) + INTERNAL_ERROR (fname); + + new_debug_line_cnt = shnum++; + + shdr_info[new_debug_line_cnt].shdr = + shdr_info[old_debug_line_cnt].shdr; + shdr_info[new_debug_line_cnt].idx = + shdr_info[old_debug_line_cnt].idx; + shdr_info[old_debug_line_cnt].idx = 0; + shdr_info[new_debug_line_cnt].name = + shdr_info[old_debug_line_cnt].name; + + shdr_info[new_debug_line_cnt].data = + elf_getdata (shdr_info[old_debug_line_cnt].scn, NULL); + if (shdr_info[new_debug_line_cnt].data == NULL) + INTERNAL_ERROR (fname); + + pack_debug_info_section (elf, + &shdr_info[old_debug_info_cnt].shdr, + shdr_info[new_debug_info_cnt].data, + shdr_info[new_debug_abbrev_cnt].data, + shdr_info[old_debug_line_cnt].scn, + &shdr_info[old_debug_line_cnt].shdr, + shdr_info[new_debug_line_cnt].data); + } + + /* We need a string table for the section headers. */ + shst = ebl_strtabinit (true); + if (shst == NULL) + error (EXIT_FAILURE, errno, gettext ("while preparing output for '%s'"), + output_fname ?: fname); + + /* Assign new section numbers. */ + shdr_info[0].idx = 0; + for (cnt = idx = 1; cnt < shnum; ++cnt) + if (shdr_info[cnt].idx > 0) + { + shdr_info[cnt].idx = idx++; + + /* Create a new section. */ + shdr_info[cnt].newscn = elf_newscn (newelf); + if (shdr_info[cnt].newscn == NULL) + error (EXIT_FAILURE, 0, gettext ("while generating output file: %s"), + elf_errmsg (-1)); + + elf_assert (elf_ndxscn (shdr_info[cnt].newscn) == shdr_info[cnt].idx); + + /* Add this name to the section header string table. */ + shdr_info[cnt].se = ebl_strtabadd (shst, shdr_info[cnt].name, 0); + } + + /* Index of the section header table in the shdr_info array. */ + shdridx = cnt; + + /* Add the section header string table section name. */ + shdr_info[cnt].se = ebl_strtabadd (shst, ".shstrtab", 10); + shdr_info[cnt].idx = idx; + + /* Create the section header. */ + 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; + /* We set the offset to zero here. Before we write the ELF file the + field must have the correct value. This is done in the final + loop over all section. Then we have all the information needed. */ + shdr_info[cnt].shdr.sh_offset = 0; + shdr_info[cnt].shdr.sh_addralign = 1; + + /* Create the section. */ + shdr_info[cnt].newscn = elf_newscn (newelf); + if (shdr_info[cnt].newscn == NULL) + error (EXIT_FAILURE, 0, + gettext ("while create section header section: %s"), + elf_errmsg (-1)); + elf_assert (elf_ndxscn (shdr_info[cnt].newscn) == idx); + + /* Finalize the string table and fill in the correct indices in the + section headers. */ + shstrtab_data = elf_newdata (shdr_info[cnt].newscn); + if (shstrtab_data == NULL) + error (EXIT_FAILURE, 0, + gettext ("while create section header string table: %s"), + elf_errmsg (-1)); + ebl_strtabfinalize (shst, shstrtab_data); + + /* We have to set the section size. */ + shdr_info[cnt].shdr.sh_size = shstrtab_data->d_size; + + /* Update the section information. */ + 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); + assert (scn != NULL); + + /* Update the name. */ + shdr_info[cnt].shdr.sh_name = ebl_strtaboffset (shdr_info[cnt].se); + + /* Update the section header from the input file. Some fields + might be section indeces which now have to be adjusted. */ + if (shdr_info[cnt].shdr.sh_link != 0) + shdr_info[cnt].shdr.sh_link = + shdr_info[shdr_info[cnt].shdr.sh_link].idx; + + if (shdr_info[cnt].shdr.sh_type == SHT_GROUP) + { + assert (shdr_info[cnt].data != NULL); + + Elf32_Word *grpref = (Elf32_Word *) shdr_info[cnt].data->d_buf; + for (size_t inner = 0; + inner < shdr_info[cnt].data->d_size / sizeof (Elf32_Word); + ++inner) + grpref[inner] = shdr_info[grpref[inner]].idx; + } + + /* Handle the SHT_REL, SHT_RELA, and SHF_INFO_LINK flag. */ + if (SH_INFO_LINK_P (&shdr_info[cnt].shdr)) + shdr_info[cnt].shdr.sh_info = + shdr_info[shdr_info[cnt].shdr.sh_info].idx; + + /* Get the data from the old file if necessary. We already + created the data for the section header string table. */ + 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); + } + + /* Set the data. This is done by copying from the old file. */ + newdata = elf_newdata (scn); + if (newdata == NULL) + INTERNAL_ERROR (fname); + + /* Copy the structure. */ + *newdata = *shdr_info[cnt].data; + + /* We know the size. */ + shdr_info[cnt].shdr.sh_size = shdr_info[cnt].data->d_size; + + /* We have to adjust symtol tables. The st_shndx member might + have to be updated. */ + 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, + ehdr->e_version); + + if (shdr_info[cnt].symtab_idx != 0) + { + assert (shdr_info[cnt].shdr.sh_type == SHT_SYMTAB_SHNDX); + /* This section has extended section information. + We have to modify that information, too. */ + shndxdata = elf_getdata (shdr_info[shdr_info[cnt].symtab_idx].scn, + NULL); + + elf_assert ((versiondata->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); + /* This section has associated version + information. We have to modify that + information, too. */ + versiondata = elf_getdata (shdr_info[shdr_info[cnt].version_idx].scn, + NULL); + + elf_assert ((versiondata->d_size / sizeof (GElf_Versym)) + >= shdr_info[cnt].data->d_size / elsize); + } + + shdr_info[cnt].newsymidx + = (Elf32_Word *) 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 >= shnum + && sym->st_shndx != SHN_XINDEX)) + { + /* This is no section index, leave it alone + unless it is moved. */ + 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; + } + + /* Get the full section index, if necessary from the + XINDEX table. */ + if (sym->st_shndx != SHN_XINDEX) + sec = shdr_info[sym->st_shndx].idx; + else + { + elf_assert (shndxdata != NULL); + + sec = shdr_info[xshndx].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 + /* This is a section symbol for a section which has + been removed. */ + elf_assert (GELF_ST_TYPE (sym->st_info) == STT_SECTION); + } + + if (destidx != inner) + { + /* The size of the symbol table changed. */ + shdr_info[cnt].shdr.sh_size = newdata->d_size + = destidx * elsize; + any_symtab_changes = true; + } + else + { + /* The symbol table didn't really change. */ + free (shdr_info[cnt].newsymidx); + shdr_info[cnt].newsymidx = NULL; + } + } + } + + /* If we have to, compute the offset of the section. */ + if (shdr_info[cnt].shdr.sh_offset == 0) + shdr_info[cnt].shdr.sh_offset + = ((lastoffset + shdr_info[cnt].shdr.sh_addralign - 1) + & ~((GElf_Off) (shdr_info[cnt].shdr.sh_addralign - 1))); + + /* Set the section header in the new file. */ + if (unlikely (gelf_update_shdr (scn, &shdr_info[cnt].shdr) == 0)) + /* There cannot be any overflows. */ + INTERNAL_ERROR (fname); + + /* Remember the last section written so far. */ + GElf_Off filesz = (shdr_info[cnt].shdr.sh_type != SHT_NOBITS + ? shdr_info[cnt].shdr.sh_size : 0); + if (lastoffset < shdr_info[cnt].shdr.sh_offset + filesz) + lastoffset = shdr_info[cnt].shdr.sh_offset + filesz; + } + + /* Adjust symbol references if symbol tables changed. */ + if (any_symtab_changes) + { + /* Find all relocation sections which use this + symbol table. */ + for (cnt = 1; cnt <= shdridx; ++cnt) + { + if (shdr_info[cnt].idx == 0) + /* Ignore sections which are discarded. When we are saving a + relocation section in a separate debug file, we must fix up + the symbol table references. */ + continue; + + if (shdr_info[cnt].shdr.sh_type == SHT_REL + || shdr_info[cnt].shdr.sh_type == SHT_RELA) + { + /* If the symbol table hasn't changed, do not do anything. */ + if (shdr_info[shdr_info[cnt].old_sh_link].newsymidx == NULL) + continue; + + Elf32_Word *newsymidx + = shdr_info[shdr_info[cnt].old_sh_link].newsymidx; + Elf_Data *d = elf_getdata (elf_getscn (newelf, + shdr_info[cnt].idx), + NULL); + assert (d != NULL); + size_t nrels = (shdr_info[cnt].shdr.sh_size + / shdr_info[cnt].shdr.sh_entsize); + + if (shdr_info[cnt].shdr.sh_type == SHT_REL) + for (size_t relidx = 0; relidx < nrels; ++relidx) + { + 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); + 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); + 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); + } + } + } + else if (shdr_info[cnt].shdr.sh_type == SHT_HASH) + { + /* We have to recompute the hash table. */ + Elf32_Word symtabidx = shdr_info[cnt].old_sh_link; + + /* We do not have to do anything if the symbol table was + not changed. */ + if (shdr_info[symtabidx].newsymidx == NULL) + continue; + + assert (shdr_info[cnt].idx > 0); + + /* The hash section in the new file. */ + scn = elf_getscn (newelf, shdr_info[cnt].idx); + + /* The symbol table data. */ + Elf_Data *symd = elf_getdata (elf_getscn (newelf, + shdr_info[symtabidx].idx), + NULL); + assert (symd != NULL); + + /* The hash table data. */ + Elf_Data *hashd = elf_getdata (scn, NULL); + assert (hashd != NULL); + + if (shdr_info[cnt].shdr.sh_entsize == sizeof (Elf32_Word)) + { + /* Sane arches first. */ + Elf32_Word *bucket = (Elf32_Word *) hashd->d_buf; + + size_t strshndx = shdr_info[symtabidx].old_sh_link; + size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, + ehdr->e_version); + + /* Adjust the nchain value. The symbol table size + changed. We keep the same size for the bucket array. */ + bucket[1] = symd->d_size / elsize; + Elf32_Word nbucket = bucket[0]; + bucket += 2; + Elf32_Word *chain = bucket + nbucket; + + /* New size of the section. */ + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + shdr->sh_size = hashd->d_size + = (2 + symd->d_size / elsize + nbucket) + * sizeof (Elf32_Word); + (void) gelf_update_shdr (scn, shdr); + + /* Clear the arrays. */ + memset (bucket, '\0', + (symd->d_size / elsize + nbucket) + * sizeof (Elf32_Word)); + + for (size_t inner = shdr_info[symtabidx].shdr.sh_info; + inner < symd->d_size / elsize; ++inner) + { + GElf_Sym sym_mem; + GElf_Sym *sym = gelf_getsym (symd, inner, &sym_mem); + elf_assert (sym != NULL); + + const char *name = elf_strptr (elf, strshndx, + sym->st_name); + elf_assert (name != NULL); + size_t hidx = elf_hash (name) % nbucket; + + if (bucket[hidx] == 0) + bucket[hidx] = inner; + else + { + hidx = bucket[hidx]; + + while (chain[hidx] != 0) + hidx = chain[hidx]; + + chain[hidx] = inner; + } + } + } + else + { + /* Alpha and S390 64-bit use 64-bit SHT_HASH entries. */ + elf_assert (shdr_info[cnt].shdr.sh_entsize + == sizeof (Elf64_Xword)); + + Elf64_Xword *bucket = (Elf64_Xword *) hashd->d_buf; + + size_t strshndx = shdr_info[symtabidx].old_sh_link; + size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, + ehdr->e_version); + + /* Adjust the nchain value. The symbol table size + changed. We keep the same size for the bucket array. */ + bucket[1] = symd->d_size / elsize; + Elf64_Xword nbucket = bucket[0]; + bucket += 2; + Elf64_Xword *chain = bucket + nbucket; + + /* New size of the section. */ + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + shdr->sh_size = hashd->d_size + = (2 + symd->d_size / elsize + nbucket) + * sizeof (Elf64_Xword); + (void) gelf_update_shdr (scn, shdr); + + /* Clear the arrays. */ + memset (bucket, '\0', + (symd->d_size / elsize + nbucket) + * sizeof (Elf64_Xword)); + + for (size_t inner = shdr_info[symtabidx].shdr.sh_info; + inner < symd->d_size / elsize; ++inner) + { + GElf_Sym sym_mem; + GElf_Sym *sym = gelf_getsym (symd, inner, &sym_mem); + elf_assert (sym != NULL); + + const char *name = elf_strptr (elf, strshndx, + sym->st_name); + elf_assert (name != NULL); + size_t hidx = elf_hash (name) % nbucket; + + if (bucket[hidx] == 0) + bucket[hidx] = inner; + else + { + hidx = bucket[hidx]; + + while (chain[hidx] != 0) + hidx = chain[hidx]; + + chain[hidx] = inner; + } + } + } + } + else if (shdr_info[cnt].shdr.sh_type == SHT_GNU_versym) + { + /* If the symbol table changed we have to adjust the + entries. */ + Elf32_Word symtabidx = shdr_info[cnt].old_sh_link; + + /* We do not have to do anything if the symbol table was + not changed. */ + if (shdr_info[symtabidx].newsymidx == NULL) + continue; + + assert (shdr_info[cnt].idx > 0); + + /* The symbol version section in the new file. */ + scn = elf_getscn (newelf, shdr_info[cnt].idx); + + /* The symbol table data. */ + Elf_Data *symd = elf_getdata (elf_getscn (newelf, + shdr_info[symtabidx].idx), + NULL); + assert (symd != NULL); + + /* The version symbol data. */ + Elf_Data *verd = elf_getdata (scn, NULL); + assert (verd != NULL); + + /* The symbol version array. */ + GElf_Half *verstab = (GElf_Half *) verd->d_buf; + + /* New indices of the symbols. */ + Elf32_Word *newsymidx = shdr_info[symtabidx].newsymidx; + + /* Walk through the list and */ + size_t elsize = gelf_fsize (elf, verd->d_type, 1, + ehdr->e_version); + for (size_t inner = 1; inner < verd->d_size / elsize; ++inner) + if (newsymidx[inner] != 0) + /* Overwriting the same array works since the + reordering can only move entries to lower indices + in the array. */ + verstab[newsymidx[inner]] = verstab[inner]; + + /* New size of the section. */ + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + shdr->sh_size = verd->d_size + = gelf_fsize (newelf, verd->d_type, + symd->d_size / gelf_fsize (elf, symd->d_type, 1, + ehdr->e_version), + ehdr->e_version); + (void) gelf_update_shdr (scn, shdr); + } + else if (shdr_info[cnt].shdr.sh_type == SHT_GROUP) + { + /* Check whether the associated symbol table changed. */ + if (shdr_info[shdr_info[cnt].old_sh_link].newsymidx != NULL) + { + /* Yes the symbol table changed. Update the section + header of the section group. */ + scn = elf_getscn (newelf, shdr_info[cnt].idx); + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + assert (shdr != NULL); + + size_t stabidx = shdr_info[cnt].old_sh_link; + shdr->sh_info = shdr_info[stabidx].newsymidx[shdr->sh_info]; + + (void) gelf_update_shdr (scn, shdr); + } + } + } + } + + /* Finally finish the ELF header. Fill in the fields not handled by + libelf from the old file. */ + 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; + /* We need to position the section header table. */ + const size_t offsize = gelf_fsize (elf, ELF_T_OFF, 1, EV_CURRENT); + newehdr->e_shoff = ((shdr_info[shdridx].shdr.sh_offset + + shdr_info[shdridx].shdr.sh_size + offsize - 1) + & ~((GElf_Off) (offsize - 1))); + newehdr->e_shentsize = gelf_fsize (elf, ELF_T_SHDR, 1, EV_CURRENT); + + /* The new section header string table index. */ + if (likely (idx < SHN_HIRESERVE) && likely (idx != SHN_XINDEX)) + newehdr->e_shstrndx = idx; + else + { + /* The index does not fit in the ELF header field. */ + shdr_info[0].scn = elf_getscn (elf, 0); + + if (gelf_getshdr (shdr_info[0].scn, &shdr_info[0].shdr) == NULL) + INTERNAL_ERROR (fname); + + shdr_info[0].shdr.sh_link = idx; + (void) gelf_update_shdr (shdr_info[0].scn, &shdr_info[0].shdr); + + newehdr->e_shstrndx = SHN_XINDEX; + } + + if (gelf_update_ehdr (newelf, newehdr) == 0) + { + error (0, 0, gettext ("%s: error while creating ELF header: %s"), + fname, elf_errmsg (-1)); + return 1; + } + + /* We have everything from the old file. */ + if (elf_cntl (elf, ELF_C_FDDONE) != 0) + { + error (0, 0, gettext ("%s: error while reading the file: %s"), + fname, elf_errmsg (-1)); + return 1; + } + + /* The ELF library better follows our layout when this is not a + relocatable object file. */ + elf_flagelf (newelf, ELF_C_SET, + (ehdr->e_type != ET_REL ? ELF_F_LAYOUT : 0) + | (permissive ? ELF_F_PERMISSIVE : 0)); + + /* Finally write the file. */ + if (elf_update (newelf, ELF_C_WRITE) == -1) + { + error (0, 0, gettext ("while writing '%s': %s"), + fname, elf_errmsg (-1)); + result = 1; + } + + fail_close: + if (shdr_info != NULL) + { + /* For some sections we might have created an table to map symbol + table indices. */ + if (any_symtab_changes) + for (cnt = 1; cnt <= shdridx; ++cnt) + free (shdr_info[cnt].newsymidx); + + /* Free the memory. */ + if ((shnum + 5) * sizeof (struct shdr_info) > MAX_STACK_ALLOC) + free (shdr_info); + } + + /* Free other resources. */ + if (shstrtab_data != NULL) + free (shstrtab_data->d_buf); + if (shst != NULL) + ebl_strtabfree (shst); + + /* That was it. Close the descriptors. */ + if (elf_end (newelf) != 0) + { + error (0, 0, gettext ("error while finishing '%s': %s"), fname, + elf_errmsg (-1)); + result = 1; + } + + fail: + /* If requested, preserve the timestamp. */ + if (tvp != NULL) + { + if (futimes (fd, tvp) != 0) + { + error (0, errno, gettext ("\ +cannot set access and modification date of '%s'"), + output_fname ?: fname); + result = 1; + } + } + + /* Close the file descriptor if we created a new file. */ + if (output_fname != NULL) + close (fd); + + return result; +} + + +static int +handle_ar (int fd, Elf *elf, const char *prefix, const char *fname, + struct timeval 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; + + /* Create the full name of the file. */ + if (prefix != NULL) + { + cp = mempcpy (cp, prefix, prefix_len); + *cp++ = ':'; + } + memcpy (cp, fname, fname_len); + + + /* Process all the files contained in the archive. */ + Elf *subelf; + Elf_Cmd cmd = ELF_C_RDWR; + int result = 0; + while ((subelf = elf_begin (fd, cmd, elf)) != NULL) + { + /* The the header for this element. */ + 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); + + /* Get next archive element. */ + cmd = elf_next (subelf); + if (unlikely (elf_end (subelf) != 0)) + INTERNAL_ERROR (fname); + } + + if (tvp != NULL) + { + if (unlikely (futimes (fd, tvp) != 0)) + { + error (0, errno, gettext ("\ +cannot set access and modification date of '%s'"), fname); + result = 1; + } + } + + if (unlikely (close (fd) != 0)) + error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname); + + return result; +}