(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理

奋斗吧
奋斗吧
擅长邻域:未填写

标签: (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理 Html/CSS博客 51CTO博客

2023-03-31 18:23:28 115浏览

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理,文章目录一、多目标文件的链接1.将<用堆栈实现倒序打印>的代码拆成两个程序文件(1)编译(2)用nm命令查看目标文件的符号表:nm目标文件(3)查看可执行文件的符号表:readelf-a可执行文件(3)实际上链接的过程是由一个链接脚本(LinkerScript)控制的:默认链接脚本:ld--verbose二、定义和申明1.为什么编译器在处理函数调用代码时需要有函数原型?


文章目录

  • 一、多目标文件的链接
  • 1.将<用堆栈实现倒序打印>的代码拆成两个程序文件
  • (1)编译
  • (2)用 nm 命令查看目标文件的符号表:nm 目标文件
  • (3)查看可执行文件的符号表:readelf -a 可执行文件
  • (3)实际上链接的过程是由一个链接脚本(Linker Script) 控制的:默认链接脚本:ld --verbose
  • 二、定义和申明
  • 1.为什么编译器在处理函数调用代码时需要有函数原型?
  • 前提:只有两个文件:main.c以及stack.c文件
  • (1)gcc的-wall选项可以看到不加函数声明的错误
  • (2)隐式声明靠不住,修改<用堆栈实现倒序打印>
  • (3)外链接extern修饰函数声明的用法及作用
  • (4)内链接static修饰函数声明的用法及作用
  • (5)外链接extern修饰变量声明的用法及作用
  • 函数声明的 extern 可写可不写,而变量声明如果不写 extern 意思就完全变了
  • (6)内链接static修饰变量声明的用法及作用
  • 2.头文件:将上面的main.c中的函数声明写在了stack.h中
  • (1)头文件的作用
  • (2)include角括号和引号的区别
  • (a)tree查看代码文件树和#include预处理指示中可以使用相对路径
  • (b)预处理指示 #ifndef STACK_H #endif有什么用?
  • (c)为什么需要防止重复包含?
  • (d)重复包含头文件会有如下问题
  • (e)为什么要包含头文件而不是 .c 文件?
  • 3.定义和声明的详细规则
  • (1)关键字对函数声明的作用
  • (2)关键字对变量声明的作用
  • 四、静态库
  • (2)编译以及打包成静态库XXX.a
  • gcc -L -l(小) -I(大)
  • (3)链接共享库和静态链接库有什么区别?
  • 五、共享库
  • 1. 编译、链接、运行
  • (1)gcc -c -fPIC xx.c xxx.c文件和gcc -c xx.c xxx.c生成的目标文件有什么不同?
  • 目标文件一般称为重定位文件
  • (iiii)那么运行时在哪些路径下找共享库呢?用ldd 可执行文件
  • (iiiii)解决共享库not fund问题的四种方法
  • 2.动态链接过程
  • 共享库的特点
  • 3.共享库的命名惯例
  • (1)系统的共享库通常带有符号链接,其link name在编译链接时使用
  • 动态库的优点
  • 六、虚拟内存管理
  • (1)通过进程id,查看其虚拟地址空间
  • (2)进程地址空间
  • (3)虚拟内存管理MMU起到了什么作用呢?
  • (4)为啥进程地址空间是独立的?注:共享库的加载地址是运行时决定的!

一、多目标文件的链接

1.将<用堆栈实现倒序打印>的代码拆成两个程序文件

  • stack.c 实现堆栈,main.c 使用堆栈
  • 解释:这段程序和原来有点不同,在<LinuxC语言中栈、队列、DFS、BFS,循环队列>中 top 总是指向栈顶元素的下一个元素,而在这段程序中 top 总是指向栈顶元素,所以要初始化成-1才表示空堆栈,这两种堆栈使用习惯都很常见
  • a 和 b 这两个变量没有用,只是为了顺便说明链接过程才加上的
/* stack.c */
char stack[512];
int top = -1;
void push(char c)
{
	stack[++top] = c;
}
 char pop(void)
{
	return stack[top--];
} 
int is_empty(void)
{
	return top == -1;
}

/* main.c */
#include <stdio.h>
int a, b = 1;
int main(void)
{
	push('a');
	push('b');
	push('c');
	while(!is_empty())
	putchar(pop());
	putchar('\n');
	return 0;
}

(1)编译

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件

(2)用 nm 命令查看目标文件的符号表:nm 目标文件

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_02

(3)查看可执行文件的符号表:readelf -a 可执行文件

  • 执行readelf -a main

(3)实际上链接的过程是由一个链接脚本(Linker Script) 控制的:默认链接脚本:ld --verbose

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_03


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_04


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_05

二、定义和申明

1.为什么编译器在处理函数调用代码时需要有函数原型?

前提:只有两个文件:main.c以及stack.c文件

(1)gcc的-wall选项可以看到不加函数声明的错误

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_06

(2)隐式声明靠不住,修改<用堆栈实现倒序打印>

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_07


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_08

(3)外链接extern修饰函数声明的用法及作用

  • 在这里只有两个文件:main.c以及stack.c文件,而main.c想要用stack.c中的函数,就得这样写;
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_09


(4)内链接static修饰函数声明的用法及作用

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_10


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_11

(5)外链接extern修饰变量声明的用法及作用

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_12

  • 以上函数和变量声明也可以写在 main 函数体里面,使所声明的标识符具有块作用域:
函数声明的 extern 可写可不写,而变量声明如果不写 extern 意思就完全变了

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_13

(6)内链接static修饰变量声明的用法及作用

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_14

2.头文件:将上面的main.c中的函数声明写在了stack.h中

(1)头文件的作用

  • 重复的代码总是应该尽量避免的,可以自己写一个头文件 stack.h :
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_15


  • 这样在 main.c 中只需包含这个头文件就可以了,而不需要写三个函数声明:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_16


(2)include角括号和引号的区别

  • 角括号: gcc 首先查找 -I选项指定的目录,然后查找系统的头文件目录(通常是 /usr/include ,在我的系统上还包括 /usr/lib/gcc/i486-linux-gnu/4.3.2/include )
  • 引号: gcc 首先查找包含头文件的 .c 文件所在的目录,然后查找 -I 选项指定的目录,然后查找系统的头文件目录

(a)tree查看代码文件树和#include预处理指示中可以使用相对路径

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_17


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_18

(b)预处理指示 #ifndef STACK_H #endif有什么用?

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_19

(c)为什么需要防止重复包含?

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_20


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_21

(d)重复包含头文件会有如下问题

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_22

(e)为什么要包含头文件而不是 .c 文件?

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_23

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_24


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_25

3.定义和声明的详细规则

(1)关键字对函数声明的作用

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_26

(2)关键字对变量声明的作用

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_27


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_28

四、静态库

(1)我们继续用 stack.c 的例子。为了便于理解,我们把 stack.c 拆成四个程序文件(虽然实际上没太大必要) ,把 main.c 改得简单一些,头文件 stack.h 不变,本节用到的代码如下所示:

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_29


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_30


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_31

(2)编译以及打包成静态库XXX.a

  • 我们把 stack.c 、 push.c 、 pop.c 、 is_empty.c 编译成目标文件:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_32


  • 然后打包成一个静态库 libstack.a :
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_33


gcc -L -l(小) -I(大)

  • 然后我们把 libstack.a 和 main.c 编译链接在一起
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_34


(3)链接共享库和静态链接库有什么区别?

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_35

  • 反汇编看上一步生成的可执行文件 main :
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_36


五、共享库

1. 编译、链接、运行

(1)gcc -c -fPIC xx.c xxx.c文件和gcc -c xx.c xxx.c生成的目标文件有什么不同?

目标文件一般称为重定位文件

  • 组成共享库的目标文件和一般的目标文件有所不同,在编译时要加 -fPIC 选项
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_37


  • 我们知道一般的目标文件称为Relocatable,在链接时可以把目标文件中各段的地址做重定位,重定位时需要修改指令

(a)我们先不加 -fPIC 选项编译生成目标文件:

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_38


(i)首先,反汇编查看push.o的目标文件

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_39

  • 指令中凡是用到 stack 和 top 的地址都用0x0表示,准备在重定位时修改;
    (ii)再看 readelf 输出的 .rel.text 段的信息,标出了指令中有四处需要在重定位时修改:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_40

  • (iii)下面编译链接成可执行文件之后再做反汇编分析:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_41

  • (b)现在看用 -fPIC 编译生成的目标文件有什么不同
    (i)
  • 指令中用到的 stack 和 top 的地址不再以0x0表示,而是以 0x0(%ebx) 表示 但其中还是留有0x0准备做进一步修改。
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_42

  • (ii)再看 readelf 输出的 .rel.text 段
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_43

  • (iii)我们先编译生成共享库再做反汇编分析:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_44


  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_45


  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_46


(iiii)那么运行时在哪些路径下找共享库呢?用ldd 可执行文件

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_47


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_48

  • 总之,共享库的搜索路径由动态链接器决定,从 ld.so(8) 的Man Page可以查到共享库路径的搜索顺序:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_49


  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_50


(iiiii)解决共享库not fund问题的四种方法

方法1(不推荐):

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_51


方法2(最常用):ldconfig

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_52


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_53

  • 现在再用 ldd 命令查看, libstack.so 就能找到了:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_54

  • 方法3:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_55


方法4(不推荐):gcc的选项:-Wl,-rpath

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_56

2.动态链接过程

(1)研究一下在 main.c 中调用共享库的函数 push 是如何实现的

  • 首先反汇编看一下 main 的指令:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_57


  • 和 “静态库”链接静态库不同, push 函数没有链接到可执行文件中。而且 call 80483d8 push@plt;这条指令调用的也不是 push 函数的地址;
共享库的特点
  • 共享库是位置无关代码,在运行时可以加载到任意地址,其加载地址只有在动态链接时才能确定, 所以在 main 函数中不可能直接通过绝对地址调用 push 函数,也是通过间接寻址来找 push 函数的。

  • 对照着上面的指令,我们用 gdb 跟踪一下:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_58


  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_59


  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_60

3.共享库的命名惯例

(1)系统的共享库通常带有符号链接,其link name在编译链接时使用

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_61

  • 按照共享库的命名惯例,每个共享库有三个文件名:real name、soname和linker name,真正的库文件(而不是符号链接) 的名字是real name,包含完整的共享库版本号。例如上面的 libcap.so.1.10 、 libc-2.8.90.so 等。

动态库的优点

  • soname是一个符号链接的名字
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_62


  • linker name仅在编译链接时使用,gcc 的 -L 选项应该指定linker name所在的目录。
    有的linker name是库文件的一个符号链接,有的linker name是一段链接脚本
    例如上面的 libc.so 就是一个linker name,它是一段链接脚本:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_63


  • 下面重新编译我们的 libstack ,指定它的soname:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_64


  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_65


六、虚拟内存管理

(1)通过进程id,查看其虚拟地址空间

  • 我们知道操作系统利用体系结构提供的VA到PA的转换机制实现虚拟内存管理
  • eg如下:
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_66

  • 解释说明如下:
  • 用 ps 命令查看当前终端下的进程,得知 bash 进程的id是29977,然后用 cat /proc/29977/maps 命令查看它的虚拟地址空间
  • /proc 目录中的文件并不是真正的磁盘文件,而是由内核虚拟出来的文件系统
  • 当前系统中运行的每个进程在 /proc 下都有一个子目录,目录名就是进程的id, 查看目录下的文件可以得到该进程的相关信息

(2)进程地址空间

  • x86平台的虚拟地址空间是0x0000 0000~0xffff ffff,大致上前3GB(0x0000 0000~0xbfff ffff) 是用户空间,后1GB(0xc000 0000~0xffff ffff) 是内核空间
  • /lib/ld-2.8.90.so 就是动态链接器 /lib/ld-linux.so.2 ,后者是前者的符号链接。标有 [vdso] 的地址范围是 linux-gate.so.1 的映射空间,我们讲过这个共享库是由内核虚拟出来的
  • (第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_67


(3)虚拟内存管理MMU起到了什么作用呢?

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_68

(4)为啥进程地址空间是独立的?注:共享库的加载地址是运行时决定的!

(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_69


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_共享库_70


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_目标文件_71


(第20章)LinuxC本质中多目标文件的链接、静态库、共享库、虚拟内存管理_函数声明_72




好博客就要一起分享哦!分享海报

此处可发布评论

评论(0展开评论

暂无评论,快来写一下吧

展开评论

您可能感兴趣的博客

客服QQ 1913284695