`

第4章 静态链接

 
阅读更多

静态链接

       一、空间与地址分配

       这里的“空间和地址”有两个含义:第一,在输出的可执行文件ab.o中的空间;第二,是在装载后的虚拟地址中的虚拟地址空间。

       现在的链接器的策略基本上都是:将a.o和b.o中相似段合并(如.text和.text段合并),然后再分配空间(ab文件中分配空间基本上是两个.text段加起来的大小;类似地,在虚拟地址空间中也去指定一段空间)。见P102图

实验:观察静态链接过程的“空间和地址分配”:

1. 将a.o和b.o链接成ab.o

两个源代码文件:

/*a.c*/
extern int shared;
int main(){
    int a=100;
    swap(&a, &shared);
}

 和

/*b.c*/
int shared =1 ;
void swap(int *a, int* b){
    *a^=*b^=*a^=*b;
}

$gcc -c a.c b.c ==> 使用-c参数只完成预处理、编译、汇编,(不链接),产生a.o和b.o

$ld a.o b.o -e main -o ab ==> 链接a.o和b.o,指定main()作为程序入口(默认入口是_start),指定输出ab

 

2. 观察“空间和地址分配情况”

$ readelf -S a.o  或  objdump -h a.o

$ readelf -S b.o  或  objdump -h b.o

$ readelf -S ab  或  objdump -h ab

 

       二、符号解析与重定位

        上面虽然解决了地址和空间的分配,但是对于a.o目标文件引用到b.o中“变量shared”和"函数swap"的问题还为解决。每个目标文件都可能定义一些符号,也可能引用到其他目标文件的符号,输入链接器的目标文件的符号表会组成一个“全局符号表”;在“符号解析”的过程中找到了一些在a.o中未定义、引用其他目标文件中定义的符号;而将a.o中这些未定义的符号的占位符(目标文件中某些偏移处),修改为正确的符号变量地址/函数入口地址(注:也是“虚拟地址空间中的地址”),进而产生可执行文件ab的过程,称为“指令修正/重定位”。

 

实验:符号解析和指令修正

 1. 通过查看a.o的重定位段(又称为“重定位表”),可以找到未定义的符号。这些符号必须通过ld链接从其他模块获取,否则报错:一般地,UND这种未定义的符号都是因为该目标文件中有关于它们的重定位项,所以在链接器扫描完所有的输入目标文件之后,所有这些未定义的符号都应该能够在全局符号表中找到,否则链接器就会报“符号未定义”错。

[hadoop@sam1 mydir]$ readelf -s a.o

Symbol table '.symtab' contains 10 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    5 

     7: 00000000    39 FUNC    GLOBAL DEFAULT    1 main

     8: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND shared

     9: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND swap

 

2. 反汇编a.o和ab,可以看到指令修正的痕迹。

 

[hadoop@sam1 mydir]$ objdump -r a.o   ==> 查看“重定位表”(即“重定位段”),可知文件a.o中分别偏移0x15和0x21两个位置的4个字节需要“重定位”(或称“符号修正”)。

a.o:     file format elf32-i386

RELOCATION RECORDS FOR [.text]:

OFFSET   TYPE              VALUE 

00000015 R_386_32          shared          ==> 每个需要重定位之处(目标文件中某个偏移)叫“重定位入口”

00000021 R_386_PC32        swap

 

Sam: 从上面可以看到重定位表的结构:一个需要重定位的section对应有一个重定位段;是一个数组,每一个元素记录的是某个偏移处需要被重定位的变量(函数)名称。

 

[hadoop@sam1 mydir]$ objdump -d a.o

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: c9                   leave  

  26: c3                   ret  

  

[hadoop@sam1 mydir]$ objdump -d ab

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 ==> 这里4个字节本类被修正为“shared的地址” (P109 “绝对近址32位寻址”方式,重定位入口修正)

 80480a5: c7 44 24 04 f8 90 04 movl   $0x80490f8,0x4(%esp)

 80480ac: 08 

 80480ad: 8d 44 24 1c           lea    0x1c(%esp),%eax

 80480b1: 89 04 24             mov    %eax,(%esp)

 80480b4: e8 03 00 00 00       call   80480bc <swap>  ==> 这里4个字节被修正为“swap的入口地址”(P109 “相对近址32位寻址”方式,重定位入口修正)

 80480b9: c9                   leave  

 80480ba: c3                   ret    

 80480bb: 90                   nop

 

080480bc <swap>:

 80480bc: 55                   push   %ebp

 80480bd: 89 e5                 mov    %esp,%ebp

 80480bf: 53                   push   %ebx

 80480c0: 8b 45 08             mov    0x8(%ebp),%eax

 80480c3: 8b 10                 mov    (%eax),%edx

 80480c5: 8b 45 0c             mov    0xc(%ebp),%eax

 80480c8: 8b 08                 mov    (%eax),%ecx

 80480ca: 8b 45 08             mov    0x8(%ebp),%eax

 80480cd: 8b 18                 mov    (%eax),%ebx

 80480cf: 8b 45 0c             mov    0xc(%ebp),%eax

 80480d2: 8b 00                 mov    (%eax),%eax

 80480d4: 31 c3                 xor    %eax,%ebx

 80480d6: 8b 45 08             mov    0x8(%ebp),%eax

 80480d9: 89 18                 mov    %ebx,(%eax)

 80480db: 8b 45 08             mov    0x8(%ebp),%eax

 80480de: 8b 00                 mov    (%eax),%eax

 80480e0: 31 c1                 xor    %eax,%ecx

 80480e2: 8b 45 0c             mov    0xc(%ebp),%eax

 80480e5: 89 08                 mov    %ecx,(%eax)

 80480e7: 8b 45 0c             mov    0xc(%ebp),%eax

 80480ea: 8b 00                 mov    (%eax),%eax

 80480ec: 31 c2                 xor    %eax,%edx

 80480ee: 8b 45 08             mov    0x8(%ebp),%eax

 80480f1: 89 10                 mov    %edx,(%eax)

 80480f3: 5b                   pop    %ebx

 80480f4: 5d                   pop    %ebp

 80480f5: c3                   ret  

 

注:对于32位x86平台的ELF文件的重定位入口修正的指令寻址方式只有以上两种。这两种方式 每个被修正的位置的长度都是4个字节。 

 

       三、COMMON块 (P112)

(1)编译为目标文件时,未初始化的局部静态变量就在.bss段中分配空间了;

(2)编译为目标文件时,未初始化的全局变量是弱符号,占用空间大小未知(因为其他编译单元中所占用的空间可能比本编译单元所占空间要大,最终分配空间取最大的一个),因此暂时标记为common;当链接器读取所有输入目标文件之后,才能最终确定在.bss段中分配的空间——因此,最终还是被要在.bss段中分配空间的。

 

实验:链接之前,未初始化的全局变量尚未放在.bss段中(而是被标记为COMMON)

 

C代码test.c

int printf(const char* format, ...);

int global_init_var=84;                 //.data
int global_uninit_var;                  //COM, 可能在别的文件中被定义

static int static_global_init_var=84;   //.data
static int static_global_uninit_var;    //.bss, 只在本文件中被用到

void func1(int i){
    printf("%d\n",i);
}

int main(void){
    static int static_var=85;           //.data
    static int static_var2;             //.bss, 只在本文件中被用到

    int a=1;                    //stack
    int b;                      //stack

    func1(static_var + static_var2 + a + b);
    return a;
}
$ gcc -c test.c -o test.o
$ readelf -s test.o
Symbol table '.symtab' contains 18 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     5: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 static_global_init_var
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     7: 0000000000000008     4 OBJECT  LOCAL  DEFAULT    3 static_var.0
     8: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 static_var2.1
     9: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    4 static_global_uninit_var
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
    11: 0000000000000000     0 SECTION LOCAL  DEFAULT    8
    12: 0000000000000000     0 SECTION LOCAL  DEFAULT    9
    13: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
    14: 0000000000000000    31 FUNC    GLOBAL DEFAULT    1 func1
    15: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    16: 000000000000001f    45 FUNC    GLOBAL DEFAULT    1 main
    17: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM global_uninit_var

  

       四、C++相关问题 (略,P113~)

  

       五、静态库链接

       一种语言的开发环境往往附带自己的“语言库”(Language Library),这些库是对操作系统API的封装。如C语言标准库中的printf()函数,最终会调用Linux下的write系统调用/或Windows下的WriteConsole系统API. 

       一个静态(链接)库可以简单看作是一组目标文件经过压缩打包形成的一个文件。

       Linux中最常用的C语言静态库libc是/usr/lib/libc.a (属于glibc项目一部分,貌似要安装才行), C语言集成开发环境VC2008附带的一些C运行库则放在$VC2008/lib/下。

 

       六、链接过程控制

       $ld -verbose  ==> 查看ld默认链接脚本

实验:使用自己的ld链接脚本——将三个Section合并到一个名为"tinytext"的Section,并抛弃.comment段

 

C程序TinyHelloWorld.c

char* str = "Hello world\n";

void print()
{	//使用write的系统调用, write的调用号为4,原型为int write(int filedesc, char* buffer, int size)
	//这里的系统调用,先将参数写入寄存器,之后传入write调用
	asm(
	"movl $13, %%edx \n\t"		//str的长度
	"movl %0, %%ecx \n\t"           //缓冲区,这里的%0,指的是“r”后面的字符串地址,也就是传入到这段汇编的参数。
	"movl $0, %%ebx \n\t"		//打印到标准输出, 也就是0
	"movl $4, %%eax	\n\t"		//将系统调用号传入eax。 
	"int $0x80		\n\t"	//执行中断, 调用write函数
	::"r" (str):"edx","ecx", "ebx"	//传入的参数列表, 被重命名的寄存器列表。
	);
}

void exit()
{
	asm(
		"movl $42,%ebx \n\t "			
		"movl $1, %eax \n\t"
		"int $0x80		\n\t"
	);
}
//这里是程序的入口
void nomain()
{
	print();	
	exit();
}

 

ld链接脚本TinyHelloWorld.lds  

ENTRY(nomain)
SECTIONS
{
. = 0x00008000 + SIZEOF_HEADERS;
tinytext : { *(.text) *(.data) *(.rodata) }
/DISCARD/ : { *(.comment) }
}

 

[hadoop@sam1 test]$ gcc -c -fno-builtin TinyHelloWorld.c

-c: 预处理、编译、汇编到.o文件,不链接

-fno-builtin:关闭GCC内置函数优化

[hadoop@sam1 test]$ ld -static -T TinyHelloWorld.lds -o TinyHelloWorld TinyHelloWorld.o

-static:使用静态链接,而非动态链接

[hadoop@sam1 test]$ ls -l

-rwxrwxr-x 1 hadoop hadoop    604 Jan 21 23:38 TinyHelloWorld

-rw-rw-r-- 1 hadoop hadoop    870 Jan 21 23:37 TinyHelloWorld.c

-rw-rw-r-- 1 hadoop hadoop    132 Jan 21 23:37 TinyHelloWorld.lds

-rw-rw-r-- 1 hadoop hadoop   1008 Jan 21 23:38 TinyHelloWorld.o

[hadoop@sam1 test]$ readelf -S TinyHelloWorld
There are 5 section headers, starting at offset 0xec:
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] tinytext          PROGBITS        00008074 000074 000051 00 WAX  0   0  4
  [ 2] .shstrtab         STRTAB          00000000 0000c5 000024 00      0   0  1
  [ 3] .symtab           SYMTAB          00000000 0001b4 000080 10      4   4  4
  [ 4] .strtab           STRTAB          00000000 000234 000028 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

        七、BFD库

       BFD库(Binary File Descriptor library)是一个GNU项目,他的目标是通过一种统一的接口处理不同格式的目标文件,需要安装binutils-dev。

 

 

 

分享到:
评论

相关推荐

    第四章 Spring MVC Rest风格的url、静态资源标签

    NULL 博文链接:https://1151461406.iteye.com/blog/2390768

    Python全栈数据工程师养成攻略

    资源名称:Python全栈数据工程师养成攻略 资源目录:内容提要前言第1章 写在前面第2章 学会Python第3章 获取数据第4章 存储数据第5章 静态可视化第6章 自然语言理解第7章 Web基础第8章 Web进阶第9章 动态可视化第10...

    H3CNE V7.0 培训视频教程【MP4版】【共37集】.rar

    第4章 局域网原理-2&广域网原理 第5章 IP基本原理-1 第6章 网络设备调试&交换机工作原理-1 第7章 命令行操作基础 第8章 交换机工作原理-2 第9章 vlan-1 第9章 vlan-2 第9章 VLAN间路由 第9章 WLAN&企业网...

    Dev-CppMingw32环境介绍(DOC)

    第四章 工程的导入与导出 第五章 在Dev-Cpp下的程序调试 第六章 在提示符下的程序调试 第七章 在你的程序中使用静态连接库 第八章 制作自己的静态链接库 第九章 制作自己的动态链接库 第十章 使用自己的动态链接库 ...

    华为数通(RS)路由交换-TCPIP协议栈 培训视频.rar

    TCPIP路由技术第一卷第四章-3-链路状态协议概述avi TCPIP路由技术第一卷第四章-2距离适量协议&部分 RIP av TCPIP路由技术第一卷第四章-1路由基础avi TCPIP路由技术第一卷第三章静态路由2av TCPIP路由技术第一卷第三...

    Cisco网络工程师和网络安全课程PPT

    教程名称:Cisco网络工程师和网络安全课程PPT课程目录:【】第10章 IPv6【】第11章 广域网【】第1章 计算机网络详解v2【】第2章 TCPIP协议和网络安全【】第3章 IP地址和子网划分【】第4章 配置Cisco网络设备【】第5...

    H3CNE V7.0 培训视频.zip

    第4章 WAN基本原理 第5章 IP基本原理 第6章 TCPUDP原理 第7章 路由器,交换机和操作系统 第8章 命令行特性 第9章 网络设备的文件管理 第10章 网路设备基本调试 第11章 以太网交换机工作原理 第12章 配置VLAN...

    计算机网络原理创新教程配套PPT

    资源名称:计算机网络原理创新教程 配套PPT资源目录:【】第1章计算机网络详解2【】第2章物理层【】第3章GNS3网络模拟器【】第4章数据链路层【】第5章IP地址和子网划分【】第6章静态路由和动态路由【】第7章网络层...

    H3CNE-RS V7.0视频30讲.zip

    第4章 WAN基本原理 第5章 IP基本原理 第6章 TCPUDP原理 第7章 路由器,交换机和操作系统 第8章 命令行特性 第9章 网络设备的文件管理 第10章 网路设备基本调试 第11章 以太网交换机工作原理 第12章 配置...

    深入浅出Visual C++动态链接库(DLL)编程(pdf版+doc版)

    第四章:VC++动态链接库编程之MFC扩展 DLL 6.1概论 6.2 MFC扩展DLL导出MFC派生类 6.3 MFC扩展DLL的加载 6.4 MFC扩展DLL加载MFC扩展DLL 6.5 MFC扩展DLL导出函数和变量 6.6 MFC扩展DLL的应用 第五章:VC++动态链接库...

    H3CNE V7.0 培训视频教程【共44集】.rar

    第三章局域网基本原理和第四章广域网基本原理 第五章IP基本原理上 第五章IP基本原理下及抓包实验 第五章IP基本原理中 第六章TCP和UDP上 第6章TCP和UDP下及第7章 路由器、交换机及其操作系统介绍 第八章命令行...

    链接器和加载器.PDF(链接器和加载器 Beta 2)

    第4章 存储空间分配 4.1 段和地址 4.2 简单的存储布局 4.3 多种段类型 4.4 段与页面的对齐 4.5 公共块和其他特殊段 4.6 链接器控制脚本 4.7 实际中的存储分配 练习 项目 第5章 符号管理 5.1 绑定和名字解析 5.2 ...

    MFC程序设计轻松入门第七章

    项目Calculate和项目ExTestCal,分别用于创建和使用静态链接库; 项目CalSquare和项目ExTestCalSquare,分别用于创建和使用规则动态链接库; 项目CalGCD和项目ExTestCalGCD,分别用于创建和使用扩展动态链接库。 第...

    MFC程序设计轻松入门第八章

    项目Calculate和项目ExTestCal,分别用于创建和使用静态链接库; 项目CalSquare和项目ExTestCalSquare,分别用于创建和使用规则动态链接库; 项目CalGCD和项目ExTestCalGCD,分别用于创建和使用扩展动态链接库。 第...

    计算机操作系统第四章作业详细版1

    1.为什么要配置层次式存储器 2.可采用哪几种方式将程序装入内存 3.何谓静态链接 4.何谓装入时动态链接 5.何谓运行时动态链接 6.在动态分区分配方式中,应

    CGI开发使用手册

    第4章 理解基本的CGI元素 第5章 将HTML和CGI用作用户界面 第三部分 CGI编程实例和服务器配置 第6章 简单CGI脚本举例 第7章 编制脚本并设置Web服务器 第8章 修改CGI脚本 第9章 如何处理自定义图像映像 第10章 使用...

    软件破解入门教程.txt

    第四章 特例分析的方法 提高篇(1) 提高篇(2) 提高篇(3) 提高篇(4) 提高篇(5) coolfly教程 破解入门第一集 破解入门第二集 破解入门第三集 破解入门第四集 破解入门第五集 破解入门第六集 破解入门第七...

    VisualC++MFC编程实例——第四部分 打包实例

    全书共分四部分进行介绍,第一部分是基础知识,第二部分讲述用户界面的实例,第三部分讲述MFC内部处理方面的实例,第四部分讲述打包实例。全书基本上面向实例进行阐述,讲解透彻、易于掌握。本书既可作为初学者和...

    CGI开发使用手册.rar

    第4章 理解基本的CGI元素 第5章 将HTML和CGI用作用户界面 第三部分 CGI编程实例和服务器配置 第6章 简单CGI脚本举例 第7章 编制脚本并设置Web服务器 第8章 修改CGI脚本 第9章 如何处理自定义图像映像 第10章 ...

Global site tag (gtag.js) - Google Analytics