You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every loadable or allocatable output section has two addresses. The first is the VMA, or virtual memory address. This is the address the section will have when the output file is run. The second is the LMA, or load memory address. This is the address at which the section will be loaded. In most cases the two addresses will be the same. An example of when they might be different is when a data section is loaded into ROM, and then copied into RAM when the program starts up (this technique is often used to initialize global variables in a ROM based system). In this case the ROM address would be the LMA, and the RAM address would be the VMA.
a.o: file format elf64-littleaarch64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
000000000000002c R_AARCH64_ADR_PREL_PG_HI21 .data
0000000000000030 R_AARCH64_ADD_ABS_LO12_NC .data
000000000000003c R_AARCH64_ADR_PREL_PG_HI21 .data
0000000000000040 R_AARCH64_ADD_ABS_LO12_NC .data
0000000000000048 R_AARCH64_ADR_PREL_PG_HI21 .bss
000000000000004c R_AARCH64_ADD_ABS_LO12_NC .bss
0000000000000058 R_AARCH64_ADR_PREL_PG_HI21 .bss+0x0000000000000004
000000000000005c R_AARCH64_ADD_ABS_LO12_NC .bss+0x0000000000000004
0000000000000068 R_AARCH64_ADR_PREL_PG_HI21 b_share
000000000000006c R_AARCH64_ADD_ABS_LO12_NC b_share
000000000000007c R_AARCH64_CALL26 b_func
0000000000000084 R_AARCH64_ADR_PREL_PG_HI21 b_share
0000000000000088 R_AARCH64_ADD_ABS_LO12_NC b_share
RELOCATION RECORDS FOR [.data]:
OFFSET TYPE VALUE
0000000000000008 R_AARCH64_ABS64 .rodata
可以看到OFFSET就是上面compare结果需要修改的位置。
使用readelf -s a.o可以读出哪里的符号没有被定义:
Symbol table '.symtab' contains 20 entries: Num: Value Size Type Bind Vis Ndx Name0: 00000000000000000 NOTYPE LOCAL DEFAULT UND 1: 00000000000000000 FILE LOCAL DEFAULT ABS a.c2: 00000000000000000 SECTION LOCAL DEFAULT 13: 00000000000000000 SECTION LOCAL DEFAULT 34: 00000000000000000 SECTION LOCAL DEFAULT 55: 00000000000000000 NOTYPE LOCAL DEFAULT 3$d6: 00000000000000004 OBJECT LOCAL DEFAULT 3 a_07: 00000000000000000 SECTION LOCAL DEFAULT 68: 00000000000000000 NOTYPE LOCAL DEFAULT 6$d9: 00000000000000088 OBJECT LOCAL DEFAULT 3 a_110: 00000000000000004 OBJECT LOCAL DEFAULT 5 a_311: 00000000000000000 NOTYPE LOCAL DEFAULT 5$d12: 00000000000000044 OBJECT LOCAL DEFAULT 5 a_413: 00000000000000000 NOTYPE LOCAL DEFAULT 1$x14: 000000000000000032 FUNC LOCAL DEFAULT 1 a_func15: 00000000000000000 SECTION LOCAL DEFAULT 816: 00000000000000000 SECTION LOCAL DEFAULT 717: 0000000000000020140 FUNCGLOBALDEFAULT 1 main18: 00000000000000000 NOTYPEGLOBALDEFAULT UND b_share19: 00000000000000000 NOTYPEGLOBALDEFAULT UND b_func
03_ELF文件_静态链接
1. Pre-condition
前面两节是对单独的c文件编译出的elf文件进行解析,静态链接是对多个c文件编译出的二进制文件进行合并的过程。我们对下面的简单的c文件进行静态链接。本文基于aarch64 armv8体系架构编译出的文件对多个目标文件的链接过程做出探究。
1.1 c文件
File1: a.c
File2: b.c
1.2 o及asm文件
编译:
aarch64-linux-gnu-gcc -c a.c b.c
分别编译出a.o
,b.o
我们查看一下a.o和b.o的具体的指令段
aarch64-linux-gnu-objdump -s -d a.o
:aarch64-linux-gnu-objdump -s -d b.o
:1.3 ab.o合体文件
使用
aarch64-linux-gnu-gcc -c a.o b.o -o ab.o
生成ab.o合体文件。2. 链接器对elf的空间与地址分配
2.1 相似段合并
链接器的作用就是对几个目标文件合并成一个输出文件,从上面的例子中可以看出,a.o和b.o合并为ab.o,ab.o和clib里面的函数合并称为ab.elf文件。a.o和b.o文件合并为ab.o采用的是相似段合并的策略,把相似的段合并到一起,在合成ab.o文件之后,.text段连续的将a.o和b.o的.text段合并到了一起,.data段也类似。这里需要注意的是:
2.2 两步链接(Two-pass Linking)
探究一下合并之后段发生变化的过程:
aarch64-linux-gnu-objdump -h a.o
aarch64-linux-gnu-objdump -h b.o
aarch64-linux-gnu-ld a.o b.o -e main -o ab
aarch64-linux-gnu-objdump -h ab
关于VMA和LMA的概念这里需要阐述一下1:
总结下来:
在06_ARMv8_指令集_一些重要的指令 的1.4部分,可以看到嵌入式系统里面LMA和VMA不一样的例子,ARMv8的LDR指令和ADRP指令加载的地址上对VMA和LMA由非常明显的区别。
对于注意2,在《程序员的自我修养》一书中p103提到:
对于这个设定,在网上也能找到相关资料2,但是在x86的体系架构中和arm体系架构中对文件进行编译,得到的.data段却有着不同的结果,是从0x00400000开始分配的。这个似乎和书上讲的不一致。本文先留个悬念,后面到虚拟内存映射再回来解答这个问题。
2.3 符号解析与重定位
a.c文件中引用了b_func,如果单独gcc -c a.c文件,此时b_func并没有值,因此在汇编上可以看到指令 ,此时跳转指令bl 地址为0。
然而在ab文件汇编里面,已经进行了符号重定位,则在ab文件的汇编中可以看到:
看到bl指令后面已经有地址 0x400194,正是b_func的入口程序。前后比较main的变化,左边是未链接的main函数,右边是经过链接的main函数
上面需要调整的地方有个重定位表(Relocation Table)保存这些重定位的信息:
aarch-linux-gnu-objdump -r a.o
可以看到OFFSET就是上面compare结果需要修改的位置。
使用
readelf -s a.o
可以读出哪里的符号没有被定义:第18/19行,可以看到b_share和b_func没有被定义。
2.4 Common block
如果有全局未初始化的变量,该符号会被认为是弱符号,同样的通过readelf -s也可以看到该符号被标记为COM。
对于c.c文件:
c_3被标记为COM,且占4个字节。这里就存在一种冲突的情况。
若在其中一个c文件中,定义了int c_3,未初始化,又在另一个文件里面定义了 double c_3,未初始化。按照common的链接规则,以最大的size为准,因此这里就是使用double c_3的size,为8。如果定义了强符号,谁强,则按照谁的来。
如果弱符号的长度大于强符号,ld链接器会报错:
ld warning: alignment 4 of symbol xxx is smaller than 8
如果在多个文件中没有extern关键字,是的编译器在多个目标文件中产生同一个变量的定义。编译器通常把未初始化的变量当做COMMON类型处理。GCC提供方法让我们把所有的未初始化的全局变量不以COMMON类型处理:
-fno-common
int c_3 __attribute__((nocommon))
如果不以common形式处理,那么在链接的时候遇到相同的符号,就会发生编译报错。
3. 静态库链接
3.1 静态链接库文件生成
d.c
e.c
aarch64-linux-gnu-gcc -c e.c d.c
aarch64-linux-gnu-ar crv ed.a e.o d.o
3.2 静态链接库文件解压
aarch64-linux-gnu-ar -t ed.a
3.3 使用静态链接文件编译
两个方法:
aarch64-linux-gnu-gcc f.c ed.a -I ./ -L ./ -o f.elf
关闭内置优化
aarch64-linux-gnu-gcc f.c ed.a -I ./ -L ./ -o f.elf -fno-builtin
4. TIPS
4.1 内建函数3
4.2 -fno-builtin编译选项4
aarch64-linux-gnu-gcc f.c ed.a -I ./ -L ./ -o f.elf -fno-builtin
Ref
Footnotes
3.1 Basic Linker Script Concepts ↩
usage - virtual address translation method with neat diagram ↩
嵌入式C语言自我修养 (11):有一种函数,叫内建函数 ↩
Gcc编译选项-fno-builtin -fno-builtin-function ↩
The text was updated successfully, but these errors were encountered: