首页 > 其他分享 >LD链接脚本

LD链接脚本

时间:2024-04-29 23:23:42浏览次数:25  
标签:脚本 输出 LD 符号 text Section 内存 链接

Linker Scripts语法解析

本文主要翻译自Top (LD) (sourceware.org)第三章,链接脚本。因此下面目录均以3开头

目录

3 前言

每个链接都由链接器脚本控制。该脚本是用链接器命令语言LD编写的。

链接器脚本的主要目的是描述如何将输入文件中的节映射到输出文件中,并控制输出文件的存储布局。大多数链接器脚本仅此而已。不过,在必要时,链接器脚本也可以使用以下命令,引导链接器执行其他操作。

链接器优先使用链接器脚本。如果没有提供链接脚本,链接器将使用默认脚本。可以通过--verbose 选项来查看默认脚本,另外有些选项比如-r-N会影响默认脚本。

使用-T 命令行选项提供自己的链接器脚本。这样,可以让自己的链接器脚本将代替默认的链接器脚本。

您也可以隐式使用链接器脚本,将它们命名为链接器的输入文件,就像要链接的文件一样。

3.1Basic Linker Script Concepts

我们需要定义一些基本概念和词汇,以便描述链接器脚本语言。

链接器将输入文件合并为一个输出文件。输出文件和每个输入文件都采用一种称为目标文件格式的特殊数据格式。每个文件都称为目标文件。输出文件通常称为可执行文件,但我们也将其称为目标文件。每个目标文件都有一个段表(section)。我们有时将输入文件中的段称为输入段;同样,输出文件中的段称为输出段。

目标文件中的每个段都有名字和大小。大多数段还有一个相关的数据块,即段内容。一个段可能被标记为可加载,这意味着在执行输出文件时应将其内容加载到内存中。没有内容的段是可分配的,这意味着在内存可以预存一段空间,但不应将任何特定内容加载到该空间(在某些情况下,该内存必须清零)。既不可加载也不可分配的段通常包含某种调试信息。

每个可加载或可分配的输出段都有两个地址。第一个是 VMA,即虚拟内存地址。这是该段在运行输出文件时的地址。第二个是 LMA,即加载内存地址。这是该段将被加载的地址。在大多数情况下,这两个地址是相同的。例如,当数据段加载到 ROM 中,然后在程序启动时复制到 RAM 中(这种技术通常用于初始化基于 ROM 的系统中的全局变量),这两个地址就可能不同。在这种情况下,ROM 地址为 LMA,RAM 地址为 VMA。

使用objdump -h,可以查看目标文件中的节。下图是段内容

image-20240415193348471

每个目标文件都有一个符号表。符号可以是已定义的,也可以是未定义的。每个符号都有一个名称,且每个定义的符号都有一个地址等信息。如果将 C 或 C++ 程序编译成目标文件,每个已定义的函数和全局变量或静态变量都会有一个已定义的符号。输入文件中引用的未定义函数或全局变量都将成为未定义符号。

可以使用 nmobjdump -t查看目标文件的符号。

3.2 Linker Script Format

链接脚本是文本文件格式。

一个链接器脚本是一系列的命令。每个命令都是一个关键字,可能后面还跟有一个参数,或者对符号的赋值。使用分号分割命令,空格通常被忽略。

类似于文件名或者格式名的字串可以直接输入。如果文件名含有一个字符例如逗号(逗号被用来分割文件名),你可以将文件名放在双引号内部。 但是禁止在文件名内使用双引号字符

你以像C语言一样在链接脚本内包含注释,由/**/ 划分。

3.3 Simple Linker Script Example

许多链接器脚本都相当简单。

最简单的链接器脚本只有一条命令:SECTIONS。您可以使用 SECTIONS 命令来描述输出文件的内存布局。

SECTIONS 命是一个强大的命令。下面我们将介绍它的一个简单用法。假设你的程序只包含代码、初始化数据和未初始化数据。它们将分别位于.text"、".data "和".bss "段。我们还假设这些段是输入文件中只出现过一次。

在本例中,假设代码应加载到地址 0x10000,数据应从地址 0x8000000 开始。

SECTIONS
{
  . = 0x10000;
  .text : { *(.text) }
  . = 0x8000000;
  .data : { *(.data) }
  .bss : { *(.bss) }
}

SECTIONS 命令的第一行设置了特殊符号". "的值,即位置计数器。如果没有以其他方式指定输出段的地址(其他方式将在后面介绍),地址将根据位置计数器的当前值设置。然后,位置计数器按输出段的大小递增。在 SECTIONS 命令开始时,位置计数器的值为 0。

第二行定义了一个输出段“ .text ”。 冒号是必需的语法 ,现在可以忽略它。在输出段名称后面的花括号中,列出应放置在此输出段中的输入段的名称。 星号*是与任何文件名匹配的通配符。*(.text)表示所有输入文件中的所有 ‘.text’ 输入段。

由于在定义输出段 ‘.text’ 时位置计数器为0x10000,因此链接程序会将输出文件中 ‘.text’ 段的地址设置为0x10000。

剩下的行定义了输出文件中的.data和.bss段。链接器会将数据段放置在地址0x8000000处。在链接器放置数据段后,位置计数器为’0x8000000 ’加上‘.data ’ 段的大小。因此‘.bss ’ 输出段在内存中将会紧紧挨在‘.data ’段后面。

链接器将通过增加位置计数器(如有必要)来确保每个输出段具有所需的对齐方式。在此示例中, ‘.text’ 和‘.data ’ 段的指定地址可以满足任何对齐方式约束,但链接器可能在‘.data ’ 和‘.bss ’ 段之间创建一个小的间隔。

如上,这就是一个简单完整的链接脚本。

3.4 Simple Linker Script Commands

这一节我们介绍简单的链接脚本命令

  • Setting the Entry Point
  • Commands Dealing with Files
  • Commands Dealing with Object File Formats
  • Assign alias names to memory regions
  • Other Linker Script Commands

3.4.1 Setting the Entry Point

在程序中执行的第一条指令称为入口点。 您可以使用 ENTRY 链接器脚本命令来设置入口点。 参数是符号名称:

ENTRY(symbol)

有几种设置入口点的方法。 链接器将通过依次尝试以下每种方法来设置入口点,并在其中一种成功后停止(优先级):

  • -e ’输入命令行选项;
  • 链接描脚本中的 ENTRY(symbol) 命令;
  • 目标专用符号值(如果已定义); 对于许多目标来说是 start 符号,但是例如基于PE和BeOS的系统检查可能的输入符号列表,并与找到的第一个符号匹配。
  • .text ’ 部段的第一个字节的地址(如果存在);
  • 地址0。

3.4.2 Commands Dealing with Files

3.4.3 Commands Dealing with Object File Formats

3.4.4 Assign alias names to memory regions

别名可以添加到使用 MEMORY 命令创建的现有内存区域中。每个名称只对应一个内存区域。

REGION_ALIAS(alias, region)

REGION_ALIAS 函数为内存区域创建别名。这样就可以灵活地将输出段映射到内存区域。下面是一个示例。

假设我们有一个嵌入式系统应用程序,该系统配有各种内存存储设备。所有系统都有一个通用的易失性RAM,可以运行代码或存储数据。有些可能有一个只读、非易失性ROM,允许代码执行和只读数据访问。有些可能有只读、非易失性存储器 ROM2,只允许读取数据,不允许执行代码。我们有四个输出段:

  • .text 程序代码

  • .rodata 只读数据

  • .data 可读写已初始化数据

  • .bss 可读写未初始化或初始化0数据

    我们的目标是提供一个链接器命令文件,其中包含一个与系统无关的输出段段,和将输出段映射到系统上可用内存区域的系统相关段。我们的嵌入式系统有三种不同的内存设置 A、B 和 C:

Section Variant A Variant B Variant C
.text RAM ROM ROM
.rodata RAM ROM ROM2
.data RAM RAM/ROM RAM/ROM2
.bss RAM RAM RAM

RAM/ROM 或 RAM/ROM2 表示该段分别加载到 ROM 或 ROM2 区域。请注意,在所有三个变体中,.data 段的加载地址都从 .rodata 段的末尾开始。

下面是处理输出段的基本链接脚本。 它包含描述内存布局的系统相关链接 cmds.memory 文件:

INCLUDE linkcmds.memory

SECTIONS
  {
    .text :
      {
        *(.text)
      } > REGION_TEXT
    .rodata :
      {
        *(.rodata)
        rodata_end = .;
      } > REGION_RODATA
    .data : AT (rodata_end)
      {
        data_start = .;
        *(.data)
      } > REGION_DATA
    data_size = SIZEOF(.data);
    data_load_start = LOADADDR(.data);
    .bss :
      {
        *(.bss)
      } > REGION_BSS
  }

现在我们需要三个不同的 linkcmds.memory 来定义内存区域以及别名。下面是A,B,C不同的 linkcmds.memory

A :所有都存入RAM

MEMORY
  {
    RAM : ORIGIN = 0, LENGTH = 4M
  }
REGION_ALIAS("REGION_TEXT", RAM);
 
REGION_ALIAS("REGION_RODATA", RAM);
 
REGION_ALIAS("REGION_DATA", RAM);
 
REGION_ALIAS("REGION_BSS", RAM);
 

B :代码和只读数据存入ROM。可读写数据放入RAM。一个已初始化了的数据的镜像被加载到ROM,并在系统启动的时候读入RAM

MEMORY
  {
    ROM : ORIGIN = 0, LENGTH = 3M
    RAM : ORIGIN = 0x10000000, LENGTH = 1M
  }
REGION_ALIAS("REGION_TEXT", ROM);
 
REGION_ALIAS("REGION_RODATA", ROM);
 
REGION_ALIAS("REGION_DATA", RAM);
 
REGION_ALIAS("REGION_BSS", RAM);
 

C :代码放入ROM,只读数据放入ROM2。可读写数据放入RAM。一个已初始化了的数据的镜像被加载到ROM2,并在系统启动的时候读入RAM

MEMORY
  {
    ROM : ORIGIN = 0, LENGTH = 2M
    ROM2 : ORIGIN = 0x10000000, LENGTH = 1M
    RAM : ORIGIN = 0x20000000, LENGTH = 1M
  }
REGION_ALIAS("REGION_TEXT", ROM);
 
REGION_ALIAS("REGION_RODATA", ROM2);
 
REGION_ALIAS("REGION_DATA", RAM);
 
REGION_ALIAS("REGION_BSS", RAM);
 

可以编写一个通用的系统初始化例程,以便在必要时将 .data 段从 ROM 或 ROM2 复制到 RAM 中:(bootloader)

#include <string.h>

extern char data_start [];
extern char data_size [];
extern char data_load_start [];

void copy_data(void)
{
  if (data_start != data_load_start)
    {
      memcpy(data_start, data_load_start, (size_t) data_size);
    }
}

3.4.5 Other Linker Script Commands

如下是其他的命令。

  • ASSERT(exp, message)

确保 exp 不为零。如果为零,则以错误代码退出链接器,并打印信息。

注意,在链接的最后阶段发生之前会检查断言。 这意味着,如果用户没有为这些符号设置值,则涉及段定义中提供的符号的表达式将失败。 该规则的唯一例外是仅引用点的提供的符号。 因此,如下断言:

  .stack :
  {
    PROVIDE (__stack = .);
    PROVIDE (__stack_size = 0x100);
    ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");
  }

如果没有在其他地方定义stack_size,就会出错。在段外定义的符号会提前被求值,可以在断言中使用,因此下面也会正确生效:

  PROVIDE (__stack_size = 0x100);
  .stack :
  {
    PROVIDE (__stack = .);
    ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");
  }
  • EXTERN(symbol symbol …)

强制将符号作为未定义符号输入到输出文件。举个例子,这样做可能会触发链接标准库中的其他模块。可以在单个 EXTERN中放入多个符号,也可多次使用 EXTERN。 此命令与 -u 命令行选项具有相同的作用。

  • FORCE_COMMON_ALLOCATION
    这个命令与’ -d ’ 命令行选项具有相同的效果:即便是使用了’-r’ 的重定位输出文件,也让 ld 为普通符号分配空间。
  • INHIBIT_COMMON_ALLOCATION
    这个命令与命令行选项 ‘–no-define-common’ 具有相同的效果 : 让 ld 不为普通符号分配空间,即便是一个非可重定位输出文件。
  • FORCE_GROUP_ALLOCATION
    这个命令与命令行选项 ‘–force-group-allocation’ 具有相同的效果 : 使ld place 段组成员像普通的输入段一样,并且即使指定了可重定位的输出文件(’ -r ')也可以删除段组。
  • INSERT [ AFTER | BEFORE ] output_section
    此命令通常在‘-T ’ 指定的脚本中使用,用来增强默认的SECTIONS。例如,重复占位程序段。它将把所有此前的链接脚本的声明插入output_section的后面(或者前面),并且使 ’-T ’不要覆盖默认链接脚本。实际插入点类似于孤儿段。参见Location Counter。插入发生在链接器把输入段映射到输出段后。在插入前,因为’-T ’的脚本在默认脚本之前被解析,在’-T’脚本中的声明会先于默认内部脚本的声明而执行。特别是,将对默认脚本中的’-T ’输出段进行输入段分配。下例为’-T ’脚本使用INSERT可能的情况:
SECTIONS
{
  OVERLAY :
  {
    .ov1 { ov1*(.text) }
    .ov2 { ov2*(.text) }
  }
}
INSERT AFTER .text;

请注意,当两次使用"-T "时,一次是覆盖默认脚本,另一次是使用 INSERT 增强该脚本,解析顺序和章节分配与默认脚本相同。应首先在命令行中指定使用 INSERT 的脚本。

  • NOCROSSREFS(section section …)
    此命令可能被用来告诉 ld,如果引用了section的参数就报错。

    在特定的程序类型中,比如使用覆盖技术的嵌入式系统,当一个段被加载到内存中,另一个段不会被加载。任何两个段之间直接的引用都会带来错误。例如,如果一个段中的代码调用另一个段中的函数,将会产生错误。

    NOCROSSREFS 命令列出了一系列输出段的名字。如果 ld 检测到任何段间交叉引用,将会报告错误并返回非零退出码。注意NOCROSSREFS使用输出段名称,而不是输入段名称。

  • NOCROSSREFS_TO(tosection fromsection …)
    此命令可能被用来告诉 ld,从其他段列表中对某个段的任何引用就会引发错误。

    当需要确保两个或多个输出段完全独立,但是在某些情况下需要单向依赖时,NOCROSSREFS 命令很有用。 例如,在多核应用程序中,可能存在可以从每个核调用的共享代码,但是出于安全考虑,绝不能回调。

    NOCROSSREFS_TO 命令携带(给出)输出段名称的列表。 其他任何段都不能引用第一段。 如果 ld 从其他任何段中检测到对第一段的任何引用,它将报告错误并返回非零退出状态。 请注意,NOCROSSREFS_TO 命令使用输出段名称,而不是输入段名称。

  • OUTPUT_ARCH(bfdarch)
    指定一个特定的输出机器架构。该参数是BFD库使用的名称之一(请参阅BFD)。通过使用带有 ’ -f ’ 选项的objdump程序,您可以看到目标文件的体系结构。

  • LD_FEATURE(string)
    此命令可用于修改 ld 行为。如果字符串是“SANE_EXPR”,那么脚本中的绝对符号和数字将被在任何地方当作数字对待。请参考 Expression Section

3.5 Assigning Values to Symbols

给链接器脚本中的符号赋值。这会定义符号并将其放入具有全局作用域的符号表中。

  • Simple Assignments
  • HIDDEN
  • PROVIDE PROVIDE
  • PROVIDE_HIDDEN PROVIDE_HIDDEN
  • Source Code Reference

3.5.1 Simple Assignments

可以使用任何C赋值操作符来赋值符号:

symbol = expression ;
symbol += expression ;
symbol -= expression ;
symbol *= expression ;
symbol /= expression ;
symbol <<= expression ;
symbol >>= expression ;
symbol &= expression ;
symbol |= expression ;

第一种情况会将符号定义为表达式的值。在其他情况下,symbol 必须已经定义,其值也会相应调整。

特殊符号名称"."表示位置计数器。只能在 SECTIONS 命令中使用。表达式后必须有分号结尾

表达式定义如下; 请参阅Expressions

你在写表达式赋值语句时,可以把它们作为单独的段,也可以作为 ’SECTIONS’ 命令中的一个语句,或者作为 ’SECTIONS’ 命令中输出段描述的一个段。

符号的有效作用区域由表达式所在的段决定,更多信息请参阅Expression Section

下面是一个在三个不同位置赋值的示例:

floating_point = 0;
SECTIONS
{
  .text :
    {
      *(.text)
      _etext = .;
    }
  _bdata = (. + 3) & ~ 3;
  .data : { *(.data) }
}

在这个例子中,, floating_point被定义为0. _etext会被定义在所有.text段中最后一个输入段后面的地址。

_bdata 将被定义为在.text输出段后面的一个4字节向上对齐的地址。

3.5.2 HIDDEN

语法 HIDDEN(symbol = expression)用于对ELF目标端口定义一个符号并且不会被导出

这个例子源于上一节 Simple Assignments, 用HIDDEN重写:

HIDDEN(floating_point = 0);
SECTIONS
{
  .text :
    {
      *(.text)
      HIDDEN(_etext = .);
    }
  HIDDEN(_bdata = (. + 3) & ~ 3);
  .data : { *(.data) }
}

在本例中,这三个符号在此模块之外都失效

3.5.3 PROVIDE

在某些情况下,链接脚本最好只定义被引用的符号,而不定义链接中包含的任何对象。例如,传统的链接器定义了符号 "etext"。但是,ANSI C 要求用户能够将 "etext "作为函数名使用而不会出错。只有在 "etext "被引用但未被定义的情况下,才能使用 PROVIDE 关键字来定义一个符号(如 "etext")。语法为 PROVIDE(symbol = expression)。

SECTIONS
{
  .text :
    {
      *(.text)
      _etext = .;
      PROVIDE(etext = .);
    }
}

在本例中,如果程序定义了_etext(带有前导下划线),链接器将给出重复定义错误。另一方面,如果程序定义了etext(没有前导下划线),链接器会默认使用程序中的定义。如果程序引用了etext但没有定义它,链接器将使用链接器脚本中的定义。

注意 -PROVIDE指令将考虑定义一个普通符号,即使这样的符号可以与PROVIDE将创建的符号组合在一起。当考虑构造函数和析构函数列表符号时,这一点尤其重要,因为它们通常被定义为普通符号。

3.5.4 PROVIDE_HIDDEN

与PROVIDE类似。对于ELF目标的端口,符号将被隐藏且不会被输出。

3.5.5 Source Code Reference

从源代码访问链接脚本定义的变量并不直观。尤其是链接脚本符号并不等同于高级语言中的变量声明,它只是一个没有值的符号。

在进一步说明之前,需要注意的是,编译器在将源代码中的名称存储到符号表时,通常会将其转换为不同的名称。例如,Fortran 编译器通常会预置或附加下划线,而 C++ 则会进行大量的'name mangling'。因此,源代码中使用的变量名称与链接器脚本中定义的同一变量名称之间可能存在差异。例如,在 C 语言中,链接脚本变量可能被称为

  extern int foo;

但在链接脚本中被定义为

  _foo = 1000;

但在其余例子中,我们假定没有进行名称转换。

在 C 语言等高级语言中声明符号时,会发生两件事。首先,编译器会在程序内存中保留足够的空间来存放符号的值。第二件事是编译器会在程序的符号表中创建一个保存符号地址的条目,即符号表包含保存符号值的内存块地址。例如,在文件作用域中的以下 C 声明:

 int foo = 1000;

在符号表中创建一个名为foo的条目。这个入口保存了一个int大小的内存块的地址,数字1000最初存储在这里。当程序引用一个符号时,编译器生成的代码首先访问符号表以查找该符号的内存块地址,然后代码从该内存块读取值。所以:

 foo = 1;

在符号表中查找符号foo,获取与该符号关联的地址,然后将值1写入该地址。而:

int *a = &foo;

在符号表中查找符号foo,获取它的地址,然后将这个地址复制到与变量a相关联的内存块中。

相比之下,链接脚本符号声明在符号表中创建一个条目,但不分配任何内存。因此,它们是一个没有值的地址。例如链接器脚本定义:

  foo = 1000;

会在符号表中创建一个名为foo的条目,该条目包含内存位置 1000 的地址,但在地址 1000 上并没有存储任何特殊内容。这意味着你无法访问链接脚本定义的符号的值,它没有值,你能做的只是访问链接脚本定义的符号的地址。

因此,在源代码中使用链接器脚本定义的符号时,应始终使用符号的地址,而不要试图使用其值。例如,假设要将名为 .ROM的一段内存内容复制到名为 .FLASH 的一段内存中,而链接器脚本中包含这些声明:

  start_of_ROM   = .ROM;
  end_of_ROM     = .ROM + sizeof (.ROM);
  start_of_FLASH = .FLASH;

那么,执行复制操作的 C 源代码将是

 extern char start_of_ROM, end_of_ROM, start_of_FLASH;
 memcpy (&start_of_FLASH, &start_of_ROM, &end_of_ROM - &start_of_ROM);

注意"&"运算符的使用。这些都是正确的。或者换一种方法,也可以将这些符号视为向量或数组的名称,这样代码也能按预期运行。

extern char start_of_ROM[], end_of_ROM[], start_of_FLASH[];
memcpy (start_of_FLASH, start_of_ROM, end_of_ROM - start_of_ROM);

注意此时不需要操作符"&"了。

3.6 SECTIONS Command

SECTIONS命令告诉链接器如何将输入段映射到输出段,以及如何将输出段放在内存中。
SECTIONS命令的格式为:

SECTIONS
{
  sections-command
  sections-command
  …
}

每个 sections-command 命令可能是下面之一:

在 SECTIONS 命令中允许使用 ENTRY 命令和符号赋值,以便于在这些命令中使用位置计数器"."。这也可以使链接器脚本更容易理解,这样可以在输出文件布局中有意义的位置使用这些命令。

下面将介绍输出段说明和overlay说明。

如果没有在链接器脚本中使用 SECTIONS 命令,链接器将按照输入文件中首次出现的顺序,将每个输入段放入名称相同的输出段。例如,如果第一个文件中包含所有输入段,那么输出文件中的段顺序将与第一个输入文件中的顺序一致。第一个段的地址为 0。

  • Output Section Description 输出段描述

  • Output Section Name 输出段名称

  • Output Section Address 输出段地址

  • Input Section 输入段

  • Output Section Data 输出段数据

  • Output Section Keywords 输出段关键字

  • Output Section Discarding 输出段忽略的内容

  • Output Section Attributes 输出段属性

  • Overlay Description 覆盖描述

3.6.1 Output Section Description

输出段的完整描述如下所示:

section [address] [(type)] :
  [AT(lma)]
  [ALIGN(section_align) | ALIGN_WITH_INPUT]
  [SUBALIGN(subsection_align)]
  [constraint]
  {
    output-section-command
    output-section-command
    …
  } [>region] [AT>lma_region] [:phdr :phdr …] [=fillexp] [,]

大段的可选段属性在多数输出段不需要使用。

SECTION 边上的空格是必须的,这样段名就没有歧义了。冒号和花括号也是必需的。如果使用了fillexp,并且下一个section -命令看起来像是表达式的延续,则可能需要在末尾使用逗号。换行符和其他空格是可选的。

当 fillexp 使用且接下来的 sections-command 看起来像是表达式的延续的时候,可能需要在后面加上逗号。

每个输出段命令可以是下列命令之一:
符号赋的值(Assignments)
输入段描述(Input Section)
直接包引用的数据值(Output Section Data)
特殊的输出段关键字(Output Section Keywords))

3.6.2 Output Section Name

输出段的名称是 section。section 必须符合输出格式的要求。在只支持有限数量段的格式中,例如 a.out,名称必须是该格式支持的名称之一(a.out,只允许使用".text"、".data "或".bss")。如果输出格式支持任意数量的段,但只支持数字而不支持名称(如 Oasys),则应以带引号的数字字符串提供名称。区段名称可以由任意字符序列组成,但如果名称中包含逗号等特殊字符,则必须加引号。

输出段名: ‘/DISCARD/’ 特殊; Output Section Discarding.

3.6.3 Output Section Address

地址是输出段的 VMA(虚拟内存地址)表达式。该地址是可选项,但如果提供了该地址,输出地址将完全按照指定的地址设置。

如果未指定输出地址,则将根据下面的优先级步骤为该段选择一个地址。该地址将根据输出段的对齐要求进行调整。对齐要求是指输出段中包含的任何输入段的最严格对齐要求。

输出段地址优先级如下:

  • 如果为该段设置了一个输出内存区域,那么它将被添加到该区域中,其地址将是该区域中的下一个空闲地址。
  • 如果使用 MEMORY 命令创建内存区域列表,那么将选择具有与该段兼容属性的第一个区域来包含该区域。该段的输出地址将是该区域中的下一个空闲地址;MEMORY
  • 如果没有指定内存区域,或者没有与段匹配的内存区域,则输出地址将基于位置计数器的当前值。

比如

.text . : { *(.text) }

.text : { *(.text) }

有着细微的不同。 第一个将.text输出段的地址设置为位置计数器的当前值。 第二个参数也会将其设置为位置计数器的当前值,但是该值与所有.text输入段中最严格的对齐方式对齐。

address 可以是任意表达式; 例如,如果要在0x10字节(16字节)边界上对齐段,以使地址的最低四位为零,则可以执行以下操作:

.text ALIGN(0x10) : { *(.text) }

这样做是因为 ALIGN 返回当前位置计数器向上对齐的指定值。

为某个段指定地址将改变位置计数器的值,前提是该段不是空的(空段将被忽略)。

3.6.4 Input Section Description

最常见的输出节命令是输入节描述。

输入节描述是最基本的链接器脚本操作。输出节用于告诉链接器如何在内存中编排程序。输入节描述用于告诉链接器如何将输入文件映射到内存布局中。

3.6.4.3 Input Section for Common Symbols

由于在许多目标文件格式中,共用符号没有特定的输入段,因此需要为共用符号使用特殊符号。链接器将共用符号视为输入段中的符号,并将其命名为 COMMON 。

与其他输入段一样,您也可以在 COMMON 段中使用文件名。您可以将某个输入文件中的常用符号放在一个段,而将其他输入文件中的常用符号放在另一个段。

大多数情况下,输入文件的共用符号被放置在输出文件的 .bss 节:

.bss { *(.bss) *(COMMON) }

有些文件格式有不止一种类型的通用符号。例如,MIPS ELF 对象文件格式区分了标准通用符号和小型通用符号。在这种情况下,链接程序将为其他类型的常用符号使用不同的特殊段名称。在 MIPS ELF 中,链接器对标准通用符号使用 "COMMON",对小型通用符号使用".scommon"。这样就可以将不同类型的常用符号映射到内存的不同位置。

[COMMON] 作用相同,但这种符号现已过时。

3.6.5 Output Section Data

可以使用 BYTE, SHORT, LONG, QUAD, SQUAD 等关键字作为输出段命令,使输出段包含精确的字节数据. 在该关键字后,括号中的表达式提供了要存储的值 (see Expressions in Linker Scripts). 表达式的值将存储在位置计数器上。

例如,下面将会存储一个单字节数据 1,然后存储一个符号为 addr 的四字节数据:

BYTE(1)
LONG(addr)

当使用64位主机或目标时, QUADSQUAD 是相同的;它们都存储一个8字节(64位)的值。主机和目标都是32位时,表达式被当作32位计算。在这种情况下 QUAD存储一个32位的值,并使用0扩展到64位, SQUAD保存32位值并使用符号位扩展到64位。

如果输出文件的目标文件格式显式的指定大小端,在正常的情况下,值将按照指定的大小端存储。当目标文件格式没有显式的指定大小端,例如,S-records,值将被按照第一个输入目标文件的大小端存储。

通过使用 ASCIZ,可以在输出段包含一个零结尾的字符串。关键字后是一个字符串,该字符串以位置计数器的当前值存储,并在末尾添加一个零字节。如果字符串包含空格,则必须用双引号括起来。字符串可以包含"\n"、"\r"、"\t "和八进制数。但不支持十六进制数。

例如,这个包含 16 个字符的字符串将创建一个17字节的区域

ASCIZ "This is 16 bytes" (这是 16 个字节)

注意:这些命令只能在段描述中起作用,而不能在段落描述之间起作用,因此以下命令会导致链接器出错:

SECTIONS { .text : { *(.text) }LONG(1) .data :{*(.data) }}

这样就可以了:

SECTIONS { .text : { *(.text) ; LONG(1) }.data :{*(.data) }}

您可以使用 FILL 命令设置当前段的填充模式。该命令后面是括号中的表达式。该段中任何未指定的内存区域(例如,由于输入段按要求对齐而留下的间隙)都将用表达式的值填充,必要时还会重复。一条 FILL 语句会覆盖章节定义中出现该语句后的内存位置;通过包含多条 FILL 语句,可以在输出章节的不同段使用不同的填充模式。

此示例说明了如何在内存的未指定区域填充值 "0x90":

FILL(0x90909090)

FILL命令类似 ’=fillexp’ 输出段属性,但其仅影响FILL命令后面的段,而不是整个段。如果同时使用,FILL命令为高优先级。参考 Output Section Fill获取更多填充细节。

3.6.6 Output Section Keywords

有两个关键字可以作为输出段的命令:

CREATE_OBJECT_SYMBOLS

要求链接器为每个输入文件创建一个符号。每个符号的名称将是相应输入文件的名称。每个符号的段将是 CREATE_OBJECT_SYMBOLS 命令所在的输出段。

这是传统的 a.out 对象文件格式。通常不用于任何其他对象文件格式

CONSTRUCTORS

在使用 a.out 目标文件格式进行链接时,链接程序会使用一种不同寻常的集合结构来支持 C++ 全局构造函数和析构函数。在链接不支持任意段的对象文件格式(如 ECOFF 和 XCOFF)时,链接程序会自动识别 C++ 全局构造函数和析构函数的名称。对于这些对象文件格式,CONSTRUCTORS 命令会告诉链接器将构造函数信息放在 CONSTRUCTORS 命令出现的输出段。对于其他对象文件格式,CONSTRUCTORS 命令将被忽略。

符号__CTOR_LIST__ 标记全局构造函数的开始,符号__CTOR_END__标记结束。同样的,__DTOR_LIST__和__DTOR_END__分别标记全局析构函数的开始和结束。第一个列表中的字是入口的数量,后面是每个构造函数或者析构函数的地址,最后是一个全零的字。编译器必须安排实际运行代码。对于这些目标文件格式,GNU C++通常从一个 __main 子程序中调用构造函数,而对 __main 的调用自动被插入到 main 的启动代码中。GNU C++通常使用 atexit 运行析构函数,或者直接从函数 exit 中退出。

对于COFF或者ELF等支持任意段名字的目标文件格式,GNU C++通常把全局构造函数和析构函数放入 .ctors 和 .dtors 段。把下面的代码放入你的链接脚本,将会创建GUN C++运行时期望的表。

      __CTOR_LIST__ = .;
      LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
      *(.ctors)
      LONG(0)
      __CTOR_END__ = .;
      __DTOR_LIST__ = .;
      LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
      *(.dtors)
      LONG(0)
      __DTOR_END__ = .;

如果你正在使用GUN C++支持的初始化优先级,初始化优先级提供了一些对全局构造函数运行顺序的控制,则你必须在链接时对构造函数排序以保证它们以正确的顺序执行。当你使用CONSTRUCTORS 命令,使用 ‘SORT_BY_NAME(CONSTRUCTORS)’ 替换它。当使用 .ctors 和 .dtors 段,使用 ‘(SORT_BY_NAME(.ctors))’ 和’ *(SORT_BY_NAME(.dtors))’ 取代 ‘*(.ctors)’ 和’ ‘(.dtors)’ 。

通常编译器和链接器会自动处理这些问题,您不需要关心它们。但是,在你自己写链接脚本且正在使用C++的时候,你可能需要考虑这些。

3.6.7 Output Section Discarding

链接器通常不会创建没有内容的输出段。这是为了方便引用输入端,因为有些输入端可能存在或者不存在任何输入文件中的段。例如:

.foo : { *(.foo) }

只有在至少有一个输入文件含有 .foo 段且 .foo 段不为空的时候才会在输出文件创建一个 .foo 段。在输出段分配空间的其他链接脚本指令也会创建输出段。点赋值也一样,即使赋值也不会创建空间,除了‘. = 0’, ‘. = . + 0’, ‘. = sym’, ‘. = . + sym’ 和‘. = ALIGN (. != 0, expr, 1)’ 其中 ’sym’ 是一个值为0的已定义绝对符号。因此你可以强制一个空的输出段使用 ‘. = .’。

链接器将忽略对被丢弃的输出段的地址分配(请参见Output Section Address),除非链接器脚本在输出段中定义了符号。在这种情况下,链接器将服从地址分配,即使该段已被丢弃,也可能将点向前推进。

特殊输出段名称 ’/DISCARD/’ 可被用来抛弃输入段。一个被分派到名为 ’/DISCARD/’ 的输出段的输入段将不会被包含在输出文件中。

这可以用来丢弃标有 ELF 标志 SHF_GNU_RETAIN 的输入段,否则这些段就可以从链接器垃圾收集中保存下来。

请注意,与"/DISCARD/"输出段相匹配的段将被丢弃,即使它们位于 ELF 段组中,而该组中还有其他未被丢弃的成员。这是有意为之。丢弃优先于分组。

3.6.8 Output Section Discarding

输出段的完整描述如下:

section [address] [(type)] :
  [AT(lma)]
  [ALIGN(section_align) | ALIGN_WITH_INPUT]
  [SUBALIGN(subsection_align)]
  [constraint]
  {
    output-section-command
    output-section-command
    …
  } [>region] [AT>lma_region] [:phdr :phdr …] [=fillexp]

我们已经描述了section, address, and output-section-command命令。在本节中,我们将描述其余的段属性。

  • Output Section Type: 输出段类型
  • Output Section LMA: 输出段LMA —加载地址
  • Forced Output Alignment: 强制输出对齐
  • Forced Input Alignment: 强制输入对齐
  • Output Section Constraint: 输出段限制
  • Output Section Region: 输出段区域
  • Output Section Phdr: 输出段phdr
  • Output Section Fill: 输出段填充
3.6.8.1 Output Section Type

每个输出段都可能有类型。类型通过括号里的关键字指定。

  • NOLOAD

    该段被标记为不可加载, 所以在程序运行时不会加载到内存中。

  • READONLY

    只读

  • DSECT

  • COPY

  • INFO

  • OVERLAY

    支持这些类型名称是为了向后兼容,但很少使用。它们都有相同的效果:该段应标记为不可分配,这样程序运行时就不会为该段分配内存。

  • TYPE = type

  • READONLY ( TYPE = type )

链接器通常根据映射到输出段的输入段设置输出段的属性。您可以使用类型来覆盖它。例如,在下面的脚本示例中, ROM 段位于内存位置 0 ,在程序运行时不需要加载它。

SECTIONS {
  ROM 0 (NOLOAD) : { … }
  …
}
3.6.8.2 Output Section LMA

每个段都有虚拟内存地址VMA和加载内存地址LMA; see Basic Linker Script Concepts. 虚拟地址由前面的 Output Section Address 指定。加载地址由 ATAT> 关键字指定。指定加载地址是可选的。

AT 用表达式做参数,指定实际的加载地址, AT> 使用内存区域名做参数,可见 MEMORY Command。段的加载地址被设置为该区域的当前空闲位置,并且按照段对齐要求进行对齐。

如果没有使用 ATAT> ,链接器会使用下面的方式来决定加载地址:

  • 指定VMA,则用VMA作为LMA
  • 即使段不可分配,用VMA作为LMA。
  • 否则如果可以找到符合当前段的一个内存区域,且此区域至少包含了一个段,则设置LMA在那里。如此VMA和LMA的区别类似于VMA和LMA在该区域的上一个段的区别。
  • 如果没有声明内存区域且默认区域覆盖了整个地址空间,则采用前面的步骤。
  • 如果找不到合适的区域或者没有前面存在的段,则LMA被设置为等于VMA。

这些功能使构建ROM映像变得容易。例如,以下链接脚本创建三个输出段:一个名为 .text ,从0x1000开始;一个名为 .mdata ,其VMA为0x2000,LMA加载在 .text 的末尾;另一个名为 .bss ,用于在地址0x3000保存未初始化的数据。符号 _data 被定义为值0x2000,这表明位置计数器保存VMA值,而不是LMA值

SECTIONS
  {
  .text 0x1000 : { *(.text) _etext = . ; }
  .mdata 0x2000 :
    AT ( ADDR (.text) + SIZEOF (.text) )
    { _data = . ; *(.data); _edata = . ;  }
  .bss 0x3000 :
    { _bstart = . ;  *(.bss) *(COMMON) ; _bend = . ;}
}

此链接脚本的运行时初始化代码应该类似于下面的形式,把初始化数据从ROM镜像复制到运行时地址。注意这些代码是如何利用链接器脚本定义的符号的。

extern char _etext, _data, _edata, _bstart, _bend;
char *src = &_etext;
char *dst = &_data;

/* ROM has data at end of text; copy it.  */
while (dst < &_edata)
  *dst++ = *src++;

/* Zero bss.  */
for (dst = &_bstart; dst< &_bend; dst++)
  *dst = 0;
3.6.8.3 Forced Output Alignment

可以使用ALIGN增加输出段的对齐。或者可以通过ALIGN_WITH_INPUT属性在整个输出段保持VMA和LMA之间的差异。

3.6.8.4 Forced Input Alignment

您可以使用SUBALIGN来强制输出段中的输入段对齐。指定的值将覆盖输入段提供的任何对齐方式,无论比原来大还是小。

3.6.8.5 Output Section Constraint

通过分别使用关键字 ONLY_IF_ROONLY_IF_RW,可以指定只有在所有输入段都是只读或所有输入段都是读写的情况下才创建输出段。

3.6.8.6 Output Section Region

您可以使用‘>region’将分段分配到先前定义的内存区域。See MEMORY Command

MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }
3.6.8.7 Output Section Phdr

你可以用 :phdr 来把一个段分配给先前定义的程序节. See PHDRS Command. 如果一个节被分配给一个或多个节,那么所有后续分配的段也将被分配给这些节,除非它们显式地使用 :phdr 修饰符。您可以使用 :NONE 来告诉链接器根本不要将该段放在任何节中。

Here is a simple example:

PHDRS { text PT_LOAD ; }
3.6.8.8 Output Section Fill

You can set the fill pattern for an entire section by using ‘=fillexp’. fillexp is an expression (see Expressions in Linker Scripts). Any otherwise unspecified regions of memory within the output section (for example, gaps left due to the required alignment of input sections) will be filled with the value, repeated as necessary. If the fill expression is a simple hex number, ie. a string of hex digit starting with ‘0x’ and without a trailing ‘k’ or ‘M’, then an arbitrarily long sequence of hex digits can be used to specify the fill pattern; Leading zeros become part of the pattern too. For all other cases, including extra parentheses or a unary +, the fill pattern is the four least significant bytes of the value of the expression. If the value is less than four bytes in size then it will be zero extended to four bytes. In all cases, the number is big-endian.

Fill Value     Fill Pattern
0x90           90 90 90 90
0x0090         00 90 00 90
144            00 00 00 90

You can also change the fill value with a FILL command in the output section commands; (see Output Section Data).

Here is a simple example:

SECTIONS { .text : { *(.text) } =0x90909090 }

3.6.9 Overlay Description

An overlay description provides an easy way to describe sections which are to be loaded as part of a single memory image but are to be run at the same memory address. At run time, some sort of overlay manager will copy the overlaid sections in and out of the runtime memory address as required, perhaps by simply manipulating addressing bits. This approach can be useful, for example, when a certain region of memory is faster than another.

Overlays are described using the OVERLAY command. The OVERLAY command is used within a SECTIONS command, like an output section description. The full syntax of the OVERLAY command is as follows:

OVERLAY [start] : [NOCROSSREFS] [AT ( ldaddr )]
  {
    secname1
      {
        output-section-command
        output-section-command
        …
      } [:phdr…] [=fill]
    secname2
      {
        output-section-command
        output-section-command
        …
      } [:phdr…] [=fill]
    …
  } [>region] [:phdr…] [=fill] [,]

Everything is optional except OVERLAY (a keyword), and each section must have a name (secname1 and secname2 above). The section definitions within the OVERLAY construct are identical to those within the general SECTIONS construct (see SECTIONS Command), except that no addresses and no memory regions may be defined for sections within an OVERLAY.

The comma at the end may be required if a fill is used and the next sections-command looks like a continuation of the expression.

The sections are all defined with the same starting address. The load addresses of the sections are arranged such that they are consecutive in memory starting at the load address used for the OVERLAY as a whole (as with normal section definitions, the load address is optional, and defaults to the start address; the start address is also optional, and defaults to the current value of the location counter).

If the NOCROSSREFS keyword is used, and there are any references among the sections, the linker will report an error. Since the sections all run at the same address, it normally does not make sense for one section to refer directly to another. See NOCROSSREFS.

For each section within the OVERLAY, the linker automatically provides two symbols. The symbol __load_start_secname is defined as the starting load address of the section. The symbol __load_stop_secname is defined as the final load address of the section. Any characters within secname which are not legal within C identifiers are removed. C (or assembler) code may use these symbols to move the overlaid sections around as necessary.

At the end of the overlay, the value of the location counter is set to the start address of the overlay plus the size of the largest section.

Here is an example. Remember that this would appear inside a SECTIONS construct.

  OVERLAY 0x1000 : AT (0x4000)
   {
     .text0 { o1/*.o(.text) }
     .text1 { o2/*.o(.text) }
   }

This will define both ‘.text0’ and ‘.text1’ to start at address 0x1000. ‘.text0’ will be loaded at address 0x4000, and ‘.text1’ will be loaded immediately after ‘.text0’. The following symbols will be defined if referenced: __load_start_text0, __load_stop_text0, __load_start_text1, __load_stop_text1.

C code to copy overlay .text1 into the overlay area might look like the following.

  extern char __load_start_text1, __load_stop_text1;
  memcpy ((char *) 0x1000, &__load_start_text1,
          &__load_stop_text1 - &__load_start_text1);

Note that the OVERLAY command is just syntactic sugar, since everything it does can be done using the more basic commands. The above example could have been written identically as follows.

  .text0 0x1000 : AT (0x4000) { o1/*.o(.text) }
  PROVIDE (__load_start_text0 = LOADADDR (.text0));
  PROVIDE (__load_stop_text0 = LOADADDR (.text0) + SIZEOF (.text0));
  .text1 0x1000 : AT (0x4000 + SIZEOF (.text0)) { o2/*.o(.text) }
  PROVIDE (__load_start_text1 = LOADADDR (.text1));
  PROVIDE (__load_stop_text1 = LOADADDR (.text1) + SIZEOF (.text1));
  . = 0x1000 + MAX (SIZEOF (.text0), SIZEOF (.text1));

3.7 MEMORY Command

链接器的默认配置允许分配所有可用内存。您可以使用 MEMORY 命令覆盖这一配置。

The MEMORY command describes the location and size of blocks of memory in the target. 您可以用它来描述链接器可以使用哪些内存区域,以及必须避免使用哪些内存区域。然后,您就可以为特定的内存区域分配区段。链接器将根据内存区域设置分段地址,并对过在·3的内存区域发出警告。链接器不会为了适应可用区域而对段进行洗牌。

一个链接器脚本可能包含多条 MEMORY 命令,但所有定义的内存块都将被视为在一条 MEMORY 命令中指定的内存块 MEMORY 命令的语法为:

MEMORY
  {
    name [(attr)] : ORIGIN = origin, LENGTH = len
    …
  }

名称是链接器脚本中用来指代区域的名称。区域名称在链接器脚本之外没有任何意义。区域名称存储在单独的名称空间中,不会与符号名称、文件名称或段名称冲突。在 MEMORY 命令中,每个内存区域必须有一个不同的名称。 当然可以查阅 Assign alias names to memory regions 来起别名。

attr 字符串是一个可选的属性列表,用于指定是否将特定内存区域用于链接器脚本中未明确映射的输入段。如 SECTIONS Command所述,如果没有为某个输入段指定输出段,链接器将创建一个与输入段同名的输出段。如果定义了区域属性,链接器将使用这些属性为其创建的输出段选择内存区域。

The attr string must consist only of the following characters:

  • ‘R’

    Read-only section

  • ‘W’

    Read/write section

  • ‘X’

    Executable section

  • ‘A’

    Allocatable section

  • ‘I’

    Initialized section

  • ‘L’

    Same as ‘I’

  • ‘!’

    反转后面任何属性的意义

如果未映射区段与"!"以外的任何所列属性相匹配,就会被放入内存区域。属性'!'会反向测试后面的字符,因此只有当未映射段不匹配后面列出的任何属性时,才会将其放入内存区域。因此,"RW!X "属性字符串将匹配任何具有 "R "和 "W "属性的未映射段,但前提是该段不具有 "X "属性。

origin 是内存区域起始地址的数字表达式。该表达式的值必须是常数,且不能包含任何符号。关键词 ORIGIN 可缩写为 orgo

len 是内存区域大小的表达式,单位为字节。与 origin 表达式一样,该表达式必须是数字表达式,且必须求值为常数。关键词 LENGTH 可被缩写为 lenl.

在下面例子中,我们指定有两个可分配的内存区域。一个起始地址为0 长度256KB,另一个起始地址0x40000000,长度为4MB 。链接器会将未明确映射到内存区域的每个部分放入 "rom "内存区域,这些部分要么是只读的,要么是可执行的。链接器将把未明确映射到内存区域的其他部分放入 "ram "内存区域。

MEMORY
  {
    rom (rx)  : ORIGIN = 0, LENGTH = 256K
    ram (!rx) : org = 0x40000000, l = 4M
  }

定义内存区域后,可以通过使用 >region 输出部分属性,引导链接器将特定的输出段放入该内存区域。比如, 有个内存区域 mem , 你可以在输出段使用 >mem 。 See Output Section Region. 如果在输出段没有指定地址, 连接器就会把地址设为内存区域的下一个可用地址. 如果指向内存区域的结合输出段太大超过该区域, 链接器就会报错

通过ORIGIN(memory)LENGTH(memory) 函数,可以访问表达式中内存的起始地址和长度

  _fstack = ORIGIN(ram) + LENGTH(ram) - 4;

3.8 PHDRS Command

ELF 目标文件格式使用程序头,也称为程序段。程序头描述了程序应如何加载到内存中。你可以使用 objdump -p将其打印出来。

在本地 ELF 系统上运行 ELF 程序时,系统加载器会读取程序头,以确定如何加载程序。这只有在程序头设置正确的情况下才会起作用。本手册不描述系统加载器如何解释程序头的细节;有关详细信息,请参阅 ELF ABI。

链接器默认会创建合理的程序头。不过,在某些情况下,您可能需要更精确地指定程序头。为此,您可以使用 PHDRS 命令。当链接器在链接器脚本中看到 PHDRS 命令时,除了指定的程序头外,它不会创建任何其他程序头。

只有在生成 ELF 输出文件时,链接程序才会注意 PHDRS 命令。在其他情况下,链接器将直接忽略 PHDRS。

这是 PHDRS 命令的语法。PHDRS、FILEHDR、AT 和 FLAGS 是关键字。

PHDRS
{
  name type [ FILEHDR ] [ PHDRS ] [ AT ( address ) ]
        [ FLAGS ( flags ) ] ;
}

name 仅供链接器脚本的 SECTIONS 命令参考。它不会被放入输出文件。程序头名称存储在单独的名称空间,不会与符号名称、文件名称或段名称冲突。每个程序头必须有一个独立的名称。程序头是按顺序处理的,通常以加载地址升序映射到程序段。

某些程序头类型描述了系统加载器将从文件中加载的内存节。在链接器脚本中,你可以通过将可分配的输出段放入内存段来指定这些内存段的内容。您可以使用" :phdr "输出段属性将段放入特定的节中。请参阅Output Section Phdr

将某些段放在一个以上的段中是正常的。这仅仅意味着一个内存段包含另一个内存段。您可以重复":phdr",在每个应包含该部分的内存段中使用一次。

如果使用" :phdr "将段放在一个或多个段中,则链接器会将所有未指定" :phdr "的后续可分配段放在同一段中。 这是为了方便起见,因为通常将一整套连续段放在单个段中。 您可以使用:NONE覆盖默认段,并告诉链接器不要将该段放在任何段中。

可以在程序头类型后使用 FILEHDR 和 PHDRS 关键字来进一步描述程序段的内容。FILEHDR 关键字表示段应包括 ELF 文件头。PHDRS 关键字表示段应包括 ELF 程序头本身。如果应用于可加载程序段 (PT_LOAD),所有先前的可加载程序段都必须包含其中一个关键字。

类型可以是以下类型之一。数字表示关键字的值。

  • PT_NULL (0)

​ 表示未使用的程序头。

  • PT_LOAD (1)

​ 表示这个程序头描述了要从文件加载的节

  • PT_DYNAMIC (2)

​ 表示可以找到动态链接信息的段。

  • PT_INTERP (3)

​ 表示可以在其中找到程序解释器名称的段。

  • PT_NOTE (4)

​ 表示包含注释信息

  • PT_SHLIB (5)

​ 保留的程序头类型,由ELF ABI定义但未指定。

  • PT_PHDR (6)

​ 表示可以在其中找到程序头的段。

  • PT_TLS (7)

​ 指示包含线程本地存储的段。

  • 表达式

    该表达式给出程序头的数字类型。 这可以用于上面未定义的类型。

链接器通常会根据组成程序段的部分来设置程序段标志。您可以使用 FLAGS 关键字明确指定段标志。flags 的值必须是整数。它用于设置程序头的 p_flags 字段。

这里是个例子 PHDRS. 展示了典型的在裸ELF系统上的程序头。

PHDRS
{
  headers PT_PHDR PHDRS ;
  interp PT_INTERP ;
  text PT_LOAD FILEHDR PHDRS ;
  data PT_LOAD ;
  dynamic PT_DYNAMIC ;
}

SECTIONS
{
  . = SIZEOF_HEADERS;
  .interp : { *(.interp) } :text :interp
  .text : { *(.text) } :text
  .rodata : { *(.rodata) } /* defaults to :text */
  …
  . = . + 0x1000; /* move to a new page in memory */
  .data : { *(.data) } :data
  .dynamic : { *(.dynamic) } :data :dynamic
  …
}

标签:脚本,输出,LD,符号,text,Section,内存,链接
From: https://www.cnblogs.com/HiDark/p/18166817

相关文章

  • Mysql启动报错:Job for mysqld.service failed because the control process exited wi
      该方法会删除mysql数据,慎用centos7上使用yum安装mysql后,启动报错[root@localhost~]#systemctlstartmysqldJobformysqld.servicefailedbecausethecontrolprocessexitedwitherrorcode.See"systemctlstatusmysqld.service"and"journalctl-xe"for......
  • [转载] RPATH/LD_LIBRARY_PATH/RUNPATH
    原文地址:https://zhuanlan.zhihu.com/p/534778561rpath和runpath都是用来指定搜索动态链接库的目录的,如果不清楚动态链接库是什么,可见静态库vs动态库。动态链接库(sharedlibraries)作为库并不像静态库一样和可执行文件绑定,而是在运行时加载。但,可执行文件在运行时怎么知道库的......
  • 通过Http链接地址爬取有赞微信商城商品信息及下载至EXCEL
    一、环境开发环境:MicrosoftVisualStudioProfessional2022(2)(64位)-Current版本17.7.7.netcore6.0AngleSharp1.1.2AngleSharp.Js0.15.0Downloder3.0.6Magicodes.IE.Excel2.7.5.1二、添加winform项目 三、添加html操作类 四、添加http请求帮助类......
  • curl多请求脚本
    #!/bin/bash#定义请求URL和数据url="https://api-adservices.apple.com/api/v1"data=""output_file="requests_log.txt"#清空或创建输出文件>"$output_file"#函数用于发送POST请求并记录日志send_post_request(){localrequest_number=$1local......
  • 1-Linux集群搭建,分发脚本,ssh免密登录
    Linux集群部署集群规划模板机安装创建完成后全部打开并登录root账户修改克隆主机名vim/etc/sysconfig/network-scripts/ifcfg-ens33esc退出并输入:wq保存按i修改IPADDR为192.168.10.101/192.168.10.102/192.168.10.103vim/etc/hostname按i修改名字......
  • 《最新出炉》系列入门篇-Python+Playwright自动化测试-40-录制生成脚本
    https://www.cnblogs.com/du-hong/p/17835463.html 1.简介各种自动化框架都会有脚本录制功能,playwright这么牛叉当然也不例外。很早之前的selenium、Jmeter工具,发展到每种浏览器都有对应的录制插件。今天我们就来看下微软自动化框架playwright是如何录制脚本的。很多小伙伴或......
  • python使用迭代生成器yield减少内存占用的方法
    在python编码中for循环处理任务时,会将所有的待遍历参量加载到内存中。其实这本没有必要,因为这些参量很有可能是一次性使用的,甚至很多场景下这些参量是不需要同时存储在内存中的,这时候就会用到本文所介绍的迭代生成器yield。1.基本使用首先我们用一个例子来演示一下迭代生成器yi......
  • python主题LDA建模和t-SNE可视化
    原文链接:http://tecdat.cn/?p=4261原文出处:拓端数据部落公众号 使用潜在Dirichlet分配(LDA)和t-SNE中的可视化进行主题建模。本文中的代码片段仅供您在阅读时更好地理解。有关完整的工作代码,请参阅完整资料。我们将首先介绍主题建模和t-SNE,然后将这些技术应用于两个数据集:20个......
  • [转]<a>标签超链接跳转到第三方系统提示:The Http request is not acceptable for the
    原文地址:TheHttprequestisnotacceptablefortherequestedresource.-CSDN博客1.问题描述在做一个点击本系统的一个按钮打开第三方链接并跳转新页面,跳转过去的第三方链接由https://ip地址组成,报以下错:TheHttprequestisnotacceptablefortherequestedresource.2.......
  • sqlserver sp_who2和inputbuffer的使用,如何查询进程执行的脚本代码【转】
    一、sp_who2的使用1、存储过程的位置 sp_who官方解释地址:https://docs.microsoft.com/zh-cn/sql/relational-databases/system-stored-procedures/sp-who-transact-sql?redirectedfrom=MSDN&view=sql-server-2017 2、查出的列名的含义CPUTime: 进程占用的......