欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

lds链接脚本基础与例子分析

程序员文章站 2022-07-12 10:55:19
...

1.基础

(1)段

  1. .data段包含初始值非0的全局变量(不管静态还是非静态)
  2. .rodata段包含被const修饰的初始值非0的全局变量
  3. .bss段包含初始值为0或未初始的全局变量(不管有没有const修饰,也不管是静态还是非静态)局部变量保存在栈中
    :有的编译器会将没有初始化的变量保存在COMMON段,等到链接时再将其放入到bss段。
  4. .text段保存代码

(2)指定不同段的地址(不用链接脚本)

编译过程

      编译: arm-linux-gcc -c -o led_on.o led_on.S  #或led_on.c
      链接: arm-linux-ld -Ttext 0 led_on.o -o led_on.elf
   生成bin: arm-linux-objcopy -o binary -S led_on.elf led_on.bin
生成反汇编: arm-linux-objdump -D led_on.elf > led_on.dis

arm-linux-objcopy
复制一个目标文件的内容到另一个文件中,可以使用不同于源文件的格式来输出目的文件,即可以进行格式转换。常用arm-linux-objcopyELF格式的可执行文件转换为bin二进制文件。

arm-linux-objcopy -O binary -S elf_file bin_file
	-O:使用指定的格式来输出文件
	-S:不从源文件中复制重定位信息和符号信息到目标文件中去

arm-linux-objdump
显示二进制文件信息,常用来查看反汇编代码。

//ELF转为反汇编
arm-linux-objdump -D elf_file>dis_file
//二进制转为反汇编
arm-linux-objdump -D -b binary -m arm bin_file > dis_file
	-D:反汇编所有段
	-b:指定目标码格式,不是必须的。可通过arm-linux-objdump -i查看支持的目标码格式
	-m machine:指定反汇编目标文件时所使用的架构,当待反汇编文件本身没有描述架构信息的时候,这个选项很有用。

arm-linux-ld

-Ttext startaddr    #直接指定代码段地址
-Tdata startaddr    #直接指定数据段地址
-Tbss startaddr     #直接指定bss段地址

例子

arm-linux-gcc -c -o link.o link.s
arm-linux-ld -Ttext 0x00000000 link.o -o link_elf_0x00000000//启动后PC=0x00000000
arm-linux-ld -Ttext 0x30000000 link.o -o link_elf_0x30000000//启动后PC=0x30000000

(3)链接地址和加载地址

  • 链接地址是程序实际运行的地址(内存)
  • 加载地址指的是程序编译后的存放地址(Flash)

(4)链接脚本格式

  • 链接脚本由一系列命令组成,每个命令由一个关键字或一条对符号的赋值语句组成,命令间用分号分开。
  • 若文件名或格式名内包含分号,需用单引号引用起来

(5)链接脚本的语法

SECTIONS {
	...
	secname start ALIGN(align) (NOLOAD) : AT ( ldadr )
	  { contents } >region :phdr =fill
	...
}
  • secnamecontents是必须的,前者用来命名这个段,后者用来确定代码中的什么部分放在这个段中。
  • start:段重定位地址,也称为VMA,即运行地址。如果代码中有位置相关的指令,程序在运行时,这个段必须放在这个地址上。
  • ALIGN(align):虽然start指定了运行地址,但是仍可以使用BLOCK(align)来指定对齐的要求一这个对齐的地址才是真正的运行地址。
  • (NOLOAD):用来告诉加载器,在运行时不用加载这个段。这个选项只有在有操作系统的情况下才有意义。
  • AT (ldadr):指定这个段在编译出来的映象文件中的地址,称为LMA,即加载地址。若不指定默认加载地址等于运行地址。通过这个选项,可以控制各段分别保存在输出文件中不同的位置。
  • >region :phdr =fill:没用到,不作介绍。

(6)简单脚本命令

  • ENTRY(SYMBOL) : 将符号SYMBOL的值设置成入口地址(执行的第一条指令的地址)。
    • 入口地址设置的方法还有(按优先级高低):
      • ld命令行的-e选项
      • 链接脚本的ENTRY(SYMBOL)命令
      • 如果定义了start符号,使用start符号值
      • 如果存在.text section, 使用其第一字节的位置值
      • 使用值0
  • INCLUDE filename:包含其他名为filename的链接脚本。脚本搜索路径由-L选项指定。
  • . = ALIGN(4):代码以4字节对齐
  • LOADADDR(.data):取data段的LMA
  • ADDR(.data):取data段的VMA

(7)C语言相关
对C语言符号地址的赋值
在C文件内定义的全局变量可以在链接脚本内被赋值,此处赋值的意思是更改变量的地址。

/* a.c */
#include <stdio.h>
int a = 100;
int main(void)
{
    printf( "&a=0x%p ", &a );
    return 0;
}

/* a.lds */
a = 3;

/* 不使用链接脚本 */
gcc -Wall -o a-without-lds a.c
&a = 0x8049598

/* 使用链接脚本 */
$ gcc -Wall -o a-with-lds a.c a.lds
&a = 0x3

将变量/函数放入指定段中
__attribute__((section("section_name"))) :将作用的函数或数据放入指定名为"section_name"对应的段中。

变量:
const int descriptor[3] __attribute__ ((section ("descr"))) = { 1,2,3 };
long long rw[10] __attribute__ ((section ("RW")));
函数:
void Function_Attributes_section_0(void) __attribute__ ((section ("new_section")));  //声明时指定段
void Function_Attributes_section_0(void)
{
    static int aStatic =0;
    aStatic++;
}

链接脚本变量
链接脚本中定义的变量可以在C语言中使用extern关键字声明并使用

2 链接脚本例子

(1)例1:基础链接脚本

SECTIONS {
   outputa 0x10000 :       //该section的VMA是0x10000
   {
        all.o              //all.o文件的所有section
        foo.o (.input1)    //foo.o文件的所有(一个文件内可有多个同名section).input1 section
   }
   outputb :               //该section的VMA是当前定位器符号的修调值(对齐后)
   {
        foo.o (.input2)
       foo1.o (.input1)
   }
   outputc :               //将非all.o、foo.o、foo1.o文件的. input1 section和.input2 section放入输出outputc section内
   {
       *(.input1)
       *(.input2)
   }
}

(2)例2
编译时使用链接脚本:

arm-linux-ld -Ttimer.lds -o timer_elf head.o init.o interrupt.o main.o

timer.lds如下

SECTIONS{
    . = 0X30000000;
    .text        : {*(.text)}
    .rodata ALIGN(4) :{*(.rodata)}
    .data ALIGN(4) :{*(.data)}
    .bss ALIGN(4) :{*(.bss)  *(COMMON)}
}

①第2行设置运行地址为0x30000000

  • .为定位器符号,不指定默认为0。存放了某个段后,定位器符号会往后移动这个段的大小长度,所以后面rodata段的地址为0X30000000+.text段大小(还要四字节对齐)

②第3行定义了一个名为.text的段,它的内容为*(.text), 表示所有输入文件的代码段。这些代码段被集合在一起,起始运行地址为0x30000000
③第4行定义了一个名为.rodata的段,在输出文件timer_elf中,它紧挨着.text段存放。其中的ALIGN (4)表示起始运行地址为4字节对齐。假设前面.text段的地址范围0x30000000~0x300003f1,则.rodata段的地址是4字节对齐后的0x300003f4。
④第5、6行的含义与第4行类似。
(3)例3:输出SECTION的LMA修改

SECTIONS
{
    .text 0x1000 : 
    { 
        *(.text) 
        _etext = . ;
    }
    .mdata 0x2000 : AT ( ADDR (.text) + SIZEOF (.text) )
    {
         _data = . ;
         *(.data);
         _edata = . ;
     }
    .bss 0x3000 :
    {
        _bstart = . ;
        *(.bss) *(COMMON) ;
        _bend = . ;
    }
}
程序中可定义上面定义的地址:
extern char _etext, _data, _edata, _bstart, _bend; 
  • .mdata 0x2000 : AT ( ADDR (.text) + SIZEOF (.text) ):生成的bin文件中mdata段紧跟着text段,但是加载到内存时,mdata段在0x2000处。(若LMA不紧接着上一个,生成的bin文件会很大,中间都是空洞,不方便烧写)

再来看一个链接脚本片段:

.data 0x30000000 : AT(0x800) #不指定默认LMA等于VMA
  • 对应的bin文件的data段会烧写在flash的800处,程序运行时,代码中实现从0x800(加载地址)复制代码到0x30000000(链接地址)