[PATCH] elf: Set p_align to the common page size if possible
Fangrui Song
i@maskray.me
Thu Dec 16 08:52:52 GMT 2021
On 2021-12-15, H.J. Lu via Binutils wrote:
>Currently, on 32-bit and 64-bit ARM, it seems that ld generates p_align
>values of 0x10000 even if no section alignment is greater than 0x1000.
>The issue is more general and probably affects other targets with
>multiple common page sizes.
>
>While file layout absolutely must take 64K page size into account, that
>does not have to be reflected in the p_align value. If running on a 64K
>kernel, the file will be loaded at a 64K page boundary by necessity. On
>a 4K kernel, 64K alignment is not needed.
>
>The glibc loader has been fixed to honor p_align:
Maybe it's just me who is very careful on the words: aligning to p_align
is a new feature, not a bug, as no ABI requires it. No ld.so I know
(FreeBSD, musl, bionic) does this.
>https://sourceware.org/bugzilla/show_bug.cgi?id=28676
>
>similar to kernel:
>
>commit ce81bb256a224259ab686742a6284930cbe4f1fa
>Author: Chris Kennelly <ckennelly@google.com>
>Date: Thu Oct 15 20:12:32 2020 -0700
>
> fs/binfmt_elf: use PT_LOAD p_align values for suitable start address
This kernel patch has no cost. It just picks a load bias, while the
glibc's .so loading patch has some costs because there is no alignment
parameter to mmap... So now, every
* (Linux x86-64) -z noseparate-code (default max-page-size=2MiB) .so incurs some munmap overhead
* arm/aarch64/powerpc (default max-page-size=65536) .so incurs some munmap overhead...
If I were to do this, I would fix objcopy first, then adjust ld's
p_align, finally tune glibc's .so loading.
>This means that on 4K kernels, we will start to do extra work for 64K
>p_align, but this pointless for pretty much all binaries (whose section
>alignment rarely exceeds 16).
>
>1. Set p_align to common page size while laying out segments aligning to
>maximum page size or section alignment. The run-time loader can align
>segments to common page size or maximum page size, depending on system
>page size.
>2. If -z max-page-size=NNN is used, p_align will be set to maximum page
>size or the largest section alignment.
>3. If a section requires alignment higher than the common page size,
>don't set p_align to the common page size.
>4. If a section requires alignment higher than the maximum page size,
>set p_align to the section alignment.
>5. For objcopy, when the commone page size != the maximum page size,
>p_align may be set to the commone page size while segments are aligned
>to the maximum page size. In this case, the input p_align will be
>ignored and the maximum page size will be used to align the ouput
>segments.
Does this patch do anything with selecting max-page-size for objcopy?
https://sourceware.org/bugzilla/show_bug.cgi?id=28689#c4
Something like gcd({p_vaddr - p_offset}) can be used, but I don't see
anything related to gcd.
>6. Update linker to disallow the commone page size > the maximum page
>size.
>
>bfd/
>
> PR ld/28689
> PR ld/28695
> * elf.c (assign_file_positions_for_load_sections): Set p_align
> to common page size while laying out segments aligning to
> maximum page size or section alignment.
> (elf_is_p_align_valid): New function.
> (copy_elf_program_header): Call elf_is_p_align_valid to determine
> if p_align is valid.
>
>include/
>
> PR ld/28689
> PR ld/28695
> * bfdlink.h (bfd_link_info): Add maxpagesize_is_set.
>
>ld/
>
> PR ld/28689
> PR ld/28695
> * emultempl/elf.em (gld${EMULATION_NAME}_handle_option): Set
> link_info.maxpagesize_is_set for -z max-page-size=NNN.
> * ldelf.c (ldelf_after_parse): Disallow link_info.commonpagesize
> > link_info.maxpagesize.
> * testsuite/ld-elf/elf.exp: Pass -z max-page-size=0x4000 to
> linker to build mbind2a and mbind2b.
> * testsuite/ld-elf/header.d: Add -z common-page-size=0x100.
> * testsuite/ld-elf/linux-x86.exp: Add PR ld/28689 tests.
> * testsuite/ld-elf/p_align-1.c: New file.
> * testsuite/ld-elf/page-size-1.d: New test.
>---
> bfd/elf.c | 78 +++++++++++++++++++++++++++++--
> include/bfdlink.h | 3 ++
> ld/emultempl/elf.em | 1 +
> ld/ldelf.c | 3 ++
> ld/testsuite/ld-elf/elf.exp | 4 +-
> ld/testsuite/ld-elf/header.d | 2 +-
> ld/testsuite/ld-elf/linux-x86.exp | 36 ++++++++++++++
> ld/testsuite/ld-elf/p_align-1.c | 25 ++++++++++
> ld/testsuite/ld-elf/page-size-1.d | 4 ++
> 9 files changed, 149 insertions(+), 7 deletions(-)
> create mode 100644 ld/testsuite/ld-elf/p_align-1.c
> create mode 100644 ld/testsuite/ld-elf/page-size-1.d
>
>diff --git a/bfd/elf.c b/bfd/elf.c
>index e6c6a8a6c05..431084760ab 100644
>--- a/bfd/elf.c
>+++ b/bfd/elf.c
>@@ -5406,6 +5406,8 @@ assign_file_positions_for_load_sections (bfd *abfd,
> Elf_Internal_Phdr *p;
> file_ptr off; /* Octets. */
> bfd_size_type maxpagesize;
>+ bfd_size_type commonpagesize;
>+ bool p_align_commonpagesize_p = false;
> unsigned int alloc, actual;
> unsigned int i, j;
> struct elf_segment_map **sorted_seg_map;
>@@ -5491,12 +5493,19 @@ assign_file_positions_for_load_sections (bfd *abfd,
> elf_sort_segments);
>
> maxpagesize = 1;
>+ commonpagesize = 1;
> if ((abfd->flags & D_PAGED) != 0)
> {
> if (link_info != NULL)
>- maxpagesize = link_info->maxpagesize;
>+ {
>+ maxpagesize = link_info->maxpagesize;
>+ commonpagesize = link_info->commonpagesize;
>+ }
> else
>- maxpagesize = bed->maxpagesize;
>+ {
>+ maxpagesize = bed->maxpagesize;
>+ commonpagesize = bed->commonpagesize;
>+ }
> }
>
> /* Sections must map to file offsets past the ELF file header. */
>@@ -5560,6 +5569,13 @@ assign_file_positions_for_load_sections (bfd *abfd,
> segment. */
> if (m->p_align_valid)
> maxpagesize = m->p_align;
>+ else if (link_info == NULL || !link_info->maxpagesize_is_set)
>+ /* Set p_align to common page size while laying out segments
>+ aligning to the maximum page size or the largest section
>+ alignment. The run-time loader can align segments to the
>+ common page size or the maximum page size, depending on
>+ system page size. */
>+ p_align_commonpagesize_p = true;
>
> p->p_align = maxpagesize;
> }
>@@ -5597,7 +5613,22 @@ assign_file_positions_for_load_sections (bfd *abfd,
> }
> align = (bfd_size_type) 1 << align_power;
> if (align < maxpagesize)
>- align = maxpagesize;
>+ {
>+ /* If a section requires alignment higher than the
>+ common page size, don't set p_align to the common
>+ page size. */
>+ if (align > commonpagesize)
>+ p_align_commonpagesize_p = false;
>+ align = maxpagesize;
>+ }
>+ else
>+ {
>+ /* If a section requires alignment higher than the
>+ maximum page size, set p_align to the section
>+ alignment. */
>+ p_align_commonpagesize_p = true;
>+ commonpagesize = align;
>+ }
> }
>
> for (i = 0; i < m->count; i++)
>@@ -5976,6 +6007,9 @@ assign_file_positions_for_load_sections (bfd *abfd,
> print_segment_map (m);
> }
> }
>+
>+ if (p_align_commonpagesize_p)
>+ p->p_align = commonpagesize;
> }
> }
>
>@@ -7485,6 +7519,39 @@ rewrite_elf_program_header (bfd *ibfd, bfd *obfd, bfd_vma maxpagesize)
> return true;
> }
>
>+/* Return true if p_align in the ELF program header in ABFD is valid. */
>+
>+static bool
>+elf_is_p_align_valid (bfd *abfd)
>+{
>+ unsigned int i;
>+ Elf_Internal_Phdr *segment;
>+ unsigned int num_segments;
>+ const struct elf_backend_data *bed = get_elf_backend_data (abfd);
>+ bfd_size_type maxpagesize = bed->maxpagesize;
>+ bfd_size_type commonpagesize = bed->commonpagesize;
>+
>+ if (commonpagesize == maxpagesize)
>+ return true;
>+
>+ /* When the commone page size != the maximum page size, p_align may
>+ be set to the commone page size while segments are aligned to
>+ the maximum page size. In this case, the input p_align will be
>+ ignored and the maximum page size will be used to align the ouput
>+ segments. */
>+ segment = elf_tdata (abfd)->phdr;
>+ num_segments = elf_elfheader (abfd)->e_phnum;
>+ for (i = 0; i < num_segments; i++, segment++)
>+ if (segment->p_type == PT_LOAD
>+ && (segment->p_align != commonpagesize
>+ || vma_page_aligned_bias (segment->p_vaddr,
>+ segment->p_offset,
>+ maxpagesize) != 0))
>+ return true;
>+
>+ return false;
>+}
>+
> /* Copy ELF program header information. */
>
> static bool
>@@ -7499,6 +7566,7 @@ copy_elf_program_header (bfd *ibfd, bfd *obfd)
> unsigned int num_segments;
> bool phdr_included = false;
> bool p_paddr_valid;
>+ bool p_palign_valid;
> unsigned int opb = bfd_octets_per_byte (ibfd, NULL);
>
> iehdr = elf_elfheader (ibfd);
>@@ -7519,6 +7587,8 @@ copy_elf_program_header (bfd *ibfd, bfd *obfd)
> break;
> }
>
>+ p_palign_valid = elf_is_p_align_valid (ibfd);
>+
> for (i = 0, segment = elf_tdata (ibfd)->phdr;
> i < num_segments;
> i++, segment++)
>@@ -7561,7 +7631,7 @@ copy_elf_program_header (bfd *ibfd, bfd *obfd)
> map->p_paddr = segment->p_paddr;
> map->p_paddr_valid = p_paddr_valid;
> map->p_align = segment->p_align;
>- map->p_align_valid = 1;
>+ map->p_align_valid = p_palign_valid;
> map->p_vaddr_offset = 0;
>
> if (map->p_type == PT_GNU_RELRO
>diff --git a/include/bfdlink.h b/include/bfdlink.h
>index 566529ee644..8cf34b05c1a 100644
>--- a/include/bfdlink.h
>+++ b/include/bfdlink.h
>@@ -525,6 +525,9 @@ struct bfd_link_info
> /* TRUE if all symbol names should be unique. */
> unsigned int unique_symbol : 1;
>
>+ /* TRUE if maxpagesize is set on command-line. */
>+ unsigned int maxpagesize_is_set : 1;
>+
> /* Char that may appear as the first char of a symbol, but should be
> skipped (like symbol_leading_char) when looking up symbols in
> wrap_hash. Used by PowerPC Linux for 'dot' symbols. */
>diff --git a/ld/emultempl/elf.em b/ld/emultempl/elf.em
>index bfaf8130a3e..5eadab9f4b9 100644
>--- a/ld/emultempl/elf.em
>+++ b/ld/emultempl/elf.em
>@@ -721,6 +721,7 @@ fragment <<EOF
> || (link_info.maxpagesize & (link_info.maxpagesize - 1)) != 0)
> einfo (_("%F%P: invalid maximum page size \`%s'\n"),
> optarg + 14);
>+ link_info.maxpagesize_is_set = true;
> }
> else if (startswith (optarg, "common-page-size="))
> {
>diff --git a/ld/ldelf.c b/ld/ldelf.c
>index 529992b02ae..ee09df5f35c 100644
>--- a/ld/ldelf.c
>+++ b/ld/ldelf.c
>@@ -72,6 +72,9 @@ ldelf_after_parse (void)
> link_info.dynamic_undefined_weak = 0;
> }
> after_parse_default ();
>+ if (link_info.commonpagesize > link_info.maxpagesize)
>+ einfo (_("%F%P: common page size (0x%v) > maximum page size (0x%v)\n"),
>+ link_info.commonpagesize, link_info.maxpagesize);
> }
>
> /* Handle the generation of DT_NEEDED tags. */
>diff --git a/ld/testsuite/ld-elf/elf.exp b/ld/testsuite/ld-elf/elf.exp
>index 01d22faad9a..ae8f76db1c7 100644
>--- a/ld/testsuite/ld-elf/elf.exp
>+++ b/ld/testsuite/ld-elf/elf.exp
>@@ -365,7 +365,7 @@ if { [istarget *-*-linux*]
> run_ld_link_exec_tests [list \
> [list \
> "Run mbind2a" \
>- "$NOPIE_LDFLAGS -Wl,-z,common-page-size=0x4000" \
>+ "$NOPIE_LDFLAGS -Wl,-z,common-page-size=0x4000,-z,max-page-size=0x4000" \
> "" \
> { mbind2a.s mbind2b.c } \
> "mbind2a" \
>@@ -374,7 +374,7 @@ if { [istarget *-*-linux*]
> ] \
> [list \
> "Run mbind2b" \
>- "-static -Wl,-z,common-page-size=0x4000" \
>+ "-static -Wl,-z,common-page-size=0x4000,-z,max-page-size=0x4000" \
> "" \
> { mbind2a.s mbind2b.c } \
> "mbind2b" \
>diff --git a/ld/testsuite/ld-elf/header.d b/ld/testsuite/ld-elf/header.d
>index c4d174a98da..67f0c981920 100644
>--- a/ld/testsuite/ld-elf/header.d
>+++ b/ld/testsuite/ld-elf/header.d
>@@ -1,5 +1,5 @@
> # target: *-*-linux* *-*-gnu* *-*-vxworks arm*-*-uclinuxfdpiceabi
>-# ld: -T header.t -z max-page-size=0x100
>+# ld: -T header.t -z max-page-size=0x100 -z common-page-size=0x100
> # objdump: -hpw
>
> #...
>diff --git a/ld/testsuite/ld-elf/linux-x86.exp b/ld/testsuite/ld-elf/linux-x86.exp
>index ee03b565faf..25a8d0411d9 100644
>--- a/ld/testsuite/ld-elf/linux-x86.exp
>+++ b/ld/testsuite/ld-elf/linux-x86.exp
>@@ -185,6 +185,42 @@ run_ld_link_exec_tests [list \
> "" \
> "tmpdir/indirect-extern-access-2.so" \
> ] \
>+ [list \
>+ "Run p_align-1a without PIE" \
>+ "$NOPIE_LDFLAGS" \
>+ "" \
>+ { p_align-1.c } \
>+ "p_align-1a" \
>+ "pass.out" \
>+ "$NOPIE_CFLAGS" \
>+ ] \
>+ [list \
>+ "Run p_align-1b with PIE" \
>+ "-pie" \
>+ "" \
>+ { p_align-1.c } \
>+ "p_align-1b" \
>+ "pass.out" \
>+ "-fpie" \
>+ ] \
>+ [list \
>+ "Run p_align-1c with -Wl,-z,max-page-size=0x1000 without PIE" \
>+ "$NOPIE_LDFLAGS -Wl,-z,max-page-size=0x1000" \
>+ "" \
>+ { p_align-1.c } \
>+ "p_align-1c" \
>+ "pass.out" \
>+ "$NOPIE_CFLAGS" \
>+ ] \
>+ [list \
>+ "Run p_align-1d with -Wl,-z,max-page-size=0x1000 with PIE" \
>+ "-pie -Wl,-z,max-page-size=0x1000" \
>+ "" \
>+ { p_align-1.c } \
>+ "p_align-1d" \
>+ "pass.out" \
>+ "-fpie" \
>+ ] \
> ]
>
> proc elfedit_test { options test output } {
>diff --git a/ld/testsuite/ld-elf/p_align-1.c b/ld/testsuite/ld-elf/p_align-1.c
>new file mode 100644
>index 00000000000..6579cd74e52
>--- /dev/null
>+++ b/ld/testsuite/ld-elf/p_align-1.c
>@@ -0,0 +1,25 @@
>+#include <stdio.h>
>+#include <stdint.h>
>+#include <stdlib.h>
>+
>+#ifndef ALIGN
>+# define ALIGN 0x800000
>+#endif
>+
>+int
>+__attribute__ ((weak))
>+is_aligned (void *p, int align)
>+{
>+ return (((uintptr_t) p) & (align - 1)) == 0;
>+}
>+
>+int foo __attribute__ ((aligned (ALIGN))) = 1;
>+
>+int
>+main (void)
>+{
>+ if (!is_aligned (&foo, ALIGN))
>+ abort ();
>+ printf ("PASS\n");
>+ return 0;
>+}
>diff --git a/ld/testsuite/ld-elf/page-size-1.d b/ld/testsuite/ld-elf/page-size-1.d
>new file mode 100644
>index 00000000000..04d2153b2f9
>--- /dev/null
>+++ b/ld/testsuite/ld-elf/page-size-1.d
>@@ -0,0 +1,4 @@
>+#source: dummy.s
>+#ld: -z common-page-size=0x4000 -z max-page-size=0x1000
>+#error: common page size \(0x4000\) > maximum page size \(0x1000\)
>+#target: *-*-linux-gnu *-*-gnu* arm*-*-uclinuxfdpiceabi
>--
>2.33.1
There is no test about max-page-size > common-page-size.
More information about the Binutils
mailing list