我在哪里可以找到实际的链接器脚本和设置gcc使用?
我尝试过的方法:
具体来说,让我们考虑一个小程序:empty. c
int main(void)
{
return 0;
}
字符集
静态地构建它,看看结果:
$ gcc -static -o empty empty.c
$ readelf -W -l empty
Elf file type is EXEC (Executable file)
Entry point 0x400f4e
There are 6 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x0bf581 0x0bf581 R E 0x200000
LOAD 0x0bfeb0 0x00000000006bfeb0 0x00000000006bfeb0 0x001d80 0x0042d8 RW 0x200000
NOTE 0x000190 0x0000000000400190 0x0000000000400190 0x000044 0x000044 R 0x4
TLS 0x0bfeb0 0x00000000006bfeb0 0x00000000006bfeb0 0x000020 0x000058 R 0x10
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x0bfeb0 0x00000000006bfeb0 0x00000000006bfeb0 0x000150 0x000150 R 0x1
Section to Segment mapping:
Segment Sections...
00 .note.ABI-tag .note.gnu.build-id .rela.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_subfreeres __libc_atexit __libc_thread_subfreeres .eh_frame .gcc_except_table
01 .tdata .init_array .fini_array .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs
02 .note.ABI-tag .note.gnu.build-id
03 .tdata .tbss
04
05 .tdata .init_array .fini_array .jcr .data.rel.ro .got
型
请注意各个部分,它们被分组为段,并放置在具有各种权限的内存区域中。
现在,让我们尝试尽可能多地了解它是如何进行这种链接的。
$ gcc -static -o empty empty.c -Wl,--verbose
GNU ld (GNU Binutils for Ubuntu) 2.24
Supported emulations:
elf_x86_64
elf32_x86_64
elf_i386
i386linux
elf_l1om
elf_k1om
i386pep
i386pe
using internal linker script:
==================================================
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
"elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib");
SECTIONS
{
/* Read-only sections, merged into text segment: */
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + 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.ldata .rela.ldata.* .rela.gnu.linkonce.l.*)
*(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*)
*(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*)
*(.rela.ifunc)
}
.rela.plt :
{
*(.rela.plt)
PROVIDE_HIDDEN (__rela_iplt_start = .);
*(.rela.iplt)
PROVIDE_HIDDEN (__rela_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) >= 24 ? 24 : 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 ? 64 / 8 : 1);
}
.lbss :
{
*(.dynlbss)
*(.lbss .lbss.* .gnu.linkonce.lb.*)
*(LARGE_COMMON)
}
. = ALIGN(64 / 8);
. = SEGMENT_START("ldata-segment", .);
.lrodata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
{
*(.lrodata .lrodata.* .gnu.linkonce.lr.*)
}
.ldata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
{
*(.ldata .ldata.* .gnu.linkonce.l.*)
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
. = ALIGN(64 / 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_*) }
}
==================================================
... <snip searching and linking actual object files>
型
但是,该脚本虽然很长,但缺少前面列出的大部分导入信息。
它如何知道将哪些部分收集到不同的负载段中?
没有PHDRS命令,虽然SEGMENT_START的使用表明在其他地方定义了该系统的一些标准段,但这些段中没有一个与相关段一起列出。
此外,它如何知道在哪里加载这些段,或者这些内存区域有什么权限?
这里没有MEMORY命令。同样,如果在其他地方定义了这个系统的一些标准内存区域,那么没有一个部分列出要使用的内存区域。
当我以前看到微控制器的默认链接器脚本时,它们非常详细。然而,这个输出表明还有更多的脚本和设置。
这些其他链接器脚本定义和设置存储在哪里?
3条答案
按热度按时间4xrmg8kj1#
我知道这是一个老问题了,但我也发现它令人沮丧的是,没有关于在链接过程中使用的选项的精确信息。这个答案显示了我找到它们的过程。
首先,我想说的是,我在查看官方文档https://gcc.gnu.org/onlinedocs/-我搜索了GCC Manual和GCC Internals Manual。我找到的唯一有意义的信息是
gcc
使用一个名为collect 2的内部工具来调用链接器。根据https://gcc.gnu.org/onlinedocs/gccint/Collect2.html“程序collect 2的工作原理是链接程序一次,然后在链接器输出文件中查找具有特定名称的符号,这些符号表示它们是构造函数”。因此,它被用来使链接成为可能。我接下来要做的是了解源代码。您可以在这里浏览https://code.woboq.org/gcc/gcc/collect2.c.html的代码。问题是它并没有真正的帮助。但是我注意到collect 2
fork_execute
函数调用了ld
。您可以深入研究fork_execute
,以发现它会生成(在分叉的程序中执行一个新程序),然后等待它完成。因为分叉和执行都是系统调用(简单地说-系统调用是应用程序用来与系统通信的方式/函数)。我决定给予一试。因此,我做了一个简单的程序,不需要任何编译(它已经编译成目标文件-所以
gcc
所要做的一切就是链接)。字符串
然后我使用strace与以下选项:
-o forked.log
将输出保存到forked.log-s 1024
长度小于1024个字符的变量不会被截断(默认值为32个字符不够)-f
-在分叉进程上启用strace-e trace=/exec
-过滤系统调用,以便仅显示以exec
开头的系统调用最终输出如下。
型
所以使用ld命令是
型
那么他妈的是做了什么呢?好吧,所有的选项都可以在手册中找到。这里是分解的输出。
/usr/bin/ld
-连接程式--build-id
-将build-id添加到二进制文件中。在我的系统中默认为sha1。--no-add-needed
-它是--no-copy-dt-needed-entries
解压缩名称-它与ELF中DT_NEEDED标记相关联,如果我正确理解,则意味着不会从输入库中复制DT_NEEDED标记--eh-frame-hdr
-“请求创建“.eh_frame_hdr”部分和ELF“PT_GNU_EH_FRAME”段头。”不管这是什么意思。--hash-style=gnu
-“设置链接器的哈希表的类型”。默认值为sysv,但有一个更新的格式gnu
。二进制文件也可以有两种格式的哈希表。-m elf_x86_64
-链接器仿真(使elf类型成为x86_64的二进制)-dynamic-linker /lib64/ld-linux-x86-64.so.2
-设置预期动态链接器的名称-o hello_gcc
-设置输出二进制/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o
-在实际程序的main之前**运行的代码/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o
-在实际程序的main之前**运行的代码/usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o
-在实际程序的main之前**运行的代码-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5
-其他库搜索路径-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64
-其他库搜索路径-L/lib/../lib64
-其他库搜索路径-L/usr/lib/../lib64
-其他库搜索路径-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../..
-其他库搜索路径/tmp/ccyl36jf.o
-这是实际程序(二进制对象)及其主函数-lgcc
--l
选项-“将namespec指定的归档或目标文件添加到要链接的文件列表中。”在这种情况下,它是gcc。--as-needed
-启用“按需”模式,该模式检查在特定点上是否需要以下库(命名空间?)-lgcc_s
- add gcc_s请注意,只有在此时确实需要时才添加。--no-as-needed
-禁用“按需”模式,该模式检查在特定点上是否需要以下库(命名空间?)-lc
-标准C命名空间/库-lgcc
-此lib应该已经设置。此选项的用法与以前的用法之间可能存在差异。--as-needed
- set“as-needed”模式。此选项的用法与以前的用法之间可能存在差异。-lgcc_s
-已描述。此选项的用法与以前的用法之间可能存在差异。--no-as-needed
-禁用“按需模式”。/usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o
-程序完成时运行附加代码/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o
-程序结束时运行附加代码。更多信息:
crt1.o
、crti.o
、crtbegin.o
、crtend.o
、crtn.o
-它们是启动、初始化、构造函数、析构函数和终结文件(根据Karim Yaghmour的《构建嵌入式Linux系统》)。也许更简单的方法
在编写此答案的过程中,我还“发现”您可以使用
-v
选项调用gcc
,并且它将返回COLLECT_GCC_OPTIONS,这与调用的ld
相同型
不过,如果您想要100%确定
ld
是如何被调用的,则最好使用strace。最后,请注意,我使用了Enterprise Linux v7和v8系统来检查我的判断是否正确。这两个系统都使用x86_64架构,在不同的架构上,结果可能会有所不同。
qncylg1j2#
在我的Ubuntu系统上,链接器脚本位于:
/lib/x86_64-linux-gnu/ldscripts
基本脚本似乎是根据目标体系结构(如elf_x86_64)选择的,每个基本体系结构都有几个变体。
我不确定,但变体似乎是基于某些链接器选项选择的。
xqk2d5yq3#
在链接时将
-Wl,--verbose
选项添加到GCC中。这将使它将--verbose
选项传递给链接器,从而使链接器打印出包含在其中的默认链接器脚本。