为什么ELF部分之间有空闲空间?

看来在Linux(Ubuntu .eh_frame位)上使用gcc 4.9.2创build的二进制文件在段.eh_frame.init_array之间有几千个未使用的字节。 从简单的可执行文件objdump -h输出示例:

 Sections: Idx Name Size VMA LMA File off Algn [...] 16 .eh_frame 000000c0 080484ac 080484ac 000004ac 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 17 .init_array 00000004 08049f08 08049f08 00000f08 2**2 CONTENTS, ALLOC, LOAD, DATA [...] 

.eh_frame在文件偏移.eh_frame处结束,但是.init_array从0xf08开始,留下0x99c = 2460字节的空洞。 所有其他部分在上一部分结束后立即开始。

未使用空间的大小各不相同,很难观察到某些更改会影响代码大小。

这个洞从哪里来? 有没有办法避免它?

更新: ld --verbose

 $ cat so.c int main() { return 0; } $ gcc so.c -Wl,--verbose -o so GNU ld (GNU Binutils for Ubuntu) 2.25 Supported emulations: elf_i386 i386linux elf32_x86_64 elf_x86_64 elf_l1om elf_k1om i386pep i386pe using internal linker script: ================================================== /* Script for -z combreloc: combine and sort reloc sections */ /* Copyright (C) 2014 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("elf32-i386", "elf32-i386", "elf32-i386") OUTPUT_ARCH(i386) ENTRY(_start) SEARCH_DIR("=/usr/i686-linux-gnu/lib32"); SEARCH_DIR("=/usr/local/lib32"); SEARCH_DIR("=/lib32"); SEARCH_DIR("=/usr/lib32"); SEARCH_DIR("=/usr/i686-linux-gnu/lib"); SEARCH_DIR("=/usr/local/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib/i386-linux-gnu"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/lib"); SECTIONS { /* Read-only sections, merged into text segment: */ PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + 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) } .rel.dyn : { *(.rel.init) *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) *(.rel.fini) *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*) *(.rel.data.rel.ro .rel.data.rel.ro.* .rel.gnu.linkonce.d.rel.ro.*) *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*) *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*) *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*) *(.rel.ctors) *(.rel.dtors) *(.rel.got) *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*) *(.rel.ifunc) } .rel.plt : { *(.rel.plt) PROVIDE_HIDDEN (__rel_iplt_start = .); *(.rel.iplt) PROVIDE_HIDDEN (__rel_iplt_end = .); } .init : { KEEP (*(SORT_NONE(.init))) } .plt : { *(.plt) *(.iplt) } .text : { *(.text.unlikely .text.*_unlikely .text.unlikely.*) *(.text.exit .text.exit.*) *(.text.startup .text.startup.*) *(.text.hot .text.hot.*) *(.text .stub .text.* .gnu.linkonce.t.*) /* .gnu.warning sections are handled specially by elf32.em. */ *(.gnu.warning) } .fini : { KEEP (*(SORT_NONE(.fini))) } PROVIDE (__etext = .); PROVIDE (_etext = .); PROVIDE (etext = .); .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } .rodata1 : { *(.rodata1) } .eh_frame_hdr : { *(.eh_frame_hdr) } .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) } .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } /* These sections are generated by the Sun/Oracle C++ compiler. */ .exception_ranges : ONLY_IF_RO { *(.exception_ranges .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. */ . = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); /* Exception handling */ .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) } .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } /* Thread Local Storage sections */ .tdata : { *(.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 (SIZEOF (.got.plt) >= 12 ? 12 : 0, .); .got.plt : { *(.got.plt) *(.igot.plt) } .data : { *(.data .data.* .gnu.linkonce.d.*) SORT(CONSTRUCTORS) } .data1 : { *(.data1) } _edata = .; PROVIDE (edata = .); . = .; __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 don't pad the .data section. */ . = ALIGN(. != 0 ? 32 / 8 : 1); } . = ALIGN(32 / 8); . = SEGMENT_START("ldata-segment", .); . = ALIGN(32 / 8); _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) } /* 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 Extension. */ .debug_macro 0 : { *(.debug_macro) } .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } } ================================================== attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/../../../i386-linux-gnu/crt1.o succeeded /usr/lib/gcc/i686-linux-gnu/4.9/../../../i386-linux-gnu/crt1.o attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/../../../i386-linux-gnu/crti.o succeeded /usr/lib/gcc/i686-linux-gnu/4.9/../../../i386-linux-gnu/crti.o attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/crtbegin.o succeeded /usr/lib/gcc/i686-linux-gnu/4.9/crtbegin.o attempt to open /tmp/ccQ0fTTK.o succeeded /tmp/ccQ0fTTK.o attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/libgcc.so failed attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/libgcc.a succeeded attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/libgcc_s.so succeeded -lgcc_s (/usr/lib/gcc/i686-linux-gnu/4.9/libgcc_s.so) attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/libc.so failed attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/libc.a failed attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/../../../i386-linux-gnu/libc.so succeeded opened script file /usr/lib/gcc/i686-linux-gnu/4.9/../../../i386-linux-gnu/libc.so opened script file /usr/lib/gcc/i686-linux-gnu/4.9/../../../i386-linux-gnu/libc.so attempt to open /lib/i386-linux-gnu/libc.so.6 succeeded /lib/i386-linux-gnu/libc.so.6 attempt to open /usr/lib/i386-linux-gnu/libc_nonshared.a succeeded (/usr/lib/i386-linux-gnu/libc_nonshared.a)elf-init.oS attempt to open /lib/i386-linux-gnu/ld-linux.so.2 succeeded /lib/i386-linux-gnu/ld-linux.so.2 /lib/i386-linux-gnu/ld-linux.so.2 attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/libgcc.so failed attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/libgcc.a succeeded attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/libgcc_s.so succeeded -lgcc_s (/usr/lib/gcc/i686-linux-gnu/4.9/libgcc_s.so) attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/crtend.o succeeded /usr/lib/gcc/i686-linux-gnu/4.9/crtend.o attempt to open /usr/lib/gcc/i686-linux-gnu/4.9/../../../i386-linux-gnu/crtn.o succeeded /usr/lib/gcc/i686-linux-gnu/4.9/../../../i386-linux-gnu/crtn.o ld-linux.so.2 needed by /lib/i386-linux-gnu/libc.so.6 found ld-linux.so.2 at /lib/i386-linux-gnu/ld-linux.so.2 

有三个区域的内存要考虑在那里:

  • 只读数据。
  • 可以在加载时修复的非懒惰重定位。
  • 数据。

现在, .eh_frame节被标记为READONLY ,因此它将进入第一节。

.init_array是初始化函数的一个函数指针数组,它可以在加载程序/库时解析为它们的绝对地址,然后标记为只读(写入函数指针是利用漏洞的常见方式),所以它进入第二个地区。

链接脚本的相关部分是:

 [...] .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) } [...] /* Adjust the address for the data segment. We want to adjust up to the same address within the page on the next page up. */ . = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); [...] .init_array : [...] .got : { *(.got) *(.igot) } . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 12 ? 12 : 0, .); .got.plt : { *(.got.plt) *(.igot.plt) } .data : [...] . = DATA_SEGMENT_END (.); 

您可以在https://sourceware.org/binutils/docs/ld/Builtin-Functions.html上查阅有关GNU ld链接器脚本的内置函数的文档。 但要注意DATA_SEGMENT_ALIGN文档是不正确的,正如Stephen Kell在binutils错误#19203中报告的:“DATA_SEGMENT_ALIGN文档与行为不一致” ,显然是Jakub Jelinek的[PATCH]修复了DATA_SEGMENT_ALIGN 。 DATA_SEGMENT_ALIGN本身是在一个名为[RFC PATCH]的binutils邮件列表线程中引入的,数据段智慧对齐 。

不知何故,如下:

 . = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); 

导致一个1页的跳转,在你的例子中,将你从0x0804856c移动到0x0804956c。

当使用链接器选项-z relro ,请求在加载时固定的重定位被标记为只读, DATA_SEGMENT_RELRO_END使前面的DATA_SEGMENT_ALIGN添加足够的填充,以使DATA_SEGMENT_RELRO_END的两个参数的DATA_SEGMENT_RELRO_END对齐到新页面。

所以,假设.got.plt至少有三个指针,那么前面三个指针(它们将被加载器使用)将会在第二个区域中,而其余的第三个.got.plt在第三个区域中。

DATA_SEGMENT_ALIGN添加的填充将您从0x0804956c移动到0x08049f08。 当所有可以被保护的文件都是只读的,只有在修改后才会被读取,你将会在0x0804a000,在一个新的页面中保持读写状态。