Squashed 'third_party/elfutils/' content from commit 555e15e

Change-Id: I61cde98949e47e5c8c09c33260de17f30921be79
git-subtree-dir: third_party/elfutils
git-subtree-split: 555e15ebe8bf1eb33d00747173cfc80cc65648a4
diff --git a/src/ChangeLog b/src/ChangeLog
new file mode 100644
index 0000000..a490705
--- /dev/null
+++ b/src/ChangeLog
@@ -0,0 +1,4058 @@
+2018-02-09  Joshua Watt  <JPEWhacker@gmail.com>
+
+	* addr2line.c (handle_address): Use FALLTHROUGH macro instead of
+	comment.
+	* elfcompress.c (parse_opt): Likewise.
+	* elflint.c (check_dynamic): Likewise.
+	(check_sections): Likewise.
+	(check_note_data): Likewise.
+	* objdump.c (parse_opt): Likewise.
+	* readelf.c (parse_opt): Likewise.
+	(attr_callback): Likewise.
+	(handle_auxv_note): Likewise.
+	* strings.c (parse_opt): Likewise.
+	* backtrace.c (callback_verify): Likewise.
+
+2018-01-25  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (print_debug_ranges_section): Initialize cu to last_cu.
+	(print_debug_loc_section): Likewise.
+
+2018-01-01  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (attr_callback): Use dwarf_form_name for unknown forms.
+	(print_debug_macro_section): Print form using dwarf_form_name.
+
+2017-12-28  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (print_debug_units): Print DIE offset in error message
+	as hex.
+
+2017-12-18  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (handle_notes_data): Don't use EXIT_FAILURE in error.
+	Adjust error message depending on whether or not we got data.
+
+2017-12-07  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (print_ops): Update data pointer and print arguments
+	to DW_OP_call2 and DW_OP_call4 as DIE offsets.
+
+2017-11-29  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (argp_options): Add "section-groups", 'g'.
+
+2017-11-29  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (print_debug_loc_section): Print CU base and unresolved
+	addresses. Adjust formatting.
+
+2017-11-29  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (print_debug_ranges_section): Print CU base and unresolved
+	addresses. Adjust formatting.
+
+2017-11-29  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (attr_callback): Set valuestr to resolved file name
+	for DW_AT_decl_file and DW_AT_call_file.
+
+2017-11-29  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (print_debug_units): Print abbrev code after DIE tag.
+
+2017-11-29  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (print_ops): Use only2 space for index. re-indent +5
+	for DW_OP_GNU_entry_value.
+
+2017-11-21  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (attr_callback): Print attribute name and form in error
+	message. If only the value cannot be retrieved/resolved don't abort.
+
+2017-10-03  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (attr_callback): Print DIE offset in error messages.
+
+2017-11-03  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (print_ops): Handle DW_OP_GNU_variable_value. Print
+	referenced DIE as offset.
+
+2017-09-10  Mark Wielaard  <mark@klomp.org>
+
+	* ar.c (do_oper_delete): Remove DEBUG conditional check.
+	(no0print): Return bool. Check snprintf return value.
+	(do_oper_insert): Initialize elf. Remove DEBUG conditional check.
+	Check no0print calls succeed. Explicitly elf_end found elfs.
+	(do_oper_extract): Make sure we don't create an empty variable
+	length array.
+
+2017-09-01  Mark Wielaard  <mark@klomp.org>
+
+	* stack.c (main): Replace \v in doc string with \n\n.
+	* unstrip.c (main): Likewise.
+
+2017-05-04  Ulf Hermann  <ulf.hermann@qt.io>
+
+	* stack.c: Print pid_t using %lld.
+
+2017-08-18  Ulf Hermann  <ulf.hermann@qt.io>
+
+	* readelf.c: Hardcode the signal numbers for non-linux systems.
+
+2017-07-26  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (print_debug_macro_section): Accept either version 4 or
+	version 5. Use DW_MACRO names instead of DW_MACRO_GNU names. Add
+	minimal support for DW_MACRO_define_sup, DW_MACRO_undef_sup,
+	DW_MACRO_import_sup, DW_MACRO_define_strx and DW_MACRO_undef_strx.
+
+2017-07-26  Mark Wielaard  <mark@klomp.org>
+
+	* readelf.c (dwarf_defaulted_string): New function.
+	(dwarf_defaulted_name): Likewise.
+	(attr_callback): Use dwarf_defaulted_name to get value of
+	DW_AT_defaulted.
+
+2017-07-20  Mark Wielaard  <mark@klomp.org>
+
+	* strip.c (handle_elf): Deal with data marker symbols pointing to
+	debug sections (they can be removed).
+
+2017-07-14  Mark Wielaard  <mark@klomp.org>
+
+	* strip (OPT_KEEP_SECTION): New define.
+	(options): Add documentation for remove-section. Add keep-section.
+	(struct section_pattern): New data type.
+	(keep_secs, remove_secs): New globals.
+	(add_pattern, free_sec_patterns, free_patterns, section_name_matches):
+	New functions.
+	(main): Call free_patterns.
+	(parse_opt): Handle 'R' and OPT_KEEP_SECTION. Check remove_comment
+	on ARGP_KEY_SUCCESS.
+	(handle_elf): Handle and sanity check keep_secs and remove_secs.
+
+2017-06-07  Mark Wielaard  <mark@klomp.org>
+
+	* strip.c (handle_elf): Introduce new handle_elf boolean. Use it to
+	determine whether to create an output and/or debug file. Remove new
+	output file on error.
+
+2017-06-06  Mark Wielaard  <mark@klomp.org>
+
+	* strip.c (handle_elf): Assume e_shstrndx section can be removed.
+
+2017-04-20  Ulf Hermann  <ulf.hermann@qt.io>
+
+	* readelf.c: Include strings.h.
+
+2017-04-20  Ulf Hermann  <ulf.hermann@qt.io>
+
+	* unstrip.c: Check shnum for 0 before subtracting from it.
+
+2017-04-20  Ulf Hermann <ulf.hermann@qt.io>
+
+	* readelf.c: Replace YESSTR and NOSTR with gettext ("yes") and
+	gettext ("no"), respectively.
+
+2017-04-05  Mark Wielaard  <mark@klomp.org>
+
+	* elflint.c (check_elf_header): Decompress all sections.
+
+2017-03-28  Mark Wielaard  <mark@klomp.org>
+
+	* elflint (check_group): Don't check if there is no flag word.
+
+2017-03-27  Mark Wielaard  <mark@klomp.org>
+
+	* elflint.c (check_elf_header): Sanity check phnum and shnum.
+
+2017-03-27  Mark Wielaard  <mark@klomp.org>
+
+	* elflint.c (check_sysv_hash): Return early if section size is
+	too small.
+	(check_sysv_hash64): Likewise.
+	(check_hash): Calculate expect_entsize to check section size.
+
+2017-03-27  Mark Wielaard  <mark@klomp.org>
+
+	* elflint.c (check_symtab_shndx): Check data->d_size.
+
+2017-03-24  Mark Wielaard  <mjw@redhat.com>
+
+	* elfcmp.c (main): If n_namesz == 0 then the note name data is the
+	empty string.
+	* readelf.c (handle_notes_data): Likewise.
+
+2017-03-24  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_gnu_hash): Check inner < max_nsyms before
+	indexing into chain array.
+
+2017-02-16  Ulf Hermann  <ulf.hermann@qt.io>
+
+	* addr2line.c: Include printversion.h
+	* ar.c: Likewise.
+	* elflint.c: Likewise.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* stack.c: Likewise.
+	* strings.c: Likewise.
+	* strip.c: Likewise.
+	* elfcmp.c: Include printversion.h, remove system.h include.
+	* elfcompress.c: Likewise.
+	* findtextrel.c: Likewise.
+	* unstrip.c: Likewise.
+
+2017-02-14  Ulf Hermann  <ulf.hermann@qt.io>
+
+	* nm.c: Include color.h.
+	* objdump.c: Likewise.
+
+2016-12-24  Mark Wielaard  <mark@klomp.org>
+
+	* Makefile.am (findtextrel_LDADD): Add $(libeu).
+	(addr2line_LDADD): Likewise.
+	(elfcmp_LDADD): Likewise.
+	* addr2line.c (print_version): Removed.
+	* ar.c (print_version): Likewise.
+	* elfcmp.c (print_version): Likewise.
+	* elfcompress.c (print_version): Likewise.
+	* elflint.c (print_version): Likewise.
+	* findtextrel.c (print_version): Likewise.
+	* nm.c (print_version): Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* stack.c: Likewise.
+	* strings.c: Likewise.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise.
+
+2016-11-17  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (options): Add optional arg SECTION for symbols.
+	(symbol_table_section): New static variable.
+	(parse_opt): Set symbol_table_section on 's'.
+	(print_symtab): Filter on symbol_table_section name is set.
+
+2016-11-10  Mark Wielaard  <mjw@redhat.com>
+
+	* ar.c (write_member): Make sure tmpbuf is large enough to contain
+	a starting '/' and ending '\0' char.
+	(do_oper_insert): Likewise.
+	* arlib.c (arlib_finalize): Format tmpbuf as PRId32 decimal.
+
+2016-11-02  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (handle_address): Add fallthrough comment.
+	* elfcompress.c (parse_opt): Adjust fallthrough comment.
+	* elflint.c (parse_opt): Add missing break after 'd' case.
+	(check_sections): Add fallthrough comments.
+	* objdump.c (parse_opt): Add explantion for fallthrough comment.
+
+2016-10-22  Kevin Cernekee  <cernekee@chromium.org>
+
+	* unstrip.c: Fix "invalid string offset" error caused by using the
+	  unstripped .symtab with the stripped .strtab.
+
+2016-10-11  Akihiko Odaki  <akihiko.odaki.4i@stu.hosei.ac.jp>
+
+	* arlib.c: Remove system.h include, add libeu.h include.
+	* arlib2.c: Remove sys/param.h include.
+	* elfcompress.c: Add libeu.h include.
+	* elflint.c: Remove sys/param.h include, add libeu.h include.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* readelf.c: Likewise.
+	* size.c: Remove sys/param.h include.
+	* strings.c: Likewise, add libeu.h include.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise.
+
+2016-10-06  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (handle_elf): Don't remove real symbols from allocated
+	symbol tables.
+
+2016-08-25  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (handle_elf): Recompress with ELF_CHF_FORCE.
+
+2016-08-06  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (handle_elf): Uncompress and recompress relocation target
+	section if necessary.
+
+2016-07-08  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am (strip_LDADD): Add libdw.
+	* elfcompress.c (process_file): Use dwelf_strtab functions instead of
+	ebl_strtab.
+	* strip.c (handle_elf): Likewise.
+	* unstrip.c (new_shstrtab): Likewise.
+
+2016-07-06  Mark Wielaard  <mjw@redhat.com>
+
+	* elf32-i386.script, i386_ld.c, ld.c, ld.h, ldgeneric.c, ldlex.l,
+	ldscript.y, libld_elf_i386.map, none_ld.c, sectionhash.c,
+	sectionhash.h, symbolhash.c, symbolhash.h, unaligned.h,
+	versionhash.c, versionhash.h, xelf.h: Removed.
+	* Makefile.am (YACC): Removed.
+	(AM_YFLAGS): Removed.
+	(AM_LFLAGS): Removed.
+	(native_ld): Removed.
+	(base_cpu): Removed.
+	(bin_PROGRAMS): Removed ld.
+	(ld_dsos): Removed.
+	(ld_SOURCES): Removed.
+	(noinst_LIBRARIES): Only libar.a.
+	(EXTRA_DIST): Just arlib.h and debugpred.h.
+	(ld_LDADD): Removed.
+	(ld_LDFLAGS): Removed.
+	(ldlex.o): Removed.
+	(ldscript.h): Removed.
+	(libld*): Removed.
+	(CLEANFILES): Just *.gconv.
+	(MAINTAINERCLEANFILES): Removed.
+
+2016-07-06  Mark Wielaard  <mjw@redhat.com>
+
+	* unstrip.c (copy_elided_sections): Use unstripped_strent[] from
+	index zero, instead of one.
+
+2016-06-28  Richard Henderson <rth@redhat.com>
+
+	* elflint.c (valid_e_machine): Add EM_BPF.
+
+2016-04-11  David Abdurachmanov  <davidlt@cern.ch>
+
+	* elfcmp.c (main): Fix self-comparison error with GCC 6.
+
+2016-03-21  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (show_symbols): Check for malloc size argument overflow.
+
+2016-02-13  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_scngrp): Call error when gelf_getshdr fails.
+	(print_symtab): Likewise.
+	(handle_hash): Likewise.
+	(dump_data_section): Print a warning if decompressing fails.
+	(print_string_section): Likewise.
+
+2016-02-13  Mark Wielaard  <mjw@redhat.com>
+
+	* elfcompress.c (parse_opt): Don't fallthrough after processing -q.
+
+2016-02-12  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (handle_elf): Correct elf_assert shndxdata check.
+
+2016-02-09  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (read_encoded): Move up.
+	(print_cfa_program): Add encoding argument. Use it for read_encoded
+	when reading DW_CFA_set_loc op.
+	(print_debug_frame_section): Pass fde_encoding to print_cfa_program.
+
+2016-02-09  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (compare_hash_gnu_hash): Check hash sh_entsize against
+	sizeof (Elf64_Xword). Correct invalid sh_entsize error message
+	section idx and name.
+
+2016-01-13  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_elf_header): Recognize ELFOSABI_FREEBSD.
+
+2016-01-08  Mark Wielaard  <mjw@redhat.com>
+
+	* elfcompress.c (compress_section): Use %zu to print size_t.
+	* readelf.c (print_shdr): Use %zx to print size_t.
+
+2015-12-16  Mark Wielaard  <mjw@redhat.com>
+
+	* elfcompress.c: New file.
+	* Makefile.am (bin_PROGRAMS): Add elfcompress.
+	(elfcompress_LDADD): New variable.
+
+2015-12-18  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (section_flags_string): Add NEWFLAG COMPRESSED.
+	(check_sections): SHF_COMPRESSED can be on any special section.
+	SHF_COMPRESSED is a valid section flag. SHF_COMPRESSED cannot
+	be used together with SHF_ALLOC or with SHT_NOBITS. Should have
+	a valid Chdr.
+
+2015-10-20  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (options): Expand -z help text.
+	(dump_data_section): Check whether we need and can decompress section
+	data and call elf_rawzdata if so,
+	(print_string_section): Likewise.
+	(elf_contains_chdrs): New function.
+	(process_elf_file): Rename print_unrelocated to print_unchanged,
+	use elf_contains_chdrs.
+	(print_scngrp): Check whether section is compressed before use.
+	(print_symtab): Likewise.
+	(handle_hash): Likewise.
+
+2015-10-16  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (argp_option): Describe --decompress,-z.
+	(print_decompress): New bool.
+	(parse_opt): Handle -z.
+	(elf_ch_type_name): New function.
+	(print_shdr): Print section compress information.
+
+2015-12-31  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_symtab): Add _edata and _end (plus extra underscore
+	variants) to to the list of possibly dangling symbols.
+
+2015-12-02  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (process_file): Accept fd and pass it on.
+	(handle_elf): Likewise.
+	(find_no_debuginfo): New.
+	(struct getdbg): Likewise.
+	(getdbg_dwflmod): Likewise.
+	(show_symbols): Take fd. If the file is ET_REL use libdwfl to get
+	the relocated Dwarf.
+
+2015-12-02  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (get_local_names): Check for duplicates in local_root tree.
+
+2015-12-02  Mark Wielaard  <mjw@redhat.com>
+
+	* unstrip.c (struct data_list): New.
+	(new_data_list): Likewise.
+	(record_new_data): Likewise.
+	(free_new_data): Likewise.
+	(adjust_relocs): Call record_new_data.
+	(add_new_section_symbols): Likewise.
+	(copy_elided_sections): Call free_new_data.
+
+2015-12-01  Mark Wielaard  <mjw@redhat.com>
+
+	* elfcmp.c (main): Close ebl1 and ebl2 backends.
+
+2015-10-16  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am [BUILD_STATIC](libdw): Add -lz.
+	[BUILD_STATIC](libelf): Likewise.
+
+2015-10-16  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_symtab): Don't check TLS symbol value against TLS
+	phdr offset in debuginfo files.
+	(check_sections): Don't try to match section offsets to phdrs offsets
+	in debuginfo files.
+
+2015-10-16  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_reloc_shdr): Reject only desthdrs if they have both
+	SHF_MERGE and SHF_STRINGS set.
+
+2015-10-13  Jose E. Marchesi  <jose.marchesi@oracle.com>
+
+	* elflint.c (check_sections): Do not rely on
+	ebl_check_special_section when checking debuginfo files.  Also
+	check that the type of WE sections in debuginfo files is NOBITS.
+
+2015-10-13  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_program_header): Check relro flags are a subset
+	of the load segment if they don't fully overlap.
+
+2015-10-07  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am (ldlex_no_Wstack_usage): New.
+	* ldlex.l ([RWX]): Make cnt unsigned.
+
+2015-10-09  Josh Stone  <jistone@redhat.com>
+
+	* elflint.c (main): Replace stat64 and fstat64 with stat and fstat.
+	* readelf.c (process_file): Likewise.
+	(process_elf_file): Replace off64_t with off_t.
+	* findtextrel.c (process_file): Replace open64 with open.
+	* ld.c (main): Replace sizeof (off64_t) with 8.
+	* strings.c: Replace off64_t with off_t throughout.
+	(main): Replace stat64 and fstat64 with stat and fstat.
+	(map_file): Replace mmap64 with mmap.
+	(read_block): Likewise, and replace lseek64 with lseek.
+	* strip.c (handle_elf): Replace ftruncate64 with ftruncate.
+	(process_file): Replace stat64 and fstat64 with stat and fstat.
+	* unstrip.c (parse_opt): Replace stat64 with stat.
+	(handle_file): Replace open64 with open.
+	(open_file): Likewise.
+
+2015-10-08  Chih-Hung Hsieh  <chh@google.com>
+
+	* ld.c (determine_output_format): Move recursive nested
+	function "try" to file scope.
+
+2015-10-04  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (handle_elf): Only sanity check section symbols to not
+	kept discarded sections if we are creating a debug file.
+
+2015-10-07  Mark Wielaard  <mjw@redhat.com>
+
+	* unstrip.c (MAX): Removed.
+	(find_alloc_sections_prelink): Allocate exact amount of bytes for
+	shdrs.
+
+2015-10-05  Chih-Hung Hsieh <chh@google.com>
+
+	* unstrip.c (find_alloc_sections_prelink): Do not allocate
+	on stack union types with variable length arrays; use xmalloc
+	for such dynamic sized objects.
+	* readelf.c (handle_core_item): Likewise, but use alloca
+	for small variable size data object and add assert(count < 128).
+
+2015-10-05  Josh Stone  <jistone@redhat.com>
+
+	* Makefile.am (libld_elf_i386.so): Add AM_V_CCLD silencer.
+	(.deps/none_ld.Po): Always silence the dummy command.
+	(make-debug-archive): Add AM_V_GEN and AM_V_at silencers.
+
+2015-10-02  Mark Wielaard  <mjw@redhat.com>
+
+	* unstrip.c (copy_elided_sections): Use SH_INFO_LINK_P, not just
+	SHF_INFO_LINK.
+
+2015-10-02  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (handle_elf): Don't move around allocated NOBITS sections.
+	Don't just mark the section header string table as unused.
+	* unstrip.c (copy_elided_sections): Add header names to strtab if
+	shstrndx equals the symtab strtabndx.
+
+2015-09-22  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (cleanup_debug): Remove old-style function definitions.
+
+2015-09-09  Chih-Hung Hsieh  <chh@google.com>
+
+	* readelf.c (print_debug_exception_table): Initialize variable before
+	it is used, because compiler does not know that error never returns.
+	(dump_arhive_index): Likewise.
+
+2015-09-04  Chih-Hung Hsieh  <chh@google.com>
+
+	* elflint.c (check_group): Replace %Z length modifier with %z.
+	(check_note_data): Likewise.
+	* findtextrel.c (process_file): Likewise.
+	* readelf.c (handle_dynamic): Likewise.
+	(handle_symtab): Likewise.
+	(handle_verneed): Likewise.
+	(handle_verdef): Likewise.
+	(handle_versym): Likewise.
+	(print_hash_info): Likewise.
+	(print_decoded_aranges_section): Likewise.
+	(print_debug_aranges_section): Likewise.
+	(print_debug_line_section): Likewise.
+	(hex_dump): Likewise.
+	(dump_data_section): Likewise.
+	(print_string_section): Likewise.
+	(dump_archive_index): Likewise.
+	* unstrip.c (adjust_relocs): Likewise.
+	(collect_symbols): likewise.
+	(get_section_name): Likewise.
+	(find_alloc_sections_prelink): Likewise.
+	(copy_elided_sections): Likewise.
+	* ldscript.y (add_id_list): Add missing '%s'.
+
+2015-09-03  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_core_item): Handle right shift >= 32 bits.
+
+2015-08-11  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_sections): When gnuld and a NOBITS section falls
+	inside a segment make sure any ELF file contents is zero.
+
+2015-07-29  Mark Wielaard  <mjw@redhat.com>
+
+	* unstrip.c (sections_flags_match): New function.
+	(sections_match): Use sections_flags_match.
+	(find_alloc_sections_prelink): Only clear matched sections if there
+	is an undo section.
+	(copy_elided_sections): Add SHF_INFO_LINK to shdr_mem.sh_flags if
+	necessary.
+
+2015-06-27  Pino Toscano  <toscano.pino@tiscali.it>
+
+	* src/strings.c: Define MAP_POPULATE if not defined already.
+
+2015-06-27  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (show_symbols): First call elf_getdata, then allocate memory.
+
+2015-06-18  Mark Wielaard  <mjw@redhat.com>
+
+	* findtextrel.c (process_file): Free segments after use.
+
+2015-06-18  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_phdr): Make sure phdr2_mem lifetime/scope equals
+	phdr2 pointer.
+
+2015-06-18  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_gnu_hash): Free lengths on invalid_data.
+
+2015-06-18  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_symtab): Only check the PT_TLS phdr if it actually
+	exists. Warn otherwise.
+
+2015-06-18  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (show_symbols): Check sizeof (sym_mem[0]), not GElf_Sym to
+	known whether or not we stack allocated memory.
+
+2015-06-18  Mark Wielaard  <mjw@redhat.com>
+
+	* strings.c (readelf): Use "<unknown>" if we cannot retrieve section
+	name.
+
+2015-06-09  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (print_dwarf_function): Always free scopes before
+	returning.
+
+2015-06-09  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (handle_ar): Mark as unused.
+	(process_file): Produce an error when trying to handle an ar.
+
+2015-05-30  Mark Wielaard  <mjw@redhat.com>
+
+	* elfcmp.c (main): Only call memcmp when d_size != 0.
+
+2015-05-23  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am: Define ldgeneric, readelf, nm, size, strip, elflint,
+	findtextrel, elfcmp objdump, ranlib, ar and unstrip no_Wstack_usage.
+
+2015-05-21  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (handle_address): Set scopes to NULL after free.
+
+2015-05-20  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (OPT_PRETTY): New constant define.
+	(argp_option): Add "pretty-print".
+	(pretty): New static bool.
+	(parse_opt): Set pretty.
+	(print_dwarf_function): Adjust output when pretty is set.
+	(print_addrsym): Likewise.
+	(handle_address): Likewise.
+
+2015-05-20  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am (addr2line_LDADD): Add demanglelib.
+	* addr2line.c (argp_option): Move demangle under output format.
+	(demangle): New static bool.
+	(demangle_buffer_len): New static size_t.
+	(demangle_buffer): New static char *.
+	(main): free demangle_buffer.
+	(parse_opt): Set demangle.
+	(symname): New static function.
+	(get_diename): Use symname.
+	(print_dwarf_function): Likewise.
+	(print_addrsym): Likewise.
+	(handle_address): Likewise.
+
+2015-05-20  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (argp_option): Add "addresses", 'a'.
+	(print_addresses): New static bool.
+	(parse_opt): Set print_addresses.
+	(get_addr_width): New static function.
+	(handle_address): Print address if print_addresses is true.
+
+2015-05-20  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (handle_address): Call strtoumax with base 16. Make
+	sure all input has been processed.
+
+2015-05-20  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line (argp_option): Group 'section' under "Input format
+	options".
+
+2015-05-12  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (debug_fd): New static variable.
+	(tmp_debug_fname): Likewise.
+	(cleanup_debug): New function using the above to clean up temporary
+	debug file.
+	(INTERNAL_ERROR): Call cleanup_debug.
+	(handle_elf): Use debug_fd and tmp_debug_fname statics and call
+	cleanup_debug before any error. Use output_fname instead of fname in
+	error messages when it exists (-o was given). SHT_NOBITS sections
+	can also be moved freely even if SHF_ALLOC is set. Add various
+	sanity checks. Use elf_assert instead of plain assert.
+
+2015-05-08  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (show_symbols): Call gelf_fsize with EV_CURRENT.
+	* strip.c (handle_elf): Likewise.
+
+2015-05-06  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_gnu_hash): Return early when 2nd hash function
+	shift too big. Check there is enough data available. Make sure
+	bitmask_words is not zero.
+	(check_verdef): Use Elf64_Word for shdr->sh_info cnt.
+	(check_verneed): Likewise.
+	(check_attributes): Break when vendor name isn't terminated.
+	Add overflow check for subsection_len.
+
+2015-05-05  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (show_symbols): Handle dwarf_linesrc returning NULL.
+
+2015-05-04  Max Filippov  <jcmvbkbc@gmail.com>
+
+	* ar.c (do_oper_extract): Replace struct timeval with struct timespec
+	and futimes with futimens.
+	* strip.c (process_file): Replace struct timeval with struct timespec.
+	(handle_elf, handle_ar): Replace struct timeval with struct timespec
+	in prototype. Replace futimes with futimens.
+
+2015-05-04  Max Filippov  <jcmvbkbc@gmail.com>
+
+	* addr2line.c (main): Drop mtrace() call and #include <mcheck.h>.
+	* ar.c: Likewise.
+	* ld.c: Likewise.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* size.c: Likewise.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise.
+
+2015-05-04  Anthony G. Basile  <blueness@gentoo.org>
+
+	* Makefile.am (readelf_LDADD, nm_LDADD, size_LDADD, strip_LDADD)
+	(ld_LDADD, elflint_LDADD, findtextrel_LDADD, addr2line_LDADD)
+	(elfcmp_LDADD, objdump_LDADD, ranlib_LDADD, strings_LDADD)
+	(ar_LDADD, unstrip_LDADD, stack_LDADD): Append $(argp_LDADD).
+
+2015-03-22  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_frame_section): Cast start to Dwarf_Off
+	before subtracting cie_id. And cast cie_offset to Dwarf_Off before
+	comparison.
+
+2015-03-22  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_gdb_index_section): Check all offsets used
+	against section d_size.
+
+2015-03-17  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug): Don't return, but always use dummy_dbg.
+
+2015-03-17  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_gdb_index_section): Add overflow checking for
+	dataend checks.
+
+2015-03-14  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (INTERNAL_ERROR): Remove __DATE__.
+	* objdump.c (INTERNAL_ERROR): Likewise.
+	* size.c (INTERNAL_ERROR): Likewise.
+	* strip.c (INTERNAL_ERROR): Likewise.
+
+2015-03-18  Petr Machata  <pmachata@redhat.com>
+
+	* readelf.c (dwarf_tag_string, dwarf_attr_string)
+	(dwarf_form_string, dwarf_lang_string, dwarf_inline_string)
+	(dwarf_encoding_string, dwarf_access_string)
+	(dwarf_visibility_string, dwarf_virtuality_string)
+	(dwarf_identifier_case_string, dwarf_calling_convention_string)
+	(dwarf_ordering_string, dwarf_discr_list_string)
+	(dwarf_locexpr_opcode_string): Adjust uses of know-dwarf.h macros
+	to match the API changes.
+
+2015-03-09  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (compare_hash_gnu_hash): Correct gnu_symbias usage.
+
+2015-01-03  Mark Wielaard  <mjw@redhat.com>
+
+	* elfcmp (main): Check section name is not NULL. Check sh_entsize
+	is not zero for symbol tables. Check phdrs are not NULL.
+	(search_for_copy_reloc): Check sh_entsize is not zero.
+
+2014-12-30  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_scn_group): Check d_buf and name are not NULL.
+	(is_rel_dyn): Check d is not NULL. Check shdr->sh_entsize is not
+	zero.
+	(check_dynamic): Check strshdr is not NULL. Check d_tag is positive.
+	(check_symtab_shndx): Check symshdr and data->d_buf are not NULL.
+	Check shdr and symshdr sh_entsize are not zero.
+	(check_gnu_hash): Make sure maskidx is smaller than bitmask_words.
+	Check symshdr->sh_entsize is not zero. Check data->d_buf is not
+	NULL.
+	(compare_hash_gnu_hash): Check sections d_buf are not NULL.
+	Check section data is large enough. Use gnu_symbias.
+	(check_group): Check section val is valid.
+	(has_copy_reloc): Check sh_entsize is not zero.
+	(check_versym): Likewise.
+	(unknown_dependency_p): Likewise.
+	(check_verneed): Break on invalid ref or offset. Don't assert.
+	Report error when next offset is zero, but more vers expected.
+	(check_verdef): Likewise.
+	(check_attributes): Make sure d_buf is not NULL.
+	(check_note): Likewise.
+	(check_note_section): Likewise.
+	(check_program_header): Make sure section name is not NULL.
+
+2014-12-26  Mark Wielaard  <mjw@redhat.com>
+
+	* strings.c (read_elf): Produce error when section data falls outside
+	file.
+
+2014-12-26  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (show_symbols): Guard against divide by zero in error check.
+	Add section index number in error message.
+
+2014-12-26  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (handle_ar): Skip over /SYM64/ entries.
+
+2014-12-26  Mark Wielaard  <mjw@redhat.com>
+
+	* nm.c (handle_ar): Break on arsym with invalid offset.
+
+2014-12-20  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_macinfo_section): Mark cus sentinel files
+	as -1 non-existent. Check macoff against sentinel cus.
+
+2014-12-20  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_exception_table): Add max_action overflow
+	check. Check action_table_end before reading slib128. Check
+	max_ar_filter underflow.
+
+2014-12-18  Ulrich Drepper  <drepper@gmail.com>
+
+	* Makefile.am: Suppress output of textrel_check command.
+
+2014-12-17  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_cfa_program): Add bounds check before each op that
+	takes at least one argument.
+
+2014-12-16  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_decoded_line_section): Print dwarf_errmsg if
+	dwarf_onesrcline or dwarf_linesrc fails.
+
+2014-12-16  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_line_section): Correct overflow check for
+	unit_length.
+	(print_debug_aranges_section): Correct overflow check for length.
+
+2014-12-15  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (notice_listptr): Return false if offset doesn't fit
+	in 61-bits.
+	(attr_callback): Warn if loclist or rangelist offset doesn't fit.
+
+2014-12-15  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_ops): Don't assert when addr_size or ref_size
+	is not 4 or 8, just report invalid data.
+
+2014-12-15  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_gdb_index_section): Add more bounds checks.
+
+2014-12-15  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_line_section): Check there is enough room
+	for DW_LNE_set_address argument. Make sure there is enough room
+	for the the initial unit_length.
+
+2014-12-14  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_attributes): Call get_uleb128 with end pointer.
+	* readelf.c (print_attributes): Likewise.
+	(print_ops): Likewise and also for get_sleb128.
+	(print_cfa_program): Likewise and add more readp bounds checks.
+	(read_encoded): Likewise.
+	(print_debug_frame_section): Likewise.
+	(print_debug_line_section): Likewise.
+	(print_debug_macinfo_section): Likewise.
+	(print_debug_macro_section): Likewise.
+	(print_debug_exception_table): Likewise.
+
+2014-12-16  Mark Wielaard  <mjw@redhat.com>
+
+	* elfcmp.c (compare_Elf32_Word): Make sure (unsigned) Elf32_Word
+	difference doesn't wrap around before returning as int.
+
+2014-12-11  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_exception_table): Check TType base offset
+	and Action table are sane.
+
+2014-12-11  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_frame_section): Check number of augmentation
+	chars to print.
+
+2014-12-09  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_file_note): Check count fits data section and
+	doesn't overflow fptr.
+
+2014-12-08  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_exception_table): Report invalid data if
+	action table doesn't immediately follow call site table.
+
+2014-12-10  Josh Stone  <jistone@redhat.com>
+
+	* addr2line.c (get_diename): New, get linkage_name or name.
+	* addr2line.c (print_dwarf_function): Use get_diename.
+	* addr2line.c (handle_address): Likewise.
+	* addr2line.c (print_diesym): Removed.
+
+2014-12-10  Josh Stone  <jistone@redhat.com>
+
+	* addr2line.c (handle_address): Find the proper inline parents.
+
+2014-12-07  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_line_section): max_ops_per_instr cannot
+	be zero.
+
+2014-12-07  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_ops): Handle zero ref_size for DW_OP_call_ref
+	and DW_OP_GNU_implicit_pointer.
+
+2014-12-04  Mark Wielaard  <mjw@redhat.com>
+
+	* objdump.c (show_relocs_x): Make sure destshdr exists.
+	(show_relocs_rel): Don't rely on shdr->sh_entsize, use gelf_fsize.
+	(show_relocs_rela): Likewise.
+	(show_relocs): Make sure destshdr, symshdr and symdata exists.
+
+2014-11-30  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_sysv_hash64): Fix overflow check.
+
+2014-11-28  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_relocs_rel): Don't reuse destshdr to store
+	section header of a relocation against a STT_SECTION symbol. Use
+	a new local variable secshdr.
+	(handle_relocs_rela): Likewise.
+
+2014-11-26  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_aranges_section): Cast Dwarf_Word length
+	to ptrdiff_t for comparison.
+
+2014-11-24  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_line_section): Check line_range is not zero
+	before usage.
+
+2014-11-23  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_aranges_section): Check length to catch
+	nexthdr overflow.
+
+2014-11-21  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_attributes): Guard against empty section.
+	Document attribute format. Break when vendor name isn't terminated.
+	Add overflow check for subsection_len. Handle both gnu and non-gnu
+	attribute tags.
+
+2014-11-22  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_sections): Call ebl_bss_plt_p without ehdr.
+	* findtextrel.c (process_file): Use elf_getphdrnum.
+	* readelf.c (process_elf_file): Remove redundant ehdr->e_phoff check.
+	(print_phdr): Check phnum.
+	* size.c (show_segments): Use elf_getphdrnum.
+	* strip.c (handle_elf): Likewise.
+	* unstrip.c (copy_elf): Likewise.
+	(copy_elided_sections): Likewise.
+	(handle_file): Likewise.
+
+2014-11-18  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_cfa_program): Fix sanity check of DW_FORM_block
+	length.
+
+2014-11-17  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_verneed): Check vna_next and vn_next exist.
+	(handle_verdef): Check vda_next and vd_next exist.
+	(handle_versym): Check vd_next, vna_next and vn_next exist.
+	Check vername and filename are not NULL before use.
+
+2014-11-17  Mark Wielaard  <mjw@redhat.com>
+
+	* elfcmp.c (main): Check section names are NULL before use.
+	* objdump.c (section_match): Likewise.
+	* size.c (show_sysv): Likewise.
+
+2014-11-17  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_frame_section): Warn if ptr_size is not 4
+	or 8 instead of just calling print_cfa_program.
+
+2014-11-16  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf (process_elf_file): Set phnum to zero if there aren't
+	actually any pheaders.
+	(print_phdr): Check there actually is a phdr.
+
+2014-11-16  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_cfa_program): Check block len before calling
+	print_ops.
+
+2014-11-14  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_frame_section): Sanity Check CIE
+	unit_length and augmentationlen.
+
+2014-11-14  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_versym): Check def == NULL before use.
+
+2014-11-08  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_versym): Initialize vername and filename array
+	elements.
+
+2014-11-07  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_sysv_hash): Sanity check section contents.
+	(handle_sysv_hash64): Likewise.
+	(handle_gnu_hash): Likewise.
+
+2014-09-14  Petr Machata  <pmachata@redhat.com>
+
+	* readelf.c (handle_relocs_rela): Typo fix, test DESTSHDR properly.
+
+2014-09-12  Petr Machata  <pmachata@redhat.com>
+
+	* readelf.c (encoded_ptr_size): In the switch statement, change
+	magic constants 3 and 4 to DW_EH_PE_* counterparts.  Still accept
+	0.  Print diagnostic for anything else.
+
+2014-08-25  Josh Stone  <jistone@redhat.com>
+
+	* Makefile.am: Prevent premature @AR@ replacement in a sed expression.
+
+2014-07-04  Menanteau Guy  <menantea@linux.vnet.ibm.com>
+	    Mark Wielaard  <mjw@redhat.com>
+
+	* elflint (check_symtab): Add ".TOC." to the list of possibly
+	dangling symbols because of sourceware PR13621.
+
+2014-06-14  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint (check_symtab): Use ebl_func_addr_mask on st_value.
+
+2014-05-27  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug): Skip section if name is NULL.
+
+2014-05-26  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_relocs_rela): Print header like handle_relocs_rel
+	does, when sh_info == 0.
+
+2014-05-26  Mark Wielaard  <mjw@redhat.com>
+
+	* unstrip.c (find_alloc_sections_prelink): Allow non-split .bss
+	section when sh_size of the original and undo .bss section are equal.
+
+2014-05-26  Mark Wielaard  <mjw@redhat.com>
+
+	* unstrip.c (options): Add --force, -F.
+	(struct arg_info): Add bool force.
+	(parse_opt): Handle 'F', set force.
+	(handle_explicit_files): Add force argument, add warn function,
+	separate check ehdr field checks, use warn.
+	(handle_dwfl_module): Add force argument, pass on to
+	handle_explicit_files.
+	(handle_output_dir_module): Add force argument, pass on to
+	handle_dwfl_module.
+	(handle_implicit_modules): Pass info->force to handle_dwfl_module and
+	handle_output_dir_module.
+	(main): Pass info.force to handle_explicit_files.
+
+2014-05-19  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_reloc_shdr): Check ebl_check_reloc_target_type.
+
+2014-05-01  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (find_no_debuginfo): Call dwfl_standard_find_debuginfo
+	if looking for alternate debug file.
+
+2014-04-11  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am (AM_CPPFLAGS): Add -I libdwelf.
+
+2014-04-22  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_core_item): Make sure variable length array
+	contains at least enough space for terminating zero char.
+
+2014-04-22  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_gdb_index_section): Use unsigned int for 31 bits
+	left shift.
+
+2014-03-13  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am: Remove no_mudflap.os. Remove libmudflap from all
+	LDADD lines.
+	* strings.c (process_chunk): Remove _MUDFLAP condition.
+
+2014-04-09  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_aranges_section): Don't get the raw section
+	data, use the possibly decompressed .[z]debug sectiondata.
+	(print_debug_ranges_section): Likewise.
+	(print_debug_frame_section): Likewise.
+	(print_debug_line_section): Likewise.
+	(print_debug_loc_section): Likewise.
+	(print_debug_macinfo_section): Likewise.
+	(print_debug_macro_section): Likewise.
+
+2014-04-10  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (buf_read_ulong): Pass actual long size to convert.
+
+2014-03-05  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (attr_callback): Print DW_FORM_sdata values as signed
+	numbers.
+
+2014-02-24  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf (print_phdr): Check there is a SHT_PROGBITS section at the
+	offset given by p_offsets for a PT_INTERP segment before trying to
+	display the interpreter string.
+
+2014-02-07  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_phdr): Check phdr->p_filesz and make sure
+	interpreter string is zero terminated before calling printf.
+
+2014-01-22  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am (nm_no_Wformat): Removed.
+	(size_no_Wformat): Likewise.
+	(strings_no_Wformat): Likewise.
+	(addr2line_no_Wformat): Likewise.
+	* size.c (show_sysv): Use fmtstr directly as literal in printf.
+	(show_sysv_one_line): Likewise.
+	* strings.c (locfmt): Removed.
+	(radix): New static enum.
+	(parse_opt): Set radix, not locfmt.
+	(process_chunk_mb): Use fmtstr directly as literal in printf based
+	on radix.
+	(process_chunk): Likewise.
+	* nm.c (show_symbols_sysv): Use fmtstr directly as literal in printf.
+	(show_symbols_bsd): Likewise.
+	(show_symbols_posix): Likewise.
+
+2014-01-21  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (show_inlines): New static boolean.
+	(print_frame): New function split out from...
+	(print_frames): ..here. If show_inlines is true and we found a
+	DIE for the frame address, call print_inline_frames otherwise
+	call print_frame. Keep track of and track frame_nr.
+	(print_inline_frames): New function.
+	(parse_opt): Handle '-i'.
+	(main): Add 'i' to options.
+
+2014-01-27  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (maxframes): Initialize to 256.
+	(main): Document new default in options. Document magic number
+	used in frames.allocated initialization.
+
+2014-01-20  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (show_debugname): New static boolean.
+	(die_name): New function.
+	(print_frames): If show_debugname is true set symname to the
+	first function-like DIE with a name in scope for the address in
+	the debuginfo.
+	(parse_opt): Handle '-d'.
+	(main): Add 'd' to options.
+
+2014-01-20  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (handle_address): Initialize scopes to NULL.
+
+2014-01-17  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (handle_elf): Check for bogus values in sh_link, sh_info,
+	st_shndx, e_shstrndx, and SHT_GROUP or SHT_SYMTAB_SHNDX data.
+	Don't use assert on input values, instead bail with "illformed" error.
+
+2014-01-17  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (handle_dynamic, handle_symtab): Check for bogus sh_link.
+	(handle_verneed, handle_verdef, handle_versym, handle_hash): Likewise.
+	(handle_scngrp): Check for bogus sh_info.
+
+2014-01-17  Jakub Jelinek  <jakub@redhat.com>
+
+	* elflint.c (section_name): Return "<invalid>" instead of
+	crashing on invalid section name.
+	(check_symtab, is_rel_dyn, check_rela, check_rel, check_dynamic,
+	check_symtab_shndx, check_hash, check_versym): Robustify.
+	(check_hash): Don't check entries beyond end of section.
+	(check_note): Don't crash if gelf_rawchunk fails.
+
+2014-01-17  Petr Machata  <pmachata@redhat.com>
+
+	* readelf.c (handle_dynamic, handle_relocs_rel)
+	(handle_relocs_rela, handle_versym, print_liblist):
+	Use gelf_fsize instead of relying on shdr->sh_entsize.
+
+2014-01-14  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_macro_section): Clear vendor array before
+	use.
+
+2014-01-15  Jan Kratochvil  <jan.kratochvil@redhat.com>
+
+	Fix corruption of non-C++ symbols by the demangler.
+	* nm.c (show_symbols_sysv, show_symbols_bsd, show_symbols_posix)
+	(show_symbols): Check for _Z.
+	* stack.c (print_frames) <USE_DEMANGLE>: Check for _Z.
+
+2014-01-02  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (show_raw): Declare unconditionally.
+	(parse_opt): Handle '-r' unconditionally.
+	(main): Show "raw" option even without USE_DEMANGLE.
+
+2014-01-02  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (print_frames): Print 0x before build-id hex-offset.
+
+2014-01-02  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (maxframes): Increase to 2048.
+	(struct frames): Add allocated field.
+	(frame_callback): If frames used is frames allocated, realloc.
+	(print_frames): Show an error if maxframes has been reached.
+	(parse_opt): Allow -n 0 for unlimited frames.
+	(main): Document -n 0 and new default 2048 frames. Allocate initial
+	number of frames with malloc.
+
+2013-12-30  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (parse_opt): Explicitly call dwfl_linux_proc_attach
+	or dwfl_core_file_attach and check for errors.
+
+2013-12-28  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (print_frames): Remove address width code and use...
+	(get_addr_width): ...this new function.
+	(show_modules): New static boolean.
+	(module_callback): New static function.
+	(parse_opt): Handle '-l'.
+	(main): Add 'l' to options. If show_modules then use dwfl_getmodules
+	with module_callback to show all detected modules and possible
+	build_id, elf and dwarf files.
+
+2013-12-27  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (frames_shown): New static boolean.
+	(EXIT_OK,EXIT_ERROR,EXIT_BAD,EXIT_USAGES): New defines.
+	(frame_callback): Return -1 on error. Don't print error.
+	(print_frames): Add arguments, tid, dwflerr and what. Print tid.
+	If there was an error report it with address and module if possible.
+	Record whether any frames were actually printed.
+	(thread_callback): Collect tid and err, pass it to print_frames.
+	(parse_opt): Use EXIT_BAD for errors. On ARGP_KEY_END print errno
+	if dwfl_linux_proc_report returned it. Check whether we are properly
+	attached with dwfl_pid.
+	(main): Document exit status. Don't report DWARF_CB_ABORT from
+	callbacks as error. Pass real errors to print_frames. Return
+	EXIT_BAD if no frames could be shown. Return EXIT_ERROR if there
+	were any non-fatal errors.
+
+2013-12-23  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am (stack_LDADD): Add demanglelib.
+	* stack.c (show_quiet): New static boolean, default false.
+	(show_raw): Likewise.
+	(demangle_buffer_len): New static size_t.
+	(demangle_buffer): New static char *.
+	(print_frames): Don't resolve pc name if show_quiet. Demangle name
+	unless show_raw.
+	(parse_opt): Handle '-q' and '-r'.
+	(main): Add 'q' and 'r' to options. Free demangle_buffer.
+
+2013-12-23  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (OPT_DEBUGINFO): New define.
+	(OPT_COREFILE): Likewise.
+	(pid): New static.
+	(core_fd): Likewise.
+	(core): Likewise.
+	(exec): Likewise.
+	(debuginfo_path): Likewise.
+	(parse_opt): Handle '-p', '--core', '-e' and '--debuginfo-path'.
+	Do argument sanity checking. Setup Dwfl.
+	(main): Add 'p', 'core', 'e' and 'debuginfo-path' to options.
+	Remove argp_child children, simplify argp doc, remove custom
+	usage message and construction of dwfl with dwfl_standard_argp.
+	Use pid directly as tid. close core and core_fd if opened. Print
+	pid of process or core.
+
+2013-12-23  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (show_build_id): New static boolean.
+	(print_frames): Print module build-id, load address and pc offset
+	if show_build_id is true.
+	(parse_opt): Handle '-b'.
+	(main): Add -b to options.
+
+2013-12-22  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (maxframes): New static unsigned. Initialize to 64.
+	(struct frame): New struct.
+	(struct frames): Likewise.
+	(dwfl): New static Dwfl pointer.
+	(frame_callback): Use arg as struct frames and fill it next frame.
+	Return DWARF_CB_ABORT when maxframes has been reached. Move
+	printing of frame to...
+	(print_frames): ...here. New function.
+	(thread_callback): Use arg as struct frames and set frames to zero.
+	Call print_frames.
+	(parse_opt): Handle '-n'.
+	(main): Add -n to options. Allocate frames using maxframes. Pass
+	frames to frame_callback and thread_callback.
+
+2013-12-20  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (show_one_tid): New static boolean.
+	(parse_opt): Handle '-1'.
+	(main): Add -1 to options. Call dwfl_getthread_frames when
+	show_one_tid is true.
+
+2013-12-18  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (options): Add symbol-sections, 'x'.
+	(show_symbol_sections): New static bool.
+	(parse_opt): Handle 'x'.
+	(print_addrsym): Use dwfl_module_addrinfo value.r
+	Also show section of address with show_symbol_sections.
+	(find_symbol): Use dwfl_module_getsym_info and set value.
+	(handle_address): Request value and use it instead of sym.st_value.
+	* readelf.c (format_dwarf_addr): Use dwfl_module_addrinfo to get
+	name and offset.
+
+2013-12-17  Masatake YAMATO  <yamato@redhat.com>
+	    Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c (show_activation, show_module, show_source): New variables.
+	(parse_opt): Set show_activation if -a option is given.
+	Set show_module if -m option is given. Set show_source if -s option
+	is given. Set all show booleans when -v option is given.
+	(main): Added `-a', `-m', `-s', and `-v' to the help message.
+	(frame_callback): Print module and source file information.
+
+2013-11-25  Petr Machata  <pmachata@redhat.com>
+
+	* elflint.c (valid_e_machine): Add EM_AARCH64.
+
+2013-11-14  Petr Machata  <pmachata@redhat.com>
+
+	* readelf.c (handle_core_item) <'h'>: New branch for handling
+	fields that shouldn't be displayed.
+
+2013-11-10  Mark Wielaard  <mjw@redhat.com>
+
+	* stack.c: Use ARGP_PROGRAM_VERSION_HOOK_DEF and
+	ARGP_PROGRAM_BUG_ADDRESS_DEF.
+	(print_version): New function.
+
+2013-11-09  Mark Wielaard  <mjw@redhat.com>
+
+	* arlib.c (arlib_init): Call snprintf before using the result
+	with memcpy.
+	(arlib_finalize): Likewise.
+	* nm.c (show_symbols_sysv): Don't modify cnt inside assert.
+
+2013-11-07  Jan Kratochvil  <jan.kratochvil@redhat.com>
+
+	* Makefile.am (bin_PROGRAMS): Add stack.
+	(stack_LDADD): New.
+	* stack.c: New file.
+
+2013-11-05  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_ranges_section): Cast address to size_t
+	before comparison.
+	(print_debug_loc_section): Likewise.
+
+2013-10-18  Mark Wielaard  <mjw@redhat.com>
+
+	* ar.c (main): Correct operation check when instance_specifed is set.
+
+2013-09-26  Petr Machata  <pmachata@redhat.com>
+
+	* readelf.c (handle_file_note): New function.
+	(handle_notes_data): Call it to handle NT_FILE notes.
+
+2013-09-26  Petr Machata  <pmachata@redhat.com>
+
+	* readelf.c (handle_siginfo_note): New function.
+	(handle_notes_data): Call it to handle NT_SIGINFO notes.
+	(buf_read_int, buf_read_ulong, buf_has_data): New functions.
+
+2013-08-13  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (options): Add "inlines", 'i'.
+	(show_inlines): New bool.
+	(parse_opt): Handle 'i'.
+	(print_diesym): New static function.
+	(print_src): New function taking code from...
+	(handle_address): here. Call print_src. Print inlines.
+
+2013-08-12  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (main): If there is a newline char at end of buf,
+	then remove it.
+
+2013-07-05  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_ops): Take CU as argument, use it to print
+	parameter_ref DIE offset.
+	(struct listptr): Replace base field with cu.
+	(listptr_base): New function.
+	(compare_listptr): Use listptr_base.
+	(notice_listptr): Take CU as argument.
+	(skip_listptr_hole): Likewise.
+	(print_debug_ranges_section): Pass NULL as CU to skip_listptr_hole.
+	(print_cfa_program): Pass NULL as CU to print_ops.
+	(struct attrcb_args): Replace cu_base field with cu.
+	(attr_callback): Pass cu not cu_base to notice_listptr.
+	(print_debug_units): Don't calculate base, just set cu.
+	(print_debug_loc_section): Pass cu to skip_listptr_hole and
+	print_ops.
+
+2013-05-06  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_ops): Format first DW_OP_GNU_implicit_pointer
+	argument as DIE offset.
+
+2013-04-24  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am: Use AM_CPPFLAGS instead of INCLUDES.
+
+2013-03-25  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (argp_options): Add decodedline.
+	(decodedline): New boolean initialized to false.
+	(parse_opt): Set decodedline when arg is decodedline.
+	(print_decoded_line_section): New function.
+	(print_debug_line_section): Call print_decoded_line_section when
+	decodedline is true.
+
+2013-03-25  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (argp_option): Add decodedaranges.
+	(decodedaranges): New boolean initialized to false.
+	(parse_opt): Set decodedaranges when arg is decodedaranges.
+	(print_debug_aranges_section): Reimplemented and original
+	implementation renamed to...
+	(print_decoded_aranges_section): this.
+
+2013-03-25  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (attrcb_args): Add Dwarf_Die.
+	(attr_callback): When highpc is in constant form also print as
+	address.
+	(print_debug_units): Set args.die.
+
+2013-03-19  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_gdb_index_section): Free format_dwarf_addr results.
+
+2013-03-18  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_gdb_index_section): Accept version 8.
+
+2013-03-01  Mark Wielaard  <mjw@redhat.com>
+
+	* findtextrel.c (process_file): Release ELF and close file when not
+	text relocations are found.
+	* strip.c (handle_elf): Track memory used for .debuglink section data
+	and free when done.
+
+2013-02-24  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_symtab): Add __bss_start__ to the list of symbols
+	allowed to have out of section values because of GNU ld bugs.
+
+2013-02-06  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_symtab): Add __bss_start and __TMC_END__ to the
+	list of symbols allowed to have out of section values because of
+	GNU ld bugs in either .symtab or .dynsym, but only when they are
+	zero sized.
+
+2013-01-24  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (argp_option): Add unresolved-address-offsets, U.
+	(print_unresolved_addresses): New static.
+	(parse_opt): Handle 'U', set print_unprocessed_values.
+	(format_dwarf_addr): Take and handle new raw argument.
+	(print_ops): Call format_dwarf_addr with raw offset values.
+	(print_debug_ranges_section): Likewise.
+	(print_debug_frame_section): Likewise.
+	(attr_callback): Likewise.
+	(print_debug_line_section): Likewise.
+	(print_debug_loc_section): Likewise.
+	(print_gdb_index_section): Likewise.
+
+2013-01-18  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (struct listptr): Add base Dwarf_Addr field.
+	(compare_listptr): Warn for same offset with different base.
+	(notice_listptr): Take base argument and set it.
+	(skip_listptr_hole): Likewise.
+	(struct attrcb_args): Removed unused cu_offset field.
+	Add cu_base Dwarf_Addr field.
+	(attr_callback): Call notice_listptr with cbargs->cu_base.
+	(print_debug_units): Set args.cu_base.
+	(print_debug_ranges_section): Get base and use for format_dwarf_addr.
+	(print_debug_loc_section): Likewise.
+
+2013-01-29  Jan Kratochvil  <jan.kratochvil@redhat.com>
+
+	* readelf.c (handle_core_items): Limit special repeated items handling
+	to single-item formats '\n', 'b' and 'B', assert OFFSET 0 there.
+
+2012-12-18  Mark Wielaard  <mark@bordewijk.wildebeest.org>
+
+	* readelf.c (ELF_INPUT_SECTION): New argp key value.
+	(argp_option): Add elf-section.
+	(elf_input_section): New static.
+	(parse_opt): Handle ELF_INPUT_SECTION and set elf_input_section.
+	(open_input_section): New function.
+	(process_file): Call open_input_section if elf_input_section set.
+
+2013-01-13  David Abdurachmanov  <David.Abdurachmanov@cern.ch>
+
+	ar.c (do_oper_delete): Fix num passed to memset.
+
+2012-12-21  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_frame_section): Adjust FDE start address
+	if pcrel before feeding it to format_dwarf_addr.
+
+2012-12-21  Mark Wielaard  <mjw@redhat.com>
+
+	* addr2line.c (main): Call dwfl_end.
+
+2012-12-11  Roland McGrath  <roland@hack.frob.com>
+
+	* nm.c (show_symbols_sysv): Fix size passed to snprintf for invalid
+	sh_name case.
+	Reported by David Abdurachmanov <David.Abdurachmanov@cern.ch>.
+
+2012-10-16  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_ops): DW_OP_skip and DW_OP_bra targets are
+	calculated beginning after the operand and 2-byte constant.
+
+2012-10-12  Jan Kratochvil  <jan.kratochvil@redhat.com>
+
+	* readelf.c (ITEM_WRAP_COLUMN, REGISTER_WRAP_COLUMN): Merge to ...
+	(WRAP_COLUMN): ... here.
+	(print_core_item): Remove parameter format_max.  Update function
+	comment.  Replace FORMAT_MAX by the real output width.
+	(handle_core_item): Remove the FORMAT_MAX values in TYPES, DO_TYPE,
+	calls of print_core_item, remove variable maxfmt, change
+	ITEM_WRAP_COLUMN to WRAP_COLUMN.
+	(handle_core_register): Remove the FORMAT_MAX values in TYPES, BITS,
+	calls of print_core_item, change REGISTER_WRAP_COLUMN to WRAP_COLUMN.
+
+2012-10-11  Jan Kratochvil  <jan.kratochvil@redhat.com>
+
+	* readelf.c (handle_core_item) <b>: Make run an outer block variable.
+	Increase run only if LASTBIT != 0.  Print last element only if RUN > 0.
+
+2012-08-27  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_macro_section): Print offset as PRIx64.
+
+2012-08-27  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (register_info): Handle loc == NULL.
+
+2012-08-22  Jeff Kenton  <jkenton@tilera.com>
+
+	* elflint.c (valid_e_machine): Add EM_TILEGX and EM_TILEPRO.
+
+2012-08-16  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (dwarf_tag_name): Renamed from dwarf_tag_string.
+	Uses new dwarf_tag_string or adds ??? or lo_user+%#x when
+	appropriate.
+	(dwarf_attr_name): Likewise.
+	(dwarf_form_name): Likewise.
+	(dwarf_lang_name): Likewise.
+	(dwarf_inline_name): Likewise.
+	(dwarf_encoding_name): Likewise.
+	(dwarf_access_name): Likewise.
+	(dwarf_visibility_name): Likewise.
+	(dwarf_virtuality_name): Likewise.
+	(dwarf_identifier_case_name): Likewise.
+	(dwarf_calling_convention_name): Likewise.
+	(dwarf_ordering_name): Likewise.
+	(dwarf_discr_list_name): Likewise.
+	(print_ops): Remove KNOWN.  Use dwarf_locexpr_opcode_string.
+	(attr_callback): Call new dwarf_foobar_name instead of old
+	dwarf_foobar_string functions.
+	(dwarf_tag_string): New function using known-dwarf.h macros.
+	(dwarf_attr_string): Likewise.
+	(dwarf_form_string): Likewise.
+	(dwarf_lang_string): Likewise.
+	(dwarf_inline_string): Likewise.
+	(dwarf_encoding_string): Likewise.
+	(dwarf_access_string): Likewise.
+	(dwarf_visibility_string): Likewise.
+	(dwarf_virtuality_string): Likewise.
+	(dwarf_identifier_case_string): Likewise.
+	(dwarf_calling_convention_string): Likewise.
+	(dwarf_ordering_string): Likewise.
+	(dwarf_discr_list_string): Likewise.
+	(dwarf_locexpr_opcode_string): Likewise.
+
+2012-06-27  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (dwarf_form_string): Handle DW_FORM_GNU_ref_alt and
+	DW_FORM_GNU_strp_alt.
+	(attr_callback): Likewise.
+
+2012-07-30  Petr Machata  <pmachata@redhat.com>
+
+	* nm.c (show_symbols_bsd): Reorder arguments in {S,}FMTSTRS (and
+	corresponding printf) so that those that are referenced by only
+	one of the formatting strings are at the end.
+
+2012-07-29  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (dwarf_lang_string): Use DW_LANG_ObjC, not DW_LANG_Objc.
+	(print_ops): Use known[op], not op_name, for DW_OP_GNU_parameter_ref.
+
+2012-07-19  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_ops): Handle DW_OP_GNU_parameter_ref.
+
+2012-07-11  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (options): Add macro to help of debug-dump.
+	(section_e): Add section_macro.
+	(section_all): Add section_macro.
+	(parse_opt): Handle macro.
+	(print_debug_macro_section): New function.
+	(print_debug): Add NEW_SECTION (macro).
+
+2012-07-10  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_gdb_index_section): Add version 7 support.
+	Keep track of cu_nr. Print kind and static/global flag for each
+	symbol. When a symbol is in the TU list add 'T'.
+
+2012-06-26  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (dwarf_attr_string): Add DW_AT_GNU_macros.
+
+2012-06-22  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_ops): Cast printf PRIu/x64 arguments to uint64_t
+	for gcc 4.7 -Wformat.
+
+2012-05-09  Roland McGrath  <roland@hack.frob.com>
+
+	* elflint (check_sections): Allow zero sized sections at (filesz) end
+	of segment. And make check overflow-proofed.
+
+2012-04-24  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_ops): Add DW_OP_GNU_push_tls_address,
+	DW_OP_GNU_uinit and DW_OP_GNU_encoded_addr.
+
+2012-03-28  Roland McGrath  <roland@hack.frob.com>
+
+	* elflint.c (special_sections): Accept SHF_INFO_LINK for reloc sections.
+
+2012-03-28  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_debug_abbrev_section): Check there is Dwarf
+	section data.
+	(print_debug_str_section): Likewise.
+
+2012-03-21  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_gdb_index_section): Accept version 6.
+
+2012-01-31  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (attr_callback): Don't special case DW_FORM_sec_offset.
+
+2012-01-21  Ulrich Drepper  <drepper@gmail.com>
+
+	* addr2line.c: Update copyright year.
+	* ar.c: Likewise.
+	* elfcmp.c: Likewise.
+	* elflint.c: Likewise.
+	* findtextrel.c: Likewise.
+	* ld.c: Likewise.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* strings.c: Likewise.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise.
+
+	* nm.c (argp_children): Define.
+	(argp): Hook up argp_children.
+	(handle_ar): Optimize puts call.
+	(show_symbols_bsd): Use positional parameters to also print color
+	codes.  Don't print STT_FILE symbols.
+	* objdump.c (options): Improve help text.
+	(argp_children): Define.
+	(argp): Hook up argp_children.
+	(disasm_info): Add elements for color codes.
+	(disasm_output): Print color codes as well.
+	(show_disasm): Set up disasm_info data for callback.
+
+2012-01-20  Roland McGrath  <roland@hack.frob.com>
+
+	* arlib-argp.c (arlib_deterministic_output): Initialize from
+	configured value.
+	(help_filter): New function.
+	(argp): Use it.
+
+	* ar.c (main): Handle oper_none as usage error.
+
+	* arlib-argp.c (options, parse_opt): Grok -U as inverse of -D.
+
+	* ranlib.c (argp): Use arlib_argp_children.
+
+	* arlib.c (arlib_init): Obey arlib_deterministic_output.
+
+	* arlib-argp.c: New file.
+	* Makefile.am (libar_a_SOURCES): Add it.
+	* arlib.h (arlib_deterministic_output, arlib_argp_children):
+	Declare new variables.
+	* ar.c (deterministic_output): Variable removed.
+	(do_oper_insert): Use arlib_deterministic_output instead.
+	(options, parse_opt): Don't handle -D here.  Add group numbers.
+	(argp): Use arlib_argp_children.
+
+2011-12-20  Roland McGrath  <roland@hack.frob.com>
+
+	* readelf.c (print_debug): Initialize DUMMY_DBG.elf.
+	Reported by Karel Klic <kklic@redhat.com>.
+
+2011-11-05  Roland McGrath  <roland@hack.frob.com>
+
+	* ar.c (deterministic_output): New flag variable.
+	(options, parse_opt): Grok -D to set it.
+	(do_oper_insert): When set, use zero from mtime, uid, and gid.
+
+	* ar.c (do_oper_insert): Fix check on elf_rawfile return value.
+
+2011-10-04  Marek Polacek  <mpolacek@redhat.com>
+
+	* readelf.c (register_info): Assume the right size of an array.
+
+2011-10-03  Ulrich Drepper  <drepper@gmail.com>
+
+	* nm.c: Recognize option --mark-special.  Still recognize --mark-weak
+	but don't show it in help anymore.
+	(mark_special): Renamed from mark_weak.
+	(parse_opt): Adjust.
+	(class_type_char): Take additional parameters for ELF file and ELF
+	header.  Treat TLS symbols like objects.
+	In case of D symbols, show u for unique symbols, R for symbols in
+	read-only sections, B for symbols in BSS sections.
+	(show_symbols_bsd): Take additional parameters for ELF file and ELF
+	header.  Adjust for class_type_char change.  Show TLS symbols with
+	@ after them in case --mark-special is selected.
+	(show_symbols_posix): Likewise.
+	(show_symbols): Adjust calls to show_symbols_bsd and
+	show_symbols_posix.
+	(show_symbols_sysv): Avoid printing adress and size for undefined
+	symbols.  Don't print initial special entry and section entries.
+
+2011-10-02  Ulrich Drepper  <drepper@gmail.com>
+
+	* Makefile.am (demanglelib): Define.
+	(nm_LDADD): Add demanglelib.
+	* nm.c (options): Add -C option.
+	(demangle): Define as global variable.
+	(parse_opt): Recognize -C.
+	(show_symbols_sysv): Handle demangling.
+	(show_symbols_bad): Likewise.
+	(show_symbols_posix): Likewise.
+	(show_symbols): Likewise.
+
+2011-07-09  Roland McGrath  <roland@hack.frob.com>
+
+	* readelf.c (options, parse_opt): Grok -W/--wide and ignore it.
+
+	* ar.c (parse_opt): Grok -u.
+
+2011-05-30  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (relocate): Make offset check overflow-proof.
+
+2011-05-23  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (relocate): Take new arguments is_rela to indicate
+	whether the relocation is from a SHT_REL or SHT_RELA section.
+	Relocate against any debug section symbol, not just STT_SECTION
+	symbols. For SHT_REL relocations, fetch addend from offset and
+	add it to symbol value if not zero.
+
+2011-05-23  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (OPT_RELOC_DEBUG): New option.
+	(argp_option): Add new --reloc-debug-sections option.
+	(main): Check new option.
+	(parse_opt): Likewise.
+	(handle_elf): Remove any relocations between debug sections
+	in ET_REL for the debug file when requested.
+
+2011-05-18  Mark Wielaard  <mjw@redhat.com>
+
+	* strip.c (handle_elf): Make sure all sections of a removed group
+	section are removed too. Don't discard SHT_GROUP sections, copy
+	section table before it gets modified. Section group signature
+	symbols don't have to be retained.
+
+2011-05-16  Jakub Jelinek  <jakub@redhat.com>
+
+	* readelf.c (print_ops): Handle DW_OP_GNU_const_type,
+	DW_OP_GNU_regval_type, DW_OP_GNU_deref_type, DW_OP_GNU_convert
+	and DW_OP_GNU_reinterpret.
+
+2011-05-17  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (dwarf_tag_string): Fixup DW_TAG_GNU_call_site and
+	DW_TAG_GNU_call_site_parameter return strings.
+
+2011-05-11  Marek Polacek  <mpolacek@redhat.com>
+
+	* nm.c (show_symbols_sysv): Remove unused if/else, remove
+	unused `prefix' and `fname' parameters.
+
+2011-05-07  Marek Polacek  <mpolacek@redhat.com>
+
+	* unstrip.c (compare_sections_nonrel): Mark this function as static.
+
+2011-04-26  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (handle_notes_data): Call ebl_object_note_type_name
+	with note name.
+
+2011-04-14  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (options): Add gdb_index.
+	(section_e): Define section_gdb_index.
+	(parse_opt): Recognize gdb_index debug-dump argument.
+	(print_gdb_index_section): New function.
+	(print_debug): Add gdb_index to debug_sections.
+
+2011-03-24  Petr Machata  <pmachata@redhat.com>
+
+	* readelf.c (print_debug_line_section): Emit initial space for all
+	opcode lines.  Print offset in front of each opcode.
+
+2011-03-22  Marek Polacek  <mpolacek@redhat.com>
+
+	* readelf.c (handle_dynamic): Don't segfault at DT_PLTREL case.
+
+2011-03-22  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (dwarf_tag_string): Support DW_TAG_GNU_call_site
+	and DW_TAG_GNU_call_site_parameter.
+	(dwarf_attr_string): Support DW_AT_GNU_call_site_value,
+	DW_AT_GNU_call_site_data_value,
+	DW_AT_GNU_call_site_target,
+	DW_AT_GNU_call_site_target_clobbered,
+	DW_AT_GNU_tail_call,
+	DW_AT_GNU_all_tail_call_sites,
+	DW_AT_GNU_all_call_sites,
+	and DW_AT_GNU_all_source_call_sites.
+	(print_ops): Handle DW_OP_GNU_entry_value.
+	(attr_callback): Handle DW_AT_GNU_call_site_value,
+	DW_AT_GNU_call_site_data_value,
+	DW_AT_GNU_call_site_target,
+	and DW_AT_GNU_call_site_target_clobbered.
+
+2011-03-10  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_symtab): Use ebl_check_st_other_bits.
+
+2011-02-27  Jan Kratochvil  <jan.kratochvil@redhat.com>
+
+	* readelf.c (reset_listptr): Clear TABLE->TABLE.
+
+2011-02-25  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (dwarf_attr_string): Add DW_AT_GNU_* handling.
+	(dwarf_form_string): Properly format and return unknown form.
+
+2011-02-23  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (section_name): New function.
+	(print_debug_abbrev_section): Use it instead of constant.
+	(print_debug_aranges_section): Likewise.
+	(print_debug_ranges_section): Likewise.
+	(print_debug_units): Likewise.
+	(print_debug_line_section): Likewise.
+	(print_debug_loc_section): Likewise.
+	(print_debug_macinfo_section): Likewise.
+	(print_debug_pubnames_section): Likewise.
+	(print_debug_str_section): Likewise.
+	(print_debug) [USE_ZLIB]: Match .zdebug_* sections too.
+	(print_debug_abbrev_section): Use decoded d_size, not sh_size.
+	(print_debug_str_section): Likewise.
+
+	* readelf.c (dwarf_attr_string): Grok DW_AT_GNU_odr_signature.
+
+2011-02-11  Roland McGrath  <roland@redhat.com>
+
+	* elfcmp.c (verbose): New variable.
+	(options, parse_opt): Grok -l/--verbose to set it.
+	(main): Under -l, keep going after first difference.
+
+	* elfcmp.c (ignore_build_id): New variable.
+	(options, parse_opt): Grok --ignore-build-id to set it.
+	(main): For SHT_NOTE sections, compare note details rather than raw
+	bytes.  Under --ignore-build-id, don't complain about differing build
+	ID contents if lengths match.
+
+2011-02-08  Roland McGrath  <roland@redhat.com>
+
+	* ldscript.y (filename_id_star): Remove unused variable.
+
+	* unstrip.c (copy_elided_sections): Remove unused variable.
+
+	* elflint.c (check_dynamic): Remove unused variables.
+
+	* elflint.c (check_symtab): Warn about missing xndx section only once.
+
+	* ldgeneric.c (check_for_duplicate2): Remove unused variable.
+
+2011-01-06  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (handle_elf): Under --strip-sections, remove all
+	non-allocated sections and never generate .gnu_debuglink.
+
+2011-01-04  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (remove_shdrs): New variable.
+	(options, parse_opt): Grok --strip-sections to set it.
+	(handle_elf): When that's set, truncate off .shstrtab and shdrs.
+
+2010-11-10  Roland McGrath  <roland@redhat.com>
+
+	* findtextrel.c (process_file): Don't assume order of sections.
+	Reported by Mike Hommey <mh@glandium.org>.
+
+2010-07-26  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_ops): Handle DW_OP_GNU_implicit_pointer.
+
+2010-08-30  Roland McGrath  <roland@redhat.com>
+
+	Print .debug_loc/.debug_ranges with cognizance of actual DIE uses.
+	* readelf.c (parse_opt): Add section_info to implicit_debug_sections
+	for ranges, loc.
+	(struct listptr, struct listptr_table): New types.
+	(compare_listptr, reset_listptr, sort_listptr): New functions.
+	(notice_listptr, skip_listptr_hole): New functions.
+	(struct attrcb_args): Add silent member.
+	(attr_callback): Call notice_listptr for loclistptr and rangelistptr.
+	Suppress output if silent, but still call notice_listptr.
+	(print_debug_units): Suppress output if section_info not requested.
+	(print_debug_loc_section): Call sort_listptr, skip_listptr_hole.
+	(print_debug_ranges_section): Likewise.
+	(print_debug): Call reset_listptr on both tables.
+
+	* readelf.c (print_debug_ranges_section): Print empty list.
+	(print_debug_loc_section): Likewise.
+
+	* readelf.c (print_debug_loc_section): Check for bogus length
+	before calling print_ops.
+	(print_ops): Check harder for bogus data that would read off end.
+
+2010-08-11  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (for_each_section_argument): Process all sections with
+	matching name, not just the first.
+
+2010-07-26  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_ops): Take new argument for CU version.
+	Fix DW_OP_call_ref decoding to depend on it.
+	(print_debug_loc_section): Update caller.
+	(print_cfa_program): Take new argument, pass it down.
+	(print_debug_frame_section): Update caller.
+	(struct attrcb_args): New member version.
+	(print_debug_units): Initialize it.
+
+2010-07-02  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_debug_frame_section): Use format_dwarf_addr for
+	initial_location.
+
+2010-06-30  Roland McGrath  <roland@redhat.com>
+
+	* strings.c (main): Use STDIN_FILENO, not STDOUT_FILENO.
+	Ignore st_size for a non-S_ISREG file descriptor.
+	(read_block): Move assert after no-mmap bail-out.
+	(read_block_no_mmap): Fix size calculations for moving buffer remnant.
+
+2010-06-22  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_debug_line_section): Fix braino in DW_LNS_set_isa.
+
+2010-06-21  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (dwarf_tag_string): Handle new v4 tags.
+	(dwarf_attr_string): Add new attributes.
+	(dwarf_tag_string): Handle DW_TAG_GNU_*.
+
+	* readelf.c (print_ops): Use 64-bit types for LEB128 operands.
+	(print_cfa_program): Likewise.
+
+2010-06-20  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_debug_units): New function, broken out of ...
+	(print_debug_info_section): ... here.  Call it.
+	(print_debug_types_section): New function.
+	(enum section_e): Add section_types alias for section_info.
+	(print_debug): Add types to the sections table.
+
+	* readelf.c (print_debug_frame_section): Handle version 4 format.
+
+	* readelf.c (print_debug_line_section): Handle version 4 format.
+
+2010-06-14  Roland McGrath  <roland@redhat.com>
+
+	* unstrip.c (copy_elided_sections): Make sure all sections' data have
+	been read in before we write anything out.
+
+2010-06-04  Roland McGrath  <roland@redhat.com>
+
+	* unstrip.c (update_shdr): New function.
+	(update_sh_size): Call it instead of gelf_update_shdr.
+	(adjust_relocs, add_new_section_symbols): Likewise.
+	(new_shstrtab, copy_elided_sections): Likewise.
+
+	* unstrip.c (copy_elided_sections): Bail if stripped file has more
+	sections than unstripped file, rather than getting confused later.
+
+2010-06-01  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (dwarf_form_string): Handle DWARF 4 forms.
+	(attr_callback): Handle DW_FORM_flag_present, DW_FORM_exprloc,
+	DW_FORM_sec_offset, DW_FORM_ref_sig8.
+
+	* readelf.c (print_debug): Don't bail if libdw setup fails.
+	Suppress complaint if we only want .eh_frame anyway.
+
+2010-05-28  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (attr_callback): Also print form information.
+
+2010-05-19  Roland McGrath  <roland@redhat.com>
+
+	* addr2line.c (find_symbol): Short-circuit on empty name.
+	(handle_address): Handle SYMBOL with no +OFFSET.
+
+2010-05-08  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_ops): Take new arg OFFSET_SIZE.
+	Use that for DW_OP_call_ref, not ADDRSIZE.
+	(print_cfa_program): Update caller.
+	(struct attrcb_args): Add offset_size field.
+	(attr_callback): Use it for print_ops call.
+	(print_debug_info_section): Initialize it.
+	(print_ops): Likewise.
+
+2010-04-14  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (handle_core_item): Fix bitmask printing.
+
+2010-04-06  Roland McGrath  <roland@redhat.com>
+
+	* ld.c (options): Fix some typos in messages.
+	* elflint.c (check_scn_group, check_group): Likewise.
+	* ldscript.y (add_id_list): Likewise.
+	* readelf.c (print_hash_info): Add xgettext:no-c-format magic comment
+	before translated string containing a literal %.
+
+2010-02-26  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (process_file): Don't leak an fd in failure case.
+
+2010-02-15  Roland McGrath  <roland@redhat.com>
+
+	* Makefile.am: Use config/eu.am for common stuff.
+
+	* readelf.c (print_debug_frame_section): Add a cast to avoid sign
+	mismatch in comparison.
+
+2010-02-02  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_encoding_base): Handle DW_EH_PE_absptr (zero).
+	(read_encoded): Likewise.
+	(print_debug_frame_section): Check for bogus augmentation length.
+	For P augmentation, use read_encoded, print the encoding description,
+	and use hex for unsigned values.
+
+2010-01-15  Roland McGrath  <roland@redhat.com>
+
+	* ar.c: Include <sys/stat.h>.
+	* elflint.c: Likewise.
+	* readelf.c: Likewise.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise
+
+2010-01-07  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_ehdr): Handle PN_XNUM.
+	(phnum): New static variable.
+	(process_elf_file): Set it with elf_getphdrnum.
+	(print_phdr): Use phnum instead of EHDR->e_phnum.
+	(print_dynamic, handle_notes): Likewise.
+	(handle_relocs_rel, handle_relocs_rela): Likewise.
+
+	* elfcmp.c (main): Use elf_getshdrnum and elf_getphdrnum.
+
+	* elflint.c (phnum): New static variable.
+	(check_elf_header): Set it, handling PN_XNUM.
+	Use that in place of EHDR->e_phnum throughout.
+	(check_symtab, check_reloc_shdr, check_dynamic): Likewise.
+	(unknown_dependency_p, check_sections, check_program_header): Likewise.
+
+2010-01-05  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (dwarf_attr_string): Match DW_AT_GNU_vector and
+	DW_AT_GNU_template_name.
+
+2010-01-04  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (handle_notes_data): Grab NT_AUXV only for name "CORE".
+	(handle_core_note): Pass NHDR and NAME to ebl_core_note.
+	(handle_core_item): Handle .format of '\n' as \n-separated strings.
+
+	* readelf.c (implicit_debug_sections): New variable.
+	(parse_opt): Set it instead of print_debug_sections for -a.
+	OR them together for print_debug check.
+	(print_debug): OR them together for section check.
+
+	* readelf.c (options): Repartition into set implied by -a and others.
+	Correct -a text to match reality.
+
+	* readelf.c (struct section_argument): Add bool member 'implicit'.
+	(parse_opt): Set it for -a cases, clear it for -x args.
+	(for_each_section_argument): Don't complain about a missing section by
+	name if it's implicit.
+
+2009-11-16  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_string_section): Punt SHT_NOBITS like empty
+	sections, just as dump_data_section already does.
+
+2009-09-21  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (special_sections): Allow MERGE and STRINGS flags to be
+	set for .comment section.
+	Patch by Mark Wielaard <mjw@redhat.com>.
+
+2009-09-08  Roland McGrath  <roland@redhat.com>
+
+	* ar.c (main): Fix typo in message format.
+
+2009-08-21  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (attr_callback): Use print_block only when we don't use
+	print_ops.
+
+2009-08-14  Roland McGrath  <roland@redhat.com>
+
+	* ar.c (do_oper_extract): Use pathconf instead of statfs.
+
+2009-08-01  Ulrich Drepper  <drepper@redhat.com>
+
+	* debugpred.h: Add two most const.
+
+2009-07-26  Mark Wielaard  <mjw@redhat.com>
+
+	* elflint.c (check_note_data): Recognize NT_GNU_GOLD_VERSION.
+
+2009-07-25  Mark Wielaard  <mjw@redhat.com>
+
+	* Makefile.am (addr2line_LDADD): Add $(libelf).
+
+2009-07-24  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_block): New function.
+	(print_ops): Use it.
+	(attr_callback): Use it for DW_FORM_block* forms.
+
+2009-07-20  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (print_ops): Add handling of DW_OP_implicit_value
+	and DW_OP_stack_value.
+
+2009-07-14  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_elf_header): Allow Linux ABI.
+	(check_symtab): Handle STB_GNU_UNIQUE.
+
+2009-07-08  Mark Wielaard  <mjw@redhat.com>
+
+	* readelf.c (attr_callback): Handle DW_Form constants for
+	DW_AT_data_member_location.
+
+2009-07-06  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (register_info): New function.  Handle unknown register #s.
+	(print_cfa_program): Use it.
+	(handle_core_register, handle_core_registers): Likewise.
+
+2009-06-28  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_address_names): New static variable.
+	(options, parse_opt): Grok -N/--numeric-addresses to clear it.
+	(format_dwarf_addr): Don't look up name if !print_address_names.
+
+2009-06-13  Ulrich Drepper  <drepper@redhat.com>
+
+	* ldgeneric.c: Don't use deprecated libelf functions.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise.
+	* ld.h: Fix up comment.
+
+2009-06-01  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_relocs): Expect ELF header argument and pass on
+	to handle_relocs_rel* functions. Adjust caller.
+	(handle_relocs_rel): Add ELF header argument.  Add special case for
+	the IRELATIVE relocations in statically linked executables.
+	(handle_relocs_rela): Likewise.
+
+2009-04-29  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_symtab): Add tests of st_other field.
+
+2009-04-23  Ulrich Drepper  <drepper@redhat.com>
+
+	* Makefile [BUILD_STATIC] (libdw): Add $(zip_LIBS).
+
+2009-04-20  Roland McGrath  <roland@redhat.com>
+
+	* addr2line.c (print_dwarf_function): Honor -s and -A for file names
+	of inline call sites.
+
+	* addr2line.c (just_section): New variable.
+	(adjust_to_section): New function, broken out of ...
+	(handle_address): ... here.
+	(options, parse_opt): Add -j/--section=NAME to set it.
+
+2009-04-15  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_debug_frame_section): Check for DW_CIE_ID_64 in
+	64-bit format header, DW_CIE_ID_32 in 32-bit format header.
+
+2009-04-14  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_attributes): Treat SHT_ARM_ATTRIBUTES on EM_ARM
+	like SHT_GNU_ATTRIBUTES.
+
+	* readelf.c (handle_core_registers): Fix error message.
+
+	* strip.c (handle_elf: check_preserved): Don't note any change when
+	.debug_data is already filled from a previous pass.
+
+2009-02-05  Ulrich Drepper  <drepper@redhat.com>
+
+	* objdump.c (show_relocs_x): Minor cleanups.
+
+	* readelf.c (print_cfa_program): Correct a few labels.
+	Print first DW_CFA_expression and DW_CFA_val_expression parameter
+	as register.
+
+2009-02-01  Ulrich Drepper  <drepper@redhat.com>
+
+	* objdump.c (show_relocs_rel, show_relocs_rela): Split common parts
+	into ...
+	(show_relocs_x): ...here.  New function.
+	(show_relocs): Better spacing in output.
+
+	* objdump.c (show_relocs_rela): Show offsets as signed values.
+
+	* ar.c (main): Fix recognition of invalid modes for a, b, i modifiers.
+	Improve some error messages.
+	Use program_invocation_short_name instead of AR macro.
+	* Makefile.am (CFLAGS_ar): Remove.
+	* elflint.c (parse_opt): ARGP_HELP_EXIT_ERR does nothing for argp_help.
+	* objdump.c (parse_opt): Likewise.
+	* readelf.c (parse_opt): Likewise.
+
+2009-01-27  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_ops): Notice short length, don't overrun buffer
+	(still need to fix LEB128).
+
+	* readelf.c (print_ops): Fix DW_OP_call[24] decoding.
+
+	* readelf.c (print_ops): Print (empty)\n when LEN == 0.
+
+2009-01-24  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_debug_frame_section): Fix computation of vma_base
+	for PC-relative mode.
+
+2009-01-23  Ulrich Drepper  <drepper@redhat.com>
+
+	* size.c (process_file): When handling archive, close file descriptor
+	here.  For unknown file format also close file descriptor.
+	(handle_ar): Don't close file descriptor here.
+
+	* readelf.c (parse_opt): Move code to add to dump_data_sections and
+	string_sections list in local function add_dump_section.  Adjust 'x'
+	key handling.  For 'a' key add .strtab, .dynstr, and .comment section
+	to string_sections list.
+
+2009-01-22  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_phdr): Don't print section mapping when no sections.
+
+	* Makefile.am (AM_CFLAGS): Pass -Wno-format for *_no_Wformat.
+
+	* readelf.c (print_debug_frame_section): Initialize IS_SIGNED to false
+	and reset it only for the 'true' cases.
+
+	* Makefile.am (addr2line_no_Wformat): New variable.
+
+	* readelf.c (print_debug_frame_section): Use t instead of j formats
+	for ptrdiff_t OFFSET.
+
+2009-01-21  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_program_header): Fix typo in .eh_frame_hdr section
+	test.  Handle debuginfo files.
+	(check_exception_data): First sanity test.
+
+2009-01-17  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_debug_exception_table): Show target of ar_disp
+	field.
+
+	* elflint.c (check_program_header): Add most consistency checks for
+	PT_GNU_EH_FRAME entry.
+
+	* addr2line.c: Use ARGP_PROGRAM_VERSION_HOOK_DEF and
+	ARGP_PROGRAM_BUG_ADDRESS_DEF.
+	* ar.c: Likewise.
+	* elfcmp.c: Likewise.
+	* elflint.c: Likewise.
+	* findtextrel.c: Likewise.
+	* ld.c: Likewise.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* readelf.c: Likewise.
+
+	* size.c: Likewise.
+	* strings.c: Likewise.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise.
+
+2009-01-16  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_program_header): Check that PT_GNU_EH_FRAME entry
+	matches .eh_frame_hdr section, if it is available.  Also check that
+	the segment is allocated, not writable, not executable.
+
+	* readelf.c: Add -e option.  Dump exception and unwind related
+	sections.  Add -e to -a.
+	(print_encoding_base): Handle DW_EH_PE_omit.
+	(print_debug_exception_table): Beginning of support.
+	(print_debug): Hook up print_debug_exception_table for
+	.gcc_except_table sections.
+
+	* readelf.c (print_debug_frame_section): Some fixes for last change.
+
+2009-01-15  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_encoding): Now a toplevel function.
+	(print_relinfo): Likewise.
+	(print_encoding_base): Broken out of print_debug_frame_section.
+	(print_debug_frame_section): Print different header for .eh_frame
+	sections.  Fix recognition of matching CIEs in .debug_frame sections.
+	Print absolute offset for PC-relative FDE locations.  Don't print
+	table header for FDEs if the table is empty.
+	(read_encoded): New function.
+	(print_debug_frame_hdr_section): New function.
+	(print_debug): Hook up print_debug_frame_hdr_section for .eh_frame_hdr
+	sections.
+
+	* readelf.c (handle_relocs_rel): Print section number.
+	(print_debug_abbrev_section): Likewise.
+	(print_debug_aranges_section): Likewise.
+	(print_debug_ranges_section): Likewise.
+	(print_debug_info_section): Likewise.
+	(print_debug_line_section): Likewise.
+	(print_debug_loc_section): Likewise.
+	(print_debug_macinfo_section): Likewise.
+	(print_debug_pubnames_section): Likewise.
+	(print_debug_str_section): Likewise.
+
+2009-01-10  Ulrich Drepper  <drepper@redhat.com>
+
+	* strings.c (read_block): Fix typo in error message string.
+
+2009-01-07  Ulrich Drepper  <drepper@redhat.com>
+
+	* ld.c (ld_new_searchdir): Fix adding to search path list.
+
+2009-01-06  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c: Implement call frame debug section dumping.
+
+2009-01-05  Roland McGrath  <roland@redhat.com>
+
+	* elfcmp.c: Exit with status 2 for errors (like cmp, diff, grep).
+	Status 1 (aka EXIT_FAILURE) is only for completed OK but not equal.
+
+2009-01-01  Ulrich Drepper  <drepper@redhat.com>
+
+	* addr2line.c: Update copyright year.
+	* ar.c: Likewise.
+	* elfcmp.c: Likewise.
+	* elflint.c: Likewise.
+	* findtextrel.c: Likewise.
+	* ld.c: Likewise.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* strings.c: Likewise.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise.
+
+2008-12-11  Roland McGrath  <roland@redhat.com>
+
+	* nm.c (sym_name): New function.
+	(show_symbols_sysv): Use it in place of elf_strptr.
+	(show_symbols_bsd, show_symbols_posix): Likewise.
+	Fixes RHBZ#476136.
+
+	* nm.c (show_symbols_sysv): Use an alloca'd backup section name when
+	elf_strptr fails.
+
+2008-12-02  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (count_dwflmod, process_file): Don't presume encoding of
+	nonzero OFFSET argument to dwfl_getmodules.
+
+2008-08-07  Roland McGrath  <roland@redhat.com>
+
+	* addr2line.c (main): Pass string to handle_address.
+	(see_one_module): New function, subroutine of handle_address.
+	(find_symbol): Likewise.
+	(handle_address): Take string argument rather than address.
+	Convert plain number, or handle strings like "(section)+offset"
+	or "symbol+offset".
+
+2008-08-01  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (handle_core_item): Handle 'B' type for 1-origin bitset.
+	For 'b' and 'B', print <x-y,z> or ~<x,y-z> rather than 1/0 string.
+
+	* readelf.c (convert): Take new argument SIZE.
+	(handle_core_register, handle_core_item): Update callers.
+	(handle_core_item): Take new arg REPEATED_SIZE.
+	(handle_core_items): Special case for a singleton item,
+	let handle_core_item handle repeats if it wants to.
+
+	* readelf.c (handle_core_items): Give abridged output
+	for identical groups repeated more than twice.
+
+2008-07-04  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (handle_core_items): Handle ELF_T_ADDR.
+
+2008-04-10  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (handle_elf): Don't keep sections that kept symbol tables
+	refer to.  Instead, just be sure to preserve the original symbol
+	table in the debug file so those symbols go with their sections and
+	can be elided from the stripped version of the symbol table.
+
+	* strip.c (handle_elf): When a discarded section kept in the debug
+	file refers to a nondiscard section via sh_link/sh_info, preserve
+	that nondiscarded section unmodified in the debug file as well.
+	Skip adjustment of discarded sections symbol table references when
+	that symbol table is copied in this way.
+
+	* elflint.c (check_symtab): Don't crash from missing symbol names
+	after diagnosing bogus strtab.
+
+	* strip.c (handle_elf): Cosmetic cleanup in special section contents
+	adjustment for symtab changes.
+
+2008-03-31  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_sections): Add checks on SHF_EXECINSTR sections:
+	must be SHT_PROGBITS, must not be SHF_WRITE.  Let backend hook
+	excuse a special section.
+
+2008-03-27  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_sections): Check that executability and writability
+	of sections is reflected in segment p_flags.
+
+2008-03-26  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_program_header): Accept PT_GNU_RELRO p_flags
+	that matches its PT_LOAD's p_flags &~ PF_W.  On sparc, PF_X really
+	is valid in RELRO.
+
+2008-02-29  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_attributes): Add a cast.
+	* elflint.c (check_attributes): Likewise.
+
+	* unaligned.h (add_8ubyte_unaligned): Cast PTR argument for parity
+	with [UNALIGNED_ACCESS_CLASS == BYTE_ORDER] definition.
+	(add_4ubyte_unaligned, add_2ubyte_unaligned): Likewise.
+
+2008-02-03  Ulrich Drepper  <drepper@redhat.com>
+
+	* i386_ld.c (elf_i386_count_relocations): Implement R_386_TLS_GD
+	when linked into executable.
+	(elf_i386_create_relocations): Likewise.
+
+2008-02-20  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_attributes): New function.
+	(process_elf_file): Call it under -A.
+
+	* elflint.c (check_attributes): Implement it for real.
+
+2008-02-19  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (special_sections): Handle .gnu.attributes section.
+	(check_sections): Likewise.
+	(check_attributes): New function.
+
+2008-02-10  Roland McGrath  <roland@redhat.com>
+
+	* elfcmp.c (main): Ignore sh_offset differences in non-SHF_ALLOC
+	sections and ET_REL files.
+
+2008-02-02  Ulrich Drepper  <drepper@redhat.com>
+
+	* elf32-i386.script: Add .eh_frame_hdr, .tdata, and .tbss sections.
+	* i386_ld.c (elf_i386_count_relocations): Handle R_386_TLS_LDO_32
+	and R_386_TLS_LE.
+	(elf_i386_create_relocations): Likewise.
+	* ld.h (struct ld_state): Add need_tls, tls_start, and tls_tcb
+	elements.
+	* ldgeneric.c (add_section): If TLS section is used, set need_tls flag.
+	(ld_generic_create_outfile): Add PT_TLS entry to program  header.
+	Fix generation of PT_GNU_STACK entry.
+
+2008-02-01  Ulrich Drepper  <drepper@redhat.com>
+
+	* ld.c (replace_args): Prevent loop over replacements if the parameter
+	is only two characters long.
+
+	* ld.c: Recognize sha1 argument for --build-id parameter.
+	* ldgeneric.c (create_build_id_section): Handle sha1.
+	(compute_hash_sum): New function.  Broken out of compute_build_id.
+	Take hash function and context as parameters.
+	(compute_build_id): Use compute_hash_sum for md5 and the new sha1
+	implementation.
+
+2008-01-31  Ulrich Drepper  <drepper@redhat.com>
+
+	* elf32-i386.script: Add .note.ABI-tag and .note.gnu.build-id sections.
+	* ld.c: Recognize --build-id command line parameter.
+	* ld.h: Define scn_dot_note_gnu_build_id.
+	(struct ld_state): Add build_id and buildidscnidx elements.
+	* ldgeneric.c: Implement --build-id command line parameter.
+	* ldlex.l (ID): Recognize - as valid character after the first one.
+
+2008-01-29  Ulrich Drepper  <drepper@redhat.com>
+
+	* ld.c (replace_args): New function.
+	(main): Use it to rewrite old-style parameters.
+
+	* elf32-i386.script: Add .gnu.hash section.
+	* ldgeneric.c (optimal_bucket_size): A tiny bit more efficient.
+	(fillin_special_symbol): Initialize st_size.
+	(sortfct_hashval): New function.
+	(create_gnu_hash): New function.
+	(create_hash): New function.
+	(ld_generic_create_outfile): Use the new functions to create the
+	hash tables.
+
+	* elflint.c (check_gnu_hash): Fix index value printed in error message.
+
+2008-01-24  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_group): Check that signature symbol for section
+	group is not an empty string.
+	* ldgeneric.c: Remove magic assignment of indeces in the dynsym
+	section.  Start implementation of --hash-style.
+	* i386_ld.c: Likewise.
+	* ld.c: Recognize --hash-style.
+	* ld.h (struct scninfo): Add comdat_group.
+	Add additional parameter to finalize_plt callback.
+
+2008-01-22  Ulrich Drepper  <drepper@redhat.com>
+
+	* ld.h (struct callbacks): Add initialize_gotplt.
+	(struct scnhead): Add scn_dot_gotplt.
+	(struct ld_state): Add gotpltscnidx.
+	* i386_ld.c (elf_i386_initialize_plt): Minor optimization.
+	(elf_i386_initialize_pltrel): Likewise.
+	(elf_i386_initialize_got): There is now a separate .got.plt, so
+	don't do the PLT-related work here.  Initialize d_type.
+	(elf_i386_initialize_gotplt): New function.
+	(elf_i386_plt0): Use ud2a after indirect jump.
+	(elf_i386_pic_plt0_entry): Likewise.
+	(elf_i386_finalize_plt): Reference now .got.plt.
+	(elf_i386_count_relocations): For GOT entries which need no relocation
+	don't bump nrel_got.
+	(elf_i386_create_relocations): Also get .got.plt.  Rewrite R-386_GOT32
+	handling for split .got/.got.plt.
+	(elf_i386_ld_init): Initialize callbacks.initialize_gotplt.
+	* elf32-i386.script: Sort sections for security.  There are no .got
+	input sections.  Add .got.plt.
+	* ldgeneric.c (ld_generic_generate_sections): Add .got.plt section.
+	(ld_generic_create_outfile): Initialize .got.plt section.
+	Use .got.plt address for _GLOBAL_OFFSET_TABLE_ symbol and DT_PLTGOT.
+
+2008-01-19  Ulrich Drepper  <drepper@redhat.com>
+
+	* i386_ld.c (elf_i386_count_relocations): PLT relocations for undefined
+	symbols are not carried over into statically linked output files.
+	Add dummy entries for more TLS relocations.
+
+	* ld.c (options): Add long names for -( and -).
+
+	* ldgeneric.c (check_definition): For newly found definitions don't
+	mark section as used if symbol is absolute.
+	(extract_from_archive): Only assign archive sequence number the first
+	time the archive is handled.  Update ld_state.last_archive_used
+	if any symbol was used.  Remove nround variable.
+	(file_process2): When using symbol from an archive, update
+	ld_state.group_start_archive, ld_state.archives, and
+	ld_state.tailarchives.
+	(ld_generic_file_process): If group is not handled anymore, after
+	freeing ELF handles for the archives, clear ld_state.archives and
+	*nextp.  Fix wrong logic in recognizing first iteration of group
+	loop.  When clearing flags, also clear ld_state.group_start_archive.
+
+2008-01-11  Ulrich Drepper  <drepper@redhat.com>
+
+	* objdump.c (show_disasm): Adjust disassembler format string for
+	removal of %e.
+
+2008-01-04  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (handle_core_items): Take new arg DESCSZ; if nonzero,
+	a size greater than the items cover means multiple sets of items.
+	(handle_core_note): Update caller.
+
+2008-01-04  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (handle_elf): Move SHDRIDX defn to silence gcc warning.
+
+2008-01-03  Roland McGrath  <roland@redhat.com>
+
+	* ld.h (linked_from_dso_p): Use __attribute__ ((__gnu_inline__)).
+
+	* elflint.c (check_dynamic): Remove duplicate initializer.
+
+2008-01-02  Ulrich Drepper  <drepper@redhat.com>
+
+	* addr2line.c: Update copyright year.
+	* ar.c: Likewise.
+	* elfcmp.c: Likewise.
+	* elflint.c: Likewise.
+	* findtextrel.c: Likewise.
+	* ld.c: Likewise.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* strings.c: Likewise.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise.
+
+2007-12-30  Ulrich Drepper  <drepper@redhat.com>
+
+	* objdump (show_disasm): Use %e after third parameter.
+
+2007-12-21  Ulrich Drepper  <drepper@redhat.com>
+
+	* strip.c: Fix wrong parenthesis in a few branch predictions.
+	* strings.c: Likewise.
+
+2007-12-20  Ulrich Drepper  <drepper@redhat.com>
+
+	* Makefile.am (DEFS): Add DEBUGPRED.
+	* addr2line.c: Include debugpred.h.
+	* ar.c: Likewise.
+	* elfcmp.c: Likewise.
+	* elflint.c: Likewise.
+	* findtextrel.c: Likewise.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* strings.c: Likewise.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise.
+	* debugpred.h: New file.
+
+	* readelf.c (handle_relocs_rel): Use elf_scnshndx.
+	(handle_relocs_rela): Likewise.
+
+	* readelf.c: Add lots of likely/unlikely.
+
+	* elflint.c: Minor cleanups.
+
+2007-11-19  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_ops): Handle all bad op codes gracefully.
+	Print their numbers instead of just ???.
+
+2007-11-09  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (attr_callback): Handle DW_AT_data_location.
+	Handle block forms to mean a DWARF expression for DW_AT_allocated,
+	DW_AT_associated, DW_AT_bit_size, DW_AT_bit_offset, DW_AT_bit_stride,
+	DW_AT_byte_size, DW_AT_byte_stride, DW_AT_count, DW_AT_lower_bound,
+	DW_AT_upper_bound.
+
+2007-10-20  Roland McGrath  <roland@redhat.com>
+
+	* unstrip.c (options): Update -R description.
+	(struct symbol): Put symbol details a union with a size_t pointer
+	`duplicate'.
+	(compare_symbols_output): Use null ->name as marker for discard
+	symbols, not zero *->map.
+	(copy_elided_sections): Record forwarding pointers for discarded
+	duplicates and fill SYMNDX_MAP elements through them.
+
+	* readelf.c (process_file): Set offline_next_address to 0 at start.
+	(struct process_dwflmod_args): New type.
+	(process_dwflmod): Take args in it, pass fd to process_elf_file.
+	(process_file): Update caller; dup FD for passing to libdwfl.
+	(process_elf_file): Take new arg FD.  For ET_REL file when
+	displaying data affected by libdwfl relocation, open a new Elf handle.
+
+2007-10-17  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_debug_line_section): For invalid data inside a
+	unit with plausible length, keep printing at the next unit boundary.
+
+	* readelf.c (attr_callback): Use dwarf_formref_die, not dwarf_formref.
+
+2007-10-16  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (hex_dump): Fix rounding error in whitespace calculation.
+
+2007-10-15  Roland McGrath  <roland@redhat.com>
+
+	* make-debug-archive.in: New file.
+	* Makefile.am (EXTRA_DIST): Add it.
+	(make-debug-archive): New target.
+	(bin_SCRIPTS, CLEANFILES): Add it.
+
+2007-10-10  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (special_sections): Add new attrflag value exact_or_gnuld.
+	Use it to check MERGE|STRINGS for .debug_str.
+	(check_sections): Handle exact_or_gnuld.
+
+2007-10-08  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (handle_core_item): Handle 'T'|0x80 to indicate
+	64-bit struct timeval with 32-bit tv_usec.
+
+2007-10-07  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (check_archive_index): New function.
+	(process_file): Call it.  Change signature to take only fd and name.
+	Use libdwfl to open the file, then iterate on its modules (multiple
+	for an archive) to print file name and call process_elf_file.
+	(main): Update caller.  Let process_file do elf_begin.
+	(count_dwflmod, process_dwflmod, find_no_debuginfo): New functions.
+	(process_elf_file): Take only Dwfl_Module * argument.
+	Don't print the file name here.
+	(print_debug_*_section): Take Dwfl_Module * argument.
+	(print_debug): Likewise.  Update caller.
+	(format_dwarf_addr): New function.
+	(print_debug_ranges_section): Use it.
+	(attr_callback): Likewise.
+	(print_debug_line_section, print_debug_loc_section): Likewise.
+
+	* readelf.c (print_debug_ranges_section): Translate all strings.
+	(print_debug_loc_section): Likewise.
+
+	* unstrip.c (copy_elided_sections): Initialize SEC.
+
+	* ar.c (do_oper_insert): Put trailing / on short names.
+
+	* arlib.h (MAX_AR_NAME_LEN): Decrease by one.
+
+	* arlib2.c (arlib_add_long_name): Adjust for header size.
+
+	* arlib.c (arlib_finalize): Pad long name table to keep size even.
+
+	* ar.c (do_oper_insert): Use write_retry for padding write.
+
+	* ar.c (do_oper_insert): Initialize CUR_OFF in no_old case.
+	Unconditionally set FOUND[CNT]->elf when setting ->mem.
+	(remember_long_name): New function.
+	(do_oper_insert): Call it.  Correctly use length of basename,
+	not original name.  Don't store long name twice for new member.
+
+2007-10-06  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_note): Skip empty segment.
+	(check_note_section): Skip empty section.
+
+	* unstrip.c (options, parse_opt, struct arg_info): Grok -R/--relocate.
+	(handle_output_dir_module, handle_implicit_modules): Pass it down.
+	(handle_dwfl_module): When set, use ET_REL already loaded by Dwfl.
+	(compare_alloc_sections): Take new arg REL, ignore address if true.
+	(compare_sections): Likewise, pass it down.
+	(compare_sections_rel, compare_sections_nonrel): New functions.
+	(find_alloc_sections_prelink, copy_elided_sections): Use them
+	instead of compare_sections.
+	(sections_match): New function, broken out of ...
+	(find_alloc_section): ... here.
+	(copy_elided_sections): Reorganize section match-up logic.
+	Use sections_match for SHF_ALLOC in ET_REL.
+	For ET_REL, let the nonzero sh_addr from the debug file dominate.
+
+	* unstrip.c (add_new_section_symbols): Take new arg REL.
+	When true, do not update section symbol values.
+	(collect_symbols): Likewise.  Update section symbols with address
+	of chosen output section, not original section.
+	(check_symtab_section_symbols, copy_elided_sections): Update callers.
+
+	* unstrip.c (compare_alloc_sections): At the same address, preserve
+	original section order.
+
+	* elflint.c (special_sections): Don't require MERGE|STRINGS for
+	.debug_str, it didn't always have them with older tools.
+
+	* elflint.c (check_symtab, check_one_reloc): Ignore sh_addr in ET_REL.
+
+2007-10-05  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_symtab): Allow SHN_UNDEF _GLOBAL_OFFSET_TABLE_ in
+	ET_REL file.
+
+	* elflint.c (check_symtab): For _GLOBAL_OFFSET_TABLE_, diagnose
+	SHN_UNDEF as "bad section".  Use shndx value in messages.
+
+	* elflint.c (special_sections): Add ".debug_str".  Decrement namelen
+	for ".debug" so it matches as a prefix.
+	(IS_KNOWN_SPECIAL): New macro.
+	(check_sections): Use it for ".plt" match.  Cite wrong SHT_NOBITS
+	type even under -d, for a .debug* or .shstrtab section.
+
+	* readelf.c (print_ops): Use hex for address operand.
+
+2007-10-04  Roland McGrath  <roland@redhat.com>
+
+	* unstrip.c (copy_elided_sections): Initialize NDX_SECTION element for
+	.gnu_debuglink section to SHN_UNDEF.  Drop STT_SECTION symbols for
+	sections mapped to SHN_UNDEF.
+
+2007-10-04  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (dump_archive_index): Avoid warning about uninitialized
+	variable with older glibc versions.
+	Add some branch prediction.
+
+2007-10-04  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_archive_index): New variable.
+	(options, parse_opt): Accept -c/--archive-index to set it.
+	(dump_archive_index): New function.
+	(process_file): Take new arg WILL_PRINT_ARCHIVE_INDEX.
+	Call dump_archive_index on archives if set.
+	(main): Update caller.
+	(any_control_option): Give it file scope, moved out of ...
+	(parse_opt): ... here.
+
+2007-10-03  Roland McGrath  <roland@redhat.com>
+
+	* unstrip.c (struct arg_info): Add `list' flag.
+	(options, parse_opt): Grok -n/--list to set it.
+	(list_module): New function.
+	(handle_implicit_modules): Call it under -n.
+
+	* elflint.c (check_note_section): New function.
+	(check_sections): Call it for SHT_NOTE.
+
+	* readelf.c (handle_notes): Use sections when available.
+
+	* elflint.c (check_note_data): New function, broken out of ...
+	(check_note): ... here.  Call it and elf_getdata_rawchunk.
+
+	* readelf.c (handle_auxv_note): Take offset as argument, not buffer.
+	Use elf_getdata_rawchunk and gelf_getauxv.
+	(handle_notes_data): New function, broken out of ...
+	(handle_notes): ... here.  Call it and elf_getdata_rawchunk.
+
+2007-10-01  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (hex_dump): Fix transposed subtraction generating spaces.
+
+	* readelf.c (hex_dump): Fix line header to be hex instead of decimal.
+
+2007-09-10  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (options): Give -p optional argument, alias --string-dump.
+	(string_sections, string_sections_tail): New static variables.
+	(parse_opt): Set them when -p has an argument.
+	(print_string_section): New function, broken out of ...
+	(print_strings): ... here.  Call it.
+	(dump_data_section): New function, broken out of ...
+	(dump_data): ... here.  Call it.
+	(for_each_section_argument): New function, broken out of ...
+	(dump_data): ... here.  Call it.
+	(dump_strings): New function.
+
+2007-08-31  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_strings): Typo fix.
+
+2007-08-23  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (printf_with_wrap): Function removed.
+	(REGISTER_WRAP_COLUMN): New macro.
+	(handle_core_register): Use print_core_item instead.
+	(struct register_info): New type.
+	(compare_registers, compare_register_sets): New functions.
+	(register_bitpos, compare_sets_by_info): New functions.
+	(handle_core_registers): Use those to segregate and sort registers
+	for display.
+
+	* readelf.c (ITEM_WRAP_COLUMN): New macro.
+	(print_core_item): New function.
+	(handle_core_item): Use it instead of printf_with_wrap.
+	(compare_core_items, compare_core_item_groups): New functions.
+	(handle_core_items): Use them.  Sort by group and force line breaks
+	between groups.
+
+	* readelf.c (handle_core_registers, handle_core_items): New functions,
+	broken out of ...
+	(handle_core_note): ... here.   Call them.
+
+2007-08-22  Roland McGrath  <roland@redhat.com>
+
+	* unstrip.c (new_shstrtab): New function, broken out of ...
+	(copy_elided_sections): ... here.
+
+2007-08-20  Roland McGrath  <roland@redhat.com>
+
+	Avoid local function trampolines in nm binary.
+	* nm.c (sort_by_address): Move to a static function instead of local
+	inside show_symbols.
+	(sort_by_name_strtab): New static variable.
+	(sort_by_name): Use it.  Move to a static function instead of local
+	inside show_symbols.
+	(show_symbols): Set sort_by_name_strtab.
+
+2007-08-19  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (handle_auxv_note): New function.
+	(handle_notes): Call it.
+
+	* readelf.c (printf_with_wrap, convert): New functions.
+	(handle_core_item, (handle_core_register): New functions.
+	(handle_notes): Call those with details from ebl_core_note.
+
+2007-08-12  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_note): Accept type 0 with name "Linux".
+
+	* elflint.c (special_sections): Accept SHF_ALLOC for ".note".
+
+	* elflint.c (section_flags_string): Return "none" for 0, not "".
+
+2007-08-11  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_note): Accept NT_GNU_HWCAP, NT_GNU_BUILD_ID.
+
+2007-08-04  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (hex_dump): Use isprint to determine whether to print
+	character itself or full stop character.
+	(dump_data): No need to check endp for NULL after strtol call.
+
+2007-08-03  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_string_sections): New variable.
+	(options, parse_opt): Handle --strings/-p to set it.
+	(print_strings): New function.
+	(process_elf_file): Call it under -p.
+
+	* readelf.c (options): Add hidden aliases --segments, --sections,
+	as taken by binutils readelf.
+
+2007-08-01  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (dump_data_sections, dump_data_sections_tail):
+	New variables.
+	(options, parse_opt): Handle --hex-dump/-x, set them.
+	(hex_dump): New function.
+	(dump_data): New function, call it.
+	(process_elf_file): Call it.
+
+2007-07-25  Roland McGrath  <roland@redhat.com>
+
+	* addr2line.c (show_symbols): New variable.
+	(print_addrsym): New function.
+	(handle_address): Call it.
+	(options, parse_opt): Handle -S/--symbols.
+
+2007-06-05  Ulrich Drepper  <drepper@redhat.com>
+
+	* addr2line.c: Update for latest autoconf header.
+	* ar.c: Likewise.
+	* elfcmp.c: Likewise.
+	* elflint.c: Likewise.
+	* findtextrel.c: Likewise.
+	* ld.c: Likewise.
+	* ldgeneric.c: Likewise.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* strings.c: Likewise.
+	* strip.c: Likewise.
+	* unstrip.c: Likewise.
+
+2007-05-18  Roland McGrath  <roland@redhat.com>
+
+	* unstrip.c (copy_elided_sections): Match up non-NOBITS sections with
+	stripped file, so as not to duplicate a section copied in both.
+
+	* strip.c (handle_elf): Keep SHT_NOTE section copies in the debug file.
+
+2007-05-17  Roland McGrath  <roland@redhat.com>
+
+	* unstrip.c (copy_elided_sections): Don't call gelf_newphdr for 0.
+
+	* unstrip.c (handle_file): Tweak BIAS != 0 warning.
+
+	* unstrip.c (handle_file): Take new arg CREATE_DIRS.  If set,
+	call make_directories here.
+	(handle_explicit_files): Take new arg CREATE_DIRS, pass it down.
+	(handle_dwfl_module): Likewise.
+	(handle_implicit_modules): Update callers.
+	(handle_output_dir_module): Likewise.  Don't do make_directories here.
+
+	* unstrip.c (get_section_name): New function, broken out of ...
+	(copy_elided_sections): here.  Update callers.
+	(find_alloc_section): Broken out of ...
+	(copy_elided_sections): ... here.  Update caller.
+	(symtab_count_leading_section_symbols): Take new arg NEWSYMDATA,
+	update STT_SECTION symbols' st_value fields as a side effect.
+	(check_symtab_section_symbols): Update caller.
+	(add_new_section_symbols): Set st_value in symbols added.
+	(collect_symbols): Reset S->value for STT_SECTION symbols recorded.
+	Take new arg SPLIT_BSS.  Adjust S->shndx recorded for symbols moved
+	from .bss to .dynbss.
+	(find_alloc_sections_prelink): New function.  Associate debug file
+	allocated SHT_NOBITS shdrs with stripped moved by prelink via
+	.gnu.prelink_undo information.
+	(copy_elided_sections): Call it when we couldn't find every allocated
+	section.  Don't use a debug file non-NOBITS section if SHF_ALLOC.
+	Take STRIPPED_EHDR arg instead of E_TYPE and PHNUM.
+	(handle_file): Update callers.
+
+	* unstrip.c (copy_elided_sections): Ignore unfound unallocated section
+	named ".comment".
+
+	* elflint.c (check_sections): Fix association of segments with
+	sections when p_memsz > p_filesz.
+
+2007-04-29  Roland McGrath  <roland@redhat.com>
+
+	* addr2line.c (options, main): Tweak argp group settings to fix
+	usage output.
+
+2007-04-28  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (handle_elf): Update debug file's SHT_NOBITS sections'
+	sizes to match sections adjusted in the stripped file.
+
+2007-04-24  Roland McGrath  <roland@redhat.com>
+
+	* elfcmp.c (OPT_HASH_INEXACT): New macro.
+	(hash_inexact): New variable.
+	(options, parse_opt): Add --hash-inexact option to set it.
+	(hash_content_equivalent): New function.
+	(main): Call it for differing SHT_HASH sections under --hash-inexact.
+
+2007-04-23  Roland McGrath  <roland@redhat.com>
+
+	* unstrip.c: New file.
+	* Makefile.am (bin_PROGRAMS): Add it.
+	(unstrip_LDADD): New variable.
+
+	* strip.c (options): Allow --output for -o.
+
+2007-02-15  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c: Remove unused code.  Add a few consts.
+
+2007-02-15  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_debug): Fix brainos in SHDR test.
+
+2007-02-05  Roland McGrath  <roland@redhat.com>
+
+	* ar.c: Include <limits.h>, since we use LONG_MAX.
+
+2007-02-05  Ulrich Drepper  <drepper@redhat.com>
+
+	* ar.c: Add ugly hack to work around gcc complaining that we
+	ignore fchown's return value.
+	(do_oper_insert): Handle error when writing padding.
+	* ranlib.c: Add fchown complain work around.
+
+	* arlib.c: Make symtab a global variable.  Change all users.
+	* arlib2.c: Likewise.
+	* ranlib.c: Likewise.
+	* ar.c: Likewise.
+	* arlib.h: Declare it.
+
+2007-01-11  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_sections): Use ebl_machine_section_flag_check on
+	SHF_MASKPROC bits separately from generic sh_flags validation.
+
+2007-02-04  Ulrich Drepper  <drepper@redhat.com>
+
+	* ar.c: New file.
+	* arlib.c: New file.
+	* arlib2.c: New file.
+	* arlib.h: New file.
+	* Makefile (noinst_LIBRARIES): Add libar.
+	(libar_a_SOURCES): Define.
+	(ar_LDADD): Define.
+	(CFLAGS_ar): Define.
+	* ranlib.c: Change to use arlib.
+
+	* elflint.c (check_symtab): Work around GNU ld bug which omits
+	sections but not symbols in those sections.
+
+2007-01-10  Ulrich Drepper  <drepper@redhat.com>
+
+	* addr2line.c: Update copyright year.
+	* elfcmp.c: Likewise.
+	* elflint.c: Likewise.
+	* findtextrel.c: Likewise.
+	* ld.c: Likewise.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c:  Likewise.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* strings.c: Likewise.
+	* strip.c: Likewise.
+
+2006-12-09  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (compare_hash_gnu_hash): New function.  Report if the
+	two hash tables have different content (module expected omission
+	of undefined symbols).
+
+2006-10-31  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_program_header): Don't complain about
+	p_filesz > p_memsz if p_memsz is zero and p_type is PT_NOTE.
+
+2006-09-19  Jakub Jelinek  <jakub@redhat.com>
+
+	* strip.c (process_file): Disallow -f on archives.
+
+2006-10-09  Roland McGrath  <roland@redhat.com>
+
+	* Makefile.am (libld_elf_i386.so): Use $(LINK), not $(CC).
+
+2006-08-29  Roland McGrath  <roland@redhat.com>
+
+	* Makefile.am (MAINTAINERCLEANFILES): New variable.
+
+	* readelf.c (handle_relocs_rel): Typo fix, test DESTSHDR properly.
+	Reported by Christian Aichinger <Greek0@gmx.net>.
+
+	* elflint.c (valid_e_machine): Add EM_ALPHA.
+	Reported by Christian Aichinger <Greek0@gmx.net>.
+
+2006-08-08  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_dynamic): Don't require DT_HASH for DT_SYMTAB.
+	Keep track of which "high DT" entries are present.
+	Check that either old or GNU-style hash table is present.
+	If GNU-style hash table is used a symbol table is mandatory.
+	Check that if any prelink entry is present all of them are.
+	(check_gnu_hash): Only fail for undefined symbols in GNU-style hash
+	table if they don't refer to functions.
+
+2006-07-17  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (struct version_namelist): Use GElf_Versym for `ndx' field.
+	(add_version): Likewise for argument.
+	(check_versym): Cast constant to GElf_Versym for comparison.
+
+2006-07-12  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (handle_gnu_hash): Add casts for machines where
+	Elf32_Word != unsigned int.
+
+2006-07-12  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_sysv_hash64): Fix printf format.
+
+2006-07-11  Roland McGrath  <roland@redhat.com>
+
+	* addr2line.c (options): English fix in -f doc string.
+
+	* addr2line.c (use_comp_dir): New variable.
+	(options, parse_opt): Grok -A/--absolute to set it.
+	(handle_address): If set, prepend dwfl_line_comp_dir results to
+	relative file names.
+
+2006-07-06  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c: Adjust for latest new hash table format.
+	* readelf.c: Likewise.
+
+	* elflint.c (check_versym): Ignore hidden bit when comparing version
+	numbers.
+
+2006-07-05  Ulrich Drepper  <drepper@redhat.com>
+
+	* ldgeneric.c (ld_generic_create_outfile): Correctly recognize
+	discarded COMDAT symbols.
+
+	* i386_ld.c (elf_i386_count_relocations): Lot of corrections.
+	(elf_i386_create_relocations): Likewise.
+	* ld.h (struct symbol): Add local and hidden bits.
+	* ld.c (create_special_section_symbol): These synthsized symbols
+	are local and hidden.
+	* ldgeneric.c (file_process2): Check whether input file matches
+	the emulation.
+	(fillin_special_symbol): Create symbols as local and/or hidden
+	if requested.
+	(ld_generic_create_outfile): Make local copy of symbol.
+	Don't hide global, defined symbols in dynamic symbol table unless
+	requested.  Synthetic symbols have no version information.
+
+	* elflint.c: Add support for checking 64-bit SysV-style hash tables.
+	* readelf.c: Add support for printing 64-bit SysV-style hash tables.
+
+2006-07-04  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (is_rel_dyn): Fix and extend DT_RELCOUNT/DT_RELACOUNT
+	testing.
+
+2006-07-03  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c: Add testing of DT_GNU_HASH.
+	* readelf.c: Implement showing histogram for DT_GNU_HASH section.
+
+	* Makefile.am: Add hacks to create dependency files for non-generic
+	linker.
+
+2006-06-12  Ulrich Drepper  <drepper@redhat.com>
+
+	* ldgeneric.c (ld_generic_generate_sections): Don't create .interp
+	section if creating a DSO and no interpreter is given.
+	(ld_generic_create_outfile): Don't store reference to symbols in
+	discarded COMDAT groups.  Don't create PHDR and INTERP program header
+	for DSO if no interpreter is specified.
+	(create_verneed_data): Pretty printing.
+
+	* ldscript.y (content): If a DSO is created don't set default
+	interpreter from linker script.
+
+	* i386_ld.c (elf_i386_count_relocations): Do not add relocations
+	for symbols in discarded COMDAT groups.
+	(elf_i386_create_relocations): Likewise.
+	* ld.h (struct scninfo): Add unused_comdat.
+	* ldgeneric.c (add_section): Also check group signature when
+	matching COMDAT sections.
+	(add_relocatable_file): Ignore symbols in COMDAT group which are
+	discarded.
+
+	* elflint.c (check_one_reloc): For *_NONE relocs only check type
+	and symbol reference.
+
+2006-06-11  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_dynamic): Fix checking value of tags which are
+	offsets in the string section.  Make sure DT_STRTAB points to the
+	section referenced in sh_link.
+
+	* ld.c (options): Add headers.  Add short option 'R' for '--rpath'.
+
+	* ld.c: Recognize --eh-frame-hdr option.
+	* ld.h (struct ld_state): Add eh_frame_hdr field.
+	* ldgeneric.c (struct unw_eh_frame_hdr): Define.
+
+	* ldgeneric.c (add_section): Use ebl_sh_flags_combine instead of
+	SH_FLAGS_COMBINE.
+	(add_relocatable_file): Minor optimization of last change.
+	(match_section): Don't preserve SHF_GROUP flag any longer.
+
+2006-06-10  Ulrich Drepper  <drepper@redhat.com>
+
+	* ld.c (parse_z_option): Recognize execstack and noexecstack.
+	Handle record and ignore as position dependent options.
+	(parse_z_option_2): Handle ignore and record here.
+	* ld.h (struct ld_state): Add execstack field.
+	* ldgeneric.c (add_relocatable_file): Recognize .note.GNU-stack
+	sections.
+	(ld_generic_create_outfile): Fix program header creation in native
+	linker.  Add PT_GNU_STACK program header.
+
+2006-06-09  Ulrich Drepper  <drepper@redhat.com>
+
+	* i386_ld.c (elf_i386_finalize_plt): Don't change symbol table entries
+	for PLT entries if there is no local definition.
+
+	* ld.c (parse_option): Handle -z ignore like --as-needed and
+	-z record like --no-as-needed.
+	* ld.h (struct ld_state): Remove ignore_unused_dsos field.
+	* ldgeneric.c (new_generated_scn): Always compute ndt_needed by
+	looping over DSOs.  When deciding about adding DT_NEEDED entries
+	use ->as_needed instead of ignore_unused_dsos.
+
+2006-05-31  Ulrich Drepper  <drepper@redhat.com>
+
+	* ld.c: Recognize --as-needed and --no-as-needed options.
+	* ld.h (struct usedfile): Add as_needed field.
+	(struct ld_state): Likewise.
+	* ldgeneric.c (ld_handle_filename_list): Copy as_needed flag from
+	the list.
+	* ldscript.y (filename_id_list): Split to correctly parse all
+	combinations.
+	(mark_as_needed): Fix loop.
+
+2006-05-28  Ulrich Drepper  <drepper@redhat.com>
+
+	* addr2line.c (print_dwarf_function): Use unsigned type for lineno
+	and colno.
+
+2006-05-27  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (handle_relocs_rela): Better notations for addon value.
+	(print_ehdr): Distinguish e_ident[EI_VERSION] from e_version.
+
+2006-04-04  Ulrich Drepper  <drepper@redhat.com>
+
+	* addr2line.c: Update copyright year.
+	* elfcmp.c: Likewise.
+	* elflint.c: Likewise.
+	* findtextrel.c: Likewise.
+	* ld.c: Likewise.
+	* nm.c: Likewise.
+	* objdump.c: Likewise.
+	* ranlib.c: Likewise.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* strings.c: Likewise.
+	* strip.c: Likewise.
+
+2006-03-09  Roland McGrath  <roland@redhat.com>
+
+	* Makefile.am (AM_LDFLAGS): New variable.
+
+2006-03-01  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (dwarf_tag_string, dwarf_attr_string): Update name tables
+	for dwarf.h changes matching 3.0 spec.
+	(dwarf_encoding_string, dwarf_lang_string, print_ops): Likewise.
+
+2005-12-04  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_one_reloc): If relocation section is not loaded,
+	don't check whether the relocations modify read-only sections or
+	loaded and unloaded sections.
+
+2005-11-28  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_one_reloc): Take additional parameters.  Use
+	them to determine whether relocation is valid in this type of
+	file.  DSOs and executables can contain relocation sections in
+	unloaded sections which just show the relocations the linker
+	applied.  Adjust all callers.
+	(check_program_header): Check that PT_PHDR is loaded and that offset
+	matches the one in the ELF header.
+
+2005-10-26  Roland McGrath  <roland@redhat.com>
+
+	* nm.c (get_var_range): dwarf_getloclist -> dwarf_getlocation.
+
+2005-09-03  Ulrich Drepper  <drepper@redhat.com>
+
+	* strip.c (handle_elf): Unify some error messages.
+	* ld.c (main): Likewise.
+	* ldgeneric.c (open_elf): Likewise.
+	* elfcmp.c (main): Likewise.
+	* elflint.c (check_elf_header): Likewise.
+
+	* size.c (process_file): Fix typo in error message.
+
+	* readelf.c: Lots of little cleanups.  Use _unlocked functions.
+
+2005-09-02  Ulrich Drepper  <drepper@redhat.com>
+
+	* strings.c (main): Reset elfmap variable after munmap call.
+	[_MUDFLAP] (map_file): Simplify mudflap debugging by not using mmap.
+
+2005-08-28  Ulrich Drepper  <drepper@redhat.com>
+
+	* ranlib.c: Don't define pread_retry and write_retry here.
+
+	* Makefile.an [BUILD_STATIC] (libdw): Add -ldl.
+	(CLEANFILES): Add *.gcno *.gcda *.gconv.
+
+	* strings.c (process_chunk): Reorder expressions in conditional
+	(process_chunk_mb): Likewise.
+
+	* strings.c: New file.
+	* Makefile.am (bin_PROGRAMS): Add strings.
+	(strings_no_Wstring): Define.
+	(strings_LDADD): Define.
+
+2005-08-27  Roland McGrath  <roland@redhat.com>
+
+	* addr2line.c (dwarf_diename_integrate): Function removed.
+	(print_dwarf_function): Use plain dwarf_diename.
+
+2005-08-24  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_versym): Versioned symbols should not have
+	local binding.
+
+2005-08-15  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_versym): Allow VER_NDX_LOCAL symbols to be
+	undefined.
+
+	* Makefile.am: Add rules to build ranlib.
+	* ranlib.c: New file.
+
+2005-08-14  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_sections): Use ebl_section_type_name and allow any
+	sh_type it recognizes.
+
+	* elflint.c (check_sections): Print unknown flags in hex, don't
+	truncate high bits.  Print section number and name for unknown type.
+
+2005-08-13  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_program_header): Use ebl_segment_type_name and
+	allow any p_type it recognizes.  Include p_type value in error
+	message for unknown type.
+
+2005-08-13  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_symtab): Simplify last change a bit.  Pass ehdr
+	to ebl_check_special_symbol.
+	(check_sections): Pass ehdr to ebl_bss_plt_p.
+
+2005-08-12  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (check_symtab): Check that _GLOBAL_OFFSET_TABLE_ st_shndx
+	refers to the right section if it's not SHN_ABS.
+	Let ebl_check_special_symbol override _G_O_T_ value and size checks.
+
+	* elflint.c (check_sections): Don't complain about a non-NOBITS
+	section taking no segment space, if it's sh_size is 0.
+
+	* elflint.c (check_sections): Use ebl_bss_plt_p to see if .plt should
+	be PROGBITS or NOBITS.
+
+	* elflint.c (check_symtab): Use ebl_check_special_symbol to override
+	standard st_value and st_size checks.
+
+2005-07-28  Roland McGrath  <roland@redhat.com>
+
+	* addr2line.c (options, parse_opt): Don't handle -e here.
+	(executable): Variable removed.
+	(argp_children): New static variable.
+	(argp): Use it.  Make const.
+	(main): Fill in argp_children from dwfl_standard_argp ().
+	Let libdwfl handle file selection, pass Dwfl handle to handle_address.
+	(print_dwarf_function): New function.  Try to figure out inline chain.
+	(elf_getname): Function removed, libdwfl does it for us.
+	(handle_address): Take Dwfl handle instead of Elf, Dwarf handles.
+	Use dwfl_module_addrname instead of elf_getname.
+	Use dwfl_module_getsrc and dwfl_lineinfo instead of libdw calls.
+	* Makefile.am (INCLUDES): Add libdwfl directory to path.
+
+2005-08-10  Ulrich Drepper  <drepper@redhat.com>
+
+	* strip.c (parse_opt): STATE parameter is now used.
+	Various little cleanups.
+
+	* readelf.c (print_debug_line_section): Correct fallout of renaming
+	of DW_LNS_set_epilog_begin.
+
+2005-08-08  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (options, parse_opt): Grok -R .comment for compatibility
+	with binutils strip.  Likewise -d, -S, as aliases for -g.
+	Likewise ignore -s/--strip-all.
+
+2005-08-07  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (process_file): Open read-only when using a different output
+	file.
+
+2005-08-06  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (in_nobits_scn): New function.
+	(check_versym): Allow references for defined symbols against versions
+	of other DSOs also for symbols in nobits sections.
+	Move a few variables around.
+
+	* Makefile.am (AM_CFLAGS): Avoid duplication.
+	Link with statis libs if BUILD_STATIC.
+
+2005-08-05  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c: Many, many more tests.  Mostly related to symbol
+	versioning.  Those sections should now be completely checked.
+
+	* readelf.c (print_dynamic): Use gelf_offscn.
+
+2005-08-04  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c: Add lots more tests: more extension symbol table sanity,
+	versioning section tests, hash table tests.  General cleanup.
+
+2005-08-02  Ulrich Drepper  <drepper@redhat.com>
+
+	* objdump.c: New file.
+	* Makefile.am (bin_PROGRAMS): Add objdump.
+	(objdump_LDADD): Define.
+
+	* elflint.c (check_reloc_shdr): New function split out from check_rela
+	and check_rel.
+	(check_one_reloc): New function.  Likewise.
+	(check_rela): Use check_reloc_shdr and check_one_reloc.
+	(check_rel): Likewise.
+	(check_program_header): Check that PT_DYNAMIC entry matches .dynamic
+	section.
+	Add checks that relocations against read-only segments are flagged,
+	that the text relocation flag is not set unnecessarily, and that
+	relocations in one section are either against loaded or not-loaded
+	segments.
+
+2005-08-01  Ulrich Drepper  <drepper@redhat.com>
+
+	* elfcmp.c (main): Ignore section count and section name string table
+	section index.
+
+2005-07-27  Roland McGrath  <roland@redhat.com>
+
+	* elfcmp.c: Include <locale.h>.
+
+2005-07-27  Ulrich Drepper  <drepper@redhat.com>
+
+	* elfcmp.c: Print name and index of differing section.
+
+2005-07-24  Ulrich Drepper  <drepper@redhat.com>
+
+	* elfcmp.c: Implement comparing gaps between sections.
+
+2005-07-23  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c: Include libeblP.h instead of libebl.h.
+	* nm.c: Likewise.
+	* readelf.c: Likewise.
+	* elfcmp.c: Likewise.
+
+	* elfcmp.c (main): Compare individual ELF header fields, excluding
+	e_shoff instead of the whole struct at once.
+	Use ebl_section_strip_p instead of SECTION_STRIP_P.
+	* strip.c: Use ebl_section_strip_p instead of SECTION_STRIP_P.
+
+2005-07-22  Ulrich Drepper  <drepper@redhat.com>
+
+	* elfcmp.c (main): Take empty section into account when comparing
+	section content.
+
+	* elflint.c (check_dynamic): Check that d_tag value is >= 0 before
+	using it.
+
+2005-07-21  Ulrich Drepper  <drepper@redhat.com>
+
+	* elfcmp.c: New file.
+	* Makefile.am (bin_PROGRAMS): Add elfcmp.
+	(elfcmp_LDADD): Define.
+
+	* elflint.c (check_rela): Check that copy relocations only reference
+	object symbols or symbols with unknown type.
+	(check_rel): Likewise.
+
+2005-06-08  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_ops): Add consts.
+
+2005-05-31  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_debug_abbrev_section): Don't bail after first CU's
+	abbreviations.  Print a header line before each CU section.
+
+	* readelf.c (print_debug_loc_section): Fix indentation for larger
+	address size.
+
+2005-05-30  Roland McGrath  <roland@redhat.com>
+
+	* readelf.c (print_debug_line_section): Print section offset of each
+	CU's table, so they are easy to find from seeing the stmt_list value.
+
+	* readelf.c (dwarf_attr_string): Add all attributes in <dwarf.h>.
+	(attr_callback): Grok DW_AT_ranges and print offset in hex.
+
+	* readelf.c (attr_callback): Add 2 to addrsize * 2 for %#0* format.
+	(print_debug_ranges_section, print_debug_loc_section): Likewise.
+
+	* readelf.c (print_ops): Take different args for indentation control.
+	(attr_callback): Caller updated.
+	Grok several more block-form attributes as being location expressions.
+	For those same attributes with udata forms, format output differently
+	for location list offset.
+	(print_debug_loc_section): Implement it for real.
+
+	* readelf.c (options): Mention ranges for --debug-dump.
+	(enum section_e): Add section_ranges.
+	(parse_opt): Grok "ranges" for -w/--debug-dump.
+	(print_debug_ranges_section): New function.
+	(print_debug): Handle .debug_ranges section.
+
+2005-05-30  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (handle_notes): At least x86-64 need not have the note
+	section values aligned to 8 bytes.
+
+2005-05-18  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (dwarf_tag_string): Add new tags.
+
+2005-05-08  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (handle_elf): Don't translate hash and versym data formats,
+	elf_getdata already did it for us.
+
+2005-05-07  Ulrich Drepper  <drepper@redhat.com>
+
+	* Makefile.am (findtextrel_LDADD): Add $(libmudflap).
+	(addr2line_LDADD): Likewise.
+
+2005-05-03  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (handle_elf): Apply symbol table fixups to discarded
+	relocation sections when they are being saved in the debug file.
+
+	* strip.c (handle_elf): Pass EHDR->e_ident[EI_DATA] to gelf_xlatetom
+	and gelf_xlatetof, not the native byte order.
+
+	* strip.c (parse_opt): Give error if -f or -o is repeated.
+	(main): Exit if argp_parse returns nonzero.
+
+	* strip.c (debug_fname_embed): New variable.
+	(options, parse_opt): New option -F to set it.
+
+2005-05-07  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (parse_opt): Make any_control_option variable
+	local.  Simplify some tests.
+
+2005-05-03  Roland McGrath  <roland@redhat.com>
+
+	* strip.c (crc32_file): Function removed (now in ../lib).
+
+2005-05-03  Roland McGrath  <roland@redhat.com>
+
+	* elflint.c (is_debuginfo): New variable.
+	(options, parse_opt): New option --debuginfo/-d to set it.
+	(check_sections): If is_debuginfo, don't complain about SHT_NOBITS.
+	(check_note): If is_debuginfo, don't try to get note contents.
+
+2005-04-24  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_debug_abbrev_section): Don't print error when end of
+	section reached.
+
+2005-04-14  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (dwarf_encoding_string): New function.
+	(dwarf_inline_string): New function.
+	(dwarf_access_string): New function.
+	(dwarf_visibility_string): New function.
+	(dwarf_virtuality_string): New function.
+	(dwarf_identifier_case_string): New function.
+	(dwarf_calling_convention_string): New function.
+	(dwarf_ordering_string): New function.
+	(dwarf_discr_list_string): New function.
+	(attr_callback): Decode man more attribute values.
+
+2005-04-01  Ulrich Drepper  <drepper@redhat.com>
+
+	* addr2line.c: Finish implementation of -f option.
+
+2005-03-29  Ulrich Drepper  <drepper@redhat.com>
+
+	* addr2line.c: New file.
+	* Makefile.am (bin_PROGRAMS): Add addr2line.
+	Define addr2line_LDADD.
+
+	* findtextrel.c: Use new dwarf_addrdie function.
+
+	* findtextrel.c: Fix usage message and re-add accidentally removed
+	line.
+
+2005-03-28  Ulrich Drepper  <drepper@redhat.com>
+
+	* findtextrel.c: New file.
+	* Makefile: Add rules to build findtextrel.
+
+2005-02-15  Ulrich Drepper  <drepper@redhat.com>
+
+	* ldlex.l: Provide ECHO definition to avoid warning.
+
+	* elflint.c (check_program_header): Fix typo in RELRO test.
+
+	* Makefile.am (AM_CFLAGS): Add more warning options.
+	* elflint.c: Fix warnings introduced by the new warning options.
+	* i386_ld.c: Likewise.
+	* ld.c: Likewise.
+	* ld.h: Likewise.
+	* ldgeneric.c: Likewise.
+	* nm.c: Likewise.
+	* readelf.c: Likewise.
+	* sectionhash.c: Likewise.
+	* size.c: Likewise.
+	* string.c: Likewise.
+
+2005-02-05  Ulrich Drepper  <drepper@redhat.com>
+
+	* Makefile.am: Check for text relocations in constructed DSOs.
+
+	* Makefile.am [MUDFLAP] (AM_CFLAGS): Add -fmudflap.  Link all apps
+	with -lmudflap.
+
+	* ldscript.y: Add as_needed handling.
+	* ldlex.l: Recognize AS_NEEDED token.
+	* ld.h (struct filename_list): Add as_needed flag.
+
+2005-02-04  Ulrich Drepper  <drepper@redhat.com>
+
+	* elflint.c (check_symtab): Correctly determine size of GOT section.
+
+2005-01-19  Ulrich Drepper  <drepper@redhat.com>
+
+	* ld.c: Remove unnecessary more_help function.  Print bug report
+	address using argp.
+	* strip.c: Likewise.
+	* size.c: Likewise.
+	* nm.c: Likewise.
+	* readelf.c: Likewise.
+	* elflint.c: Likewise.
+
+	* elflint.c (main): Don't check for parameter problems here.
+	(parse_opt): Do it here, where we get informed about some of them
+	anyway.
+
+	* readelf.c (main): Don't check for parameter problems here.
+	(parse_opt): Do it here, where we get informed about some of them
+	anyway.
+
+2005-01-11  Ulrich Drepper  <drepper@redhat.com>
+
+	* strip.c: Update copyright year.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* nm.c: Likewise.
+	* ld.c: Likewise.
+	* elflint.c: Likewise.
+
+	* elflint.c (check_symtab): Don't warn about wrong size for
+	_DYNAMIC and __GLOBAL_OFFSET_TABLE__ for --gnu-ld.
+
+2004-10-05  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_phdr): In section mapping, also indicate
+	sections in read-only segments.
+
+2004-09-25  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c: Make compile with gcc 4.0.
+	* strip.c: Likewise.
+
+2004-08-16  Ulrich Drepper  <drepper@redhat.com>
+
+	* strip.c (handle_elf): Rewrite dynamic memory handling to use of
+	allocate to work around gcc 3.4 bug.
+
+2004-01-25  Ulrich Drepper  <drepper@redhat.com>
+
+	* ldlex.l (invalid_char): Better error message.
+
+2004-01-23  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c: Print SHT_GNU_LIBLIST sections.
+
+	* none_ld.c: New file.
+
+2004-01-21  Ulrich Drepper  <drepper@redhat.com>
+
+	* Makefile.am: Enable building of machine specific linker.
+
+2004-01-20  Ulrich Drepper  <drepper@redhat.com>
+
+	* Makefile.am: Support building with mudflap.
+
+	* i386_ld.c: Fix warnings gcc 3.4 spits out.
+	* ldgeneric.c: Likewise.
+	* ldscript.y: Likewise.
+	* readelf.c: Likewise.
+	* strip.c: Likewise.
+
+	* readelf.c (print_debug_line_section): Determine address size
+	correctly.
+
+2004-01-19  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_phdr): Show which sections are covered by the
+	PT_GNU_RELRO entry.
+
+	* elflint.c (check_program_header): Check PT_GNU_RELRO entry.
+
+	* readelf.c (print_debug_macinfo_section): Implement.
+
+2004-01-18  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_debug_line_section): Implement.
+
+2004-01-17  Ulrich Drepper  <drepper@redhat.com>
+
+	* src/elflint.c: Use PACKAGE_NAME instead of PACKAGE.
+	* src/ld.c: Likewise.
+	* src/nm.c: Likewise.
+	* src/readelf.c: Likewise.
+	* src/size.c: Likewise.
+	* src/strip.c: Likewise.
+
+	* strip.c: Add a few more unlikely.  Reduce scope of some variables.
+
+	* Makefile.am: Support building with mudflap.
+
+2004-01-16  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_debug_info_section): Free dies memory.
+
+	* readelf.c: Print .debug_info section content.
+
+2004-01-13  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_shdr): Add support for SHF_ORDERED and SHF_EXCLUDE.
+
+2004-01-12  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (print_debug_aranges): Implement using libdw.
+
+2004-01-11  Ulrich Drepper  <drepper@redhat.com>
+
+	* nm.c: Adjust for Dwarf_Files type and dwarf_lineno interface change.
+
+	* readelf.c: Use libdw instead of libdwarf.  Not all of the old
+	behavior is available yet.
+	* Makefile.am: Link readelf with libdw.  Remove libdwarf include path.
+
+2004-01-09  Ulrich Drepper  <drepper@redhat.com>
+
+	* nm.c (get_local_names): Adjust call to dwarf_nextcu.
+
+	* nm.c: Implement getting information about local variables.
+
+2004-01-07  Ulrich Drepper  <drepper@redhat.com>
+
+	* nm.c: Read also debug information for local symbols.
+
+2004-01-05  Ulrich Drepper  <drepper@redhat.com>
+
+	* nm.c: Shuffle dwarf handling code around so the maximum column
+	width can be computed ahead of printing.  Avoid collection symbols
+	which are not printed anyway.
+
+	* nm.c: Rewrite dwarf handling to use libdw.
+	* Makefile.am (AM_CFLAGS): Add -std parameter.
+	(INCLUDES): Find header in libdw subdir.
+	(nm_LDADD): Replace libdwarf with libdw.
+
+	* elflint.c: Update copyright year.
+	* readelf.c: Likewise.
+	* size.c: Likewise.
+	* strip.c: Likewise.
+	* nm.c: Likewise.
+
+2003-12-31  Ulrich Drepper  <drepper@redhat.com>
+
+	* strip.c (process_file): Close file before returning.
+
+2003-11-19  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (handle_dynamic): Make column for tag name wider.
+
+2003-09-29  Ulrich Drepper  <drepper@redhat.com>
+
+	* readelf.c (handle_dynamic): Always terminate tag name with a space.
+
+2003-09-25  Ulrich Drepper  <drepper@redhat.com>
+
+	* strip.c (process_file): Don't mmap the input file, we modify the
+	data structures and don't want the change end up on disk.
+
+2003-09-23  Jakub Jelinek  <jakub@redhat.com>
+
+	* unaligned.h (union u_2ubyte_unaligned,
+	union u_4ubyte_unaligned, union u_8ubyte_unaligned): Add
+	packed attribute.
+	(add_2ubyte_unaligned, add_4ubyte_unaligned,
+	add_8ubyte_unaligned): Avoid nesting bswap_NN macros.
+	Read/store value through _ptr->u instead of *_ptr.
+
+2003-09-22  Ulrich Drepper  <drepper@redhat.com>
+
+	* size.c (show_sysv): Change type of maxlen to int.
+
+	* strip.c (handle_elf): Handle the 64-bit archs which is 64-bit
+	buckets.
+
+	* i386_ld.c: Many many fixes and extensions.
+	* ld.c: Likewise.
+	* ldgeneric.c: Likewise.
+
+2003-08-16  Ulrich Drepper  <drepper@redhat.com>
+
+	* ldgeneric.c (check_definition): Don't add symbol on dso_list if
+	the reference is from another DSO.
+
+2003-08-15  Ulrich Drepper  <drepper@redhat.com>
+
+	* ldgeneric.c (find_entry_point): It is no fatal error if no entry
+	point is found when creating a DSO.
+
+2003-08-14  Ulrich Drepper  <drepper@redhat.com>
+
+	* ld.c (main): Always call FLAG_UNRESOLVED.
+	* ldgeneric.c (ld_generic_flag_unresolved): Only complain about
+	undefined symbols if not creating DSO or ld_state.nodefs is not set.
+
+2003-08-13  Ulrich Drepper  <drepper@redhat.com>
+
+	* Makefile.in: Depend on libebl.a, not libebl.so.
+
+	* ld.c (main): Mark stream for linker script as locked by caller.
+	(read_version_script): Likewise.
+	* ldlex.c: Define fread and fwrite to _unlocked variant.
+
+	* i386_ld.c (elf_i386_finalize_plt): Replace #ifdefs with uses of
+	target_bswap_32.
+	* unaligned.h: Define target_bswap_16, target_bswap_32, and
+	target_bswap_64.
+	(store_2ubyte_unaligned, store_4ubyte_unaligned,
+	store_8ubyte_unaligned): Define using new macros.
+
+2003-08-12  Ulrich Drepper  <drepper@redhat.com>
+
+	* i386_ld.c (elf_i386_finalize_plt): Use packed structs to access
+	possibly unaligned memory.  Support use of big endian machines.
+
+2003-08-11  Ulrich Drepper  <drepper@redhat.com>
+
+	* Moved to CVS archive.
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..2b1c0dc
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,113 @@
+## Process this file with automake to create Makefile.in
+##
+## Copyright (C) 1996-2014, 2016 Red Hat, Inc.
+## This file is part of elfutils.
+##
+## This file is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## elfutils is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program.  If not, see <http://www.gnu.org/licenses/>.
+##
+include $(top_srcdir)/config/eu.am
+DEFS += $(YYDEBUG) -DDEBUGPRED=@DEBUGPRED@ \
+	-DSRCDIR=\"$(shell cd $(srcdir);pwd)\" -DOBJDIR=\"$(shell pwd)\"
+AM_CPPFLAGS += -I$(srcdir)/../libelf -I$(srcdir)/../libebl \
+	    -I$(srcdir)/../libdw -I$(srcdir)/../libdwelf \
+	    -I$(srcdir)/../libdwfl -I$(srcdir)/../libasm
+
+AM_LDFLAGS = -Wl,-rpath-link,../libelf:../libdw
+
+bin_PROGRAMS = readelf nm size strip elflint findtextrel addr2line \
+	       elfcmp objdump ranlib strings ar unstrip stack elfcompress
+
+noinst_LIBRARIES = libar.a
+
+libar_a_SOURCES = arlib.c arlib2.c arlib-argp.c
+
+EXTRA_DIST = arlib.h debugpred.h
+
+bin_SCRIPTS = make-debug-archive
+EXTRA_DIST += make-debug-archive.in
+CLEANFILES += make-debug-archive
+
+if BUILD_STATIC
+libasm = ../libasm/libasm.a
+libdw = ../libdw/libdw.a -lz $(zip_LIBS) $(libelf) $(libebl) -ldl
+libelf = ../libelf/libelf.a -lz
+else
+libasm = ../libasm/libasm.so
+libdw = ../libdw/libdw.so
+libelf = ../libelf/libelf.so
+endif
+libebl = ../libebl/libebl.a
+libeu = ../lib/libeu.a
+
+if DEMANGLE
+demanglelib = -lstdc++
+endif
+
+# Bad, bad stack usage...
+readelf_no_Wstack_usage = yes
+nm_no_Wstack_usage = yes
+size_no_Wstack_usage = yes
+strip_no_Wstack_usage = yes
+elflint_no_Wstack_usage = yes
+findtextrel_no_Wstack_usage = yes
+elfcmp_no_Wstack_usage = yes
+objdump_no_Wstack_usage = yes
+ranlib_no_Wstack_usage = yes
+ar_no_Wstack_usage = yes
+unstrip_no_Wstack_usage = yes
+
+readelf_LDADD = $(libdw) $(libebl) $(libelf) $(libeu) $(argp_LDADD) -ldl
+nm_LDADD = $(libdw) $(libebl) $(libelf) $(libeu) $(argp_LDADD) -ldl \
+	   $(demanglelib)
+size_LDADD = $(libelf) $(libeu) $(argp_LDADD)
+strip_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD) -ldl
+elflint_LDADD  = $(libebl) $(libelf) $(libeu) $(argp_LDADD) -ldl
+findtextrel_LDADD = $(libdw) $(libelf) $(libeu) $(argp_LDADD)
+addr2line_LDADD = $(libdw) $(libelf) $(libeu) $(argp_LDADD) $(demanglelib)
+elfcmp_LDADD = $(libebl) $(libelf) $(libeu) $(argp_LDADD) -ldl
+objdump_LDADD  = $(libasm) $(libebl) $(libelf) $(libeu) $(argp_LDADD) -ldl
+ranlib_LDADD = libar.a $(libelf) $(libeu) $(argp_LDADD)
+strings_LDADD = $(libelf) $(libeu) $(argp_LDADD)
+ar_LDADD = libar.a $(libelf) $(libeu) $(argp_LDADD)
+unstrip_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD) -ldl
+stack_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD) -ldl $(demanglelib)
+elfcompress_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD)
+
+installcheck-binPROGRAMS: $(bin_PROGRAMS)
+	bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \
+	  case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \
+	   *" $$p "* | *" $(srcdir)/$$p "*) continue;; \
+	  esac; \
+	  f=`echo "$$p" | \
+	     sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+	  for opt in --help --version; do \
+	    if LD_LIBRARY_PATH=$(DESTDIR)$(libdir) \
+	       $(DESTDIR)$(bindir)/$$f $$opt > c$${pid}_.out 2> c$${pid}_.err \
+		 && test -n "`cat c$${pid}_.out`" \
+		 && test -z "`cat c$${pid}_.err`"; then :; \
+	    else echo "$$f does not support $$opt" 1>&2; bad=1; fi; \
+	  done; \
+	done; rm -f c$${pid}_.???; exit $$bad
+
+CLEANFILES += *.gconv
+
+make-debug-archive: $(srcdir)/make-debug-archive.in
+	$(AM_V_GEN)UNSTRIP=$(bindir)/`echo unstrip | sed '$(transform)'`; \
+	AR=$(bindir)/`echo ar | sed '$(transform)'`; \
+	sed -e "s,[@]UNSTRIP[@],$$UNSTRIP,g" -e "s,[@]AR[@],$$AR,g" \
+	    -e "s%[@]PACKAGE_NAME[@]%$(PACKAGE_NAME)%g" \
+	    -e "s%[@]PACKAGE_VERSION[@]%$(PACKAGE_VERSION)%g" \
+	    $(srcdir)/make-debug-archive.in > $@.new
+	$(AM_V_at)chmod +x $@.new
+	$(AM_V_at)mv -f $@.new $@
diff --git a/src/addr2line.c b/src/addr2line.c
new file mode 100644
index 0000000..444ee52
--- /dev/null
+++ b/src/addr2line.c
@@ -0,0 +1,814 @@
+/* Locate source files and line information for given addresses
+   Copyright (C) 2005-2010, 2012, 2013, 2015 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2005.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <assert.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libdwfl.h>
+#include <dwarf.h>
+#include <libintl.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <system.h>
+#include <printversion.h>
+
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+
+/* Values for the parameters which have no short form.  */
+#define OPT_DEMANGLER 0x100
+#define OPT_PRETTY 0x101  /* 'p' is already used to select the process.  */
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, N_("Input format options:"), 2 },
+  { "section", 'j', "NAME", 0,
+    N_("Treat addresses as offsets relative to NAME section."), 0 },
+
+  { NULL, 0, NULL, 0, N_("Output format options:"), 3 },
+  { "addresses", 'a', NULL, 0, N_("Print address before each entry"), 0 },
+  { "basenames", 's', NULL, 0, N_("Show only base names of source files"), 0 },
+  { "absolute", 'A', NULL, 0,
+    N_("Show absolute file names using compilation directory"), 0 },
+  { "functions", 'f', NULL, 0, N_("Also show function names"), 0 },
+  { "symbols", 'S', NULL, 0, N_("Also show symbol or section names"), 0 },
+  { "symbols-sections", 'x', NULL, 0, N_("Also show symbol and the section names"), 0 },
+  { "flags", 'F', NULL, 0, N_("Also show line table flags"), 0 },
+  { "inlines", 'i', NULL, 0,
+    N_("Show all source locations that caused inline expansion of subroutines at the address."),
+    0 },
+  { "demangle", 'C', "ARG", OPTION_ARG_OPTIONAL,
+    N_("Show demangled symbols (ARG is always ignored)"), 0 },
+  { "pretty-print", OPT_PRETTY, NULL, 0,
+    N_("Print all information on one line, and indent inlines"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Miscellaneous:"), 0 },
+  /* Unsupported options.  */
+  { "target", 'b', "ARG", OPTION_HIDDEN, NULL, 0 },
+  { "demangler", OPT_DEMANGLER, "ARG", OPTION_HIDDEN, NULL, 0 },
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("\
+Locate source files and line information for ADDRs (in a.out by default).");
+
+/* Strings for arguments in help texts.  */
+static const char args_doc[] = N_("[ADDR...]");
+
+/* Prototype for option handler.  */
+static error_t parse_opt (int key, char *arg, struct argp_state *state);
+
+static struct argp_child argp_children[2]; /* [0] is set in main.  */
+
+/* Data structure to communicate with argp functions.  */
+static const struct argp argp =
+{
+  options, parse_opt, args_doc, doc, argp_children, NULL, NULL
+};
+
+
+/* Handle ADDR.  */
+static int handle_address (const char *addr, Dwfl *dwfl);
+
+/* True when we should print the address for each entry.  */
+static bool print_addresses;
+
+/* True if only base names of files should be shown.  */
+static bool only_basenames;
+
+/* True if absolute file names based on DW_AT_comp_dir should be shown.  */
+static bool use_comp_dir;
+
+/* True if line flags should be shown.  */
+static bool show_flags;
+
+/* True if function names should be shown.  */
+static bool show_functions;
+
+/* True if ELF symbol or section info should be shown.  */
+static bool show_symbols;
+
+/* True if section associated with a symbol address should be shown.  */
+static bool show_symbol_sections;
+
+/* If non-null, take address parameters as relative to named section.  */
+static const char *just_section;
+
+/* True if all inlined subroutines of the current address should be shown.  */
+static bool show_inlines;
+
+/* True if all names need to be demangled.  */
+static bool demangle;
+
+/* True if all information should be printed on one line.  */
+static bool pretty;
+
+#ifdef USE_DEMANGLE
+static size_t demangle_buffer_len = 0;
+static char *demangle_buffer = NULL;
+#endif
+
+int
+main (int argc, char *argv[])
+{
+  int remaining;
+  int result = 0;
+
+  /* We use no threads here which can interfere with handling a stream.  */
+  (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  (void) textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  This includes opening the modules.  */
+  argp_children[0].argp = dwfl_standard_argp ();
+  argp_children[0].group = 1;
+  Dwfl *dwfl = NULL;
+  (void) argp_parse (&argp, argc, argv, 0, &remaining, &dwfl);
+  assert (dwfl != NULL);
+
+  /* Now handle the addresses.  In case none are given on the command
+     line, read from stdin.  */
+  if (remaining == argc)
+    {
+      /* We use no threads here which can interfere with handling a stream.  */
+      (void) __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+
+      char *buf = NULL;
+      size_t len = 0;
+      ssize_t chars;
+      while (!feof_unlocked (stdin))
+	{
+	  if ((chars = getline (&buf, &len, stdin)) < 0)
+	    break;
+
+	  if (buf[chars - 1] == '\n')
+	    buf[chars - 1] = '\0';
+
+	  result = handle_address (buf, dwfl);
+	}
+
+      free (buf);
+    }
+  else
+    {
+      do
+	result = handle_address (argv[remaining], dwfl);
+      while (++remaining < argc);
+    }
+
+  dwfl_end (dwfl);
+
+#ifdef USE_DEMANGLE
+  free (demangle_buffer);
+#endif
+
+  return result;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+  switch (key)
+    {
+    case ARGP_KEY_INIT:
+      state->child_inputs[0] = state->input;
+      break;
+
+    case 'a':
+      print_addresses = true;
+      break;
+
+    case 'b':
+    case 'C':
+    case OPT_DEMANGLER:
+      demangle = true;
+      break;
+
+    case 's':
+      only_basenames = true;
+      break;
+
+    case 'A':
+      use_comp_dir = true;
+      break;
+
+    case 'f':
+      show_functions = true;
+      break;
+
+    case 'F':
+      show_flags = true;
+      break;
+
+    case 'S':
+      show_symbols = true;
+      break;
+
+    case 'x':
+      show_symbols = true;
+      show_symbol_sections = true;
+      break;
+
+    case 'j':
+      just_section = arg;
+      break;
+
+    case 'i':
+      show_inlines = true;
+      break;
+
+    case OPT_PRETTY:
+      pretty = true;
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+static const char *
+symname (const char *name)
+{
+#ifdef USE_DEMANGLE
+  // Require GNU v3 ABI by the "_Z" prefix.
+  if (demangle && name[0] == '_' && name[1] == 'Z')
+    {
+      int status = -1;
+      char *dsymname = __cxa_demangle (name, demangle_buffer,
+				       &demangle_buffer_len, &status);
+      if (status == 0)
+	name = demangle_buffer = dsymname;
+    }
+#endif
+  return name;
+}
+
+static const char *
+get_diename (Dwarf_Die *die)
+{
+  Dwarf_Attribute attr;
+  const char *name;
+
+  name = dwarf_formstring (dwarf_attr_integrate (die, DW_AT_MIPS_linkage_name,
+						 &attr)
+			   ?: dwarf_attr_integrate (die, DW_AT_linkage_name,
+						    &attr));
+
+  if (name == NULL)
+    name = dwarf_diename (die) ?: "??";
+
+  return name;
+}
+
+static bool
+print_dwarf_function (Dwfl_Module *mod, Dwarf_Addr addr)
+{
+  Dwarf_Addr bias = 0;
+  Dwarf_Die *cudie = dwfl_module_addrdie (mod, addr, &bias);
+
+  Dwarf_Die *scopes;
+  int nscopes = dwarf_getscopes (cudie, addr - bias, &scopes);
+  if (nscopes <= 0)
+    return false;
+
+  bool res = false;
+  for (int i = 0; i < nscopes; ++i)
+    switch (dwarf_tag (&scopes[i]))
+      {
+      case DW_TAG_subprogram:
+	{
+	  const char *name = get_diename (&scopes[i]);
+	  if (name == NULL)
+	    goto done;
+	  printf ("%s%c", symname (name), pretty ? ' ' : '\n');
+	  res = true;
+	  goto done;
+	}
+
+      case DW_TAG_inlined_subroutine:
+	{
+	  const char *name = get_diename (&scopes[i]);
+	  if (name == NULL)
+	    goto done;
+
+	  /* When using --pretty-print we only show inlines on their
+	     own line.  Just print the first subroutine name.  */
+	  if (pretty)
+	    {
+	      printf ("%s ", symname (name));
+	      res = true;
+	      goto done;
+	    }
+	  else
+	    printf ("%s inlined", symname (name));
+
+	  Dwarf_Files *files;
+	  if (dwarf_getsrcfiles (cudie, &files, NULL) == 0)
+	    {
+	      Dwarf_Attribute attr_mem;
+	      Dwarf_Word val;
+	      if (dwarf_formudata (dwarf_attr (&scopes[i],
+					       DW_AT_call_file,
+					       &attr_mem), &val) == 0)
+		{
+		  const char *file = dwarf_filesrc (files, val, NULL, NULL);
+		  unsigned int lineno = 0;
+		  unsigned int colno = 0;
+		  if (dwarf_formudata (dwarf_attr (&scopes[i],
+						   DW_AT_call_line,
+						   &attr_mem), &val) == 0)
+		    lineno = val;
+		  if (dwarf_formudata (dwarf_attr (&scopes[i],
+						   DW_AT_call_column,
+						   &attr_mem), &val) == 0)
+		    colno = val;
+
+		  const char *comp_dir = "";
+		  const char *comp_dir_sep = "";
+
+		  if (file == NULL)
+		    file = "???";
+		  else if (only_basenames)
+		    file = basename (file);
+		  else if (use_comp_dir && file[0] != '/')
+		    {
+		      const char *const *dirs;
+		      size_t ndirs;
+		      if (dwarf_getsrcdirs (files, &dirs, &ndirs) == 0
+			  && dirs[0] != NULL)
+			{
+			  comp_dir = dirs[0];
+			  comp_dir_sep = "/";
+			}
+		    }
+
+		  if (lineno == 0)
+		    printf (" from %s%s%s",
+			    comp_dir, comp_dir_sep, file);
+		  else if (colno == 0)
+		    printf (" at %s%s%s:%u",
+			    comp_dir, comp_dir_sep, file, lineno);
+		  else
+		    printf (" at %s%s%s:%u:%u",
+			    comp_dir, comp_dir_sep, file, lineno, colno);
+		}
+	    }
+	  printf (" in ");
+	  continue;
+	}
+      }
+
+done:
+  free (scopes);
+  return res;
+}
+
+static void
+print_addrsym (Dwfl_Module *mod, GElf_Addr addr)
+{
+  GElf_Sym s;
+  GElf_Off off;
+  const char *name = dwfl_module_addrinfo (mod, addr, &off, &s,
+					   NULL, NULL, NULL);
+  if (name == NULL)
+    {
+      /* No symbol name.  Get a section name instead.  */
+      int i = dwfl_module_relocate_address (mod, &addr);
+      if (i >= 0)
+	name = dwfl_module_relocation_info (mod, i, NULL);
+      if (name == NULL)
+	printf ("??%c", pretty ? ' ': '\n');
+      else
+	printf ("(%s)+%#" PRIx64 "%c", name, addr, pretty ? ' ' : '\n');
+    }
+  else
+    {
+      name = symname (name);
+      if (off == 0)
+	printf ("%s", name);
+      else
+	printf ("%s+%#" PRIx64 "", name, off);
+
+      // Also show section name for address.
+      if (show_symbol_sections)
+	{
+	  Dwarf_Addr ebias;
+	  Elf_Scn *scn = dwfl_module_address_section (mod, &addr, &ebias);
+	  if (scn != NULL)
+	    {
+	      GElf_Shdr shdr_mem;
+	      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	      if (shdr != NULL)
+		{
+		  Elf *elf = dwfl_module_getelf (mod, &ebias);
+		  GElf_Ehdr ehdr;
+		  if (gelf_getehdr (elf, &ehdr) != NULL)
+		    printf (" (%s)", elf_strptr (elf, ehdr.e_shstrndx,
+						 shdr->sh_name));
+		}
+	    }
+	}
+      printf ("%c", pretty ? ' ' : '\n');
+    }
+}
+
+static int
+see_one_module (Dwfl_Module *mod,
+		void **userdata __attribute__ ((unused)),
+		const char *name __attribute__ ((unused)),
+		Dwarf_Addr start __attribute__ ((unused)),
+		void *arg)
+{
+  Dwfl_Module **result = arg;
+  if (*result != NULL)
+    return DWARF_CB_ABORT;
+  *result = mod;
+  return DWARF_CB_OK;
+}
+
+static int
+find_symbol (Dwfl_Module *mod,
+	     void **userdata __attribute__ ((unused)),
+	     const char *name __attribute__ ((unused)),
+	     Dwarf_Addr start __attribute__ ((unused)),
+	     void *arg)
+{
+  const char *looking_for = ((void **) arg)[0];
+  GElf_Sym *symbol = ((void **) arg)[1];
+  GElf_Addr *value = ((void **) arg)[2];
+
+  int n = dwfl_module_getsymtab (mod);
+  for (int i = 1; i < n; ++i)
+    {
+      const char *symbol_name = dwfl_module_getsym_info (mod, i, symbol,
+							 value, NULL, NULL,
+							 NULL);
+      if (symbol_name == NULL || symbol_name[0] == '\0')
+	continue;
+      switch (GELF_ST_TYPE (symbol->st_info))
+	{
+	case STT_SECTION:
+	case STT_FILE:
+	case STT_TLS:
+	  break;
+	default:
+	  if (!strcmp (symbol_name, looking_for))
+	    {
+	      ((void **) arg)[0] = NULL;
+	      return DWARF_CB_ABORT;
+	    }
+	}
+    }
+
+  return DWARF_CB_OK;
+}
+
+static bool
+adjust_to_section (const char *name, uintmax_t *addr, Dwfl *dwfl)
+{
+  /* It was (section)+offset.  This makes sense if there is
+     only one module to look in for a section.  */
+  Dwfl_Module *mod = NULL;
+  if (dwfl_getmodules (dwfl, &see_one_module, &mod, 0) != 0
+      || mod == NULL)
+    error (EXIT_FAILURE, 0, gettext ("Section syntax requires"
+				     " exactly one module"));
+
+  int nscn = dwfl_module_relocations (mod);
+  for (int i = 0; i < nscn; ++i)
+    {
+      GElf_Word shndx;
+      const char *scn = dwfl_module_relocation_info (mod, i, &shndx);
+      if (unlikely (scn == NULL))
+	break;
+      if (!strcmp (scn, name))
+	{
+	  /* Found the section.  */
+	  GElf_Shdr shdr_mem;
+	  GElf_Addr shdr_bias;
+	  GElf_Shdr *shdr = gelf_getshdr
+	    (elf_getscn (dwfl_module_getelf (mod, &shdr_bias), shndx),
+	     &shdr_mem);
+	  if (unlikely (shdr == NULL))
+	    break;
+
+	  if (*addr >= shdr->sh_size)
+	    error (0, 0,
+		   gettext ("offset %#" PRIxMAX " lies outside"
+			    " section '%s'"),
+		   *addr, scn);
+
+	  *addr += shdr->sh_addr + shdr_bias;
+	  return true;
+	}
+    }
+
+  return false;
+}
+
+static void
+print_src (const char *src, int lineno, int linecol, Dwarf_Die *cu)
+{
+  const char *comp_dir = "";
+  const char *comp_dir_sep = "";
+
+  if (only_basenames)
+    src = basename (src);
+  else if (use_comp_dir && src[0] != '/')
+    {
+      Dwarf_Attribute attr;
+      comp_dir = dwarf_formstring (dwarf_attr (cu, DW_AT_comp_dir, &attr));
+      if (comp_dir != NULL)
+	comp_dir_sep = "/";
+    }
+
+  if (linecol != 0)
+    printf ("%s%s%s:%d:%d",
+	    comp_dir, comp_dir_sep, src, lineno, linecol);
+  else
+    printf ("%s%s%s:%d",
+	    comp_dir, comp_dir_sep, src, lineno);
+}
+
+static int
+get_addr_width (Dwfl_Module *mod)
+{
+  // Try to find the address width if possible.
+  static int width = 0;
+  if (width == 0 && mod != NULL)
+    {
+      Dwarf_Addr bias;
+      Elf *elf = dwfl_module_getelf (mod, &bias);
+      if (elf != NULL)
+        {
+	  GElf_Ehdr ehdr_mem;
+	  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
+	  if (ehdr != NULL)
+	    width = ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 8 : 16;
+	}
+    }
+  if (width == 0)
+    width = 16;
+
+  return width;
+}
+
+static int
+handle_address (const char *string, Dwfl *dwfl)
+{
+  char *endp;
+  uintmax_t addr = strtoumax (string, &endp, 16);
+  if (endp == string || *endp != '\0')
+    {
+      bool parsed = false;
+      int i, j;
+      char *name = NULL;
+      if (sscanf (string, "(%m[^)])%" PRIiMAX "%n", &name, &addr, &i) == 2
+	  && string[i] == '\0')
+	parsed = adjust_to_section (name, &addr, dwfl);
+      switch (sscanf (string, "%m[^-+]%n%" PRIiMAX "%n", &name, &i, &addr, &j))
+	{
+	default:
+	  break;
+	case 1:
+	  addr = 0;
+	  j = i;
+	  FALLTHROUGH;
+	case 2:
+	  if (string[j] != '\0')
+	    break;
+
+	  /* It was symbol[+offset].  */
+	  GElf_Sym sym;
+	  GElf_Addr value = 0;
+	  void *arg[3] = { name, &sym, &value };
+	  (void) dwfl_getmodules (dwfl, &find_symbol, arg, 0);
+	  if (arg[0] != NULL)
+	    error (0, 0, gettext ("cannot find symbol '%s'"), name);
+	  else
+	    {
+	      if (sym.st_size != 0 && addr >= sym.st_size)
+		error (0, 0,
+		       gettext ("offset %#" PRIxMAX " lies outside"
+				" contents of '%s'"),
+		       addr, name);
+	      addr += value;
+	      parsed = true;
+	    }
+	  break;
+	}
+
+      free (name);
+      if (!parsed)
+	return 1;
+    }
+  else if (just_section != NULL
+	   && !adjust_to_section (just_section, &addr, dwfl))
+    return 1;
+
+  Dwfl_Module *mod = dwfl_addrmodule (dwfl, addr);
+
+  if (print_addresses)
+    {
+      int width = get_addr_width (mod);
+      printf ("0x%.*" PRIx64 "%s", width, addr, pretty ? ": " : "\n");
+    }
+
+  if (show_functions)
+    {
+      /* First determine the function name.  Use the DWARF information if
+	 possible.  */
+      if (! print_dwarf_function (mod, addr) && !show_symbols)
+	{
+	  const char *name = dwfl_module_addrname (mod, addr);
+	  name = name != NULL ? symname (name) : "??";
+	  printf ("%s%c", name, pretty ? ' ' : '\n');
+	}
+    }
+
+  if (show_symbols)
+    print_addrsym (mod, addr);
+
+  if ((show_functions || show_symbols) && pretty)
+    printf ("at ");
+
+  Dwfl_Line *line = dwfl_module_getsrc (mod, addr);
+
+  const char *src;
+  int lineno, linecol;
+
+  if (line != NULL && (src = dwfl_lineinfo (line, &addr, &lineno, &linecol,
+					    NULL, NULL)) != NULL)
+    {
+      print_src (src, lineno, linecol, dwfl_linecu (line));
+      if (show_flags)
+	{
+	  Dwarf_Addr bias;
+	  Dwarf_Line *info = dwfl_dwarf_line (line, &bias);
+	  assert (info != NULL);
+
+	  inline void show (int (*get) (Dwarf_Line *, bool *),
+			    const char *note)
+	  {
+	    bool flag;
+	    if ((*get) (info, &flag) == 0 && flag)
+	      fputs (note, stdout);
+	  }
+	  inline void show_int (int (*get) (Dwarf_Line *, unsigned int *),
+				const char *name)
+	  {
+	    unsigned int val;
+	    if ((*get) (info, &val) == 0 && val != 0)
+	      printf (" (%s %u)", name, val);
+	  }
+
+	  show (&dwarf_linebeginstatement, " (is_stmt)");
+	  show (&dwarf_lineblock, " (basic_block)");
+	  show (&dwarf_lineprologueend, " (prologue_end)");
+	  show (&dwarf_lineepiloguebegin, " (epilogue_begin)");
+	  show_int (&dwarf_lineisa, "isa");
+	  show_int (&dwarf_linediscriminator, "discriminator");
+	}
+      putchar ('\n');
+    }
+  else
+    puts ("??:0");
+
+  if (show_inlines)
+    {
+      Dwarf_Addr bias = 0;
+      Dwarf_Die *cudie = dwfl_module_addrdie (mod, addr, &bias);
+
+      Dwarf_Die *scopes = NULL;
+      int nscopes = dwarf_getscopes (cudie, addr - bias, &scopes);
+      if (nscopes < 0)
+	return 1;
+
+      if (nscopes > 0)
+	{
+	  Dwarf_Die subroutine;
+	  Dwarf_Off dieoff = dwarf_dieoffset (&scopes[0]);
+	  dwarf_offdie (dwfl_module_getdwarf (mod, &bias),
+			dieoff, &subroutine);
+	  free (scopes);
+	  scopes = NULL;
+
+	  nscopes = dwarf_getscopes_die (&subroutine, &scopes);
+	  if (nscopes > 1)
+	    {
+	      Dwarf_Die cu;
+	      Dwarf_Files *files;
+	      if (dwarf_diecu (&scopes[0], &cu, NULL, NULL) != NULL
+		  && dwarf_getsrcfiles (cudie, &files, NULL) == 0)
+		{
+		  for (int i = 0; i < nscopes - 1; i++)
+		    {
+		      Dwarf_Word val;
+		      Dwarf_Attribute attr;
+		      Dwarf_Die *die = &scopes[i];
+		      if (dwarf_tag (die) != DW_TAG_inlined_subroutine)
+			continue;
+
+		      if (pretty)
+			printf (" (inlined by) ");
+
+		      if (show_functions)
+			{
+			  /* Search for the parent inline or function.  It
+			     might not be directly above this inline -- e.g.
+			     there could be a lexical_block in between.  */
+			  for (int j = i + 1; j < nscopes; j++)
+			    {
+			      Dwarf_Die *parent = &scopes[j];
+			      int tag = dwarf_tag (parent);
+			      if (tag == DW_TAG_inlined_subroutine
+				  || tag == DW_TAG_entry_point
+				  || tag == DW_TAG_subprogram)
+				{
+				  printf ("%s%s",
+					  symname (get_diename (parent)),
+					  pretty ? " at " : "\n");
+				  break;
+				}
+			    }
+			}
+
+		      src = NULL;
+		      lineno = 0;
+		      linecol = 0;
+		      if (dwarf_formudata (dwarf_attr (die, DW_AT_call_file,
+						       &attr), &val) == 0)
+			src = dwarf_filesrc (files, val, NULL, NULL);
+
+		      if (dwarf_formudata (dwarf_attr (die, DW_AT_call_line,
+						       &attr), &val) == 0)
+			lineno = val;
+
+		      if (dwarf_formudata (dwarf_attr (die, DW_AT_call_column,
+						       &attr), &val) == 0)
+			linecol = val;
+
+		      if (src != NULL)
+			{
+			  print_src (src, lineno, linecol, &cu);
+			  putchar ('\n');
+			}
+		      else
+			puts ("??:0");
+		    }
+		}
+	    }
+	}
+      free (scopes);
+    }
+
+  return 0;
+}
+
+
+#include "debugpred.h"
diff --git a/src/ar.c b/src/ar.c
new file mode 100644
index 0000000..818115b
--- /dev/null
+++ b/src/ar.c
@@ -0,0 +1,1555 @@
+/* Create, modify, and extract from archives.
+   Copyright (C) 2005-2012, 2016, 2017 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2005.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <assert.h>
+#include <error.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libintl.h>
+#include <limits.h>
+#include <locale.h>
+#include <search.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <system.h>
+#include <printversion.h>
+
+#include "arlib.h"
+
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Prototypes for local functions.  */
+static int do_oper_extract (int oper, const char *arfname, char **argv,
+			    int argc, long int instance);
+static int do_oper_delete (const char *arfname, char **argv, int argc,
+			   long int instance);
+static int do_oper_insert (int oper, const char *arfname, char **argv,
+			   int argc, const char *member);
+
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, N_("Commands:"), 1 },
+  { NULL, 'd', NULL, 0, N_("Delete files from archive."), 0 },
+  { NULL, 'm', NULL, 0, N_("Move files in archive."), 0 },
+  { NULL, 'p', NULL, 0, N_("Print files in archive."), 0 },
+  { NULL, 'q', NULL, 0, N_("Quick append files to archive."), 0 },
+  { NULL, 'r', NULL, 0,
+    N_("Replace existing or insert new file into archive."), 0 },
+  { NULL, 't', NULL, 0, N_("Display content of archive."), 0 },
+  { NULL, 'x', NULL, 0, N_("Extract files from archive."), 0 },
+
+  { NULL, 0, NULL, 0, N_("Command Modifiers:"), 2 },
+  { NULL, 'o', NULL, 0, N_("Preserve original dates."), 0 },
+  { NULL, 'N', NULL, 0, N_("Use instance [COUNT] of name."), 0 },
+  { NULL, 'C', NULL, 0,
+    N_("Do not replace existing files with extracted files."), 0 },
+  { NULL, 'T', NULL, 0, N_("Allow filename to be truncated if necessary."),
+    0 },
+  { NULL, 'v', NULL, 0, N_("Provide verbose output."), 0 },
+  { NULL, 's', NULL, 0, N_("Force regeneration of symbol table."), 0 },
+  { NULL, 'a', NULL, 0, N_("Insert file after [MEMBER]."), 0 },
+  { NULL, 'b', NULL, 0, N_("Insert file before [MEMBER]."), 0 },
+  { NULL, 'i', NULL, 0, N_("Same as -b."), 0 },
+  { NULL, 'c', NULL, 0, N_("Suppress message when library has to be created."),
+    0 },
+  { NULL, 'P', NULL, 0, N_("Use full path for file matching."), 0 },
+  { NULL, 'u', NULL, 0, N_("Update only older files in archive."), 0 },
+
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("Create, modify, and extract from archives.");
+
+/* Strings for arguments in help texts.  */
+static const char args_doc[] = N_("[MEMBER] [COUNT] ARCHIVE [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, arlib_argp_children, NULL, NULL
+};
+
+
+/* What operation to perform.  */
+static enum
+  {
+    oper_none,
+    oper_delete,
+    oper_move,
+    oper_print,
+    oper_qappend,
+    oper_replace,
+    oper_list,
+    oper_extract
+  } operation;
+
+/* Modifiers.  */
+static bool verbose;
+static bool preserve_dates;
+static bool instance_specifed;
+static bool dont_replace_existing;
+static bool allow_truncate_fname;
+static bool force_symtab;
+static bool suppress_create_msg;
+static bool full_path;
+static bool update_newer;
+static enum { ipos_none, ipos_before, ipos_after } ipos;
+
+
+int
+main (int argc, char *argv[])
+{
+  /* We use no threads here which can interfere with handling a stream.  */
+  (void) __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+  (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+  (void) __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  (void) textdomain (PACKAGE_TARNAME);
+
+  /* For historical reasons the options in the first parameter need
+     not be preceded by a dash.  Add it now if necessary.  */
+  if (argc > 1 && argv[1][0] != '-')
+    {
+      size_t len = strlen (argv[1]) + 1;
+      char *newp = alloca (len + 1);
+      newp[0] = '-';
+      memcpy (&newp[1], argv[1], len);
+      argv[1] = newp;
+    }
+
+  /* Parse and process arguments.  */
+  int remaining;
+  (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &remaining, NULL);
+
+  /* Tell the library which version we are expecting.  */
+  (void) elf_version (EV_CURRENT);
+
+  /* Handle the [MEMBER] parameter.  */
+  const char *member = NULL;
+  if (ipos != ipos_none)
+    {
+      /* Only valid for certain operations.  */
+      if (operation != oper_move && operation != oper_replace)
+	error (1, 0, gettext ("\
+'a', 'b', and 'i' are only allowed with the 'm' and 'r' options"));
+
+      if (remaining == argc)
+	{
+	  error (0, 0, gettext ("\
+MEMBER parameter required for 'a', 'b', and 'i' modifiers"));
+	  argp_help (&argp, stderr, ARGP_HELP_USAGE | ARGP_HELP_SEE,
+		     program_invocation_short_name);
+	  exit (EXIT_FAILURE);
+	}
+
+      member = argv[remaining++];
+    }
+
+  /* Handle the [COUNT] parameter.  */
+  long int instance = -1;
+  if (instance_specifed)
+    {
+      /* Only valid for certain operations.  */
+      if (operation != oper_extract && operation != oper_delete)
+	error (1, 0, gettext ("\
+'N' is only meaningful with the 'x' and 'd' options"));
+
+      if (remaining == argc)
+	{
+	  error (0, 0, gettext ("COUNT parameter required"));
+	  argp_help (&argp, stderr, ARGP_HELP_SEE,
+		     program_invocation_short_name);
+	  exit (EXIT_FAILURE);
+	}
+
+      char *endp;
+      errno = 0;
+      if (((instance = strtol (argv[remaining], &endp, 10)) == LONG_MAX
+	   && errno == ERANGE)
+	  || instance <= 0
+	  || *endp != '\0')
+	error (1, 0, gettext ("invalid COUNT parameter %s"), argv[remaining]);
+
+      ++remaining;
+    }
+
+  if ((dont_replace_existing || allow_truncate_fname)
+      && unlikely (operation != oper_extract))
+    error (1, 0, gettext ("'%c' is only meaningful with the 'x' option"),
+	   dont_replace_existing ? 'C' : 'T');
+
+  /* There must at least be one more parameter specifying the archive.   */
+  if (remaining == argc)
+    {
+      error (0, 0, gettext ("archive name required"));
+      argp_help (&argp, stderr, ARGP_HELP_SEE, program_invocation_short_name);
+      exit (EXIT_FAILURE);
+    }
+
+  const char *arfname = argv[remaining++];
+  argv += remaining;
+  argc -= remaining;
+
+  int status;
+  switch (operation)
+    {
+    case oper_none:
+      error (0, 0, gettext ("command option required"));
+      argp_help (&argp, stderr, ARGP_HELP_STD_ERR,
+		 program_invocation_short_name);
+      status = 1;
+      break;
+
+    case oper_list:
+    case oper_print:
+      status = do_oper_extract (operation, arfname, argv, argc, -1);
+      break;
+
+    case oper_extract:
+      status = do_oper_extract (operation, arfname, argv, argc, instance);
+      break;
+
+    case oper_delete:
+      status = do_oper_delete (arfname, argv, argc, instance);
+      break;
+
+    case oper_move:
+    case oper_qappend:
+    case oper_replace:
+      status = do_oper_insert (operation, arfname, argv, argc, member);
+      break;
+
+    default:
+      assert (! "should not happen");
+      status = 1;
+      break;
+    }
+
+  return status;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg __attribute__ ((unused)),
+	   struct argp_state *state __attribute__ ((unused)))
+{
+  switch (key)
+    {
+    case 'd':
+    case 'm':
+    case 'p':
+    case 'q':
+    case 'r':
+    case 't':
+    case 'x':
+      if (operation != oper_none)
+	{
+	  error (0, 0, gettext ("More than one operation specified"));
+	  argp_help (&argp, stderr, ARGP_HELP_SEE,
+		     program_invocation_short_name);
+	  exit (EXIT_FAILURE);
+	}
+
+      switch (key)
+	{
+	case 'd':
+	  operation = oper_delete;
+	  break;
+	case 'm':
+	  operation = oper_move;
+	  break;
+	case 'p':
+	  operation = oper_print;
+	  break;
+	case 'q':
+	  operation = oper_qappend;
+	  break;
+	case 'r':
+	  operation = oper_replace;
+	  break;
+	case 't':
+	  operation = oper_list;
+	  break;
+	case 'x':
+	  operation = oper_extract;
+	  break;
+	}
+      break;
+
+    case 'a':
+      ipos = ipos_after;
+      break;
+
+    case 'b':
+    case 'i':
+      ipos = ipos_before;
+      break;
+
+    case 'c':
+      suppress_create_msg = true;
+      break;
+
+    case 'C':
+      dont_replace_existing = true;
+      break;
+
+    case 'N':
+      instance_specifed = true;
+      break;
+
+    case 'o':
+      preserve_dates = true;
+      break;
+
+    case 'P':
+      full_path = true;
+      break;
+
+    case 's':
+      force_symtab = true;
+      break;
+
+    case 'T':
+      allow_truncate_fname = true;
+      break;
+
+    case 'u':
+      update_newer = true;
+      break;
+
+    case 'v':
+      verbose = true;
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+
+static int
+open_archive (const char *arfname, int flags, int mode, Elf **elf,
+	      struct stat *st, bool miss_allowed)
+{
+  int fd = open (arfname, flags, mode);
+  if (fd == -1)
+    {
+      if (miss_allowed)
+	return -1;
+
+      error (EXIT_FAILURE, errno, gettext ("cannot open archive '%s'"),
+	     arfname);
+    }
+
+  if (elf != NULL)
+    {
+      Elf_Cmd cmd = flags == O_RDONLY ? ELF_C_READ_MMAP : ELF_C_RDWR_MMAP;
+
+      *elf = elf_begin (fd, cmd, NULL);
+      if (*elf == NULL)
+	error (EXIT_FAILURE, 0, gettext ("cannot open archive '%s': %s"),
+	       arfname, elf_errmsg (-1));
+
+      if (flags == O_RDONLY && elf_kind (*elf) != ELF_K_AR)
+	error (EXIT_FAILURE, 0, gettext ("%s: not an archive file"), arfname);
+    }
+
+  if (st != NULL && fstat (fd, st) != 0)
+    error (EXIT_FAILURE, errno, gettext ("cannot stat archive '%s'"),
+	   arfname);
+
+  return fd;
+}
+
+
+static void
+not_found (int argc, char *argv[argc], bool found[argc])
+{
+  for (int i = 0; i < argc; ++i)
+    if (!found[i])
+      printf (gettext ("no entry %s in archive\n"), argv[i]);
+}
+
+
+static int
+copy_content (Elf *elf, int newfd, off_t off, size_t n)
+{
+  size_t len;
+  char *rawfile = elf_rawfile (elf, &len);
+
+  assert (off + n <= len);
+
+  /* Tell the kernel we will read all the pages sequentially.  */
+  size_t ps = sysconf (_SC_PAGESIZE);
+  if (n > 2 * ps)
+    posix_madvise (rawfile + (off & ~(ps - 1)), n, POSIX_MADV_SEQUENTIAL);
+
+  return write_retry (newfd, rawfile + off, n) != (ssize_t) n;
+}
+
+
+static int
+do_oper_extract (int oper, const char *arfname, char **argv, int argc,
+		 long int instance)
+{
+  bool found[argc > 0 ? argc : 1];
+  memset (found, '\0', sizeof (found));
+
+  size_t name_max = 0;
+  inline bool should_truncate_fname (void)
+  {
+    if (errno == ENAMETOOLONG && allow_truncate_fname)
+      {
+	if (name_max == 0)
+	  {
+	    long int len = pathconf (".", _PC_NAME_MAX);
+	    if (len > 0)
+	      name_max = len;
+	  }
+	return name_max != 0;
+      }
+    return false;
+  }
+
+  off_t index_off = -1;
+  size_t index_size = 0;
+  off_t cur_off = SARMAG;
+
+  int status = 0;
+  Elf *elf;
+  int fd = open_archive (arfname, O_RDONLY, 0, &elf, NULL, false);
+
+  if (hcreate (2 * argc) == 0)
+    error (EXIT_FAILURE, errno, gettext ("cannot create hash table"));
+
+  for (int cnt = 0; cnt < argc; ++cnt)
+    {
+      ENTRY entry = { .key = argv[cnt], .data = &argv[cnt] };
+      if (hsearch (entry, ENTER) == NULL)
+	error (EXIT_FAILURE, errno,
+	       gettext ("cannot insert into hash table"));
+    }
+
+  struct stat st;
+  if (force_symtab)
+    {
+      if (fstat (fd, &st) != 0)
+	{
+	  error (0, errno, gettext ("cannot stat '%s'"), arfname);
+	  close (fd);
+	  return 1;
+	}
+      arlib_init ();
+    }
+
+  Elf_Cmd cmd = ELF_C_READ_MMAP;
+  Elf *subelf;
+  while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
+    {
+      Elf_Arhdr *arhdr = elf_getarhdr (subelf);
+
+      if (strcmp (arhdr->ar_name, "/") == 0)
+	{
+	  index_off = elf_getaroff (subelf);
+	  index_size = arhdr->ar_size;
+	  goto next;
+	}
+      if (strcmp (arhdr->ar_name, "//") == 0)
+	goto next;
+
+      if (force_symtab)
+	{
+	  arlib_add_symbols (elf, arfname, arhdr->ar_name, cur_off);
+	  cur_off += (((arhdr->ar_size + 1) & ~((off_t) 1))
+		      + sizeof (struct ar_hdr));
+	}
+
+      bool do_extract = argc <= 0;
+      if (!do_extract)
+	{
+	  ENTRY entry;
+	  entry.key = arhdr->ar_name;
+	  ENTRY *res = hsearch (entry, FIND);
+	  if (res != NULL && (instance < 0 || instance-- == 0)
+	      && !found[(char **) res->data - argv])
+	    found[(char **) res->data - argv] = do_extract = true;
+	}
+
+      if (do_extract)
+	{
+	  if (verbose)
+	    {
+	      if (oper == oper_print)
+		{
+		  printf ("\n<%s>\n\n", arhdr->ar_name);
+
+		  /* We have to flush now because now we use the descriptor
+		     directly.  */
+		  fflush (stdout);
+		}
+	      else if (oper == oper_list)
+		{
+		  char datestr[100];
+		  strftime (datestr, sizeof (datestr), "%b %e %H:%M %Y",
+			    localtime (&arhdr->ar_date));
+
+		  printf ("%c%c%c%c%c%c%c%c%c %u/%u %6ju %s %s\n",
+			  (arhdr->ar_mode & S_IRUSR) ? 'r' : '-',
+			  (arhdr->ar_mode & S_IWUSR) ? 'w' : '-',
+			  (arhdr->ar_mode & S_IXUSR)
+			  ? ((arhdr->ar_mode & S_ISUID) ? 's' : 'x')
+			  : ((arhdr->ar_mode & S_ISUID) ? 'S' : '-'),
+			  (arhdr->ar_mode & S_IRGRP) ? 'r' : '-',
+			  (arhdr->ar_mode & S_IWGRP) ? 'w' : '-',
+			  (arhdr->ar_mode & S_IXGRP)
+			  ? ((arhdr->ar_mode & S_ISGID) ? 's' : 'x')
+			  : ((arhdr->ar_mode & S_ISGID) ? 'S' : '-'),
+			  (arhdr->ar_mode & S_IROTH) ? 'r' : '-',
+			  (arhdr->ar_mode & S_IWOTH) ? 'w' : '-',
+			  (arhdr->ar_mode & S_IXOTH)
+			  ? ((arhdr->ar_mode & S_ISVTX) ? 't' : 'x')
+			  : ((arhdr->ar_mode & S_ISVTX) ? 'T' : '-'),
+			  arhdr->ar_uid,
+			  arhdr->ar_gid,
+			  (uintmax_t) arhdr->ar_size,
+			  datestr,
+			  arhdr->ar_name);
+		}
+	      else
+		printf ("x - %s\n", arhdr->ar_name);
+	    }
+
+	  if (oper == oper_list)
+	    {
+	      if (!verbose)
+		puts (arhdr->ar_name);
+
+	      goto next;
+	    }
+
+	  size_t nleft;
+	  char *data = elf_rawfile (subelf, &nleft);
+	  if (data == NULL)
+	    {
+	      error (0, 0, gettext ("cannot read content of %s: %s"),
+		     arhdr->ar_name, elf_errmsg (-1));
+	      status = 1;
+	      goto next;
+	    }
+
+	  int xfd;
+	  char tempfname[] = "XXXXXX";
+	  bool use_mkstemp = true;
+
+	  if (oper == oper_print)
+	    xfd = STDOUT_FILENO;
+	  else
+	    {
+	      xfd = mkstemp (tempfname);
+	      if (unlikely (xfd == -1))
+		{
+		  /* We cannot create a temporary file.  Try to overwrite
+		     the file or create it if it does not exist.  */
+		  int flags = O_WRONLY | O_CREAT;
+		  if (dont_replace_existing)
+		    flags |= O_EXCL;
+		  else
+		    flags |= O_TRUNC;
+		  xfd = open (arhdr->ar_name, flags, 0600);
+		  if (unlikely (xfd == -1))
+		    {
+		      int printlen = INT_MAX;
+
+		      if (should_truncate_fname ())
+			{
+			  /* Try to truncate the name.  First find out by how
+			     much.  */
+			  printlen = name_max;
+			  char truncfname[name_max + 1];
+			  *((char *) mempcpy (truncfname, arhdr->ar_name,
+					      name_max)) = '\0';
+
+			  xfd = open (truncfname, flags, 0600);
+			}
+
+		      if (xfd == -1)
+			{
+			  error (0, errno, gettext ("cannot open %.*s"),
+				 (int) printlen, arhdr->ar_name);
+			  status = 1;
+			  goto next;
+			}
+		    }
+
+		  use_mkstemp = false;
+		}
+	    }
+
+	  ssize_t n;
+	  while ((n = TEMP_FAILURE_RETRY (write (xfd, data, nleft))) != -1)
+	    {
+	      nleft -= n;
+	      if (nleft == 0)
+		break;
+	      data += n;
+	    }
+
+	  if (unlikely (n == -1))
+	    {
+	      error (0, errno, gettext ("failed to write %s"), arhdr->ar_name);
+	      status = 1;
+	      unlink (tempfname);
+	      close (xfd);
+	      goto next;
+	    }
+
+	  if (oper != oper_print)
+	    {
+	      /* Fix up the mode.  */
+	      if (unlikely (fchmod (xfd, arhdr->ar_mode) != 0))
+		{
+		  error (0, errno, gettext ("cannot change mode of %s"),
+			 arhdr->ar_name);
+		  status = 0;
+		}
+
+	      if (preserve_dates)
+		{
+		  struct timespec tv[2];
+		  tv[0].tv_sec = arhdr->ar_date;
+		  tv[0].tv_nsec = 0;
+		  tv[1].tv_sec = arhdr->ar_date;
+		  tv[1].tv_nsec = 0;
+
+		  if (unlikely (futimens (xfd, tv) != 0))
+		    {
+		      error (0, errno,
+			     gettext ("cannot change modification time of %s"),
+			     arhdr->ar_name);
+		      status = 1;
+		    }
+		}
+
+	      /* If we used a temporary file, move it do the right
+		 name now.  */
+	      if (use_mkstemp)
+		{
+		  int r;
+
+		  if (dont_replace_existing)
+		    {
+		      r = link (tempfname, arhdr->ar_name);
+		      if (likely (r == 0))
+			unlink (tempfname);
+		    }
+		  else
+		    r = rename (tempfname, arhdr->ar_name);
+
+		  if (unlikely (r) != 0)
+		    {
+		      int printlen = INT_MAX;
+
+		      if (should_truncate_fname ())
+			{
+			  /* Try to truncate the name.  First find out by how
+			     much.  */
+			  printlen = name_max;
+			  char truncfname[name_max + 1];
+			  *((char *) mempcpy (truncfname, arhdr->ar_name,
+					      name_max)) = '\0';
+
+			  if (dont_replace_existing)
+			    {
+			      r = link (tempfname, truncfname);
+			      if (likely (r == 0))
+				unlink (tempfname);
+			    }
+			  else
+			    r = rename (tempfname, truncfname);
+			}
+
+		      if (r != 0)
+			{
+			  error (0, errno, gettext ("\
+cannot rename temporary file to %.*s"),
+				 printlen, arhdr->ar_name);
+			  unlink (tempfname);
+			  status = 1;
+			}
+		    }
+		}
+
+	      close (xfd);
+	    }
+	}
+
+    next:
+      cmd = elf_next (subelf);
+      if (elf_end (subelf) != 0)
+	error (1, 0, "%s: %s", arfname, elf_errmsg (-1));
+    }
+
+  hdestroy ();
+
+  if (force_symtab)
+    {
+      arlib_finalize ();
+
+      if (symtab.symsnamelen != 0
+	  /* We have to rewrite the file also if it initially had an index
+	     but now does not need one anymore.  */
+	  || (symtab.symsnamelen == 0 && index_size != 0))
+	{
+	  char tmpfname[strlen (arfname) + 7];
+	  strcpy (stpcpy (tmpfname, arfname), "XXXXXX");
+	  int newfd = mkstemp (tmpfname);
+	  if (unlikely (newfd == -1))
+	    {
+	    nonew:
+	      error (0, errno, gettext ("cannot create new file"));
+	      status = 1;
+	    }
+	  else
+	    {
+	      /* Create the header.  */
+	      if (unlikely (write_retry (newfd, ARMAG, SARMAG) != SARMAG))
+		{
+		  // XXX Use /prof/self/fd/%d ???
+		nonew_unlink:
+		  unlink (tmpfname);
+		  if (newfd != -1)
+		    close (newfd);
+		  goto nonew;
+		}
+
+	      /* Create the new file.  There are three parts as far we are
+		 concerned: 1. original context before the index, 2. the
+		 new index, 3. everything after the new index.  */
+	      off_t rest_off;
+	      if (index_off != -1)
+		rest_off = (index_off + sizeof (struct ar_hdr)
+			    + ((index_size + 1) & ~1ul));
+	      else
+		rest_off = SARMAG;
+
+	      if ((symtab.symsnamelen != 0
+		   && ((write_retry (newfd, symtab.symsoff,
+				     symtab.symsofflen)
+			!= (ssize_t) symtab.symsofflen)
+		       || (write_retry (newfd, symtab.symsname,
+					symtab.symsnamelen)
+			   != (ssize_t) symtab.symsnamelen)))
+		  /* Even if the original file had content before the
+		     symbol table, we write it in the correct order.  */
+		  || (index_off != SARMAG
+		      && copy_content (elf, newfd, SARMAG, index_off - SARMAG))
+		  || copy_content (elf, newfd, rest_off, st.st_size - rest_off)
+		  /* Set the mode of the new file to the same values the
+		     original file has.  */
+		  || fchmod (newfd, st.st_mode & ALLPERMS) != 0
+		  /* Never complain about fchown failing.  */
+		  || (({asm ("" :: "r" (fchown (newfd, st.st_uid,
+						st.st_gid))); }),
+		      close (newfd) != 0)
+		  || (newfd = -1, rename (tmpfname, arfname) != 0))
+		goto nonew_unlink;
+	    }
+	}
+    }
+
+  elf_end (elf);
+
+  close (fd);
+
+  not_found (argc, argv, found);
+
+  return status;
+}
+
+
+struct armem
+{
+  off_t off;
+  off_t old_off;
+  size_t size;
+  long int long_name_off;
+  struct armem *next;
+  void *mem;
+  time_t sec;
+  uid_t uid;
+  gid_t gid;
+  mode_t mode;
+  const char *name;
+  Elf *elf;
+};
+
+
+static int
+write_member (struct armem *memb, off_t *startp, off_t *lenp, Elf *elf,
+	      off_t end_off, int newfd)
+{
+  struct ar_hdr arhdr;
+  /* The ar_name is not actually zero teminated, but we need that for
+     snprintf.  Also if the name is too long, then the string starts
+     with '/' plus an index off number (decimal).  */
+  char tmpbuf[sizeof (arhdr.ar_name) + 2];
+
+  bool changed_header = memb->long_name_off != -1;
+  if (changed_header)
+    {
+      /* In case of a long file name we assume the archive header
+	 changed and we write it here.  */
+      memcpy (&arhdr, elf_rawfile (elf, NULL) + *startp, sizeof (arhdr));
+
+      snprintf (tmpbuf, sizeof (tmpbuf), "/%-*ld",
+		(int) sizeof (arhdr.ar_name), memb->long_name_off);
+      changed_header = memcmp (arhdr.ar_name, tmpbuf,
+			       sizeof (arhdr.ar_name)) != 0;
+    }
+
+  /* If the files are adjacent in the old file extend the range.  */
+  if (*startp != -1 && !changed_header && *startp + *lenp == memb->old_off)
+    {
+      /* Extend the current range.  */
+      *lenp += (memb->next != NULL
+		? memb->next->off : end_off) - memb->off;
+      return 0;
+    }
+
+  /* Write out the old range.  */
+  if (*startp != -1 && copy_content (elf, newfd, *startp, *lenp))
+    return -1;
+
+  *startp = memb->old_off;
+  *lenp = (memb->next != NULL ? memb->next->off : end_off) - memb->off;
+
+  if (changed_header)
+    {
+      memcpy (arhdr.ar_name, tmpbuf, sizeof (arhdr.ar_name));
+
+      if (unlikely (write_retry (newfd, &arhdr, sizeof (arhdr))
+		    != sizeof (arhdr)))
+	return -1;
+
+      *startp += sizeof (struct ar_hdr);
+      assert ((size_t) *lenp >= sizeof (struct ar_hdr));
+      *lenp -= sizeof (struct ar_hdr);
+    }
+
+  return 0;
+}
+
+/* Store the name in the long name table if necessary.
+   Record its offset or -1 if we did not need to use the table.  */
+static void
+remember_long_name (struct armem *mem, const char *name, size_t namelen)
+{
+  mem->long_name_off = (namelen > MAX_AR_NAME_LEN
+			? arlib_add_long_name (name, namelen)
+			: -1l);
+}
+
+static int
+do_oper_delete (const char *arfname, char **argv, int argc,
+		long int instance)
+{
+  bool *found = alloca (sizeof (bool) * argc);
+  memset (found, '\0', sizeof (bool) * argc);
+
+  /* List of the files we keep.  */
+  struct armem *to_copy = NULL;
+
+  int status = 0;
+  Elf *elf;
+  struct stat st;
+  int fd = open_archive (arfname, O_RDONLY, 0, &elf, &st, false);
+
+  if (hcreate (2 * argc) == 0)
+    error (EXIT_FAILURE, errno, gettext ("cannot create hash table"));
+
+  for (int cnt = 0; cnt < argc; ++cnt)
+    {
+      ENTRY entry = { .key = argv[cnt], .data = &argv[cnt] };
+      if (hsearch (entry, ENTER) == NULL)
+	error (EXIT_FAILURE, errno,
+	       gettext ("cannot insert into hash table"));
+    }
+
+  arlib_init ();
+
+  off_t cur_off = SARMAG;
+  Elf_Cmd cmd = ELF_C_READ_MMAP;
+  Elf *subelf;
+  while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
+    {
+      Elf_Arhdr *arhdr = elf_getarhdr (subelf);
+
+      /* Ignore the symbol table and the long file name table here.  */
+      if (strcmp (arhdr->ar_name, "/") == 0
+	  || strcmp (arhdr->ar_name, "//") == 0)
+	goto next;
+
+      bool do_delete = argc <= 0;
+      if (!do_delete)
+	{
+	  ENTRY entry;
+	  entry.key = arhdr->ar_name;
+	  ENTRY *res = hsearch (entry, FIND);
+	  if (res != NULL && (instance < 0 || instance-- == 0)
+	      && !found[(char **) res->data - argv])
+	    found[(char **) res->data - argv] = do_delete = true;
+	}
+
+      if (do_delete)
+	{
+	  if (verbose)
+	    printf ("d - %s\n", arhdr->ar_name);
+	}
+      else
+	{
+	  struct armem *newp = alloca (sizeof (struct armem));
+	  newp->old_off = elf_getaroff (subelf);
+	  newp->off = cur_off;
+
+	  cur_off += (((arhdr->ar_size + 1) & ~((off_t) 1))
+		      + sizeof (struct ar_hdr));
+
+	  if (to_copy == NULL)
+	    to_copy = newp->next = newp;
+	  else
+	    {
+	      newp->next = to_copy->next;
+	      to_copy = to_copy->next = newp;
+	    }
+
+	  /* If we recreate the symbol table read the file's symbol
+	     table now.  */
+	  arlib_add_symbols (subelf, arfname, arhdr->ar_name, newp->off);
+
+	  /* Remember long file names.  */
+	  remember_long_name (newp, arhdr->ar_name, strlen (arhdr->ar_name));
+	}
+
+    next:
+      cmd = elf_next (subelf);
+      if (elf_end (subelf) != 0)
+	error (1, 0, "%s: %s", arfname, elf_errmsg (-1));
+    }
+
+  arlib_finalize ();
+
+  hdestroy ();
+
+  /* Create a new, temporary file in the same directory as the
+     original file.  */
+  char tmpfname[strlen (arfname) + 7];
+  strcpy (stpcpy (tmpfname, arfname), "XXXXXX");
+  int newfd = mkstemp (tmpfname);
+  if (unlikely (newfd == -1))
+    goto nonew;
+
+  /* Create the header.  */
+  if (unlikely (write_retry (newfd, ARMAG, SARMAG) != SARMAG))
+    {
+      // XXX Use /prof/self/fd/%d ???
+    nonew_unlink:
+      unlink (tmpfname);
+      if (newfd != -1)
+	close (newfd);
+    nonew:
+      error (0, errno, gettext ("cannot create new file"));
+      status = 1;
+      goto errout;
+    }
+
+  /* If the archive is empty that is all we have to do.  */
+  if (likely (to_copy != NULL))
+    {
+      /* Write the symbol table or the long file name table or both.  */
+      if (symtab.symsnamelen != 0
+	  && ((write_retry (newfd, symtab.symsoff, symtab.symsofflen)
+	       != (ssize_t) symtab.symsofflen)
+	      || (write_retry (newfd, symtab.symsname, symtab.symsnamelen)
+		  != (ssize_t) symtab.symsnamelen)))
+	goto nonew_unlink;
+
+      if (symtab.longnameslen > sizeof (struct ar_hdr)
+	  && (write_retry (newfd, symtab.longnames, symtab.longnameslen)
+	      != (ssize_t) symtab.longnameslen))
+	goto nonew_unlink;
+
+      /* NULL-terminate the list of files to copy.  */
+      struct armem *last = to_copy;
+      to_copy = to_copy->next;
+      last->next = NULL;
+
+      off_t start = -1;
+      off_t len = -1;
+
+      do
+	if (write_member (to_copy, &start, &len, elf, cur_off, newfd) != 0)
+	  goto nonew_unlink;
+      while ((to_copy = to_copy->next) != NULL);
+
+      /* Write the last part.  */
+      if (copy_content (elf, newfd, start, len))
+	goto nonew_unlink;
+    }
+
+  /* Set the mode of the new file to the same values the original file
+     has.  */
+  if (fchmod (newfd, st.st_mode & ALLPERMS) != 0
+      /* Never complain about fchown failing.  */
+      || (({asm ("" :: "r" (fchown (newfd, st.st_uid, st.st_gid))); }),
+	  close (newfd) != 0)
+      || (newfd = -1, rename (tmpfname, arfname) != 0))
+    goto nonew_unlink;
+
+ errout:
+  elf_end (elf);
+
+  arlib_fini ();
+
+  close (fd);
+
+  not_found (argc, argv, found);
+
+  return status;
+}
+
+
+/* Prints the given value in the given buffer without a trailing zero char.
+   Returns false if the given value doesn't fit in the given buffer.  */
+static bool
+no0print (bool ofmt, char *buf, int bufsize, long int val)
+{
+  char tmpbuf[bufsize + 1];
+  int ret = snprintf (tmpbuf, sizeof (tmpbuf), ofmt ? "%-*lo" : "%-*ld",
+		      bufsize, val);
+  if (ret >= (int) sizeof (tmpbuf))
+    return false;
+  memcpy (buf, tmpbuf, bufsize);
+  return true;
+}
+
+
+static int
+do_oper_insert (int oper, const char *arfname, char **argv, int argc,
+		const char *member)
+{
+  int status = 0;
+  Elf *elf = NULL;
+  struct stat st;
+  int fd = open_archive (arfname, O_RDONLY, 0, &elf, &st, oper != oper_move);
+
+  /* List of the files we keep.  */
+  struct armem *all = NULL;
+  struct armem *after_memberelem = NULL;
+  struct armem **found = alloca (sizeof (*found) * argc);
+  memset (found, '\0', sizeof (*found) * argc);
+
+  arlib_init ();
+
+  /* Initialize early for no_old case.  */
+  off_t cur_off = SARMAG;
+
+  if (fd == -1)
+    {
+      if (!suppress_create_msg)
+	fprintf (stderr, "%s: creating %s\n",
+		 program_invocation_short_name, arfname);
+
+      goto no_old;
+    }
+
+  /* Store the names of all files from the command line in a hash
+     table so that we can match it.  Note that when no file name is
+     given we are basically doing nothing except recreating the
+     index.  */
+  if (oper != oper_qappend)
+    {
+      if (hcreate (2 * argc) == 0)
+	error (EXIT_FAILURE, errno, gettext ("cannot create hash table"));
+
+      for (int cnt = 0; cnt < argc; ++cnt)
+	{
+	  ENTRY entry;
+	  entry.key = full_path ? argv[cnt] : basename (argv[cnt]);
+	  entry.data = &argv[cnt];
+	  if (hsearch (entry, ENTER) == NULL)
+	    error (EXIT_FAILURE, errno,
+		   gettext ("cannot insert into hash table"));
+	}
+    }
+
+  /* While iterating over the current content of the archive we must
+     determine a number of things: which archive members to keep,
+     which are replaced, and where to insert the new members.  */
+  Elf_Cmd cmd = ELF_C_READ_MMAP;
+  Elf *subelf;
+  while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
+    {
+      Elf_Arhdr *arhdr = elf_getarhdr (subelf);
+
+      /* Ignore the symbol table and the long file name table here.  */
+      if (strcmp (arhdr->ar_name, "/") == 0
+	  || strcmp (arhdr->ar_name, "//") == 0)
+	goto next;
+
+      struct armem *newp = alloca (sizeof (struct armem));
+      newp->old_off = elf_getaroff (subelf);
+      newp->size = arhdr->ar_size;
+      newp->sec = arhdr->ar_date;
+      newp->mem = NULL;
+
+      /* Remember long file names.  */
+      remember_long_name (newp, arhdr->ar_name, strlen (arhdr->ar_name));
+
+      /* Check whether this is a file we are looking for.  */
+      if (oper != oper_qappend)
+	{
+	  /* Check whether this is the member used as the insert point.  */
+	  if (member != NULL && strcmp (arhdr->ar_name, member) == 0)
+	    {
+	      /* Note that all == NULL means insert at the beginning.  */
+	      if (ipos == ipos_before)
+		after_memberelem = all;
+	      else
+		after_memberelem = newp;
+	      member = NULL;
+	    }
+
+	  ENTRY entry;
+	  entry.key = arhdr->ar_name;
+	  ENTRY *res = hsearch (entry, FIND);
+	  if (res != NULL && found[(char **) res->data - argv] == NULL)
+	    {
+	      found[(char **) res->data - argv] = newp;
+
+	      /* If we insert before or after a certain element move
+		 all files to a special list.  */
+	      if (unlikely (ipos != ipos_none || oper == oper_move))
+		{
+		  if (after_memberelem == newp)
+		    /* Since we remove this element even though we should
+		       insert everything after it, we in fact insert
+		       everything after the previous element.  */
+		    after_memberelem = all;
+
+		  goto next;
+		}
+	    }
+	}
+
+      if (all == NULL)
+	all = newp->next = newp;
+      else
+	{
+	  newp->next = all->next;
+	  all = all->next = newp;
+	}
+
+    next:
+      cmd = elf_next (subelf);
+      if (elf_end (subelf) != 0)
+	error (EXIT_FAILURE, 0, "%s: %s", arfname, elf_errmsg (-1));
+    }
+
+  if (oper != oper_qappend)
+    hdestroy ();
+
+ no_old:
+  if (member != NULL)
+    error (EXIT_FAILURE, 0, gettext ("position member %s not found"),
+	   member);
+
+  if (oper == oper_move)
+    {
+      /* Make sure all requested elements are found in the archive.  */
+      for (int cnt = 0; cnt < argc; ++cnt)
+	{
+	  if (found[cnt] == NULL)
+	    {
+	      fprintf (stderr, gettext ("%s: no entry %s in archive!\n"),
+		       program_invocation_short_name, argv[cnt]);
+	      status = 1;
+	    }
+
+	  if (verbose)
+	    printf ("m - %s\n", argv[cnt]);
+	}
+    }
+  else
+    {
+      /* Open all the new files, get their sizes and add all symbols.  */
+      for (int cnt = 0; cnt < argc; ++cnt)
+	{
+	  const char *bname = basename (argv[cnt]);
+	  size_t bnamelen = strlen (bname);
+	  if (found[cnt] == NULL)
+	    {
+	      found[cnt] = alloca (sizeof (struct armem));
+	      found[cnt]->old_off = -1;
+
+	      remember_long_name (found[cnt], bname, bnamelen);
+	    }
+
+	  struct stat newst;
+	  Elf *newelf;
+	  int newfd = open (argv[cnt], O_RDONLY);
+	  if (newfd == -1)
+	    {
+	      error (0, errno, gettext ("cannot open %s"), argv[cnt]);
+	      status = 1;
+	    }
+	  else if (fstat (newfd, &newst) == -1)
+	    {
+	      error (0, errno, gettext ("cannot stat %s"), argv[cnt]);
+	      close (newfd);
+	      status = 1;
+	    }
+	  else if (!S_ISREG (newst.st_mode))
+	    {
+	      error (0, errno, gettext ("%s is no regular file"), argv[cnt]);
+	      close (newfd);
+	      status = 1;
+	    }
+	  else if (update_newer
+		   && found[cnt]->old_off != -1l
+		   && found[cnt]->sec > st.st_mtime)
+	    /* Do nothing, the file in the archive is younger.  */
+	    close (newfd);
+	  else if ((newelf = elf_begin (newfd, ELF_C_READ_MMAP, NULL))
+		   == NULL)
+	    {
+	      fprintf (stderr,
+		       gettext ("cannot get ELF descriptor for %s: %s\n"),
+		       argv[cnt], elf_errmsg (-1));
+	      status = 1;
+	    }
+	  else
+	    {
+	      if (verbose)
+		printf ("%c - %s\n",
+			found[cnt]->old_off == -1l ? 'a' : 'r', argv[cnt]);
+
+	      found[cnt]->elf = newelf;
+	      found[cnt]->sec = arlib_deterministic_output ? 0 : newst.st_mtime;
+	      found[cnt]->uid = arlib_deterministic_output ? 0 : newst.st_uid;
+	      found[cnt]->gid = arlib_deterministic_output ? 0 : newst.st_gid;
+	      found[cnt]->mode = newst.st_mode;
+	      found[cnt]->name = bname;
+
+	      found[cnt]->mem = elf_rawfile (newelf, &found[cnt]->size);
+	      if (found[cnt]->mem == NULL
+		  || elf_cntl (newelf, ELF_C_FDDONE) != 0)
+		error (EXIT_FAILURE, 0, gettext ("cannot read %s: %s"),
+		       argv[cnt], elf_errmsg (-1));
+
+	      close (newfd);
+
+	      if (found[cnt]->old_off != -1l)
+		/* Remember long file names.  */
+		remember_long_name (found[cnt], bname, bnamelen);
+	    }
+	}
+    }
+
+  if (status != 0)
+    {
+      elf_end (elf);
+
+      arlib_fini ();
+
+      close (fd);
+
+      return status;
+    }
+
+  /* If we have no entry point so far add at the end.  AFTER_MEMBERELEM
+     being NULL when adding before an entry means add at the beginning.  */
+  if (ipos != ipos_before && after_memberelem == NULL)
+    after_memberelem = all;
+
+  /* Convert the circular list into a normal list first.  */
+  if (all != NULL)
+    {
+      struct armem *tmp = all;
+      all = all->next;
+      tmp->next = NULL;
+    }
+
+  struct armem *last_added = after_memberelem;
+  for (int cnt = 0; cnt < argc; ++cnt)
+    if (oper != oper_replace || found[cnt]->old_off == -1)
+      {
+	if (last_added == NULL)
+	  {
+	    found[cnt]->next = all;
+	    last_added = all = found[cnt];
+	  }
+	else
+	  {
+	    found[cnt]->next = last_added->next;
+	    last_added = last_added->next = found[cnt];
+	  }
+      }
+
+  /* Finally compute the offset and add the symbols for the files
+     after the insert point.  */
+  if (likely (all != NULL))
+    for (struct armem *memp = all; memp != NULL; memp = memp->next)
+      {
+	memp->off = cur_off;
+
+	if (memp->mem == NULL)
+	  {
+	    Elf_Arhdr *arhdr;
+	    /* Fake initializing arhdr and subelf to keep gcc calm.  */
+	    asm ("" : "=m" (arhdr), "=m" (subelf));
+	    if (elf_rand (elf, memp->old_off) == 0
+		|| (subelf = elf_begin (fd, ELF_C_READ_MMAP, elf)) == NULL
+		|| (arhdr = elf_getarhdr (subelf)) == NULL)
+	      /* This should never happen since we already looked at the
+		 archive content.  But who knows...  */
+	      error (EXIT_FAILURE, 0, "%s: %s", arfname, elf_errmsg (-1));
+
+	    arlib_add_symbols (subelf, arfname, arhdr->ar_name, cur_off);
+
+	    elf_end (subelf);
+	  }
+	else
+	  arlib_add_symbols (memp->elf, arfname, memp->name, cur_off);
+
+	cur_off += (((memp->size + 1) & ~((off_t) 1))
+		    + sizeof (struct ar_hdr));
+      }
+
+  /* Now we have all the information for the symbol table and long
+     file name table.  Construct the final layout.  */
+  arlib_finalize ();
+
+  /* Create a new, temporary file in the same directory as the
+     original file.  */
+  char tmpfname[strlen (arfname) + 7];
+  strcpy (stpcpy (tmpfname, arfname), "XXXXXX");
+  int newfd;
+  if (fd != -1)
+    newfd = mkstemp (tmpfname);
+  else
+    {
+      newfd = open (arfname, O_RDWR | O_CREAT | O_EXCL, DEFFILEMODE);
+      if (newfd == -1 && errno == EEXIST)
+	/* Bah, first the file did not exist, now it does.  Restart.  */
+	return do_oper_insert (oper, arfname, argv, argc, member);
+    }
+  if (unlikely (newfd == -1))
+    goto nonew;
+
+  /* Create the header.  */
+  if (unlikely (write_retry (newfd, ARMAG, SARMAG) != SARMAG))
+    {
+    nonew_unlink:
+      if (fd != -1)
+	{
+	  // XXX Use /prof/self/fd/%d ???
+	  unlink (tmpfname);
+	  if (newfd != -1)
+	    close (newfd);
+	}
+    nonew:
+      error (0, errno, gettext ("cannot create new file"));
+      status = 1;
+      goto errout;
+    }
+
+  /* If the new archive is not empty we actually have something to do.  */
+  if (likely (all != NULL))
+    {
+      /* Write the symbol table or the long file name table or both.  */
+      if (symtab.symsnamelen != 0
+	  && ((write_retry (newfd, symtab.symsoff, symtab.symsofflen)
+	       != (ssize_t) symtab.symsofflen)
+	      || (write_retry (newfd, symtab.symsname, symtab.symsnamelen)
+		  != (ssize_t) symtab.symsnamelen)))
+	goto nonew_unlink;
+
+      if (symtab.longnameslen > sizeof (struct ar_hdr)
+	  && (write_retry (newfd, symtab.longnames, symtab.longnameslen)
+	      != (ssize_t) symtab.longnameslen))
+	goto nonew_unlink;
+
+      off_t start = -1;
+      off_t len = -1;
+
+      while (all != NULL)
+	{
+	  if (all->mem != NULL)
+	    {
+	      /* This is a new file.  If there is anything from the
+		 archive left to be written do it now.  */
+	      if (start != -1  && copy_content (elf, newfd, start, len))
+		goto nonew_unlink;
+
+	      start = -1;
+	      len = -1;
+
+	      /* Create the header.  */
+	      struct ar_hdr arhdr;
+	      /* The ar_name is not actually zero teminated, but we
+		 need that for snprintf.  Also if the name is too
+		 long, then the string starts with '/' plus an index
+		 off number (decimal).  */
+	      char tmpbuf[sizeof (arhdr.ar_name) + 2];
+	      if (all->long_name_off == -1)
+		{
+		  size_t namelen = strlen (all->name);
+		  char *p = mempcpy (arhdr.ar_name, all->name, namelen);
+		  *p++ = '/';
+		  memset (p, ' ', sizeof (arhdr.ar_name) - namelen - 1);
+		}
+	      else
+		{
+		  snprintf (tmpbuf, sizeof (tmpbuf), "/%-*ld",
+			    (int) sizeof (arhdr.ar_name), all->long_name_off);
+		  memcpy (arhdr.ar_name, tmpbuf, sizeof (arhdr.ar_name));
+		}
+
+	      if (! no0print (false, arhdr.ar_date, sizeof (arhdr.ar_date),
+			      all->sec))
+		{
+		  error (0, errno, gettext ("cannot represent ar_date"));
+		  goto nonew_unlink;
+		}
+	      if (! no0print (false, arhdr.ar_uid, sizeof (arhdr.ar_uid),
+			      all->uid))
+		{
+		  error (0, errno, gettext ("cannot represent ar_uid"));
+		  goto nonew_unlink;
+		}
+	      if (! no0print (false, arhdr.ar_gid, sizeof (arhdr.ar_gid),
+			      all->gid))
+		{
+		  error (0, errno, gettext ("cannot represent ar_gid"));
+		  goto nonew_unlink;
+		}
+	      if (! no0print (true, arhdr.ar_mode, sizeof (arhdr.ar_mode),
+			all->mode))
+		{
+		  error (0, errno, gettext ("cannot represent ar_mode"));
+		  goto nonew_unlink;
+		}
+	      if (! no0print (false, arhdr.ar_size, sizeof (arhdr.ar_size),
+			all->size))
+		{
+		  error (0, errno, gettext ("cannot represent ar_size"));
+		  goto nonew_unlink;
+		}
+	      memcpy (arhdr.ar_fmag, ARFMAG, sizeof (arhdr.ar_fmag));
+
+	      if (unlikely (write_retry (newfd, &arhdr, sizeof (arhdr))
+			    != sizeof (arhdr)))
+		goto nonew_unlink;
+
+	      /* Now the file itself.  */
+	      if (unlikely (write_retry (newfd, all->mem, all->size)
+			    != (off_t) all->size))
+		goto nonew_unlink;
+
+	      /* Pad the file if its size is odd.  */
+	      if ((all->size & 1) != 0)
+		if (unlikely (write_retry (newfd, "\n", 1) != 1))
+		  goto nonew_unlink;
+	    }
+	  else
+	    {
+	      /* This is a member from the archive.  */
+	      if (write_member (all, &start, &len, elf, cur_off, newfd)
+		  != 0)
+		goto nonew_unlink;
+	    }
+
+	  all = all->next;
+	}
+
+      /* Write the last part.  */
+      if (start != -1 && copy_content (elf, newfd, start, len))
+	goto nonew_unlink;
+    }
+
+  /* Set the mode of the new file to the same values the original file
+     has.  */
+  if (fd != -1
+      && (fchmod (newfd, st.st_mode & ALLPERMS) != 0
+	  /* Never complain about fchown failing.  */
+	  || (({asm ("" :: "r" (fchown (newfd, st.st_uid, st.st_gid))); }),
+	      close (newfd) != 0)
+	  || (newfd = -1, rename (tmpfname, arfname) != 0)))
+      goto nonew_unlink;
+
+ errout:
+  for (int cnt = 0; cnt < argc; ++cnt)
+    elf_end (found[cnt]->elf);
+
+  elf_end (elf);
+
+  arlib_fini ();
+
+  if (fd != -1)
+    close (fd);
+
+  return status;
+}
+
+
+#include "debugpred.h"
diff --git a/src/arlib-argp.c b/src/arlib-argp.c
new file mode 100644
index 0000000..1bdd8d0
--- /dev/null
+++ b/src/arlib-argp.c
@@ -0,0 +1,94 @@
+/* Options common to ar and ranlib.
+   Copyright (C) 2012 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <libintl.h>
+
+#include "arlib.h"
+
+bool arlib_deterministic_output = DEFAULT_AR_DETERMINISTIC;
+
+static const struct argp_option options[] =
+  {
+    { NULL, 'D', NULL, 0,
+      N_("Use zero for uid, gid, and date in archive members."), 0 },
+    { NULL, 'U', NULL, 0,
+      N_("Use actual uid, gid, and date in archive members."), 0 },
+
+    { NULL, 0, NULL, 0, NULL, 0 }
+  };
+
+static error_t
+parse_opt (int key, char *arg __attribute__ ((unused)),
+           struct argp_state *state __attribute__ ((unused)))
+{
+  switch (key)
+    {
+    case 'D':
+      arlib_deterministic_output = true;
+      break;
+
+    case 'U':
+      arlib_deterministic_output = false;
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+static char *
+help_filter (int key, const char *text, void *input __attribute__ ((unused)))
+{
+  inline char *text_for_default (void)
+  {
+    char *new_text;
+    if (unlikely (asprintf (&new_text, gettext ("%s (default)"), text) < 0))
+      return (char *) text;
+    return new_text;
+  }
+
+  switch (key)
+    {
+    case 'D':
+      if (DEFAULT_AR_DETERMINISTIC)
+        return text_for_default ();
+      break;
+    case 'U':
+      if (! DEFAULT_AR_DETERMINISTIC)
+        return text_for_default ();
+      break;
+    }
+
+  return (char *) text;
+}
+
+static const struct argp argp =
+  {
+    options, parse_opt, NULL, NULL, NULL, help_filter, NULL
+  };
+
+const struct argp_child arlib_argp_children[] =
+  {
+    { &argp, 0, "", 2 },
+    { NULL, 0, NULL, 0 }
+  };
diff --git a/src/arlib.c b/src/arlib.c
new file mode 100644
index 0000000..e0839aa
--- /dev/null
+++ b/src/arlib.c
@@ -0,0 +1,277 @@
+/* Functions to handle creation of Linux archives.
+   Copyright (C) 2007-2012, 2016 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2007.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <assert.h>
+#include <error.h>
+#include <gelf.h>
+#include <inttypes.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <libeu.h>
+
+#include "arlib.h"
+
+
+/* The one symbol table we hanble.  */
+struct arlib_symtab symtab;
+
+
+/* Initialize ARLIB_SYMTAB structure.  */
+void
+arlib_init (void)
+{
+#define obstack_chunk_alloc xmalloc
+#define obstack_chunk_free free
+  obstack_init (&symtab.symsoffob);
+  obstack_init (&symtab.symsnameob);
+  obstack_init (&symtab.longnamesob);
+
+  /* We add the archive header here as well, that avoids allocating
+     another memory block.  */
+  struct ar_hdr ar_hdr;
+  memcpy (ar_hdr.ar_name, "/               ", sizeof (ar_hdr.ar_name));
+  /* Using snprintf here has a problem: the call always wants to add a
+     NUL byte.  We could use a trick whereby we specify the target
+     buffer size longer than it is and this would not actually fail,
+     since all the fields are consecutive and we fill them in
+     sequence (i.e., the NUL byte gets overwritten).  But
+     _FORTIFY_SOURCE=2 would not let us play these games.  Therefore
+     we play it safe.  */
+  char tmpbuf[sizeof (ar_hdr.ar_date) + 1];
+  int s = snprintf (tmpbuf, sizeof (tmpbuf), "%-*lld",
+		    (int) sizeof (ar_hdr.ar_date),
+                    (arlib_deterministic_output ? 0
+                     : (long long int) time (NULL)));
+  memcpy (ar_hdr.ar_date, tmpbuf, s);
+  assert ((sizeof (struct ar_hdr)  % sizeof (uint32_t)) == 0);
+
+  /* Note the string for the ar_uid and ar_gid cases is longer than
+     necessary.  This does not matter since we copy only as much as
+     necessary but it helps the compiler to use the same string for
+     the ar_mode case.  */
+  memcpy (ar_hdr.ar_uid, "0       ", sizeof (ar_hdr.ar_uid));
+  memcpy (ar_hdr.ar_gid, "0       ", sizeof (ar_hdr.ar_gid));
+  memcpy (ar_hdr.ar_mode, "0       ", sizeof (ar_hdr.ar_mode));
+  memcpy (ar_hdr.ar_fmag, ARFMAG, sizeof (ar_hdr.ar_fmag));
+
+  /* Add the archive header to the file content.  */
+  obstack_grow (&symtab.symsoffob, &ar_hdr, sizeof (ar_hdr));
+
+  /* The first word in the offset table specifies the size.  Create
+     such an entry now.  The real value will be filled-in later.  For
+     all supported platforms the following is true.  */
+  assert (sizeof (uint32_t) == sizeof (int));
+  obstack_int_grow (&symtab.symsoffob, 0);
+
+  /* The long name obstack also gets its archive header.  As above,
+     some of the input strings are longer than required but we only
+     copy the necessary part.  */
+  memcpy (ar_hdr.ar_name, "//              ", sizeof (ar_hdr.ar_name));
+  memcpy (ar_hdr.ar_date, "            ", sizeof (ar_hdr.ar_date));
+  memcpy (ar_hdr.ar_uid, "            ", sizeof (ar_hdr.ar_uid));
+  memcpy (ar_hdr.ar_gid, "            ", sizeof (ar_hdr.ar_gid));
+  memcpy (ar_hdr.ar_mode, "            ", sizeof (ar_hdr.ar_mode));
+  /* The ar_size field will be filled in later and ar_fmag is already OK.  */
+  obstack_grow (&symtab.longnamesob, &ar_hdr, sizeof (ar_hdr));
+
+  /* All other members are zero.  */
+  symtab.symsofflen = 0;
+  symtab.symsoff = NULL;
+  symtab.symsnamelen = 0;
+  symtab.symsname = NULL;
+}
+
+
+/* Finalize ARLIB_SYMTAB content.  */
+void
+arlib_finalize (void)
+{
+  /* Note that the size is stored as decimal string in 10 chars,
+     without zero terminator (we add + 1 here only so snprintf can
+     put it at the end, we then don't use it when we memcpy it).  */
+  char tmpbuf[sizeof (((struct ar_hdr *) NULL)->ar_size) + 1];
+
+  symtab.longnameslen = obstack_object_size (&symtab.longnamesob);
+  if (symtab.longnameslen != sizeof (struct ar_hdr))
+    {
+      if ((symtab.longnameslen & 1) != 0)
+	{
+	  /* Add one more byte to make length even.  */
+	  obstack_grow (&symtab.longnamesob, "\n", 1);
+	  ++symtab.longnameslen;
+	}
+
+      symtab.longnames = obstack_finish (&symtab.longnamesob);
+
+      int s = snprintf (tmpbuf, sizeof (tmpbuf), "%-*" PRIu32 "",
+			(int) sizeof (((struct ar_hdr *) NULL)->ar_size),
+			(uint32_t) (symtab.longnameslen - sizeof (struct ar_hdr)));
+      memcpy (&((struct ar_hdr *) symtab.longnames)->ar_size, tmpbuf, s);
+    }
+
+  symtab.symsofflen = obstack_object_size (&symtab.symsoffob);
+  assert (symtab.symsofflen % sizeof (uint32_t) == 0);
+  if (symtab.symsofflen != 0)
+    {
+      symtab.symsoff = (uint32_t *) obstack_finish (&symtab.symsoffob);
+
+      /* Fill in the number of offsets now.  */
+      symtab.symsoff[AR_HDR_WORDS] = le_bswap_32 ((symtab.symsofflen
+						    - sizeof (struct ar_hdr))
+						   / sizeof (uint32_t) - 1);
+    }
+
+  symtab.symsnamelen = obstack_object_size (&symtab.symsnameob);
+  if ((symtab.symsnamelen & 1) != 0)
+    {
+      /* Add one more NUL byte to make length even.  */
+      obstack_grow (&symtab.symsnameob, "", 1);
+      ++symtab.symsnamelen;
+    }
+  symtab.symsname = obstack_finish (&symtab.symsnameob);
+
+  /* Determine correction for the offsets in the symbol table.   */
+  off_t disp = 0;
+  if (symtab.symsnamelen > 0)
+    disp = symtab.symsofflen + symtab.symsnamelen;
+  if (symtab.longnameslen > sizeof (struct ar_hdr))
+    disp += symtab.longnameslen;
+
+  if (disp != 0 && symtab.symsoff != NULL)
+    {
+      uint32_t nsyms = le_bswap_32 (symtab.symsoff[AR_HDR_WORDS]);
+
+      for (uint32_t cnt = 1; cnt <= nsyms; ++cnt)
+	{
+	  uint32_t val = le_bswap_32 (symtab.symsoff[AR_HDR_WORDS + cnt]);
+	  val += disp;
+	  symtab.symsoff[AR_HDR_WORDS + cnt] = le_bswap_32 (val);
+	}
+    }
+
+  /* See comment for ar_date above.  */
+  memcpy (&((struct ar_hdr *) symtab.symsoff)->ar_size, tmpbuf,
+	  snprintf (tmpbuf, sizeof (tmpbuf), "%-*" PRIu32 "",
+		    (int) sizeof (((struct ar_hdr *) NULL)->ar_size),
+		    (uint32_t) (symtab.symsofflen + symtab.symsnamelen
+				- sizeof (struct ar_hdr))));
+}
+
+
+/* Free resources for ARLIB_SYMTAB.  */
+void
+arlib_fini (void)
+{
+  obstack_free (&symtab.symsoffob, NULL);
+  obstack_free (&symtab.symsnameob, NULL);
+  obstack_free (&symtab.longnamesob, NULL);
+}
+
+
+/* Add name a file offset of a symbol.  */
+void
+arlib_add_symref (const char *symname, off_t symoff)
+{
+  /* For all supported platforms the following is true.  */
+  assert (sizeof (uint32_t) == sizeof (int));
+  obstack_int_grow (&symtab.symsoffob, (int) le_bswap_32 (symoff));
+
+  size_t symname_len = strlen (symname) + 1;
+  obstack_grow (&symtab.symsnameob, symname, symname_len);
+}
+
+
+/* Add symbols from ELF with value OFFSET to the symbol table SYMTAB.  */
+void
+arlib_add_symbols (Elf *elf, const char *arfname, const char *membername,
+		   off_t off)
+{
+  if (sizeof (off) > sizeof (uint32_t) && off > ~((uint32_t) 0))
+    /* The archive is too big.  */
+    error (EXIT_FAILURE, 0, gettext ("the archive '%s' is too large"),
+	   arfname);
+
+  /* We only add symbol tables for ELF files.  It makes not much sense
+     to add symbols from executables but we do so for compatibility.
+     For DSOs and executables we use the dynamic symbol table, for
+     relocatable files all the DT_SYMTAB tables.  */
+  if (elf_kind (elf) != ELF_K_ELF)
+    return;
+
+  GElf_Ehdr ehdr_mem;
+  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
+  if (ehdr == NULL)
+    error (EXIT_FAILURE, 0, gettext ("cannot read ELF header of %s(%s): %s"),
+	   arfname, membername, elf_errmsg (-1));
+
+  GElf_Word symtype;
+  if (ehdr->e_type == ET_REL)
+    symtype = SHT_SYMTAB;
+  else if (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_DYN)
+    symtype = SHT_DYNSYM;
+  else
+    /* We do not handle that type.  */
+    return;
+
+  /* Iterate over all sections.  */
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (elf, scn)) != NULL)
+    {
+      /* Get the section header.  */
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+      if (shdr == NULL)
+	continue;
+
+      if (shdr->sh_type != symtype)
+	continue;
+
+      Elf_Data *data = elf_getdata (scn, NULL);
+      if (data == NULL)
+	continue;
+
+      int nsyms = shdr->sh_size / shdr->sh_entsize;
+      for (int ndx = shdr->sh_info; ndx < nsyms; ++ndx)
+	{
+	  GElf_Sym sym_mem;
+	  GElf_Sym *sym = gelf_getsym (data, ndx, &sym_mem);
+	  if (sym == NULL)
+	    continue;
+
+	  /* Ignore undefined symbols.  */
+	  if (sym->st_shndx == SHN_UNDEF)
+	    continue;
+
+	  /* Use this symbol.  */
+	  const char *symname = elf_strptr (elf, shdr->sh_link, sym->st_name);
+	  if (symname != NULL)
+	    arlib_add_symref (symname, off);
+	}
+
+      /* Only relocatable files can have more than one symbol table.  */
+      if (ehdr->e_type != ET_REL)
+	break;
+    }
+}
diff --git a/src/arlib.h b/src/arlib.h
new file mode 100644
index 0000000..e117166
--- /dev/null
+++ b/src/arlib.h
@@ -0,0 +1,98 @@
+/* Copyright (C) 2007-2012 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2007.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef _ARLIB_H
+#define _ARLIB_H	1
+
+#include <ar.h>
+#include <argp.h>
+#include <byteswap.h>
+#include <endian.h>
+#include <libelf.h>
+#include <obstack.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+
+/* State of -D/-U flags.  */
+extern bool arlib_deterministic_output;
+
+/* For options common to ar and ranlib.  */
+extern const struct argp_child arlib_argp_children[];
+
+
+/* Maximum length of a file name that fits directly into the ar header.
+   We cannot use the final byte since a / goes there.  */
+#define MAX_AR_NAME_LEN (sizeof (((struct ar_hdr *) NULL)->ar_name) - 1)
+
+
+/* Words matching in size to archive header.  */
+#define AR_HDR_WORDS (sizeof (struct ar_hdr) / sizeof (uint32_t))
+
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+# define le_bswap_32(val) bswap_32 (val)
+#else
+# define le_bswap_32(val) (val)
+#endif
+
+
+/* Symbol table type.  */
+struct arlib_symtab
+{
+  /* Symbol table handling.  */
+  struct obstack symsoffob;
+  struct obstack symsnameob;
+  size_t symsofflen;
+  uint32_t *symsoff;
+  size_t symsnamelen;
+  char *symsname;
+
+  /* Long filename handling.  */
+  struct obstack longnamesob;
+  size_t longnameslen;
+  char *longnames;
+};
+
+
+/* Global variable with symbol table.  */
+extern struct arlib_symtab symtab;
+
+
+/* Initialize ARLIB_SYMTAB structure.  */
+extern void arlib_init (void);
+
+/* Finalize ARLIB_SYMTAB content.  */
+extern void arlib_finalize (void);
+
+/* Free resources for ARLIB_SYMTAB.  */
+extern void arlib_fini (void);
+
+/* Add symbols from ELF with value OFFSET to the symbol table SYMTAB.  */
+extern void arlib_add_symbols (Elf *elf, const char *arfname,
+			       const char *membername, off_t off);
+
+/* Add name a file offset of a symbol.  */
+extern void arlib_add_symref (const char *symname, off_t symoff);
+
+/* Add long file name FILENAME of length FILENAMELEN to the symbol table
+   SYMTAB.  Return the offset into the long file name table.  */
+extern long int arlib_add_long_name (const char *filename, size_t filenamelen);
+
+#endif	/* arlib.h */
diff --git a/src/arlib2.c b/src/arlib2.c
new file mode 100644
index 0000000..553fc57
--- /dev/null
+++ b/src/arlib2.c
@@ -0,0 +1,42 @@
+/* Functions to handle creation of Linux archives.
+   Copyright (C) 2007 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2007.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <error.h>
+#include <libintl.h>
+#include <limits.h>
+#include <string.h>
+
+#include "arlib.h"
+
+
+/* Add long file name FILENAME of length FILENAMELEN to the symbol table
+   SYMTAB.  Return the offset into the long file name table.  */
+long int
+arlib_add_long_name (const char *filename, size_t filenamelen)
+{
+  size_t size = obstack_object_size (&symtab.longnamesob);
+
+  obstack_grow (&symtab.longnamesob, filename, filenamelen);
+  obstack_grow (&symtab.longnamesob, "/\n", 2);
+
+  return size - sizeof (struct ar_hdr);
+}
diff --git a/src/debugpred.h b/src/debugpred.h
new file mode 100644
index 0000000..4845a6e
--- /dev/null
+++ b/src/debugpred.h
@@ -0,0 +1,45 @@
+/* Support to debug branch prediction.
+   Copyright (C) 2007 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2007.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+
+#if DEBUGPRED
+extern const unsigned long int __start_predict_data;
+extern const unsigned long int __stop_predict_data;
+extern const unsigned long int __start_predict_line;
+extern const char *const __start_predict_file;
+
+static void
+__attribute__ ((destructor))
+predprint (void)
+{
+  const unsigned long int *s = &__start_predict_data;
+  const unsigned long int *e = &__stop_predict_data;
+  const unsigned long int *sl = &__start_predict_line;
+  const char *const *sf = &__start_predict_file;
+  while (s < e)
+    {
+      if (s[0] != 0 || s[1] != 0)
+	printf ("%s:%lu: wrong=%lu, correct=%lu%s\n", *sf, *sl, s[0], s[1],
+		s[0] > s[1] ? "   <==== WARNING" : "");
+      ++sl;
+      ++sf;
+      s += 2;
+    }
+}
+#endif
diff --git a/src/elfcmp.c b/src/elfcmp.c
new file mode 100644
index 0000000..5046420
--- /dev/null
+++ b/src/elfcmp.c
@@ -0,0 +1,896 @@
+/* Compare relevant content of two ELF files.
+   Copyright (C) 2005-2012, 2014, 2015 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2005.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <assert.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <libintl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <printversion.h>
+#include "../libelf/elf-knowledge.h"
+#include "../libebl/libeblP.h"
+
+
+/* Prototypes of local functions.  */
+static Elf *open_file (const char *fname, int *fdp, Ebl **eblp);
+static bool search_for_copy_reloc (Ebl *ebl, size_t scnndx, int symndx);
+static  int regioncompare (const void *p1, const void *p2);
+
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+/* Values for the parameters which have no short form.  */
+#define OPT_GAPS		0x100
+#define OPT_HASH_INEXACT	0x101
+#define OPT_IGNORE_BUILD_ID	0x102
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, N_("Control options:"), 0 },
+  { "verbose", 'l', NULL, 0,
+    N_("Output all differences, not just the first"), 0 },
+  { "gaps", OPT_GAPS, "ACTION", 0, N_("Control treatment of gaps in loadable segments [ignore|match] (default: ignore)"), 0 },
+  { "hash-inexact", OPT_HASH_INEXACT, NULL, 0,
+    N_("Ignore permutation of buckets in SHT_HASH section"), 0 },
+  { "ignore-build-id", OPT_IGNORE_BUILD_ID, NULL, 0,
+    N_("Ignore differences in build ID"), 0 },
+  { "quiet", 'q', NULL, 0, N_("Output nothing; yield exit status only"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Miscellaneous:"), 0 },
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("\
+Compare relevant parts of two ELF files for equality.");
+
+/* Strings for arguments in help texts.  */
+static const char args_doc[] = N_("FILE1 FILE2");
+
+/* 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
+};
+
+
+/* How to treat gaps in loadable segments.  */
+static enum
+  {
+    gaps_ignore = 0,
+    gaps_match
+  }
+  gaps;
+
+/* Structure to hold information about used regions.  */
+struct region
+{
+  GElf_Addr from;
+  GElf_Addr to;
+  struct region *next;
+};
+
+/* Nonzero if only exit status is wanted.  */
+static bool quiet;
+
+/* True iff multiple differences should be output.  */
+static bool verbose;
+
+/* True iff SHT_HASH treatment should be generous.  */
+static bool hash_inexact;
+
+/* True iff build ID notes should be ignored.  */
+static bool ignore_build_id;
+
+static bool hash_content_equivalent (size_t entsize, Elf_Data *, Elf_Data *);
+
+
+int
+main (int argc, char *argv[])
+{
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  (void) textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  int remaining;
+  (void) argp_parse (&argp, argc, argv, 0, &remaining, NULL);
+
+  /* We expect exactly two non-option parameters.  */
+  if (unlikely (remaining + 2 != argc))
+    {
+      fputs (gettext ("Invalid number of parameters.\n"), stderr);
+      argp_help (&argp, stderr, ARGP_HELP_SEE, program_invocation_short_name);
+      exit (1);
+    }
+
+  if (quiet)
+    verbose = false;
+
+  /* Comparing the files is done in two phases:
+     1. compare all sections.  Sections which are irrelevant (i.e., if
+	strip would remove them) are ignored.  Some section types are
+	handled special.
+     2. all parts of the loadable segments which are not parts of any
+	section is compared according to the rules of the --gaps option.
+  */
+  int result = 0;
+  elf_version (EV_CURRENT);
+
+  const char *const fname1 = argv[remaining];
+  int fd1;
+  Ebl *ebl1;
+  Elf *elf1 = open_file (fname1, &fd1, &ebl1);
+
+  const char *const fname2 = argv[remaining + 1];
+  int fd2;
+  Ebl *ebl2;
+  Elf *elf2 = open_file (fname2, &fd2, &ebl2);
+
+  GElf_Ehdr ehdr1_mem;
+  GElf_Ehdr *ehdr1 = gelf_getehdr (elf1, &ehdr1_mem);
+  if (ehdr1 == NULL)
+    error (2, 0, gettext ("cannot get ELF header of '%s': %s"),
+	   fname1, elf_errmsg (-1));
+  GElf_Ehdr ehdr2_mem;
+  GElf_Ehdr *ehdr2 = gelf_getehdr (elf2, &ehdr2_mem);
+  if (ehdr2 == NULL)
+    error (2, 0, gettext ("cannot get ELF header of '%s': %s"),
+	   fname2, elf_errmsg (-1));
+
+#define DIFFERENCE							      \
+  do									      \
+    {									      \
+      result = 1;							      \
+      if (! verbose)							      \
+	goto out;							      \
+    }									      \
+  while (0)
+
+  /* Compare the ELF headers.  */
+  if (unlikely (memcmp (ehdr1->e_ident, ehdr2->e_ident, EI_NIDENT) != 0
+		|| ehdr1->e_type != ehdr2->e_type
+		|| ehdr1->e_machine != ehdr2->e_machine
+		|| ehdr1->e_version != ehdr2->e_version
+		|| ehdr1->e_entry != ehdr2->e_entry
+		|| ehdr1->e_phoff != ehdr2->e_phoff
+		|| ehdr1->e_flags != ehdr2->e_flags
+		|| ehdr1->e_ehsize != ehdr2->e_ehsize
+		|| ehdr1->e_phentsize != ehdr2->e_phentsize
+		|| ehdr1->e_phnum != ehdr2->e_phnum
+		|| ehdr1->e_shentsize != ehdr2->e_shentsize))
+    {
+      if (! quiet)
+	error (0, 0, gettext ("%s %s diff: ELF header"), fname1, fname2);
+      DIFFERENCE;
+    }
+
+  size_t shnum1;
+  size_t shnum2;
+  if (unlikely (elf_getshdrnum (elf1, &shnum1) != 0))
+    error (2, 0, gettext ("cannot get section count of '%s': %s"),
+	   fname1, elf_errmsg (-1));
+  if (unlikely (elf_getshdrnum (elf2, &shnum2) != 0))
+    error (2, 0, gettext ("cannot get section count of '%s': %s"),
+	   fname2, elf_errmsg (-1));
+  if (unlikely (shnum1 != shnum2))
+    {
+      if (! quiet)
+	error (0, 0, gettext ("%s %s diff: section count"), fname1, fname2);
+      DIFFERENCE;
+    }
+
+  size_t phnum1;
+  size_t phnum2;
+  if (unlikely (elf_getphdrnum (elf1, &phnum1) != 0))
+    error (2, 0, gettext ("cannot get program header count of '%s': %s"),
+	   fname1, elf_errmsg (-1));
+  if (unlikely (elf_getphdrnum (elf2, &phnum2) != 0))
+    error (2, 0, gettext ("cannot get program header count of '%s': %s"),
+	   fname2, elf_errmsg (-1));
+  if (unlikely (phnum1 != phnum2))
+    {
+      if (! quiet)
+	error (0, 0, gettext ("%s %s diff: program header count"),
+	       fname1, fname2);
+      DIFFERENCE;
+    }
+
+  /* Iterate over all sections.  We expect the sections in the two
+     files to match exactly.  */
+  Elf_Scn *scn1 = NULL;
+  Elf_Scn *scn2 = NULL;
+  struct region *regions = NULL;
+  size_t nregions = 0;
+  while (1)
+    {
+      GElf_Shdr shdr1_mem;
+      GElf_Shdr *shdr1;
+      const char *sname1 = NULL;
+      do
+	{
+	  scn1 = elf_nextscn (elf1, scn1);
+	  shdr1 = gelf_getshdr (scn1, &shdr1_mem);
+	  if (shdr1 != NULL)
+	    sname1 = elf_strptr (elf1, ehdr1->e_shstrndx, shdr1->sh_name);
+	}
+      while (scn1 != NULL
+	     && ebl_section_strip_p (ebl1, ehdr1, shdr1, sname1, true, false));
+
+      GElf_Shdr shdr2_mem;
+      GElf_Shdr *shdr2;
+      const char *sname2 = NULL;
+      do
+	{
+	  scn2 = elf_nextscn (elf2, scn2);
+	  shdr2 = gelf_getshdr (scn2, &shdr2_mem);
+	  if (shdr2 != NULL)
+	    sname2 = elf_strptr (elf2, ehdr2->e_shstrndx, shdr2->sh_name);
+	}
+      while (scn2 != NULL
+	     && ebl_section_strip_p (ebl2, ehdr2, shdr2, sname2, true, false));
+
+      if (scn1 == NULL || scn2 == NULL)
+	break;
+
+      if (gaps != gaps_ignore && (shdr1->sh_flags & SHF_ALLOC) != 0)
+	{
+	  struct region *newp = (struct region *) alloca (sizeof (*newp));
+	  newp->from = shdr1->sh_offset;
+	  newp->to = shdr1->sh_offset + shdr1->sh_size;
+	  newp->next = regions;
+	  regions = newp;
+
+	  ++nregions;
+	}
+
+      /* Compare the headers.  We allow the name to be at a different
+	 location.  */
+      if (unlikely (sname1 == NULL || sname2 == NULL
+		    || strcmp (sname1, sname2) != 0))
+	{
+	  error (0, 0, gettext ("%s %s differ: section [%zu], [%zu] name"),
+		 fname1, fname2, elf_ndxscn (scn1), elf_ndxscn (scn2));
+	  DIFFERENCE;
+	}
+
+      /* We ignore certain sections.  */
+      if ((sname1 != NULL && strcmp (sname1, ".gnu_debuglink") == 0)
+	  || (sname1 != NULL && strcmp (sname1, ".gnu.prelink_undo") == 0))
+	continue;
+
+      if (shdr1->sh_type != shdr2->sh_type
+	  // XXX Any flags which should be ignored?
+	  || shdr1->sh_flags != shdr2->sh_flags
+	  || shdr1->sh_addr != shdr2->sh_addr
+	  || (shdr1->sh_offset != shdr2->sh_offset
+	      && (shdr1->sh_flags & SHF_ALLOC)
+	      && ehdr1->e_type != ET_REL)
+	  || shdr1->sh_size != shdr2->sh_size
+	  || shdr1->sh_link != shdr2->sh_link
+	  || shdr1->sh_info != shdr2->sh_info
+	  || shdr1->sh_addralign != shdr2->sh_addralign
+	  || shdr1->sh_entsize != shdr2->sh_entsize)
+	{
+	  error (0, 0, gettext ("%s %s differ: section [%zu] '%s' header"),
+		 fname1, fname2, elf_ndxscn (scn1), sname1);
+	  DIFFERENCE;
+	}
+
+      Elf_Data *data1 = elf_getdata (scn1, NULL);
+      if (data1 == NULL)
+	error (2, 0,
+	       gettext ("cannot get content of section %zu in '%s': %s"),
+	       elf_ndxscn (scn1), fname1, elf_errmsg (-1));
+
+      Elf_Data *data2 = elf_getdata (scn2, NULL);
+      if (data2 == NULL)
+	error (2, 0,
+	       gettext ("cannot get content of section %zu in '%s': %s"),
+	       elf_ndxscn (scn2), fname2, elf_errmsg (-1));
+
+      switch (shdr1->sh_type)
+	{
+	case SHT_DYNSYM:
+	case SHT_SYMTAB:
+	  if (shdr1->sh_entsize == 0)
+	    error (2, 0,
+		   gettext ("symbol table [%zu] in '%s' has zero sh_entsize"),
+		   elf_ndxscn (scn1), fname1);
+
+	  /* Iterate over the symbol table.  We ignore the st_size
+	     value of undefined symbols.  */
+	  for (int ndx = 0; ndx < (int) (shdr1->sh_size / shdr1->sh_entsize);
+	       ++ndx)
+	    {
+	      GElf_Sym sym1_mem;
+	      GElf_Sym *sym1 = gelf_getsym (data1, ndx, &sym1_mem);
+	      if (sym1 == NULL)
+		error (2, 0,
+		       gettext ("cannot get symbol in '%s': %s"),
+		       fname1, elf_errmsg (-1));
+	      GElf_Sym sym2_mem;
+	      GElf_Sym *sym2 = gelf_getsym (data2, ndx, &sym2_mem);
+	      if (sym2 == NULL)
+		error (2, 0,
+		       gettext ("cannot get symbol in '%s': %s"),
+		       fname2, elf_errmsg (-1));
+
+	      const char *name1 = elf_strptr (elf1, shdr1->sh_link,
+					      sym1->st_name);
+	      const char *name2 = elf_strptr (elf2, shdr2->sh_link,
+					      sym2->st_name);
+	      if (unlikely (name1 == NULL || name2 == NULL
+			    || strcmp (name1, name2) != 0
+			    || sym1->st_value != sym2->st_value
+			    || (sym1->st_size != sym2->st_size
+				&& sym1->st_shndx != SHN_UNDEF)
+			    || sym1->st_info != sym2->st_info
+			    || sym1->st_other != sym2->st_other
+			    || sym1->st_shndx != sym2->st_shndx))
+		{
+		  // XXX Do we want to allow reordered symbol tables?
+		symtab_mismatch:
+		  if (! quiet)
+		    {
+		      if (elf_ndxscn (scn1) == elf_ndxscn (scn2))
+			error (0, 0,
+			       gettext ("%s %s differ: symbol table [%zu]"),
+			       fname1, fname2, elf_ndxscn (scn1));
+		      else
+			error (0, 0, gettext ("\
+%s %s differ: symbol table [%zu,%zu]"),
+			       fname1, fname2, elf_ndxscn (scn1),
+			       elf_ndxscn (scn2));
+		    }
+		  DIFFERENCE;
+		  break;
+		}
+
+	      if (sym1->st_shndx == SHN_UNDEF
+		  && sym1->st_size != sym2->st_size)
+		{
+		  /* The size of the symbol in the object defining it
+		     might have changed.  That is OK unless the symbol
+		     is used in a copy relocation.  Look over the
+		     sections in both files and determine which
+		     relocation section uses this symbol table
+		     section.  Then look through the relocations to
+		     see whether any copy relocation references this
+		     symbol.  */
+		  if (search_for_copy_reloc (ebl1, elf_ndxscn (scn1), ndx)
+		      || search_for_copy_reloc (ebl2, elf_ndxscn (scn2), ndx))
+		    goto symtab_mismatch;
+		}
+	    }
+	  break;
+
+	case SHT_NOTE:
+	  /* Parse the note format and compare the notes themselves.  */
+	  {
+	    GElf_Nhdr note1;
+	    GElf_Nhdr note2;
+
+	    size_t off1 = 0;
+	    size_t off2 = 0;
+	    size_t name_offset;
+	    size_t desc_offset;
+	    while (off1 < data1->d_size
+		   && (off1 = gelf_getnote (data1, off1, &note1,
+					    &name_offset, &desc_offset)) > 0)
+	      {
+		const char *name1 = (note1.n_namesz == 0
+				     ? "" : data1->d_buf + name_offset);
+		const void *desc1 = data1->d_buf + desc_offset;
+		if (off2 >= data2->d_size)
+		  {
+		    if (! quiet)
+		      error (0, 0, gettext ("\
+%s %s differ: section [%zu] '%s' number of notes"),
+			     fname1, fname2, elf_ndxscn (scn1), sname1);
+		    DIFFERENCE;
+		  }
+		off2 = gelf_getnote (data2, off2, &note2,
+				     &name_offset, &desc_offset);
+		if (off2 == 0)
+		  error (2, 0, gettext ("\
+cannot read note section [%zu] '%s' in '%s': %s"),
+			 elf_ndxscn (scn2), sname2, fname2, elf_errmsg (-1));
+		const char *name2 = (note2.n_namesz == 0
+				     ? "" : data2->d_buf + name_offset);
+		const void *desc2 = data2->d_buf + desc_offset;
+
+		if (note1.n_namesz != note2.n_namesz
+		    || memcmp (name1, name2, note1.n_namesz))
+		  {
+		    if (! quiet)
+		      error (0, 0, gettext ("\
+%s %s differ: section [%zu] '%s' note name"),
+			     fname1, fname2, elf_ndxscn (scn1), sname1);
+		    DIFFERENCE;
+		  }
+		if (note1.n_type != note2.n_type)
+		  {
+		    if (! quiet)
+		      error (0, 0, gettext ("\
+%s %s differ: section [%zu] '%s' note '%s' type"),
+			     fname1, fname2, elf_ndxscn (scn1), sname1, name1);
+		    DIFFERENCE;
+		  }
+		if (note1.n_descsz != note2.n_descsz
+		    || memcmp (desc1, desc2, note1.n_descsz))
+		  {
+		    if (note1.n_type == NT_GNU_BUILD_ID
+			&& note1.n_namesz == sizeof "GNU"
+			&& !memcmp (name1, "GNU", sizeof "GNU"))
+		      {
+			if (note1.n_descsz != note2.n_descsz)
+			  {
+			    if (! quiet)
+			      error (0, 0, gettext ("\
+%s %s differ: build ID length"),
+				     fname1, fname2);
+			    DIFFERENCE;
+			  }
+			else if (! ignore_build_id)
+			  {
+			    if (! quiet)
+			      error (0, 0, gettext ("\
+%s %s differ: build ID content"),
+				     fname1, fname2);
+			    DIFFERENCE;
+			  }
+		      }
+		    else
+		      {
+			if (! quiet)
+			  error (0, 0, gettext ("\
+%s %s differ: section [%zu] '%s' note '%s' content"),
+				 fname1, fname2, elf_ndxscn (scn1), sname1,
+				 name1);
+			DIFFERENCE;
+		      }
+		  }
+	      }
+	    if (off2 < data2->d_size)
+	      {
+		if (! quiet)
+		  error (0, 0, gettext ("\
+%s %s differ: section [%zu] '%s' number of notes"),
+			 fname1, fname2, elf_ndxscn (scn1), sname1);
+		DIFFERENCE;
+	      }
+	  }
+	  break;
+
+	default:
+	  /* Compare the section content byte for byte.  */
+	  assert (shdr1->sh_type == SHT_NOBITS
+		  || (data1->d_buf != NULL || data1->d_size == 0));
+	  assert (shdr2->sh_type == SHT_NOBITS
+		  || (data2->d_buf != NULL || data1->d_size == 0));
+
+	  if (unlikely (data1->d_size != data2->d_size
+			|| (shdr1->sh_type != SHT_NOBITS
+			    && data1->d_size != 0
+			    && memcmp (data1->d_buf, data2->d_buf,
+				       data1->d_size) != 0)))
+	    {
+	      if (hash_inexact
+		  && shdr1->sh_type == SHT_HASH
+		  && data1->d_size == data2->d_size
+		  && hash_content_equivalent (shdr1->sh_entsize, data1, data2))
+		break;
+
+	      if (! quiet)
+		{
+		  if (elf_ndxscn (scn1) == elf_ndxscn (scn2))
+		    error (0, 0, gettext ("\
+%s %s differ: section [%zu] '%s' content"),
+			   fname1, fname2, elf_ndxscn (scn1), sname1);
+		  else
+		    error (0, 0, gettext ("\
+%s %s differ: section [%zu,%zu] '%s' content"),
+			   fname1, fname2, elf_ndxscn (scn1),
+			   elf_ndxscn (scn2), sname1);
+		}
+	      DIFFERENCE;
+	    }
+	  break;
+	}
+    }
+
+  if (unlikely (scn1 != scn2))
+    {
+      if (! quiet)
+	error (0, 0,
+	       gettext ("%s %s differ: unequal amount of important sections"),
+	       fname1, fname2);
+      DIFFERENCE;
+    }
+
+  /* We we look at gaps, create artificial ones for the parts of the
+     program which we are not in sections.  */
+  struct region ehdr_region;
+  struct region phdr_region;
+  if (gaps != gaps_ignore)
+    {
+      ehdr_region.from = 0;
+      ehdr_region.to = ehdr1->e_ehsize;
+      ehdr_region.next = &phdr_region;
+
+      phdr_region.from = ehdr1->e_phoff;
+      phdr_region.to = ehdr1->e_phoff + phnum1 * ehdr1->e_phentsize;
+      phdr_region.next = regions;
+
+      regions = &ehdr_region;
+      nregions += 2;
+    }
+
+  /* If we need to look at the gaps we need access to the file data.  */
+  char *raw1 = NULL;
+  size_t size1 = 0;
+  char *raw2 = NULL;
+  size_t size2 = 0;
+  struct region *regionsarr = alloca (nregions * sizeof (struct region));
+  if (gaps != gaps_ignore)
+    {
+      raw1 = elf_rawfile (elf1, &size1);
+      if (raw1 == NULL )
+	error (2, 0, gettext ("cannot load data of '%s': %s"),
+	       fname1, elf_errmsg (-1));
+
+      raw2 = elf_rawfile (elf2, &size2);
+      if (raw2 == NULL )
+	error (2, 0, gettext ("cannot load data of '%s': %s"),
+	       fname2, elf_errmsg (-1));
+
+      for (size_t cnt = 0; cnt < nregions; ++cnt)
+	{
+	  regionsarr[cnt] = *regions;
+	  regions = regions->next;
+	}
+
+      qsort (regionsarr, nregions, sizeof (regionsarr[0]), regioncompare);
+    }
+
+  /* Compare the program header tables.  */
+  for (unsigned int ndx = 0; ndx < phnum1; ++ndx)
+    {
+      GElf_Phdr phdr1_mem;
+      GElf_Phdr *phdr1 = gelf_getphdr (elf1, ndx, &phdr1_mem);
+      if (phdr1 == NULL)
+	error (2, 0,
+	       gettext ("cannot get program header entry %d of '%s': %s"),
+	       ndx, fname1, elf_errmsg (-1));
+      GElf_Phdr phdr2_mem;
+      GElf_Phdr *phdr2 = gelf_getphdr (elf2, ndx, &phdr2_mem);
+      if (phdr2 == NULL)
+	error (2, 0,
+	       gettext ("cannot get program header entry %d of '%s': %s"),
+	       ndx, fname2, elf_errmsg (-1));
+
+      if (unlikely (memcmp (phdr1, phdr2, sizeof (GElf_Phdr)) != 0))
+	{
+	  if (! quiet)
+	    error (0, 0, gettext ("%s %s differ: program header %d"),
+		   fname1, fname2, ndx);
+	  DIFFERENCE;
+	}
+
+      if (gaps != gaps_ignore && phdr1->p_type == PT_LOAD)
+	{
+	  size_t cnt = 0;
+	  while (cnt < nregions && regionsarr[cnt].to < phdr1->p_offset)
+	    ++cnt;
+
+	  GElf_Off last = phdr1->p_offset;
+	  GElf_Off end = phdr1->p_offset + phdr1->p_filesz;
+	  while (cnt < nregions && regionsarr[cnt].from < end)
+	    {
+	      if (last < regionsarr[cnt].from)
+		{
+		  /* Compare the [LAST,FROM) region.  */
+		  assert (gaps == gaps_match);
+		  if (unlikely (memcmp (raw1 + last, raw2 + last,
+					regionsarr[cnt].from - last) != 0))
+		    {
+		    gapmismatch:
+		      if (!quiet)
+			error (0, 0, gettext ("%s %s differ: gap"),
+			       fname1, fname2);
+		      DIFFERENCE;
+		      break;
+		    }
+
+		}
+	      last = regionsarr[cnt].to;
+	      ++cnt;
+	    }
+
+	  if (cnt == nregions && last < end)
+	    goto gapmismatch;
+	}
+    }
+
+ out:
+  elf_end (elf1);
+  elf_end (elf2);
+  ebl_closebackend (ebl1);
+  ebl_closebackend (ebl2);
+  close (fd1);
+  close (fd2);
+
+  return result;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg,
+	   struct argp_state *state __attribute__ ((unused)))
+{
+  switch (key)
+    {
+    case 'q':
+      quiet = true;
+      break;
+
+    case 'l':
+      verbose = true;
+      break;
+
+    case OPT_GAPS:
+      if (strcasecmp (arg, "ignore") == 0)
+	gaps = gaps_ignore;
+      else if (likely (strcasecmp (arg, "match") == 0))
+	gaps = gaps_match;
+      else
+	{
+	  fprintf (stderr,
+		   gettext ("Invalid value '%s' for --gaps parameter."),
+		   arg);
+	  argp_help (&argp, stderr, ARGP_HELP_SEE,
+		     program_invocation_short_name);
+	  exit (1);
+	}
+      break;
+
+    case OPT_HASH_INEXACT:
+      hash_inexact = true;
+      break;
+
+    case OPT_IGNORE_BUILD_ID:
+      ignore_build_id = true;
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+
+static Elf *
+open_file (const char *fname, int *fdp, Ebl **eblp)
+{
+  int fd = open (fname, O_RDONLY);
+  if (unlikely (fd == -1))
+    error (2, errno, gettext ("cannot open '%s'"), fname);
+  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+  if (elf == NULL)
+    error (2, 0,
+	   gettext ("cannot create ELF descriptor for '%s': %s"),
+	   fname, elf_errmsg (-1));
+  Ebl *ebl = ebl_openbackend (elf);
+  if (ebl == NULL)
+    error (2, 0,
+	   gettext ("cannot create EBL descriptor for '%s'"), fname);
+
+  *fdp = fd;
+  *eblp = ebl;
+  return elf;
+}
+
+
+static bool
+search_for_copy_reloc (Ebl *ebl, size_t scnndx, int symndx)
+{
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+      if (shdr == NULL)
+	error (2, 0,
+	       gettext ("cannot get section header of section %zu: %s"),
+	       elf_ndxscn (scn), elf_errmsg (-1));
+
+      if ((shdr->sh_type != SHT_REL && shdr->sh_type != SHT_RELA)
+	  || shdr->sh_link != scnndx)
+	continue;
+
+      Elf_Data *data = elf_getdata (scn, NULL);
+      if (data == NULL)
+	error (2, 0,
+	       gettext ("cannot get content of section %zu: %s"),
+	       elf_ndxscn (scn), elf_errmsg (-1));
+
+      if (shdr->sh_type == SHT_REL && shdr->sh_entsize != 0)
+	for (int ndx = 0; ndx < (int) (shdr->sh_size / shdr->sh_entsize);
+	     ++ndx)
+	  {
+	    GElf_Rel rel_mem;
+	    GElf_Rel *rel = gelf_getrel (data, ndx, &rel_mem);
+	    if (rel == NULL)
+	      error (2, 0, gettext ("cannot get relocation: %s"),
+		     elf_errmsg (-1));
+
+	    if ((int) GELF_R_SYM (rel->r_info) == symndx
+		&& ebl_copy_reloc_p (ebl, GELF_R_TYPE (rel->r_info)))
+	      return true;
+	  }
+      else if (shdr->sh_entsize != 0)
+	for (int ndx = 0; ndx < (int) (shdr->sh_size / shdr->sh_entsize);
+	     ++ndx)
+	  {
+	    GElf_Rela rela_mem;
+	    GElf_Rela *rela = gelf_getrela (data, ndx, &rela_mem);
+	    if (rela == NULL)
+	      error (2, 0, gettext ("cannot get relocation: %s"),
+		     elf_errmsg (-1));
+
+	    if ((int) GELF_R_SYM (rela->r_info) == symndx
+		&& ebl_copy_reloc_p (ebl, GELF_R_TYPE (rela->r_info)))
+	      return true;
+	  }
+    }
+
+  return false;
+}
+
+
+static int
+regioncompare (const void *p1, const void *p2)
+{
+  const struct region *r1 = (const struct region *) p1;
+  const struct region *r2 = (const struct region *) p2;
+
+  if (r1->from < r2->from)
+    return -1;
+  return 1;
+}
+
+
+static int
+compare_Elf32_Word (const void *p1, const void *p2)
+{
+  const Elf32_Word *w1 = p1;
+  const Elf32_Word *w2 = p2;
+  return *w1 < *w2 ? -1 : *w1 > *w2 ? 1 : 0;
+}
+
+static int
+compare_Elf64_Xword (const void *p1, const void *p2)
+{
+  const Elf64_Xword *w1 = p1;
+  const Elf64_Xword *w2 = p2;
+  return *w1 < *w2 ? -1 : *w1 > *w2 ? 1 : 0;
+}
+
+static bool
+hash_content_equivalent (size_t entsize, Elf_Data *data1, Elf_Data *data2)
+{
+#define CHECK_HASH(Hash_Word)						      \
+  {									      \
+    const Hash_Word *const hash1 = data1->d_buf;			      \
+    const Hash_Word *const hash2 = data2->d_buf;			      \
+    const size_t nbucket = hash1[0];					      \
+    const size_t nchain = hash1[1];					      \
+    if (data1->d_size != (2 + nbucket + nchain) * sizeof hash1[0]	      \
+	|| hash2[0] != nbucket || hash2[1] != nchain)			      \
+      return false;							      \
+									      \
+    const Hash_Word *const bucket1 = &hash1[2];				      \
+    const Hash_Word *const chain1 = &bucket1[nbucket];			      \
+    const Hash_Word *const bucket2 = &hash2[2];				      \
+    const Hash_Word *const chain2 = &bucket2[nbucket];			      \
+									      \
+    bool chain_ok[nchain];						      \
+    Hash_Word temp1[nchain - 1];					      \
+    Hash_Word temp2[nchain - 1];					      \
+    memset (chain_ok, 0, sizeof chain_ok);				      \
+    for (size_t i = 0; i < nbucket; ++i)				      \
+      {									      \
+	if (bucket1[i] >= nchain || bucket2[i] >= nchain)		      \
+	  return false;							      \
+									      \
+	size_t b1 = 0;							      \
+	for (size_t p = bucket1[i]; p != STN_UNDEF; p = chain1[p])	      \
+	  if (p >= nchain || b1 >= nchain - 1)				      \
+	    return false;						      \
+	  else								      \
+	    temp1[b1++] = p;						      \
+									      \
+	size_t b2 = 0;							      \
+	for (size_t p = bucket2[i]; p != STN_UNDEF; p = chain2[p])	      \
+	  if (p >= nchain || b2 >= nchain - 1)				      \
+	    return false;						      \
+	  else								      \
+	    temp2[b2++] = p;						      \
+									      \
+	if (b1 != b2)							      \
+	  return false;							      \
+									      \
+	qsort (temp1, b1, sizeof temp1[0], compare_##Hash_Word);	      \
+	qsort (temp2, b2, sizeof temp2[0], compare_##Hash_Word);	      \
+									      \
+	for (b1 = 0; b1 < b2; ++b1)					      \
+	  if (temp1[b1] != temp2[b1])					      \
+	    return false;						      \
+	  else								      \
+	    chain_ok[temp1[b1]] = true;					      \
+      }									      \
+									      \
+    for (size_t i = 0; i < nchain; ++i)					      \
+      if (!chain_ok[i] && chain1[i] != chain2[i])			      \
+	return false;							      \
+									      \
+    return true;							      \
+  }
+
+  switch (entsize)
+    {
+    case 4:
+      CHECK_HASH (Elf32_Word);
+      break;
+    case 8:
+      CHECK_HASH (Elf64_Xword);
+      break;
+    }
+
+  return false;
+}
+
+
+#include "debugpred.h"
diff --git a/src/elfcompress.c b/src/elfcompress.c
new file mode 100644
index 0000000..25378a4
--- /dev/null
+++ b/src/elfcompress.c
@@ -0,0 +1,1322 @@
+/* Compress or decompress an ELF file.
+   Copyright (C) 2015, 2016 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <assert.h>
+#include <argp.h>
+#include <error.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <locale.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include ELFUTILS_HEADER(elf)
+#include ELFUTILS_HEADER(ebl)
+#include ELFUTILS_HEADER(dwelf)
+#include <gelf.h>
+#include "libeu.h"
+#include "printversion.h"
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+static int verbose = 0; /* < 0, no warnings, > 0 extra verbosity.  */
+static bool force = false;
+static bool permissive = false;
+static const char *foutput = NULL;
+
+#define T_UNSET 0
+#define T_DECOMPRESS 1    /* none */
+#define T_COMPRESS_ZLIB 2 /* zlib */
+#define T_COMPRESS_GNU  3 /* zlib-gnu */
+static int type = T_UNSET;
+
+struct section_pattern
+{
+  char *pattern;
+  struct section_pattern *next;
+};
+
+static struct section_pattern *patterns = NULL;
+
+static void
+add_pattern (const char *pattern)
+{
+  struct section_pattern *p = xmalloc (sizeof *p);
+  p->pattern = xstrdup (pattern);
+  p->next = patterns;
+  patterns = p;
+}
+
+static void
+free_patterns (void)
+{
+  struct section_pattern *pattern = patterns;
+  while (pattern != NULL)
+    {
+      struct section_pattern *p = pattern;
+      pattern = p->next;
+      free (p->pattern);
+      free (p);
+    }
+}
+
+static error_t
+parse_opt (int key, char *arg __attribute__ ((unused)),
+	   struct argp_state *state __attribute__ ((unused)))
+{
+  switch (key)
+    {
+    case 'v':
+      verbose++;
+      break;
+
+    case 'q':
+      verbose--;
+      break;
+
+    case 'f':
+      force = true;
+      break;
+
+    case 'p':
+      permissive = true;
+      break;
+
+    case 'n':
+      add_pattern (arg);
+      break;
+
+    case 'o':
+      if (foutput != NULL)
+	argp_error (state, N_("-o option specified twice"));
+      else
+	foutput = arg;
+      break;
+
+    case 't':
+      if (type != T_UNSET)
+	argp_error (state, N_("-t option specified twice"));
+
+      if (strcmp ("none", arg) == 0)
+	type = T_DECOMPRESS;
+      else if (strcmp ("zlib", arg) == 0 || strcmp ("zlib-gabi", arg) == 0)
+	type = T_COMPRESS_ZLIB;
+      else if (strcmp ("zlib-gnu", arg) == 0 || strcmp ("gnu", arg) == 0)
+	type = T_COMPRESS_GNU;
+      else
+	argp_error (state, N_("unknown compression type '%s'"), arg);
+      break;
+
+    case ARGP_KEY_SUCCESS:
+      if (type == T_UNSET)
+	type = T_COMPRESS_ZLIB;
+      if (patterns == NULL)
+	add_pattern (".?(z)debug*");
+      break;
+
+    case ARGP_KEY_NO_ARGS:
+      /* We need at least one input file.  */
+      argp_error (state, N_("No input file given"));
+      break;
+
+    case ARGP_KEY_ARGS:
+      if (foutput != NULL && state->argc - state->next > 1)
+	argp_error (state,
+		    N_("Only one input file allowed together with '-o'"));
+      /* We only use this for checking the number of arguments, we don't
+	 actually want to consume them.  */
+      FALLTHROUGH;
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+static bool
+section_name_matches (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;
+}
+
+static int
+setshdrstrndx (Elf *elf, GElf_Ehdr *ehdr, size_t ndx)
+{
+  if (ndx < SHN_LORESERVE)
+    ehdr->e_shstrndx = ndx;
+  else
+    {
+      ehdr->e_shstrndx = SHN_XINDEX;
+      Elf_Scn *zscn = elf_getscn (elf, 0);
+      GElf_Shdr zshdr_mem;
+      GElf_Shdr *zshdr = gelf_getshdr (zscn, &zshdr_mem);
+      if (zshdr == NULL)
+	return -1;
+      zshdr->sh_link = ndx;
+      if (gelf_update_shdr (zscn, zshdr) == 0)
+	return -1;
+    }
+
+  if (gelf_update_ehdr (elf, ehdr) == 0)
+    return -1;
+
+  return 0;
+}
+
+static int
+compress_section (Elf_Scn *scn, size_t orig_size, const char *name,
+		  const char *newname, size_t ndx,
+		  bool gnu, bool compress, bool report_verbose)
+{
+  int res;
+  unsigned int flags = compress && force ? ELF_CHF_FORCE : 0;
+  if (gnu)
+    res = elf_compress_gnu (scn, compress ? 1 : 0, flags);
+  else
+    res = elf_compress (scn, compress ? ELFCOMPRESS_ZLIB : 0, flags);
+
+  if (res < 0)
+    error (0, 0, "Couldn't decompress section [%zd] %s: %s",
+	   ndx, name, elf_errmsg (-1));
+  else
+    {
+      if (compress && res == 0)
+	{
+	  if (verbose >= 0)
+	    printf ("[%zd] %s NOT compressed, wouldn't be smaller\n",
+		    ndx, name);
+	}
+
+      if (report_verbose && res > 0)
+	{
+	  printf ("[%zd] %s %s", ndx, name,
+		  compress ? "compressed" : "decompressed");
+	  if (newname != NULL)
+	    printf (" -> %s", newname);
+
+	  /* Reload shdr, it has changed.  */
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	  if (shdr == NULL)
+	    {
+	      error (0, 0, "Couldn't get shdr for section [%zd]", ndx);
+	      return -1;
+	    }
+	  float new = shdr->sh_size;
+	  float orig = orig_size ?: 1;
+	  printf (" (%zu => %" PRIu64 " %.2f%%)\n",
+		  orig_size, shdr->sh_size, (new / orig) * 100);
+	}
+    }
+
+  return res;
+}
+
+static int
+process_file (const char *fname)
+{
+  if (verbose > 0)
+    printf ("processing: %s\n", fname);
+
+  /* The input ELF.  */
+  int fd = -1;
+  Elf *elf = NULL;
+
+  /* The output ELF.  */
+  char *fnew = NULL;
+  int fdnew = -1;
+  Elf *elfnew = NULL;
+
+  /* Buffer for (one) new section name if necessary.  */
+  char *snamebuf = NULL;
+
+  /* String table (and symbol table), if section names need adjusting.  */
+  Dwelf_Strtab *names = NULL;
+  Dwelf_Strent **scnstrents = NULL;
+  Dwelf_Strent **symstrents = NULL;
+  char **scnnames = NULL;
+
+  /* Section data from names.  */
+  void *namesbuf = NULL;
+
+  /* Which sections match and need to be (un)compressed.  */
+  unsigned int *sections = NULL;
+
+  /* How many sections are we talking about?  */
+  size_t shnum = 0;
+
+#define WORD_BITS (8U * sizeof (unsigned int))
+  void set_section (size_t ndx)
+  {
+    sections[ndx / WORD_BITS] |= (1U << (ndx % WORD_BITS));
+  }
+
+  bool get_section (size_t ndx)
+  {
+    return (sections[ndx / WORD_BITS] & (1U << (ndx % WORD_BITS))) != 0;
+  }
+
+  int cleanup (int res)
+  {
+    elf_end (elf);
+    close (fd);
+
+    elf_end (elfnew);
+    close (fdnew);
+
+    if (fnew != NULL)
+      {
+	unlink (fnew);
+	free (fnew);
+	fnew = NULL;
+      }
+
+    free (snamebuf);
+    if (names != NULL)
+      {
+	dwelf_strtab_free (names);
+	free (scnstrents);
+	free (symstrents);
+	free (namesbuf);
+	if (scnnames != NULL)
+	  {
+	    for (size_t n = 0; n < shnum; n++)
+	      free (scnnames[n]);
+	    free (scnnames);
+	  }
+      }
+
+    free (sections);
+
+    return res;
+  }
+
+  fd = open (fname, O_RDONLY);
+  if (fd < 0)
+    {
+      error (0, errno, "Couldn't open %s\n", fname);
+      return cleanup (-1);
+    }
+
+  elf = elf_begin (fd, ELF_C_READ, NULL);
+  if (elf == NULL)
+    {
+      error (0, 0, "Couldn't open ELF file %s for reading: %s",
+	     fname, elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  /* We dont' handle ar files (or anything else), we probably should.  */
+  Elf_Kind kind = elf_kind (elf);
+  if (kind != ELF_K_ELF)
+    {
+      if (kind == ELF_K_AR)
+	error (0, 0, "Cannot handle ar files: %s", fname);
+      else
+	error (0, 0, "Unknown file type: %s", fname);
+      return cleanup (-1);
+    }
+
+  struct stat st;
+  if (fstat (fd, &st) != 0)
+    {
+      error (0, errno, "Couldn't fstat %s", fname);
+      return cleanup (-1);
+    }
+
+  GElf_Ehdr ehdr;
+  if (gelf_getehdr (elf, &ehdr) == NULL)
+    {
+      error (0, 0, "Couldn't get ehdr for %s: %s", fname, elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  /* Get the section header string table.  */
+  size_t shdrstrndx;
+  if (elf_getshdrstrndx (elf, &shdrstrndx) != 0)
+    {
+      error (0, 0, "Couldn't get section header string table index in %s: %s",
+	     fname, elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  /* How many sections are we talking about?  */
+  if (elf_getshdrnum (elf, &shnum) != 0)
+    {
+      error (0, 0, "Couldn't get number of sections in %s: %s",
+	     fname, elf_errmsg (1));
+      return cleanup (-1);
+    }
+
+  if (shnum == 0)
+    {
+      error (0, 0, "ELF file %s has no sections", fname);
+      return cleanup (-1);
+    }
+
+  sections = xcalloc (shnum / 8 + 1, sizeof (unsigned int));
+
+  size_t phnum;
+  if (elf_getphdrnum (elf, &phnum) != 0)
+    {
+      error (0, 0, "Couldn't get phdrnum: %s", elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  /* Whether we need to adjust any section names (going to/from GNU
+     naming).  If so we'll need to build a new section header string
+     table.  */
+  bool adjust_names = false;
+
+  /* If there are phdrs we want to maintain the layout of the
+     allocated sections in the file.  */
+  bool layout = phnum != 0;
+
+  /* While going through all sections keep track of last section data
+     offset if needed to keep the layout.  We are responsible for
+     adding the section offsets and headers (e_shoff) in that case
+     (which we will place after the last section).  */
+  GElf_Off last_offset = 0;
+  if (layout)
+    last_offset = (ehdr.e_phoff
+		   + gelf_fsize (elf, ELF_T_PHDR, phnum, EV_CURRENT));
+
+  /* Which section, if any, is a symbol table that shares a string
+     table with the section header string table?  */
+  size_t symtabndx = 0;
+
+  /* We do three passes over all sections.
+
+     First an inspection pass over the old Elf to see which section
+     data needs to be copied and/or transformed, which sections need a
+     names change and whether there is a symbol table that might need
+     to be adjusted be if the section header name table is changed.
+
+     Second a collection pass that creates the Elf sections and copies
+     the data.  This pass will compress/decompress section data when
+     needed.  And it will collect all data needed if we'll need to
+     construct a new string table. Afterwards the new string table is
+     constructed.
+
+     Third a fixup/adjustment pass over the new Elf that will adjust
+     any section references (names) and adjust the layout based on the
+     new sizes of the sections if necessary.  This pass is optional if
+     we aren't responsible for the layout and the section header
+     string table hasn't been changed.  */
+
+  /* Inspection pass.  */
+  size_t maxnamelen = 0;
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (elf, scn)) != NULL)
+    {
+      size_t ndx = elf_ndxscn (scn);
+      if (ndx > shnum)
+	{
+	  error (0, 0, "Unexpected section number %zd, expected only %zd",
+		 ndx, shnum);
+	  cleanup (-1);
+	}
+
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+      if (shdr == NULL)
+	{
+	  error (0, 0, "Couldn't get shdr for section %zd", ndx);
+	  return cleanup (-1);
+	}
+
+      const char *sname = elf_strptr (elf, shdrstrndx, shdr->sh_name);
+      if (sname == NULL)
+	{
+	  error (0, 0, "Couldn't get name for section %zd", ndx);
+	  return cleanup (-1);
+	}
+
+      if (section_name_matches (sname))
+	{
+	  if (shdr->sh_type != SHT_NOBITS
+	      && (shdr->sh_flags & SHF_ALLOC) == 0)
+	    {
+	      set_section (ndx);
+	      /* Check if we might want to change this section name.  */
+	      if (! adjust_names
+		  && ((type != T_COMPRESS_GNU
+		       && strncmp (sname, ".zdebug",
+				   strlen (".zdebug")) == 0)
+		      || (type == T_COMPRESS_GNU
+			  && strncmp (sname, ".debug",
+				      strlen (".debug")) == 0)))
+		adjust_names = true;
+
+	      /* We need a buffer this large if we change the names.  */
+	      if (adjust_names)
+		{
+		  size_t slen = strlen (sname);
+		  if (slen > maxnamelen)
+		    maxnamelen = slen;
+		}
+	    }
+	  else
+	    if (verbose >= 0)
+	      printf ("[%zd] %s ignoring %s section\n", ndx, sname,
+		      (shdr->sh_type == SHT_NOBITS ? "no bits" : "allocated"));
+	}
+
+      if (shdr->sh_type == SHT_SYMTAB)
+	{
+	  /* Check if we might have to adjust the symbol name indexes.  */
+	  if (shdr->sh_link == shdrstrndx)
+	    {
+	      if (symtabndx != 0)
+		{
+		  error (0, 0,
+			 "Multiple symbol tables (%zd, %zd) using the same string table unsupported", symtabndx, ndx);
+		  return cleanup (-1);
+		}
+	      symtabndx = ndx;
+	    }
+	}
+
+      /* Keep track of last allocated data offset.  */
+      if (layout)
+	if ((shdr->sh_flags & SHF_ALLOC) != 0)
+	  {
+	    GElf_Off off = shdr->sh_offset + (shdr->sh_type != SHT_NOBITS
+					      ? shdr->sh_size : 0);
+	    if (last_offset < off)
+	      last_offset = off;
+	  }
+    }
+
+  if (adjust_names)
+    {
+      names = dwelf_strtab_init (true);
+      if (names == NULL)
+	{
+	  error (0, 0, "Not enough memory for new strtab");
+	  return cleanup (-1);
+	}
+      scnstrents = xmalloc (shnum
+			    * sizeof (Dwelf_Strent *));
+      scnnames = xcalloc (shnum, sizeof (char *));
+    }
+
+  /* Create a new (temporary) ELF file for the result.  */
+  if (foutput == NULL)
+    {
+      size_t fname_len = strlen (fname);
+      fnew = xmalloc (fname_len + sizeof (".XXXXXX"));
+      strcpy (mempcpy (fnew, fname, fname_len), ".XXXXXX");
+      fdnew = mkstemp (fnew);
+    }
+  else
+    {
+      fnew = xstrdup (foutput);
+      fdnew = open (fnew, O_WRONLY | O_CREAT, st.st_mode & ALLPERMS);
+    }
+
+  if (fdnew < 0)
+    {
+      error (0, errno, "Couldn't create output file %s", fnew);
+      /* Since we didn't create it we don't want to try to unlink it.  */
+      free (fnew);
+      fnew = NULL;
+      return cleanup (-1);
+    }
+
+  elfnew = elf_begin (fdnew, ELF_C_WRITE, NULL);
+  if (elfnew == NULL)
+    {
+      error (0, 0, "Couldn't open new ELF %s for writing: %s",
+	     fnew, elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  /* Create the new ELF header and copy over all the data.  */
+  if (gelf_newehdr (elfnew, gelf_getclass (elf)) == 0)
+    {
+      error (0, 0, "Couldn't create new ehdr: %s", elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  GElf_Ehdr newehdr;
+  if (gelf_getehdr (elfnew, &newehdr) == NULL)
+    {
+      error (0, 0, "Couldn't get new ehdr: %s", elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  newehdr.e_ident[EI_DATA] = ehdr.e_ident[EI_DATA];
+  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;
+
+  if (gelf_update_ehdr (elfnew, &newehdr) == 0)
+    {
+      error (0, 0, "Couldn't update ehdr: %s", elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  /* Copy over the phdrs as is.  */
+  if (phnum != 0)
+    {
+      if (gelf_newphdr (elfnew, phnum) == 0)
+	{
+	  error (0, 0, "Couldn't create phdrs: %s", elf_errmsg (-1));
+	  return cleanup (-1);
+	}
+
+      for (size_t cnt = 0; cnt < phnum; ++cnt)
+	{
+	  GElf_Phdr phdr_mem;
+	  GElf_Phdr *phdr = gelf_getphdr (elf, cnt, &phdr_mem);
+	  if (phdr == NULL)
+	    {
+	      error (0, 0, "Couldn't get phdr %zd: %s", cnt, elf_errmsg (-1));
+	      return cleanup (-1);
+	    }
+	  if (gelf_update_phdr (elfnew, cnt, phdr) == 0)
+	    {
+	      error (0, 0, "Couldn't create phdr %zd: %s", cnt,
+		     elf_errmsg (-1));
+	      return cleanup (-1);
+	    }
+	}
+    }
+
+  /* Possibly add a 'z' and zero terminator.  */
+  if (maxnamelen > 0)
+    snamebuf = xmalloc (maxnamelen + 2);
+
+  /* We might want to read/adjust the section header strings and
+     symbol tables.  If so, and those sections are to be compressed
+     then we will have to decompress it during the collection pass and
+     compress it again in the fixup pass.  Don't compress unnecessary
+     and keep track of whether or not to compress them (later in the
+     fixup pass).  Also record the original size, so we can report the
+     difference later when we do compress.  */
+  int shstrtab_compressed = T_UNSET;
+  size_t shstrtab_size = 0;
+  char *shstrtab_name = NULL;
+  char *shstrtab_newname = NULL;
+  int symtab_compressed = T_UNSET;
+  size_t symtab_size = 0;
+  char *symtab_name = NULL;
+  char *symtab_newname = NULL;
+
+  /* Collection pass.  Copy over the sections, (de)compresses matching
+     sections, collect names of sections and symbol table if
+     necessary.  */
+  scn = NULL;
+  while ((scn = elf_nextscn (elf, scn)) != NULL)
+    {
+      size_t ndx = elf_ndxscn (scn);
+      assert (ndx < shnum);
+
+      /* (de)compress if section matched.  */
+      char *sname = NULL;
+      char *newname = NULL;
+      if (get_section (ndx))
+	{
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	  if (shdr == NULL)
+	    {
+	      error (0, 0, "Couldn't get shdr for section %zd", ndx);
+	      return cleanup (-1);
+	    }
+
+	  uint64_t size = shdr->sh_size;
+	  sname = elf_strptr (elf, shdrstrndx, shdr->sh_name);
+	  if (sname == NULL)
+	    {
+	      error (0, 0, "Couldn't get name for section %zd", ndx);
+	      return cleanup (-1);
+	    }
+
+	  /* strdup sname, the shdrstrndx section itself might be
+	     (de)compressed, invalidating the string pointers.  */
+	  sname = xstrdup (sname);
+
+	  /* We might want to decompress (and rename), but not
+	     compress during this pass since we might need the section
+	     data in later passes.  Skip those sections for now and
+	     compress them in the fixup pass.  */
+	  bool skip_compress_section = (adjust_names
+					&& (ndx == shdrstrndx
+					    || ndx == symtabndx));
+
+	  switch (type)
+	    {
+	    case T_DECOMPRESS:
+	      if ((shdr->sh_flags & SHF_COMPRESSED) != 0)
+		{
+		  if (compress_section (scn, size, sname, NULL, ndx,
+					false, false, verbose > 0) < 0)
+		    return cleanup (-1);
+		}
+	      else if (strncmp (sname, ".zdebug", strlen (".zdebug")) == 0)
+		{
+		  snamebuf[0] = '.';
+		  strcpy (&snamebuf[1], &sname[2]);
+		  newname = snamebuf;
+		  if (compress_section (scn, size, sname, newname, ndx,
+					true, false, verbose > 0) < 0)
+		    return cleanup (-1);
+		}
+	      else if (verbose > 0)
+		printf ("[%zd] %s already decompressed\n", ndx, sname);
+	      break;
+
+	    case T_COMPRESS_GNU:
+	      if (strncmp (sname, ".debug", strlen (".debug")) == 0)
+		{
+		  if ((shdr->sh_flags & SHF_COMPRESSED) != 0)
+		    {
+		      /* First decompress to recompress GNU style.
+			 Don't report even when verbose.  */
+		      if (compress_section (scn, size, sname, NULL, ndx,
+					    false, false, false) < 0)
+			return cleanup (-1);
+		    }
+
+		  snamebuf[0] = '.';
+		  snamebuf[1] = 'z';
+		  strcpy (&snamebuf[2], &sname[1]);
+		  newname = snamebuf;
+
+		  if (skip_compress_section)
+		    {
+		      if (ndx == shdrstrndx)
+			{
+			  shstrtab_size = size;
+			  shstrtab_compressed = T_COMPRESS_GNU;
+			  shstrtab_name = xstrdup (sname);
+			  shstrtab_newname = xstrdup (newname);
+			}
+		      else
+			{
+			  symtab_size = size;
+			  symtab_compressed = T_COMPRESS_GNU;
+			  symtab_name = xstrdup (sname);
+			  symtab_newname = xstrdup (newname);
+			}
+		    }
+		  else
+		    {
+		      int res = compress_section (scn, size, sname, newname,
+						  ndx, true, true,
+						  verbose > 0);
+		      if (res < 0)
+			return cleanup (-1);
+
+		      if (res == 0)
+			newname = NULL;
+		    }
+		}
+	      else if (verbose >= 0)
+		{
+		  if (strncmp (sname, ".zdebug", strlen (".zdebug")) == 0)
+		    printf ("[%zd] %s unchanged, already GNU compressed",
+			    ndx, sname);
+		  else
+		    printf ("[%zd] %s cannot GNU compress section not starting with .debug\n",
+			    ndx, sname);
+		}
+	      break;
+
+	    case T_COMPRESS_ZLIB:
+	      if ((shdr->sh_flags & SHF_COMPRESSED) == 0)
+		{
+		  if (strncmp (sname, ".zdebug", strlen (".zdebug")) == 0)
+		    {
+		      /* First decompress to recompress zlib style.
+			 Don't report even when verbose.  */
+		      if (compress_section (scn, size, sname, NULL, ndx,
+					    true, false, false) < 0)
+			return cleanup (-1);
+
+		      snamebuf[0] = '.';
+		      strcpy (&snamebuf[1], &sname[2]);
+		      newname = snamebuf;
+		    }
+
+		  if (skip_compress_section)
+		    {
+		      if (ndx == shdrstrndx)
+			{
+			  shstrtab_size = size;
+			  shstrtab_compressed = T_COMPRESS_ZLIB;
+			  shstrtab_name = xstrdup (sname);
+			  shstrtab_newname = (newname == NULL
+					      ? NULL : xstrdup (newname));
+			}
+		      else
+			{
+			  symtab_size = size;
+			  symtab_compressed = T_COMPRESS_ZLIB;
+			  symtab_name = xstrdup (sname);
+			  symtab_newname = (newname == NULL
+					    ? NULL : xstrdup (newname));
+			}
+		    }
+		  else if (compress_section (scn, size, sname, newname, ndx,
+					     false, true, verbose > 0) < 0)
+		    return cleanup (-1);
+		}
+	      else if (verbose > 0)
+		printf ("[%zd] %s already compressed\n", ndx, sname);
+	      break;
+	    }
+
+	  free (sname);
+	}
+
+      Elf_Scn *newscn = elf_newscn (elfnew);
+      if (newscn == NULL)
+	{
+	  error (0, 0, "Couldn't create new section %zd", ndx);
+	  return cleanup (-1);
+	}
+
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+      if (shdr == NULL)
+	{
+	  error (0, 0, "Couldn't get shdr for section %zd", ndx);
+	  return cleanup (-1);
+	}
+
+      if (gelf_update_shdr (newscn, shdr) == 0)
+        {
+	  error (0, 0, "Couldn't update section header %zd", ndx);
+	  return cleanup (-1);
+	}
+
+      /* Except for the section header string table all data can be
+	 copied as is.  The section header string table will be
+	 created later and the symbol table might be fixed up if
+	 necessary.  */
+      if (! adjust_names || ndx != shdrstrndx)
+	{
+	  Elf_Data *data = elf_getdata (scn, NULL);
+	  if (data == NULL)
+	    {
+	      error (0, 0, "Couldn't get data from section %zd", ndx);
+	      return cleanup (-1);
+	    }
+
+	  Elf_Data *newdata = elf_newdata (newscn);
+	  if (newdata == NULL)
+	    {
+	      error (0, 0, "Couldn't create new data for section %zd", ndx);
+	      return cleanup (-1);
+	    }
+
+	  *newdata = *data;
+	}
+
+      /* Keep track of the (new) section names.  */
+      if (adjust_names)
+	{
+	  char *name;
+	  if (newname != NULL)
+	    name = newname;
+	  else
+	    {
+	      name = elf_strptr (elf, shdrstrndx, shdr->sh_name);
+	      if (name == NULL)
+		{
+		  error (0, 0, "Couldn't get name for section [%zd]", ndx);
+		  return cleanup (-1);
+		}
+	    }
+
+	  /* We need to keep a copy of the name till the strtab is done.  */
+	  name = scnnames[ndx] = xstrdup (name);
+	  if ((scnstrents[ndx] = dwelf_strtab_add (names, name)) == NULL)
+	    {
+	      error (0, 0, "No memory to add section name string table");
+	      return cleanup (-1);
+	    }
+
+	  /* If the symtab shares strings then add those too.  */
+	  if (ndx == symtabndx)
+	    {
+	      /* If the section is (still) compressed we'll need to
+		 uncompress it first to adjust the data, then
+		 recompress it in the fixup pass.  */
+	      if (symtab_compressed == T_UNSET)
+		{
+		  size_t size = shdr->sh_size;
+		  if ((shdr->sh_flags == SHF_COMPRESSED) != 0)
+		    {
+		      /* Don't report the (internal) uncompression.  */
+		      if (compress_section (newscn, size, sname, NULL, ndx,
+					    false, false, false) < 0)
+			return cleanup (-1);
+
+		      symtab_size = size;
+		      symtab_compressed = T_COMPRESS_ZLIB;
+		    }
+		  else if (strncmp (name, ".zdebug", strlen (".zdebug")) == 0)
+		    {
+		      /* Don't report the (internal) uncompression.  */
+		      if (compress_section (newscn, size, sname, NULL, ndx,
+					    true, false, false) < 0)
+			return cleanup (-1);
+
+		      symtab_size = size;
+		      symtab_compressed = T_COMPRESS_GNU;
+		    }
+		}
+
+	      Elf_Data *symd = elf_getdata (newscn, NULL);
+	      if (symd == NULL)
+		{
+		  error (0, 0, "Couldn't get symtab data for section [%zd] %s",
+			 ndx, name);
+		  return cleanup (-1);
+		}
+	      size_t elsize = gelf_fsize (elfnew, ELF_T_SYM, 1, EV_CURRENT);
+	      size_t syms = symd->d_size / elsize;
+	      symstrents = xmalloc (syms * sizeof (Dwelf_Strent *));
+	      for (size_t i = 0; i < syms; i++)
+		{
+		  GElf_Sym sym_mem;
+		  GElf_Sym *sym = gelf_getsym (symd, i, &sym_mem);
+		  if (sym == NULL)
+		    {
+		      error (0, 0, "Couldn't get symbol %zd", i);
+		      return cleanup (-1);
+		    }
+		  if (sym->st_name != 0)
+		    {
+		      /* Note we take the name from the original ELF,
+			 since the new one will not have setup the
+			 strtab yet.  */
+		      const char *symname = elf_strptr (elf, shdrstrndx,
+							sym->st_name);
+		      if (symname == NULL)
+			{
+			  error (0, 0, "Couldn't get symbol %zd name", i);
+			  return cleanup (-1);
+			}
+		      symstrents[i] = dwelf_strtab_add (names, symname);
+		      if (symstrents[i] == NULL)
+			{
+			  error (0, 0, "No memory to add to symbol name");
+			  return cleanup (-1);
+			}
+		    }
+		}
+	    }
+	}
+    }
+
+  if (adjust_names)
+    {
+      /* We got all needed strings, put the new data in the shstrtab.  */
+      if (verbose > 0)
+	printf ("[%zd] Updating section string table\n", shdrstrndx);
+
+      scn = elf_getscn (elfnew, shdrstrndx);
+      if (scn == NULL)
+	{
+	  error (0, 0, "Couldn't get new section header string table [%zd]",
+		 shdrstrndx);
+	  return cleanup (-1);
+	}
+
+      Elf_Data *data = elf_newdata (scn);
+      if (data == NULL)
+	{
+	  error (0, 0, "Couldn't create new section header string table data");
+	  return cleanup (-1);
+	}
+      if (dwelf_strtab_finalize (names, data) == NULL)
+	{
+	  error (0, 0, "Not enough memory to create string table");
+	  return cleanup (-1);
+	}
+      namesbuf = data->d_buf;
+
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+      if (shdr == NULL)
+	{
+	  error (0, 0, "Couldn't get shdr for new section strings %zd",
+		 shdrstrndx);
+	  return cleanup (-1);
+	}
+
+      /* Note that we also might have to compress and possibly set
+	 sh_off below */
+      shdr->sh_name = dwelf_strent_off (scnstrents[shdrstrndx]);
+      shdr->sh_type = SHT_STRTAB;
+      shdr->sh_flags = 0;
+      shdr->sh_addr = 0;
+      shdr->sh_offset = 0;
+      shdr->sh_size = data->d_size;
+      shdr->sh_link = SHN_UNDEF;
+      shdr->sh_info = SHN_UNDEF;
+      shdr->sh_addralign = 1;
+      shdr->sh_entsize = 0;
+
+      if (gelf_update_shdr (scn, shdr) == 0)
+	{
+	  error (0, 0, "Couldn't update new section strings [%zd]",
+		 shdrstrndx);
+	  return cleanup (-1);
+	}
+
+      /* We might have to compress the data if the user asked us to,
+	 or if the section was already compressed (and the user didn't
+	 ask for decompression).  Note somewhat identical code for
+	 symtab below.  */
+      if (shstrtab_compressed == T_UNSET)
+	{
+	  /* The user didn't ask for compression, but maybe it was
+	     compressed in the original ELF file.  */
+	  Elf_Scn *oldscn = elf_getscn (elf, shdrstrndx);
+	  if (oldscn == NULL)
+	    {
+	      error (0, 0, "Couldn't get section header string table [%zd]",
+		     shdrstrndx);
+	      return cleanup (-1);
+	    }
+
+	  shdr = gelf_getshdr (oldscn, &shdr_mem);
+	  if (shdr == NULL)
+	    {
+	      error (0, 0, "Couldn't get shdr for old section strings [%zd]",
+		     shdrstrndx);
+	      return cleanup (-1);
+	    }
+
+	  shstrtab_name = elf_strptr (elf, shdrstrndx, shdr->sh_name);
+	  if (shstrtab_name == NULL)
+	    {
+	      error (0, 0, "Couldn't get name for old section strings [%zd]",
+		     shdrstrndx);
+	      return cleanup (-1);
+	    }
+
+	  shstrtab_size = shdr->sh_size;
+	  if ((shdr->sh_flags & SHF_COMPRESSED) != 0)
+	    shstrtab_compressed = T_COMPRESS_ZLIB;
+	  else if (strncmp (shstrtab_name, ".zdebug", strlen (".zdebug")) == 0)
+	    shstrtab_compressed = T_COMPRESS_GNU;
+	}
+
+      /* Should we (re)compress?  */
+      if (shstrtab_compressed != T_UNSET)
+	{
+	  if (compress_section (scn, shstrtab_size, shstrtab_name,
+				shstrtab_newname, shdrstrndx,
+				shstrtab_compressed == T_COMPRESS_GNU,
+				true, verbose > 0) < 0)
+	    return cleanup (-1);
+	}
+    }
+
+  /* Make sure to re-get the new ehdr.  Adding phdrs and shdrs will
+     have changed it.  */
+  if (gelf_getehdr (elfnew, &newehdr) == NULL)
+    {
+      error (0, 0, "Couldn't re-get new ehdr: %s", elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  /* Set this after the sections have been created, otherwise section
+     zero might not exist yet.  */
+  if (setshdrstrndx (elfnew, &newehdr, shdrstrndx) != 0)
+    {
+      error (0, 0, "Couldn't set new shdrstrndx: %s", elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  /* Fixup pass.  Adjust string table references, symbol table and
+     layout if necessary.  */
+  if (layout || adjust_names)
+    {
+      scn = NULL;
+      while ((scn = elf_nextscn (elfnew, scn)) != NULL)
+	{
+	  size_t ndx = elf_ndxscn (scn);
+
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	  if (shdr == NULL)
+	    {
+	      error (0, 0, "Couldn't get shdr for section %zd", ndx);
+	      return cleanup (-1);
+	    }
+
+	  /* Keep the offset of allocated sections so they are at the
+	     same place in the file. Add (possibly changed)
+	     unallocated ones after the allocated ones.  */
+	  if ((shdr->sh_flags & SHF_ALLOC) == 0)
+	    {
+	      /* Zero means one.  No alignment constraints.  */
+	      size_t addralign = shdr->sh_addralign ?: 1;
+	      last_offset = (last_offset + addralign - 1) & ~(addralign - 1);
+	      shdr->sh_offset = last_offset;
+	      if (shdr->sh_type != SHT_NOBITS)
+		last_offset += shdr->sh_size;
+	    }
+
+	  if (adjust_names)
+	    shdr->sh_name = dwelf_strent_off (scnstrents[ndx]);
+
+	  if (gelf_update_shdr (scn, shdr) == 0)
+	    {
+	      error (0, 0, "Couldn't update section header %zd", ndx);
+	      return cleanup (-1);
+	    }
+
+	  if (adjust_names && ndx == symtabndx)
+	    {
+	      if (verbose > 0)
+		printf ("[%zd] Updating symbol table\n", symtabndx);
+
+	      Elf_Data *symd = elf_getdata (scn, NULL);
+	      if (symd == NULL)
+		{
+		  error (0, 0, "Couldn't get new symtab data section [%zd]",
+			 ndx);
+		  return cleanup (-1);
+		}
+	      size_t elsize = gelf_fsize (elfnew, ELF_T_SYM, 1, EV_CURRENT);
+	      size_t syms = symd->d_size / elsize;
+	      for (size_t i = 0; i < syms; i++)
+		{
+		  GElf_Sym sym_mem;
+		  GElf_Sym *sym = gelf_getsym (symd, i, &sym_mem);
+		  if (sym == NULL)
+		    {
+		      error (0, 0, "2 Couldn't get symbol %zd", i);
+		      return cleanup (-1);
+		    }
+
+		  if (sym->st_name != 0)
+		    {
+		      sym->st_name = dwelf_strent_off (symstrents[i]);
+
+		      if (gelf_update_sym (symd, i, sym) == 0)
+			{
+			  error (0, 0, "Couldn't update symbol %zd", i);
+			  return cleanup (-1);
+			}
+		    }
+		}
+
+	      /* We might have to compress the data if the user asked
+		 us to, or if the section was already compressed (and
+		 the user didn't ask for decompression).  Note
+		 somewhat identical code for shstrtab above.  */
+	      if (symtab_compressed == T_UNSET)
+		{
+		  /* The user didn't ask for compression, but maybe it was
+		     compressed in the original ELF file.  */
+		  Elf_Scn *oldscn = elf_getscn (elf, symtabndx);
+		  if (oldscn == NULL)
+		    {
+		      error (0, 0, "Couldn't get symbol table [%zd]",
+			     symtabndx);
+		      return cleanup (-1);
+		    }
+
+		  shdr = gelf_getshdr (oldscn, &shdr_mem);
+		  if (shdr == NULL)
+		    {
+		      error (0, 0, "Couldn't get old symbol table shdr [%zd]",
+			     symtabndx);
+		      return cleanup (-1);
+		    }
+
+		  symtab_name = elf_strptr (elf, shdrstrndx, shdr->sh_name);
+		  if (symtab_name == NULL)
+		    {
+		      error (0, 0, "Couldn't get old symbol table name [%zd]",
+			     symtabndx);
+		      return cleanup (-1);
+		    }
+
+		  symtab_size = shdr->sh_size;
+		  if ((shdr->sh_flags & SHF_COMPRESSED) != 0)
+		    symtab_compressed = T_COMPRESS_ZLIB;
+		  else if (strncmp (symtab_name, ".zdebug",
+				    strlen (".zdebug")) == 0)
+		    symtab_compressed = T_COMPRESS_GNU;
+		}
+
+	      /* Should we (re)compress?  */
+	      if (symtab_compressed != T_UNSET)
+		{
+		  if (compress_section (scn, symtab_size, symtab_name,
+					symtab_newname, symtabndx,
+					symtab_compressed == T_COMPRESS_GNU,
+					true, verbose > 0) < 0)
+		    return cleanup (-1);
+		}
+	    }
+	}
+    }
+
+  /* If we have phdrs we want elf_update to layout the SHF_ALLOC
+     sections precisely as in the original file.  In that case we are
+     also responsible for setting phoff and shoff */
+  if (layout)
+    {
+      if (gelf_getehdr (elfnew, &newehdr) == NULL)
+	{
+	  error (0, 0, "Couldn't get ehdr: %s", elf_errmsg (-1));
+	  return cleanup (-1);
+	}
+
+      /* Position the shdrs after the last (unallocated) section.  */
+      const size_t offsize = gelf_fsize (elfnew, ELF_T_OFF, 1, EV_CURRENT);
+      newehdr.e_shoff = ((last_offset + offsize - 1)
+			 & ~((GElf_Off) (offsize - 1)));
+
+      /* The phdrs go in the same place as in the original file.
+	 Normally right after the ELF header.  */
+      newehdr.e_phoff = ehdr.e_phoff;
+
+      if (gelf_update_ehdr (elfnew, &newehdr) == 0)
+	{
+	  error (0, 0, "Couldn't update ehdr: %s", elf_errmsg (-1));
+	  return cleanup (-1);
+	}
+    }
+
+  elf_flagelf (elfnew, ELF_C_SET, ((layout ? ELF_F_LAYOUT : 0)
+				   | (permissive ? ELF_F_PERMISSIVE : 0)));
+
+  if (elf_update (elfnew, ELF_C_WRITE) < 0)
+    {
+      error (0, 0, "Couldn't write %s: %s", fnew, elf_errmsg (-1));
+      return cleanup (-1);
+    }
+
+  elf_end (elfnew);
+  elfnew = NULL;
+
+  /* Try to match mode and owner.group of the original file.  */
+  if (fchmod (fdnew, st.st_mode & ALLPERMS) != 0)
+    if (verbose >= 0)
+      error (0, errno, "Couldn't fchmod %s", fnew);
+  if (fchown (fdnew, st.st_uid, st.st_gid) != 0)
+    if (verbose >= 0)
+      error (0, errno, "Couldn't fchown %s", fnew);
+
+  /* Finally replace the old file with the new file.  */
+  if (foutput == NULL)
+    if (rename (fnew, fname) != 0)
+      {
+	error (0, errno, "Couldn't rename %s to %s", fnew, fname);
+	return cleanup (-1);
+      }
+
+  /* We are finally done with the new file, don't unlink it now.  */
+  free (fnew);
+  fnew = NULL;
+
+  return cleanup (0);
+}
+
+int
+main (int argc, char **argv)
+{
+  const struct argp_option options[] =
+    {
+      { "output", 'o', "FILE", 0,
+	N_("Place (de)compressed output into FILE"),
+	0 },
+      { "type", 't', "TYPE", 0,
+	N_("What type of compression to apply. TYPE can be 'none' (decompress), 'zlib' (ELF ZLIB compression, the default, 'zlib-gabi' is an alias) or 'zlib-gnu' (.zdebug GNU style compression, 'gnu' is an alias)"),
+	0 },
+      { "name", 'n', "SECTION", 0,
+	N_("SECTION name to (de)compress, SECTION is an extended wildcard pattern (defaults to '.?(z)debug*')"),
+	0 },
+      { "verbose", 'v', NULL, 0,
+	N_("Print a message for each section being (de)compressed"),
+	0 },
+      { "force", 'f', NULL, 0,
+	N_("Force compression of section even if it would become larger"),
+	0 },
+      { "permissive", 'p', NULL, 0,
+	N_("Relax a few rules to handle slightly broken ELF files"),
+	0 },
+      { "quiet", 'q', NULL, 0,
+	N_("Be silent when a section cannot be compressed"),
+	0 },
+      { NULL, 0, NULL, 0, NULL, 0 }
+    };
+
+  const struct argp argp =
+    {
+      .options = options,
+      .parser = parse_opt,
+      .args_doc = N_("FILE..."),
+      .doc = N_("Compress or decompress sections in an ELF file.")
+    };
+
+  int remaining;
+  if (argp_parse (&argp, argc, argv, 0, &remaining, NULL) != 0)
+    return EXIT_FAILURE;
+
+  /* Should already be handled by ARGP_KEY_NO_ARGS case above,
+     just sanity check.  */
+  if (remaining >= argc)
+    error (EXIT_FAILURE, 0, N_("No input file given"));
+
+  /* Likewise for the ARGP_KEY_ARGS case above, an extra sanity check.  */
+  if (foutput != NULL && remaining + 1 < argc)
+    error (EXIT_FAILURE, 0,
+	   N_("Only one input file allowed together with '-o'"));
+
+  elf_version (EV_CURRENT);
+
+  /* Process all the remaining files.  */
+  int result = 0;
+  do
+    result |= process_file (argv[remaining]);
+  while (++remaining < argc);
+
+  free_patterns ();
+  return result;
+}
diff --git a/src/elflint.c b/src/elflint.c
new file mode 100644
index 0000000..df1b3a0
--- /dev/null
+++ b/src/elflint.c
@@ -0,0 +1,4756 @@
+/* Pedantic checking of ELF files compliance with gABI/psABI spec.
+   Copyright (C) 2001-2015, 2017 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2001.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <assert.h>
+#include <byteswap.h>
+#include <endian.h>
+#include <error.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <inttypes.h>
+#include <libintl.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <elf-knowledge.h>
+#include <libeu.h>
+#include <system.h>
+#include <printversion.h>
+#include "../libelf/libelfP.h"
+#include "../libelf/common.h"
+#include "../libebl/libeblP.h"
+#include "../libdw/libdwP.h"
+#include "../libdwfl/libdwflP.h"
+#include "../libdw/memory-access.h"
+
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+#define ARGP_strict	300
+#define ARGP_gnuld	301
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { "strict", ARGP_strict, NULL, 0,
+    N_("Be extremely strict, flag level 2 features."), 0 },
+  { "quiet", 'q', NULL, 0, N_("Do not print anything if successful"), 0 },
+  { "debuginfo", 'd', NULL, 0, N_("Binary is a separate debuginfo file"), 0 },
+  { "gnu-ld", ARGP_gnuld, NULL, 0,
+    N_("Binary has been created with GNU ld and is therefore known to be \
+broken in certain ways"), 0 },
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("\
+Pedantic checking of ELF files compliance with gABI/psABI spec.");
+
+/* 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
+};
+
+
+/* Declarations of local functions.  */
+static void process_file (int fd, Elf *elf, const char *prefix,
+			  const char *suffix, const char *fname, size_t size,
+			  bool only_one);
+static void process_elf_file (Elf *elf, const char *prefix, const char *suffix,
+			      const char *fname, size_t size, bool only_one);
+static void check_note_section (Ebl *ebl, GElf_Ehdr *ehdr,
+				GElf_Shdr *shdr, int idx);
+
+
+/* Report an error.  */
+#define ERROR(str, args...) \
+  do {									      \
+    printf (str, ##args);						      \
+    ++error_count;							      \
+  } while (0)
+static unsigned int error_count;
+
+/* True if we should perform very strict testing.  */
+static bool be_strict;
+
+/* True if no message is to be printed if the run is succesful.  */
+static bool be_quiet;
+
+/* True if binary is from strip -f, not a normal ELF file.  */
+static bool is_debuginfo;
+
+/* True if binary is assumed to be generated with GNU ld.  */
+static bool gnuld;
+
+/* Index of section header string table.  */
+static uint32_t shstrndx;
+
+/* Array to count references in section groups.  */
+static int *scnref;
+
+/* Numbers of sections and program headers.  */
+static unsigned int shnum;
+static unsigned int phnum;
+
+
+int
+main (int argc, char *argv[])
+{
+  /* Set locale.  */
+  setlocale (LC_ALL, "");
+
+  /* Initialize the message catalog.  */
+  textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  int remaining;
+  argp_parse (&argp, argc, argv, 0, &remaining, NULL);
+
+  /* Before we start tell the ELF library which version we are using.  */
+  elf_version (EV_CURRENT);
+
+  /* Now process all the files given at the command line.  */
+  bool only_one = remaining + 1 == argc;
+  do
+    {
+      /* Open the file.  */
+      int fd = open (argv[remaining], O_RDONLY);
+      if (fd == -1)
+	{
+	  error (0, errno, gettext ("cannot open input file"));
+	  continue;
+	}
+
+      /* Create an `Elf' descriptor.  */
+      Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+      if (elf == NULL)
+	ERROR (gettext ("cannot generate Elf descriptor: %s\n"),
+	       elf_errmsg (-1));
+      else
+	{
+	  unsigned int prev_error_count = error_count;
+	  struct stat st;
+
+	  if (fstat (fd, &st) != 0)
+	    {
+	      printf ("cannot stat '%s': %m\n", argv[remaining]);
+	      close (fd);
+	      continue;
+	    }
+
+	  process_file (fd, elf, NULL, NULL, argv[remaining], st.st_size,
+			only_one);
+
+	  /* Now we can close the descriptor.  */
+	  if (elf_end (elf) != 0)
+	    ERROR (gettext ("error while closing Elf descriptor: %s\n"),
+		   elf_errmsg (-1));
+
+	  if (prev_error_count == error_count && !be_quiet)
+	    puts (gettext ("No errors"));
+	}
+
+      close (fd);
+    }
+  while (++remaining < argc);
+
+  return error_count != 0;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg __attribute__ ((unused)),
+	   struct argp_state *state __attribute__ ((unused)))
+{
+  switch (key)
+    {
+    case ARGP_strict:
+      be_strict = true;
+      break;
+
+    case 'q':
+      be_quiet = true;
+      break;
+
+    case 'd':
+      is_debuginfo = true;
+      break;
+
+    case ARGP_gnuld:
+      gnuld = true;
+      break;
+
+    case ARGP_KEY_NO_ARGS:
+      fputs (gettext ("Missing file name.\n"), stderr);
+      argp_help (&argp, stderr, ARGP_HELP_SEE, program_invocation_short_name);
+      exit (EXIT_FAILURE);
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+
+/* Process one file.  */
+static void
+process_file (int fd, Elf *elf, const char *prefix, const char *suffix,
+	      const char *fname, size_t size, bool only_one)
+{
+  /* We can handle two types of files: ELF files and archives.  */
+  Elf_Kind kind = elf_kind (elf);
+
+  switch (kind)
+    {
+    case ELF_K_ELF:
+      /* Yes!  It's an ELF file.  */
+      process_elf_file (elf, prefix, suffix, fname, size, only_one);
+      break;
+
+    case ELF_K_AR:
+      {
+	Elf *subelf;
+	Elf_Cmd cmd = ELF_C_READ_MMAP;
+	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 new_suffix[(suffix == NULL ? 0 : strlen (suffix)) + 2];
+	char *cp = new_prefix;
+
+	/* Create the full name of the file.  */
+	if (prefix != NULL)
+	  {
+	    cp = mempcpy (cp, prefix, prefix_len);
+	    *cp++ = '(';
+	    strcpy (stpcpy (new_suffix, suffix), ")");
+	  }
+	else
+	  new_suffix[0] = '\0';
+	memcpy (cp, fname, fname_len);
+
+	/* It's an archive.  We process each file in it.  */
+	while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
+	  {
+	    kind = elf_kind (subelf);
+
+	    /* Call this function recursively.  */
+	    if (kind == ELF_K_ELF || kind == ELF_K_AR)
+	      {
+		Elf_Arhdr *arhdr = elf_getarhdr (subelf);
+		assert (arhdr != NULL);
+
+		process_file (fd, subelf, new_prefix, new_suffix,
+			      arhdr->ar_name, arhdr->ar_size, false);
+	      }
+
+	    /* Get next archive element.  */
+	    cmd = elf_next (subelf);
+	    if (elf_end (subelf) != 0)
+	      ERROR (gettext (" error while freeing sub-ELF descriptor: %s\n"),
+		     elf_errmsg (-1));
+	  }
+      }
+      break;
+
+    default:
+      /* We cannot do anything.  */
+      ERROR (gettext ("\
+Not an ELF file - it has the wrong magic bytes at the start\n"));
+      break;
+    }
+}
+
+
+static const char *
+section_name (Ebl *ebl, int idx)
+{
+  GElf_Shdr shdr_mem;
+  GElf_Shdr *shdr;
+  const char *ret;
+
+  if ((unsigned int) idx > shnum)
+    return "<invalid>";
+
+  shdr = gelf_getshdr (elf_getscn (ebl->elf, idx), &shdr_mem);
+  if (shdr == NULL)
+    return "<invalid>";
+
+  ret = elf_strptr (ebl->elf, shstrndx, shdr->sh_name);
+  if (ret == NULL)
+    return "<invalid>";
+  return ret;
+}
+
+
+static const int valid_e_machine[] =
+  {
+    EM_M32, EM_SPARC, EM_386, EM_68K, EM_88K, EM_860, EM_MIPS, EM_S370,
+    EM_MIPS_RS3_LE, EM_PARISC, EM_VPP500, EM_SPARC32PLUS, EM_960, EM_PPC,
+    EM_PPC64, EM_S390, EM_V800, EM_FR20, EM_RH32, EM_RCE, EM_ARM,
+    EM_FAKE_ALPHA, EM_SH, EM_SPARCV9, EM_TRICORE, EM_ARC, EM_H8_300,
+    EM_H8_300H, EM_H8S, EM_H8_500, EM_IA_64, EM_MIPS_X, EM_COLDFIRE,
+    EM_68HC12, EM_MMA, EM_PCP, EM_NCPU, EM_NDR1, EM_STARCORE, EM_ME16,
+    EM_ST100, EM_TINYJ, EM_X86_64, EM_PDSP, EM_FX66, EM_ST9PLUS, EM_ST7,
+    EM_68HC16, EM_68HC11, EM_68HC08, EM_68HC05, EM_SVX, EM_ST19, EM_VAX,
+    EM_CRIS, EM_JAVELIN, EM_FIREPATH, EM_ZSP, EM_MMIX, EM_HUANY, EM_PRISM,
+    EM_AVR, EM_FR30, EM_D10V, EM_D30V, EM_V850, EM_M32R, EM_MN10300,
+    EM_MN10200, EM_PJ, EM_OPENRISC, EM_ARC_A5, EM_XTENSA, EM_ALPHA,
+    EM_TILEGX, EM_TILEPRO, EM_AARCH64, EM_BPF
+  };
+#define nvalid_e_machine \
+  (sizeof (valid_e_machine) / sizeof (valid_e_machine[0]))
+
+
+static void
+check_elf_header (Ebl *ebl, GElf_Ehdr *ehdr, size_t size)
+{
+  char buf[512];
+  size_t cnt;
+
+  /* Check e_ident field.  */
+  if (ehdr->e_ident[EI_MAG0] != ELFMAG0)
+    ERROR ("e_ident[%d] != '%c'\n", EI_MAG0, ELFMAG0);
+  if (ehdr->e_ident[EI_MAG1] != ELFMAG1)
+    ERROR ("e_ident[%d] != '%c'\n", EI_MAG1, ELFMAG1);
+  if (ehdr->e_ident[EI_MAG2] != ELFMAG2)
+    ERROR ("e_ident[%d] != '%c'\n", EI_MAG2, ELFMAG2);
+  if (ehdr->e_ident[EI_MAG3] != ELFMAG3)
+    ERROR ("e_ident[%d] != '%c'\n", EI_MAG3, ELFMAG3);
+
+  if (ehdr->e_ident[EI_CLASS] != ELFCLASS32
+      && ehdr->e_ident[EI_CLASS] != ELFCLASS64)
+    ERROR (gettext ("e_ident[%d] == %d is no known class\n"),
+	   EI_CLASS, ehdr->e_ident[EI_CLASS]);
+
+  if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB
+      && ehdr->e_ident[EI_DATA] != ELFDATA2MSB)
+    ERROR (gettext ("e_ident[%d] == %d is no known data encoding\n"),
+	   EI_DATA, ehdr->e_ident[EI_DATA]);
+
+  if (ehdr->e_ident[EI_VERSION] != EV_CURRENT)
+    ERROR (gettext ("unknown ELF header version number e_ident[%d] == %d\n"),
+	   EI_VERSION, ehdr->e_ident[EI_VERSION]);
+
+  /* We currently don't handle any OS ABIs other than Linux and the
+     kFreeBSD variant of Debian.  */
+  if (ehdr->e_ident[EI_OSABI] != ELFOSABI_NONE
+      && ehdr->e_ident[EI_OSABI] != ELFOSABI_LINUX
+      && ehdr->e_ident[EI_OSABI] != ELFOSABI_FREEBSD)
+    ERROR (gettext ("unsupported OS ABI e_ident[%d] == '%s'\n"),
+	   EI_OSABI,
+	   ebl_osabi_name (ebl, ehdr->e_ident[EI_OSABI], buf, sizeof (buf)));
+
+  /* No ABI versions other than zero supported either.  */
+  if (ehdr->e_ident[EI_ABIVERSION] != 0)
+    ERROR (gettext ("unsupport ABI version e_ident[%d] == %d\n"),
+	   EI_ABIVERSION, ehdr->e_ident[EI_ABIVERSION]);
+
+  for (cnt = EI_PAD; cnt < EI_NIDENT; ++cnt)
+    if (ehdr->e_ident[cnt] != 0)
+      ERROR (gettext ("e_ident[%zu] is not zero\n"), cnt);
+
+  /* Check the e_type field.  */
+  if (ehdr->e_type != ET_REL && ehdr->e_type != ET_EXEC
+      && ehdr->e_type != ET_DYN && ehdr->e_type != ET_CORE)
+    ERROR (gettext ("unknown object file type %d\n"), ehdr->e_type);
+
+  /* Check the e_machine field.  */
+  for (cnt = 0; cnt < nvalid_e_machine; ++cnt)
+    if (valid_e_machine[cnt] == ehdr->e_machine)
+      break;
+  if (cnt == nvalid_e_machine)
+    ERROR (gettext ("unknown machine type %d\n"), ehdr->e_machine);
+
+  /* Check the e_version field.  */
+  if (ehdr->e_version != EV_CURRENT)
+    ERROR (gettext ("unknown object file version\n"));
+
+  /* Check the e_phoff and e_phnum fields.  */
+  if (ehdr->e_phoff == 0)
+    {
+      if (ehdr->e_phnum != 0)
+	ERROR (gettext ("invalid program header offset\n"));
+      else if (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_DYN)
+	ERROR (gettext ("\
+executables and DSOs cannot have zero program header offset\n"));
+    }
+  else if (ehdr->e_phnum == 0)
+    ERROR (gettext ("invalid number of program header entries\n"));
+
+  /* Check the e_shoff field.  */
+  shnum = ehdr->e_shnum;
+  shstrndx = ehdr->e_shstrndx;
+  if (ehdr->e_shoff == 0)
+    {
+      if (ehdr->e_shnum != 0)
+	ERROR (gettext ("invalid section header table offset\n"));
+      else if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN
+	       && ehdr->e_type != ET_CORE)
+	ERROR (gettext ("section header table must be present\n"));
+    }
+  else
+    {
+      if (ehdr->e_shnum == 0)
+	{
+	  /* Get the header of the zeroth section.  The sh_size field
+	     might contain the section number.  */
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
+	  if (shdr != NULL)
+	    {
+	      /* The error will be reported later.  */
+	      if (shdr->sh_size == 0)
+		ERROR (gettext ("\
+invalid number of section header table entries\n"));
+	      else
+		shnum = shdr->sh_size;
+	    }
+	}
+
+      if (ehdr->e_shstrndx == SHN_XINDEX)
+	{
+	  /* Get the header of the zeroth section.  The sh_size field
+	     might contain the section number.  */
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
+	  if (shdr != NULL && shdr->sh_link < shnum)
+	    shstrndx = shdr->sh_link;
+	}
+      else if (shstrndx >= shnum)
+	ERROR (gettext ("invalid section header index\n"));
+    }
+
+  /* Check the shdrs actually exist.  And uncompress them before
+     further checking.  Indexes between sections reference the
+     uncompressed data.  */
+  unsigned int scnt;
+  Elf_Scn *scn = NULL;
+  for (scnt = 1; scnt < shnum; ++scnt)
+     {
+	scn = elf_nextscn (ebl->elf, scn);
+	if (scn == NULL)
+	  break;
+	/* If the section wasn't compressed this does nothing, but
+	   returns an error.  We don't care.  */
+	elf_compress (scn, 0, 0);
+     }
+  if (scnt < shnum)
+    ERROR (gettext ("Can only check %u headers, shnum was %u\n"), scnt, shnum);
+  shnum = scnt;
+
+  phnum = ehdr->e_phnum;
+  if (ehdr->e_phnum == PN_XNUM)
+    {
+      /* Get the header of the zeroth section.  The sh_info field
+	 might contain the phnum count.  */
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
+      if (shdr != NULL)
+	{
+	  /* The error will be reported later.  */
+	  if (shdr->sh_info < PN_XNUM)
+	    ERROR (gettext ("\
+invalid number of program header table entries\n"));
+	  else
+	    phnum = shdr->sh_info;
+	}
+    }
+
+  /* Check the phdrs actually exist. */
+  unsigned int pcnt;
+  for (pcnt = 0; pcnt < phnum; ++pcnt)
+     {
+	GElf_Phdr phdr_mem;
+	GElf_Phdr *phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem);
+	if (phdr == NULL)
+	  break;
+     }
+  if (pcnt < phnum)
+    ERROR (gettext ("Can only check %u headers, phnum was %u\n"), pcnt, phnum);
+  phnum = pcnt;
+
+  /* Check the e_flags field.  */
+  if (!ebl_machine_flag_check (ebl, ehdr->e_flags))
+    ERROR (gettext ("invalid machine flags: %s\n"),
+	   ebl_machine_flag_name (ebl, ehdr->e_flags, buf, sizeof (buf)));
+
+  /* Check e_ehsize, e_phentsize, and e_shentsize fields.  */
+  if (gelf_getclass (ebl->elf) == ELFCLASS32)
+    {
+      if (ehdr->e_ehsize != 0 && ehdr->e_ehsize != sizeof (Elf32_Ehdr))
+	ERROR (gettext ("invalid ELF header size: %hd\n"), ehdr->e_ehsize);
+
+      if (ehdr->e_phentsize != 0 && ehdr->e_phentsize != sizeof (Elf32_Phdr))
+	ERROR (gettext ("invalid program header size: %hd\n"),
+	       ehdr->e_phentsize);
+      else if (ehdr->e_phoff + phnum * ehdr->e_phentsize > size)
+	ERROR (gettext ("invalid program header position or size\n"));
+
+      if (ehdr->e_shentsize != 0 && ehdr->e_shentsize != sizeof (Elf32_Shdr))
+	ERROR (gettext ("invalid section header size: %hd\n"),
+	       ehdr->e_shentsize);
+      else if (ehdr->e_shoff + shnum * ehdr->e_shentsize > size)
+	ERROR (gettext ("invalid section header position or size\n"));
+    }
+  else if (gelf_getclass (ebl->elf) == ELFCLASS64)
+    {
+      if (ehdr->e_ehsize != 0 && ehdr->e_ehsize != sizeof (Elf64_Ehdr))
+	ERROR (gettext ("invalid ELF header size: %hd\n"), ehdr->e_ehsize);
+
+      if (ehdr->e_phentsize != 0 && ehdr->e_phentsize != sizeof (Elf64_Phdr))
+	ERROR (gettext ("invalid program header size: %hd\n"),
+	       ehdr->e_phentsize);
+      else if (ehdr->e_phoff + phnum * ehdr->e_phentsize > size)
+	ERROR (gettext ("invalid program header position or size\n"));
+
+      if (ehdr->e_shentsize != 0 && ehdr->e_shentsize != sizeof (Elf64_Shdr))
+	ERROR (gettext ("invalid section header size: %hd\n"),
+	       ehdr->e_shentsize);
+      else if (ehdr->e_shoff + ehdr->e_shnum * ehdr->e_shentsize > size)
+	ERROR (gettext ("invalid section header position or size\n"));
+    }
+}
+
+
+/* Check that there is a section group section with index < IDX which
+   contains section IDX and that there is exactly one.  */
+static void
+check_scn_group (Ebl *ebl, int idx)
+{
+  if (scnref[idx] == 0)
+    {
+      /* No reference so far.  Search following sections, maybe the
+	 order is wrong.  */
+      size_t cnt;
+
+      for (cnt = idx + 1; cnt < shnum; ++cnt)
+	{
+	  Elf_Scn *scn = elf_getscn (ebl->elf, cnt);
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	  if (shdr == NULL)
+	    /* We cannot get the section header so we cannot check it.
+	       The error to get the section header will be shown
+	       somewhere else.  */
+	    continue;
+
+	  if (shdr->sh_type != SHT_GROUP)
+	    continue;
+
+	  Elf_Data *data = elf_getdata (scn, NULL);
+	  if (data == NULL || data->d_buf == NULL
+	      || data->d_size < sizeof (Elf32_Word))
+	    /* Cannot check the section.  */
+	    continue;
+
+	  Elf32_Word *grpdata = (Elf32_Word *) data->d_buf;
+	  for (size_t inner = 1; inner < data->d_size / sizeof (Elf32_Word);
+	       ++inner)
+	    if (grpdata[inner] == (Elf32_Word) idx)
+	      goto out;
+	}
+
+    out:
+      if (cnt == shnum)
+	ERROR (gettext ("\
+section [%2d] '%s': section with SHF_GROUP flag set not part of a section group\n"),
+	       idx, section_name (ebl, idx));
+      else
+	ERROR (gettext ("\
+section [%2d] '%s': section group [%2zu] '%s' does not precede group member\n"),
+	       idx, section_name (ebl, idx),
+	       cnt, section_name (ebl, cnt));
+    }
+}
+
+
+static void
+check_symtab (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
+{
+  bool no_xndx_warned = false;
+  int no_pt_tls = 0;
+  Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL)
+    {
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  GElf_Shdr strshdr_mem;
+  GElf_Shdr *strshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
+				     &strshdr_mem);
+  if (strshdr == NULL)
+    return;
+
+  if (strshdr->sh_type != SHT_STRTAB)
+    {
+      ERROR (gettext ("section [%2d] '%s': referenced as string table for section [%2d] '%s' but type is not SHT_STRTAB\n"),
+	     shdr->sh_link, section_name (ebl, shdr->sh_link),
+	     idx, section_name (ebl, idx));
+      strshdr = NULL;
+    }
+
+  /* Search for an extended section index table section.  */
+  Elf_Data *xndxdata = NULL;
+  Elf32_Word xndxscnidx = 0;
+  bool found_xndx = false;
+  for (size_t cnt = 1; cnt < shnum; ++cnt)
+    if (cnt != (size_t) idx)
+      {
+	Elf_Scn *xndxscn = elf_getscn (ebl->elf, cnt);
+	GElf_Shdr xndxshdr_mem;
+	GElf_Shdr *xndxshdr = gelf_getshdr (xndxscn, &xndxshdr_mem);
+	if (xndxshdr == NULL)
+	  continue;
+
+	if (xndxshdr->sh_type == SHT_SYMTAB_SHNDX
+	    && xndxshdr->sh_link == (GElf_Word) idx)
+	  {
+	    if (found_xndx)
+	      ERROR (gettext ("\
+section [%2d] '%s': symbol table cannot have more than one extended index section\n"),
+		     idx, section_name (ebl, idx));
+
+	    xndxdata = elf_getdata (xndxscn, NULL);
+	    xndxscnidx = elf_ndxscn (xndxscn);
+	    found_xndx = true;
+	  }
+      }
+
+  size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_SYM, 1, EV_CURRENT);
+  if (shdr->sh_entsize != sh_entsize)
+    ERROR (gettext ("\
+section [%2u] '%s': entry size is does not match ElfXX_Sym\n"),
+	   idx, section_name (ebl, idx));
+
+  /* Test the zeroth entry.  */
+  GElf_Sym sym_mem;
+  Elf32_Word xndx;
+  GElf_Sym *sym = gelf_getsymshndx (data, xndxdata, 0, &sym_mem, &xndx);
+  if (sym == NULL)
+      ERROR (gettext ("section [%2d] '%s': cannot get symbol %d: %s\n"),
+	     idx, section_name (ebl, idx), 0, elf_errmsg (-1));
+  else
+    {
+      if (sym->st_name != 0)
+	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
+	       idx, section_name (ebl, idx), "st_name");
+      if (sym->st_value != 0)
+	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
+	       idx, section_name (ebl, idx), "st_value");
+      if (sym->st_size != 0)
+	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
+	       idx, section_name (ebl, idx), "st_size");
+      if (sym->st_info != 0)
+	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
+	       idx, section_name (ebl, idx), "st_info");
+      if (sym->st_other != 0)
+	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
+	       idx, section_name (ebl, idx), "st_other");
+      if (sym->st_shndx != 0)
+	ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
+	       idx, section_name (ebl, idx), "st_shndx");
+      if (xndxdata != NULL && xndx != 0)
+	ERROR (gettext ("\
+section [%2d] '%s': XINDEX for zeroth entry not zero\n"),
+	       xndxscnidx, section_name (ebl, xndxscnidx));
+    }
+
+  for (size_t cnt = 1; cnt < shdr->sh_size / sh_entsize; ++cnt)
+    {
+      sym = gelf_getsymshndx (data, xndxdata, cnt, &sym_mem, &xndx);
+      if (sym == NULL)
+	{
+	  ERROR (gettext ("section [%2d] '%s': cannot get symbol %zu: %s\n"),
+		 idx, section_name (ebl, idx), cnt, elf_errmsg (-1));
+	  continue;
+	}
+
+      const char *name = NULL;
+      if (strshdr == NULL)
+	name = "";
+      else if (sym->st_name >= strshdr->sh_size)
+	ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: invalid name value\n"),
+	       idx, section_name (ebl, idx), cnt);
+      else
+	{
+	  name = elf_strptr (ebl->elf, shdr->sh_link, sym->st_name);
+	  if (name == NULL)
+	    name = "";
+	}
+
+      if (sym->st_shndx == SHN_XINDEX)
+	{
+	  if (xndxdata == NULL)
+	    {
+	      if (!no_xndx_warned)
+		ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: too large section index but no extended section index section\n"),
+		       idx, section_name (ebl, idx), cnt);
+	      no_xndx_warned = true;
+	    }
+	  else if (xndx < SHN_LORESERVE)
+	    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: XINDEX used for index which would fit in st_shndx (%" PRIu32 ")\n"),
+		   xndxscnidx, section_name (ebl, xndxscnidx), cnt,
+		   xndx);
+	}
+      else if ((sym->st_shndx >= SHN_LORESERVE
+		// && sym->st_shndx <= SHN_HIRESERVE    always true
+		&& sym->st_shndx != SHN_ABS
+		&& sym->st_shndx != SHN_COMMON)
+	       || (sym->st_shndx >= shnum
+		   && (sym->st_shndx < SHN_LORESERVE
+		       /* || sym->st_shndx > SHN_HIRESERVE  always false */)))
+	ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: invalid section index\n"),
+	       idx, section_name (ebl, idx), cnt);
+      else
+	xndx = sym->st_shndx;
+
+      if (GELF_ST_TYPE (sym->st_info) >= STT_NUM
+	  && !ebl_symbol_type_name (ebl, GELF_ST_TYPE (sym->st_info), NULL, 0))
+	ERROR (gettext ("section [%2d] '%s': symbol %zu: unknown type\n"),
+	       idx, section_name (ebl, idx), cnt);
+
+      if (GELF_ST_BIND (sym->st_info) >= STB_NUM
+	  && !ebl_symbol_binding_name (ebl, GELF_ST_BIND (sym->st_info), NULL,
+				       0))
+	ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: unknown symbol binding\n"),
+	       idx, section_name (ebl, idx), cnt);
+      if (GELF_ST_BIND (sym->st_info) == STB_GNU_UNIQUE
+	  && GELF_ST_TYPE (sym->st_info) != STT_OBJECT)
+	ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: unique symbol not of object type\n"),
+	       idx, section_name (ebl, idx), cnt);
+
+      if (xndx == SHN_COMMON)
+	{
+	  /* Common symbols can only appear in relocatable files.  */
+	  if (ehdr->e_type != ET_REL)
+	    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: COMMON only allowed in relocatable files\n"),
+		   idx, section_name (ebl, idx), cnt);
+	  if (cnt < shdr->sh_info)
+	    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: local COMMON symbols are nonsense\n"),
+		   idx, section_name (ebl, idx), cnt);
+	  if (GELF_R_TYPE (sym->st_info) == STT_FUNC)
+	    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: function in COMMON section is nonsense\n"),
+		   idx, section_name (ebl, idx), cnt);
+	}
+      else if (xndx > 0 && xndx < shnum)
+	{
+	  GElf_Shdr destshdr_mem;
+	  GElf_Shdr *destshdr;
+
+	  destshdr = gelf_getshdr (elf_getscn (ebl->elf, xndx), &destshdr_mem);
+	  if (destshdr != NULL)
+	    {
+	      GElf_Addr sh_addr = (ehdr->e_type == ET_REL ? 0
+				   : destshdr->sh_addr);
+	      GElf_Addr st_value;
+	      if (GELF_ST_TYPE (sym->st_info) == STT_FUNC
+		  || (GELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC))
+		st_value = sym->st_value & ebl_func_addr_mask (ebl);
+	      else
+		st_value = sym->st_value;
+	      if (GELF_ST_TYPE (sym->st_info) != STT_TLS)
+		{
+		  if (! ebl_check_special_symbol (ebl, ehdr, sym, name,
+						  destshdr))
+		    {
+		      if (st_value - sh_addr > destshdr->sh_size)
+			{
+			  /* GNU ld has severe bugs.  When it decides to remove
+			     empty sections it leaves symbols referencing them
+			     behind.  These are symbols in .symtab or .dynsym
+			     and for the named symbols have zero size.  See
+			     sourceware PR13621.  */
+			  if (!gnuld
+			      || (strcmp (section_name (ebl, idx), ".symtab")
+			          && strcmp (section_name (ebl, idx),
+					     ".dynsym"))
+			      || sym->st_size != 0
+			      || (strcmp (name, "__preinit_array_start") != 0
+				  && strcmp (name, "__preinit_array_end") != 0
+				  && strcmp (name, "__init_array_start") != 0
+				  && strcmp (name, "__init_array_end") != 0
+				  && strcmp (name, "__fini_array_start") != 0
+				  && strcmp (name, "__fini_array_end") != 0
+				  && strcmp (name, "__bss_start") != 0
+				  && strcmp (name, "__bss_start__") != 0
+				  && strcmp (name, "__TMC_END__") != 0
+				  && strcmp (name, ".TOC.") != 0
+				  && strcmp (name, "_edata") != 0
+				  && strcmp (name, "__edata") != 0
+				  && strcmp (name, "_end") != 0
+				  && strcmp (name, "__end") != 0))
+			    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: st_value out of bounds\n"),
+				   idx, section_name (ebl, idx), cnt);
+			}
+		      else if ((st_value - sh_addr
+				+ sym->st_size) > destshdr->sh_size)
+			ERROR (gettext ("\
+section [%2d] '%s': symbol %zu does not fit completely in referenced section [%2d] '%s'\n"),
+			       idx, section_name (ebl, idx), cnt,
+			       (int) xndx, section_name (ebl, xndx));
+		    }
+		}
+	      else
+		{
+		  if ((destshdr->sh_flags & SHF_TLS) == 0)
+		    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: referenced section [%2d] '%s' does not have SHF_TLS flag set\n"),
+			   idx, section_name (ebl, idx), cnt,
+			   (int) xndx, section_name (ebl, xndx));
+
+		  if (ehdr->e_type == ET_REL)
+		    {
+		      /* For object files the symbol value must fall
+			 into the section.  */
+		      if (st_value > destshdr->sh_size)
+			ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: st_value out of bounds of referenced section [%2d] '%s'\n"),
+			       idx, section_name (ebl, idx), cnt,
+			       (int) xndx, section_name (ebl, xndx));
+		      else if (st_value + sym->st_size
+			       > destshdr->sh_size)
+			ERROR (gettext ("\
+section [%2d] '%s': symbol %zu does not fit completely in referenced section [%2d] '%s'\n"),
+			       idx, section_name (ebl, idx), cnt,
+			       (int) xndx, section_name (ebl, xndx));
+		    }
+		  else
+		    {
+		      GElf_Phdr phdr_mem;
+		      GElf_Phdr *phdr = NULL;
+		      unsigned int pcnt;
+
+		      for (pcnt = 0; pcnt < phnum; ++pcnt)
+			{
+			  phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem);
+			  if (phdr != NULL && phdr->p_type == PT_TLS)
+			    break;
+			}
+
+		      if (pcnt == phnum)
+			{
+			  if (no_pt_tls++ == 0)
+			    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: TLS symbol but no TLS program header entry\n"),
+				   idx, section_name (ebl, idx), cnt);
+			}
+		      else if (phdr == NULL)
+			{
+			    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: TLS symbol but couldn't get TLS program header entry\n"),
+				   idx, section_name (ebl, idx), cnt);
+			}
+		      else if (!is_debuginfo)
+			{
+			  if (st_value
+			      < destshdr->sh_offset - phdr->p_offset)
+			    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: st_value short of referenced section [%2d] '%s'\n"),
+				   idx, section_name (ebl, idx), cnt,
+				   (int) xndx, section_name (ebl, xndx));
+			  else if (st_value
+				   > (destshdr->sh_offset - phdr->p_offset
+				      + destshdr->sh_size))
+			    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: st_value out of bounds of referenced section [%2d] '%s'\n"),
+				   idx, section_name (ebl, idx), cnt,
+				   (int) xndx, section_name (ebl, xndx));
+			  else if (st_value + sym->st_size
+				   > (destshdr->sh_offset - phdr->p_offset
+				      + destshdr->sh_size))
+			    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu does not fit completely in referenced section [%2d] '%s'\n"),
+				   idx, section_name (ebl, idx), cnt,
+				   (int) xndx, section_name (ebl, xndx));
+			}
+		    }
+		}
+	    }
+	}
+
+      if (GELF_ST_BIND (sym->st_info) == STB_LOCAL)
+	{
+	  if (cnt >= shdr->sh_info)
+	    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: local symbol outside range described in sh_info\n"),
+		   idx, section_name (ebl, idx), cnt);
+	}
+      else
+	{
+	  if (cnt < shdr->sh_info)
+	    ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: non-local symbol outside range described in sh_info\n"),
+		   idx, section_name (ebl, idx), cnt);
+	}
+
+      if (GELF_ST_TYPE (sym->st_info) == STT_SECTION
+	  && GELF_ST_BIND (sym->st_info) != STB_LOCAL)
+	ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: non-local section symbol\n"),
+	       idx, section_name (ebl, idx), cnt);
+
+      if (name != NULL)
+	{
+	  if (strcmp (name, "_GLOBAL_OFFSET_TABLE_") == 0)
+	    {
+	      /* Check that address and size match the global offset table.  */
+
+	      GElf_Shdr destshdr_mem;
+	      GElf_Shdr *destshdr = gelf_getshdr (elf_getscn (ebl->elf, xndx),
+						  &destshdr_mem);
+
+	      if (destshdr == NULL && xndx == SHN_ABS)
+		{
+		  /* In a DSO, we have to find the GOT section by name.  */
+		  Elf_Scn *gotscn = NULL;
+		  Elf_Scn *gscn = NULL;
+		  while ((gscn = elf_nextscn (ebl->elf, gscn)) != NULL)
+		    {
+		      destshdr = gelf_getshdr (gscn, &destshdr_mem);
+		      assert (destshdr != NULL);
+		      const char *sname = elf_strptr (ebl->elf,
+						      ehdr->e_shstrndx,
+						      destshdr->sh_name);
+		      if (sname != NULL)
+			{
+			  if (strcmp (sname, ".got.plt") == 0)
+			    break;
+			  if (strcmp (sname, ".got") == 0)
+			    /* Do not stop looking.
+			       There might be a .got.plt section.  */
+			    gotscn = gscn;
+			}
+
+		      destshdr = NULL;
+		    }
+
+		  if (destshdr == NULL && gotscn != NULL)
+		    destshdr = gelf_getshdr (gotscn, &destshdr_mem);
+		}
+
+	      const char *sname = ((destshdr == NULL || xndx == SHN_UNDEF)
+				   ? NULL
+				   : elf_strptr (ebl->elf, ehdr->e_shstrndx,
+						 destshdr->sh_name));
+	      if (sname == NULL)
+		{
+		  if (xndx != SHN_UNDEF || ehdr->e_type != ET_REL)
+		    ERROR (gettext ("\
+section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol refers to \
+bad section [%2d]\n"),
+			   idx, section_name (ebl, idx), xndx);
+		}
+	      else if (strcmp (sname, ".got.plt") != 0
+		       && strcmp (sname, ".got") != 0)
+		ERROR (gettext ("\
+section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol refers to \
+section [%2d] '%s'\n"),
+		       idx, section_name (ebl, idx), xndx, sname);
+
+	      if (destshdr != NULL)
+		{
+		  /* Found it.  */
+		  if (!ebl_check_special_symbol (ebl, ehdr, sym, name,
+						 destshdr))
+		    {
+		      if (ehdr->e_type != ET_REL
+			  && sym->st_value != destshdr->sh_addr)
+			/* This test is more strict than the psABIs which
+			   usually allow the symbol to be in the middle of
+			   the .got section, allowing negative offsets.  */
+			ERROR (gettext ("\
+section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol value %#" PRIx64 " does not match %s section address %#" PRIx64 "\n"),
+			       idx, section_name (ebl, idx),
+			       (uint64_t) sym->st_value,
+			       sname, (uint64_t) destshdr->sh_addr);
+
+		      if (!gnuld && sym->st_size != destshdr->sh_size)
+			ERROR (gettext ("\
+section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol size %" PRIu64 " does not match %s section size %" PRIu64 "\n"),
+			       idx, section_name (ebl, idx),
+			       (uint64_t) sym->st_size,
+			       sname, (uint64_t) destshdr->sh_size);
+		    }
+		}
+	      else
+		ERROR (gettext ("\
+section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol present, but no .got section\n"),
+		       idx, section_name (ebl, idx));
+	    }
+	  else if (strcmp (name, "_DYNAMIC") == 0)
+	    /* Check that address and size match the dynamic section.
+	       We locate the dynamic section via the program header
+	       entry.  */
+	    for (unsigned int pcnt = 0; pcnt < phnum; ++pcnt)
+	      {
+		GElf_Phdr phdr_mem;
+		GElf_Phdr *phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem);
+
+		if (phdr != NULL && phdr->p_type == PT_DYNAMIC)
+		  {
+		    if (sym->st_value != phdr->p_vaddr)
+		      ERROR (gettext ("\
+section [%2d] '%s': _DYNAMIC_ symbol value %#" PRIx64 " does not match dynamic segment address %#" PRIx64 "\n"),
+			     idx, section_name (ebl, idx),
+			     (uint64_t) sym->st_value,
+			     (uint64_t) phdr->p_vaddr);
+
+		    if (!gnuld && sym->st_size != phdr->p_memsz)
+		      ERROR (gettext ("\
+section [%2d] '%s': _DYNAMIC symbol size %" PRIu64 " does not match dynamic segment size %" PRIu64 "\n"),
+			     idx, section_name (ebl, idx),
+			     (uint64_t) sym->st_size,
+			     (uint64_t) phdr->p_memsz);
+
+		    break;
+		  }
+	    }
+	}
+
+      if (GELF_ST_VISIBILITY (sym->st_other) != STV_DEFAULT
+	  && shdr->sh_type == SHT_DYNSYM)
+	ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: symbol in dynamic symbol table with non-default visibility\n"),
+	       idx, section_name (ebl, idx), cnt);
+      if (! ebl_check_st_other_bits (ebl, sym->st_other))
+	ERROR (gettext ("\
+section [%2d] '%s': symbol %zu: unknown bit set in st_other\n"),
+	       idx, section_name (ebl, idx), cnt);
+
+    }
+}
+
+
+static bool
+is_rel_dyn (Ebl *ebl, const GElf_Ehdr *ehdr, int idx, const GElf_Shdr *shdr,
+	    bool is_rela)
+{
+  /* If this is no executable or DSO it cannot be a .rel.dyn section.  */
+  if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
+    return false;
+
+  /* Check the section name.  Unfortunately necessary.  */
+  if (strcmp (section_name (ebl, idx), is_rela ? ".rela.dyn" : ".rel.dyn"))
+    return false;
+
+  /* When a .rel.dyn section is used a DT_RELCOUNT dynamic section
+     entry can be present as well.  */
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      GElf_Shdr rcshdr_mem;
+      const GElf_Shdr *rcshdr = gelf_getshdr (scn, &rcshdr_mem);
+
+      if (rcshdr == NULL)
+	break;
+
+      if (rcshdr->sh_type == SHT_DYNAMIC && rcshdr->sh_entsize != 0)
+	{
+	  /* Found the dynamic section.  Look through it.  */
+	  Elf_Data *d = elf_getdata (scn, NULL);
+	  size_t cnt;
+
+	  if (d == NULL)
+	    ERROR (gettext ("\
+section [%2d] '%s': cannot get section data.\n"),
+		   idx, section_name (ebl, idx));
+
+	  for (cnt = 1; cnt < rcshdr->sh_size / rcshdr->sh_entsize; ++cnt)
+	    {
+	      GElf_Dyn dyn_mem;
+	      GElf_Dyn *dyn = gelf_getdyn (d, cnt, &dyn_mem);
+
+	      if (dyn == NULL)
+		break;
+
+	      if (dyn->d_tag == DT_RELCOUNT)
+		{
+		  /* Found it.  Does the type match.  */
+		  if (is_rela)
+		    ERROR (gettext ("\
+section [%2d] '%s': DT_RELCOUNT used for this RELA section\n"),
+			   idx, section_name (ebl, idx));
+		  else
+		    {
+		      /* Does the number specified number of relative
+			 relocations exceed the total number of
+			 relocations?  */
+		      if (shdr->sh_entsize != 0
+			  && dyn->d_un.d_val > (shdr->sh_size
+						/ shdr->sh_entsize))
+			ERROR (gettext ("\
+section [%2d] '%s': DT_RELCOUNT value %d too high for this section\n"),
+			       idx, section_name (ebl, idx),
+			       (int) dyn->d_un.d_val);
+
+		      /* Make sure the specified number of relocations are
+			 relative.  */
+		      Elf_Data *reldata = elf_getdata (elf_getscn (ebl->elf,
+								   idx), NULL);
+		      if (reldata != NULL && shdr->sh_entsize != 0)
+			for (size_t inner = 0;
+			     inner < shdr->sh_size / shdr->sh_entsize;
+			     ++inner)
+			  {
+			    GElf_Rel rel_mem;
+			    GElf_Rel *rel = gelf_getrel (reldata, inner,
+							 &rel_mem);
+			    if (rel == NULL)
+			      /* The problem will be reported elsewhere.  */
+			      break;
+
+			    if (ebl_relative_reloc_p (ebl,
+						      GELF_R_TYPE (rel->r_info)))
+			      {
+				if (inner >= dyn->d_un.d_val)
+				  ERROR (gettext ("\
+section [%2d] '%s': relative relocations after index %d as specified by DT_RELCOUNT\n"),
+					 idx, section_name (ebl, idx),
+					 (int) dyn->d_un.d_val);
+			      }
+			    else if (inner < dyn->d_un.d_val)
+			      ERROR (gettext ("\
+section [%2d] '%s': non-relative relocation at index %zu; DT_RELCOUNT specified %d relative relocations\n"),
+				     idx, section_name (ebl, idx),
+				     inner, (int) dyn->d_un.d_val);
+			  }
+		    }
+		}
+
+	      if (dyn->d_tag == DT_RELACOUNT)
+		{
+		  /* Found it.  Does the type match.  */
+		  if (!is_rela)
+		    ERROR (gettext ("\
+section [%2d] '%s': DT_RELACOUNT used for this REL section\n"),
+			   idx, section_name (ebl, idx));
+		  else
+		    {
+		      /* Does the number specified number of relative
+			 relocations exceed the total number of
+			 relocations?  */
+		      if (shdr->sh_entsize != 0
+			  && dyn->d_un.d_val > shdr->sh_size / shdr->sh_entsize)
+			ERROR (gettext ("\
+section [%2d] '%s': DT_RELCOUNT value %d too high for this section\n"),
+			       idx, section_name (ebl, idx),
+			       (int) dyn->d_un.d_val);
+
+		      /* Make sure the specified number of relocations are
+			 relative.  */
+		      Elf_Data *reldata = elf_getdata (elf_getscn (ebl->elf,
+								   idx), NULL);
+		      if (reldata != NULL && shdr->sh_entsize != 0)
+			for (size_t inner = 0;
+			     inner < shdr->sh_size / shdr->sh_entsize;
+			     ++inner)
+			  {
+			    GElf_Rela rela_mem;
+			    GElf_Rela *rela = gelf_getrela (reldata, inner,
+							    &rela_mem);
+			    if (rela == NULL)
+			      /* The problem will be reported elsewhere.  */
+			      break;
+
+			    if (ebl_relative_reloc_p (ebl,
+						      GELF_R_TYPE (rela->r_info)))
+			      {
+				if (inner >= dyn->d_un.d_val)
+				  ERROR (gettext ("\
+section [%2d] '%s': relative relocations after index %d as specified by DT_RELCOUNT\n"),
+					 idx, section_name (ebl, idx),
+					 (int) dyn->d_un.d_val);
+			      }
+			    else if (inner < dyn->d_un.d_val)
+			      ERROR (gettext ("\
+section [%2d] '%s': non-relative relocation at index %zu; DT_RELCOUNT specified %d relative relocations\n"),
+				     idx, section_name (ebl, idx),
+				     inner, (int) dyn->d_un.d_val);
+			  }
+		    }
+		}
+	    }
+
+	  break;
+	}
+    }
+
+  return true;
+}
+
+
+struct loaded_segment
+{
+  GElf_Addr from;
+  GElf_Addr to;
+  bool read_only;
+  struct loaded_segment *next;
+};
+
+
+/* Check whether binary has text relocation flag set.  */
+static bool textrel;
+
+/* Keep track of whether text relocation flag is needed.  */
+static bool needed_textrel;
+
+
+static bool
+check_reloc_shdr (Ebl *ebl, const GElf_Ehdr *ehdr, const GElf_Shdr *shdr,
+		  int idx, int reltype, GElf_Shdr **destshdrp,
+		  GElf_Shdr *destshdr_memp, struct loaded_segment **loadedp)
+{
+  bool reldyn = false;
+
+  /* Check whether the link to the section we relocate is reasonable.  */
+  if (shdr->sh_info >= shnum)
+    ERROR (gettext ("section [%2d] '%s': invalid destination section index\n"),
+	   idx, section_name (ebl, idx));
+  else if (shdr->sh_info != 0)
+    {
+      *destshdrp = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_info),
+				 destshdr_memp);
+      if (*destshdrp != NULL)
+	{
+	  if(! ebl_check_reloc_target_type (ebl, (*destshdrp)->sh_type))
+	    {
+	      reldyn = is_rel_dyn (ebl, ehdr, idx, shdr, true);
+	      if (!reldyn)
+		ERROR (gettext ("\
+section [%2d] '%s': invalid destination section type\n"),
+		       idx, section_name (ebl, idx));
+	      else
+		{
+		  /* There is no standard, but we require that .rel{,a}.dyn
+		     sections have a sh_info value of zero.  */
+		  if (shdr->sh_info != 0)
+		    ERROR (gettext ("\
+section [%2d] '%s': sh_info should be zero\n"),
+			   idx, section_name (ebl, idx));
+		}
+	    }
+
+	  if ((((*destshdrp)->sh_flags & SHF_MERGE) != 0)
+	      && ((*destshdrp)->sh_flags & SHF_STRINGS) != 0)
+	    ERROR (gettext ("\
+section [%2d] '%s': no relocations for merge-able string sections possible\n"),
+		   idx, section_name (ebl, idx));
+	}
+    }
+
+  size_t sh_entsize = gelf_fsize (ebl->elf, reltype, 1, EV_CURRENT);
+  if (shdr->sh_entsize != sh_entsize)
+    ERROR (gettext (reltype == ELF_T_RELA ? "\
+section [%2d] '%s': section entry size does not match ElfXX_Rela\n" : "\
+section [%2d] '%s': section entry size does not match ElfXX_Rel\n"),
+	   idx, section_name (ebl, idx));
+
+  /* In preparation of checking whether relocations are text
+     relocations or not we need to determine whether the file is
+     flagged to have text relocation and we need to determine a) what
+     the loaded segments are and b) which are read-only.  This will
+     also allow us to determine whether the same reloc section is
+     modifying loaded and not loaded segments.  */
+  for (unsigned int i = 0; i < phnum; ++i)
+    {
+      GElf_Phdr phdr_mem;
+      GElf_Phdr *phdr = gelf_getphdr (ebl->elf, i, &phdr_mem);
+      if (phdr == NULL)
+	continue;
+
+      if (phdr->p_type == PT_LOAD)
+	{
+	  struct loaded_segment *newp = xmalloc (sizeof (*newp));
+	  newp->from = phdr->p_vaddr;
+	  newp->to = phdr->p_vaddr + phdr->p_memsz;
+	  newp->read_only = (phdr->p_flags & PF_W) == 0;
+	  newp->next = *loadedp;
+	  *loadedp = newp;
+	}
+      else if (phdr->p_type == PT_DYNAMIC)
+	{
+	  Elf_Scn *dynscn = gelf_offscn (ebl->elf, phdr->p_offset);
+	  GElf_Shdr dynshdr_mem;
+	  GElf_Shdr *dynshdr = gelf_getshdr (dynscn, &dynshdr_mem);
+	  Elf_Data *dyndata = elf_getdata (dynscn, NULL);
+	  if (dynshdr != NULL && dynshdr->sh_type == SHT_DYNAMIC
+	      && dyndata != NULL && dynshdr->sh_entsize != 0)
+	    for (size_t j = 0; j < dynshdr->sh_size / dynshdr->sh_entsize; ++j)
+	      {
+		GElf_Dyn dyn_mem;
+		GElf_Dyn *dyn = gelf_getdyn (dyndata, j, &dyn_mem);
+		if (dyn != NULL
+		    && (dyn->d_tag == DT_TEXTREL
+			|| (dyn->d_tag == DT_FLAGS
+			    && (dyn->d_un.d_val & DF_TEXTREL) != 0)))
+		  {
+		    textrel = true;
+		    break;
+		  }
+	      }
+	}
+    }
+
+  /* A quick test which can be easily done here (although it is a bit
+     out of place): the text relocation flag makes only sense if there
+     is a segment which is not writable.  */
+  if (textrel)
+    {
+      struct loaded_segment *seg = *loadedp;
+      while (seg != NULL && !seg->read_only)
+	seg = seg->next;
+      if (seg == NULL)
+	ERROR (gettext ("\
+text relocation flag set but there is no read-only segment\n"));
+    }
+
+  return reldyn;
+}
+
+
+enum load_state
+  {
+    state_undecided,
+    state_loaded,
+    state_unloaded,
+    state_error
+  };
+
+
+static void
+check_one_reloc (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *relshdr, int idx,
+		 size_t cnt, const GElf_Shdr *symshdr, Elf_Data *symdata,
+		 GElf_Addr r_offset, GElf_Xword r_info,
+		 const GElf_Shdr *destshdr, bool reldyn,
+		 struct loaded_segment *loaded, enum load_state *statep)
+{
+  bool known_broken = gnuld;
+
+  if (!ebl_reloc_type_check (ebl, GELF_R_TYPE (r_info)))
+    ERROR (gettext ("section [%2d] '%s': relocation %zu: invalid type\n"),
+	   idx, section_name (ebl, idx), cnt);
+  else if (((ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
+	    /* The executable/DSO can contain relocation sections with
+	       all the relocations the linker has applied.  Those sections
+	       are marked non-loaded, though.  */
+	    || (relshdr->sh_flags & SHF_ALLOC) != 0)
+	   && !ebl_reloc_valid_use (ebl, GELF_R_TYPE (r_info)))
+    ERROR (gettext ("\
+section [%2d] '%s': relocation %zu: relocation type invalid for the file type\n"),
+	   idx, section_name (ebl, idx), cnt);
+
+  if (symshdr != NULL
+      && ((GELF_R_SYM (r_info) + 1)
+	  * gelf_fsize (ebl->elf, ELF_T_SYM, 1, EV_CURRENT)
+	  > symshdr->sh_size))
+    ERROR (gettext ("\
+section [%2d] '%s': relocation %zu: invalid symbol index\n"),
+	   idx, section_name (ebl, idx), cnt);
+
+  /* No more tests if this is a no-op relocation.  */
+  if (ebl_none_reloc_p (ebl, GELF_R_TYPE (r_info)))
+    return;
+
+  if (ebl_gotpc_reloc_check (ebl, GELF_R_TYPE (r_info)))
+    {
+      const char *name;
+      char buf[64];
+      GElf_Sym sym_mem;
+      GElf_Sym *sym = gelf_getsym (symdata, GELF_R_SYM (r_info), &sym_mem);
+      if (sym != NULL
+	  /* Get the name for the symbol.  */
+	  && (name = elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name))
+	  && strcmp (name, "_GLOBAL_OFFSET_TABLE_") !=0 )
+	ERROR (gettext ("\
+section [%2d] '%s': relocation %zu: only symbol '_GLOBAL_OFFSET_TABLE_' can be used with %s\n"),
+	       idx, section_name (ebl, idx), cnt,
+	       ebl_reloc_type_name (ebl, GELF_R_SYM (r_info),
+				    buf, sizeof (buf)));
+    }
+
+  if (reldyn)
+    {
+      // XXX TODO Check .rel.dyn section addresses.
+    }
+  else if (!known_broken)
+    {
+      if (destshdr != NULL
+	  && GELF_R_TYPE (r_info) != 0
+	  && (r_offset - (ehdr->e_type == ET_REL ? 0
+			  : destshdr->sh_addr)) >= destshdr->sh_size)
+	ERROR (gettext ("\
+section [%2d] '%s': relocation %zu: offset out of bounds\n"),
+	       idx, section_name (ebl, idx), cnt);
+    }
+
+  GElf_Sym sym_mem;
+  GElf_Sym *sym = gelf_getsym (symdata, GELF_R_SYM (r_info), &sym_mem);
+
+  if (ebl_copy_reloc_p (ebl, GELF_R_TYPE (r_info))
+      /* Make sure the referenced symbol is an object or unspecified.  */
+      && sym != NULL
+      && GELF_ST_TYPE (sym->st_info) != STT_NOTYPE
+      && GELF_ST_TYPE (sym->st_info) != STT_OBJECT)
+    {
+      char buf[64];
+      ERROR (gettext ("section [%2d] '%s': relocation %zu: copy relocation against symbol of type %s\n"),
+	     idx, section_name (ebl, idx), cnt,
+	     ebl_symbol_type_name (ebl, GELF_ST_TYPE (sym->st_info),
+				   buf, sizeof (buf)));
+    }
+
+  if ((ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
+      || (relshdr->sh_flags & SHF_ALLOC) != 0)
+    {
+      bool in_loaded_seg = false;
+      while (loaded != NULL)
+	{
+	  if (r_offset < loaded->to
+	      && r_offset + (sym == NULL ? 0 : sym->st_size) >= loaded->from)
+	    {
+	      /* The symbol is in this segment.  */
+	      if  (loaded->read_only)
+		{
+		  if (textrel)
+		    needed_textrel = true;
+		  else
+		    ERROR (gettext ("section [%2d] '%s': relocation %zu: read-only section modified but text relocation flag not set\n"),
+			   idx, section_name (ebl, idx), cnt);
+		}
+
+	      in_loaded_seg = true;
+	    }
+
+	  loaded = loaded->next;
+	}
+
+      if (*statep == state_undecided)
+	*statep = in_loaded_seg ? state_loaded : state_unloaded;
+      else if ((*statep == state_unloaded && in_loaded_seg)
+	       || (*statep == state_loaded && !in_loaded_seg))
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': relocations are against loaded and unloaded data\n"),
+		 idx, section_name (ebl, idx));
+	  *statep = state_error;
+	}
+    }
+}
+
+
+static void
+check_rela (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
+{
+  Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL)
+    {
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  /* Check the fields of the section header.  */
+  GElf_Shdr destshdr_mem;
+  GElf_Shdr *destshdr = NULL;
+  struct loaded_segment *loaded = NULL;
+  bool reldyn = check_reloc_shdr (ebl, ehdr, shdr, idx, ELF_T_RELA, &destshdr,
+				  &destshdr_mem, &loaded);
+
+  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
+  GElf_Shdr symshdr_mem;
+  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
+  Elf_Data *symdata = elf_getdata (symscn, NULL);
+  enum load_state state = state_undecided;
+
+  size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_RELA, 1, EV_CURRENT);
+  for (size_t cnt = 0; cnt < shdr->sh_size / sh_entsize; ++cnt)
+    {
+      GElf_Rela rela_mem;
+      GElf_Rela *rela = gelf_getrela (data, cnt, &rela_mem);
+      if (rela == NULL)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': cannot get relocation %zu: %s\n"),
+		 idx, section_name (ebl, idx), cnt, elf_errmsg (-1));
+	  continue;
+	}
+
+      check_one_reloc (ebl, ehdr, shdr, idx, cnt, symshdr, symdata,
+		       rela->r_offset, rela->r_info, destshdr, reldyn, loaded,
+		       &state);
+    }
+
+  while (loaded != NULL)
+    {
+      struct loaded_segment *old = loaded;
+      loaded = loaded->next;
+      free (old);
+    }
+}
+
+
+static void
+check_rel (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
+{
+  Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL)
+    {
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  /* Check the fields of the section header.  */
+  GElf_Shdr destshdr_mem;
+  GElf_Shdr *destshdr = NULL;
+  struct loaded_segment *loaded = NULL;
+  bool reldyn = check_reloc_shdr (ebl, ehdr, shdr, idx, ELF_T_REL, &destshdr,
+				  &destshdr_mem, &loaded);
+
+  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
+  GElf_Shdr symshdr_mem;
+  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
+  Elf_Data *symdata = elf_getdata (symscn, NULL);
+  enum load_state state = state_undecided;
+
+  size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_REL, 1, EV_CURRENT);
+  for (size_t cnt = 0; cnt < shdr->sh_size / sh_entsize; ++cnt)
+    {
+      GElf_Rel rel_mem;
+      GElf_Rel *rel = gelf_getrel (data, cnt, &rel_mem);
+      if (rel == NULL)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': cannot get relocation %zu: %s\n"),
+		 idx, section_name (ebl, idx), cnt, elf_errmsg (-1));
+	  continue;
+	}
+
+      check_one_reloc (ebl, ehdr, shdr, idx, cnt, symshdr, symdata,
+		       rel->r_offset, rel->r_info, destshdr, reldyn, loaded,
+		       &state);
+    }
+
+  while (loaded != NULL)
+    {
+      struct loaded_segment *old = loaded;
+      loaded = loaded->next;
+      free (old);
+    }
+}
+
+
+/* Number of dynamic sections.  */
+static int ndynamic;
+
+
+static void
+check_dynamic (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
+{
+  Elf_Data *data;
+  GElf_Shdr strshdr_mem;
+  GElf_Shdr *strshdr;
+  size_t cnt;
+  static const bool dependencies[DT_NUM][DT_NUM] =
+    {
+      [DT_NEEDED] = { [DT_STRTAB] = true },
+      [DT_PLTRELSZ] = { [DT_JMPREL] = true },
+      [DT_HASH] = { [DT_SYMTAB] = true },
+      [DT_STRTAB] = { [DT_STRSZ] = true },
+      [DT_SYMTAB] = { [DT_STRTAB] = true, [DT_SYMENT] = true },
+      [DT_RELA] = { [DT_RELASZ] = true, [DT_RELAENT] = true },
+      [DT_RELASZ] = { [DT_RELA] = true },
+      [DT_RELAENT] = { [DT_RELA] = true },
+      [DT_STRSZ] = { [DT_STRTAB] = true },
+      [DT_SYMENT] = { [DT_SYMTAB] = true },
+      [DT_SONAME] = { [DT_STRTAB] = true },
+      [DT_RPATH] = { [DT_STRTAB] = true },
+      [DT_REL] = { [DT_RELSZ] = true, [DT_RELENT] = true },
+      [DT_RELSZ] = { [DT_REL] = true },
+      [DT_RELENT] = { [DT_REL] = true },
+      [DT_JMPREL] = { [DT_PLTRELSZ] = true, [DT_PLTREL] = true },
+      [DT_RUNPATH] = { [DT_STRTAB] = true },
+      [DT_PLTREL] = { [DT_JMPREL] = true },
+    };
+  bool has_dt[DT_NUM];
+  bool has_val_dt[DT_VALNUM];
+  bool has_addr_dt[DT_ADDRNUM];
+  static const bool level2[DT_NUM] =
+    {
+      [DT_RPATH] = true,
+      [DT_SYMBOLIC] = true,
+      [DT_TEXTREL] = true,
+      [DT_BIND_NOW] = true
+    };
+  static const bool mandatory[DT_NUM] =
+    {
+      [DT_NULL] = true,
+      [DT_STRTAB] = true,
+      [DT_SYMTAB] = true,
+      [DT_STRSZ] = true,
+      [DT_SYMENT] = true
+    };
+
+  memset (has_dt, '\0', sizeof (has_dt));
+  memset (has_val_dt, '\0', sizeof (has_val_dt));
+  memset (has_addr_dt, '\0', sizeof (has_addr_dt));
+
+  if (++ndynamic == 2)
+    ERROR (gettext ("more than one dynamic section present\n"));
+
+  data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL)
+    {
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  strshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link), &strshdr_mem);
+  if (strshdr != NULL && strshdr->sh_type != SHT_STRTAB)
+    ERROR (gettext ("\
+section [%2d] '%s': referenced as string table for section [%2d] '%s' but type is not SHT_STRTAB\n"),
+	   shdr->sh_link, section_name (ebl, shdr->sh_link),
+	   idx, section_name (ebl, idx));
+  else if (strshdr == NULL)
+    {
+      ERROR (gettext ("\
+section [%2d]: referenced as string table for section [%2d] '%s' but section link value is invalid\n"),
+	   shdr->sh_link, idx, section_name (ebl, idx));
+      return;
+    }
+
+  size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_DYN, 1, EV_CURRENT);
+  if (shdr->sh_entsize != sh_entsize)
+    ERROR (gettext ("\
+section [%2d] '%s': section entry size does not match ElfXX_Dyn\n"),
+	   idx, section_name (ebl, idx));
+
+  if (shdr->sh_info != 0)
+    ERROR (gettext ("section [%2d] '%s': sh_info not zero\n"),
+	   idx, section_name (ebl, idx));
+
+  bool non_null_warned = false;
+  for (cnt = 0; cnt < shdr->sh_size / sh_entsize; ++cnt)
+    {
+      GElf_Dyn dyn_mem;
+      GElf_Dyn *dyn = gelf_getdyn (data, cnt, &dyn_mem);
+      if (dyn == NULL)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': cannot get dynamic section entry %zu: %s\n"),
+		 idx, section_name (ebl, idx), cnt, elf_errmsg (-1));
+	  continue;
+	}
+
+      if (has_dt[DT_NULL] && dyn->d_tag != DT_NULL && ! non_null_warned)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': non-DT_NULL entries follow DT_NULL entry\n"),
+		 idx, section_name (ebl, idx));
+	  non_null_warned = true;
+	}
+
+      if (!ebl_dynamic_tag_check (ebl, dyn->d_tag))
+	ERROR (gettext ("section [%2d] '%s': entry %zu: unknown tag\n"),
+	       idx, section_name (ebl, idx), cnt);
+
+      if (dyn->d_tag >= 0 && dyn->d_tag < DT_NUM)
+	{
+	  if (has_dt[dyn->d_tag]
+	      && dyn->d_tag != DT_NEEDED
+	      && dyn->d_tag != DT_NULL
+	      && dyn->d_tag != DT_POSFLAG_1)
+	    {
+	      char buf[50];
+	      ERROR (gettext ("\
+section [%2d] '%s': entry %zu: more than one entry with tag %s\n"),
+		     idx, section_name (ebl, idx), cnt,
+		     ebl_dynamic_tag_name (ebl, dyn->d_tag,
+					   buf, sizeof (buf)));
+	    }
+
+	  if (be_strict && level2[dyn->d_tag])
+	    {
+	      char buf[50];
+	      ERROR (gettext ("\
+section [%2d] '%s': entry %zu: level 2 tag %s used\n"),
+		     idx, section_name (ebl, idx), cnt,
+		     ebl_dynamic_tag_name (ebl, dyn->d_tag,
+					   buf, sizeof (buf)));
+	    }
+
+	  has_dt[dyn->d_tag] = true;
+	}
+      else if (dyn->d_tag >= 0 && dyn->d_tag <= DT_VALRNGHI
+	       && DT_VALTAGIDX (dyn->d_tag) < DT_VALNUM)
+	has_val_dt[DT_VALTAGIDX (dyn->d_tag)] = true;
+      else if (dyn->d_tag >= 0 && dyn->d_tag <= DT_ADDRRNGHI
+	       && DT_ADDRTAGIDX (dyn->d_tag) < DT_ADDRNUM)
+	has_addr_dt[DT_ADDRTAGIDX (dyn->d_tag)] = true;
+
+      if (dyn->d_tag == DT_PLTREL && dyn->d_un.d_val != DT_REL
+	  && dyn->d_un.d_val != DT_RELA)
+	ERROR (gettext ("\
+section [%2d] '%s': entry %zu: DT_PLTREL value must be DT_REL or DT_RELA\n"),
+	       idx, section_name (ebl, idx), cnt);
+
+      /* Check that addresses for entries are in loaded segments.  */
+      switch (dyn->d_tag)
+	{
+	  size_t n;
+	case DT_STRTAB:
+	  /* We require the referenced section is the same as the one
+	     specified in sh_link.  */
+	  if (strshdr->sh_addr != dyn->d_un.d_val)
+	    {
+	      ERROR (gettext ("\
+section [%2d] '%s': entry %zu: pointer does not match address of section [%2d] '%s' referenced by sh_link\n"),
+		     idx, section_name (ebl, idx), cnt,
+		     shdr->sh_link, section_name (ebl, shdr->sh_link));
+	      break;
+	    }
+	  goto check_addr;
+
+	default:
+	  if (dyn->d_tag < DT_ADDRRNGLO || dyn->d_tag > DT_ADDRRNGHI)
+	    /* Value is no pointer.  */
+	    break;
+	  FALLTHROUGH;
+
+	case DT_AUXILIARY:
+	case DT_FILTER:
+	case DT_FINI:
+	case DT_FINI_ARRAY:
+	case DT_HASH:
+	case DT_INIT:
+	case DT_INIT_ARRAY:
+	case DT_JMPREL:
+	case DT_PLTGOT:
+	case DT_REL:
+	case DT_RELA:
+	case DT_SYMBOLIC:
+	case DT_SYMTAB:
+	case DT_VERDEF:
+	case DT_VERNEED:
+	case DT_VERSYM:
+	check_addr:
+	  for (n = 0; n < phnum; ++n)
+	    {
+	      GElf_Phdr phdr_mem;
+	      GElf_Phdr *phdr = gelf_getphdr (ebl->elf, n, &phdr_mem);
+	      if (phdr != NULL && phdr->p_type == PT_LOAD
+		  && phdr->p_vaddr <= dyn->d_un.d_ptr
+		  && phdr->p_vaddr + phdr->p_memsz > dyn->d_un.d_ptr)
+		break;
+	    }
+	  if (unlikely (n >= phnum))
+	    {
+	      char buf[50];
+	      ERROR (gettext ("\
+section [%2d] '%s': entry %zu: %s value must point into loaded segment\n"),
+		     idx, section_name (ebl, idx), cnt,
+		     ebl_dynamic_tag_name (ebl, dyn->d_tag, buf,
+					   sizeof (buf)));
+	    }
+	  break;
+
+	case DT_NEEDED:
+	case DT_RPATH:
+	case DT_RUNPATH:
+	case DT_SONAME:
+	  if (dyn->d_un.d_ptr >= strshdr->sh_size)
+	    {
+	      char buf[50];
+	      ERROR (gettext ("\
+section [%2d] '%s': entry %zu: %s value must be valid offset in section [%2d] '%s'\n"),
+		     idx, section_name (ebl, idx), cnt,
+		     ebl_dynamic_tag_name (ebl, dyn->d_tag, buf,
+					   sizeof (buf)),
+		     shdr->sh_link, section_name (ebl, shdr->sh_link));
+	    }
+	  break;
+	}
+    }
+
+  for (cnt = 1; cnt < DT_NUM; ++cnt)
+    if (has_dt[cnt])
+      {
+	for (int inner = 0; inner < DT_NUM; ++inner)
+	  if (dependencies[cnt][inner] && ! has_dt[inner])
+	    {
+	      char buf1[50];
+	      char buf2[50];
+
+	      ERROR (gettext ("\
+section [%2d] '%s': contains %s entry but not %s\n"),
+		     idx, section_name (ebl, idx),
+		     ebl_dynamic_tag_name (ebl, cnt, buf1, sizeof (buf1)),
+		     ebl_dynamic_tag_name (ebl, inner, buf2, sizeof (buf2)));
+	    }
+      }
+    else
+      {
+	if (mandatory[cnt])
+	  {
+	    char buf[50];
+	    ERROR (gettext ("\
+section [%2d] '%s': mandatory tag %s not present\n"),
+		   idx, section_name (ebl, idx),
+		   ebl_dynamic_tag_name (ebl, cnt, buf, sizeof (buf)));
+	  }
+      }
+
+  /* Make sure we have an hash table.  */
+  if (!has_dt[DT_HASH] && !has_addr_dt[DT_ADDRTAGIDX (DT_GNU_HASH)])
+    ERROR (gettext ("\
+section [%2d] '%s': no hash section present\n"),
+	   idx, section_name (ebl, idx));
+
+  /* The GNU-style hash table also needs a symbol table.  */
+  if (!has_dt[DT_HASH] && has_addr_dt[DT_ADDRTAGIDX (DT_GNU_HASH)]
+      && !has_dt[DT_SYMTAB])
+    ERROR (gettext ("\
+section [%2d] '%s': contains %s entry but not %s\n"),
+	   idx, section_name (ebl, idx),
+	   "DT_GNU_HASH", "DT_SYMTAB");
+
+  /* Check the rel/rela tags.  At least one group must be available.  */
+  if ((has_dt[DT_RELA] || has_dt[DT_RELASZ] || has_dt[DT_RELAENT])
+      && (!has_dt[DT_RELA] || !has_dt[DT_RELASZ] || !has_dt[DT_RELAENT]))
+    ERROR (gettext ("\
+section [%2d] '%s': not all of %s, %s, and %s are present\n"),
+	   idx, section_name (ebl, idx),
+	   "DT_RELA", "DT_RELASZ", "DT_RELAENT");
+
+  if ((has_dt[DT_REL] || has_dt[DT_RELSZ] || has_dt[DT_RELENT])
+      && (!has_dt[DT_REL] || !has_dt[DT_RELSZ] || !has_dt[DT_RELENT]))
+    ERROR (gettext ("\
+section [%2d] '%s': not all of %s, %s, and %s are present\n"),
+	   idx, section_name (ebl, idx),
+	   "DT_REL", "DT_RELSZ", "DT_RELENT");
+
+  /* Check that all prelink sections are present if any of them is.  */
+  if (has_val_dt[DT_VALTAGIDX (DT_GNU_PRELINKED)]
+      || has_val_dt[DT_VALTAGIDX (DT_CHECKSUM)])
+    {
+      if (!has_val_dt[DT_VALTAGIDX (DT_GNU_PRELINKED)])
+	ERROR (gettext ("\
+section [%2d] '%s': %s tag missing in DSO marked during prelinking\n"),
+	       idx, section_name (ebl, idx), "DT_GNU_PRELINKED");
+      if (!has_val_dt[DT_VALTAGIDX (DT_CHECKSUM)])
+	ERROR (gettext ("\
+section [%2d] '%s': %s tag missing in DSO marked during prelinking\n"),
+	       idx, section_name (ebl, idx), "DT_CHECKSUM");
+
+      /* Only DSOs can be marked like this.  */
+      if (ehdr->e_type != ET_DYN)
+	ERROR (gettext ("\
+section [%2d] '%s': non-DSO file marked as dependency during prelink\n"),
+	       idx, section_name (ebl, idx));
+    }
+
+  if (has_val_dt[DT_VALTAGIDX (DT_GNU_CONFLICTSZ)]
+      || has_val_dt[DT_VALTAGIDX (DT_GNU_LIBLISTSZ)]
+      || has_addr_dt[DT_ADDRTAGIDX (DT_GNU_CONFLICT)]
+      || has_addr_dt[DT_ADDRTAGIDX (DT_GNU_LIBLIST)])
+    {
+      if (!has_val_dt[DT_VALTAGIDX (DT_GNU_CONFLICTSZ)])
+	ERROR (gettext ("\
+section [%2d] '%s': %s tag missing in prelinked executable\n"),
+	       idx, section_name (ebl, idx), "DT_GNU_CONFLICTSZ");
+      if (!has_val_dt[DT_VALTAGIDX (DT_GNU_LIBLISTSZ)])
+	ERROR (gettext ("\
+section [%2d] '%s': %s tag missing in prelinked executable\n"),
+	       idx, section_name (ebl, idx), "DT_GNU_LIBLISTSZ");
+      if (!has_addr_dt[DT_ADDRTAGIDX (DT_GNU_CONFLICT)])
+	ERROR (gettext ("\
+section [%2d] '%s': %s tag missing in prelinked executable\n"),
+	       idx, section_name (ebl, idx), "DT_GNU_CONFLICT");
+      if (!has_addr_dt[DT_ADDRTAGIDX (DT_GNU_LIBLIST)])
+	ERROR (gettext ("\
+section [%2d] '%s': %s tag missing in prelinked executable\n"),
+	       idx, section_name (ebl, idx), "DT_GNU_LIBLIST");
+    }
+}
+
+
+static void
+check_symtab_shndx (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
+{
+  if (ehdr->e_type != ET_REL)
+    {
+      ERROR (gettext ("\
+section [%2d] '%s': only relocatable files can have extended section index\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
+  GElf_Shdr symshdr_mem;
+  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
+  if (symshdr != NULL && symshdr->sh_type != SHT_SYMTAB)
+    ERROR (gettext ("\
+section [%2d] '%s': extended section index section not for symbol table\n"),
+	   idx, section_name (ebl, idx));
+  else if (symshdr == NULL)
+    ERROR (gettext ("\
+section [%2d] '%s': sh_link extended section index [%2d] is invalid\n"),
+	   idx, section_name (ebl, idx), shdr->sh_link);
+  Elf_Data *symdata = elf_getdata (symscn, NULL);
+  if (symdata == NULL)
+    ERROR (gettext ("cannot get data for symbol section\n"));
+
+  if (shdr->sh_entsize != sizeof (Elf32_Word))
+    ERROR (gettext ("\
+section [%2d] '%s': entry size does not match Elf32_Word\n"),
+	   idx, section_name (ebl, idx));
+
+  if (symshdr != NULL
+      && shdr->sh_entsize != 0
+      && symshdr->sh_entsize != 0
+      && (shdr->sh_size / shdr->sh_entsize
+	  < symshdr->sh_size / symshdr->sh_entsize))
+    ERROR (gettext ("\
+section [%2d] '%s': extended index table too small for symbol table\n"),
+	   idx, section_name (ebl, idx));
+
+  if (shdr->sh_info != 0)
+    ERROR (gettext ("section [%2d] '%s': sh_info not zero\n"),
+	   idx, section_name (ebl, idx));
+
+  for (size_t cnt = idx + 1; cnt < shnum; ++cnt)
+    {
+      GElf_Shdr rshdr_mem;
+      GElf_Shdr *rshdr = gelf_getshdr (elf_getscn (ebl->elf, cnt), &rshdr_mem);
+      if (rshdr != NULL && rshdr->sh_type == SHT_SYMTAB_SHNDX
+	  && rshdr->sh_link == shdr->sh_link)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': extended section index in section [%2zu] '%s' refers to same symbol table\n"),
+		 idx, section_name (ebl, idx),
+		 cnt, section_name (ebl, cnt));
+	  break;
+	}
+    }
+
+  Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL || data->d_buf == NULL)
+    {
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  if (data->d_size < sizeof (Elf32_Word)
+      || *((Elf32_Word *) data->d_buf) != 0)
+    ERROR (gettext ("symbol 0 should have zero extended section index\n"));
+
+  for (size_t cnt = 1; cnt < data->d_size / sizeof (Elf32_Word); ++cnt)
+    {
+      Elf32_Word xndx = ((Elf32_Word *) data->d_buf)[cnt];
+
+      if (xndx != 0)
+	{
+	  GElf_Sym sym_data;
+	  GElf_Sym *sym = gelf_getsym (symdata, cnt, &sym_data);
+	  if (sym == NULL)
+	    {
+	      ERROR (gettext ("cannot get data for symbol %zu\n"), cnt);
+	      continue;
+	    }
+
+	  if (sym->st_shndx != SHN_XINDEX)
+	    ERROR (gettext ("\
+extended section index is %" PRIu32 " but symbol index is not XINDEX\n"),
+		   (uint32_t) xndx);
+	}
+    }
+}
+
+
+static void
+check_sysv_hash (Ebl *ebl, GElf_Shdr *shdr, Elf_Data *data, int idx,
+		 GElf_Shdr *symshdr)
+{
+  Elf32_Word nbucket = ((Elf32_Word *) data->d_buf)[0];
+  Elf32_Word nchain = ((Elf32_Word *) data->d_buf)[1];
+
+  if (shdr->sh_size < (2 + nbucket + nchain) * sizeof (Elf32_Word))
+    {
+      ERROR (gettext ("\
+section [%2d] '%s': hash table section is too small (is %ld, expected %ld)\n"),
+	     idx, section_name (ebl, idx), (long int) shdr->sh_size,
+	     (long int) ((2 + nbucket + nchain) * sizeof (Elf32_Word)));
+      return;
+    }
+
+  size_t maxidx = nchain;
+
+  if (symshdr != NULL && symshdr->sh_entsize != 0)
+    {
+      size_t symsize = symshdr->sh_size / symshdr->sh_entsize;
+
+      if (nchain > symshdr->sh_size / symshdr->sh_entsize)
+	ERROR (gettext ("section [%2d] '%s': chain array too large\n"),
+	       idx, section_name (ebl, idx));
+
+      maxidx = symsize;
+    }
+
+  Elf32_Word *buf = (Elf32_Word *) data->d_buf;
+  Elf32_Word *end = (Elf32_Word *) ((char *) data->d_buf + shdr->sh_size);
+  size_t cnt;
+  for (cnt = 2; cnt < 2 + nbucket; ++cnt)
+    {
+      if (buf + cnt >= end)
+	break;
+      else if (buf[cnt] >= maxidx)
+      ERROR (gettext ("\
+section [%2d] '%s': hash bucket reference %zu out of bounds\n"),
+	     idx, section_name (ebl, idx), cnt - 2);
+    }
+
+  for (; cnt < 2 + nbucket + nchain; ++cnt)
+    {
+      if (buf + cnt >= end)
+	break;
+      else if (buf[cnt] >= maxidx)
+      ERROR (gettext ("\
+section [%2d] '%s': hash chain reference %zu out of bounds\n"),
+	     idx, section_name (ebl, idx), cnt - 2 - nbucket);
+    }
+}
+
+
+static void
+check_sysv_hash64 (Ebl *ebl, GElf_Shdr *shdr, Elf_Data *data, int idx,
+		 GElf_Shdr *symshdr)
+{
+  Elf64_Xword nbucket = ((Elf64_Xword *) data->d_buf)[0];
+  Elf64_Xword nchain = ((Elf64_Xword *) data->d_buf)[1];
+
+  if (shdr->sh_size < (2 + nbucket + nchain) * sizeof (Elf64_Xword))
+    {
+      ERROR (gettext ("\
+section [%2d] '%s': hash table section is too small (is %ld, expected %ld)\n"),
+	     idx, section_name (ebl, idx), (long int) shdr->sh_size,
+	     (long int) ((2 + nbucket + nchain) * sizeof (Elf64_Xword)));
+      return;
+    }
+
+  size_t maxidx = nchain;
+
+  if (symshdr != NULL && symshdr->sh_entsize != 0)
+    {
+      size_t symsize = symshdr->sh_size / symshdr->sh_entsize;
+
+      if (nchain > symshdr->sh_size / symshdr->sh_entsize)
+	ERROR (gettext ("section [%2d] '%s': chain array too large\n"),
+	       idx, section_name (ebl, idx));
+
+      maxidx = symsize;
+    }
+
+  Elf64_Xword *buf = (Elf64_Xword *) data->d_buf;
+  Elf64_Xword *end = (Elf64_Xword *) ((char *) data->d_buf + shdr->sh_size);
+  size_t cnt;
+  for (cnt = 2; cnt < 2 + nbucket; ++cnt)
+    {
+      if (buf + cnt >= end)
+	break;
+      else if (buf[cnt] >= maxidx)
+      ERROR (gettext ("\
+section [%2d] '%s': hash bucket reference %zu out of bounds\n"),
+	     idx, section_name (ebl, idx), cnt - 2);
+    }
+
+  for (; cnt < 2 + nbucket + nchain; ++cnt)
+    {
+      if (buf + cnt >= end)
+	break;
+      else if (buf[cnt] >= maxidx)
+      ERROR (gettext ("\
+section [%2d] '%s': hash chain reference %" PRIu64 " out of bounds\n"),
+	       idx, section_name (ebl, idx), (uint64_t) cnt - 2 - nbucket);
+    }
+}
+
+
+static void
+check_gnu_hash (Ebl *ebl, GElf_Shdr *shdr, Elf_Data *data, int idx,
+		GElf_Shdr *symshdr)
+{
+  if (data->d_size < 4 * sizeof (Elf32_Word))
+    {
+      ERROR (gettext ("\
+section [%2d] '%s': not enough data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  Elf32_Word nbuckets = ((Elf32_Word *) data->d_buf)[0];
+  Elf32_Word symbias = ((Elf32_Word *) data->d_buf)[1];
+  Elf32_Word bitmask_words = ((Elf32_Word *) data->d_buf)[2];
+
+  if (bitmask_words == 0 || !powerof2 (bitmask_words))
+    {
+      ERROR (gettext ("\
+section [%2d] '%s': bitmask size zero or not power of 2: %u\n"),
+	     idx, section_name (ebl, idx), bitmask_words);
+      return;
+    }
+
+  size_t bitmask_idxmask = bitmask_words - 1;
+  if (gelf_getclass (ebl->elf) == ELFCLASS64)
+    bitmask_words *= 2;
+  Elf32_Word shift = ((Elf32_Word *) data->d_buf)[3];
+
+  /* Is there still room for the sym chain?
+     Use uint64_t calculation to prevent 32bit overlow.  */
+  uint64_t used_buf = (4ULL + bitmask_words + nbuckets) * sizeof (Elf32_Word);
+  if (used_buf > data->d_size)
+    {
+      ERROR (gettext ("\
+section [%2d] '%s': hash table section is too small (is %ld, expected at least %ld)\n"),
+	     idx, section_name (ebl, idx), (long int) shdr->sh_size,
+	     (long int) used_buf);
+      return;
+    }
+
+  if (shift > 31)
+    {
+      ERROR (gettext ("\
+section [%2d] '%s': 2nd hash function shift too big: %u\n"),
+	     idx, section_name (ebl, idx), shift);
+      return;
+    }
+
+  size_t maxidx = shdr->sh_size / sizeof (Elf32_Word) - (4 + bitmask_words
+							 + nbuckets);
+
+  if (symshdr != NULL && symshdr->sh_entsize != 0)
+    maxidx = MIN (maxidx, symshdr->sh_size / symshdr->sh_entsize);
+
+  /* We need the symbol section data.  */
+  Elf_Data *symdata = elf_getdata (elf_getscn (ebl->elf, shdr->sh_link), NULL);
+
+  union
+  {
+    Elf32_Word *p32;
+    Elf64_Xword *p64;
+  } bitmask = { .p32 = &((Elf32_Word *) data->d_buf)[4] },
+      collected = { .p32 = xcalloc (bitmask_words, sizeof (Elf32_Word)) };
+
+  size_t classbits = gelf_getclass (ebl->elf) == ELFCLASS32 ? 32 : 64;
+
+  size_t cnt;
+  for (cnt = 4 + bitmask_words; cnt < 4 + bitmask_words + nbuckets; ++cnt)
+    {
+      Elf32_Word symidx = ((Elf32_Word *) data->d_buf)[cnt];
+
+      if (symidx == 0)
+	continue;
+
+      if (symidx < symbias)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': hash chain for bucket %zu lower than symbol index bias\n"),
+		 idx, section_name (ebl, idx), cnt - (4 + bitmask_words));
+	  continue;
+	}
+
+      while (symidx - symbias < maxidx)
+	{
+	  Elf32_Word chainhash = ((Elf32_Word *) data->d_buf)[4
+							      + bitmask_words
+							      + nbuckets
+							      + symidx
+							      - symbias];
+
+	  if (symdata != NULL)
+	    {
+	      /* Check that the referenced symbol is not undefined.  */
+	      GElf_Sym sym_mem;
+	      GElf_Sym *sym = gelf_getsym (symdata, symidx, &sym_mem);
+	      if (sym != NULL && sym->st_shndx == SHN_UNDEF
+		  && GELF_ST_TYPE (sym->st_info) != STT_FUNC)
+		ERROR (gettext ("\
+section [%2d] '%s': symbol %u referenced in chain for bucket %zu is undefined\n"),
+		       idx, section_name (ebl, idx), symidx,
+		       cnt - (4 + bitmask_words));
+
+	      const char *symname = (sym != NULL
+				     ? elf_strptr (ebl->elf, symshdr->sh_link,
+						   sym->st_name)
+				     : NULL);
+	      if (symname != NULL)
+		{
+		  Elf32_Word hval = elf_gnu_hash (symname);
+		  if ((hval & ~1u) != (chainhash & ~1u))
+		    ERROR (gettext ("\
+section [%2d] '%s': hash value for symbol %u in chain for bucket %zu wrong\n"),
+			   idx, section_name (ebl, idx), symidx,
+			   cnt - (4 + bitmask_words));
+
+		  /* Set the bits in the bitmask.  */
+		  size_t maskidx = (hval / classbits) & bitmask_idxmask;
+		  if (maskidx >= bitmask_words)
+		    {
+		      ERROR (gettext ("\
+section [%2d] '%s': mask index for symbol %u in chain for bucket %zu wrong\n"),
+			     idx, section_name (ebl, idx), symidx,
+			     cnt - (4 + bitmask_words));
+		      return;
+		    }
+		  if (classbits == 32)
+		    {
+		      collected.p32[maskidx]
+			|= UINT32_C (1) << (hval & (classbits - 1));
+		      collected.p32[maskidx]
+			|= UINT32_C (1) << ((hval >> shift) & (classbits - 1));
+		    }
+		  else
+		    {
+		      collected.p64[maskidx]
+			|= UINT64_C (1) << (hval & (classbits - 1));
+		      collected.p64[maskidx]
+			|= UINT64_C (1) << ((hval >> shift) & (classbits - 1));
+		    }
+		}
+	    }
+
+	  if ((chainhash & 1) != 0)
+	    break;
+
+	  ++symidx;
+	}
+
+      if (symidx - symbias >= maxidx)
+	ERROR (gettext ("\
+section [%2d] '%s': hash chain for bucket %zu out of bounds\n"),
+	       idx, section_name (ebl, idx), cnt - (4 + bitmask_words));
+      else if (symshdr != NULL && symshdr->sh_entsize != 0
+	       && symidx > symshdr->sh_size / symshdr->sh_entsize)
+	ERROR (gettext ("\
+section [%2d] '%s': symbol reference in chain for bucket %zu out of bounds\n"),
+	       idx, section_name (ebl, idx), cnt - (4 + bitmask_words));
+    }
+
+  if (memcmp (collected.p32, bitmask.p32, bitmask_words * sizeof (Elf32_Word)))
+    ERROR (gettext ("\
+section [%2d] '%s': bitmask does not match names in the hash table\n"),
+	   idx, section_name (ebl, idx));
+
+  free (collected.p32);
+}
+
+
+static void
+check_hash (int tag, Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
+{
+  if (ehdr->e_type == ET_REL)
+    {
+      ERROR (gettext ("\
+section [%2d] '%s': relocatable files cannot have hash tables\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL || data->d_buf == NULL)
+    {
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  GElf_Shdr symshdr_mem;
+  GElf_Shdr *symshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
+				     &symshdr_mem);
+  if (symshdr != NULL && symshdr->sh_type != SHT_DYNSYM)
+    ERROR (gettext ("\
+section [%2d] '%s': hash table not for dynamic symbol table\n"),
+	   idx, section_name (ebl, idx));
+  else if (symshdr == NULL)
+    ERROR (gettext ("\
+section [%2d] '%s': invalid sh_link symbol table section index [%2d]\n"),
+	   idx, section_name (ebl, idx), shdr->sh_link);
+
+  size_t expect_entsize = (tag == SHT_GNU_HASH
+			   ? (gelf_getclass (ebl->elf) == ELFCLASS32
+			      ? sizeof (Elf32_Word) : 0)
+			   : (size_t) ebl_sysvhash_entrysize (ebl));
+
+  if (shdr->sh_entsize != expect_entsize)
+    ERROR (gettext ("\
+section [%2d] '%s': hash table entry size incorrect\n"),
+	   idx, section_name (ebl, idx));
+
+  if ((shdr->sh_flags & SHF_ALLOC) == 0)
+    ERROR (gettext ("section [%2d] '%s': not marked to be allocated\n"),
+	   idx, section_name (ebl, idx));
+
+  if (shdr->sh_size < (tag == SHT_GNU_HASH ? 4 : 2) * (expect_entsize ?: 4))
+    {
+      ERROR (gettext ("\
+section [%2d] '%s': hash table has not even room for initial administrative entries\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  switch (tag)
+    {
+    case SHT_HASH:
+      if (ebl_sysvhash_entrysize (ebl) == sizeof (Elf64_Xword))
+	check_sysv_hash64 (ebl, shdr, data, idx, symshdr);
+      else
+	check_sysv_hash (ebl, shdr, data, idx, symshdr);
+      break;
+
+    case SHT_GNU_HASH:
+      check_gnu_hash (ebl, shdr, data, idx, symshdr);
+      break;
+
+    default:
+      assert (! "should not happen");
+    }
+}
+
+
+/* Compare content of both hash tables, it must be identical.  */
+static void
+compare_hash_gnu_hash (Ebl *ebl, GElf_Ehdr *ehdr, size_t hash_idx,
+		       size_t gnu_hash_idx)
+{
+  Elf_Scn *hash_scn = elf_getscn (ebl->elf, hash_idx);
+  Elf_Data *hash_data = elf_getdata (hash_scn, NULL);
+  GElf_Shdr hash_shdr_mem;
+  GElf_Shdr *hash_shdr = gelf_getshdr (hash_scn, &hash_shdr_mem);
+  Elf_Scn *gnu_hash_scn = elf_getscn (ebl->elf, gnu_hash_idx);
+  Elf_Data *gnu_hash_data = elf_getdata (gnu_hash_scn, NULL);
+  GElf_Shdr gnu_hash_shdr_mem;
+  GElf_Shdr *gnu_hash_shdr = gelf_getshdr (gnu_hash_scn, &gnu_hash_shdr_mem);
+
+  if (hash_shdr == NULL || gnu_hash_shdr == NULL
+      || hash_data == NULL || hash_data->d_buf == NULL
+      || gnu_hash_data == NULL || gnu_hash_data->d_buf == NULL)
+    /* None of these pointers should be NULL since we used the
+       sections already.  We are careful nonetheless.  */
+    return;
+
+  /* The link must point to the same symbol table.  */
+  if (hash_shdr->sh_link != gnu_hash_shdr->sh_link)
+    {
+      ERROR (gettext ("\
+sh_link in hash sections [%2zu] '%s' and [%2zu] '%s' not identical\n"),
+	     hash_idx, elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name),
+	     gnu_hash_idx,
+	     elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name));
+      return;
+    }
+
+  Elf_Scn *sym_scn = elf_getscn (ebl->elf, hash_shdr->sh_link);
+  Elf_Data *sym_data = elf_getdata (sym_scn, NULL);
+  GElf_Shdr sym_shdr_mem;
+  GElf_Shdr *sym_shdr = gelf_getshdr (sym_scn, &sym_shdr_mem);
+
+  if (sym_data == NULL || sym_data->d_buf == NULL
+      || sym_shdr == NULL || sym_shdr->sh_entsize == 0)
+    return;
+
+  const char *hash_name;
+  const char *gnu_hash_name;
+  hash_name  = elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name);
+  gnu_hash_name  = elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name);
+
+  if (gnu_hash_data->d_size < 4 * sizeof (Elf32_Word))
+    {
+      ERROR (gettext ("\
+hash section [%2zu] '%s' does not contain enough data\n"),
+	     gnu_hash_idx, gnu_hash_name);
+      return;
+    }
+
+  uint32_t nentries = sym_shdr->sh_size / sym_shdr->sh_entsize;
+  char *used = alloca (nentries);
+  memset (used, '\0', nentries);
+
+  /* First go over the GNU_HASH table and mark the entries as used.  */
+  const Elf32_Word *gnu_hasharr = (Elf32_Word *) gnu_hash_data->d_buf;
+  Elf32_Word gnu_nbucket = gnu_hasharr[0];
+  Elf32_Word gnu_symbias = gnu_hasharr[1];
+  const int bitmap_factor = ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 1 : 2;
+  const Elf32_Word *gnu_bucket = (gnu_hasharr
+				  + (4 + gnu_hasharr[2] * bitmap_factor));
+  const Elf32_Word *gnu_chain = gnu_bucket + gnu_hasharr[0];
+
+  if (gnu_hasharr[2] == 0)
+    {
+      ERROR (gettext ("\
+hash section [%2zu] '%s' has zero bit mask words\n"),
+	     gnu_hash_idx, gnu_hash_name);
+      return;
+    }
+
+  uint64_t used_buf = ((4ULL + gnu_hasharr[2] * bitmap_factor + gnu_nbucket)
+		       * sizeof (Elf32_Word));
+  uint32_t max_nsyms = (gnu_hash_data->d_size - used_buf) / sizeof (Elf32_Word);
+  if (used_buf > gnu_hash_data->d_size)
+    {
+      ERROR (gettext ("\
+hash section [%2zu] '%s' uses too much data\n"),
+	     gnu_hash_idx, gnu_hash_name);
+      return;
+    }
+
+  for (Elf32_Word cnt = 0; cnt < gnu_nbucket; ++cnt)
+    {
+      if (gnu_bucket[cnt] != STN_UNDEF)
+	{
+	  Elf32_Word symidx = gnu_bucket[cnt] - gnu_symbias;
+	  do
+	    {
+	      if (symidx >= max_nsyms || symidx + gnu_symbias >= nentries)
+		{
+		  ERROR (gettext ("\
+hash section [%2zu] '%s' invalid symbol index %" PRIu32 " (max_nsyms: %" PRIu32 ", nentries: %" PRIu32 "\n"),
+			 gnu_hash_idx, gnu_hash_name, symidx, max_nsyms, nentries);
+		  return;
+		}
+	      used[symidx + gnu_symbias] |= 1;
+	    }
+	  while ((gnu_chain[symidx++] & 1u) == 0);
+	}
+    }
+
+  /* Now go over the old hash table and check that we cover the same
+     entries.  */
+  if (hash_shdr->sh_entsize == sizeof (Elf32_Word))
+    {
+      const Elf32_Word *hasharr = (Elf32_Word *) hash_data->d_buf;
+      if (hash_data->d_size < 2 * sizeof (Elf32_Word))
+	{
+	  ERROR (gettext ("\
+hash section [%2zu] '%s' does not contain enough data\n"),
+		 hash_idx, hash_name);
+	  return;
+	}
+
+      Elf32_Word nbucket = hasharr[0];
+      Elf32_Word nchain = hasharr[1];
+      uint64_t hash_used = (2ULL + nchain + nbucket) * sizeof (Elf32_Word);
+      if (hash_used > hash_data->d_size)
+	{
+	  ERROR (gettext ("\
+hash section [%2zu] '%s' uses too much data\n"),
+		 hash_idx, hash_name);
+	  return;
+	}
+
+      const Elf32_Word *bucket = &hasharr[2];
+      const Elf32_Word *chain = &hasharr[2 + nbucket];
+
+      for (Elf32_Word cnt = 0; cnt < nbucket; ++cnt)
+	{
+	  Elf32_Word symidx = bucket[cnt];
+	  while (symidx != STN_UNDEF && symidx < nentries && symidx < nchain)
+	    {
+	      used[symidx] |= 2;
+	      symidx = chain[symidx];
+	    }
+	}
+    }
+  else if (hash_shdr->sh_entsize == sizeof (Elf64_Xword))
+    {
+      const Elf64_Xword *hasharr = (Elf64_Xword *) hash_data->d_buf;
+      if (hash_data->d_size < 2 * sizeof (Elf32_Word))
+	{
+	  ERROR (gettext ("\
+hash section [%2zu] '%s' does not contain enough data\n"),
+		 hash_idx, hash_name);
+	  return;
+	}
+
+      Elf64_Xword nbucket = hasharr[0];
+      Elf64_Xword nchain = hasharr[1];
+      uint64_t maxwords = hash_data->d_size / sizeof (Elf64_Xword);
+      if (maxwords < 2
+	  || maxwords - 2 < nbucket
+	  || maxwords - 2 - nbucket < nchain)
+	{
+	  ERROR (gettext ("\
+hash section [%2zu] '%s' uses too much data\n"),
+		 hash_idx, hash_name);
+	  return;
+	}
+
+      const Elf64_Xword *bucket = &hasharr[2];
+      const Elf64_Xword *chain = &hasharr[2 + nbucket];
+
+      for (Elf64_Xword cnt = 0; cnt < nbucket; ++cnt)
+	{
+	  Elf64_Xword symidx = bucket[cnt];
+	  while (symidx != STN_UNDEF && symidx < nentries && symidx < nchain)
+	    {
+	      used[symidx] |= 2;
+	      symidx = chain[symidx];
+	    }
+	}
+    }
+  else
+    {
+      ERROR (gettext ("\
+hash section [%2zu] '%s' invalid sh_entsize\n"),
+	     hash_idx, hash_name);
+      return;
+    }
+
+  /* Now see which entries are not set in one or both hash tables
+     (unless the symbol is undefined in which case it can be omitted
+     in the new table format).  */
+  if ((used[0] & 1) != 0)
+    ERROR (gettext ("section [%2zu] '%s': reference to symbol index 0\n"),
+	   gnu_hash_idx,
+	   elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name));
+  if ((used[0] & 2) != 0)
+    ERROR (gettext ("section [%2zu] '%s': reference to symbol index 0\n"),
+	   hash_idx, elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name));
+
+  for (uint32_t cnt = 1; cnt < nentries; ++cnt)
+    if (used[cnt] != 0 && used[cnt] != 3)
+      {
+	if (used[cnt] == 1)
+	  ERROR (gettext ("\
+symbol %d referenced in new hash table in [%2zu] '%s' but not in old hash table in [%2zu] '%s'\n"),
+		 cnt, gnu_hash_idx,
+		 elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name),
+		 hash_idx,
+		 elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name));
+	else
+	  {
+	    GElf_Sym sym_mem;
+	    GElf_Sym *sym = gelf_getsym (sym_data, cnt, &sym_mem);
+
+	    if (sym != NULL && sym->st_shndx != STN_UNDEF)
+	      ERROR (gettext ("\
+symbol %d referenced in old hash table in [%2zu] '%s' but not in new hash table in [%2zu] '%s'\n"),
+		     cnt, hash_idx,
+		     elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name),
+		     gnu_hash_idx,
+		     elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name));
+	  }
+      }
+}
+
+
+static void
+check_null (Ebl *ebl, GElf_Shdr *shdr, int idx)
+{
+#define TEST(name, extra) \
+  if (extra && shdr->sh_##name != 0)					      \
+    ERROR (gettext ("section [%2d] '%s': nonzero sh_%s for NULL section\n"),  \
+	   idx, section_name (ebl, idx), #name)
+
+  TEST (name, 1);
+  TEST (flags, 1);
+  TEST (addr, 1);
+  TEST (offset, 1);
+  TEST (size, idx != 0);
+  TEST (link, idx != 0);
+  TEST (info, 1);
+  TEST (addralign, 1);
+  TEST (entsize, 1);
+}
+
+
+static void
+check_group (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
+{
+  if (ehdr->e_type != ET_REL)
+    {
+      ERROR (gettext ("\
+section [%2d] '%s': section groups only allowed in relocatable object files\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  /* Check that sh_link is an index of a symbol table.  */
+  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
+  GElf_Shdr symshdr_mem;
+  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
+  if (symshdr == NULL)
+    ERROR (gettext ("section [%2d] '%s': cannot get symbol table: %s\n"),
+	   idx, section_name (ebl, idx), elf_errmsg (-1));
+  else
+    {
+      if (symshdr->sh_type != SHT_SYMTAB)
+	ERROR (gettext ("\
+section [%2d] '%s': section reference in sh_link is no symbol table\n"),
+	       idx, section_name (ebl, idx));
+
+      if (shdr->sh_info >= symshdr->sh_size / gelf_fsize (ebl->elf, ELF_T_SYM,
+							  1, EV_CURRENT))
+	ERROR (gettext ("\
+section [%2d] '%s': invalid symbol index in sh_info\n"),
+	       idx, section_name (ebl, idx));
+
+      if (shdr->sh_flags != 0)
+	ERROR (gettext ("section [%2d] '%s': sh_flags not zero\n"),
+	       idx, section_name (ebl, idx));
+
+      GElf_Sym sym_data;
+      GElf_Sym *sym = gelf_getsym (elf_getdata (symscn, NULL), shdr->sh_info,
+				   &sym_data);
+      if (sym == NULL)
+	ERROR (gettext ("\
+section [%2d] '%s': cannot get symbol for signature\n"),
+	       idx, section_name (ebl, idx));
+      else if (elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name) == NULL)
+	ERROR (gettext ("\
+section [%2d] '%s': cannot get symbol name for signature\n"),
+	       idx, section_name (ebl, idx));
+      else if (strcmp (elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name),
+		       "") == 0)
+	ERROR (gettext ("\
+section [%2d] '%s': signature symbol cannot be empty string\n"),
+	       idx, section_name (ebl, idx));
+
+      if (be_strict
+	  && shdr->sh_entsize != elf32_fsize (ELF_T_WORD, 1, EV_CURRENT))
+	ERROR (gettext ("section [%2d] '%s': sh_flags not set correctly\n"),
+	       idx, section_name (ebl, idx));
+    }
+
+  Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL || data->d_buf == NULL)
+    ERROR (gettext ("section [%2d] '%s': cannot get data: %s\n"),
+	   idx, section_name (ebl, idx), elf_errmsg (-1));
+  else
+    {
+      size_t elsize = elf32_fsize (ELF_T_WORD, 1, EV_CURRENT);
+      size_t cnt;
+      Elf32_Word val;
+
+      if (data->d_size % elsize != 0)
+	ERROR (gettext ("\
+section [%2d] '%s': section size not multiple of sizeof(Elf32_Word)\n"),
+	       idx, section_name (ebl, idx));
+
+      if (data->d_size < elsize)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': section group without flags word\n"),
+	       idx, section_name (ebl, idx));
+	  return;
+	}
+      else if (be_strict)
+	{
+	  if (data->d_size < 2 * elsize)
+	    ERROR (gettext ("\
+section [%2d] '%s': section group without member\n"),
+		   idx, section_name (ebl, idx));
+	  else if (data->d_size < 3 * elsize)
+	    ERROR (gettext ("\
+section [%2d] '%s': section group with only one member\n"),
+		   idx, section_name (ebl, idx));
+	}
+
+#if ALLOW_UNALIGNED
+      val = *((Elf32_Word *) data->d_buf);
+#else
+      memcpy (&val, data->d_buf, elsize);
+#endif
+      if ((val & ~GRP_COMDAT) != 0)
+	ERROR (gettext ("section [%2d] '%s': unknown section group flags\n"),
+	       idx, section_name (ebl, idx));
+
+      for (cnt = elsize; cnt < data->d_size; cnt += elsize)
+	{
+#if ALLOW_UNALIGNED
+	  val = *((Elf32_Word *) ((char *) data->d_buf + cnt));
+#else
+	  memcpy (&val, (char *) data->d_buf + cnt, elsize);
+#endif
+
+	  if (val > shnum)
+	    ERROR (gettext ("\
+section [%2d] '%s': section index %zu out of range\n"),
+		   idx, section_name (ebl, idx), cnt / elsize);
+	  else
+	    {
+	      GElf_Shdr refshdr_mem;
+	      GElf_Shdr *refshdr = gelf_getshdr (elf_getscn (ebl->elf, val),
+						 &refshdr_mem);
+	      if (refshdr == NULL)
+		ERROR (gettext ("\
+section [%2d] '%s': cannot get section header for element %zu: %s\n"),
+		       idx, section_name (ebl, idx), cnt / elsize,
+		       elf_errmsg (-1));
+	      else
+		{
+		  if (refshdr->sh_type == SHT_GROUP)
+		    ERROR (gettext ("\
+section [%2d] '%s': section group contains another group [%2d] '%s'\n"),
+			   idx, section_name (ebl, idx),
+			   val, section_name (ebl, val));
+
+		  if ((refshdr->sh_flags & SHF_GROUP) == 0)
+		    ERROR (gettext ("\
+section [%2d] '%s': element %zu references section [%2d] '%s' without SHF_GROUP flag set\n"),
+			   idx, section_name (ebl, idx), cnt / elsize,
+			   val, section_name (ebl, val));
+		}
+
+	      if (val < shnum && ++scnref[val] == 2)
+		ERROR (gettext ("\
+section [%2d] '%s' is contained in more than one section group\n"),
+		       val, section_name (ebl, val));
+	    }
+	}
+    }
+}
+
+
+static const char *
+section_flags_string (GElf_Word flags, char *buf, size_t len)
+{
+  if (flags == 0)
+    return "none";
+
+  static const struct
+  {
+    GElf_Word flag;
+    const char *name;
+  } known_flags[] =
+    {
+#define NEWFLAG(name) { SHF_##name, #name }
+      NEWFLAG (WRITE),
+      NEWFLAG (ALLOC),
+      NEWFLAG (EXECINSTR),
+      NEWFLAG (MERGE),
+      NEWFLAG (STRINGS),
+      NEWFLAG (INFO_LINK),
+      NEWFLAG (LINK_ORDER),
+      NEWFLAG (OS_NONCONFORMING),
+      NEWFLAG (GROUP),
+      NEWFLAG (TLS),
+      NEWFLAG (COMPRESSED)
+    };
+#undef NEWFLAG
+  const size_t nknown_flags = sizeof (known_flags) / sizeof (known_flags[0]);
+
+  char *cp = buf;
+
+  for (size_t cnt = 0; cnt < nknown_flags; ++cnt)
+    if (flags & known_flags[cnt].flag)
+      {
+	if (cp != buf && len > 1)
+	  {
+	    *cp++ = '|';
+	    --len;
+	  }
+
+	size_t ncopy = MIN (len - 1, strlen (known_flags[cnt].name));
+	cp = mempcpy (cp, known_flags[cnt].name, ncopy);
+	len -= ncopy;
+
+	flags ^= known_flags[cnt].flag;
+      }
+
+  if (flags != 0 || cp == buf)
+    snprintf (cp, len - 1, "%" PRIx64, (uint64_t) flags);
+
+  *cp = '\0';
+
+  return buf;
+}
+
+
+static int
+has_copy_reloc (Ebl *ebl, unsigned int symscnndx, unsigned int symndx)
+{
+  /* First find the relocation section for the symbol table.  */
+  Elf_Scn *scn = NULL;
+  GElf_Shdr shdr_mem;
+  GElf_Shdr *shdr = NULL;
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      shdr = gelf_getshdr (scn, &shdr_mem);
+      if (shdr != NULL
+	  && (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA)
+	  && shdr->sh_link == symscnndx)
+	/* Found the section.  */
+	break;
+    }
+
+  if (scn == NULL)
+    return 0;
+
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (data == NULL || shdr->sh_entsize == 0)
+    return 0;
+
+  if (shdr->sh_type == SHT_REL)
+    for (int i = 0; (size_t) i < shdr->sh_size / shdr->sh_entsize; ++i)
+      {
+	GElf_Rel rel_mem;
+	GElf_Rel *rel = gelf_getrel (data, i, &rel_mem);
+	if (rel == NULL)
+	  continue;
+
+	if (GELF_R_SYM (rel->r_info) == symndx
+	    && ebl_copy_reloc_p (ebl, GELF_R_TYPE (rel->r_info)))
+	  return 1;
+      }
+  else
+    for (int i = 0; (size_t) i < shdr->sh_size / shdr->sh_entsize; ++i)
+      {
+	GElf_Rela rela_mem;
+	GElf_Rela *rela = gelf_getrela (data, i, &rela_mem);
+	if (rela == NULL)
+	  continue;
+
+	if (GELF_R_SYM (rela->r_info) == symndx
+	    && ebl_copy_reloc_p (ebl, GELF_R_TYPE (rela->r_info)))
+	  return 1;
+      }
+
+  return 0;
+}
+
+
+static int
+in_nobits_scn (Ebl *ebl, unsigned int shndx)
+{
+  GElf_Shdr shdr_mem;
+  GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, shndx), &shdr_mem);
+  return shdr != NULL && shdr->sh_type == SHT_NOBITS;
+}
+
+
+static struct version_namelist
+{
+  const char *objname;
+  const char *name;
+  GElf_Versym ndx;
+  enum { ver_def, ver_need } type;
+  struct version_namelist *next;
+} *version_namelist;
+
+
+static int
+add_version (const char *objname, const char *name, GElf_Versym ndx, int type)
+{
+  /* Check that there are no duplications.  */
+  struct version_namelist *nlp = version_namelist;
+  while (nlp != NULL)
+    {
+      if (((nlp->objname == NULL && objname == NULL)
+	   || (nlp->objname != NULL && objname != NULL
+	       && strcmp (nlp->objname, objname) == 0))
+	  && strcmp (nlp->name, name) == 0)
+	return nlp->type == ver_def ? 1 : -1;
+      nlp = nlp->next;
+    }
+
+  nlp = xmalloc (sizeof (*nlp));
+  nlp->objname = objname;
+  nlp->name = name;
+  nlp->ndx = ndx;
+  nlp->type = type;
+  nlp->next = version_namelist;
+  version_namelist = nlp;
+
+  return 0;
+}
+
+
+static void
+check_versym (Ebl *ebl, int idx)
+{
+  Elf_Scn *scn = elf_getscn (ebl->elf, idx);
+  GElf_Shdr shdr_mem;
+  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+  if (shdr == NULL)
+    /* The error has already been reported.  */
+    return;
+
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (data == NULL)
+    {
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
+  GElf_Shdr symshdr_mem;
+  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
+  if (symshdr == NULL)
+    /* The error has already been reported.  */
+    return;
+
+  if (symshdr->sh_type != SHT_DYNSYM)
+    {
+      ERROR (gettext ("\
+section [%2d] '%s' refers in sh_link to section [%2d] '%s' which is no dynamic symbol table\n"),
+	     idx, section_name (ebl, idx),
+	     shdr->sh_link, section_name (ebl, shdr->sh_link));
+      return;
+    }
+
+  /* The number of elements in the version symbol table must be the
+     same as the number of symbols.  */
+  if (shdr->sh_entsize != 0 && symshdr->sh_entsize != 0
+      && (shdr->sh_size / shdr->sh_entsize
+	  != symshdr->sh_size / symshdr->sh_entsize))
+    ERROR (gettext ("\
+section [%2d] '%s' has different number of entries than symbol table [%2d] '%s'\n"),
+	   idx, section_name (ebl, idx),
+	   shdr->sh_link, section_name (ebl, shdr->sh_link));
+
+  Elf_Data *symdata = elf_getdata (symscn, NULL);
+  if (symdata == NULL || shdr->sh_entsize == 0)
+    /* The error has already been reported.  */
+    return;
+
+  for (int cnt = 1; (size_t) cnt < shdr->sh_size / shdr->sh_entsize; ++cnt)
+    {
+      GElf_Versym versym_mem;
+      GElf_Versym *versym = gelf_getversym (data, cnt, &versym_mem);
+      if (versym == NULL)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': symbol %d: cannot read version data\n"),
+		 idx, section_name (ebl, idx), cnt);
+	  break;
+	}
+
+      GElf_Sym sym_mem;
+      GElf_Sym *sym = gelf_getsym (symdata, cnt, &sym_mem);
+      if (sym == NULL)
+	/* Already reported elsewhere.  */
+	continue;
+
+      if (*versym == VER_NDX_GLOBAL)
+	{
+	  /* Global symbol.  Make sure it is not defined as local.  */
+	  if (GELF_ST_BIND (sym->st_info) == STB_LOCAL)
+	    ERROR (gettext ("\
+section [%2d] '%s': symbol %d: local symbol with global scope\n"),
+		   idx, section_name (ebl, idx), cnt);
+	}
+      else if (*versym != VER_NDX_LOCAL)
+	{
+	  /* Versioned symbol.  Make sure it is not defined as local.  */
+	  if (!gnuld && GELF_ST_BIND (sym->st_info) == STB_LOCAL)
+	    ERROR (gettext ("\
+section [%2d] '%s': symbol %d: local symbol with version\n"),
+		   idx, section_name (ebl, idx), cnt);
+
+	  /* Look through the list of defined versions and locate the
+	     index we need for this symbol.  */
+	  struct version_namelist *runp = version_namelist;
+	  while (runp != NULL)
+	    if (runp->ndx == (*versym & (GElf_Versym) 0x7fff))
+	      break;
+	    else
+	      runp = runp->next;
+
+	  if (runp == NULL)
+	    ERROR (gettext ("\
+section [%2d] '%s': symbol %d: invalid version index %d\n"),
+		   idx, section_name (ebl, idx), cnt, (int) *versym);
+	  else if (sym->st_shndx == SHN_UNDEF
+		   && runp->type == ver_def)
+	    ERROR (gettext ("\
+section [%2d] '%s': symbol %d: version index %d is for defined version\n"),
+		   idx, section_name (ebl, idx), cnt, (int) *versym);
+	  else if (sym->st_shndx != SHN_UNDEF
+		   && runp->type == ver_need)
+	    {
+	      /* Unless this symbol has a copy relocation associated
+		 this must not happen.  */
+	      if (!has_copy_reloc (ebl, shdr->sh_link, cnt)
+		  && !in_nobits_scn (ebl, sym->st_shndx))
+		ERROR (gettext ("\
+section [%2d] '%s': symbol %d: version index %d is for requested version\n"),
+		       idx, section_name (ebl, idx), cnt, (int) *versym);
+	    }
+	}
+    }
+}
+
+
+static int
+unknown_dependency_p (Elf *elf, const char *fname)
+{
+  GElf_Phdr phdr_mem;
+  GElf_Phdr *phdr = NULL;
+
+  unsigned int i;
+  for (i = 0; i < phnum; ++i)
+    if ((phdr = gelf_getphdr (elf, i, &phdr_mem)) != NULL
+	&& phdr->p_type == PT_DYNAMIC)
+      break;
+
+  if (i == phnum)
+    return 1;
+  assert (phdr != NULL);
+  Elf_Scn *scn = gelf_offscn (elf, phdr->p_offset);
+  GElf_Shdr shdr_mem;
+  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (shdr != NULL && shdr->sh_type == SHT_DYNAMIC
+      && data != NULL && shdr->sh_entsize != 0)
+    for (size_t j = 0; j < shdr->sh_size / shdr->sh_entsize; ++j)
+      {
+	GElf_Dyn dyn_mem;
+	GElf_Dyn *dyn = gelf_getdyn (data, j, &dyn_mem);
+	if (dyn != NULL && dyn->d_tag == DT_NEEDED)
+	  {
+	    const char *str = elf_strptr (elf, shdr->sh_link, dyn->d_un.d_val);
+	    if (str != NULL && strcmp (str, fname) == 0)
+	      /* Found it.  */
+	      return 0;
+	  }
+      }
+
+  return 1;
+}
+
+
+static unsigned int nverneed;
+
+static void
+check_verneed (Ebl *ebl, GElf_Shdr *shdr, int idx)
+{
+  if (++nverneed == 2)
+    ERROR (gettext ("more than one version reference section present\n"));
+
+  GElf_Shdr strshdr_mem;
+  GElf_Shdr *strshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
+				     &strshdr_mem);
+  if (strshdr == NULL)
+    return;
+  if (strshdr->sh_type != SHT_STRTAB)
+    ERROR (gettext ("\
+section [%2d] '%s': sh_link does not link to string table\n"),
+	   idx, section_name (ebl, idx));
+
+  Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL)
+    {
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+  unsigned int offset = 0;
+  for (Elf64_Word cnt = shdr->sh_info; cnt > 0; )
+    {
+      cnt--;
+
+      /* Get the data at the next offset.  */
+      GElf_Verneed needmem;
+      GElf_Verneed *need = gelf_getverneed (data, offset, &needmem);
+      if (need == NULL)
+	break;
+
+      unsigned int auxoffset = offset + need->vn_aux;
+
+      if (need->vn_version != EV_CURRENT)
+	ERROR (gettext ("\
+section [%2d] '%s': entry %d has wrong version %d\n"),
+	       idx, section_name (ebl, idx), cnt, (int) need->vn_version);
+
+      if (need->vn_cnt > 0 && need->vn_aux < gelf_fsize (ebl->elf, ELF_T_VNEED,
+							 1, EV_CURRENT))
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': entry %d has wrong offset of auxiliary data\n"),
+	         idx, section_name (ebl, idx), cnt);
+	  break;
+	}
+
+      const char *libname = elf_strptr (ebl->elf, shdr->sh_link,
+					need->vn_file);
+      if (libname == NULL)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': entry %d has invalid file reference\n"),
+		 idx, section_name (ebl, idx), cnt);
+	  goto next_need;
+	}
+
+      /* Check that there is a DT_NEEDED entry for the referenced library.  */
+      if (unknown_dependency_p (ebl->elf, libname))
+	ERROR (gettext ("\
+section [%2d] '%s': entry %d references unknown dependency\n"),
+	       idx, section_name (ebl, idx), cnt);
+
+      for (int cnt2 = need->vn_cnt; --cnt2 >= 0; )
+	{
+	  GElf_Vernaux auxmem;
+	  GElf_Vernaux *aux = gelf_getvernaux (data, auxoffset, &auxmem);
+	  if (aux == NULL)
+	    break;
+
+	  if ((aux->vna_flags & ~VER_FLG_WEAK) != 0)
+	    ERROR (gettext ("\
+section [%2d] '%s': auxiliary entry %d of entry %d has unknown flag\n"),
+		   idx, section_name (ebl, idx), need->vn_cnt - cnt2, cnt);
+
+	  const char *verstr = elf_strptr (ebl->elf, shdr->sh_link,
+					   aux->vna_name);
+	  if (verstr == NULL)
+	    {
+	      ERROR (gettext ("\
+section [%2d] '%s': auxiliary entry %d of entry %d has invalid name reference\n"),
+		     idx, section_name (ebl, idx), need->vn_cnt - cnt2, cnt);
+	      break;
+	    }
+	  else
+	    {
+	      GElf_Word hashval = elf_hash (verstr);
+	      if (hashval != aux->vna_hash)
+		ERROR (gettext ("\
+section [%2d] '%s': auxiliary entry %d of entry %d has wrong hash value: %#x, expected %#x\n"),
+		       idx, section_name (ebl, idx), need->vn_cnt - cnt2,
+		       cnt, (int) hashval, (int) aux->vna_hash);
+
+	      int res = add_version (libname, verstr, aux->vna_other,
+				     ver_need);
+	      if (unlikely (res !=0))
+		{
+		  ERROR (gettext ("\
+section [%2d] '%s': auxiliary entry %d of entry %d has duplicate version name '%s'\n"),
+			 idx, section_name (ebl, idx), need->vn_cnt - cnt2,
+			 cnt, verstr);
+		}
+	    }
+
+	  if ((aux->vna_next != 0 || cnt2 > 0)
+	      && aux->vna_next < gelf_fsize (ebl->elf, ELF_T_VNAUX, 1,
+					     EV_CURRENT))
+	    {
+	      ERROR (gettext ("\
+section [%2d] '%s': auxiliary entry %d of entry %d has wrong next field\n"),
+		     idx, section_name (ebl, idx), need->vn_cnt - cnt2, cnt);
+	      break;
+	    }
+
+	  auxoffset += MAX (aux->vna_next,
+			    gelf_fsize (ebl->elf, ELF_T_VNAUX, 1, EV_CURRENT));
+	}
+
+      /* Find the next offset.  */
+    next_need:
+      offset += need->vn_next;
+
+      if ((need->vn_next != 0 || cnt > 0)
+	  && offset < auxoffset)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': entry %d has invalid offset to next entry\n"),
+	         idx, section_name (ebl, idx), cnt);
+	  break;
+	}
+
+      if (need->vn_next == 0 && cnt > 0)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': entry %d has zero offset to next entry, but sh_info says there are more entries\n"),
+	         idx, section_name (ebl, idx), cnt);
+	  break;
+	}
+    }
+}
+
+
+static unsigned int nverdef;
+
+static void
+check_verdef (Ebl *ebl, GElf_Shdr *shdr, int idx)
+{
+  if (++nverdef == 2)
+    ERROR (gettext ("more than one version definition section present\n"));
+
+  GElf_Shdr strshdr_mem;
+  GElf_Shdr *strshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
+				     &strshdr_mem);
+  if (strshdr == NULL)
+    return;
+  if (strshdr->sh_type != SHT_STRTAB)
+    ERROR (gettext ("\
+section [%2d] '%s': sh_link does not link to string table\n"),
+	   idx, section_name (ebl, idx));
+
+  Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL)
+    {
+    no_data:
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  /* Iterate over all version definition entries.  We check that there
+     is a BASE entry and that each index is unique.  To do the later
+     we collection the information in a list which is later
+     examined.  */
+  struct namelist
+  {
+    const char *name;
+    struct namelist *next;
+  } *namelist = NULL;
+  struct namelist *refnamelist = NULL;
+
+  bool has_base = false;
+  unsigned int offset = 0;
+  for (Elf64_Word cnt = shdr->sh_info; cnt > 0; )
+    {
+      cnt--;
+
+      /* Get the data at the next offset.  */
+      GElf_Verdef defmem;
+      GElf_Verdef *def = gelf_getverdef (data, offset, &defmem);
+      if (def == NULL)
+	goto no_data;
+
+      if ((def->vd_flags & VER_FLG_BASE) != 0)
+	{
+	  if (has_base)
+	    ERROR (gettext ("\
+section [%2d] '%s': more than one BASE definition\n"),
+		   idx, section_name (ebl, idx));
+	  if (def->vd_ndx != VER_NDX_GLOBAL)
+	    ERROR (gettext ("\
+section [%2d] '%s': BASE definition must have index VER_NDX_GLOBAL\n"),
+		   idx, section_name (ebl, idx));
+	  has_base = true;
+	}
+      if ((def->vd_flags & ~(VER_FLG_BASE|VER_FLG_WEAK)) != 0)
+	ERROR (gettext ("\
+section [%2d] '%s': entry %d has unknown flag\n"),
+	       idx, section_name (ebl, idx), cnt);
+
+      if (def->vd_version != EV_CURRENT)
+	ERROR (gettext ("\
+section [%2d] '%s': entry %d has wrong version %d\n"),
+	       idx, section_name (ebl, idx), cnt, (int) def->vd_version);
+
+      if (def->vd_cnt > 0 && def->vd_aux < gelf_fsize (ebl->elf, ELF_T_VDEF,
+						       1, EV_CURRENT))
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': entry %d has wrong offset of auxiliary data\n"),
+	         idx, section_name (ebl, idx), cnt);
+	  break;
+	}
+
+      unsigned int auxoffset = offset + def->vd_aux;
+      GElf_Verdaux auxmem;
+      GElf_Verdaux *aux = gelf_getverdaux (data, auxoffset, &auxmem);
+      if (aux == NULL)
+	goto no_data;
+
+      const char *name = elf_strptr (ebl->elf, shdr->sh_link, aux->vda_name);
+      if (name == NULL)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': entry %d has invalid name reference\n"),
+		 idx, section_name (ebl, idx), cnt);
+	  goto next_def;
+	}
+      GElf_Word hashval = elf_hash (name);
+      if (def->vd_hash != hashval)
+	ERROR (gettext ("\
+section [%2d] '%s': entry %d has wrong hash value: %#x, expected %#x\n"),
+	       idx, section_name (ebl, idx), cnt, (int) hashval,
+	       (int) def->vd_hash);
+
+      int res = add_version (NULL, name, def->vd_ndx, ver_def);
+      if (unlikely (res !=0))
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': entry %d has duplicate version name '%s'\n"),
+		 idx, section_name (ebl, idx), cnt, name);
+	}
+
+      struct namelist *newname = alloca (sizeof (*newname));
+      newname->name = name;
+      newname->next = namelist;
+      namelist = newname;
+
+      auxoffset += aux->vda_next;
+      for (int cnt2 = 1; cnt2 < def->vd_cnt; ++cnt2)
+	{
+	  aux = gelf_getverdaux (data, auxoffset, &auxmem);
+	  if (aux == NULL)
+	    goto no_data;
+
+	  name = elf_strptr (ebl->elf, shdr->sh_link, aux->vda_name);
+	  if (name == NULL)
+	    {
+	      ERROR (gettext ("\
+section [%2d] '%s': entry %d has invalid name reference in auxiliary data\n"),
+		     idx, section_name (ebl, idx), cnt);
+	      break;
+	    }
+	  else
+	    {
+	      newname = alloca (sizeof (*newname));
+	      newname->name = name;
+	      newname->next = refnamelist;
+	      refnamelist = newname;
+	    }
+
+	  if ((aux->vda_next != 0 || cnt2 + 1 < def->vd_cnt)
+	      && aux->vda_next < gelf_fsize (ebl->elf, ELF_T_VDAUX, 1,
+					     EV_CURRENT))
+	    {
+	      ERROR (gettext ("\
+section [%2d] '%s': entry %d has wrong next field in auxiliary data\n"),
+		     idx, section_name (ebl, idx), cnt);
+	      break;
+	    }
+
+	  auxoffset += MAX (aux->vda_next,
+			    gelf_fsize (ebl->elf, ELF_T_VDAUX, 1, EV_CURRENT));
+	}
+
+      /* Find the next offset.  */
+    next_def:
+      offset += def->vd_next;
+
+      if ((def->vd_next != 0 || cnt > 0)
+	  && offset < auxoffset)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': entry %d has invalid offset to next entry\n"),
+	         idx, section_name (ebl, idx), cnt);
+	  break;
+	}
+
+      if (def->vd_next == 0 && cnt > 0)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': entry %d has zero offset to next entry, but sh_info says there are more entries\n"),
+	         idx, section_name (ebl, idx), cnt);
+	  break;
+	}
+    }
+
+  if (!has_base)
+    ERROR (gettext ("section [%2d] '%s': no BASE definition\n"),
+	   idx, section_name (ebl, idx));
+
+  /* Check whether the referenced names are available.  */
+  while (namelist != NULL)
+    {
+      struct version_namelist *runp = version_namelist;
+      while (runp != NULL)
+	{
+	  if (runp->type == ver_def
+	      && strcmp (runp->name, namelist->name) == 0)
+	    break;
+	  runp = runp->next;
+	}
+
+      if (runp == NULL)
+	ERROR (gettext ("\
+section [%2d] '%s': unknown parent version '%s'\n"),
+	       idx, section_name (ebl, idx), namelist->name);
+
+      namelist = namelist->next;
+    }
+}
+
+static void
+check_attributes (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
+{
+  if (shdr->sh_size == 0)
+    {
+      ERROR (gettext ("section [%2d] '%s': empty object attributes section\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  Elf_Data *data = elf_rawdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL || data->d_size == 0 || data->d_buf == NULL)
+    {
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  inline size_t pos (const unsigned char *p)
+  {
+    return p - (const unsigned char *) data->d_buf;
+  }
+
+  const unsigned char *p = data->d_buf;
+  if (*p++ != 'A')
+    {
+      ERROR (gettext ("section [%2d] '%s': unrecognized attribute format\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  inline size_t left (void)
+  {
+    return (const unsigned char *) data->d_buf + data->d_size - p;
+  }
+
+  while (left () >= 4)
+    {
+      uint32_t len;
+      memcpy (&len, p, sizeof len);
+
+      if (len == 0)
+	ERROR (gettext ("\
+section [%2d] '%s': offset %zu: zero length field in attribute section\n"),
+	       idx, section_name (ebl, idx), pos (p));
+
+      if (MY_ELFDATA != ehdr->e_ident[EI_DATA])
+	CONVERT (len);
+
+      if (len > left ())
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': offset %zu: invalid length in attribute section\n"),
+		 idx, section_name (ebl, idx), pos (p));
+	  break;
+	}
+
+      const unsigned char *name = p + sizeof len;
+      p += len;
+
+      unsigned const char *q = memchr (name, '\0', len);
+      if (q == NULL)
+	{
+	  ERROR (gettext ("\
+section [%2d] '%s': offset %zu: unterminated vendor name string\n"),
+		 idx, section_name (ebl, idx), pos (p));
+	  break;
+	}
+      ++q;
+
+      if (q - name == sizeof "gnu" && !memcmp (name, "gnu", sizeof "gnu"))
+	while (q < p)
+	  {
+	    unsigned const char *chunk = q;
+
+	    unsigned int subsection_tag;
+	    get_uleb128 (subsection_tag, q, p);
+
+	    if (q >= p)
+	      {
+		ERROR (gettext ("\
+section [%2d] '%s': offset %zu: endless ULEB128 in attribute subsection tag\n"),
+		       idx, section_name (ebl, idx), pos (chunk));
+		break;
+	      }
+
+	    uint32_t subsection_len;
+	    if (p - q < (ptrdiff_t) sizeof subsection_len)
+	      {
+		ERROR (gettext ("\
+section [%2d] '%s': offset %zu: truncated attribute section\n"),
+		       idx, section_name (ebl, idx), pos (q));
+		break;
+	      }
+
+	    memcpy (&subsection_len, q, sizeof subsection_len);
+	    if (subsection_len == 0)
+	      {
+		ERROR (gettext ("\
+section [%2d] '%s': offset %zu: zero length field in attribute subsection\n"),
+		       idx, section_name (ebl, idx), pos (q));
+
+		q += sizeof subsection_len;
+		continue;
+	      }
+
+	    if (MY_ELFDATA != ehdr->e_ident[EI_DATA])
+	      CONVERT (subsection_len);
+
+	    /* Don't overflow, ptrdiff_t might be 32bits, but signed.  */
+	    if (p - chunk < (ptrdiff_t) subsection_len
+	        || subsection_len >= (uint32_t) PTRDIFF_MAX)
+	      {
+		ERROR (gettext ("\
+section [%2d] '%s': offset %zu: invalid length in attribute subsection\n"),
+		       idx, section_name (ebl, idx), pos (q));
+		break;
+	      }
+
+	    const unsigned char *subsection_end = chunk + subsection_len;
+	    chunk = q;
+	    q = subsection_end;
+
+	    if (subsection_tag != 1) /* Tag_File */
+	      ERROR (gettext ("\
+section [%2d] '%s': offset %zu: attribute subsection has unexpected tag %u\n"),
+		     idx, section_name (ebl, idx), pos (chunk), subsection_tag);
+	    else
+	      {
+		chunk += sizeof subsection_len;
+		while (chunk < q)
+		  {
+		    unsigned int tag;
+		    get_uleb128 (tag, chunk, q);
+
+		    uint64_t value = 0;
+		    const unsigned char *r = chunk;
+		    if (tag == 32 || (tag & 1) == 0)
+		      {
+			get_uleb128 (value, r, q);
+			if (r > q)
+			  {
+			    ERROR (gettext ("\
+section [%2d] '%s': offset %zu: endless ULEB128 in attribute tag\n"),
+				   idx, section_name (ebl, idx), pos (chunk));
+			    break;
+			  }
+		      }
+		    if (tag == 32 || (tag & 1) != 0)
+		      {
+			r = memchr (r, '\0', q - r);
+			if (r == NULL)
+			  {
+			    ERROR (gettext ("\
+section [%2d] '%s': offset %zu: unterminated string in attribute\n"),
+				   idx, section_name (ebl, idx), pos (chunk));
+			    break;
+			  }
+			++r;
+		      }
+
+		    const char *tag_name = NULL;
+		    const char *value_name = NULL;
+		    if (!ebl_check_object_attribute (ebl, (const char *) name,
+						     tag, value,
+						     &tag_name, &value_name))
+		      ERROR (gettext ("\
+section [%2d] '%s': offset %zu: unrecognized attribute tag %u\n"),
+			     idx, section_name (ebl, idx), pos (chunk), tag);
+		    else if ((tag & 1) == 0 && value_name == NULL)
+		      ERROR (gettext ("\
+section [%2d] '%s': offset %zu: unrecognized %s attribute value %" PRIu64 "\n"),
+			     idx, section_name (ebl, idx), pos (chunk),
+			     tag_name, value);
+
+		    chunk = r;
+		  }
+	      }
+	  }
+      else
+	ERROR (gettext ("\
+section [%2d] '%s': offset %zu: vendor '%s' unknown\n"),
+	       idx, section_name (ebl, idx), pos (p), name);
+    }
+
+  if (left () != 0)
+    ERROR (gettext ("\
+section [%2d] '%s': offset %zu: extra bytes after last attribute section\n"),
+	   idx, section_name (ebl, idx), pos (p));
+}
+
+static bool has_loadable_segment;
+static bool has_interp_segment;
+
+static const struct
+{
+  const char *name;
+  size_t namelen;
+  GElf_Word type;
+  enum { unused, exact, atleast, exact_or_gnuld } attrflag;
+  GElf_Word attr;
+  GElf_Word attr2;
+} special_sections[] =
+  {
+    /* See figure 4-14 in the gABI.  */
+    { ".bss", 5, SHT_NOBITS, exact, SHF_ALLOC | SHF_WRITE, 0 },
+    { ".comment", 8, SHT_PROGBITS, atleast, 0, SHF_MERGE | SHF_STRINGS },
+    { ".data", 6, SHT_PROGBITS, exact, SHF_ALLOC | SHF_WRITE, 0 },
+    { ".data1", 7, SHT_PROGBITS, exact, SHF_ALLOC | SHF_WRITE, 0 },
+    { ".debug_str", 11, SHT_PROGBITS, exact_or_gnuld, SHF_MERGE | SHF_STRINGS, 0 },
+    { ".debug", 6, SHT_PROGBITS, exact, 0, 0 },
+    { ".dynamic", 9, SHT_DYNAMIC, atleast, SHF_ALLOC, SHF_WRITE },
+    { ".dynstr", 8, SHT_STRTAB, exact, SHF_ALLOC, 0 },
+    { ".dynsym", 8, SHT_DYNSYM, exact, SHF_ALLOC, 0 },
+    { ".fini", 6, SHT_PROGBITS, exact, SHF_ALLOC | SHF_EXECINSTR, 0 },
+    { ".fini_array", 12, SHT_FINI_ARRAY, exact, SHF_ALLOC | SHF_WRITE, 0 },
+    { ".got", 5, SHT_PROGBITS, unused, 0, 0 }, // XXX more info?
+    { ".hash", 6, SHT_HASH, exact, SHF_ALLOC, 0 },
+    { ".init", 6, SHT_PROGBITS, exact, SHF_ALLOC | SHF_EXECINSTR, 0 },
+    { ".init_array", 12, SHT_INIT_ARRAY, exact, SHF_ALLOC | SHF_WRITE, 0 },
+    { ".interp", 8, SHT_PROGBITS, atleast, 0, SHF_ALLOC }, // XXX more tests?
+    { ".line", 6, SHT_PROGBITS, exact, 0, 0 },
+    { ".note", 6, SHT_NOTE, atleast, 0, SHF_ALLOC },
+    { ".plt", 5, SHT_PROGBITS, unused, 0, 0 }, // XXX more tests
+    { ".preinit_array", 15, SHT_PREINIT_ARRAY, exact, SHF_ALLOC | SHF_WRITE, 0 },
+    { ".rela", 5, SHT_RELA, atleast, 0, SHF_ALLOC | SHF_INFO_LINK }, // XXX more tests
+    { ".rel", 4, SHT_REL, atleast, 0, SHF_ALLOC | SHF_INFO_LINK }, // XXX more tests
+    { ".rodata", 8, SHT_PROGBITS, atleast, SHF_ALLOC, SHF_MERGE | SHF_STRINGS },
+    { ".rodata1", 9, SHT_PROGBITS, atleast, SHF_ALLOC, SHF_MERGE | SHF_STRINGS },
+    { ".shstrtab", 10, SHT_STRTAB, exact, 0, 0 },
+    { ".strtab", 8, SHT_STRTAB, atleast, 0, SHF_ALLOC }, // XXX more tests
+    { ".symtab", 8, SHT_SYMTAB, atleast, 0, SHF_ALLOC }, // XXX more tests
+    { ".symtab_shndx", 14, SHT_SYMTAB_SHNDX, atleast, 0, SHF_ALLOC }, // XXX more tests
+    { ".tbss", 6, SHT_NOBITS, exact, SHF_ALLOC | SHF_WRITE | SHF_TLS, 0 },
+    { ".tdata", 7, SHT_PROGBITS, exact, SHF_ALLOC | SHF_WRITE | SHF_TLS, 0 },
+    { ".tdata1", 8, SHT_PROGBITS, exact, SHF_ALLOC | SHF_WRITE | SHF_TLS, 0 },
+    { ".text", 6, SHT_PROGBITS, exact, SHF_ALLOC | SHF_EXECINSTR, 0 },
+
+    /* The following are GNU extensions.  */
+    { ".gnu.version", 13, SHT_GNU_versym, exact, SHF_ALLOC, 0 },
+    { ".gnu.version_d", 15, SHT_GNU_verdef, exact, SHF_ALLOC, 0 },
+    { ".gnu.version_r", 15, SHT_GNU_verneed, exact, SHF_ALLOC, 0 },
+    { ".gnu.attributes", 16, SHT_GNU_ATTRIBUTES, exact, 0, 0 },
+  };
+#define nspecial_sections \
+  (sizeof (special_sections) / sizeof (special_sections[0]))
+
+#define IS_KNOWN_SPECIAL(idx, string, prefix)			      \
+  (special_sections[idx].namelen == sizeof string - (prefix ? 1 : 0)  \
+   && !memcmp (special_sections[idx].name, string, \
+	       sizeof string - (prefix ? 1 : 0)))
+
+
+/* Indeces of some sections we need later.  */
+static size_t eh_frame_hdr_scnndx;
+static size_t eh_frame_scnndx;
+static size_t gcc_except_table_scnndx;
+
+
+static void
+check_sections (Ebl *ebl, GElf_Ehdr *ehdr)
+{
+  if (ehdr->e_shoff == 0)
+    /* No section header.  */
+    return;
+
+  /* Allocate array to count references in section groups.  */
+  scnref = (int *) xcalloc (shnum, sizeof (int));
+
+  /* Check the zeroth section first.  It must not have any contents
+     and the section header must contain nonzero value at most in the
+     sh_size and sh_link fields.  */
+  GElf_Shdr shdr_mem;
+  GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
+  if (shdr == NULL)
+    ERROR (gettext ("cannot get section header of zeroth section\n"));
+  else
+    {
+      if (shdr->sh_name != 0)
+	ERROR (gettext ("zeroth section has nonzero name\n"));
+      if (shdr->sh_type != 0)
+	ERROR (gettext ("zeroth section has nonzero type\n"));
+      if (shdr->sh_flags != 0)
+	ERROR (gettext ("zeroth section has nonzero flags\n"));
+      if (shdr->sh_addr != 0)
+	ERROR (gettext ("zeroth section has nonzero address\n"));
+      if (shdr->sh_offset != 0)
+	ERROR (gettext ("zeroth section has nonzero offset\n"));
+      if (shdr->sh_addralign != 0)
+	ERROR (gettext ("zeroth section has nonzero align value\n"));
+      if (shdr->sh_entsize != 0)
+	ERROR (gettext ("zeroth section has nonzero entry size value\n"));
+
+      if (shdr->sh_size != 0 && ehdr->e_shnum != 0)
+	ERROR (gettext ("\
+zeroth section has nonzero size value while ELF header has nonzero shnum value\n"));
+
+      if (shdr->sh_link != 0 && ehdr->e_shstrndx != SHN_XINDEX)
+	ERROR (gettext ("\
+zeroth section has nonzero link value while ELF header does not signal overflow in shstrndx\n"));
+
+      if (shdr->sh_info != 0 && ehdr->e_phnum != PN_XNUM)
+	ERROR (gettext ("\
+zeroth section has nonzero link value while ELF header does not signal overflow in phnum\n"));
+    }
+
+  int *segment_flags = xcalloc (phnum, sizeof segment_flags[0]);
+
+  bool dot_interp_section = false;
+
+  size_t hash_idx = 0;
+  size_t gnu_hash_idx = 0;
+
+  size_t versym_scnndx = 0;
+  for (size_t cnt = 1; cnt < shnum; ++cnt)
+    {
+      Elf_Scn *scn = elf_getscn (ebl->elf, cnt);
+      shdr = gelf_getshdr (scn, &shdr_mem);
+      if (shdr == NULL)
+	{
+	  ERROR (gettext ("\
+cannot get section header for section [%2zu] '%s': %s\n"),
+		 cnt, section_name (ebl, cnt), elf_errmsg (-1));
+	  continue;
+	}
+
+      const char *scnname = elf_strptr (ebl->elf, shstrndx, shdr->sh_name);
+
+      if (scnname == NULL)
+	ERROR (gettext ("section [%2zu]: invalid name\n"), cnt);
+      else
+	{
+	  /* Check whether it is one of the special sections defined in
+	     the gABI.  */
+	  size_t s;
+	  for (s = 0; s < nspecial_sections; ++s)
+	    if (strncmp (scnname, special_sections[s].name,
+			 special_sections[s].namelen) == 0)
+	      {
+		char stbuf1[100];
+		char stbuf2[100];
+		char stbuf3[100];
+
+		GElf_Word good_type = special_sections[s].type;
+		if (IS_KNOWN_SPECIAL (s, ".plt", false)
+		    && ebl_bss_plt_p (ebl))
+		  good_type = SHT_NOBITS;
+
+		/* In a debuginfo file, any normal section can be SHT_NOBITS.
+		   This is only invalid for DWARF sections and .shstrtab.  */
+		if (shdr->sh_type != good_type
+		    && (shdr->sh_type != SHT_NOBITS
+			|| !is_debuginfo
+			|| IS_KNOWN_SPECIAL (s, ".debug_str", false)
+			|| IS_KNOWN_SPECIAL (s, ".debug", true)
+			|| IS_KNOWN_SPECIAL (s, ".shstrtab", false)))
+		  ERROR (gettext ("\
+section [%2d] '%s' has wrong type: expected %s, is %s\n"),
+			 (int) cnt, scnname,
+			 ebl_section_type_name (ebl, special_sections[s].type,
+						stbuf1, sizeof (stbuf1)),
+			 ebl_section_type_name (ebl, shdr->sh_type,
+						stbuf2, sizeof (stbuf2)));
+
+		if (special_sections[s].attrflag == exact
+		    || special_sections[s].attrflag == exact_or_gnuld)
+		  {
+		    /* Except for the link order, group bit and
+		       compression flag all the other bits should
+		       match exactly.  */
+		    if ((shdr->sh_flags
+			 & ~(SHF_LINK_ORDER | SHF_GROUP | SHF_COMPRESSED))
+			!= special_sections[s].attr
+			&& (special_sections[s].attrflag == exact || !gnuld))
+		      ERROR (gettext ("\
+section [%2zu] '%s' has wrong flags: expected %s, is %s\n"),
+			     cnt, scnname,
+			     section_flags_string (special_sections[s].attr,
+						   stbuf1, sizeof (stbuf1)),
+			     section_flags_string (shdr->sh_flags
+						   & ~SHF_LINK_ORDER,
+						   stbuf2, sizeof (stbuf2)));
+		  }
+		else if (special_sections[s].attrflag == atleast)
+		  {
+		    if ((shdr->sh_flags & special_sections[s].attr)
+			!= special_sections[s].attr
+			|| ((shdr->sh_flags
+			     & ~(SHF_LINK_ORDER | SHF_GROUP | SHF_COMPRESSED
+				 | special_sections[s].attr
+				 | special_sections[s].attr2))
+			    != 0))
+		      ERROR (gettext ("\
+section [%2zu] '%s' has wrong flags: expected %s and possibly %s, is %s\n"),
+			     cnt, scnname,
+			     section_flags_string (special_sections[s].attr,
+						   stbuf1, sizeof (stbuf1)),
+			     section_flags_string (special_sections[s].attr2,
+						   stbuf2, sizeof (stbuf2)),
+			     section_flags_string (shdr->sh_flags
+						   & ~(SHF_LINK_ORDER
+						       | SHF_GROUP),
+						   stbuf3, sizeof (stbuf3)));
+		  }
+
+		if (strcmp (scnname, ".interp") == 0)
+		  {
+		    dot_interp_section = true;
+
+		    if (ehdr->e_type == ET_REL)
+		      ERROR (gettext ("\
+section [%2zu] '%s' present in object file\n"),
+			     cnt, scnname);
+
+		    if ((shdr->sh_flags & SHF_ALLOC) != 0
+			&& !has_loadable_segment)
+		      ERROR (gettext ("\
+section [%2zu] '%s' has SHF_ALLOC flag set but there is no loadable segment\n"),
+			     cnt, scnname);
+		    else if ((shdr->sh_flags & SHF_ALLOC) == 0
+			     && has_loadable_segment)
+		      ERROR (gettext ("\
+section [%2zu] '%s' has SHF_ALLOC flag not set but there are loadable segments\n"),
+			     cnt, scnname);
+		  }
+		else
+		  {
+		    if (strcmp (scnname, ".symtab_shndx") == 0
+			&& ehdr->e_type != ET_REL)
+		      ERROR (gettext ("\
+section [%2zu] '%s' is extension section index table in non-object file\n"),
+			     cnt, scnname);
+
+		    /* These sections must have the SHF_ALLOC flag set iff
+		       a loadable segment is available.
+
+		       .relxxx
+		       .strtab
+		       .symtab
+		       .symtab_shndx
+
+		       Check that if there is a reference from the
+		       loaded section these sections also have the
+		       ALLOC flag set.  */
+#if 0
+		    // XXX TODO
+		    if ((shdr->sh_flags & SHF_ALLOC) != 0
+			&& !has_loadable_segment)
+		      ERROR (gettext ("\
+section [%2zu] '%s' has SHF_ALLOC flag set but there is no loadable segment\n"),
+			     cnt, scnname);
+		    else if ((shdr->sh_flags & SHF_ALLOC) == 0
+			     && has_loadable_segment)
+		      ERROR (gettext ("\
+section [%2zu] '%s' has SHF_ALLOC flag not set but there are loadable segments\n"),
+			     cnt, scnname);
+#endif
+		  }
+
+		break;
+	      }
+
+	  /* Remember a few special sections for later.  */
+	  if (strcmp (scnname, ".eh_frame_hdr") == 0)
+	    eh_frame_hdr_scnndx = cnt;
+	  else if (strcmp (scnname, ".eh_frame") == 0)
+	    eh_frame_scnndx = cnt;
+	  else if (strcmp (scnname, ".gcc_except_table") == 0)
+	    gcc_except_table_scnndx = cnt;
+	}
+
+      if (shdr->sh_entsize != 0 && shdr->sh_size % shdr->sh_entsize)
+	ERROR (gettext ("\
+section [%2zu] '%s': size not multiple of entry size\n"),
+	       cnt, section_name (ebl, cnt));
+
+      if (elf_strptr (ebl->elf, shstrndx, shdr->sh_name) == NULL)
+	ERROR (gettext ("cannot get section header\n"));
+
+      if (shdr->sh_type >= SHT_NUM
+	  && shdr->sh_type != SHT_GNU_ATTRIBUTES
+	  && shdr->sh_type != SHT_GNU_LIBLIST
+	  && shdr->sh_type != SHT_CHECKSUM
+	  && shdr->sh_type != SHT_GNU_verdef
+	  && shdr->sh_type != SHT_GNU_verneed
+	  && shdr->sh_type != SHT_GNU_versym
+	  && ebl_section_type_name (ebl, shdr->sh_type, NULL, 0) == NULL)
+	ERROR (gettext ("section [%2zu] '%s' has unsupported type %d\n"),
+	       cnt, section_name (ebl, cnt),
+	       (int) shdr->sh_type);
+
+#define ALL_SH_FLAGS (SHF_WRITE | SHF_ALLOC | SHF_EXECINSTR | SHF_MERGE \
+		      | SHF_STRINGS | SHF_INFO_LINK | SHF_LINK_ORDER \
+		      | SHF_OS_NONCONFORMING | SHF_GROUP | SHF_TLS \
+		      | SHF_COMPRESSED)
+      if (shdr->sh_flags & ~(GElf_Xword) ALL_SH_FLAGS)
+	{
+	  GElf_Xword sh_flags = shdr->sh_flags & ~(GElf_Xword) ALL_SH_FLAGS;
+	  if (sh_flags & SHF_MASKPROC)
+	    {
+	      if (!ebl_machine_section_flag_check (ebl,
+						   sh_flags & SHF_MASKPROC))
+		ERROR (gettext ("section [%2zu] '%s'"
+				" contains invalid processor-specific flag(s)"
+				" %#" PRIx64 "\n"),
+		       cnt, section_name (ebl, cnt), sh_flags & SHF_MASKPROC);
+	      sh_flags &= ~(GElf_Xword) SHF_MASKPROC;
+	    }
+	  if (sh_flags != 0)
+	    ERROR (gettext ("section [%2zu] '%s' contains unknown flag(s)"
+			    " %#" PRIx64 "\n"),
+		   cnt, section_name (ebl, cnt), sh_flags);
+	}
+      if (shdr->sh_flags & SHF_TLS)
+	{
+	  // XXX Correct?
+	  if (shdr->sh_addr != 0 && !gnuld)
+	    ERROR (gettext ("\
+section [%2zu] '%s': thread-local data sections address not zero\n"),
+		   cnt, section_name (ebl, cnt));
+
+	  // XXX TODO more tests!?
+	}
+
+      if (shdr->sh_flags & SHF_COMPRESSED)
+	{
+	  if (shdr->sh_flags & SHF_ALLOC)
+	    ERROR (gettext ("\
+section [%2zu] '%s': allocated section cannot be compressed\n"),
+		   cnt, section_name (ebl, cnt));
+
+	  if (shdr->sh_type == SHT_NOBITS)
+	    ERROR (gettext ("\
+section [%2zu] '%s': nobits section cannot be compressed\n"),
+		   cnt, section_name (ebl, cnt));
+
+	  GElf_Chdr chdr;
+	  if (gelf_getchdr (scn, &chdr) == NULL)
+	    ERROR (gettext ("\
+section [%2zu] '%s': compressed section with no compression header: %s\n"),
+		   cnt, section_name (ebl, cnt), elf_errmsg (-1));
+	}
+
+      if (shdr->sh_link >= shnum)
+	ERROR (gettext ("\
+section [%2zu] '%s': invalid section reference in link value\n"),
+	       cnt, section_name (ebl, cnt));
+
+      if (SH_INFO_LINK_P (shdr) && shdr->sh_info >= shnum)
+	ERROR (gettext ("\
+section [%2zu] '%s': invalid section reference in info value\n"),
+	       cnt, section_name (ebl, cnt));
+
+      if ((shdr->sh_flags & SHF_MERGE) == 0
+	  && (shdr->sh_flags & SHF_STRINGS) != 0
+	  && be_strict)
+	ERROR (gettext ("\
+section [%2zu] '%s': strings flag set without merge flag\n"),
+	       cnt, section_name (ebl, cnt));
+
+      if ((shdr->sh_flags & SHF_MERGE) != 0 && shdr->sh_entsize == 0)
+	ERROR (gettext ("\
+section [%2zu] '%s': merge flag set but entry size is zero\n"),
+	       cnt, section_name (ebl, cnt));
+
+      if (shdr->sh_flags & SHF_GROUP)
+	check_scn_group (ebl, cnt);
+
+      if (shdr->sh_flags & SHF_EXECINSTR)
+	{
+	  switch (shdr->sh_type)
+	    {
+	    case SHT_PROGBITS:
+	      break;
+
+	    case SHT_NOBITS:
+	      if (is_debuginfo)
+		break;
+	      FALLTHROUGH;
+	    default:
+	      ERROR (gettext ("\
+section [%2zu] '%s' has unexpected type %d for an executable section\n"),
+		     cnt, section_name (ebl, cnt), shdr->sh_type);
+	      break;
+	    }
+
+	  if (shdr->sh_flags & SHF_WRITE)
+	    {
+	      if (is_debuginfo && shdr->sh_type != SHT_NOBITS)
+		ERROR (gettext ("\
+section [%2zu] '%s' must be of type NOBITS in debuginfo files\n"),
+		       cnt, section_name (ebl, cnt));
+
+	      if (!is_debuginfo
+		  && !ebl_check_special_section (ebl, cnt, shdr,
+						 section_name (ebl, cnt)))
+		ERROR (gettext ("\
+section [%2zu] '%s' is both executable and writable\n"),
+		       cnt, section_name (ebl, cnt));
+	    }
+	}
+
+      if (ehdr->e_type != ET_REL && (shdr->sh_flags & SHF_ALLOC) != 0
+	  && !is_debuginfo)
+	{
+	  /* Make sure the section is contained in a loaded segment
+	     and that the initialization part matches NOBITS sections.  */
+	  unsigned int pcnt;
+	  GElf_Phdr phdr_mem;
+	  GElf_Phdr *phdr;
+
+	  for (pcnt = 0; pcnt < phnum; ++pcnt)
+	    if ((phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem)) != NULL
+		&& ((phdr->p_type == PT_LOAD
+		     && (shdr->sh_flags & SHF_TLS) == 0)
+		    || (phdr->p_type == PT_TLS
+			&& (shdr->sh_flags & SHF_TLS) != 0))
+		&& phdr->p_offset <= shdr->sh_offset
+		&& ((shdr->sh_offset - phdr->p_offset <= phdr->p_filesz
+		     && (shdr->sh_offset - phdr->p_offset < phdr->p_filesz
+			 || shdr->sh_size == 0))
+		    || (shdr->sh_offset - phdr->p_offset < phdr->p_memsz
+			&& shdr->sh_type == SHT_NOBITS)))
+	      {
+		/* Found the segment.  */
+		if (phdr->p_offset + phdr->p_memsz
+		    < shdr->sh_offset + shdr->sh_size)
+		  ERROR (gettext ("\
+section [%2zu] '%s' not fully contained in segment of program header entry %d\n"),
+			 cnt, section_name (ebl, cnt), pcnt);
+
+		if (shdr->sh_type == SHT_NOBITS)
+		  {
+		    if (shdr->sh_offset < phdr->p_offset + phdr->p_filesz
+			&& !is_debuginfo)
+		      {
+			if (!gnuld)
+			  ERROR (gettext ("\
+section [%2zu] '%s' has type NOBITS but is read from the file in segment of program header entry %d\n"),
+				 cnt, section_name (ebl, cnt), pcnt);
+			else
+			  {
+			    /* This is truly horrible. GNU ld might put a
+			       NOBITS section in the middle of a PT_LOAD
+			       segment, assuming the next gap in the file
+			       actually consists of zero bits...
+			       So it really is like a PROGBITS section
+			       where the data is all zeros.  Check those
+			       zero bytes are really there.  */
+			    bool bad;
+			    Elf_Data *databits;
+			    databits = elf_getdata_rawchunk (ebl->elf,
+							     shdr->sh_offset,
+							     shdr->sh_size,
+							     ELF_T_BYTE);
+			    bad = (databits == NULL
+				   || databits->d_size != shdr->sh_size);
+			    for (size_t idx = 0;
+				 idx < databits->d_size && ! bad;
+				 idx++)
+			      bad = ((char *) databits->d_buf)[idx] != 0;
+
+			    if (bad)
+			      ERROR (gettext ("\
+section [%2zu] '%s' has type NOBITS but is read from the file in segment of program header entry %d and file contents is non-zero\n"),
+				     cnt, section_name (ebl, cnt), pcnt);
+			  }
+		      }
+		  }
+		else
+		  {
+		    const GElf_Off end = phdr->p_offset + phdr->p_filesz;
+		    if (shdr->sh_offset > end ||
+			(shdr->sh_offset == end && shdr->sh_size != 0))
+		      ERROR (gettext ("\
+section [%2zu] '%s' has not type NOBITS but is not read from the file in segment of program header entry %d\n"),
+			 cnt, section_name (ebl, cnt), pcnt);
+		  }
+
+		if (shdr->sh_type != SHT_NOBITS)
+		  {
+		    if ((shdr->sh_flags & SHF_EXECINSTR) != 0)
+		      {
+			segment_flags[pcnt] |= PF_X;
+			if ((phdr->p_flags & PF_X) == 0)
+			  ERROR (gettext ("\
+section [%2zu] '%s' is executable in nonexecutable segment %d\n"),
+				 cnt, section_name (ebl, cnt), pcnt);
+		      }
+
+		    if ((shdr->sh_flags & SHF_WRITE) != 0)
+		      {
+			segment_flags[pcnt] |= PF_W;
+			if (0	/* XXX vdso images have this */
+			    && (phdr->p_flags & PF_W) == 0)
+			  ERROR (gettext ("\
+section [%2zu] '%s' is writable in unwritable segment %d\n"),
+				 cnt, section_name (ebl, cnt), pcnt);
+		      }
+		  }
+
+		break;
+	      }
+
+	  if (pcnt == phnum)
+	    ERROR (gettext ("\
+section [%2zu] '%s': alloc flag set but section not in any loaded segment\n"),
+		   cnt, section_name (ebl, cnt));
+	}
+
+      if (cnt == shstrndx && shdr->sh_type != SHT_STRTAB)
+	ERROR (gettext ("\
+section [%2zu] '%s': ELF header says this is the section header string table but type is not SHT_TYPE\n"),
+	       cnt, section_name (ebl, cnt));
+
+      switch (shdr->sh_type)
+	{
+	case SHT_DYNSYM:
+	  if (ehdr->e_type == ET_REL)
+	    ERROR (gettext ("\
+section [%2zu] '%s': relocatable files cannot have dynamic symbol tables\n"),
+		   cnt, section_name (ebl, cnt));
+	  FALLTHROUGH;
+	case SHT_SYMTAB:
+	  check_symtab (ebl, ehdr, shdr, cnt);
+	  break;
+
+	case SHT_RELA:
+	  check_rela (ebl, ehdr, shdr, cnt);
+	  break;
+
+	case SHT_REL:
+	  check_rel (ebl, ehdr, shdr, cnt);
+	  break;
+
+	case SHT_DYNAMIC:
+	  check_dynamic (ebl, ehdr, shdr, cnt);
+	  break;
+
+	case SHT_SYMTAB_SHNDX:
+	  check_symtab_shndx (ebl, ehdr, shdr, cnt);
+	  break;
+
+	case SHT_HASH:
+	  check_hash (shdr->sh_type, ebl, ehdr, shdr, cnt);
+	  hash_idx = cnt;
+	  break;
+
+	case SHT_GNU_HASH:
+	  check_hash (shdr->sh_type, ebl, ehdr, shdr, cnt);
+	  gnu_hash_idx = cnt;
+	  break;
+
+	case SHT_NULL:
+	  check_null (ebl, shdr, cnt);
+	  break;
+
+	case SHT_GROUP:
+	  check_group (ebl, ehdr, shdr, cnt);
+	  break;
+
+	case SHT_NOTE:
+	  check_note_section (ebl, ehdr, shdr, cnt);
+	  break;
+
+	case SHT_GNU_versym:
+	  /* We cannot process this section now since we have no guarantee
+	     that the verneed and verdef sections have already been read.
+	     Just remember the section index.  */
+	  if (versym_scnndx != 0)
+	    ERROR (gettext ("more than one version symbol table present\n"));
+	  versym_scnndx = cnt;
+	  break;
+
+	case SHT_GNU_verneed:
+	  check_verneed (ebl, shdr, cnt);
+	  break;
+
+	case SHT_GNU_verdef:
+	  check_verdef (ebl, shdr, cnt);
+	  break;
+
+	case SHT_GNU_ATTRIBUTES:
+	  check_attributes (ebl, ehdr, shdr, cnt);
+	  break;
+
+	default:
+	  /* Nothing.  */
+	  break;
+	}
+    }
+
+  if (has_interp_segment && !dot_interp_section)
+    ERROR (gettext ("INTERP program header entry but no .interp section\n"));
+
+  if (!is_debuginfo)
+    for (unsigned int pcnt = 0; pcnt < phnum; ++pcnt)
+      {
+	GElf_Phdr phdr_mem;
+	GElf_Phdr *phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem);
+	if (phdr != NULL && (phdr->p_type == PT_LOAD || phdr->p_type == PT_TLS))
+	  {
+	    if ((phdr->p_flags & PF_X) != 0
+		&& (segment_flags[pcnt] & PF_X) == 0)
+	      ERROR (gettext ("\
+loadable segment [%u] is executable but contains no executable sections\n"),
+		     pcnt);
+
+	    if ((phdr->p_flags & PF_W) != 0
+		&& (segment_flags[pcnt] & PF_W) == 0)
+	      ERROR (gettext ("\
+loadable segment [%u] is writable but contains no writable sections\n"),
+		     pcnt);
+	  }
+      }
+
+  free (segment_flags);
+
+  if (version_namelist != NULL)
+    {
+      if (versym_scnndx == 0)
+    ERROR (gettext ("\
+no .gnu.versym section present but .gnu.versym_d or .gnu.versym_r section exist\n"));
+      else
+	check_versym (ebl, versym_scnndx);
+
+      /* Check for duplicate index numbers.  */
+      do
+	{
+	  struct version_namelist *runp = version_namelist->next;
+	  while (runp != NULL)
+	    {
+	      if (version_namelist->ndx == runp->ndx)
+		{
+		  ERROR (gettext ("duplicate version index %d\n"),
+			 (int) version_namelist->ndx);
+		  break;
+		}
+	      runp = runp->next;
+	    }
+
+	  struct version_namelist *old = version_namelist;
+	  version_namelist = version_namelist->next;
+	  free (old);
+	}
+      while (version_namelist != NULL);
+    }
+  else if (versym_scnndx != 0)
+    ERROR (gettext ("\
+.gnu.versym section present without .gnu.versym_d or .gnu.versym_r\n"));
+
+  if (hash_idx != 0 && gnu_hash_idx != 0)
+    compare_hash_gnu_hash (ebl, ehdr, hash_idx, gnu_hash_idx);
+
+  free (scnref);
+}
+
+
+static GElf_Off
+check_note_data (Ebl *ebl, const GElf_Ehdr *ehdr,
+		 Elf_Data *data, int shndx, int phndx, GElf_Off start)
+{
+  size_t offset = 0;
+  size_t last_offset = 0;
+  GElf_Nhdr nhdr;
+  size_t name_offset;
+  size_t desc_offset;
+  while (offset < data->d_size
+	 && (offset = gelf_getnote (data, offset,
+				    &nhdr, &name_offset, &desc_offset)) > 0)
+    {
+      last_offset = offset;
+
+      /* Make sure it is one of the note types we know about.  */
+      if (ehdr->e_type == ET_CORE)
+	switch (nhdr.n_type)
+	  {
+	  case NT_PRSTATUS:
+	  case NT_FPREGSET:
+	  case NT_PRPSINFO:
+	  case NT_TASKSTRUCT:		/* NT_PRXREG on Solaris.  */
+	  case NT_PLATFORM:
+	  case NT_AUXV:
+	  case NT_GWINDOWS:
+	  case NT_ASRS:
+	  case NT_PSTATUS:
+	  case NT_PSINFO:
+	  case NT_PRCRED:
+	  case NT_UTSNAME:
+	  case NT_LWPSTATUS:
+	  case NT_LWPSINFO:
+	  case NT_PRFPXREG:
+	    /* Known type.  */
+	    break;
+
+	  default:
+	    if (shndx == 0)
+	      ERROR (gettext ("\
+phdr[%d]: unknown core file note type %" PRIu32 " at offset %" PRIu64 "\n"),
+		     phndx, (uint32_t) nhdr.n_type, start + offset);
+	    else
+	      ERROR (gettext ("\
+section [%2d] '%s': unknown core file note type %" PRIu32
+			      " at offset %zu\n"),
+		     shndx, section_name (ebl, shndx),
+		     (uint32_t) nhdr.n_type, offset);
+	  }
+      else
+	switch (nhdr.n_type)
+	  {
+	  case NT_GNU_ABI_TAG:
+	  case NT_GNU_HWCAP:
+	  case NT_GNU_BUILD_ID:
+	  case NT_GNU_GOLD_VERSION:
+	    break;
+
+	  case 0:
+	    /* Linux vDSOs use a type 0 note for the kernel version word.  */
+	    if (nhdr.n_namesz == sizeof "Linux"
+		&& !memcmp (data->d_buf + name_offset, "Linux", sizeof "Linux"))
+	      break;
+	    FALLTHROUGH;
+	  default:
+	    if (shndx == 0)
+	      ERROR (gettext ("\
+phdr[%d]: unknown object file note type %" PRIu32 " at offset %zu\n"),
+		     phndx, (uint32_t) nhdr.n_type, offset);
+	    else
+	      ERROR (gettext ("\
+section [%2d] '%s': unknown object file note type %" PRIu32
+			      " at offset %zu\n"),
+		     shndx, section_name (ebl, shndx),
+		     (uint32_t) nhdr.n_type, offset);
+	  }
+    }
+
+  return last_offset;
+}
+
+
+static void
+check_note (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Phdr *phdr, int cnt)
+{
+  if (ehdr->e_type != ET_CORE && ehdr->e_type != ET_REL
+      && ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
+    ERROR (gettext ("\
+phdr[%d]: no note entries defined for the type of file\n"),
+	   cnt);
+
+  if (is_debuginfo)
+    /* The p_offset values in a separate debug file are bogus.  */
+    return;
+
+  if (phdr->p_filesz == 0)
+    return;
+
+  GElf_Off notes_size = 0;
+  Elf_Data *data = elf_getdata_rawchunk (ebl->elf,
+					 phdr->p_offset, phdr->p_filesz,
+					 ELF_T_NHDR);
+  if (data != NULL && data->d_buf != NULL)
+    notes_size = check_note_data (ebl, ehdr, data, 0, cnt, phdr->p_offset);
+
+  if (notes_size == 0)
+    ERROR (gettext ("phdr[%d]: cannot get content of note section: %s\n"),
+	   cnt, elf_errmsg (-1));
+  else if (notes_size != phdr->p_filesz)
+    ERROR (gettext ("phdr[%d]: extra %" PRIu64 " bytes after last note\n"),
+	   cnt, phdr->p_filesz - notes_size);
+}
+
+
+static void
+check_note_section (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
+{
+  if (shdr->sh_size == 0)
+    return;
+
+  Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
+  if (data == NULL || data->d_buf == NULL)
+    {
+      ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
+	     idx, section_name (ebl, idx));
+      return;
+    }
+
+  if (ehdr->e_type != ET_CORE && ehdr->e_type != ET_REL
+      && ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
+    ERROR (gettext ("\
+section [%2d] '%s': no note entries defined for the type of file\n"),
+	     idx, section_name (ebl, idx));
+
+  GElf_Off notes_size = check_note_data (ebl, ehdr, data, idx, 0, 0);
+
+  if (notes_size == 0)
+    ERROR (gettext ("section [%2d] '%s': cannot get content of note section\n"),
+	   idx, section_name (ebl, idx));
+  else if (notes_size != shdr->sh_size)
+    ERROR (gettext ("section [%2d] '%s': extra %" PRIu64
+		    " bytes after last note\n"),
+	   idx, section_name (ebl, idx), shdr->sh_size - notes_size);
+}
+
+
+/* Index of the PT_GNU_EH_FRAME program eader entry.  */
+static int pt_gnu_eh_frame_pndx;
+
+
+static void
+check_program_header (Ebl *ebl, GElf_Ehdr *ehdr)
+{
+  if (ehdr->e_phoff == 0)
+    return;
+
+  if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN
+      && ehdr->e_type != ET_CORE)
+    ERROR (gettext ("\
+only executables, shared objects, and core files can have program headers\n"));
+
+  int num_pt_interp = 0;
+  int num_pt_tls = 0;
+  int num_pt_relro = 0;
+
+  for (unsigned int cnt = 0; cnt < phnum; ++cnt)
+    {
+      GElf_Phdr phdr_mem;
+      GElf_Phdr *phdr;
+
+      phdr = gelf_getphdr (ebl->elf, cnt, &phdr_mem);
+      if (phdr == NULL)
+	{
+	  ERROR (gettext ("cannot get program header entry %d: %s\n"),
+		 cnt, elf_errmsg (-1));
+	  continue;
+	}
+
+      if (phdr->p_type >= PT_NUM && phdr->p_type != PT_GNU_EH_FRAME
+	  && phdr->p_type != PT_GNU_STACK && phdr->p_type != PT_GNU_RELRO
+	  /* Check for a known machine-specific type.  */
+	  && ebl_segment_type_name (ebl, phdr->p_type, NULL, 0) == NULL)
+	ERROR (gettext ("\
+program header entry %d: unknown program header entry type %#" PRIx64 "\n"),
+	       cnt, (uint64_t) phdr->p_type);
+
+      if (phdr->p_type == PT_LOAD)
+	has_loadable_segment = true;
+      else if (phdr->p_type == PT_INTERP)
+	{
+	  if (++num_pt_interp != 1)
+	    {
+	      if (num_pt_interp == 2)
+		ERROR (gettext ("\
+more than one INTERP entry in program header\n"));
+	    }
+	  has_interp_segment = true;
+	}
+      else if (phdr->p_type == PT_TLS)
+	{
+	  if (++num_pt_tls == 2)
+	    ERROR (gettext ("more than one TLS entry in program header\n"));
+	}
+      else if (phdr->p_type == PT_NOTE)
+	check_note (ebl, ehdr, phdr, cnt);
+      else if (phdr->p_type == PT_DYNAMIC)
+	{
+	  if (ehdr->e_type == ET_EXEC && ! has_interp_segment)
+	    ERROR (gettext ("\
+static executable cannot have dynamic sections\n"));
+	  else
+	    {
+	      /* Check that the .dynamic section, if it exists, has
+		 the same address.  */
+	      Elf_Scn *scn = NULL;
+	      while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+		{
+		  GElf_Shdr shdr_mem;
+		  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+		  if (shdr != NULL && shdr->sh_type == SHT_DYNAMIC)
+		    {
+		      if (phdr->p_offset != shdr->sh_offset)
+			ERROR (gettext ("\
+dynamic section reference in program header has wrong offset\n"));
+		      if (phdr->p_memsz != shdr->sh_size)
+			ERROR (gettext ("\
+dynamic section size mismatch in program and section header\n"));
+		      break;
+		    }
+		}
+	    }
+	}
+      else if (phdr->p_type == PT_GNU_RELRO)
+	{
+	  if (++num_pt_relro == 2)
+	    ERROR (gettext ("\
+more than one GNU_RELRO entry in program header\n"));
+	  else
+	    {
+	      /* Check that the region is in a writable segment.  */
+	      unsigned int inner;
+	      for (inner = 0; inner < phnum; ++inner)
+		{
+		  GElf_Phdr phdr2_mem;
+		  GElf_Phdr *phdr2;
+
+		  phdr2 = gelf_getphdr (ebl->elf, inner, &phdr2_mem);
+		  if (phdr2 == NULL)
+		    continue;
+
+		  if (phdr2->p_type == PT_LOAD
+		      && phdr->p_vaddr >= phdr2->p_vaddr
+		      && (phdr->p_vaddr + phdr->p_memsz
+			  <= phdr2->p_vaddr + phdr2->p_memsz))
+		    {
+		      if ((phdr2->p_flags & PF_W) == 0)
+			ERROR (gettext ("\
+loadable segment GNU_RELRO applies to is not writable\n"));
+		      /* Unless fully covered, relro flags could be a
+			 subset of the phdrs2 flags.  For example the load
+			 segment could also have PF_X set.  */
+		      if (phdr->p_vaddr == phdr2->p_vaddr
+			  && (phdr->p_vaddr + phdr->p_memsz
+			      == phdr2->p_vaddr + phdr2->p_memsz))
+			{
+			  if ((phdr2->p_flags & ~PF_W)
+			      != (phdr->p_flags & ~PF_W))
+			    ERROR (gettext ("\
+loadable segment [%u] flags do not match GNU_RELRO [%u] flags\n"),
+				   cnt, inner);
+			}
+		      else
+			{
+			  if ((phdr->p_flags & ~phdr2->p_flags) != 0)
+			    ERROR (gettext ("\
+GNU_RELRO [%u] flags are not a subset of the loadable segment [%u] flags\n"),
+				   inner, cnt);
+			}
+		      break;
+		    }
+		}
+
+	      if (inner >= phnum)
+		ERROR (gettext ("\
+%s segment not contained in a loaded segment\n"), "GNU_RELRO");
+	    }
+	}
+      else if (phdr->p_type == PT_PHDR)
+	{
+	  /* Check that the region is in a writable segment.  */
+	  unsigned int inner;
+	  for (inner = 0; inner < phnum; ++inner)
+	    {
+	      GElf_Phdr phdr2_mem;
+	      GElf_Phdr *phdr2;
+
+	      phdr2 = gelf_getphdr (ebl->elf, inner, &phdr2_mem);
+	      if (phdr2 != NULL
+		  && phdr2->p_type == PT_LOAD
+		  && phdr->p_vaddr >= phdr2->p_vaddr
+		  && (phdr->p_vaddr + phdr->p_memsz
+		      <= phdr2->p_vaddr + phdr2->p_memsz))
+		break;
+	    }
+
+	  if (inner >= phnum)
+	    ERROR (gettext ("\
+%s segment not contained in a loaded segment\n"), "PHDR");
+
+	  /* Check that offset in segment corresponds to offset in ELF
+	     header.  */
+	  if (phdr->p_offset != ehdr->e_phoff)
+	    ERROR (gettext ("\
+program header offset in ELF header and PHDR entry do not match"));
+	}
+      else if (phdr->p_type == PT_GNU_EH_FRAME)
+	{
+	  /* If there is an .eh_frame_hdr section it must be
+	     referenced by this program header entry.  */
+	  Elf_Scn *scn = NULL;
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = NULL;
+	  bool any = false;
+	  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+	    {
+	      any = true;
+	      shdr = gelf_getshdr (scn, &shdr_mem);
+	      if (shdr != NULL
+		  && shdr->sh_type == (is_debuginfo
+				       ? SHT_NOBITS : SHT_PROGBITS)
+		  && elf_strptr (ebl->elf, shstrndx, shdr->sh_name) != NULL
+		  && ! strcmp (".eh_frame_hdr",
+			       elf_strptr (ebl->elf, shstrndx, shdr->sh_name)))
+		{
+		  if (! is_debuginfo)
+		    {
+		      if (phdr->p_offset != shdr->sh_offset)
+			ERROR (gettext ("\
+call frame search table reference in program header has wrong offset\n"));
+		      if (phdr->p_memsz != shdr->sh_size)
+			ERROR (gettext ("\
+call frame search table size mismatch in program and section header\n"));
+		    }
+		  break;
+		}
+	    }
+
+	  if (scn == NULL)
+	    {
+	      /* If there is no section header table we don't
+		 complain.  But if there is one there should be an
+		 entry for .eh_frame_hdr.  */
+	      if (any)
+		ERROR (gettext ("\
+PT_GNU_EH_FRAME present but no .eh_frame_hdr section\n"));
+	    }
+	  else
+	    {
+	      /* The section must be allocated and not be writable and
+		 executable.  */
+	      if ((phdr->p_flags & PF_R) == 0)
+		ERROR (gettext ("\
+call frame search table must be allocated\n"));
+	      else if (shdr != NULL && (shdr->sh_flags & SHF_ALLOC) == 0)
+		ERROR (gettext ("\
+section [%2zu] '%s' must be allocated\n"), elf_ndxscn (scn), ".eh_frame_hdr");
+
+	      if ((phdr->p_flags & PF_W) != 0)
+		ERROR (gettext ("\
+call frame search table must not be writable\n"));
+	      else if (shdr != NULL && (shdr->sh_flags & SHF_WRITE) != 0)
+		ERROR (gettext ("\
+section [%2zu] '%s' must not be writable\n"),
+		       elf_ndxscn (scn), ".eh_frame_hdr");
+
+	      if ((phdr->p_flags & PF_X) != 0)
+		ERROR (gettext ("\
+call frame search table must not be executable\n"));
+	      else if (shdr != NULL && (shdr->sh_flags & SHF_EXECINSTR) != 0)
+		ERROR (gettext ("\
+section [%2zu] '%s' must not be executable\n"),
+		       elf_ndxscn (scn), ".eh_frame_hdr");
+	    }
+
+	  /* Remember which entry this is.  */
+	  pt_gnu_eh_frame_pndx = cnt;
+	}
+
+      if (phdr->p_filesz > phdr->p_memsz
+	  && (phdr->p_memsz != 0 || phdr->p_type != PT_NOTE))
+	ERROR (gettext ("\
+program header entry %d: file size greater than memory size\n"),
+	       cnt);
+
+      if (phdr->p_align > 1)
+	{
+	  if (!powerof2 (phdr->p_align))
+	    ERROR (gettext ("\
+program header entry %d: alignment not a power of 2\n"), cnt);
+	  else if ((phdr->p_vaddr - phdr->p_offset) % phdr->p_align != 0)
+	    ERROR (gettext ("\
+program header entry %d: file offset and virtual address not module of alignment\n"), cnt);
+	}
+    }
+}
+
+
+static void
+check_exception_data (Ebl *ebl __attribute__ ((unused)),
+		      GElf_Ehdr *ehdr __attribute__ ((unused)))
+{
+  if ((ehdr->e_type == ET_EXEC || ehdr->e_type == ET_DYN)
+      && pt_gnu_eh_frame_pndx == 0 && eh_frame_hdr_scnndx != 0)
+    ERROR (gettext ("executable/DSO with .eh_frame_hdr section does not have "
+		    "a PT_GNU_EH_FRAME program header entry"));
+}
+
+
+/* Process one file.  */
+static void
+process_elf_file (Elf *elf, const char *prefix, const char *suffix,
+		  const char *fname, size_t size, bool only_one)
+{
+  /* Reset variables.  */
+  ndynamic = 0;
+  nverneed = 0;
+  nverdef = 0;
+  textrel = false;
+  needed_textrel = false;
+  has_loadable_segment = false;
+  has_interp_segment = false;
+
+  GElf_Ehdr ehdr_mem;
+  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
+  Ebl *ebl;
+
+  /* Print the file name.  */
+  if (!only_one)
+    {
+      if (prefix != NULL)
+	printf ("\n%s(%s)%s:\n", prefix, fname, suffix);
+      else
+	printf ("\n%s:\n", fname);
+    }
+
+  if (ehdr == NULL)
+    {
+      ERROR (gettext ("cannot read ELF header: %s\n"), elf_errmsg (-1));
+      return;
+    }
+
+  ebl = ebl_openbackend (elf);
+  /* If there is no appropriate backend library we cannot test
+     architecture and OS specific features.  Any encountered extension
+     is an error.  */
+
+  /* Go straight by the gABI, check all the parts in turn.  */
+  check_elf_header (ebl, ehdr, size);
+
+  /* Check the program header.  */
+  check_program_header (ebl, ehdr);
+
+  /* Next the section headers.  It is OK if there are no section
+     headers at all.  */
+  check_sections (ebl, ehdr);
+
+  /* Check the exception handling data, if it exists.  */
+  if (pt_gnu_eh_frame_pndx != 0 || eh_frame_hdr_scnndx != 0
+      || eh_frame_scnndx != 0 || gcc_except_table_scnndx != 0)
+    check_exception_data (ebl, ehdr);
+
+  /* Report if no relocation section needed the text relocation flag.  */
+  if (textrel && !needed_textrel)
+    ERROR (gettext ("text relocation flag set but not needed\n"));
+
+  /* Free the resources.  */
+  ebl_closebackend (ebl);
+}
+
+
+#include "debugpred.h"
diff --git a/src/findtextrel.c b/src/findtextrel.c
new file mode 100644
index 0000000..8f1e239
--- /dev/null
+++ b/src/findtextrel.c
@@ -0,0 +1,611 @@
+/* Locate source files or functions which caused text relocations.
+   Copyright (C) 2005-2010, 2012, 2014 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2005.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <assert.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libdw.h>
+#include <libintl.h>
+#include <locale.h>
+#include <search.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <printversion.h>
+
+
+struct segments
+{
+  GElf_Addr from;
+  GElf_Addr to;
+};
+
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+/* Values for the parameters which have no short form.  */
+#define OPT_DEBUGINFO 0x100
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, N_("Input Selection:"), 0 },
+  { "root", 'r', "PATH", 0, N_("Prepend PATH to all file names"), 0 },
+  { "debuginfo", OPT_DEBUGINFO, "PATH", 0,
+    N_("Use PATH as root of debuginfo hierarchy"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Miscellaneous:"), 0 },
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("\
+Locate source of text relocations in FILEs (a.out by default).");
+
+/* 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, bool more_than_one);
+
+/* Check for text relocations in the given file.  The segment
+   information is known.  */
+static void check_rel (size_t nsegments, struct segments segments[nsegments],
+		       GElf_Addr addr, Elf *elf, Elf_Scn *symscn, Dwarf *dw,
+		       const char *fname, bool more_than_one,
+		       void **knownsrcs);
+
+
+
+/* User-provided root directory.  */
+static const char *rootdir = "/";
+
+/* Root of debuginfo directory hierarchy.  */
+static const char *debuginfo_root;
+
+
+int
+main (int argc, char *argv[])
+{
+  int remaining;
+  int result = 0;
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  (void) textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  (void) argp_parse (&argp, argc, argv, 0, &remaining, NULL);
+
+  /* Tell the library which version we are expecting.  */
+  elf_version (EV_CURRENT);
+
+  /* If the user has not specified the root directory for the
+     debuginfo hierarchy, we have to determine it ourselves.  */
+  if (debuginfo_root == NULL)
+    {
+      // XXX The runtime should provide this information.
+#if defined __ia64__ || defined __alpha__
+      debuginfo_root = "/usr/lib/debug";
+#else
+      debuginfo_root = (sizeof (long int) == 4
+			? "/usr/lib/debug" : "/usr/lib64/debug");
+#endif
+    }
+
+  if (remaining == argc)
+    result = process_file ("a.out", false);
+  else
+    {
+      /* Process all the remaining files.  */
+      const bool more_than_one = remaining + 1 < argc;
+
+      do
+	result |= process_file (argv[remaining], more_than_one);
+      while (++remaining < argc);
+    }
+
+  return result;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg,
+	   struct argp_state *state __attribute__ ((unused)))
+{
+  switch (key)
+    {
+    case 'r':
+      rootdir = arg;
+      break;
+
+    case OPT_DEBUGINFO:
+      debuginfo_root = arg;
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+
+static void
+noop (void *arg __attribute__ ((unused)))
+{
+}
+
+
+static int
+process_file (const char *fname, bool more_than_one)
+{
+  int result = 0;
+  void *knownsrcs = NULL;
+
+  size_t fname_len = strlen (fname);
+  size_t rootdir_len = strlen (rootdir);
+  const char *real_fname = fname;
+  if (fname[0] == '/' && (rootdir[0] != '/' || rootdir[1] != '\0'))
+    {
+      /* Prepend the user-provided root directory.  */
+      char *new_fname = alloca (rootdir_len + fname_len + 2);
+      *((char *) mempcpy (stpcpy (mempcpy (new_fname, rootdir, rootdir_len),
+				  "/"),
+			  fname, fname_len)) = '\0';
+      real_fname = new_fname;
+    }
+
+  int fd = open (real_fname, O_RDONLY);
+  if (fd == -1)
+    {
+      error (0, errno, gettext ("cannot open '%s'"), fname);
+      return 1;
+    }
+
+  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+  if (elf == NULL)
+    {
+      error (0, 0, gettext ("cannot create ELF descriptor for '%s': %s"),
+	     fname, elf_errmsg (-1));
+      goto err_close;
+    }
+
+  /* Make sure the file is a DSO.  */
+  GElf_Ehdr ehdr_mem;
+  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
+  if (ehdr == NULL)
+    {
+      error (0, 0, gettext ("cannot get ELF header '%s': %s"),
+	     fname, elf_errmsg (-1));
+    err_elf_close:
+      elf_end (elf);
+    err_close:
+      close (fd);
+      return 1;
+    }
+
+  if (ehdr->e_type != ET_DYN)
+    {
+      error (0, 0, gettext ("'%s' is not a DSO or PIE"), fname);
+      goto err_elf_close;
+    }
+
+  /* Determine whether the DSO has text relocations at all and locate
+     the symbol table.  */
+  Elf_Scn *symscn = NULL;
+  Elf_Scn *scn = NULL;
+  bool seen_dynamic = false;
+  bool have_textrel = false;
+  while ((scn = elf_nextscn (elf, scn)) != NULL
+	 && (!seen_dynamic || symscn == NULL))
+    {
+      /* Handle the section if it is a symbol table.  */
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr == NULL)
+	{
+	  error (0, 0,
+		 gettext ("getting get section header of section %zu: %s"),
+		 elf_ndxscn (scn), elf_errmsg (-1));
+	  goto err_elf_close;
+	}
+
+      switch (shdr->sh_type)
+	{
+	case SHT_DYNAMIC:
+	  if (!seen_dynamic)
+	    {
+	      seen_dynamic = true;
+
+	      Elf_Data *data = elf_getdata (scn, NULL);
+
+	      for (size_t cnt = 0; cnt < shdr->sh_size / shdr->sh_entsize;
+		   ++cnt)
+		{
+		  GElf_Dyn dynmem;
+		  GElf_Dyn *dyn;
+
+		  dyn = gelf_getdyn (data, cnt, &dynmem);
+		  if (dyn == NULL)
+		    {
+		      error (0, 0, gettext ("cannot read dynamic section: %s"),
+			     elf_errmsg (-1));
+		      goto err_elf_close;
+		    }
+
+		  if (dyn->d_tag == DT_TEXTREL
+		      || (dyn->d_tag == DT_FLAGS
+			  && (dyn->d_un.d_val & DF_TEXTREL) != 0))
+		    have_textrel = true;
+		}
+	    }
+	  break;
+
+	case SHT_SYMTAB:
+	  symscn = scn;
+	  break;
+	}
+    }
+
+  if (!have_textrel)
+    {
+      error (0, 0, gettext ("no text relocations reported in '%s'"), fname);
+      goto err_elf_close;
+    }
+
+  int fd2 = -1;
+  Elf *elf2 = NULL;
+  /* Get the address ranges for the loaded segments.  */
+  size_t nsegments_max = 10;
+  size_t nsegments = 0;
+  struct segments *segments
+    = (struct segments *) malloc (nsegments_max * sizeof (segments[0]));
+  if (segments == NULL)
+    error (1, errno, gettext ("while reading ELF file"));
+
+  size_t phnum;
+  if (elf_getphdrnum (elf, &phnum) != 0)
+    error (1, 0, gettext ("cannot get program header count: %s"),
+           elf_errmsg (-1));
+
+
+  for (size_t i = 0; i < phnum; ++i)
+    {
+      GElf_Phdr phdr_mem;
+      GElf_Phdr *phdr = gelf_getphdr (elf, i, &phdr_mem);
+      if (phdr == NULL)
+	{
+	  error (0, 0,
+		 gettext ("cannot get program header index at offset %zd: %s"),
+		 i, elf_errmsg (-1));
+	  result = 1;
+	  goto next;
+	}
+
+      if (phdr->p_type == PT_LOAD && (phdr->p_flags & PF_W) == 0)
+	{
+	  if (nsegments == nsegments_max)
+	    {
+	      nsegments_max *= 2;
+	      segments
+		= (struct segments *) realloc (segments,
+					       nsegments_max
+					       * sizeof (segments[0]));
+	      if (segments == NULL)
+		{
+		  error (0, 0, gettext ("\
+cannot get program header index at offset %zd: %s"),
+			 i, elf_errmsg (-1));
+		  result = 1;
+		  goto next;
+		}
+	    }
+
+	  segments[nsegments].from = phdr->p_vaddr;
+	  segments[nsegments].to = phdr->p_vaddr + phdr->p_memsz;
+	  ++nsegments;
+	}
+    }
+
+  if (nsegments > 0)
+    {
+
+      Dwarf *dw = dwarf_begin_elf (elf, DWARF_C_READ, NULL);
+      /* Look for debuginfo files if the information is not the in
+	 opened file itself.  This makes only sense if the input file
+	 is specified with an absolute path.  */
+      if (dw == NULL && fname[0] == '/')
+	{
+	  size_t debuginfo_rootlen = strlen (debuginfo_root);
+	  char *difname = (char *) alloca (rootdir_len + debuginfo_rootlen
+					   + fname_len + 8);
+	  strcpy (mempcpy (stpcpy (mempcpy (mempcpy (difname, rootdir,
+						     rootdir_len),
+					    debuginfo_root,
+					    debuginfo_rootlen),
+				   "/"),
+			   fname, fname_len),
+		  ".debug");
+
+	  fd2 = open (difname, O_RDONLY);
+	  if (fd2 != -1
+	      && (elf2 = elf_begin (fd2, ELF_C_READ_MMAP, NULL)) != NULL)
+	    dw = dwarf_begin_elf (elf2, DWARF_C_READ, NULL);
+	}
+
+      /* Look at all relocations and determine which modify
+	 write-protected segments.  */
+      scn = NULL;
+      while ((scn = elf_nextscn (elf, scn)) != NULL)
+	{
+	  /* Handle the section if it is a symbol table.  */
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+	  if (shdr == NULL)
+	    {
+	      error (0, 0,
+		     gettext ("cannot get section header of section %zu: %s"),
+		     elf_ndxscn (scn), elf_errmsg (-1));
+	      result = 1;
+	      goto next;
+	    }
+
+	  if ((shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA)
+	      && symscn == NULL)
+	    {
+	      symscn = elf_getscn (elf, shdr->sh_link);
+	      if (symscn == NULL)
+		{
+		  error (0, 0, gettext ("\
+cannot get symbol table section %zu in '%s': %s"),
+			 (size_t) shdr->sh_link, fname, elf_errmsg (-1));
+		  result = 1;
+		  goto next;
+		}
+	    }
+
+	  if (shdr->sh_type == SHT_REL)
+	    {
+	      Elf_Data *data = elf_getdata (scn, NULL);
+
+	      for (int cnt = 0;
+		   (size_t) cnt < shdr->sh_size / shdr->sh_entsize;
+		   ++cnt)
+		{
+		  GElf_Rel rel_mem;
+		  GElf_Rel *rel = gelf_getrel (data, cnt, &rel_mem);
+		  if (rel == NULL)
+		    {
+		      error (0, 0, gettext ("\
+cannot get relocation at index %d in section %zu in '%s': %s"),
+			     cnt, elf_ndxscn (scn), fname, elf_errmsg (-1));
+		      result = 1;
+		      goto next;
+		    }
+
+		  check_rel (nsegments, segments, rel->r_offset, elf,
+			     symscn, dw, fname, more_than_one, &knownsrcs);
+		}
+	    }
+	  else if (shdr->sh_type == SHT_RELA)
+	    {
+	      Elf_Data *data = elf_getdata (scn, NULL);
+
+	      for (int cnt = 0;
+		   (size_t) cnt < shdr->sh_size / shdr->sh_entsize;
+		   ++cnt)
+		{
+		  GElf_Rela rela_mem;
+		  GElf_Rela *rela = gelf_getrela (data, cnt, &rela_mem);
+		  if (rela == NULL)
+		    {
+		      error (0, 0, gettext ("\
+cannot get relocation at index %d in section %zu in '%s': %s"),
+			     cnt, elf_ndxscn (scn), fname, elf_errmsg (-1));
+		      result = 1;
+		      goto next;
+		    }
+
+		  check_rel (nsegments, segments, rela->r_offset, elf,
+			     symscn, dw, fname, more_than_one, &knownsrcs);
+		}
+	    }
+	}
+
+      dwarf_end (dw);
+    }
+
+ next:
+  elf_end (elf);
+  elf_end (elf2);
+  close (fd);
+  if (fd2 != -1)
+    close (fd2);
+
+  free (segments);
+  tdestroy (knownsrcs, noop);
+
+  return result;
+}
+
+
+static int
+ptrcompare (const void *p1, const void *p2)
+{
+  if ((uintptr_t) p1 < (uintptr_t) p2)
+    return -1;
+  if ((uintptr_t) p1 > (uintptr_t) p2)
+    return 1;
+  return 0;
+}
+
+
+static void
+check_rel (size_t nsegments, struct segments segments[nsegments],
+	   GElf_Addr addr, Elf *elf, Elf_Scn *symscn, Dwarf *dw,
+	   const char *fname, bool more_than_one, void **knownsrcs)
+{
+  for (size_t cnt = 0; cnt < nsegments; ++cnt)
+    if (segments[cnt].from <= addr && segments[cnt].to > addr)
+      {
+	Dwarf_Die die_mem;
+	Dwarf_Die *die;
+	Dwarf_Line *line;
+	const char *src;
+
+	if (more_than_one)
+	  printf ("%s: ", fname);
+
+	if ((die = dwarf_addrdie (dw, addr, &die_mem)) != NULL
+	    && (line = dwarf_getsrc_die (die, addr)) != NULL
+	    && (src = dwarf_linesrc (line, NULL, NULL)) != NULL)
+	  {
+	    /* There can be more than one relocation against one file.
+	       Try to avoid multiple messages.  And yes, the code uses
+	       pointer comparison.  */
+	    if (tfind (src, knownsrcs, ptrcompare) == NULL)
+	      {
+		printf (gettext ("%s not compiled with -fpic/-fPIC\n"), src);
+		tsearch (src, knownsrcs, ptrcompare);
+	      }
+	    return;
+	  }
+	else
+	  {
+	    /* At least look at the symbol table to see which function
+	       the modified address is in.  */
+	    Elf_Data *symdata = elf_getdata (symscn, NULL);
+	    GElf_Shdr shdr_mem;
+	    GElf_Shdr *shdr = gelf_getshdr (symscn, &shdr_mem);
+	    if (shdr != NULL)
+	      {
+		GElf_Addr lowaddr = 0;
+		int lowidx = -1;
+		GElf_Addr highaddr = ~0ul;
+		int highidx = -1;
+		GElf_Sym sym_mem;
+		GElf_Sym *sym;
+
+		for (int i = 0; (size_t) i < shdr->sh_size / shdr->sh_entsize;
+		     ++i)
+		  {
+		    sym = gelf_getsym (symdata, i, &sym_mem);
+		    if (sym == NULL)
+		      continue;
+
+		    if (sym->st_value < addr && sym->st_value > lowaddr)
+		      {
+			lowaddr = sym->st_value;
+			lowidx = i;
+		      }
+		    if (sym->st_value > addr && sym->st_value < highaddr)
+		      {
+			highaddr = sym->st_value;
+			highidx = i;
+		      }
+		  }
+
+		if (lowidx != -1)
+		  {
+		    sym = gelf_getsym (symdata, lowidx, &sym_mem);
+		    assert (sym != NULL);
+
+		    const char *lowstr = elf_strptr (elf, shdr->sh_link,
+						     sym->st_name);
+
+		    if (sym->st_value + sym->st_size > addr)
+		      {
+			/* It is this function.  */
+			if (tfind (lowstr, knownsrcs, ptrcompare) == NULL)
+			  {
+			    printf (gettext ("\
+the file containing the function '%s' is not compiled with -fpic/-fPIC\n"),
+				    lowstr);
+			    tsearch (lowstr, knownsrcs, ptrcompare);
+			  }
+		      }
+		    else if (highidx == -1)
+		      printf (gettext ("\
+the file containing the function '%s' might not be compiled with -fpic/-fPIC\n"),
+			      lowstr);
+		    else
+		      {
+			sym = gelf_getsym (symdata, highidx, &sym_mem);
+			assert (sym != NULL);
+
+			printf (gettext ("\
+either the file containing the function '%s' or the file containing the function '%s' is not compiled with -fpic/-fPIC\n"),
+				lowstr, elf_strptr (elf, shdr->sh_link,
+						    sym->st_name));
+		      }
+		    return;
+		  }
+		else if (highidx != -1)
+		  {
+		    sym = gelf_getsym (symdata, highidx, &sym_mem);
+		    assert (sym != NULL);
+
+		    printf (gettext ("\
+the file containing the function '%s' might not be compiled with -fpic/-fPIC\n"),
+			    elf_strptr (elf, shdr->sh_link, sym->st_name));
+		    return;
+		  }
+	      }
+	  }
+
+	printf (gettext ("\
+a relocation modifies memory at offset %llu in a write-protected segment\n"),
+		(unsigned long long int) addr);
+	break;
+      }
+}
+
+
+#include "debugpred.h"
diff --git a/src/make-debug-archive.in b/src/make-debug-archive.in
new file mode 100644
index 0000000..c3fcbce
--- /dev/null
+++ b/src/make-debug-archive.in
@@ -0,0 +1,132 @@
+#!/bin/sh
+#
+# Script to make an offline archive for debugging with libdwfl-based tools.
+#
+#	make-debug-archive ARCHIVE {options}
+#	make-debug-archive --kernel [--force] [RELEASE]
+#
+# Valid options are those listed under 'Input selection options'
+# by running @UNSTRIP@ --help.
+#
+# The archive installed by --kernel be used automatically by -K.
+# An offline archive can be used via -e in any tool that accepts those options.
+#
+
+UNSTRIP=${UNSTRIP:-@UNSTRIP@}
+AR=${AR:-@AR@}
+SUDO=${SUDO:-/usr/bin/sudo}
+
+LS=/bin/ls
+RM=/bin/rm
+MV=/bin/mv
+MKDIR=/bin/mkdir
+XARGS=/usr/bin/xargs
+
+outdir=${TMPDIR:-/tmp}/debugar$$
+
+usage()
+{
+  echo "Usage: $0 ARCHIVE {options}"
+  echo "   or: $0 --kernel [--sudo] [--force] [RELEASE]"
+  echo
+  echo "Valid options are listed under 'Input selection options'"
+  echo "when running: $UNSTRIP --help"
+  echo
+  echo "The --kernel form updates the file used by -K if the"
+  echo "kernel installation has changed, or always with --force."
+  echo "With --sudo, touches the installed file via $SUDO."
+}
+
+fatal_usage()
+{
+  usage >&2
+  exit 2
+}
+
+script_version()
+{
+  echo "`basename $0` (@PACKAGE_NAME@) @PACKAGE_VERSION@"
+  echo "Copyright (C) 2007 Red Hat, Inc."
+  echo "This is free software; see the source for copying conditions."
+  echo "There is NO warranty; not even for MERCHANTABILITY or"
+  echo "FITNESS FOR A PARTICULAR PURPOSE."
+  echo "Written by Roland McGrath."
+}
+
+sudo=
+kernel=no
+force_kernel=no
+while [ $# -gt 0 ]; do
+  case "x$1" in
+  x--help) usage; exit 0 ;;
+  x--version) script_version; exit 0 ;;
+  x--kernel) kernel=yes ;;
+  x--force) force_kernel=yes ;;
+  x--sudo) sudo=$SUDO ;;
+  *) break ;;
+  esac
+  shift
+done
+
+if [ $kernel = no ] && [ $force_kernel = yes -o -n "$sudo" ]; then
+  usage
+fi
+
+if [ $kernel = yes ]; then
+  if [ $# -eq 0 ]; then
+    release=`uname -r`
+  elif [ $# -eq 1 ]; then
+    release=$1
+  else
+    fatal_usage
+  fi
+
+  dir=/usr/lib/debug/lib/modules/$release
+  archive=$dir/debug.a
+  dep=/lib/modules/$release/modules.dep
+
+  if [ ! -d $dir ]; then
+    echo >&2 "$0: $dir not installed"
+    exit 1
+  fi
+
+  # Without --force, bail if the kernel installation is not newer.
+  # This file is normally touched by installing new kernels or modules.
+  if [ $force_kernel = no -a "$archive" -nt "$dep" ]; then
+    exit 0
+  fi
+
+  # We have to kill the old one first, because our own -K would use it.
+  [ ! -e "$archive" ] || $sudo $RM -f "$archive" || exit
+
+  set "$archive" "-K$release"
+fi
+
+if [ $# -lt 2 ]; then
+  fatal_usage
+fi
+
+archive="$1"
+shift
+
+case "$archive" in
+/*) ;;
+*) archive="`/bin/pwd`/$archive" ;;
+esac
+
+if [ -z "$sudo" ]; then
+  new_archive="$archive.new"
+else
+  new_archive="$outdir.a"
+fi
+
+$RM -f "$new_archive" || exit
+
+trap '$RM -rf "$outdir" "$new_archive"' 0 1 2 15
+
+$MKDIR "$outdir" &&
+$UNSTRIP -d "$outdir" -m -a -R "$@" &&
+(cd "$outdir" && $LS | $XARGS $AR cq "$new_archive") &&
+$sudo $MV -f "$new_archive" "$archive"
+
+exit
diff --git a/src/nm.c b/src/nm.c
new file mode 100644
index 0000000..969c6d3
--- /dev/null
+++ b/src/nm.c
@@ -0,0 +1,1599 @@
+/* Print symbol information from ELF file in human-readable form.
+   Copyright (C) 2000-2008, 2009, 2011, 2012, 2014, 2015 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2000.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <ar.h>
+#include <argp.h>
+#include <assert.h>
+#include <ctype.h>
+#include <dwarf.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <inttypes.h>
+#include <libdw.h>
+#include <libintl.h>
+#include <locale.h>
+#include <obstack.h>
+#include <search.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libeu.h>
+#include <system.h>
+#include <color.h>
+#include <printversion.h>
+#include "../libebl/libeblP.h"
+#include "../libdwfl/libdwflP.h"
+
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+
+/* Values for the parameters which have no short form.  */
+#define OPT_DEFINED		0x100
+#define OPT_MARK_SPECIAL	0x101
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, N_("Output selection:"), 0 },
+  { "debug-syms", 'a', NULL, 0, N_("Display debugger-only symbols"), 0 },
+  { "defined-only", OPT_DEFINED, NULL, 0, N_("Display only defined symbols"),
+    0 },
+  { "dynamic", 'D', NULL, 0,
+    N_("Display dynamic symbols instead of normal symbols"), 0 },
+  { "extern-only", 'g', NULL, 0, N_("Display only external symbols"), 0 },
+  { "undefined-only", 'u', NULL, 0, N_("Display only undefined symbols"), 0 },
+  { "print-armap", 's', NULL, 0,
+    N_("Include index for symbols from archive members"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Output format:"), 0 },
+  { "print-file-name", 'A', NULL, 0,
+    N_("Print name of the input file before every symbol"), 0 },
+  { NULL, 'o', NULL, OPTION_HIDDEN, "Same as -A", 0 },
+  { "format", 'f', "FORMAT", 0,
+    N_("Use the output format FORMAT.  FORMAT can be `bsd', `sysv' or `posix'.  The default is `sysv'"),
+    0 },
+  { NULL, 'B', NULL, 0, N_("Same as --format=bsd"), 0 },
+  { "portability", 'P', NULL, 0, N_("Same as --format=posix"), 0 },
+  { "radix", 't', "RADIX", 0, N_("Use RADIX for printing symbol values"), 0 },
+  { "mark-special", OPT_MARK_SPECIAL, NULL, 0, N_("Mark special symbols"), 0 },
+  { "mark-weak", OPT_MARK_SPECIAL, NULL, OPTION_HIDDEN, "", 0 },
+  { "print-size", 'S', NULL, 0, N_("Print size of defined symbols"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Output options:"), 0 },
+  { "numeric-sort", 'n', NULL, 0, N_("Sort symbols numerically by address"),
+    0 },
+  { "no-sort", 'p', NULL, 0, N_("Do not sort the symbols"), 0 },
+  { "reverse-sort", 'r', NULL, 0, N_("Reverse the sense of the sort"), 0 },
+#ifdef USE_DEMANGLE
+  { "demangle", 'C', NULL, 0,
+    N_("Decode low-level symbol names into source code names"), 0 },
+#endif
+  { NULL, 0, NULL, 0, N_("Miscellaneous:"), 0 },
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("List symbols from FILEs (a.out by default).");
+
+/* 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);
+
+/* Parser children.  */
+static struct argp_child argp_children[] =
+  {
+    { &color_argp, 0, N_("Output formatting"), 2 },
+    { NULL, 0, NULL, 0}
+  };
+
+/* Data structure to communicate with argp functions.  */
+static struct argp argp =
+{
+  options, parse_opt, args_doc, doc, argp_children, NULL, NULL
+};
+
+
+/* Print symbols in file named FNAME.  */
+static int process_file (const char *fname, bool more_than_one);
+
+/* Handle content of archive.  */
+static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname,
+		      const char *suffix);
+
+/* Handle ELF file.  */
+static int handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
+		       const char *suffix);
+
+
+#define INTERNAL_ERROR(fname) \
+  error (EXIT_FAILURE, 0, gettext ("%s: INTERNAL ERROR %d (%s): %s"),      \
+	 fname, __LINE__, PACKAGE_VERSION, elf_errmsg (-1))
+
+
+/* Internal representation of symbols.  */
+typedef struct GElf_SymX
+{
+  GElf_Sym sym;
+  Elf32_Word xndx;
+  char *where;
+} GElf_SymX;
+
+
+/* User-selectable options.  */
+
+/* The selected output format.  */
+static enum
+{
+  format_sysv = 0,
+  format_bsd,
+  format_posix
+} format;
+
+/* Print defined, undefined, or both?  */
+static bool hide_undefined;
+static bool hide_defined;
+
+/* Print local symbols also?  */
+static bool hide_local;
+
+/* Nonzero if full filename should precede every symbol.  */
+static bool print_file_name;
+
+/* If true print size of defined symbols in BSD format.  */
+static bool print_size;
+
+/* If true print archive index.  */
+static bool print_armap;
+
+/* If true reverse sorting.  */
+static bool reverse_sort;
+
+#ifdef USE_DEMANGLE
+/* If true demangle symbols.  */
+static bool demangle;
+#endif
+
+/* Type of the section we are printing.  */
+static GElf_Word symsec_type = SHT_SYMTAB;
+
+/* Sorting selection.  */
+static enum
+{
+  sort_name = 0,
+  sort_numeric,
+  sort_nosort
+} sort;
+
+/* Radix for printed numbers.  */
+static enum
+{
+  radix_hex = 0,
+  radix_decimal,
+  radix_octal
+} radix;
+
+/* If nonzero mark special symbols:
+   - weak symbols are distinguished from global symbols by adding
+     a `*' after the identifying letter for the symbol class and type.
+   - TLS symbols are distinguished from normal symbols by adding
+     a '@' after the identifying letter for the symbol class and type.  */
+static bool mark_special;
+
+
+int
+main (int argc, char *argv[])
+{
+  int remaining;
+  int result = 0;
+
+  /* We use no threads here which can interfere with handling a stream.  */
+  (void) __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+  (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+  (void) __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  (void) textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  (void) argp_parse (&argp, argc, argv, 0, &remaining, NULL);
+
+  /* Tell the library which version we are expecting.  */
+  (void) elf_version (EV_CURRENT);
+
+  if (remaining == argc)
+    /* The user didn't specify a name so we use a.out.  */
+    result = process_file ("a.out", false);
+  else
+    {
+      /* Process all the remaining files.  */
+      const bool more_than_one = remaining + 1 < argc;
+
+      do
+	result |= process_file (argv[remaining], more_than_one);
+      while (++remaining < argc);
+    }
+
+  return result;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg,
+	   struct argp_state *state __attribute__ ((unused)))
+{
+  switch (key)
+    {
+    case 'a':
+      /* XXX */
+      break;
+
+#ifdef USE_DEMANGLE
+    case 'C':
+      demangle = true;
+      break;
+#endif
+
+    case 'f':
+      if (strcmp (arg, "bsd") == 0)
+	format = format_bsd;
+      else if (strcmp (arg, "posix") == 0)
+	format = format_posix;
+      else
+	/* Be bug compatible.  The BFD implementation also defaulted to
+	   using the SysV format if nothing else matches.  */
+	format = format_sysv;
+      break;
+
+    case 'g':
+      hide_local = true;
+      break;
+
+    case 'n':
+      sort = sort_numeric;
+      break;
+
+    case 'p':
+      sort = sort_nosort;
+      break;
+
+    case 't':
+      if (strcmp (arg, "10") == 0 || strcmp (arg, "d") == 0)
+	radix = radix_decimal;
+      else if (strcmp (arg, "8") == 0 || strcmp (arg, "o") == 0)
+	radix = radix_octal;
+      else
+	radix = radix_hex;
+      break;
+
+    case 'u':
+      hide_undefined = false;
+      hide_defined = true;
+      break;
+
+    case 'A':
+    case 'o':
+      print_file_name = true;
+      break;
+
+    case 'B':
+      format = format_bsd;
+      break;
+
+    case 'D':
+      symsec_type = SHT_DYNSYM;
+      break;
+
+    case 'P':
+      format = format_posix;
+      break;
+
+    case OPT_DEFINED:
+      hide_undefined = true;
+      hide_defined = false;
+      break;
+
+    case OPT_MARK_SPECIAL:
+      mark_special = true;
+      break;
+
+    case 'S':
+      print_size = true;
+      break;
+
+    case 's':
+      print_armap = true;
+      break;
+
+    case 'r':
+      reverse_sort = true;
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+
+/* Open the file and determine the type.  */
+static int
+process_file (const char *fname, bool more_than_one)
+{
+  /* Open the file.  */
+  int fd = open (fname, O_RDONLY);
+  if (fd == -1)
+    {
+      error (0, errno, gettext ("cannot open '%s'"), fname);
+      return 1;
+    }
+
+  /* Now get the ELF descriptor.  */
+  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+  if (elf != NULL)
+    {
+      if (elf_kind (elf) == ELF_K_ELF)
+	{
+	  int result = handle_elf (fd, elf, more_than_one ? "" : NULL,
+				   fname, NULL);
+
+	  if (elf_end (elf) != 0)
+	    INTERNAL_ERROR (fname);
+
+	  if (close (fd) != 0)
+	    error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
+
+	  return result;
+	}
+      else if (elf_kind (elf) == ELF_K_AR)
+	{
+	  int result = handle_ar (fd, elf, NULL, fname, NULL);
+
+	  if (elf_end (elf) != 0)
+	    INTERNAL_ERROR (fname);
+
+	  if (close (fd) != 0)
+	    error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
+
+	  return result;
+	}
+
+      /* We cannot handle this type.  Close the descriptor anyway.  */
+      if (elf_end (elf) != 0)
+	INTERNAL_ERROR (fname);
+    }
+
+  error (0, 0, gettext ("%s: File format not recognized"), fname);
+
+  return 1;
+}
+
+
+static int
+handle_ar (int fd, Elf *elf, const char *prefix, const char *fname,
+	   const char *suffix)
+{
+  size_t fname_len = strlen (fname) + 1;
+  size_t prefix_len = prefix != NULL ? strlen (prefix) : 0;
+  char new_prefix[prefix_len + fname_len + 2];
+  size_t suffix_len = suffix != NULL ? strlen (suffix) : 0;
+  char new_suffix[suffix_len + 2];
+  Elf *subelf;
+  Elf_Cmd cmd = ELF_C_READ_MMAP;
+  int result = 0;
+
+  char *cp = new_prefix;
+  if (prefix != NULL)
+    cp = stpcpy (cp, prefix);
+  cp = stpcpy (cp, fname);
+  stpcpy (cp, "[");
+
+  cp = new_suffix;
+  if (suffix != NULL)
+    cp = stpcpy (cp, suffix);
+  stpcpy (cp, "]");
+
+  /* First print the archive index if this is wanted.  */
+  if (print_armap)
+    {
+      Elf_Arsym *arsym = elf_getarsym (elf, NULL);
+
+      if (arsym != NULL)
+	{
+	  Elf_Arhdr *arhdr = NULL;
+	  size_t arhdr_off = 0;	/* Note: 0 is no valid offset.  */
+
+	  fputs_unlocked (gettext("\nArchive index:\n"), stdout);
+
+	  while (arsym->as_off != 0)
+	    {
+	      if (arhdr_off != arsym->as_off
+		  && (elf_rand (elf, arsym->as_off) != arsym->as_off
+		      || (subelf = elf_begin (fd, cmd, elf)) == NULL
+		      || (arhdr = elf_getarhdr (subelf)) == NULL))
+		{
+		  error (0, 0, gettext ("invalid offset %zu for symbol %s"),
+			 arsym->as_off, arsym->as_name);
+		  break;
+		}
+
+	      printf (gettext ("%s in %s\n"), arsym->as_name, arhdr->ar_name);
+
+	      ++arsym;
+	    }
+
+	  if (elf_rand (elf, SARMAG) != SARMAG)
+	    {
+	      error (0, 0,
+		     gettext ("cannot reset archive offset to beginning"));
+	      return 1;
+	    }
+	}
+    }
+
+  /* Process all the files contained in the archive.  */
+  while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
+    {
+      /* The the header for this element.  */
+      Elf_Arhdr *arhdr = elf_getarhdr (subelf);
+
+      /* Skip over the index entries.  */
+      if (strcmp (arhdr->ar_name, "/") != 0
+	  && strcmp (arhdr->ar_name, "//") != 0
+	  && strcmp (arhdr->ar_name, "/SYM64/") != 0)
+	{
+	  if (elf_kind (subelf) == ELF_K_ELF)
+	    result |= handle_elf (fd, subelf, new_prefix, arhdr->ar_name,
+				  new_suffix);
+	  else if (elf_kind (subelf) == ELF_K_AR)
+	    result |= handle_ar (fd, subelf, new_prefix, arhdr->ar_name,
+				 new_suffix);
+	  else
+	    {
+	      error (0, 0, gettext ("%s%s%s: file format not recognized"),
+		     new_prefix, arhdr->ar_name, new_suffix);
+	      result = 1;
+	    }
+	}
+
+      /* Get next archive element.  */
+      cmd = elf_next (subelf);
+      if (elf_end (subelf) != 0)
+	INTERNAL_ERROR (fname);
+    }
+
+  return result;
+}
+
+
+/* Mapping of radix and binary class to length.  */
+static const int length_map[2][3] =
+{
+  [ELFCLASS32 - 1] =
+  {
+    [radix_hex] = 8,
+    [radix_decimal] = 10,
+    [radix_octal] = 11
+  },
+  [ELFCLASS64 - 1] =
+  {
+    [radix_hex] = 16,
+    [radix_decimal] = 20,
+    [radix_octal] = 22
+  }
+};
+
+
+static int
+global_compare (const void *p1, const void *p2)
+{
+  const Dwarf_Global *g1 = (const Dwarf_Global *) p1;
+  const Dwarf_Global *g2 = (const Dwarf_Global *) p2;
+
+  return strcmp (g1->name, g2->name);
+}
+
+
+static void *global_root;
+
+
+static int
+get_global (Dwarf *dbg __attribute__ ((unused)), Dwarf_Global *global,
+	    void *arg __attribute__ ((unused)))
+{
+  tsearch (memcpy (xmalloc (sizeof (Dwarf_Global)), global,
+		   sizeof (Dwarf_Global)),
+	   &global_root, global_compare);
+
+  return DWARF_CB_OK;
+}
+
+
+struct local_name
+{
+  const char *name;
+  const char *file;
+  Dwarf_Word lineno;
+  Dwarf_Addr lowpc;
+  Dwarf_Addr highpc;
+};
+
+
+static int
+local_compare (const void *p1, const void *p2)
+{
+  struct local_name *g1 = (struct local_name *) p1;
+  struct local_name *g2 = (struct local_name *) p2;
+  int result;
+
+  result = strcmp (g1->name, g2->name);
+  if (result == 0)
+    {
+      if (g1->lowpc <= g2->lowpc && g1->highpc >= g2->highpc)
+	{
+	  /* g2 is contained in g1.  Update the data.  */
+	  g2->lowpc = g1->lowpc;
+	  g2->highpc = g1->highpc;
+	  result = 0;
+	}
+      else if (g2->lowpc <= g1->lowpc && g2->highpc >= g1->highpc)
+	{
+	  /* g1 is contained in g2.  Update the data.  */
+	  g1->lowpc = g2->lowpc;
+	  g1->highpc = g2->highpc;
+	  result = 0;
+	}
+      else
+	result = g1->lowpc < g2->lowpc ? -1 : 1;
+    }
+
+  return result;
+}
+
+
+static int
+get_var_range (Dwarf_Die *die, Dwarf_Word *lowpc, Dwarf_Word *highpc)
+{
+  Dwarf_Attribute locattr_mem;
+  Dwarf_Attribute *locattr = dwarf_attr (die, DW_AT_location, &locattr_mem);
+  if  (locattr == NULL)
+    return 1;
+
+  Dwarf_Op *loc;
+  size_t nloc;
+  if (dwarf_getlocation (locattr, &loc, &nloc) != 0)
+    return 1;
+
+  /* Interpret the location expressions.  */
+  // XXX For now just the simple one:
+  if (nloc == 1 && loc[0].atom == DW_OP_addr)
+    {
+      *lowpc = *highpc = loc[0].number;
+      return 0;
+    }
+
+  return 1;
+}
+
+
+
+static void *local_root;
+
+
+static void
+get_local_names (Dwarf *dbg)
+{
+  Dwarf_Off offset = 0;
+  Dwarf_Off old_offset;
+  size_t hsize;
+
+  while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL,
+		       NULL) == 0)
+    {
+      Dwarf_Die cudie_mem;
+      Dwarf_Die *cudie = dwarf_offdie (dbg, old_offset + hsize, &cudie_mem);
+
+      /* If we cannot get the CU DIE there is no need to go on with
+	 this CU.  */
+      if (cudie == NULL)
+	continue;
+      /* This better be a CU DIE.  */
+      if (dwarf_tag (cudie) != DW_TAG_compile_unit)
+	continue;
+
+      /* Get the line information.  */
+      Dwarf_Files *files;
+      size_t nfiles;
+      if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
+	continue;
+
+      Dwarf_Die die_mem;
+      Dwarf_Die *die = &die_mem;
+      if (dwarf_child (cudie, die) == 0)
+	/* Iterate over all immediate children of the CU DIE.  */
+	do
+	  {
+	    int tag = dwarf_tag (die);
+	    if (tag != DW_TAG_subprogram && tag != DW_TAG_variable)
+	      continue;
+
+	    /* We are interested in five attributes: name, decl_file,
+	       decl_line, low_pc, and high_pc.  */
+	    Dwarf_Attribute attr_mem;
+	    Dwarf_Attribute *attr = dwarf_attr (die, DW_AT_name, &attr_mem);
+	    const char *name = dwarf_formstring (attr);
+	    if (name == NULL)
+	      continue;
+
+	    Dwarf_Word fileidx;
+	    attr = dwarf_attr (die, DW_AT_decl_file, &attr_mem);
+	    if (dwarf_formudata (attr, &fileidx) != 0 || fileidx >= nfiles)
+	      continue;
+
+	    Dwarf_Word lineno;
+	    attr = dwarf_attr (die, DW_AT_decl_line, &attr_mem);
+	    if (dwarf_formudata (attr, &lineno) != 0 || lineno == 0)
+	      continue;
+
+	    Dwarf_Addr lowpc;
+	    Dwarf_Addr highpc;
+	    if (tag == DW_TAG_subprogram)
+	      {
+		if (dwarf_lowpc (die, &lowpc) != 0
+		    || dwarf_highpc (die, &highpc) != 0)
+		  continue;
+	      }
+	    else
+	      {
+		if (get_var_range (die, &lowpc, &highpc) != 0)
+		  continue;
+	      }
+
+	    /* We have all the information.  Create a record.  */
+	    struct local_name *newp
+	      = (struct local_name *) xmalloc (sizeof (*newp));
+	    newp->name = name;
+	    newp->file = dwarf_filesrc (files, fileidx, NULL, NULL);
+	    newp->lineno = lineno;
+	    newp->lowpc = lowpc;
+	    newp->highpc = highpc;
+
+	   /* Check whether a similar local_name is already in the
+	      cache.  That should not happen.  But if it does, we
+	      don't want to leak memory.  */
+	    struct local_name **tres = tsearch (newp, &local_root,
+						local_compare);
+	    if (tres == NULL)
+              error (EXIT_FAILURE, errno,
+                     gettext ("cannot create search tree"));
+	    else if (*tres != newp)
+	      free (newp);
+	  }
+	while (dwarf_siblingof (die, die) == 0);
+    }
+}
+
+/* Do elf_strptr, but return a backup string and never NULL.  */
+static const char *
+sym_name (Elf *elf, GElf_Word strndx, GElf_Word st_name, char buf[], size_t n)
+{
+  const char *symstr = elf_strptr (elf, strndx, st_name);
+  if (symstr == NULL)
+    {
+      snprintf (buf, n, "[invalid st_name %#" PRIx32 "]", st_name);
+      symstr = buf;
+    }
+  return symstr;
+}
+
+/* Show symbols in SysV format.  */
+static void
+show_symbols_sysv (Ebl *ebl, GElf_Word strndx, const char *fullname,
+		   GElf_SymX *syms, size_t nsyms, int longest_name,
+		   int longest_where)
+{
+  size_t shnum;
+  if (elf_getshdrnum (ebl->elf, &shnum) < 0)
+    INTERNAL_ERROR (fullname);
+
+  bool scnnames_malloced = shnum * sizeof (const char *) > 128 * 1024;
+  const char **scnnames;
+  if (scnnames_malloced)
+    scnnames = (const char **) xmalloc (sizeof (const char *) * shnum);
+  else
+    scnnames = (const char **) alloca (sizeof (const char *) * shnum);
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0)
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  /* Cache the section names.  */
+  Elf_Scn *scn = NULL;
+  size_t cnt = 1;
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+
+      assert (elf_ndxscn (scn) == cnt);
+      cnt++;
+
+      char *name = elf_strptr (ebl->elf, shstrndx,
+			       gelf_getshdr (scn, &shdr_mem)->sh_name);
+      if (unlikely (name == NULL))
+	{
+	  const size_t bufsz = sizeof "[invalid sh_name 0x12345678]";
+	  name = alloca (bufsz);
+	  snprintf (name, bufsz, "[invalid sh_name %#" PRIx32 "]",
+		    gelf_getshdr (scn, &shdr_mem)->sh_name);
+	}
+      scnnames[elf_ndxscn (scn)] = name;
+    }
+
+  int digits = length_map[gelf_getclass (ebl->elf) - 1][radix];
+
+  /* We always print this prolog.  */
+  printf (gettext ("\n\nSymbols from %s:\n\n"), fullname);
+
+  /* The header line.  */
+  printf (gettext ("%*s%-*s %-*s Class  Type     %-*s %*s Section\n\n"),
+	  print_file_name ? (int) strlen (fullname) + 1: 0, "",
+	  longest_name, sgettext ("sysv|Name"),
+	  /* TRANS: the "sysv|" parts makes the string unique.  */
+	  digits, sgettext ("sysv|Value"),
+	  /* TRANS: the "sysv|" parts makes the string unique.  */
+	  digits, sgettext ("sysv|Size"),
+	  /* TRANS: the "sysv|" parts makes the string unique.  */
+	  longest_where, sgettext ("sysv|Line"));
+
+#ifdef USE_DEMANGLE
+  size_t demangle_buffer_len = 0;
+  char *demangle_buffer = NULL;
+#endif
+
+  /* Iterate over all symbols.  */
+  for (cnt = 1; cnt < nsyms; ++cnt)
+    {
+      /* In this format SECTION entries are not printed.  */
+      if (GELF_ST_TYPE (syms[cnt].sym.st_info) == STT_SECTION)
+	continue;
+
+      char symstrbuf[50];
+      const char *symstr = sym_name (ebl->elf, strndx, syms[cnt].sym.st_name,
+				     symstrbuf, sizeof symstrbuf);
+
+#ifdef USE_DEMANGLE
+      /* Demangle if necessary.  Require GNU v3 ABI by the "_Z" prefix.  */
+      if (demangle && symstr[0] == '_' && symstr[1] == 'Z')
+	{
+	  int status = -1;
+	  char *dmsymstr = __cxa_demangle (symstr, demangle_buffer,
+					   &demangle_buffer_len, &status);
+
+	  if (status == 0)
+	    symstr = dmsymstr;
+	}
+#endif
+
+      char symbindbuf[50];
+      char symtypebuf[50];
+      char secnamebuf[1024];
+      char addressbuf[(64 + 2) / 3 + 1];
+      char sizebuf[(64 + 2) / 3 + 1];
+
+      /* If we have to precede the line with the file name.  */
+      if (print_file_name)
+	{
+	  fputs_unlocked (fullname, stdout);
+	  putchar_unlocked (':');
+	}
+
+      /* Covert the address.  */
+      if (syms[cnt].sym.st_shndx == SHN_UNDEF)
+	addressbuf[0] = sizebuf[0] = '\0';
+      else
+	{
+	  snprintf (addressbuf, sizeof (addressbuf),
+		    (radix == radix_hex ? "%0*" PRIx64
+		     : (radix == radix_decimal ? "%0*" PRId64
+			: "%0*" PRIo64)),
+		    digits, syms[cnt].sym.st_value);
+	  snprintf (sizebuf, sizeof (sizebuf),
+		    (radix == radix_hex ? "%0*" PRIx64
+		     : (radix == radix_decimal ? "%0*" PRId64
+			: "%0*" PRIo64)),
+		    digits, syms[cnt].sym.st_size);
+	}
+
+      /* Print the actual string.  */
+      printf ("%-*s|%s|%-6s|%-8s|%s|%*s|%s\n",
+	      longest_name, symstr, addressbuf,
+	      ebl_symbol_binding_name (ebl,
+				       GELF_ST_BIND (syms[cnt].sym.st_info),
+				       symbindbuf, sizeof (symbindbuf)),
+	      ebl_symbol_type_name (ebl, GELF_ST_TYPE (syms[cnt].sym.st_info),
+				    symtypebuf, sizeof (symtypebuf)),
+	      sizebuf, longest_where, syms[cnt].where,
+	      ebl_section_name (ebl, syms[cnt].sym.st_shndx, syms[cnt].xndx,
+				secnamebuf, sizeof (secnamebuf), scnnames,
+				shnum));
+    }
+
+#ifdef USE_DEMANGLE
+  free (demangle_buffer);
+#endif
+
+  if (scnnames_malloced)
+    free (scnnames);
+}
+
+
+static char
+class_type_char (Elf *elf, const GElf_Ehdr *ehdr, GElf_Sym *sym)
+{
+  int local_p = GELF_ST_BIND (sym->st_info) == STB_LOCAL;
+
+  /* XXX Add support for architecture specific types and classes.  */
+  if (sym->st_shndx == SHN_ABS)
+    return local_p ? 'a' : 'A';
+
+  if (sym->st_shndx == SHN_UNDEF)
+    /* Undefined symbols must be global.  */
+    return 'U';
+
+  char result = "NDTSFBD         "[GELF_ST_TYPE (sym->st_info)];
+
+  if (result == 'D')
+    {
+      /* Special handling: unique data symbols.  */
+      if (ehdr->e_ident[EI_OSABI] == ELFOSABI_LINUX
+	  && GELF_ST_BIND (sym->st_info) == STB_GNU_UNIQUE)
+	result = 'u';
+      else
+	{
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (elf_getscn (elf, sym->st_shndx),
+					  &shdr_mem);
+	  if (shdr != NULL)
+	    {
+	      if ((shdr->sh_flags & SHF_WRITE) == 0)
+		result = 'R';
+	      else if (shdr->sh_type == SHT_NOBITS)
+		result = 'B';
+	    }
+	}
+    }
+
+  return local_p ? tolower (result) : result;
+}
+
+
+static void
+show_symbols_bsd (Elf *elf, const GElf_Ehdr *ehdr, GElf_Word strndx,
+		  const char *prefix, const char *fname, const char *fullname,
+		  GElf_SymX *syms, size_t nsyms)
+{
+  int digits = length_map[gelf_getclass (elf) - 1][radix];
+
+  if (prefix != NULL && ! print_file_name)
+    printf ("\n%s:\n", fname);
+
+#ifdef USE_DEMANGLE
+  size_t demangle_buffer_len = 0;
+  char *demangle_buffer = NULL;
+#endif
+
+  /* Iterate over all symbols.  */
+  for (size_t cnt = 0; cnt < nsyms; ++cnt)
+    {
+      char symstrbuf[50];
+      const char *symstr = sym_name (elf, strndx, syms[cnt].sym.st_name,
+				     symstrbuf, sizeof symstrbuf);
+
+      /* Printing entries with a zero-length name makes the output
+	 not very well parseable.  Since these entries don't carry
+	 much information we leave them out.  */
+      if (symstr[0] == '\0')
+	continue;
+
+      /* We do not print the entries for files.  */
+      if (GELF_ST_TYPE (syms[cnt].sym.st_info) == STT_FILE)
+	continue;
+
+#ifdef USE_DEMANGLE
+      /* Demangle if necessary.  Require GNU v3 ABI by the "_Z" prefix.  */
+      if (demangle && symstr[0] == '_' && symstr[1] == 'Z')
+	{
+	  int status = -1;
+	  char *dmsymstr = __cxa_demangle (symstr, demangle_buffer,
+					   &demangle_buffer_len, &status);
+
+	  if (status == 0)
+	    symstr = dmsymstr;
+	}
+#endif
+
+      /* If we have to precede the line with the file name.  */
+      if (print_file_name)
+	{
+	  fputs_unlocked (fullname, stdout);
+	  putchar_unlocked (':');
+	}
+
+      bool is_tls = GELF_ST_TYPE (syms[cnt].sym.st_info) == STT_TLS;
+      bool is_weak = GELF_ST_BIND (syms[cnt].sym.st_info) == STB_WEAK;
+      const char *marker = (mark_special
+			    ? (is_tls ? "@" : (is_weak ? "*" : " ")) : "");
+
+      if (syms[cnt].sym.st_shndx == SHN_UNDEF)
+	{
+	  const char *color = "";
+	  if (color_mode)
+	    {
+	      if (is_tls)
+		color = color_undef_tls;
+	      else if (is_weak)
+		color = color_undef_weak;
+	      else
+		color = color_undef;
+	    }
+
+	  printf ("%*s %sU%s %s", digits, "", color, marker, symstr);
+	}
+      else
+	{
+	  const char *color = "";
+	  if (color_mode)
+	    {
+	      if (is_tls)
+		color = color_tls;
+	      else if (is_weak)
+		color = color_weak;
+	      else
+		color = color_symbol;
+	    }
+	  if (print_size && syms[cnt].sym.st_size != 0)
+	    {
+#define HEXFMT "%6$s%2$0*1$" PRIx64 "%8$s %10$0*9$" PRIx64 " %7$s%3$c%4$s %5$s"
+#define DECFMT "%6$s%2$*1$" PRId64 "%8$s %10$*9$" PRId64 " %7$s%3$c%4$s %5$s"
+#define OCTFMT "%6$s%2$0*1$" PRIo64 "%8$s %10$0*9$" PRIo64 " %7$s%3$c%4$s %5$s"
+	      printf ((radix == radix_hex ? HEXFMT
+		       : (radix == radix_decimal ? DECFMT : OCTFMT)),
+		      digits, syms[cnt].sym.st_value,
+		      class_type_char (elf, ehdr, &syms[cnt].sym), marker,
+		      symstr,
+		      color_mode ? color_address : "",
+		      color,
+		      color_mode ? color_off : "",
+		      digits, (uint64_t) syms[cnt].sym.st_size);
+#undef HEXFMT
+#undef DECFMT
+#undef OCTFMT
+	    }
+	  else
+	    {
+#define HEXFMT "%6$s%2$0*1$" PRIx64 "%8$s %7$s%3$c%4$s %5$s"
+#define DECFMT "%6$s%2$*1$" PRId64 "%8$s %7$s%3$c%4$s %5$s"
+#define OCTFMT "%6$s%2$0*1$" PRIo64 "%8$s %7$s%3$c%4$s %5$s"
+	      printf ((radix == radix_hex ? HEXFMT
+		       : (radix == radix_decimal ? DECFMT : OCTFMT)),
+		      digits, syms[cnt].sym.st_value,
+		      class_type_char (elf, ehdr, &syms[cnt].sym), marker,
+		      symstr,
+		      color_mode ? color_address : "",
+		      color,
+		      color_mode ? color_off : "");
+#undef HEXFMT
+#undef DECFMT
+#undef OCTFMT
+	    }
+	}
+
+      if (color_mode)
+	fputs_unlocked (color_off, stdout);
+      putchar_unlocked ('\n');
+    }
+
+#ifdef USE_DEMANGLE
+  free (demangle_buffer);
+#endif
+}
+
+
+static void
+show_symbols_posix (Elf *elf, const GElf_Ehdr *ehdr, GElf_Word strndx,
+		    const char *prefix, const char *fullname, GElf_SymX *syms,
+		    size_t nsyms)
+{
+  if (prefix != NULL && ! print_file_name)
+    printf ("%s:\n", fullname);
+
+  int digits = length_map[gelf_getclass (elf) - 1][radix];
+
+#ifdef USE_DEMANGLE
+  size_t demangle_buffer_len = 0;
+  char *demangle_buffer = NULL;
+#endif
+
+  /* Iterate over all symbols.  */
+  for (size_t cnt = 0; cnt < nsyms; ++cnt)
+    {
+      char symstrbuf[50];
+      const char *symstr = sym_name (elf, strndx, syms[cnt].sym.st_name,
+				     symstrbuf, sizeof symstrbuf);
+
+      /* Printing entries with a zero-length name makes the output
+	 not very well parseable.  Since these entries don't carry
+	 much information we leave them out.  */
+      if (symstr[0] == '\0')
+	continue;
+
+#ifdef USE_DEMANGLE
+      /* Demangle if necessary.  Require GNU v3 ABI by the "_Z" prefix.  */
+      if (demangle && symstr[0] == '_' && symstr[1] == 'Z')
+	{
+	  int status = -1;
+	  char *dmsymstr = __cxa_demangle (symstr, demangle_buffer,
+					   &demangle_buffer_len, &status);
+
+	  if (status == 0)
+	    symstr = dmsymstr;
+	}
+#endif
+
+      /* If we have to precede the line with the file name.  */
+      if (print_file_name)
+	{
+	  fputs_unlocked (fullname, stdout);
+	  putchar_unlocked (':');
+	  putchar_unlocked (' ');
+	}
+
+      printf ((radix == radix_hex
+	       ? "%s %c%s %0*" PRIx64 " %0*" PRIx64 "\n"
+	       : (radix == radix_decimal
+		  ? "%s %c%s %*" PRId64 " %*" PRId64 "\n"
+		  : "%s %c%s %0*" PRIo64 " %0*" PRIo64 "\n")),
+	      symstr,
+	      class_type_char (elf, ehdr, &syms[cnt].sym),
+	      mark_special
+	      ? (GELF_ST_TYPE (syms[cnt].sym.st_info) == STT_TLS
+		 ? "@"
+		 : (GELF_ST_BIND (syms[cnt].sym.st_info) == STB_WEAK
+		    ? "*" : " "))
+	      : "",
+	      digits, syms[cnt].sym.st_value,
+	      digits, syms[cnt].sym.st_size);
+    }
+
+#ifdef USE_DEMANGLE
+  free (demangle_buffer);
+#endif
+}
+
+
+/* Maximum size of memory we allocate on the stack.  */
+#define MAX_STACK_ALLOC	65536
+
+static int
+sort_by_address (const void *p1, const void *p2)
+{
+  GElf_SymX *s1 = (GElf_SymX *) p1;
+  GElf_SymX *s2 = (GElf_SymX *) p2;
+
+  int result = (s1->sym.st_value < s2->sym.st_value
+		? -1 : (s1->sym.st_value == s2->sym.st_value ? 0 : 1));
+
+  return reverse_sort ? -result : result;
+}
+
+static Elf_Data *sort_by_name_strtab;
+
+static int
+sort_by_name (const void *p1, const void *p2)
+{
+  GElf_SymX *s1 = (GElf_SymX *) p1;
+  GElf_SymX *s2 = (GElf_SymX *) p2;
+
+  const char *n1 = sort_by_name_strtab->d_buf + s1->sym.st_name;
+  const char *n2 = sort_by_name_strtab->d_buf + s2->sym.st_name;
+
+  int result = strcmp (n1, n2);
+
+  return reverse_sort ? -result : result;
+}
+
+/* Stub libdwfl callback, only the ELF handle already open is ever
+   used.  Only used for finding the alternate debug file if the Dwarf
+   comes from the main file.  We are not interested in separate
+   debuginfo.  */
+static int
+find_no_debuginfo (Dwfl_Module *mod,
+		   void **userdata,
+		   const char *modname,
+		   Dwarf_Addr base,
+		   const char *file_name,
+		   const char *debuglink_file,
+		   GElf_Word debuglink_crc,
+		   char **debuginfo_file_name)
+{
+  Dwarf_Addr dwbias;
+  dwfl_module_info (mod, NULL, NULL, NULL, &dwbias, NULL, NULL, NULL);
+
+  /* We are only interested if the Dwarf has been setup on the main
+     elf file but is only missing the alternate debug link.  If dwbias
+     hasn't even been setup, this is searching for separate debuginfo
+     for the main elf.  We don't care in that case.  */
+  if (dwbias == (Dwarf_Addr) -1)
+    return -1;
+
+  return dwfl_standard_find_debuginfo (mod, userdata, modname, base,
+				       file_name, debuglink_file,
+				       debuglink_crc, debuginfo_file_name);
+}
+
+/* Get the Dwarf for the module/file we want.  */
+struct getdbg
+{
+  const char *name;
+  Dwarf **dbg;
+};
+
+static int
+getdbg_dwflmod (Dwfl_Module *dwflmod,
+		void **userdata __attribute__ ((unused)),
+		const char *name,
+		Dwarf_Addr base __attribute__ ((unused)),
+		void *arg)
+{
+  struct getdbg *get = (struct getdbg *) arg;
+  if (get != NULL && get->name != NULL && strcmp (get->name, name) == 0)
+    {
+      Dwarf_Addr bias;
+      *get->dbg = dwfl_module_getdwarf (dwflmod, &bias);
+      return DWARF_CB_ABORT;
+    }
+
+  return DWARF_CB_OK;
+}
+
+static void
+show_symbols (int fd, Ebl *ebl, GElf_Ehdr *ehdr,
+	      Elf_Scn *scn, Elf_Scn *xndxscn,
+	      GElf_Shdr *shdr, const char *prefix, const char *fname,
+	      const char *fullname)
+{
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0)
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  /* The section is that large.  */
+  size_t size = shdr->sh_size;
+  /* One entry is this large.  */
+  size_t entsize = shdr->sh_entsize;
+
+  /* Consistency checks.  */
+  if (entsize == 0
+      || entsize != gelf_fsize (ebl->elf, ELF_T_SYM, 1, EV_CURRENT))
+    error (0, 0,
+	   gettext ("%s: entry size in section %zd `%s' is not what we expect"),
+	   fullname, elf_ndxscn (scn),
+	   elf_strptr (ebl->elf, shstrndx, shdr->sh_name));
+  else if (size % entsize != 0)
+    error (0, 0,
+	   gettext ("%s: size of section %zd `%s' is not multiple of entry size"),
+	   fullname, elf_ndxscn (scn),
+	   elf_strptr (ebl->elf, shstrndx, shdr->sh_name));
+
+  /* Compute number of entries.  Handle buggy entsize values.  */
+  size_t nentries = size / (entsize ?: 1);
+
+
+#define obstack_chunk_alloc xmalloc
+#define obstack_chunk_free free
+  struct obstack whereob;
+  obstack_init (&whereob);
+
+  /* Get a DWARF debugging descriptor.  It's no problem if this isn't
+     possible.  We just won't print any line number information.  */
+  Dwarf *dbg = NULL;
+  Dwfl *dwfl = NULL;
+  if (format == format_sysv)
+    {
+      if (ehdr->e_type != ET_REL)
+	dbg = dwarf_begin_elf (ebl->elf, DWARF_C_READ, NULL);
+      else
+	{
+	  /* Abuse libdwfl to do the relocations for us.  This is just
+	     for the ET_REL file containing Dwarf, so no need for
+	     fancy lookups.  */
+
+	  /* Duplicate an fd for dwfl_report_offline to swallow.  */
+	  int dwfl_fd = dup (fd);
+	  if (likely (dwfl_fd >= 0))
+	    {
+	      static const Dwfl_Callbacks callbacks =
+		{
+		  .section_address = dwfl_offline_section_address,
+		  .find_debuginfo = find_no_debuginfo
+		};
+	      dwfl = dwfl_begin (&callbacks);
+	      if (likely (dwfl != NULL))
+		{
+		  /* Let 0 be the logical address of the file (or
+		     first in archive).  */
+		  dwfl->offline_next_address = 0;
+		  if (dwfl_report_offline (dwfl, fname, fname, dwfl_fd)
+		      == NULL)
+		    {
+		      /* Consumed on success, not on failure.  */
+		      close (dwfl_fd);
+		    }
+		  else
+		    {
+		      dwfl_report_end (dwfl, NULL, NULL);
+
+		      struct getdbg get = { .name = fname, .dbg = &dbg };
+		      dwfl_getmodules (dwfl, &getdbg_dwflmod, &get, 0);
+		    }
+		}
+	    }
+	}
+      if (dbg != NULL)
+	{
+	  (void) dwarf_getpubnames (dbg, get_global, NULL, 0);
+
+	  get_local_names (dbg);
+	}
+    }
+
+  /* Get the data of the section.  */
+  Elf_Data *data = elf_getdata (scn, NULL);
+  Elf_Data *xndxdata = elf_getdata (xndxscn, NULL);
+  if (data == NULL || (xndxscn != NULL && xndxdata == NULL))
+    INTERNAL_ERROR (fullname);
+
+  /* Allocate the memory.
+
+     XXX We can use a dirty trick here.  Since GElf_Sym == Elf64_Sym we
+     can use the data memory instead of copying again if what we read
+     is a 64 bit file.  */
+  if (nentries > SIZE_MAX / sizeof (GElf_SymX))
+    error (EXIT_FAILURE, 0,
+          gettext ("%s: entries (%zd) in section %zd `%s' is too large"),
+          fullname, nentries, elf_ndxscn (scn),
+          elf_strptr (ebl->elf, shstrndx, shdr->sh_name));
+  GElf_SymX *sym_mem;
+  if (nentries * sizeof (GElf_SymX) < MAX_STACK_ALLOC)
+    sym_mem = (GElf_SymX *) alloca (nentries * sizeof (GElf_SymX));
+  else
+    sym_mem = (GElf_SymX *) xmalloc (nentries * sizeof (GElf_SymX));
+
+  /* Iterate over all symbols.  */
+#ifdef USE_DEMANGLE
+  size_t demangle_buffer_len = 0;
+  char *demangle_buffer = NULL;
+#endif
+  int longest_name = 4;
+  int longest_where = 4;
+  size_t nentries_used = 0;
+  for (size_t cnt = 0; cnt < nentries; ++cnt)
+    {
+      GElf_Sym *sym = gelf_getsymshndx (data, xndxdata, cnt,
+					&sym_mem[nentries_used].sym,
+					&sym_mem[nentries_used].xndx);
+      if (sym == NULL)
+	INTERNAL_ERROR (fullname);
+
+      /* Filter out administrative symbols without a name and those
+	 deselected by the user with command line options.  */
+      if ((hide_undefined && sym->st_shndx == SHN_UNDEF)
+	  || (hide_defined && sym->st_shndx != SHN_UNDEF)
+	  || (hide_local && GELF_ST_BIND (sym->st_info) == STB_LOCAL))
+	continue;
+
+      sym_mem[nentries_used].where = "";
+      if (format == format_sysv)
+	{
+	  const char *symstr = elf_strptr (ebl->elf, shdr->sh_link,
+					   sym->st_name);
+	  if (symstr == NULL)
+	    continue;
+
+#ifdef USE_DEMANGLE
+	  /* Demangle if necessary.  Require GNU v3 ABI by the "_Z" prefix.  */
+	  if (demangle && symstr[0] == '_' && symstr[1] == 'Z')
+	    {
+	      int status = -1;
+	      char *dmsymstr = __cxa_demangle (symstr, demangle_buffer,
+					       &demangle_buffer_len, &status);
+
+	      if (status == 0)
+		symstr = dmsymstr;
+	    }
+#endif
+
+	  longest_name = MAX ((size_t) longest_name, strlen (symstr));
+
+	  if (sym->st_shndx != SHN_UNDEF
+	      && GELF_ST_BIND (sym->st_info) != STB_LOCAL
+	      && global_root != NULL)
+	    {
+	      Dwarf_Global fake = { .name = symstr };
+	      Dwarf_Global **found = tfind (&fake, &global_root,
+					    global_compare);
+	      if (found != NULL)
+		{
+		  Dwarf_Die die_mem;
+		  Dwarf_Die *die = dwarf_offdie (dbg, (*found)->die_offset,
+						 &die_mem);
+
+		  Dwarf_Die cudie_mem;
+		  Dwarf_Die *cudie = NULL;
+
+		  Dwarf_Addr lowpc;
+		  Dwarf_Addr highpc;
+		  if (die != NULL
+		      && dwarf_lowpc (die, &lowpc) == 0
+		      && lowpc <= sym->st_value
+		      && dwarf_highpc (die, &highpc) == 0
+		      && highpc > sym->st_value)
+		    cudie = dwarf_offdie (dbg, (*found)->cu_offset,
+					  &cudie_mem);
+		  if (cudie != NULL)
+		    {
+		      Dwarf_Line *line = dwarf_getsrc_die (cudie,
+							   sym->st_value);
+		      if (line != NULL)
+			{
+			  /* We found the line.  */
+			  int lineno;
+			  (void) dwarf_lineno (line, &lineno);
+			  const char *file = dwarf_linesrc (line, NULL, NULL);
+			  file = (file != NULL) ? basename (file) : "???";
+			  int n;
+			  n = obstack_printf (&whereob, "%s:%d%c", file,
+					      lineno, '\0');
+			  sym_mem[nentries_used].where
+			    = obstack_finish (&whereob);
+
+			  /* The return value of obstack_print included the
+			     NUL byte, so subtract one.  */
+			  if (--n > (int) longest_where)
+			    longest_where = (size_t) n;
+			}
+		    }
+		}
+	    }
+
+	  /* Try to find the symbol among the local symbols.  */
+	  if (sym_mem[nentries_used].where[0] == '\0')
+	    {
+	      struct local_name fake =
+		{
+		  .name = symstr,
+		  .lowpc = sym->st_value,
+		  .highpc = sym->st_value,
+		};
+	      struct local_name **found = tfind (&fake, &local_root,
+						 local_compare);
+	      if (found != NULL)
+		{
+		  /* We found the line.  */
+		  int n = obstack_printf (&whereob, "%s:%" PRIu64 "%c",
+					  basename ((*found)->file),
+					  (*found)->lineno,
+					  '\0');
+		  sym_mem[nentries_used].where = obstack_finish (&whereob);
+
+		  /* The return value of obstack_print included the
+		     NUL byte, so subtract one.  */
+		  if (--n > (int) longest_where)
+		    longest_where = (size_t) n;
+		}
+	    }
+	}
+
+      /* We use this entry.  */
+      ++nentries_used;
+    }
+#ifdef USE_DEMANGLE
+  free (demangle_buffer);
+#endif
+  /* Now we know the exact number.  */
+  nentries = nentries_used;
+
+  /* Sort the entries according to the users wishes.  */
+  if (sort == sort_name)
+    {
+      sort_by_name_strtab = elf_getdata (elf_getscn (ebl->elf, shdr->sh_link),
+					 NULL);
+      qsort (sym_mem, nentries, sizeof (GElf_SymX), sort_by_name);
+    }
+  else if (sort == sort_numeric)
+    qsort (sym_mem, nentries, sizeof (GElf_SymX), sort_by_address);
+
+  /* Finally print according to the users selection.  */
+  switch (format)
+    {
+    case format_sysv:
+      show_symbols_sysv (ebl, shdr->sh_link, fullname, sym_mem, nentries,
+			 longest_name, longest_where);
+      break;
+
+    case format_bsd:
+      show_symbols_bsd (ebl->elf, ehdr, shdr->sh_link, prefix, fname, fullname,
+			sym_mem, nentries);
+      break;
+
+    case format_posix:
+    default:
+      assert (format == format_posix);
+      show_symbols_posix (ebl->elf, ehdr, shdr->sh_link, prefix, fullname,
+			  sym_mem, nentries);
+      break;
+    }
+
+  /* Free all memory.  */
+  if (nentries * sizeof (sym_mem[0]) >= MAX_STACK_ALLOC)
+    free (sym_mem);
+
+  obstack_free (&whereob, NULL);
+
+  if (dbg != NULL)
+    {
+      tdestroy (global_root, free);
+      global_root = NULL;
+
+      tdestroy (local_root, free);
+      local_root = NULL;
+
+      if (dwfl == NULL)
+	(void) dwarf_end (dbg);
+    }
+  if (dwfl != NULL)
+    dwfl_end (dwfl);
+}
+
+
+static int
+handle_elf (int fd, Elf *elf, const char *prefix, const char *fname,
+	    const char *suffix)
+{
+  size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
+  size_t suffix_len = suffix == NULL ? 0 : strlen (suffix);
+  size_t fname_len = strlen (fname) + 1;
+  char fullname[prefix_len + 1 + fname_len + suffix_len];
+  char *cp = fullname;
+  Elf_Scn *scn = NULL;
+  int any = 0;
+  int result = 0;
+  GElf_Ehdr ehdr_mem;
+  GElf_Ehdr *ehdr;
+  Ebl *ebl;
+
+  /* Get the backend for this object file type.  */
+  ebl = ebl_openbackend (elf);
+
+  /* We need the ELF header in a few places.  */
+  ehdr = gelf_getehdr (elf, &ehdr_mem);
+  if (ehdr == NULL)
+    INTERNAL_ERROR (fullname);
+
+  /* If we are asked to print the dynamic symbol table and this is
+     executable or dynamic executable, fail.  */
+  if (symsec_type == SHT_DYNSYM
+      && ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
+    {
+      /* XXX Add machine specific object file types.  */
+      error (0, 0, gettext ("%s%s%s%s: Invalid operation"),
+	     prefix ?: "", prefix ? "(" : "", fname, prefix ? ")" : "");
+      result = 1;
+      goto out;
+    }
+
+  /* Create the full name of the file.  */
+  if (prefix != NULL)
+    cp = mempcpy (cp, prefix, prefix_len);
+  cp = mempcpy (cp, fname, fname_len);
+  if (suffix != NULL)
+    memcpy (cp - 1, suffix, suffix_len + 1);
+
+  /* Find the symbol table.
+
+     XXX Can there be more than one?  Do we print all?  Currently we do.  */
+  while ((scn = elf_nextscn (elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr == NULL)
+	INTERNAL_ERROR (fullname);
+
+      if (shdr->sh_type == symsec_type)
+	{
+	  Elf_Scn *xndxscn = NULL;
+
+	  /* We have a symbol table.  First make sure we remember this.  */
+	  any = 1;
+
+	  /* Look for an extended section index table for this section.  */
+	  if (symsec_type == SHT_SYMTAB)
+	    {
+	      size_t scnndx = elf_ndxscn (scn);
+
+	      while ((xndxscn = elf_nextscn (elf, xndxscn)) != NULL)
+		{
+		  GElf_Shdr xndxshdr_mem;
+		  GElf_Shdr *xndxshdr = gelf_getshdr (xndxscn, &xndxshdr_mem);
+
+		  if (xndxshdr == NULL)
+		    INTERNAL_ERROR (fullname);
+
+		  if (xndxshdr->sh_type == SHT_SYMTAB_SHNDX
+		      && xndxshdr->sh_link == scnndx)
+		    break;
+		}
+	    }
+
+	  show_symbols (fd, ebl, ehdr, scn, xndxscn, shdr, prefix, fname,
+			fullname);
+	}
+    }
+
+  if (! any)
+    {
+      error (0, 0, gettext ("%s%s%s: no symbols"),
+	     prefix ?: "", prefix ? ":" : "", fname);
+      result = 1;
+    }
+
+ out:
+  /* Close the ELF backend library descriptor.  */
+  ebl_closebackend (ebl);
+
+  return result;
+}
+
+
+#include "debugpred.h"
diff --git a/src/objdump.c b/src/objdump.c
new file mode 100644
index 0000000..0dd9a6a
--- /dev/null
+++ b/src/objdump.c
@@ -0,0 +1,795 @@
+/* Print information from ELF file in human-readable form.
+   Copyright (C) 2005, 2006, 2007, 2009, 2011, 2012, 2014, 2015 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2005.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <error.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libintl.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libeu.h>
+#include <system.h>
+#include <color.h>
+#include <printversion.h>
+#include "../libebl/libeblP.h"
+
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, N_("Mode selection:"), 0 },
+  { "reloc", 'r', NULL, 0, N_("Display relocation information."), 0 },
+  { "full-contents", 's', NULL, 0,
+    N_("Display the full contents of all sections requested"), 0 },
+  { "disassemble", 'd', NULL, 0,
+    N_("Display assembler code of executable sections"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Output content selection:"), 0 },
+  { "section", 'j', "NAME", 0,
+    N_("Only display information for section NAME."), 0 },
+
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("\
+Show information from FILEs (a.out by default).");
+
+/* 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);
+
+/* Parser children.  */
+static struct argp_child argp_children[] =
+  {
+    { &color_argp, 0, N_("Output formatting"), 2 },
+    { NULL, 0, NULL, 0}
+  };
+
+/* Data structure to communicate with argp functions.  */
+static const struct argp argp =
+{
+  options, parse_opt, args_doc, doc, argp_children, NULL, NULL
+};
+
+
+/* Print symbols in file named FNAME.  */
+static int process_file (const char *fname, bool more_than_one);
+
+/* Handle content of archive.  */
+static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname,
+		      const char *suffix);
+
+/* Handle ELF file.  */
+static int handle_elf (Elf *elf, const char *prefix, const char *fname,
+		       const char *suffix);
+
+
+#define INTERNAL_ERROR(fname) \
+  error (EXIT_FAILURE, 0, gettext ("%s: INTERNAL ERROR %d (%s): %s"),      \
+	 fname, __LINE__, PACKAGE_VERSION, elf_errmsg (-1))
+
+
+/* List of sections which should be used.  */
+static struct section_list
+{
+  bool is_name;
+  union
+  {
+    const char *name;
+    uint32_t scnndx;
+  };
+  struct section_list *next;
+} *section_list;
+
+
+/* If true print archive index.  */
+static bool print_relocs;
+
+/* If true print full contents of requested sections.  */
+static bool print_full_content;
+
+/* If true print disassembled output..  */
+static bool print_disasm;
+
+
+int
+main (int argc, char *argv[])
+{
+  /* We use no threads here which can interfere with handling a stream.  */
+  (void) __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+  (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+  (void) __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  (void) textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  int remaining;
+  (void) argp_parse (&argp, argc, argv, 0, &remaining, NULL);
+
+  /* Tell the library which version we are expecting.  */
+  (void) elf_version (EV_CURRENT);
+
+  int result = 0;
+  if (remaining == argc)
+    /* The user didn't specify a name so we use a.out.  */
+    result = process_file ("a.out", false);
+  else
+    {
+      /* Process all the remaining files.  */
+      const bool more_than_one = remaining + 1 < argc;
+
+      do
+	result |= process_file (argv[remaining], more_than_one);
+      while (++remaining < argc);
+    }
+
+  return result;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg,
+	   struct argp_state *state __attribute__ ((unused)))
+{
+  /* True if any of the control options is set.  */
+  static bool any_control_option;
+
+  switch (key)
+    {
+    case 'j':
+      {
+	struct section_list *newp = xmalloc (sizeof (*newp));
+	char *endp;
+	newp->scnndx = strtoul (arg, &endp, 0);
+	if (*endp == 0)
+	  newp->is_name = false;
+	else
+	  {
+	    newp->name = arg;
+	    newp->is_name = true;
+	  }
+	newp->next = section_list;
+	section_list = newp;
+      }
+      any_control_option = true;
+      break;
+
+    case 'd':
+      print_disasm = true;
+      any_control_option = true;
+      break;
+
+    case 'r':
+      print_relocs = true;
+      any_control_option = true;
+      break;
+
+    case 's':
+      print_full_content = true;
+      any_control_option = true;
+      break;
+
+    case ARGP_KEY_FINI:
+      if (! any_control_option)
+	{
+	  fputs (gettext ("No operation specified.\n"), stderr);
+	  argp_help (&argp, stderr, ARGP_HELP_SEE,
+		     program_invocation_short_name);
+	  exit (EXIT_FAILURE);
+	}
+      /* We only use this for checking the number of arguments, we don't
+	 actually want to consume them.  */
+      FALLTHROUGH;
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+
+/* Open the file and determine the type.  */
+static int
+process_file (const char *fname, bool more_than_one)
+{
+  /* Open the file.  */
+  int fd = open (fname, O_RDONLY);
+  if (fd == -1)
+    {
+      error (0, errno, gettext ("cannot open %s"), fname);
+      return 1;
+    }
+
+  /* Now get the ELF descriptor.  */
+  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+  if (elf != NULL)
+    {
+      if (elf_kind (elf) == ELF_K_ELF)
+	{
+	  int result = handle_elf (elf, more_than_one ? "" : NULL,
+				   fname, NULL);
+
+	  if (elf_end (elf) != 0)
+	    INTERNAL_ERROR (fname);
+
+	  if (close (fd) != 0)
+	    error (EXIT_FAILURE, errno, gettext ("while close `%s'"), fname);
+
+	  return result;
+	}
+      else if (elf_kind (elf) == ELF_K_AR)
+	{
+	  int result = handle_ar (fd, elf, NULL, fname, NULL);
+
+	  if (elf_end (elf) != 0)
+	    INTERNAL_ERROR (fname);
+
+	  if (close (fd) != 0)
+	    error (EXIT_FAILURE, errno, gettext ("while close `%s'"), fname);
+
+	  return result;
+	}
+
+      /* We cannot handle this type.  Close the descriptor anyway.  */
+      if (elf_end (elf) != 0)
+	INTERNAL_ERROR (fname);
+    }
+
+  error (0, 0, gettext ("%s: File format not recognized"), fname);
+
+  return 1;
+}
+
+
+static int
+handle_ar (int fd, Elf *elf, const char *prefix, const char *fname,
+	   const char *suffix)
+{
+  size_t fname_len = strlen (fname) + 1;
+  size_t prefix_len = prefix != NULL ? strlen (prefix) : 0;
+  char new_prefix[prefix_len + fname_len + 2];
+  size_t suffix_len = suffix != NULL ? strlen (suffix) : 0;
+  char new_suffix[suffix_len + 2];
+  Elf *subelf;
+  Elf_Cmd cmd = ELF_C_READ_MMAP;
+  int result = 0;
+
+  char *cp = new_prefix;
+  if (prefix != NULL)
+    cp = stpcpy (cp, prefix);
+  cp = stpcpy (cp, fname);
+  stpcpy (cp, "[");
+
+  cp = new_suffix;
+  if (suffix != NULL)
+    cp = stpcpy (cp, suffix);
+  stpcpy (cp, "]");
+
+  /* Process all the files contained in the archive.  */
+  while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
+    {
+      /* The the header for this element.  */
+      Elf_Arhdr *arhdr = elf_getarhdr (subelf);
+
+      /* Skip over the index entries.  */
+      if (strcmp (arhdr->ar_name, "/") != 0
+	  && strcmp (arhdr->ar_name, "//") != 0)
+	{
+	  if (elf_kind (subelf) == ELF_K_ELF)
+	    result |= handle_elf (subelf, new_prefix, arhdr->ar_name,
+				  new_suffix);
+	  else if (elf_kind (subelf) == ELF_K_AR)
+	    result |= handle_ar (fd, subelf, new_prefix, arhdr->ar_name,
+				 new_suffix);
+	  else
+	    {
+	      error (0, 0, gettext ("%s%s%s: file format not recognized"),
+		     new_prefix, arhdr->ar_name, new_suffix);
+	      result = 1;
+	    }
+	}
+
+      /* Get next archive element.  */
+      cmd = elf_next (subelf);
+      if (elf_end (subelf) != 0)
+	INTERNAL_ERROR (fname);
+    }
+
+  return result;
+}
+
+
+static void
+show_relocs_x (Ebl *ebl, GElf_Shdr *shdr, Elf_Data *symdata,
+	       Elf_Data *xndxdata, size_t symstrndx, size_t shstrndx,
+	       GElf_Addr r_offset, GElf_Xword r_info, GElf_Sxword r_addend)
+{
+  int elfclass = gelf_getclass (ebl->elf);
+  char buf[128];
+
+  printf ("%0*" PRIx64 " %-20s ",
+	  elfclass == ELFCLASS32 ? 8 : 16, r_offset,
+	  ebl_reloc_type_name (ebl, GELF_R_TYPE (r_info), buf, sizeof (buf)));
+
+  Elf32_Word xndx;
+  GElf_Sym symmem;
+  GElf_Sym *sym = gelf_getsymshndx (symdata, xndxdata, GELF_R_SYM (r_info),
+				    &symmem, &xndx);
+
+  if (sym == NULL)
+    printf ("<%s %ld>",
+	    gettext ("INVALID SYMBOL"), (long int) GELF_R_SYM (r_info));
+  else if (GELF_ST_TYPE (sym->st_info) != STT_SECTION)
+    printf ("%s",
+	    elf_strptr (ebl->elf, symstrndx, sym->st_name));
+  else
+    {
+      GElf_Shdr destshdr_mem;
+      GElf_Shdr *destshdr;
+      destshdr = gelf_getshdr (elf_getscn (ebl->elf,
+					   sym->st_shndx == SHN_XINDEX
+					   ? xndx : sym->st_shndx),
+			       &destshdr_mem);
+
+      if (shdr == NULL || destshdr == NULL)
+	printf ("<%s %ld>",
+		gettext ("INVALID SECTION"),
+		(long int) (sym->st_shndx == SHN_XINDEX
+			    ? xndx : sym->st_shndx));
+      else
+	printf ("%s",
+		elf_strptr (ebl->elf, shstrndx, destshdr->sh_name));
+    }
+
+  if (r_addend != 0)
+    {
+      char sign = '+';
+      if (r_addend < 0)
+	{
+	  sign = '-';
+	  r_addend = -r_addend;
+	}
+      printf ("%c%#" PRIx64, sign, r_addend);
+    }
+  putchar ('\n');
+}
+
+
+static void
+show_relocs_rel (Ebl *ebl, GElf_Shdr *shdr, Elf_Data *data,
+		 Elf_Data *symdata, Elf_Data *xndxdata, size_t symstrndx,
+		 size_t shstrndx)
+{
+  size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_REL, 1, EV_CURRENT);
+  int nentries = shdr->sh_size / sh_entsize;
+
+  for (int cnt = 0; cnt < nentries; ++cnt)
+    {
+      GElf_Rel relmem;
+      GElf_Rel *rel;
+
+      rel = gelf_getrel (data, cnt, &relmem);
+      if (rel != NULL)
+	show_relocs_x (ebl, shdr, symdata, xndxdata, symstrndx, shstrndx,
+		       rel->r_offset, rel->r_info, 0);
+    }
+}
+
+
+static void
+show_relocs_rela (Ebl *ebl, GElf_Shdr *shdr, Elf_Data *data,
+		  Elf_Data *symdata, Elf_Data *xndxdata, size_t symstrndx,
+		  size_t shstrndx)
+{
+  size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_RELA, 1, EV_CURRENT);
+  int nentries = shdr->sh_size / sh_entsize;
+
+  for (int cnt = 0; cnt < nentries; ++cnt)
+    {
+      GElf_Rela relmem;
+      GElf_Rela *rel;
+
+      rel = gelf_getrela (data, cnt, &relmem);
+      if (rel != NULL)
+	show_relocs_x (ebl, shdr, symdata, xndxdata, symstrndx, shstrndx,
+		       rel->r_offset, rel->r_info, rel->r_addend);
+    }
+}
+
+
+static bool
+section_match (Elf *elf, uint32_t scnndx, GElf_Shdr *shdr, size_t shstrndx)
+{
+  if (section_list == NULL)
+    return true;
+
+  struct section_list *runp = section_list;
+  const char *name = elf_strptr (elf, shstrndx, shdr->sh_name);
+
+  do
+    {
+      if (runp->is_name)
+	{
+	  if (name && strcmp (runp->name, name) == 0)
+	    return true;
+	}
+      else
+	{
+	  if (runp->scnndx == scnndx)
+	    return true;
+	}
+
+      runp = runp->next;
+    }
+  while (runp != NULL);
+
+  return false;
+}
+
+
+static int
+show_relocs (Ebl *ebl, const char *fname, uint32_t shstrndx)
+{
+  int elfclass = gelf_getclass (ebl->elf);
+
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr == NULL)
+	INTERNAL_ERROR (fname);
+
+      if (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA)
+	{
+	  if  (! section_match (ebl->elf, elf_ndxscn (scn), shdr, shstrndx))
+	    continue;
+
+	  GElf_Shdr destshdr_mem;
+	  GElf_Shdr *destshdr = gelf_getshdr (elf_getscn (ebl->elf,
+							  shdr->sh_info),
+					      &destshdr_mem);
+	  if (unlikely (destshdr == NULL))
+	    continue;
+
+	  printf (gettext ("\nRELOCATION RECORDS FOR [%s]:\n"
+			   "%-*s TYPE                 VALUE\n"),
+		  elf_strptr (ebl->elf, shstrndx, destshdr->sh_name),
+		  elfclass == ELFCLASS32 ? 8 : 16, gettext ("OFFSET"));
+
+	  /* Get the data of the section.  */
+	  Elf_Data *data = elf_getdata (scn, NULL);
+	  if (data == NULL)
+	    continue;
+
+	  /* Get the symbol table information.  */
+	  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
+	  GElf_Shdr symshdr_mem;
+	  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
+	  Elf_Data *symdata = elf_getdata (symscn, NULL);
+	  if (unlikely (symshdr == NULL || symdata == NULL))
+	    continue;
+
+	  /* Search for the optional extended section index table.  */
+	  Elf_Data *xndxdata = NULL;
+	  Elf_Scn *xndxscn = NULL;
+	  while ((xndxscn = elf_nextscn (ebl->elf, xndxscn)) != NULL)
+	    {
+	      GElf_Shdr xndxshdr_mem;
+	      GElf_Shdr *xndxshdr;
+
+	      xndxshdr = gelf_getshdr (xndxscn, &xndxshdr_mem);
+	      if (xndxshdr != NULL && xndxshdr->sh_type == SHT_SYMTAB_SHNDX
+		  && xndxshdr->sh_link == elf_ndxscn (symscn))
+		{
+		  /* Found it.  */
+		  xndxdata = elf_getdata (xndxscn, NULL);
+		  break;
+		}
+	    }
+
+	  if (shdr->sh_type == SHT_REL)
+	    show_relocs_rel (ebl, shdr, data, symdata, xndxdata,
+			     symshdr->sh_link, shstrndx);
+	  else
+	    show_relocs_rela (ebl, shdr, data, symdata, xndxdata,
+			      symshdr->sh_link, shstrndx);
+
+	  putchar ('\n');
+	}
+    }
+
+  return 0;
+}
+
+
+static int
+show_full_content (Ebl *ebl, const char *fname, uint32_t shstrndx)
+{
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr == NULL)
+	INTERNAL_ERROR (fname);
+
+      if (shdr->sh_type == SHT_PROGBITS && shdr->sh_size > 0)
+	{
+	  if  (! section_match (ebl->elf, elf_ndxscn (scn), shdr, shstrndx))
+	    continue;
+
+	  printf (gettext ("Contents of section %s:\n"),
+		  elf_strptr (ebl->elf, shstrndx, shdr->sh_name));
+
+	  /* Get the data of the section.  */
+	  Elf_Data *data = elf_getdata (scn, NULL);
+	  if (data == NULL)
+	    continue;
+
+	  unsigned char *cp = data->d_buf;
+	  size_t cnt;
+	  for (cnt = 0; cnt + 16 < data->d_size; cp += 16, cnt += 16)
+	    {
+	      printf (" %04zx ", cnt);
+
+	      for (size_t inner = 0; inner < 16; inner += 4)
+		printf ("%02hhx%02hhx%02hhx%02hhx ",
+			cp[inner], cp[inner + 1], cp[inner + 2],
+			cp[inner + 3]);
+	      fputc_unlocked (' ', stdout);
+
+	      for (size_t inner = 0; inner < 16; ++inner)
+		fputc_unlocked (isascii (cp[inner]) && isprint (cp[inner])
+				? cp[inner] : '.', stdout);
+	      fputc_unlocked ('\n', stdout);
+	    }
+
+	  printf (" %04zx ", cnt);
+
+	  size_t remaining = data->d_size - cnt;
+	  size_t inner;
+	  for (inner = 0; inner + 4 <= remaining; inner += 4)
+	    printf ("%02hhx%02hhx%02hhx%02hhx ",
+		    cp[inner], cp[inner + 1], cp[inner + 2], cp[inner + 3]);
+
+	  for (; inner < remaining; ++inner)
+	    printf ("%02hhx", cp[inner]);
+
+	  for (inner = 2 * (16 - inner) + (16 - inner + 3) / 4 + 1; inner > 0;
+	       --inner)
+	    fputc_unlocked (' ', stdout);
+
+	  for (inner = 0; inner < remaining; ++inner)
+	    fputc_unlocked (isascii (cp[inner]) && isprint (cp[inner])
+			    ? cp[inner] : '.', stdout);
+	  fputc_unlocked ('\n', stdout);
+
+	  fputc_unlocked ('\n', stdout);
+	}
+    }
+
+  return 0;
+}
+
+
+struct disasm_info
+{
+  GElf_Addr addr;
+  const uint8_t *cur;
+  const uint8_t *last_end;
+  const char *address_color;
+  const char *bytes_color;
+};
+
+
+// XXX This is not the preferred output for all architectures.  Needs
+// XXX customization, too.
+static int
+disasm_output (char *buf, size_t buflen, void *arg)
+{
+  struct disasm_info *info = (struct disasm_info *) arg;
+
+  if (info->address_color != NULL)
+    printf ("%s%8" PRIx64 "%s:   ",
+	    info->address_color, (uint64_t) info->addr, color_off);
+  else
+    printf ("%8" PRIx64 ":   ", (uint64_t) info->addr);
+
+  if (info->bytes_color != NULL)
+    fputs_unlocked (info->bytes_color, stdout);
+  size_t cnt;
+  for (cnt = 0; cnt < (size_t) MIN (info->cur - info->last_end, 8); ++cnt)
+    printf (" %02" PRIx8, info->last_end[cnt]);
+  if (info->bytes_color != NULL)
+    fputs_unlocked (color_off, stdout);
+
+  printf ("%*s %.*s\n",
+	  (int) (8 - cnt) * 3 + 1, "", (int) buflen, buf);
+
+  info->addr += cnt;
+
+  /* We limit the number of bytes printed before the mnemonic to 8.
+     Print the rest on a separate, following line.  */
+  if (info->cur - info->last_end > 8)
+    {
+      if (info->address_color != NULL)
+	printf ("%s%8" PRIx64 "%s:   ",
+		info->address_color, (uint64_t) info->addr, color_off);
+      else
+	printf ("%8" PRIx64 ":   ", (uint64_t) info->addr);
+
+      if (info->bytes_color != NULL)
+	fputs_unlocked (info->bytes_color, stdout);
+      for (; cnt < (size_t) (info->cur - info->last_end); ++cnt)
+	printf (" %02" PRIx8, info->last_end[cnt]);
+      if (info->bytes_color != NULL)
+	fputs_unlocked (color_off, stdout);
+      putchar_unlocked ('\n');
+      info->addr += info->cur - info->last_end - 8;
+    }
+
+  info->last_end = info->cur;
+
+  return 0;
+}
+
+
+static int
+show_disasm (Ebl *ebl, const char *fname, uint32_t shstrndx)
+{
+  DisasmCtx_t *ctx = disasm_begin (ebl, ebl->elf, NULL /* XXX TODO */);
+  if (ctx == NULL)
+    error (EXIT_FAILURE, 0, gettext ("cannot disassemble"));
+
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr == NULL)
+	INTERNAL_ERROR (fname);
+
+      if (shdr->sh_type == SHT_PROGBITS && shdr->sh_size > 0
+	  && (shdr->sh_flags & SHF_EXECINSTR) != 0)
+	{
+	  if  (! section_match (ebl->elf, elf_ndxscn (scn), shdr, shstrndx))
+	    continue;
+
+	  Elf_Data *data = elf_getdata (scn, NULL);
+	  if (data == NULL)
+	    continue;
+
+	  printf ("Disassembly of section %s:\n\n",
+		  elf_strptr (ebl->elf, shstrndx, shdr->sh_name));
+
+	  struct disasm_info info;
+	  info.addr = shdr->sh_addr;
+	  info.last_end = info.cur = data->d_buf;
+	  char *fmt;
+	  if (color_mode)
+	    {
+	      info.address_color = color_address;
+	      info.bytes_color = color_bytes;
+
+	      if (asprintf (&fmt, "%s%%7m %s%%.1o,%s%%.2o,%s%%.3o%%34a %s%%l",
+			    color_mnemonic ?: "",
+			    color_operand1 ?: "",
+			    color_operand2 ?: "",
+			    color_operand3 ?: "",
+			    color_label ?: "") < 0)
+		error (EXIT_FAILURE, errno, _("cannot allocate memory"));
+	    }
+	  else
+	    {
+	      info.address_color = info.bytes_color = NULL;
+
+	      fmt = "%7m %.1o,%.2o,%.3o%34a %l";
+	    }
+
+	  disasm_cb (ctx, &info.cur, info.cur + data->d_size, info.addr,
+		     fmt, disasm_output, &info, NULL /* XXX */);
+
+	  if (color_mode)
+	    free (fmt);
+	}
+    }
+
+  (void) disasm_end (ctx);
+
+  return 0;
+}
+
+
+static int
+handle_elf (Elf *elf, const char *prefix, const char *fname,
+	    const char *suffix)
+{
+
+  /* Get the backend for this object file type.  */
+  Ebl *ebl = ebl_openbackend (elf);
+
+  printf ("%s: elf%d-%s\n\n",
+	  fname, gelf_getclass (elf) == ELFCLASS32 ? 32 : 64,
+	  ebl_backend_name (ebl));
+
+  /* Create the full name of the file.  */
+  size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
+  size_t suffix_len = suffix == NULL ? 0 : strlen (suffix);
+  size_t fname_len = strlen (fname) + 1;
+  char fullname[prefix_len + 1 + fname_len + suffix_len];
+  char *cp = fullname;
+  if (prefix != NULL)
+    cp = mempcpy (cp, prefix, prefix_len);
+  cp = mempcpy (cp, fname, fname_len);
+  if (suffix != NULL)
+    memcpy (cp - 1, suffix, suffix_len + 1);
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0)
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  int result = 0;
+  if (print_disasm)
+    result = show_disasm (ebl, fullname, shstrndx);
+  if (print_relocs && !print_disasm)
+    result = show_relocs (ebl, fullname, shstrndx);
+  if (print_full_content)
+    result = show_full_content (ebl, fullname, shstrndx);
+
+  /* Close the ELF backend library descriptor.  */
+  ebl_closebackend (ebl);
+
+  return result;
+}
+
+
+#include "debugpred.h"
diff --git a/src/ranlib.c b/src/ranlib.c
new file mode 100644
index 0000000..cc0ee23
--- /dev/null
+++ b/src/ranlib.c
@@ -0,0 +1,282 @@
+/* Generate an index to speed access to archives.
+   Copyright (C) 2005-2012 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2005.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <ar.h>
+#include <argp.h>
+#include <assert.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libintl.h>
+#include <locale.h>
+#include <obstack.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <system.h>
+#include <printversion.h>
+
+#include "arlib.h"
+
+
+/* Prototypes for local functions.  */
+static int handle_file (const char *fname);
+
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("Generate an index to speed access to archives.");
+
+/* Strings for arguments in help texts.  */
+static const char args_doc[] = N_("ARCHIVE");
+
+/* Data structure to communicate with argp functions.  */
+static const struct argp argp =
+{
+  options, NULL, args_doc, doc, arlib_argp_children, NULL, NULL
+};
+
+
+int
+main (int argc, char *argv[])
+{
+  /* We use no threads here which can interfere with handling a stream.  */
+  (void) __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+  (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+  (void) __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  (void) textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  int remaining;
+  (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &remaining, NULL);
+
+  /* Tell the library which version we are expecting.  */
+  (void) elf_version (EV_CURRENT);
+
+  /* There must at least be one more parameter specifying the archive.   */
+  if (remaining == argc)
+    {
+      error (0, 0, gettext ("Archive name required"));
+      argp_help (&argp, stderr, ARGP_HELP_SEE, "ranlib");
+      exit (EXIT_FAILURE);
+    }
+
+  /* We accept the names of multiple archives.  */
+  int status = 0;
+  do
+    status |= handle_file (argv[remaining]);
+  while (++remaining < argc);
+
+  return status;
+}
+
+
+static int
+copy_content (Elf *elf, int newfd, off_t off, size_t n)
+{
+  size_t len;
+  char *rawfile = elf_rawfile (elf, &len);
+
+  assert (off + n <= len);
+
+  /* Tell the kernel we will read all the pages sequentially.  */
+  size_t ps = sysconf (_SC_PAGESIZE);
+  if (n > 2 * ps)
+    posix_madvise (rawfile + (off & ~(ps - 1)), n, POSIX_MADV_SEQUENTIAL);
+
+  return write_retry (newfd, rawfile + off, n) != (ssize_t) n;
+}
+
+
+/* Handle a file given on the command line.  */
+static int
+handle_file (const char *fname)
+{
+  int fd = open (fname, O_RDONLY);
+  if (fd == -1)
+    {
+      error (0, errno, gettext ("cannot open '%s'"), fname);
+      return 1;
+    }
+
+  struct stat st;
+  if (fstat (fd, &st) != 0)
+    {
+      error (0, errno, gettext ("cannot stat '%s'"), fname);
+      close (fd);
+      return 1;
+    }
+
+  /* First we walk through the file, looking for all ELF files to
+     collect symbols from.  */
+  Elf *arelf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+  if (arelf == NULL)
+    {
+      error (0, 0, gettext ("cannot create ELF descriptor for '%s': %s"),
+	     fname, elf_errmsg (-1));
+      close (fd);
+      return 1;
+    }
+
+  if (elf_kind (arelf) != ELF_K_AR)
+    {
+      error (0, 0, gettext ("'%s' is no archive"), fname);
+      elf_end (arelf);
+      close (fd);
+      return 1;
+    }
+
+  arlib_init ();
+
+  /* Iterate over the content of the archive.  */
+  off_t index_off = -1;
+  size_t index_size = 0;
+  off_t cur_off = SARMAG;
+  Elf *elf;
+  Elf_Cmd cmd = ELF_C_READ_MMAP;
+  while ((elf = elf_begin (fd, cmd, arelf)) != NULL)
+    {
+      Elf_Arhdr *arhdr = elf_getarhdr (elf);
+      assert (arhdr != NULL);
+
+      /* If this is the index, remember the location.  */
+      if (strcmp (arhdr->ar_name, "/") == 0)
+	{
+	  index_off = elf_getaroff (elf);
+	  index_size = arhdr->ar_size;
+	}
+      else
+	{
+	  arlib_add_symbols (elf, fname, arhdr->ar_name, cur_off);
+	  cur_off += (((arhdr->ar_size + 1) & ~((off_t) 1))
+		      + sizeof (struct ar_hdr));
+	}
+
+      /* Get next archive element.  */
+      cmd = elf_next (elf);
+      if (elf_end (elf) != 0)
+	error (0, 0, gettext ("error while freeing sub-ELF descriptor: %s"),
+	       elf_errmsg (-1));
+    }
+
+  arlib_finalize ();
+
+  /* If the file contains no symbols we need not do anything.  */
+  int status = 0;
+  if (symtab.symsnamelen != 0
+      /* We have to rewrite the file also if it initially had an index
+	 but now does not need one anymore.  */
+      || (symtab.symsnamelen == 0 && index_size != 0))
+    {
+      /* Create a new, temporary file in the same directory as the
+	 original file.  */
+      char tmpfname[strlen (fname) + 7];
+      strcpy (stpcpy (tmpfname, fname), "XXXXXX");
+      int newfd = mkstemp (tmpfname);
+      if (unlikely (newfd == -1))
+	{
+	nonew:
+	  error (0, errno, gettext ("cannot create new file"));
+	  status = 1;
+	}
+      else
+	{
+	  /* Create the header.  */
+	  if (unlikely (write_retry (newfd, ARMAG, SARMAG) != SARMAG))
+	    {
+	      // XXX Use /prof/self/fd/%d ???
+	    nonew_unlink:
+	      unlink (tmpfname);
+	      if (newfd != -1)
+		close (newfd);
+	      goto nonew;
+	    }
+
+	  /* Create the new file.  There are three parts as far we are
+	     concerned: 1. original context before the index, 2. the
+	     new index, 3. everything after the new index.  */
+	  off_t rest_off;
+	  if (index_off != -1)
+	    rest_off = (index_off + sizeof (struct ar_hdr)
+			+ ((index_size + 1) & ~1ul));
+	  else
+	    rest_off = SARMAG;
+
+	  if ((symtab.symsnamelen != 0
+	       && ((write_retry (newfd, symtab.symsoff,
+				 symtab.symsofflen)
+		    != (ssize_t) symtab.symsofflen)
+		   || (write_retry (newfd, symtab.symsname,
+				    symtab.symsnamelen)
+		       != (ssize_t) symtab.symsnamelen)))
+	      /* Even if the original file had content before the
+		 symbol table, we write it in the correct order.  */
+	      || (index_off > SARMAG
+		  && copy_content (arelf, newfd, SARMAG, index_off - SARMAG))
+	      || copy_content (arelf, newfd, rest_off, st.st_size - rest_off)
+	      /* Set the mode of the new file to the same values the
+		 original file has.  */
+	      || fchmod (newfd, st.st_mode & ALLPERMS) != 0
+	      /* Never complain about fchown failing.  */
+	      || (({asm ("" :: "r" (fchown (newfd, st.st_uid, st.st_gid))); }),
+		  close (newfd) != 0)
+	      || (newfd = -1, rename (tmpfname, fname) != 0))
+	    goto nonew_unlink;
+	}
+    }
+
+  elf_end (arelf);
+
+  arlib_fini ();
+
+  close (fd);
+
+  return status;
+}
+
+
+#include "debugpred.h"
diff --git a/src/readelf.c b/src/readelf.c
new file mode 100644
index 0000000..d606cf5
--- /dev/null
+++ b/src/readelf.c
@@ -0,0 +1,9973 @@
+/* Print information from ELF file in human-readable form.
+   Copyright (C) 1999-2017 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 1999.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <assert.h>
+#include <ctype.h>
+#include <dwarf.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <inttypes.h>
+#include <langinfo.h>
+#include <libdw.h>
+#include <libdwfl.h>
+#include <libintl.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <signal.h>
+
+#include <libeu.h>
+#include <system.h>
+#include <printversion.h>
+#include "../libelf/libelfP.h"
+#include "../libelf/common.h"
+#include "../libebl/libeblP.h"
+#include "../libdwelf/libdwelf.h"
+#include "../libdw/libdwP.h"
+#include "../libdwfl/libdwflP.h"
+#include "../libdw/memory-access.h"
+
+#include "../libdw/known-dwarf.h"
+
+#ifdef __linux__
+#define CORE_SIGILL  SIGILL
+#define CORE_SIGBUS  SIGBUS
+#define CORE_SIGFPE  SIGFPE
+#define CORE_SIGSEGV SIGSEGV
+#define CORE_SI_USER SI_USER
+#else
+/* We want the linux version of those as that is what shows up in the core files. */
+#define CORE_SIGILL  4  /* Illegal instruction (ANSI).  */
+#define CORE_SIGBUS  7  /* BUS error (4.2 BSD).  */
+#define CORE_SIGFPE  8  /* Floating-point exception (ANSI).  */
+#define CORE_SIGSEGV 11 /* Segmentation violation (ANSI).  */
+#define CORE_SI_USER 0  /* Sent by kill, sigsend.  */
+#endif
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+/* argp key value for --elf-section, non-ascii.  */
+#define ELF_INPUT_SECTION 256
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, N_("ELF input selection:"), 0 },
+  { "elf-section", ELF_INPUT_SECTION, "SECTION", OPTION_ARG_OPTIONAL,
+    N_("Use the named SECTION (default .gnu_debugdata) as (compressed) ELF "
+       "input data"), 0 },
+  { NULL, 0, NULL, 0, N_("ELF output selection:"), 0 },
+  { "all", 'a', NULL, 0,
+    N_("All these plus -p .strtab -p .dynstr -p .comment"), 0 },
+  { "dynamic", 'd', NULL, 0, N_("Display the dynamic segment"), 0 },
+  { "file-header", 'h', NULL, 0, N_("Display the ELF file header"), 0 },
+  { "histogram", 'I', NULL, 0,
+    N_("Display histogram of bucket list lengths"), 0 },
+  { "program-headers", 'l', NULL, 0, N_("Display the program headers"), 0 },
+  { "segments", 'l', NULL, OPTION_ALIAS | OPTION_HIDDEN, NULL, 0 },
+  { "relocs", 'r', NULL, 0, N_("Display relocations"), 0 },
+  { "section-groups", 'g', NULL, 0, N_("Display the section groups"), 0 },
+  { "section-headers", 'S', NULL, 0, N_("Display the sections' headers"), 0 },
+  { "sections", 'S', NULL, OPTION_ALIAS | OPTION_HIDDEN, NULL, 0 },
+  { "symbols", 's', "SECTION", OPTION_ARG_OPTIONAL,
+    N_("Display the symbol table sections"), 0 },
+  { "version-info", 'V', NULL, 0, N_("Display versioning information"), 0 },
+  { "notes", 'n', NULL, 0, N_("Display the ELF notes"), 0 },
+  { "arch-specific", 'A', NULL, 0,
+    N_("Display architecture specific information, if any"), 0 },
+  { "exception", 'e', NULL, 0,
+    N_("Display sections for exception handling"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Additional output selection:"), 0 },
+  { "debug-dump", 'w', "SECTION", OPTION_ARG_OPTIONAL,
+    N_("Display DWARF section content.  SECTION can be one of abbrev, "
+       "aranges, decodedaranges, frame, gdb_index, info, loc, line, "
+       "decodedline, ranges, pubnames, str, macinfo, macro or exception"), 0 },
+  { "hex-dump", 'x', "SECTION", 0,
+    N_("Dump the uninterpreted contents of SECTION, by number or name"), 0 },
+  { "strings", 'p', "SECTION", OPTION_ARG_OPTIONAL,
+    N_("Print string contents of sections"), 0 },
+  { "string-dump", 'p', NULL, OPTION_ALIAS | OPTION_HIDDEN, NULL, 0 },
+  { "archive-index", 'c', NULL, 0,
+    N_("Display the symbol index of an archive"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Output control:"), 0 },
+  { "numeric-addresses", 'N', NULL, 0,
+    N_("Do not find symbol names for addresses in DWARF data"), 0 },
+  { "unresolved-address-offsets", 'U', NULL, 0,
+    N_("Display just offsets instead of resolving values to addresses in DWARF data"), 0 },
+  { "wide", 'W', NULL, 0,
+    N_("Ignored for compatibility (lines always wide)"), 0 },
+  { "decompress", 'z', NULL, 0,
+    N_("Show compression information for compressed sections (when used with -S); decompress section before dumping data (when used with -p or -x)"), 0 },
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("\
+Print information from ELF file in human-readable form.");
+
+/* 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
+};
+
+/* If non-null, the section from which we should read to (compressed) ELF.  */
+static const char *elf_input_section = NULL;
+
+/* Flags set by the option controlling the output.  */
+
+/* True if dynamic segment should be printed.  */
+static bool print_dynamic_table;
+
+/* True if the file header should be printed.  */
+static bool print_file_header;
+
+/* True if the program headers should be printed.  */
+static bool print_program_header;
+
+/* True if relocations should be printed.  */
+static bool print_relocations;
+
+/* True if the section headers should be printed.  */
+static bool print_section_header;
+
+/* True if the symbol table should be printed.  */
+static bool print_symbol_table;
+
+/* A specific section name, or NULL to print all symbol tables.  */
+static char *symbol_table_section;
+
+/* True if the version information should be printed.  */
+static bool print_version_info;
+
+/* True if section groups should be printed.  */
+static bool print_section_groups;
+
+/* True if bucket list length histogram should be printed.  */
+static bool print_histogram;
+
+/* True if the architecture specific data should be printed.  */
+static bool print_arch;
+
+/* True if note section content should be printed.  */
+static bool print_notes;
+
+/* True if SHF_STRINGS section content should be printed.  */
+static bool print_string_sections;
+
+/* True if archive index should be printed.  */
+static bool print_archive_index;
+
+/* True if any of the control options except print_archive_index is set.  */
+static bool any_control_option;
+
+/* True if we should print addresses from DWARF in symbolic form.  */
+static bool print_address_names = true;
+
+/* True if we should print raw values instead of relativized addresses.  */
+static bool print_unresolved_addresses = false;
+
+/* True if we should print the .debug_aranges section using libdw.  */
+static bool decodedaranges = false;
+
+/* True if we should print the .debug_aranges section using libdw.  */
+static bool decodedline = false;
+
+/* True if we want to show more information about compressed sections.  */
+static bool print_decompress = false;
+
+/* Select printing of debugging sections.  */
+static enum section_e
+{
+  section_abbrev = 1,		/* .debug_abbrev  */
+  section_aranges = 2,		/* .debug_aranges  */
+  section_frame = 4,		/* .debug_frame or .eh_frame & al.  */
+  section_info = 8,		/* .debug_info, .debug_types  */
+  section_types = section_info,
+  section_line = 16,		/* .debug_line  */
+  section_loc = 32,		/* .debug_loc  */
+  section_pubnames = 64,	/* .debug_pubnames  */
+  section_str = 128,		/* .debug_str  */
+  section_macinfo = 256,	/* .debug_macinfo  */
+  section_ranges = 512, 	/* .debug_ranges  */
+  section_exception = 1024,	/* .eh_frame & al.  */
+  section_gdb_index = 2048,	/* .gdb_index  */
+  section_macro = 4096,		/* .debug_macro  */
+  section_all = (section_abbrev | section_aranges | section_frame
+		 | section_info | section_line | section_loc
+		 | section_pubnames | section_str | section_macinfo
+		 | section_ranges | section_exception | section_gdb_index
+		 | section_macro)
+} print_debug_sections, implicit_debug_sections;
+
+/* Select hex dumping of sections.  */
+static struct section_argument *dump_data_sections;
+static struct section_argument **dump_data_sections_tail = &dump_data_sections;
+
+/* Select string dumping of sections.  */
+static struct section_argument *string_sections;
+static struct section_argument **string_sections_tail = &string_sections;
+
+struct section_argument
+{
+  struct section_argument *next;
+  const char *arg;
+  bool implicit;
+};
+
+/* Numbers of sections and program headers in the file.  */
+static size_t shnum;
+static size_t phnum;
+
+
+/* Declarations of local functions.  */
+static void process_file (int fd, const char *fname, bool only_one);
+static void process_elf_file (Dwfl_Module *dwflmod, int fd);
+static void print_ehdr (Ebl *ebl, GElf_Ehdr *ehdr);
+static void print_shdr (Ebl *ebl, GElf_Ehdr *ehdr);
+static void print_phdr (Ebl *ebl, GElf_Ehdr *ehdr);
+static void print_scngrp (Ebl *ebl);
+static void print_dynamic (Ebl *ebl);
+static void print_relocs (Ebl *ebl, GElf_Ehdr *ehdr);
+static void handle_relocs_rel (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn,
+			       GElf_Shdr *shdr);
+static void handle_relocs_rela (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn,
+				GElf_Shdr *shdr);
+static void print_symtab (Ebl *ebl, int type);
+static void handle_symtab (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr);
+static void print_verinfo (Ebl *ebl);
+static void handle_verneed (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr);
+static void handle_verdef (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr);
+static void handle_versym (Ebl *ebl, Elf_Scn *scn,
+			   GElf_Shdr *shdr);
+static void print_debug (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr *ehdr);
+static void handle_hash (Ebl *ebl);
+static void handle_notes (Ebl *ebl, GElf_Ehdr *ehdr);
+static void print_liblist (Ebl *ebl);
+static void print_attributes (Ebl *ebl, const GElf_Ehdr *ehdr);
+static void dump_data (Ebl *ebl);
+static void dump_strings (Ebl *ebl);
+static void print_strings (Ebl *ebl);
+static void dump_archive_index (Elf *, const char *);
+
+
+int
+main (int argc, char *argv[])
+{
+  /* Set locale.  */
+  setlocale (LC_ALL, "");
+
+  /* Initialize the message catalog.  */
+  textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  int remaining;
+  argp_parse (&argp, argc, argv, 0, &remaining, NULL);
+
+  /* Before we start tell the ELF library which version we are using.  */
+  elf_version (EV_CURRENT);
+
+  /* Now process all the files given at the command line.  */
+  bool only_one = remaining + 1 == argc;
+  do
+    {
+      /* Open the file.  */
+      int fd = open (argv[remaining], O_RDONLY);
+      if (fd == -1)
+	{
+	  error (0, errno, gettext ("cannot open input file"));
+	  continue;
+	}
+
+      process_file (fd, argv[remaining], only_one);
+
+      close (fd);
+    }
+  while (++remaining < argc);
+
+  return error_message_count != 0;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg,
+	   struct argp_state *state __attribute__ ((unused)))
+{
+  void add_dump_section (const char *name, bool implicit)
+  {
+    struct section_argument *a = xmalloc (sizeof *a);
+    a->arg = name;
+    a->next = NULL;
+    a->implicit = implicit;
+    struct section_argument ***tailp
+      = key == 'x' ? &dump_data_sections_tail : &string_sections_tail;
+    **tailp = a;
+    *tailp = &a->next;
+  }
+
+  switch (key)
+    {
+    case 'a':
+      print_file_header = true;
+      print_program_header = true;
+      print_relocations = true;
+      print_section_header = true;
+      print_symbol_table = true;
+      print_version_info = true;
+      print_dynamic_table = true;
+      print_section_groups = true;
+      print_histogram = true;
+      print_arch = true;
+      print_notes = true;
+      implicit_debug_sections |= section_exception;
+      add_dump_section (".strtab", true);
+      add_dump_section (".dynstr", true);
+      add_dump_section (".comment", true);
+      any_control_option = true;
+      break;
+    case 'A':
+      print_arch = true;
+      any_control_option = true;
+      break;
+    case 'd':
+      print_dynamic_table = true;
+      any_control_option = true;
+      break;
+    case 'e':
+      print_debug_sections |= section_exception;
+      any_control_option = true;
+      break;
+    case 'g':
+      print_section_groups = true;
+      any_control_option = true;
+      break;
+    case 'h':
+      print_file_header = true;
+      any_control_option = true;
+      break;
+    case 'I':
+      print_histogram = true;
+      any_control_option = true;
+      break;
+    case 'l':
+      print_program_header = true;
+      any_control_option = true;
+      break;
+    case 'n':
+      print_notes = true;
+      any_control_option = true;
+      break;
+    case 'r':
+      print_relocations = true;
+      any_control_option = true;
+     break;
+    case 'S':
+      print_section_header = true;
+      any_control_option = true;
+      break;
+    case 's':
+      print_symbol_table = true;
+      any_control_option = true;
+      symbol_table_section = arg;
+      break;
+    case 'V':
+      print_version_info = true;
+      any_control_option = true;
+      break;
+    case 'c':
+      print_archive_index = true;
+      break;
+    case 'w':
+      if (arg == NULL)
+	print_debug_sections = section_all;
+      else if (strcmp (arg, "abbrev") == 0)
+	print_debug_sections |= section_abbrev;
+      else if (strcmp (arg, "aranges") == 0)
+	print_debug_sections |= section_aranges;
+      else if (strcmp (arg, "decodedaranges") == 0)
+	{
+	  print_debug_sections |= section_aranges;
+	  decodedaranges = true;
+	}
+      else if (strcmp (arg, "ranges") == 0)
+	{
+	  print_debug_sections |= section_ranges;
+	  implicit_debug_sections |= section_info;
+	}
+      else if (strcmp (arg, "frame") == 0 || strcmp (arg, "frames") == 0)
+	print_debug_sections |= section_frame;
+      else if (strcmp (arg, "info") == 0)
+	print_debug_sections |= section_info;
+      else if (strcmp (arg, "loc") == 0)
+	{
+	  print_debug_sections |= section_loc;
+	  implicit_debug_sections |= section_info;
+	}
+      else if (strcmp (arg, "line") == 0)
+	print_debug_sections |= section_line;
+      else if (strcmp (arg, "decodedline") == 0)
+	{
+	  print_debug_sections |= section_line;
+	  decodedline = true;
+	}
+      else if (strcmp (arg, "pubnames") == 0)
+	print_debug_sections |= section_pubnames;
+      else if (strcmp (arg, "str") == 0)
+	print_debug_sections |= section_str;
+      else if (strcmp (arg, "macinfo") == 0)
+	print_debug_sections |= section_macinfo;
+      else if (strcmp (arg, "macro") == 0)
+	print_debug_sections |= section_macro;
+      else if (strcmp (arg, "exception") == 0)
+	print_debug_sections |= section_exception;
+      else if (strcmp (arg, "gdb_index") == 0)
+	print_debug_sections |= section_gdb_index;
+      else
+	{
+	  fprintf (stderr, gettext ("Unknown DWARF debug section `%s'.\n"),
+		   arg);
+	  argp_help (&argp, stderr, ARGP_HELP_SEE,
+		     program_invocation_short_name);
+	  exit (1);
+	}
+      any_control_option = true;
+      break;
+    case 'p':
+      any_control_option = true;
+      if (arg == NULL)
+	{
+	  print_string_sections = true;
+	  break;
+	}
+      FALLTHROUGH;
+    case 'x':
+      add_dump_section (arg, false);
+      any_control_option = true;
+      break;
+    case 'N':
+      print_address_names = false;
+      break;
+    case 'U':
+      print_unresolved_addresses = true;
+      break;
+    case ARGP_KEY_NO_ARGS:
+      fputs (gettext ("Missing file name.\n"), stderr);
+      goto do_argp_help;
+    case ARGP_KEY_FINI:
+      if (! any_control_option && ! print_archive_index)
+	{
+	  fputs (gettext ("No operation specified.\n"), stderr);
+	do_argp_help:
+	  argp_help (&argp, stderr, ARGP_HELP_SEE,
+		     program_invocation_short_name);
+	  exit (EXIT_FAILURE);
+	}
+      break;
+    case 'W':			/* Ignored.  */
+      break;
+    case 'z':
+      print_decompress = true;
+      break;
+    case ELF_INPUT_SECTION:
+      if (arg == NULL)
+	elf_input_section = ".gnu_debugdata";
+      else
+	elf_input_section = arg;
+      break;
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+
+/* Create a file descriptor to read the data from the
+   elf_input_section given a file descriptor to an ELF file.  */
+static int
+open_input_section (int fd)
+{
+  size_t shnums;
+  size_t cnt;
+  size_t shstrndx;
+  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+  if (elf == NULL)
+    {
+      error (0, 0, gettext ("cannot generate Elf descriptor: %s"),
+	     elf_errmsg (-1));
+      return -1;
+    }
+
+  if (elf_getshdrnum (elf, &shnums) < 0)
+    {
+      error (0, 0, gettext ("cannot determine number of sections: %s"),
+	     elf_errmsg (-1));
+    open_error:
+      elf_end (elf);
+      return -1;
+    }
+
+  if (elf_getshdrstrndx (elf, &shstrndx) < 0)
+    {
+      error (0, 0, gettext ("cannot get section header string table index"));
+      goto open_error;
+    }
+
+  for (cnt = 0; cnt < shnums; ++cnt)
+    {
+      Elf_Scn *scn = elf_getscn (elf, cnt);
+      if (scn == NULL)
+	{
+	  error (0, 0, gettext ("cannot get section: %s"),
+		 elf_errmsg (-1));
+	  goto open_error;
+	}
+
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+      if (unlikely (shdr == NULL))
+	{
+	  error (0, 0, gettext ("cannot get section header: %s"),
+		 elf_errmsg (-1));
+	  goto open_error;
+	}
+
+      const char *sname = elf_strptr (elf, shstrndx, shdr->sh_name);
+      if (sname == NULL)
+	{
+	  error (0, 0, gettext ("cannot get section name"));
+	  goto open_error;
+	}
+
+      if (strcmp (sname, elf_input_section) == 0)
+	{
+	  Elf_Data *data = elf_rawdata (scn, NULL);
+	  if (data == NULL)
+	    {
+	      error (0, 0, gettext ("cannot get %s content: %s"),
+		     sname, elf_errmsg (-1));
+	      goto open_error;
+	    }
+
+	  /* Create (and immediately unlink) a temporary file to store
+	     section data in to create a file descriptor for it.  */
+	  const char *tmpdir = getenv ("TMPDIR") ?: P_tmpdir;
+	  static const char suffix[] = "/readelfXXXXXX";
+	  int tmplen = strlen (tmpdir) + sizeof (suffix);
+	  char *tempname = alloca (tmplen);
+	  sprintf (tempname, "%s%s", tmpdir, suffix);
+
+	  int sfd = mkstemp (tempname);
+	  if (sfd == -1)
+	    {
+	      error (0, 0, gettext ("cannot create temp file '%s'"),
+		     tempname);
+	      goto open_error;
+	    }
+	  unlink (tempname);
+
+	  ssize_t size = data->d_size;
+	  if (write_retry (sfd, data->d_buf, size) != size)
+	    {
+	      error (0, 0, gettext ("cannot write section data"));
+	      goto open_error;
+	    }
+
+	  if (elf_end (elf) != 0)
+	    {
+	      error (0, 0, gettext ("error while closing Elf descriptor: %s"),
+		     elf_errmsg (-1));
+	      return -1;
+	    }
+
+	  if (lseek (sfd, 0, SEEK_SET) == -1)
+	    {
+	      error (0, 0, gettext ("error while rewinding file descriptor"));
+	      return -1;
+	    }
+
+	  return sfd;
+	}
+    }
+
+  /* Named section not found.  */
+  if (elf_end (elf) != 0)
+    error (0, 0, gettext ("error while closing Elf descriptor: %s"),
+	   elf_errmsg (-1));
+  return -1;
+}
+
+/* Check if the file is an archive, and if so dump its index.  */
+static void
+check_archive_index (int fd, const char *fname, bool only_one)
+{
+  /* Create an `Elf' descriptor.  */
+  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+  if (elf == NULL)
+    error (0, 0, gettext ("cannot generate Elf descriptor: %s"),
+	   elf_errmsg (-1));
+  else
+    {
+      if (elf_kind (elf) == ELF_K_AR)
+	{
+	  if (!only_one)
+	    printf ("\n%s:\n\n", fname);
+	  dump_archive_index (elf, fname);
+	}
+      else
+	error (0, 0,
+	       gettext ("'%s' is not an archive, cannot print archive index"),
+	       fname);
+
+      /* Now we can close the descriptor.  */
+      if (elf_end (elf) != 0)
+	error (0, 0, gettext ("error while closing Elf descriptor: %s"),
+	       elf_errmsg (-1));
+    }
+}
+
+/* Trivial callback used for checking if we opened an archive.  */
+static int
+count_dwflmod (Dwfl_Module *dwflmod __attribute__ ((unused)),
+	       void **userdata __attribute__ ((unused)),
+	       const char *name __attribute__ ((unused)),
+	       Dwarf_Addr base __attribute__ ((unused)),
+	       void *arg)
+{
+  if (*(bool *) arg)
+    return DWARF_CB_ABORT;
+  *(bool *) arg = true;
+  return DWARF_CB_OK;
+}
+
+struct process_dwflmod_args
+{
+  int fd;
+  bool only_one;
+};
+
+static int
+process_dwflmod (Dwfl_Module *dwflmod,
+		 void **userdata __attribute__ ((unused)),
+		 const char *name __attribute__ ((unused)),
+		 Dwarf_Addr base __attribute__ ((unused)),
+		 void *arg)
+{
+  const struct process_dwflmod_args *a = arg;
+
+  /* Print the file name.  */
+  if (!a->only_one)
+    {
+      const char *fname;
+      dwfl_module_info (dwflmod, NULL, NULL, NULL, NULL, NULL, &fname, NULL);
+
+      printf ("\n%s:\n\n", fname);
+    }
+
+  process_elf_file (dwflmod, a->fd);
+
+  return DWARF_CB_OK;
+}
+
+/* Stub libdwfl callback, only the ELF handle already open is ever used.
+   Only used for finding the alternate debug file if the Dwarf comes from
+   the main file.  We are not interested in separate debuginfo.  */
+static int
+find_no_debuginfo (Dwfl_Module *mod,
+		   void **userdata,
+		   const char *modname,
+		   Dwarf_Addr base,
+		   const char *file_name,
+		   const char *debuglink_file,
+		   GElf_Word debuglink_crc,
+		   char **debuginfo_file_name)
+{
+  Dwarf_Addr dwbias;
+  dwfl_module_info (mod, NULL, NULL, NULL, &dwbias, NULL, NULL, NULL);
+
+  /* We are only interested if the Dwarf has been setup on the main
+     elf file but is only missing the alternate debug link.  If dwbias
+     hasn't even been setup, this is searching for separate debuginfo
+     for the main elf.  We don't care in that case.  */
+  if (dwbias == (Dwarf_Addr) -1)
+    return -1;
+
+  return dwfl_standard_find_debuginfo (mod, userdata, modname, base,
+				       file_name, debuglink_file,
+				       debuglink_crc, debuginfo_file_name);
+}
+
+/* Process one input file.  */
+static void
+process_file (int fd, const char *fname, bool only_one)
+{
+  if (print_archive_index)
+    check_archive_index (fd, fname, only_one);
+
+  if (!any_control_option)
+    return;
+
+  if (elf_input_section != NULL)
+    {
+      /* Replace fname and fd with section content. */
+      char *fnname = alloca (strlen (fname) + strlen (elf_input_section) + 2);
+      sprintf (fnname, "%s:%s", fname, elf_input_section);
+      fd = open_input_section (fd);
+      if (fd == -1)
+        {
+          error (0, 0, gettext ("No such section '%s' in '%s'"),
+		 elf_input_section, fname);
+          return;
+        }
+      fname = fnname;
+    }
+
+  /* Duplicate an fd for dwfl_report_offline to swallow.  */
+  int dwfl_fd = dup (fd);
+  if (unlikely (dwfl_fd < 0))
+    error (EXIT_FAILURE, errno, "dup");
+
+  /* Use libdwfl in a trivial way to open the libdw handle for us.
+     This takes care of applying relocations to DWARF data in ET_REL files.  */
+  static const Dwfl_Callbacks callbacks =
+    {
+      .section_address = dwfl_offline_section_address,
+      .find_debuginfo = find_no_debuginfo
+    };
+  Dwfl *dwfl = dwfl_begin (&callbacks);
+  if (likely (dwfl != NULL))
+    /* Let 0 be the logical address of the file (or first in archive).  */
+    dwfl->offline_next_address = 0;
+  if (dwfl_report_offline (dwfl, fname, fname, dwfl_fd) == NULL)
+    {
+      struct stat st;
+      if (fstat (dwfl_fd, &st) != 0)
+	error (0, errno, gettext ("cannot stat input file"));
+      else if (unlikely (st.st_size == 0))
+	error (0, 0, gettext ("input file is empty"));
+      else
+	error (0, 0, gettext ("failed reading '%s': %s"),
+	       fname, dwfl_errmsg (-1));
+      close (dwfl_fd);		/* Consumed on success, not on failure.  */
+    }
+  else
+    {
+      dwfl_report_end (dwfl, NULL, NULL);
+
+      if (only_one)
+	{
+	  /* Clear ONLY_ONE if we have multiple modules, from an archive.  */
+	  bool seen = false;
+	  only_one = dwfl_getmodules (dwfl, &count_dwflmod, &seen, 0) == 0;
+	}
+
+      /* Process the one or more modules gleaned from this file.  */
+      struct process_dwflmod_args a = { .fd = fd, .only_one = only_one };
+      dwfl_getmodules (dwfl, &process_dwflmod, &a, 0);
+    }
+  dwfl_end (dwfl);
+
+  /* Need to close the replaced fd if we created it.  Caller takes
+     care of original.  */
+  if (elf_input_section != NULL)
+    close (fd);
+}
+
+/* Check whether there are any compressed sections in the ELF file.  */
+static bool
+elf_contains_chdrs (Elf *elf)
+{
+  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_flags & SHF_COMPRESSED) != 0)
+	return true;
+    }
+  return false;
+}
+
+/* Process one ELF file.  */
+static void
+process_elf_file (Dwfl_Module *dwflmod, int fd)
+{
+  GElf_Addr dwflbias;
+  Elf *elf = dwfl_module_getelf (dwflmod, &dwflbias);
+
+  GElf_Ehdr ehdr_mem;
+  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
+
+  if (ehdr == NULL)
+    {
+    elf_error:
+      error (0, 0, gettext ("cannot read ELF header: %s"), elf_errmsg (-1));
+      return;
+    }
+
+  Ebl *ebl = ebl_openbackend (elf);
+  if (unlikely (ebl == NULL))
+    {
+    ebl_error:
+      error (0, errno, gettext ("cannot create EBL handle"));
+      return;
+    }
+
+  /* Determine the number of sections.  */
+  if (unlikely (elf_getshdrnum (ebl->elf, &shnum) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot determine number of sections: %s"),
+	   elf_errmsg (-1));
+
+  /* Determine the number of phdrs.  */
+  if (unlikely (elf_getphdrnum (ebl->elf, &phnum) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot determine number of program headers: %s"),
+	   elf_errmsg (-1));
+
+  /* For an ET_REL file, libdwfl has adjusted the in-core shdrs and
+     may have applied relocation to some sections.  If there are any
+     compressed sections, any pass (or libdw/libdwfl) might have
+     uncompressed them.  So we need to get a fresh Elf handle on the
+     file to display those.  */
+  bool print_unchanged = ((print_section_header
+			   || print_relocations
+			   || dump_data_sections != NULL
+			   || print_notes)
+			  && (ehdr->e_type == ET_REL
+			      || elf_contains_chdrs (ebl->elf)));
+
+  Elf *pure_elf = NULL;
+  Ebl *pure_ebl = ebl;
+  if (print_unchanged)
+    {
+      /* Read the file afresh.  */
+      off_t aroff = elf_getaroff (elf);
+      pure_elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+      if (aroff > 0)
+	{
+	  /* Archive member.  */
+	  (void) elf_rand (pure_elf, aroff);
+	  Elf *armem = elf_begin (-1, ELF_C_READ_MMAP, pure_elf);
+	  elf_end (pure_elf);
+	  pure_elf = armem;
+	}
+      if (pure_elf == NULL)
+	goto elf_error;
+      pure_ebl = ebl_openbackend (pure_elf);
+      if (pure_ebl == NULL)
+	goto ebl_error;
+    }
+
+  if (print_file_header)
+    print_ehdr (ebl, ehdr);
+  if (print_section_header)
+    print_shdr (pure_ebl, ehdr);
+  if (print_program_header)
+    print_phdr (ebl, ehdr);
+  if (print_section_groups)
+    print_scngrp (ebl);
+  if (print_dynamic_table)
+    print_dynamic (ebl);
+  if (print_relocations)
+    print_relocs (pure_ebl, ehdr);
+  if (print_histogram)
+    handle_hash (ebl);
+  if (print_symbol_table)
+    print_symtab (ebl, SHT_DYNSYM);
+  if (print_version_info)
+    print_verinfo (ebl);
+  if (print_symbol_table)
+    print_symtab (ebl, SHT_SYMTAB);
+  if (print_arch)
+    print_liblist (ebl);
+  if (print_arch)
+    print_attributes (ebl, ehdr);
+  if (dump_data_sections != NULL)
+    dump_data (pure_ebl);
+  if (string_sections != NULL)
+    dump_strings (ebl);
+  if ((print_debug_sections | implicit_debug_sections) != 0)
+    print_debug (dwflmod, ebl, ehdr);
+  if (print_notes)
+    handle_notes (pure_ebl, ehdr);
+  if (print_string_sections)
+    print_strings (ebl);
+
+  ebl_closebackend (ebl);
+
+  if (pure_ebl != ebl)
+    {
+      ebl_closebackend (pure_ebl);
+      elf_end (pure_elf);
+    }
+}
+
+
+/* Print file type.  */
+static void
+print_file_type (unsigned short int e_type)
+{
+  if (likely (e_type <= ET_CORE))
+    {
+      static const char *const knowntypes[] =
+      {
+	N_("NONE (None)"),
+	N_("REL (Relocatable file)"),
+	N_("EXEC (Executable file)"),
+	N_("DYN (Shared object file)"),
+	N_("CORE (Core file)")
+      };
+      puts (gettext (knowntypes[e_type]));
+    }
+  else if (e_type >= ET_LOOS && e_type <= ET_HIOS)
+    printf (gettext ("OS Specific: (%x)\n"),  e_type);
+  else if (e_type >= ET_LOPROC /* && e_type <= ET_HIPROC always true */)
+    printf (gettext ("Processor Specific: (%x)\n"),  e_type);
+  else
+    puts ("???");
+}
+
+
+/* Print ELF header.  */
+static void
+print_ehdr (Ebl *ebl, GElf_Ehdr *ehdr)
+{
+  fputs_unlocked (gettext ("ELF Header:\n  Magic:  "), stdout);
+  for (size_t cnt = 0; cnt < EI_NIDENT; ++cnt)
+    printf (" %02hhx", ehdr->e_ident[cnt]);
+
+  printf (gettext ("\n  Class:                             %s\n"),
+	  ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? "ELF32"
+	  : ehdr->e_ident[EI_CLASS] == ELFCLASS64 ? "ELF64"
+	  : "\?\?\?");
+
+  printf (gettext ("  Data:                              %s\n"),
+	  ehdr->e_ident[EI_DATA] == ELFDATA2LSB
+	  ? "2's complement, little endian"
+	  : ehdr->e_ident[EI_DATA] == ELFDATA2MSB
+	  ? "2's complement, big endian" : "\?\?\?");
+
+  printf (gettext ("  Ident Version:                     %hhd %s\n"),
+	  ehdr->e_ident[EI_VERSION],
+	  ehdr->e_ident[EI_VERSION] == EV_CURRENT ? gettext ("(current)")
+	  : "(\?\?\?)");
+
+  char buf[512];
+  printf (gettext ("  OS/ABI:                            %s\n"),
+	  ebl_osabi_name (ebl, ehdr->e_ident[EI_OSABI], buf, sizeof (buf)));
+
+  printf (gettext ("  ABI Version:                       %hhd\n"),
+	  ehdr->e_ident[EI_ABIVERSION]);
+
+  fputs_unlocked (gettext ("  Type:                              "), stdout);
+  print_file_type (ehdr->e_type);
+
+  printf (gettext ("  Machine:                           %s\n"), ebl->name);
+
+  printf (gettext ("  Version:                           %d %s\n"),
+	  ehdr->e_version,
+	  ehdr->e_version  == EV_CURRENT ? gettext ("(current)") : "(\?\?\?)");
+
+  printf (gettext ("  Entry point address:               %#" PRIx64 "\n"),
+	  ehdr->e_entry);
+
+  printf (gettext ("  Start of program headers:          %" PRId64 " %s\n"),
+	  ehdr->e_phoff, gettext ("(bytes into file)"));
+
+  printf (gettext ("  Start of section headers:          %" PRId64 " %s\n"),
+	  ehdr->e_shoff, gettext ("(bytes into file)"));
+
+  printf (gettext ("  Flags:                             %s\n"),
+	  ebl_machine_flag_name (ebl, ehdr->e_flags, buf, sizeof (buf)));
+
+  printf (gettext ("  Size of this header:               %" PRId16 " %s\n"),
+	  ehdr->e_ehsize, gettext ("(bytes)"));
+
+  printf (gettext ("  Size of program header entries:    %" PRId16 " %s\n"),
+	  ehdr->e_phentsize, gettext ("(bytes)"));
+
+  printf (gettext ("  Number of program headers entries: %" PRId16),
+	  ehdr->e_phnum);
+  if (ehdr->e_phnum == PN_XNUM)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
+      if (shdr != NULL)
+	printf (gettext (" (%" PRIu32 " in [0].sh_info)"),
+		(uint32_t) shdr->sh_info);
+      else
+	fputs_unlocked (gettext (" ([0] not available)"), stdout);
+    }
+  fputc_unlocked ('\n', stdout);
+
+  printf (gettext ("  Size of section header entries:    %" PRId16 " %s\n"),
+	  ehdr->e_shentsize, gettext ("(bytes)"));
+
+  printf (gettext ("  Number of section headers entries: %" PRId16),
+	  ehdr->e_shnum);
+  if (ehdr->e_shnum == 0)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
+      if (shdr != NULL)
+	printf (gettext (" (%" PRIu32 " in [0].sh_size)"),
+		(uint32_t) shdr->sh_size);
+      else
+	fputs_unlocked (gettext (" ([0] not available)"), stdout);
+    }
+  fputc_unlocked ('\n', stdout);
+
+  if (unlikely (ehdr->e_shstrndx == SHN_XINDEX))
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
+      if (shdr != NULL)
+	/* We managed to get the zeroth section.  */
+	snprintf (buf, sizeof (buf), gettext (" (%" PRIu32 " in [0].sh_link)"),
+		  (uint32_t) shdr->sh_link);
+      else
+	{
+	  strncpy (buf, gettext (" ([0] not available)"), sizeof (buf));
+	  buf[sizeof (buf) - 1] = '\0';
+	}
+
+      printf (gettext ("  Section header string table index: XINDEX%s\n\n"),
+	      buf);
+    }
+  else
+    printf (gettext ("  Section header string table index: %" PRId16 "\n\n"),
+	    ehdr->e_shstrndx);
+}
+
+
+static const char *
+get_visibility_type (int value)
+{
+  switch (value)
+    {
+    case STV_DEFAULT:
+      return "DEFAULT";
+    case STV_INTERNAL:
+      return "INTERNAL";
+    case STV_HIDDEN:
+      return "HIDDEN";
+    case STV_PROTECTED:
+      return "PROTECTED";
+    default:
+      return "???";
+    }
+}
+
+static const char *
+elf_ch_type_name (unsigned int code)
+{
+  if (code == 0)
+    return "NONE";
+
+  if (code == ELFCOMPRESS_ZLIB)
+    return "ZLIB";
+
+  return "UNKNOWN";
+}
+
+/* Print the section headers.  */
+static void
+print_shdr (Ebl *ebl, GElf_Ehdr *ehdr)
+{
+  size_t cnt;
+  size_t shstrndx;
+
+  if (! print_file_header)
+    printf (gettext ("\
+There are %d section headers, starting at offset %#" PRIx64 ":\n\
+\n"),
+	    ehdr->e_shnum, ehdr->e_shoff);
+
+  /* Get the section header string table index.  */
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  puts (gettext ("Section Headers:"));
+
+  if (ehdr->e_ident[EI_CLASS] == ELFCLASS32)
+    puts (gettext ("[Nr] Name                 Type         Addr     Off    Size   ES Flags Lk Inf Al"));
+  else
+    puts (gettext ("[Nr] Name                 Type         Addr             Off      Size     ES Flags Lk Inf Al"));
+
+  if (print_decompress)
+    {
+      if (ehdr->e_ident[EI_CLASS] == ELFCLASS32)
+	puts (gettext ("     [Compression  Size   Al]"));
+      else
+	puts (gettext ("     [Compression  Size     Al]"));
+    }
+
+  for (cnt = 0; cnt < shnum; ++cnt)
+    {
+      Elf_Scn *scn = elf_getscn (ebl->elf, cnt);
+
+      if (unlikely (scn == NULL))
+	error (EXIT_FAILURE, 0, gettext ("cannot get section: %s"),
+	       elf_errmsg (-1));
+
+      /* Get the section header.  */
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+      if (unlikely (shdr == NULL))
+	error (EXIT_FAILURE, 0, gettext ("cannot get section header: %s"),
+	       elf_errmsg (-1));
+
+      char flagbuf[20];
+      char *cp = flagbuf;
+      if (shdr->sh_flags & SHF_WRITE)
+	*cp++ = 'W';
+      if (shdr->sh_flags & SHF_ALLOC)
+	*cp++ = 'A';
+      if (shdr->sh_flags & SHF_EXECINSTR)
+	*cp++ = 'X';
+      if (shdr->sh_flags & SHF_MERGE)
+	*cp++ = 'M';
+      if (shdr->sh_flags & SHF_STRINGS)
+	*cp++ = 'S';
+      if (shdr->sh_flags & SHF_INFO_LINK)
+	*cp++ = 'I';
+      if (shdr->sh_flags & SHF_LINK_ORDER)
+	*cp++ = 'L';
+      if (shdr->sh_flags & SHF_OS_NONCONFORMING)
+	*cp++ = 'N';
+      if (shdr->sh_flags & SHF_GROUP)
+	*cp++ = 'G';
+      if (shdr->sh_flags & SHF_TLS)
+	*cp++ = 'T';
+      if (shdr->sh_flags & SHF_COMPRESSED)
+	*cp++ = 'C';
+      if (shdr->sh_flags & SHF_ORDERED)
+	*cp++ = 'O';
+      if (shdr->sh_flags & SHF_EXCLUDE)
+	*cp++ = 'E';
+      *cp = '\0';
+
+      const char *sname;
+      char buf[128];
+      sname = elf_strptr (ebl->elf, shstrndx, shdr->sh_name) ?: "<corrupt>";
+      printf ("[%2zu] %-20s %-12s %0*" PRIx64 " %0*" PRIx64 " %0*" PRIx64
+	      " %2" PRId64 " %-5s %2" PRId32 " %3" PRId32
+	      " %2" PRId64 "\n",
+	      cnt, sname,
+	      ebl_section_type_name (ebl, shdr->sh_type, buf, sizeof (buf)),
+	      ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 8 : 16, shdr->sh_addr,
+	      ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 6 : 8, shdr->sh_offset,
+	      ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 6 : 8, shdr->sh_size,
+	      shdr->sh_entsize, flagbuf, shdr->sh_link, shdr->sh_info,
+	      shdr->sh_addralign);
+
+      if (print_decompress)
+	{
+	  if ((shdr->sh_flags & SHF_COMPRESSED) != 0)
+	    {
+	      GElf_Chdr chdr;
+	      if (gelf_getchdr (scn, &chdr) != NULL)
+		printf ("     [ELF %s (%" PRId32 ") %0*" PRIx64
+			" %2" PRId64 "]\n",
+			elf_ch_type_name (chdr.ch_type),
+			chdr.ch_type,
+			ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 6 : 8,
+			chdr.ch_size, chdr.ch_addralign);
+	      else
+		error (0, 0,
+		       gettext ("bad compression header for section %zd: %s"),
+		       elf_ndxscn (scn), elf_errmsg (-1));
+	    }
+	  else if (strncmp(".zdebug", sname, strlen (".zdebug")) == 0)
+	    {
+	      ssize_t size;
+	      if ((size = dwelf_scn_gnu_compressed_size (scn)) >= 0)
+		printf ("     [GNU ZLIB     %0*zx   ]\n",
+			ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 6 : 8, size);
+	      else
+		error (0, 0,
+		       gettext ("bad gnu compressed size for section %zd: %s"),
+		       elf_ndxscn (scn), elf_errmsg (-1));
+	    }
+	}
+    }
+
+  fputc_unlocked ('\n', stdout);
+}
+
+
+/* Print the program header.  */
+static void
+print_phdr (Ebl *ebl, GElf_Ehdr *ehdr)
+{
+  if (phnum == 0)
+    /* No program header, this is OK in relocatable objects.  */
+    return;
+
+  puts (gettext ("Program Headers:"));
+  if (ehdr->e_ident[EI_CLASS] == ELFCLASS32)
+    puts (gettext ("\
+  Type           Offset   VirtAddr   PhysAddr   FileSiz  MemSiz   Flg Align"));
+  else
+    puts (gettext ("\
+  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align"));
+
+  /* Process all program headers.  */
+  bool has_relro = false;
+  GElf_Addr relro_from = 0;
+  GElf_Addr relro_to = 0;
+  for (size_t cnt = 0; cnt < phnum; ++cnt)
+    {
+      char buf[128];
+      GElf_Phdr mem;
+      GElf_Phdr *phdr = gelf_getphdr (ebl->elf, cnt, &mem);
+
+      /* If for some reason the header cannot be returned show this.  */
+      if (unlikely (phdr == NULL))
+	{
+	  puts ("  ???");
+	  continue;
+	}
+
+      printf ("  %-14s 0x%06" PRIx64 " 0x%0*" PRIx64 " 0x%0*" PRIx64
+	      " 0x%06" PRIx64 " 0x%06" PRIx64 " %c%c%c 0x%" PRIx64 "\n",
+	      ebl_segment_type_name (ebl, phdr->p_type, buf, sizeof (buf)),
+	      phdr->p_offset,
+	      ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 8 : 16, phdr->p_vaddr,
+	      ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 8 : 16, phdr->p_paddr,
+	      phdr->p_filesz,
+	      phdr->p_memsz,
+	      phdr->p_flags & PF_R ? 'R' : ' ',
+	      phdr->p_flags & PF_W ? 'W' : ' ',
+	      phdr->p_flags & PF_X ? 'E' : ' ',
+	      phdr->p_align);
+
+      if (phdr->p_type == PT_INTERP)
+	{
+	  /* If we are sure the file offset is valid then we can show
+	     the user the name of the interpreter.  We check whether
+	     there is a section at the file offset.  Normally there
+	     would be a section called ".interp".  But in separate
+	     .debug files it is a NOBITS section (and so doesn't match
+	     with gelf_offscn).  Which probably means the offset is
+	     not valid another reason could be because the ELF file
+	     just doesn't contain any section headers, in that case
+	     just play it safe and don't display anything.  */
+
+	  Elf_Scn *scn = gelf_offscn (ebl->elf, phdr->p_offset);
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+	  size_t maxsize;
+	  char *filedata = elf_rawfile (ebl->elf, &maxsize);
+
+	  if (shdr != NULL && shdr->sh_type == SHT_PROGBITS
+	      && filedata != NULL && phdr->p_offset < maxsize
+	      && phdr->p_filesz <= maxsize - phdr->p_offset
+	      && memchr (filedata + phdr->p_offset, '\0',
+			 phdr->p_filesz) != NULL)
+	    printf (gettext ("\t[Requesting program interpreter: %s]\n"),
+		    filedata + phdr->p_offset);
+	}
+      else if (phdr->p_type == PT_GNU_RELRO)
+	{
+	  has_relro = true;
+	  relro_from = phdr->p_vaddr;
+	  relro_to = relro_from + phdr->p_memsz;
+	}
+    }
+
+  if (ehdr->e_shnum == 0)
+    /* No sections in the file.  Punt.  */
+    return;
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  puts (gettext ("\n Section to Segment mapping:\n  Segment Sections..."));
+
+  for (size_t cnt = 0; cnt < phnum; ++cnt)
+    {
+      /* Print the segment number.  */
+      printf ("   %2.2zu     ", cnt);
+
+      GElf_Phdr phdr_mem;
+      GElf_Phdr *phdr = gelf_getphdr (ebl->elf, cnt, &phdr_mem);
+      /* This must not happen.  */
+      if (unlikely (phdr == NULL))
+	error (EXIT_FAILURE, 0, gettext ("cannot get program header: %s"),
+	       elf_errmsg (-1));
+
+      /* Iterate over the sections.  */
+      bool in_relro = false;
+      bool in_ro = false;
+      for (size_t inner = 1; inner < shnum; ++inner)
+	{
+	  Elf_Scn *scn = elf_getscn (ebl->elf, inner);
+	  /* This should not happen.  */
+	  if (unlikely (scn == NULL))
+	    error (EXIT_FAILURE, 0, gettext ("cannot get section: %s"),
+		   elf_errmsg (-1));
+
+	  /* Get the section header.  */
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	  if (unlikely (shdr == NULL))
+	    error (EXIT_FAILURE, 0,
+		   gettext ("cannot get section header: %s"),
+		   elf_errmsg (-1));
+
+	  if (shdr->sh_size > 0
+	      /* Compare allocated sections by VMA, unallocated
+		 sections by file offset.  */
+	      && (shdr->sh_flags & SHF_ALLOC
+		  ? (shdr->sh_addr >= phdr->p_vaddr
+		     && (shdr->sh_addr + shdr->sh_size
+			 <= phdr->p_vaddr + phdr->p_memsz))
+		  : (shdr->sh_offset >= phdr->p_offset
+		     && (shdr->sh_offset + shdr->sh_size
+			 <= phdr->p_offset + phdr->p_filesz))))
+	    {
+	      if (has_relro && !in_relro
+		  && shdr->sh_addr >= relro_from
+		  && shdr->sh_addr + shdr->sh_size <= relro_to)
+		{
+		  fputs_unlocked (" [RELRO:", stdout);
+		  in_relro = true;
+		}
+	      else if (has_relro && in_relro && shdr->sh_addr >= relro_to)
+		{
+		  fputs_unlocked ("]", stdout);
+		  in_relro =  false;
+		}
+	      else if (has_relro && in_relro
+		       && shdr->sh_addr + shdr->sh_size > relro_to)
+		fputs_unlocked ("] <RELRO:", stdout);
+	      else if (phdr->p_type == PT_LOAD && (phdr->p_flags & PF_W) == 0)
+		{
+		  if (!in_ro)
+		    {
+		      fputs_unlocked (" [RO:", stdout);
+		      in_ro = true;
+		    }
+		}
+	      else
+		{
+		  /* Determine the segment this section is part of.  */
+		  size_t cnt2;
+		  GElf_Phdr phdr2_mem;
+		  GElf_Phdr *phdr2 = NULL;
+		  for (cnt2 = 0; cnt2 < phnum; ++cnt2)
+		    {
+		      phdr2 = gelf_getphdr (ebl->elf, cnt2, &phdr2_mem);
+
+		      if (phdr2 != NULL && phdr2->p_type == PT_LOAD
+			  && shdr->sh_addr >= phdr2->p_vaddr
+			  && (shdr->sh_addr + shdr->sh_size
+			      <= phdr2->p_vaddr + phdr2->p_memsz))
+			break;
+		    }
+
+		  if (cnt2 < phnum)
+		    {
+		      if ((phdr2->p_flags & PF_W) == 0 && !in_ro)
+			{
+			  fputs_unlocked (" [RO:", stdout);
+			  in_ro = true;
+			}
+		      else if ((phdr2->p_flags & PF_W) != 0 && in_ro)
+			{
+			  fputs_unlocked ("]", stdout);
+			  in_ro = false;
+			}
+		    }
+		}
+
+	      printf (" %s",
+		      elf_strptr (ebl->elf, shstrndx, shdr->sh_name));
+
+	      /* Signal that this sectin is only partially covered.  */
+	      if (has_relro && in_relro
+		       && shdr->sh_addr + shdr->sh_size > relro_to)
+		{
+		  fputs_unlocked (">", stdout);
+		  in_relro =  false;
+		}
+	    }
+	}
+      if (in_relro || in_ro)
+	fputs_unlocked ("]", stdout);
+
+      /* Finish the line.  */
+      fputc_unlocked ('\n', stdout);
+    }
+}
+
+
+static const char *
+section_name (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr)
+{
+  return elf_strptr (ebl->elf, ehdr->e_shstrndx, shdr->sh_name) ?: "???";
+}
+
+
+static void
+handle_scngrp (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr)
+{
+  /* Get the data of the section.  */
+  Elf_Data *data = elf_getdata (scn, NULL);
+
+  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
+  GElf_Shdr symshdr_mem;
+  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
+  Elf_Data *symdata = elf_getdata (symscn, NULL);
+
+  if (data == NULL || data->d_size < sizeof (Elf32_Word) || symshdr == NULL
+      || symdata == NULL)
+    return;
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  Elf32_Word *grpref = (Elf32_Word *) data->d_buf;
+
+  GElf_Sym sym_mem;
+  GElf_Sym *sym = gelf_getsym (symdata, shdr->sh_info, &sym_mem);
+
+  printf ((grpref[0] & GRP_COMDAT)
+	  ? ngettext ("\
+\nCOMDAT section group [%2zu] '%s' with signature '%s' contains %zu entry:\n",
+		      "\
+\nCOMDAT section group [%2zu] '%s' with signature '%s' contains %zu entries:\n",
+		      data->d_size / sizeof (Elf32_Word) - 1)
+	  : ngettext ("\
+\nSection group [%2zu] '%s' with signature '%s' contains %zu entry:\n", "\
+\nSection group [%2zu] '%s' with signature '%s' contains %zu entries:\n",
+		      data->d_size / sizeof (Elf32_Word) - 1),
+	  elf_ndxscn (scn),
+	  elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+	  (sym == NULL ? NULL
+	   : elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name))
+	  ?: gettext ("<INVALID SYMBOL>"),
+	  data->d_size / sizeof (Elf32_Word) - 1);
+
+  for (size_t cnt = 1; cnt < data->d_size / sizeof (Elf32_Word); ++cnt)
+    {
+      GElf_Shdr grpshdr_mem;
+      GElf_Shdr *grpshdr = gelf_getshdr (elf_getscn (ebl->elf, grpref[cnt]),
+					 &grpshdr_mem);
+
+      const char *str;
+      printf ("  [%2u] %s\n",
+	      grpref[cnt],
+	      grpshdr != NULL
+	      && (str = elf_strptr (ebl->elf, shstrndx, grpshdr->sh_name))
+	      ? str : gettext ("<INVALID SECTION>"));
+    }
+}
+
+
+static void
+print_scngrp (Ebl *ebl)
+{
+  /* Find all relocation sections and handle them.  */
+  Elf_Scn *scn = NULL;
+
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+       /* Handle the section if it is a symbol table.  */
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr != NULL && shdr->sh_type == SHT_GROUP)
+	{
+	  if ((shdr->sh_flags & SHF_COMPRESSED) != 0)
+	    {
+	      if (elf_compress (scn, 0, 0) < 0)
+		printf ("WARNING: %s [%zd]\n",
+			gettext ("Couldn't uncompress section"),
+			elf_ndxscn (scn));
+	      shdr = gelf_getshdr (scn, &shdr_mem);
+	      if (unlikely (shdr == NULL))
+		error (EXIT_FAILURE, 0,
+		       gettext ("cannot get section [%zd] header: %s"),
+		       elf_ndxscn (scn),
+		       elf_errmsg (-1));
+	    }
+	  handle_scngrp (ebl, scn, shdr);
+	}
+    }
+}
+
+
+static const struct flags
+{
+  int mask;
+  const char *str;
+} dt_flags[] =
+  {
+    { DF_ORIGIN, "ORIGIN" },
+    { DF_SYMBOLIC, "SYMBOLIC" },
+    { DF_TEXTREL, "TEXTREL" },
+    { DF_BIND_NOW, "BIND_NOW" },
+    { DF_STATIC_TLS, "STATIC_TLS" }
+  };
+static const int ndt_flags = sizeof (dt_flags) / sizeof (dt_flags[0]);
+
+static const struct flags dt_flags_1[] =
+  {
+    { DF_1_NOW, "NOW" },
+    { DF_1_GLOBAL, "GLOBAL" },
+    { DF_1_GROUP, "GROUP" },
+    { DF_1_NODELETE, "NODELETE" },
+    { DF_1_LOADFLTR, "LOADFLTR" },
+    { DF_1_INITFIRST, "INITFIRST" },
+    { DF_1_NOOPEN, "NOOPEN" },
+    { DF_1_ORIGIN, "ORIGIN" },
+    { DF_1_DIRECT, "DIRECT" },
+    { DF_1_TRANS, "TRANS" },
+    { DF_1_INTERPOSE, "INTERPOSE" },
+    { DF_1_NODEFLIB, "NODEFLIB" },
+    { DF_1_NODUMP, "NODUMP" },
+    { DF_1_CONFALT, "CONFALT" },
+    { DF_1_ENDFILTEE, "ENDFILTEE" },
+    { DF_1_DISPRELDNE, "DISPRELDNE" },
+    { DF_1_DISPRELPND, "DISPRELPND" },
+  };
+static const int ndt_flags_1 = sizeof (dt_flags_1) / sizeof (dt_flags_1[0]);
+
+static const struct flags dt_feature_1[] =
+  {
+    { DTF_1_PARINIT, "PARINIT" },
+    { DTF_1_CONFEXP, "CONFEXP" }
+  };
+static const int ndt_feature_1 = (sizeof (dt_feature_1)
+				  / sizeof (dt_feature_1[0]));
+
+static const struct flags dt_posflag_1[] =
+  {
+    { DF_P1_LAZYLOAD, "LAZYLOAD" },
+    { DF_P1_GROUPPERM, "GROUPPERM" }
+  };
+static const int ndt_posflag_1 = (sizeof (dt_posflag_1)
+				  / sizeof (dt_posflag_1[0]));
+
+
+static void
+print_flags (int class, GElf_Xword d_val, const struct flags *flags,
+		int nflags)
+{
+  bool first = true;
+  int cnt;
+
+  for (cnt = 0; cnt < nflags; ++cnt)
+    if (d_val & flags[cnt].mask)
+      {
+	if (!first)
+	  putchar_unlocked (' ');
+	fputs_unlocked (flags[cnt].str, stdout);
+	d_val &= ~flags[cnt].mask;
+	first = false;
+      }
+
+  if (d_val != 0)
+    {
+      if (!first)
+	putchar_unlocked (' ');
+      printf ("%#0*" PRIx64, class == ELFCLASS32 ? 10 : 18, d_val);
+    }
+
+  putchar_unlocked ('\n');
+}
+
+
+static void
+print_dt_flags (int class, GElf_Xword d_val)
+{
+  print_flags (class, d_val, dt_flags, ndt_flags);
+}
+
+
+static void
+print_dt_flags_1 (int class, GElf_Xword d_val)
+{
+  print_flags (class, d_val, dt_flags_1, ndt_flags_1);
+}
+
+
+static void
+print_dt_feature_1 (int class, GElf_Xword d_val)
+{
+  print_flags (class, d_val, dt_feature_1, ndt_feature_1);
+}
+
+
+static void
+print_dt_posflag_1 (int class, GElf_Xword d_val)
+{
+  print_flags (class, d_val, dt_posflag_1, ndt_posflag_1);
+}
+
+
+static void
+handle_dynamic (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr)
+{
+  int class = gelf_getclass (ebl->elf);
+  GElf_Shdr glink_mem;
+  GElf_Shdr *glink;
+  Elf_Data *data;
+  size_t cnt;
+  size_t shstrndx;
+  size_t sh_entsize;
+
+  /* Get the data of the section.  */
+  data = elf_getdata (scn, NULL);
+  if (data == NULL)
+    return;
+
+  /* Get the section header string table index.  */
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  sh_entsize = gelf_fsize (ebl->elf, ELF_T_DYN, 1, EV_CURRENT);
+
+  glink = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link), &glink_mem);
+  if (glink == NULL)
+    error (EXIT_FAILURE, 0, gettext ("invalid sh_link value in section %zu"),
+	   elf_ndxscn (scn));
+
+  printf (ngettext ("\
+\nDynamic segment contains %lu entry:\n Addr: %#0*" PRIx64 "  Offset: %#08" PRIx64 "  Link to section: [%2u] '%s'\n",
+		    "\
+\nDynamic segment contains %lu entries:\n Addr: %#0*" PRIx64 "  Offset: %#08" PRIx64 "  Link to section: [%2u] '%s'\n",
+		    shdr->sh_size / sh_entsize),
+	  (unsigned long int) (shdr->sh_size / sh_entsize),
+	  class == ELFCLASS32 ? 10 : 18, shdr->sh_addr,
+	  shdr->sh_offset,
+	  (int) shdr->sh_link,
+	  elf_strptr (ebl->elf, shstrndx, glink->sh_name));
+  fputs_unlocked (gettext ("  Type              Value\n"), stdout);
+
+  for (cnt = 0; cnt < shdr->sh_size / sh_entsize; ++cnt)
+    {
+      GElf_Dyn dynmem;
+      GElf_Dyn *dyn = gelf_getdyn (data, cnt, &dynmem);
+      if (dyn == NULL)
+	break;
+
+      char buf[64];
+      printf ("  %-17s ",
+	      ebl_dynamic_tag_name (ebl, dyn->d_tag, buf, sizeof (buf)));
+
+      switch (dyn->d_tag)
+	{
+	case DT_NULL:
+	case DT_DEBUG:
+	case DT_BIND_NOW:
+	case DT_TEXTREL:
+	  /* No further output.  */
+	  fputc_unlocked ('\n', stdout);
+	  break;
+
+	case DT_NEEDED:
+	  printf (gettext ("Shared library: [%s]\n"),
+		  elf_strptr (ebl->elf, shdr->sh_link, dyn->d_un.d_val));
+	  break;
+
+	case DT_SONAME:
+	  printf (gettext ("Library soname: [%s]\n"),
+		  elf_strptr (ebl->elf, shdr->sh_link, dyn->d_un.d_val));
+	  break;
+
+	case DT_RPATH:
+	  printf (gettext ("Library rpath: [%s]\n"),
+		  elf_strptr (ebl->elf, shdr->sh_link, dyn->d_un.d_val));
+	  break;
+
+	case DT_RUNPATH:
+	  printf (gettext ("Library runpath: [%s]\n"),
+		  elf_strptr (ebl->elf, shdr->sh_link, dyn->d_un.d_val));
+	  break;
+
+	case DT_PLTRELSZ:
+	case DT_RELASZ:
+	case DT_STRSZ:
+	case DT_RELSZ:
+	case DT_RELAENT:
+	case DT_SYMENT:
+	case DT_RELENT:
+	case DT_PLTPADSZ:
+	case DT_MOVEENT:
+	case DT_MOVESZ:
+	case DT_INIT_ARRAYSZ:
+	case DT_FINI_ARRAYSZ:
+	case DT_SYMINSZ:
+	case DT_SYMINENT:
+	case DT_GNU_CONFLICTSZ:
+	case DT_GNU_LIBLISTSZ:
+	  printf (gettext ("%" PRId64 " (bytes)\n"), dyn->d_un.d_val);
+	  break;
+
+	case DT_VERDEFNUM:
+	case DT_VERNEEDNUM:
+	case DT_RELACOUNT:
+	case DT_RELCOUNT:
+	  printf ("%" PRId64 "\n", dyn->d_un.d_val);
+	  break;
+
+	case DT_PLTREL:;
+	  const char *tagname = ebl_dynamic_tag_name (ebl, dyn->d_un.d_val,
+						      NULL, 0);
+	  puts (tagname ?: "???");
+	  break;
+
+	case DT_FLAGS:
+	  print_dt_flags (class, dyn->d_un.d_val);
+	  break;
+
+	case DT_FLAGS_1:
+	  print_dt_flags_1 (class, dyn->d_un.d_val);
+	  break;
+
+	case DT_FEATURE_1:
+	  print_dt_feature_1 (class, dyn->d_un.d_val);
+	  break;
+
+	case DT_POSFLAG_1:
+	  print_dt_posflag_1 (class, dyn->d_un.d_val);
+	  break;
+
+	default:
+	  printf ("%#0*" PRIx64 "\n",
+		  class == ELFCLASS32 ? 10 : 18, dyn->d_un.d_val);
+	  break;
+	}
+    }
+}
+
+
+/* Print the dynamic segment.  */
+static void
+print_dynamic (Ebl *ebl)
+{
+  for (size_t i = 0; i < phnum; ++i)
+    {
+      GElf_Phdr phdr_mem;
+      GElf_Phdr *phdr = gelf_getphdr (ebl->elf, i, &phdr_mem);
+
+      if (phdr != NULL && phdr->p_type == PT_DYNAMIC)
+	{
+	  Elf_Scn *scn = gelf_offscn (ebl->elf, phdr->p_offset);
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	  if (shdr != NULL && shdr->sh_type == SHT_DYNAMIC)
+	    handle_dynamic (ebl, scn, shdr);
+	  break;
+	}
+    }
+}
+
+
+/* Print relocations.  */
+static void
+print_relocs (Ebl *ebl, GElf_Ehdr *ehdr)
+{
+  /* Find all relocation sections and handle them.  */
+  Elf_Scn *scn = NULL;
+
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+       /* Handle the section if it is a symbol table.  */
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (likely (shdr != NULL))
+	{
+	  if (shdr->sh_type == SHT_REL)
+	    handle_relocs_rel (ebl, ehdr, scn, shdr);
+	  else if (shdr->sh_type == SHT_RELA)
+	    handle_relocs_rela (ebl, ehdr, scn, shdr);
+	}
+    }
+}
+
+
+/* Handle a relocation section.  */
+static void
+handle_relocs_rel (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn, GElf_Shdr *shdr)
+{
+  int class = gelf_getclass (ebl->elf);
+  size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_REL, 1, EV_CURRENT);
+  int nentries = shdr->sh_size / sh_entsize;
+
+  /* Get the data of the section.  */
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (data == NULL)
+    return;
+
+  /* Get the symbol table information.  */
+  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
+  GElf_Shdr symshdr_mem;
+  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
+  Elf_Data *symdata = elf_getdata (symscn, NULL);
+
+  /* Get the section header of the section the relocations are for.  */
+  GElf_Shdr destshdr_mem;
+  GElf_Shdr *destshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_info),
+				      &destshdr_mem);
+
+  if (unlikely (symshdr == NULL || symdata == NULL || destshdr == NULL))
+    {
+      printf (gettext ("\nInvalid symbol table at offset %#0" PRIx64 "\n"),
+	      shdr->sh_offset);
+      return;
+    }
+
+  /* Search for the optional extended section index table.  */
+  Elf_Data *xndxdata = NULL;
+  int xndxscnidx = elf_scnshndx (scn);
+  if (unlikely (xndxscnidx > 0))
+    xndxdata = elf_getdata (elf_getscn (ebl->elf, xndxscnidx), NULL);
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  if (shdr->sh_info != 0)
+    printf (ngettext ("\
+\nRelocation section [%2zu] '%s' for section [%2u] '%s' at offset %#0" PRIx64 " contains %d entry:\n",
+		    "\
+\nRelocation section [%2zu] '%s' for section [%2u] '%s' at offset %#0" PRIx64 " contains %d entries:\n",
+		      nentries),
+	    elf_ndxscn (scn),
+	    elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+	    (unsigned int) shdr->sh_info,
+	    elf_strptr (ebl->elf, shstrndx, destshdr->sh_name),
+	    shdr->sh_offset,
+	    nentries);
+  else
+    /* The .rel.dyn section does not refer to a specific section but
+       instead of section index zero.  Do not try to print a section
+       name.  */
+    printf (ngettext ("\
+\nRelocation section [%2u] '%s' at offset %#0" PRIx64 " contains %d entry:\n",
+		    "\
+\nRelocation section [%2u] '%s' at offset %#0" PRIx64 " contains %d entries:\n",
+		      nentries),
+	    (unsigned int) elf_ndxscn (scn),
+	    elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+	    shdr->sh_offset,
+	    nentries);
+  fputs_unlocked (class == ELFCLASS32
+		  ? gettext ("\
+  Offset      Type                 Value       Name\n")
+		  : gettext ("\
+  Offset              Type                 Value               Name\n"),
+	 stdout);
+
+  int is_statically_linked = 0;
+  for (int cnt = 0; cnt < nentries; ++cnt)
+    {
+      GElf_Rel relmem;
+      GElf_Rel *rel = gelf_getrel (data, cnt, &relmem);
+      if (likely (rel != NULL))
+	{
+	  char buf[128];
+	  GElf_Sym symmem;
+	  Elf32_Word xndx;
+	  GElf_Sym *sym = gelf_getsymshndx (symdata, xndxdata,
+					    GELF_R_SYM (rel->r_info),
+					    &symmem, &xndx);
+	  if (unlikely (sym == NULL))
+	    {
+	      /* As a special case we have to handle relocations in static
+		 executables.  This only happens for IRELATIVE relocations
+		 (so far).  There is no symbol table.  */
+	      if (is_statically_linked == 0)
+		{
+		  /* Find the program header and look for a PT_INTERP entry. */
+		  is_statically_linked = -1;
+		  if (ehdr->e_type == ET_EXEC)
+		    {
+		      is_statically_linked = 1;
+
+		      for (size_t inner = 0; inner < phnum; ++inner)
+			{
+			  GElf_Phdr phdr_mem;
+			  GElf_Phdr *phdr = gelf_getphdr (ebl->elf, inner,
+							  &phdr_mem);
+			  if (phdr != NULL && phdr->p_type == PT_INTERP)
+			    {
+			      is_statically_linked = -1;
+			      break;
+			    }
+			}
+		    }
+		}
+
+	      if (is_statically_linked > 0 && shdr->sh_link == 0)
+		printf ("\
+  %#0*" PRIx64 "  %-20s %*s  %s\n",
+			class == ELFCLASS32 ? 10 : 18, rel->r_offset,
+			ebl_reloc_type_check (ebl, GELF_R_TYPE (rel->r_info))
+			/* Avoid the leading R_ which isn't carrying any
+			   information.  */
+			? ebl_reloc_type_name (ebl, GELF_R_TYPE (rel->r_info),
+					       buf, sizeof (buf)) + 2
+			: gettext ("<INVALID RELOC>"),
+			class == ELFCLASS32 ? 10 : 18, "",
+			elf_strptr (ebl->elf, shstrndx, destshdr->sh_name));
+	      else
+		printf ("  %#0*" PRIx64 "  %-20s <%s %ld>\n",
+			class == ELFCLASS32 ? 10 : 18, rel->r_offset,
+			ebl_reloc_type_check (ebl, GELF_R_TYPE (rel->r_info))
+			/* Avoid the leading R_ which isn't carrying any
+			   information.  */
+			? ebl_reloc_type_name (ebl, GELF_R_TYPE (rel->r_info),
+					       buf, sizeof (buf)) + 2
+			: gettext ("<INVALID RELOC>"),
+			gettext ("INVALID SYMBOL"),
+			(long int) GELF_R_SYM (rel->r_info));
+	    }
+	  else if (GELF_ST_TYPE (sym->st_info) != STT_SECTION)
+	    printf ("  %#0*" PRIx64 "  %-20s %#0*" PRIx64 "  %s\n",
+		    class == ELFCLASS32 ? 10 : 18, rel->r_offset,
+		    likely (ebl_reloc_type_check (ebl,
+						  GELF_R_TYPE (rel->r_info)))
+		    /* Avoid the leading R_ which isn't carrying any
+		       information.  */
+		    ? ebl_reloc_type_name (ebl, GELF_R_TYPE (rel->r_info),
+					   buf, sizeof (buf)) + 2
+		    : gettext ("<INVALID RELOC>"),
+		    class == ELFCLASS32 ? 10 : 18, sym->st_value,
+		    elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name));
+	  else
+	    {
+	      /* This is a relocation against a STT_SECTION symbol.  */
+	      GElf_Shdr secshdr_mem;
+	      GElf_Shdr *secshdr;
+	      secshdr = gelf_getshdr (elf_getscn (ebl->elf,
+						  sym->st_shndx == SHN_XINDEX
+						  ? xndx : sym->st_shndx),
+				      &secshdr_mem);
+
+	      if (unlikely (secshdr == NULL))
+		printf ("  %#0*" PRIx64 "  %-20s <%s %ld>\n",
+			class == ELFCLASS32 ? 10 : 18, rel->r_offset,
+			ebl_reloc_type_check (ebl, GELF_R_TYPE (rel->r_info))
+			/* Avoid the leading R_ which isn't carrying any
+			   information.  */
+			? ebl_reloc_type_name (ebl, GELF_R_TYPE (rel->r_info),
+					       buf, sizeof (buf)) + 2
+			: gettext ("<INVALID RELOC>"),
+			gettext ("INVALID SECTION"),
+			(long int) (sym->st_shndx == SHN_XINDEX
+				    ? xndx : sym->st_shndx));
+	      else
+		printf ("  %#0*" PRIx64 "  %-20s %#0*" PRIx64 "  %s\n",
+			class == ELFCLASS32 ? 10 : 18, rel->r_offset,
+			ebl_reloc_type_check (ebl, GELF_R_TYPE (rel->r_info))
+			/* Avoid the leading R_ which isn't carrying any
+			   information.  */
+			? ebl_reloc_type_name (ebl, GELF_R_TYPE (rel->r_info),
+					       buf, sizeof (buf)) + 2
+			: gettext ("<INVALID RELOC>"),
+			class == ELFCLASS32 ? 10 : 18, sym->st_value,
+			elf_strptr (ebl->elf, shstrndx, secshdr->sh_name));
+	    }
+	}
+    }
+}
+
+
+/* Handle a relocation section.  */
+static void
+handle_relocs_rela (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn, GElf_Shdr *shdr)
+{
+  int class = gelf_getclass (ebl->elf);
+  size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_RELA, 1, EV_CURRENT);
+  int nentries = shdr->sh_size / sh_entsize;
+
+  /* Get the data of the section.  */
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (data == NULL)
+    return;
+
+  /* Get the symbol table information.  */
+  Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
+  GElf_Shdr symshdr_mem;
+  GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
+  Elf_Data *symdata = elf_getdata (symscn, NULL);
+
+  /* Get the section header of the section the relocations are for.  */
+  GElf_Shdr destshdr_mem;
+  GElf_Shdr *destshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_info),
+				      &destshdr_mem);
+
+  if (unlikely (symshdr == NULL || symdata == NULL || destshdr == NULL))
+    {
+      printf (gettext ("\nInvalid symbol table at offset %#0" PRIx64 "\n"),
+	      shdr->sh_offset);
+      return;
+    }
+
+  /* Search for the optional extended section index table.  */
+  Elf_Data *xndxdata = NULL;
+  int xndxscnidx = elf_scnshndx (scn);
+  if (unlikely (xndxscnidx > 0))
+    xndxdata = elf_getdata (elf_getscn (ebl->elf, xndxscnidx), NULL);
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  if (shdr->sh_info != 0)
+    printf (ngettext ("\
+\nRelocation section [%2zu] '%s' for section [%2u] '%s' at offset %#0" PRIx64 " contains %d entry:\n",
+		    "\
+\nRelocation section [%2zu] '%s' for section [%2u] '%s' at offset %#0" PRIx64 " contains %d entries:\n",
+		    nentries),
+	  elf_ndxscn (scn),
+	  elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+	  (unsigned int) shdr->sh_info,
+	  elf_strptr (ebl->elf, shstrndx, destshdr->sh_name),
+	  shdr->sh_offset,
+	  nentries);
+  else
+    /* The .rela.dyn section does not refer to a specific section but
+       instead of section index zero.  Do not try to print a section
+       name.  */
+    printf (ngettext ("\
+\nRelocation section [%2u] '%s' at offset %#0" PRIx64 " contains %d entry:\n",
+		    "\
+\nRelocation section [%2u] '%s' at offset %#0" PRIx64 " contains %d entries:\n",
+		      nentries),
+	    (unsigned int) elf_ndxscn (scn),
+	    elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+	    shdr->sh_offset,
+	    nentries);
+  fputs_unlocked (class == ELFCLASS32
+		  ? gettext ("\
+  Offset      Type            Value       Addend Name\n")
+		  : gettext ("\
+  Offset              Type            Value               Addend Name\n"),
+		  stdout);
+
+  int is_statically_linked = 0;
+  for (int cnt = 0; cnt < nentries; ++cnt)
+    {
+      GElf_Rela relmem;
+      GElf_Rela *rel = gelf_getrela (data, cnt, &relmem);
+      if (likely (rel != NULL))
+	{
+	  char buf[64];
+	  GElf_Sym symmem;
+	  Elf32_Word xndx;
+	  GElf_Sym *sym = gelf_getsymshndx (symdata, xndxdata,
+					    GELF_R_SYM (rel->r_info),
+					    &symmem, &xndx);
+
+	  if (unlikely (sym == NULL))
+	    {
+	      /* As a special case we have to handle relocations in static
+		 executables.  This only happens for IRELATIVE relocations
+		 (so far).  There is no symbol table.  */
+	      if (is_statically_linked == 0)
+		{
+		  /* Find the program header and look for a PT_INTERP entry. */
+		  is_statically_linked = -1;
+		  if (ehdr->e_type == ET_EXEC)
+		    {
+		      is_statically_linked = 1;
+
+		      for (size_t inner = 0; inner < phnum; ++inner)
+			{
+			  GElf_Phdr phdr_mem;
+			  GElf_Phdr *phdr = gelf_getphdr (ebl->elf, inner,
+							  &phdr_mem);
+			  if (phdr != NULL && phdr->p_type == PT_INTERP)
+			    {
+			      is_statically_linked = -1;
+			      break;
+			    }
+			}
+		    }
+		}
+
+	      if (is_statically_linked > 0 && shdr->sh_link == 0)
+		printf ("\
+  %#0*" PRIx64 "  %-15s %*s  %#6" PRIx64 " %s\n",
+			class == ELFCLASS32 ? 10 : 18, rel->r_offset,
+			ebl_reloc_type_check (ebl, GELF_R_TYPE (rel->r_info))
+			/* Avoid the leading R_ which isn't carrying any
+			   information.  */
+			? ebl_reloc_type_name (ebl, GELF_R_TYPE (rel->r_info),
+					       buf, sizeof (buf)) + 2
+			: gettext ("<INVALID RELOC>"),
+			class == ELFCLASS32 ? 10 : 18, "",
+			rel->r_addend,
+			elf_strptr (ebl->elf, shstrndx, destshdr->sh_name));
+	      else
+		printf ("  %#0*" PRIx64 "  %-15s <%s %ld>\n",
+			class == ELFCLASS32 ? 10 : 18, rel->r_offset,
+			ebl_reloc_type_check (ebl, GELF_R_TYPE (rel->r_info))
+			/* Avoid the leading R_ which isn't carrying any
+			   information.  */
+			? ebl_reloc_type_name (ebl, GELF_R_TYPE (rel->r_info),
+					       buf, sizeof (buf)) + 2
+			: gettext ("<INVALID RELOC>"),
+			gettext ("INVALID SYMBOL"),
+			(long int) GELF_R_SYM (rel->r_info));
+	    }
+	  else if (GELF_ST_TYPE (sym->st_info) != STT_SECTION)
+	    printf ("\
+  %#0*" PRIx64 "  %-15s %#0*" PRIx64 "  %+6" PRId64 " %s\n",
+		    class == ELFCLASS32 ? 10 : 18, rel->r_offset,
+		    likely (ebl_reloc_type_check (ebl,
+						  GELF_R_TYPE (rel->r_info)))
+		    /* Avoid the leading R_ which isn't carrying any
+		       information.  */
+		    ? ebl_reloc_type_name (ebl, GELF_R_TYPE (rel->r_info),
+					   buf, sizeof (buf)) + 2
+		    : gettext ("<INVALID RELOC>"),
+		    class == ELFCLASS32 ? 10 : 18, sym->st_value,
+		    rel->r_addend,
+		    elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name));
+	  else
+	    {
+	      /* This is a relocation against a STT_SECTION symbol.  */
+	      GElf_Shdr secshdr_mem;
+	      GElf_Shdr *secshdr;
+	      secshdr = gelf_getshdr (elf_getscn (ebl->elf,
+						  sym->st_shndx == SHN_XINDEX
+						  ? xndx : sym->st_shndx),
+				      &secshdr_mem);
+
+	      if (unlikely (secshdr == NULL))
+		printf ("  %#0*" PRIx64 "  %-15s <%s %ld>\n",
+			class == ELFCLASS32 ? 10 : 18, rel->r_offset,
+			ebl_reloc_type_check (ebl, GELF_R_TYPE (rel->r_info))
+			/* Avoid the leading R_ which isn't carrying any
+			   information.  */
+			? ebl_reloc_type_name (ebl, GELF_R_TYPE (rel->r_info),
+					       buf, sizeof (buf)) + 2
+			: gettext ("<INVALID RELOC>"),
+			gettext ("INVALID SECTION"),
+			(long int) (sym->st_shndx == SHN_XINDEX
+				    ? xndx : sym->st_shndx));
+	      else
+		printf ("\
+  %#0*" PRIx64 "  %-15s %#0*" PRIx64 "  %+6" PRId64 " %s\n",
+			class == ELFCLASS32 ? 10 : 18, rel->r_offset,
+			ebl_reloc_type_check (ebl, GELF_R_TYPE (rel->r_info))
+			/* Avoid the leading R_ which isn't carrying any
+			   information.  */
+			? ebl_reloc_type_name (ebl, GELF_R_TYPE (rel->r_info),
+					       buf, sizeof (buf)) + 2
+			: gettext ("<INVALID RELOC>"),
+			class == ELFCLASS32 ? 10 : 18, sym->st_value,
+			rel->r_addend,
+			elf_strptr (ebl->elf, shstrndx, secshdr->sh_name));
+	    }
+	}
+    }
+}
+
+
+/* Print the program header.  */
+static void
+print_symtab (Ebl *ebl, int type)
+{
+  /* Find the symbol table(s).  For this we have to search through the
+     section table.  */
+  Elf_Scn *scn = NULL;
+
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      /* Handle the section if it is a symbol table.  */
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr != NULL && shdr->sh_type == (GElf_Word) type)
+	{
+	  if (symbol_table_section != NULL)
+	    {
+	      /* Get the section header string table index.  */
+	      size_t shstrndx;
+	      const char *sname;
+	      if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+		error (EXIT_FAILURE, 0,
+		       gettext ("cannot get section header string table index"));
+	      sname = elf_strptr (ebl->elf, shstrndx, shdr->sh_name);
+	      if (sname == NULL || strcmp (sname, symbol_table_section) != 0)
+		continue;
+	    }
+
+	  if ((shdr->sh_flags & SHF_COMPRESSED) != 0)
+	    {
+	      if (elf_compress (scn, 0, 0) < 0)
+		printf ("WARNING: %s [%zd]\n",
+			gettext ("Couldn't uncompress section"),
+			elf_ndxscn (scn));
+	      shdr = gelf_getshdr (scn, &shdr_mem);
+	      if (unlikely (shdr == NULL))
+		error (EXIT_FAILURE, 0,
+		       gettext ("cannot get section [%zd] header: %s"),
+		       elf_ndxscn (scn), elf_errmsg (-1));
+	    }
+	  handle_symtab (ebl, scn, shdr);
+	}
+    }
+}
+
+
+static void
+handle_symtab (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr)
+{
+  Elf_Data *versym_data = NULL;
+  Elf_Data *verneed_data = NULL;
+  Elf_Data *verdef_data = NULL;
+  Elf_Data *xndx_data = NULL;
+  int class = gelf_getclass (ebl->elf);
+  Elf32_Word verneed_stridx = 0;
+  Elf32_Word verdef_stridx = 0;
+
+  /* Get the data of the section.  */
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (data == NULL)
+    return;
+
+  /* Find out whether we have other sections we might need.  */
+  Elf_Scn *runscn = NULL;
+  while ((runscn = elf_nextscn (ebl->elf, runscn)) != NULL)
+    {
+      GElf_Shdr runshdr_mem;
+      GElf_Shdr *runshdr = gelf_getshdr (runscn, &runshdr_mem);
+
+      if (likely (runshdr != NULL))
+	{
+	  if (runshdr->sh_type == SHT_GNU_versym
+	      && runshdr->sh_link == elf_ndxscn (scn))
+	    /* Bingo, found the version information.  Now get the data.  */
+	    versym_data = elf_getdata (runscn, NULL);
+	  else if (runshdr->sh_type == SHT_GNU_verneed)
+	    {
+	      /* This is the information about the needed versions.  */
+	      verneed_data = elf_getdata (runscn, NULL);
+	      verneed_stridx = runshdr->sh_link;
+	    }
+	  else if (runshdr->sh_type == SHT_GNU_verdef)
+	    {
+	      /* This is the information about the defined versions.  */
+	      verdef_data = elf_getdata (runscn, NULL);
+	      verdef_stridx = runshdr->sh_link;
+	    }
+	  else if (runshdr->sh_type == SHT_SYMTAB_SHNDX
+	      && runshdr->sh_link == elf_ndxscn (scn))
+	    /* Extended section index.  */
+	    xndx_data = elf_getdata (runscn, NULL);
+	}
+    }
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  GElf_Shdr glink_mem;
+  GElf_Shdr *glink = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
+				   &glink_mem);
+  if (glink == NULL)
+    error (EXIT_FAILURE, 0, gettext ("invalid sh_link value in section %zu"),
+	   elf_ndxscn (scn));
+
+  /* Now we can compute the number of entries in the section.  */
+  unsigned int nsyms = data->d_size / (class == ELFCLASS32
+				       ? sizeof (Elf32_Sym)
+				       : sizeof (Elf64_Sym));
+
+  printf (ngettext ("\nSymbol table [%2u] '%s' contains %u entry:\n",
+		    "\nSymbol table [%2u] '%s' contains %u entries:\n",
+		    nsyms),
+	  (unsigned int) elf_ndxscn (scn),
+	  elf_strptr (ebl->elf, shstrndx, shdr->sh_name), nsyms);
+  printf (ngettext (" %lu local symbol  String table: [%2u] '%s'\n",
+		    " %lu local symbols  String table: [%2u] '%s'\n",
+		    shdr->sh_info),
+	  (unsigned long int) shdr->sh_info,
+	  (unsigned int) shdr->sh_link,
+	  elf_strptr (ebl->elf, shstrndx, glink->sh_name));
+
+  fputs_unlocked (class == ELFCLASS32
+		  ? gettext ("\
+  Num:    Value   Size Type    Bind   Vis          Ndx Name\n")
+		  : gettext ("\
+  Num:            Value   Size Type    Bind   Vis          Ndx Name\n"),
+		  stdout);
+
+  for (unsigned int cnt = 0; cnt < nsyms; ++cnt)
+    {
+      char typebuf[64];
+      char bindbuf[64];
+      char scnbuf[64];
+      Elf32_Word xndx;
+      GElf_Sym sym_mem;
+      GElf_Sym *sym = gelf_getsymshndx (data, xndx_data, cnt, &sym_mem, &xndx);
+
+      if (unlikely (sym == NULL))
+	continue;
+
+      /* Determine the real section index.  */
+      if (likely (sym->st_shndx != SHN_XINDEX))
+	xndx = sym->st_shndx;
+
+      printf (gettext ("\
+%5u: %0*" PRIx64 " %6" PRId64 " %-7s %-6s %-9s %6s %s"),
+	      cnt,
+	      class == ELFCLASS32 ? 8 : 16,
+	      sym->st_value,
+	      sym->st_size,
+	      ebl_symbol_type_name (ebl, GELF_ST_TYPE (sym->st_info),
+				    typebuf, sizeof (typebuf)),
+	      ebl_symbol_binding_name (ebl, GELF_ST_BIND (sym->st_info),
+				       bindbuf, sizeof (bindbuf)),
+	      get_visibility_type (GELF_ST_VISIBILITY (sym->st_other)),
+	      ebl_section_name (ebl, sym->st_shndx, xndx, scnbuf,
+				sizeof (scnbuf), NULL, shnum),
+	      elf_strptr (ebl->elf, shdr->sh_link, sym->st_name));
+
+      if (versym_data != NULL)
+	{
+	  /* Get the version information.  */
+	  GElf_Versym versym_mem;
+	  GElf_Versym *versym = gelf_getversym (versym_data, cnt, &versym_mem);
+
+	  if (versym != NULL && ((*versym & 0x8000) != 0 || *versym > 1))
+	    {
+	      bool is_nobits = false;
+	      bool check_def = xndx != SHN_UNDEF;
+
+	      if (xndx < SHN_LORESERVE || sym->st_shndx == SHN_XINDEX)
+		{
+		  GElf_Shdr symshdr_mem;
+		  GElf_Shdr *symshdr =
+		    gelf_getshdr (elf_getscn (ebl->elf, xndx), &symshdr_mem);
+
+		  is_nobits = (symshdr != NULL
+			       && symshdr->sh_type == SHT_NOBITS);
+		}
+
+	      if (is_nobits || ! check_def)
+		{
+		  /* We must test both.  */
+		  GElf_Vernaux vernaux_mem;
+		  GElf_Vernaux *vernaux = NULL;
+		  size_t vn_offset = 0;
+
+		  GElf_Verneed verneed_mem;
+		  GElf_Verneed *verneed = gelf_getverneed (verneed_data, 0,
+							   &verneed_mem);
+		  while (verneed != NULL)
+		    {
+		      size_t vna_offset = vn_offset;
+
+		      vernaux = gelf_getvernaux (verneed_data,
+						 vna_offset += verneed->vn_aux,
+						 &vernaux_mem);
+		      while (vernaux != NULL
+			     && vernaux->vna_other != *versym
+			     && vernaux->vna_next != 0)
+			{
+			  /* Update the offset.  */
+			  vna_offset += vernaux->vna_next;
+
+			  vernaux = (vernaux->vna_next == 0
+				     ? NULL
+				     : gelf_getvernaux (verneed_data,
+							vna_offset,
+							&vernaux_mem));
+			}
+
+		      /* Check whether we found the version.  */
+		      if (vernaux != NULL && vernaux->vna_other == *versym)
+			/* Found it.  */
+			break;
+
+		      vn_offset += verneed->vn_next;
+		      verneed = (verneed->vn_next == 0
+				 ? NULL
+				 : gelf_getverneed (verneed_data, vn_offset,
+						    &verneed_mem));
+		    }
+
+		  if (vernaux != NULL && vernaux->vna_other == *versym)
+		    {
+		      printf ("@%s (%u)",
+			      elf_strptr (ebl->elf, verneed_stridx,
+					  vernaux->vna_name),
+			      (unsigned int) vernaux->vna_other);
+		      check_def = 0;
+		    }
+		  else if (unlikely (! is_nobits))
+		    error (0, 0, gettext ("bad dynamic symbol"));
+		  else
+		    check_def = 1;
+		}
+
+	      if (check_def && *versym != 0x8001)
+		{
+		  /* We must test both.  */
+		  size_t vd_offset = 0;
+
+		  GElf_Verdef verdef_mem;
+		  GElf_Verdef *verdef = gelf_getverdef (verdef_data, 0,
+							&verdef_mem);
+		  while (verdef != NULL)
+		    {
+		      if (verdef->vd_ndx == (*versym & 0x7fff))
+			/* Found the definition.  */
+			break;
+
+		      vd_offset += verdef->vd_next;
+		      verdef = (verdef->vd_next == 0
+				? NULL
+				: gelf_getverdef (verdef_data, vd_offset,
+						  &verdef_mem));
+		    }
+
+		  if (verdef != NULL)
+		    {
+		      GElf_Verdaux verdaux_mem;
+		      GElf_Verdaux *verdaux
+			= gelf_getverdaux (verdef_data,
+					   vd_offset + verdef->vd_aux,
+					   &verdaux_mem);
+
+		      if (verdaux != NULL)
+			printf ((*versym & 0x8000) ? "@%s" : "@@%s",
+				elf_strptr (ebl->elf, verdef_stridx,
+					    verdaux->vda_name));
+		    }
+		}
+	    }
+	}
+
+      putchar_unlocked ('\n');
+    }
+}
+
+
+/* Print version information.  */
+static void
+print_verinfo (Ebl *ebl)
+{
+  /* Find the version information sections.  For this we have to
+     search through the section table.  */
+  Elf_Scn *scn = NULL;
+
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      /* Handle the section if it is part of the versioning handling.  */
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (likely (shdr != NULL))
+	{
+	  if (shdr->sh_type == SHT_GNU_verneed)
+	    handle_verneed (ebl, scn, shdr);
+	  else if (shdr->sh_type == SHT_GNU_verdef)
+	    handle_verdef (ebl, scn, shdr);
+	  else if (shdr->sh_type == SHT_GNU_versym)
+	    handle_versym (ebl, scn, shdr);
+	}
+    }
+}
+
+
+static const char *
+get_ver_flags (unsigned int flags)
+{
+  static char buf[32];
+  char *endp;
+
+  if (flags == 0)
+    return gettext ("none");
+
+  if (flags & VER_FLG_BASE)
+    endp = stpcpy (buf, "BASE ");
+  else
+    endp = buf;
+
+  if (flags & VER_FLG_WEAK)
+    {
+      if (endp != buf)
+	endp = stpcpy (endp, "| ");
+
+      endp = stpcpy (endp, "WEAK ");
+    }
+
+  if (unlikely (flags & ~(VER_FLG_BASE | VER_FLG_WEAK)))
+    {
+      strncpy (endp, gettext ("| <unknown>"), buf + sizeof (buf) - endp);
+      buf[sizeof (buf) - 1] = '\0';
+    }
+
+  return buf;
+}
+
+
+static void
+handle_verneed (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr)
+{
+  int class = gelf_getclass (ebl->elf);
+
+  /* Get the data of the section.  */
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (data == NULL)
+    return;
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  GElf_Shdr glink_mem;
+  GElf_Shdr *glink = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
+				   &glink_mem);
+  if (glink == NULL)
+    error (EXIT_FAILURE, 0, gettext ("invalid sh_link value in section %zu"),
+	   elf_ndxscn (scn));
+
+  printf (ngettext ("\
+\nVersion needs section [%2u] '%s' contains %d entry:\n Addr: %#0*" PRIx64 "  Offset: %#08" PRIx64 "  Link to section: [%2u] '%s'\n",
+		    "\
+\nVersion needs section [%2u] '%s' contains %d entries:\n Addr: %#0*" PRIx64 "  Offset: %#08" PRIx64 "  Link to section: [%2u] '%s'\n",
+		    shdr->sh_info),
+	  (unsigned int) elf_ndxscn (scn),
+	  elf_strptr (ebl->elf, shstrndx, shdr->sh_name), shdr->sh_info,
+	  class == ELFCLASS32 ? 10 : 18, shdr->sh_addr,
+	  shdr->sh_offset,
+	  (unsigned int) shdr->sh_link,
+	  elf_strptr (ebl->elf, shstrndx, glink->sh_name));
+
+  unsigned int offset = 0;
+  for (int cnt = shdr->sh_info; --cnt >= 0; )
+    {
+      /* Get the data at the next offset.  */
+      GElf_Verneed needmem;
+      GElf_Verneed *need = gelf_getverneed (data, offset, &needmem);
+      if (unlikely (need == NULL))
+	break;
+
+      printf (gettext ("  %#06x: Version: %hu  File: %s  Cnt: %hu\n"),
+	      offset, (unsigned short int) need->vn_version,
+	      elf_strptr (ebl->elf, shdr->sh_link, need->vn_file),
+	      (unsigned short int) need->vn_cnt);
+
+      unsigned int auxoffset = offset + need->vn_aux;
+      for (int cnt2 = need->vn_cnt; --cnt2 >= 0; )
+	{
+	  GElf_Vernaux auxmem;
+	  GElf_Vernaux *aux = gelf_getvernaux (data, auxoffset, &auxmem);
+	  if (unlikely (aux == NULL))
+	    break;
+
+	  printf (gettext ("  %#06x: Name: %s  Flags: %s  Version: %hu\n"),
+		  auxoffset,
+		  elf_strptr (ebl->elf, shdr->sh_link, aux->vna_name),
+		  get_ver_flags (aux->vna_flags),
+		  (unsigned short int) aux->vna_other);
+
+	  if (aux->vna_next == 0)
+	    break;
+
+	  auxoffset += aux->vna_next;
+	}
+
+      /* Find the next offset.  */
+      if (need->vn_next == 0)
+	break;
+
+      offset += need->vn_next;
+    }
+}
+
+
+static void
+handle_verdef (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr)
+{
+  /* Get the data of the section.  */
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (data == NULL)
+    return;
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  GElf_Shdr glink_mem;
+  GElf_Shdr *glink = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
+				   &glink_mem);
+  if (glink == NULL)
+    error (EXIT_FAILURE, 0, gettext ("invalid sh_link value in section %zu"),
+	   elf_ndxscn (scn));
+
+  int class = gelf_getclass (ebl->elf);
+  printf (ngettext ("\
+\nVersion definition section [%2u] '%s' contains %d entry:\n Addr: %#0*" PRIx64 "  Offset: %#08" PRIx64 "  Link to section: [%2u] '%s'\n",
+		    "\
+\nVersion definition section [%2u] '%s' contains %d entries:\n Addr: %#0*" PRIx64 "  Offset: %#08" PRIx64 "  Link to section: [%2u] '%s'\n",
+		    shdr->sh_info),
+	  (unsigned int) elf_ndxscn (scn),
+	  elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+	  shdr->sh_info,
+	  class == ELFCLASS32 ? 10 : 18, shdr->sh_addr,
+	  shdr->sh_offset,
+	  (unsigned int) shdr->sh_link,
+	  elf_strptr (ebl->elf, shstrndx, glink->sh_name));
+
+  unsigned int offset = 0;
+  for (int cnt = shdr->sh_info; --cnt >= 0; )
+    {
+      /* Get the data at the next offset.  */
+      GElf_Verdef defmem;
+      GElf_Verdef *def = gelf_getverdef (data, offset, &defmem);
+      if (unlikely (def == NULL))
+	break;
+
+      unsigned int auxoffset = offset + def->vd_aux;
+      GElf_Verdaux auxmem;
+      GElf_Verdaux *aux = gelf_getverdaux (data, auxoffset, &auxmem);
+      if (unlikely (aux == NULL))
+	break;
+
+      printf (gettext ("\
+  %#06x: Version: %hd  Flags: %s  Index: %hd  Cnt: %hd  Name: %s\n"),
+	      offset, def->vd_version,
+	      get_ver_flags (def->vd_flags),
+	      def->vd_ndx,
+	      def->vd_cnt,
+	      elf_strptr (ebl->elf, shdr->sh_link, aux->vda_name));
+
+      auxoffset += aux->vda_next;
+      for (int cnt2 = 1; cnt2 < def->vd_cnt; ++cnt2)
+	{
+	  aux = gelf_getverdaux (data, auxoffset, &auxmem);
+	  if (unlikely (aux == NULL))
+	    break;
+
+	  printf (gettext ("  %#06x: Parent %d: %s\n"),
+		  auxoffset, cnt2,
+		  elf_strptr (ebl->elf, shdr->sh_link, aux->vda_name));
+
+	  if (aux->vda_next == 0)
+	    break;
+
+	  auxoffset += aux->vda_next;
+	}
+
+      /* Find the next offset.  */
+      if (def->vd_next == 0)
+	break;
+      offset += def->vd_next;
+    }
+}
+
+
+static void
+handle_versym (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr)
+{
+  int class = gelf_getclass (ebl->elf);
+  const char **vername;
+  const char **filename;
+
+  /* Get the data of the section.  */
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (data == NULL)
+    return;
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  /* We have to find the version definition section and extract the
+     version names.  */
+  Elf_Scn *defscn = NULL;
+  Elf_Scn *needscn = NULL;
+
+  Elf_Scn *verscn = NULL;
+  while ((verscn = elf_nextscn (ebl->elf, verscn)) != NULL)
+    {
+      GElf_Shdr vershdr_mem;
+      GElf_Shdr *vershdr = gelf_getshdr (verscn, &vershdr_mem);
+
+      if (likely (vershdr != NULL))
+	{
+	  if (vershdr->sh_type == SHT_GNU_verdef)
+	    defscn = verscn;
+	  else if (vershdr->sh_type == SHT_GNU_verneed)
+	    needscn = verscn;
+	}
+    }
+
+  size_t nvername;
+  if (defscn != NULL || needscn != NULL)
+    {
+      /* We have a version information (better should have).  Now get
+	 the version names.  First find the maximum version number.  */
+      nvername = 0;
+      if (defscn != NULL)
+	{
+	  /* Run through the version definitions and find the highest
+	     index.  */
+	  unsigned int offset = 0;
+	  Elf_Data *defdata;
+	  GElf_Shdr defshdrmem;
+	  GElf_Shdr *defshdr;
+
+	  defdata = elf_getdata (defscn, NULL);
+	  if (unlikely (defdata == NULL))
+	    return;
+
+	  defshdr = gelf_getshdr (defscn, &defshdrmem);
+	  if (unlikely (defshdr == NULL))
+	    return;
+
+	  for (unsigned int cnt = 0; cnt < defshdr->sh_info; ++cnt)
+	    {
+	      GElf_Verdef defmem;
+	      GElf_Verdef *def;
+
+	      /* Get the data at the next offset.  */
+	      def = gelf_getverdef (defdata, offset, &defmem);
+	      if (unlikely (def == NULL))
+		break;
+
+	      nvername = MAX (nvername, (size_t) (def->vd_ndx & 0x7fff));
+
+	      if (def->vd_next == 0)
+		break;
+	      offset += def->vd_next;
+	    }
+	}
+      if (needscn != NULL)
+	{
+	  unsigned int offset = 0;
+	  Elf_Data *needdata;
+	  GElf_Shdr needshdrmem;
+	  GElf_Shdr *needshdr;
+
+	  needdata = elf_getdata (needscn, NULL);
+	  if (unlikely (needdata == NULL))
+	    return;
+
+	  needshdr = gelf_getshdr (needscn, &needshdrmem);
+	  if (unlikely (needshdr == NULL))
+	    return;
+
+	  for (unsigned int cnt = 0; cnt < needshdr->sh_info; ++cnt)
+	    {
+	      GElf_Verneed needmem;
+	      GElf_Verneed *need;
+	      unsigned int auxoffset;
+	      int cnt2;
+
+	      /* Get the data at the next offset.  */
+	      need = gelf_getverneed (needdata, offset, &needmem);
+	      if (unlikely (need == NULL))
+		break;
+
+	      /* Run through the auxiliary entries.  */
+	      auxoffset = offset + need->vn_aux;
+	      for (cnt2 = need->vn_cnt; --cnt2 >= 0; )
+		{
+		  GElf_Vernaux auxmem;
+		  GElf_Vernaux *aux;
+
+		  aux = gelf_getvernaux (needdata, auxoffset, &auxmem);
+		  if (unlikely (aux == NULL))
+		    break;
+
+		  nvername = MAX (nvername,
+				  (size_t) (aux->vna_other & 0x7fff));
+
+		  if (aux->vna_next == 0)
+		    break;
+		  auxoffset += aux->vna_next;
+		}
+
+	      if (need->vn_next == 0)
+		break;
+	      offset += need->vn_next;
+	    }
+	}
+
+      /* This is the number of versions we know about.  */
+      ++nvername;
+
+      /* Allocate the array.  */
+      vername = (const char **) alloca (nvername * sizeof (const char *));
+      memset(vername, 0, nvername * sizeof (const char *));
+      filename = (const char **) alloca (nvername * sizeof (const char *));
+      memset(filename, 0, nvername * sizeof (const char *));
+
+      /* Run through the data structures again and collect the strings.  */
+      if (defscn != NULL)
+	{
+	  /* Run through the version definitions and find the highest
+	     index.  */
+	  unsigned int offset = 0;
+	  Elf_Data *defdata;
+	  GElf_Shdr defshdrmem;
+	  GElf_Shdr *defshdr;
+
+	  defdata = elf_getdata (defscn, NULL);
+	  if (unlikely (defdata == NULL))
+	    return;
+
+	  defshdr = gelf_getshdr (defscn, &defshdrmem);
+	  if (unlikely (defshdr == NULL))
+	    return;
+
+	  for (unsigned int cnt = 0; cnt < defshdr->sh_info; ++cnt)
+	    {
+
+	      /* Get the data at the next offset.  */
+	      GElf_Verdef defmem;
+	      GElf_Verdef *def = gelf_getverdef (defdata, offset, &defmem);
+	      if (unlikely (def == NULL))
+		break;
+
+	      GElf_Verdaux auxmem;
+	      GElf_Verdaux *aux = gelf_getverdaux (defdata,
+						   offset + def->vd_aux,
+						   &auxmem);
+	      if (unlikely (aux == NULL))
+		break;
+
+	      vername[def->vd_ndx & 0x7fff]
+		= elf_strptr (ebl->elf, defshdr->sh_link, aux->vda_name);
+	      filename[def->vd_ndx & 0x7fff] = NULL;
+
+	      if (def->vd_next == 0)
+		break;
+	      offset += def->vd_next;
+	    }
+	}
+      if (needscn != NULL)
+	{
+	  unsigned int offset = 0;
+
+	  Elf_Data *needdata = elf_getdata (needscn, NULL);
+	  GElf_Shdr needshdrmem;
+	  GElf_Shdr *needshdr = gelf_getshdr (needscn, &needshdrmem);
+	  if (unlikely (needdata == NULL || needshdr == NULL))
+	    return;
+
+	  for (unsigned int cnt = 0; cnt < needshdr->sh_info; ++cnt)
+	    {
+	      /* Get the data at the next offset.  */
+	      GElf_Verneed needmem;
+	      GElf_Verneed *need = gelf_getverneed (needdata, offset,
+						    &needmem);
+	      if (unlikely (need == NULL))
+		break;
+
+	      /* Run through the auxiliary entries.  */
+	      unsigned int auxoffset = offset + need->vn_aux;
+	      for (int cnt2 = need->vn_cnt; --cnt2 >= 0; )
+		{
+		  GElf_Vernaux auxmem;
+		  GElf_Vernaux *aux = gelf_getvernaux (needdata, auxoffset,
+						       &auxmem);
+		  if (unlikely (aux == NULL))
+		    break;
+
+		  vername[aux->vna_other & 0x7fff]
+		    = elf_strptr (ebl->elf, needshdr->sh_link, aux->vna_name);
+		  filename[aux->vna_other & 0x7fff]
+		    = elf_strptr (ebl->elf, needshdr->sh_link, need->vn_file);
+
+		  if (aux->vna_next == 0)
+		    break;
+		  auxoffset += aux->vna_next;
+		}
+
+	      if (need->vn_next == 0)
+		break;
+	      offset += need->vn_next;
+	    }
+	}
+    }
+  else
+    {
+      vername = NULL;
+      nvername = 1;
+      filename = NULL;
+    }
+
+  GElf_Shdr glink_mem;
+  GElf_Shdr *glink = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
+				   &glink_mem);
+  size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_HALF, 1, EV_CURRENT);
+  if (glink == NULL)
+    error (EXIT_FAILURE, 0, gettext ("invalid sh_link value in section %zu"),
+	   elf_ndxscn (scn));
+
+  /* Print the header.  */
+  printf (ngettext ("\
+\nVersion symbols section [%2u] '%s' contains %d entry:\n Addr: %#0*" PRIx64 "  Offset: %#08" PRIx64 "  Link to section: [%2u] '%s'",
+		    "\
+\nVersion symbols section [%2u] '%s' contains %d entries:\n Addr: %#0*" PRIx64 "  Offset: %#08" PRIx64 "  Link to section: [%2u] '%s'",
+		    shdr->sh_size / sh_entsize),
+	  (unsigned int) elf_ndxscn (scn),
+	  elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+	  (int) (shdr->sh_size / sh_entsize),
+	  class == ELFCLASS32 ? 10 : 18, shdr->sh_addr,
+	  shdr->sh_offset,
+	  (unsigned int) shdr->sh_link,
+	  elf_strptr (ebl->elf, shstrndx, glink->sh_name));
+
+  /* Now we can finally look at the actual contents of this section.  */
+  for (unsigned int cnt = 0; cnt < shdr->sh_size / sh_entsize; ++cnt)
+    {
+      if (cnt % 2 == 0)
+	printf ("\n %4d:", cnt);
+
+      GElf_Versym symmem;
+      GElf_Versym *sym = gelf_getversym (data, cnt, &symmem);
+      if (sym == NULL)
+	break;
+
+      switch (*sym)
+	{
+	  ssize_t n;
+	case 0:
+	  fputs_unlocked (gettext ("   0 *local*                     "),
+			  stdout);
+	  break;
+
+	case 1:
+	  fputs_unlocked (gettext ("   1 *global*                    "),
+			  stdout);
+	  break;
+
+	default:
+	  n = printf ("%4d%c%s",
+		      *sym & 0x7fff, *sym & 0x8000 ? 'h' : ' ',
+		      (vername != NULL
+		       && (unsigned int) (*sym & 0x7fff) < nvername)
+		      ? vername[*sym & 0x7fff] : "???");
+	  if ((unsigned int) (*sym & 0x7fff) < nvername
+	      && filename != NULL && filename[*sym & 0x7fff] != NULL)
+	    n += printf ("(%s)", filename[*sym & 0x7fff]);
+	  printf ("%*s", MAX (0, 33 - (int) n), " ");
+	  break;
+	}
+    }
+  putchar_unlocked ('\n');
+}
+
+
+static void
+print_hash_info (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr, size_t shstrndx,
+		 uint_fast32_t maxlength, Elf32_Word nbucket,
+		 uint_fast32_t nsyms, uint32_t *lengths, const char *extrastr)
+{
+  uint32_t *counts = (uint32_t *) xcalloc (maxlength + 1, sizeof (uint32_t));
+
+  for (Elf32_Word cnt = 0; cnt < nbucket; ++cnt)
+    ++counts[lengths[cnt]];
+
+  GElf_Shdr glink_mem;
+  GElf_Shdr *glink = gelf_getshdr (elf_getscn (ebl->elf,
+					       shdr->sh_link),
+				   &glink_mem);
+  if (glink == NULL)
+    {
+      error (0, 0, gettext ("invalid sh_link value in section %zu"),
+	     elf_ndxscn (scn));
+      return;
+    }
+
+  printf (ngettext ("\
+\nHistogram for bucket list length in section [%2u] '%s' (total of %d bucket):\n Addr: %#0*" PRIx64 "  Offset: %#08" PRIx64 "  Link to section: [%2u] '%s'\n",
+		    "\
+\nHistogram for bucket list length in section [%2u] '%s' (total of %d buckets):\n Addr: %#0*" PRIx64 "  Offset: %#08" PRIx64 "  Link to section: [%2u] '%s'\n",
+		    nbucket),
+	  (unsigned int) elf_ndxscn (scn),
+	  elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+	  (int) nbucket,
+	  gelf_getclass (ebl->elf) == ELFCLASS32 ? 10 : 18,
+	  shdr->sh_addr,
+	  shdr->sh_offset,
+	  (unsigned int) shdr->sh_link,
+	  elf_strptr (ebl->elf, shstrndx, glink->sh_name));
+
+  if (extrastr != NULL)
+    fputs (extrastr, stdout);
+
+  if (likely (nbucket > 0))
+    {
+      uint64_t success = 0;
+
+      /* xgettext:no-c-format */
+      fputs_unlocked (gettext ("\
+ Length  Number  % of total  Coverage\n"), stdout);
+      printf (gettext ("      0  %6" PRIu32 "      %5.1f%%\n"),
+	      counts[0], (counts[0] * 100.0) / nbucket);
+
+      uint64_t nzero_counts = 0;
+      for (Elf32_Word cnt = 1; cnt <= maxlength; ++cnt)
+	{
+	  nzero_counts += counts[cnt] * cnt;
+	  printf (gettext ("\
+%7d  %6" PRIu32 "      %5.1f%%    %5.1f%%\n"),
+		  (int) cnt, counts[cnt], (counts[cnt] * 100.0) / nbucket,
+		  (nzero_counts * 100.0) / nsyms);
+	}
+
+      Elf32_Word acc = 0;
+      for (Elf32_Word cnt = 1; cnt <= maxlength; ++cnt)
+	{
+	  acc += cnt;
+	  success += counts[cnt] * acc;
+	}
+
+      printf (gettext ("\
+ Average number of tests:   successful lookup: %f\n\
+			  unsuccessful lookup: %f\n"),
+	      (double) success / (double) nzero_counts,
+	      (double) nzero_counts / (double) nbucket);
+    }
+
+  free (counts);
+}
+
+
+/* This function handles the traditional System V-style hash table format.  */
+static void
+handle_sysv_hash (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr, size_t shstrndx)
+{
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (unlikely (data == NULL))
+    {
+      error (0, 0, gettext ("cannot get data for section %d: %s"),
+	     (int) elf_ndxscn (scn), elf_errmsg (-1));
+      return;
+    }
+
+  if (unlikely (data->d_size < 2 * sizeof (Elf32_Word)))
+    {
+    invalid_data:
+      error (0, 0, gettext ("invalid data in sysv.hash section %d"),
+	     (int) elf_ndxscn (scn));
+      return;
+    }
+
+  Elf32_Word nbucket = ((Elf32_Word *) data->d_buf)[0];
+  Elf32_Word nchain = ((Elf32_Word *) data->d_buf)[1];
+
+  uint64_t used_buf = (2ULL + nchain + nbucket) * sizeof (Elf32_Word);
+  if (used_buf > data->d_size)
+    goto invalid_data;
+
+  Elf32_Word *bucket = &((Elf32_Word *) data->d_buf)[2];
+  Elf32_Word *chain = &((Elf32_Word *) data->d_buf)[2 + nbucket];
+
+  uint32_t *lengths = (uint32_t *) xcalloc (nbucket, sizeof (uint32_t));
+
+  uint_fast32_t maxlength = 0;
+  uint_fast32_t nsyms = 0;
+  for (Elf32_Word cnt = 0; cnt < nbucket; ++cnt)
+    {
+      Elf32_Word inner = bucket[cnt];
+      while (inner > 0 && inner < nchain)
+	{
+	  ++nsyms;
+	  if (maxlength < ++lengths[cnt])
+	    ++maxlength;
+
+	  inner = chain[inner];
+	}
+    }
+
+  print_hash_info (ebl, scn, shdr, shstrndx, maxlength, nbucket, nsyms,
+		   lengths, NULL);
+
+  free (lengths);
+}
+
+
+/* This function handles the incorrect, System V-style hash table
+   format some 64-bit architectures use.  */
+static void
+handle_sysv_hash64 (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr, size_t shstrndx)
+{
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (unlikely (data == NULL))
+    {
+      error (0, 0, gettext ("cannot get data for section %d: %s"),
+	     (int) elf_ndxscn (scn), elf_errmsg (-1));
+      return;
+    }
+
+  if (unlikely (data->d_size < 2 * sizeof (Elf64_Xword)))
+    {
+    invalid_data:
+      error (0, 0, gettext ("invalid data in sysv.hash64 section %d"),
+	     (int) elf_ndxscn (scn));
+      return;
+    }
+
+  Elf64_Xword nbucket = ((Elf64_Xword *) data->d_buf)[0];
+  Elf64_Xword nchain = ((Elf64_Xword *) data->d_buf)[1];
+
+  uint64_t maxwords = data->d_size / sizeof (Elf64_Xword);
+  if (maxwords < 2
+      || maxwords - 2 < nbucket
+      || maxwords - 2 - nbucket < nchain)
+    goto invalid_data;
+
+  Elf64_Xword *bucket = &((Elf64_Xword *) data->d_buf)[2];
+  Elf64_Xword *chain = &((Elf64_Xword *) data->d_buf)[2 + nbucket];
+
+  uint32_t *lengths = (uint32_t *) xcalloc (nbucket, sizeof (uint32_t));
+
+  uint_fast32_t maxlength = 0;
+  uint_fast32_t nsyms = 0;
+  for (Elf64_Xword cnt = 0; cnt < nbucket; ++cnt)
+    {
+      Elf64_Xword inner = bucket[cnt];
+      while (inner > 0 && inner < nchain)
+	{
+	  ++nsyms;
+	  if (maxlength < ++lengths[cnt])
+	    ++maxlength;
+
+	  inner = chain[inner];
+	}
+    }
+
+  print_hash_info (ebl, scn, shdr, shstrndx, maxlength, nbucket, nsyms,
+		   lengths, NULL);
+
+  free (lengths);
+}
+
+
+/* This function handles the GNU-style hash table format.  */
+static void
+handle_gnu_hash (Ebl *ebl, Elf_Scn *scn, GElf_Shdr *shdr, size_t shstrndx)
+{
+  uint32_t *lengths = NULL;
+  Elf_Data *data = elf_getdata (scn, NULL);
+  if (unlikely (data == NULL))
+    {
+      error (0, 0, gettext ("cannot get data for section %d: %s"),
+	     (int) elf_ndxscn (scn), elf_errmsg (-1));
+      return;
+    }
+
+  if (unlikely (data->d_size < 4 * sizeof (Elf32_Word)))
+    {
+    invalid_data:
+      free (lengths);
+      error (0, 0, gettext ("invalid data in gnu.hash section %d"),
+	     (int) elf_ndxscn (scn));
+      return;
+    }
+
+  Elf32_Word nbucket = ((Elf32_Word *) data->d_buf)[0];
+  Elf32_Word symbias = ((Elf32_Word *) data->d_buf)[1];
+
+  /* Next comes the size of the bitmap.  It's measured in words for
+     the architecture.  It's 32 bits for 32 bit archs, and 64 bits for
+     64 bit archs.  There is always a bloom filter present, so zero is
+     an invalid value.  */
+  Elf32_Word bitmask_words = ((Elf32_Word *) data->d_buf)[2];
+  if (gelf_getclass (ebl->elf) == ELFCLASS64)
+    bitmask_words *= 2;
+
+  if (bitmask_words == 0)
+    goto invalid_data;
+
+  Elf32_Word shift = ((Elf32_Word *) data->d_buf)[3];
+
+  /* Is there still room for the sym chain?
+     Use uint64_t calculation to prevent 32bit overlow.  */
+  uint64_t used_buf = (4ULL + bitmask_words + nbucket) * sizeof (Elf32_Word);
+  uint32_t max_nsyms = (data->d_size - used_buf) / sizeof (Elf32_Word);
+  if (used_buf > data->d_size)
+    goto invalid_data;
+
+  lengths = (uint32_t *) xcalloc (nbucket, sizeof (uint32_t));
+
+  Elf32_Word *bitmask = &((Elf32_Word *) data->d_buf)[4];
+  Elf32_Word *bucket = &((Elf32_Word *) data->d_buf)[4 + bitmask_words];
+  Elf32_Word *chain = &((Elf32_Word *) data->d_buf)[4 + bitmask_words
+						    + nbucket];
+
+  /* Compute distribution of chain lengths.  */
+  uint_fast32_t maxlength = 0;
+  uint_fast32_t nsyms = 0;
+  for (Elf32_Word cnt = 0; cnt < nbucket; ++cnt)
+    if (bucket[cnt] != 0)
+      {
+	Elf32_Word inner = bucket[cnt] - symbias;
+	do
+	  {
+	    ++nsyms;
+	    if (maxlength < ++lengths[cnt])
+	      ++maxlength;
+	    if (inner >= max_nsyms)
+	      goto invalid_data;
+	  }
+	while ((chain[inner++] & 1) == 0);
+      }
+
+  /* Count bits in bitmask.  */
+  uint_fast32_t nbits = 0;
+  for (Elf32_Word cnt = 0; cnt < bitmask_words; ++cnt)
+    {
+      uint_fast32_t word = bitmask[cnt];
+
+      word = (word & 0x55555555) + ((word >> 1) & 0x55555555);
+      word = (word & 0x33333333) + ((word >> 2) & 0x33333333);
+      word = (word & 0x0f0f0f0f) + ((word >> 4) & 0x0f0f0f0f);
+      word = (word & 0x00ff00ff) + ((word >> 8) & 0x00ff00ff);
+      nbits += (word & 0x0000ffff) + ((word >> 16) & 0x0000ffff);
+    }
+
+  char *str;
+  if (unlikely (asprintf (&str, gettext ("\
+ Symbol Bias: %u\n\
+ Bitmask Size: %zu bytes  %" PRIuFAST32 "%% bits set  2nd hash shift: %u\n"),
+			  (unsigned int) symbias,
+			  bitmask_words * sizeof (Elf32_Word),
+			  ((nbits * 100 + 50)
+			   / (uint_fast32_t) (bitmask_words
+					      * sizeof (Elf32_Word) * 8)),
+			  (unsigned int) shift) == -1))
+    error (EXIT_FAILURE, 0, gettext ("memory exhausted"));
+
+  print_hash_info (ebl, scn, shdr, shstrndx, maxlength, nbucket, nsyms,
+		   lengths, str);
+
+  free (str);
+  free (lengths);
+}
+
+
+/* Find the symbol table(s).  For this we have to search through the
+   section table.  */
+static void
+handle_hash (Ebl *ebl)
+{
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      /* Handle the section if it is a symbol table.  */
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (likely (shdr != NULL))
+	{
+	  if ((shdr->sh_type == SHT_HASH || shdr->sh_type == SHT_GNU_HASH)
+	      && (shdr->sh_flags & SHF_COMPRESSED) != 0)
+	    {
+	      if (elf_compress (scn, 0, 0) < 0)
+		printf ("WARNING: %s [%zd]\n",
+			gettext ("Couldn't uncompress section"),
+			elf_ndxscn (scn));
+	      shdr = gelf_getshdr (scn, &shdr_mem);
+	      if (unlikely (shdr == NULL))
+		error (EXIT_FAILURE, 0,
+		       gettext ("cannot get section [%zd] header: %s"),
+		       elf_ndxscn (scn), elf_errmsg (-1));
+	    }
+
+	  if (shdr->sh_type == SHT_HASH)
+	    {
+	      if (ebl_sysvhash_entrysize (ebl) == sizeof (Elf64_Xword))
+		handle_sysv_hash64 (ebl, scn, shdr, shstrndx);
+	      else
+		handle_sysv_hash (ebl, scn, shdr, shstrndx);
+	    }
+	  else if (shdr->sh_type == SHT_GNU_HASH)
+	    handle_gnu_hash (ebl, scn, shdr, shstrndx);
+	}
+    }
+}
+
+
+static void
+print_liblist (Ebl *ebl)
+{
+  /* Find the library list sections.  For this we have to search
+     through the section table.  */
+  Elf_Scn *scn = NULL;
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr != NULL && shdr->sh_type == SHT_GNU_LIBLIST)
+	{
+	  size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_LIB, 1, EV_CURRENT);
+	  int nentries = shdr->sh_size / sh_entsize;
+	  printf (ngettext ("\
+\nLibrary list section [%2zu] '%s' at offset %#0" PRIx64 " contains %d entry:\n",
+			    "\
+\nLibrary list section [%2zu] '%s' at offset %#0" PRIx64 " contains %d entries:\n",
+			    nentries),
+		  elf_ndxscn (scn),
+		  elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+		  shdr->sh_offset,
+		  nentries);
+
+	  Elf_Data *data = elf_getdata (scn, NULL);
+	  if (data == NULL)
+	    return;
+
+	  puts (gettext ("\
+       Library                       Time Stamp          Checksum Version Flags"));
+
+	  for (int cnt = 0; cnt < nentries; ++cnt)
+	    {
+	      GElf_Lib lib_mem;
+	      GElf_Lib *lib = gelf_getlib (data, cnt, &lib_mem);
+	      if (unlikely (lib == NULL))
+		continue;
+
+	      time_t t = (time_t) lib->l_time_stamp;
+	      struct tm *tm = gmtime (&t);
+	      if (unlikely (tm == NULL))
+		continue;
+
+	      printf ("  [%2d] %-29s %04u-%02u-%02uT%02u:%02u:%02u %08x %-7u %u\n",
+		      cnt, elf_strptr (ebl->elf, shdr->sh_link, lib->l_name),
+		      tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+		      tm->tm_hour, tm->tm_min, tm->tm_sec,
+		      (unsigned int) lib->l_checksum,
+		      (unsigned int) lib->l_version,
+		      (unsigned int) lib->l_flags);
+	    }
+	}
+    }
+}
+
+static void
+print_attributes (Ebl *ebl, const GElf_Ehdr *ehdr)
+{
+  /* Find the object attributes sections.  For this we have to search
+     through the section table.  */
+  Elf_Scn *scn = NULL;
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr == NULL || (shdr->sh_type != SHT_GNU_ATTRIBUTES
+			   && (shdr->sh_type != SHT_ARM_ATTRIBUTES
+			       || ehdr->e_machine != EM_ARM)))
+	continue;
+
+      printf (gettext ("\
+\nObject attributes section [%2zu] '%s' of %" PRIu64
+		       " bytes at offset %#0" PRIx64 ":\n"),
+	      elf_ndxscn (scn),
+	      elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+	      shdr->sh_size, shdr->sh_offset);
+
+      Elf_Data *data = elf_rawdata (scn, NULL);
+      if (unlikely (data == NULL || data->d_size == 0))
+	return;
+
+      const unsigned char *p = data->d_buf;
+
+      /* There is only one 'version', A.  */
+      if (unlikely (*p++ != 'A'))
+	return;
+
+      fputs_unlocked (gettext ("  Owner          Size\n"), stdout);
+
+      inline size_t left (void)
+      {
+	return (const unsigned char *) data->d_buf + data->d_size - p;
+      }
+
+      /* Loop over the sections.  */
+      while (left () >= 4)
+	{
+	  /* Section length.  */
+	  uint32_t len;
+	  memcpy (&len, p, sizeof len);
+
+	  if (MY_ELFDATA != ehdr->e_ident[EI_DATA])
+	    CONVERT (len);
+
+	  if (unlikely (len > left ()))
+	    break;
+
+	  /* Section vendor name.  */
+	  const unsigned char *name = p + sizeof len;
+	  p += len;
+
+	  unsigned const char *q = memchr (name, '\0', len);
+	  if (unlikely (q == NULL))
+	    break;
+	  ++q;
+
+	  printf (gettext ("  %-13s  %4" PRIu32 "\n"), name, len);
+
+	  bool gnu_vendor = (q - name == sizeof "gnu"
+			     && !memcmp (name, "gnu", sizeof "gnu"));
+
+	  /* Loop over subsections.  */
+	  if (shdr->sh_type != SHT_GNU_ATTRIBUTES
+	      || gnu_vendor)
+	    while (q < p)
+	      {
+		const unsigned char *const sub = q;
+
+		unsigned int subsection_tag;
+		get_uleb128 (subsection_tag, q, p);
+		if (unlikely (q >= p))
+		  break;
+
+		uint32_t subsection_len;
+		if (unlikely (p - sub < (ptrdiff_t) sizeof subsection_len))
+		  break;
+
+		memcpy (&subsection_len, q, sizeof subsection_len);
+
+		if (MY_ELFDATA != ehdr->e_ident[EI_DATA])
+		  CONVERT (subsection_len);
+
+		/* Don't overflow, ptrdiff_t might be 32bits, but signed.  */
+		if (unlikely (subsection_len == 0
+			      || subsection_len >= (uint32_t) PTRDIFF_MAX
+			      || p - sub < (ptrdiff_t) subsection_len))
+		  break;
+
+		const unsigned char *r = q + sizeof subsection_len;
+		q = sub + subsection_len;
+
+		switch (subsection_tag)
+		  {
+		  default:
+		    /* Unknown subsection, print and skip.  */
+		    printf (gettext ("    %-4u %12" PRIu32 "\n"),
+			    subsection_tag, subsection_len);
+		    break;
+
+		  case 1:	/* Tag_File */
+		    printf (gettext ("    File: %11" PRIu32 "\n"),
+			    subsection_len);
+
+		    while (r < q)
+		      {
+			unsigned int tag;
+			get_uleb128 (tag, r, q);
+			if (unlikely (r >= q))
+			  break;
+
+			/* GNU style tags have either a uleb128 value,
+			   when lowest bit is not set, or a string
+			   when the lowest bit is set.
+			   "compatibility" (32) is special.  It has
+			   both a string and a uleb128 value.  For
+			   non-gnu we assume 6 till 31 only take ints.
+			   XXX see arm backend, do we need a separate
+			   hook?  */
+			uint64_t value = 0;
+			const char *string = NULL;
+			if (tag == 32 || (tag & 1) == 0
+			    || (! gnu_vendor && (tag > 5 && tag < 32)))
+			  {
+			    get_uleb128 (value, r, q);
+			    if (r > q)
+			      break;
+			  }
+			if (tag == 32
+			    || ((tag & 1) != 0
+				&& (gnu_vendor
+				    || (! gnu_vendor && tag > 32)))
+			    || (! gnu_vendor && tag > 3 && tag < 6))
+			  {
+			    string = (const char *) r;
+			    r = memchr (r, '\0', q - r);
+			    if (r == NULL)
+			      break;
+			    ++r;
+			  }
+
+			const char *tag_name = NULL;
+			const char *value_name = NULL;
+			ebl_check_object_attribute (ebl, (const char *) name,
+						    tag, value,
+						    &tag_name, &value_name);
+
+			if (tag_name != NULL)
+			  {
+			    if (tag == 32)
+			      printf (gettext ("      %s: %" PRId64 ", %s\n"),
+				      tag_name, value, string);
+			    else if (string == NULL && value_name == NULL)
+			      printf (gettext ("      %s: %" PRId64 "\n"),
+				      tag_name, value);
+			    else
+			      printf (gettext ("      %s: %s\n"),
+				      tag_name, string ?: value_name);
+			  }
+			else
+			  {
+			    /* For "gnu" vendor 32 "compatibility" has
+			       already been handled above.  */
+			    assert (tag != 32
+				    || strcmp ((const char *) name, "gnu"));
+			    if (string == NULL)
+			      printf (gettext ("      %u: %" PRId64 "\n"),
+				      tag, value);
+			    else
+			      printf (gettext ("      %u: %s\n"),
+				      tag, string);
+			  }
+		      }
+		  }
+	      }
+	}
+    }
+}
+
+
+static char *
+format_dwarf_addr (Dwfl_Module *dwflmod,
+		   int address_size, Dwarf_Addr address, Dwarf_Addr raw)
+{
+  /* See if there is a name we can give for this address.  */
+  GElf_Sym sym;
+  GElf_Off off = 0;
+  const char *name = (print_address_names && ! print_unresolved_addresses)
+    ? dwfl_module_addrinfo (dwflmod, address, &off, &sym, NULL, NULL, NULL)
+    : NULL;
+
+  const char *scn;
+  if (print_unresolved_addresses)
+    {
+      address = raw;
+      scn = NULL;
+    }
+  else
+    {
+      /* Relativize the address.  */
+      int n = dwfl_module_relocations (dwflmod);
+      int i = n < 1 ? -1 : dwfl_module_relocate_address (dwflmod, &address);
+
+      /* In an ET_REL file there is a section name to refer to.  */
+      scn = (i < 0 ? NULL
+	     : dwfl_module_relocation_info (dwflmod, i, NULL));
+    }
+
+  char *result;
+  if ((name != NULL
+       ? (off != 0
+	  ? (scn != NULL
+	     ? (address_size == 0
+		? asprintf (&result,
+			    gettext ("%s+%#" PRIx64 " <%s+%#" PRIx64 ">"),
+			    scn, address, name, off)
+		: asprintf (&result,
+			    gettext ("%s+%#0*" PRIx64 " <%s+%#" PRIx64 ">"),
+			    scn, 2 + address_size * 2, address,
+			    name, off))
+	     : (address_size == 0
+		? asprintf (&result,
+			    gettext ("%#" PRIx64 " <%s+%#" PRIx64 ">"),
+			    address, name, off)
+		: asprintf (&result,
+			    gettext ("%#0*" PRIx64 " <%s+%#" PRIx64 ">"),
+			    2 + address_size * 2, address,
+			    name, off)))
+	  : (scn != NULL
+	     ? (address_size == 0
+		? asprintf (&result,
+			    gettext ("%s+%#" PRIx64 " <%s>"),
+			    scn, address, name)
+		: asprintf (&result,
+			    gettext ("%s+%#0*" PRIx64 " <%s>"),
+			    scn, 2 + address_size * 2, address, name))
+	     : (address_size == 0
+		? asprintf (&result,
+			    gettext ("%#" PRIx64 " <%s>"),
+			    address, name)
+		: asprintf (&result,
+			    gettext ("%#0*" PRIx64 " <%s>"),
+			    2 + address_size * 2, address, name))))
+       : (scn != NULL
+	  ? (address_size == 0
+	     ? asprintf (&result,
+			 gettext ("%s+%#" PRIx64),
+			 scn, address)
+	     : asprintf (&result,
+			 gettext ("%s+%#0*" PRIx64),
+			 scn, 2 + address_size * 2, address))
+	  : (address_size == 0
+	     ? asprintf (&result,
+			 "%#" PRIx64,
+			 address)
+	     : asprintf (&result,
+			 "%#0*" PRIx64,
+			 2 + address_size * 2, address)))) < 0)
+    error (EXIT_FAILURE, 0, _("memory exhausted"));
+
+  return result;
+}
+
+static const char *
+dwarf_tag_string (unsigned int tag)
+{
+  switch (tag)
+    {
+#define DWARF_ONE_KNOWN_DW_TAG(NAME, CODE) case CODE: return #NAME;
+      DWARF_ALL_KNOWN_DW_TAG
+#undef DWARF_ONE_KNOWN_DW_TAG
+    default:
+      return NULL;
+    }
+}
+
+
+static const char *
+dwarf_attr_string (unsigned int attrnum)
+{
+  switch (attrnum)
+    {
+#define DWARF_ONE_KNOWN_DW_AT(NAME, CODE) case CODE: return #NAME;
+      DWARF_ALL_KNOWN_DW_AT
+#undef DWARF_ONE_KNOWN_DW_AT
+    default:
+      return NULL;
+    }
+}
+
+
+static const char *
+dwarf_form_string (unsigned int form)
+{
+  switch (form)
+    {
+#define DWARF_ONE_KNOWN_DW_FORM(NAME, CODE) case CODE: return #NAME;
+      DWARF_ALL_KNOWN_DW_FORM
+#undef DWARF_ONE_KNOWN_DW_FORM
+    default:
+      return NULL;
+    }
+}
+
+
+static const char *
+dwarf_lang_string (unsigned int lang)
+{
+  switch (lang)
+    {
+#define DWARF_ONE_KNOWN_DW_LANG(NAME, CODE) case CODE: return #NAME;
+      DWARF_ALL_KNOWN_DW_LANG
+#undef DWARF_ONE_KNOWN_DW_LANG
+    default:
+      return NULL;
+    }
+}
+
+
+static const char *
+dwarf_inline_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+#define DWARF_ONE_KNOWN_DW_INL(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_INL
+#undef DWARF_ONE_KNOWN_DW_INL
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+static const char *
+dwarf_encoding_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+#define DWARF_ONE_KNOWN_DW_ATE(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_ATE
+#undef DWARF_ONE_KNOWN_DW_ATE
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+static const char *
+dwarf_access_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+#define DWARF_ONE_KNOWN_DW_ACCESS(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_ACCESS
+#undef DWARF_ONE_KNOWN_DW_ACCESS
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+static const char *
+dwarf_defaulted_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+#define DWARF_ONE_KNOWN_DW_DEFAULTED(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_DEFAULTED
+#undef DWARF_ONE_KNOWN_DW_DEFAULTED
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+static const char *
+dwarf_visibility_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+#define DWARF_ONE_KNOWN_DW_VIS(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_VIS
+#undef DWARF_ONE_KNOWN_DW_VIS
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+static const char *
+dwarf_virtuality_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+#define DWARF_ONE_KNOWN_DW_VIRTUALITY(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_VIRTUALITY
+#undef DWARF_ONE_KNOWN_DW_VIRTUALITY
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+static const char *
+dwarf_identifier_case_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+#define DWARF_ONE_KNOWN_DW_ID(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_ID
+#undef DWARF_ONE_KNOWN_DW_ID
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+static const char *
+dwarf_calling_convention_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+#define DWARF_ONE_KNOWN_DW_CC(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_CC
+#undef DWARF_ONE_KNOWN_DW_CC
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+static const char *
+dwarf_ordering_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+#define DWARF_ONE_KNOWN_DW_ORD(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_ORD
+#undef DWARF_ONE_KNOWN_DW_ORD
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+static const char *
+dwarf_discr_list_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+#define DWARF_ONE_KNOWN_DW_DSC(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_DSC
+#undef DWARF_ONE_KNOWN_DW_DSC
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+static const char *
+dwarf_locexpr_opcode_string (unsigned int code)
+{
+  static const char *const known[] =
+    {
+      /* Normally we can't affort building huge table of 64K entries,
+	 most of them zero, just because there are a couple defined
+	 values at the far end.  In case of opcodes, it's OK.  */
+#define DWARF_ONE_KNOWN_DW_OP(NAME, CODE) [CODE] = #NAME,
+      DWARF_ALL_KNOWN_DW_OP
+#undef DWARF_ONE_KNOWN_DW_OP
+    };
+
+  if (likely (code < sizeof (known) / sizeof (known[0])))
+    return known[code];
+
+  return NULL;
+}
+
+
+/* Used by all dwarf_foo_name functions.  */
+static const char *
+string_or_unknown (const char *known, unsigned int code,
+                   unsigned int lo_user, unsigned int hi_user,
+		   bool print_unknown_num)
+{
+  static char unknown_buf[20];
+
+  if (likely (known != NULL))
+    return known;
+
+  if (lo_user != 0 && code >= lo_user && code <= hi_user)
+    {
+      snprintf (unknown_buf, sizeof unknown_buf, "lo_user+%#x",
+		code - lo_user);
+      return unknown_buf;
+    }
+
+  if (print_unknown_num)
+    {
+      snprintf (unknown_buf, sizeof unknown_buf, "??? (%#x)", code);
+      return unknown_buf;
+    }
+
+  return "???";
+}
+
+
+static const char *
+dwarf_tag_name (unsigned int tag)
+{
+  const char *ret = dwarf_tag_string (tag);
+  return string_or_unknown (ret, tag, DW_TAG_lo_user, DW_TAG_hi_user, true);
+}
+
+static const char *
+dwarf_attr_name (unsigned int attr)
+{
+  const char *ret = dwarf_attr_string (attr);
+  return string_or_unknown (ret, attr, DW_AT_lo_user, DW_AT_hi_user, true);
+}
+
+
+static const char *
+dwarf_form_name (unsigned int form)
+{
+  const char *ret = dwarf_form_string (form);
+  return string_or_unknown (ret, form, 0, 0, true);
+}
+
+
+static const char *
+dwarf_lang_name (unsigned int lang)
+{
+  const char *ret = dwarf_lang_string (lang);
+  return string_or_unknown (ret, lang, DW_LANG_lo_user, DW_LANG_hi_user, false);
+}
+
+
+static const char *
+dwarf_inline_name (unsigned int code)
+{
+  const char *ret = dwarf_inline_string (code);
+  return string_or_unknown (ret, code, 0, 0, false);
+}
+
+
+static const char *
+dwarf_encoding_name (unsigned int code)
+{
+  const char *ret = dwarf_encoding_string (code);
+  return string_or_unknown (ret, code, DW_ATE_lo_user, DW_ATE_hi_user, false);
+}
+
+
+static const char *
+dwarf_access_name (unsigned int code)
+{
+  const char *ret = dwarf_access_string (code);
+  return string_or_unknown (ret, code, 0, 0, false);
+}
+
+
+static const char *
+dwarf_defaulted_name (unsigned int code)
+{
+  const char *ret = dwarf_defaulted_string (code);
+  return string_or_unknown (ret, code, 0, 0, false);
+}
+
+
+static const char *
+dwarf_visibility_name (unsigned int code)
+{
+  const char *ret = dwarf_visibility_string (code);
+  return string_or_unknown (ret, code, 0, 0, false);
+}
+
+
+static const char *
+dwarf_virtuality_name (unsigned int code)
+{
+  const char *ret = dwarf_virtuality_string (code);
+  return string_or_unknown (ret, code, 0, 0, false);
+}
+
+
+static const char *
+dwarf_identifier_case_name (unsigned int code)
+{
+  const char *ret = dwarf_identifier_case_string (code);
+  return string_or_unknown (ret, code, 0, 0, false);
+}
+
+
+static const char *
+dwarf_calling_convention_name (unsigned int code)
+{
+  const char *ret = dwarf_calling_convention_string (code);
+  return string_or_unknown (ret, code, DW_CC_lo_user, DW_CC_hi_user, false);
+}
+
+
+static const char *
+dwarf_ordering_name (unsigned int code)
+{
+  const char *ret = dwarf_ordering_string (code);
+  return string_or_unknown (ret, code, 0, 0, false);
+}
+
+
+static const char *
+dwarf_discr_list_name (unsigned int code)
+{
+  const char *ret = dwarf_discr_list_string (code);
+  return string_or_unknown (ret, code, 0, 0, false);
+}
+
+
+static void
+print_block (size_t n, const void *block)
+{
+  if (n == 0)
+    puts (_("empty block"));
+  else
+    {
+      printf (_("%zu byte block:"), n);
+      const unsigned char *data = block;
+      do
+	printf (" %02x", *data++);
+      while (--n > 0);
+      putchar ('\n');
+    }
+}
+
+static void
+print_ops (Dwfl_Module *dwflmod, Dwarf *dbg, int indent, int indentrest,
+	   unsigned int vers, unsigned int addrsize, unsigned int offset_size,
+	   struct Dwarf_CU *cu, Dwarf_Word len, const unsigned char *data)
+{
+  const unsigned int ref_size = vers < 3 ? addrsize : offset_size;
+
+  if (len == 0)
+    {
+      printf ("%*s(empty)\n", indent, "");
+      return;
+    }
+
+#define NEED(n)		if (len < (Dwarf_Word) (n)) goto invalid
+#define CONSUME(n)	NEED (n); else len -= (n)
+
+  Dwarf_Word offset = 0;
+  while (len-- > 0)
+    {
+      uint_fast8_t op = *data++;
+
+      const char *op_name = dwarf_locexpr_opcode_string (op);
+      if (unlikely (op_name == NULL))
+	{
+	  static char buf[20];
+	  if (op >= DW_OP_lo_user)
+	    snprintf (buf, sizeof buf, "lo_user+%#x", op - DW_OP_lo_user);
+	  else
+	    snprintf (buf, sizeof buf, "??? (%#x)", op);
+	  op_name = buf;
+	}
+
+      switch (op)
+	{
+	case DW_OP_addr:;
+	  /* Address operand.  */
+	  Dwarf_Word addr;
+	  NEED (addrsize);
+	  if (addrsize == 4)
+	    addr = read_4ubyte_unaligned (dbg, data);
+	  else if (addrsize == 8)
+	    addr = read_8ubyte_unaligned (dbg, data);
+	  else
+	    goto invalid;
+	  data += addrsize;
+	  CONSUME (addrsize);
+
+	  char *a = format_dwarf_addr (dwflmod, 0, addr, addr);
+	  printf ("%*s[%2" PRIuMAX "] %s %s\n",
+		  indent, "", (uintmax_t) offset, op_name, a);
+	  free (a);
+
+	  offset += 1 + addrsize;
+	  break;
+
+	case DW_OP_call_ref:
+	case DW_OP_GNU_variable_value:
+	  /* Offset operand.  */
+	  if (ref_size != 4 && ref_size != 8)
+	    goto invalid; /* Cannot be used in CFA.  */
+	  NEED (ref_size);
+	  if (ref_size == 4)
+	    addr = read_4ubyte_unaligned (dbg, data);
+	  else
+	    addr = read_8ubyte_unaligned (dbg, data);
+	  data += ref_size;
+	  CONSUME (ref_size);
+	  /* addr is a DIE offset, so format it as one.  */
+	  printf ("%*s[%2" PRIuMAX "] %s [%6" PRIxMAX "]\n",
+		  indent, "", (uintmax_t) offset,
+		  op_name, (uintmax_t) addr);
+	  offset += 1 + ref_size;
+	  break;
+
+	case DW_OP_deref_size:
+	case DW_OP_xderef_size:
+	case DW_OP_pick:
+	case DW_OP_const1u:
+	  // XXX value might be modified by relocation
+	  NEED (1);
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRIu8 "\n",
+		  indent, "", (uintmax_t) offset,
+		  op_name, *((uint8_t *) data));
+	  ++data;
+	  --len;
+	  offset += 2;
+	  break;
+
+	case DW_OP_const2u:
+	  NEED (2);
+	  // XXX value might be modified by relocation
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRIu16 "\n",
+		  indent, "", (uintmax_t) offset,
+		  op_name, read_2ubyte_unaligned (dbg, data));
+	  CONSUME (2);
+	  data += 2;
+	  offset += 3;
+	  break;
+
+	case DW_OP_const4u:
+	  NEED (4);
+	  // XXX value might be modified by relocation
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRIu32 "\n",
+		  indent, "", (uintmax_t) offset,
+		  op_name, read_4ubyte_unaligned (dbg, data));
+	  CONSUME (4);
+	  data += 4;
+	  offset += 5;
+	  break;
+
+	case DW_OP_const8u:
+	  NEED (8);
+	  // XXX value might be modified by relocation
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRIu64 "\n",
+		  indent, "", (uintmax_t) offset,
+		  op_name, (uint64_t) read_8ubyte_unaligned (dbg, data));
+	  CONSUME (8);
+	  data += 8;
+	  offset += 9;
+	  break;
+
+	case DW_OP_const1s:
+	  NEED (1);
+	  // XXX value might be modified by relocation
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRId8 "\n",
+		  indent, "", (uintmax_t) offset,
+		  op_name, *((int8_t *) data));
+	  ++data;
+	  --len;
+	  offset += 2;
+	  break;
+
+	case DW_OP_const2s:
+	  NEED (2);
+	  // XXX value might be modified by relocation
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRId16 "\n",
+		  indent, "", (uintmax_t) offset,
+		  op_name, read_2sbyte_unaligned (dbg, data));
+	  CONSUME (2);
+	  data += 2;
+	  offset += 3;
+	  break;
+
+	case DW_OP_const4s:
+	  NEED (4);
+	  // XXX value might be modified by relocation
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRId32 "\n",
+		  indent, "", (uintmax_t) offset,
+		  op_name, read_4sbyte_unaligned (dbg, data));
+	  CONSUME (4);
+	  data += 4;
+	  offset += 5;
+	  break;
+
+	case DW_OP_const8s:
+	  NEED (8);
+	  // XXX value might be modified by relocation
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRId64 "\n",
+		  indent, "", (uintmax_t) offset,
+		  op_name, read_8sbyte_unaligned (dbg, data));
+	  CONSUME (8);
+	  data += 8;
+	  offset += 9;
+	  break;
+
+	case DW_OP_piece:
+	case DW_OP_regx:
+	case DW_OP_plus_uconst:
+	case DW_OP_constu:;
+	  const unsigned char *start = data;
+	  uint64_t uleb;
+	  NEED (1);
+	  get_uleb128 (uleb, data, data + len);
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRIu64 "\n",
+		  indent, "", (uintmax_t) offset, op_name, uleb);
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_bit_piece:
+	  start = data;
+	  uint64_t uleb2;
+	  NEED (1);
+	  get_uleb128 (uleb, data, data + len);
+	  NEED (1);
+	  get_uleb128 (uleb2, data, data + len);
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRIu64 ", %" PRIu64 "\n",
+		  indent, "", (uintmax_t) offset, op_name, uleb, uleb2);
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_fbreg:
+	case DW_OP_breg0 ... DW_OP_breg31:
+	case DW_OP_consts:
+	  start = data;
+	  int64_t sleb;
+	  NEED (1);
+	  get_sleb128 (sleb, data, data + len);
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRId64 "\n",
+		  indent, "", (uintmax_t) offset, op_name, sleb);
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_bregx:
+	  start = data;
+	  NEED (1);
+	  get_uleb128 (uleb, data, data + len);
+	  NEED (1);
+	  get_sleb128 (sleb, data, data + len);
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRIu64 " %" PRId64 "\n",
+		  indent, "", (uintmax_t) offset, op_name, uleb, sleb);
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_call2:
+	  NEED (2);
+	  printf ("%*s[%2" PRIuMAX "] %s [%6" PRIx16 "]\n",
+		  indent, "", (uintmax_t) offset, op_name,
+		  read_2ubyte_unaligned (dbg, data));
+	  CONSUME (2);
+	  data += 2;
+	  offset += 3;
+	  break;
+
+	case DW_OP_call4:
+	  NEED (4);
+	  printf ("%*s[%2" PRIuMAX "] %s [%6" PRIx32 "]\n",
+		  indent, "", (uintmax_t) offset, op_name,
+		  read_4ubyte_unaligned (dbg, data));
+	  CONSUME (4);
+	  data += 4;
+	  offset += 5;
+	  break;
+
+	case DW_OP_skip:
+	case DW_OP_bra:
+	  NEED (2);
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRIuMAX "\n",
+		  indent, "", (uintmax_t) offset, op_name,
+		  (uintmax_t) (offset + read_2sbyte_unaligned (dbg, data) + 3));
+	  CONSUME (2);
+	  data += 2;
+	  offset += 3;
+	  break;
+
+	case DW_OP_implicit_value:
+	  start = data;
+	  NEED (1);
+	  get_uleb128 (uleb, data, data + len);
+	  printf ("%*s[%2" PRIuMAX "] %s: ",
+		  indent, "", (uintmax_t) offset, op_name);
+	  NEED (uleb);
+	  print_block (uleb, data);
+	  data += uleb;
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_GNU_implicit_pointer:
+	  /* DIE offset operand.  */
+	  start = data;
+	  NEED (ref_size);
+	  if (ref_size != 4 && ref_size != 8)
+	    goto invalid; /* Cannot be used in CFA.  */
+	  if (ref_size == 4)
+	    addr = read_4ubyte_unaligned (dbg, data);
+	  else
+	    addr = read_8ubyte_unaligned (dbg, data);
+	  data += ref_size;
+	  /* Byte offset operand.  */
+	  NEED (1);
+	  get_sleb128 (sleb, data, data + len);
+
+	  printf ("%*s[%2" PRIuMAX "] %s [%6" PRIxMAX "] %+" PRId64 "\n",
+		  indent, "", (intmax_t) offset,
+		  op_name, (uintmax_t) addr, sleb);
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_GNU_entry_value:
+	  /* Size plus expression block.  */
+	  start = data;
+	  NEED (1);
+	  get_uleb128 (uleb, data, data + len);
+	  printf ("%*s[%2" PRIuMAX "] %s:\n",
+		  indent, "", (uintmax_t) offset, op_name);
+	  NEED (uleb);
+	  print_ops (dwflmod, dbg, indent + 5, indent + 5, vers,
+		     addrsize, offset_size, cu, uleb, data);
+	  data += uleb;
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_GNU_const_type:
+	  /* uleb128 CU relative DW_TAG_base_type DIE offset, 1-byte
+	     unsigned size plus block.  */
+	  start = data;
+	  NEED (1);
+	  get_uleb128 (uleb, data, data + len);
+	  if (! print_unresolved_addresses && cu != NULL)
+	    uleb += cu->start;
+	  NEED (1);
+	  uint8_t usize = *(uint8_t *) data++;
+	  NEED (usize);
+	  printf ("%*s[%2" PRIuMAX "] %s [%6" PRIxMAX "] ",
+		  indent, "", (uintmax_t) offset, op_name, uleb);
+	  print_block (usize, data);
+	  data += usize;
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_GNU_regval_type:
+	  /* uleb128 register number, uleb128 CU relative
+	     DW_TAG_base_type DIE offset.  */
+	  start = data;
+	  NEED (1);
+	  get_uleb128 (uleb, data, data + len);
+	  NEED (1);
+	  get_uleb128 (uleb2, data, data + len);
+	  if (! print_unresolved_addresses && cu != NULL)
+	    uleb2 += cu->start;
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRIu64 " [%6" PRIx64 "]\n",
+		  indent, "", (uintmax_t) offset, op_name, uleb, uleb2);
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_GNU_deref_type:
+	  /* 1-byte unsigned size of value, uleb128 CU relative
+	     DW_TAG_base_type DIE offset.  */
+	  start = data;
+	  NEED (1);
+	  usize = *(uint8_t *) data++;
+	  NEED (1);
+	  get_uleb128 (uleb, data, data + len);
+	  if (! print_unresolved_addresses && cu != NULL)
+	    uleb += cu->start;
+	  printf ("%*s[%2" PRIuMAX "] %s %" PRIu8 " [%6" PRIxMAX "]\n",
+		  indent, "", (uintmax_t) offset,
+		  op_name, usize, uleb);
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_GNU_convert:
+	case DW_OP_GNU_reinterpret:
+	  /* uleb128 CU relative offset to DW_TAG_base_type, or zero
+	     for conversion to untyped.  */
+	  start = data;
+	  NEED (1);
+	  get_uleb128 (uleb, data, data + len);
+	  if (uleb != 0 && ! print_unresolved_addresses && cu != NULL)
+	    uleb += cu->start;
+	  printf ("%*s[%2" PRIuMAX "] %s [%6" PRIxMAX "]\n",
+		  indent, "", (uintmax_t) offset, op_name, uleb);
+	  CONSUME (data - start);
+	  offset += 1 + (data - start);
+	  break;
+
+	case DW_OP_GNU_parameter_ref:
+	  /* 4 byte CU relative reference to the abstract optimized away
+	     DW_TAG_formal_parameter.  */
+	  NEED (4);
+	  uintmax_t param_off = (uintmax_t) read_4ubyte_unaligned (dbg, data);
+	  if (! print_unresolved_addresses && cu != NULL)
+	    param_off += cu->start;
+	  printf ("%*s[%2" PRIuMAX "] %s [%6" PRIxMAX "]\n",
+		  indent, "", (uintmax_t) offset, op_name, param_off);
+	  CONSUME (4);
+	  data += 4;
+	  offset += 5;
+	  break;
+
+	default:
+	  /* No Operand.  */
+	  printf ("%*s[%2" PRIuMAX "] %s\n",
+		  indent, "", (uintmax_t) offset, op_name);
+	  ++offset;
+	  break;
+	}
+
+      indent = indentrest;
+      continue;
+
+    invalid:
+      printf (gettext ("%*s[%2" PRIuMAX "] %s  <TRUNCATED>\n"),
+	      indent, "", (uintmax_t) offset, op_name);
+      break;
+    }
+}
+
+
+struct listptr
+{
+  Dwarf_Off offset:(64 - 3);
+  bool addr64:1;
+  bool dwarf64:1;
+  bool warned:1;
+  struct Dwarf_CU *cu;
+};
+
+#define listptr_offset_size(p)	((p)->dwarf64 ? 8 : 4)
+#define listptr_address_size(p)	((p)->addr64 ? 8 : 4)
+
+static Dwarf_Addr
+listptr_base (struct listptr *p)
+{
+  Dwarf_Addr base;
+  Dwarf_Die cu = CUDIE (p->cu);
+  /* Find the base address of the compilation unit.  It will normally
+     be specified by DW_AT_low_pc.  In DWARF-3 draft 4, the base
+     address could be overridden by DW_AT_entry_pc.  It's been
+     removed, but GCC emits DW_AT_entry_pc and not DW_AT_lowpc for
+     compilation units with discontinuous ranges.  */
+  if (unlikely (dwarf_lowpc (&cu, &base) != 0))
+    {
+      Dwarf_Attribute attr_mem;
+      if (dwarf_formaddr (dwarf_attr (&cu, DW_AT_entry_pc, &attr_mem),
+			  &base) != 0)
+	base = 0;
+    }
+  return base;
+}
+
+static int
+compare_listptr (const void *a, const void *b, void *arg)
+{
+  const char *name = arg;
+  struct listptr *p1 = (void *) a;
+  struct listptr *p2 = (void *) b;
+
+  if (p1->offset < p2->offset)
+    return -1;
+  if (p1->offset > p2->offset)
+    return 1;
+
+  if (!p1->warned && !p2->warned)
+    {
+      if (p1->addr64 != p2->addr64)
+	{
+	  p1->warned = p2->warned = true;
+	  error (0, 0,
+		 gettext ("%s %#" PRIx64 " used with different address sizes"),
+		 name, (uint64_t) p1->offset);
+	}
+      if (p1->dwarf64 != p2->dwarf64)
+	{
+	  p1->warned = p2->warned = true;
+	  error (0, 0,
+		 gettext ("%s %#" PRIx64 " used with different offset sizes"),
+		 name, (uint64_t) p1->offset);
+	}
+      if (listptr_base (p1) != listptr_base (p2))
+	{
+	  p1->warned = p2->warned = true;
+	  error (0, 0,
+		 gettext ("%s %#" PRIx64 " used with different base addresses"),
+		 name, (uint64_t) p1->offset);
+	}
+    }
+
+  return 0;
+}
+
+struct listptr_table
+{
+  size_t n;
+  size_t alloc;
+  struct listptr *table;
+};
+
+static struct listptr_table known_loclistptr;
+static struct listptr_table known_rangelistptr;
+
+static void
+reset_listptr (struct listptr_table *table)
+{
+  free (table->table);
+  table->table = NULL;
+  table->n = table->alloc = 0;
+}
+
+/* Returns false if offset doesn't fit.  See struct listptr.  */
+static bool
+notice_listptr (enum section_e section, struct listptr_table *table,
+		uint_fast8_t address_size, uint_fast8_t offset_size,
+		struct Dwarf_CU *cu, Dwarf_Off offset)
+{
+  if (print_debug_sections & section)
+    {
+      if (table->n == table->alloc)
+	{
+	  if (table->alloc == 0)
+	    table->alloc = 128;
+	  else
+	    table->alloc *= 2;
+	  table->table = xrealloc (table->table,
+				   table->alloc * sizeof table->table[0]);
+	}
+
+      struct listptr *p = &table->table[table->n++];
+
+      *p = (struct listptr)
+	{
+	  .addr64 = address_size == 8,
+	  .dwarf64 = offset_size == 8,
+	  .offset = offset,
+	  .cu = cu
+	};
+
+      if (p->offset != offset)
+	{
+	  table->n--;
+	  return false;
+	}
+    }
+  return true;
+}
+
+static void
+sort_listptr (struct listptr_table *table, const char *name)
+{
+  if (table->n > 0)
+    qsort_r (table->table, table->n, sizeof table->table[0],
+	     &compare_listptr, (void *) name);
+}
+
+static bool
+skip_listptr_hole (struct listptr_table *table, size_t *idxp,
+		   uint_fast8_t *address_sizep, uint_fast8_t *offset_sizep,
+		   Dwarf_Addr *base, struct Dwarf_CU **cu, ptrdiff_t offset,
+		   unsigned char **readp, unsigned char *endp)
+{
+  if (table->n == 0)
+    return false;
+
+  while (*idxp < table->n && table->table[*idxp].offset < (Dwarf_Off) offset)
+    ++*idxp;
+
+  struct listptr *p = &table->table[*idxp];
+
+  if (*idxp == table->n
+      || p->offset >= (Dwarf_Off) (endp - *readp + offset))
+    {
+      *readp = endp;
+      printf (gettext (" [%6tx]  <UNUSED GARBAGE IN REST OF SECTION>\n"),
+	      offset);
+      return true;
+    }
+
+  if (p->offset != (Dwarf_Off) offset)
+    {
+      *readp += p->offset - offset;
+      printf (gettext (" [%6tx]  <UNUSED GARBAGE> ... %" PRIu64 " bytes ...\n"),
+	      offset, (Dwarf_Off) p->offset - offset);
+      return true;
+    }
+
+  if (address_sizep != NULL)
+    *address_sizep = listptr_address_size (p);
+  if (offset_sizep != NULL)
+    *offset_sizep = listptr_offset_size (p);
+  if (base != NULL)
+    *base = listptr_base (p);
+  if (cu != NULL)
+    *cu = p->cu;
+
+  return false;
+}
+
+
+static void
+print_debug_abbrev_section (Dwfl_Module *dwflmod __attribute__ ((unused)),
+			    Ebl *ebl, GElf_Ehdr *ehdr,
+			    Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  const size_t sh_size = (dbg->sectiondata[IDX_debug_abbrev] ?
+			  dbg->sectiondata[IDX_debug_abbrev]->d_size : 0);
+
+  printf (gettext ("\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"
+		   " [ Code]\n"),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset);
+
+  Dwarf_Off offset = 0;
+  while (offset < sh_size)
+    {
+      printf (gettext ("\nAbbreviation section at offset %" PRIu64 ":\n"),
+	      offset);
+
+      while (1)
+	{
+	  size_t length;
+	  Dwarf_Abbrev abbrev;
+
+	  int res = dwarf_offabbrev (dbg, offset, &length, &abbrev);
+	  if (res != 0)
+	    {
+	      if (unlikely (res < 0))
+		{
+		  printf (gettext ("\
+ *** error while reading abbreviation: %s\n"),
+			  dwarf_errmsg (-1));
+		  return;
+		}
+
+	      /* This is the NUL byte at the end of the section.  */
+	      ++offset;
+	      break;
+	    }
+
+	  /* We know these calls can never fail.  */
+	  unsigned int code = dwarf_getabbrevcode (&abbrev);
+	  unsigned int tag = dwarf_getabbrevtag (&abbrev);
+	  int has_children = dwarf_abbrevhaschildren (&abbrev);
+
+	  printf (gettext (" [%5u] offset: %" PRId64
+			   ", children: %s, tag: %s\n"),
+		  code, (int64_t) offset,
+		  has_children ? gettext ("yes") : gettext ("no"),
+		  dwarf_tag_name (tag));
+
+	  size_t cnt = 0;
+	  unsigned int name;
+	  unsigned int form;
+	  Dwarf_Off enoffset;
+	  while (dwarf_getabbrevattr (&abbrev, cnt,
+				      &name, &form, &enoffset) == 0)
+	    {
+	      printf ("          attr: %s, form: %s, offset: %#" PRIx64 "\n",
+		      dwarf_attr_name (name), dwarf_form_name (form),
+		      (uint64_t) enoffset);
+
+	      ++cnt;
+	    }
+
+	  offset += length;
+	}
+    }
+}
+
+
+/* Print content of DWARF .debug_aranges section.  We fortunately do
+   not have to know a bit about the structure of the section, libdwarf
+   takes care of it.  */
+static void
+print_decoded_aranges_section (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn,
+			       GElf_Shdr *shdr, Dwarf *dbg)
+{
+  Dwarf_Aranges *aranges;
+  size_t cnt;
+  if (unlikely (dwarf_getaranges (dbg, &aranges, &cnt) != 0))
+    {
+      error (0, 0, gettext ("cannot get .debug_aranges content: %s"),
+	     dwarf_errmsg (-1));
+      return;
+    }
+
+  GElf_Shdr glink_mem;
+  GElf_Shdr *glink;
+  glink = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link), &glink_mem);
+  if (glink == NULL)
+    {
+      error (0, 0, gettext ("invalid sh_link value in section %zu"),
+	     elf_ndxscn (scn));
+      return;
+    }
+
+  printf (ngettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 " contains %zu entry:\n",
+		    "\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 " contains %zu entries:\n",
+		    cnt),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset, cnt);
+
+  /* Compute floor(log16(cnt)).  */
+  size_t tmp = cnt;
+  int digits = 1;
+  while (tmp >= 16)
+    {
+      ++digits;
+      tmp >>= 4;
+    }
+
+  for (size_t n = 0; n < cnt; ++n)
+    {
+      Dwarf_Arange *runp = dwarf_onearange (aranges, n);
+      if (unlikely (runp == NULL))
+	{
+	  printf ("cannot get arange %zu: %s\n", n, dwarf_errmsg (-1));
+	  return;
+	}
+
+      Dwarf_Addr start;
+      Dwarf_Word length;
+      Dwarf_Off offset;
+
+      if (unlikely (dwarf_getarangeinfo (runp, &start, &length, &offset) != 0))
+	printf (gettext (" [%*zu] ???\n"), digits, n);
+      else
+	printf (gettext (" [%*zu] start: %0#*" PRIx64
+			 ", length: %5" PRIu64 ", CU DIE offset: %6"
+			 PRId64 "\n"),
+		digits, n, ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 10 : 18,
+		(uint64_t) start, (uint64_t) length, (int64_t) offset);
+    }
+}
+
+
+/* Print content of DWARF .debug_aranges section.  */
+static void
+print_debug_aranges_section (Dwfl_Module *dwflmod __attribute__ ((unused)),
+			     Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn,
+			     GElf_Shdr *shdr, Dwarf *dbg)
+{
+  if (decodedaranges)
+    {
+      print_decoded_aranges_section (ebl, ehdr, scn, shdr, dbg);
+      return;
+    }
+
+  Elf_Data *data = dbg->sectiondata[IDX_debug_aranges];
+
+  if (unlikely (data == NULL))
+    {
+      error (0, 0, gettext ("cannot get .debug_aranges content: %s"),
+	     elf_errmsg (-1));
+      return;
+    }
+
+  printf (gettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset);
+
+  const unsigned char *readp = data->d_buf;
+  const unsigned char *readendp = readp + data->d_size;
+
+  while (readp < readendp)
+    {
+      const unsigned char *hdrstart = readp;
+      size_t start_offset = hdrstart - (const unsigned char *) data->d_buf;
+
+      printf (gettext ("\nTable at offset %zu:\n"), start_offset);
+      if (readp + 4 > readendp)
+	{
+	invalid_data:
+	  error (0, 0, gettext ("invalid data in section [%zu] '%s'"),
+		 elf_ndxscn (scn), section_name (ebl, ehdr, shdr));
+	  return;
+	}
+
+      Dwarf_Word length = read_4ubyte_unaligned_inc (dbg, readp);
+      unsigned int length_bytes = 4;
+      if (length == DWARF3_LENGTH_64_BIT)
+	{
+	  if (readp + 8 > readendp)
+	    goto invalid_data;
+	  length = read_8ubyte_unaligned_inc (dbg, readp);
+	  length_bytes = 8;
+	}
+
+      const unsigned char *nexthdr = readp + length;
+      printf (gettext ("\n Length:        %6" PRIu64 "\n"),
+	      (uint64_t) length);
+
+      if (unlikely (length > (size_t) (readendp - readp)))
+	goto invalid_data;
+
+      if (length == 0)
+	continue;
+
+      if (readp + 2 > readendp)
+	goto invalid_data;
+      uint_fast16_t version = read_2ubyte_unaligned_inc (dbg, readp);
+      printf (gettext (" DWARF version: %6" PRIuFAST16 "\n"),
+	      version);
+      if (version != 2)
+	{
+	  error (0, 0, gettext ("unsupported aranges version"));
+	  goto next_table;
+	}
+
+      Dwarf_Word offset;
+      if (readp + length_bytes > readendp)
+	goto invalid_data;
+      if (length_bytes == 8)
+	offset = read_8ubyte_unaligned_inc (dbg, readp);
+      else
+	offset = read_4ubyte_unaligned_inc (dbg, readp);
+      printf (gettext (" CU offset:     %6" PRIx64 "\n"),
+	      (uint64_t) offset);
+
+      if (readp + 1 > readendp)
+	goto invalid_data;
+      unsigned int address_size = *readp++;
+      printf (gettext (" Address size:  %6" PRIu64 "\n"),
+	      (uint64_t) address_size);
+      if (address_size != 4 && address_size != 8)
+	{
+	  error (0, 0, gettext ("unsupported address size"));
+	  goto next_table;
+	}
+
+      unsigned int segment_size = *readp++;
+      printf (gettext (" Segment size:  %6" PRIu64 "\n\n"),
+	      (uint64_t) segment_size);
+      if (segment_size != 0 && segment_size != 4 && segment_size != 8)
+	{
+	  error (0, 0, gettext ("unsupported segment size"));
+	  goto next_table;
+	}
+
+      /* Round the address to the next multiple of 2*address_size.  */
+      readp += ((2 * address_size - ((readp - hdrstart) % (2 * address_size)))
+		% (2 * address_size));
+
+      while (readp < nexthdr)
+	{
+	  Dwarf_Word range_address;
+	  Dwarf_Word range_length;
+	  Dwarf_Word segment = 0;
+	  if (readp + 2 * address_size + segment_size > readendp)
+	    goto invalid_data;
+	  if (address_size == 4)
+	    {
+	      range_address = read_4ubyte_unaligned_inc (dbg, readp);
+	      range_length = read_4ubyte_unaligned_inc (dbg, readp);
+	    }
+	  else
+	    {
+	      range_address = read_8ubyte_unaligned_inc (dbg, readp);
+	      range_length = read_8ubyte_unaligned_inc (dbg, readp);
+	    }
+
+	  if (segment_size == 4)
+	    segment = read_4ubyte_unaligned_inc (dbg, readp);
+	  else if (segment_size == 8)
+	    segment = read_8ubyte_unaligned_inc (dbg, readp);
+
+	  if (range_address == 0 && range_length == 0 && segment == 0)
+	    break;
+
+	  char *b = format_dwarf_addr (dwflmod, address_size, range_address,
+				       range_address);
+	  char *e = format_dwarf_addr (dwflmod, address_size,
+				       range_address + range_length - 1,
+				       range_length);
+	  if (segment_size != 0)
+	    printf (gettext ("   %s..%s (%" PRIx64 ")\n"), b, e,
+		    (uint64_t) segment);
+	  else
+	    printf (gettext ("   %s..%s\n"), b, e);
+	  free (b);
+	  free (e);
+	}
+
+    next_table:
+      if (readp != nexthdr)
+	{
+	  size_t padding = nexthdr - readp;
+	  printf (gettext ("   %zu padding bytes\n"), padding);
+	  readp = nexthdr;
+	}
+    }
+}
+
+
+/* Print content of DWARF .debug_ranges section.  */
+static void
+print_debug_ranges_section (Dwfl_Module *dwflmod,
+			    Ebl *ebl, GElf_Ehdr *ehdr,
+			    Elf_Scn *scn, GElf_Shdr *shdr,
+			    Dwarf *dbg)
+{
+  Elf_Data *data = dbg->sectiondata[IDX_debug_ranges];
+
+  if (unlikely (data == NULL))
+    {
+      error (0, 0, gettext ("cannot get .debug_ranges content: %s"),
+	     elf_errmsg (-1));
+      return;
+    }
+
+  printf (gettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset);
+
+  sort_listptr (&known_rangelistptr, "rangelistptr");
+  size_t listptr_idx = 0;
+
+  uint_fast8_t address_size = ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8;
+
+  bool first = true;
+  Dwarf_Addr base = 0;
+  unsigned char *const endp = (unsigned char *) data->d_buf + data->d_size;
+  unsigned char *readp = data->d_buf;
+  Dwarf_CU *last_cu = NULL;
+  while (readp < endp)
+    {
+      ptrdiff_t offset = readp - (unsigned char *) data->d_buf;
+      Dwarf_CU *cu = last_cu;
+
+      if (first && skip_listptr_hole (&known_rangelistptr, &listptr_idx,
+				      &address_size, NULL, &base, &cu,
+				      offset, &readp, endp))
+	continue;
+
+      if (last_cu != cu)
+	{
+	  char *basestr = format_dwarf_addr (dwflmod, address_size,
+					     base, base);
+	  Dwarf_Die cudie;
+	  if (dwarf_cu_die (cu, &cudie,
+			    NULL, NULL, NULL, NULL,
+			    NULL, NULL) == NULL)
+	    printf (gettext ("\n Unknown CU base: %s\n"), basestr);
+	  else
+	    printf (gettext ("\n CU [%6" PRIx64 "] base: %s\n"),
+		    dwarf_dieoffset (&cudie), basestr);
+	  free (basestr);
+	}
+      last_cu = cu;
+
+      if (unlikely (data->d_size - offset < (size_t) address_size * 2))
+	{
+	  printf (gettext (" [%6tx]  <INVALID DATA>\n"), offset);
+	  break;
+	}
+
+      Dwarf_Addr begin;
+      Dwarf_Addr end;
+      if (address_size == 8)
+	{
+	  begin = read_8ubyte_unaligned_inc (dbg, readp);
+	  end = read_8ubyte_unaligned_inc (dbg, readp);
+	}
+      else
+	{
+	  begin = read_4ubyte_unaligned_inc (dbg, readp);
+	  end = read_4ubyte_unaligned_inc (dbg, readp);
+	  if (begin == (Dwarf_Addr) (uint32_t) -1)
+	    begin = (Dwarf_Addr) -1l;
+	}
+
+      if (begin == (Dwarf_Addr) -1l) /* Base address entry.  */
+	{
+	  char *b = format_dwarf_addr (dwflmod, address_size, end, end);
+	  printf (gettext (" [%6tx] base address\n          %s\n"), offset, b);
+	  free (b);
+	  base = end;
+	}
+      else if (begin == 0 && end == 0) /* End of list entry.  */
+	{
+	  if (first)
+	    printf (gettext (" [%6tx] empty list\n"), offset);
+	  first = true;
+	}
+      else
+	{
+	  /* We have an address range entry.  */
+	  if (first)		/* First address range entry in a list.  */
+	    printf (" [%6tx] ", offset);
+	  else
+	    printf ("          ");
+
+	  printf ("range %" PRIx64 ", %" PRIx64 "\n", begin, end);
+	  if (! print_unresolved_addresses)
+	    {
+	      char *b = format_dwarf_addr (dwflmod, address_size, base + begin,
+					   base + begin);
+	      char *e = format_dwarf_addr (dwflmod, address_size,
+					   base + end - 1, base + end);
+	      printf ("          %s..\n", b);
+	      printf ("          %s\n", e);
+	      free (b);
+	      free (e);
+	    }
+
+	  first = false;
+	}
+    }
+}
+
+#define REGNAMESZ 16
+static const char *
+register_info (Ebl *ebl, unsigned int regno, const Ebl_Register_Location *loc,
+	       char name[REGNAMESZ], int *bits, int *type)
+{
+  const char *set;
+  const char *pfx;
+  int ignore;
+  ssize_t n = ebl_register_info (ebl, regno, name, REGNAMESZ, &pfx, &set,
+				 bits ?: &ignore, type ?: &ignore);
+  if (n <= 0)
+    {
+      if (loc != NULL)
+	snprintf (name, REGNAMESZ, "reg%u", loc->regno);
+      else
+	snprintf (name, REGNAMESZ, "??? 0x%x", regno);
+      if (bits != NULL)
+	*bits = loc != NULL ? loc->bits : 0;
+      if (type != NULL)
+	*type = DW_ATE_unsigned;
+      set = "??? unrecognized";
+    }
+  else
+    {
+      if (bits != NULL && *bits <= 0)
+	*bits = loc != NULL ? loc->bits : 0;
+      if (type != NULL && *type == DW_ATE_void)
+	*type = DW_ATE_unsigned;
+
+    }
+  return set;
+}
+
+static const unsigned char *
+read_encoded (unsigned int encoding, const unsigned char *readp,
+	      const unsigned char *const endp, uint64_t *res, Dwarf *dbg)
+{
+  if ((encoding & 0xf) == DW_EH_PE_absptr)
+    encoding = gelf_getclass (dbg->elf) == ELFCLASS32
+      ? DW_EH_PE_udata4 : DW_EH_PE_udata8;
+
+  switch (encoding & 0xf)
+    {
+    case DW_EH_PE_uleb128:
+      get_uleb128 (*res, readp, endp);
+      break;
+    case DW_EH_PE_sleb128:
+      get_sleb128 (*res, readp, endp);
+      break;
+    case DW_EH_PE_udata2:
+      if (readp + 2 > endp)
+	goto invalid;
+      *res = read_2ubyte_unaligned_inc (dbg, readp);
+      break;
+    case DW_EH_PE_udata4:
+      if (readp + 4 > endp)
+	goto invalid;
+      *res = read_4ubyte_unaligned_inc (dbg, readp);
+      break;
+    case DW_EH_PE_udata8:
+      if (readp + 8 > endp)
+	goto invalid;
+      *res = read_8ubyte_unaligned_inc (dbg, readp);
+      break;
+    case DW_EH_PE_sdata2:
+      if (readp + 2 > endp)
+	goto invalid;
+      *res = read_2sbyte_unaligned_inc (dbg, readp);
+      break;
+    case DW_EH_PE_sdata4:
+      if (readp + 4 > endp)
+	goto invalid;
+      *res = read_4sbyte_unaligned_inc (dbg, readp);
+      break;
+    case DW_EH_PE_sdata8:
+      if (readp + 8 > endp)
+	goto invalid;
+      *res = read_8sbyte_unaligned_inc (dbg, readp);
+      break;
+    default:
+    invalid:
+      error (1, 0,
+	     gettext ("invalid encoding"));
+    }
+
+  return readp;
+}
+
+
+static void
+print_cfa_program (const unsigned char *readp, const unsigned char *const endp,
+		   Dwarf_Word vma_base, unsigned int code_align,
+		   int data_align,
+		   unsigned int version, unsigned int ptr_size,
+		   unsigned int encoding,
+		   Dwfl_Module *dwflmod, Ebl *ebl, Dwarf *dbg)
+{
+  char regnamebuf[REGNAMESZ];
+  const char *regname (unsigned int regno)
+  {
+    register_info (ebl, regno, NULL, regnamebuf, NULL, NULL);
+    return regnamebuf;
+  }
+
+  puts ("\n   Program:");
+  Dwarf_Word pc = vma_base;
+  while (readp < endp)
+    {
+      unsigned int opcode = *readp++;
+
+      if (opcode < DW_CFA_advance_loc)
+	/* Extended opcode.  */
+	switch (opcode)
+	  {
+	    uint64_t op1;
+	    int64_t sop1;
+	    uint64_t op2;
+	    int64_t sop2;
+
+	  case DW_CFA_nop:
+	    puts ("     nop");
+	    break;
+	  case DW_CFA_set_loc:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    readp = read_encoded (encoding, readp, endp, &op1, dbg);
+	    printf ("     set_loc %#" PRIx64 " to %#" PRIx64 "\n",
+		    op1, pc = vma_base + op1);
+	    break;
+	  case DW_CFA_advance_loc1:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    printf ("     advance_loc1 %u to %#" PRIx64 "\n",
+		    *readp, pc += *readp * code_align);
+	    ++readp;
+	    break;
+	  case DW_CFA_advance_loc2:
+	    if ((uint64_t) (endp - readp) < 2)
+	      goto invalid;
+	    op1 = read_2ubyte_unaligned_inc (dbg, readp);
+	    printf ("     advance_loc2 %" PRIu64 " to %#" PRIx64 "\n",
+		    op1, pc += op1 * code_align);
+	    break;
+	  case DW_CFA_advance_loc4:
+	    if ((uint64_t) (endp - readp) < 4)
+	      goto invalid;
+	    op1 = read_4ubyte_unaligned_inc (dbg, readp);
+	    printf ("     advance_loc4 %" PRIu64 " to %#" PRIx64 "\n",
+		    op1, pc += op1 * code_align);
+	    break;
+	  case DW_CFA_offset_extended:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op2, readp, endp);
+	    printf ("     offset_extended r%" PRIu64 " (%s) at cfa%+" PRId64
+		    "\n",
+		    op1, regname (op1), op2 * data_align);
+	    break;
+	  case DW_CFA_restore_extended:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    printf ("     restore_extended r%" PRIu64 " (%s)\n",
+		    op1, regname (op1));
+	    break;
+	  case DW_CFA_undefined:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    printf ("     undefined r%" PRIu64 " (%s)\n", op1, regname (op1));
+	    break;
+	  case DW_CFA_same_value:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    printf ("     same_value r%" PRIu64 " (%s)\n", op1, regname (op1));
+	    break;
+	  case DW_CFA_register:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op2, readp, endp);
+	    printf ("     register r%" PRIu64 " (%s) in r%" PRIu64 " (%s)\n",
+		    op1, regname (op1), op2, regname (op2));
+	    break;
+	  case DW_CFA_remember_state:
+	    puts ("     remember_state");
+	    break;
+	  case DW_CFA_restore_state:
+	    puts ("     restore_state");
+	    break;
+	  case DW_CFA_def_cfa:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op2, readp, endp);
+	    printf ("     def_cfa r%" PRIu64 " (%s) at offset %" PRIu64 "\n",
+		    op1, regname (op1), op2);
+	    break;
+	  case DW_CFA_def_cfa_register:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    printf ("     def_cfa_register r%" PRIu64 " (%s)\n",
+		    op1, regname (op1));
+	    break;
+	  case DW_CFA_def_cfa_offset:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    printf ("     def_cfa_offset %" PRIu64 "\n", op1);
+	    break;
+	  case DW_CFA_def_cfa_expression:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);	/* Length of DW_FORM_block.  */
+	    printf ("     def_cfa_expression %" PRIu64 "\n", op1);
+	    if ((uint64_t) (endp - readp) < op1)
+	      {
+	    invalid:
+	        fputs (gettext ("         <INVALID DATA>\n"), stdout);
+		return;
+	      }
+	    print_ops (dwflmod, dbg, 10, 10, version, ptr_size, 0, NULL,
+		       op1, readp);
+	    readp += op1;
+	    break;
+	  case DW_CFA_expression:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op2, readp, endp);	/* Length of DW_FORM_block.  */
+	    printf ("     expression r%" PRIu64 " (%s) \n",
+		    op1, regname (op1));
+	    if ((uint64_t) (endp - readp) < op2)
+	      goto invalid;
+	    print_ops (dwflmod, dbg, 10, 10, version, ptr_size, 0, NULL,
+		       op2, readp);
+	    readp += op2;
+	    break;
+	  case DW_CFA_offset_extended_sf:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_sleb128 (sop2, readp, endp);
+	    printf ("     offset_extended_sf r%" PRIu64 " (%s) at cfa%+"
+		    PRId64 "\n",
+		    op1, regname (op1), sop2 * data_align);
+	    break;
+	  case DW_CFA_def_cfa_sf:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_sleb128 (sop2, readp, endp);
+	    printf ("     def_cfa_sf r%" PRIu64 " (%s) at offset %" PRId64 "\n",
+		    op1, regname (op1), sop2 * data_align);
+	    break;
+	  case DW_CFA_def_cfa_offset_sf:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_sleb128 (sop1, readp, endp);
+	    printf ("     def_cfa_offset_sf %" PRId64 "\n", sop1 * data_align);
+	    break;
+	  case DW_CFA_val_offset:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op2, readp, endp);
+	    printf ("     val_offset %" PRIu64 " at offset %" PRIu64 "\n",
+		    op1, op2 * data_align);
+	    break;
+	  case DW_CFA_val_offset_sf:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_sleb128 (sop2, readp, endp);
+	    printf ("     val_offset_sf %" PRIu64 " at offset %" PRId64 "\n",
+		    op1, sop2 * data_align);
+	    break;
+	  case DW_CFA_val_expression:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op2, readp, endp);	/* Length of DW_FORM_block.  */
+	    printf ("     val_expression r%" PRIu64 " (%s)\n",
+		    op1, regname (op1));
+	    if ((uint64_t) (endp - readp) < op2)
+	      goto invalid;
+	    print_ops (dwflmod, dbg, 10, 10, version, ptr_size, 0,
+		       NULL, op2, readp);
+	    readp += op2;
+	    break;
+	  case DW_CFA_MIPS_advance_loc8:
+	    if ((uint64_t) (endp - readp) < 8)
+	      goto invalid;
+	    op1 = read_8ubyte_unaligned_inc (dbg, readp);
+	    printf ("     MIPS_advance_loc8 %" PRIu64 " to %#" PRIx64 "\n",
+		    op1, pc += op1 * code_align);
+	    break;
+	  case DW_CFA_GNU_window_save:
+	    puts ("     GNU_window_save");
+	    break;
+	  case DW_CFA_GNU_args_size:
+	    if ((uint64_t) (endp - readp) < 1)
+	      goto invalid;
+	    get_uleb128 (op1, readp, endp);
+	    printf ("     args_size %" PRIu64 "\n", op1);
+	    break;
+	  default:
+	    printf ("     ??? (%u)\n", opcode);
+	    break;
+	  }
+      else if (opcode < DW_CFA_offset)
+	printf ("     advance_loc %u to %#" PRIx64 "\n",
+		opcode & 0x3f, pc += (opcode & 0x3f) * code_align);
+      else if (opcode < DW_CFA_restore)
+	{
+	  uint64_t offset;
+	  if ((uint64_t) (endp - readp) < 1)
+	    goto invalid;
+	  get_uleb128 (offset, readp, endp);
+	  printf ("     offset r%u (%s) at cfa%+" PRId64 "\n",
+		  opcode & 0x3f, regname (opcode & 0x3f), offset * data_align);
+	}
+      else
+	printf ("     restore r%u (%s)\n",
+		opcode & 0x3f, regname (opcode & 0x3f));
+    }
+}
+
+
+static unsigned int
+encoded_ptr_size (int encoding, unsigned int ptr_size)
+{
+  switch (encoding & 7)
+    {
+    case DW_EH_PE_udata4:
+      return 4;
+    case DW_EH_PE_udata8:
+      return 8;
+    case 0:
+      return ptr_size;
+    }
+
+  fprintf (stderr, "Unsupported pointer encoding: %#x, "
+	   "assuming pointer size of %d.\n", encoding, ptr_size);
+  return ptr_size;
+}
+
+
+static unsigned int
+print_encoding (unsigned int val)
+{
+  switch (val & 0xf)
+    {
+    case DW_EH_PE_absptr:
+      fputs ("absptr", stdout);
+      break;
+    case DW_EH_PE_uleb128:
+      fputs ("uleb128", stdout);
+      break;
+    case DW_EH_PE_udata2:
+      fputs ("udata2", stdout);
+      break;
+    case DW_EH_PE_udata4:
+      fputs ("udata4", stdout);
+      break;
+    case DW_EH_PE_udata8:
+      fputs ("udata8", stdout);
+      break;
+    case DW_EH_PE_sleb128:
+      fputs ("sleb128", stdout);
+      break;
+    case DW_EH_PE_sdata2:
+      fputs ("sdata2", stdout);
+      break;
+    case DW_EH_PE_sdata4:
+      fputs ("sdata4", stdout);
+      break;
+    case DW_EH_PE_sdata8:
+      fputs ("sdata8", stdout);
+      break;
+    default:
+      /* We did not use any of the bits after all.  */
+      return val;
+    }
+
+  return val & ~0xf;
+}
+
+
+static unsigned int
+print_relinfo (unsigned int val)
+{
+  switch (val & 0x70)
+    {
+    case DW_EH_PE_pcrel:
+      fputs ("pcrel", stdout);
+      break;
+    case DW_EH_PE_textrel:
+      fputs ("textrel", stdout);
+      break;
+    case DW_EH_PE_datarel:
+      fputs ("datarel", stdout);
+      break;
+    case DW_EH_PE_funcrel:
+      fputs ("funcrel", stdout);
+      break;
+    case DW_EH_PE_aligned:
+      fputs ("aligned", stdout);
+      break;
+    default:
+      return val;
+    }
+
+  return val & ~0x70;
+}
+
+
+static void
+print_encoding_base (const char *pfx, unsigned int fde_encoding)
+{
+  printf ("(%s", pfx);
+
+  if (fde_encoding == DW_EH_PE_omit)
+    puts ("omit)");
+  else
+    {
+      unsigned int w = fde_encoding;
+
+      w = print_encoding (w);
+
+      if (w & 0x70)
+	{
+	  if (w != fde_encoding)
+	    fputc_unlocked (' ', stdout);
+
+	  w = print_relinfo (w);
+	}
+
+      if (w != 0)
+	printf ("%s%x", w != fde_encoding ? " " : "", w);
+
+      puts (")");
+    }
+}
+
+
+static void
+print_debug_frame_section (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr *ehdr,
+			   Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  size_t shstrndx;
+  /* We know this call will succeed since it did in the caller.  */
+  (void) elf_getshdrstrndx (ebl->elf, &shstrndx);
+  const char *scnname = elf_strptr (ebl->elf, shstrndx, shdr->sh_name);
+
+  /* Needed if we find PC-relative addresses.  */
+  GElf_Addr bias;
+  if (dwfl_module_getelf (dwflmod, &bias) == NULL)
+    {
+      error (0, 0, gettext ("cannot get ELF: %s"), dwfl_errmsg (-1));
+      return;
+    }
+
+  bool is_eh_frame = strcmp (scnname, ".eh_frame") == 0;
+  Elf_Data *data = (is_eh_frame
+		    ? elf_rawdata (scn, NULL)
+		    : dbg->sectiondata[IDX_debug_frame]);
+
+  if (unlikely (data == NULL))
+    {
+      error (0, 0, gettext ("cannot get %s content: %s"),
+	     scnname, elf_errmsg (-1));
+      return;
+    }
+
+  if (is_eh_frame)
+    printf (gettext ("\
+\nCall frame information section [%2zu] '%s' at offset %#" PRIx64 ":\n"),
+	    elf_ndxscn (scn), scnname, (uint64_t) shdr->sh_offset);
+  else
+    printf (gettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"),
+	    elf_ndxscn (scn), scnname, (uint64_t) shdr->sh_offset);
+
+  struct cieinfo
+  {
+    ptrdiff_t cie_offset;
+    const char *augmentation;
+    unsigned int code_alignment_factor;
+    unsigned int data_alignment_factor;
+    uint8_t address_size;
+    uint8_t fde_encoding;
+    uint8_t lsda_encoding;
+    struct cieinfo *next;
+  } *cies = NULL;
+
+  const unsigned char *readp = data->d_buf;
+  const unsigned char *const dataend = ((unsigned char *) data->d_buf
+					+ data->d_size);
+  while (readp < dataend)
+    {
+      if (unlikely (readp + 4 > dataend))
+	{
+	invalid_data:
+	  error (0, 0, gettext ("invalid data in section [%zu] '%s'"),
+		     elf_ndxscn (scn), scnname);
+	      return;
+	}
+
+      /* At the beginning there must be a CIE.  There can be multiple,
+	 hence we test tis in a loop.  */
+      ptrdiff_t offset = readp - (unsigned char *) data->d_buf;
+
+      Dwarf_Word unit_length = read_4ubyte_unaligned_inc (dbg, readp);
+      unsigned int length = 4;
+      if (unlikely (unit_length == 0xffffffff))
+	{
+	  if (unlikely (readp + 8 > dataend))
+	    goto invalid_data;
+
+	  unit_length = read_8ubyte_unaligned_inc (dbg, readp);
+	  length = 8;
+	}
+
+      if (unlikely (unit_length == 0))
+	{
+	  printf (gettext ("\n [%6tx] Zero terminator\n"), offset);
+	  continue;
+	}
+
+      Dwarf_Word maxsize = dataend - readp;
+      if (unlikely (unit_length > maxsize))
+	goto invalid_data;
+
+      unsigned int ptr_size = ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8;
+
+      ptrdiff_t start = readp - (unsigned char *) data->d_buf;
+      const unsigned char *const cieend = readp + unit_length;
+      if (unlikely (cieend > dataend || readp + 8 > dataend))
+	goto invalid_data;
+
+      Dwarf_Off cie_id;
+      if (length == 4)
+	{
+	  cie_id = read_4ubyte_unaligned_inc (dbg, readp);
+	  if (!is_eh_frame && cie_id == DW_CIE_ID_32)
+	    cie_id = DW_CIE_ID_64;
+	}
+      else
+	cie_id = read_8ubyte_unaligned_inc (dbg, readp);
+
+      uint_fast8_t version = 2;
+      unsigned int code_alignment_factor;
+      int data_alignment_factor;
+      unsigned int fde_encoding = 0;
+      unsigned int lsda_encoding = 0;
+      Dwarf_Word initial_location = 0;
+      Dwarf_Word vma_base = 0;
+
+      if (cie_id == (is_eh_frame ? 0 : DW_CIE_ID_64))
+	{
+	  version = *readp++;
+	  const char *const augmentation = (const char *) readp;
+	  readp = memchr (readp, '\0', cieend - readp);
+	  if (unlikely (readp == NULL))
+	    goto invalid_data;
+	  ++readp;
+
+	  uint_fast8_t segment_size = 0;
+	  if (version >= 4)
+	    {
+	      if (cieend - readp < 5)
+		goto invalid_data;
+	      ptr_size = *readp++;
+	      segment_size = *readp++;
+	    }
+
+	  if (cieend - readp < 1)
+	    goto invalid_data;
+	  get_uleb128 (code_alignment_factor, readp, cieend);
+	  if (cieend - readp < 1)
+	    goto invalid_data;
+	  get_sleb128 (data_alignment_factor, readp, cieend);
+
+	  /* In some variant for unwind data there is another field.  */
+	  if (strcmp (augmentation, "eh") == 0)
+	    readp += ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8;
+
+	  unsigned int return_address_register;
+	  if (cieend - readp < 1)
+	    goto invalid_data;
+	  if (unlikely (version == 1))
+	    return_address_register = *readp++;
+	  else
+	    get_uleb128 (return_address_register, readp, cieend);
+
+	  printf ("\n [%6tx] CIE length=%" PRIu64 "\n"
+		  "   CIE_id:                   %" PRIu64 "\n"
+		  "   version:                  %u\n"
+		  "   augmentation:             \"%s\"\n",
+		  offset, (uint64_t) unit_length, (uint64_t) cie_id,
+		  version, augmentation);
+	  if (version >= 4)
+	    printf ("   address_size:             %u\n"
+		    "   segment_size:             %u\n",
+		    ptr_size, segment_size);
+	  printf ("   code_alignment_factor:    %u\n"
+		  "   data_alignment_factor:    %d\n"
+		  "   return_address_register:  %u\n",
+		  code_alignment_factor,
+		  data_alignment_factor, return_address_register);
+
+	  if (augmentation[0] == 'z')
+	    {
+	      unsigned int augmentationlen;
+	      get_uleb128 (augmentationlen, readp, cieend);
+
+	      if (augmentationlen > (size_t) (cieend - readp))
+		{
+		  error (0, 0, gettext ("invalid augmentation length"));
+		  readp = cieend;
+		  continue;
+		}
+
+	      const char *hdr = "Augmentation data:";
+	      const char *cp = augmentation + 1;
+	      while (*cp != '\0' && cp < augmentation + augmentationlen + 1)
+		{
+		  printf ("   %-26s%#x ", hdr, *readp);
+		  hdr = "";
+
+		  if (*cp == 'R')
+		    {
+		      fde_encoding = *readp++;
+		      print_encoding_base (gettext ("FDE address encoding: "),
+					   fde_encoding);
+		    }
+		  else if (*cp == 'L')
+		    {
+		      lsda_encoding = *readp++;
+		      print_encoding_base (gettext ("LSDA pointer encoding: "),
+					   lsda_encoding);
+		    }
+		  else if (*cp == 'P')
+		    {
+		      /* Personality.  This field usually has a relocation
+			 attached pointing to __gcc_personality_v0.  */
+		      const unsigned char *startp = readp;
+		      unsigned int encoding = *readp++;
+		      uint64_t val = 0;
+		      readp = read_encoded (encoding, readp,
+					    readp - 1 + augmentationlen,
+					    &val, dbg);
+
+		      while (++startp < readp)
+			printf ("%#x ", *startp);
+
+		      putchar ('(');
+		      print_encoding (encoding);
+		      putchar (' ');
+		      switch (encoding & 0xf)
+			{
+			case DW_EH_PE_sleb128:
+			case DW_EH_PE_sdata2:
+			case DW_EH_PE_sdata4:
+			  printf ("%" PRId64 ")\n", val);
+			  break;
+			default:
+			  printf ("%#" PRIx64 ")\n", val);
+			  break;
+			}
+		    }
+		  else
+		    printf ("(%x)\n", *readp++);
+
+		  ++cp;
+		}
+	    }
+
+	  if (likely (ptr_size == 4 || ptr_size == 8))
+	    {
+	      struct cieinfo *newp = alloca (sizeof (*newp));
+	      newp->cie_offset = offset;
+	      newp->augmentation = augmentation;
+	      newp->fde_encoding = fde_encoding;
+	      newp->lsda_encoding = lsda_encoding;
+	      newp->address_size = ptr_size;
+	      newp->code_alignment_factor = code_alignment_factor;
+	      newp->data_alignment_factor = data_alignment_factor;
+	      newp->next = cies;
+	      cies = newp;
+	    }
+	}
+      else
+	{
+	  struct cieinfo *cie = cies;
+	  while (cie != NULL)
+	    if (is_eh_frame
+		? ((Dwarf_Off) start - cie_id) == (Dwarf_Off) cie->cie_offset
+		: cie_id == (Dwarf_Off) cie->cie_offset)
+	      break;
+	    else
+	      cie = cie->next;
+	  if (unlikely (cie == NULL))
+	    {
+	      puts ("invalid CIE reference in FDE");
+	      return;
+	    }
+
+	  /* Initialize from CIE data.  */
+	  fde_encoding = cie->fde_encoding;
+	  lsda_encoding = cie->lsda_encoding;
+	  ptr_size = encoded_ptr_size (fde_encoding, cie->address_size);
+	  code_alignment_factor = cie->code_alignment_factor;
+	  data_alignment_factor = cie->data_alignment_factor;
+
+	  const unsigned char *base = readp;
+	  // XXX There are sometimes relocations for this value
+	  initial_location = read_addr_unaligned_inc (ptr_size, dbg, readp);
+	  Dwarf_Word address_range
+	    = read_addr_unaligned_inc (ptr_size, dbg, readp);
+
+	  /* pcrel for an FDE address is relative to the runtime
+	     address of the start_address field itself.  Sign extend
+	     if necessary to make sure the calculation is done on the
+	     full 64 bit address even when initial_location only holds
+	     the lower 32 bits.  */
+	  Dwarf_Addr pc_start = initial_location;
+	  if (ptr_size == 4)
+	    pc_start = (uint64_t) (int32_t) pc_start;
+	  if ((fde_encoding & 0x70) == DW_EH_PE_pcrel)
+	    pc_start += ((uint64_t) shdr->sh_addr
+			 + (base - (const unsigned char *) data->d_buf)
+			 - bias);
+
+	  char *a = format_dwarf_addr (dwflmod, cie->address_size,
+				       pc_start, initial_location);
+	  printf ("\n [%6tx] FDE length=%" PRIu64 " cie=[%6tx]\n"
+		  "   CIE_pointer:              %" PRIu64 "\n"
+		  "   initial_location:         %s",
+		  offset, (uint64_t) unit_length,
+		  cie->cie_offset, (uint64_t) cie_id, a);
+	  free (a);
+	  if ((fde_encoding & 0x70) == DW_EH_PE_pcrel)
+	    {
+	      vma_base = (((uint64_t) shdr->sh_offset
+			   + (base - (const unsigned char *) data->d_buf)
+			   + (uint64_t) initial_location)
+			  & (ptr_size == 4
+			     ? UINT64_C (0xffffffff)
+			     : UINT64_C (0xffffffffffffffff)));
+	      printf (gettext (" (offset: %#" PRIx64 ")"),
+		      (uint64_t) vma_base);
+	    }
+
+	  printf ("\n   address_range:            %#" PRIx64,
+		  (uint64_t) address_range);
+	  if ((fde_encoding & 0x70) == DW_EH_PE_pcrel)
+	    printf (gettext (" (end offset: %#" PRIx64 ")"),
+		    ((uint64_t) vma_base + (uint64_t) address_range)
+		    & (ptr_size == 4
+		       ? UINT64_C (0xffffffff)
+		       : UINT64_C (0xffffffffffffffff)));
+	  putchar ('\n');
+
+	  if (cie->augmentation[0] == 'z')
+	    {
+	      unsigned int augmentationlen;
+	      if (cieend - readp < 1)
+		goto invalid_data;
+	      get_uleb128 (augmentationlen, readp, cieend);
+
+	      if (augmentationlen > (size_t) (cieend - readp))
+		{
+		  error (0, 0, gettext ("invalid augmentation length"));
+		  readp = cieend;
+		  continue;
+		}
+
+	      if (augmentationlen > 0)
+		{
+		  const char *hdr = "Augmentation data:";
+		  const char *cp = cie->augmentation + 1;
+		  unsigned int u = 0;
+		  while (*cp != '\0'
+			 && cp < cie->augmentation + augmentationlen + 1)
+		    {
+		      if (*cp == 'L')
+			{
+			  uint64_t lsda_pointer;
+			  const unsigned char *p
+			    = read_encoded (lsda_encoding, &readp[u],
+					    &readp[augmentationlen],
+					    &lsda_pointer, dbg);
+			  u = p - readp;
+			  printf (gettext ("\
+   %-26sLSDA pointer: %#" PRIx64 "\n"),
+				  hdr, lsda_pointer);
+			  hdr = "";
+			}
+		      ++cp;
+		    }
+
+		  while (u < augmentationlen)
+		    {
+		      printf ("   %-26s%#x\n", hdr, readp[u++]);
+		      hdr = "";
+		    }
+		}
+
+	      readp += augmentationlen;
+	    }
+	}
+
+      /* Handle the initialization instructions.  */
+      if (ptr_size != 4 && ptr_size !=8)
+	printf ("invalid CIE pointer size (%u), must be 4 or 8.\n", ptr_size);
+      else
+	print_cfa_program (readp, cieend, vma_base, code_alignment_factor,
+			   data_alignment_factor, version, ptr_size,
+			   fde_encoding, dwflmod, ebl, dbg);
+      readp = cieend;
+    }
+}
+
+
+struct attrcb_args
+{
+  Dwfl_Module *dwflmod;
+  Dwarf *dbg;
+  Dwarf_Die *die;
+  int level;
+  bool silent;
+  unsigned int version;
+  unsigned int addrsize;
+  unsigned int offset_size;
+  struct Dwarf_CU *cu;
+};
+
+
+static int
+attr_callback (Dwarf_Attribute *attrp, void *arg)
+{
+  struct attrcb_args *cbargs = (struct attrcb_args *) arg;
+  const int level = cbargs->level;
+  Dwarf_Die *die = cbargs->die;
+
+  unsigned int attr = dwarf_whatattr (attrp);
+  if (unlikely (attr == 0))
+    {
+      if (!cbargs->silent)
+	error (0, 0, gettext ("DIE [%" PRIx64 "] "
+			      "cannot get attribute code: %s"),
+	       dwarf_dieoffset (die), dwarf_errmsg (-1));
+      return DWARF_CB_ABORT;
+    }
+
+  unsigned int form = dwarf_whatform (attrp);
+  if (unlikely (form == 0))
+    {
+      if (!cbargs->silent)
+	error (0, 0, gettext ("DIE [%" PRIx64 "] "
+			      "cannot get attribute form: %s"),
+	       dwarf_dieoffset (die), dwarf_errmsg (-1));
+      return DWARF_CB_ABORT;
+    }
+
+  switch (form)
+    {
+    case DW_FORM_addr:
+      if (!cbargs->silent)
+	{
+	  Dwarf_Addr addr;
+	  if (unlikely (dwarf_formaddr (attrp, &addr) != 0))
+	    {
+	    attrval_out:
+	      if (!cbargs->silent)
+		error (0, 0, gettext ("DIE [%" PRIx64 "] "
+				      "cannot get attribute '%s' (%s) value: "
+				      "%s"),
+		       dwarf_dieoffset (die),
+		       dwarf_attr_name (attr),
+		       dwarf_form_name (form),
+		       dwarf_errmsg (-1));
+	      /* Don't ABORT, it might be other attributes can be resolved.  */
+	      return DWARF_CB_OK;
+	    }
+	  char *a = format_dwarf_addr (cbargs->dwflmod, cbargs->addrsize,
+				       addr, addr);
+	  printf ("           %*s%-20s (%s) %s\n",
+		  (int) (level * 2), "", dwarf_attr_name (attr),
+		  dwarf_form_name (form), a);
+	  free (a);
+	}
+      break;
+
+    case DW_FORM_indirect:
+    case DW_FORM_strp:
+    case DW_FORM_string:
+    case DW_FORM_GNU_strp_alt:
+      if (cbargs->silent)
+	break;
+      const char *str = dwarf_formstring (attrp);
+      if (unlikely (str == NULL))
+	goto attrval_out;
+      printf ("           %*s%-20s (%s) \"%s\"\n",
+	      (int) (level * 2), "", dwarf_attr_name (attr),
+	      dwarf_form_name (form), str);
+      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:
+    case DW_FORM_GNU_ref_alt:
+      if (cbargs->silent)
+	break;
+      Dwarf_Die ref;
+      if (unlikely (dwarf_formref_die (attrp, &ref) == NULL))
+	goto attrval_out;
+
+      printf ("           %*s%-20s (%s) [%6" PRIxMAX "]\n",
+	      (int) (level * 2), "", dwarf_attr_name (attr),
+	      dwarf_form_name (form), (uintmax_t) dwarf_dieoffset (&ref));
+      break;
+
+    case DW_FORM_ref_sig8:
+      if (cbargs->silent)
+	break;
+      printf ("           %*s%-20s (%s) {%6" PRIx64 "}\n",
+	      (int) (level * 2), "", dwarf_attr_name (attr),
+	      dwarf_form_name (form),
+	      (uint64_t) read_8ubyte_unaligned (attrp->cu->dbg, attrp->valp));
+      break;
+
+    case DW_FORM_sec_offset:
+    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;
+
+      const char *valuestr = NULL;
+      switch (attr)
+	{
+	  /* This case can take either a constant or a loclistptr.  */
+	case DW_AT_data_member_location:
+	  if (form != DW_FORM_sec_offset
+	      && (cbargs->version >= 4
+		  || (form != DW_FORM_data4 && form != DW_FORM_data8)))
+	    {
+	      if (!cbargs->silent)
+		printf ("           %*s%-20s (%s) %" PRIxMAX "\n",
+			(int) (level * 2), "", dwarf_attr_name (attr),
+			dwarf_form_name (form), (uintmax_t) num);
+	      return DWARF_CB_OK;
+	    }
+	  FALLTHROUGH;
+
+	/* These cases always take a loclistptr and no constant. */
+	case DW_AT_location:
+	case DW_AT_data_location:
+	case DW_AT_vtable_elem_location:
+	case DW_AT_string_length:
+	case DW_AT_use_location:
+	case DW_AT_frame_base:
+	case DW_AT_return_addr:
+	case DW_AT_static_link:
+	case DW_AT_GNU_call_site_value:
+	case DW_AT_GNU_call_site_data_value:
+	case DW_AT_GNU_call_site_target:
+	case DW_AT_GNU_call_site_target_clobbered:
+	  {
+	    bool nlpt = notice_listptr (section_loc, &known_loclistptr,
+					cbargs->addrsize, cbargs->offset_size,
+					cbargs->cu, num);
+	    if (!cbargs->silent)
+	      printf ("           %*s%-20s (%s) location list [%6" PRIxMAX "]%s\n",
+		      (int) (level * 2), "", dwarf_attr_name (attr),
+		      dwarf_form_name (form), (uintmax_t) num,
+		      nlpt ? "" : " <WARNING offset too big>");
+	  }
+	  return DWARF_CB_OK;
+
+	case DW_AT_ranges:
+	  {
+	    bool nlpt = notice_listptr (section_ranges, &known_rangelistptr,
+					cbargs->addrsize, cbargs->offset_size,
+					cbargs->cu, num);
+	    if (!cbargs->silent)
+	      printf ("           %*s%-20s (%s) range list [%6" PRIxMAX "]%s\n",
+		      (int) (level * 2), "", dwarf_attr_name (attr),
+		      dwarf_form_name (form), (uintmax_t) num,
+		      nlpt ? "" : " <WARNING offset too big>");
+	  }
+	  return DWARF_CB_OK;
+
+	case DW_AT_language:
+	  valuestr = dwarf_lang_name (num);
+	  break;
+	case DW_AT_encoding:
+	  valuestr = dwarf_encoding_name (num);
+	  break;
+	case DW_AT_accessibility:
+	  valuestr = dwarf_access_name (num);
+	  break;
+	case DW_AT_defaulted:
+	  valuestr = dwarf_defaulted_name (num);
+	  break;
+	case DW_AT_visibility:
+	  valuestr = dwarf_visibility_name (num);
+	  break;
+	case DW_AT_virtuality:
+	  valuestr = dwarf_virtuality_name (num);
+	  break;
+	case DW_AT_identifier_case:
+	  valuestr = dwarf_identifier_case_name (num);
+	  break;
+	case DW_AT_calling_convention:
+	  valuestr = dwarf_calling_convention_name (num);
+	  break;
+	case DW_AT_inline:
+	  valuestr = dwarf_inline_name (num);
+	  break;
+	case DW_AT_ordering:
+	  valuestr = dwarf_ordering_name (num);
+	  break;
+	case DW_AT_discr_list:
+	  valuestr = dwarf_discr_list_name (num);
+	  break;
+	case DW_AT_decl_file:
+	case DW_AT_call_file:
+	  {
+	    /* Try to get the actual file, the current interface only
+	       gives us full paths, but we only want to show the file
+	       name for now.  */
+	    Dwarf_Die cudie;
+	    if (dwarf_cu_die (cbargs->cu, &cudie,
+			      NULL, NULL, NULL, NULL, NULL, NULL) != NULL)
+	      {
+		Dwarf_Files *files;
+		size_t nfiles;
+		if (dwarf_getsrcfiles (&cudie, &files, &nfiles) == 0)
+		  {
+		    valuestr = dwarf_filesrc (files, num, NULL, NULL);
+		    char *filename = strrchr (valuestr, '/');
+		    if (filename != NULL)
+		      valuestr = filename + 1;
+		  }
+	      }
+	  }
+	  break;
+	default:
+	  /* Nothing.  */
+	  break;
+	}
+
+      if (cbargs->silent)
+	break;
+
+      /* When highpc is in constant form it is relative to lowpc.
+	 In that case also show the address.  */
+      Dwarf_Addr highpc;
+      if (attr == DW_AT_high_pc && dwarf_highpc (cbargs->die, &highpc) == 0)
+	{
+	  char *a = format_dwarf_addr (cbargs->dwflmod, cbargs->addrsize,
+				       highpc, highpc);
+	  printf ("           %*s%-20s (%s) %" PRIuMAX " (%s)\n",
+		  (int) (level * 2), "", dwarf_attr_name (attr),
+		  dwarf_form_name (form), (uintmax_t) num, a);
+	  free (a);
+	}
+      else
+	{
+	  Dwarf_Sword snum = 0;
+	  if (form == DW_FORM_sdata)
+	    if (unlikely (dwarf_formsdata (attrp, &snum) != 0))
+	      goto attrval_out;
+
+	  if (valuestr == NULL)
+	    {
+	      printf ("           %*s%-20s (%s)",
+		      (int) (level * 2), "", dwarf_attr_name (attr),
+		      dwarf_form_name (form));
+	      if (form == DW_FORM_sdata)
+		printf (" %" PRIdMAX "\n", (intmax_t) snum);
+	      else
+		printf (" %" PRIuMAX "\n", (uintmax_t) num);
+	    }
+	  else
+	    {
+	      printf ("           %*s%-20s (%s) %s",
+		      (int) (level * 2), "", dwarf_attr_name (attr),
+		      dwarf_form_name (form), valuestr);
+	      if (form == DW_FORM_sdata)
+		printf (" (%" PRIdMAX ")\n", (intmax_t) snum);
+	      else
+		printf (" (%" PRIuMAX ")\n", (uintmax_t) num);
+	    }
+	}
+      break;
+
+    case DW_FORM_flag:
+      if (cbargs->silent)
+	break;
+      bool flag;
+      if (unlikely (dwarf_formflag (attrp, &flag) != 0))
+	goto attrval_out;
+
+      printf ("           %*s%-20s (%s) %s\n",
+	      (int) (level * 2), "", dwarf_attr_name (attr),
+	      dwarf_form_name (form), flag ? gettext ("yes") : gettext ("no"));
+      break;
+
+    case DW_FORM_flag_present:
+      if (cbargs->silent)
+	break;
+      printf ("           %*s%-20s (%s) %s\n",
+	      (int) (level * 2), "", dwarf_attr_name (attr),
+	      dwarf_form_name (form), gettext ("yes"));
+      break;
+
+    case DW_FORM_exprloc:
+    case DW_FORM_block4:
+    case DW_FORM_block2:
+    case DW_FORM_block1:
+    case DW_FORM_block:
+      if (cbargs->silent)
+	break;
+      Dwarf_Block block;
+      if (unlikely (dwarf_formblock (attrp, &block) != 0))
+	goto attrval_out;
+
+      printf ("           %*s%-20s (%s) ",
+	      (int) (level * 2), "", dwarf_attr_name (attr),
+	      dwarf_form_name (form));
+
+      switch (attr)
+	{
+	default:
+	  if (form != DW_FORM_exprloc)
+	    {
+	      print_block (block.length, block.data);
+	      break;
+	    }
+	  FALLTHROUGH;
+
+	case DW_AT_location:
+	case DW_AT_data_location:
+	case DW_AT_data_member_location:
+	case DW_AT_vtable_elem_location:
+	case DW_AT_string_length:
+	case DW_AT_use_location:
+	case DW_AT_frame_base:
+	case DW_AT_return_addr:
+	case DW_AT_static_link:
+	case DW_AT_allocated:
+	case DW_AT_associated:
+	case DW_AT_bit_size:
+	case DW_AT_bit_offset:
+	case DW_AT_bit_stride:
+	case DW_AT_byte_size:
+	case DW_AT_byte_stride:
+	case DW_AT_count:
+	case DW_AT_lower_bound:
+	case DW_AT_upper_bound:
+	case DW_AT_GNU_call_site_value:
+	case DW_AT_GNU_call_site_data_value:
+	case DW_AT_GNU_call_site_target:
+	case DW_AT_GNU_call_site_target_clobbered:
+	  putchar ('\n');
+	  print_ops (cbargs->dwflmod, cbargs->dbg,
+		     12 + level * 2, 12 + level * 2,
+		     cbargs->version, cbargs->addrsize, cbargs->offset_size,
+		     attrp->cu, block.length, block.data);
+	  break;
+	}
+      break;
+
+    default:
+      if (cbargs->silent)
+	break;
+      printf ("           %*s%-20s (%s) ???\n",
+	      (int) (level * 2), "", dwarf_attr_name (attr),
+	      dwarf_form_name (form));
+      break;
+    }
+
+  return DWARF_CB_OK;
+}
+
+static void
+print_debug_units (Dwfl_Module *dwflmod,
+		   Ebl *ebl, GElf_Ehdr *ehdr,
+		   Elf_Scn *scn, GElf_Shdr *shdr,
+		   Dwarf *dbg, bool debug_types)
+{
+  const bool silent = !(print_debug_sections & section_info);
+  const char *secname = section_name (ebl, ehdr, shdr);
+
+  if (!silent)
+    printf (gettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n [Offset]\n"),
+	    elf_ndxscn (scn), secname, (uint64_t) shdr->sh_offset);
+
+  /* If the section is empty we don't have to do anything.  */
+  if (!silent && 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;
+  uint64_t typesig;
+  Dwarf_Off typeoff;
+ next_cu:
+  if (dwarf_next_unit (dbg, offset, &nextcu, &cuhl, &version,
+		       &abbroffset, &addrsize, &offsize,
+		       debug_types ? &typesig : NULL,
+		       debug_types ? &typeoff : NULL) != 0)
+    goto do_return;
+
+  if (!silent)
+    {
+      if (debug_types)
+	printf (gettext (" Type unit at offset %" PRIu64 ":\n"
+			 " Version: %" PRIu16 ", Abbreviation section offset: %"
+			 PRIu64 ", Address size: %" PRIu8
+			 ", Offset size: %" PRIu8
+			 "\n Type signature: %#" PRIx64
+			 ", Type offset: %#" PRIx64 "\n"),
+		(uint64_t) offset, version, abbroffset, addrsize, offsize,
+		typesig, (uint64_t) typeoff);
+      else
+	printf (gettext (" Compilation unit at offset %" PRIu64 ":\n"
+			 " Version: %" PRIu16 ", Abbreviation section offset: %"
+			 PRIu64 ", Address size: %" PRIu8
+			 ", Offset size: %" PRIu8 "\n"),
+		(uint64_t) offset, version, abbroffset, addrsize, offsize);
+    }
+
+  struct attrcb_args args =
+    {
+      .dwflmod = dwflmod,
+      .dbg = dbg,
+      .silent = silent,
+      .version = version,
+      .addrsize = addrsize,
+      .offset_size = offsize
+    };
+
+  offset += cuhl;
+
+  int level = 0;
+
+  if (unlikely ((debug_types ? dwarf_offdie_types : dwarf_offdie)
+		(dbg, offset, &dies[level]) == NULL))
+    {
+      if (!silent)
+	error (0, 0, gettext ("cannot get DIE at offset %" PRIu64
+			      " in section '%s': %s"),
+	       (uint64_t) offset, secname, dwarf_errmsg (-1));
+      goto do_return;
+    }
+
+  args.cu = dies[0].cu;
+
+  do
+    {
+      offset = dwarf_dieoffset (&dies[level]);
+      if (unlikely (offset == ~0ul))
+	{
+	  if (!silent)
+	    error (0, 0, gettext ("cannot get DIE offset: %s"),
+		   dwarf_errmsg (-1));
+	  goto do_return;
+	}
+
+      int tag = dwarf_tag (&dies[level]);
+      if (unlikely (tag == DW_TAG_invalid))
+	{
+	  if (!silent)
+	    error (0, 0, gettext ("cannot get tag of DIE at offset [%" PRIx64
+				  "] in section '%s': %s"),
+		   (uint64_t) offset, secname, dwarf_errmsg (-1));
+	  goto do_return;
+	}
+
+      if (!silent)
+	{
+	  unsigned int code = dwarf_getabbrevcode (dies[level].abbrev);
+	  printf (" [%6" PRIx64 "]  %*s%-20s abbrev: %u\n",
+		  (uint64_t) offset, (int) (level * 2), "",
+		  dwarf_tag_name (tag), code);
+	}
+
+      /* Print the attribute values.  */
+      args.level = level;
+      args.die = &dies[level];
+      (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 (unlikely (res == -1))
+	    {
+	      if (!silent)
+		error (0, 0, gettext ("cannot get next DIE: %s\n"),
+		       dwarf_errmsg (-1));
+	      goto do_return;
+	    }
+	}
+      else if (unlikely (res < 0))
+	{
+	  if (!silent)
+	    error (0, 0, gettext ("cannot get next DIE: %s"),
+		   dwarf_errmsg (-1));
+	  goto do_return;
+	}
+      else
+	++level;
+    }
+  while (level >= 0);
+
+  offset = nextcu;
+  if (offset != 0)
+     goto next_cu;
+
+ do_return:
+  free (dies);
+}
+
+static void
+print_debug_info_section (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr *ehdr,
+			  Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  print_debug_units (dwflmod, ebl, ehdr, scn, shdr, dbg, false);
+}
+
+static void
+print_debug_types_section (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr *ehdr,
+			   Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  print_debug_units (dwflmod, ebl, ehdr, scn, shdr, dbg, true);
+}
+
+
+static void
+print_decoded_line_section (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr *ehdr,
+			    Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  printf (gettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n\n"),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset);
+
+  size_t address_size
+    = elf_getident (ebl->elf, NULL)[EI_CLASS] == ELFCLASS32 ? 4 : 8;
+
+  Dwarf_Off cuoffset;
+  Dwarf_Off ncuoffset = 0;
+  size_t hsize;
+  while (dwarf_nextcu (dbg, cuoffset = ncuoffset, &ncuoffset, &hsize,
+		       NULL, NULL, NULL) == 0)
+    {
+      Dwarf_Die cudie;
+      if (dwarf_offdie (dbg, cuoffset + hsize, &cudie) == NULL)
+	continue;
+
+      size_t nlines;
+      Dwarf_Lines *lines;
+      if (dwarf_getsrclines (&cudie, &lines, &nlines) != 0)
+	continue;
+
+      printf (" CU [%" PRIx64 "] %s\n",
+	      dwarf_dieoffset (&cudie), dwarf_diename (&cudie));
+      printf ("  line:col SBPE* disc isa op address"
+	      " (Statement Block Prologue Epilogue *End)\n");
+      const char *last_file = "";
+      for (size_t n = 0; n < nlines; n++)
+	{
+	  Dwarf_Line *line = dwarf_onesrcline (lines, n);
+	  if (line == NULL)
+	    {
+	      printf ("  dwarf_onesrcline: %s\n", dwarf_errmsg (-1));
+	      continue;
+	    }
+	  Dwarf_Word mtime, length;
+	  const char *file = dwarf_linesrc (line, &mtime, &length);
+	  if (file == NULL)
+	    {
+	      printf ("  <%s> (mtime: ?, length: ?)\n", dwarf_errmsg (-1));
+	      last_file = "";
+	    }
+	  else if (strcmp (last_file, file) != 0)
+	    {
+	      printf ("  %s (mtime: %" PRIu64 ", length: %" PRIu64 ")\n",
+		      file, mtime, length);
+	      last_file = file;
+	    }
+
+	  int lineno, colno;
+	  bool statement, endseq, block, prologue_end, epilogue_begin;
+	  unsigned int lineop, isa, disc;
+	  Dwarf_Addr address;
+	  dwarf_lineaddr (line, &address);
+	  dwarf_lineno (line, &lineno);
+	  dwarf_linecol (line, &colno);
+	  dwarf_lineop_index (line, &lineop);
+	  dwarf_linebeginstatement (line, &statement);
+	  dwarf_lineendsequence (line, &endseq);
+	  dwarf_lineblock (line, &block);
+	  dwarf_lineprologueend (line, &prologue_end);
+	  dwarf_lineepiloguebegin (line, &epilogue_begin);
+	  dwarf_lineisa (line, &isa);
+	  dwarf_linediscriminator (line, &disc);
+
+	  /* End sequence is special, it is one byte past.  */
+	  char *a = format_dwarf_addr (dwflmod, address_size,
+				       address - (endseq ? 1 : 0), address);
+	  printf ("  %4d:%-3d %c%c%c%c%c %4d %3d %2d %s\n",
+		  lineno, colno,
+		  (statement ? 'S' : ' '),
+		  (block ? 'B' : ' '),
+		  (prologue_end ? 'P' : ' '),
+		  (epilogue_begin ? 'E' : ' '),
+		  (endseq ? '*' : ' '),
+		  disc, isa, lineop, a);
+	  free (a);
+
+	  if (endseq)
+	    printf("\n");
+	}
+    }
+}
+
+
+static void
+print_debug_line_section (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr *ehdr,
+			  Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  if (decodedline)
+    {
+      print_decoded_line_section (dwflmod, ebl, ehdr, scn, shdr, dbg);
+      return;
+    }
+
+  printf (gettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset);
+
+  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 = dbg->sectiondata[IDX_debug_line];
+  if (unlikely (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;
+
+      printf (gettext ("\nTable at offset %zu:\n"), start_offset);
+
+      if (unlikely (linep + 4 > lineendp))
+	goto invalid_data;
+      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), section_name (ebl, ehdr, shdr));
+	      return;
+	    }
+	  unit_length = read_8ubyte_unaligned_inc (dbg, linep);
+	  length = 8;
+	}
+
+      /* Check whether we have enough room in the section.  */
+      if (unlikely (unit_length > (size_t) (lineendp - linep)
+	  || unit_length < 2 + length + 5 * 1))
+	goto invalid_data;
+      lineendp = linep + unit_length;
+
+      /* The next element of the header is the version identifier.  */
+      uint_fast16_t version = 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.  */
+      uint_fast8_t minimum_instr_len = *linep++;
+
+      /* Next the maximum operations per instruction, in version 4 format.  */
+      uint_fast8_t max_ops_per_instr = version < 4 ? 1 : *linep++;
+
+	/* Then the flag determining the default value of the is_stmt
+	   register.  */
+      uint_fast8_t default_is_stmt = *linep++;
+
+      /* Now the line base.  */
+      int_fast8_t line_base = *((const int_fast8_t *) linep);
+      ++linep;
+
+      /* And the line range.  */
+      uint_fast8_t line_range = *linep++;
+
+      /* The opcode base.  */
+      uint_fast8_t opcode_base = *linep++;
+
+      /* Print what we got so far.  */
+      printf (gettext ("\n"
+		       " Length:                     %" PRIu64 "\n"
+		       " DWARF version:              %" PRIuFAST16 "\n"
+		       " Prologue length:            %" PRIu64 "\n"
+		       " Minimum instruction length: %" PRIuFAST8 "\n"
+		       " Maximum operations per instruction: %" PRIuFAST8 "\n"
+		       " Initial value if '%s': %" PRIuFAST8 "\n"
+		       " Line base:                  %" PRIdFAST8 "\n"
+		       " Line range:                 %" PRIuFAST8 "\n"
+		       " Opcode base:                %" PRIuFAST8 "\n"
+		       "\n"
+		       "Opcodes:\n"),
+	      (uint64_t) unit_length, version, (uint64_t) header_length,
+	      minimum_instr_len, max_ops_per_instr,
+	      "is_stmt", default_is_stmt, line_base,
+	      line_range, opcode_base);
+
+      if (unlikely (linep + opcode_base - 1 >= lineendp))
+	{
+	invalid_unit:
+	  error (0, 0,
+		 gettext ("invalid data at offset %tu in section [%zu] '%s'"),
+		 linep - (const unsigned char *) data->d_buf,
+		 elf_ndxscn (scn), section_name (ebl, ehdr, shdr));
+	  linep = lineendp;
+	  continue;
+	}
+      int opcode_base_l10 = 1;
+      unsigned int tmp = opcode_base;
+      while (tmp > 10)
+	{
+	  tmp /= 10;
+	  ++opcode_base_l10;
+	}
+      const uint8_t *standard_opcode_lengths = linep - 1;
+      for (uint_fast8_t cnt = 1; cnt < opcode_base; ++cnt)
+	printf (ngettext ("  [%*" PRIuFAST8 "]  %hhu argument\n",
+			  "  [%*" PRIuFAST8 "]  %hhu arguments\n",
+			  (int) linep[cnt - 1]),
+		opcode_base_l10, cnt, linep[cnt - 1]);
+      linep += opcode_base - 1;
+      if (unlikely (linep >= lineendp))
+	goto invalid_unit;
+
+      puts (gettext ("\nDirectory table:"));
+      while (*linep != 0)
+	{
+	  unsigned char *endp = memchr (linep, '\0', lineendp - linep);
+	  if (unlikely (endp == NULL))
+	    goto invalid_unit;
+
+	  printf (" %s\n", (char *) linep);
+
+	  linep = endp + 1;
+	}
+      /* Skip the final NUL byte.  */
+      ++linep;
+
+      if (unlikely (linep >= lineendp))
+	goto invalid_unit;
+      puts (gettext ("\nFile name table:\n"
+		     " Entry Dir   Time      Size      Name"));
+      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 (unlikely (endp == NULL))
+	    goto invalid_unit;
+	  linep = endp + 1;
+
+	  /* Then the index.  */
+	  unsigned int diridx;
+	  if (lineendp - linep < 1)
+	    goto invalid_unit;
+	  get_uleb128 (diridx, linep, lineendp);
+
+	  /* Next comes the modification time.  */
+	  unsigned int mtime;
+	  if (lineendp - linep < 1)
+	    goto invalid_unit;
+	  get_uleb128 (mtime, linep, lineendp);
+
+	  /* Finally the length of the file.  */
+	  unsigned int fsize;
+	  if (lineendp - linep < 1)
+	    goto invalid_unit;
+	  get_uleb128 (fsize, linep, lineendp);
+
+	  printf (" %-5u %-5u %-9u %-9u %s\n",
+		  cnt, diridx, mtime, fsize, fname);
+	}
+      /* Skip the final NUL byte.  */
+      ++linep;
+
+      puts (gettext ("\nLine number statements:"));
+      Dwarf_Word address = 0;
+      unsigned int op_index = 0;
+      size_t line = 1;
+      uint_fast8_t is_stmt = default_is_stmt;
+
+      /* Default address value, in case we do not find the CU.  */
+      size_t address_size
+	= elf_getident (ebl->elf, NULL)[EI_CLASS] == ELFCLASS32 ? 4 : 8;
+
+      /* Determine the CU this block is for.  */
+      Dwarf_Off cuoffset;
+      Dwarf_Off ncuoffset = 0;
+      size_t hsize;
+      while (dwarf_nextcu (dbg, cuoffset = ncuoffset, &ncuoffset, &hsize,
+			   NULL, NULL, NULL) == 0)
+	{
+	  Dwarf_Die cudie;
+	  if (dwarf_offdie (dbg, cuoffset + hsize, &cudie) == NULL)
+	    continue;
+	  Dwarf_Attribute stmt_list;
+	  if (dwarf_attr (&cudie, DW_AT_stmt_list, &stmt_list) == NULL)
+	    continue;
+	  Dwarf_Word lineoff;
+	  if (dwarf_formudata (&stmt_list, &lineoff) != 0)
+	    continue;
+	  if (lineoff == start_offset)
+	    {
+	      /* Found the CU.  */
+	      address_size = cudie.cu->address_size;
+	      break;
+	    }
+	}
+
+      /* Apply the "operation advance" from a special opcode
+	 or DW_LNS_advance_pc (as per DWARF4 6.2.5.1).  */
+      unsigned int op_addr_advance;
+      bool show_op_index;
+      inline void advance_pc (unsigned int op_advance)
+      {
+	op_addr_advance = minimum_instr_len * ((op_index + op_advance)
+					       / max_ops_per_instr);
+	address += op_advance;
+	show_op_index = (op_index > 0 ||
+			 (op_index + op_advance) % max_ops_per_instr > 0);
+	op_index = (op_index + op_advance) % max_ops_per_instr;
+      }
+
+      if (max_ops_per_instr == 0)
+	{
+	  error (0, 0,
+		 gettext ("invalid maximum operations per instruction is zero"));
+	  linep = lineendp;
+	  continue;
+	}
+
+      while (linep < lineendp)
+	{
+	  size_t offset = linep - (const unsigned char *) data->d_buf;
+	  unsigned int u128;
+	  int s128;
+
+	  /* Read the opcode.  */
+	  unsigned int opcode = *linep++;
+
+	  printf (" [%6" PRIx64 "]", (uint64_t)offset);
+	  /* Is this a special opcode?  */
+	  if (likely (opcode >= opcode_base))
+	    {
+	      if (unlikely (line_range == 0))
+		goto invalid_unit;
+
+	      /* Yes.  Handling this is quite easy since the opcode value
+		 is computed with
+
+		 opcode = (desired line increment - line_base)
+			   + (line_range * address advance) + opcode_base
+	      */
+	      int line_increment = (line_base
+				    + (opcode - opcode_base) % line_range);
+
+	      /* Perform the increments.  */
+	      line += line_increment;
+	      advance_pc ((opcode - opcode_base) / line_range);
+
+	      char *a = format_dwarf_addr (dwflmod, 0, address, address);
+	      if (show_op_index)
+		printf (gettext ("\
+ special opcode %u: address+%u = %s, op_index = %u, line%+d = %zu\n"),
+			opcode, op_addr_advance, a, op_index,
+			line_increment, line);
+	      else
+		printf (gettext ("\
+ special opcode %u: address+%u = %s, line%+d = %zu\n"),
+			opcode, op_addr_advance, a, line_increment, line);
+	      free (a);
+	    }
+	  else if (opcode == 0)
+	    {
+	      /* This an extended opcode.  */
+	      if (unlikely (linep + 2 > lineendp))
+		goto invalid_unit;
+
+	      /* The length.  */
+	      unsigned int len = *linep++;
+
+	      if (unlikely (linep + len > lineendp))
+		goto invalid_unit;
+
+	      /* The sub-opcode.  */
+	      opcode = *linep++;
+
+	      printf (gettext (" extended opcode %u: "), opcode);
+
+	      switch (opcode)
+		{
+		case DW_LNE_end_sequence:
+		  puts (gettext (" end of sequence"));
+
+		  /* Reset the registers we care about.  */
+		  address = 0;
+		  op_index = 0;
+		  line = 1;
+		  is_stmt = default_is_stmt;
+		  break;
+
+		case DW_LNE_set_address:
+		  op_index = 0;
+		  if (unlikely ((size_t) (lineendp - linep) < address_size))
+		    goto invalid_unit;
+		  if (address_size == 4)
+		    address = read_4ubyte_unaligned_inc (dbg, linep);
+		  else
+		    address = read_8ubyte_unaligned_inc (dbg, linep);
+		  {
+		    char *a = format_dwarf_addr (dwflmod, 0, address, address);
+		    printf (gettext (" set address to %s\n"), a);
+		    free (a);
+		  }
+		  break;
+
+		case DW_LNE_define_file:
+		  {
+		    char *fname = (char *) linep;
+		    unsigned char *endp = memchr (linep, '\0',
+						  lineendp - linep);
+		    if (unlikely (endp == NULL))
+		      goto invalid_unit;
+		    linep = endp + 1;
+
+		    unsigned int diridx;
+		    if (lineendp - linep < 1)
+		      goto invalid_unit;
+		    get_uleb128 (diridx, linep, lineendp);
+		    Dwarf_Word mtime;
+		    if (lineendp - linep < 1)
+		      goto invalid_unit;
+		    get_uleb128 (mtime, linep, lineendp);
+		    Dwarf_Word filelength;
+		    if (lineendp - linep < 1)
+		      goto invalid_unit;
+		    get_uleb128 (filelength, linep, lineendp);
+
+		    printf (gettext ("\
+ define new file: dir=%u, mtime=%" PRIu64 ", length=%" PRIu64 ", name=%s\n"),
+			    diridx, (uint64_t) mtime, (uint64_t) filelength,
+			    fname);
+		  }
+		  break;
+
+		case DW_LNE_set_discriminator:
+		  /* Takes one ULEB128 parameter, the discriminator.  */
+		  if (unlikely (standard_opcode_lengths[opcode] != 1))
+		    goto invalid_unit;
+
+		  get_uleb128 (u128, linep, lineendp);
+		  printf (gettext (" set discriminator to %u\n"), u128);
+		  break;
+
+		default:
+		  /* Unknown, ignore it.  */
+		  puts (gettext (" unknown opcode"));
+		  linep += len - 1;
+		  break;
+		}
+	    }
+	  else if (opcode <= DW_LNS_set_isa)
+	    {
+	      /* This is a known standard opcode.  */
+	      switch (opcode)
+		{
+		case DW_LNS_copy:
+		  /* Takes no argument.  */
+		  puts (gettext (" copy"));
+		  break;
+
+		case DW_LNS_advance_pc:
+		  /* Takes one uleb128 parameter which is added to the
+		     address.  */
+		  get_uleb128 (u128, linep, lineendp);
+		  advance_pc (u128);
+		  {
+		    char *a = format_dwarf_addr (dwflmod, 0, address, address);
+		    if (show_op_index)
+		      printf (gettext ("\
+ advance address by %u to %s, op_index to %u\n"),
+			      op_addr_advance, a, op_index);
+		    else
+		      printf (gettext (" advance address by %u to %s\n"),
+			      op_addr_advance, a);
+		    free (a);
+		  }
+		  break;
+
+		case DW_LNS_advance_line:
+		  /* Takes one sleb128 parameter which is added to the
+		     line.  */
+		  get_sleb128 (s128, linep, lineendp);
+		  line += s128;
+		  printf (gettext ("\
+ advance line by constant %d to %" PRId64 "\n"),
+			  s128, (int64_t) line);
+		  break;
+
+		case DW_LNS_set_file:
+		  /* Takes one uleb128 parameter which is stored in file.  */
+		  get_uleb128 (u128, linep, lineendp);
+		  printf (gettext (" set file to %" PRIu64 "\n"),
+			  (uint64_t) u128);
+		  break;
+
+		case DW_LNS_set_column:
+		  /* Takes one uleb128 parameter which is stored in column.  */
+		  if (unlikely (standard_opcode_lengths[opcode] != 1))
+		    goto invalid_unit;
+
+		  get_uleb128 (u128, linep, lineendp);
+		  printf (gettext (" set column to %" PRIu64 "\n"),
+			  (uint64_t) u128);
+		  break;
+
+		case DW_LNS_negate_stmt:
+		  /* Takes no argument.  */
+		  is_stmt = 1 - is_stmt;
+		  printf (gettext (" set '%s' to %" PRIuFAST8 "\n"),
+			  "is_stmt", is_stmt);
+		  break;
+
+		case DW_LNS_set_basic_block:
+		  /* Takes no argument.  */
+		  puts (gettext (" set basic block flag"));
+		  break;
+
+		case DW_LNS_const_add_pc:
+		  /* Takes no argument.  */
+
+		  if (unlikely (line_range == 0))
+		    goto invalid_unit;
+
+		  advance_pc ((255 - opcode_base) / line_range);
+		  {
+		    char *a = format_dwarf_addr (dwflmod, 0, address, address);
+		    if (show_op_index)
+		      printf (gettext ("\
+ advance address by constant %u to %s, op_index to %u\n"),
+			      op_addr_advance, a, op_index);
+		    else
+		      printf (gettext ("\
+ advance address by constant %u to %s\n"),
+			      op_addr_advance, a);
+		    free (a);
+		  }
+		  break;
+
+		case DW_LNS_fixed_advance_pc:
+		  /* Takes one 16 bit parameter which is added to the
+		     address.  */
+		  if (unlikely (standard_opcode_lengths[opcode] != 1))
+		    goto invalid_unit;
+
+		  u128 = read_2ubyte_unaligned_inc (dbg, linep);
+		  address += u128;
+		  op_index = 0;
+		  {
+		    char *a = format_dwarf_addr (dwflmod, 0, address, address);
+		    printf (gettext ("\
+ advance address by fixed value %u to %s\n"),
+			    u128, a);
+		    free (a);
+		  }
+		  break;
+
+		case DW_LNS_set_prologue_end:
+		  /* Takes no argument.  */
+		  puts (gettext (" set prologue end flag"));
+		  break;
+
+		case DW_LNS_set_epilogue_begin:
+		  /* Takes no argument.  */
+		  puts (gettext (" set epilogue begin flag"));
+		  break;
+
+		case DW_LNS_set_isa:
+		  /* Takes one uleb128 parameter which is stored in isa.  */
+		  if (unlikely (standard_opcode_lengths[opcode] != 1))
+		    goto invalid_unit;
+
+		  get_uleb128 (u128, linep, lineendp);
+		  printf (gettext (" set isa to %u\n"), u128);
+		  break;
+		}
+	    }
+	  else
+	    {
+	      /* This is a new opcode the generator but not we know about.
+		 Read the parameters associated with it but then discard
+		 everything.  Read all the parameters for this opcode.  */
+	      printf (ngettext (" unknown opcode with %" PRIu8 " parameter:",
+				" unknown opcode with %" PRIu8 " parameters:",
+				standard_opcode_lengths[opcode]),
+		      standard_opcode_lengths[opcode]);
+	      for (int n = standard_opcode_lengths[opcode]; n > 0; --n)
+		{
+		  get_uleb128 (u128, linep, lineendp);
+		  if (n != standard_opcode_lengths[opcode])
+		    putc_unlocked (',', stdout);
+		  printf (" %u", u128);
+		}
+
+	      /* Next round, ignore this opcode.  */
+	      continue;
+	    }
+	}
+    }
+
+  /* There must only be one data block.  */
+  assert (elf_getdata (scn, data) == NULL);
+}
+
+
+static void
+print_debug_loc_section (Dwfl_Module *dwflmod,
+			 Ebl *ebl, GElf_Ehdr *ehdr,
+			 Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  Elf_Data *data = dbg->sectiondata[IDX_debug_loc];
+
+  if (unlikely (data == NULL))
+    {
+      error (0, 0, gettext ("cannot get .debug_loc content: %s"),
+	     elf_errmsg (-1));
+      return;
+    }
+
+  printf (gettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset);
+
+  sort_listptr (&known_loclistptr, "loclistptr");
+  size_t listptr_idx = 0;
+
+  uint_fast8_t address_size = ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8;
+  uint_fast8_t offset_size = 4;
+
+  bool first = true;
+  Dwarf_Addr base = 0;
+  unsigned char *readp = data->d_buf;
+  unsigned char *const endp = (unsigned char *) data->d_buf + data->d_size;
+  Dwarf_CU *last_cu = NULL;
+  while (readp < endp)
+    {
+      ptrdiff_t offset = readp - (unsigned char *) data->d_buf;
+      Dwarf_CU *cu = last_cu;
+
+      if (first && skip_listptr_hole (&known_loclistptr, &listptr_idx,
+				      &address_size, &offset_size, &base,
+				      &cu, offset, &readp, endp))
+	continue;
+
+      if (last_cu != cu)
+       {
+	char *basestr = format_dwarf_addr (dwflmod, address_size,
+					   base, base);
+	Dwarf_Die cudie;
+	if (dwarf_cu_die (cu, &cudie,
+			  NULL, NULL, NULL, NULL,
+			  NULL, NULL) == NULL)
+	  printf (gettext ("\n Unknown CU base: %s\n"), basestr);
+	else
+	  printf (gettext ("\n CU [%6" PRIx64 "] base: %s\n"),
+		  dwarf_dieoffset (&cudie), basestr);
+	free (basestr);
+       }
+      last_cu = cu;
+
+      if (unlikely (data->d_size - offset < (size_t) address_size * 2))
+	{
+	  printf (gettext (" [%6tx]  <INVALID DATA>\n"), offset);
+	  break;
+	}
+
+      Dwarf_Addr begin;
+      Dwarf_Addr end;
+      if (address_size == 8)
+	{
+	  begin = read_8ubyte_unaligned_inc (dbg, readp);
+	  end = read_8ubyte_unaligned_inc (dbg, readp);
+	}
+      else
+	{
+	  begin = read_4ubyte_unaligned_inc (dbg, readp);
+	  end = read_4ubyte_unaligned_inc (dbg, readp);
+	  if (begin == (Dwarf_Addr) (uint32_t) -1)
+	    begin = (Dwarf_Addr) -1l;
+	}
+
+      if (begin == (Dwarf_Addr) -1l) /* Base address entry.  */
+	{
+	  char *b = format_dwarf_addr (dwflmod, address_size, end, end);
+	  printf (gettext (" [%6tx] base address\n          %s\n"), offset, b);
+	  free (b);
+	  base = end;
+	}
+      else if (begin == 0 && end == 0) /* End of list entry.  */
+	{
+	  if (first)
+	    printf (gettext (" [%6tx] empty list\n"), offset);
+	  first = true;
+	}
+      else
+	{
+	  /* We have a location expression entry.  */
+	  uint_fast16_t len = read_2ubyte_unaligned_inc (dbg, readp);
+
+	  if (first)		/* First entry in a list.  */
+	    printf (" [%6tx] ", offset);
+	  else
+	    printf ("          ");
+
+	  printf ("range %" PRIx64 ", %" PRIx64 "\n", begin, end);
+	  if (! print_unresolved_addresses)
+	    {
+	      char *b = format_dwarf_addr (dwflmod, address_size, base + begin,
+					   base + begin);
+	      char *e = format_dwarf_addr (dwflmod, address_size,
+					   base + end - 1, base + end);
+	      printf ("          %s..\n", b);
+	      printf ("          %s\n", e);
+	      free (b);
+	      free (e);
+	    }
+
+	  if (endp - readp <= (ptrdiff_t) len)
+	    {
+	      fputs (gettext ("   <INVALID DATA>\n"), stdout);
+	      break;
+	    }
+
+	  print_ops (dwflmod, dbg, 11, 11,
+		     cu != NULL ? cu->version : 3,
+		     address_size, offset_size, cu, len, readp);
+
+	  first = false;
+	  readp += len;
+	}
+    }
+}
+
+struct mac_culist
+{
+  Dwarf_Die die;
+  Dwarf_Off offset;
+  Dwarf_Files *files;
+  struct mac_culist *next;
+};
+
+
+static int
+mac_compare (const void *p1, const void *p2)
+{
+  struct mac_culist *m1 = (struct mac_culist *) p1;
+  struct mac_culist *m2 = (struct mac_culist *) p2;
+
+  if (m1->offset < m2->offset)
+    return -1;
+  if (m1->offset > m2->offset)
+    return 1;
+  return 0;
+}
+
+
+static void
+print_debug_macinfo_section (Dwfl_Module *dwflmod __attribute__ ((unused)),
+			     Ebl *ebl, GElf_Ehdr *ehdr,
+			     Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  printf (gettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset);
+  putc_unlocked ('\n', stdout);
+
+  /* There is no function in libdw to iterate over the raw content of
+     the section but it is easy enough to do.  */
+  Elf_Data *data = dbg->sectiondata[IDX_debug_macinfo];
+  if (unlikely (data == NULL || data->d_buf == NULL))
+    {
+      error (0, 0, gettext ("cannot get macro information section data: %s"),
+	     elf_errmsg (-1));
+      return;
+    }
+
+  /* Get the source file information for all CUs.  */
+  Dwarf_Off offset;
+  Dwarf_Off ncu = 0;
+  size_t hsize;
+  struct mac_culist *culist = NULL;
+  size_t nculist = 0;
+  while (dwarf_nextcu (dbg, offset = ncu, &ncu, &hsize, NULL, NULL, NULL) == 0)
+    {
+      Dwarf_Die cudie;
+      if (dwarf_offdie (dbg, offset + hsize, &cudie) == NULL)
+	continue;
+
+      Dwarf_Attribute attr;
+      if (dwarf_attr (&cudie, DW_AT_macro_info, &attr) == NULL)
+	continue;
+
+      Dwarf_Word macoff;
+      if (dwarf_formudata (&attr, &macoff) != 0)
+	continue;
+
+      struct mac_culist *newp = (struct mac_culist *) alloca (sizeof (*newp));
+      newp->die = cudie;
+      newp->offset = macoff;
+      newp->files = NULL;
+      newp->next = culist;
+      culist = newp;
+      ++nculist;
+    }
+
+  /* Convert the list into an array for easier consumption.  */
+  struct mac_culist *cus = (struct mac_culist *) alloca ((nculist + 1)
+							 * sizeof (*cus));
+  /* Add sentinel.  */
+  cus[nculist].offset = data->d_size;
+  cus[nculist].files = (Dwarf_Files *) -1l;
+  if (nculist > 0)
+    {
+      for (size_t cnt = nculist - 1; culist != NULL; --cnt)
+	{
+	  assert (cnt < nculist);
+	  cus[cnt] = *culist;
+	  culist = culist->next;
+	}
+
+      /* Sort the array according to the offset in the .debug_macinfo
+	 section.  Note we keep the sentinel at the end.  */
+      qsort (cus, nculist, sizeof (*cus), mac_compare);
+    }
+
+  const unsigned char *readp = (const unsigned char *) data->d_buf;
+  const unsigned char *readendp = readp + data->d_size;
+  int level = 1;
+
+  while (readp < readendp)
+    {
+      unsigned int opcode = *readp++;
+      unsigned int u128;
+      unsigned int u128_2;
+      const unsigned char *endp;
+
+      switch (opcode)
+	{
+	case DW_MACINFO_define:
+	case DW_MACINFO_undef:
+	case DW_MACINFO_vendor_ext:
+	  /*  For the first two opcodes the parameters are
+		line, string
+	      For the latter
+		number, string.
+	      We can treat these cases together.  */
+	  get_uleb128 (u128, readp, readendp);
+
+	  endp = memchr (readp, '\0', readendp - readp);
+	  if (unlikely (endp == NULL))
+	    {
+	      printf (gettext ("\
+%*s*** non-terminated string at end of section"),
+		      level, "");
+	      return;
+	    }
+
+	  if (opcode == DW_MACINFO_define)
+	    printf ("%*s#define %s, line %u\n",
+		    level, "", (char *) readp, u128);
+	  else if (opcode == DW_MACINFO_undef)
+	    printf ("%*s#undef %s, line %u\n",
+		    level, "", (char *) readp, u128);
+	  else
+	    printf (" #vendor-ext %s, number %u\n", (char *) readp, u128);
+
+	  readp = endp + 1;
+	  break;
+
+	case DW_MACINFO_start_file:
+	  /* The two parameters are line and file index, in this order.  */
+	  get_uleb128 (u128, readp, readendp);
+	  if (readendp - readp < 1)
+	    {
+	      printf (gettext ("\
+%*s*** missing DW_MACINFO_start_file argument at end of section"),
+		      level, "");
+	      return;
+	    }
+	  get_uleb128 (u128_2, readp, readendp);
+
+	  /* Find the CU DIE for this file.  */
+	  size_t macoff = readp - (const unsigned char *) data->d_buf;
+	  const char *fname = "???";
+	  if (macoff >= cus[0].offset)
+	    {
+	      while (macoff >= cus[1].offset && cus[1].offset != data->d_size)
+		++cus;
+
+	      if (cus[0].files == NULL
+		&& dwarf_getsrcfiles (&cus[0].die, &cus[0].files, NULL) != 0)
+		cus[0].files = (Dwarf_Files *) -1l;
+
+	      if (cus[0].files != (Dwarf_Files *) -1l)
+		fname = (dwarf_filesrc (cus[0].files, u128_2, NULL, NULL)
+			 ?: "???");
+	    }
+
+	  printf ("%*sstart_file %u, [%u] %s\n",
+		  level, "", u128, u128_2, fname);
+	  ++level;
+	  break;
+
+	case DW_MACINFO_end_file:
+	  --level;
+	  printf ("%*send_file\n", level, "");
+	  /* Nothing more to do.  */
+	  break;
+
+	default:
+	  // XXX gcc seems to generate files with a trailing zero.
+	  if (unlikely (opcode != 0 || readp != readendp))
+	    printf ("%*s*** invalid opcode %u\n", level, "", opcode);
+	  break;
+	}
+    }
+}
+
+
+static void
+print_debug_macro_section (Dwfl_Module *dwflmod __attribute__ ((unused)),
+			   Ebl *ebl, GElf_Ehdr *ehdr,
+			   Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  printf (gettext ("\
+\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset);
+  putc_unlocked ('\n', stdout);
+
+  Elf_Data *data = dbg->sectiondata[IDX_debug_macro];
+  if (unlikely (data == NULL || data->d_buf == NULL))
+    {
+      error (0, 0, gettext ("cannot get macro information section data: %s"),
+	     elf_errmsg (-1));
+      return;
+    }
+
+  /* Get the source file information for all CUs.  Uses same
+     datastructure as macinfo.  But uses offset field to directly
+     match .debug_line offset.  And just stored in a list.  */
+  Dwarf_Off offset;
+  Dwarf_Off ncu = 0;
+  size_t hsize;
+  struct mac_culist *culist = NULL;
+  size_t nculist = 0;
+  while (dwarf_nextcu (dbg, offset = ncu, &ncu, &hsize, NULL, NULL, NULL) == 0)
+    {
+      Dwarf_Die cudie;
+      if (dwarf_offdie (dbg, offset + hsize, &cudie) == NULL)
+	continue;
+
+      Dwarf_Attribute attr;
+      if (dwarf_attr (&cudie, DW_AT_stmt_list, &attr) == NULL)
+	continue;
+
+      Dwarf_Word lineoff;
+      if (dwarf_formudata (&attr, &lineoff) != 0)
+	continue;
+
+      struct mac_culist *newp = (struct mac_culist *) alloca (sizeof (*newp));
+      newp->die = cudie;
+      newp->offset = lineoff;
+      newp->files = NULL;
+      newp->next = culist;
+      culist = newp;
+      ++nculist;
+    }
+
+  const unsigned char *readp = (const unsigned char *) data->d_buf;
+  const unsigned char *readendp = readp + data->d_size;
+
+  while (readp < readendp)
+    {
+      printf (gettext (" Offset:             0x%" PRIx64 "\n"),
+	      (uint64_t) (readp - (const unsigned char *) data->d_buf));
+
+      // Header, 2 byte version, 1 byte flag, optional .debug_line offset,
+      // optional vendor extension macro entry table.
+      if (readp + 2 > readendp)
+	{
+	invalid_data:
+	  error (0, 0, gettext ("invalid data"));
+	  return;
+	}
+      const uint16_t vers = read_2ubyte_unaligned_inc (dbg, readp);
+      printf (gettext (" Version:            %" PRIu16 "\n"), vers);
+
+      // Version 4 is the GNU extension for DWARF4.  DWARF5 will use version
+      // 5 when it gets standardized.
+      if (vers != 4 && vers != 5)
+	{
+	  printf (gettext ("  unknown version, cannot parse section\n"));
+	  return;
+	}
+
+      if (readp + 1 > readendp)
+	goto invalid_data;
+      const unsigned char flag = *readp++;
+      printf (gettext (" Flag:               0x%" PRIx8 "\n"), flag);
+
+      unsigned int offset_len = (flag & 0x01) ? 8 : 4;
+      printf (gettext (" Offset length:      %" PRIu8 "\n"), offset_len);
+      Dwarf_Off line_offset = -1;
+      if (flag & 0x02)
+	{
+	  if (offset_len == 8)
+	    line_offset = read_8ubyte_unaligned_inc (dbg, readp);
+	  else
+	    line_offset = read_4ubyte_unaligned_inc (dbg, readp);
+	  printf (gettext (" .debug_line offset: 0x%" PRIx64 "\n"),
+		  line_offset);
+	}
+
+      const unsigned char *vendor[DW_MACRO_hi_user - DW_MACRO_lo_user];
+      memset (vendor, 0, sizeof vendor);
+      if (flag & 0x04)
+	{
+	  // 1 byte length, for each item, 1 byte opcode, uleb128 number
+	  // of arguments, for each argument 1 byte form code.
+	  if (readp + 1 > readendp)
+	    goto invalid_data;
+	  unsigned int tlen = *readp++;
+	  printf (gettext ("  extension opcode table, %" PRIu8 " items:\n"),
+		  tlen);
+	  for (unsigned int i = 0; i < tlen; i++)
+	    {
+	      if (readp + 1 > readendp)
+		goto invalid_data;
+	      unsigned int opcode = *readp++;
+	      printf (gettext ("    [%" PRIx8 "]"), opcode);
+	      if (opcode < DW_MACRO_lo_user
+		  || opcode > DW_MACRO_hi_user)
+		goto invalid_data;
+	      // Record the start of description for this vendor opcode.
+	      // uleb128 nr args, 1 byte per arg form.
+	      vendor[opcode - DW_MACRO_lo_user] = readp;
+	      if (readp + 1 > readendp)
+		goto invalid_data;
+	      unsigned int args = *readp++;
+	      if (args > 0)
+		{
+		  printf (gettext (" %" PRIu8 " arguments:"), args);
+		  while (args > 0)
+		    {
+		      if (readp + 1 > readendp)
+			goto invalid_data;
+		      unsigned int form = *readp++;
+		      printf (" %s", dwarf_form_name (form));
+		      if (form != DW_FORM_data1
+			  && form != DW_FORM_data2
+			  && form != DW_FORM_data4
+			  && form != DW_FORM_data8
+			  && form != DW_FORM_sdata
+			  && form != DW_FORM_udata
+			  && form != DW_FORM_block
+			  && form != DW_FORM_block1
+			  && form != DW_FORM_block2
+			  && form != DW_FORM_block4
+			  && form != DW_FORM_flag
+			  && form != DW_FORM_string
+			  && form != DW_FORM_strp
+			  && form != DW_FORM_sec_offset)
+			goto invalid_data;
+		      args--;
+		      if (args > 0)
+			putchar_unlocked (',');
+		    }
+		}
+	      else
+		printf (gettext (" no arguments."));
+	      putchar_unlocked ('\n');
+	    }
+	}
+      putchar_unlocked ('\n');
+
+      int level = 1;
+      if (readp + 1 > readendp)
+	goto invalid_data;
+      unsigned int opcode = *readp++;
+      while (opcode != 0)
+	{
+	  unsigned int u128;
+	  unsigned int u128_2;
+	  const unsigned char *endp;
+	  uint64_t off;
+
+          switch (opcode)
+            {
+            case DW_MACRO_start_file:
+	      get_uleb128 (u128, readp, readendp);
+	      if (readp >= readendp)
+		goto invalid_data;
+	      get_uleb128 (u128_2, readp, readendp);
+
+	      /* Find the CU DIE that matches this line offset.  */
+	      const char *fname = "???";
+	      if (line_offset != (Dwarf_Off) -1)
+		{
+		  struct mac_culist *cu = culist;
+		  while (cu != NULL && line_offset != cu->offset)
+		    cu = cu->next;
+		  if (cu != NULL)
+		    {
+		      if (cu->files == NULL
+			  && dwarf_getsrcfiles (&cu->die, &cu->files,
+						NULL) != 0)
+			cu->files = (Dwarf_Files *) -1l;
+
+		      if (cu->files != (Dwarf_Files *) -1l)
+			fname = (dwarf_filesrc (cu->files, u128_2,
+						NULL, NULL) ?: "???");
+		    }
+		}
+	      printf ("%*sstart_file %u, [%u] %s\n",
+		      level, "", u128, u128_2, fname);
+	      ++level;
+	      break;
+
+	    case DW_MACRO_end_file:
+	      --level;
+	      printf ("%*send_file\n", level, "");
+	      break;
+
+	    case DW_MACRO_define:
+	      get_uleb128 (u128, readp, readendp);
+	      endp = memchr (readp, '\0', readendp - readp);
+	      if (endp == NULL)
+		goto invalid_data;
+	      printf ("%*s#define %s, line %u\n",
+		      level, "", readp, u128);
+	      readp = endp + 1;
+	      break;
+
+	    case DW_MACRO_undef:
+	      get_uleb128 (u128, readp, readendp);
+	      endp = memchr (readp, '\0', readendp - readp);
+	      if (endp == NULL)
+		goto invalid_data;
+	      printf ("%*s#undef %s, line %u\n",
+		      level, "", readp, u128);
+	      readp = endp + 1;
+	      break;
+
+	    case DW_MACRO_define_strp:
+	      get_uleb128 (u128, readp, readendp);
+	      if (readp + offset_len > readendp)
+		goto invalid_data;
+	      if (offset_len == 8)
+		off = read_8ubyte_unaligned_inc (dbg, readp);
+	      else
+		off = read_4ubyte_unaligned_inc (dbg, readp);
+	      printf ("%*s#define %s, line %u (indirect)\n",
+		      level, "", dwarf_getstring (dbg, off, NULL), u128);
+	      break;
+
+	    case DW_MACRO_undef_strp:
+	      get_uleb128 (u128, readp, readendp);
+	      if (readp + offset_len > readendp)
+		goto invalid_data;
+	      if (offset_len == 8)
+		off = read_8ubyte_unaligned_inc (dbg, readp);
+	      else
+		off = read_4ubyte_unaligned_inc (dbg, readp);
+	      printf ("%*s#undef %s, line %u (indirect)\n",
+		      level, "", dwarf_getstring (dbg, off, NULL), u128);
+	      break;
+
+	    case DW_MACRO_import:
+	      if (readp + offset_len > readendp)
+		goto invalid_data;
+	      if (offset_len == 8)
+		off = read_8ubyte_unaligned_inc (dbg, readp);
+	      else
+		off = read_4ubyte_unaligned_inc (dbg, readp);
+	      printf ("%*s#include offset 0x%" PRIx64 "\n",
+		      level, "", off);
+	      break;
+
+	    case DW_MACRO_define_sup:
+	      get_uleb128 (u128, readp, readendp);
+	      if (readp + offset_len > readendp)
+		goto invalid_data;
+	      if (offset_len == 8)
+		off = read_8ubyte_unaligned_inc (dbg, readp);
+	      else
+		off = read_4ubyte_unaligned_inc (dbg, readp);
+	      // Needs support for reading from supplementary object file.
+	      printf ("%*s#define <str-at-0x%" PRIx64 ">, line %u (sup)\n",
+		      level, "", off, u128);
+	      break;
+
+	    case DW_MACRO_undef_sup:
+	      get_uleb128 (u128, readp, readendp);
+	      if (readp + offset_len > readendp)
+		goto invalid_data;
+	      if (offset_len == 8)
+		off = read_8ubyte_unaligned_inc (dbg, readp);
+	      else
+		off = read_4ubyte_unaligned_inc (dbg, readp);
+	      // Needs support for reading from supplementary object file.
+	      printf ("%*s#undef <str-at-0x%" PRIx64 ">, line %u (sup)\n",
+		      level, "", off, u128);
+	      break;
+
+	    case DW_MACRO_import_sup:
+	      if (readp + offset_len > readendp)
+		goto invalid_data;
+	      if (offset_len == 8)
+		off = read_8ubyte_unaligned_inc (dbg, readp);
+	      else
+		off = read_4ubyte_unaligned_inc (dbg, readp);
+	      printf ("%*s#include offset 0x%" PRIx64 " (sup)\n",
+		      level, "", off);
+	      break;
+
+	    case DW_MACRO_define_strx:
+	      get_uleb128 (u128, readp, readendp);
+	      if (readp + offset_len > readendp)
+		goto invalid_data;
+	      if (offset_len == 8)
+		off = read_8ubyte_unaligned_inc (dbg, readp);
+	      else
+		off = read_4ubyte_unaligned_inc (dbg, readp);
+	      // Needs support for reading indirect string offset table
+	      printf ("%*s#define <str-at-0x%" PRIx64 ">, line %u (strx)\n",
+		      level, "", off, u128);
+	      break;
+
+	    case DW_MACRO_undef_strx:
+	      get_uleb128 (u128, readp, readendp);
+	      if (readp + offset_len > readendp)
+		goto invalid_data;
+	      if (offset_len == 8)
+		off = read_8ubyte_unaligned_inc (dbg, readp);
+	      else
+		off = read_4ubyte_unaligned_inc (dbg, readp);
+	      // Needs support for reading indirect string offset table.
+	      printf ("%*s#undef <str-at-0x%" PRIx64 ">, line %u (strx)\n",
+		      level, "", off, u128);
+	      break;
+
+	    default:
+	      printf ("%*svendor opcode 0x%" PRIx8, level, "", opcode);
+	      if (opcode < DW_MACRO_lo_user
+		  || opcode > DW_MACRO_lo_user
+		  || vendor[opcode - DW_MACRO_lo_user] == NULL)
+		goto invalid_data;
+
+	      const unsigned char *op_desc;
+	      op_desc = vendor[opcode - DW_MACRO_lo_user];
+
+	      // Just skip the arguments, we cannot really interpret them,
+	      // but print as much as we can.
+	      unsigned int args = *op_desc++;
+	      while (args > 0)
+		{
+		  unsigned int form = *op_desc++;
+		  Dwarf_Word val;
+		  switch (form)
+		    {
+		    case DW_FORM_data1:
+		      if (readp + 1 > readendp)
+			goto invalid_data;
+		      val = *readp++;
+		      printf (" %" PRIx8, (unsigned int) val);
+		      break;
+
+		    case DW_FORM_data2:
+		      if (readp + 2 > readendp)
+			goto invalid_data;
+		      val = read_2ubyte_unaligned_inc (dbg, readp);
+		      printf(" %" PRIx16, (unsigned int) val);
+		      break;
+
+		    case DW_FORM_data4:
+		      if (readp + 4 > readendp)
+			goto invalid_data;
+		      val = read_4ubyte_unaligned_inc (dbg, readp);
+		      printf (" %" PRIx32, (unsigned int) val);
+		      break;
+
+		    case DW_FORM_data8:
+		      if (readp + 8 > readendp)
+			goto invalid_data;
+		      val = read_8ubyte_unaligned_inc (dbg, readp);
+		      printf (" %" PRIx64, val);
+		      break;
+
+		    case DW_FORM_sdata:
+		      get_sleb128 (val, readp, readendp);
+		      printf (" %" PRIx64, val);
+		      break;
+
+		    case DW_FORM_udata:
+		      get_uleb128 (val, readp, readendp);
+		      printf (" %" PRIx64, val);
+		      break;
+
+		    case DW_FORM_block:
+		      get_uleb128 (val, readp, readendp);
+		      printf (" block[%" PRIu64 "]", val);
+		      if (readp + val > readendp)
+			goto invalid_data;
+		      readp += val;
+		      break;
+
+		    case DW_FORM_block1:
+		      if (readp + 1 > readendp)
+			goto invalid_data;
+		      val = *readp++;
+		      printf (" block[%" PRIu64 "]", val);
+		      if (readp + val > readendp)
+			goto invalid_data;
+		      break;
+
+		    case DW_FORM_block2:
+		      if (readp + 2 > readendp)
+			goto invalid_data;
+		      val = read_2ubyte_unaligned_inc (dbg, readp);
+		      printf (" block[%" PRIu64 "]", val);
+		      if (readp + val > readendp)
+			goto invalid_data;
+		      break;
+
+		    case DW_FORM_block4:
+		      if (readp + 2 > readendp)
+			goto invalid_data;
+		      val =read_4ubyte_unaligned_inc (dbg, readp);
+		      printf (" block[%" PRIu64 "]", val);
+		      if (readp + val > readendp)
+			goto invalid_data;
+		      break;
+
+		    case DW_FORM_flag:
+		      if (readp + 1 > readendp)
+			goto invalid_data;
+		      val = *readp++;
+		      printf (" %s", val != 0 ? gettext ("yes") : gettext ("no"));
+		      break;
+
+		    case DW_FORM_string:
+		      endp = memchr (readp, '\0', readendp - readp);
+		      if (endp == NULL)
+			goto invalid_data;
+		      printf (" %s", readp);
+		      readp = endp + 1;
+		      break;
+
+		    case DW_FORM_strp:
+		      if (readp + offset_len > readendp)
+			goto invalid_data;
+		      if (offset_len == 8)
+			val = read_8ubyte_unaligned_inc (dbg, readp);
+		      else
+			val = read_4ubyte_unaligned_inc (dbg, readp);
+		      printf (" %s", dwarf_getstring (dbg, val, NULL));
+		      break;
+
+		    case DW_FORM_sec_offset:
+		      if (readp + offset_len > readendp)
+			goto invalid_data;
+		      if (offset_len == 8)
+			val = read_8ubyte_unaligned_inc (dbg, readp);
+		      else
+			val = read_4ubyte_unaligned_inc (dbg, readp);
+		      printf (" %" PRIx64, val);
+		      break;
+
+		      default:
+			error (0, 0, gettext ("vendor opcode not verified?"));
+			return;
+		    }
+
+		  args--;
+		  if (args > 0)
+		    putchar_unlocked (',');
+		}
+	      putchar_unlocked ('\n');
+	    }
+
+	  if (readp + 1 > readendp)
+	    goto invalid_data;
+	  opcode = *readp++;
+	  if (opcode == 0)
+	    putchar_unlocked ('\n');
+	}
+    }
+}
+
+
+/* Callback for printing global names.  */
+static int
+print_pubnames (Dwarf *dbg __attribute__ ((unused)), Dwarf_Global *global,
+		void *arg)
+{
+  int *np = (int *) arg;
+
+  printf (gettext (" [%5d] DIE offset: %6" PRId64
+		   ", CU DIE offset: %6" PRId64 ", name: %s\n"),
+	  (*np)++, global->die_offset, global->cu_offset, global->name);
+
+  return 0;
+}
+
+
+/* Print the known exported symbols in the DWARF section '.debug_pubnames'.  */
+static void
+print_debug_pubnames_section (Dwfl_Module *dwflmod __attribute__ ((unused)),
+			      Ebl *ebl, GElf_Ehdr *ehdr,
+			      Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  printf (gettext ("\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset);
+
+  int n = 0;
+  (void) dwarf_getpubnames (dbg, print_pubnames, &n, 0);
+}
+
+/* Print the content of the DWARF string section '.debug_str'.  */
+static void
+print_debug_str_section (Dwfl_Module *dwflmod __attribute__ ((unused)),
+			 Ebl *ebl, GElf_Ehdr *ehdr,
+			 Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  const size_t sh_size = (dbg->sectiondata[IDX_debug_str] ?
+			  dbg->sectiondata[IDX_debug_str]->d_size : 0);
+
+  /* Compute floor(log16(shdr->sh_size)).  */
+  GElf_Addr tmp = sh_size;
+  int digits = 1;
+  while (tmp >= 16)
+    {
+      ++digits;
+      tmp >>= 4;
+    }
+  digits = MAX (4, digits);
+
+  printf (gettext ("\nDWARF section [%2zu] '%s' at offset %#" PRIx64 ":\n"
+		   " %*s  String\n"),
+	  elf_ndxscn (scn),
+	  section_name (ebl, ehdr, shdr), (uint64_t) shdr->sh_offset,
+	  /* TRANS: the debugstr| prefix makes the string unique.  */
+	  digits + 2, sgettext ("debugstr|Offset"));
+
+  Dwarf_Off offset = 0;
+  while (offset < sh_size)
+    {
+      size_t len;
+      const char *str = dwarf_getstring (dbg, offset, &len);
+      if (unlikely (str == NULL))
+	{
+	  printf (gettext (" *** error while reading strings: %s\n"),
+		  dwarf_errmsg (-1));
+	  break;
+	}
+
+      printf (" [%*" PRIx64 "]  \"%s\"\n", digits, (uint64_t) offset, str);
+
+      offset += len + 1;
+    }
+}
+
+
+/* Print the content of the call frame search table section
+   '.eh_frame_hdr'.  */
+static void
+print_debug_frame_hdr_section (Dwfl_Module *dwflmod __attribute__ ((unused)),
+			       Ebl *ebl __attribute__ ((unused)),
+			       GElf_Ehdr *ehdr __attribute__ ((unused)),
+			       Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  printf (gettext ("\
+\nCall frame search table section [%2zu] '.eh_frame_hdr':\n"),
+	  elf_ndxscn (scn));
+
+  Elf_Data *data = elf_rawdata (scn, NULL);
+
+  if (unlikely (data == NULL))
+    {
+      error (0, 0, gettext ("cannot get %s content: %s"),
+	     ".eh_frame_hdr", elf_errmsg (-1));
+      return;
+    }
+
+  const unsigned char *readp = data->d_buf;
+  const unsigned char *const dataend = ((unsigned char *) data->d_buf
+					+ data->d_size);
+
+  if (unlikely (readp + 4 > dataend))
+    {
+    invalid_data:
+      error (0, 0, gettext ("invalid data"));
+      return;
+    }
+
+  unsigned int version = *readp++;
+  unsigned int eh_frame_ptr_enc = *readp++;
+  unsigned int fde_count_enc = *readp++;
+  unsigned int table_enc = *readp++;
+
+  printf (" version:          %u\n"
+	  " eh_frame_ptr_enc: %#x ",
+	  version, eh_frame_ptr_enc);
+  print_encoding_base ("", eh_frame_ptr_enc);
+  printf (" fde_count_enc:    %#x ", fde_count_enc);
+  print_encoding_base ("", fde_count_enc);
+  printf (" table_enc:        %#x ", table_enc);
+  print_encoding_base ("", table_enc);
+
+  uint64_t eh_frame_ptr = 0;
+  if (eh_frame_ptr_enc != DW_EH_PE_omit)
+    {
+      readp = read_encoded (eh_frame_ptr_enc, readp, dataend, &eh_frame_ptr,
+			    dbg);
+      if (unlikely (readp == NULL))
+	goto invalid_data;
+
+      printf (" eh_frame_ptr:     %#" PRIx64, eh_frame_ptr);
+      if ((eh_frame_ptr_enc & 0x70) == DW_EH_PE_pcrel)
+	printf (" (offset: %#" PRIx64 ")",
+		/* +4 because of the 4 byte header of the section.  */
+		(uint64_t) shdr->sh_offset + 4 + eh_frame_ptr);
+
+      putchar_unlocked ('\n');
+    }
+
+  uint64_t fde_count = 0;
+  if (fde_count_enc != DW_EH_PE_omit)
+    {
+      readp = read_encoded (fde_count_enc, readp, dataend, &fde_count, dbg);
+      if (unlikely (readp == NULL))
+	goto invalid_data;
+
+      printf (" fde_count:        %" PRIu64 "\n", fde_count);
+    }
+
+  if (fde_count == 0 || table_enc == DW_EH_PE_omit)
+    return;
+
+  puts (" Table:");
+
+  /* Optimize for the most common case.  */
+  if (table_enc == (DW_EH_PE_datarel | DW_EH_PE_sdata4))
+    while (fde_count > 0 && readp + 8 <= dataend)
+      {
+	int32_t initial_location = read_4sbyte_unaligned_inc (dbg, readp);
+	uint64_t initial_offset = ((uint64_t) shdr->sh_offset
+				   + (int64_t) initial_location);
+	int32_t address = read_4sbyte_unaligned_inc (dbg, readp);
+	// XXX Possibly print symbol name or section offset for initial_offset
+	printf ("  %#" PRIx32 " (offset: %#6" PRIx64 ") -> %#" PRIx32
+		" fde=[%6" PRIx64 "]\n",
+		initial_location, initial_offset,
+		address, address - (eh_frame_ptr + 4));
+      }
+  else
+    while (0 && readp < dataend)
+      {
+
+      }
+}
+
+
+/* Print the content of the exception handling table section
+   '.eh_frame_hdr'.  */
+static void
+print_debug_exception_table (Dwfl_Module *dwflmod __attribute__ ((unused)),
+			     Ebl *ebl __attribute__ ((unused)),
+			     GElf_Ehdr *ehdr __attribute__ ((unused)),
+			     Elf_Scn *scn,
+			     GElf_Shdr *shdr __attribute__ ((unused)),
+			     Dwarf *dbg __attribute__ ((unused)))
+{
+  printf (gettext ("\
+\nException handling table section [%2zu] '.gcc_except_table':\n"),
+	  elf_ndxscn (scn));
+
+  Elf_Data *data = elf_rawdata (scn, NULL);
+
+  if (unlikely (data == NULL))
+    {
+      error (0, 0, gettext ("cannot get %s content: %s"),
+	     ".gcc_except_table", elf_errmsg (-1));
+      return;
+    }
+
+  const unsigned char *readp = data->d_buf;
+  const unsigned char *const dataend = readp + data->d_size;
+
+  if (unlikely (readp + 1 > dataend))
+    {
+    invalid_data:
+      error (0, 0, gettext ("invalid data"));
+      return;
+    }
+  unsigned int lpstart_encoding = *readp++;
+  printf (gettext (" LPStart encoding:    %#x "), lpstart_encoding);
+  print_encoding_base ("", lpstart_encoding);
+  if (lpstart_encoding != DW_EH_PE_omit)
+    {
+      uint64_t lpstart;
+      readp = read_encoded (lpstart_encoding, readp, dataend, &lpstart, dbg);
+      printf (" LPStart:             %#" PRIx64 "\n", lpstart);
+    }
+
+  if (unlikely (readp + 1 > dataend))
+    goto invalid_data;
+  unsigned int ttype_encoding = *readp++;
+  printf (gettext (" TType encoding:      %#x "), ttype_encoding);
+  print_encoding_base ("", ttype_encoding);
+  const unsigned char *ttype_base = NULL;
+  if (ttype_encoding != DW_EH_PE_omit)
+    {
+      unsigned int ttype_base_offset;
+      get_uleb128 (ttype_base_offset, readp, dataend);
+      printf (" TType base offset:   %#x\n", ttype_base_offset);
+      if ((size_t) (dataend - readp) > ttype_base_offset)
+        ttype_base = readp + ttype_base_offset;
+    }
+
+  if (unlikely (readp + 1 > dataend))
+    goto invalid_data;
+  unsigned int call_site_encoding = *readp++;
+  printf (gettext (" Call site encoding:  %#x "), call_site_encoding);
+  print_encoding_base ("", call_site_encoding);
+  unsigned int call_site_table_len;
+  get_uleb128 (call_site_table_len, readp, dataend);
+
+  const unsigned char *const action_table = readp + call_site_table_len;
+  if (unlikely (action_table > dataend))
+    goto invalid_data;
+  unsigned int u = 0;
+  unsigned int max_action = 0;
+  while (readp < action_table)
+    {
+      if (u == 0)
+	puts (gettext ("\n Call site table:"));
+
+      uint64_t call_site_start;
+      readp = read_encoded (call_site_encoding, readp, dataend,
+			    &call_site_start, dbg);
+      uint64_t call_site_length;
+      readp = read_encoded (call_site_encoding, readp, dataend,
+			    &call_site_length, dbg);
+      uint64_t landing_pad;
+      readp = read_encoded (call_site_encoding, readp, dataend,
+			    &landing_pad, dbg);
+      unsigned int action;
+      get_uleb128 (action, readp, dataend);
+      max_action = MAX (action, max_action);
+      printf (gettext (" [%4u] Call site start:   %#" PRIx64 "\n"
+		       "        Call site length:  %" PRIu64 "\n"
+		       "        Landing pad:       %#" PRIx64 "\n"
+		       "        Action:            %u\n"),
+	      u++, call_site_start, call_site_length, landing_pad, action);
+    }
+  if (readp != action_table)
+    goto invalid_data;
+
+  unsigned int max_ar_filter = 0;
+  if (max_action > 0)
+    {
+      puts ("\n Action table:");
+
+      size_t maxdata = (size_t) (dataend - action_table);
+      if (max_action > maxdata || maxdata - max_action < 1)
+	{
+	invalid_action_table:
+	  fputs (gettext ("   <INVALID DATA>\n"), stdout);
+	  return;
+	}
+
+      const unsigned char *const action_table_end
+	= action_table + max_action + 1;
+
+      u = 0;
+      do
+	{
+	  int ar_filter;
+	  get_sleb128 (ar_filter, readp, action_table_end);
+	  if (ar_filter > 0 && (unsigned int) ar_filter > max_ar_filter)
+	    max_ar_filter = ar_filter;
+	  int ar_disp;
+	  if (readp >= action_table_end)
+	    goto invalid_action_table;
+	  get_sleb128 (ar_disp, readp, action_table_end);
+
+	  printf (" [%4u] ar_filter:  % d\n"
+		  "        ar_disp:    % -5d",
+		  u, ar_filter, ar_disp);
+	  if (abs (ar_disp) & 1)
+	    printf (" -> [%4u]\n", u + (ar_disp + 1) / 2);
+	  else if (ar_disp != 0)
+	    puts (" -> ???");
+	  else
+	    putchar_unlocked ('\n');
+	  ++u;
+	}
+      while (readp < action_table_end);
+    }
+
+  if (max_ar_filter > 0 && ttype_base != NULL)
+    {
+      unsigned char dsize;
+      puts ("\n TType table:");
+
+      // XXX Not *4, size of encoding;
+      switch (ttype_encoding & 7)
+	{
+	case DW_EH_PE_udata2:
+	case DW_EH_PE_sdata2:
+	  dsize = 2;
+	  break;
+	case DW_EH_PE_udata4:
+	case DW_EH_PE_sdata4:
+	  dsize = 4;
+	  break;
+	case DW_EH_PE_udata8:
+	case DW_EH_PE_sdata8:
+	  dsize = 8;
+	  break;
+	default:
+	  dsize = 0;
+	  error (1, 0, gettext ("invalid TType encoding"));
+	}
+
+      if (max_ar_filter
+	  > (size_t) (ttype_base - (const unsigned char *) data->d_buf) / dsize)
+	goto invalid_data;
+
+      readp = ttype_base - max_ar_filter * dsize;
+      do
+	{
+	  uint64_t ttype;
+	  readp = read_encoded (ttype_encoding, readp, ttype_base, &ttype,
+				dbg);
+	  printf (" [%4u] %#" PRIx64 "\n", max_ar_filter--, ttype);
+	}
+      while (readp < ttype_base);
+    }
+}
+
+/* Print the content of the '.gdb_index' section.
+   http://sourceware.org/gdb/current/onlinedocs/gdb/Index-Section-Format.html
+*/
+static void
+print_gdb_index_section (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr *ehdr,
+			 Elf_Scn *scn, GElf_Shdr *shdr, Dwarf *dbg)
+{
+  printf (gettext ("\nGDB section [%2zu] '%s' at offset %#" PRIx64
+		   " contains %" PRId64 " bytes :\n"),
+	  elf_ndxscn (scn), section_name (ebl, ehdr, shdr),
+	  (uint64_t) shdr->sh_offset, (uint64_t) shdr->sh_size);
+
+  Elf_Data *data = elf_rawdata (scn, NULL);
+
+  if (unlikely (data == NULL))
+    {
+      error (0, 0, gettext ("cannot get %s content: %s"),
+	     ".gdb_index", elf_errmsg (-1));
+      return;
+    }
+
+  // .gdb_index is always in little endian.
+  Dwarf dummy_dbg = { .other_byte_order = MY_ELFDATA != ELFDATA2LSB };
+  dbg = &dummy_dbg;
+
+  const unsigned char *readp = data->d_buf;
+  const unsigned char *const dataend = readp + data->d_size;
+
+  if (unlikely (readp + 4 > dataend))
+    {
+    invalid_data:
+      error (0, 0, gettext ("invalid data"));
+      return;
+    }
+
+  int32_t vers = read_4ubyte_unaligned (dbg, readp);
+  printf (gettext (" Version:         %" PRId32 "\n"), vers);
+
+  // The only difference between version 4 and version 5 is the
+  // hash used for generating the table.  Version 6 contains symbols
+  // for inlined functions, older versions didn't.  Version 7 adds
+  // symbol kinds.  Version 8 just indicates that it correctly includes
+  // TUs for symbols.
+  if (vers < 4 || vers > 8)
+    {
+      printf (gettext ("  unknown version, cannot parse section\n"));
+      return;
+    }
+
+  readp += 4;
+  if (unlikely (readp + 4 > dataend))
+    goto invalid_data;
+
+  uint32_t cu_off = read_4ubyte_unaligned (dbg, readp);
+  printf (gettext (" CU offset:       %#" PRIx32 "\n"), cu_off);
+
+  readp += 4;
+  if (unlikely (readp + 4 > dataend))
+    goto invalid_data;
+
+  uint32_t tu_off = read_4ubyte_unaligned (dbg, readp);
+  printf (gettext (" TU offset:       %#" PRIx32 "\n"), tu_off);
+
+  readp += 4;
+  if (unlikely (readp + 4 > dataend))
+    goto invalid_data;
+
+  uint32_t addr_off = read_4ubyte_unaligned (dbg, readp);
+  printf (gettext (" address offset:  %#" PRIx32 "\n"), addr_off);
+
+  readp += 4;
+  if (unlikely (readp + 4 > dataend))
+    goto invalid_data;
+
+  uint32_t sym_off = read_4ubyte_unaligned (dbg, readp);
+  printf (gettext (" symbol offset:   %#" PRIx32 "\n"), sym_off);
+
+  readp += 4;
+  if (unlikely (readp + 4 > dataend))
+    goto invalid_data;
+
+  uint32_t const_off = read_4ubyte_unaligned (dbg, readp);
+  printf (gettext (" constant offset: %#" PRIx32 "\n"), const_off);
+
+  if (unlikely ((size_t) (dataend - (const unsigned char *) data->d_buf)
+		< const_off))
+    goto invalid_data;
+
+  readp = data->d_buf + cu_off;
+
+  const unsigned char *nextp = data->d_buf + tu_off;
+  if (tu_off >= data->d_size)
+    goto invalid_data;
+
+  size_t cu_nr = (nextp - readp) / 16;
+
+  printf (gettext ("\n CU list at offset %#" PRIx32
+		   " contains %zu entries:\n"),
+	  cu_off, cu_nr);
+
+  size_t n = 0;
+  while (dataend - readp >= 16 && n < cu_nr)
+    {
+      uint64_t off = read_8ubyte_unaligned (dbg, readp);
+      readp += 8;
+
+      uint64_t len = read_8ubyte_unaligned (dbg, readp);
+      readp += 8;
+
+      printf (" [%4zu] start: %0#8" PRIx64
+	      ", length: %5" PRIu64 "\n", n, off, len);
+      n++;
+    }
+
+  readp = data->d_buf + tu_off;
+  nextp = data->d_buf + addr_off;
+  if (addr_off >= data->d_size)
+    goto invalid_data;
+
+  size_t tu_nr = (nextp - readp) / 24;
+
+  printf (gettext ("\n TU list at offset %#" PRIx32
+		   " contains %zu entries:\n"),
+	  tu_off, tu_nr);
+
+  n = 0;
+  while (dataend - readp >= 24 && n < tu_nr)
+    {
+      uint64_t off = read_8ubyte_unaligned (dbg, readp);
+      readp += 8;
+
+      uint64_t type = read_8ubyte_unaligned (dbg, readp);
+      readp += 8;
+
+      uint64_t sig = read_8ubyte_unaligned (dbg, readp);
+      readp += 8;
+
+      printf (" [%4zu] CU offset: %5" PRId64
+	      ", type offset: %5" PRId64
+	      ", signature: %0#8" PRIx64 "\n", n, off, type, sig);
+      n++;
+    }
+
+  readp = data->d_buf + addr_off;
+  nextp = data->d_buf + sym_off;
+  if (sym_off >= data->d_size)
+    goto invalid_data;
+
+  size_t addr_nr = (nextp - readp) / 20;
+
+  printf (gettext ("\n Address list at offset %#" PRIx32
+		   " contains %zu entries:\n"),
+	  addr_off, addr_nr);
+
+  n = 0;
+  while (dataend - readp >= 20 && n < addr_nr)
+    {
+      uint64_t low = read_8ubyte_unaligned (dbg, readp);
+      readp += 8;
+
+      uint64_t high = read_8ubyte_unaligned (dbg, readp);
+      readp += 8;
+
+      uint32_t idx = read_4ubyte_unaligned (dbg, readp);
+      readp += 4;
+
+      char *l = format_dwarf_addr (dwflmod, 8, low, low);
+      char *h = format_dwarf_addr (dwflmod, 8, high - 1, high);
+      printf (" [%4zu] %s..%s, CU index: %5" PRId32 "\n",
+	      n, l, h, idx);
+      free (l);
+      free (h);
+      n++;
+    }
+
+  const unsigned char *const_start = data->d_buf + const_off;
+  if (const_off >= data->d_size)
+    goto invalid_data;
+
+  readp = data->d_buf + sym_off;
+  nextp = const_start;
+  size_t sym_nr = (nextp - readp) / 8;
+
+  printf (gettext ("\n Symbol table at offset %#" PRIx32
+		   " contains %zu slots:\n"),
+	  addr_off, sym_nr);
+
+  n = 0;
+  while (dataend - readp >= 8 && n < sym_nr)
+    {
+      uint32_t name = read_4ubyte_unaligned (dbg, readp);
+      readp += 4;
+
+      uint32_t vector = read_4ubyte_unaligned (dbg, readp);
+      readp += 4;
+
+      if (name != 0 || vector != 0)
+	{
+	  const unsigned char *sym = const_start + name;
+	  if (unlikely ((size_t) (dataend - const_start) < name
+			|| memchr (sym, '\0', dataend - sym) == NULL))
+	    goto invalid_data;
+
+	  printf (" [%4zu] symbol: %s, CUs: ", n, sym);
+
+	  const unsigned char *readcus = const_start + vector;
+	  if (unlikely ((size_t) (dataend - const_start) < vector))
+	    goto invalid_data;
+	  uint32_t cus = read_4ubyte_unaligned (dbg, readcus);
+	  while (cus--)
+	    {
+	      uint32_t cu_kind, cu, kind;
+	      bool is_static;
+	      readcus += 4;
+	      if (unlikely (readcus + 4 > dataend))
+		goto invalid_data;
+	      cu_kind = read_4ubyte_unaligned (dbg, readcus);
+	      cu = cu_kind & ((1 << 24) - 1);
+	      kind = (cu_kind >> 28) & 7;
+	      is_static = cu_kind & (1U << 31);
+	      if (cu > cu_nr - 1)
+		printf ("%" PRId32 "T", cu - (uint32_t) cu_nr);
+	      else
+		printf ("%" PRId32, cu);
+	      if (kind != 0)
+		{
+		  printf (" (");
+		  switch (kind)
+		    {
+		    case 1:
+		      printf ("type");
+		      break;
+		    case 2:
+		      printf ("var");
+		      break;
+		    case 3:
+		      printf ("func");
+		      break;
+		    case 4:
+		      printf ("other");
+		      break;
+		    default:
+		      printf ("unknown-0x%" PRIx32, kind);
+		      break;
+		    }
+		  printf (":%c)", (is_static ? 'S' : 'G'));
+		}
+	      if (cus > 0)
+		printf (", ");
+	    }
+	  printf ("\n");
+	}
+      n++;
+    }
+}
+
+static void
+print_debug (Dwfl_Module *dwflmod, Ebl *ebl, GElf_Ehdr *ehdr)
+{
+  /* Before we start the real work get a debug context descriptor.  */
+  Dwarf_Addr dwbias;
+  Dwarf *dbg = dwfl_module_getdwarf (dwflmod, &dwbias);
+  Dwarf dummy_dbg =
+    {
+      .elf = ebl->elf,
+      .other_byte_order = MY_ELFDATA != ehdr->e_ident[EI_DATA]
+    };
+  if (dbg == NULL)
+    {
+      if ((print_debug_sections & ~section_exception) != 0)
+	error (0, 0, gettext ("cannot get debug context descriptor: %s"),
+	       dwfl_errmsg (-1));
+      dbg = &dummy_dbg;
+    }
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  /* Look through all the sections for the debugging sections to print.  */
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr != NULL && shdr->sh_type == SHT_PROGBITS)
+	{
+	  static const struct
+	  {
+	    const char *name;
+	    enum section_e bitmask;
+	    void (*fp) (Dwfl_Module *, Ebl *,
+			GElf_Ehdr *, Elf_Scn *, GElf_Shdr *, Dwarf *);
+	  } debug_sections[] =
+	    {
+#define NEW_SECTION(name) \
+	      { ".debug_" #name, section_##name, print_debug_##name##_section }
+	      NEW_SECTION (abbrev),
+	      NEW_SECTION (aranges),
+	      NEW_SECTION (frame),
+	      NEW_SECTION (info),
+	      NEW_SECTION (types),
+	      NEW_SECTION (line),
+	      NEW_SECTION (loc),
+	      NEW_SECTION (pubnames),
+	      NEW_SECTION (str),
+	      NEW_SECTION (macinfo),
+	      NEW_SECTION (macro),
+	      NEW_SECTION (ranges),
+	      { ".eh_frame", section_frame | section_exception,
+		print_debug_frame_section },
+	      { ".eh_frame_hdr", section_frame | section_exception,
+		print_debug_frame_hdr_section },
+	      { ".gcc_except_table", section_frame | section_exception,
+		print_debug_exception_table },
+	      { ".gdb_index", section_gdb_index, print_gdb_index_section }
+	    };
+	  const int ndebug_sections = (sizeof (debug_sections)
+				       / sizeof (debug_sections[0]));
+	  const char *name = elf_strptr (ebl->elf, shstrndx,
+					 shdr->sh_name);
+	  if (name == NULL)
+	    continue;
+
+	  int n;
+	  for (n = 0; n < ndebug_sections; ++n)
+	    if (strcmp (name, debug_sections[n].name) == 0
+		|| (name[0] == '.' && name[1] == 'z'
+		    && debug_sections[n].name[1] == 'd'
+		    && strcmp (&name[2], &debug_sections[n].name[1]) == 0)
+		)
+	      {
+		if ((print_debug_sections | implicit_debug_sections)
+		    & debug_sections[n].bitmask)
+		  debug_sections[n].fp (dwflmod, ebl, ehdr, scn, shdr, dbg);
+		break;
+	      }
+	}
+    }
+
+  reset_listptr (&known_loclistptr);
+  reset_listptr (&known_rangelistptr);
+}
+
+
+#define ITEM_INDENT		4
+#define WRAP_COLUMN		75
+
+/* Print "NAME: FORMAT", wrapping when output text would make the line
+   exceed WRAP_COLUMN.  Unpadded numbers look better for the core items
+   but this function is also used for registers which should be printed
+   aligned.  Fortunately registers output uses fixed fields width (such
+   as %11d) for the alignment.
+
+   Line breaks should not depend on the particular values although that
+   may happen in some cases of the core items.  */
+
+static unsigned int
+__attribute__ ((format (printf, 6, 7)))
+print_core_item (unsigned int colno, char sep, unsigned int wrap,
+		 size_t name_width, const char *name, const char *format, ...)
+{
+  size_t len = strlen (name);
+  if (name_width < len)
+    name_width = len;
+
+  char *out;
+  va_list ap;
+  va_start (ap, format);
+  int out_len = vasprintf (&out, format, ap);
+  va_end (ap);
+  if (out_len == -1)
+    error (EXIT_FAILURE, 0, _("memory exhausted"));
+
+  size_t n = name_width + sizeof ": " - 1 + out_len;
+
+  if (colno == 0)
+    {
+      printf ("%*s", ITEM_INDENT, "");
+      colno = ITEM_INDENT + n;
+    }
+  else if (colno + 2 + n < wrap)
+    {
+      printf ("%c ", sep);
+      colno += 2 + n;
+    }
+  else
+    {
+      printf ("\n%*s", ITEM_INDENT, "");
+      colno = ITEM_INDENT + n;
+    }
+
+  printf ("%s: %*s%s", name, (int) (name_width - len), "", out);
+
+  free (out);
+
+  return colno;
+}
+
+static const void *
+convert (Elf *core, Elf_Type type, uint_fast16_t count,
+	 void *value, const void *data, size_t size)
+{
+  Elf_Data valuedata =
+    {
+      .d_type = type,
+      .d_buf = value,
+      .d_size = size ?: gelf_fsize (core, type, count, EV_CURRENT),
+      .d_version = EV_CURRENT,
+    };
+  Elf_Data indata =
+    {
+      .d_type = type,
+      .d_buf = (void *) data,
+      .d_size = valuedata.d_size,
+      .d_version = EV_CURRENT,
+    };
+
+  Elf_Data *d = (gelf_getclass (core) == ELFCLASS32
+		 ? elf32_xlatetom : elf64_xlatetom)
+    (&valuedata, &indata, elf_getident (core, NULL)[EI_DATA]);
+  if (d == NULL)
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot convert core note data: %s"), elf_errmsg (-1));
+
+  return data + indata.d_size;
+}
+
+typedef uint8_t GElf_Byte;
+
+static unsigned int
+handle_core_item (Elf *core, const Ebl_Core_Item *item, const void *desc,
+		  unsigned int colno, size_t *repeated_size)
+{
+  uint_fast16_t count = item->count ?: 1;
+  /* Ebl_Core_Item count is always a small number.
+     Make sure the backend didn't put in some large bogus value.  */
+  assert (count < 128);
+
+#define TYPES								      \
+  DO_TYPE (BYTE, Byte, "0x%.2" PRIx8, "%" PRId8);			      \
+  DO_TYPE (HALF, Half, "0x%.4" PRIx16, "%" PRId16);			      \
+  DO_TYPE (WORD, Word, "0x%.8" PRIx32, "%" PRId32);			      \
+  DO_TYPE (SWORD, Sword, "%" PRId32, "%" PRId32);			      \
+  DO_TYPE (XWORD, Xword, "0x%.16" PRIx64, "%" PRId64);			      \
+  DO_TYPE (SXWORD, Sxword, "%" PRId64, "%" PRId64)
+
+#define DO_TYPE(NAME, Name, hex, dec) GElf_##Name Name
+  typedef union { TYPES; } value_t;
+  void *data = alloca (count * sizeof (value_t));
+#undef DO_TYPE
+
+#define DO_TYPE(NAME, Name, hex, dec) \
+    GElf_##Name *value_##Name __attribute__((unused)) = data
+  TYPES;
+#undef DO_TYPE
+
+  size_t size = gelf_fsize (core, item->type, count, EV_CURRENT);
+  size_t convsize = size;
+  if (repeated_size != NULL)
+    {
+      if (*repeated_size > size && (item->format == 'b' || item->format == 'B'))
+	{
+	  data = alloca (*repeated_size);
+	  count *= *repeated_size / size;
+	  convsize = count * size;
+	  *repeated_size -= convsize;
+	}
+      else if (item->count != 0 || item->format != '\n')
+	*repeated_size -= size;
+    }
+
+  convert (core, item->type, count, data, desc + item->offset, convsize);
+
+  Elf_Type type = item->type;
+  if (type == ELF_T_ADDR)
+    type = gelf_getclass (core) == ELFCLASS32 ? ELF_T_WORD : ELF_T_XWORD;
+
+  switch (item->format)
+    {
+    case 'd':
+      assert (count == 1);
+      switch (type)
+	{
+#define DO_TYPE(NAME, Name, hex, dec)					      \
+	  case ELF_T_##NAME:						      \
+	    colno = print_core_item (colno, ',', WRAP_COLUMN,		      \
+				     0, item->name, dec, value_##Name[0]); \
+	    break
+	  TYPES;
+#undef DO_TYPE
+	default:
+	  abort ();
+	}
+      break;
+
+    case 'x':
+      assert (count == 1);
+      switch (type)
+	{
+#define DO_TYPE(NAME, Name, hex, dec)					      \
+	  case ELF_T_##NAME:						      \
+	    colno = print_core_item (colno, ',', WRAP_COLUMN,		      \
+				     0, item->name, hex, value_##Name[0]);      \
+	    break
+	  TYPES;
+#undef DO_TYPE
+	default:
+	  abort ();
+	}
+      break;
+
+    case 'b':
+    case 'B':
+      assert (size % sizeof (unsigned int) == 0);
+      unsigned int nbits = count * size * 8;
+      unsigned int pop = 0;
+      for (const unsigned int *i = data; (void *) i < data + count * size; ++i)
+	pop += __builtin_popcount (*i);
+      bool negate = pop > nbits / 2;
+      const unsigned int bias = item->format == 'b';
+
+      {
+	char printed[(negate ? nbits - pop : pop) * 16 + 1];
+	char *p = printed;
+	*p = '\0';
+
+	if (BYTE_ORDER != LITTLE_ENDIAN && size > sizeof (unsigned int))
+	  {
+	    assert (size == sizeof (unsigned int) * 2);
+	    for (unsigned int *i = data;
+		 (void *) i < data + count * size; i += 2)
+	      {
+		unsigned int w = i[1];
+		i[1] = i[0];
+		i[0] = w;
+	      }
+	  }
+
+	unsigned int lastbit = 0;
+	unsigned int run = 0;
+	for (const unsigned int *i = data;
+	     (void *) i < data + count * size; ++i)
+	  {
+	    unsigned int bit = ((void *) i - data) * 8;
+	    unsigned int w = negate ? ~*i : *i;
+	    while (w != 0)
+	      {
+		/* Note that a right shift equal to (or greater than)
+		   the number of bits of w is undefined behaviour.  In
+		   particular when the least significant bit is bit 32
+		   (w = 0x8000000) then w >>= n is undefined.  So
+		   explicitly handle that case separately.  */
+		unsigned int n = ffs (w);
+		if (n < sizeof (w) * 8)
+		  w >>= n;
+		else
+		  w = 0;
+		bit += n;
+
+		if (lastbit != 0 && lastbit + 1 == bit)
+		  ++run;
+		else
+		  {
+		    if (lastbit == 0)
+		      p += sprintf (p, "%u", bit - bias);
+		    else if (run == 0)
+		      p += sprintf (p, ",%u", bit - bias);
+		    else
+		      p += sprintf (p, "-%u,%u", lastbit - bias, bit - bias);
+		    run = 0;
+		  }
+
+		lastbit = bit;
+	      }
+	  }
+	if (lastbit > 0 && run > 0 && lastbit + 1 != nbits)
+	  p += sprintf (p, "-%u", lastbit - bias);
+
+	colno = print_core_item (colno, ',', WRAP_COLUMN, 0, item->name,
+				 negate ? "~<%s>" : "<%s>", printed);
+      }
+      break;
+
+    case 'T':
+    case (char) ('T'|0x80):
+      assert (count == 2);
+      Dwarf_Word sec;
+      Dwarf_Word usec;
+      switch (type)
+	{
+#define DO_TYPE(NAME, Name, hex, dec)					      \
+	  case ELF_T_##NAME:						      \
+	    sec = value_##Name[0];					      \
+	    usec = value_##Name[1];					      \
+	    break
+	  TYPES;
+#undef DO_TYPE
+	default:
+	  abort ();
+	}
+      if (unlikely (item->format == (char) ('T'|0x80)))
+	{
+	  /* This is a hack for an ill-considered 64-bit ABI where
+	     tv_usec is actually a 32-bit field with 32 bits of padding
+	     rounding out struct timeval.  We've already converted it as
+	     a 64-bit field.  For little-endian, this just means the
+	     high half is the padding; it's presumably zero, but should
+	     be ignored anyway.  For big-endian, it means the 32-bit
+	     field went into the high half of USEC.  */
+	  GElf_Ehdr ehdr_mem;
+	  GElf_Ehdr *ehdr = gelf_getehdr (core, &ehdr_mem);
+	  if (likely (ehdr->e_ident[EI_DATA] == ELFDATA2MSB))
+	    usec >>= 32;
+	  else
+	    usec &= UINT32_MAX;
+	}
+      colno = print_core_item (colno, ',', WRAP_COLUMN, 0, item->name,
+			       "%" PRIu64 ".%.6" PRIu64, sec, usec);
+      break;
+
+    case 'c':
+      assert (count == 1);
+      colno = print_core_item (colno, ',', WRAP_COLUMN, 0, item->name,
+			       "%c", value_Byte[0]);
+      break;
+
+    case 's':
+      colno = print_core_item (colno, ',', WRAP_COLUMN, 0, item->name,
+			       "%.*s", (int) count, value_Byte);
+      break;
+
+    case '\n':
+      /* This is a list of strings separated by '\n'.  */
+      assert (item->count == 0);
+      assert (repeated_size != NULL);
+      assert (item->name == NULL);
+      if (unlikely (item->offset >= *repeated_size))
+	break;
+
+      const char *s = desc + item->offset;
+      size = *repeated_size - item->offset;
+      *repeated_size = 0;
+      while (size > 0)
+	{
+	  const char *eol = memchr (s, '\n', size);
+	  int len = size;
+	  if (eol != NULL)
+	    len = eol - s;
+	  printf ("%*s%.*s\n", ITEM_INDENT, "", len, s);
+	  if (eol == NULL)
+	    break;
+	  size -= eol + 1 - s;
+	  s = eol + 1;
+	}
+
+      colno = WRAP_COLUMN;
+      break;
+
+    case 'h':
+      break;
+
+    default:
+      error (0, 0, "XXX not handling format '%c' for %s",
+	     item->format, item->name);
+      break;
+    }
+
+#undef TYPES
+
+  return colno;
+}
+
+
+/* Sort items by group, and by layout offset within each group.  */
+static int
+compare_core_items (const void *a, const void *b)
+{
+  const Ebl_Core_Item *const *p1 = a;
+  const Ebl_Core_Item *const *p2 = b;
+  const Ebl_Core_Item *item1 = *p1;
+  const Ebl_Core_Item *item2 = *p2;
+
+  return ((item1->group == item2->group ? 0
+	   : strcmp (item1->group, item2->group))
+	  ?: (int) item1->offset - (int) item2->offset);
+}
+
+/* Sort item groups by layout offset of the first item in the group.  */
+static int
+compare_core_item_groups (const void *a, const void *b)
+{
+  const Ebl_Core_Item *const *const *p1 = a;
+  const Ebl_Core_Item *const *const *p2 = b;
+  const Ebl_Core_Item *const *group1 = *p1;
+  const Ebl_Core_Item *const *group2 = *p2;
+  const Ebl_Core_Item *item1 = *group1;
+  const Ebl_Core_Item *item2 = *group2;
+
+  return (int) item1->offset - (int) item2->offset;
+}
+
+static unsigned int
+handle_core_items (Elf *core, const void *desc, size_t descsz,
+		   const Ebl_Core_Item *items, size_t nitems)
+{
+  if (nitems == 0)
+    return 0;
+  unsigned int colno = 0;
+
+  /* FORMAT '\n' makes sense to be present only as a single item as it
+     processes all the data of a note.  FORMATs 'b' and 'B' have a special case
+     if present as a single item but they can be also processed with other
+     items below.  */
+  if (nitems == 1 && (items[0].format == '\n' || items[0].format == 'b'
+		      || items[0].format == 'B'))
+    {
+      assert (items[0].offset == 0);
+      size_t size = descsz;
+      colno = handle_core_item (core, items, desc, colno, &size);
+      /* If SIZE is not zero here there is some remaining data.  But we do not
+	 know how to process it anyway.  */
+      return colno;
+    }
+  for (size_t i = 0; i < nitems; ++i)
+    assert (items[i].format != '\n');
+
+  /* Sort to collect the groups together.  */
+  const Ebl_Core_Item *sorted_items[nitems];
+  for (size_t i = 0; i < nitems; ++i)
+    sorted_items[i] = &items[i];
+  qsort (sorted_items, nitems, sizeof sorted_items[0], &compare_core_items);
+
+  /* Collect the unique groups and sort them.  */
+  const Ebl_Core_Item **groups[nitems];
+  groups[0] = &sorted_items[0];
+  size_t ngroups = 1;
+  for (size_t i = 1; i < nitems; ++i)
+    if (sorted_items[i]->group != sorted_items[i - 1]->group
+	&& strcmp (sorted_items[i]->group, sorted_items[i - 1]->group))
+      groups[ngroups++] = &sorted_items[i];
+  qsort (groups, ngroups, sizeof groups[0], &compare_core_item_groups);
+
+  /* Write out all the groups.  */
+  const void *last = desc;
+  do
+    {
+      for (size_t i = 0; i < ngroups; ++i)
+	{
+	  for (const Ebl_Core_Item **item = groups[i];
+	       (item < &sorted_items[nitems]
+		&& ((*item)->group == groups[i][0]->group
+		    || !strcmp ((*item)->group, groups[i][0]->group)));
+	       ++item)
+	    colno = handle_core_item (core, *item, desc, colno, NULL);
+
+	  /* Force a line break at the end of the group.  */
+	  colno = WRAP_COLUMN;
+	}
+
+      if (descsz == 0)
+	break;
+
+      /* This set of items consumed a certain amount of the note's data.
+	 If there is more data there, we have another unit of the same size.
+	 Loop to print that out too.  */
+      const Ebl_Core_Item *item = &items[nitems - 1];
+      size_t eltsz = item->offset + gelf_fsize (core, item->type,
+						item->count ?: 1, EV_CURRENT);
+
+      int reps = -1;
+      do
+	{
+	  ++reps;
+	  desc += eltsz;
+	  descsz -= eltsz;
+	}
+      while (descsz >= eltsz && !memcmp (desc, last, eltsz));
+
+      if (reps == 1)
+	{
+	  /* For just one repeat, print it unabridged twice.  */
+	  desc -= eltsz;
+	  descsz += eltsz;
+	}
+      else if (reps > 1)
+	printf (gettext ("\n%*s... <repeats %u more times> ..."),
+		ITEM_INDENT, "", reps);
+
+      last = desc;
+    }
+  while (descsz > 0);
+
+  return colno;
+}
+
+static unsigned int
+handle_bit_registers (const Ebl_Register_Location *regloc, const void *desc,
+		      unsigned int colno)
+{
+  desc += regloc->offset;
+
+  abort ();			/* XXX */
+  return colno;
+}
+
+
+static unsigned int
+handle_core_register (Ebl *ebl, Elf *core, int maxregname,
+		      const Ebl_Register_Location *regloc, const void *desc,
+		      unsigned int colno)
+{
+  if (regloc->bits % 8 != 0)
+    return handle_bit_registers (regloc, desc, colno);
+
+  desc += regloc->offset;
+
+  for (int reg = regloc->regno; reg < regloc->regno + regloc->count; ++reg)
+    {
+      char name[REGNAMESZ];
+      int bits;
+      int type;
+      register_info (ebl, reg, regloc, name, &bits, &type);
+
+#define TYPES								      \
+      BITS (8, BYTE, "%4" PRId8, "0x%.2" PRIx8);			      \
+      BITS (16, HALF, "%6" PRId16, "0x%.4" PRIx16);			      \
+      BITS (32, WORD, "%11" PRId32, " 0x%.8" PRIx32);			      \
+      BITS (64, XWORD, "%20" PRId64, "  0x%.16" PRIx64)
+
+#define BITS(bits, xtype, sfmt, ufmt)				\
+      uint##bits##_t b##bits; int##bits##_t b##bits##s
+      union { TYPES; uint64_t b128[2]; } value;
+#undef	BITS
+
+      switch (type)
+	{
+	case DW_ATE_unsigned:
+	case DW_ATE_signed:
+	case DW_ATE_address:
+	  switch (bits)
+	    {
+#define BITS(bits, xtype, sfmt, ufmt)					      \
+	    case bits:							      \
+	      desc = convert (core, ELF_T_##xtype, 1, &value, desc, 0);	      \
+	      if (type == DW_ATE_signed)				      \
+		colno = print_core_item (colno, ' ', WRAP_COLUMN,	      \
+					 maxregname, name,		      \
+					 sfmt, value.b##bits##s);	      \
+	      else							      \
+		colno = print_core_item (colno, ' ', WRAP_COLUMN,	      \
+					 maxregname, name,		      \
+					 ufmt, value.b##bits);		      \
+	      break
+
+	    TYPES;
+
+	    case 128:
+	      assert (type == DW_ATE_unsigned);
+	      desc = convert (core, ELF_T_XWORD, 2, &value, desc, 0);
+	      int be = elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB;
+	      colno = print_core_item (colno, ' ', WRAP_COLUMN,
+				       maxregname, name,
+				       "0x%.16" PRIx64 "%.16" PRIx64,
+				       value.b128[!be], value.b128[be]);
+	      break;
+
+	    default:
+	      abort ();
+#undef	BITS
+	    }
+	  break;
+
+	default:
+	  /* Print each byte in hex, the whole thing in native byte order.  */
+	  assert (bits % 8 == 0);
+	  const uint8_t *bytes = desc;
+	  desc += bits / 8;
+	  char hex[bits / 4 + 1];
+	  hex[bits / 4] = '\0';
+	  int incr = 1;
+	  if (elf_getident (core, NULL)[EI_DATA] == ELFDATA2LSB)
+	    {
+	      bytes += bits / 8 - 1;
+	      incr = -1;
+	    }
+	  size_t idx = 0;
+	  for (char *h = hex; bits > 0; bits -= 8, idx += incr)
+	    {
+	      *h++ = "0123456789abcdef"[bytes[idx] >> 4];
+	      *h++ = "0123456789abcdef"[bytes[idx] & 0xf];
+	    }
+	  colno = print_core_item (colno, ' ', WRAP_COLUMN,
+				   maxregname, name, "0x%s", hex);
+	  break;
+	}
+      desc += regloc->pad;
+
+#undef TYPES
+    }
+
+  return colno;
+}
+
+
+struct register_info
+{
+  const Ebl_Register_Location *regloc;
+  const char *set;
+  char name[REGNAMESZ];
+  int regno;
+  int bits;
+  int type;
+};
+
+static int
+register_bitpos (const struct register_info *r)
+{
+  return (r->regloc->offset * 8
+	  + ((r->regno - r->regloc->regno)
+	     * (r->regloc->bits + r->regloc->pad * 8)));
+}
+
+static int
+compare_sets_by_info (const struct register_info *r1,
+		      const struct register_info *r2)
+{
+  return ((int) r2->bits - (int) r1->bits
+	  ?: register_bitpos (r1) - register_bitpos (r2));
+}
+
+/* Sort registers by set, and by size and layout offset within each set.  */
+static int
+compare_registers (const void *a, const void *b)
+{
+  const struct register_info *r1 = a;
+  const struct register_info *r2 = b;
+
+  /* Unused elements sort last.  */
+  if (r1->regloc == NULL)
+    return r2->regloc == NULL ? 0 : 1;
+  if (r2->regloc == NULL)
+    return -1;
+
+  return ((r1->set == r2->set ? 0 : strcmp (r1->set, r2->set))
+	  ?: compare_sets_by_info (r1, r2));
+}
+
+/* Sort register sets by layout offset of the first register in the set.  */
+static int
+compare_register_sets (const void *a, const void *b)
+{
+  const struct register_info *const *p1 = a;
+  const struct register_info *const *p2 = b;
+  return compare_sets_by_info (*p1, *p2);
+}
+
+static unsigned int
+handle_core_registers (Ebl *ebl, Elf *core, const void *desc,
+		       const Ebl_Register_Location *reglocs, size_t nregloc)
+{
+  if (nregloc == 0)
+    return 0;
+
+  ssize_t maxnreg = ebl_register_info (ebl, 0, NULL, 0, NULL, NULL, NULL, NULL);
+  if (maxnreg <= 0)
+    {
+      for (size_t i = 0; i < nregloc; ++i)
+	if (maxnreg < reglocs[i].regno + reglocs[i].count)
+	  maxnreg = reglocs[i].regno + reglocs[i].count;
+      assert (maxnreg > 0);
+    }
+
+  struct register_info regs[maxnreg];
+  memset (regs, 0, sizeof regs);
+
+  /* Sort to collect the sets together.  */
+  int maxreg = 0;
+  for (size_t i = 0; i < nregloc; ++i)
+    for (int reg = reglocs[i].regno;
+	 reg < reglocs[i].regno + reglocs[i].count;
+	 ++reg)
+      {
+	assert (reg < maxnreg);
+	if (reg > maxreg)
+	  maxreg = reg;
+	struct register_info *info = &regs[reg];
+	info->regloc = &reglocs[i];
+	info->regno = reg;
+	info->set = register_info (ebl, reg, &reglocs[i],
+				   info->name, &info->bits, &info->type);
+      }
+  qsort (regs, maxreg + 1, sizeof regs[0], &compare_registers);
+
+  /* Collect the unique sets and sort them.  */
+  inline bool same_set (const struct register_info *a,
+			const struct register_info *b)
+  {
+    return (a < &regs[maxnreg] && a->regloc != NULL
+	    && b < &regs[maxnreg] && b->regloc != NULL
+	    && a->bits == b->bits
+	    && (a->set == b->set || !strcmp (a->set, b->set)));
+  }
+  struct register_info *sets[maxreg + 1];
+  sets[0] = &regs[0];
+  size_t nsets = 1;
+  for (int i = 1; i <= maxreg; ++i)
+    if (regs[i].regloc != NULL && !same_set (&regs[i], &regs[i - 1]))
+      sets[nsets++] = &regs[i];
+  qsort (sets, nsets, sizeof sets[0], &compare_register_sets);
+
+  /* Write out all the sets.  */
+  unsigned int colno = 0;
+  for (size_t i = 0; i < nsets; ++i)
+    {
+      /* Find the longest name of a register in this set.  */
+      size_t maxname = 0;
+      const struct register_info *end;
+      for (end = sets[i]; same_set (sets[i], end); ++end)
+	{
+	  size_t len = strlen (end->name);
+	  if (len > maxname)
+	    maxname = len;
+	}
+
+      for (const struct register_info *reg = sets[i];
+	   reg < end;
+	   reg += reg->regloc->count ?: 1)
+	colno = handle_core_register (ebl, core, maxname,
+				      reg->regloc, desc, colno);
+
+      /* Force a line break at the end of the group.  */
+      colno = WRAP_COLUMN;
+    }
+
+  return colno;
+}
+
+static void
+handle_auxv_note (Ebl *ebl, Elf *core, GElf_Word descsz, GElf_Off desc_pos)
+{
+  Elf_Data *data = elf_getdata_rawchunk (core, desc_pos, descsz, ELF_T_AUXV);
+  if (data == NULL)
+  elf_error:
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot convert core note data: %s"), elf_errmsg (-1));
+
+  const size_t nauxv = descsz / gelf_fsize (core, ELF_T_AUXV, 1, EV_CURRENT);
+  for (size_t i = 0; i < nauxv; ++i)
+    {
+      GElf_auxv_t av_mem;
+      GElf_auxv_t *av = gelf_getauxv (data, i, &av_mem);
+      if (av == NULL)
+	goto elf_error;
+
+      const char *name;
+      const char *fmt;
+      if (ebl_auxv_info (ebl, av->a_type, &name, &fmt) == 0)
+	{
+	  /* Unknown type.  */
+	  if (av->a_un.a_val == 0)
+	    printf ("    %" PRIu64 "\n", av->a_type);
+	  else
+	    printf ("    %" PRIu64 ": %#" PRIx64 "\n",
+		    av->a_type, av->a_un.a_val);
+	}
+      else
+	switch (fmt[0])
+	  {
+	  case '\0':		/* Normally zero.  */
+	    if (av->a_un.a_val == 0)
+	      {
+		printf ("    %s\n", name);
+		break;
+	      }
+	    FALLTHROUGH;
+	  case 'x':		/* hex */
+	  case 'p':		/* address */
+	  case 's':		/* address of string */
+	    printf ("    %s: %#" PRIx64 "\n", name, av->a_un.a_val);
+	    break;
+	  case 'u':
+	    printf ("    %s: %" PRIu64 "\n", name, av->a_un.a_val);
+	    break;
+	  case 'd':
+	    printf ("    %s: %" PRId64 "\n", name, av->a_un.a_val);
+	    break;
+
+	  case 'b':
+	    printf ("    %s: %#" PRIx64 "  ", name, av->a_un.a_val);
+	    GElf_Xword bit = 1;
+	    const char *pfx = "<";
+	    for (const char *p = fmt + 1; *p != 0; p = strchr (p, '\0') + 1)
+	      {
+		if (av->a_un.a_val & bit)
+		  {
+		    printf ("%s%s", pfx, p);
+		    pfx = " ";
+		  }
+		bit <<= 1;
+	      }
+	    printf (">\n");
+	    break;
+
+	  default:
+	    abort ();
+	  }
+    }
+}
+
+static bool
+buf_has_data (unsigned char const *ptr, unsigned char const *end, size_t sz)
+{
+  return ptr < end && (size_t) (end - ptr) >= sz;
+}
+
+static bool
+buf_read_int (Elf *core, unsigned char const **ptrp, unsigned char const *end,
+	      int *retp)
+{
+  if (! buf_has_data (*ptrp, end, 4))
+    return false;
+
+  *ptrp = convert (core, ELF_T_WORD, 1, retp, *ptrp, 4);
+  return true;
+}
+
+static bool
+buf_read_ulong (Elf *core, unsigned char const **ptrp, unsigned char const *end,
+		uint64_t *retp)
+{
+  size_t sz = gelf_fsize (core, ELF_T_ADDR, 1, EV_CURRENT);
+  if (! buf_has_data (*ptrp, end, sz))
+    return false;
+
+  union
+  {
+    uint64_t u64;
+    uint32_t u32;
+  } u;
+
+  *ptrp = convert (core, ELF_T_ADDR, 1, &u, *ptrp, sz);
+
+  if (sz == 4)
+    *retp = u.u32;
+  else
+    *retp = u.u64;
+  return true;
+}
+
+static void
+handle_siginfo_note (Elf *core, GElf_Word descsz, GElf_Off desc_pos)
+{
+  Elf_Data *data = elf_getdata_rawchunk (core, desc_pos, descsz, ELF_T_BYTE);
+  if (data == NULL)
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot convert core note data: %s"), elf_errmsg (-1));
+
+  unsigned char const *ptr = data->d_buf;
+  unsigned char const *const end = data->d_buf + data->d_size;
+
+  /* Siginfo head is three ints: signal number, error number, origin
+     code.  */
+  int si_signo, si_errno, si_code;
+  if (! buf_read_int (core, &ptr, end, &si_signo)
+      || ! buf_read_int (core, &ptr, end, &si_errno)
+      || ! buf_read_int (core, &ptr, end, &si_code))
+    {
+    fail:
+      printf ("    Not enough data in NT_SIGINFO note.\n");
+      return;
+    }
+
+  /* Next is a pointer-aligned union of structures.  On 64-bit
+     machines, that implies a word of padding.  */
+  if (gelf_getclass (core) == ELFCLASS64)
+    ptr += 4;
+
+  printf ("    si_signo: %d, si_errno: %d, si_code: %d\n",
+	  si_signo, si_errno, si_code);
+
+  if (si_code > 0)
+    switch (si_signo)
+      {
+      case CORE_SIGILL:
+      case CORE_SIGFPE:
+      case CORE_SIGSEGV:
+      case CORE_SIGBUS:
+	{
+	  uint64_t addr;
+	  if (! buf_read_ulong (core, &ptr, end, &addr))
+	    goto fail;
+	  printf ("    fault address: %#" PRIx64 "\n", addr);
+	  break;
+	}
+      default:
+	;
+      }
+  else if (si_code == CORE_SI_USER)
+    {
+      int pid, uid;
+      if (! buf_read_int (core, &ptr, end, &pid)
+	  || ! buf_read_int (core, &ptr, end, &uid))
+	goto fail;
+      printf ("    sender PID: %d, sender UID: %d\n", pid, uid);
+    }
+}
+
+static void
+handle_file_note (Elf *core, GElf_Word descsz, GElf_Off desc_pos)
+{
+  Elf_Data *data = elf_getdata_rawchunk (core, desc_pos, descsz, ELF_T_BYTE);
+  if (data == NULL)
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot convert core note data: %s"), elf_errmsg (-1));
+
+  unsigned char const *ptr = data->d_buf;
+  unsigned char const *const end = data->d_buf + data->d_size;
+
+  uint64_t count, page_size;
+  if (! buf_read_ulong (core, &ptr, end, &count)
+      || ! buf_read_ulong (core, &ptr, end, &page_size))
+    {
+    fail:
+      printf ("    Not enough data in NT_FILE note.\n");
+      return;
+    }
+
+  size_t addrsize = gelf_fsize (core, ELF_T_ADDR, 1, EV_CURRENT);
+  uint64_t maxcount = (size_t) (end - ptr) / (3 * addrsize);
+  if (count > maxcount)
+    goto fail;
+
+  /* Where file names are stored.  */
+  unsigned char const *const fstart = ptr + 3 * count * addrsize;
+  char const *fptr = (char *) fstart;
+
+  printf ("    %" PRId64 " files:\n", count);
+  for (uint64_t i = 0; i < count; ++i)
+    {
+      uint64_t mstart, mend, moffset;
+      if (! buf_read_ulong (core, &ptr, fstart, &mstart)
+	  || ! buf_read_ulong (core, &ptr, fstart, &mend)
+	  || ! buf_read_ulong (core, &ptr, fstart, &moffset))
+	goto fail;
+
+      const char *fnext = memchr (fptr, '\0', (char *) end - fptr);
+      if (fnext == NULL)
+	goto fail;
+
+      int ct = printf ("      %08" PRIx64 "-%08" PRIx64
+		       " %08" PRIx64 " %" PRId64,
+		       mstart, mend, moffset * page_size, mend - mstart);
+      printf ("%*s%s\n", ct > 50 ? 3 : 53 - ct, "", fptr);
+
+      fptr = fnext + 1;
+    }
+}
+
+static void
+handle_core_note (Ebl *ebl, const GElf_Nhdr *nhdr,
+		  const char *name, const void *desc)
+{
+  GElf_Word regs_offset;
+  size_t nregloc;
+  const Ebl_Register_Location *reglocs;
+  size_t nitems;
+  const Ebl_Core_Item *items;
+
+  if (! ebl_core_note (ebl, nhdr, name,
+		       &regs_offset, &nregloc, &reglocs, &nitems, &items))
+    return;
+
+  /* Pass 0 for DESCSZ when there are registers in the note,
+     so that the ITEMS array does not describe the whole thing.
+     For non-register notes, the actual descsz might be a multiple
+     of the unit size, not just exactly the unit size.  */
+  unsigned int colno = handle_core_items (ebl->elf, desc,
+					  nregloc == 0 ? nhdr->n_descsz : 0,
+					  items, nitems);
+  if (colno != 0)
+    putchar_unlocked ('\n');
+
+  colno = handle_core_registers (ebl, ebl->elf, desc + regs_offset,
+				 reglocs, nregloc);
+  if (colno != 0)
+    putchar_unlocked ('\n');
+}
+
+static void
+handle_notes_data (Ebl *ebl, const GElf_Ehdr *ehdr,
+		   GElf_Off start, Elf_Data *data)
+{
+  fputs_unlocked (gettext ("  Owner          Data size  Type\n"), stdout);
+
+  if (data == NULL)
+    goto bad_note;
+
+  size_t offset = 0;
+  GElf_Nhdr nhdr;
+  size_t name_offset;
+  size_t desc_offset;
+  while (offset < data->d_size
+	 && (offset = gelf_getnote (data, offset,
+				    &nhdr, &name_offset, &desc_offset)) > 0)
+    {
+      const char *name = nhdr.n_namesz == 0 ? "" : data->d_buf + name_offset;
+      const char *desc = data->d_buf + desc_offset;
+
+      char buf[100];
+      char buf2[100];
+      printf (gettext ("  %-13.*s  %9" PRId32 "  %s\n"),
+	      (int) nhdr.n_namesz, name, nhdr.n_descsz,
+	      ehdr->e_type == ET_CORE
+	      ? ebl_core_note_type_name (ebl, nhdr.n_type,
+					 buf, sizeof (buf))
+	      : ebl_object_note_type_name (ebl, name, nhdr.n_type,
+					   buf2, sizeof (buf2)));
+
+      /* Filter out invalid entries.  */
+      if (memchr (name, '\0', nhdr.n_namesz) != NULL
+	  /* XXX For now help broken Linux kernels.  */
+	  || 1)
+	{
+	  if (ehdr->e_type == ET_CORE)
+	    {
+	      if (nhdr.n_type == NT_AUXV
+		  && (nhdr.n_namesz == 4 /* Broken old Linux kernels.  */
+		      || (nhdr.n_namesz == 5 && name[4] == '\0'))
+		  && !memcmp (name, "CORE", 4))
+		handle_auxv_note (ebl, ebl->elf, nhdr.n_descsz,
+				  start + desc_offset);
+	      else if (nhdr.n_namesz == 5 && strcmp (name, "CORE") == 0)
+		switch (nhdr.n_type)
+		  {
+		  case NT_SIGINFO:
+		    handle_siginfo_note (ebl->elf, nhdr.n_descsz,
+					 start + desc_offset);
+		    break;
+
+		  case NT_FILE:
+		    handle_file_note (ebl->elf, nhdr.n_descsz,
+				      start + desc_offset);
+		    break;
+
+		  default:
+		    handle_core_note (ebl, &nhdr, name, desc);
+		  }
+	      else
+		handle_core_note (ebl, &nhdr, name, desc);
+	    }
+	  else
+	    ebl_object_note (ebl, name, nhdr.n_type, nhdr.n_descsz, desc);
+	}
+    }
+
+  if (offset == data->d_size)
+    return;
+
+ bad_note:
+  error (0, 0,
+	 gettext ("cannot get content of note: %s"),
+	 data != NULL ? "garbage data" : elf_errmsg (-1));
+}
+
+static void
+handle_notes (Ebl *ebl, GElf_Ehdr *ehdr)
+{
+  /* If we have section headers, just look for SHT_NOTE sections.
+     In a debuginfo file, the program headers are not reliable.  */
+  if (shnum != 0)
+    {
+      /* Get the section header string table index.  */
+      size_t shstrndx;
+      if (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0)
+	error (EXIT_FAILURE, 0,
+	       gettext ("cannot get section header string table index"));
+
+      Elf_Scn *scn = NULL;
+      while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+	{
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+	  if (shdr == NULL || shdr->sh_type != SHT_NOTE)
+	    /* Not what we are looking for.  */
+	    continue;
+
+	  printf (gettext ("\
+\nNote section [%2zu] '%s' of %" PRIu64 " bytes at offset %#0" PRIx64 ":\n"),
+		  elf_ndxscn (scn),
+		  elf_strptr (ebl->elf, shstrndx, shdr->sh_name),
+		  shdr->sh_size, shdr->sh_offset);
+
+	  handle_notes_data (ebl, ehdr, shdr->sh_offset,
+			     elf_getdata (scn, NULL));
+	}
+      return;
+    }
+
+  /* We have to look through the program header to find the note
+     sections.  There can be more than one.  */
+  for (size_t cnt = 0; cnt < phnum; ++cnt)
+    {
+      GElf_Phdr mem;
+      GElf_Phdr *phdr = gelf_getphdr (ebl->elf, cnt, &mem);
+
+      if (phdr == NULL || phdr->p_type != PT_NOTE)
+	/* Not what we are looking for.  */
+	continue;
+
+      printf (gettext ("\
+\nNote segment of %" PRIu64 " bytes at offset %#0" PRIx64 ":\n"),
+	      phdr->p_filesz, phdr->p_offset);
+
+      handle_notes_data (ebl, ehdr, phdr->p_offset,
+			 elf_getdata_rawchunk (ebl->elf,
+					       phdr->p_offset, phdr->p_filesz,
+					       ELF_T_NHDR));
+    }
+}
+
+
+static void
+hex_dump (const uint8_t *data, size_t len)
+{
+  size_t pos = 0;
+  while (pos < len)
+    {
+      printf ("  0x%08zx ", pos);
+
+      const size_t chunk = MIN (len - pos, 16);
+
+      for (size_t i = 0; i < chunk; ++i)
+	if (i % 4 == 3)
+	  printf ("%02x ", data[pos + i]);
+	else
+	  printf ("%02x", data[pos + i]);
+
+      if (chunk < 16)
+	printf ("%*s", (int) ((16 - chunk) * 2 + (16 - chunk + 3) / 4), "");
+
+      for (size_t i = 0; i < chunk; ++i)
+	{
+	  unsigned char b = data[pos + i];
+	  printf ("%c", isprint (b) ? b : '.');
+	}
+
+      putchar ('\n');
+      pos += chunk;
+    }
+}
+
+static void
+dump_data_section (Elf_Scn *scn, const GElf_Shdr *shdr, const char *name)
+{
+  if (shdr->sh_size == 0 || shdr->sh_type == SHT_NOBITS)
+    printf (gettext ("\nSection [%zu] '%s' has no data to dump.\n"),
+	    elf_ndxscn (scn), name);
+  else
+    {
+      if (print_decompress)
+	{
+	  /* We try to decompress the section, but keep the old shdr around
+	     so we can show both the original shdr size and the uncompressed
+	     data size.   */
+	  if ((shdr->sh_flags & SHF_COMPRESSED) != 0)
+	    {
+	      if (elf_compress (scn, 0, 0) < 0)
+		printf ("WARNING: %s [%zd]\n",
+			gettext ("Couldn't uncompress section"),
+			elf_ndxscn (scn));
+	    }
+	  else if (strncmp (name, ".zdebug", strlen (".zdebug")) == 0)
+	    {
+	      if (elf_compress_gnu (scn, 0, 0) < 0)
+		printf ("WARNING: %s [%zd]\n",
+			gettext ("Couldn't uncompress section"),
+			elf_ndxscn (scn));
+	    }
+	}
+
+      Elf_Data *data = elf_rawdata (scn, NULL);
+      if (data == NULL)
+	error (0, 0, gettext ("cannot get data for section [%zu] '%s': %s"),
+	       elf_ndxscn (scn), name, elf_errmsg (-1));
+      else
+	{
+	  if (data->d_size == shdr->sh_size)
+	    printf (gettext ("\nHex dump of section [%zu] '%s', %" PRIu64
+			     " bytes at offset %#0" PRIx64 ":\n"),
+		    elf_ndxscn (scn), name,
+		    shdr->sh_size, shdr->sh_offset);
+	  else
+	    printf (gettext ("\nHex dump of section [%zu] '%s', %" PRIu64
+			     " bytes (%zd uncompressed) at offset %#0"
+			     PRIx64 ":\n"),
+		    elf_ndxscn (scn), name,
+		    shdr->sh_size, data->d_size, shdr->sh_offset);
+	  hex_dump (data->d_buf, data->d_size);
+	}
+    }
+}
+
+static void
+print_string_section (Elf_Scn *scn, const GElf_Shdr *shdr, const char *name)
+{
+  if (shdr->sh_size == 0 || shdr->sh_type == SHT_NOBITS)
+    printf (gettext ("\nSection [%zu] '%s' has no strings to dump.\n"),
+	    elf_ndxscn (scn), name);
+  else
+    {
+      if (print_decompress)
+	{
+	  /* We try to decompress the section, but keep the old shdr around
+	     so we can show both the original shdr size and the uncompressed
+	     data size.  */
+	  if ((shdr->sh_flags & SHF_COMPRESSED) != 0)
+	    {
+	      if (elf_compress (scn, 0, 0) < 0)
+		printf ("WARNING: %s [%zd]\n",
+			gettext ("Couldn't uncompress section"),
+			elf_ndxscn (scn));
+	    }
+	  else if (strncmp (name, ".zdebug", strlen (".zdebug")) == 0)
+	    {
+	      if (elf_compress_gnu (scn, 0, 0) < 0)
+		printf ("WARNING: %s [%zd]\n",
+			gettext ("Couldn't uncompress section"),
+			elf_ndxscn (scn));
+	    }
+	}
+
+      Elf_Data *data = elf_rawdata (scn, NULL);
+      if (data == NULL)
+	error (0, 0, gettext ("cannot get data for section [%zu] '%s': %s"),
+	       elf_ndxscn (scn), name, elf_errmsg (-1));
+      else
+	{
+	  if (data->d_size == shdr->sh_size)
+	    printf (gettext ("\nString section [%zu] '%s' contains %" PRIu64
+			     " bytes at offset %#0" PRIx64 ":\n"),
+		    elf_ndxscn (scn), name,
+		    shdr->sh_size, shdr->sh_offset);
+	  else
+	    printf (gettext ("\nString section [%zu] '%s' contains %" PRIu64
+			     " bytes (%zd uncompressed) at offset %#0"
+			     PRIx64 ":\n"),
+		    elf_ndxscn (scn), name,
+		    shdr->sh_size, data->d_size, shdr->sh_offset);
+
+	  const char *start = data->d_buf;
+	  const char *const limit = start + data->d_size;
+	  do
+	    {
+	      const char *end = memchr (start, '\0', limit - start);
+	      const size_t pos = start - (const char *) data->d_buf;
+	      if (unlikely (end == NULL))
+		{
+		  printf ("  [%6zx]- %.*s\n",
+			  pos, (int) (limit - start), start);
+		  break;
+		}
+	      printf ("  [%6zx]  %s\n", pos, start);
+	      start = end + 1;
+	    } while (start < limit);
+	}
+    }
+}
+
+static void
+for_each_section_argument (Elf *elf, const struct section_argument *list,
+			   void (*dump) (Elf_Scn *scn, const GElf_Shdr *shdr,
+					 const char *name))
+{
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (elf_getshdrstrndx (elf, &shstrndx) < 0)
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  for (const struct section_argument *a = list; a != NULL; a = a->next)
+    {
+      Elf_Scn *scn;
+      GElf_Shdr shdr_mem;
+      const char *name = NULL;
+
+      char *endp = NULL;
+      unsigned long int shndx = strtoul (a->arg, &endp, 0);
+      if (endp != a->arg && *endp == '\0')
+	{
+	  scn = elf_getscn (elf, shndx);
+	  if (scn == NULL)
+	    {
+	      error (0, 0, gettext ("\nsection [%lu] does not exist"), shndx);
+	      continue;
+	    }
+
+	  if (gelf_getshdr (scn, &shdr_mem) == NULL)
+	    error (EXIT_FAILURE, 0, gettext ("cannot get section header: %s"),
+		   elf_errmsg (-1));
+	  name = elf_strptr (elf, shstrndx, shdr_mem.sh_name);
+	}
+      else
+	{
+	  /* Need to look up the section by name.  */
+	  scn = NULL;
+	  bool found = false;
+	  while ((scn = elf_nextscn (elf, scn)) != NULL)
+	    {
+	      if (gelf_getshdr (scn, &shdr_mem) == NULL)
+		continue;
+	      name = elf_strptr (elf, shstrndx, shdr_mem.sh_name);
+	      if (name == NULL)
+		continue;
+	      if (!strcmp (name, a->arg))
+		{
+		  found = true;
+		  (*dump) (scn, &shdr_mem, name);
+		}
+	    }
+
+	  if (unlikely (!found) && !a->implicit)
+	    error (0, 0, gettext ("\nsection '%s' does not exist"), a->arg);
+	}
+    }
+}
+
+static void
+dump_data (Ebl *ebl)
+{
+  for_each_section_argument (ebl->elf, dump_data_sections, &dump_data_section);
+}
+
+static void
+dump_strings (Ebl *ebl)
+{
+  for_each_section_argument (ebl->elf, string_sections, &print_string_section);
+}
+
+static void
+print_strings (Ebl *ebl)
+{
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (ebl->elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  Elf_Scn *scn;
+  GElf_Shdr shdr_mem;
+  const char *name;
+  scn = NULL;
+  while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
+    {
+      if (gelf_getshdr (scn, &shdr_mem) == NULL)
+	continue;
+
+      if (shdr_mem.sh_type != SHT_PROGBITS
+	  || !(shdr_mem.sh_flags & SHF_STRINGS))
+	continue;
+
+      name = elf_strptr (ebl->elf, shstrndx, shdr_mem.sh_name);
+      if (name == NULL)
+	continue;
+
+      print_string_section (scn, &shdr_mem, name);
+    }
+}
+
+static void
+dump_archive_index (Elf *elf, const char *fname)
+{
+  size_t narsym;
+  const Elf_Arsym *arsym = elf_getarsym (elf, &narsym);
+  if (arsym == NULL)
+    {
+      int result = elf_errno ();
+      if (unlikely (result != ELF_E_NO_INDEX))
+	error (EXIT_FAILURE, 0,
+	       gettext ("cannot get symbol index of archive '%s': %s"),
+	       fname, elf_errmsg (result));
+      else
+	printf (gettext ("\nArchive '%s' has no symbol index\n"), fname);
+      return;
+    }
+
+  printf (gettext ("\nIndex of archive '%s' has %zu entries:\n"),
+	  fname, narsym);
+
+  size_t as_off = 0;
+  for (const Elf_Arsym *s = arsym; s < &arsym[narsym - 1]; ++s)
+    {
+      if (s->as_off != as_off)
+	{
+	  as_off = s->as_off;
+
+	  Elf *subelf = NULL;
+	  if (unlikely (elf_rand (elf, as_off) == 0)
+	      || unlikely ((subelf = elf_begin (-1, ELF_C_READ_MMAP, elf))
+			   == NULL))
+#if __GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 7)
+	    while (1)
+#endif
+	      error (EXIT_FAILURE, 0,
+		     gettext ("cannot extract member at offset %zu in '%s': %s"),
+		     as_off, fname, elf_errmsg (-1));
+
+	  const Elf_Arhdr *h = elf_getarhdr (subelf);
+
+	  printf (gettext ("Archive member '%s' contains:\n"), h->ar_name);
+
+	  elf_end (subelf);
+	}
+
+      printf ("\t%s\n", s->as_name);
+    }
+}
+
+#include "debugpred.h"
diff --git a/src/size.c b/src/size.c
new file mode 100644
index 0000000..ad8dbcb
--- /dev/null
+++ b/src/size.c
@@ -0,0 +1,657 @@
+/* Print size information from ELF file.
+   Copyright (C) 2000-2007,2009,2012,2014,2015 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2000.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <error.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <inttypes.h>
+#include <libelf.h>
+#include <libintl.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <system.h>
+#include <printversion.h>
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+
+/* Values for the parameters which have no short form.  */
+#define OPT_FORMAT	0x100
+#define OPT_RADIX	0x101
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, N_("Output format:"), 0 },
+  { "format", OPT_FORMAT, "FORMAT", 0,
+    N_("Use the output format FORMAT.  FORMAT can be `bsd' or `sysv'.  "
+       "The default is `bsd'"), 0 },
+  { NULL, 'A', NULL, 0, N_("Same as `--format=sysv'"), 0 },
+  { NULL, 'B', NULL, 0, N_("Same as `--format=bsd'"), 0 },
+  { "radix", OPT_RADIX, "RADIX", 0, N_("Use RADIX for printing symbol values"),
+    0},
+  { NULL, 'd', NULL, 0, N_("Same as `--radix=10'"), 0 },
+  { NULL, 'o', NULL, 0, N_("Same as `--radix=8'"), 0 },
+  { NULL, 'x', NULL, 0, N_("Same as `--radix=16'"), 0 },
+  { NULL, 'f', NULL, 0,
+    N_("Similar to `--format=sysv' output but in one line"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Output options:"), 0 },
+  { NULL, 'F', NULL, 0,
+    N_("Print size and permission flags for loadable segments"), 0 },
+  { "totals", 't', NULL, 0, N_("Display the total sizes (bsd only)"), 0 },
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("\
+List section sizes of FILEs (a.out by default).");
+
+/* 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 content of archive.  */
+static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname);
+
+/* Handle ELF file.  */
+static void handle_elf (Elf *elf, const char *fullname, const char *fname);
+
+/* Show total size.  */
+static void show_bsd_totals (void);
+
+#define INTERNAL_ERROR(fname) \
+  error (EXIT_FAILURE, 0, gettext ("%s: INTERNAL ERROR %d (%s): %s"),      \
+	 fname, __LINE__, PACKAGE_VERSION, elf_errmsg (-1))
+
+
+/* User-selectable options.  */
+
+/* The selected output format.  */
+static enum
+{
+  format_bsd = 0,
+  format_sysv,
+  format_sysv_one_line,
+  format_segments
+} format;
+
+/* Radix for printed numbers.  */
+static enum
+{
+  radix_decimal = 0,
+  radix_hex,
+  radix_octal
+} radix;
+
+
+/* Mapping of radix and binary class to length.  */
+static const int length_map[2][3] =
+{
+  [ELFCLASS32 - 1] =
+  {
+    [radix_hex] = 8,
+    [radix_decimal] = 10,
+    [radix_octal] = 11
+  },
+  [ELFCLASS64 - 1] =
+  {
+    [radix_hex] = 16,
+    [radix_decimal] = 20,
+    [radix_octal] = 22
+  }
+};
+
+/* True if total sizes should be printed.  */
+static bool totals;
+/* To print the total sizes in a reasonable format remember the higest
+   "class" of ELF binaries processed.  */
+static int totals_class;
+
+
+int
+main (int argc, char *argv[])
+{
+  int remaining;
+  int result = 0;
+
+  /* We use no threads here which can interfere with handling a stream.  */
+  __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+  __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+  __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  argp_parse (&argp, argc, argv, 0, &remaining, NULL);
+
+
+  /* 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
+    /* Process all the remaining files.  */
+    do
+      result |= process_file (argv[remaining]);
+    while (++remaining < argc);
+
+  /* Print the total sizes but only if the output format is BSD and at
+     least one file has been correctly read (i.e., we recognized the
+     class).  */
+  if (totals && format == format_bsd && totals_class != 0)
+    show_bsd_totals ();
+
+  return result;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg,
+	   struct argp_state *state __attribute__ ((unused)))
+{
+  switch (key)
+    {
+    case 'd':
+      radix = radix_decimal;
+      break;
+
+    case 'f':
+      format = format_sysv_one_line;
+      break;
+
+    case 'o':
+      radix = radix_octal;
+      break;
+
+    case 'x':
+      radix = radix_hex;
+      break;
+
+    case 'A':
+      format = format_sysv;
+      break;
+
+    case 'B':
+      format = format_bsd;
+      break;
+
+    case 'F':
+      format = format_segments;
+      break;
+
+    case OPT_FORMAT:
+      if (strcmp (arg, "bsd") == 0 || strcmp (arg, "berkeley") == 0)
+	format = format_bsd;
+      else if (likely (strcmp (arg, "sysv") == 0))
+	format = format_sysv;
+      else
+	error (EXIT_FAILURE, 0, gettext ("Invalid format: %s"), arg);
+      break;
+
+    case OPT_RADIX:
+      if (strcmp (arg, "x") == 0 || strcmp (arg, "16") == 0)
+	radix = radix_hex;
+      else if (strcmp (arg, "d") == 0 || strcmp (arg, "10") == 0)
+	radix = radix_decimal;
+      else if (strcmp (arg, "o") == 0 || strcmp (arg, "8") == 0)
+	radix = radix_octal;
+      else
+	error (EXIT_FAILURE, 0, gettext ("Invalid radix: %s"), arg);
+      break;
+
+    case 't':
+      totals = true;
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+
+/* Open the file and determine the type.  */
+static int
+process_file (const char *fname)
+{
+  int fd = open (fname, O_RDONLY);
+  if (unlikely (fd == -1))
+    {
+      error (0, errno, gettext ("cannot open '%s'"), fname);
+      return 1;
+    }
+
+  /* Now get the ELF descriptor.  */
+  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+  if (likely (elf != NULL))
+    {
+      if (elf_kind (elf) == ELF_K_ELF)
+	{
+	  handle_elf (elf, NULL, fname);
+
+	  if (unlikely (elf_end (elf) != 0))
+	    INTERNAL_ERROR (fname);
+
+	  if (unlikely (close (fd) != 0))
+	    error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
+
+	  return 0;
+	}
+      else if (likely (elf_kind (elf) == ELF_K_AR))
+	{
+	  int result = handle_ar (fd, elf, NULL, fname);
+
+	  if (unlikely  (close (fd) != 0))
+	    error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
+
+	  return result;
+	}
+
+      /* We cannot handle this type.  Close the descriptor anyway.  */
+      if (unlikely (elf_end (elf) != 0))
+	INTERNAL_ERROR (fname);
+    }
+
+  if (unlikely (close (fd) != 0))
+    error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
+
+  error (0, 0, gettext ("%s: file format not recognized"), fname);
+
+  return 1;
+}
+
+
+/* Print the BSD-style header.  This is done exactly once.  */
+static void
+print_header (Elf *elf)
+{
+  static int done;
+
+  if (! done)
+    {
+      int ddigits = length_map[gelf_getclass (elf) - 1][radix_decimal];
+      int xdigits = length_map[gelf_getclass (elf) - 1][radix_hex];
+
+      printf ("%*s %*s %*s %*s %*s %s\n",
+	      ddigits - 2, sgettext ("bsd|text"),
+	      ddigits - 2, sgettext ("bsd|data"),
+	      ddigits - 2, sgettext ("bsd|bss"),
+	      ddigits - 2, sgettext ("bsd|dec"),
+	      xdigits - 2, sgettext ("bsd|hex"),
+	      sgettext ("bsd|filename"));
+
+      done = 1;
+    }
+}
+
+
+static int
+handle_ar (int fd, Elf *elf, const char *prefix, const char *fname)
+{
+  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.  */
+  int result = 0;
+  Elf *subelf;
+  Elf_Cmd cmd = ELF_C_READ_MMAP;
+  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)
+	handle_elf (subelf, new_prefix, arhdr->ar_name);
+      else if (likely (elf_kind (subelf) == ELF_K_AR))
+	result |= handle_ar (fd, subelf, new_prefix, arhdr->ar_name);
+      /* else signal error??? */
+
+      /* Get next archive element.  */
+      cmd = elf_next (subelf);
+      if (unlikely (elf_end (subelf) != 0))
+	INTERNAL_ERROR (fname);
+    }
+
+  if (unlikely (elf_end (elf) != 0))
+    INTERNAL_ERROR (fname);
+
+  return result;
+}
+
+
+/* Show sizes in SysV format.  */
+static void
+show_sysv (Elf *elf, const char *prefix, const char *fname,
+	   const char *fullname)
+{
+  int maxlen = 10;
+  const int digits = length_map[gelf_getclass (elf) - 1][radix];
+
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  /* First round over the sections: determine the longest section name.  */
+  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)
+	INTERNAL_ERROR (fullname);
+
+      /* Ignore all sections which are not used at runtime.  */
+      const char *name = elf_strptr (elf, shstrndx, shdr->sh_name);
+      if (name != NULL && (shdr->sh_flags & SHF_ALLOC) != 0)
+	maxlen = MAX (maxlen, (int) strlen (name));
+    }
+
+  fputs_unlocked (fname, stdout);
+  if (prefix != NULL)
+    printf (gettext (" (ex %s)"), prefix);
+  printf (":\n%-*s %*s %*s\n",
+	  maxlen, sgettext ("sysv|section"),
+	  digits - 2, sgettext ("sysv|size"),
+	  digits, sgettext ("sysv|addr"));
+
+  /* Iterate over all sections.  */
+  GElf_Off total = 0;
+  while ((scn = elf_nextscn (elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      /* Ignore all sections which are not used at runtime.  */
+      if ((shdr->sh_flags & SHF_ALLOC) != 0)
+	{
+	  printf ((radix == radix_hex
+		   ? "%-*s %*" PRIx64 " %*" PRIx64 "\n"
+		   : (radix == radix_decimal
+		      ? "%-*s %*" PRId64 " %*" PRId64 "\n"
+		      : "%-*s %*" PRIo64 " %*" PRIo64 "\n")),
+		  maxlen, elf_strptr (elf, shstrndx, shdr->sh_name),
+		  digits - 2, shdr->sh_size,
+		  digits, shdr->sh_addr);
+
+	  total += shdr->sh_size;
+	}
+    }
+
+  if (radix == radix_hex)
+    printf ("%-*s %*" PRIx64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
+	    digits - 2, total);
+  else if (radix == radix_decimal)
+    printf ("%-*s %*" PRId64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
+	    digits - 2, total);
+  else
+    printf ("%-*s %*" PRIo64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
+	    digits - 2, total);
+}
+
+
+/* Show sizes in SysV format in one line.  */
+static void
+show_sysv_one_line (Elf *elf)
+{
+  /* Get the section header string table index.  */
+  size_t shstrndx;
+  if (unlikely (elf_getshdrstrndx (elf, &shstrndx) < 0))
+    error (EXIT_FAILURE, 0,
+	   gettext ("cannot get section header string table index"));
+
+  /* Iterate over all sections.  */
+  GElf_Off total = 0;
+  bool first = true;
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (elf, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      /* Ignore all sections which are not used at runtime.  */
+      if ((shdr->sh_flags & SHF_ALLOC) == 0)
+	continue;
+
+      if (! first)
+	fputs_unlocked (" + ", stdout);
+      first = false;
+
+      printf ((radix == radix_hex ? "%" PRIx64 "(%s)"
+	       : (radix == radix_decimal ? "%" PRId64 "(%s)"
+		  : "%" PRIo64 "(%s)")),
+	      shdr->sh_size, elf_strptr (elf, shstrndx, shdr->sh_name));
+
+      total += shdr->sh_size;
+    }
+
+  if (radix == radix_hex)
+    printf (" = %#" PRIx64 "\n", total);
+  else if (radix == radix_decimal)
+    printf (" = %" PRId64 "\n", total);
+  else
+    printf (" = %" PRIo64 "\n", total);
+}
+
+
+/* Variables to add up the sizes of all files.  */
+static uintmax_t total_textsize;
+static uintmax_t total_datasize;
+static uintmax_t total_bsssize;
+
+
+/* Show sizes in BSD format.  */
+static void
+show_bsd (Elf *elf, const char *prefix, const char *fname,
+	  const char *fullname)
+{
+  GElf_Off textsize = 0;
+  GElf_Off datasize = 0;
+  GElf_Off bsssize = 0;
+  const int ddigits = length_map[gelf_getclass (elf) - 1][radix_decimal];
+  const int xdigits = length_map[gelf_getclass (elf) - 1][radix_hex];
+
+  /* Iterate over all sections.  */
+  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)
+	INTERNAL_ERROR (fullname);
+
+      /* Ignore all sections which are not marked as loaded.  */
+      if ((shdr->sh_flags & SHF_ALLOC) == 0)
+	continue;
+
+      if ((shdr->sh_flags & SHF_WRITE) == 0)
+	textsize += shdr->sh_size;
+      else if (shdr->sh_type == SHT_NOBITS)
+	bsssize += shdr->sh_size;
+      else
+	datasize += shdr->sh_size;
+    }
+
+  printf ("%*" PRId64 " %*" PRId64 " %*" PRId64 " %*" PRId64 " %*"
+	  PRIx64 " %s",
+	  ddigits - 2, textsize,
+	  ddigits - 2, datasize,
+	  ddigits - 2, bsssize,
+	  ddigits - 2, textsize + datasize + bsssize,
+	  xdigits - 2, textsize + datasize + bsssize,
+	  fname);
+  if (prefix != NULL)
+    printf (gettext (" (ex %s)"), prefix);
+  fputs_unlocked ("\n", stdout);
+
+  total_textsize += textsize;
+  total_datasize += datasize;
+  total_bsssize += bsssize;
+
+  totals_class = MAX (totals_class, gelf_getclass (elf));
+}
+
+
+/* Show total size.  */
+static void
+show_bsd_totals (void)
+{
+  int ddigits = length_map[totals_class - 1][radix_decimal];
+  int xdigits = length_map[totals_class - 1][radix_hex];
+
+  printf ("%*" PRIuMAX " %*" PRIuMAX " %*" PRIuMAX " %*" PRIuMAX " %*"
+	  PRIxMAX " %s",
+	  ddigits - 2, total_textsize,
+	  ddigits - 2, total_datasize,
+	  ddigits - 2, total_bsssize,
+	  ddigits - 2, total_textsize + total_datasize + total_bsssize,
+	  xdigits - 2, total_textsize + total_datasize + total_bsssize,
+	  gettext ("(TOTALS)\n"));
+}
+
+
+/* Show size and permission of loadable segments.  */
+static void
+show_segments (Elf *elf, const char *fullname)
+{
+  size_t phnum;
+  if (elf_getphdrnum (elf, &phnum) != 0)
+    INTERNAL_ERROR (fullname);
+
+  GElf_Off total = 0;
+  bool first = true;
+  for (size_t cnt = 0; cnt < phnum; ++cnt)
+    {
+      GElf_Phdr phdr_mem;
+      GElf_Phdr *phdr;
+
+      phdr = gelf_getphdr (elf, cnt, &phdr_mem);
+      if (phdr == NULL)
+	INTERNAL_ERROR (fullname);
+
+      if (phdr->p_type != PT_LOAD)
+	/* Only load segments.  */
+	continue;
+
+      if (! first)
+	fputs_unlocked (" + ", stdout);
+      first = false;
+
+      printf (radix == radix_hex ? "%" PRIx64 "(%c%c%c)"
+	      : (radix == radix_decimal ? "%" PRId64 "(%c%c%c)"
+		 : "%" PRIo64 "(%c%c%c)"),
+	      phdr->p_memsz,
+	      (phdr->p_flags & PF_R) == 0 ? '-' : 'r',
+	      (phdr->p_flags & PF_W) == 0 ? '-' : 'w',
+	      (phdr->p_flags & PF_X) == 0 ? '-' : 'x');
+
+      total += phdr->p_memsz;
+    }
+
+  if (radix == radix_hex)
+    printf (" = %#" PRIx64 "\n", total);
+  else if (radix == radix_decimal)
+    printf (" = %" PRId64 "\n", total);
+  else
+    printf (" = %" PRIo64 "\n", total);
+}
+
+
+static void
+handle_elf (Elf *elf, const char *prefix, const char *fname)
+{
+  size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
+  size_t fname_len = strlen (fname) + 1;
+  char fullname[prefix_len + 1 + fname_len];
+  char *cp = fullname;
+
+  /* Create the full name of the file.  */
+  if (prefix != NULL)
+    {
+      cp = mempcpy (cp, prefix, prefix_len);
+      *cp++ = ':';
+    }
+  memcpy (cp, fname, fname_len);
+
+  if (format == format_sysv)
+    show_sysv (elf, prefix, fname, fullname);
+  else if (format == format_sysv_one_line)
+    show_sysv_one_line (elf);
+  else if (format == format_segments)
+    show_segments (elf, fullname);
+  else
+    {
+      print_header (elf);
+
+      show_bsd (elf, prefix, fname, fullname);
+    }
+}
+
+
+#include "debugpred.h"
diff --git a/src/stack.c b/src/stack.c
new file mode 100644
index 0000000..52ae3a8
--- /dev/null
+++ b/src/stack.c
@@ -0,0 +1,759 @@
+/* Unwinding of frames like gstack/pstack.
+   Copyright (C) 2013-2014 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <assert.h>
+#include <argp.h>
+#include <error.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <string.h>
+#include <locale.h>
+#include <fcntl.h>
+#include ELFUTILS_HEADER(dwfl)
+
+#include <dwarf.h>
+#include <system.h>
+#include <printversion.h>
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+/* non-printable argp options.  */
+#define OPT_DEBUGINFO	0x100
+#define OPT_COREFILE	0x101
+
+static bool show_activation = false;
+static bool show_module = false;
+static bool show_build_id = false;
+static bool show_source = false;
+static bool show_one_tid = false;
+static bool show_quiet = false;
+static bool show_raw = false;
+static bool show_modules = false;
+static bool show_debugname = false;
+static bool show_inlines = false;
+
+static int maxframes = 256;
+
+struct frame
+{
+  Dwarf_Addr pc;
+  bool isactivation;
+};
+
+struct frames
+{
+  int frames;
+  int allocated;
+  struct frame *frame;
+};
+
+static Dwfl *dwfl = NULL;
+static pid_t pid = 0;
+static int core_fd = -1;
+static Elf *core = NULL;
+static const char *exec = NULL;
+static char *debuginfo_path = NULL;
+
+static const Dwfl_Callbacks proc_callbacks =
+  {
+    .find_elf = dwfl_linux_proc_find_elf,
+    .find_debuginfo = dwfl_standard_find_debuginfo,
+    .debuginfo_path = &debuginfo_path,
+  };
+
+static const Dwfl_Callbacks core_callbacks =
+  {
+    .find_elf = dwfl_build_id_find_elf,
+    .find_debuginfo = dwfl_standard_find_debuginfo,
+    .debuginfo_path = &debuginfo_path,
+  };
+
+#ifdef USE_DEMANGLE
+static size_t demangle_buffer_len = 0;
+static char *demangle_buffer = NULL;
+#endif
+
+/* Whether any frames have been shown at all.  Determines exit status.  */
+static bool frames_shown = false;
+
+/* Program exit codes. All frames shown without any errors is GOOD.
+   Some frames shown with some non-fatal errors is an ERROR.  A fatal
+   error or no frames shown at all is BAD.  A command line USAGE exit
+   is generated by argp_error.  */
+#define EXIT_OK     0
+#define EXIT_ERROR  1
+#define EXIT_BAD    2
+#define EXIT_USAGE 64
+
+static int
+get_addr_width (Dwfl_Module *mod)
+{
+  // Try to find the address wide if possible.
+  static int width = 0;
+  if (width == 0 && mod)
+    {
+      Dwarf_Addr bias;
+      Elf *elf = dwfl_module_getelf (mod, &bias);
+      if (elf)
+        {
+	  GElf_Ehdr ehdr_mem;
+	  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
+	  if (ehdr)
+	    width = ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 8 : 16;
+	}
+    }
+  if (width == 0)
+    width = 16;
+
+  return width;
+}
+
+static int
+module_callback (Dwfl_Module *mod, void **userdata __attribute__((unused)),
+		 const char *name, Dwarf_Addr start,
+		 void *arg __attribute__((unused)))
+{
+  /* Forces resolving of main elf and debug files. */
+  Dwarf_Addr bias;
+  Elf *elf = dwfl_module_getelf (mod, &bias);
+  Dwarf *dwarf = dwfl_module_getdwarf (mod, &bias);
+
+  Dwarf_Addr end;
+  const char *mainfile;
+  const char *debugfile;
+  const char *modname = dwfl_module_info (mod, NULL, NULL, &end, NULL,
+                                          NULL, &mainfile, &debugfile);
+  assert (strcmp (modname, name) == 0);
+
+  int width = get_addr_width (mod);
+  printf ("0x%0*" PRIx64 "-0x%0*" PRIx64 " %s\n",
+	  width, start, width, end, basename (name));
+
+  const unsigned char *id;
+  GElf_Addr id_vaddr;
+  int id_len = dwfl_module_build_id (mod, &id, &id_vaddr);
+  if (id_len > 0)
+    {
+      printf ("  [");
+      do
+	printf ("%02" PRIx8, *id++);
+      while (--id_len > 0);
+      printf ("]\n");
+    }
+
+  if (elf != NULL)
+    printf ("  %s\n", mainfile != NULL ? mainfile : "-");
+  if (dwarf != NULL)
+    printf ("  %s\n", debugfile != NULL ? debugfile : "-");
+
+  return DWARF_CB_OK;
+}
+
+static int
+frame_callback (Dwfl_Frame *state, void *arg)
+{
+  struct frames *frames = (struct frames *) arg;
+  int nr = frames->frames;
+  if (! dwfl_frame_pc (state, &frames->frame[nr].pc,
+		       &frames->frame[nr].isactivation))
+    return -1;
+
+  frames->frames++;
+  if (frames->frames == maxframes)
+    return DWARF_CB_ABORT;
+
+  if (frames->frames == frames->allocated)
+    {
+      frames->allocated *= 2;
+      frames->frame = realloc (frames->frame,
+			       sizeof (struct frame) * frames->allocated);
+      if (frames->frame == NULL)
+	error (EXIT_BAD, errno, "realloc frames.frame");
+    }
+
+  return DWARF_CB_OK;
+}
+
+static const char*
+die_name (Dwarf_Die *die)
+{
+  Dwarf_Attribute attr;
+  const char *name;
+  name = dwarf_formstring (dwarf_attr_integrate (die,
+						 DW_AT_MIPS_linkage_name,
+						 &attr)
+			   ?: dwarf_attr_integrate (die,
+						    DW_AT_linkage_name,
+						    &attr));
+  if (name == NULL)
+    name = dwarf_diename (die);
+
+  return name;
+}
+
+static void
+print_frame (int nr, Dwarf_Addr pc, bool isactivation,
+	     Dwarf_Addr pc_adjusted, Dwfl_Module *mod,
+	     const char *symname, Dwarf_Die *cudie,
+	     Dwarf_Die *die)
+{
+  int width = get_addr_width (mod);
+  printf ("#%-2u 0x%0*" PRIx64, nr, width, (uint64_t) pc);
+
+  if (show_activation)
+    printf ("%4s", ! isactivation ? "- 1" : "");
+
+  if (symname != NULL)
+    {
+#ifdef USE_DEMANGLE
+      // Require GNU v3 ABI by the "_Z" prefix.
+      if (! show_raw && symname[0] == '_' && symname[1] == 'Z')
+	{
+	  int status = -1;
+	  char *dsymname = __cxa_demangle (symname, demangle_buffer,
+					   &demangle_buffer_len, &status);
+	  if (status == 0)
+	    symname = demangle_buffer = dsymname;
+	}
+#endif
+      printf (" %s", symname);
+    }
+
+  const char* fname;
+  Dwarf_Addr start;
+  fname = dwfl_module_info(mod, NULL, &start,
+			   NULL, NULL, NULL, NULL, NULL);
+  if (show_module)
+    {
+      if (fname != NULL)
+	printf (" - %s", fname);
+    }
+
+  if (show_build_id)
+    {
+      const unsigned char *id;
+      GElf_Addr id_vaddr;
+      int id_len = dwfl_module_build_id (mod, &id, &id_vaddr);
+      if (id_len > 0)
+	{
+	  printf ("\n    [");
+	  do
+	    printf ("%02" PRIx8, *id++);
+	  while (--id_len > 0);
+	  printf ("]@0x%0" PRIx64 "+0x%" PRIx64,
+		  start, pc_adjusted - start);
+	}
+    }
+
+  if (show_source)
+    {
+      int line, col;
+      const char* sname;
+      line = col = -1;
+      sname = NULL;
+      if (die != NULL)
+	{
+	  Dwarf_Files *files;
+	  if (dwarf_getsrcfiles (cudie, &files, NULL) == 0)
+	    {
+	      Dwarf_Attribute attr;
+	      Dwarf_Word val;
+	      if (dwarf_formudata (dwarf_attr (die, DW_AT_call_file, &attr),
+				   &val) == 0)
+		{
+		  sname = dwarf_filesrc (files, val, NULL, NULL);
+		  if (dwarf_formudata (dwarf_attr (die, DW_AT_call_line,
+						   &attr), &val) == 0)
+		    {
+		      line = val;
+		      if (dwarf_formudata (dwarf_attr (die, DW_AT_call_column,
+						       &attr), &val) == 0)
+			col = val;
+		    }
+		}
+	    }
+	}
+      else
+	{
+	  Dwfl_Line *lineobj = dwfl_module_getsrc(mod, pc_adjusted);
+	  if (lineobj)
+	    sname = dwfl_lineinfo (lineobj, NULL, &line, &col, NULL, NULL);
+	}
+
+      if (sname != NULL)
+	{
+	  printf ("\n    %s", sname);
+	  if (line > 0)
+	    {
+	      printf (":%d", line);
+	      if (col > 0)
+		printf (":%d", col);
+	    }
+	}
+    }
+  printf ("\n");
+}
+
+static void
+print_inline_frames (int *nr, Dwarf_Addr pc, bool isactivation,
+		     Dwarf_Addr pc_adjusted, Dwfl_Module *mod,
+		     const char *symname, Dwarf_Die *cudie, Dwarf_Die *die)
+{
+  Dwarf_Die *scopes = NULL;
+  int nscopes = dwarf_getscopes_die (die, &scopes);
+  if (nscopes > 0)
+    {
+      /* scopes[0] == die, the lowest level, for which we already have
+	 the name.  This is the actual source location where it
+	 happened.  */
+      print_frame ((*nr)++, pc, isactivation, pc_adjusted, mod, symname,
+		   NULL, NULL);
+
+      /* last_scope is the source location where the next frame/function
+	 call was done. */
+      Dwarf_Die *last_scope = &scopes[0];
+      for (int i = 1; i < nscopes && (maxframes == 0 || *nr < maxframes); i++)
+	{
+	  Dwarf_Die *scope = &scopes[i];
+	  int tag = dwarf_tag (scope);
+	  if (tag != DW_TAG_inlined_subroutine
+	      && tag != DW_TAG_entry_point
+	      && tag != DW_TAG_subprogram)
+	    continue;
+
+	  symname = die_name (scope);
+	  print_frame ((*nr)++, pc, isactivation, pc_adjusted, mod, symname,
+		       cudie, last_scope);
+
+	  /* Found the "top-level" in which everything was inlined?  */
+	  if (tag == DW_TAG_subprogram)
+	    break;
+
+	  last_scope = scope;
+	}
+    }
+  free (scopes);
+}
+
+static void
+print_frames (struct frames *frames, pid_t tid, int dwflerr, const char *what)
+{
+  if (frames->frames > 0)
+    frames_shown = true;
+
+  printf ("TID %lld:\n", (long long) tid);
+  int frame_nr = 0;
+  for (int nr = 0; nr < frames->frames && (maxframes == 0
+					   || frame_nr < maxframes); nr++)
+    {
+      Dwarf_Addr pc = frames->frame[nr].pc;
+      bool isactivation = frames->frame[nr].isactivation;
+      Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1);
+
+      /* Get PC->SYMNAME.  */
+      Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted);
+      const char *symname = NULL;
+      Dwarf_Die die_mem;
+      Dwarf_Die *die = NULL;
+      Dwarf_Die *cudie = NULL;
+      if (mod && ! show_quiet)
+	{
+	  if (show_debugname)
+	    {
+	      Dwarf_Addr bias = 0;
+	      Dwarf_Die *scopes = NULL;
+	      cudie = dwfl_module_addrdie (mod, pc_adjusted, &bias);
+	      int nscopes = dwarf_getscopes (cudie, pc_adjusted - bias,
+					     &scopes);
+
+	      /* Find the first function-like DIE with a name in scope.  */
+	      for (int i = 0; symname == NULL && i < nscopes; i++)
+		{
+		  Dwarf_Die *scope = &scopes[i];
+		  int tag = dwarf_tag (scope);
+		  if (tag == DW_TAG_subprogram
+		      || tag == DW_TAG_inlined_subroutine
+		      || tag == DW_TAG_entry_point)
+		    symname = die_name (scope);
+
+		  if (symname != NULL)
+		    {
+		      die_mem = *scope;
+		      die = &die_mem;
+		    }
+		}
+	      free (scopes);
+	    }
+
+	  if (symname == NULL)
+	    symname = dwfl_module_addrname (mod, pc_adjusted);
+	}
+
+      if (show_inlines && die != NULL)
+	print_inline_frames (&frame_nr, pc, isactivation, pc_adjusted, mod,
+			     symname, cudie, die);
+      else
+	print_frame (frame_nr++, pc, isactivation, pc_adjusted, mod, symname,
+		     NULL, NULL);
+    }
+
+  if (frames->frames > 0 && frame_nr == maxframes)
+    error (0, 0, "tid %lld: shown max number of frames "
+	   "(%d, use -n 0 for unlimited)", (long long) tid, maxframes);
+  else if (dwflerr != 0)
+    {
+      if (frames->frames > 0)
+	{
+	  unsigned nr = frames->frames - 1;
+	  Dwarf_Addr pc = frames->frame[nr].pc;
+	  bool isactivation = frames->frame[nr].isactivation;
+	  Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1);
+	  Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted);
+	  const char *mainfile = NULL;
+	  const char *modname = dwfl_module_info (mod, NULL, NULL, NULL, NULL,
+						  NULL, &mainfile, NULL);
+	  if (modname == NULL || modname[0] == '\0')
+	    {
+	      if (mainfile != NULL)
+		modname = mainfile;
+	      else
+		modname = "<unknown>";
+	    }
+	  error (0, 0, "%s tid %lld at 0x%" PRIx64 " in %s: %s", what,
+		 (long long) tid, pc_adjusted, modname, dwfl_errmsg (dwflerr));
+	}
+      else
+	error (0, 0, "%s tid %lld: %s", what, (long long) tid,
+	       dwfl_errmsg (dwflerr));
+    }
+}
+
+static int
+thread_callback (Dwfl_Thread *thread, void *thread_arg)
+{
+  struct frames *frames = (struct frames *) thread_arg;
+  pid_t tid = dwfl_thread_tid (thread);
+  int err = 0;
+  frames->frames = 0;
+  switch (dwfl_thread_getframes (thread, frame_callback, thread_arg))
+    {
+    case DWARF_CB_OK:
+    case DWARF_CB_ABORT:
+      break;
+    case -1:
+      err = dwfl_errno ();
+      break;
+    default:
+      abort ();
+    }
+  print_frames (frames, tid, err, "dwfl_thread_getframes");
+  return DWARF_CB_OK;
+}
+
+static error_t
+parse_opt (int key, char *arg __attribute__ ((unused)),
+	   struct argp_state *state)
+{
+  switch (key)
+    {
+    case 'p':
+      pid = atoi (arg);
+      if (pid == 0)
+	argp_error (state, N_("-p PID should be a positive process id."));
+      break;
+
+    case OPT_COREFILE:
+      core_fd = open (arg, O_RDONLY);
+      if (core_fd < 0)
+	error (EXIT_BAD, errno, N_("Cannot open core file '%s'"), arg);
+      elf_version (EV_CURRENT);
+      core = elf_begin (core_fd, ELF_C_READ_MMAP, NULL);
+      if (core == NULL)
+	error (EXIT_BAD, 0, "core '%s' elf_begin: %s", arg, elf_errmsg(-1));
+      break;
+
+    case 'e':
+      exec = arg;
+      break;
+
+    case OPT_DEBUGINFO:
+      debuginfo_path = arg;
+      break;
+
+    case 'm':
+      show_module = true;
+      break;
+
+    case 's':
+      show_source = true;
+      break;
+
+    case 'a':
+      show_activation = true;
+      break;
+
+    case 'd':
+      show_debugname = true;
+      break;
+
+    case 'i':
+      show_inlines = show_debugname = true;
+      break;
+
+    case 'v':
+      show_activation = show_source = show_module = show_debugname = true;
+      show_inlines = true;
+      break;
+
+    case 'b':
+      show_build_id = true;
+      break;
+
+    case 'q':
+      show_quiet = true;
+      break;
+
+    case 'r':
+      show_raw = true;
+      break;
+
+    case '1':
+      show_one_tid = true;
+      break;
+
+    case 'n':
+      maxframes = atoi (arg);
+      if (maxframes < 0)
+	{
+	  argp_error (state, N_("-n MAXFRAMES should be 0 or higher."));
+	  return EINVAL;
+	}
+      break;
+
+    case 'l':
+      show_modules = true;
+      break;
+
+    case ARGP_KEY_END:
+      if (core == NULL && exec != NULL)
+	argp_error (state,
+		    N_("-e EXEC needs a core given by --core."));
+
+      if (pid == 0 && show_one_tid == true)
+	argp_error (state,
+		    N_("-1 needs a thread id given by -p."));
+
+      if ((pid == 0 && core == NULL) || (pid != 0 && core != NULL))
+	argp_error (state,
+		    N_("One of -p PID or --core COREFILE should be given."));
+
+      if (pid != 0)
+	{
+	  dwfl = dwfl_begin (&proc_callbacks);
+	  if (dwfl == NULL)
+	    error (EXIT_BAD, 0, "dwfl_begin: %s", dwfl_errmsg (-1));
+
+	  int err = dwfl_linux_proc_report (dwfl, pid);
+	  if (err < 0)
+	    error (EXIT_BAD, 0, "dwfl_linux_proc_report pid %lld: %s",
+		   (long long) pid, dwfl_errmsg (-1));
+	  else if (err > 0)
+	    error (EXIT_BAD, err, "dwfl_linux_proc_report pid %lld",
+		   (long long) pid);
+	}
+
+      if (core != NULL)
+	{
+	  dwfl = dwfl_begin (&core_callbacks);
+	  if (dwfl == NULL)
+	    error (EXIT_BAD, 0, "dwfl_begin: %s", dwfl_errmsg (-1));
+	  if (dwfl_core_file_report (dwfl, core, exec) < 0)
+	    error (EXIT_BAD, 0, "dwfl_core_file_report: %s", dwfl_errmsg (-1));
+	}
+
+      if (dwfl_report_end (dwfl, NULL, NULL) != 0)
+	error (EXIT_BAD, 0, "dwfl_report_end: %s", dwfl_errmsg (-1));
+
+      if (pid != 0)
+	{
+	  int err = dwfl_linux_proc_attach (dwfl, pid, false);
+	  if (err < 0)
+	    error (EXIT_BAD, 0, "dwfl_linux_proc_attach pid %lld: %s",
+		   (long long) pid, dwfl_errmsg (-1));
+	  else if (err > 0)
+	    error (EXIT_BAD, err, "dwfl_linux_proc_attach pid %lld",
+		   (long long) pid);
+	}
+
+      if (core != NULL)
+	{
+	  if (dwfl_core_file_attach (dwfl, core) < 0)
+	    error (EXIT_BAD, 0, "dwfl_core_file_report: %s", dwfl_errmsg (-1));
+	}
+
+      /* Makes sure we are properly attached.  */
+      if (dwfl_pid (dwfl) < 0)
+	error (EXIT_BAD, 0, "dwfl_pid: %s\n", dwfl_errmsg (-1));
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+int
+main (int argc, char **argv)
+{
+  /* We use no threads here which can interfere with handling a stream.  */
+  __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+  __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+  __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  const struct argp_option options[] =
+    {
+      { NULL, 0, NULL, 0, N_("Input selection options:"), 0 },
+      { "pid", 'p', "PID", 0,
+	N_("Show stack of process PID"), 0 },
+      { "core", OPT_COREFILE, "COREFILE", 0,
+	N_("Show stack found in COREFILE"), 0 },
+      {  "executable", 'e', "EXEC", 0, N_("(optional) EXECUTABLE that produced COREFILE"), 0 },
+      { "debuginfo-path", OPT_DEBUGINFO, "PATH", 0,
+	N_("Search path for separate debuginfo files"), 0 },
+
+      { NULL, 0, NULL, 0, N_("Output selection options:"), 0 },
+      { "activation",  'a', NULL, 0,
+	N_("Additionally show frame activation"), 0 },
+      { "debugname",  'd', NULL, 0,
+	N_("Additionally try to lookup DWARF debuginfo name for frame address"),
+	0 },
+      { "inlines",  'i', NULL, 0,
+	N_("Additionally show inlined function frames using DWARF debuginfo if available (implies -d)"), 0 },
+      { "module",  'm', NULL, 0,
+	N_("Additionally show module file information"), 0 },
+      { "source",  's', NULL, 0,
+	N_("Additionally show source file information"), 0 },
+      { "verbose", 'v', NULL, 0,
+	N_("Show all additional information (activation, debugname, inlines, module and source)"), 0 },
+      { "quiet", 'q', NULL, 0,
+	N_("Do not resolve address to function symbol name"), 0 },
+      { "raw", 'r', NULL, 0,
+	N_("Show raw function symbol names, do not try to demangle names"), 0 },
+      { "build-id",  'b', NULL, 0,
+	N_("Show module build-id, load address and pc offset"), 0 },
+      { NULL, '1', NULL, 0,
+	N_("Show the backtrace of only one thread"), 0 },
+      { NULL, 'n', "MAXFRAMES", 0,
+	N_("Show at most MAXFRAMES per thread (default 256, use 0 for unlimited)"), 0 },
+      { "list-modules", 'l', NULL, 0,
+	N_("Show module memory map with build-id, elf and debug files detected"), 0 },
+      { NULL, 0, NULL, 0, NULL, 0 }
+    };
+
+  const struct argp argp =
+    {
+      .options = options,
+      .parser = parse_opt,
+      .doc = N_("Print a stack for each thread in a process or core file.\n\
+\n\
+Program exits with return code 0 if all frames were shown without \
+any errors.  If some frames were shown, but there were some non-fatal \
+errors, possibly causing an incomplete backtrace, the program exits \
+with return code 1.  If no frames could be shown, or a fatal error \
+occured the program exits with return code 2.  If the program was \
+invoked with bad or missing arguments it will exit with return code 64.")
+    };
+
+  argp_parse (&argp, argc, argv, 0, NULL, NULL);
+
+  if (show_modules)
+    {
+      printf ("PID %lld - %s module memory map\n", (long long) dwfl_pid (dwfl),
+	      pid != 0 ? "process" : "core");
+      if (dwfl_getmodules (dwfl, module_callback, NULL, 0) != 0)
+	error (EXIT_BAD, 0, "dwfl_getmodules: %s", dwfl_errmsg (-1));
+    }
+
+  struct frames frames;
+  /* When maxframes is zero, then 2048 is just the initial allocation
+     that will be increased using realloc in framecallback ().  */
+  frames.allocated = maxframes == 0 ? 2048 : maxframes;
+  frames.frames = 0;
+  frames.frame = malloc (sizeof (struct frame) * frames.allocated);
+  if (frames.frame == NULL)
+    error (EXIT_BAD, errno, "malloc frames.frame");
+
+  if (show_one_tid)
+    {
+      int err = 0;
+      switch (dwfl_getthread_frames (dwfl, pid, frame_callback, &frames))
+	{
+	case DWARF_CB_OK:
+	case DWARF_CB_ABORT:
+	  break;
+	case -1:
+	  err = dwfl_errno ();
+	  break;
+	default:
+	  abort ();
+	}
+      print_frames (&frames, pid, err, "dwfl_getthread_frames");
+    }
+  else
+    {
+      printf ("PID %lld - %s\n", (long long) dwfl_pid (dwfl),
+	      pid != 0 ? "process" : "core");
+      switch (dwfl_getthreads (dwfl, thread_callback, &frames))
+	{
+	case DWARF_CB_OK:
+	case DWARF_CB_ABORT:
+	  break;
+	case -1:
+	  error (0, 0, "dwfl_getthreads: %s", dwfl_errmsg (-1));
+	  break;
+	default:
+	  abort ();
+	}
+    }
+  free (frames.frame);
+  dwfl_end (dwfl);
+
+  if (core != NULL)
+    elf_end (core);
+
+  if (core_fd != -1)
+    close (core_fd);
+
+#ifdef USE_DEMANGLE
+  free (demangle_buffer);
+#endif
+
+  if (! frames_shown)
+    error (EXIT_BAD, 0, N_("Couldn't show any frames."));
+
+  return error_message_count != 0 ? EXIT_ERROR : EXIT_OK;
+}
diff --git a/src/strings.c b/src/strings.c
new file mode 100644
index 0000000..03d0f13
--- /dev/null
+++ b/src/strings.c
@@ -0,0 +1,748 @@
+/* Print the strings of printable characters in files.
+   Copyright (C) 2005-2010, 2012, 2014 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2005.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <assert.h>
+#include <ctype.h>
+#include <endian.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <inttypes.h>
+#include <libintl.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/mman.h>
+#include <sys/stat.h>
+
+#include <libeu.h>
+#include <system.h>
+#include <printversion.h>
+
+#ifndef MAP_POPULATE
+# define MAP_POPULATE 0
+#endif
+
+
+/* Prototypes of local functions.  */
+static int read_fd (int fd, const char *fname, off_t fdlen);
+static int read_elf (Elf *elf, int fd, const char *fname, off_t fdlen);
+
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, N_("Output Selection:"), 0 },
+  { "all", 'a', NULL, 0, N_("Scan entire file, not only loaded sections"), 0 },
+  { "bytes", 'n', "MIN-LEN", 0,
+    N_("Only NUL-terminated sequences of MIN-LEN characters or more are printed"), 0 },
+  { "encoding", 'e', "SELECTOR", 0, N_("\
+Select character size and endianess: s = 7-bit, S = 8-bit, {b,l} = 16-bit, {B,L} = 32-bit"),
+    0},
+  { "print-file-name", 'f', NULL, 0,
+    N_("Print name of the file before each string."), 0 },
+  { "radix", 't', "{o,d,x}", 0,
+    N_("Print location of the string in base 8, 10, or 16 respectively."), 0 },
+  { NULL, 'o', NULL, 0, N_("Alias for --radix=o"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Miscellaneous:"), 0 },
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("\
+Print the strings of printable characters in files.");
+
+/* 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
+};
+
+
+/* Global variables.  */
+
+/* True if whole file and not only loaded sections are looked at.  */
+static bool entire_file;
+
+/* Minimum length of any sequence reported.  */
+static size_t min_len = 4;
+
+/* Number of bytes per character.  */
+static size_t bytes_per_char = 1;
+
+/* Minimum length of any sequence reported in bytes.  */
+static size_t min_len_bytes;
+
+/* True if multibyte characters are in big-endian order.  */
+static bool big_endian;
+
+/* True unless 7-bit ASCII are expected.  */
+static bool char_7bit;
+
+/* True if file names should be printed before strings.  */
+static bool print_file_name;
+
+/* Radix for printed numbers.  */
+static enum
+{
+  radix_none = 0,
+  radix_decimal,
+  radix_hex,
+  radix_octal
+} radix = radix_none;
+
+
+/* Page size in use.  */
+static size_t ps;
+
+
+/* Mapped parts of the ELF file.  */
+static unsigned char *elfmap;
+static unsigned char *elfmap_base;
+static size_t elfmap_size;
+static off_t elfmap_off;
+
+
+int
+main (int argc, char *argv[])
+{
+  /* We use no threads.  */
+  __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+  __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  (void) textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  int remaining;
+  (void) argp_parse (&argp, argc, argv, 0, &remaining, NULL);
+
+  /* Tell the library which version we are expecting.  */
+  elf_version (EV_CURRENT);
+
+  /* Determine the page size.  We will likely need it a couple of times.  */
+  ps = sysconf (_SC_PAGESIZE);
+
+  struct stat st;
+  int result = 0;
+  if (remaining == argc)
+    /* We read from standard input.  This we cannot do for a
+       structured file.  */
+    result = read_fd (STDIN_FILENO,
+		      print_file_name ? "{standard input}" : NULL,
+		      (fstat (STDIN_FILENO, &st) == 0 && S_ISREG (st.st_mode))
+		      ? st.st_size : INT64_C (0x7fffffffffffffff));
+  else
+    do
+      {
+	int fd = (strcmp (argv[remaining], "-") == 0
+		  ? STDIN_FILENO : open (argv[remaining], O_RDONLY));
+	if (unlikely (fd == -1))
+	  {
+	    error (0, errno, gettext ("cannot open '%s'"), argv[remaining]);
+	    result = 1;
+	  }
+	else
+	  {
+	    const char *fname = print_file_name ? argv[remaining] : NULL;
+	    int fstat_fail = fstat (fd, &st);
+	    off_t fdlen = (fstat_fail
+			     ? INT64_C (0x7fffffffffffffff) : st.st_size);
+	    if (fdlen > (off_t) min_len_bytes)
+	      {
+		Elf *elf = NULL;
+		if (entire_file
+		    || fstat_fail
+		    || !S_ISREG (st.st_mode)
+		    || (elf = elf_begin (fd, ELF_C_READ, NULL)) == NULL
+		    || elf_kind (elf) != ELF_K_ELF)
+		  result |= read_fd (fd, fname, fdlen);
+		else
+		  result |= read_elf (elf, fd, fname, fdlen);
+
+		/* This call will succeed even if ELF is NULL.  */
+		elf_end (elf);
+	      }
+
+	    if (strcmp (argv[remaining], "-") != 0)
+	      close (fd);
+	  }
+
+	if (elfmap != NULL && elfmap != MAP_FAILED)
+	  munmap (elfmap, elfmap_size);
+	elfmap = NULL;
+      }
+    while (++remaining < argc);
+
+  return result;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg,
+	   struct argp_state *state __attribute__ ((unused)))
+{
+  switch (key)
+    {
+    case 'a':
+      entire_file = true;
+      break;
+
+    case 'e':
+      /* We expect a string of one character.  */
+      switch (arg[1] != '\0' ? '\0' : arg[0])
+	{
+	case 's':
+	case 'S':
+	  char_7bit = arg[0] == 's';
+	  bytes_per_char = 1;
+	  break;
+
+	case 'b':
+	case 'B':
+	  big_endian = true;
+	  FALLTHROUGH;
+
+	case 'l':
+	case 'L':
+	  bytes_per_char = isupper (arg[0]) ? 4 : 2;
+	  break;
+
+	default:
+	  error (0, 0, gettext ("invalid value '%s' for %s parameter"),
+		 arg, "-e");
+	  argp_help (&argp, stderr, ARGP_HELP_SEE, "strings");
+	  return ARGP_ERR_UNKNOWN;
+	}
+      break;
+
+    case 'f':
+      print_file_name = true;
+      break;
+
+    case 'n':
+      min_len = atoi (arg);
+      break;
+
+    case 'o':
+      goto octfmt;
+
+    case 't':
+      switch (arg[0])
+	{
+	case 'd':
+	  radix = radix_decimal;
+	  break;
+
+	case 'o':
+	octfmt:
+	  radix = radix_octal;
+	  break;
+
+	case 'x':
+	  radix = radix_hex;
+	  break;
+
+	default:
+	  error (0, 0, gettext ("invalid value '%s' for %s parameter"),
+		 arg, "-t");
+	  argp_help (&argp, stderr, ARGP_HELP_SEE, "strings");
+	  return ARGP_ERR_UNKNOWN;
+	}
+      break;
+
+    case ARGP_KEY_FINI:
+      /* Compute the length in bytes of any match.  */
+      if (min_len <= 0 || min_len > INT_MAX / bytes_per_char)
+	error (EXIT_FAILURE, 0,
+	       gettext ("invalid minimum length of matched string size"));
+      min_len_bytes = min_len * bytes_per_char;
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+
+static void
+process_chunk_mb (const char *fname, const unsigned char *buf, off_t to,
+		  size_t len, char **unprinted)
+{
+  size_t curlen = *unprinted == NULL ? 0 : strlen (*unprinted);
+  const unsigned char *start = buf;
+  while (len >= bytes_per_char)
+    {
+      uint32_t ch;
+
+      if (bytes_per_char == 2)
+	{
+	  if (big_endian)
+	    ch = buf[0] << 8 | buf[1];
+	  else
+	    ch = buf[1] << 8 | buf[0];
+	}
+      else
+	{
+	  if (big_endian)
+	    ch = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
+	  else
+	    ch = buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];
+	}
+
+      if (ch <= 255 && (isprint (ch) || ch == '\t'))
+	{
+	  ++buf;
+	  ++curlen;
+	}
+      else
+	{
+	  if (curlen >= min_len)
+	    {
+	      /* We found a match.  */
+	      if (unlikely (fname != NULL))
+		{
+		  fputs_unlocked (fname, stdout);
+		  fputs_unlocked (": ", stdout);
+		}
+
+	      if (unlikely (radix != radix_none))
+		printf ((radix == radix_octal ? "%7" PRIo64 " "
+			 : (radix == radix_decimal ? "%7" PRId64 " "
+			    : "%7" PRIx64 " ")),
+			(int64_t) to - len - (buf - start));
+
+	      if (unlikely (*unprinted != NULL))
+		{
+		  fputs_unlocked (*unprinted, stdout);
+		  free (*unprinted);
+		  *unprinted = NULL;
+		}
+
+	      /* There is no sane way of printing the string.  If we
+		 assume the file data is encoded in UCS-2/UTF-16 or
+		 UCS-4/UTF-32 respectively we could covert the string.
+		 But there is no such guarantee.  */
+	      fwrite_unlocked (start, 1, buf - start, stdout);
+	      putc_unlocked ('\n', stdout);
+	    }
+
+	  start = ++buf;
+	  curlen =  0;
+
+	  if (len <= min_len)
+	    break;
+	}
+
+      --len;
+    }
+
+  if (curlen != 0)
+    *unprinted = xstrndup ((const char *) start, curlen);
+}
+
+
+static void
+process_chunk (const char *fname, const unsigned char *buf, off_t to,
+	       size_t len, char **unprinted)
+{
+  /* We are not going to slow the check down for the 2- and 4-byte
+     encodings.  Handle them special.  */
+  if (unlikely (bytes_per_char != 1))
+    {
+      process_chunk_mb (fname, buf, to, len, unprinted);
+      return;
+    }
+
+  size_t curlen = *unprinted == NULL ? 0 : strlen (*unprinted);
+  const unsigned char *start = buf;
+  while (len > 0)
+    {
+      if ((isprint (*buf) || *buf == '\t') && (! char_7bit || *buf <= 127))
+	{
+	  ++buf;
+	  ++curlen;
+	}
+      else
+	{
+	  if (curlen >= min_len)
+	    {
+	      /* We found a match.  */
+	      if (likely (fname != NULL))
+		{
+		  fputs_unlocked (fname, stdout);
+		  fputs_unlocked (": ", stdout);
+		}
+
+	      if (likely (radix != radix_none))
+		printf ((radix == radix_octal ? "%7" PRIo64 " "
+			 : (radix == radix_decimal ? "%7" PRId64 " "
+			    : "%7" PRIx64 " ")),
+			(int64_t) to - len - (buf - start));
+
+	      if (unlikely (*unprinted != NULL))
+		{
+		  fputs_unlocked (*unprinted, stdout);
+		  free (*unprinted);
+		  *unprinted = NULL;
+		}
+	      fwrite_unlocked (start, 1, buf - start, stdout);
+	      putc_unlocked ('\n', stdout);
+	    }
+
+	  start = ++buf;
+	  curlen =  0;
+
+	  if (len <= min_len)
+	    break;
+	}
+
+      --len;
+    }
+
+  if (curlen != 0)
+    *unprinted = xstrndup ((const char *) start, curlen);
+}
+
+
+/* Map a file in as large chunks as possible.  */
+static void *
+map_file (int fd, off_t start_off, off_t fdlen, size_t *map_sizep)
+{
+  /* Maximum size we mmap.  We use an #ifdef to avoid overflows on
+     32-bit machines.  64-bit machines these days do not have usable
+     address spaces larger than about 43 bits.  Not that any file
+     should be that large.  */
+# if SIZE_MAX > 0xffffffff
+  const size_t mmap_max = 0x4000000000lu;
+# else
+  const size_t mmap_max = 0x40000000lu;
+# endif
+
+  /* Try to mmap the file.  */
+  size_t map_size = MIN ((off_t) mmap_max, fdlen);
+  const size_t map_size_min = MAX (MAX (SIZE_MAX / 16, 2 * ps),
+				   roundup (2 * min_len_bytes + 1, ps));
+  void *mem;
+  while (1)
+    {
+      /* We map the memory for reading only here.  Since we will
+	 always look at every byte of the file it makes sense to
+	 use MAP_POPULATE.  */
+      mem = mmap (NULL, map_size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
+		  fd, start_off);
+      if (mem != MAP_FAILED)
+	{
+	  /* We will go through the mapping sequentially.  */
+	  (void) posix_madvise (mem, map_size, POSIX_MADV_SEQUENTIAL);
+	  break;
+	}
+      if (errno != EINVAL && errno != ENOMEM)
+	/* This is an error other than the lack of address space.  */
+	break;
+
+      /* Maybe the size of the mapping is too big.  Try again.  */
+      map_size /= 2;
+      if (map_size < map_size_min)
+	/* That size should have fit.  */
+	break;
+    }
+
+  *map_sizep = map_size;
+  return mem;
+}
+
+
+/* Read the file without mapping.  */
+static int
+read_block_no_mmap (int fd, const char *fname, off_t from, off_t fdlen)
+{
+  char *unprinted = NULL;
+#define CHUNKSIZE 65536
+  unsigned char *buf = xmalloc (CHUNKSIZE + min_len_bytes
+				+ bytes_per_char - 1);
+  size_t ntrailer = 0;
+  int result = 0;
+  while (fdlen > 0)
+    {
+      ssize_t n = TEMP_FAILURE_RETRY (read (fd, buf + ntrailer,
+					    MIN (fdlen, CHUNKSIZE)));
+      if (n == 0)
+	{
+	  /* There are less than MIN_LEN+1 bytes left so there cannot be
+	     another match.  */
+	  assert (unprinted == NULL || ntrailer == 0);
+	  break;
+	}
+      if (unlikely (n < 0))
+	{
+	  /* Something went wrong.  */
+	  result = 1;
+	  break;
+	}
+
+      /* Account for the number of bytes read in this round.  */
+      fdlen -= n;
+
+      /* Do not use the signed N value.  Note that the addition cannot
+	 overflow.  */
+      size_t nb = (size_t) n + ntrailer;
+      if (nb >= min_len_bytes)
+	{
+	  /* We only use complete characters.  */
+	  nb &= ~(bytes_per_char - 1);
+
+	  process_chunk (fname, buf, from + nb, nb, &unprinted);
+
+	  /* If the last bytes of the buffer (modulo the character
+	     size) have been printed we are not copying them.  */
+	  size_t to_keep = unprinted != NULL ? 0 : min_len_bytes;
+
+	  memmove (buf, buf + nb - to_keep, to_keep);
+	  ntrailer = to_keep;
+	  from += nb;
+	}
+      else
+	ntrailer = nb;
+    }
+
+  free (buf);
+
+  /* Don't print anything we collected so far.  There is no
+     terminating NUL byte.  */
+  free (unprinted);
+
+  return result;
+}
+
+
+static int
+read_block (int fd, const char *fname, off_t fdlen, off_t from, off_t to)
+{
+  if (elfmap == NULL)
+    {
+      /* We need a completely new mapping.  */
+      elfmap_off = from & ~(ps - 1);
+      elfmap_base = elfmap = map_file (fd, elfmap_off, fdlen, &elfmap_size);
+
+      if (unlikely (elfmap == MAP_FAILED))
+	/* Let the kernel know we are going to read everything in sequence.  */
+	(void) posix_fadvise (fd, 0, 0, POSIX_FADV_SEQUENTIAL);
+    }
+
+  if (unlikely (elfmap == MAP_FAILED))
+    {
+      /* Read from the file descriptor.  For this we must position the
+	 read pointer.  */
+      // XXX Eventually add flag which avoids this if the position
+      // XXX is known to match.
+      if (from != 0 && lseek (fd, from, SEEK_SET) != from)
+	error (EXIT_FAILURE, errno, gettext ("lseek failed"));
+
+      return read_block_no_mmap (fd, fname, from, to - from);
+    }
+
+  assert ((off_t) min_len_bytes < fdlen);
+
+  if (to < (off_t) elfmap_off || from > (off_t) (elfmap_off + elfmap_size))
+    {
+      /* The existing mapping cannot fit at all.  Map the new area.
+	 We always map the full range of ELFMAP_SIZE bytes even if
+	 this extend beyond the end of the file.  The Linux kernel
+	 handles this OK if the access pages are not touched.  */
+      elfmap_off = from & ~(ps - 1);
+      if (mmap (elfmap, elfmap_size, PROT_READ,
+		MAP_PRIVATE | MAP_POPULATE | MAP_FIXED, fd, from)
+	  == MAP_FAILED)
+	error (EXIT_FAILURE, errno, gettext ("re-mmap failed"));
+      elfmap_base = elfmap;
+    }
+
+  char *unprinted = NULL;
+
+  /* Use the existing mapping as much as possible.  If necessary, map
+     new pages.  */
+  if (from >= (off_t) elfmap_off
+      && from < (off_t) (elfmap_off + elfmap_size))
+    /* There are at least a few bytes in this mapping which we can
+       use.  */
+    process_chunk (fname, elfmap_base + (from - elfmap_off),
+		   MIN (to, (off_t) (elfmap_off + elfmap_size)),
+		   MIN (to, (off_t) (elfmap_off + elfmap_size)) - from,
+		   &unprinted);
+
+  if (to > (off_t) (elfmap_off + elfmap_size))
+    {
+      unsigned char *remap_base = elfmap_base;
+      size_t read_now = elfmap_size - (elfmap_base - elfmap);
+
+      assert (from >= (off_t) elfmap_off
+	      && from < (off_t) (elfmap_off + elfmap_size));
+      off_t handled_to = elfmap_off + elfmap_size;
+      assert (elfmap == elfmap_base
+	      || (elfmap_base - elfmap
+		  == (ptrdiff_t) ((min_len_bytes + ps - 1) & ~(ps - 1))));
+      if (elfmap == elfmap_base)
+	{
+	  size_t keep_area = (min_len_bytes + ps - 1) & ~(ps - 1);
+	  assert (elfmap_size >= keep_area + ps);
+	  /* The keep area is used for the content of the previous
+	     buffer we have to keep.  This means copying those bytes
+	     and for this we have to make the data writable.  */
+	  if (unlikely (mprotect (elfmap, keep_area, PROT_READ | PROT_WRITE)
+			!= 0))
+	    error (EXIT_FAILURE, errno, gettext ("mprotect failed"));
+
+	  elfmap_base = elfmap + keep_area;
+	}
+
+      while (1)
+	{
+	  /* Map the rest of the file, eventually again in pieces.
+	     We speed things up with a nice Linux feature.  Note
+	     that we have at least two pages mapped.  */
+	  size_t to_keep = unprinted != NULL ? 0 : min_len_bytes;
+
+	  assert (read_now >= to_keep);
+	  memmove (elfmap_base - to_keep,
+		   remap_base + read_now - to_keep, to_keep);
+	  remap_base = elfmap_base;
+
+	  assert ((elfmap_size - (elfmap_base - elfmap)) % bytes_per_char
+		  == 0);
+	  read_now = MIN (to - handled_to,
+			  (ptrdiff_t) elfmap_size - (elfmap_base - elfmap));
+
+	  assert (handled_to % ps == 0);
+	  assert (handled_to % bytes_per_char == 0);
+	  if (mmap (remap_base, read_now, PROT_READ,
+		    MAP_PRIVATE | MAP_POPULATE | MAP_FIXED, fd, handled_to)
+	      == MAP_FAILED)
+	    error (EXIT_FAILURE, errno, gettext ("re-mmap failed"));
+	  elfmap_off = handled_to;
+
+	  process_chunk (fname, remap_base - to_keep,
+			 elfmap_off + (read_now & ~(bytes_per_char - 1)),
+			 to_keep + (read_now & ~(bytes_per_char - 1)),
+			 &unprinted);
+	  handled_to += read_now;
+	  if (handled_to >= to)
+	    break;
+	}
+    }
+
+  /* Don't print anything we collected so far.  There is no
+     terminating NUL byte.  */
+  free (unprinted);
+
+  return 0;
+}
+
+
+static int
+read_fd (int fd, const char *fname, off_t fdlen)
+{
+  return read_block (fd, fname, fdlen, 0, fdlen);
+}
+
+
+static int
+read_elf (Elf *elf, int fd, const char *fname, off_t fdlen)
+{
+  assert (fdlen >= 0);
+
+  /* We will look at each section separately.  The ELF file is not
+     mmapped.  The libelf implementation will load the needed parts on
+     demand.  Since we only interate over the section header table the
+     memory consumption at this stage is kept minimal.  */
+  Elf_Scn *scn = elf_nextscn (elf, NULL);
+  if (scn == NULL)
+    return read_fd (fd, fname, fdlen);
+
+  int result = 0;
+  do
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      /* Only look in sections which are loaded at runtime and
+	 actually have content.  */
+      if (shdr != NULL && shdr->sh_type != SHT_NOBITS
+	  && (shdr->sh_flags & SHF_ALLOC) != 0)
+	{
+	  if (shdr->sh_offset > (Elf64_Off) fdlen
+	      || fdlen - shdr->sh_offset < shdr->sh_size)
+	    {
+	      size_t strndx = 0;
+	      const char *sname;
+	      if (unlikely (elf_getshdrstrndx (elf, &strndx) < 0))
+		sname = "<unknown>";
+	      else
+		sname = elf_strptr (elf, strndx, shdr->sh_name) ?: "<unknown>";
+	      error (0, 0,
+		     gettext ("Skipping section %zd '%s' data outside file"),
+		     elf_ndxscn (scn), sname);
+	      result = 1;
+	    }
+	  else
+	    result |= read_block (fd, fname, fdlen, shdr->sh_offset,
+				  shdr->sh_offset + shdr->sh_size);
+	}
+    }
+  while ((scn = elf_nextscn (elf, scn)) != NULL);
+
+  if (elfmap != NULL && elfmap != MAP_FAILED)
+    munmap (elfmap, elfmap_size);
+  elfmap = NULL;
+
+  return result;
+}
+
+
+#include "debugpred.h"
diff --git a/src/strip.c b/src/strip.c
new file mode 100644
index 0000000..773ed54
--- /dev/null
+++ b/src/strip.c
@@ -0,0 +1,2427 @@
+/* Discard section not used at runtime from object files.
+   Copyright (C) 2000-2012, 2014, 2015, 2016, 2017 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Ulrich Drepper <drepper@redhat.com>, 2000.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <assert.h>
+#include <byteswap.h>
+#include <endian.h>
+#include <error.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <gelf.h>
+#include <libelf.h>
+#include <libintl.h>
+#include <locale.h>
+#include <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;
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+
+/* Values for the parameters which have no short form.  */
+#define OPT_REMOVE_COMMENT	0x100
+#define OPT_PERMISSIVE		0x101
+#define OPT_STRIP_SECTIONS	0x102
+#define OPT_RELOC_DEBUG 	0x103
+#define OPT_KEEP_SECTION 	0x104
+
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, 0, N_("Output selection:"), 0 },
+  { "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 reversable, needs -f)"), 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 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("Discard symbols from object files.");
+
+/* 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 timespec tvp[2]);
+
+/* Handle all files contained in the archive.  */
+static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname,
+		      struct timespec tvp[2]) __attribute__ ((unused));
+
+static int debug_fd = -1;
+static char *tmp_debug_fname = NULL;
+
+/* Close debug file descriptor, if opened. And remove temporary debug file.  */
+static void cleanup_debug (void);
+
+#define INTERNAL_ERROR(fname) \
+  do { \
+    cleanup_debug (); \
+    error (EXIT_FAILURE, 0, gettext ("%s: INTERNAL ERROR %d (%s): %s"),      \
+	   fname, __LINE__, PACKAGE_VERSION, elf_errmsg (-1)); \
+  } while (0)
+
+
+/* Name of the output file.  */
+static const char *output_fname;
+
+/* Name of the debug output file.  */
+static const char *debug_fname;
+
+/* Name to pretend the debug output file has.  */
+static const char *debug_fname_embed;
+
+/* If true output files shall have same date as the input file.  */
+static bool preserve_dates;
+
+/* If true .comment sections will be removed.  */
+static bool remove_comment;
+
+/* If true remove all debug sections.  */
+static bool remove_debug;
+
+/* If true remove all section headers.  */
+static bool remove_shdrs;
+
+/* If true relax some ELF rules for input files.  */
+static bool permissive;
+
+/* If true perform relocations between debug sections.  */
+static bool reloc_debug;
+
+/* Sections the user explicitly wants to keep or remove.  */
+struct section_pattern
+{
+  char *pattern;
+  struct section_pattern *next;
+};
+
+static struct section_pattern *keep_secs = NULL;
+static struct section_pattern *remove_secs = NULL;
+
+static void
+add_pattern (struct section_pattern **patterns, const char *pattern)
+{
+  struct section_pattern *p = xmalloc (sizeof *p);
+  p->pattern = xstrdup (pattern);
+  p->next = *patterns;
+  *patterns = p;
+}
+
+static void
+free_sec_patterns (struct section_pattern *patterns)
+{
+  struct section_pattern *pattern = patterns;
+  while (pattern != NULL)
+    {
+      struct section_pattern *p = pattern;
+      pattern = p->next;
+      free (p->pattern);
+      free (p);
+    }
+}
+
+static void
+free_patterns (void)
+{
+  free_sec_patterns (keep_secs);
+  free_sec_patterns (remove_secs);
+}
+
+static bool
+section_name_matches (struct section_pattern *patterns, const char *name)
+{
+  struct section_pattern *pattern = patterns;
+  while (pattern != NULL)
+    {
+      if (fnmatch (pattern->pattern, name, FNM_EXTMATCH) == 0)
+	return true;
+      pattern = pattern->next;
+    }
+  return false;
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  int remaining;
+  int result = 0;
+
+  /* We use no threads here which can interfere with handling a stream.  */
+  __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+  __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+  __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  if (argp_parse (&argp, argc, argv, 0, &remaining, NULL) != 0)
+    return EXIT_FAILURE;
+
+  if (reloc_debug && debug_fname == NULL)
+    error (EXIT_FAILURE, 0,
+	   gettext ("--reloc-debug-sections used without -f"));
+
+  /* Tell the library which version we are expecting.  */
+  elf_version (EV_CURRENT);
+
+  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 || debug_fname != NULL)
+	  && remaining + 1 < argc)
+	error (EXIT_FAILURE, 0, gettext ("\
+Only one input file allowed together with '-o' and '-f'"));
+
+      /* Process all the remaining files.  */
+      do
+	result |= process_file (argv[remaining]);
+      while (++remaining < argc);
+    }
+
+  free_patterns ();
+  return result;
+}
+
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+  switch (key)
+    {
+    case 'f':
+      if (debug_fname != NULL)
+	{
+	  error (0, 0, gettext ("-f option specified twice"));
+	  return EINVAL;
+	}
+      debug_fname = arg;
+      break;
+
+    case 'F':
+      if (debug_fname_embed != NULL)
+	{
+	  error (0, 0, gettext ("-F option specified twice"));
+	  return EINVAL;
+	}
+      debug_fname_embed = arg;
+      break;
+
+    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_RELOC_DEBUG:
+      reloc_debug = 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':			/* Ignored for compatibility.  */
+      break;
+
+    case ARGP_KEY_SUCCESS:
+      if (remove_comment == true
+	  && section_name_matches (keep_secs, ".comment"))
+	{
+	  argp_error (state,
+		      gettext ("cannot both keep and remove .comment section"));
+	  return EINVAL;
+	}
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  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 stat pre_st;
+  struct timespec tv[2];
+ again:
+  if (preserve_dates)
+    {
+      if (stat (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.  */
+      tv[0] = pre_st.st_atim;
+      tv[1] = pre_st.st_mtim;
+    }
+
+  /* Open the file.  */
+  int fd = open (fname, output_fname == NULL ? O_RDWR : O_RDONLY);
+  if (fd == -1)
+    {
+      error (0, errno, gettext ("while opening '%s'"), fname);
+      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 stat st;
+  if (fstat (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 || debug_fname != NULL))
+	{
+	  error (0, 0, gettext ("%s: cannot use -o or -f when stripping archive"),
+		 fname);
+	  result = 1;
+	}
+      else
+	{
+	  /* We would like to support ar archives, but currently it just
+	     doesn't work at all since we call elf_clone on the members
+	     which doesn't really support ar members.
+	     result = handle_ar (fd, elf, NULL, fname,
+				 preserve_dates ? tv : NULL);
+	   */
+	  error (0, 0, gettext ("%s: no support for stripping archive"),
+		 fname);
+	  result = 1;
+	}
+      break;
+
+    default:
+      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;
+}
+
+
+/* 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 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;
+  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;		/* 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;
+    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;
+
+  /* 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;
+	}
+    }
+
+  debug_fd = -1;
+
+  /* Get the EBL handling.  Removing all debugging symbols with the -g
+     option or resolving all relocations between debug sections with
+     the --reloc-debug-sections option are currently the only reasons
+     we need EBL so don't open the backend unless necessary.  */
+  Ebl *ebl = NULL;
+  if (remove_debug || reloc_debug)
+    {
+      ebl = ebl_openbackend (elf);
+      if (ebl == NULL)
+	{
+	  error (0, errno, gettext ("cannot open EBL backend"));
+	  result = 1;
+	  goto fail;
+	}
+    }
+
+  /* Open the additional file the debug information will be stored in.  */
+  if (debug_fname != NULL)
+    {
+      /* Create a temporary file name.  We do not want to overwrite
+	 the debug file if the file would not contain any
+	 information.  */
+      size_t debug_fname_len = strlen (debug_fname);
+      tmp_debug_fname = (char *) 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, gettext ("cannot open '%s'"), debug_fname);
+	  result = 1;
+	  goto fail;
+	}
+    }
+
+  /* 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_getshdrstrndx (elf, &shstrndx) < 0))
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, 0,
+	     gettext ("cannot get section header string table index"));
+    }
+
+  /* Get the number of phdrs in the old file.  */
+  size_t phnum;
+  if (elf_getphdrnum (elf, &phnum) != 0)
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, 0, gettext ("cannot get number of phdrs"));
+    }
+
+  /* We now create a new ELF descriptor for the same file.  We
+     construct it almost exactly in the same way with some information
+     dropped.  */
+  Elf *newelf;
+  if (output_fname != NULL)
+    newelf = elf_begin (fd, ELF_C_WRITE_MMAP, NULL);
+  else
+    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, phnum) == 0)))
+    {
+      error (0, 0, gettext ("cannot create new file '%s': %s"),
+	     output_fname ?: fname, elf_errmsg (-1));
+      goto fail;
+    }
+
+  /* Copy over the old program header if needed.  */
+  if (ehdr->e_type != ET_REL)
+    for (cnt = 0; cnt < 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 (debug_fname != NULL)
+    {
+      /* Also create an ELF descriptor for the debug file */
+      debugelf = elf_begin (debug_fd, ELF_C_WRITE_MMAP, NULL);
+      if (unlikely (gelf_newehdr (debugelf, gelf_getclass (elf)) == 0)
+	  || (ehdr->e_type != ET_REL
+	      && unlikely (gelf_newphdr (debugelf, phnum) == 0)))
+	{
+	  error (0, 0, gettext ("cannot create new file '%s': %s"),
+		 debug_fname, elf_errmsg (-1));
+	  goto fail_close;
+	}
+
+      /* Copy over the old program header if needed.  */
+      if (ehdr->e_type != ET_REL)
+	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);
+	  }
+    }
+
+  /* Number of sections.  */
+  size_t shnum;
+  if (unlikely (elf_getshdrnum (elf, &shnum) < 0))
+    {
+      error (0, 0, gettext ("cannot determine number of sections: %s"),
+	     elf_errmsg (-1));
+      goto fail_close;
+    }
+
+  if (shstrndx >= shnum)
+    goto illformed;
+
+#define elf_assert(test) do { if (!(test)) goto illformed; } while (0)
+
+  /* Storage for section information.  We leave room for two more
+     entries since we unconditionally create a section header string
+     table.  Maybe some weird tool created an ELF file without one.
+     The other one is used for the debug link section.  */
+  if ((shnum + 2) * sizeof (struct shdr_info) > MAX_STACK_ALLOC)
+    shdr_info = (struct 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));
+    }
+
+  /* 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;
+	}
+
+      /* Sanity check the user.  */
+      if (section_name_matches (remove_secs, shdr_info[cnt].name))
+	{
+	  if ((shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) != 0)
+	    {
+	      error (0, 0,
+		     gettext ("Cannot remove allocated section '%s'"),
+		     shdr_info[cnt].name);
+	      result = 1;
+	      goto fail_close;
+	    }
+
+	  if (section_name_matches (keep_secs, shdr_info[cnt].name))
+	    {
+	      error (0, 0,
+		     gettext ("Cannot both keep and remove section '%s'"),
+		     shdr_info[cnt].name);
+	      result = 1;
+	      goto fail_close;
+	    }
+	}
+
+      /* Mark them as present but not yet investigated.  */
+      shdr_info[cnt].idx = 1;
+
+      /* Remember the shdr.sh_link value.  */
+      shdr_info[cnt].old_sh_link = shdr_info[cnt].shdr.sh_link;
+      if (shdr_info[cnt].old_sh_link >= shnum)
+	goto illformed;
+
+      /* Sections in files other than relocatable object files which
+	 not loaded can be freely moved by us.  In theory we can also
+	 freely move around allocated nobits sections.  But we don't
+	 to keep the layout of all allocated sections as similar as
+	 possible to the original file.  In relocatable object files
+	 everything can be moved.  */
+      if (ehdr->e_type == ET_REL
+	  || (shdr_info[cnt].shdr.sh_flags & SHF_ALLOC) == 0)
+	shdr_info[cnt].shdr.sh_offset = 0;
+
+      /* 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
+	      || shdr_info[cnt].data->d_size < sizeof (Elf32_Word))
+	    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;
+    }
+
+  /* Now determine which sections can go away.  The general rule is that
+     all sections which are not used at runtime are stripped out.  But
+     there are a few exceptions:
+
+     - special sections named ".comment" and ".note" are kept
+     - OS or architecture specific sections are kept since we might not
+       know how to handle them
+     - if a section is referred to from a section which is not removed
+       in the sh_link or sh_info element it cannot be removed either
+     - the user might have explicitly said to remove or keep a section
+  */
+  for (cnt = 1; cnt < shnum; ++cnt)
+    /* Check whether the section can be removed.  Since we will create
+       a new .shstrtab assume it will be removed too.  */
+    if (remove_shdrs ? !(shdr_info[cnt].shdr.sh_flags & SHF_ALLOC)
+	: (ebl_section_strip_p (ebl, ehdr, &shdr_info[cnt].shdr,
+				shdr_info[cnt].name, remove_comment,
+				remove_debug)
+	   || cnt == ehdr->e_shstrndx
+	   || section_name_matches (remove_secs, shdr_info[cnt].name)))
+      {
+	/* The user might want to explicitly keep this one.  */
+	if (section_name_matches (keep_secs, shdr_info[cnt].name))
+	  continue;
+
+	/* For now assume this section will be removed.  */
+	shdr_info[cnt].idx = 0;
+
+	idx = shdr_info[cnt].group_idx;
+	while (idx != 0)
+	  {
+	    /* The section group data is already loaded.  */
+	    elf_assert (shdr_info[idx].data != NULL
+			&& shdr_info[idx].data->d_buf != NULL
+			&& shdr_info[idx].data->d_size >= sizeof (Elf32_Word));
+
+	    /* If the references section group is a normal section
+	       group and has one element remaining, or if it is an
+	       empty COMDAT section group it is removed.  */
+	    bool is_comdat = (((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;
+		/* Continue recursively.  */
+		idx = shdr_info[idx].group_idx;
+	      }
+	    else
+	      break;
+	  }
+      }
+
+  /* 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 == 0)
+	    {
+	      /* If a relocation section is marked as being removed make
+		 sure the section it is relocating is removed, too.  */
+	      if (shdr_info[cnt].shdr.sh_type == SHT_REL
+		   || 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 a group section is marked as being removed make
+		 sure all the sections it contains are being removed, too.  */
+	      if (shdr_info[cnt].shdr.sh_type == SHT_GROUP)
+		{
+		  Elf32_Word *grpref;
+		  grpref = (Elf32_Word *) shdr_info[cnt].data->d_buf;
+		  for (size_t in = 1;
+		       in < shdr_info[cnt].data->d_size / sizeof (Elf32_Word);
+		       ++in)
+		    if (grpref[in] < shnum)
+		      {
+			if (shdr_info[grpref[in]].idx != 0)
+			  {
+			    shdr_info[cnt].idx = 1;
+			    break;
+			  }
+		      }
+		    else
+		      goto illformed;
+		}
+	    }
+
+	  if (shdr_info[cnt].idx == 1)
+	    {
+	      /* 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 (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))
+		{
+		  /* 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, 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)
+			  /* 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)
+			/* This symbol table has a real symbol in
+			   a discarded section.  So preserve the
+			   original table in the debug file.  Unless
+			   it is a redundant data marker to a debug
+			   (data only) section.  */
+			if (! (ebl_section_strip_p (ebl, ehdr,
+						    &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;
+		    }
+		}
+
+	      /* 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;
+	    }
+
+	  if (debug_fname != NULL
+	      && (shdr_info[cnt].idx == 0 || shdr_info[cnt].debug_data != NULL))
+	    {
+	      /* This section is being preserved in the debug file.
+		 Sections it refers to must be preserved there too.
+
+		 In this pass we mark sections to be preserved in both
+		 files by setting the .debug_data pointer to the original
+		 file's .data pointer.  Below, we'll copy the section
+		 contents.  */
+
+	      inline void check_preserved (size_t i)
+	      {
+		if (i != 0 && i < shnum + 2 && shdr_info[i].idx != 0
+		    && shdr_info[i].debug_data == NULL)
+		  {
+		    if (shdr_info[i].data == NULL)
+		      shdr_info[i].data = elf_getdata (shdr_info[i].scn, NULL);
+		    if (shdr_info[i].data == NULL)
+		      INTERNAL_ERROR (fname);
+
+		    shdr_info[i].debug_data = shdr_info[i].data;
+		    changes |= i < cnt;
+		  }
+	      }
+
+	      check_preserved (shdr_info[cnt].shdr.sh_link);
+	      if (SH_INFO_LINK_P (&shdr_info[cnt].shdr))
+		check_preserved (shdr_info[cnt].shdr.sh_info);
+	    }
+	}
+    }
+  while (changes);
+
+  /* Copy the removed sections to the debug output file.
+     The ones that are not removed in the stripped file are SHT_NOBITS.  */
+  if (debug_fname != NULL)
+    {
+      for (cnt = 1; cnt < shnum; ++cnt)
+	{
+	  scn = elf_newscn (debugelf);
+	  if (scn == NULL)
+	    {
+	      cleanup_debug ();
+	      error (EXIT_FAILURE, 0,
+		     gettext ("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 != ehdr->e_shstrndx);
+
+	  /* Set the section header in the new file.  */
+	  GElf_Shdr debugshdr = shdr_info[cnt].shdr;
+	  if (discard_section)
+	    debugshdr.sh_type = SHT_NOBITS;
+
+	  if (unlikely (gelf_update_shdr (scn, &debugshdr) == 0))
+	    /* There cannot be any overflows.  */
+	    INTERNAL_ERROR (fname);
+
+	  /* Get the data from the old file if necessary. */
+	  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.  */
+	  Elf_Data *debugdata = elf_newdata (scn);
+	  if (debugdata == NULL)
+	    INTERNAL_ERROR (fname);
+
+	  /* Copy the structure.  This data may be modified in place
+	     before we write out the file.  */
+	  *debugdata = *shdr_info[cnt].data;
+	  if (discard_section)
+	    debugdata->d_buf = NULL;
+	  else if (shdr_info[cnt].debug_data != NULL
+		   || shdr_info[cnt].shdr.sh_type == SHT_GROUP)
+	    {
+	      /* Copy the original data before it gets modified.  */
+	      shdr_info[cnt].debug_data = debugdata;
+	      if (debugdata->d_buf == NULL)
+		INTERNAL_ERROR (fname);
+	      debugdata->d_buf = memcpy (xmalloc (debugdata->d_size),
+					 debugdata->d_buf, debugdata->d_size);
+	    }
+	}
+
+      /* Finish the ELF header.  Fill in the fields not handled by
+	 libelf from the old file.  */
+      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;
+      debugehdr->e_shstrndx = ehdr->e_shstrndx;
+
+      if (unlikely (gelf_update_ehdr (debugelf, debugehdr) == 0))
+	{
+	  error (0, 0, gettext ("%s: error while creating ELF header: %s"),
+		 debug_fname, elf_errmsg (-1));
+	  result = 1;
+	  goto fail_close;
+	}
+    }
+
+  /* Although we always create a new section header string table we
+     don't explicitly mark the existing one as unused.  It can still
+     be used through a symbol table section we are keeping.  If not it
+     will already be marked as unused.  */
+
+  /* We need a string table for the section headers.  */
+  shst = dwelf_strtab_init (true);
+  if (shst == NULL)
+    {
+      cleanup_debug ();
+      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)
+	  {
+	    cleanup_debug ();
+	    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 = dwelf_strtab_add (shst, shdr_info[cnt].name);
+      }
+
+  /* Test whether we are doing anything at all.  Either all removable
+     sections are already gone.  Or the only section we would remove is
+     the .shstrtab section which we would add again.  */
+  bool removing_sections = !(cnt == idx
+			     || (cnt == idx + 1
+				 && shdr_info[ehdr->e_shstrndx].idx == 0));
+  if (output_fname == NULL && !removing_sections)
+      goto fail_close;
+
+  /* Create the reference to the file with the debug info (if any).  */
+  if (debug_fname != NULL && !remove_shdrs && removing_sections)
+    {
+      /* Add the section header string table section name.  */
+      shdr_info[cnt].se = dwelf_strtab_add_len (shst, ".gnu_debuglink", 15);
+      shdr_info[cnt].idx = idx++;
+
+      /* Create the section header.  */
+      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;
+      /* 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;
+
+      /* Create the section.  */
+      shdr_info[cnt].newscn = elf_newscn (newelf);
+      if (shdr_info[cnt].newscn == NULL)
+	{
+	  cleanup_debug ();
+	  error (EXIT_FAILURE, 0,
+		 gettext ("while create section header section: %s"),
+		 elf_errmsg (-1));
+	}
+      elf_assert (elf_ndxscn (shdr_info[cnt].newscn) == shdr_info[cnt].idx);
+
+      shdr_info[cnt].data = elf_newdata (shdr_info[cnt].newscn);
+      if (shdr_info[cnt].data == NULL)
+	{
+	  cleanup_debug ();
+	  error (EXIT_FAILURE, 0, gettext ("cannot allocate section data: %s"),
+		 elf_errmsg (-1));
+	}
+
+      char *debug_basename = basename (debug_fname_embed ?: debug_fname);
+      off_t crc_offset = strlen (debug_basename) + 1;
+      /* Align to 4 byte boundary */
+      crc_offset = ((crc_offset - 1) & ~3) + 4;
+
+      shdr_info[cnt].data->d_align = 4;
+      shdr_info[cnt].shdr.sh_size = shdr_info[cnt].data->d_size
+	= crc_offset + 4;
+      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);
+
+      /* Cache this Elf_Data describing the CRC32 word in the section.
+	 We'll fill this in when we have written the debug file.  */
+      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;
+
+      /* One more section done.  */
+      ++cnt;
+    }
+
+  /* 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 = dwelf_strtab_add_len (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)
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, 0,
+	     gettext ("while create section header section: %s"),
+	     elf_errmsg (-1));
+    }
+  elf_assert (elf_ndxscn (shdr_info[cnt].newscn) == idx);
+
+  /* Finalize the string table and fill in the correct indices in the
+     section headers.  */
+  shstrtab_data = elf_newdata (shdr_info[cnt].newscn);
+  if (shstrtab_data == NULL)
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, 0,
+	     gettext ("while create section header string table: %s"),
+	     elf_errmsg (-1));
+    }
+  if (dwelf_strtab_finalize (shst, shstrtab_data) == NULL)
+    {
+      cleanup_debug ();
+      error (EXIT_FAILURE, 0,
+	     gettext ("no memory to create section header string table"));
+    }
+
+  /* We have to set the section size.  */
+  shdr_info[cnt].shdr.sh_size = shstrtab_data->d_size;
+
+  /* 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);
+	elf_assert (scn != NULL);
+
+	/* Update the name.  */
+	shdr_info[cnt].shdr.sh_name = dwelf_strent_off (shdr_info[cnt].se);
+
+	/* Update the section header from the input file.  Some fields
+	   might be section indeces which now have to be adjusted.  Keep
+	   the index to the "current" sh_link in case we need it to lookup
+	   symbol table names.  */
+	size_t sh_link = shdr_info[cnt].shdr.sh_link;
+	if (shdr_info[cnt].shdr.sh_link != 0)
+	  shdr_info[cnt].shdr.sh_link =
+	    shdr_info[shdr_info[cnt].shdr.sh_link].idx;
+
+	if (shdr_info[cnt].shdr.sh_type == SHT_GROUP)
+	  {
+	    elf_assert (shdr_info[cnt].data != NULL
+			&& shdr_info[cnt].data->d_buf != NULL);
+
+	    Elf32_Word *grpref = (Elf32_Word *) shdr_info[cnt].data->d_buf;
+	    for (size_t inner = 0;
+		 inner < shdr_info[cnt].data->d_size / sizeof (Elf32_Word);
+		 ++inner)
+	      if (grpref[inner] < shnum)
+		grpref[inner] = shdr_info[grpref[inner]].idx;
+	      else
+		goto illformed;
+	  }
+
+	/* Handle the SHT_REL, SHT_RELA, and SHF_INFO_LINK flag.  */
+	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 symbol tables.  The st_shndx member might
+	       have to be updated.  */
+	    if (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM
+		|| shdr_info[cnt].shdr.sh_type == SHT_SYMTAB)
+	      {
+		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);
+		    /* 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 (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);
+		    /* 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 != NULL
+				&& versiondata->d_buf != NULL
+				&& ((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)
+		      elf_assert (shndxdata != NULL
+				  && shndxdata->d_buf != NULL);
+		    size_t sidx = (sym->st_shndx != SHN_XINDEX
+				   ? sym->st_shndx : xshndx);
+		    sec = shdr_info[sidx].idx;
+
+		    if (sec != 0)
+		      {
+			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)
+		      {
+			/* Removing a real symbol from an allocated
+			   symbol table is hard and probably a
+			   mistake.  Really removing it means
+			   rewriting the dynamic segment and hash
+			   sections.  Just warn and set the symbol
+			   section to UNDEF.  */
+			error (0, 0,
+			       gettext ("Cannot remove symbol [%zd] from allocated symbol table [%zd]"), inner, cnt);
+			sym->st_shndx = SHN_UNDEF;
+			if (gelf_update_sym (shdr_info[cnt].data, destidx,
+					     sym) == 0)
+			  INTERNAL_ERROR (fname);
+			shdr_info[cnt].newsymidx[inner] = destidx++;
+		      }
+		    else if (debug_fname != NULL
+			     && shdr_info[cnt].debug_data == NULL)
+		      /* The symbol points to a section that is discarded
+			 but isn't preserved in the debug file. Check that
+			 this is a section or group signature symbol
+			 for a section which has been removed.  Or a special
+			 data marker symbol to a debug section.  */
+		      {
+			elf_assert (GELF_ST_TYPE (sym->st_info) == STT_SECTION
+				    || ((shdr_info[sidx].shdr.sh_type
+					 == SHT_GROUP)
+					&& (shdr_info[sidx].shdr.sh_info
+					    == inner))
+				    || ebl_data_marker_symbol (ebl, sym,
+						elf_strptr (elf, sh_link,
+							    sym->st_name)));
+		      }
+		  }
+
+		if (destidx != inner)
+		  {
+		    /* 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)
+      {
+	/* Update section headers when the data size has changed.
+	   We also update the SHT_NOBITS section in the debug
+	   file so that the section headers match in sh_size.  */
+	inline void update_section_size (const Elf_Data *newdata)
+	{
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	  shdr->sh_size = newdata->d_size;
+	  (void) gelf_update_shdr (scn, shdr);
+	  if (debugelf != NULL)
+	    {
+	      /* libelf will use d_size to set sh_size.  */
+	      Elf_Data *debugdata = elf_getdata (elf_getscn (debugelf,
+							     cnt), NULL);
+	      if (debugdata == NULL)
+		INTERNAL_ERROR (fname);
+	      debugdata->d_size = newdata->d_size;
+	    }
+	}
+
+	if (shdr_info[cnt].idx == 0 && debug_fname == NULL)
+	  /* Ignore sections which are discarded.  When we are saving a
+	     relocation section in a separate debug file, we must fix up
+	     the symbol table references.  */
+	  continue;
+
+	const Elf32_Word symtabidx = shdr_info[cnt].old_sh_link;
+	elf_assert (symtabidx < shnum + 2);
+	const Elf32_Word *const newsymidx = shdr_info[symtabidx].newsymidx;
+	switch (shdr_info[cnt].shdr.sh_type)
+	  {
+	    inline bool no_symtab_updates (void)
+	    {
+	      /* If the symbol table hasn't changed, do not do anything.  */
+	      if (shdr_info[symtabidx].newsymidx == NULL)
+		return true;
+
+	      /* If the symbol table is not discarded, but additionally
+		 duplicated in the separate debug file and this section
+		 is discarded, don't adjust anything.  */
+	      return (shdr_info[cnt].idx == 0
+		      && shdr_info[symtabidx].debug_data != NULL);
+	    }
+
+	  case SHT_REL:
+	  case SHT_RELA:
+	    if (no_symtab_updates ())
+	      break;
+
+	    Elf_Data *d = elf_getdata (shdr_info[cnt].idx == 0
+				       ? elf_getscn (debugelf, cnt)
+				       : elf_getscn (newelf,
+						     shdr_info[cnt].idx),
+				       NULL);
+	    elf_assert (d != NULL && d->d_buf != NULL
+			&& shdr_info[cnt].shdr.sh_entsize != 0);
+	    size_t nrels = (shdr_info[cnt].shdr.sh_size
+			    / shdr_info[cnt].shdr.sh_entsize);
+
+	    size_t symsize = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
+	    const Elf32_Word symidxn = (shdr_info[symtabidx].data->d_size
+					/ symsize);
+	    if (shdr_info[cnt].shdr.sh_type == SHT_REL)
+	      for (size_t relidx = 0; relidx < nrels; ++relidx)
+		{
+		  GElf_Rel rel_mem;
+		  if (gelf_getrel (d, relidx, &rel_mem) == NULL)
+		    INTERNAL_ERROR (fname);
+
+		  size_t symidx = GELF_R_SYM (rel_mem.r_info);
+		  elf_assert (symidx < symidxn);
+		  if (newsymidx[symidx] != symidx)
+		    {
+		      rel_mem.r_info
+			= GELF_R_INFO (newsymidx[symidx],
+				       GELF_R_TYPE (rel_mem.r_info));
+
+		      if (gelf_update_rel (d, relidx, &rel_mem) == 0)
+			INTERNAL_ERROR (fname);
+		    }
+		}
+	    else
+	      for (size_t relidx = 0; relidx < nrels; ++relidx)
+		{
+		  GElf_Rela rel_mem;
+		  if (gelf_getrela (d, relidx, &rel_mem) == NULL)
+		    INTERNAL_ERROR (fname);
+
+		  size_t symidx = GELF_R_SYM (rel_mem.r_info);
+		  elf_assert (symidx < symidxn);
+		  if (newsymidx[symidx] != symidx)
+		    {
+		      rel_mem.r_info
+			= GELF_R_INFO (newsymidx[symidx],
+				       GELF_R_TYPE (rel_mem.r_info));
+
+		      if (gelf_update_rela (d, relidx, &rel_mem) == 0)
+			INTERNAL_ERROR (fname);
+		    }
+		}
+	    break;
+
+	  case SHT_HASH:
+	    if (no_symtab_updates ())
+	      break;
+
+	    /* We have to recompute the hash table.  */
+
+	    elf_assert (shdr_info[cnt].idx > 0);
+
+	    /* The hash section in the new file.  */
+	    scn = elf_getscn (newelf, shdr_info[cnt].idx);
+
+	    /* The symbol table data.  */
+	    Elf_Data *symd = elf_getdata (elf_getscn (newelf,
+						      shdr_info[symtabidx].idx),
+					  NULL);
+	    elf_assert (symd != NULL && symd->d_buf != NULL);
+
+	    /* The hash table data.  */
+	    Elf_Data *hashd = elf_getdata (scn, NULL);
+	    elf_assert (hashd != NULL && hashd->d_buf != NULL);
+
+	    if (shdr_info[cnt].shdr.sh_entsize == sizeof (Elf32_Word))
+	      {
+		/* Sane arches first.  */
+		elf_assert (hashd->d_size >= 2 * sizeof (Elf32_Word));
+		Elf32_Word *bucket = (Elf32_Word *) hashd->d_buf;
+
+		size_t strshndx = shdr_info[symtabidx].old_sh_link;
+		size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
+
+		Elf32_Word nchain = bucket[1];
+		Elf32_Word nbucket = bucket[0];
+		uint64_t used_buf = ((2ULL + nchain + nbucket)
+				     * sizeof (Elf32_Word));
+		elf_assert (used_buf <= hashd->d_size);
+
+		/* Adjust the nchain value.  The symbol table size
+		   changed.  We keep the same size for the bucket array.  */
+		bucket[1] = symd->d_size / elsize;
+		bucket += 2;
+		Elf32_Word *chain = bucket + nbucket;
+
+		/* New size of the section.  */
+		size_t n_size = ((2 + symd->d_size / elsize + nbucket)
+				 * sizeof (Elf32_Word));
+		elf_assert (n_size <= hashd->d_size);
+		hashd->d_size = n_size;
+		update_section_size (hashd);
+
+		/* Clear the arrays.  */
+		memset (bucket, '\0',
+			(symd->d_size / elsize + nbucket)
+			* sizeof (Elf32_Word));
+
+		for (size_t inner = shdr_info[symtabidx].shdr.sh_info;
+		     inner < symd->d_size / elsize; ++inner)
+		  {
+		    GElf_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
+	      {
+		/* Alpha and S390 64-bit use 64-bit SHT_HASH entries.  */
+		elf_assert (shdr_info[cnt].shdr.sh_entsize
+			    == sizeof (Elf64_Xword));
+
+		Elf64_Xword *bucket = (Elf64_Xword *) hashd->d_buf;
+
+		size_t strshndx = shdr_info[symtabidx].old_sh_link;
+		size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
+
+		elf_assert (symd->d_size >= 2 * sizeof (Elf64_Xword));
+		Elf64_Xword nbucket = bucket[0];
+		Elf64_Xword nchain = bucket[1];
+		uint64_t maxwords = hashd->d_size / sizeof (Elf64_Xword);
+		elf_assert (maxwords >= 2
+			    && maxwords - 2 >= nbucket
+			    && maxwords - 2 - nbucket >= nchain);
+
+		/* Adjust the nchain value.  The symbol table size
+		   changed.  We keep the same size for the bucket array.  */
+		bucket[1] = symd->d_size / elsize;
+		bucket += 2;
+		Elf64_Xword *chain = bucket + nbucket;
+
+		/* New size of the section.  */
+		size_t n_size = ((2 + symd->d_size / elsize + nbucket)
+				 * sizeof (Elf64_Xword));
+		elf_assert (n_size <= hashd->d_size);
+		hashd->d_size = n_size;
+		update_section_size (hashd);
+
+		/* Clear the arrays.  */
+		memset (bucket, '\0',
+			(symd->d_size / elsize + nbucket)
+			* sizeof (Elf64_Xword));
+
+		for (size_t inner = shdr_info[symtabidx].shdr.sh_info;
+		     inner < symd->d_size / elsize; ++inner)
+		  {
+		    GElf_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:
+	    /* If the symbol table changed we have to adjust the entries.  */
+	    if (no_symtab_updates ())
+	      break;
+
+	    elf_assert (shdr_info[cnt].idx > 0);
+
+	    /* The symbol version section in the new file.  */
+	    scn = elf_getscn (newelf, shdr_info[cnt].idx);
+
+	    /* The symbol table data.  */
+	    symd = elf_getdata (elf_getscn (newelf, shdr_info[symtabidx].idx),
+				NULL);
+	    elf_assert (symd != NULL && symd->d_buf != NULL);
+	    size_t symz = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
+	    const Elf32_Word syms = (shdr_info[symtabidx].data->d_size / symz);
+
+	    /* The version symbol data.  */
+	    Elf_Data *verd = elf_getdata (scn, NULL);
+	    elf_assert (verd != NULL && verd->d_buf != NULL);
+
+	    /* The symbol version array.  */
+	    GElf_Half *verstab = (GElf_Half *) verd->d_buf;
+
+	    /* Walk through the list and */
+	    size_t elsize = gelf_fsize (elf, verd->d_type, 1, EV_CURRENT);
+	    Elf32_Word vers = verd->d_size / elsize;
+	    for (size_t inner = 1; inner < vers && inner < syms; ++inner)
+	      if (newsymidx[inner] != 0 && newsymidx[inner] < vers)
+		/* Overwriting the same array works since the
+		   reordering can only move entries to lower indices
+		   in the array.  */
+		verstab[newsymidx[inner]] = verstab[inner];
+
+	    /* New size of the section.  */
+	    verd->d_size = gelf_fsize (newelf, verd->d_type,
+				       symd->d_size
+				       / gelf_fsize (elf, symd->d_type, 1,
+						     EV_CURRENT),
+				       EV_CURRENT);
+	    update_section_size (verd);
+	    break;
+
+	  case SHT_GROUP:
+	    if (no_symtab_updates ())
+	      break;
+
+	    /* Yes, the symbol table changed.
+	       Update the section header of the section group.  */
+	    scn = elf_getscn (newelf, shdr_info[cnt].idx);
+	    GElf_Shdr shdr_mem;
+	    GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	    elf_assert (shdr != NULL);
+
+	    size_t symsz = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
+	    const Elf32_Word symn = (shdr_info[symtabidx].data->d_size
+				     / symsz);
+	    elf_assert (shdr->sh_info < symn);
+	    shdr->sh_info = newsymidx[shdr->sh_info];
+
+	    (void) gelf_update_shdr (scn, shdr);
+	    break;
+	  }
+      }
+
+  /* Remove any relocations between debug sections in ET_REL
+     for the debug file when requested.  These relocations are always
+     zero based between the unallocated sections.  */
+  if (debug_fname != NULL && removing_sections
+      && reloc_debug && ehdr->e_type == ET_REL)
+    {
+      scn = NULL;
+      cnt = 0;
+      while ((scn = elf_nextscn (debugelf, scn)) != NULL)
+	{
+	  cnt++;
+	  /* We need the actual section and header from the debugelf
+	     not just the cached original in shdr_info because we
+	     might want to change the size.  */
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	  if (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA)
+	    {
+	      /* Make sure that this relocation section points to a
+		 section to relocate with contents, that isn't
+		 allocated and that is a debug section.  */
+	      Elf_Scn *tscn = elf_getscn (debugelf, shdr->sh_info);
+	      GElf_Shdr tshdr_mem;
+	      GElf_Shdr *tshdr = gelf_getshdr (tscn, &tshdr_mem);
+	      if (tshdr->sh_type == SHT_NOBITS
+		  || tshdr->sh_size == 0
+		  || (tshdr->sh_flags & SHF_ALLOC) != 0)
+		continue;
+
+	      const char *tname =  elf_strptr (debugelf, shstrndx,
+					       tshdr->sh_name);
+	      if (! tname || ! ebl_debugscn_p (ebl, tname))
+		continue;
+
+	      /* OK, lets relocate all trivial cross debug section
+		 relocations. */
+	      Elf_Data *reldata = elf_getdata (scn, NULL);
+	      if (reldata == NULL || reldata->d_buf == NULL)
+		INTERNAL_ERROR (fname);
+
+	      /* Make sure we adjust the uncompressed debug data
+		 (and recompress if necessary at the end).  */
+	      GElf_Chdr tchdr;
+	      int tcompress_type = 0;
+	      if (gelf_getchdr (tscn, &tchdr) != NULL)
+		{
+		  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);
+
+	      /* Pick up the symbol table and shndx table to
+		 resolve relocation symbol indexes.  */
+	      Elf64_Word symt = shdr->sh_link;
+	      Elf_Data *symdata, *xndxdata;
+	      elf_assert (symt < shnum + 2);
+	      elf_assert (shdr_info[symt].symtab_idx < shnum + 2);
+	      symdata = (shdr_info[symt].debug_data
+			 ?: shdr_info[symt].data);
+	      xndxdata = (shdr_info[shdr_info[symt].symtab_idx].debug_data
+			  ?: shdr_info[shdr_info[symt].symtab_idx].data);
+
+	      /* Apply one relocation.  Returns true when trivial
+		 relocation actually done.  */
+	      bool relocate (GElf_Addr offset, const GElf_Sxword addend,
+			     bool is_rela, int rtype, int symndx)
+	      {
+		/* R_*_NONE relocs can always just be removed.  */
+		if (rtype == 0)
+		  return true;
+
+		/* We only do simple absolute relocations.  */
+		Elf_Type type = ebl_reloc_simple_type (ebl, rtype);
+		if (type == ELF_T_NUM)
+		  return false;
+
+		/* These are the types we can relocate.  */
+#define TYPES   DO_TYPE (BYTE, Byte); DO_TYPE (HALF, Half);		\
+		DO_TYPE (WORD, Word); DO_TYPE (SWORD, Sword);		\
+		DO_TYPE (XWORD, Xword); DO_TYPE (SXWORD, Sxword)
+
+		/* And only for relocations against other debug sections.  */
+		GElf_Sym sym_mem;
+		Elf32_Word xndx;
+		GElf_Sym *sym = gelf_getsymshndx (symdata, xndxdata,
+						  symndx, &sym_mem,
+						  &xndx);
+		Elf32_Word sec = (sym->st_shndx == SHN_XINDEX
+				  ? xndx : sym->st_shndx);
+		if (sec >= shnum + 2)
+		  INTERNAL_ERROR (fname);
+
+		if (ebl_debugscn_p (ebl, shdr_info[sec].name))
+		  {
+		    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_FAILURE, 0, gettext ("bad relocation"));
+		      }
+
+		    /* When the symbol value is zero then for SHT_REL
+		       sections this is all that needs to be checked.
+		       The addend is contained in the original data at
+		       the offset already.  So if the (section) symbol
+		       address is zero and the given addend is zero
+		       just remove the relocation, it isn't needed
+		       anymore.  */
+		    if (addend == 0 && sym->st_value == 0)
+		      return true;
+
+		    Elf_Data tmpdata =
+		      {
+			.d_type = type,
+			.d_buf = &tmpbuf,
+			.d_size = size,
+			.d_version = EV_CURRENT,
+		      };
+		    Elf_Data rdata =
+		      {
+			.d_type = type,
+			.d_buf = tdata->d_buf + offset,
+			.d_size = size,
+			.d_version = EV_CURRENT,
+		      };
+
+		    GElf_Addr value = sym->st_value;
+		    if (is_rela)
+		      {
+			/* For SHT_RELA sections we just take the
+			   given addend and add it to the value.  */
+			value += addend;
+		      }
+		    else
+		      {
+			/* For SHT_REL sections we have to peek at
+			   what is already in the section at the given
+			   offset to get the addend.  */
+			Elf_Data *d = gelf_xlatetom (debugelf, &tmpdata,
+						     &rdata,
+						     ehdr->e_ident[EI_DATA]);
+			if (d == NULL)
+			  INTERNAL_ERROR (fname);
+			assert (d == &tmpdata);
+		      }
+
+		    switch (type)
+		      {
+#define DO_TYPE(NAME, Name)					\
+			case ELF_T_##NAME:			\
+			  tmpbuf.Name += (GElf_##Name) value;	\
+			  break;
+			TYPES;
+#undef DO_TYPE
+		      default:
+			abort ();
+		      }
+
+		    /* Now finally put in the new value.  */
+		    Elf_Data *s = gelf_xlatetof (debugelf, &rdata,
+						 &tmpdata,
+						 ehdr->e_ident[EI_DATA]);
+		    if (s == NULL)
+		      INTERNAL_ERROR (fname);
+		    assert (s == &rdata);
+
+		    return true;
+		  }
+		return false;
+	      }
+
+	      if (shdr->sh_entsize == 0)
+		INTERNAL_ERROR (fname);
+
+	      size_t nrels = shdr->sh_size / shdr->sh_entsize;
+	      size_t next = 0;
+	      if (shdr->sh_type == SHT_REL)
+		for (size_t relidx = 0; relidx < nrels; ++relidx)
+		  {
+		    GElf_Rel rel_mem;
+		    GElf_Rel *r = gelf_getrel (reldata, relidx, &rel_mem);
+		    if (! relocate (r->r_offset, 0, false,
+				    GELF_R_TYPE (r->r_info),
+				    GELF_R_SYM (r->r_info)))
+		      {
+			if (relidx != next)
+			  gelf_update_rel (reldata, next, r);
+			++next;
+		      }
+		  }
+	      else
+		for (size_t relidx = 0; relidx < nrels; ++relidx)
+		  {
+		    GElf_Rela rela_mem;
+		    GElf_Rela *r = gelf_getrela (reldata, relidx, &rela_mem);
+		    if (! relocate (r->r_offset, r->r_addend, true,
+				    GELF_R_TYPE (r->r_info),
+				    GELF_R_SYM (r->r_info)))
+		      {
+			if (relidx != next)
+			  gelf_update_rela (reldata, next, r);
+			++next;
+		      }
+		  }
+
+	      nrels = next;
+	      shdr->sh_size = reldata->d_size = nrels * shdr->sh_entsize;
+	      gelf_update_shdr (scn, shdr);
+
+	      if (tcompress_type != 0)
+		if (elf_compress (tscn, tcompress_type, ELF_CHF_FORCE) != 1)
+		  INTERNAL_ERROR (fname);
+	    }
+	}
+    }
+
+  /* Now that we have done all adjustments to the data,
+     we can actually write out the debug file.  */
+  if (debug_fname != NULL && removing_sections)
+    {
+      /* Finally write the file.  */
+      if (unlikely (elf_update (debugelf, ELF_C_WRITE) == -1))
+	{
+	  error (0, 0, gettext ("while writing '%s': %s"),
+		 tmp_debug_fname, elf_errmsg (-1));
+	  result = 1;
+	  goto fail_close;
+	}
+
+      /* Create the real output file.  First rename, then change the
+	 mode.  */
+      if (rename (tmp_debug_fname, debug_fname) != 0
+	  || fchmod (debug_fd, mode) != 0)
+	{
+	  error (0, errno, gettext ("while creating '%s'"), debug_fname);
+	  result = 1;
+	  goto fail_close;
+	}
+
+      /* The temporary file does not exist anymore.  */
+      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
+	    };
+
+	  /* Compute the checksum which we will add to the executable.  */
+	  if (crc32_file (debug_fd, &debug_crc) != 0)
+	    {
+	      error (0, errno, gettext ("\
+while computing checksum for debug information"));
+	      unlink (debug_fname);
+	      result = 1;
+	      goto fail_close;
+	    }
+
+	  /* Store it in the debuglink section data.  */
+	  if (unlikely (gelf_xlatetof (newelf, &debuglink_crc_data,
+				       &debug_crc_data, ehdr->e_ident[EI_DATA])
+			!= &debuglink_crc_data))
+	    INTERNAL_ERROR (fname);
+	}
+    }
+
+  /* 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"),
+	     output_fname ?: fname, elf_errmsg (-1));
+      cleanup_debug ();
+      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));
+      cleanup_debug ();
+      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"),
+	     output_fname ?: fname, elf_errmsg (-1));
+      result = 1;
+    }
+
+  if (remove_shdrs)
+    {
+      /* libelf can't cope without the section headers being properly intact.
+	 So we just let it write them normally, and then we nuke them later.  */
+
+      if (newehdr->e_ident[EI_CLASS] == ELFCLASS32)
+	{
+	  assert (offsetof (Elf32_Ehdr, e_shentsize) + sizeof (Elf32_Half)
+		  == offsetof (Elf32_Ehdr, e_shnum));
+	  assert (offsetof (Elf32_Ehdr, e_shnum) + sizeof (Elf32_Half)
+		  == offsetof (Elf32_Ehdr, e_shstrndx));
+	  const Elf32_Off zero_off = 0;
+	  const Elf32_Half zero[3] = { 0, 0, SHN_UNDEF };
+	  if (pwrite_retry (fd, &zero_off, sizeof zero_off,
+			    offsetof (Elf32_Ehdr, e_shoff)) != sizeof zero_off
+	      || (pwrite_retry (fd, zero, sizeof zero,
+				offsetof (Elf32_Ehdr, e_shentsize))
+		  != sizeof zero)
+	      || ftruncate (fd, shdr_info[shdridx].shdr.sh_offset) < 0)
+	    {
+	      error (0, errno, gettext ("while writing '%s'"),
+		     output_fname ?: fname);
+	      result = 1;
+	    }
+	}
+      else
+	{
+	  assert (offsetof (Elf64_Ehdr, e_shentsize) + sizeof (Elf64_Half)
+		  == offsetof (Elf64_Ehdr, e_shnum));
+	  assert (offsetof (Elf64_Ehdr, e_shnum) + sizeof (Elf64_Half)
+		  == offsetof (Elf64_Ehdr, e_shstrndx));
+	  const Elf64_Off zero_off = 0;
+	  const Elf64_Half zero[3] = { 0, 0, SHN_UNDEF };
+	  if (pwrite_retry (fd, &zero_off, sizeof zero_off,
+			    offsetof (Elf64_Ehdr, e_shoff)) != sizeof zero_off
+	      || (pwrite_retry (fd, zero, sizeof zero,
+				offsetof (Elf64_Ehdr, e_shentsize))
+		  != sizeof zero)
+	      || ftruncate (fd, shdr_info[shdridx].shdr.sh_offset) < 0)
+	    {
+	      error (0, errno, gettext ("while writing '%s'"),
+		     output_fname ?: fname);
+	      result = 1;
+	    }
+	}
+    }
+
+ fail_close:
+  if (shdr_info != NULL)
+    {
+      /* For some sections we might have created an table to map symbol
+	 table indices.  Or we might kept (original) data around to put
+	 into the .debug file.  */
+      for (cnt = 1; cnt <= shdridx; ++cnt)
+	{
+	  free (shdr_info[cnt].newsymidx);
+	  if (shdr_info[cnt].debug_data != NULL)
+	    free (shdr_info[cnt].debug_data->d_buf);
+	}
+
+      /* Free data we allocated for the .gnu_debuglink section. */
+      free (debuglink_buf);
+
+      /* Free the memory.  */
+      if ((shnum + 2) * sizeof (struct shdr_info) > MAX_STACK_ALLOC)
+	free (shdr_info);
+    }
+
+  /* Free other resources.  */
+  if (shstrtab_data != NULL)
+    free (shstrtab_data->d_buf);
+  if (shst != NULL)
+    dwelf_strtab_free (shst);
+
+  /* That was it.  Close the descriptors.  */
+  if (elf_end (newelf) != 0)
+    {
+      error (0, 0, gettext ("error while finishing '%s': %s"),
+	     output_fname ?: fname, elf_errmsg (-1));
+      result = 1;
+    }
+
+  if (debugelf != NULL && elf_end (debugelf) != 0)
+    {
+      error (0, 0, gettext ("error while finishing '%s': %s"), debug_fname,
+	     elf_errmsg (-1));
+      result = 1;
+    }
+
+ fail:
+  /* Close the EBL backend.  */
+  if (ebl != NULL)
+    ebl_closebackend (ebl);
+
+  cleanup_debug ();
+
+  /* If requested, preserve the timestamp.  */
+  if (tvp != NULL)
+    {
+      if (futimens (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);
+      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;
+
+  /* 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 (futimens (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;
+}
+
+
+#include "debugpred.h"
diff --git a/src/unstrip.c b/src/unstrip.c
new file mode 100644
index 0000000..f368e69
--- /dev/null
+++ b/src/unstrip.c
@@ -0,0 +1,2456 @@
+/* Combine stripped files with separate symbols and debug information.
+   Copyright (C) 2007-2012, 2014, 2015 Red Hat, Inc.
+   This file is part of elfutils.
+   Written by Roland McGrath <roland@redhat.com>, 2007.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* TODO:
+
+  * SHX_XINDEX
+
+  * prelink vs .debug_* linked addresses
+
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <argp.h>
+#include <assert.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <libintl.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <gelf.h>
+#include <libebl.h>
+#include <libdwfl.h>
+#include "libdwelf.h"
+#include "libeu.h"
+#include "printversion.h"
+
+#ifndef _
+# define _(str) gettext (str)
+#endif
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  /* Group 2 will follow group 1 from dwfl_standard_argp.  */
+  { "match-file-names", 'f', NULL, 0,
+    N_("Match MODULE against file names, not module names"), 2 },
+  { "ignore-missing", 'i', NULL, 0, N_("Silently skip unfindable files"), 0 },
+
+  { NULL, 0, NULL, 0, N_("Output options:"), 0 },
+  { "output", 'o', "FILE", 0, N_("Place output into FILE"), 0 },
+  { "output-directory", 'd', "DIRECTORY",
+    0, N_("Create multiple output files under DIRECTORY"), 0 },
+  { "module-names", 'm', NULL, 0, N_("Use module rather than file names"), 0 },
+  { "all", 'a', NULL, 0,
+    N_("Create output for modules that have no separate debug information"),
+    0 },
+  { "relocate", 'R', NULL, 0,
+    N_("Apply relocations to section contents in ET_REL files"), 0 },
+  { "list-only", 'n', NULL, 0,
+    N_("Only list module and file names, build IDs"), 0 },
+ { "force", 'F', NULL, 0,
+    N_("Force combining files even if some ELF headers don't seem to match"),
+   0 },
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+struct arg_info
+{
+  const char *output_file;
+  const char *output_dir;
+  Dwfl *dwfl;
+  char **args;
+  bool list;
+  bool all;
+  bool ignore;
+  bool modnames;
+  bool match_files;
+  bool relocate;
+  bool force;
+};
+
+/* Handle program arguments.  */
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+  struct arg_info *info = state->input;
+
+  switch (key)
+    {
+    case ARGP_KEY_INIT:
+      state->child_inputs[0] = &info->dwfl;
+      break;
+
+    case 'o':
+      if (info->output_file != NULL)
+	{
+	  argp_error (state, _("-o option specified twice"));
+	  return EINVAL;
+	}
+      info->output_file = arg;
+      break;
+
+    case 'd':
+      if (info->output_dir != NULL)
+	{
+	  argp_error (state, _("-d option specified twice"));
+	  return EINVAL;
+	}
+      info->output_dir = arg;
+      break;
+
+    case 'm':
+      info->modnames = true;
+      break;
+    case 'f':
+      info->match_files = true;
+      break;
+    case 'a':
+      info->all = true;
+      break;
+    case 'i':
+      info->ignore = true;
+      break;
+    case 'n':
+      info->list = true;
+      break;
+    case 'R':
+      info->relocate = true;
+      break;
+    case 'F':
+      info->force = true;
+      break;
+
+    case ARGP_KEY_ARGS:
+    case ARGP_KEY_NO_ARGS:
+      /* We "consume" all the arguments here.  */
+      info->args = &state->argv[state->next];
+
+      if (info->output_file != NULL && info->output_dir != NULL)
+	{
+	  argp_error (state, _("only one of -o or -d allowed"));
+	  return EINVAL;
+	}
+
+      if (info->list && (info->dwfl == NULL
+			 || info->output_dir != NULL
+			 || info->output_file != NULL))
+	{
+	  argp_error (state,
+		      _("-n cannot be used with explicit files or -o or -d"));
+	  return EINVAL;
+	}
+
+      if (info->output_dir != NULL)
+	{
+	  struct stat st;
+	  error_t fail = 0;
+	  if (stat (info->output_dir, &st) < 0)
+	    fail = errno;
+	  else if (!S_ISDIR (st.st_mode))
+	    fail = ENOTDIR;
+	  if (fail)
+	    {
+	      argp_failure (state, EXIT_FAILURE, fail,
+			    _("output directory '%s'"), info->output_dir);
+	      return fail;
+	    }
+	}
+
+      if (info->dwfl == NULL)
+	{
+	  if (state->next + 2 != state->argc)
+	    {
+	      argp_error (state, _("exactly two file arguments are required"));
+	      return EINVAL;
+	    }
+
+	  if (info->ignore || info->all || info->modnames || info->relocate)
+	    {
+	      argp_error (state, _("\
+-m, -a, -R, and -i options not allowed with explicit files"));
+	      return EINVAL;
+	    }
+
+	  /* Bail out immediately to prevent dwfl_standard_argp's parser
+	     from defaulting to "-e a.out".  */
+	  return ENOSYS;
+	}
+      else if (info->output_file == NULL && info->output_dir == NULL
+	       && !info->list)
+	{
+	  argp_error (state,
+		      _("-o or -d is required when using implicit files"));
+	  return EINVAL;
+	}
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+#define ELF_CHECK(call, msg)						      \
+  do									      \
+    {									      \
+      if (unlikely (!(call)))						      \
+	error (EXIT_FAILURE, 0, msg, elf_errmsg (-1));			      \
+    } while (0)
+
+/* Copy INELF to newly-created OUTELF, exit via error for any problems.  */
+static void
+copy_elf (Elf *outelf, Elf *inelf)
+{
+  ELF_CHECK (gelf_newehdr (outelf, gelf_getclass (inelf)),
+	     _("cannot create ELF header: %s"));
+
+  GElf_Ehdr ehdr_mem;
+  GElf_Ehdr *ehdr = gelf_getehdr (inelf, &ehdr_mem);
+  ELF_CHECK (gelf_update_ehdr (outelf, ehdr),
+	     _("cannot copy ELF header: %s"));
+
+  size_t phnum;
+  ELF_CHECK (elf_getphdrnum (inelf, &phnum) == 0,
+	     _("cannot get number of program headers: %s"));
+
+  if (phnum > 0)
+    {
+      ELF_CHECK (gelf_newphdr (outelf, phnum),
+		 _("cannot create program headers: %s"));
+
+      GElf_Phdr phdr_mem;
+      for (size_t i = 0; i < phnum; ++i)
+	ELF_CHECK (gelf_update_phdr (outelf, i,
+				     gelf_getphdr (inelf, i, &phdr_mem)),
+		   _("cannot copy program header: %s"));
+    }
+
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (inelf, scn)) != NULL)
+    {
+      Elf_Scn *newscn = elf_newscn (outelf);
+
+      GElf_Shdr shdr_mem;
+      ELF_CHECK (gelf_update_shdr (newscn, gelf_getshdr (scn, &shdr_mem)),
+		 _("cannot copy section header: %s"));
+
+      Elf_Data *data = elf_getdata (scn, NULL);
+      ELF_CHECK (data != NULL, _("cannot get section data: %s"));
+      Elf_Data *newdata = elf_newdata (newscn);
+      ELF_CHECK (newdata != NULL, _("cannot copy section data: %s"));
+      *newdata = *data;
+      elf_flagdata (newdata, ELF_C_SET, ELF_F_DIRTY);
+    }
+}
+
+/* Create directories containing PATH.  */
+static void
+make_directories (const char *path)
+{
+  const char *lastslash = strrchr (path, '/');
+  if (lastslash == NULL)
+    return;
+
+  while (lastslash > path && lastslash[-1] == '/')
+    --lastslash;
+  if (lastslash == path)
+    return;
+
+  char *dir = strndupa (path, lastslash - path);
+  while (mkdir (dir, 0777) < 0 && errno != EEXIST)
+    if (errno == ENOENT)
+      make_directories (dir);
+    else
+      error (EXIT_FAILURE, errno, _("cannot create directory '%s'"), dir);
+}
+
+/* Keep track of new section data we are creating, so we can free it
+   when done.  */
+struct data_list
+{
+  void *data;
+  struct data_list *next;
+};
+
+struct data_list *new_data_list;
+
+static void
+record_new_data (void *data)
+{
+  struct data_list *next = new_data_list;
+  new_data_list = xmalloc (sizeof (struct data_list));
+  new_data_list->data = data;
+  new_data_list->next = next;
+}
+
+static void
+free_new_data (void)
+{
+  struct data_list *list = new_data_list;
+  while (list != NULL)
+    {
+      struct data_list *next = list->next;
+      free (list->data);
+      free (list);
+      list = next;
+    }
+  new_data_list = NULL;
+}
+
+/* The binutils linker leaves gratuitous section symbols in .symtab
+   that strip has to remove.  Older linkers likewise include a
+   symbol for every section, even unallocated ones, in .dynsym.
+   Because of this, the related sections can shrink in the stripped
+   file from their original size.  Older versions of strip do not
+   adjust the sh_size field in the debuginfo file's SHT_NOBITS
+   version of the section header, so it can appear larger.  */
+static bool
+section_can_shrink (const GElf_Shdr *shdr)
+{
+  switch (shdr->sh_type)
+    {
+    case SHT_SYMTAB:
+    case SHT_DYNSYM:
+    case SHT_HASH:
+    case SHT_GNU_versym:
+      return true;
+    }
+  return false;
+}
+
+/* See if this symbol table has a leading section symbol for every single
+   section, in order.  The binutils linker produces this.  While we're here,
+   update each section symbol's st_value.  */
+static size_t
+symtab_count_leading_section_symbols (Elf *elf, Elf_Scn *scn, size_t shnum,
+				      Elf_Data *newsymdata)
+{
+  Elf_Data *data = elf_getdata (scn, NULL);
+  Elf_Data *shndxdata = NULL;	/* XXX */
+
+  for (size_t i = 1; i < shnum; ++i)
+    {
+      GElf_Sym sym_mem;
+      GElf_Word shndx = SHN_UNDEF;
+      GElf_Sym *sym = gelf_getsymshndx (data, shndxdata, i, &sym_mem, &shndx);
+      ELF_CHECK (sym != NULL, _("cannot get symbol table entry: %s"));
+
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (elf_getscn (elf, i), &shdr_mem);
+      ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+      if (sym->st_shndx != SHN_XINDEX)
+	shndx = sym->st_shndx;
+
+      if (shndx != i || GELF_ST_TYPE (sym->st_info) != STT_SECTION)
+	return i;
+
+      sym->st_value = shdr->sh_addr;
+      if (sym->st_shndx != SHN_XINDEX)
+	shndx = SHN_UNDEF;
+      ELF_CHECK (gelf_update_symshndx (newsymdata, shndxdata, i, sym, shndx),
+		 _("cannot update symbol table: %s"));
+    }
+
+  return shnum;
+}
+
+static void
+update_shdr (Elf_Scn *outscn, GElf_Shdr *newshdr)
+{
+  ELF_CHECK (gelf_update_shdr (outscn, newshdr),
+	     _("cannot update section header: %s"));
+}
+
+/* We expanded the output section, so update its header.  */
+static void
+update_sh_size (Elf_Scn *outscn, const Elf_Data *data)
+{
+  GElf_Shdr shdr_mem;
+  GElf_Shdr *newshdr = gelf_getshdr (outscn, &shdr_mem);
+  ELF_CHECK (newshdr != NULL, _("cannot get section header: %s"));
+
+  newshdr->sh_size = data->d_size;
+
+  update_shdr (outscn, newshdr);
+}
+
+/* Update relocation sections using the symbol table.  */
+static void
+adjust_relocs (Elf_Scn *outscn, Elf_Scn *inscn, const GElf_Shdr *shdr,
+	       size_t map[], const GElf_Shdr *symshdr)
+{
+  Elf_Data *data = elf_getdata (outscn, NULL);
+
+  inline void adjust_reloc (GElf_Xword *info)
+    {
+      size_t ndx = GELF_R_SYM (*info);
+      if (ndx != STN_UNDEF)
+	*info = GELF_R_INFO (map[ndx - 1], GELF_R_TYPE (*info));
+    }
+
+  switch (shdr->sh_type)
+    {
+    case SHT_REL:
+      for (size_t i = 0; i < shdr->sh_size / shdr->sh_entsize; ++i)
+	{
+	  GElf_Rel rel_mem;
+	  GElf_Rel *rel = gelf_getrel (data, i, &rel_mem);
+	  adjust_reloc (&rel->r_info);
+	  ELF_CHECK (gelf_update_rel (data, i, rel),
+		     _("cannot update relocation: %s"));
+	}
+      break;
+
+    case SHT_RELA:
+      for (size_t i = 0; i < shdr->sh_size / shdr->sh_entsize; ++i)
+	{
+	  GElf_Rela rela_mem;
+	  GElf_Rela *rela = gelf_getrela (data, i, &rela_mem);
+	  adjust_reloc (&rela->r_info);
+	  ELF_CHECK (gelf_update_rela (data, i, rela),
+		     _("cannot update relocation: %s"));
+	}
+      break;
+
+    case SHT_GROUP:
+      {
+	GElf_Shdr shdr_mem;
+	GElf_Shdr *newshdr = gelf_getshdr (outscn, &shdr_mem);
+	ELF_CHECK (newshdr != NULL, _("cannot get section header: %s"));
+	if (newshdr->sh_info != STN_UNDEF)
+	  {
+	    newshdr->sh_info = map[newshdr->sh_info - 1];
+	    update_shdr (outscn, newshdr);
+	  }
+	break;
+      }
+
+    case SHT_HASH:
+      /* We must expand the table and rejigger its contents.  */
+      {
+	const size_t nsym = symshdr->sh_size / symshdr->sh_entsize;
+	const size_t onent = shdr->sh_size / shdr->sh_entsize;
+	assert (data->d_size == shdr->sh_size);
+
+#define CONVERT_HASH(Hash_Word)						      \
+	{								      \
+	  const Hash_Word *const old_hash = data->d_buf;		      \
+	  const size_t nbucket = old_hash[0];				      \
+	  const size_t nchain = old_hash[1];				      \
+	  const Hash_Word *const old_bucket = &old_hash[2];		      \
+	  const Hash_Word *const old_chain = &old_bucket[nbucket];	      \
+	  assert (onent == 2 + nbucket + nchain);			      \
+									      \
+	  const size_t nent = 2 + nbucket + nsym;			      \
+	  Hash_Word *const new_hash = xcalloc (nent, sizeof new_hash[0]);     \
+	  Hash_Word *const new_bucket = &new_hash[2];			      \
+	  Hash_Word *const new_chain = &new_bucket[nbucket];		      \
+									      \
+	  new_hash[0] = nbucket;					      \
+	  new_hash[1] = nsym;						      \
+	  for (size_t i = 0; i < nbucket; ++i)				      \
+	    if (old_bucket[i] != STN_UNDEF)				      \
+	      new_bucket[i] = map[old_bucket[i] - 1];			      \
+									      \
+	  for (size_t i = 1; i < nchain; ++i)				      \
+	    if (old_chain[i] != STN_UNDEF)				      \
+	      new_chain[map[i - 1]] = map[old_chain[i] - 1];		      \
+									      \
+	  record_new_data (new_hash);					\
+	  data->d_buf = new_hash;					      \
+	  data->d_size = nent * sizeof new_hash[0];			      \
+	}
+
+	switch (shdr->sh_entsize)
+	  {
+	  case 4:
+	    CONVERT_HASH (Elf32_Word);
+	    break;
+	  case 8:
+	    CONVERT_HASH (Elf64_Xword);
+	    break;
+	  default:
+	    abort ();
+	  }
+
+	elf_flagdata (data, ELF_C_SET, ELF_F_DIRTY);
+	update_sh_size (outscn, data);
+
+#undef	CONVERT_HASH
+      }
+      break;
+
+    case SHT_GNU_versym:
+      /* We must expand the table and move its elements around.  */
+      {
+	const size_t nent = symshdr->sh_size / symshdr->sh_entsize;
+	const size_t onent = shdr->sh_size / shdr->sh_entsize;
+	assert (nent >= onent);
+
+	/* We don't bother using gelf_update_versym because there is
+	   really no conversion to be done.  */
+	assert (sizeof (Elf32_Versym) == sizeof (GElf_Versym));
+	assert (sizeof (Elf64_Versym) == sizeof (GElf_Versym));
+	GElf_Versym *versym = xcalloc (nent, sizeof versym[0]);
+
+	for (size_t i = 1; i < onent; ++i)
+	  {
+	    GElf_Versym *v = gelf_getversym (data, i, &versym[map[i - 1]]);
+	    ELF_CHECK (v != NULL, _("cannot get symbol version: %s"));
+	  }
+
+	record_new_data (versym);
+	data->d_buf = versym;
+	data->d_size = nent * shdr->sh_entsize;
+	elf_flagdata (data, ELF_C_SET, ELF_F_DIRTY);
+	update_sh_size (outscn, data);
+      }
+      break;
+
+    default:
+      error (EXIT_FAILURE, 0,
+	     _("unexpected section type in [%zu] with sh_link to symtab"),
+	     elf_ndxscn (inscn));
+    }
+}
+
+/* Adjust all the relocation sections in the file.  */
+static void
+adjust_all_relocs (Elf *elf, Elf_Scn *symtab, const GElf_Shdr *symshdr,
+		   size_t map[])
+{
+  size_t new_sh_link = elf_ndxscn (symtab);
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (elf, scn)) != NULL)
+    if (scn != symtab)
+      {
+	GElf_Shdr shdr_mem;
+	GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+	if (shdr->sh_type != SHT_NOBITS && shdr->sh_link == new_sh_link)
+	  adjust_relocs (scn, scn, shdr, map, symshdr);
+      }
+}
+
+/* The original file probably had section symbols for all of its
+   sections, even the unallocated ones.  To match it as closely as
+   possible, add in section symbols for the added sections.  */
+static Elf_Data *
+add_new_section_symbols (Elf_Scn *old_symscn, size_t old_shnum,
+			 Elf *elf, bool rel, Elf_Scn *symscn, size_t shnum)
+{
+  const size_t added = shnum - old_shnum;
+
+  GElf_Shdr shdr_mem;
+  GElf_Shdr *shdr = gelf_getshdr (symscn, &shdr_mem);
+  ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+  const size_t nsym = shdr->sh_size / shdr->sh_entsize;
+  size_t symndx_map[nsym - 1];
+
+  shdr->sh_info += added;
+  shdr->sh_size += added * shdr->sh_entsize;
+  update_shdr (symscn, shdr);
+
+  Elf_Data *symdata = elf_getdata (symscn, NULL);
+  Elf_Data *shndxdata = NULL;	/* XXX */
+
+  symdata->d_size = shdr->sh_size;
+  symdata->d_buf = xmalloc (symdata->d_size);
+  record_new_data (symdata->d_buf);
+
+  /* Copy the existing section symbols.  */
+  Elf_Data *old_symdata = elf_getdata (old_symscn, NULL);
+  for (size_t i = 0; i < old_shnum; ++i)
+    {
+      GElf_Sym sym_mem;
+      GElf_Word shndx = SHN_UNDEF;
+      GElf_Sym *sym = gelf_getsymshndx (old_symdata, shndxdata,
+					i, &sym_mem, &shndx);
+      ELF_CHECK (gelf_update_symshndx (symdata, shndxdata, i,
+				       sym, shndx),
+		 _("cannot update symbol table: %s"));
+
+      if (i > 0)
+	symndx_map[i - 1] = i;
+    }
+
+  /* Add in the new section symbols.  */
+  for (size_t i = old_shnum; i < shnum; ++i)
+    {
+      GElf_Shdr i_shdr_mem;
+      GElf_Shdr *i_shdr = gelf_getshdr (elf_getscn (elf, i), &i_shdr_mem);
+      ELF_CHECK (i_shdr != NULL, _("cannot get section header: %s"));
+      GElf_Sym sym =
+	{
+	  .st_value = rel ? 0 : i_shdr->sh_addr,
+	  .st_info = GELF_ST_INFO (STB_LOCAL, STT_SECTION),
+	  .st_shndx = i < SHN_LORESERVE ? i : SHN_XINDEX
+	};
+      GElf_Word shndx = i < SHN_LORESERVE ? SHN_UNDEF : i;
+      ELF_CHECK (gelf_update_symshndx (symdata, shndxdata, i,
+				       &sym, shndx),
+		 _("cannot update symbol table: %s"));
+    }
+
+  /* Now copy the rest of the existing symbols.  */
+  for (size_t i = old_shnum; i < nsym; ++i)
+    {
+      GElf_Sym sym_mem;
+      GElf_Word shndx = SHN_UNDEF;
+      GElf_Sym *sym = gelf_getsymshndx (old_symdata, shndxdata,
+					i, &sym_mem, &shndx);
+      ELF_CHECK (gelf_update_symshndx (symdata, shndxdata,
+				       i + added, sym, shndx),
+		 _("cannot update symbol table: %s"));
+
+      symndx_map[i - 1] = i + added;
+    }
+
+  /* Adjust any relocations referring to the old symbol table.  */
+  adjust_all_relocs (elf, symscn, shdr, symndx_map);
+
+  return symdata;
+}
+
+/* This has the side effect of updating STT_SECTION symbols' values,
+   in case of prelink adjustments.  */
+static Elf_Data *
+check_symtab_section_symbols (Elf *elf, bool rel, Elf_Scn *scn,
+			      size_t shnum, size_t shstrndx,
+			      Elf_Scn *oscn, size_t oshnum, size_t oshstrndx,
+			      size_t debuglink)
+{
+  size_t n = symtab_count_leading_section_symbols (elf, oscn, oshnum,
+						   elf_getdata (scn, NULL));
+
+  if (n == oshnum)
+    return add_new_section_symbols (oscn, n, elf, rel, scn, shnum);
+
+  if (n == oshstrndx || (n == debuglink && n == oshstrndx - 1))
+    return add_new_section_symbols (oscn, n, elf, rel, scn, shstrndx);
+
+  return NULL;
+}
+
+struct section
+{
+  Elf_Scn *scn;
+  const char *name;
+  Elf_Scn *outscn;
+  Dwelf_Strent *strent;
+  GElf_Shdr shdr;
+};
+
+static int
+compare_alloc_sections (const struct section *s1, const struct section *s2,
+			bool rel)
+{
+  if (!rel)
+    {
+      /* Sort by address.  */
+      if (s1->shdr.sh_addr < s2->shdr.sh_addr)
+	return -1;
+      if (s1->shdr.sh_addr > s2->shdr.sh_addr)
+	return 1;
+    }
+
+  /* At the same address, preserve original section order.  */
+  return (ssize_t) elf_ndxscn (s1->scn) - (ssize_t) elf_ndxscn (s2->scn);
+}
+
+static int
+compare_unalloc_sections (const GElf_Shdr *shdr1, const GElf_Shdr *shdr2,
+			  const char *name1, const char *name2)
+{
+  /* Sort by sh_flags as an arbitrary ordering.  */
+  if (shdr1->sh_flags < shdr2->sh_flags)
+    return -1;
+  if (shdr1->sh_flags > shdr2->sh_flags)
+    return 1;
+
+  /* Sort by name as last resort.  */
+  return strcmp (name1, name2);
+}
+
+static int
+compare_sections (const void *a, const void *b, bool rel)
+{
+  const struct section *s1 = a;
+  const struct section *s2 = b;
+
+  /* Sort all non-allocated sections last.  */
+  if ((s1->shdr.sh_flags ^ s2->shdr.sh_flags) & SHF_ALLOC)
+    return (s1->shdr.sh_flags & SHF_ALLOC) ? -1 : 1;
+
+  return ((s1->shdr.sh_flags & SHF_ALLOC)
+	  ? compare_alloc_sections (s1, s2, rel)
+	  : compare_unalloc_sections (&s1->shdr, &s2->shdr,
+				      s1->name, s2->name));
+}
+
+static int
+compare_sections_rel (const void *a, const void *b)
+{
+  return compare_sections (a, b, true);
+}
+
+static int
+compare_sections_nonrel (const void *a, const void *b)
+{
+  return compare_sections (a, b, false);
+}
+
+
+struct symbol
+{
+  size_t *map;
+
+  union
+  {
+    const char *name;
+    Dwelf_Strent *strent;
+  };
+  union
+  {
+    struct
+    {
+      GElf_Addr value;
+      GElf_Xword size;
+      GElf_Word shndx;
+      union
+      {
+	struct
+	{
+	  uint8_t info;
+	  uint8_t other;
+	} info;
+	int16_t compare;
+      };
+    };
+
+    /* For a symbol discarded after first sort, this matches its better's
+       map pointer.  */
+    size_t *duplicate;
+  };
+};
+
+/* Collect input symbols into our internal form.  */
+static void
+collect_symbols (Elf *outelf, bool rel, Elf_Scn *symscn, Elf_Scn *strscn,
+		 const size_t nent, const GElf_Addr bias,
+		 const size_t scnmap[], struct symbol *table, size_t *map,
+		 struct section *split_bss)
+{
+  Elf_Data *symdata = elf_getdata (symscn, NULL);
+  Elf_Data *strdata = elf_getdata (strscn, NULL);
+  Elf_Data *shndxdata = NULL;	/* XXX */
+
+  for (size_t i = 1; i < nent; ++i)
+    {
+      GElf_Sym sym_mem;
+      GElf_Word shndx = SHN_UNDEF;
+      GElf_Sym *sym = gelf_getsymshndx (symdata, shndxdata, i,
+					&sym_mem, &shndx);
+      ELF_CHECK (sym != NULL, _("cannot get symbol table entry: %s"));
+      if (sym->st_shndx != SHN_XINDEX)
+	shndx = sym->st_shndx;
+
+      if (sym->st_name >= strdata->d_size)
+	error (EXIT_FAILURE, 0,
+	       _("invalid string offset in symbol [%zu]"), i);
+
+      struct symbol *s = &table[i - 1];
+      s->map = &map[i - 1];
+      s->name = strdata->d_buf + sym->st_name;
+      s->value = sym->st_value + bias;
+      s->size = sym->st_size;
+      s->shndx = shndx;
+      s->info.info = sym->st_info;
+      s->info.other = sym->st_other;
+
+      if (scnmap != NULL && shndx != SHN_UNDEF && shndx < SHN_LORESERVE)
+	s->shndx = scnmap[shndx - 1];
+
+      if (GELF_ST_TYPE (s->info.info) == STT_SECTION && !rel)
+	{
+	  /* Update the value to match the output section.  */
+	  GElf_Shdr shdr_mem;
+	  GElf_Shdr *shdr = gelf_getshdr (elf_getscn (outelf, s->shndx),
+					  &shdr_mem);
+	  ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+	  s->value = shdr->sh_addr;
+	}
+      else if (split_bss != NULL
+	       && s->value < split_bss->shdr.sh_addr
+	       && s->value >= split_bss[-1].shdr.sh_addr
+	       && shndx == elf_ndxscn (split_bss->outscn))
+	/* This symbol was in .bss and was split into .dynbss.  */
+	s->shndx = elf_ndxscn (split_bss[-1].outscn);
+    }
+}
+
+
+#define CMP(value)							      \
+  if (s1->value < s2->value)						      \
+    return -1;								      \
+  if (s1->value > s2->value)						      \
+    return 1
+
+/* Compare symbols with a consistent ordering,
+   but one only meaningful for equality.  */
+static int
+compare_symbols (const void *a, const void *b)
+{
+  const struct symbol *s1 = a;
+  const struct symbol *s2 = b;
+
+  CMP (value);
+  CMP (size);
+  CMP (shndx);
+
+  return (s1->compare - s2->compare) ?: strcmp (s1->name, s2->name);
+}
+
+/* Compare symbols for output order after slots have been assigned.  */
+static int
+compare_symbols_output (const void *a, const void *b)
+{
+  const struct symbol *s1 = a;
+  const struct symbol *s2 = b;
+  int cmp;
+
+  /* Sort discarded symbols last.  */
+  cmp = (s1->name == NULL) - (s2->name == NULL);
+
+  if (cmp == 0)
+    /* Local symbols must come first.  */
+    cmp = ((GELF_ST_BIND (s2->info.info) == STB_LOCAL)
+	   - (GELF_ST_BIND (s1->info.info) == STB_LOCAL));
+
+  if (cmp == 0)
+    /* binutils always puts section symbols first.  */
+    cmp = ((GELF_ST_TYPE (s2->info.info) == STT_SECTION)
+	   - (GELF_ST_TYPE (s1->info.info) == STT_SECTION));
+
+  if (cmp == 0)
+    {
+      if (GELF_ST_TYPE (s1->info.info) == STT_SECTION)
+	{
+	  /* binutils always puts section symbols in section index order.  */
+	  CMP (shndx);
+	  else
+	    assert (s1 == s2);
+	}
+
+      /* Nothing really matters, so preserve the original order.  */
+      CMP (map);
+      else
+	assert (s1 == s2);
+    }
+
+  return cmp;
+}
+
+#undef CMP
+
+/* Return true if the flags of the sections match, ignoring the SHF_INFO_LINK
+   flag if the section contains relocation information.  */
+static bool
+sections_flags_match (Elf64_Xword sh_flags1, Elf64_Xword sh_flags2,
+		      Elf64_Word sh_type)
+{
+  if (sh_type == SHT_REL || sh_type == SHT_RELA)
+    {
+      sh_flags1 &= ~SHF_INFO_LINK;
+      sh_flags2 &= ~SHF_INFO_LINK;
+    }
+
+  return sh_flags1 == sh_flags2;
+}
+
+/* Return true iff the flags, size, and name match.  */
+static bool
+sections_match (const struct section *sections, size_t i,
+		const GElf_Shdr *shdr, const char *name)
+{
+  return (sections_flags_match (sections[i].shdr.sh_flags, shdr->sh_flags,
+				sections[i].shdr.sh_type)
+	  && (sections[i].shdr.sh_size == shdr->sh_size
+	      || (sections[i].shdr.sh_size < shdr->sh_size
+		  && section_can_shrink (&sections[i].shdr)))
+	  && !strcmp (sections[i].name, name));
+}
+
+/* Locate a matching allocated section in SECTIONS.  */
+static struct section *
+find_alloc_section (const GElf_Shdr *shdr, GElf_Addr bias, const char *name,
+		    struct section sections[], size_t nalloc)
+{
+  const GElf_Addr addr = shdr->sh_addr + bias;
+  size_t l = 0, u = nalloc;
+  while (l < u)
+    {
+      size_t i = (l + u) / 2;
+      if (addr < sections[i].shdr.sh_addr)
+	u = i;
+      else if (addr > sections[i].shdr.sh_addr)
+	l = i + 1;
+      else
+	{
+	  /* We've found allocated sections with this address.
+	     Find one with matching size, flags, and name.  */
+	  while (i > 0 && sections[i - 1].shdr.sh_addr == addr)
+	    --i;
+	  for (; i < nalloc && sections[i].shdr.sh_addr == addr;
+	       ++i)
+	    if (sections_match (sections, i, shdr, name))
+	      return &sections[i];
+	  break;
+	}
+    }
+  return NULL;
+}
+
+static inline const char *
+get_section_name (size_t ndx, const GElf_Shdr *shdr, const Elf_Data *shstrtab)
+{
+  if (shdr->sh_name >= shstrtab->d_size)
+    error (EXIT_FAILURE, 0, _("cannot read section [%zu] name: %s"),
+	   ndx, elf_errmsg (-1));
+  return shstrtab->d_buf + shdr->sh_name;
+}
+
+/* Fix things up when prelink has moved some allocated sections around
+   and the debuginfo file's section headers no longer match up.
+   This fills in SECTIONS[0..NALLOC-1].outscn or exits.
+   If there was a .bss section that was split into two sections
+   with the new one preceding it in sh_addr, we return that pointer.  */
+static struct section *
+find_alloc_sections_prelink (Elf *debug, Elf_Data *debug_shstrtab,
+			     Elf *main, const GElf_Ehdr *main_ehdr,
+			     Elf_Data *main_shstrtab, GElf_Addr bias,
+			     struct section *sections,
+			     size_t nalloc, size_t nsections)
+{
+  Elf_Scn *undo = NULL;
+  for (size_t i = nalloc; i < nsections; ++i)
+    {
+      const struct section *sec = &sections[i];
+      if (sec->shdr.sh_type == SHT_PROGBITS
+	  && !(sec->shdr.sh_flags & SHF_ALLOC)
+	  && !strcmp (sec->name, ".gnu.prelink_undo"))
+	{
+	  undo = sec->scn;
+	  break;
+	}
+    }
+
+  /* Find the original allocated sections before prelinking.  */
+  struct section *undo_sections = NULL;
+  size_t undo_nalloc = 0;
+  if (undo != NULL)
+    {
+      /* Clear assignments that might have been bogus.  */
+      for (size_t i = 0; i < nalloc; ++i)
+	sections[i].outscn = NULL;
+
+      Elf_Data *undodata = elf_rawdata (undo, NULL);
+      ELF_CHECK (undodata != NULL,
+		 _("cannot read '.gnu.prelink_undo' section: %s"));
+
+      union
+      {
+	Elf32_Ehdr e32;
+	Elf64_Ehdr e64;
+      } ehdr;
+      Elf_Data dst =
+	{
+	  .d_buf = &ehdr,
+	  .d_size = sizeof ehdr,
+	  .d_type = ELF_T_EHDR,
+	  .d_version = EV_CURRENT
+	};
+      Elf_Data src = *undodata;
+      src.d_size = gelf_fsize (main, ELF_T_EHDR, 1, EV_CURRENT);
+      src.d_type = ELF_T_EHDR;
+      ELF_CHECK (gelf_xlatetom (main, &dst, &src,
+				main_ehdr->e_ident[EI_DATA]) != NULL,
+		 _("cannot read '.gnu.prelink_undo' section: %s"));
+
+      uint_fast16_t phnum;
+      uint_fast16_t shnum;
+      if (ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32)
+	{
+	  phnum = ehdr.e32.e_phnum;
+	  shnum = ehdr.e32.e_shnum;
+	}
+      else
+	{
+	  phnum = ehdr.e64.e_phnum;
+	  shnum = ehdr.e64.e_shnum;
+	}
+
+      bool class32 = ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32;
+      size_t shsize = class32 ? sizeof (Elf32_Shdr) : sizeof (Elf64_Shdr);
+      if (unlikely (shnum == 0 || shnum > SIZE_MAX / shsize + 1))
+	error (EXIT_FAILURE, 0, _("overflow with shnum = %zu in '%s' section"),
+	       (size_t) shnum, ".gnu.prelink_undo");
+
+      --shnum;
+
+      size_t phsize = gelf_fsize (main, ELF_T_PHDR, phnum, EV_CURRENT);
+      src.d_buf += src.d_size + phsize;
+      src.d_size = gelf_fsize (main, ELF_T_SHDR, shnum, EV_CURRENT);
+      src.d_type = ELF_T_SHDR;
+      if ((size_t) (src.d_buf - undodata->d_buf) > undodata->d_size
+	  || undodata->d_size - (src.d_buf - undodata->d_buf) != src.d_size)
+	error (EXIT_FAILURE, 0, _("invalid contents in '%s' section"),
+	       ".gnu.prelink_undo");
+
+      const size_t shdr_bytes = shnum * shsize;
+      void *shdr = xmalloc (shdr_bytes);
+      dst.d_buf = shdr;
+      dst.d_size = shdr_bytes;
+      ELF_CHECK (gelf_xlatetom (main, &dst, &src,
+				main_ehdr->e_ident[EI_DATA]) != NULL,
+		 _("cannot read '.gnu.prelink_undo' section: %s"));
+
+      undo_sections = xmalloc (shnum * sizeof undo_sections[0]);
+      for (size_t i = 0; i < shnum; ++i)
+	{
+	  struct section *sec = &undo_sections[undo_nalloc];
+	  Elf32_Shdr (*s32)[shnum] = shdr;
+	  Elf64_Shdr (*s64)[shnum] = shdr;
+	  if (class32)
+	    {
+#define COPY(field) sec->shdr.field = (*s32)[i].field
+	      COPY (sh_name);
+	      COPY (sh_type);
+	      COPY (sh_flags);
+	      COPY (sh_addr);
+	      COPY (sh_offset);
+	      COPY (sh_size);
+	      COPY (sh_link);
+	      COPY (sh_info);
+	      COPY (sh_addralign);
+	      COPY (sh_entsize);
+#undef	COPY
+	    }
+	  else
+	    sec->shdr = (*s64)[i];
+	  if (sec->shdr.sh_flags & SHF_ALLOC)
+	    {
+	      sec->shdr.sh_addr += bias;
+	      sec->name = get_section_name (i + 1, &sec->shdr, main_shstrtab);
+	      sec->scn = elf_getscn (main, i + 1); /* Really just for ndx.  */
+	      sec->outscn = NULL;
+	      sec->strent = NULL;
+	      ++undo_nalloc;
+	    }
+	}
+      qsort (undo_sections, undo_nalloc,
+	     sizeof undo_sections[0], compare_sections_nonrel);
+      free (shdr);
+    }
+
+  bool fail = false;
+  inline void check_match (bool match, Elf_Scn *scn, const char *name)
+    {
+      if (!match)
+	{
+	  fail = true;
+	  error (0, 0, _("cannot find matching section for [%zu] '%s'"),
+		 elf_ndxscn (scn), name);
+	}
+    }
+
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (debug, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+      ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+      if (!(shdr->sh_flags & SHF_ALLOC))
+	continue;
+
+      const char *name = get_section_name (elf_ndxscn (scn), shdr,
+					   debug_shstrtab);
+
+      if (undo_sections != NULL)
+	{
+	  struct section *sec = find_alloc_section (shdr, 0, name,
+						    undo_sections,
+						    undo_nalloc);
+	  if (sec != NULL)
+	    {
+	      sec->outscn = scn;
+	      continue;
+	    }
+	}
+
+      /* If there is no prelink info, we are just here to find
+	 the sections to give error messages about.  */
+      for (size_t i = 0; shdr != NULL && i < nalloc; ++i)
+	if (sections[i].outscn == scn)
+	  shdr = NULL;
+      check_match (shdr == NULL, scn, name);
+    }
+
+  if (fail)
+    exit (EXIT_FAILURE);
+
+  /* Now we have lined up output sections for each of the original sections
+     before prelinking.  Translate those to the prelinked sections.
+     This matches what prelink's undo_sections does.  */
+  struct section *split_bss = NULL;
+  for (size_t i = 0; i < undo_nalloc; ++i)
+    {
+      const struct section *undo_sec = &undo_sections[i];
+
+      const char *name = undo_sec->name;
+      scn = undo_sec->scn; /* This is just for elf_ndxscn.  */
+
+      for (size_t j = 0; j < nalloc; ++j)
+	{
+	  struct section *sec = &sections[j];
+#define RELA_SCALED(field) \
+	  (2 * sec->shdr.field == 3 * undo_sec->shdr.field)
+	  if (sec->outscn == NULL
+	      && sec->shdr.sh_name == undo_sec->shdr.sh_name
+	      && sec->shdr.sh_flags == undo_sec->shdr.sh_flags
+	      && sec->shdr.sh_addralign == undo_sec->shdr.sh_addralign
+	      && (((sec->shdr.sh_type == undo_sec->shdr.sh_type
+		    && sec->shdr.sh_entsize == undo_sec->shdr.sh_entsize
+		    && (sec->shdr.sh_size == undo_sec->shdr.sh_size
+			|| (sec->shdr.sh_size > undo_sec->shdr.sh_size
+			    && main_ehdr->e_type == ET_EXEC
+			    && !strcmp (sec->name, ".dynstr"))))
+		   || (sec->shdr.sh_size == undo_sec->shdr.sh_size
+		       && ((sec->shdr.sh_entsize == undo_sec->shdr.sh_entsize
+			    && undo_sec->shdr.sh_type == SHT_NOBITS)
+			   || undo_sec->shdr.sh_type == SHT_PROGBITS)
+		       && !strcmp (sec->name, ".plt")))
+		  || (sec->shdr.sh_type == SHT_RELA
+		      && undo_sec->shdr.sh_type == SHT_REL
+		      && RELA_SCALED (sh_entsize) && RELA_SCALED (sh_size))
+		  || (sec->shdr.sh_entsize == undo_sec->shdr.sh_entsize
+		      && (sec->shdr.sh_type == undo_sec->shdr.sh_type
+			  || (sec->shdr.sh_type == SHT_PROGBITS
+			      && undo_sec->shdr.sh_type == SHT_NOBITS))
+		      && sec->shdr.sh_size <= undo_sec->shdr.sh_size
+		      && (!strcmp (sec->name, ".bss")
+			  || !strcmp (sec->name, ".sbss"))
+		      && (sec->shdr.sh_size == undo_sec->shdr.sh_size
+			  || (split_bss = sec) > sections))))
+	    {
+	      sec->outscn = undo_sec->outscn;
+	      undo_sec = NULL;
+	      break;
+	    }
+	}
+
+      check_match (undo_sec == NULL, scn, name);
+    }
+
+  free (undo_sections);
+
+  if (fail)
+    exit (EXIT_FAILURE);
+
+  return split_bss;
+}
+
+/* Create new .shstrtab contents, subroutine of copy_elided_sections.
+   This can't be open coded there and still use variable-length auto arrays,
+   since the end of our block would free other VLAs too.  */
+static Elf_Data *
+new_shstrtab (Elf *unstripped, size_t unstripped_shnum,
+	      Elf_Data *shstrtab, size_t unstripped_shstrndx,
+	      struct section *sections, size_t stripped_shnum,
+	      Dwelf_Strtab *strtab)
+{
+  if (strtab == NULL)
+    return NULL;
+
+  Dwelf_Strent *unstripped_strent[unstripped_shnum - 1];
+  memset (unstripped_strent, 0, sizeof unstripped_strent);
+  for (struct section *sec = sections;
+       sec < &sections[stripped_shnum - 1];
+       ++sec)
+    if (sec->outscn != NULL)
+      {
+	if (sec->strent == NULL)
+	  {
+	    sec->strent = dwelf_strtab_add (strtab, sec->name);
+	    ELF_CHECK (sec->strent != NULL,
+		       _("cannot add section name to string table: %s"));
+	  }
+	unstripped_strent[elf_ndxscn (sec->outscn) - 1] = sec->strent;
+      }
+
+  /* Add names of sections we aren't touching.  */
+  for (size_t i = 0; i < unstripped_shnum - 1; ++i)
+    if (unstripped_strent[i] == NULL)
+      {
+	Elf_Scn *scn = elf_getscn (unstripped, i + 1);
+	GElf_Shdr shdr_mem;
+	GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	const char *name = get_section_name (i + 1, shdr, shstrtab);
+	unstripped_strent[i] = dwelf_strtab_add (strtab, name);
+	ELF_CHECK (unstripped_strent[i] != NULL,
+		   _("cannot add section name to string table: %s"));
+      }
+    else
+      unstripped_strent[i] = NULL;
+
+  /* Now finalize the string table so we can get offsets.  */
+  Elf_Data *strtab_data = elf_getdata (elf_getscn (unstripped,
+						   unstripped_shstrndx), NULL);
+  ELF_CHECK (elf_flagdata (strtab_data, ELF_C_SET, ELF_F_DIRTY),
+	     _("cannot update section header string table data: %s"));
+  if (dwelf_strtab_finalize (strtab, strtab_data) == NULL)
+    error (EXIT_FAILURE, 0, "Not enough memory to create string table");
+
+  /* Update the sh_name fields of sections we aren't modifying later.  */
+  for (size_t i = 0; i < unstripped_shnum - 1; ++i)
+    if (unstripped_strent[i] != NULL)
+      {
+	Elf_Scn *scn = elf_getscn (unstripped, i + 1);
+	GElf_Shdr shdr_mem;
+	GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	shdr->sh_name = dwelf_strent_off (unstripped_strent[i]);
+	if (i + 1 == unstripped_shstrndx)
+	  shdr->sh_size = strtab_data->d_size;
+	update_shdr (scn, shdr);
+      }
+
+  return strtab_data;
+}
+
+/* Fill in any SHT_NOBITS sections in UNSTRIPPED by
+   copying their contents and sh_type from STRIPPED.  */
+static void
+copy_elided_sections (Elf *unstripped, Elf *stripped,
+		      const GElf_Ehdr *stripped_ehdr, GElf_Addr bias)
+{
+  size_t unstripped_shstrndx;
+  ELF_CHECK (elf_getshdrstrndx (unstripped, &unstripped_shstrndx) == 0,
+	     _("cannot get section header string table section index: %s"));
+
+  size_t stripped_shstrndx;
+  ELF_CHECK (elf_getshdrstrndx (stripped, &stripped_shstrndx) == 0,
+	     _("cannot get section header string table section index: %s"));
+
+  size_t unstripped_shnum;
+  ELF_CHECK (elf_getshdrnum (unstripped, &unstripped_shnum) == 0,
+	     _("cannot get section count: %s"));
+
+  size_t stripped_shnum;
+  ELF_CHECK (elf_getshdrnum (stripped, &stripped_shnum) == 0,
+	     _("cannot get section count: %s"));
+
+  if (unlikely (stripped_shnum > unstripped_shnum))
+    error (EXIT_FAILURE, 0, _("\
+more sections in stripped file than debug file -- arguments reversed?"));
+
+  /* Cache the stripped file's section details.  */
+  struct section sections[stripped_shnum - 1];
+  Elf_Scn *scn = NULL;
+  while ((scn = elf_nextscn (stripped, scn)) != NULL)
+    {
+      size_t i = elf_ndxscn (scn) - 1;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &sections[i].shdr);
+      ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+      sections[i].name = elf_strptr (stripped, stripped_shstrndx,
+				     shdr->sh_name);
+      if (sections[i].name == NULL)
+	error (EXIT_FAILURE, 0, _("cannot read section [%zu] name: %s"),
+	       elf_ndxscn (scn), elf_errmsg (-1));
+      sections[i].scn = scn;
+      sections[i].outscn = NULL;
+      sections[i].strent = NULL;
+    }
+
+  const struct section *stripped_symtab = NULL;
+
+  /* Sort the sections, allocated by address and others after.  */
+  qsort (sections, stripped_shnum - 1, sizeof sections[0],
+	 stripped_ehdr->e_type == ET_REL
+	 ? compare_sections_rel : compare_sections_nonrel);
+  size_t nalloc = stripped_shnum - 1;
+  while (nalloc > 0 && !(sections[nalloc - 1].shdr.sh_flags & SHF_ALLOC))
+    {
+      --nalloc;
+      if (sections[nalloc].shdr.sh_type == SHT_SYMTAB)
+	stripped_symtab = &sections[nalloc];
+    }
+
+  /* Locate a matching unallocated section in SECTIONS.  */
+  inline struct section *find_unalloc_section (const GElf_Shdr *shdr,
+					       const char *name)
+    {
+      size_t l = nalloc, u = stripped_shnum - 1;
+      while (l < u)
+	{
+	  size_t i = (l + u) / 2;
+	  struct section *sec = &sections[i];
+	  int cmp = compare_unalloc_sections (shdr, &sec->shdr,
+					      name, sec->name);
+	  if (cmp < 0)
+	    u = i;
+	  else if (cmp > 0)
+	    l = i + 1;
+	  else
+	    return sec;
+	}
+      return NULL;
+    }
+
+  Elf_Data *shstrtab = elf_getdata (elf_getscn (unstripped,
+						unstripped_shstrndx), NULL);
+  ELF_CHECK (shstrtab != NULL,
+	     _("cannot read section header string table: %s"));
+
+  /* Match each debuginfo section with its corresponding stripped section.  */
+  bool check_prelink = false;
+  Elf_Scn *unstripped_symtab = NULL;
+  size_t unstripped_strndx = 0;
+  size_t alloc_avail = 0;
+  scn = NULL;
+  while ((scn = elf_nextscn (unstripped, scn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+      ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+      if (shdr->sh_type == SHT_SYMTAB)
+	{
+	  unstripped_symtab = scn;
+	  unstripped_strndx = shdr->sh_link;
+	  continue;
+	}
+
+      const size_t ndx = elf_ndxscn (scn);
+      if (ndx == unstripped_shstrndx || ndx == unstripped_strndx)
+	continue;
+
+      const char *name = get_section_name (ndx, shdr, shstrtab);
+
+      struct section *sec = NULL;
+      if (shdr->sh_flags & SHF_ALLOC)
+	{
+	  if (stripped_ehdr->e_type != ET_REL)
+	    {
+	      /* Look for the section that matches.  */
+	      sec = find_alloc_section (shdr, bias, name, sections, nalloc);
+	      if (sec == NULL)
+		{
+		  /* We couldn't figure it out.  It may be a prelink issue.  */
+		  check_prelink = true;
+		  continue;
+		}
+	    }
+	  else
+	    {
+	      /* The sh_addr of allocated sections does not help us,
+		 but the order usually matches.  */
+	      if (likely (sections_match (sections, alloc_avail, shdr, name)))
+		sec = &sections[alloc_avail++];
+	      else
+		for (size_t i = alloc_avail + 1; i < nalloc; ++i)
+		  if (sections_match (sections, i, shdr, name))
+		    {
+		      sec = &sections[i];
+		      break;
+		    }
+	    }
+	}
+      else
+	{
+	  /* Look for the section that matches.  */
+	  sec = find_unalloc_section (shdr, name);
+	  if (sec == NULL)
+	    {
+	      /* An additional unallocated section is fine if not SHT_NOBITS.
+		 We looked it up anyway in case it's an unallocated section
+		 copied in both files (e.g. SHT_NOTE), and don't keep both.  */
+	      if (shdr->sh_type != SHT_NOBITS)
+		continue;
+
+	      /* Somehow some old .debug files wound up with SHT_NOBITS
+		 .comment sections, so let those pass.  */
+	      if (!strcmp (name, ".comment"))
+		continue;
+	    }
+	}
+
+      if (sec == NULL)
+	error (EXIT_FAILURE, 0,
+	       _("cannot find matching section for [%zu] '%s'"),
+	       elf_ndxscn (scn), name);
+
+      sec->outscn = scn;
+    }
+
+  /* If that failed due to changes made by prelink, we take another tack.
+     We keep track of a .bss section that was partly split into .dynbss
+     so that collect_symbols can update symbols' st_shndx fields.  */
+  struct section *split_bss = NULL;
+  if (check_prelink)
+    {
+      Elf_Data *data = elf_getdata (elf_getscn (stripped, stripped_shstrndx),
+				    NULL);
+      ELF_CHECK (data != NULL,
+		 _("cannot read section header string table: %s"));
+      split_bss = find_alloc_sections_prelink (unstripped, shstrtab,
+					       stripped, stripped_ehdr,
+					       data, bias, sections,
+					       nalloc, stripped_shnum - 1);
+    }
+
+  /* Make sure each main file section has a place to go.  */
+  const struct section *stripped_dynsym = NULL;
+  size_t debuglink = SHN_UNDEF;
+  size_t ndx_section[stripped_shnum - 1];
+  Dwelf_Strtab *strtab = NULL;
+  for (struct section *sec = sections;
+       sec < &sections[stripped_shnum - 1];
+       ++sec)
+    {
+      size_t secndx = elf_ndxscn (sec->scn);
+
+      if (sec->outscn == NULL)
+	{
+	  /* We didn't find any corresponding section for this.  */
+
+	  if (secndx == stripped_shstrndx)
+	    {
+	      /* We only need one .shstrtab.  */
+	      ndx_section[secndx - 1] = unstripped_shstrndx;
+	      continue;
+	    }
+
+	  if (unstripped_symtab != NULL && sec == stripped_symtab)
+	    {
+	      /* We don't need a second symbol table.  */
+	      ndx_section[secndx - 1] = elf_ndxscn (unstripped_symtab);
+	      continue;
+	    }
+
+	  if (unstripped_symtab != NULL && stripped_symtab != NULL
+	      && secndx == stripped_symtab->shdr.sh_link
+	      && unstripped_strndx != 0)
+	    {
+	      /* ... nor its string table.  */
+	      ndx_section[secndx - 1] = unstripped_strndx;
+	      continue;
+	    }
+
+	  if (!(sec->shdr.sh_flags & SHF_ALLOC)
+	      && !strcmp (sec->name, ".gnu_debuglink"))
+	    {
+	      /* This was created by stripping.  We don't want it.  */
+	      debuglink = secndx;
+	      ndx_section[secndx - 1] = SHN_UNDEF;
+	      continue;
+	    }
+
+	  sec->outscn = elf_newscn (unstripped);
+	  Elf_Data *newdata = elf_newdata (sec->outscn);
+	  ELF_CHECK (newdata != NULL && gelf_update_shdr (sec->outscn,
+							  &sec->shdr),
+		     _("cannot add new section: %s"));
+
+	  if (strtab == NULL)
+	    strtab = dwelf_strtab_init (true);
+	  sec->strent = dwelf_strtab_add (strtab, sec->name);
+	  ELF_CHECK (sec->strent != NULL,
+		     _("cannot add section name to string table: %s"));
+	}
+
+      /* Cache the mapping of original section indices to output sections.  */
+      ndx_section[secndx - 1] = elf_ndxscn (sec->outscn);
+    }
+
+  /* We added some sections, so we need a new shstrtab.  */
+  Elf_Data *strtab_data = new_shstrtab (unstripped, unstripped_shnum,
+					shstrtab, unstripped_shstrndx,
+					sections, stripped_shnum,
+					strtab);
+
+  /* Get the updated section count.  */
+  ELF_CHECK (elf_getshdrnum (unstripped, &unstripped_shnum) == 0,
+	     _("cannot get section count: %s"));
+
+  bool placed[unstripped_shnum - 1];
+  memset (placed, 0, sizeof placed);
+
+  /* Now update the output sections and copy in their data.  */
+  GElf_Off offset = 0;
+  for (const struct section *sec = sections;
+       sec < &sections[stripped_shnum - 1];
+       ++sec)
+    if (sec->outscn != NULL)
+      {
+	GElf_Shdr shdr_mem;
+	GElf_Shdr *shdr = gelf_getshdr (sec->outscn, &shdr_mem);
+	ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+	/* In an ET_REL file under --relocate, the sh_addr of SHF_ALLOC
+	   sections will have been set nonzero by relocation.  This
+	   touched the shdrs of whichever file had the symtab.  sh_addr
+	   is still zero in the corresponding shdr.  The relocated
+	   address is what we want to use.  */
+	if (stripped_ehdr->e_type != ET_REL
+	    || !(shdr_mem.sh_flags & SHF_ALLOC)
+	    || shdr_mem.sh_addr == 0)
+	  shdr_mem.sh_addr = sec->shdr.sh_addr;
+
+	shdr_mem.sh_type = sec->shdr.sh_type;
+	shdr_mem.sh_size = sec->shdr.sh_size;
+	shdr_mem.sh_info = sec->shdr.sh_info;
+	shdr_mem.sh_link = sec->shdr.sh_link;
+
+	/* Buggy binutils objdump might have stripped the SHF_INFO_LINK
+	   put it back if necessary.  */
+	if ((sec->shdr.sh_type == SHT_REL || sec->shdr.sh_type == SHT_RELA)
+	    && sec->shdr.sh_flags != shdr_mem.sh_flags
+	    && (sec->shdr.sh_flags & SHF_INFO_LINK) != 0)
+	  shdr_mem.sh_flags |= SHF_INFO_LINK;
+
+	if (sec->shdr.sh_link != SHN_UNDEF)
+	  shdr_mem.sh_link = ndx_section[sec->shdr.sh_link - 1];
+	if (SH_INFO_LINK_P (&sec->shdr) && sec->shdr.sh_info != 0)
+	  shdr_mem.sh_info = ndx_section[sec->shdr.sh_info - 1];
+
+	if (strtab != NULL)
+	  shdr_mem.sh_name = dwelf_strent_off (sec->strent);
+
+	Elf_Data *indata = elf_getdata (sec->scn, NULL);
+	ELF_CHECK (indata != NULL, _("cannot get section data: %s"));
+	Elf_Data *outdata = elf_getdata (sec->outscn, NULL);
+	ELF_CHECK (outdata != NULL, _("cannot copy section data: %s"));
+	*outdata = *indata;
+	elf_flagdata (outdata, ELF_C_SET, ELF_F_DIRTY);
+
+	/* Preserve the file layout of the allocated sections.  */
+	if (stripped_ehdr->e_type != ET_REL && (shdr_mem.sh_flags & SHF_ALLOC))
+	  {
+	    shdr_mem.sh_offset = sec->shdr.sh_offset;
+	    placed[elf_ndxscn (sec->outscn) - 1] = true;
+
+	    const GElf_Off end_offset = (shdr_mem.sh_offset
+					 + (shdr_mem.sh_type == SHT_NOBITS
+					    ? 0 : shdr_mem.sh_size));
+	    if (end_offset > offset)
+	      offset = end_offset;
+	  }
+
+	update_shdr (sec->outscn, &shdr_mem);
+
+	if (shdr_mem.sh_type == SHT_SYMTAB || shdr_mem.sh_type == SHT_DYNSYM)
+	  {
+	    /* We must adjust all the section indices in the symbol table.  */
+
+	    Elf_Data *shndxdata = NULL;	/* XXX */
+
+	    for (size_t i = 1; i < shdr_mem.sh_size / shdr_mem.sh_entsize; ++i)
+	      {
+		GElf_Sym sym_mem;
+		GElf_Word shndx = SHN_UNDEF;
+		GElf_Sym *sym = gelf_getsymshndx (outdata, shndxdata,
+						  i, &sym_mem, &shndx);
+		ELF_CHECK (sym != NULL,
+			   _("cannot get symbol table entry: %s"));
+		if (sym->st_shndx != SHN_XINDEX)
+		  shndx = sym->st_shndx;
+
+		if (shndx != SHN_UNDEF && shndx < SHN_LORESERVE)
+		  {
+		    if (shndx >= stripped_shnum)
+		      error (EXIT_FAILURE, 0,
+			     _("symbol [%zu] has invalid section index"), i);
+
+		    shndx = ndx_section[shndx - 1];
+		    if (shndx < SHN_LORESERVE)
+		      {
+			sym->st_shndx = shndx;
+			shndx = SHN_UNDEF;
+		      }
+		    else
+		      sym->st_shndx = SHN_XINDEX;
+
+		    ELF_CHECK (gelf_update_symshndx (outdata, shndxdata,
+						     i, sym, shndx),
+			       _("cannot update symbol table: %s"));
+		  }
+	      }
+
+	    if (shdr_mem.sh_type == SHT_SYMTAB)
+	      stripped_symtab = sec;
+	    if (shdr_mem.sh_type == SHT_DYNSYM)
+	      stripped_dynsym = sec;
+	  }
+      }
+
+  /* We may need to update the symbol table.  */
+  Elf_Data *symdata = NULL;
+  Dwelf_Strtab *symstrtab = NULL;
+  Elf_Data *symstrdata = NULL;
+  if (unstripped_symtab != NULL && (stripped_symtab != NULL
+				    || check_prelink /* Section adjustments. */
+				    || (stripped_ehdr->e_type != ET_REL
+					&& bias != 0)))
+    {
+      /* Merge the stripped file's symbol table into the unstripped one.  */
+      const size_t stripped_nsym = (stripped_symtab == NULL ? 1
+				    : (stripped_symtab->shdr.sh_size
+				       / stripped_symtab->shdr.sh_entsize));
+
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (unstripped_symtab, &shdr_mem);
+      ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+      const size_t unstripped_nsym = shdr->sh_size / shdr->sh_entsize;
+
+      /* First collect all the symbols from both tables.  */
+
+      const size_t total_syms = stripped_nsym - 1 + unstripped_nsym - 1;
+      struct symbol symbols[total_syms];
+      size_t symndx_map[total_syms];
+
+      if (stripped_symtab != NULL)
+	collect_symbols (unstripped, stripped_ehdr->e_type == ET_REL,
+			 stripped_symtab->scn,
+			 elf_getscn (stripped, stripped_symtab->shdr.sh_link),
+			 stripped_nsym, 0, ndx_section,
+			 symbols, symndx_map, NULL);
+
+      Elf_Scn *unstripped_strtab = elf_getscn (unstripped, shdr->sh_link);
+      collect_symbols (unstripped, stripped_ehdr->e_type == ET_REL,
+		       unstripped_symtab, unstripped_strtab, unstripped_nsym,
+		       stripped_ehdr->e_type == ET_REL ? 0 : bias, NULL,
+		       &symbols[stripped_nsym - 1],
+		       &symndx_map[stripped_nsym - 1], split_bss);
+
+      /* Next, sort our array of all symbols.  */
+      qsort (symbols, total_syms, sizeof symbols[0], compare_symbols);
+
+      /* Now we can weed out the duplicates.  Assign remaining symbols
+	 new slots, collecting a map from old indices to new.  */
+      size_t nsym = 0;
+      for (struct symbol *s = symbols; s < &symbols[total_syms]; ++s)
+	{
+	  /* Skip a section symbol for a removed section.  */
+	  if (s->shndx == SHN_UNDEF
+	      && GELF_ST_TYPE (s->info.info) == STT_SECTION)
+	    {
+	      s->name = NULL;	/* Mark as discarded. */
+	      *s->map = STN_UNDEF;
+	      s->duplicate = NULL;
+	      continue;
+	    }
+
+	  struct symbol *n = s;
+	  while (n + 1 < &symbols[total_syms] && !compare_symbols (s, n + 1))
+	    ++n;
+
+	  while (s < n)
+	    {
+	      /* This is a duplicate.  Its twin will get the next slot.  */
+	      s->name = NULL;	/* Mark as discarded. */
+	      s->duplicate = n->map;
+	      ++s;
+	    }
+
+	  /* Allocate the next slot.  */
+	  *s->map = ++nsym;
+	}
+
+      /* Now we sort again, to determine the order in the output.  */
+      qsort (symbols, total_syms, sizeof symbols[0], compare_symbols_output);
+
+      if (nsym < total_syms)
+	/* The discarded symbols are now at the end of the table.  */
+	assert (symbols[nsym].name == NULL);
+
+      /* Now a final pass updates the map with the final order,
+	 and builds up the new string table.  */
+      symstrtab = dwelf_strtab_init (true);
+      for (size_t i = 0; i < nsym; ++i)
+	{
+	  assert (symbols[i].name != NULL);
+	  assert (*symbols[i].map != 0);
+	  *symbols[i].map = 1 + i;
+	  symbols[i].strent = dwelf_strtab_add (symstrtab, symbols[i].name);
+	}
+
+      /* Scan the discarded symbols too, just to update their slots
+	 in SYMNDX_MAP to refer to their live duplicates.  */
+      for (size_t i = nsym; i < total_syms; ++i)
+	{
+	  assert (symbols[i].name == NULL);
+	  if (symbols[i].duplicate == NULL)
+	    assert (*symbols[i].map == STN_UNDEF);
+	  else
+	    {
+	      assert (*symbols[i].duplicate != STN_UNDEF);
+	      *symbols[i].map = *symbols[i].duplicate;
+	    }
+	}
+
+      /* Now we are ready to write the new symbol table.  */
+      symdata = elf_getdata (unstripped_symtab, NULL);
+      symstrdata = elf_getdata (unstripped_strtab, NULL);
+      Elf_Data *shndxdata = NULL;	/* XXX */
+
+      /* If symtab and the section header table share the string table
+	 add the section names to the strtab and then (after finalizing)
+	 fixup the section header sh_names.  Also dispose of the old data.  */
+      Dwelf_Strent *unstripped_strent[unstripped_shnum - 1];
+      if (unstripped_shstrndx == elf_ndxscn (unstripped_strtab))
+	{
+	  for (size_t i = 0; i < unstripped_shnum - 1; ++i)
+	    {
+	      Elf_Scn *sec = elf_getscn (unstripped, i + 1);
+	      GElf_Shdr mem;
+	      GElf_Shdr *hdr = gelf_getshdr (sec, &mem);
+	      const char *name = get_section_name (i + 1, hdr, shstrtab);
+	      unstripped_strent[i] = dwelf_strtab_add (symstrtab, name);
+	      ELF_CHECK (unstripped_strent[i] != NULL,
+			 _("cannot add section name to string table: %s"));
+	    }
+
+	  if (strtab != NULL)
+	    {
+	      dwelf_strtab_free (strtab);
+	      free (strtab_data->d_buf);
+	      strtab = NULL;
+	    }
+	}
+
+      if (dwelf_strtab_finalize (symstrtab, symstrdata) == NULL)
+	error (EXIT_FAILURE, 0, "Not enough memory to create symbol table");
+
+      elf_flagdata (symstrdata, ELF_C_SET, ELF_F_DIRTY);
+
+      /* And update the section header names if necessary.  */
+      if (unstripped_shstrndx == elf_ndxscn (unstripped_strtab))
+	{
+	  for (size_t i = 0; i < unstripped_shnum - 1; ++i)
+	    {
+	      Elf_Scn *sec = elf_getscn (unstripped, i + 1);
+	      GElf_Shdr mem;
+	      GElf_Shdr *hdr = gelf_getshdr (sec, &mem);
+	      shdr->sh_name = dwelf_strent_off (unstripped_strent[i]);
+	      update_shdr (sec, hdr);
+	    }
+	}
+
+      /* Now update the symtab shdr.  Reload symtab shdr because sh_name
+	 might have changed above. */
+      shdr = gelf_getshdr (unstripped_symtab, &shdr_mem);
+      ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+      shdr->sh_size = symdata->d_size = (1 + nsym) * shdr->sh_entsize;
+      symdata->d_buf = xmalloc (symdata->d_size);
+      record_new_data (symdata->d_buf);
+
+      GElf_Sym sym;
+      memset (&sym, 0, sizeof sym);
+      ELF_CHECK (gelf_update_symshndx (symdata, shndxdata, 0, &sym, SHN_UNDEF),
+		 _("cannot update symbol table: %s"));
+
+      shdr->sh_info = 1;
+      for (size_t i = 0; i < nsym; ++i)
+	{
+	  struct symbol *s = &symbols[i];
+
+	  /* Fill in the symbol details.  */
+	  sym.st_name = dwelf_strent_off (s->strent);
+	  sym.st_value = s->value; /* Already biased to output address.  */
+	  sym.st_size = s->size;
+	  sym.st_shndx = s->shndx; /* Already mapped to output index.  */
+	  sym.st_info = s->info.info;
+	  sym.st_other = s->info.other;
+
+	  /* Keep track of the number of leading local symbols.  */
+	  if (GELF_ST_BIND (sym.st_info) == STB_LOCAL)
+	    {
+	      assert (shdr->sh_info == 1 + i);
+	      shdr->sh_info = 1 + i + 1;
+	    }
+
+	  ELF_CHECK (gelf_update_symshndx (symdata, shndxdata, 1 + i,
+					   &sym, SHN_UNDEF),
+		     _("cannot update symbol table: %s"));
+
+	}
+      elf_flagdata (symdata, ELF_C_SET, ELF_F_DIRTY);
+      update_shdr (unstripped_symtab, shdr);
+
+      if (stripped_symtab != NULL)
+	{
+	  /* Adjust any relocations referring to the old symbol table.  */
+	  const size_t old_sh_link = elf_ndxscn (stripped_symtab->scn);
+	  for (const struct section *sec = sections;
+	       sec < &sections[stripped_shnum - 1];
+	       ++sec)
+	    if (sec->outscn != NULL && sec->shdr.sh_link == old_sh_link)
+	      adjust_relocs (sec->outscn, sec->scn, &sec->shdr,
+			     symndx_map, shdr);
+	}
+
+      /* Also adjust references to the other old symbol table.  */
+      adjust_all_relocs (unstripped, unstripped_symtab, shdr,
+			 &symndx_map[stripped_nsym - 1]);
+    }
+  else if (stripped_symtab != NULL && stripped_shnum != unstripped_shnum)
+    check_symtab_section_symbols (unstripped,
+				  stripped_ehdr->e_type == ET_REL,
+				  stripped_symtab->scn,
+				  unstripped_shnum, unstripped_shstrndx,
+				  stripped_symtab->outscn,
+				  stripped_shnum, stripped_shstrndx,
+				  debuglink);
+
+  if (stripped_dynsym != NULL)
+    (void) check_symtab_section_symbols (unstripped,
+					 stripped_ehdr->e_type == ET_REL,
+					 stripped_dynsym->outscn,
+					 unstripped_shnum,
+					 unstripped_shstrndx,
+					 stripped_dynsym->scn, stripped_shnum,
+					 stripped_shstrndx, debuglink);
+
+  /* We need to preserve the layout of the stripped file so the
+     phdrs will match up.  This requires us to do our own layout of
+     the added sections.  We do manual layout even for ET_REL just
+     so we can try to match what the original probably had.  */
+
+  elf_flagelf (unstripped, ELF_C_SET, ELF_F_LAYOUT);
+
+  if (offset == 0)
+    /* For ET_REL we are starting the layout from scratch.  */
+    offset = gelf_fsize (unstripped, ELF_T_EHDR, 1, EV_CURRENT);
+
+  bool skip_reloc = false;
+  do
+    {
+      skip_reloc = !skip_reloc;
+      for (size_t i = 0; i < unstripped_shnum - 1; ++i)
+	if (!placed[i])
+	  {
+	    scn = elf_getscn (unstripped, 1 + i);
+
+	    GElf_Shdr shdr_mem;
+	    GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+	    ELF_CHECK (shdr != NULL, _("cannot get section header: %s"));
+
+	    /* We must make sure we have read in the data of all sections
+	       beforehand and marked them to be written out.  When we're
+	       modifying the existing file in place, we might overwrite
+	       this part of the file before we get to handling the section.  */
+
+	    ELF_CHECK (elf_flagdata (elf_getdata (scn, NULL),
+				     ELF_C_SET, ELF_F_DIRTY),
+		       _("cannot read section data: %s"));
+
+	    if (skip_reloc
+		&& (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA))
+	      continue;
+
+	    GElf_Off align = shdr->sh_addralign ?: 1;
+	    offset = (offset + align - 1) & -align;
+	    shdr->sh_offset = offset;
+	    if (shdr->sh_type != SHT_NOBITS)
+	      offset += shdr->sh_size;
+
+	    update_shdr (scn, shdr);
+
+	    if (unstripped_shstrndx == 1 + i)
+	      {
+		/* Place the section headers immediately after
+		   .shstrtab, and update the ELF header.  */
+
+		GElf_Ehdr ehdr_mem;
+		GElf_Ehdr *ehdr = gelf_getehdr (unstripped, &ehdr_mem);
+		ELF_CHECK (ehdr != NULL, _("cannot get ELF header: %s"));
+
+		GElf_Off sh_align = gelf_getclass (unstripped) * 4;
+		offset = (offset + sh_align - 1) & -sh_align;
+		ehdr->e_shnum = unstripped_shnum;
+		ehdr->e_shoff = offset;
+		offset += unstripped_shnum * ehdr->e_shentsize;
+		ELF_CHECK (gelf_update_ehdr (unstripped, ehdr),
+			   _("cannot update ELF header: %s"));
+	      }
+
+	    placed[i] = true;
+	  }
+    }
+  while (skip_reloc);
+
+  size_t phnum;
+  ELF_CHECK (elf_getphdrnum (stripped, &phnum) == 0,
+	     _("cannot get number of program headers: %s"));
+
+  if (phnum > 0)
+    ELF_CHECK (gelf_newphdr (unstripped, phnum),
+	       _("cannot create program headers: %s"));
+
+  /* Copy each program header from the stripped file.  */
+  for (size_t i = 0; i < phnum; ++i)
+    {
+      GElf_Phdr phdr_mem;
+      GElf_Phdr *phdr = gelf_getphdr (stripped, i, &phdr_mem);
+      ELF_CHECK (phdr != NULL, _("cannot get program header: %s"));
+
+      ELF_CHECK (gelf_update_phdr (unstripped, i, phdr),
+		 _("cannot update program header: %s"));
+    }
+
+  /* Finally, write out the file.  */
+  ELF_CHECK (elf_update (unstripped, ELF_C_WRITE) > 0,
+	     _("cannot write output file: %s"));
+
+  if (strtab != NULL)
+    {
+      dwelf_strtab_free (strtab);
+      free (strtab_data->d_buf);
+    }
+
+  if (symstrtab != NULL)
+    {
+      dwelf_strtab_free (symstrtab);
+      free (symstrdata->d_buf);
+    }
+  free_new_data ();
+}
+
+/* Process one pair of files, already opened.  */
+static void
+handle_file (const char *output_file, bool create_dirs,
+	     Elf *stripped, const GElf_Ehdr *stripped_ehdr,
+	     Elf *unstripped)
+{
+  size_t phnum;
+  ELF_CHECK (elf_getphdrnum (stripped, &phnum) == 0,
+	     _("cannot get number of program headers: %s"));
+
+  /* Determine the address bias between the debuginfo file and the main
+     file, which may have been modified by prelinking.  */
+  GElf_Addr bias = 0;
+  if (unstripped != NULL)
+    for (size_t i = 0; i < phnum; ++i)
+      {
+	GElf_Phdr phdr_mem;
+	GElf_Phdr *phdr = gelf_getphdr (stripped, i, &phdr_mem);
+	ELF_CHECK (phdr != NULL, _("cannot get program header: %s"));
+	if (phdr->p_type == PT_LOAD)
+	  {
+	    GElf_Phdr unstripped_phdr_mem;
+	    GElf_Phdr *unstripped_phdr = gelf_getphdr (unstripped, i,
+						       &unstripped_phdr_mem);
+	    ELF_CHECK (unstripped_phdr != NULL,
+		       _("cannot get program header: %s"));
+	    bias = phdr->p_vaddr - unstripped_phdr->p_vaddr;
+	    break;
+	  }
+      }
+
+  /* One day we could adjust all the DWARF data (like prelink itself does).  */
+  if (bias != 0)
+    {
+      if (output_file == NULL)
+	error (0, 0, _("\
+DWARF data not adjusted for prelinking bias; consider prelink -u"));
+      else
+	error (0, 0, _("\
+DWARF data in '%s' not adjusted for prelinking bias; consider prelink -u"),
+	       output_file);
+    }
+
+  if (output_file == NULL)
+    /* Modify the unstripped file in place.  */
+    copy_elided_sections (unstripped, stripped, stripped_ehdr, bias);
+  else
+    {
+      if (create_dirs)
+	make_directories (output_file);
+
+      /* Copy the unstripped file and then modify it.  */
+      int outfd = open (output_file, O_RDWR | O_CREAT,
+			  stripped_ehdr->e_type == ET_REL ? 0666 : 0777);
+      if (outfd < 0)
+	error (EXIT_FAILURE, errno, _("cannot open '%s'"), output_file);
+      Elf *outelf = elf_begin (outfd, ELF_C_WRITE, NULL);
+      ELF_CHECK (outelf != NULL, _("cannot create ELF descriptor: %s"));
+
+      if (unstripped == NULL)
+	{
+	  /* Actually, we are just copying out the main file as it is.  */
+	  copy_elf (outelf, stripped);
+	  if (stripped_ehdr->e_type != ET_REL)
+	    elf_flagelf (outelf, ELF_C_SET, ELF_F_LAYOUT);
+	  ELF_CHECK (elf_update (outelf, ELF_C_WRITE) > 0,
+		     _("cannot write output file: %s"));
+	}
+      else
+	{
+	  copy_elf (outelf, unstripped);
+	  copy_elided_sections (outelf, stripped, stripped_ehdr, bias);
+	}
+
+      elf_end (outelf);
+      close (outfd);
+    }
+}
+
+static int
+open_file (const char *file, bool writable)
+{
+  int fd = open (file, writable ? O_RDWR : O_RDONLY);
+  if (fd < 0)
+    error (EXIT_FAILURE, errno, _("cannot open '%s'"), file);
+  return fd;
+}
+
+/* Handle a pair of files we need to open by name.  */
+static void
+handle_explicit_files (const char *output_file, bool create_dirs, bool force,
+		       const char *stripped_file, const char *unstripped_file)
+{
+
+  /* Warn, and exit if not forced to continue, if some ELF header
+     sanity check for the stripped and unstripped files failed.  */
+  void warn (const char *msg)
+  {
+    error (force ? 0 : EXIT_FAILURE, 0, "%s'%s' and '%s' %s%s.",
+	   force ? _("WARNING: ") : "",
+	   stripped_file, unstripped_file, msg,
+	   force ? "" : _(", use --force"));
+  }
+
+  int stripped_fd = open_file (stripped_file, false);
+  Elf *stripped = elf_begin (stripped_fd, ELF_C_READ, NULL);
+  GElf_Ehdr stripped_ehdr;
+  ELF_CHECK (gelf_getehdr (stripped, &stripped_ehdr),
+	     _("cannot create ELF descriptor: %s"));
+
+  int unstripped_fd = -1;
+  Elf *unstripped = NULL;
+  if (unstripped_file != NULL)
+    {
+      unstripped_fd = open_file (unstripped_file, output_file == NULL);
+      unstripped = elf_begin (unstripped_fd,
+			      (output_file == NULL ? ELF_C_RDWR : ELF_C_READ),
+			      NULL);
+      GElf_Ehdr unstripped_ehdr;
+      ELF_CHECK (gelf_getehdr (unstripped, &unstripped_ehdr),
+		 _("cannot create ELF descriptor: %s"));
+
+      if (memcmp (stripped_ehdr.e_ident,
+		  unstripped_ehdr.e_ident, EI_NIDENT) != 0)
+	warn (_("ELF header identification (e_ident) different"));
+
+      if (stripped_ehdr.e_type != unstripped_ehdr.e_type)
+	warn (_("ELF header type (e_type) different"));
+
+      if (stripped_ehdr.e_machine != unstripped_ehdr.e_machine)
+	warn (_("ELF header machine type (e_machine) different"));
+
+      if (stripped_ehdr.e_phnum < unstripped_ehdr.e_phnum)
+	warn (_("stripped program header (e_phnum) smaller than unstripped"));
+    }
+
+  handle_file (output_file, create_dirs, stripped, &stripped_ehdr, unstripped);
+
+  elf_end (stripped);
+  close (stripped_fd);
+
+  elf_end (unstripped);
+  close (unstripped_fd);
+}
+
+
+/* Handle a pair of files opened implicitly by libdwfl for one module.  */
+static void
+handle_dwfl_module (const char *output_file, bool create_dirs, bool force,
+		    Dwfl_Module *mod, bool all, bool ignore, bool relocate)
+{
+  GElf_Addr bias;
+  Elf *stripped = dwfl_module_getelf (mod, &bias);
+  if (stripped == NULL)
+    {
+      if (ignore)
+	return;
+
+      const char *file;
+      const char *modname = dwfl_module_info (mod, NULL, NULL, NULL,
+					      NULL, NULL, &file, NULL);
+      if (file == NULL)
+	error (EXIT_FAILURE, 0,
+	       _("cannot find stripped file for module '%s': %s"),
+	       modname, dwfl_errmsg (-1));
+      else
+	error (EXIT_FAILURE, 0,
+	       _("cannot open stripped file '%s' for module '%s': %s"),
+	       modname, file, dwfl_errmsg (-1));
+    }
+
+  Elf *debug = dwarf_getelf (dwfl_module_getdwarf (mod, &bias));
+  if (debug == NULL && !all)
+    {
+      if (ignore)
+	return;
+
+      const char *file;
+      const char *modname = dwfl_module_info (mod, NULL, NULL, NULL,
+					      NULL, NULL, NULL, &file);
+      if (file == NULL)
+	error (EXIT_FAILURE, 0,
+	       _("cannot find debug file for module '%s': %s"),
+	       modname, dwfl_errmsg (-1));
+      else
+	error (EXIT_FAILURE, 0,
+	       _("cannot open debug file '%s' for module '%s': %s"),
+	       modname, file, dwfl_errmsg (-1));
+    }
+
+  if (debug == stripped)
+    {
+      if (all)
+	debug = NULL;
+      else
+	{
+	  const char *file;
+	  const char *modname = dwfl_module_info (mod, NULL, NULL, NULL,
+						  NULL, NULL, &file, NULL);
+	  error (EXIT_FAILURE, 0, _("module '%s' file '%s' is not stripped"),
+		 modname, file);
+	}
+    }
+
+  GElf_Ehdr stripped_ehdr;
+  ELF_CHECK (gelf_getehdr (stripped, &stripped_ehdr),
+	     _("cannot create ELF descriptor: %s"));
+
+  if (stripped_ehdr.e_type == ET_REL)
+    {
+      if (!relocate)
+	{
+	  /* We can't use the Elf handles already open,
+	     because the DWARF sections have been relocated.  */
+
+	  const char *stripped_file = NULL;
+	  const char *unstripped_file = NULL;
+	  (void) dwfl_module_info (mod, NULL, NULL, NULL, NULL, NULL,
+				   &stripped_file, &unstripped_file);
+
+	  handle_explicit_files (output_file, create_dirs, force,
+				 stripped_file, unstripped_file);
+	  return;
+	}
+
+      /* Relocation is what we want!  This ensures that all sections that can
+	 get sh_addr values assigned have them, even ones not used in DWARF.
+	 They might still be used in the symbol table.  */
+      if (dwfl_module_relocations (mod) < 0)
+	error (EXIT_FAILURE, 0,
+	       _("cannot cache section addresses for module '%s': %s"),
+	       dwfl_module_info (mod, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
+	       dwfl_errmsg (-1));
+    }
+
+  handle_file (output_file, create_dirs, stripped, &stripped_ehdr, debug);
+}
+
+/* Handle one module being written to the output directory.  */
+static void
+handle_output_dir_module (const char *output_dir, Dwfl_Module *mod, bool force,
+			  bool all, bool ignore, bool modnames, bool relocate)
+{
+  if (! modnames)
+    {
+      /* Make sure we've searched for the ELF file.  */
+      GElf_Addr bias;
+      (void) dwfl_module_getelf (mod, &bias);
+    }
+
+  const char *file;
+  const char *name = dwfl_module_info (mod, NULL, NULL, NULL,
+				       NULL, NULL, &file, NULL);
+
+  if (file == NULL && ignore)
+    return;
+
+  char *output_file;
+  if (asprintf (&output_file, "%s/%s", output_dir, modnames ? name : file) < 0)
+    error (EXIT_FAILURE, 0, _("memory exhausted"));
+
+  handle_dwfl_module (output_file, true, force, mod, all, ignore, relocate);
+}
+
+
+static void
+list_module (Dwfl_Module *mod)
+{
+  /* Make sure we have searched for the files.  */
+  GElf_Addr bias;
+  bool have_elf = dwfl_module_getelf (mod, &bias) != NULL;
+  bool have_dwarf = dwfl_module_getdwarf (mod, &bias) != NULL;
+
+  const char *file;
+  const char *debug;
+  Dwarf_Addr start;
+  Dwarf_Addr end;
+  const char *name = dwfl_module_info (mod, NULL, &start, &end,
+				       NULL, NULL, &file, &debug);
+  if (file != NULL && debug != NULL && (debug == file || !strcmp (debug, file)))
+    debug = ".";
+
+  const unsigned char *id;
+  GElf_Addr id_vaddr;
+  int id_len = dwfl_module_build_id (mod, &id, &id_vaddr);
+
+  printf ("%#" PRIx64 "+%#" PRIx64 " ", start, end - start);
+
+  if (id_len > 0)
+    {
+      do
+	printf ("%02" PRIx8, *id++);
+      while (--id_len > 0);
+      if (id_vaddr != 0)
+	printf ("@%#" PRIx64, id_vaddr);
+    }
+  else
+    putchar ('-');
+
+  printf (" %s %s %s\n",
+	  file ?: have_elf ? "." : "-",
+	  debug ?: have_dwarf ? "." : "-",
+	  name);
+}
+
+
+struct match_module_info
+{
+  char **patterns;
+  Dwfl_Module *found;
+  bool match_files;
+};
+
+static int
+match_module (Dwfl_Module *mod,
+	      void **userdata __attribute__ ((unused)),
+	      const char *name,
+	      Dwarf_Addr start __attribute__ ((unused)),
+	      void *arg)
+{
+  struct match_module_info *info = arg;
+
+  if (info->patterns[0] == NULL) /* Match all.  */
+    {
+    match:
+      info->found = mod;
+      return DWARF_CB_ABORT;
+    }
+
+  if (info->match_files)
+    {
+      /* Make sure we've searched for the ELF file.  */
+      GElf_Addr bias;
+      (void) dwfl_module_getelf (mod, &bias);
+
+      const char *file;
+      const char *check = dwfl_module_info (mod, NULL, NULL, NULL,
+					    NULL, NULL, &file, NULL);
+      assert (check == name);
+      if (file == NULL)
+	return DWARF_CB_OK;
+
+      name = file;
+    }
+
+  for (char **p = info->patterns; *p != NULL; ++p)
+    if (fnmatch (*p, name, 0) == 0)
+      goto match;
+
+  return DWARF_CB_OK;
+}
+
+/* Handle files opened implicitly via libdwfl.  */
+static void
+handle_implicit_modules (const struct arg_info *info)
+{
+  struct match_module_info mmi = { info->args, NULL, info->match_files };
+  inline ptrdiff_t next (ptrdiff_t offset)
+    {
+      return dwfl_getmodules (info->dwfl, &match_module, &mmi, offset);
+    }
+  ptrdiff_t offset = next (0);
+  if (offset == 0)
+    error (EXIT_FAILURE, 0, _("no matching modules found"));
+
+  if (info->list)
+    do
+      list_module (mmi.found);
+    while ((offset = next (offset)) > 0);
+  else if (info->output_dir == NULL)
+    {
+      if (next (offset) != 0)
+	error (EXIT_FAILURE, 0, _("matched more than one module"));
+      handle_dwfl_module (info->output_file, false, info->force, mmi.found,
+			  info->all, info->ignore, info->relocate);
+    }
+  else
+    do
+      handle_output_dir_module (info->output_dir, mmi.found, info->force,
+				info->all, info->ignore,
+				info->modnames, info->relocate);
+    while ((offset = next (offset)) > 0);
+}
+
+int
+main (int argc, char **argv)
+{
+  /* We use no threads here which can interfere with handling a stream.  */
+  __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+  __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+  __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  setlocale (LC_ALL, "");
+
+  /* Make sure the message catalog can be found.  */
+  bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
+
+  /* Initialize the message catalog.  */
+  textdomain (PACKAGE_TARNAME);
+
+  /* Parse and process arguments.  */
+  const struct argp_child argp_children[] =
+    {
+      {
+	.argp = dwfl_standard_argp (),
+	.header = N_("Input selection options:"),
+	.group = 1,
+      },
+      { .argp = NULL },
+    };
+  const struct argp argp =
+    {
+      .options = options,
+      .parser = parse_opt,
+      .children = argp_children,
+      .args_doc = N_("STRIPPED-FILE DEBUG-FILE\n[MODULE...]"),
+      .doc = N_("\
+Combine stripped files with separate symbols and debug information.\n\
+\n\
+The first form puts the result in DEBUG-FILE if -o was not given.\n\
+\n\
+MODULE arguments give file name patterns matching modules to process.\n\
+With -f these match the file name of the main (stripped) file \
+(slashes are never special), otherwise they match the simple module names.  \
+With no arguments, process all modules found.\n\
+\n\
+Multiple modules are written to files under OUTPUT-DIRECTORY, \
+creating subdirectories as needed.  \
+With -m these files have simple module names, otherwise they have the \
+name of the main file complete with directory underneath OUTPUT-DIRECTORY.\n\
+\n\
+With -n no files are written, but one line to standard output for each module:\
+\n\tSTART+SIZE BUILDID FILE DEBUGFILE MODULENAME\n\
+START and SIZE are hexadecimal giving the address bounds of the module.  \
+BUILDID is hexadecimal for the build ID bits, or - if no ID is known; \
+the hexadecimal may be followed by @0xADDR giving the address where the \
+ID resides if that is known.  \
+FILE is the file name found for the module, or - if none was found, \
+or . if an ELF image is available but not from any named file.  \
+DEBUGFILE is the separate debuginfo file name, \
+or - if no debuginfo was found, or . if FILE contains the debug information.\
+")
+    };
+
+  int remaining;
+  struct arg_info info = { .args = NULL };
+  error_t result = argp_parse (&argp, argc, argv, 0, &remaining, &info);
+  if (result == ENOSYS)
+    assert (info.dwfl == NULL);
+  else if (result)
+    return EXIT_FAILURE;
+  assert (info.args != NULL);
+
+  /* Tell the library which version we are expecting.  */
+  elf_version (EV_CURRENT);
+
+  if (info.dwfl == NULL)
+    {
+      assert (result == ENOSYS);
+
+      if (info.output_dir != NULL)
+	{
+	  char *file;
+	  if (asprintf (&file, "%s/%s", info.output_dir, info.args[0]) < 0)
+	    error (EXIT_FAILURE, 0, _("memory exhausted"));
+	  handle_explicit_files (file, true, info.force,
+				 info.args[0], info.args[1]);
+	  free (file);
+	}
+      else
+	handle_explicit_files (info.output_file, false, info.force,
+			       info.args[0], info.args[1]);
+    }
+  else
+    {
+      /* parse_opt checked this.  */
+      assert (info.output_file != NULL || info.output_dir != NULL || info.list);
+
+      handle_implicit_modules (&info);
+
+      dwfl_end (info.dwfl);
+    }
+
+  return 0;
+}
+
+
+#include "debugpred.h"