静态链接一般分为两步,第一步是空间和地址的分配,第二步是符号解析和重定位。
本篇以arm-himix200-linux为工具链,add_m.c和main.c代码作为例子介绍相关内容。
add_m.c的代码
#include<stdio.h>
int sun=9;
int add(int a,int b)
{
sun+=1;
printf("sun = %d\n",sun);
return a+b;
}
int sub(int a,int b)
{
add(a,b);
return a-b;
}
int sub1(int a,int b)
{
sub(a,b);
return a-b;
}
main.c的代码
#include<stdio.h>
int mun = 10;
extern sun;
int main(int argc , char**argv)
{
printf("chen_test\n");
int ad = 40,su =20;
printf("add = %d\n",add(ad,su));
printf("mun = %d\n",mun);
printf("sun = %d\n",sun);
return 0;
}
编译、链接
arm-himix200-linux-gcc -c -o add_m.o add_m.c 编译
arm-himix200-linux-gcc -c -o main.o main.c 编译
arm-himix200-linux-gcc -o main main.o add_m.o 链接
1、空间和地址的分配
静态链接的过程,其实就是把几个输入目标文件加工合并成一个输出文件,链接器会计算所有输入目标文件的各个段(数据段、代码段等)的长度、位置及其属性,然后把相似段合并,合并过程中,各个目标文件的各个段的内容的位置是相对不变的,只是把每个相似段拼凑在一块,这样可以保持每一块段里的内容是可以相对寻址的,知道段的起始位置,就可以通过相对寻址,即偏移地址来确认符号的地址。链接器合并各个相似段后,计算合并段总的长度和位置。再根据符号在对应段起始位置的相对偏移地址,即可确定符号的地址。同时链接器会收集输入目标文件中的符号表中的所有的符号定义和符号引用,并做成一个全局符号表。
arm-himix200-linux-objdump -h main
objdump -h可以查看目标文件的各个段的大小、分配的虚拟地址、文件偏移等。
root@chen:/home/chenjg/share/test/static# arm-himix200-linux-objdump -h main
main: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 00000013 00010154 00010154 00000154 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 00010168 00010168 00000168 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .hash 00000024 00010188 00010188 00000188 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .dynsym 00000040 000101ac 000101ac 000001ac 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynstr 0000003c 000101ec 000101ec 000001ec 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .gnu.version 00000008 00010228 00010228 00000228 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version_r 00000020 00010230 00010230 00000230 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .rel.dyn 00000008 00010250 00010250 00000250 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rel.plt 00000018 00010258 00010258 00000258 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .init 0000000c 00010270 00010270 00000270 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
10 .plt 00000038 0001027c 0001027c 0000027c 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .text 0000027c 000102b4 000102b4 000002b4 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
...
2、符号解析和重定位
(1)重定位表
对于静态链接来说,重定位表存在于可重定位文件里,重定位表主要包含两个部分,一个是重定位入口的偏移,表示重定位要修正的位置,该位置是相对于可重定位文件的段起始的偏移。另一个是重定位入口的类型和符号,每种处理器都有自己的一套重定位入口的类型,各种处理器的指令格式不一样,重定位所修正的指令格式也不一样。 重定位表的作用就是用来记录对外引用的符号在目标文件的哪些位置,用什么指令修正方式去修正引用符号的地址。
下面介绍变量和函数是如何重定位的。
变量
在add_m.c定义一个全局变量sun,在main.c文件里引用它。通过查看main.o的重重定位表和反汇编代码,如下,发现,由于main.c文件引用的sun变量是在add_m.c文件里定义的,所以编译的时候,sun变量地址是无法确认的,只能当做0来处理。重定位表中sun的偏移地址为00000090,查看main.o的反汇编代码,实际对应的是
90: 00000000 .word 0x00000000
这里说明一下,在汇编里,点一个机器长的全局变量,是通过.word指令来表示的。.word指令后面是跟着变量的地址。一开始引用符号sun的地址是0的。包括·在本文件内定义的mun变量的地址一开始也是0。
main.o的重定位表:
root@chen:/home/chenjg/share/test/static# arm-himix200-linux-objdump -r main.o
main.o: file format elf32-littlearm
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000018 R_ARM_CALL puts
00000034 R_ARM_CALL add
00000044 R_ARM_CALL printf
00000058 R_ARM_CALL printf
0000006c R_ARM_CALL printf
00000080 R_ARM_ABS32 .rodata
00000084 R_ARM_ABS32 .rodata
00000088 R_ARM_ABS32 mun
0000008c R_ARM_ABS32 .rodata
00000090 R_ARM_ABS32 sun
00000094 R_ARM_ABS32 .rodata
main.o的反汇编代码:
root@chen:/home/chenjg/share/test/static# arm-himix200-linux-objdump -S main.o
main.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <main>:
0: e92d4800 push {fp, lr}
4: e28db004 add fp, sp, #4
8: e24dd010 sub sp, sp, #16
c: e50b0010 str r0, [fp, #-16]
10: e50b1014 str r1, [fp, #-20] ; 0xffffffec
14: e59f0064 ldr r0, [pc, #100] ; 80 <main+0x80>
18: ebfffffe bl 0 <puts>
1c: e3a03028 mov r3, #40 ; 0x28
20: e50b3008 str r3, [fp, #-8]
24: e3a03014 mov r3, #20
28: e50b300c str r3, [fp, #-12]
2c: e51b100c ldr r1, [fp, #-12]
30: e51b0008 ldr r0, [fp, #-8]
34: ebfffffe bl 0 <add>
38: e1a03000 mov r3, r0
3c: e1a01003 mov r1, r3
40: e59f003c ldr r0, [pc, #60] ; 84 <main+0x84>
44: ebfffffe bl 0 <printf>
48: e59f3038 ldr r3, [pc, #56] ; 88 <main+0x88>
4c: e5933000 ldr r3, [r3]
50: e1a01003 mov r1, r3
54: e59f0030 ldr r0, [pc, #48] ; 8c <main+0x8c>
58: ebfffffe bl 0 <printf>
5c: e59f302c ldr r3, [pc, #44] ; 90 <main+0x90>
60: e5933000 ldr r3, [r3]
64: e1a01003 mov r1, r3
68: e59f0024 ldr r0, [pc, #36] ; 94 <main+0x94>
6c: ebfffffe bl 0 <printf>
70: e3a03000 mov r3, #0
74: e1a00003 mov r0, r3
78: e24bd004 sub sp, fp, #4
7c: e8bd8800 pop {fp, pc}
80: 00000000 .word 0x00000000
84: 0000000c .word 0x0000000c
88: 00000000 .word 0x00000000
8c: 00000018 .word 0x00000018
90: 00000000 .word 0x00000000
94: 00000024 .word 0x00000024
可执行文件main的反汇编代码:
0001043c <main>:
1043c: e92d4800 push {fp, lr}
10440: e28db004 add fp, sp, #4
10444: e24dd010 sub sp, sp, #16
10448: e50b0010 str r0, [fp, #-16]
1044c: e50b1014 str r1, [fp, #-20] ; 0xffffffec
10450: e59f0064 ldr r0, [pc, #100] ; 104bc <main+0x80>
10454: ebffffa2 bl 102e4 <puts@plt>
10458: e3a03028 mov r3, #40 ; 0x28
1045c: e50b3008 str r3, [fp, #-8]
10460: e3a03014 mov r3, #20
10464: e50b300c str r3, [fp, #-12]
10468: e51b100c ldr r1, [fp, #-12]
1046c: e51b0008 ldr r0, [fp, #-8]
10470: eb000017 bl 104d4 <add>
10474: e1a03000 mov r3, r0
10478: e1a01003 mov r1, r3
1047c: e59f003c ldr r0, [pc, #60] ; 104c0 <main+0x84>
10480: ebffff94 bl 102d8 <printf@plt>
10484: e59f3038 ldr r3, [pc, #56] ; 104c4 <main+0x88>
10488: e5933000 ldr r3, [r3]
1048c: e1a01003 mov r1, r3
10490: e59f0030 ldr r0, [pc, #48] ; 104c8 <main+0x8c>
10494: ebffff8f bl 102d8 <printf@plt>
10498: e59f302c ldr r3, [pc, #44] ; 104cc <main+0x90>
1049c: e5933000 ldr r3, [r3]
104a0: e1a01003 mov r1, r3
104a4: e59f0024 ldr r0, [pc, #36] ; 104d0 <main+0x94>
104a8: ebffff8a bl 102d8 <printf@plt>
104ac: e3a03000 mov r3, #0
104b0: e1a00003 mov r0, r3
104b4: e24bd004 sub sp, fp, #4
104b8: e8bd8800 pop {fp, pc}
104bc: 00010610 .word 0x00010610
104c0: 0001061c .word 0x0001061c
104c4: 0002102c .word 0x0002102c
104c8: 00010628 .word 0x00010628
104cc: 00021030 .word 0x00021030
104d0: 00010634 .word 0x00010634
再看可执行文件main的反汇编代码,经过链接,符号解析、重定位后,变量符号sun和mun的地址也被修正了。例如sun变量,一开始符号sun是相对于main函数地址的偏移00000090,所以链接后,main函数的地址确认后,在main函数地址的基础上,加上偏移00000090,即main+0x94 得到104d0的地址,即
104d0: 00010634 .word 0x00010634
从而得到变量sun的地址0x00010634。
函数
在main.c文件里调用add函数,是在add_m.c文件里定义的符号,所以会出现在重定位表里,从重定位表里看,相对于main函数的偏移地址为00000034,但在链接之前,add函数的地址是不确定的,即为0.
34: ebfffffe bl 0 <add>
链接后,其地址被修正为104d4 ,即链接后,add函数分配的真实虚拟地址。
总结:
重定位的过程中,每个重定位的人口都是对一个符号的引用,当连接器对某个符号的引用进行重定位时,就会去查找有所有输入目标文件的符号组成的全局符号表,找到后就会进行重定位,即地址的修正。