gcc 12.2 (или ld?) варит большие бинарники на aarch64 или Загадка дыры

Обратил внимание, что размер исполняемых файлов стал больше, чем было на armhf. Глянул - из минимального int main (int argc, char **argv) {return 0;} теперь получается (после gcc -Wall mini.c -o mini; strip mini) около 67 кбайт, из которых около 64 кбайт забито нулями.

Подозреваю, что виноват не сам компилятор, потому что если получить mini.o, то его размер в районе 1200 байт.

Полностью это примерно так:

root@orangepizero2w:~/test# cat mini.c

int main (int argc, char **argv) {

return 0;

}
root@orangepizero2w:~/test# gcc -o mini mini.c
root@orangepizero2w:~/test# strip mini
root@orangepizero2w:~/test# ls -l mini
-rwxr-xr-x 1 root root 67592 ноя 15 13:29 mini
root@orangepizero2w:~/test# ldd mini
    linux-vdso.so.1 (0x0000ffff9157f000)
    libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffff91370000)
    /lib/ld-linux-aarch64.so.1 (0x0000ffff91542000)
root@orangepizero2w:~/test# gcc -c -o mini.o mini.c
root@orangepizero2w:~/test# ls -l
итого 76
-rwxr-xr-x 1 root root 67592 ноя 15 13:29 mini
-rw-r--r-- 1 root root    50 ноя 15 13:21 mini.c
-rw-r--r-- 1 root root  1272 ноя 15 13:30 mini.o
root@orangepizero2w:~/test#

Вопрос: нафига, а главное — зачем? И как оно раньше работало без безусловного резервирования 64 килобайт?

Upd: попробовал посмотреть через objdump, в котором ничего не понимаю:

Разделы:
Idx Name               Разм      VMA               LMA               Фа  смещ.  Выр.  Флаги
  0 .interp            0000001b  0000000000000238  0000000000000238  00000238  2**0  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.gnu.build-id 00000024  0000000000000254  0000000000000254  00000254  2**2  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.ABI-tag      00000020  0000000000000278  0000000000000278  00000278  2**2  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash          0000001c  0000000000000298  0000000000000298  00000298  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym            000000d8  00000000000002b8  00000000000002b8  000002b8  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr            0000008d  0000000000000390  0000000000000390  00000390  2**0  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version       00000012  000000000000041e  000000000000041e  0000041e  2**1  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r     00000030  0000000000000430  0000000000000430  00000430  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rela.dyn          000000c0  0000000000000460  0000000000000460  00000460  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.plt          00000060  0000000000000520  0000000000000520  00000520  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init              00000018  0000000000000580  0000000000000580  00000580  2**2  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt               00000060  00000000000005a0  00000000000005a0  000005a0  2**4  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text              0000012c  0000000000000600  0000000000000600  00000600  2**6  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .fini              00000014  000000000000072c  000000000000072c  0000072c  2**2  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .rodata            00000004  0000000000000740  0000000000000740  00000740  2**2  CONTENTS, ALLOC, LOAD, READONLY, DATA
 15 .eh_frame_hdr      0000003c  0000000000000744  0000000000000744  00000744  2**2  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame          000000a4  0000000000000780  0000000000000780  00000780  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .init_array        00000008  000000000001fdc8  000000000001fdc8  0000fdc8  2**3  CONTENTS, ALLOC, LOAD, DATA
 18 .fini_array        00000008  000000000001fdd0  000000000001fdd0  0000fdd0  2**3  CONTENTS, ALLOC, LOAD, DATA
 19 .dynamic           000001e0  000000000001fdd8  000000000001fdd8  0000fdd8  2**3  CONTENTS, ALLOC, LOAD, DATA
 20 .got               00000030  000000000001ffb8  000000000001ffb8  0000ffb8  2**3  CONTENTS, ALLOC, LOAD, DATA
 21 .got.plt           00000038  000000000001ffe8  000000000001ffe8  0000ffe8  2**3  CONTENTS, ALLOC, LOAD, DATA
 22 .data              00000010  0000000000020020  0000000000020020  00010020  2**3  CONTENTS, ALLOC, LOAD, DATA
 23 .bss               00000008  0000000000020030  0000000000020030  00010030  2**0  ALLOC
 24 .comment           0000001f  0000000000000000  0000000000000000  00010030  2**0  CONTENTS, READONLY

Между секциями 16 и 17 и лежит тот кусок ничего размером 64 килобайт (точнее, сколько понимаю, 62884 байт) — та самая дыра из заголовка вопроса.

Linker script, выдаваемый компилятором (извините, я не осилил спрятать code под спойлер):

 используется внутренний сценарий компоновщика:
 ==================================================
 /* Script for -pie -z combreloc */
 /* Copyright (C) 2014-2023 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-bigaarch64",
          "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/aarch64-linux-gnu"); SEARCH_DIR("=/lib/aarch64-linux-gnu"); SEARCH_DIR("=/usr/lib/aarch64-linux-gnu"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/aarch64-linux-gnu/lib");
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0)); . = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS;
  .interp         : { *(.interp) }
  .note.gnu.build-id  : { *(.note.gnu.build-id) }
  .hash           : { *(.hash) }
  .gnu.hash       : { *(.gnu.hash) }
  .dynsym         : { *(.dynsym) }
  .dynstr         : { *(.dynstr) }
  .gnu.version    : { *(.gnu.version) }
  .gnu.version_d  : { *(.gnu.version_d) }
  .gnu.version_r  : { *(.gnu.version_r) }
  .rela.dyn       :
    {
      *(.rela.init)
      *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
      *(.rela.fini)
      *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
      *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
      *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
      *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
      *(.rela.ctors)
      *(.rela.dtors)
      *(.rela.got)
      *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
      *(.rela.ifunc)
    }
  .rela.plt       :
    {
      *(.rela.plt)
      *(.rela.iplt)
    }
  .init           :
  {
    KEEP (*(SORT_NONE(.init)))
  } =0x1f2003d5
  .plt            : ALIGN(16) { *(.plt) *(.iplt) }
  .text           :
  {
    *(.text.unlikely .text.*_unlikely .text.unlikely.*)
    *(.text.exit .text.exit.*)
    *(.text.startup .text.startup.*)
    *(.text.hot .text.hot.*)
    *(SORT(.text.sorted.*))
    *(.text .stub .text.* .gnu.linkonce.t.*)
    /* .gnu.warning sections are handled specially by elf.em.  */
    *(.gnu.warning)
  } =0x1f2003d5
  .fini           :
  {
    KEEP (*(SORT_NONE(.fini)))
  } =0x1f2003d5
  PROVIDE (__etext = .);
  PROVIDE (_etext = .);
  PROVIDE (etext = .);
  .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
  .rodata1        : { *(.rodata1) }
  .eh_frame_hdr   : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
  .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .sframe         : ONLY_IF_RO { *(.sframe) *(.sframe.*) }
  .gcc_except_table   : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
  .gnu_extab   : ONLY_IF_RO { *(.gnu_extab*) }
  /* These sections are generated by the Sun/Oracle C++ compiler.  */
  .exception_ranges   : ONLY_IF_RO { *(.exception_ranges*) }
  /* Adjust the address for the data segment.  We want to adjust up to
     the same address within the page on the next page up.  */
  . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
  /* Exception handling  */
  .eh_frame       : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .sframe         : ONLY_IF_RW { *(.sframe) *(.sframe.*) }
  .gnu_extab      : ONLY_IF_RW { *(.gnu_extab) }
  .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
  .exception_ranges   : ONLY_IF_RW { *(.exception_ranges*) }
  /* Thread Local Storage sections  */
  .tdata      :
   {
     PROVIDE_HIDDEN (__tdata_start = .);
     *(.tdata .tdata.* .gnu.linkonce.td.*)
   }
  .tbss       : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
  .preinit_array    :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  }
  .init_array    :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
    KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
    PROVIDE_HIDDEN (__init_array_end = .);
  }
  .fini_array    :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
    KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
    PROVIDE_HIDDEN (__fini_array_end = .);
  }
  .ctors          :
  {
    /* gcc uses crtbegin.o to find the start of
       the constructors, so we make sure it is
       first.  Because this is a wildcard, it
       doesn't matter if the user does not
       actually link against crtbegin.o; the
       linker won't look for a file to match a
       wildcard.  The wildcard also means that it
       doesn't matter which directory crtbegin.o
       is in.  */
    KEEP (*crtbegin.o(.ctors))
    KEEP (*crtbegin?.o(.ctors))
    /* We don't want to include the .ctor section from
       the crtend.o file until after the sorted ctors.
       The .ctor section from the crtend file contains the
       end of ctors marker and it must be last */
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  }
  .dtors          :
  {
    KEEP (*crtbegin.o(.dtors))
    KEEP (*crtbegin?.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
  }
  .jcr            : { KEEP (*(.jcr)) }
  .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
  .dynamic        : { *(.dynamic) }
  .got            : { *(.got) *(.igot) }
  . = DATA_SEGMENT_RELRO_END (24, .);
  .got.plt        : { *(.got.plt) *(.igot.plt) }
  .data           :
  {
    PROVIDE (__data_start = .);
    *(.data .data.* .gnu.linkonce.d.*)
    SORT(CONSTRUCTORS)
  }
  .data1          : { *(.data1) }
  _edata = .; PROVIDE (edata = .);
  . = .;
  __bss_start = .;
  __bss_start__ = .;
  .bss            :
  {
   *(.dynbss)
   *(.bss .bss.* .gnu.linkonce.b.*)
   *(COMMON)
   /* Align here to ensure that the .bss section occupies space up to
      _end.  Align after .bss to ensure correct alignment even if the
      .bss section disappears because there are no input sections.
      FIXME: Why do we need it? When there is no .bss section, we do not
      pad the .data section.  */
   . = ALIGN(. != 0 ? 64 / 8 : 1);
  }
  _bss_end__ = .; __bss_end__ = .;
  . = ALIGN(64 / 8);
  . = SEGMENT_START("ldata-segment", .);
  . = ALIGN(64 / 8);
  __end__ = .;
  _end = .; PROVIDE (end = .);
  . = DATA_SEGMENT_END (.);
  /* Stabs debugging sections.  */
  .stab          0 : { *(.stab) }
  .stabstr       0 : { *(.stabstr) }
  .stab.excl     0 : { *(.stab.excl) }
  .stab.exclstr  0 : { *(.stab.exclstr) }
  .stab.index    0 : { *(.stab.index) }
  .stab.indexstr 0 : { *(.stab.indexstr) }
  .comment       0 : { *(.comment) }
  .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section so we begin them at 0.  */
  /* DWARF 1.  */
  .debug          0 : { *(.debug) }
  .line           0 : { *(.line) }
  /* GNU DWARF 1 extensions.  */
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  /* DWARF 1.1 and DWARF 2.  */
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  /* DWARF 2.  */
  .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev   0 : { *(.debug_abbrev) }
  .debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end) }
  .debug_frame    0 : { *(.debug_frame) }
  .debug_str      0 : { *(.debug_str) }
  .debug_loc      0 : { *(.debug_loc) }
  .debug_macinfo  0 : { *(.debug_macinfo) }
  /* SGI/MIPS DWARF 2 extensions.  */
  .debug_weaknames 0 : { *(.debug_weaknames) }
  .debug_funcnames 0 : { *(.debug_funcnames) }
  .debug_typenames 0 : { *(.debug_typenames) }
  .debug_varnames  0 : { *(.debug_varnames) }
  /* DWARF 3.  */
  .debug_pubtypes 0 : { *(.debug_pubtypes) }
  .debug_ranges   0 : { *(.debug_ranges) }
  /* DWARF 5.  */
  .debug_addr     0 : { *(.debug_addr) }
  .debug_line_str 0 : { *(.debug_line_str) }
  .debug_loclists 0 : { *(.debug_loclists) }
  .debug_macro    0 : { *(.debug_macro) }
  .debug_names    0 : { *(.debug_names) }
  .debug_rnglists 0 : { *(.debug_rnglists) }
  .debug_str_offsets 0 : { *(.debug_str_offsets) }
  .debug_sup      0 : { *(.debug_sup) }
  .ARM.attributes 0 : { KEEP (*(.ARM.attributes)) KEEP (*(.gnu.attributes)) }
  .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) }
  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}


==================================================

Ответы (1 шт):

Автор решения: Pak Uula

Ответ нашелся здесь: https://stackoverflow.com/questions/33944047/why-is-there-unused-empty-space-between-elf-sections

В ld-скрипте есть несколько инструкций:

. = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
/* ... */
. = DATA_SEGMENT_RELRO_END (24, .);
  .got.plt        : { *(.got.plt) *(.igot.plt) }
  .data           :
  {
    PROVIDE (__data_start = .);
    *(.data .data.* .gnu.linkonce.d.*)
    SORT(CONSTRUCTORS)
  }

Инструкция DATA_SEGMENT_RELRO_END требует, чтобы текущее смещение + 24 оказалось на границе страницы. То есть первые три указателя в got.plt находятся на одной странице, а остаток таблицы на другой.

Инструкция DATA_SEGMENT_ALIGN собственно и задаёт выравнивание, которое используется в DATA_SEGMENT_RELRO_END. Заодно вычисляет текущее смещение таким образом, чтобы все секции между DATA_SEGMENT_ALIGN и DATA_SEGMENT_RELRO_END шли подряд без просветов.

Судя по тому, что у вас секция .data расположена по смещению 0x10020, в новых бинутилсах поменяли размер страницы на 0x10000.

Вам нужно либо откатить binutils на версию с другим размером страницы, либо создать свой ld-script. Сохраните дефолтный скрипт, который ld выдал по ключу --verbose, в файл script.ld и вместо MAXPAGESIZE и COMMONPAGESIZE подставьте ваше значение размера страницы. Кастомный скрипт задаётся ключом gcc -T ./script.ld

Для примера я поменял размер страницы на 0x1000

. = DATA_SEGMENT_ALIGN (0x1000,0x1000); 

исполняемый файл получился размером 13Kб

UPD

Не нужно править ld-скрипт, размер страницы можно задать из командной строки -Wl,-zcommon-page-size=0x1000 -Wl,-zmax-page-size=0x1000

→ Ссылка