静态链接原理以及过程

通常程序的编译中,或多或少会调用其它库中的函数接口,本篇blog就是讲静态库的调用流程。通常我们知道编译一个可执行程序会有这四个过程:预处理、编译、汇编以及链接。前面三步就是产生目标文件.o的过程,链接就是把各个.o文件粘在一起,构成一个可执行文件。而链接主要分为两步:第一是空间和地址的分配,第二是符号解析与重定位

1.空间和地址的分配

每个.o文件都有自己的段属性,比如.text、.data等等这些,链接的第一步就是将这些段属性合并在一起。下面将以a.c和b.c为示范说明链接中空间和地址的分配。

/*a.c*/
extern int shared;

int main(void)
{
	int a = 100;
	swap(&a, &shared);
	
	return 0;
}
/*b.c*/
int shared = 1;

void swap(int* a, int* b)
{
	*a ^= *b ^= *a ^= *b;
}

1.1 相似段的合并

使用objdump -h 来查看每个.o文件的段属性,并且使用链接器ld将a.o和b.o合并成可执行文件ab:ld a.o b.o -e main -o ab,采用相似段合并的方法,这里主要看.text和.data的段,.text一般是程序代码,.data是数据存放。从下面可以看出:.text ab(size 0x66)=a.o(size 0x2c) + b.o(size 0x3a)           .data ab(size 0x4)=a.o(size 0x0) + b.o(size 0x4)


a.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000002c  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000060  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000060  2**0
                  ALLOC
  3 .comment      0000002c  00000000  00000000  00000060  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  00000000  00000000  0000008c  2**0
                  CONTENTS, READONLY
  5 .eh_frame     00000038  00000000  00000000  0000008c  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

b.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000003a  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000004  00000000  00000000  00000070  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000074  2**0
                  ALLOC
  3 .comment      0000002c  00000000  00000000  00000074  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  00000000  00000000  000000a0  2**0
                  CONTENTS, READONLY
  5 .eh_frame     0000003c  00000000  00000000  000000a0  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

ab:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000066  08048094  08048094  00000094  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .eh_frame     0000005c  080480fc  080480fc  000000fc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .data         00000004  08049158  08049158  00000158  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .comment      0000002b  00000000  00000000  0000015c  2**0
                  CONTENTS, READONLY

1.2 符号地址的确定

在上面的段属性合并之后,那么符号地址也将差不多确定好了,也就是上面的VMA(Virtual Memory Address)。这里为啥可以将main函数和swap的地址区分开来了?这是因为编译中以main为程序的执行入口,所以main的地址比swap要先,又根据前面目标文件中的函数的大小,可以将main和swap的地址确定下来。

符号类型虚拟地址
main函数0x08048094
swap函数0x080480c0
shared变量0x08048158

2.符号解析与重定位

2.1重定位

前面知道a.c中引用了b.c中的函数,那么肯定在链接过程中需要将a.o中的swap和shared重定位的。先看a.o文件的反汇编objdump -d a.o ,可以看出在11处,使用shared的变量,在20处引用外部函数swap。本来是想shared的值赋值到寄存器%esp中,但是不知道shared的地址,所以使用0x00000000来代替。同理,在20处调用swap,可以看出e8 fc ff ff ff,其中e8是操作码,是一个近址相对位移调用指令,也就是下一条指令的地址0x25+0xfffffffc(-4)=0x21。

在ab中可以看出,这两处的地址都被修正了,重定位到相应的地址上去了。

a.o:     file format elf32-i386


Disassembly of section .text:

00000000 <main>:
   0:	55                   	push   %ebp
   1:	89 e5                	mov    %esp,%ebp
   3:	83 e4 f0             	and    $0xfffffff0,%esp
   6:	83 ec 20             	sub    $0x20,%esp
   9:	c7 44 24 1c 64 00 00 	movl   $0x64,0x1c(%esp)
  10:	00 
  11:	c7 44 24 04 00 00 00 	movl   $0x0,0x4(%esp)
  18:	00 
  19:	8d 44 24 1c          	lea    0x1c(%esp),%eax
  1d:	89 04 24             	mov    %eax,(%esp)
  20:	e8 fc ff ff ff       	call   21 <main+0x21>
  25:	b8 00 00 00 00       	mov    $0x0,%eax
  2a:	c9                   
ab:     file format elf32-i386


Disassembly of section .text:

08048094 <main>:
 8048094:	55                   	push   %ebp
 8048095:	89 e5                	mov    %esp,%ebp
 8048097:	83 e4 f0             	and    $0xfffffff0,%esp
 804809a:	83 ec 20             	sub    $0x20,%esp
 804809d:	c7 44 24 1c 64 00 00 	movl   $0x64,0x1c(%esp)
 80480a4:	00 
 80480a5:	c7 44 24 04 58 91 04 	movl   $0x8049158,0x4(%esp)
 80480ac:	08 
 80480ad:	8d 44 24 1c          	lea    0x1c(%esp),%eax
 80480b1:	89 04 24             	mov    %eax,(%esp)
 80480b4:	e8 07 00 00 00       	call   80480c0 <swap>
 80480b9:	b8 00 00 00 00       	mov    $0x0,%eax
 80480be:	c9                   	leave  
 80480bf:	c3                   	ret   

2.2 重定位表

那么在合并ab时候,怎么会知道那个文件中需要重定位了?这个时候,需要重定位的文件中会有重定位表,使用如下的命令来查看重定位表:objdump -r a.o。其中R_386_32是表示绝对地址修正,R_386_PC32是表示相对地址修正。

a.o:     file format elf32-i386

RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE 
00000015 R_386_32          shared
00000021 R_386_PC32        swap

其实我们也可以通过符号表来查看哪些函数或者变量在本文件中没有定义:readelf -s a.o,比如奥看下表就知道其中的shared和swap没有在a.o中定义,需要重定。位到其他的文件中

Symbol table '.symtab' contains 11 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS a.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000000     0 SECTION LOCAL  DEFAULT    6 
     6: 00000000     0 SECTION LOCAL  DEFAULT    7 
     7: 00000000     0 SECTION LOCAL  DEFAULT    5 
     8: 00000000    44 FUNC    GLOBAL DEFAULT    1 main
     9: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND shared
    10: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND swap

3.链接脚本

一般程序的运行时在操作系统中运行,那么可以使用编译器中默认的链接脚本,一般放在/usr/lib/ldscripts/下。但是想kernel、BootLoader等这些程序怎么办,因为他们本身就是操作系统,所在这些程序的编译需要指定链接脚本,当然,自己编译一些程序的时候,也可以不使用编译器提供的链接脚本,而使用自己的,使用如下的命令:ld  -T  link.script

比如:下面的一个简单的链接脚本,其中ENTRY是表示程序的入口是main函数,后面的SECTIONS命令一般是链接脚本的主体,这个命令指定了各种输入段到输出段的变换,SECTIONS后面紧跟一对大括号,里面包括了SECTIONS的变换规则,其中有三条语句,每条语句一行。第一条是赋值语句,确定当前虚拟地址;第二条是转换规则,将所有输入文件中的名字为.text、.data、.rodata都合并到输出文件的tinytext中;第三条规则是将输入文件中.comment的段都丢弃,不保存到输出文件中。

ENTRY(main)

SECTIONS
{
    . = 0x0804800 + SIZEOF_HEADERS;

    tinytext : { *(.text) *(.data) *(.rodate) }
    
    /DISCARD/ : { *(.comment) }
}

 

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值