链接脚本
Posted Li-Yongjun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链接脚本相关的知识,希望对你有一定的参考价值。
文章目录
什么是链接
链接就是将多个目标文件、系统库的目标文件、库文件链接起来,最终生成可以在特定平台运行的可执行文件。
用到的工具为 ld。
ld -T
“-T” 选项,可以直接使用它来指定代码段、数据段、bss 段的起始地址;也可以用来指定一个链接脚本,在链接脚本中进行更复杂的地址设置。
“-T” 选项只用于链接 Bootloader、内核等“没有底层软件支持的”软件;链接运行在操作系统之上的应用程序,无需指定 “-T” 选项,它们使用默认的方式进行链接。
直接指定代码段、数据段、bss 段的起始地址
格式如下:
-Ttext startaddr
-Tdata startaddr
-Tbss startaddr
其中 startaddr 分别表示代码段、数据段和 bss 段的起始地址。
示例
/* start.S */
.global _start
_start:
/* 设置处理器进入 SVC 模式 */
mrs r0, cpsr /* 读取 cpsr 到 r0 */
bic r0, r0, #0x1f /* 清除 cpsr bit4~0 */
orr r0, r0, #0x13 /* 使用 SVC 模式 */
msr cpsr, r0 /* 将 r0 写入到 cpsr */
/* 设置 SP 指针 */
ldr sp, =0x80200000
b main /* 跳转到 C 语言 main 函数 */
// main.c
#include "main.h"
/* 使能外设时钟 */
void clk_enable(void)
CCM_CCGR1 = 0xFFFFFFFF;
CCM_CCGR2 = 0xFFFFFFFF;
CCM_CCGR3 = 0xFFFFFFFF;
CCM_CCGR4 = 0xFFFFFFFF;
CCM_CCGR5 = 0xFFFFFFFF;
CCM_CCGR6 = 0xFFFFFFFF;
/* 初始化 LED */
void led_init(void)
SW_MUX_GPIO1_IO03 = 0x5; /* 复用为 GPIO1-IO03 */
SW_PAD_GPIO1_IO03 = 0x10B0; /* 设置 GPIO1-IO03 电气属性 */
/* GPIO 初始化 */
GPIO1_GDIR = 0x08; /* 设置为输出 */
GPIO1_DR = 0x0; /* 打开 LED 灯 */
int main(void)
clk_enable(); /* 使能外设时钟 */
led_init(); /* 初始化 LED */
while (1)
return 0;
# Makefile
objs = start.o main.o
ledc.bin : $(objs)
arm-linux-gnueabihf-ld -Ttext 0x87800000 $^ -o ledc.elf
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
%.o : %.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
%.o : %.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis
链接命令将会展开为
arm-linux-gnueabihf-ld -Ttext 0x87800000 start.o main.o -o ledc.elf
由于 start.o 写在前面,所以 text 段的一开始存放的就是 srart.o 的内容,佐证如下
;ledc.dis
ledc.elf: 文件格式 elf32-littlearm
Disassembly of section .text:
87800000 <_start>:
87800000: e10f0000 mrs r0, CPSR
87800004: e3c0001f bic r0, r0, #31
87800008: e3800013 orr r0, r0, #19
8780000c: e129f000 msr CPSR_fc, r0
87800010: e51fd000 ldr sp, [pc, #-0] ; 87800018 <_start+0x18>
87800014: ea000055 b 87800170 <__main_from_arm>
87800018: 80200000 eorhi r0, r0, r0
...
0x87800000 地址开始就是存放 start.o 中的代码 _start 指令。
使用链接脚本设置地址
ld 使用 imx6u.lds 来设置可执行文件 ledc.bin 的地址信息,脚本文件内容如下
/* imx6u.lds */
SECTIONS
. = 0x87800000;
.text :
start.o
*(.text)
.rodata ALIGN(4) : *(.rodata*)
.data ALIGN(4) : *(.data)
__bss_start = .;
.bss ALIGN(4) : *(.bss) *(COMMON)
__bss_end = .;
# Makefile
objs = main.o start.o
ledc.bin : $(objs)
arm-linux-gnueabihf-ld -T imx6u.lds $^ -o ledc.elf
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
%.o : %.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
%.o : %.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis
链接命令展开为
arm-linux-gnueabihf-ld -T imx6u.lds main.o start.o -o ledc.elf
并且,由于链接脚本显式的指定了 start.o 放在 “.text” 段的开始,所以 Makefile 中就无需考虑 start.o 和 main.o 书写的先后顺序了。
查看反编译内容,_start 地址为 0x87800000,符合预期
/* ledc.dis */
ledc.elf: 文件格式 elf32-littlearm
Disassembly of section .text:
87800000 <_start>:
87800000: e10f0000 mrs r0, CPSR
87800004: e3c0001f bic r0, r0, #31
87800008: e3800013 orr r0, r0, #19
8780000c: e129f000 msr CPSR_fc, r0
87800010: e51fd000 ldr sp, [pc, #-0] ; 87800018 <_start+0x18>
87800014: ea000041 b 87800120 <__main_from_arm>
87800018: 80200000 eorhi r0, r0, r0
...
示例链接脚本解释
第 2 行,对一个特殊的符号 “.” 进行赋值,“.” 在链接脚本里面叫做定位计数器,默认的定位计数器为 0。我们要求代码链接到以 0x87800000 为起始地址的地方,因此这一行给 “.” 赋值为 0x87800000,后面的文件或者段都会以 0x87800000 为起始地址开始链接。
第 3 行,“.text” 是段名,段名后面要加个空格(不然,链接器会把冒号也算进段名),再跟个冒号。冒号后面的大括号里面可以填上要链接到 “.text” 这个段里面的所有文件,“(.text)” 中的 “” 是通配符,表示所有输入文件的 .text 段都放到 “.text” 中。
第 5 行,start.o 写在 “.text” 的最开始,表明将 start.o 放在 “.text” 段的最开始位置。因为 start.o 里面包含着第一个要执行的指令。
第 9 行,和第三行一样,定义了一个名为 “.data” 的段,然后所有文件的 “.data” 段都放到这里面。但是这一行多了一个 “ALIGN(4)”,这是用来对 “.data” 段的起始地址做字节对齐的,ALIGN(4) 表示 4 字节对齐。也就是说 “.data” 段的起始地址要能被 4 整除。
第 10、12 行,“__bss_start” 和 “__bss_end” 是符号,这两行就是对这两个符号进行赋值,其值为定位符 “.”。这两个符号用来保存 “.bss” 段的起始地址和结束地址。“.bss” 段存放的是定义了但没有被初始化的全局变量和静态变量,我们需要手动对 “.bss” 段的变量进行清零,因此需要知道 “.bss” 段的起始和结束地址,这样直接对这段内存赋值为 0 即可完成清零(在汇编或 C 文件里面使用这两个符号)。
__bss_start 、 __bss_end 符号的使用实例
在 uboot 的 crt0_64.S 汇编文件中,就使用了 __bss_start、__bss_end 两个符号,实现对 “.bss” 段清零的功能。汇编代码如下,
arch/arm/lib/crt0_64.S
/*
* Clear BSS section
*/
ldr x0, =__bss_start /* this is auto-relocated! */
ldr x1, =__bss_end /* this is auto-relocated! */
mov x2, #0
clear_loop:
str x2, [x0]
add x0, x0, #8
cmp x0, x1
b.lo clear_loop
链接脚本简介
链接脚本的基本命令是 SECTIONS 命令,它描述了输出文件的“映射图”:输出文件中各段、各文件怎么放置。
一个 SECTIONS 命令内部包含一个或多个段,段(Section)是链接脚本的基本单元,它表示输入文件中的某部分怎么放置。
链接脚本格式:
SECTIONS
…
secname start ALIGN(align) (NOLOAD) : AT(ldadr)
contents > region :phaddr =fill
…
secname 和 contents 是必须的,前者用来命名这个段,后者用来确定代码中的什么部分放在这个段中。
start 是这个段重定位地址,也称运行地址。如果代码中有位置无关的指令,程序在运行时,这个段必须放在这个地址上。
ALIGN(align) 虽然 start 指定了运行地址,但是仍可以使用 BLOCK(align) 来指定对齐的要求——这个对齐的地址才是真正的运行地址。
(NOLOAD) 用来告诉加载器,在运行时不用加载这个段。显然,这个选项只有在有操作系统的情况下才有意义。
AT(ldadr) 指定这个段在编译出来的映像文件中的地址——加载地址(load address)。如果不使用这个选项,则加载地址等于运行地址。
以上是关于链接脚本的主要内容,如果未能解决你的问题,请参考以下文章