软硬链接 动静态库(深入地址空间)

news2024/9/23 3:27:18

文章目录

  • 前言:
  • 软硬链接:
  • 动态库和静态库:
    • 如何制作静态库?
    • 如何制作动态库?
    • 动态库 VS 静态库
  • 文本写入 && 二进制写入
  • 🚀动态库加载与可执行程序与地址空间的关系:
    • 🧛‍♀️可执行程序&&地址空间
    • 🎨动态库的加载

前言:

​ 在前面一章,我们介绍了文件系统,下面我们来对关于文件I/O操作的进一步补充,我们来聊一聊软硬链接是个什么,还有我们之前粗略的讲解的动静态库,最后我们想来进一步谈谈地址空间的概念。

软硬链接:

  • 先看现象
    我在这里创建了两个文件,然后我分别输入指令,然后看看当前目录:
    ln -s file_target1.txt file_soft.link
    ln file_target2.txt file_hard.link
    image-20240905145130638

  • 分析现象:

    1. 软连接之后是一个独立的文件,因为它有独立的inode
    2. 硬链接之后不是一个独立的文件,因为它没有独立的inode,用的确是目标文件的inode。
    3. 属性中有一列‘1’‘’1‘’2‘的数字的,这一列是硬链接数。
  • 分析特点:

    1. 软连接的文件内容 目标文件所对应的路径字符串。(类似于windows的快捷方式)
      image-20240905151003977
    2. 硬链接就是一个文件名和inode的映射关系,建立硬链接,就是在指定目录下,添加一个新的文件名和inode number的映射关系。
    3. 这些数字其实文件磁盘的引用计数,用来统计有多少个文件名字符串通过inode number指向当前文件。
      因为当前创建了硬链接,就有一个“别名”指向同一个文件,因此有引用计数为2.

    所以我们定位文件,只有两种方式:

    1、通过路径(软链接)
    2、直接找到目标文件的inode(硬链接)

  • 理解目录:

    1. 任何一个目录,刚开始创建的时候引用计数一定是“2”
      image-20240905151747135
    2. 在目录A下再新建一个目录,会让A目录的引用计数自动+1
      image-20240905153134558
    3. 想要知道一个目录内部有几个目录,要让该目录引用计数 - 2即可。
  • 为什么创建目录会自动变2呢?

    image-20240905155453189

    因为每个目录创建的时候都会生成 . 和 … 文件,这就会产生硬链接。

  • Linux系统中,不允许给目录建立硬链接
    例:假设你当前的路径为/home/XXX/Work/root_hard,你对根目录创建了硬链接,这就会导致当操作系统遍历当前路径时遇到root_hard,就会回到根目录,造成死循环。
    但是这不同于.和..,因为所以操作系统都认识这两个小家伙,所以不会造成死循环!

  • 硬链接的作用:

    1. 构建Linux的路径结构,让我们可以用.和..来进行路径定位
    2. 一般用硬链接做文件备份

动态库和静态库:

​ 我们在前面讲解gcc的使用时,有对动态库和静态库有粗略的介绍,主要还是对动静态链接做过讲解。我们下面来回顾一下:
​ 在linux操作系统中,我们以后缀 .so 作为动态库,. a 作为静态库,而在windows中,我们以后缀 .dll 最为动态库,.lib 作为静态库,当然我们可以在linux查以下该可执行文件运用了哪些库,输入指令:ldd a.out
image-20240910124542371

image-20240910125306473

image-20240910125322740

这里我们默认是使用的动态库,当然你也可以让程序使用静态库,但是默认一般centos是没有静态库这个的,因此你可以尝试安装然后使用静态链接来创建可执行程序,具体的操作可以看看我之前写的blog

如何制作静态库?

​ 假设有一天,你承担了一个项目需要编写一个简易计算器,实现最简单的加法运算和减法运算。现在你已经实现了,通过add.c来实现函数中的算法,add.h来声明函数,同理sub.c和sub.h一个是实现一个是声明。但是你在提交程序时他们希望能借鉴你的算法,让别的程序也能用这个方法,同时会给你相应的报酬。本来你只是以为提交一个可执行程序就好了,然后他们输入数字就好了,但现在需要把你辛辛苦苦写的代码交给他们,但是你并不想让他们看到你的代码里面算法是如何实现的,这你该怎么去解决呢?
image-20240910131933130

​ 聪明的你肯定想到了,只要我把代码加密,他们不就看不懂了吗?同时还有满足算法能正常实现,那你又该如何去解决呢?其实对于加密,我们只需要将所编写的代码转换至二进制语言,这样就可以做到计算机能看懂而人看不懂不就好了吗,所以你就尝试使用gcc -c这个操作,将代码进行汇编成二进制目标文件,即:
gcc -c add.c最后得到的就是add.o
image-20240910132047230

我们可以打开add.o看看内容:
image-20240910132112037

不难发现我们什么都看不懂,同理我们也可以对sub.c也这么做。
那这样子我们还能正常编译gcc main.c add.o sub.o形成可执行程序吗?
image-20240910132402884

不难发现,当然可以成功也可以正常运行。

可是,那边又来刁难你了,现在他们想让你把这些实现算法的.o文件打包成一个静态库,然后发给他们。那你又该怎么打包成静态库呢?
你知道是使用ar -rc来进行打包:
image-20240910145859205

这时我们就有了我们自己搞的一个库,但是我们不能直接gcc main.c libmymath.a这样喔,因为对于C/C++的库,gcc编译器默认是认识,而对于libmymath.a这是我们自己写的,gcc编译器默认是不认识的,所以对于这种第三方库,我们需要向gcc指明位置,这样才能使用。
gcc - I "头文件位置" -L "库的位置" -l"库名"
以上是gcc拓展出的各个选项,这里要额外说一下库名。
libmymath.a来说,这个**库名准确来说是去掉lib和.a,**得到的mymath才是库名,如果你不规范的话,是会报错的!
image-20240910150617291

当然你也可以尝试将你打包好的静态库libmymath.a复制到/lib64目录下,将各个头文件复制到/usr/include中,这样你就可以将自己的库和头文件放在系统当中,下次就可以直接#include <sub.h>和#include <add.h>了。但是不建议这样子做,因为你现在写的这些代码还是有点挫。现在你已经利用静态库生成了可执行程序,所以你就算删除了静态库也不会影响可执行程序的启动。

如何制作动态库?

​ 现在你想要制作一个动态库,首先你需要先将.c文件汇编形成.o文件,但是这里要区别于静态库,我们需要在gcc后面加入-fPIC这个我们后面讲!
输入:gcc -fPIC -c add.c sub.c
image-20240910153347155

这样就有.o文件了,然后我们在输入指令:gcc -shared -o libmymath.so *.o
image-20240910155006608

这个时候我们想要像一个正常的操作系统那样,存在一个include目录用来存放头文件和一个lib目录用来存放动态库,因此我们将这些有关文件拷贝至各个文件中:
image-20240910160515122

所以我们在要编译形成可执行程序,我们可以这么写:
gcc -I ./include -L ./lib -lmyc
image-20240910160656610

然后我们执行a.out
image-20240910160854641
程序报错了,说找不到我们创建的动态库,我们lld查看一下:
image-20240910160947774
确实是找不到,那是因为你只是将这个动态库告诉了gcc编译器没有告诉操作系统而操作系统是要在程序运行的时候查找动态库并加载运行。

静态库就不存在这个问题,因为编译期间,已经将库中的代码拷贝至可执行程序内部了!加载和库就没有关系了!

具体的解决方法:

  1. libmyc.so 拷贝至 /lib64目录下(不建议)
  2. 创建软连接(快捷方式)sudo ln -s 当前路径的 libmymath.so /lib64/libmyc.so
  3. 使用环境变量,LD_LIBRARY_PATH(加载库路径)
    LD_LIBRARY_PATH = $LD_LIBRARY_PATH:/当前路径,但是这种方法退出之后就不存在了。
  4. 将环境变量添加系统中(永久生效)
    vim ~./bashrc
  5. 修改/etc/ld.so.conf.d,新增自己的配置文件,再执行ldconfig加载起来

动态库 VS 静态库

​ 默认链接动态库,如果你没有使用 -static,并且只提供静态库,那么编译器就只能静态链接当前的静态库,其它库正常动态链接。

  • -static的意义是什么呢?
    必须强制的将我们的程序进行静态链接,这就要求我链接的任何库都必须提供对饮的静态库版本。

文本写入 && 二进制写入

​ 二进制将数据在内存中的样子原封不动的搬到文件中,文本格式则是将每一个数据转换成字符写入到文件中,他们在大小上,布局上都有着区别。由此可以看出,2进制文件可以从读出来直接用,但是文本文件还多一个“翻译”的过程,因此2进制文件的可移植性好。

🚀动态库加载与可执行程序与地址空间的关系:

​ 我们在前面讲解地址空间时,我们有了解过关于内存中的物理的地址和与虚拟地址mm_struct各个区域的映射关系。那么动态库在加载的时候会处于哪个区呢?
image-20240911084756307

不难发现我们在加载动态库时,是将动态库放在了虚拟地址中的==“共享区”==

​ 我现在先了解一个轮廓框架,我们先深入了解下地址控制与可执行程序相关的话题,然后我们再回头来看动态库的加载,到那时我们就都能串联起来了!

🧛‍♀️可执行程序&&地址空间

​ 现在我有几个问题,

  • 我们形成可执行程序后,代码当中有地址吗?是什么地址?

    当我们形成可执行程序后,代码肯定是先经过了预处理检查语法,再进行编译形成汇编语言,然后将汇编语言汇编形成二进制代码供计算机读入。这个时候我们编写一个简单的c语言程序再进行反汇编操作,看看各个汇编指令的地址是如何分布的,可以使用objdump -S a.out > test.s来讲代码保存起来。

    image-20240911092745683

    image-20240911092807529

    这里的汇编指令太多了,在这里我们只看一部分。

    所以我们的可执行程序里面到处都是地址

    同时我们进行size可以看到这个可执行程序已经分好了各种区域,类似我们mm_struct中的地址空间划分。
    image-20240911093050444

    并且我们也发现了可执行程序划分出来的区域,那我们在这里推测可执行程序其实与mm_struct中各个区域划分有关。

    一般在Linux形成的可执行程序中,是以ELF格式的可执行程序,二进制食欲自己的固定格式的,包括elf可执行程序的头部,可执行程序的属性

    可执行程序编译之后,会形成很多行汇编语句,每条汇编语句都有对应的地址
    既然每条语句都有属于他的地址,那我们可以理解这些一个一个的地址就是我们所谓的虚拟地址

    在这里要注意的是,对于一个test.c文件,无论你进行多少次gcc进行编译形成汇编语句,你在反汇编后,每条语句对应的虚拟地址都是一样的不会变化的!

  • 对于这些语句的地址又是如何编制的呢?

    我们在编址时一般有两种:其一是相对编址;其二是绝对编址。

    相对编址时,使用起始地址加上对应的偏移量
    image-20240911094703741

    绝对编制就是我们原本的样子:
    image-20240911094731271

    绝对编址就是我们本身的方式,也叫做平坦模式,所以整体是线性增长的。
    同时我们也可以认为这其实就是虚拟地址

  • mm_struct的各个区域的大小是固定的吗?”

    先说结论,当然不固定

    我们就拿这一篇博客举例子,我个人现在是在typora上面写博客的,但我写完一篇博客后这个typora的大小差不多是8KB左右。但是我今天想玩CS:GO那对于这个可执行程序就不只是8KB了喔,你所占空间都不一样,那你的代码长度和内容肯定也不一样,那对于他们的mm_struct中的各个区域的大小也就不一样,这是肯定的!

    那我们又该如何进行分区呢?

    其实我们在打开可执行程序时,默认会打开一个一个动态库:

    image-20240911103633059

    这个库里面存放着,就是我们的加载器

    我们会先把加载器的库加载到内存中,执行库中的函数将可执行程序拷贝到内存,加载器对可执行程序的头部进行解析。可以得到main函数的地址。

    所以 ELF + 加载器 我们就可以解析得知各个区域中的起始位置结束位置 + main函数地址
    就像我们之前说的,mm_struct内部的各个区域都有对应的start 和 end

  • 程序是如何加载的?

    我们在前面的学习中,知道要执行一个可执行程序,是操作系统创建进程,通过进程来实现的。
    进程 = 内核数据结构 + 代码和数据,那我们在执行一个可执行程序时,是先创建PCB还是先将代码和数据加载到内存中呢?
    答案就是六个字:“先描述,再组织”

    所以首先我们会先创建进程的内核数据结构(PCB),再将可执行程序的代码和数据加载到内存中。

    既然先创建PCB,那么在将可执行程序的代码和数据加载到内存之前,ELF格式和加载器就已经对一个可执行程序进行了“预处理”,将这个程序的各个区域划分都会找到并且记录起来,所以在创建PCB之后也会生成对应的mm_struct,然后通过加载器将mm_struct的各个区域划分好
    image-20240911105506252

    然后可执行程序的代码和数据就会加载至内存当中。
    所以虚拟地址空间这个概念不是OS独有的,是由OS+编译器+加载器一起构成的

  • 页表的映射关系是怎么创建的?

    现在我们的mm_struct已经初始化完毕,但是可执行程序加载到了内存当中,所以操作系统需要进行管理,需要建立物理内存地址与虚拟地址之间的映射关系,当然完成这个工作的是“页表”。

    我们都知道程序的入口是main函数,而我们在由加载器对ELF格式下的可执行程序进行解析时,初始化了mm_struct,而mm_struct不仅对每个分区标记了对应的start和end,还找到了main函数的入口地址这个地址最先是由CPU中的pc指针来指向的,pc指针接下来就会保存当前执行指令的下一条虚拟地址,然后再不断执行。

    正是有了main函数地址,而我的PCB也创建好了mm_struct也初始化好了,那么这时,在磁盘中的可执行程序的代码和数据就会进入内存当中。

    既然你进了内存里面了,那你操作系统本身就会管理内存,那你既然在内存当中,所以你在内存当中的物理地址我不就知道到了吗?

    所以对于main函数来说,你的虚拟地址在我的pc指针里,这我能找到,而你的物理地址呢,我操作系统也能找到,那你们之间不就建立起来了映射关系吗?页表不就可以出现来管理了吗?

    image-20240911134758265

🎨动态库的加载

​ 在介绍动态库加载的时候,我们知道了动态库是加载到mm_struct的代码共享区中。库既然是在磁盘当中,那么它也有属于自己的虚拟地址,只不过没有main函数地址罢了。假设现在创建一个库,里面存放着add函数,而我的.c文件中要执行add函数。
image-20240911145154494

当我们执行到Add时,此时我们的动态库还没加载,那么当然就需要将动态库也加载到内存,同时映射到虚拟地址空间。

这里我们发现,动态库对应的libmyc.so存在一个虚拟地址,而libmyc.so库里面也存在一个add函数,这个函数也有一个虚拟地址,那他们之间有什么关系吗?

image-20240911151344997
实际上库中的函数地址就是库函数地址为0的偏移量,当想要访问到库函数时,我们就需要知道他的地址,他的地址就是两者的相加:0x5555+0x1111=0x6666
所以有了地址我们就可以动态库的函数了,也就是跳转到共享区,执行完毕再跳回来。

所以这个地址被映射到虚拟地址空间的那个位置重要吗?
并不重要,所以制作动态库时需要-fPIC,叫做与地址无关码!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2127035.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

el-input设置type=‘number‘和v-model.number的区别

el-input设置typenumber’与设置.number修饰符的区别 1. 设置type‘number’ 使用el-input时想收集数字类型的数据&#xff0c;我们首先会想到typenumber&#xff0c;设置完type为number时会限制我们输入的内容只能为数字&#xff0c;不能为字符/汉字等非数字类型的数值&…

数据结构(7.2_3)——分块查找

分块查找的算法思想 分块查找&#xff0c;又称索引顺序查找&#xff0c;算法过程如下&#xff1a; 在索引表中确定待查记录所属的分块(可顺序、可折半)在块内顺序查找 "索引表"中保存每个分块的最大关键字和分块存储区间 特点&#xff1a;块内无序&#xff0c;块…

新的 PIXHELL 攻击从隔离系统中窃取机密

在一篇新发表的论文中&#xff0c;本古里安大学的 Mordechai Guri 博士揭露了“PIXHELL”攻击。 这是一种从隔离、音频隔离系统中窃取敏感数据的新方法。 此次攻击允许恶意攻击者通过 LCD 屏幕产生的声学信号泄露信息&#xff0c;利用线圈噪音和由操纵的像素模式引起的电容器…

【安全系列--处理挖矿】

现象&#xff1a;我们云上waf提示有台服务器存在挖矿行为 解决思路&#xff1a; 1、查看服务器的进程情况 top发现服务的CPU使用率非常高 2、使用性能分析工具perf查看占用的cpu进程 sudo apt install linux-tools-common发现一些kernel进程存在异常 3、使用find查一下这…

在广袤的数据通信旷野,“伙伴+华为”体系点亮星云

在浩浩荡荡的智能化变革中&#xff0c;从用户层面看&#xff0c;来自地市区县的各个行业与企业是真正的主体&#xff0c;以他们为主体的商业市场&#xff0c;提供了智能化进程中最为关键的广度。 而从技术角度看&#xff0c;数据通信是千行万业都需要的基本数字化能力。数据通信…

[Web安全 网络安全]-文件包含漏洞

文章目录&#xff1a; 一&#xff1a;前言 1.什么是文件包含漏洞 2.文件包含漏洞的成因 3.文件包含漏洞的分类 4.文件包含漏洞的防御策略 5.文件包含函数&#xff08;触发点Sink&#xff09; 6.环境 6.1 靶场 6.2 其他工具 二&#xff1a;文件包含LFI labs靶场实验…

组播 2024 9 11

PIM&#xff08;Protocol Independent Multicast&#xff09;是一种常用的组播路由协议&#xff0c;其独立于底层的单播路由协议&#xff0c;能够在多种网络环境中有效地实现多播路由功能。PIM主要有两种模式&#xff1a;PIM Sparse Mode (PIM-SM) 和 PIM Dense Mode (PIM-DM)&…

sqli-labs靶场自动化利用工具——第1关

文章目录 概要整体架构流程技术细节执行效果小结 概要 Sqli-Labs靶场对于网安专业的学生或正在学习网安的朋友来说并不陌生&#xff0c;或者说已经很熟悉。那有没有朋友想过自己开发一个测试脚本能实现自动化化测试sqli-labs呢&#xff1f;可能有些人会说不是有sqlmap&#xf…

【TCP】相关机制:异常处理

文章目录 1 . **进程崩溃**2 . **主机关机**&#xff08;正常流程的关机&#xff09;3 . **主机掉电**&#xff08;直接拔电源&#xff09;1. 接收方掉电2. 发送方掉电 4 . **网线断开** 1 . 进程崩溃 Java 中的体现就是抛出异常&#xff0c;但没人 catch&#xff0c;最终异常…

Apache SeaTunnel Zeta 引擎源码解析(二) Client端的任务提交流程

作者&#xff1a;刘乃杰 编辑整理&#xff1a;曾辉 引入 本系列文章是基于 Apache SeaTunnel 2.3.6版本&#xff0c;围绕Zeta引擎给大家介绍其任务是如何从提交到运行的全流程&#xff0c;希望通过这篇文档&#xff0c;对刚刚上手SeaTunnel的朋友提供一些帮助。 我们整体的文…

如何用 Scrapy 爬取网站数据并在 Easysearch 中进行存储检索分析

做过数据分析和爬虫程序的小伙伴想必对 Scrapy 这个爬虫框架已经很熟悉了。今天给大家介绍下&#xff0c;如何基于 Scrapy 快速编写一个爬虫程序并利用 Easysearch 储存、检索、分析爬取的数据。我们以极限科技的官网 Blog 为数据源&#xff0c;做下实操演示。 安装 scrapy 使…

Linux shell编程学习笔记79:cpio命令——文件和目录归档工具(下)

在 Linux shell编程学习笔记78&#xff1a;cpio命令——文件和目录归档工具&#xff08;上&#xff09;-CSDN博客https://blog.csdn.net/Purpleendurer/article/details/142095476?spm1001.2014.3001.5501中&#xff0c;我们研究了 cpio命令 的功能、格式、选项说明 以及 cpi…

Maven下载安装

下载 下载地址&#xff1a;Maven – Download Apache Maven 选择合适的版本进行下载 windows&Linux安装 1, 解压apache-maven-3.6.1.rar即安装完成 2&#xff0c; 配置环境变量MAVEN_HOME为安装路径&#xff0c;并将MAVEN_HOME的bin目录配置到PATH下 3&#xff0c;…

黑马点评21——最佳实践-键值设计

文章目录 如何优雅的设计keyBigKey问题选择合适的数据结构 如何优雅的设计key BigKey问题 redis-rdb-tools 选择合适的数据结构 动态修改的&#xff0c;重启后就又失效了&#xff0c;而且最好上线不要超过1000 这样解决也不存在bigkey的问题了。

leetcode 1811 寻找面试候选人(postgresql)

需求 表: Contests ------------------ | Column Name | Type | ------------------ | contest_id | int | | gold_medal | int | | silver_medal | int | | bronze_medal | int | ------------------ contest_id 是该表的主键. 该表包含LeetCode竞赛的ID和该场比赛中金牌、银…

maya的重命名物体和材质工具(带ai过程)

对材质同样也有效 被AI干失业的卖衣服的小姐姐&#xff0c;开的士的小哥哥都可以再就业的易上手教程&#xff0c; 先看效果&#xff01; 对物体命名也是&#xff0c;相当的美观 先提出需求我想在maya中批量重命名物体怎么办&#xff1f;AI给你弄个短代码 &#xff0c;放进AI进…

Trying to install openai in chaquopy in android studio but getting build failed

题意&#xff1a;“尝试在 Android Studio 的 Chaquopy 中安装 OpenAI&#xff0c;但构建失败。” 问题背景&#xff1a; Pretty much title, tried to import openai in android studio to use their api for my android app but everytime I run the app I get the same bui…

SQL注入+CTF实例

SQL注入的做题步骤 1.判断数字型还是字符型 数字型&#xff1a; select * from table where id$id; 字符型&#xff1a; select * from table where id$id; # 一般是单引号闭合&#xff0c;也有可能是双引号&#xff0c;又或者是)、")、))等等都有可能 可以用and 11和an…

康谋分享 | 汽车仿真与AI的结合应用

在自动驾驶领域&#xff0c;实现高质量的虚拟传感器输出是一项关键的挑战。所有的架构和实现都会涉及来自质量、性能和功能集成等方面的需求。aiSim也不例外&#xff0c;因此我们会更加关注于多个因素的协调&#xff0c;其中&#xff0c;aiSim传感器实现的神经网络渲染仿真方案…

[001-03-007].第07节:Redis中的管道

我的后端学习大纲 我的Redis学习大纲 1、Redis管道的由来&#xff1a; 1.Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。一个请求会遵循以下步骤&#xff1a; 客户端向服务端发送命令分四步(发送命令→命令排队→命令执行→返回结果)&#xff0c;并监听Socket…