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

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
  • 只指定代码段的地址,则数据段和bss段紧跟着代码段存放

(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(链接地址)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tilblackout

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值