ld链接脚本简述

最近在学习《MIT6.828》,因此顺便简单学习一下ld脚本。主要参考gnu的官方文档,对其用法做一个简单的概括。LD,同时参考了一些网上的资料, ld - 链接脚本学习笔记与实践过程

本文的布局以及内容基本按照LD进行描述,强烈建议读者自行阅读官方文档LD

链接脚本概述

当链接器将可重定位文件通过重定位等操作生成可执行程序的时候,链接脚本可以控制输入文件中的各个段在生成文件中的位置,并且可以规定生成的可执行文件加载入内存之后的空间布局。 链接器ld默认使用其内部的连接脚本,因此在一般情况下,我们都不需要对其进行修改。在使用ld的时候,通过-T选项,可以使用自己写的链接脚本完成链接过程,否则会使用默认的链接脚本。

一些基本概念

ELF文件是由一些section组成的,最重要的是3个:.text段,即代码段,保存着运行时的指令的二进制代码;.data段,即数据段,保存着已经初始化的全局变量以及局部静态变量;.bss段,即未初始化的数据段,保存着未指定初始值或初始值为0的全局变量以及局部静态变量,将.bss.data区段分开的原因:.bss在磁盘上可以只保留标记,而不必真正的分配那么大的磁盘空间给这部分区域,只需在加载入内存之后,分配这些区域,这样可以有效地节省磁盘空间。

section可以被标记为loadable,意味着当输出文件运行起来的时候,这个sectioncontent信息应当被加载到memory中。

section没有content信息,并且被标记为allocatable,意味着会有一块内存会被创建,但是却没有任何内容放在上面,程序运行起来的时候无需要加载任何信息到memory中。

如果section既不是loadable也不是allocatable,那么意味着它包含了一些debug的信息。

链接脚本的输出文件是一个elf文件,里面包含了多个section,包括loadable的section,也包括allocatable section。每一个section包含两个地址,分别是VMA以及LMA

VMAvirtual memory address,LMA是load memory address。大多数两类地址是相同的,但是在嵌入式开发中不大相同,LMAflash地址,而VMA是将flash加载到ram里面运行的ram地址。

一个简单例子

##

SECTIONS
{
  . = 0x10000;
  .text : { *(.text) }
  . = 0x8000000;
  .data : { *(.data) }
  .bss : { *(.bss) }
}
  • SECTIONS字段描述了输出的ELF文件的内存布局。

  • .符号是一个定位符,描述了当前位置的地址。如果不使用 "."来指定开始地址,那么将会从0开始分配地址。
  • .text表示输出文件中将会生成一个.text段。*号是一个通配符,匹配所有的输入elf文件,*(.text) 表示所有的输入elf文件中的.text段。由于定位符.被设置为0x10000,因此在输出的文件中,.text段的起始地址将被设置为0x10000,(每个sectiond都有一个Addr字段,用来表明在加载到内存中的地址)可以通过readelf命令查看。
  • 同样的,.data段的地址被设置为0x8000000.bss段的地址被设置为0x8000000 + sizoef(.data),即直接被放置到了.data的后面。

代码举例

  1. 我们定义两个文件,main.c和add.c,其中main.c调用了add.c中的代码,我们将分别查看使用了上诉链接脚本和默认链接脚本时,其地址的变化。
    //main.c
    int initial_data = 100;
    extern int uinitial_data;
    int add(int lhs, int rhs);;
    void main() {
        uinitial_data = 20;
        int c = add(initial_data, uinitial_data);
    }
    
    //add.c
    int uinitial_data;
    int add(int lhs, int rhs) {
        return lhs + rhs;
    }
    
  2. 先编译出可重定位文件main.oadd.o,并分别查看其segmentgcc -c main.cgcc -c add.c

从节头中可以看出,由于未进行链接,所有的Address全为0。

  1. 使用默认链接脚本进行链接,ld -o main main.o add.o,并显示其节头。(这个并不是gcc中使用的用法,此处只是进行举例)

发现其.text地址Address为0x4000e8。

  1. 使用自定义的连接脚本,ld -T my.ld -o main main.o add.o,并显示其节头

从图中可以看出.text.data.bss的节的地址和链接脚本中的配置是一致的。 我们还可以改变.bss.data的相对地址,并将.data改名为.dataTest

连接脚本的一些基本语法

设置程序执行入口

  • 在程序中执行的第一条指令称为程序入口。可以用下述命令指定程序的入口。
    ENTRY(symbol)
    
  • 程序入口的设置规则,优先级由高到低
    • ld -e选项指定
    • ENTRY(symbol)设置
    • 特定的符号的值。例如很多程序使用符号start的值作为入口地址。
    • 代码段的第一个地址
    • 地址0

设置变量

PROVIDE

provide在脚本中定义了一个变量,之后,C代码中可以通过extern来使用这个变量。

SECTIONS
{
  .text :
    {
      *(.text)
      _etext = .;
      PROVIDE(etext = .);
    }
}
  • 如果程序中定义了变量_etext,那么链接的时候会报重复定义的错误。如果程序中定义了etext,程序不会报错,而是使用程序自己定义的那个变量。如果程序未定义etext而使用了它,那么程序就会使用链接脚本中定义的变量。
Tags: compiler
Share: X (Twitter) Facebook LinkedIn