当前位置: 首页 > article >正文

Linux 基础io_ELF_虚拟物理地址_动态库加载

1.可执行程序格式 ELF

[wws@hcss-ecs-178e myshell]$ ll
total 56
-rw-rw-r-- 1 wws wws    92 Oct 17 19:14 file
-rw-rw-r-- 1 wws wws    82 Oct 12 16:51 makefile
-rw-r--r-- 1 wws wws    90 Oct 17 19:13 myfile
-rwxrwxr-x 1 wws wws 20128 Oct 16 21:02 myshell
-rw-rw-r-- 1 wws wws  7627 Oct 16 21:16 myshell.cc
[wws@hcss-ecs-178e myshell]$ file myshell
myshell: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=285576a85c296a5f746e5570999a6c447128bd52, not stripped
[wws@hcss-ecs-178e myshell]$ size myshell
   text    data     bss     dec     hex filename
   9159     900    3192   13251    33c3 myshell

file 查看文件的基本情况 size查看可执行程序的结构情况,text数据段大小 data初始化数据大小 bss未初始化数据大小  dec10进制总大小 hex16进制总大小 filename名称

前两个部分是系统在加载时用到的,中间黄色部分Section 每一个小框就是一个节 不同的节存储不同的内容 比如

text节

存储程序的机器代码,也就是可执行指令。

data节

存储初始化的全局和静态变量。这些变量在程序启动时会被加载到内存中。

bss节

存储未初始化的全局和静态变量。

...

最后一个部分Section Header Table 它的作用是保存每个节的偏移量和大小...,通过节的偏移量和大小,可以计算出每个节在ELF文件中的范围。

2.重谈地址空间(可执行程序 加载)

我们知道每一个进程都有对应的mm_struct虚拟地址空间,它通过页表与物理内存建立映射关系。每次访问虚拟地址时,系统会通过页表查找相应的物理地址。

现在有几个问题

1.可执行性程序在加载到内存前有地址吗?

在加载到内存之前,可执行程序确实具有地址,但这些地址主要是虚拟地址,而非物理地址。我们在编译时就可以看到地址,但这些地址其实是虚拟地址。

从这张图就可以看出在加载到物理内存前,可执行程序就有程序自己生成的逻辑地址了,之前不说说虚拟地址吗?逻辑地址是什么?逻辑地址=起始地址+偏移量(平坦模式下起始地址==0)知道一条指令的地址+它的长度=下一个指令的地址  逻辑地址在数字层面上就和虚拟地址一样了  只不过在磁盘ELF中一般说逻辑地址 加载到内存时说虚拟地址

2.mm_struct由谁初始化的?

我们知道可执行程序加载到内存前就有虚拟地址,每个节的起始的虚拟地址也知道(节偏移+节大小) 

程序内部进行函数跳转也是用的虚拟地址。

每个节的虚拟地址的范围就是mm_struct中每个区域的起始范围。

3.现在可执行程序加载到内存,虚拟地址空间也通过页表跟内存建立映射关系,接下来CPU怎么开始调度呢?

将 PC 寄存器设置为入口点地址,要从ELF的ELF Header中读取

但这是一个虚拟地址,CPU怎么找对应的物理地址呢?

PC:程序计数器是一个指向下一条将要执行的指令的内存地址的寄存器

EIP:用于指向当前正在执行的指令。保存了下一条要执行的指令的地址,并且在程序执行过程中会自动更新。

MMU硬件:是负责虚拟地址到物理地址转换的硬件模块。

CR3:存储页表的物理地址

现在pc中有第一条指令的虚拟地址,再根据MUU+CR3找到对应的物理地址,再把该物理地址里面的内容(指令+虚拟地址)读入到EIP,最后根据读入的虚拟地址+指令长度=下一个指令的虚拟地址更新PC。形成闭环

所以虚拟地址是CPU 操作系统 编译器 共同协助下的产物。

为什么要有虚拟地址空间?

有了虚拟地址空间,编译器在编址的时候就不用考虑物理内存的情况,直接在平坦模式下从0000开始不断生成地址,完成编译器与操作系统的解耦。

3.重谈区域划分

现在我们知道了mm_struct里面代码段 数据段是根据可执行程序ELF格式中的Section(text节 data节...)的虚拟地址的范围划分的。但里面栈区 堆区 共享区的范围怎么划分的?又怎么对它们进行管理?

其实在mm_struct中有struct vm_area_struct结构体,vm_area_struct是 Linux 内核中用于管理虚拟内存区域的结构体。里面存有该区域的起始位置

struct vm_area_struct {
    struct mm_struct *vm_mm;        // 指向该虚拟内存区域所属的内存管理结构
    unsigned long vm_start;          // 虚拟内存区域的起始地址
    unsigned long vm_end;            // 虚拟内存区域的结束地址
    struct vm_area_struct *vm_next;  // 指向下一个虚拟内存区域的指针
    pgprot_t vm_page_prot;           // 页的保护属性
    unsigned long vm_flags;          // 该虚拟内存区域的标志
    struct file *vm_file;            // 如果是映射文件,该指针指向文件结构
    unsigned long vm_pgoff;          // 在映射文件时的偏移
    // 可能还有其他字段
};

每一个区域都有自己的vm_area_struct,并通过链表的形式相互连接。

4.加载动态库

我们知道动态库也是ELF结构,对里面函数方式进行编址,也是按照基址0+偏移量 进行的。(库名:偏移量 库名会变成地址)

库加载到内存也需要被管理,struct libso 里面记录了加载到内存中的库的信息,引用计数,指针

#include <stddef.h>  // for size_t

// 定义共享库结构体
struct libso {
    char *name;               // 库的名称
    char *path;               // 库的路径
    void *handle;             // 库的句柄
    size_t size;              // 库的大小
    int ref_count;            // 引用计数
    struct libso *next;       // 指向下一个 libso 结构体的指针
};

库映射到mm_struct。知道库在物理内存占据的空间大小,以及物理地址起始位置。通过页表建立映射关系,映射到mm_struct的位置由系统找等大小的虚拟地址空间。并在vm_area_struct的链表中链接自己的vm_area_struct(虚拟地址的起始位置)

所以正文代码中怎么找到库函数呢?

库名:偏移量 库名会变成地址 当系统找到等大的虚拟地址空间分配给库,库就有了虚拟起始地址,此时把库名改成 库的虚拟起始地址 再加上偏移量,就能找到库函数的虚拟地址了。

正文代码段是不能修改的,也就不能把库名该为 库的虚拟起始地址。

其实在ELF中的节有一个是GOT全局偏移表,它里面存放的就是库函数的虚拟地址(库名:偏移量)。

代码区找库函数时是这样写的 call GOT虚拟地址:目标库函数的下标

GOT的虚拟地址在加载到内存前就已经编址好了。通过call GOT虚拟地址:目标库函数的下标 就可以找到目标库函数的虚拟地址需要等系统分配好虚拟地址 才能找到库的虚拟起始地址 进而通过偏移量找到库函数虚拟地址)


http://www.kler.cn/a/369367.html

相关文章:

  • Kmeans聚类算法简述
  • Sql执行较慢的排查方式
  • member access within null pointer of type ‘ListNode‘
  • 【IC每日一题】
  • ReactOS系统中平衡二叉树。给定地址超导其所属区块MmFindRegion()
  • XQT_UI 组件|02| 按钮 XPushButton
  • Segugio:一款针对恶意软件的进程执行跟踪与安全分析工具
  • Python爬虫:揭开商品详情的神秘面纱
  • 美的471温湿精控智能养鲜冰箱:创新科技,引领未来家居生活
  • 基于Python大数据的招聘数据分析及大屏可视化系统
  • 3.1.3 看对于“肮脏”页面的处理
  • 介绍 Docker 的基本概念和优势,以及在应用程序开发中的实际应用。(AI)
  • Spring-Day3
  • Redis 哨兵 问题
  • 深入理解 SQL 中的 WITH AS 语法
  • Flutter TextField和Button组件开发登录页面案例
  • Spark入门到实践
  • Codeforces Round 981 (Div. 3) A-D
  • JDK的下载
  • [C++]——红黑树(附源码)
  • 基于人脸识别系统设计与仿真-基于matlab
  • qt QApplication详解
  • labelimg使用教程
  • 【数据结构】二叉树——堆
  • 【STM32】SD卡
  • SpringBoot支付回调枚举+策略+工厂模式