|
如果用过arm交叉编译器,一定看过
arm-ld, arm-as, arm-gcc, arm-nm等一系列工具,它们的用途是干什么的呢?
(1)arm-as 用于将 xxx.s 汇编文件编译为 xxx.o 文件
- <font size="4">eg : arm-as -o add.o add.s</font>
复制代码
(2)在写多文件工程的时候,每个汇编文件被 arm-as工具单独编译为.o文件,c文件用arm-gcc编译
而arm-ld则将所有的xxx.o 文件链接为二进制执行文件,因此称为链接器
有三种可执行文件:一种是.elf,一种是.bin,.hex是一种特殊的可执行文件
链接器有两个作用:
1.符号解析(Symbol Resolution)
2.重定向(Relocating)
下面以汇编文件为例来讲解:
汇编语句的语法如下:
- <font size="4">label: instruction @ comment</font>
复制代码
label 为标签, @ 后面是注释语句
instruction是执行的指令
一个简单的加法的例子如下:
- <font size="4"> .text
- start: @ Label, not really required
- mov r0, #5 @ Load register r0 with the value 5
- mov r1, #4 @ Load register r1 with the value 4
- add r2, r1, r0 @ Add r0 and r1 and store in r2
- stop: b stop @ Infinite loop to stop execution</font>
复制代码
1.符号解析
在单文件工程中,在生成目标文件时,所有指向labels的引用都被它们相应的地址所代替,这是由汇编器(assembler)实现。
但是在多文件工程里面,如果引用的标签是在另一个文件,那么汇编器无法解析,它会将这个标签标记为"unresolved"。当所有的xxx.o传递给链接器的时候,链接器通过具有这个标签的文件来决定这些引用的值。
一个求和的例子如下:
main.s 文件
- <font size="4"> .text
- b start @ Skip over the data
- arr: .byte 10, 20, 25 @ Read-only array of bytes
- eoa: @ Address of end of array + 1
- .align
- start:
- ldr r0, =arr @ r0 = &arr
- ldr r1, =eoa @ r1 = &eoa
- bl sum @ Invoke the sum subroutine
- stop: b stop</font>
复制代码 这里arr 是一个数组。指令首先调到 start, r0寄存器装载arr数组的首地址, r1装载 eoa的地址(其实就是数组的末地址+1),然后跳转到 sum,这里使用bl ,是可返回跳转,lr寄存器会记录当前的位置
sum-sub.s 文件
- <font size="4"> @ Args
- @ r0: Start address of array
- @ r1: End address of array
- @
- @ Result
- @ r3: Sum of Array
- .global sum
- sum: mov r3, #0 @ r3 = 0
- loop: ldrb r2, [r0], #1 @ r2 = *r0++ ; Get array element
- add r3, r2, r3 @ r3 += r2 ; Calculate sum
- cmp r0, r1 @ if (r0 != r1) ; Check if hit end-of-array
- bne loop @ goto loop ; Loop
- mov pc, lr @ pc = lr ; Return when done</font>
复制代码 这里的 sum 里面有一个 Loop循环,结束的条件就是r0 = r1, loop循环里面就是让 r2 = *r0++, r3 += r2
结束之后,调回到lr寄存器记录的位置。
这里.global 的作用声明label对其他文件可见.
在C语言里面,所有变量(variables)在函数外面声明对其他文件是可见的,除非显式声明为static
而在汇编语言里面,所有的标签(label)都是只对本文件可见,除非使用.global显式声明。
使用nm工具可以查看xxx.o文件的所有符号标签
- <font size="4">$ arm-nm main.o
- 00000004 t arr
- 00000007 t eoa
- 00000008 t start
- 00000018 t stop
- U sum
- $ arm-nm sum-sub.o
- 00000004 t loop
- 00000000 T sum</font>
复制代码 这里 "t" 表示 这个符号是被定义的
"U" 表示这个符号没有被定义(undefined)
"T" 表示这个符号是全局的(.global)
2. 重定向
重定向是改变已经分配给label的地址的过程,这包含链接所有的标签引用来反映新的分配的地址
重定向执行的原因有两个: 段合并(section merging)和段放置(section placement)
要想了解重定向的过程吗,首先得了解 段(section)的概念
代码和数据有不同的运行要求。比如,代码是放置在只读Memory,而数据要求可读可写的Memory
如果代码和数据没有交错分布在memory里面,这就很方便实现。
基于这一目的,我们将program 分成一个一个的段
大部分的program至少有两个段,一个 .text 段用于代码,一个.data段用于数据
汇编器通过 xxx.s文件的 .text 和 .data 来识别要放到哪一个段里面
想象一个段是一个篮子。当汇编器一条一条指令编译的时候,它根据指示将 code 和data 放到各自的篮子里面。
如图所示
(1)段合并:
在执行多文件工程时,具有相同名字的段(比如: .text)可能会出现。链接器负责将输入文件(xxx.o)的段合并。默认情况下,每个文件相同名字的段连续放置,并且标签引用指向新的地址。
- <font size="4">$ arm-nm main.o
- 00000004 t arr
- 00000007 t eoa
- 00000008 t start
- 00000018 t stop
- U sum
- $ arm-nm sum-sub.o
- 00000004 t loop ❶
- 00000000 T sum
- $ arm-ld -Ttext=0x0 -o sum.elf main.o sum-sub.o
- $ arm-nm sum.elf
- ...
- 00000004 t arr
- 00000007 t eoa
- 00000008 t start
- 00000018 t stop
- 00000028 t loop ❷
- 00000024 T sum</font>
复制代码 还是前面的例子,在经过链接器之后 , sum-sub.o 里面 .text段的标签 loop 被放在 main.o .text段的后面地址发生改变。而且main.o里面没有被解析的sum标签被解析为 新的地址
(2)段放置
在段被编译之后,每个段都被假定从地址0开始。每个标签都被分配相对于段起始位置的地址。当最终的可执行文件创建的时候,段的起始地址变为X,那么段里面所有定义的标签地址都增加X。
段放置的效果可以看每个 .o文件和最终的可执行文件的符号表(symbol table)
下面的例子里面我们将.text段放置在 0x100位置
- <font size="4">$ arm-none-eabi-as -o sum.o sum.s
- $ arm-none-eabi-nm -n sum.o
- 00000000 t entry ❶
- 00000004 t arr
- 00000007 t eoa
- 00000008 t start
- 00000014 t loop
- 00000024 t stop
- $ arm-none-eabi-ld -Ttext=0x100 -o sum.elf sum.o ❷
- $ arm-none-eabi-nm -n sum.elf
- 00000100 t entry ❸
- 00000104 t arr
- 00000107 t eoa
- 00000108 t start
- 00000114 t loop
- 00000124 t stop
- ...</font>
复制代码 注1 : xxx.o 文件的段的起始位置是从0开始的
2: 在经过链接器之后, .text段被放置在0x100处
3 : 所有的标签都被重新解析和定向,放置在新的位置
下面是整个过程
|
赞赏
-
2
查看全部赞赏
-
|