程序的加载与虚拟内存

发布于 2023-02-20  345 次阅读


前序: 磁盘,flash等都属于外存;主存可以理解为内存,cpu访问的是主存中的数据。

c语言或其他语言的源代码文件存在于磁盘上,由其经过gcc编译成的可执行文件也存在于磁盘,该可执行文件是无法直接交由cpu执行的, 一个可执行文件要发挥其作用,首先是把自己从外存加载(过程很复杂,其中包含了很多细节,不详说)到主存当中一份, 从磁盘中的一份程序文件变成了内存中的一个进程对应的虚拟内存区域。

该虚拟地址空间是由操作系统抽象(分页机制) 出的一个抽象层,供程序员读取,因为程序员直接对物理内存(物理内存即主机中的内存条)进行操作十分困难且麻烦,编码调试也十分不易, 所以操作系统会映射一个虚拟地址空间,起到一个中间桥梁作用,cpu访问物理内存,程序员访问虚拟内存,而虚拟内存由操作系统从物理内存映射出,这样连接贯通,如下图:

32位系统的虚拟内存大小是4GB(内存编码的基本单位是字节,2^ 32字节,转化单位即为4GB)虚拟内存的空间划分是根据进程号来划分区域,即每打开一个进程,虚拟内存就会分配一个区域给该进程,每一个进程都独享划分出的一份4GB内存区域。

例如X86 linux的默认情况下被划分成一比三的内核空间与用户空间,内核空间由多个进程共享(由于每一个虚拟进程都会占据一定量的物理内存空间,没必要每一份进程都向物理内存中装载一个内核,多个进程共享内核,起到节省资源的作用,与此相似的还有一个glibc动态链接库。) 。见下图(VM1为一个进程,VM2为一个进程,VM3为一个进程。一比三意思为Kernel:VM为一比三,Kernel+VM1为一个4GB内存区域,Kernel+VM2为一个4GB内存区域,Kernel+VM3为一个4GB内存区域)

对于一个4GB虚拟内存区域个结构图如下,最上方的kernel 内核空间是所有虚拟内存共享的。

对于windows,linux等操作系统,实际上是一个操作系统内核。内核外边套着用户程序,这样才成为一个操作系统。一个gnu的软件加上linux操作系统内核称为gnu linux操作系统。终端里各种各样的命令行工具实际上不属于linux内核的,而是一套是由GNU协会开发的用户态的可执行程序,属于GNU,gnu的软件源由ARCH来完成一些新软件的安装与管理。KDE来部署一个具体的桌面环境,如下图。

操作系统的内核的核心作用是管理硬件 ,肩负着硬件管理作用的所有的代码都是由内核本体和内核上挂载的一些内核驱动来完成的,这些与硬件交互的代码是内核态代码运行在内核空间中,而KDE 都是跑在用户台的,以一个虚拟内存的形式被操作系统管理,并且占用一定的物理内存。

上面所述为32位系统, 32位虚拟内存结构与64位的虚拟内存结构如下图

code(text)与data段是由ELF 文件程序中加载过来的,内部还含有其他节,见下图,heap空间是用来满足用户动态内存申请需要的。 Memmory Mapping Region 空间大部分是用来映射一个外部文件或者外部数据,如glibc。 PWN的最经典,最常见,最高危的一类漏洞基本都出自于Stack这段区域。Stack区域的作用简单概括就是控制程序执行流,协助程序来完成程序流转移和恢复。Kernel space 区域是操作系统内核加载的一个空间。

静态存储区在进程运行的整个生命周期中都是不变的, 动态存储区(heap,Stack),Stack会随着函数的调用与返回不断地创建与销毁,heap会随着用户的内存申请会动态的分配与释放。

磁盘中ELF文件的不同的功能区域被划分成不同的节,不同的功能区域对应的节由于读写执行权限相同会被合并成一个一个的段在虚拟内存中发挥作用 。节视图是用来放在磁盘中划分功能的,段视图是程序装载到内存中用来划分不同的读写执行权限的。
左边图是LF文件在磁盘中的结构,右边图是ELF文件在内存中的结构。可见在磁盘中的ELF文件中具有读和写权限的有.data .bss .got.plt等节,进入虚拟内存后会整合成DATA段,此段具有读和写的权限。剩下的节具有读权限,或者说是具有可读可执行权限,进入虚拟内存后会整合成CODE(text)段。在磁盘中的ELF文件加载到虚拟内存中时只占据了一部分,一个ELF可执行程序对应的一个进程的虚拟内存映像,这个映像不仅仅包含了ELF程序中的内容,还包含了大量的其他的数据区域。 查看一个ELF文件的结构,可以借助IDA静态分析工具,还可以借助linux的objdump工具,如左图下方的命令; 右边图下方的命令需找到 对应的ELF文件生成的进程号,终端会生成一个对应的进程的虚拟内存的结构。
不同的节由于读写执行权限相同被合并成一个段,这样的操作是由Linker来完成的,然后磁盘里的静态ELF程序文件变成内存中的一个进程映像,这个过程是由操作系统完成映射的。不同的操作系统映射得到的一个虚拟内存空间是不一样的,所以上图的分部是对于linux来说的。
内存地址常以16进制整数表示,单位为Byte。因为16进制与2进制有2^4的关系,所以从右到左每四个二进制数字可以对应一个16进制数字,如上图0x3c中的3对应0011,c对应1100,如此 进行快速转化。
知识备用:

一个字(word)时两个字节(byte),双字(dword)是4个字节,四字(qword)是八个字节。


穿过云层我试着努力向你奔跑