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 шт):
Ответ нашелся здесь: 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