静态链接之深度解剖

静态链接一般分为两步,第一步是空间和地址的分配,第二步是符号解析和重定位。
本篇以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函数分配的真实虚拟地址。

总结:
重定位的过程中,每个重定位的人口都是对一个符号的引用,当连接器对某个符号的引用进行重定位时,就会去查找有所有输入目标文件的符号组成的全局符号表,找到后就会进行重定位,即地址的修正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值