• 链接脚本


    什么是链接

    链接就是将多个目标文件系统库的目标文件库文件链接起来,最终生成可以在特定平台运行的可执行文件
    用到的工具为 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    链接命令将会展开为

    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
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    链接命令展开为

    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
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    示例链接脚本解释

    第 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    链接脚本简介

    链接脚本的基本命令是 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)。如果不使用这个选项,则加载地址等于运行地址。

  • 相关阅读:
    微信小程序 ——入门介绍及简单的小程序编写
    leetcode做题笔记232. 用栈实现队列
    反射机制篇
    SPA项目开发之动态树+数据表格+分页
    DBeaver连接本地MySQL
    MySQL的Redo log 、Undo log、 Binlog
    【性能测试】慢查询日志分析-实战篇
    Nvme Spec 第一章节学习
    SSL证书验签时要带www吗?
    【分享】使用 PXE + Kickstart 无人值守安装 Linux
  • 原文地址:https://blog.csdn.net/lyndon_li/article/details/126800909