链接脚本ld

1 基础内容

1.1 程序编译阶段

一般而言,程序编译经历下图四个阶段,链接是编译的最后一步,无论是在PC上编译代码,还是在PC上使用嵌入式gcc工具交叉编译嵌入式代码,编译过程都是如下几步
在这里插入图片描述

1.2 链接脚本

链接过程是将各式各样的.o文件链接为一个文件的过程。链接脚本描述连接器如何将这些输入文件(.o)文件映射为一个输出文件的,并且定义了输出文件的memory layout。几乎所有的链接脚本都是在做这些事情。

在使用ld的时候,通过-T选项,可以使用自己写的链接脚本完成链接过程,否则会使用默认的链接脚本。

基础的链接脚本概念

接下来需要定义一些基础的概念以及词汇用来描述链接脚本语言。

连接器将输入文件组合为一个单独的输出文件。输入文件和输出文件的数据类型是我们熟知的object file format。每个文件被称为object file。输出文件不仅仅可以称为object file,有时也会叫他executable可执行文件,出于我们描述的目的,我们依旧称他为object file。

每一个object,有很多其他的信息,但是最重要的是section的列表。这也是着重要谈的。大多数时候,我们会把输入的Object文件里面的section称之为 input section。链接输出的单个Object文件里面的section称之为output section。

在Object文件中,每一个section有一个名字还有对应的size信息,几乎所有的section都有一个数据关联块(associated block of data)。

section可以被标记为loadable,意味着当输出文件运行起来的时候,这个section的content信息应当被加载到memory中。

section没有content信息,并且被标记为allocatable,意味着会有一块内存会被创建,但是却没有任何内容放在上面,程序运行起来的时候无需要加载任何信息到memory中。

如果section既不是loadable也不是allocatable,那么意味着它包含了一些debug的信息。

链接脚本的输出文件是一个Object文件,里面包含了多个section,包括loadable的section,也包括allocatable section。每一个section包含两个地址,分别是VMA以及LMA。

VMA是virtual memory address,LMA是load memory address。大多数两类地址是相同的,但是在嵌入式开发中不大相同,LMA是flash地址,而VMA是将flash加载到ram里面运行的ram地址。

例如下述的嵌入式C代码,显然全局变量a在编译成bin的时候是烧写进flash的,但是程序运行起来之后a的数据是需要加载进ram的,因为a是一个变量,不排除程序运行过程中代码会改变他的值。因此flash会存放他的初始值,运行的时候从ROM加载到RAM,他在ROM中的地址是LMA,在RAM中的地址是VMA。

下文会使用objdumo -h指令查看object文件里面的section,以及对应的VMA和LMA。

每个object文件拥有一个symbols列表,里面包含的symbol可以是被定义或者未被定义的。每一个symbol有名字和地址。如果是C/C++程序编译出来的.o文件。所有的函数和全局静态变量都处于defined的状态,在输入文件中引用的每个未定义的函数或全局变量都将成为一个未定义的符号。

使用 objdumo -t指令可以查看对应object的symbols。

1.4 链接脚本格式

链接脚本是text文件,空格忽略。

1.5 简单的链接脚本例子

假设连接器的输入object里面只有代码.text段,数据.data段,未初始化的data段.bss。

假设代码希望放在0x10000,数据希望放在0x8000000。下面代码将会描述这样的链接脚本

SECTIONS
{
  . = 0x10000;
  .text : {*(.text)}
  . = 0x8000000;
  .data : {*(.data)}
  .bss : {*(.bss)}
}

第一行,使用’.'给memory map定位地址。如果不适用 "."来指定开始地址,那么将会从0开始分配地址。

第二行定义了一个output scetion,名字叫’.text’,后面花括号里面的内容是其他.o文件里面作为输入的section名称,输入的section会被存放到output section中。是通配符的含义,可以匹配任意文件名 ,(.text) 意味着所有输入文件中的.text段。

因为location counter 被配置为0x10000,因此连接器会把text的内容存放到0x10000中。

后面的几行与text段同理,.data段和.bss段都是从0x8000000开始的,并且是紧紧挨在一起的。

连接器会保证每个段的对齐方式,以此作为依据来增加loaction counter。

2 实践链接器过程

为了深度体验连接器的工作过程,我们创建add.c文件,data.c 和main.c文件三个文件,并且写自己定义的链接脚本将这个三个.c文件链接为一个输出文件

main.c
----------------
extern int add(int a , int b);
extern int data1;
extern int data2;
int main(void){
        add(data1,data2)
        return 0;
}

data.c
-----------------
int data1=10;
int data2=20;

add.c
-----------------
int add(int a , int b){
    return a+b;
}

使用arm gcc 生成每个.c对应的.o文件

arm-none-eabi-gcc -c main.c
arm-none-eabi-gcc -c data.c
arm-none-eabi-gcc -c add.c

2.1 观察main.o的内容

使用objdump工具观察编译生成的main.o文件

arm-none-eabi-objdump -h main.o

得到

main.o:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000040  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000074  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000074  2**0
                  ALLOC
  3 .comment      00000080  00000000  00000000  00000074  2**0
                  CONTENTS, READONLY
  4 .ARM.attributes 00000030  00000000  00000000  000000f4  2**0
                  CONTENTS, READONLY

为了更清晰的看到每个段的含义,使用

arm-none-eabi-objdump -s -d main.o

查看反汇编

Contents of section .text:
 0000 00482de9 04b08de2 28309fe5 002093e5  .H-.....(0... ..
 0010 24309fe5 003093e5 0310a0e1 0200a0e1  $0...0..........
 0020 feffffeb 0030a0e3 0300a0e1 04d04be2  .....0........K.
 0030 0048bde8 1eff2fe1 00000000 00000000  .H..../.........
Contents of section .comment:
 0000 00474343 3a202847 4e552054 6f6f6c73  .GCC: (GNU Tools
 0010 20666f72 2041726d 20456d62 65646465   for Arm Embedde
 0020 64205072 6f636573 736f7273 20372d32  d Processors 7-2
 0030 3031382d 71322d75 70646174 65292037  018-q2-update) 7
 0040 2e332e31 20323031 38303632 32202872  .3.1 20180622 (r
 0050 656c6561 73652920 5b41524d 2f656d62  elease) [ARM/emb
 0060 65646465 642d372d 6272616e 63682072  edded-7-branch r
 0070 65766973 696f6e20 32363139 30375d00  evision 261907].
Contents of section .ARM.attributes:
 0000 412f0000 00616561 62690001 25000000  A/...aeabi..%...
 0010 0541524d 3754444d 49000602 08010901  .ARM7TDMI.......
 0020 12041401 15011703 18011901 1a011e06  ................

Disassembly of section .text:

00000000 <main>:
   0:   e92d4800        push    {fp, lr}
   4:   e28db004        add     fp, sp, #4
   8:   e59f3028        ldr     r3, [pc, #40]   ; 38 <main+0x38>
   c:   e5932000        ldr     r2, [r3]
  10:   e59f3024        ldr     r3, [pc, #36]   ; 3c <main+0x3c>
  14:   e5933000        ldr     r3, [r3]
  18:   e1a01003        mov     r1, r3
  1c:   e1a00002        mov     r0, r2
  20:   ebfffffe        bl      0 <add>
  24:   e3a03000        mov     r3, #0
  28:   e1a00003        mov     r0, r3
  2c:   e24bd004        sub     sp, fp, #4
  30:   e8bd4800        pop     {fp, lr}
  34:   e12fff1e        bx      lr
        ...


看到有一个需要链接的位置

 ebfffffe        bl      0 <add>

有一个标签,bl目前跳转的地方是0地址,后期连接器应该是需要把这个值改回来的。

总体来看main.o需要链接两个全局变量和一个函数位置。函数位置看到了 后期应该会替换,两个全局变量尚且没看到,是38<main+0x38> 这个字符串吗很有可能。

因为看到把[pc, #40]的数据和[pc, #36]的数据最后放到了r0 r1寄存器中,然后启用的调用add函数,猜测通过r0 r1传递变量。具体的看后面链接成功之后的输出.o的内容。

从二进制可以显然的看出,comment ARM.attributes存放了一些工具链相关的信息,并不需要关心。

观察data.o 的内容

使用objdump工具观察编译生成的data.o文件

data.o:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000000  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000008  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  0000003c  2**0
                  ALLOC
  3 .comment      00000080  00000000  00000000  0000003c  2**0
                  CONTENTS, READONLY
  4 .ARM.attributes 00000030  00000000  00000000  000000bc  2**0
                  CONTENTS, READONLY

显然data.c里面没有包含任何有用的代码信息,只是使用int定义了两个全局变量,int的长度是4个字节,两个int于是占用了8个字节和.data 段的长度信息一致。

arm-none-eabi-objdump -s -d data.o

查的反汇编

data.o:     file format elf32-littlearm

Contents of section .data:
 0000 0a000000 14000000                    ........        
Contents of section .comment:
 0000 00474343 3a202847 4e552054 6f6f6c73  .GCC: (GNU Tools
 0010 20666f72 2041726d 20456d62 65646465   for Arm Embedde
 0020 64205072 6f636573 736f7273 20372d32  d Processors 7-2
 0030 3031382d 71322d75 70646174 65292037  018-q2-update) 7
 0040 2e332e31 20323031 38303632 32202872  .3.1 20180622 (r
 0050 656c6561 73652920 5b41524d 2f656d62  elease) [ARM/emb
 0060 65646465 642d372d 6272616e 63682072  edded-7-branch r
 0070 65766973 696f6e20 32363139 30375d00  evision 261907].
Contents of section .ARM.attributes:
 0000 412f0000 00616561 62690001 25000000  A/...aeabi..%...
 0010 0541524d 3754444d 49000602 08010901  .ARM7TDMI.......
 0020 12041401 15011703 18011901 1a011e06  ................

显然的看出,data.o里面只有.data段的数据,并且长度为8个字节,小端模式,的确包含了0x0000000a 以及 0x00000014两个全局变量的数据。

观察add.o 的内容

使用objdump工具观察编译生成的add.o文件

arm-none-eabi-objdump -h add.o

得到

add.o:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000030  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000064  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000064  2**0
                  ALLOC
  3 .comment      00000080  00000000  00000000  00000064  2**0
                  CONTENTS, READONLY
  4 .ARM.attributes 00000030  00000000  00000000  000000e4  2**0
                  CONTENTS, READONLY

显然add.c里面没有包含任何有用的数据信息,只有代码信息,对应.text段的长度不为零,其他为零。

arm-none-eabi-objdump -s -d add.o

得到

add.o:     file format elf32-littlearm

Contents of section .text:
 0000 04b02de5 00b08de2 0cd04de2 08000be5  ..-.......M.....
 0010 0c100be5 08201be5 0c301be5 033082e0  ..... ...0...0..
 0020 0300a0e1 00d08be2 04b09de4 1eff2fe1  ............../.
Contents of section .comment:
 0000 00474343 3a202847 4e552054 6f6f6c73  .GCC: (GNU Tools
 0010 20666f72 2041726d 20456d62 65646465   for Arm Embedde
 0020 64205072 6f636573 736f7273 20372d32  d Processors 7-2
 0030 3031382d 71322d75 70646174 65292037  018-q2-update) 7
 0040 2e332e31 20323031 38303632 32202872  .3.1 20180622 (r
 0050 656c6561 73652920 5b41524d 2f656d62  elease) [ARM/emb
 0060 65646465 642d372d 6272616e 63682072  edded-7-branch r
 0070 65766973 696f6e20 32363139 30375d00  evision 261907].
Contents of section .ARM.attributes:
 0000 412f0000 00616561 62690001 25000000  A/...aeabi..%...
 0010 0541524d 3754444d 49000602 08010901  .ARM7TDMI.......
 0020 12041401 15011703 18011901 1a011e06  ................

Disassembly of section .text:

00000000 <add>:
   0:   e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
   4:   e28db000        add     fp, sp, #0
   8:   e24dd00c        sub     sp, sp, #12
   c:   e50b0008        str     r0, [fp, #-8]
  10:   e50b100c        str     r1, [fp, #-12]
  14:   e51b2008        ldr     r2, [fp, #-8]
  18:   e51b300c        ldr     r3, [fp, #-12]
  1c:   e0823003        add     r3, r2, r3
  20:   e1a00003        mov     r0, r3
  24:   e28bd000        add     sp, fp, #0
  28:   e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
  2c:   e12fff1e        bx      lr

看到汇编里面

add     r3, r2, r3

把r2和r3的数据相加存放在r3里面,实现了add的功能,然后用Mov把r3的数据放在了r0里面,实现return的功能?这一段汇编是实现了add功能的汇编代码。
思考:现在有三个.o文件,分别是data.o add.o main.o 其中data.o放了两个全局变量的具体取值,add.o存放了使得两个整形数相加的函数实现,main.o则是使用了data.o的数据,add.o的函数。main.o在链接之前并不知道数据的具体数值,也不知道调用函数的函数位置,因此事先会把不知道的信息空出来,等到链接的时候再合并。因此接下来要观察链接的结果。

为三个.o 文件编写链接脚本

假设,我们目标的memorymap ,数据段存放在0x80000位置,代码段存放再0x10000的位置。

于是写下如下代码
my.ld

SECTIONS { 
  . = 0x10000; 
  .text : {*(.text)}   
  . = 0x80000;   
  .data : {*(.data)}   
  .bss : {*(.bss)} 
}

链接

使用链接器链接

arm-none-eabi-ld add.o data.o main.o -T my.ld -o out

得到输出文件out
使用objdump观察out文件

/e/7-2018-q2/bin/arm-none-eabi-objdump.exe -h out

得到

out:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000070  00010000  00010000  00010000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000008  00080000  00080000  00020000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .comment      0000007f  00000000  00000000  00020008  2**0
                  CONTENTS, READONLY
  3 .ARM.attributes 00000030  00000000  00000000  00020087  2**0
                  CONTENTS, READONLY

看到链接后的out文件的text和data size是多个.o文件的和。

反汇编

out:     file format elf32-littlearm

Contents of section .text:
 10000 04b02de5 00b08de2 0cd04de2 08000be5  ..-.......M.....
 10010 0c100be5 08201be5 0c301be5 033082e0  ..... ...0...0..
 10020 0300a0e1 00d08be2 04b09de4 1eff2fe1  ............../.
 10030 00482de9 04b08de2 28309fe5 002093e5  .H-.....(0... ..
 10040 24309fe5 003093e5 0310a0e1 0200a0e1  $0...0..........
 10050 eaffffeb 0030a0e3 0300a0e1 04d04be2  .....0........K.
 10060 0048bde8 1eff2fe1 00000800 04000800  .H..../.........
Contents of section .data:
 80000 0a000000 14000000                    ........
Contents of section .comment:
 0000 4743433a 2028474e 5520546f 6f6c7320  GCC: (GNU Tools
 0010 666f7220 41726d20 456d6265 64646564  for Arm Embedded
 0020 2050726f 63657373 6f727320 372d3230   Processors 7-20
 0030 31382d71 322d7570 64617465 2920372e  18-q2-update) 7.
 0040 332e3120 32303138 30363232 20287265  3.1 20180622 (re
 0050 6c656173 6529205b 41524d2f 656d6265  lease) [ARM/embe
 0060 64646564 2d372d62 72616e63 68207265  dded-7-branch re
 0070 76697369 6f6e2032 36313930 375d00    vision 261907].
Contents of section .ARM.attributes:
 0000 412f0000 00616561 62690001 25000000  A/...aeabi..%...
 0010 0541524d 3754444d 49000602 08010901  .ARM7TDMI.......
 0020 12041401 15011703 18011901 1a011e06  ................

Disassembly of section .text:

00010000 <add>:
   10000:       e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
   10004:       e28db000        add     fp, sp, #0
   10008:       e24dd00c        sub     sp, sp, #12
   1000c:       e50b0008        str     r0, [fp, #-8]
   10010:       e50b100c        str     r1, [fp, #-12]
   10014:       e51b2008        ldr     r2, [fp, #-8]
   10018:       e51b300c        ldr     r3, [fp, #-12]
   1001c:       e0823003        add     r3, r2, r3
   10020:       e1a00003        mov     r0, r3
   10024:       e28bd000        add     sp, fp, #0
   10028:       e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
   1002c:       e12fff1e        bx      lr

00010030 <main>:
   10030:       e92d4800        push    {fp, lr}
   10034:       e28db004        add     fp, sp, #4
   10038:       e59f3028        ldr     r3, [pc, #40]   ; 10068 <main+0x38>
   1003c:       e5932000        ldr     r2, [r3]
   10040:       e59f3024        ldr     r3, [pc, #36]   ; 1006c <main+0x3c>
   10044:       e5933000        ldr     r3, [r3]
   10048:       e1a01003        mov     r1, r3
   1004c:       e1a00002        mov     r0, r2
   10050:       ebffffea        bl      10000 <add>
   10054:       e3a03000        mov     r3, #0
   10058:       e1a00003        mov     r0, r3
   1005c:       e24bd004        sub     sp, fp, #4
   10060:       e8bd4800        pop     {fp, lr}
   10064:       e12fff1e        bx      lr
   10068:       00080000        .word   0x00080000
   1006c:       00080004        .word   0x00080004

观察到如下两点

1、原先 2c 行 bl 0位置被连接器替换为了bl 10000。而0x10000刚好是add的代码地址
2、全局变量的地址,由函数首地址+0x38 和 函数首地址+0x3c获取,显然再这个例子函数首地址是0x10030,也就是到0x10068和0x1006c两个地址中获值。看到链接之后再0x10068和0x1006c这两个地址上放了存放数据的地址0x80000和0x80004。这个刚好和我们的链接脚本数据存放的位置符合。我们链接脚本中把数据放在了0x80000的位置

参考链接:ld - 链接脚本学习笔记与实践过程

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值