summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Wielaard <mark@klomp.org>2018-08-12 15:35:18 +0200
committerMark Wielaard <mark@klomp.org>2018-09-13 14:25:22 +0200
commitfb0457f4671e7e0f8331453348005ed6beab275e (patch)
tree5efa18a7afd9543e644e4e2ab2b14a4d9f887498
parenta1e892e920bbde60a9daa1f98c105c227ee7427d (diff)
libelf: Fix some issues with ELF_C_RDWR_MMAP.
When ELF_C_RDWR_MMAP is used libelf might have to write overlapping memory when moving the section data or headers. Make sure to use memmove, not memcpy. Also the size of the underlying file might have to change. That means we will have to also extend the mmap region with mremap. Since we are using direct pointers into the mmapped area we cannot move the mmap, only extend it. This might still fail if there is not enough free memory available to extend the mmap region. Two new test programs have been added. elfcopy which copies a whole elf file (using either ELF_C_WRITE or ELF_C_WRITE_MMAP). And addsections which adds new sections to an existing ELF file (using either ELF_C_RDWR or ELF_C_RDWR_MMAP). The newly added test will fail under valgrind without the fixes. Signed-off-by: Mark Wielaard <mark@klomp.org>
-rw-r--r--libelf/ChangeLog6
-rw-r--r--libelf/elf32_updatefile.c10
-rw-r--r--libelf/elf_update.c30
-rw-r--r--tests/ChangeLog11
-rw-r--r--tests/Makefile.am11
-rw-r--r--tests/addsections.c286
-rw-r--r--tests/elfcopy.c286
-rwxr-xr-xtests/run-copyadd-sections.sh87
8 files changed, 713 insertions, 14 deletions
diff --git a/libelf/ChangeLog b/libelf/ChangeLog
index 7c884b00..35421222 100644
--- a/libelf/ChangeLog
+++ b/libelf/ChangeLog
@@ -1,3 +1,9 @@
+2018-09-12 Mark Wielaard <mark@klomp.org>
+
+ * elf32_updatefile.c (updatemmap): Use memmove, not memcpy.
+ * elf_update.c (write_file): Try to mremap if file needs to be
+ extended.
+
2018-08-18 Mark Wielaard <mark@klomp.org>
* libelf.h (elf_compress_gnu): Add documentation about
diff --git a/libelf/elf32_updatefile.c b/libelf/elf32_updatefile.c
index 7ac99510..545ce083 100644
--- a/libelf/elf32_updatefile.c
+++ b/libelf/elf32_updatefile.c
@@ -203,7 +203,7 @@ __elfw2(LIBELFBITS,updatemmap) (Elf *elf, int change_bo, size_t shnum)
sizeof (ElfW2(LIBELFBITS,Phdr)) * phnum, 1);
}
else
- memcpy (elf->map_address + elf->start_offset + ehdr->e_phoff,
+ memmove (elf->map_address + elf->start_offset + ehdr->e_phoff,
elf->state.ELFW(elf,LIBELFBITS).phdr,
sizeof (ElfW2(LIBELFBITS,Phdr)) * phnum);
@@ -371,9 +371,11 @@ __elfw2(LIBELFBITS,updatemmap) (Elf *elf, int change_bo, size_t shnum)
last_position += dl->data.d.d_size;
}
else if (dl->data.d.d_size != 0)
- last_position = mempcpy (last_position,
- dl->data.d.d_buf,
- dl->data.d.d_size);
+ {
+ memmove (last_position, dl->data.d.d_buf,
+ dl->data.d.d_size);
+ last_position += dl->data.d.d_size;
+ }
scn_changed = true;
}
diff --git a/libelf/elf_update.c b/libelf/elf_update.c
index 8ce07829..36997c2b 100644
--- a/libelf/elf_update.c
+++ b/libelf/elf_update.c
@@ -93,13 +93,29 @@ write_file (Elf *elf, off_t size, int change_bo, size_t shnum)
ENOSPC. Otherwise we ignore the error and treat it as just hint. */
if (elf->parent == NULL
&& (elf->maximum_size == ~((size_t) 0)
- || (size_t) size > elf->maximum_size)
- && unlikely (posix_fallocate (elf->fildes, 0, size) != 0))
- if (errno == ENOSPC)
- {
- __libelf_seterrno (ELF_E_WRITE_ERROR);
- return -1;
- }
+ || (size_t) size > elf->maximum_size))
+ {
+ if (unlikely (posix_fallocate (elf->fildes, 0, size) != 0))
+ if (errno == ENOSPC)
+ {
+ __libelf_seterrno (ELF_E_WRITE_ERROR);
+ return -1;
+ }
+
+ /* Extend the mmap address if needed. */
+ if (elf->cmd == ELF_C_RDWR_MMAP
+ && (size_t) size > elf->maximum_size)
+ {
+ if (mremap (elf->map_address, elf->maximum_size,
+ size, 0) == MAP_FAILED)
+ {
+ __libelf_seterrno (ELF_E_WRITE_ERROR);
+ return -1;
+ }
+ elf->maximum_size = size;
+ }
+
+ }
/* The file is mmaped. */
if ((class == ELFCLASS32
diff --git a/tests/ChangeLog b/tests/ChangeLog
index 8d70891a..57098578 100644
--- a/tests/ChangeLog
+++ b/tests/ChangeLog
@@ -1,3 +1,14 @@
+2018-09-12 Mark Wielaard <mark@klomp.org>
+
+ * Makefile.am (check_PROGRAMS): Add elfcopy and addsections.
+ (TESTS): Add run-copyadd-sections.sh.
+ (EXTRA_DIST): Likewise.
+ (elfcopy_LDADD): New variable.
+ (addsections_LDADD): Likewise.
+ * addsections.c: New file.
+ * elfcopy.c: Likewise.
+ * run-copyadd-sections.sh: New test.
+
2018-09-11 Mark Wielaard <mark@klomp.org>
* backtrace-dwarf.c (main): Add section attribute.
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 29460834..e0edef0c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -59,7 +59,8 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \
elfgetzdata elfputzdata zstrptr emptyfile vendorelf \
fillfile dwarf_default_lower_bound dwarf-die-addr-die \
get-units-invalid get-units-split attr-integrate-skel \
- all-dwarf-ranges unit-info next_cfi
+ all-dwarf-ranges unit-info next_cfi \
+ elfcopy addsections
asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \
asm-tst6 asm-tst7 asm-tst8 asm-tst9
@@ -153,7 +154,8 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \
run-attr-integrate-skel.sh \
run-all-dwarf-ranges.sh run-unit-info.sh \
run-reloc-bpf.sh \
- run-next-cfi.sh run-next-cfi-self.sh
+ run-next-cfi.sh run-next-cfi-self.sh \
+ run-copyadd-sections.sh
if !BIARCH
export ELFUTILS_DISABLE_BIARCH = 1
@@ -403,7 +405,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
testfile-ranges-hello.dwo.bz2 testfile-ranges-world.dwo.bz2 \
run-unit-info.sh run-next-cfi.sh run-next-cfi-self.sh \
testfile-riscv64.bz2 testfile-riscv64-s.bz2 \
- testfile-riscv64-core.bz2
+ testfile-riscv64-core.bz2 \
+ run-copyadd-sections.sh
if USE_VALGRIND
valgrind_cmd='valgrind -q --leak-check=full --error-exitcode=1'
@@ -571,6 +574,8 @@ attr_integrate_skel_LDADD = $(libdw)
all_dwarf_ranges_LDADD = $(libdw)
unit_info_LDADD = $(libdw)
next_cfi_LDADD = $(libelf) $(libdw)
+elfcopy_LDADD = $(libelf)
+addsections_LDADD = $(libelf)
# We want to test the libelf header against the system elf.h header.
# Don't include any -I CPPFLAGS.
diff --git a/tests/addsections.c b/tests/addsections.c
new file mode 100644
index 00000000..391c5b47
--- /dev/null
+++ b/tests/addsections.c
@@ -0,0 +1,286 @@
+/* Test program for adding (more than SHN_LORESERVE) sections.
+ Copyright (C) 2018 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 <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include ELFUTILS_HEADER(elf)
+#include <gelf.h>
+
+
+/* shstrndx is special, might overflow into section zero header sh_link. */
+static int
+setshstrndx (Elf *elf, size_t ndx)
+{
+ printf ("setshstrndx: %zd\n", ndx);
+
+ GElf_Ehdr ehdr_mem;
+ GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
+ if (ehdr == NULL)
+ return -1;
+
+ 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;
+}
+
+/* Will add nr new '.extra' sections and a new '.new_shstrtab' section
+ at the end. */
+static void
+add_sections (const char *name, size_t nr, int use_mmap)
+{
+ printf ("add_sections '%s': %zd\n", name, nr);
+
+ int fd = open (name, O_RDWR);
+ if (fd < 0)
+ {
+ fprintf (stderr, "Couldn't open file '%s': %s\n",
+ name, strerror (errno));
+ exit (1);
+ }
+
+ Elf *elf = elf_begin (fd, use_mmap ? ELF_C_RDWR_MMAP : ELF_C_RDWR, NULL);
+ if (elf == NULL)
+ {
+ fprintf (stderr, "Couldn't open ELF file '%s': %s\n",
+ name, elf_errmsg (-1));
+ exit (1);
+ }
+
+ /* We will add a new shstrtab section with two new names at the end.
+ Just get the current shstrtab table and add two entries '.extra'
+ and '.new_shstrtab' at the end of the table, so all existing indexes
+ are still valid. */
+ size_t shstrndx;
+ if (elf_getshdrstrndx (elf, &shstrndx) < 0)
+ {
+ printf ("cannot get shstrndx: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ Elf_Scn *shstrtab_scn = elf_getscn (elf, shstrndx);
+ if (shstrtab_scn == NULL)
+ {
+ printf ("couldn't get shstrtab scn: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+ Elf_Data *shstrtab_data = elf_getdata (shstrtab_scn, NULL);
+ if (shstrtab_data == NULL)
+ {
+ printf ("couldn't get shstrtab data: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+ size_t new_shstrtab_size = (shstrtab_data->d_size
+ + strlen (".extra") + 1
+ + strlen (".new_shstrtab") + 1);
+ void *new_shstrtab_buf = malloc (new_shstrtab_size);
+ if (new_shstrtab_buf == NULL)
+ {
+ printf ("couldn't allocate new shstrtab data d_buf\n");
+ exit (1);
+ }
+ memcpy (new_shstrtab_buf, shstrtab_data->d_buf, shstrtab_data->d_size);
+ size_t extra_idx = shstrtab_data->d_size;
+ size_t new_shstrtab_idx = extra_idx + strlen (".extra") + 1;
+ strcpy (new_shstrtab_buf + extra_idx, ".extra");
+ strcpy (new_shstrtab_buf + new_shstrtab_idx, ".new_shstrtab");
+
+ // Add lots of .extra sections...
+ size_t cnt = 0;
+ while (cnt++ < nr)
+ {
+ Elf_Scn *scn = elf_newscn (elf);
+ if (scn == NULL)
+ {
+ printf ("cannot create .extra section (%zd): %s\n", cnt,
+ elf_errmsg (-1));
+ exit (1);
+ }
+
+ Elf_Data *data = elf_newdata (scn);
+ if (data == NULL)
+ {
+ printf ("couldn't create new section data (%zd): %s\n", cnt,
+ elf_errmsg (-1));
+ exit (1);
+ }
+
+ data->d_size = strlen ("extra") + 1;
+ data->d_buf = "extra";
+ data->d_type = ELF_T_BYTE;
+ data->d_align = 1;
+
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+ if (shdr == NULL)
+ {
+ printf ("cannot get header for new section (%zd): %s\n", cnt,
+ elf_errmsg (-1));
+ exit (1);
+ }
+
+ shdr->sh_type = SHT_PROGBITS;
+ shdr->sh_flags = 0;
+ shdr->sh_addr = 0;
+ shdr->sh_link = SHN_UNDEF;
+ shdr->sh_info = SHN_UNDEF;
+ shdr->sh_addralign = 1;
+ shdr->sh_entsize = 0;
+ shdr->sh_size = data->d_size;
+ shdr->sh_name = extra_idx;
+
+ if (gelf_update_shdr (scn, shdr) == 0)
+ {
+ printf ("cannot update new section header (%zd): %s\n", cnt,
+ elf_errmsg (-1));
+ exit (1);
+ }
+ }
+
+ // Create new shstrtab section.
+ Elf_Scn *new_shstrtab_scn = elf_newscn (elf);
+ if (new_shstrtab_scn == NULL)
+ {
+ printf ("cannot create new shstrtab section: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ Elf_Data *new_shstrtab_data = elf_newdata (new_shstrtab_scn);
+ if (new_shstrtab_data == NULL)
+ {
+ printf ("couldn't create new shstrtab section data: %s\n",
+ elf_errmsg (-1));
+ exit (1);
+ }
+
+ new_shstrtab_data->d_size = new_shstrtab_size;
+ new_shstrtab_data->d_buf = new_shstrtab_buf;
+ new_shstrtab_data->d_type = ELF_T_BYTE;
+ new_shstrtab_data->d_align = 1;
+
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (new_shstrtab_scn, &shdr_mem);
+ if (shdr == NULL)
+ {
+ printf ("cannot get header for new shstrtab section: %s\n",
+ elf_errmsg (-1));
+ exit (1);
+ }
+
+ shdr->sh_type = SHT_STRTAB;
+ shdr->sh_flags = 0;
+ shdr->sh_addr = 0;
+ shdr->sh_link = SHN_UNDEF;
+ shdr->sh_info = SHN_UNDEF;
+ shdr->sh_addralign = 1;
+ shdr->sh_entsize = 0;
+ shdr->sh_size = new_shstrtab_size;
+ shdr->sh_name = new_shstrtab_idx;
+
+ // Finished new shstrtab section, update the header.
+ if (gelf_update_shdr (new_shstrtab_scn, shdr) == 0)
+ {
+ printf ("cannot update new shstrtab section header: %s\n",
+ elf_errmsg (-1));
+ exit (1);
+ }
+
+ // Set it as the new shstrtab section to get the names correct.
+ size_t new_shstrndx = elf_ndxscn (new_shstrtab_scn);
+ if (setshstrndx (elf, new_shstrndx) < 0)
+ {
+ printf ("cannot set shstrndx: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ // Write everything to disk.
+ if (elf_update (elf, ELF_C_WRITE) < 0)
+ {
+ printf ("failure in elf_update: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ if (elf_end (elf) != 0)
+ {
+ printf ("couldn't cleanup elf '%s': %s\n", name, elf_errmsg (-1));
+ exit (1);
+ }
+
+ if (close (fd) != 0)
+ {
+ printf ("couldn't close '%s': %s\n", name, strerror (errno));
+ exit (1);
+ }
+
+ free (new_shstrtab_buf);
+}
+
+int
+main (int argc, char *argv[])
+{
+ elf_version (EV_CURRENT);
+
+ /* Takes the given file, and adds the given number of sections. */
+ if (argc < 3 || argc > 4)
+ {
+ fprintf (stderr, "addsections [--mmap] nr elf.file\n");
+ exit (1);
+ }
+
+ int argn = 1;
+ bool use_mmap = false;
+ if (strcmp (argv[argn], "--mmap") == 0)
+ {
+ use_mmap = true;
+ argn++;
+ }
+
+ size_t nr = atoi (argv[argn++]);
+ const char *file = argv[argn];
+ add_sections (file, nr, use_mmap);
+
+ return 0;
+}
diff --git a/tests/elfcopy.c b/tests/elfcopy.c
new file mode 100644
index 00000000..9000cc96
--- /dev/null
+++ b/tests/elfcopy.c
@@ -0,0 +1,286 @@
+/* Test program for copying a whole ELF file using libelf.
+ Copyright (C) 2018 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 <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include ELFUTILS_HEADER(elf)
+#include <gelf.h>
+
+
+/* shstrndx is special, might overflow into section zero header sh_link. */
+static int
+setshstrndx (Elf *elf, size_t ndx)
+{
+ printf ("setshstrndx: %zd\n", ndx);
+
+ GElf_Ehdr ehdr_mem;
+ GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
+ if (ehdr == NULL)
+ return -1;
+
+ 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;
+}
+
+/* Copies all elements of an ELF file either using mmap or read. */
+static void
+copy_elf (const char *in, const char *out, bool use_mmap)
+{
+ printf ("\ncopy_elf: %s -> %s (%s)\n", in, out, use_mmap ? "mmap" : "read");
+
+ /* Existing ELF file. */
+ int fda = open (in, O_RDONLY);
+ if (fda < 0)
+ {
+ fprintf (stderr, "Couldn't open file '%s': %s\n",
+ in, strerror (errno));
+ exit (1);
+ }
+
+ Elf *elfa = elf_begin (fda, use_mmap ? ELF_C_READ_MMAP : ELF_C_READ, NULL);
+ if (elfa == NULL)
+ {
+ fprintf (stderr, "Couldn't open ELF file '%s': %s\n",
+ in, elf_errmsg (-1));
+ exit (1);
+ }
+
+ /* Open new file. */
+ int fdb = open (out, O_RDWR | O_CREAT | O_TRUNC, 0644);
+ if (fdb < 0)
+ {
+ fprintf (stderr, "Couldn't create file '%s': %s\n",
+ out, strerror (errno));
+ exit (1);
+ }
+
+ Elf *elfb = elf_begin (fdb, use_mmap ? ELF_C_WRITE_MMAP : ELF_C_WRITE, NULL);
+ if (elfb == NULL)
+ {
+ fprintf (stderr, "Couldn't create ELF file '%s': %s\n",
+ out, elf_errmsg (-1));
+ exit (1);
+ }
+
+ // Copy ELF header.
+ GElf_Ehdr ehdr_mema;
+ GElf_Ehdr *ehdra = gelf_getehdr (elfa, &ehdr_mema);
+ if (ehdra == NULL)
+ {
+ printf ("cannot get ELF header: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ int class = gelf_getclass (elfa);
+ // Create an ELF header.
+ if (gelf_newehdr (elfb, class) == 0)
+ {
+ printf ("cannot create ELF header: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ /* New elf header is an exact copy. */
+ GElf_Ehdr ehdr_memb;
+ GElf_Ehdr *ehdrb = &ehdr_memb;
+ *ehdrb = *ehdra;
+ if (gelf_update_ehdr (elfb, ehdrb) == 0)
+ {
+ printf ("cannot update ELF header: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ /* shstrndx is special. (Technically phdrnum and shdrnum are also
+ special, but they are handled by libelf.) */
+ size_t shstrndx;
+ if (elf_getshdrstrndx (elfa, &shstrndx) < 0)
+ {
+ printf ("cannot get shstrndx: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+ if (setshstrndx (elfb, shstrndx) < 0)
+ {
+ printf ("cannot set shstrndx: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ /* If there are phdrs, copy them over. */
+ size_t phnum;
+ if (elf_getphdrnum (elfa, &phnum) != 0)
+ {
+ printf ("cannot get phdrs: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ if (phnum > 0)
+ {
+ if (gelf_newphdr (elfb, phnum) == 0)
+ {
+ printf ("cannot create phdrs: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ for (size_t cnt = 0; cnt < phnum; ++cnt)
+ {
+ GElf_Phdr phdr_mem;
+ GElf_Phdr *phdr = gelf_getphdr (elfa, cnt, &phdr_mem);
+ if (phdr == NULL)
+ {
+ printf ("couldn't get phdr %zd: %s\n", cnt, elf_errmsg (-1));
+ exit (1);
+ }
+
+ if (gelf_update_phdr (elfb, cnt, phdr) == 0)
+ {
+ printf ("couldn't update phdr %zd: %s\n", cnt, elf_errmsg (-1));
+ exit (1);
+ }
+ }
+ }
+
+ /* Copy all sections, headers and data. */
+ Elf_Scn *scn = NULL;
+ while ((scn = elf_nextscn (elfa, scn)) != NULL)
+ {
+ /* Get the header. */
+ GElf_Shdr shdr;
+ if (gelf_getshdr (scn, &shdr) == NULL)
+ {
+ printf ("couldn't get shdr: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ /* Create new section. */
+ Elf_Scn *new_scn = elf_newscn (elfb);
+ if (new_scn == NULL)
+ {
+ printf ("couldn't create new section: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ if (gelf_update_shdr (new_scn, &shdr) == 0)
+ {
+ printf ("couldn't update shdr: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ /* Copy over section data. */
+ Elf_Data *data = NULL;
+ while ((data = elf_getdata (scn, data)) != NULL)
+ {
+ Elf_Data *new_data = elf_newdata (new_scn);
+ if (new_data == NULL)
+ {
+ printf ("couldn't create new section data: %s\n",
+ elf_errmsg (-1));
+ exit (1);
+ }
+ *new_data = *data;
+ }
+ }
+
+ /* Write everything to disk. If there are any phdrs then we want
+ the exact same layout. Do we want ELF_F_PERMISSIVE? */
+ if (phnum > 0)
+ elf_flagelf (elfb, ELF_C_SET, ELF_F_LAYOUT);
+ if (elf_update (elfb, ELF_C_WRITE) < 0)
+ {
+ printf ("failure in elf_update: %s\n", elf_errmsg (-1));
+ exit (1);
+ }
+
+ if (elf_end (elfa) != 0)
+ {
+ printf ("couldn't cleanup elf '%s': %s\n", in, elf_errmsg (-1));
+ exit (1);
+ }
+
+ if (close (fda) != 0)
+ {
+ printf ("couldn't close '%s': %s\n", in, strerror (errno));
+ exit (1);
+ }
+
+ if (elf_end (elfb) != 0)
+ {
+ printf ("couldn't cleanup elf '%s': %s\n", out, elf_errmsg (-1));
+ exit (1);
+ }
+
+ if (close (fdb) != 0)
+ {
+ printf ("couldn't close '%s': %s\n", out, strerror (errno));
+ exit (1);
+ }
+}
+
+int
+main (int argc, const char *argv[])
+{
+ elf_version (EV_CURRENT);
+
+ /* Takes the given file, and create a new identical one. */
+ if (argc < 3 || argc > 4)
+ {
+ fprintf (stderr, "elfcopy [--mmap] in.elf out.elf\n");
+ exit (1);
+ }
+
+ int argn = 1;
+ bool use_mmap = false;
+ if (strcmp (argv[argn], "--mmap") == 0)
+ {
+ use_mmap = true;
+ argn++;
+ }
+
+ const char *in = argv[argn++];
+ const char *out = argv[argn];
+ copy_elf (in, out, use_mmap);
+
+ return 0;
+}
diff --git a/tests/run-copyadd-sections.sh b/tests/run-copyadd-sections.sh
new file mode 100755
index 00000000..bc20f6ee
--- /dev/null
+++ b/tests/run-copyadd-sections.sh
@@ -0,0 +1,87 @@
+#! /bin/sh
+# Copyright (C) 2018 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/>.
+
+. $srcdir/test-subr.sh
+
+test_copy_and_add ()
+{
+ in_file="$1"
+ out_file="${in_file}.copy"
+ out_file_mmap="${out_file}.mmap"
+
+ testfiles ${in_file}
+ tempfiles ${out_file} ${out_file_mmap} readelf.out
+
+ # Can we copy the file?
+ testrun ${abs_builddir}/elfcopy ${in_file} ${out_file}
+ testrun ${abs_top_builddir}/src/elfcmp ${in_file} ${out_file}
+
+ # Can we add a section (in-place)?
+ testrun ${abs_builddir}/addsections 3 ${out_file}
+ testrun ${abs_top_builddir}/src/readelf -S ${out_file} > readelf.out
+ nr=$(grep '.extra' readelf.out | wc -l)
+ if test ${nr} != 3; then
+ # Show what went wrong
+ testrun ${abs_top_builddir}/src/readelf -S ${out_file}
+ exit 1
+ fi
+
+ # Can we add a section (in-place) using ELF_C_WRITE_MMAP?
+ testrun ${abs_builddir}/elfcopy --mmap ${in_file} ${out_file_mmap}
+ testrun ${abs_top_builddir}/src/elfcmp ${in_file} ${out_file_mmap}
+
+ # Can we add a section (in-place) using ELF_C_RDWR_MMAP?
+ # Note we are only adding one sections, adding more might fail
+ # because mremap cannot extend too much.
+ testrun ${abs_builddir}/addsections --mmap 1 ${out_file_mmap}
+ testrun ${abs_top_builddir}/src/readelf -S ${out_file_mmap} > readelf.out
+ nr=$(grep '.extra' readelf.out | wc -l)
+ if test ${nr} != 1; then
+ # Show what went wrong
+ testrun ${abs_top_builddir}/src/readelf -S ${out_file_mmap}
+ exit 1
+ fi
+}
+
+# A collection of random testfiles to test 32/64bit, little/big endian
+# and non-ET_REL (with phdrs)/ET_REL (without phdrs).
+
+# 32bit, big endian, rel
+test_copy_and_add testfile29
+
+# 64bit, big endian, rel
+test_copy_and_add testfile23
+
+# 32bit, little endian, rel
+test_copy_and_add testfile9
+
+# 64bit, little endian, rel
+test_copy_and_add testfile38
+
+# 32bit, big endian, non-rel
+test_copy_and_add testfile26
+
+# 64bit, big endian, non-rel
+test_copy_and_add testfile27
+
+# 32bit, little endian, non-rel
+test_copy_and_add testfile
+
+# 64bit, little endian, non-rel
+test_copy_and_add testfile10
+
+exit 0