链接脚本

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)。如果不使用这个选项,则加载地址等于运行地址。

以上是关于链接脚本的主要内容,如果未能解决你的问题,请参考以下文章

第三次作业3

uva1590

第三周作业3功能测试

ucore os 初始化

年终大放送-食品行业数据库查询汇总

链接脚本解析