about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRoland McGrath <roland@hack.frob.com>2014-04-03 10:47:14 -0700
committerRoland McGrath <roland@hack.frob.com>2014-04-03 10:47:14 -0700
commitfcccd51286acbf9c19ac57ab7143e257d58323fd (patch)
tree011b737ea7df67117682d2fd4e0a6998c8eca840
parentf6488e2b7f13529cde762d02a0352071c078ff9a (diff)
downloadglibc-fcccd51286acbf9c19ac57ab7143e257d58323fd.tar.gz
glibc-fcccd51286acbf9c19ac57ab7143e257d58323fd.tar.xz
glibc-fcccd51286acbf9c19ac57ab7143e257d58323fd.zip
Factor mmap/munmap of PT_LOAD segments out of _dl_map_object_from_fd et al.
-rw-r--r--ChangeLog23
-rw-r--r--elf/dl-close.c2
-rw-r--r--elf/dl-fptr.c4
-rw-r--r--elf/dl-load.c221
-rw-r--r--elf/dl-load.h135
-rw-r--r--elf/dl-map-segments.h153
-rw-r--r--elf/dl-unmap-segments.h35
-rw-r--r--ports/ChangeLog.hppa5
-rw-r--r--ports/sysdeps/hppa/dl-fptr.c4
-rw-r--r--sysdeps/aarch64/tlsdesc.c4
-rw-r--r--sysdeps/arm/tlsdesc.c4
-rw-r--r--sysdeps/generic/ldsodefs.h4
-rw-r--r--sysdeps/i386/tlsdesc.c4
-rw-r--r--sysdeps/tile/dl-runtime.c3
-rw-r--r--sysdeps/x86_64/tlsdesc.c4
15 files changed, 391 insertions, 214 deletions
diff --git a/ChangeLog b/ChangeLog
index e36b689d29..7a1c10620a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+2014-04-03  Roland McGrath  <roland@hack.frob.com>
+
+	* elf/dl-unmap-segments.h: New file.
+	* sysdeps/generic/ldsodefs.h
+	(DL_UNMAP): Use _dl_unmap_segments in place of __munmap.
+	* elf/dl-close.c: Include <dl-unmap-segments.h>.
+	* elf/dl-fptr.c: Likewise.
+	(_dl_unmap): Use _dl_unmap_segments in place of __munmap.
+	* sysdeps/aarch64/tlsdesc.c: Likewise.
+	* sysdeps/arm/tlsdesc.c: Likewise.
+	* sysdeps/i386/tlsdesc.c: Likewise.
+	* sysdeps/tile/dl-runtime.c: Likewise.
+	* sysdeps/x86_64/tlsdesc.c: Likewise.
+	* elf/dl-load.h: New file.
+	* elf/dl-load.c: Include it.
+	(MAP_FILE, MAP_COPY, MAP_BASE_ADDR):
+	Macros moved to dl-load.h.
+	(ELF_PREFERRED_ADDRESS_DATA, ELF_PREFERRED_ADDRESS): Likewise.
+	(_dl_map_object_from_fd): Type 'struct loadcmd' moved to dl-load.h.
+	Use _dl_unmap_segments in place of __munmap.
+	Break out segment-mapping loop into ...
+	* elf/dl-map-segments.h (_dl_map_segments): ... here, in new file.
+
 2014-04-03  Will Newton  <will.newton@linaro.org>
 
 	* elf/dl-lookup.c (do_lookup_x): Remove comment
diff --git a/elf/dl-close.c b/elf/dl-close.c
index f36ea0f779..d74d55eb87 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -33,6 +33,8 @@
 #include <tls.h>
 #include <stap-probe.h>
 
+#include <dl-unmap-segments.h>
+
 
 /* Type of the constructor functions.  */
 typedef void (*fini_t) (void);
diff --git a/elf/dl-fptr.c b/elf/dl-fptr.c
index 40cb40a3f1..f1478e26bb 100644
--- a/elf/dl-fptr.c
+++ b/elf/dl-fptr.c
@@ -25,6 +25,7 @@
 #include <ldsodefs.h>
 #include <elf/dynamic-link.h>
 #include <dl-fptr.h>
+#include <dl-unmap-segments.h>
 #include <atomic.h>
 
 #ifndef ELF_MACHINE_BOOT_FPTR_TABLE_LEN
@@ -269,8 +270,7 @@ _dl_unmap (struct link_map *map)
   struct fdesc *head = NULL, *tail = NULL;
   size_t i;
 
-  __munmap ((void *) map->l_map_start,
-	    map->l_map_end - map->l_map_start);
+  _dl_unmap_segments (map);
 
   if (ftab == NULL)
     return;
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 16b101af0c..cfa7f2525f 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -38,39 +38,9 @@
 #include <stap-probe.h>
 
 #include <dl-dst.h>
-
-/* On some systems, no flag bits are given to specify file mapping.  */
-#ifndef MAP_FILE
-# define MAP_FILE	0
-#endif
-
-/* The right way to map in the shared library files is MAP_COPY, which
-   makes a virtual copy of the data at the time of the mmap call; this
-   guarantees the mapped pages will be consistent even if the file is
-   overwritten.  Some losing VM systems like Linux's lack MAP_COPY.  All we
-   get is MAP_PRIVATE, which copies each page when it is modified; this
-   means if the file is overwritten, we may at some point get some pages
-   from the new version after starting with pages from the old version.
-
-   To make up for the lack and avoid the overwriting problem,
-   what Linux does have is MAP_DENYWRITE.  This prevents anyone
-   from modifying the file while we have it mapped.  */
-#ifndef MAP_COPY
-# ifdef MAP_DENYWRITE
-#  define MAP_COPY	(MAP_PRIVATE | MAP_DENYWRITE)
-# else
-#  define MAP_COPY	MAP_PRIVATE
-# endif
-#endif
-
-/* Some systems link their relocatable objects for another base address
-   than 0.  We want to know the base address for these such that we can
-   subtract this address from the segment addresses during mapping.
-   This results in a more efficient address space usage.  Defaults to
-   zero for almost all systems.  */
-#ifndef MAP_BASE_ADDR
-# define MAP_BASE_ADDR(l)	0
-#endif
+#include <dl-load.h>
+#include <dl-map-segments.h>
+#include <dl-unmap-segments.h>
 
 
 #include <endian.h>
@@ -85,18 +55,6 @@
 
 #define STRING(x) __STRING (x)
 
-/* Handle situations where we have a preferred location in memory for
-   the shared objects.  */
-#ifdef ELF_PREFERRED_ADDRESS_DATA
-ELF_PREFERRED_ADDRESS_DATA;
-#endif
-#ifndef ELF_PREFERRED_ADDRESS
-# define ELF_PREFERRED_ADDRESS(loader, maplength, mapstartpref) (mapstartpref)
-#endif
-#ifndef ELF_FIXED_ADDRESS
-# define ELF_FIXED_ADDRESS(loader, mapstart) ((void) 0)
-#endif
-
 
 int __stack_prot attribute_hidden attribute_relro
 #if _STACK_GROWS_DOWN && defined PROT_GROWSDOWN
@@ -1093,12 +1051,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
 
   {
     /* Scan the program header table, collecting its load commands.  */
-    struct loadcmd
-      {
-	ElfW(Addr) mapstart, mapend, dataend, allocend;
-	ElfW(Off) mapoff;
-	int prot;
-      } loadcmds[l->l_phnum], *c;
+    struct loadcmd loadcmds[l->l_phnum];
     size_t nloadcmds = 0;
     bool has_holes = false;
 
@@ -1137,7 +1090,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
 	      goto call_lose;
 	    }
 
-	  c = &loadcmds[nloadcmds++];
+	  struct loadcmd *c = &loadcmds[nloadcmds++];
 	  c->mapstart = ph->p_vaddr & ~(GLRO(dl_pagesize) - 1);
 	  c->mapend = ((ph->p_vaddr + ph->p_filesz + GLRO(dl_pagesize) - 1)
 		       & ~(GLRO(dl_pagesize) - 1));
@@ -1263,154 +1216,26 @@ cannot allocate TLS data structures for initial thread");
 	goto call_lose;
       }
 
-    /* Now process the load commands and map segments into memory.  */
-    c = loadcmds;
-
-    /* Length of the sections to be loaded.  */
-    maplength = loadcmds[nloadcmds - 1].allocend - c->mapstart;
-
-    if (__glibc_likely (type == ET_DYN))
-      {
-	/* This is a position-independent shared object.  We can let the
-	   kernel map it anywhere it likes, but we must have space for all
-	   the segments in their specified positions relative to the first.
-	   So we map the first segment without MAP_FIXED, but with its
-	   extent increased to cover all the segments.  Then we remove
-	   access from excess portion, and there is known sufficient space
-	   there to remap from the later segments.
-
-	   As a refinement, sometimes we have an address that we would
-	   prefer to map such objects at; but this is only a preference,
-	   the OS can do whatever it likes. */
-	ElfW(Addr) mappref;
-	mappref = (ELF_PREFERRED_ADDRESS (loader, maplength,
-					  c->mapstart & GLRO(dl_use_load_bias))
-		   - MAP_BASE_ADDR (l));
-
-	/* Remember which part of the address space this object uses.  */
-	l->l_map_start = (ElfW(Addr)) __mmap ((void *) mappref, maplength,
-					      c->prot,
-					      MAP_COPY|MAP_FILE,
-					      fd, c->mapoff);
-	if (__glibc_unlikely ((void *) l->l_map_start == MAP_FAILED))
-	  {
-	  map_error:
-	    errstring = N_("failed to map segment from shared object");
-	    goto call_lose_errno;
-	  }
-
-	l->l_map_end = l->l_map_start + maplength;
-	l->l_addr = l->l_map_start - c->mapstart;
-
-	if (has_holes)
-	  /* Change protection on the excess portion to disallow all access;
-	     the portions we do not remap later will be inaccessible as if
-	     unallocated.  Then jump into the normal segment-mapping loop to
-	     handle the portion of the segment past the end of the file
-	     mapping.  */
-	  __mprotect ((caddr_t) (l->l_addr + c->mapend),
-		      loadcmds[nloadcmds - 1].mapstart - c->mapend,
-		      PROT_NONE);
-
-	l->l_contiguous = 1;
-
-	goto postmap;
-      }
-
-    /* This object is loaded at a fixed address.  This must never
-       happen for objects loaded with dlopen().  */
-    if (__glibc_unlikely ((mode & __RTLD_OPENEXEC) == 0))
+    if (__glibc_unlikely (type != ET_DYN)
+	&& __glibc_unlikely ((mode & __RTLD_OPENEXEC) == 0))
       {
+	/* This object is loaded at a fixed address.  This must never
+	   happen for objects loaded with dlopen.  */
 	errstring = N_("cannot dynamically load executable");
 	goto call_lose;
       }
 
-    /* Notify ELF_PREFERRED_ADDRESS that we have to load this one
-       fixed.  */
-    ELF_FIXED_ADDRESS (loader, c->mapstart);
-
-
-    /* Remember which part of the address space this object uses.  */
-    l->l_map_start = c->mapstart + l->l_addr;
-    l->l_map_end = l->l_map_start + maplength;
-    l->l_contiguous = !has_holes;
-
-    while (c < &loadcmds[nloadcmds])
-      {
-	if (c->mapend > c->mapstart
-	    /* Map the segment contents from the file.  */
-	    && (__mmap ((void *) (l->l_addr + c->mapstart),
-			c->mapend - c->mapstart, c->prot,
-			MAP_FIXED|MAP_COPY|MAP_FILE,
-			fd, c->mapoff)
-		== MAP_FAILED))
-	  goto map_error;
-
-      postmap:
-	if (c->prot & PROT_EXEC)
-	  l->l_text_end = l->l_addr + c->mapend;
-
-	if (l->l_phdr == 0
-	    && c->mapoff <= header->e_phoff
-	    && ((size_t) (c->mapend - c->mapstart + c->mapoff)
-		>= header->e_phoff + header->e_phnum * sizeof (ElfW(Phdr))))
-	  /* Found the program header in this segment.  */
-	  l->l_phdr = (void *) (uintptr_t) (c->mapstart + header->e_phoff
-					    - c->mapoff);
-
-	if (c->allocend > c->dataend)
-	  {
-	    /* Extra zero pages should appear at the end of this segment,
-	       after the data mapped from the file.   */
-	    ElfW(Addr) zero, zeroend, zeropage;
-
-	    zero = l->l_addr + c->dataend;
-	    zeroend = l->l_addr + c->allocend;
-	    zeropage = ((zero + GLRO(dl_pagesize) - 1)
-			& ~(GLRO(dl_pagesize) - 1));
-
-	    if (zeroend < zeropage)
-	      /* All the extra data is in the last page of the segment.
-		 We can just zero it.  */
-	      zeropage = zeroend;
-
-	    if (zeropage > zero)
-	      {
-		/* Zero the final part of the last page of the segment.  */
-		if (__glibc_unlikely ((c->prot & PROT_WRITE) == 0))
-		  {
-		    /* Dag nab it.  */
-		    if (__mprotect ((caddr_t) (zero
-					       & ~(GLRO(dl_pagesize) - 1)),
-				    GLRO(dl_pagesize), c->prot|PROT_WRITE) < 0)
-		      {
-			errstring = N_("cannot change memory protections");
-			goto call_lose_errno;
-		      }
-		  }
-		memset ((void *) zero, '\0', zeropage - zero);
-		if (__glibc_unlikely ((c->prot & PROT_WRITE) == 0))
-		  __mprotect ((caddr_t) (zero & ~(GLRO(dl_pagesize) - 1)),
-			      GLRO(dl_pagesize), c->prot);
-	      }
-
-	    if (zeroend > zeropage)
-	      {
-		/* Map the remaining zero pages in from the zero fill FD.  */
-		caddr_t mapat;
-		mapat = __mmap ((caddr_t) zeropage, zeroend - zeropage,
-				c->prot, MAP_ANON|MAP_PRIVATE|MAP_FIXED,
-				-1, 0);
-		if (__glibc_unlikely (mapat == MAP_FAILED))
-		  {
-		    errstring = N_("cannot map zero-fill pages");
-		    goto call_lose_errno;
-		  }
-	      }
-	  }
-
-	++c;
-      }
+    /* Length of the sections to be loaded.  */
+    maplength = loadcmds[nloadcmds - 1].allocend - loadcmds[0].mapstart;
+
+    /* Now process the load commands and map segments into memory.
+       This is responsible for filling in:
+       l_map_start, l_map_end, l_addr, l_contiguous, l_text_end, l_phdr
+     */
+    errstring = _dl_map_segments (l, fd, header, type, loadcmds, nloadcmds,
+                                  maplength, has_holes, loader);
+    if (__glibc_unlikely (errstring != NULL))
+      goto call_lose;
   }
 
   if (l->l_ld == 0)
@@ -1432,7 +1257,7 @@ cannot allocate TLS data structures for initial thread");
       && (mode & __RTLD_DLOPEN))
     {
       /* We are not supposed to load this object.  Free all resources.  */
-      __munmap ((void *) l->l_map_start, l->l_map_end - l->l_map_start);
+      _dl_unmap_segments (l);
 
       if (!l->l_libname->dont_free)
 	free (l->l_libname);
@@ -1738,8 +1563,8 @@ open_verify (const char *name, struct filebuf *fbp, struct link_map *loader,
       assert (sizeof (fbp->buf) > sizeof (ElfW(Ehdr)));
       /* Read in the header.  */
       do
-        {
-          ssize_t retlen = __libc_read (fd, fbp->buf + fbp->len,
+	{
+	  ssize_t retlen = __libc_read (fd, fbp->buf + fbp->len,
 					sizeof (fbp->buf) - fbp->len);
 	  if (retlen <= 0)
 	    break;
diff --git a/elf/dl-load.h b/elf/dl-load.h
new file mode 100644
index 0000000000..602840e5f8
--- /dev/null
+++ b/elf/dl-load.h
@@ -0,0 +1,135 @@
+/* Map in a shared object's segments from the file.
+   Copyright (C) 1995-2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_LOAD_H
+#define _DL_LOAD_H	1
+
+#include <link.h>
+#include <sys/mman.h>
+
+
+/* On some systems, no flag bits are given to specify file mapping.  */
+#ifndef MAP_FILE
+# define MAP_FILE       0
+#endif
+
+/* The right way to map in the shared library files is MAP_COPY, which
+   makes a virtual copy of the data at the time of the mmap call; this
+   guarantees the mapped pages will be consistent even if the file is
+   overwritten.  Some losing VM systems like Linux's lack MAP_COPY.  All we
+   get is MAP_PRIVATE, which copies each page when it is modified; this
+   means if the file is overwritten, we may at some point get some pages
+   from the new version after starting with pages from the old version.
+
+   To make up for the lack and avoid the overwriting problem,
+   what Linux does have is MAP_DENYWRITE.  This prevents anyone
+   from modifying the file while we have it mapped.  */
+#ifndef MAP_COPY
+# ifdef MAP_DENYWRITE
+#  define MAP_COPY      (MAP_PRIVATE | MAP_DENYWRITE)
+# else
+#  define MAP_COPY      MAP_PRIVATE
+# endif
+#endif
+
+/* Some systems link their relocatable objects for another base address
+   than 0.  We want to know the base address for these such that we can
+   subtract this address from the segment addresses during mapping.
+   This results in a more efficient address space usage.  Defaults to
+   zero for almost all systems.  */
+#ifndef MAP_BASE_ADDR
+# define MAP_BASE_ADDR(l)       0
+#endif
+
+
+/* Handle situations where we have a preferred location in memory for
+   the shared objects.  */
+#ifdef ELF_PREFERRED_ADDRESS_DATA
+ELF_PREFERRED_ADDRESS_DATA;
+#endif
+#ifndef ELF_PREFERRED_ADDRESS
+# define ELF_PREFERRED_ADDRESS(loader, maplength, mapstartpref) (mapstartpref)
+#endif
+#ifndef ELF_FIXED_ADDRESS
+# define ELF_FIXED_ADDRESS(loader, mapstart) ((void) 0)
+#endif
+
+
+/* This structure describes one PT_LOAD command.
+   Its details have been expanded out and converted.  */
+struct loadcmd
+{
+  ElfW(Addr) mapstart, mapend, dataend, allocend;
+  ElfW(Off) mapoff;
+  int prot;                             /* PROT_* bits.  */
+};
+
+
+/* This is a subroutine of _dl_map_segments.  It should be called for each
+   load command, some time after L->l_addr has been set correctly.  It is
+   responsible for setting up the l_text_end and l_phdr fields.  */
+static void __always_inline
+_dl_postprocess_loadcmd (struct link_map *l, const ElfW(Ehdr) *header,
+                         const struct loadcmd *c)
+{
+  if (c->prot & PROT_EXEC)
+    l->l_text_end = l->l_addr + c->mapend;
+
+  if (l->l_phdr == 0
+      && c->mapoff <= header->e_phoff
+      && ((size_t) (c->mapend - c->mapstart + c->mapoff)
+          >= header->e_phoff + header->e_phnum * sizeof (ElfW(Phdr))))
+    /* Found the program header in this segment.  */
+    l->l_phdr = (void *) (uintptr_t) (c->mapstart + header->e_phoff
+                                      - c->mapoff);
+}
+
+
+/* This is a subroutine of _dl_map_object_from_fd.  It is responsible
+   for filling in several fields in *L: l_map_start, l_map_end, l_addr,
+   l_contiguous, l_text_end, l_phdr.  On successful return, all the
+   segments are mapped (or copied, or whatever) from the file into their
+   final places in the address space, with the correct page permissions,
+   and any bss-like regions already zeroed.  It returns a null pointer
+   on success, or an error message string (to be translated) on error
+   (having also set errno).
+
+   The file <dl-map-segments.h> defines this function.  The canonical
+   implementation in elf/dl-map-segments.h might be replaced by a sysdeps
+   version.  */
+static const char *_dl_map_segments (struct link_map *l, int fd,
+                                     const ElfW(Ehdr) *header, int type,
+                                     const struct loadcmd loadcmds[],
+                                     size_t nloadcmds,
+                                     const size_t maplength,
+                                     bool has_holes,
+                                     struct link_map *loader);
+
+/* All the error message strings _dl_map_segments might return are
+   listed here so that different implementations in different sysdeps
+   dl-map-segments.h files all use consistent strings that are
+   guaranteed to have translations.  */
+#define DL_MAP_SEGMENTS_ERROR_MAP_SEGMENT \
+  N_("failed to map segment from shared object")
+#define DL_MAP_SEGMENTS_ERROR_MPROTECT \
+  N_("cannot change memory protections")
+#define DL_MAP_SEGMENTS_ERROR_MAP_ZERO_FILL \
+  N_("cannot map zero-fill pages")
+
+
+#endif	/* dl-load.h */
diff --git a/elf/dl-map-segments.h b/elf/dl-map-segments.h
new file mode 100644
index 0000000000..71671cd573
--- /dev/null
+++ b/elf/dl-map-segments.h
@@ -0,0 +1,153 @@
+/* Map in a shared object's segments.  Generic version.
+   Copyright (C) 1995-2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <dl-load.h>
+
+/* This implementation assumes (as does the corresponding implementation
+   of _dl_unmap_segments, in dl-unmap-segments.h) that shared objects
+   are always laid out with all segments contiguous (or with gaps
+   between them small enough that it's preferable to reserve all whole
+   pages inside the gaps with PROT_NONE mappings rather than permitting
+   other use of those parts of the address space).  */
+
+static __always_inline const char *
+_dl_map_segments (struct link_map *l, int fd,
+                  const ElfW(Ehdr) *header, int type,
+                  const struct loadcmd loadcmds[], size_t nloadcmds,
+                  const size_t maplength, bool has_holes,
+                  struct link_map *loader)
+{
+  const struct loadcmd *c = loadcmds;
+
+  if (__glibc_likely (type == ET_DYN))
+    {
+      /* This is a position-independent shared object.  We can let the
+         kernel map it anywhere it likes, but we must have space for all
+         the segments in their specified positions relative to the first.
+         So we map the first segment without MAP_FIXED, but with its
+         extent increased to cover all the segments.  Then we remove
+         access from excess portion, and there is known sufficient space
+         there to remap from the later segments.
+
+         As a refinement, sometimes we have an address that we would
+         prefer to map such objects at; but this is only a preference,
+         the OS can do whatever it likes. */
+      ElfW(Addr) mappref
+        = (ELF_PREFERRED_ADDRESS (loader, maplength,
+                                  c->mapstart & GLRO(dl_use_load_bias))
+           - MAP_BASE_ADDR (l));
+
+      /* Remember which part of the address space this object uses.  */
+      l->l_map_start = (ElfW(Addr)) __mmap ((void *) mappref, maplength,
+                                            c->prot,
+                                            MAP_COPY|MAP_FILE,
+                                            fd, c->mapoff);
+      if (__glibc_unlikely ((void *) l->l_map_start == MAP_FAILED))
+        return DL_MAP_SEGMENTS_ERROR_MAP_SEGMENT;
+
+      l->l_map_end = l->l_map_start + maplength;
+      l->l_addr = l->l_map_start - c->mapstart;
+
+      if (has_holes)
+        /* Change protection on the excess portion to disallow all access;
+           the portions we do not remap later will be inaccessible as if
+           unallocated.  Then jump into the normal segment-mapping loop to
+           handle the portion of the segment past the end of the file
+           mapping.  */
+        __mprotect ((caddr_t) (l->l_addr + c->mapend),
+                    loadcmds[nloadcmds - 1].mapstart - c->mapend,
+                    PROT_NONE);
+
+      l->l_contiguous = 1;
+
+      goto postmap;
+    }
+
+  /* Remember which part of the address space this object uses.  */
+  l->l_map_start = c->mapstart + l->l_addr;
+  l->l_map_end = l->l_map_start + maplength;
+  l->l_contiguous = !has_holes;
+
+  while (c < &loadcmds[nloadcmds])
+    {
+      if (c->mapend > c->mapstart
+          /* Map the segment contents from the file.  */
+          && (__mmap ((void *) (l->l_addr + c->mapstart),
+                      c->mapend - c->mapstart, c->prot,
+                      MAP_FIXED|MAP_COPY|MAP_FILE,
+                      fd, c->mapoff)
+              == MAP_FAILED))
+        return DL_MAP_SEGMENTS_ERROR_MAP_SEGMENT;
+
+    postmap:
+      _dl_postprocess_loadcmd (l, header, c);
+
+      if (c->allocend > c->dataend)
+        {
+          /* Extra zero pages should appear at the end of this segment,
+             after the data mapped from the file.   */
+          ElfW(Addr) zero, zeroend, zeropage;
+
+          zero = l->l_addr + c->dataend;
+          zeroend = l->l_addr + c->allocend;
+          zeropage = ((zero + GLRO(dl_pagesize) - 1)
+                      & ~(GLRO(dl_pagesize) - 1));
+
+          if (zeroend < zeropage)
+            /* All the extra data is in the last page of the segment.
+               We can just zero it.  */
+            zeropage = zeroend;
+
+          if (zeropage > zero)
+            {
+              /* Zero the final part of the last page of the segment.  */
+              if (__glibc_unlikely ((c->prot & PROT_WRITE) == 0))
+                {
+                  /* Dag nab it.  */
+                  if (__mprotect ((caddr_t) (zero
+                                             & ~(GLRO(dl_pagesize) - 1)),
+                                  GLRO(dl_pagesize), c->prot|PROT_WRITE) < 0)
+                    return DL_MAP_SEGMENTS_ERROR_MPROTECT;
+                }
+              memset ((void *) zero, '\0', zeropage - zero);
+              if (__glibc_unlikely ((c->prot & PROT_WRITE) == 0))
+                __mprotect ((caddr_t) (zero & ~(GLRO(dl_pagesize) - 1)),
+                            GLRO(dl_pagesize), c->prot);
+            }
+
+          if (zeroend > zeropage)
+            {
+              /* Map the remaining zero pages in from the zero fill FD.  */
+              caddr_t mapat;
+              mapat = __mmap ((caddr_t) zeropage, zeroend - zeropage,
+                              c->prot, MAP_ANON|MAP_PRIVATE|MAP_FIXED,
+                              -1, 0);
+              if (__glibc_unlikely (mapat == MAP_FAILED))
+                return DL_MAP_SEGMENTS_ERROR_MAP_ZERO_FILL;
+            }
+        }
+
+      ++c;
+    }
+
+  /* Notify ELF_PREFERRED_ADDRESS that we have to load this one
+     fixed.  */
+  ELF_FIXED_ADDRESS (loader, c->mapstart);
+
+  return NULL;
+}
diff --git a/elf/dl-unmap-segments.h b/elf/dl-unmap-segments.h
new file mode 100644
index 0000000000..ce9c3fecfa
--- /dev/null
+++ b/elf/dl-unmap-segments.h
@@ -0,0 +1,35 @@
+/* Unmap a shared object's segments.  Generic version.
+   Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_UNMAP_SEGMENTS_H
+#define _DL_UNMAP_SEGMENTS_H	1
+
+#include <link.h>
+#include <sys/mman.h>
+
+/* _dl_map_segments ensures that any whole pages in gaps between segments
+   are filled in with PROT_NONE mappings.  So we can just unmap the whole
+   range in one fell swoop.  */
+
+static __always_inline void
+_dl_unmap_segments (struct link_map *l)
+{
+  __munmap ((void *) l->l_map_start, l->l_map_end - l->l_map_start);
+}
+
+#endif  /* dl-unmap-segments.h */
diff --git a/ports/ChangeLog.hppa b/ports/ChangeLog.hppa
index 61aeabc30b..4433ad5ca4 100644
--- a/ports/ChangeLog.hppa
+++ b/ports/ChangeLog.hppa
@@ -1,3 +1,8 @@
+2014-03-18  Roland McGrath  <roland@hack.frob.com>
+
+	* sysdeps/hppa/dl-fptr.c: Include <dl-unmap-segments.h>.
+	(_dl_unmap): Use _dl_unmap_segments in place of __munmap.
+
 2014-02-28  Roland McGrath  <roland@hack.frob.com>
 
 	* sysdeps/hppa/nptl/tls.h (TLS_TCB_AT_TP): New macro.
diff --git a/ports/sysdeps/hppa/dl-fptr.c b/ports/sysdeps/hppa/dl-fptr.c
index 7404f5ff91..f975664dd6 100644
--- a/ports/sysdeps/hppa/dl-fptr.c
+++ b/ports/sysdeps/hppa/dl-fptr.c
@@ -26,6 +26,7 @@
 #include <ldsodefs.h>
 #include <elf/dynamic-link.h>
 #include <dl-fptr.h>
+#include <dl-unmap-segments.h>
 #include <atomic.h>
 
 #ifndef ELF_MACHINE_BOOT_FPTR_TABLE_LEN
@@ -284,8 +285,7 @@ _dl_unmap (struct link_map *map)
   struct fdesc *head = NULL, *tail = NULL;
   size_t i;
 
-  __munmap ((void *) map->l_map_start,
-	    map->l_map_end - map->l_map_start);
+  _dl_unmap_segments (map);
 
   if (ftab == NULL)
     return;
diff --git a/sysdeps/aarch64/tlsdesc.c b/sysdeps/aarch64/tlsdesc.c
index 0921230676..1b4181956c 100644
--- a/sysdeps/aarch64/tlsdesc.c
+++ b/sysdeps/aarch64/tlsdesc.c
@@ -23,6 +23,7 @@
 #include <elf/dynamic-link.h>
 #include <tls.h>
 #include <dl-tlsdesc.h>
+#include <dl-unmap-segments.h>
 #include <tlsdeschtab.h>
 
 /* The following functions take an entry_check_offset argument.  It's
@@ -144,8 +145,7 @@ void
 internal_function
 _dl_unmap (struct link_map *map)
 {
-  __munmap ((void *) (map)->l_map_start,
-	    (map)->l_map_end - (map)->l_map_start);
+  _dl_unmap_segments (map);
 
 #if SHARED
   if (map->l_mach.tlsdesc_table)
diff --git a/sysdeps/arm/tlsdesc.c b/sysdeps/arm/tlsdesc.c
index fc754d6c42..e52034ded6 100644
--- a/sysdeps/arm/tlsdesc.c
+++ b/sysdeps/arm/tlsdesc.c
@@ -21,6 +21,7 @@
 #include <elf/dynamic-link.h>
 #include <tls.h>
 #include <dl-tlsdesc.h>
+#include <dl-unmap-segments.h>
 #include <tlsdeschtab.h>
 
 /* This function is used to lazily resolve TLS_DESC REL relocations
@@ -146,8 +147,7 @@ void
 internal_function
 _dl_unmap (struct link_map *map)
 {
-  __munmap ((void *) (map)->l_map_start,
-	    (map)->l_map_end - (map)->l_map_start);
+  _dl_unmap_segments (map);
 
 #if SHARED
   /* _dl_unmap is only called for dlopen()ed libraries, for which
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 65cd709fef..6cf5e1b0b8 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -91,9 +91,7 @@ typedef struct link_map *lookup_t;
 
 /* Unmap a loaded object, called by _dl_close (). */
 #ifndef DL_UNMAP_IS_SPECIAL
-# define DL_UNMAP(map) \
- __munmap ((void *) (map)->l_map_start,					      \
-	   (map)->l_map_end - (map)->l_map_start)
+# define DL_UNMAP(map)	_dl_unmap_segments (map)
 #endif
 
 /* By default we do not need special support to initialize DSOs loaded
diff --git a/sysdeps/i386/tlsdesc.c b/sysdeps/i386/tlsdesc.c
index 9b6e359ae0..ef4a1ddf98 100644
--- a/sysdeps/i386/tlsdesc.c
+++ b/sysdeps/i386/tlsdesc.c
@@ -21,6 +21,7 @@
 #include <elf/dynamic-link.h>
 #include <tls.h>
 #include <dl-tlsdesc.h>
+#include <dl-unmap-segments.h>
 #include <tlsdeschtab.h>
 
 /* The following 4 functions take an entry_check_offset argument.
@@ -258,8 +259,7 @@ void
 internal_function
 _dl_unmap (struct link_map *map)
 {
-  __munmap ((void *) (map)->l_map_start,
-	    (map)->l_map_end - (map)->l_map_start);
+  _dl_unmap_segments (map);
 
 #if SHARED
   if (map->l_mach.tlsdesc_table)
diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
index 3bfb830320..8bc2911850 100644
--- a/sysdeps/tile/dl-runtime.c
+++ b/sysdeps/tile/dl-runtime.c
@@ -27,6 +27,7 @@
 
 #include <sys/mman.h>
 #include <arch/sim.h>
+#include <dl-unmap-segments.h>
 
 /* Like realpath(), but simplified: no dynamic memory use, no lstat(),
    no set_errno(), no valid "rpath" on error, etc.  This handles some
@@ -154,5 +155,5 @@ void internal_function
 _dl_unmap (struct link_map *l)
 {
   sim_dlclose (l->l_map_start);
-  __munmap ((void *) l->l_map_start, l->l_map_end - l->l_map_start);
+  _dl_unmap_segments (map);
 }
diff --git a/sysdeps/x86_64/tlsdesc.c b/sysdeps/x86_64/tlsdesc.c
index 0daa87f8fd..c1c5e033c0 100644
--- a/sysdeps/x86_64/tlsdesc.c
+++ b/sysdeps/x86_64/tlsdesc.c
@@ -21,6 +21,7 @@
 #include <elf/dynamic-link.h>
 #include <tls.h>
 #include <dl-tlsdesc.h>
+#include <dl-unmap-segments.h>
 #include <tlsdeschtab.h>
 
 /* The following 2 functions take a caller argument, that contains the
@@ -136,8 +137,7 @@ void
 internal_function
 _dl_unmap (struct link_map *map)
 {
-  __munmap ((void *) (map)->l_map_start,
-	    (map)->l_map_end - (map)->l_map_start);
+  _dl_unmap_segments (map);
 
 #if SHARED
   /* _dl_unmap is only called for dlopen()ed libraries, for which