链接脚本解析

1. 概述

链接器的作用主要是对符号的解析以及将符号与地址进行绑定。要实现这个功能需要依赖链接脚本,链接脚本大多数情况下用来链接输入文件,并生成目标文件。编译器的“-T”参数就是用来指定链接脚本的。

2. 链接脚本

需要解析的链接脚本代码如程序清单 2.1所示。 
程序清单 2.1 链接脚本源码

OUTPUT_FORMAT("elf32-tradlittlemips")
OUTPUT_ARCH(mips)
ENTRY(_start)
SECTIONS
{
  /* Read-onlysections, merged into text segment: */
  . = 0x80100000;
  .text      : 
  {
    _ftext = . ;
    *(.text)
    *(.rodata)
    *(.rodata1)
    *(.reginfo)
    *(.init)
    *(.stub)
    /* .gnu.warningsections are handled specially by elf32.em. */
    *(.gnu.warning)
  } =0
  _etext = .;
  PROVIDE (etext =.);
  .fini      : { *(.fini)    } =0
  .data    :
  {
    _fdata = . ;
    *(.data)
    CONSTRUCTORS
  }
  .data1   : { *(.data1) }
  .ctors         :
  {
               __CTOR_LIST__ = .;
               LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
              *(.ctors)
               LONG(0)
               __CTOR_END__ = .;
  }
  .dtors         :
  {
               __DTOR_LIST__ = .;
               LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
              *(.dtors)
               LONG(0)
               __DTOR_END__ = .;
  }
  _gp = ALIGN(16) +0x7ff0;
  .got           :
  {
    *(.got.plt)*(.got)
   }
  /* We want thesmall data sections together, so single-instruction offsets
     can access them all, and initialized dataall before uninitialized, so
     we can shortenthe on-disk segment size.  */
  .sdata     : { *(.sdata) }
  .lit8 : {*(.lit8) }
  .lit4 : {*(.lit4) }
  _edata  =  .;
  PROVIDE (edata =.);
  __bss_start = .;
  _fbss = .;
  .sbss      : { *(.sbss) *(.scommon) }
  .bss       :
  {
   *(.dynbss)
   *(.bss)
   *(COMMON)
  }
  . = ALIGN(16);
  __bss_end = .;
  _end = .;__end =.; end = .; 
  PROVIDE (end =.);
  /* These areneeded for ELF backends which have not yet been
     converted tothe new style linker.  */
  .stab 0 : {*(.stab) }
  .stabstr 0 : {*(.stabstr) }
  /* DWARF debugsections.
     Symbols in the.debug DWARF section are relative to the beginning of the
     section so webegin .debug at 0.  It‘s not clear yetwhat needs to happen
     for theothers.   */
  .debug          0 : { *(.debug) }
 .debug_srcinfo  0 : {*(.debug_srcinfo) }
 .debug_aranges  0 : {*(.debug_aranges) }
  .debug_pubnames 0: { *(.debug_pubnames) }
 .debug_sfnames  0 : {*(.debug_sfnames) }
.line           0 :{ *(.line) }
  /* These mustappear regardless of  .  */
  .gptab.sdata : {*(.gptab.data) *(.gptab.sdata) }
  .gptab.sbss : {*(.gptab.bss) *(.gptab.sbss) }
}

3. 链接脚本逐句解析

OUTPUT_FORMAT("elf32-tradlittlemips")
OUTPUT_ARCH(mips)

OUTPUT_FORMAT 和 OUTPUT_ARCH 都是 ld 脚本的保留字命令。OUTPUT_FORMAT 说明输出二进制文件的格式,OUTPUT_ARCH 说明输出文件所在平台。

ENTRY(_start)1

ENTRY 命令的作用是,将后面括号中的符号值设置成入口地址。入口地址(entry point)的定义:进程执行的第一条用户空间的指令在进程地址空间中的地址。 
ld 有多种方法设置进程入口地址,通常它按以下顺序设置:(编号越前, 优先级越高) 
1. ld 命令行的“-e”选项; 
2. 链接脚本的 ENTRY(SYMBOL) 命令; 
3. 如果定义了 start 符号, 使用 start 符号值; 
4. 如果存在 .text section, 使用 .text section 的第一字节的位置值; 
5. 使用值 0。

SECTIONS
{

SECTIONS 命令告诉 ld 如何把输入文件的 sections 映射到输出文件的各个 section:即如何将输入 section 合为输出 section;如何把输出 section 放入程序地址空间 (VMA) 和进程地址空间 (LMA) 。其格式如下:

SECTIONS{
….
}
/* Read-only sections, merged into text segment: */
  . = 0x80100000;

这句把定位器符号置为 0x80100000 (若不指定,则该符号的初始值为 0)。 
. 是一个特殊的符号,它是定位器,即一个位置指针,指向程序地址空间内的某个位置(或某section内的偏移,前提是它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用。

.text      : 
  {
    _ftext = . ;
    *(.text)
    *(.rodata)
    *(.rodata1)
    *(.reginfo)
    *(.init)
    *(.stub)
    /* .gnu.warningsections are handled specially by elf32.em. */
    *(.gnu.warning)
  } =0

.text : 表示text段开始。 
(.text) 将所有(符号代表任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定。 
} =0 表示合并时留下的空隙用 0 填充。

_etext = .;
  PROVIDE (etext = .);

_etext = .;很多变量都定义成等于这个 . 符,实际上这个符号所代表的值是在变化的,随着脚本越往后走,值越增加,根据前面填充的多少自动往后加。 
PROVIDE关键字用于定义这类符号:在目标文件内被引用,但没有在任何目标文件内被定义的符号。 
此时定义了一个 etext 符号,当目标文件内引用了 etext 符号,却没有定义它时,etext 符号对应的地址被定义为 .text section 之后的第一个字节的地址。

.fini     
: { *(.fini)    } =0

含义同前文。

.data    :
  {
    _fdata = . ;
    *(.data)
    CONSTRUCTORS
  }
  .data1  : { *(.data1) }

此处代码就是用于描述数据段了。 
CONSTRUCTORS 是一个保留字命令。与 c++ 内的(全局对象的)构造函数和(全局对像的)析构函数相关。

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

对于支持任意section名的目标文件格式,比如COFF、ELF格式,GNU C++将全局构造和全局析构信息分别放入 .ctors section 和 .dtors section 内。 
当链接器生成的目标文件格式不支持任意section名字时,比如ECOFF、XCOFF格式,链接器将通过名字来识别全局构造和全局析构,对于这些文件格式,链接器把与全局构造和全局析构的相关信息放入出现 CONSTRUCTORS 关键字的输出section内。

符号CTORS_LIST表示全局构造信息的的开始处,CTORS_END表示全局构造信息的结束处。 
这两块信息的开始处是一字长的信息,表示该块信息有多少项数据,然后以值为零的一字长数据结束。 
一般来说,GNU C++在函数__main内安排全局构造代码的运行,而__main函数被初始化代码(在main函数调用之前执行)调用。

_gp = ALIGN(16) + 0x7ff0;1

_gp是一个重要的全局变量,用作全局引用的一个指针。

.got           :
  {
    *(.got.plt)*(.got)
   }
  /* We want thesmall data sections together, so single-instruction offsets
     can accessthem all, and initialized data all before uninitialized, so
     we can shortenthe on-disk segment size.  */
  .sdata     : { *(.sdata) }
  .lit8 : {*(.lit8) }
  .lit4 :{ *(.lit4) }

含义同前文。

_edata  =  .;
  PROVIDE (edata = .);

意义与前面的 etext 类似。edata 符号也较为重要。

__bss_start = .;
  _fbss = .;
  .sbss      : { *(.sbss) *(.scommon) }
  .bss       :
  {
   *(.dynbss)
   *(.bss)
   *(COMMON)
  }
  . = ALIGN(16);
  __bss_end = .;
  _end = .;__end =.; end = .; 
  PROVIDE (end = .);

此处是描述BSS段。COMMON 这个保留字的意义: 
通用符号(common symbol)的输入section:在许多目标文件格式中,通用符号并没有占用一个section。链接器认为,输入文件的所有通用符号在名为COMMON的section内。上例中将所有输入文件的所有通用符号放入输出.bss section内。 
上述脚本,定义了几个重要的符号:

__bss_start = .;
__bss_end = .;
_end = .;
__end = .;
end = .;

这些内容在代码中可能会用到的。

/* These are needed for ELF backends which have not yetbeen
     converted tothe new style linker.  */
  .stab 0 : {*(.stab) }
  .stabstr 0 : {*(.stabstr) }
  /* DWARF debugsections.
     Symbols in the.debug DWARF section are relative to the beginning of the
     section so webegin .debug at 0.  It‘s not clear yetwhat needs to happen
     for theothers.   */
  .debug          0 : { *(.debug) }
 .debug_srcinfo  0 : {*(.debug_srcinfo) }
 .debug_aranges  0 : {*(.debug_aranges) }
  .debug_pubnames 0: { *(.debug_pubnames) }
 .debug_sfnames  0 : {*(.debug_sfnames) }
  .line           0 : { *(.line) }
  /* These mustappear regardless of  .  */
  .gptab.sdata : {*(.gptab.data) *(.gptab.sdata) }
  .gptab.sbss : {*(.gptab.bss) *(.gptab.sbss) }
}

最后这部分内容意义与上述类似,看英文注释可以明白,是新版本链接器所需要的一些内容。

4. 免责声明

内部交流文档,仅针对SylixOS平台,若发现相关错误或者建议,请及时联系文档创建者进行修订和更新。

时间: 2024-09-28 20:10:08

链接脚本解析的相关文章

[转]Linux下的链接脚本基础

[转]http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml 1. 前言 每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事情.连接器有个默认的内置连接脚本, 可用ld –verbose查看. 连接选项-r和-N可以影响默

【转】链接脚本(1)

1.什么是ld?它有什么作用? ld是GNU binutils工具集中的一个,是众多Linkers(链接器)的一种.完成的功能自然也就是链接器的基本功能:把各种目标文件和库文件链接起来,并重定向它们的数据,完成符号解析.Linking其实主要就是完成四个方面的工作:storage allocation.symbol management.libraries.relocation. ld可以识别一种Linker command Language表示的linker scriopt文件来显式的控制链接

GNU-ld链接脚本浅析

http://blogold.chinaunix.net/u/30686/showart_357384.html 本文乃转载, 我在其基础上做了少量修改. 原作者的E-mail是[email protected].ac.cn. 完成于2005.11.5-2005.11.8 0. Contents 1. 概论2. 基本概念3. 脚本格式4. 简单例子5. 简单脚本命令6. 对符号的赋值7. SECTIONS命令8. MEMORY命令9. PHDRS命令10. VERSION命令11. 脚本内的表达

Linux下的lds链接脚本详解【转】

转自:http://www.cnblogs.com/li-hao/p/4107964.html 转载自:http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml 一. 概论 每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事

ARM链接脚本

1. 概论 每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的段放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事情. 2. 基本概念 链接器把一个或多个输入文件合成一个输出文件. 输入文件:  目标文件或链接脚本文件.输出文件:  目标文件或可执行文件.目标文件(包括可执行文件)具有固定的格式, 在UNIX或GNU/Linux平台下, 一般为ELF格式. 若想了解

Linux下的lds链接脚本详解

1. 概论2. 基本概念3. 脚本格式4. 简单例子5. 简单脚本命令6. 对符号的赋值7. SECTIONS命令8. MEMORY命令9. PHDRS命令10. VERSION命令11. 脚本内的表达式12. 暗含的连接脚本 1. 概论 每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事情. 连接器有

关于链接脚本中程序入口的一些问题

http://www.jb51.net/article/62360.htm 在编写普通的c语言程序时,我们都会先写一个main函数,并认为main函数是所有程序的入口函数,其实main函数只是我们所编写的程序的入口函数,真正可执行文件的入口点并不是main函数,在执行main函数之前还有许多的初始化工作需要做,这些在main函数之前的工作是由标准 C 库完成的,然后再由标准  C 库调用main函数. 真正可执行文件的入口点可以通过查看链接脚本(在使用ld命令时加上-verbose参数)可以看出

Linux链接脚本学习--lds(转)

Linux链接脚本学习--lds 一.概论 ld: GNU的链接器. 用来把一定量的目标文件跟档案文件链接在一起,并重新定位它们的数据,链接符号引用. 一般编译一个程序时,最后一步就是运行ld进行链接 每一个链接都被一个链接脚本所控制,这个脚本是用链接命令语言书写的. 二.链接脚本 链接脚本的一个主要目的是描述输入文件中的各个段(数据段,代码段,堆,栈,bss)如何被映射到输出文件中,并控制输出文件的内存排布. 链接器总是使用链接脚本的,如果你不提供,则链接器会使用一个缺省的脚本,这个脚本是被编

链接脚本之LMA VMA解释

链接脚本中的LMA和VMA是什么意思.这个问题纠结了一段时间,今天在看<ARM体系结构与编程>时,豁然开朗,写下自己的认识.分享例如以下: LMA:载入地址 位于存储器中的地址  LOAD MEMORY ADDRESS VMA:执行地址(虚拟地址) 执行时的地址 VIRTUAL MEMORY ADDRESS  为什么用VMA表示呐?由于cpu执行的地址都是虚拟地址,经过MMU转为物理地址.在没有开MMU的裸板下,延续了这一称呼.理解为执行地址. 为什么要分 两种地址? 执行映像文件时,有些域能