12. 重定位及动态链接

12.1 符号的重定位

1. 重定位步骤

在这里插入图片描述

2. 重定位信息

在这里插入图片描述

3. 重定位操作举例

(1)源文件

在这里插入图片描述

(2)链接结果

在这里插入图片描述

(3)R_386_PC32的重定位
main.o重定位前

在这里插入图片描述
在这里插入图片描述

R_386_PC32的重定位方式

在这里插入图片描述
在这里插入图片描述

(4) R_386_32的重定位
重定位前

在这里插入图片描述
在这里插入图片描述

R_386_32的重定位方式

在这里插入图片描述

swap.o重定位

在这里插入图片描述
在这里插入图片描述

重定位后

在这里插入图片描述

12.2 可执行文件的加载

1. 加载方法

(1)通过调用execve系统调用函数来调用加载器
(2)加载器(loader)根据可执行文件的程序(段)头表中的信息,将可执行文件的代码和数据从磁盘“拷 贝”到存储器中
(3)加载后,将PC(EIP)设定指向 Entry point(即符号_start处),最终执行main函数,以启动程序执行
在这里插入图片描述

ELF头信息举例
在这里插入图片描述

2. 程序的加载和运行

(1) UNIX/Linux系统中,可通过调用execve()函数来启动加载器。
(2) execve()函数的功能是在当前进程上下文中加载并运行一个新程序。 execve()函数的用法如下:
     int execve(char *filename, char *argv[], *envp[]);
   ①filename:加载并运行的可执行文件名(如./hello)
   ②argv:可带参数列表
   ③envp:环境变量列表
 若错误(如找不到指定文件filename) ,则返回-1,并将控制权交给调用程序;
 若函数执行成功,则不返回 ,最终将控制权传递到可执行目标中的主函数main。
(3) 主函数main()的原型形式如下:
     int main(int argc, char **argv, char **envp);
   或者: int main(int argc, char *argv[], char *envp[]);
argc指定参数个数,参数列表中第一个总是命令名(可执行文件名)
 例如:命令行为“ld -o test main.o test.o” 时,argc=6

3. 程序的加载和运行过程举例

Step1: 在shell命令行提示符后输入命令:$./hello[enter]
Step2: shell命令行解释器构造argv和envp
在这里插入图片描述
Step3: 调用fork()函数,创建一个子进程,与父进程shell完全相同( 只读/共享),包括只读代码段、可读写数据段、堆以及用户栈等。
Step4: 调用execve()函数,在当前进程(新创建的子进程)的上下文中加载并运行hello程序。将hello中的.text节、.data节、.bss节等内容加载到当前进程的虚拟地址空间(仅修改当前进程上下文中关于存储映像的一些数据结构,不从磁盘拷贝代码、数据等内容)
Step5:调用hello程序的main()函数,hello程序开始在一个进程的上下文中运行。int main(int argc, char *argv[], char *envp[]);

12.3 共享库和动态链接

1. 动态链接的共享库(Shared Libraries)

静态库有一些缺点:
 – 库函数(如printf)被包含在每个运行进程的代码段中,对于并发运行上百个进程的系统,造成极大的主存资源浪费
 – 库函数(如printf)被合并在可执行目标中,磁盘上存放着数千个可执行文件,造成磁盘空间的极大浪费
 – 程序员需关注是否有函数库的新版本出现,并须定期下载、重新编译和链接,更新困难、使用不便
解决方案: Shared Libraries (共享库)

(1)共享库

① 是一个目标文件,包含有代码和数据
② 从程序中分离出来,磁盘和内存中都只有一个备份
③ 可以动态地在装入时运行时被加载并链接
④ Window称其为动态链接库(Dynamic Link Libraries,.dll文件)
⑤ Linux称其为动态共享对象(Dynamic Shared Objects,.so文件)

(2)共享库的动态链接与优点

动态链接可以按以下两种方式进行:
① 在第一次加载并运行时进行(load-time linking).
 – Linux通常由动态链接器(ld-linux.so)自动处理
 – 标准C库(libc.so) 通常按这种方式动态被链接
② 在已经开始运行后进行(run-time linking).
 – 在Linux中,通过调用dlopen()等接口来实现(用于分发软件包、构建高性能Web服务器等)

共享库的优点:
① 在内存中只有一个备份,被所有进程共享,节省内存空间
② 一个共享库目标文件被所有程序共享链接,节省磁盘空间
③ 共享库升级时,被自动加载到内存和程序动态链接,使用方便
④ 共享库可分模块、独立、用不同编程语言进行开发,效率高
⑤ 第三方开发的共享库可作为程序插件,使程序功能易于扩展

(3) 自定义一个动态共享库文件

自定义库函数

	//myproc1.c
	#include <stdio.h>
	void myfunc1() 
	{  
		printf("%s","This is myfunc1!\n"); 
	} 
	
	//myproc2.c
	#include <stdio.h>
	void myfunc2() 
	{  
		printf("%s","This is myfunc2\n"); 
	} 

生成库文件

$gcc -c myproc1.c myproc2.c  //预处理、编译、汇编
$gcc -shared -fPIC -o mylib.so myproc1.o myproc2.o //生成位置无关的共享代码库文件mylib.so

2. 动态链接

(1)加载时的动态链接

在这里插入图片描述
在这里插入图片描述

(2)运行时的动态链接

  可通过动态链接器接口提供的函数在运行时进行动态链接。类UNIX系统中的动 态链接器接口定义了相应的函数,如 dlopen, dlsym, dlerror, dlclose等, 其头文件为dlfcn.h。

	#include <stdio.h> 
	#include <dlfcn.h> 
	int main() 
	{ 
		void *handle; 
		void (*myfunc1)(); 
		char *error; 
		/* 动态装入包含函数myfunc1()的共享库文件*/ 
		handle = dlopen("./mylib.so", RTLD_LAZY); 
		if (!handle) { 
			fprintf(stderr, "%s\n", dlerror()); 
			exit(1);
		} 
		/* 获得一个指向函数myfunc1()的指针myfunc1*/ 
		myfunc1 = dlsym(handle, "myfunc1"); 
		if ((error = dlerror()) != NULL) { 
			fprintf(stderr, "%s\n", error); 
			exit(1);
		} 
		/* 现在可以像调用其他函数一样调用函数myfunc1() */ 
		myfunc1(); 
		/* 关闭(卸载)共享库文件*/ 
		if (dlclose(handle)< 0) { 
			fprintf(stderr, "%s\n", dlerror()); 
			exit(1);
		} 
		return 0;
	}

3. 位置无关代码(PIC)

动态链接用到一个重要概念:
 – 位置无关代码(Position-Independent Code,PIC )
 – GCC选项 -fPIC 指示生成PIC代码
共享库代码是一种PIC
 – 共享库代码的位置可以是不确定
 – 即使共享库代码的长度发生变化,也不影响调用它的程序
引入PIC的目的
 – 链接器无需修改代码即可将共享库加载到任意地址运行
所有引用情况
(1) 模块内的过程调用、跳转,采用PC相对偏移寻址
(2) 模块内数据访问,如模块内的全局变量和静态变量
(3) 模块外的过程调用、跳转
(4) 模块外的数据访问,如外部变量的访问
要实现动态链接, 必须生成PIC代码 ;
要生成PIC代码,主要解决(3)(4)两个问题

① 模块内的过程调用、跳转

在这里插入图片描述

② 模块内数据访问

在这里插入图片描述

③ 模块外的数据访问

在这里插入图片描述

④ 模块外的过程调用、跳转

在这里插入图片描述
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值