Hack 虚拟内存系列(三):虚拟内存图解
字数 1601 2025-08-20 18:17:42

虚拟内存图解教学文档

1. 虚拟内存基础概念

虚拟内存是现代操作系统提供的一种内存管理技术,它为每个进程提供了一个连续的、私有的地址空间,使得每个进程都认为自己独占整个内存空间。

虚拟内存的主要特点:

  • 每个进程有自己独立的地址空间
  • 内存地址与实际物理内存分离
  • 提供内存保护机制
  • 允许使用比实际物理内存更大的地址空间

2. 实验环境准备

测试环境

  • 操作系统:Ubuntu 14.04 LTS
  • 内核版本:Linux 4.4.0-31-generic
  • 架构:x86_64

所需工具

  1. gcc:用于编译C程序
  2. objdump:用于反汇编可执行文件
  3. udcli(Udis86反汇编器):用于交互式反汇编
  4. bc:用于进制转换计算
  5. /proc/[pid]/maps:查看进程内存映射

3. 虚拟内存布局探索

3.1 栈(Stack)的定位

栈用于存储局部变量、函数调用信息等。通过打印局部变量的地址可以定位栈的位置。

#include <stdlib.h>
#include <stdio.h>

int main(void) {
    int a;
    printf("Address of a: %p\n", (void *)&a);
    return (EXIT_SUCCESS);
}

运行结果示例:

Address of a: 0x7ffd14b8bd9c

栈的特点:

  • 位于高地址区域
  • 向下增长(地址递减)
  • 存储局部变量、函数参数、返回地址等

3.2 堆(Heap)的定位

堆用于动态内存分配,通过malloc函数分配的内存位于堆中。

#include <stdlib.h>
#include <stdio.h>

int main(void) {
    int a;
    void *p;
    
    printf("Address of a: %p\n", (void *)&a);
    p = malloc(98);
    printf("Allocated space in the heap: %p\n", p);
    return (EXIT_SUCCESS);
}

运行结果示例:

Address of a: 0x7ffd4204c554
Allocated space in the heap: 0x901010

堆的特点:

  • 位于低地址区域(相对于栈)
  • 向上增长(地址递增)
  • 通过malloc/free管理内存

3.3 代码段(Text Segment)的定位

代码段存储程序的执行代码,通过打印main函数的地址可以定位代码段。

#include <stdlib.h>
#include <stdio.h>

int main(void) {
    int a;
    void *p;
    
    printf("Address of a: %p\n", (void *)&a);
    p = malloc(98);
    printf("Allocated space in the heap: %p\n", p);
    printf("Address of function main: %p\n", (void *)main);
    return (EXIT_SUCCESS);
}

运行结果示例:

Address of a: 0x7ffdced37d74
Allocated space in the heap: 0x2199010
Address of function main: 0x40060d

验证代码段内容:

objdump -M intel -j .text -d 程序名 | grep '<main>:' -A 10

代码段特点:

  • 位于最低的地址区域
  • 具有可执行权限
  • 存储程序指令

3.4 命令行参数和环境变量

命令行参数和环境变量存储在栈之上的区域。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int ac, char **av, char **env) {
    printf("Address of the array of arguments: %p\n", (void *)av);
    printf("Addresses of the arguments:\n");
    for (int i = 0; i < ac; i++) {
        printf("[%s]:%p ", av[i], av[i]);
    }
    printf("\nAddress of the array of environment variables: %p\n", (void *)env);
    return (EXIT_SUCCESS);
}

运行结果示例:

Address of the array of arguments: 0x7ffe7d6d8e98
Addresses of the arguments:
[./prog]:0x7ffe7d6da373 [Hello]:0x7ffe7d6da377
Address of the array of environment variables: 0x7ffe7d6d8ec0

内存布局顺序:

  1. argv数组(命令行参数指针数组)
  2. env数组(环境变量指针数组)
  3. 实际的参数字符串
  4. 实际的环境变量字符串

3.5 栈增长方向验证

通过比较不同函数中局部变量的地址验证栈的增长方向。

#include <stdlib.h>
#include <stdio.h>

void f(void) {
    int a, b, c;
    printf("[f] Addresses: a=%p, b=%p, c=%p\n", &a, &b, &c);
}

int main(void) {
    int a;
    printf("[main] Address of a: %p\n", &a);
    f();
    return (EXIT_SUCCESS);
}

运行结果示例:

[main] Address of a: 0x7ffdae53ea4c
[f] Addresses: a=0x7ffdae53ea04, b=0x7ffdae53ea08, c=0x7ffdae53ea0c

结论:栈向低地址方向增长(后调用的函数变量地址更小)

4. /proc/[pid]/maps分析

通过/proc文件系统查看进程的完整内存映射。

#include <stdlib.h>
#include <stdio.h>

int main(void) {
    printf("My PID: %d\n", getpid());
    getchar();  // 暂停程序,查看/proc/[pid]/maps
    return (EXIT_SUCCESS);
}

示例maps输出:

00400000-00401000 r-xp 00000000 08:01 171828 /home/user/prog
00600000-00601000 r--p 00000000 08:01 171828 /home/user/prog
00601000-00602000 rw-p 00001000 08:01 171828 /home/user/prog
02050000-02071000 rw-p 00000000 00:00 0 [heap]
7f68caa1c000-7f68cabd6000 r-xp 00000000 08:01 136253 /lib/x86_64-linux-gnu/libc-2.19.so
7fff16c62000-7fff16c83000 rw-p 00000000 00:00 0 [stack]

关键区域解释:

  1. 00400000-00401000:代码段,可执行权限
  2. 00600000-00601000:只读数据段
  3. 00601000-00602000:可读写数据段
  4. 02050000-02071000:堆区域
  5. 7fff16c62000-7fff16c83000:栈区域

5. 完整的虚拟内存布局图

基于实验结果,64位Linux系统的进程虚拟内存布局如下(从高地址到低地址):

+-----------------------+
| 内核空间               | 
+-----------------------+
| 栈 (向下增长)          | 0x7fff16c62000-7fff16c83000
| 环境变量和命令行参数    |
+-----------------------+
| 共享库映射区           | 
+-----------------------+
| 堆 (向上增长)          | 0x02050000-02071000
+-----------------------+
| 未初始化数据段 (bss)    | 0x00601000-00602000
| 已初始化数据段 (data)   | 0x00600000-00601000
+-----------------------+
| 代码段 (text)          | 0x00400000-00401000
+-----------------------+

6. 关键发现与总结

  1. 栈和堆的位置

    • 栈位于高地址区域,向下增长
    • 堆位于低地址区域,向上增长
    • 两者之间有巨大的地址空间用于共享库映射等
  2. 内存分配细节

    • malloc分配的内存地址不是从堆的最开始处开始
    • 堆管理器会使用前16字节左右的空间用于管理信息
  3. 可执行文件分段加载

    • 代码段(.text):只读可执行
    • 数据段(.data):已初始化全局变量
    • BSS段(.bss):未初始化全局变量
    • 每个段加载到内存的不同区域,具有不同权限
  4. 命令行参数和环境变量

    • 存储在栈之上的区域
    • 实际字符串存储在更高地址处
    • argv和env数组在内存中相邻
  5. 验证方法

    • 使用/proc/[pid]/maps验证内存区域
    • 通过打印变量地址定位内存区域
    • 使用反汇编工具验证代码段内容

7. 进一步探索方向

  1. 堆分配的内部机制(malloc实现原理)
  2. 内存分页机制和页表
  3. 共享库的动态链接过程
  4. 内存映射文件(mmap)的实现
  5. 虚拟内存与物理内存的转换机制

通过本实验,我们深入了解了Linux进程的虚拟内存布局,掌握了通过编程和系统工具分析内存布局的方法,为后续深入研究操作系统内存管理机制打下了坚实基础。

虚拟内存图解教学文档 1. 虚拟内存基础概念 虚拟内存是现代操作系统提供的一种内存管理技术,它为每个进程提供了一个连续的、私有的地址空间,使得每个进程都认为自己独占整个内存空间。 虚拟内存的主要特点: 每个进程有自己独立的地址空间 内存地址与实际物理内存分离 提供内存保护机制 允许使用比实际物理内存更大的地址空间 2. 实验环境准备 测试环境 操作系统:Ubuntu 14.04 LTS 内核版本:Linux 4.4.0-31-generic 架构:x86_ 64 所需工具 gcc :用于编译C程序 objdump :用于反汇编可执行文件 udcli (Udis86反汇编器):用于交互式反汇编 bc :用于进制转换计算 /proc/[ pid]/maps :查看进程内存映射 3. 虚拟内存布局探索 3.1 栈(Stack)的定位 栈用于存储局部变量、函数调用信息等。通过打印局部变量的地址可以定位栈的位置。 运行结果示例: 栈的特点: 位于高地址区域 向下增长(地址递减) 存储局部变量、函数参数、返回地址等 3.2 堆(Heap)的定位 堆用于动态内存分配,通过malloc函数分配的内存位于堆中。 运行结果示例: 堆的特点: 位于低地址区域(相对于栈) 向上增长(地址递增) 通过malloc/free管理内存 3.3 代码段(Text Segment)的定位 代码段存储程序的执行代码,通过打印main函数的地址可以定位代码段。 运行结果示例: 验证代码段内容: 代码段特点: 位于最低的地址区域 具有可执行权限 存储程序指令 3.4 命令行参数和环境变量 命令行参数和环境变量存储在栈之上的区域。 运行结果示例: 内存布局顺序: 栈 argv数组(命令行参数指针数组) env数组(环境变量指针数组) 实际的参数字符串 实际的环境变量字符串 3.5 栈增长方向验证 通过比较不同函数中局部变量的地址验证栈的增长方向。 运行结果示例: 结论:栈向低地址方向增长(后调用的函数变量地址更小) 4. /proc/[ pid ]/maps分析 通过/proc文件系统查看进程的完整内存映射。 示例maps输出: 关键区域解释: 00400000-00401000 :代码段,可执行权限 00600000-00601000 :只读数据段 00601000-00602000 :可读写数据段 02050000-02071000 :堆区域 7fff16c62000-7fff16c83000 :栈区域 5. 完整的虚拟内存布局图 基于实验结果,64位Linux系统的进程虚拟内存布局如下(从高地址到低地址): 6. 关键发现与总结 栈和堆的位置 : 栈位于高地址区域,向下增长 堆位于低地址区域,向上增长 两者之间有巨大的地址空间用于共享库映射等 内存分配细节 : malloc分配的内存地址不是从堆的最开始处开始 堆管理器会使用前16字节左右的空间用于管理信息 可执行文件分段加载 : 代码段(.text):只读可执行 数据段(.data):已初始化全局变量 BSS段(.bss):未初始化全局变量 每个段加载到内存的不同区域,具有不同权限 命令行参数和环境变量 : 存储在栈之上的区域 实际字符串存储在更高地址处 argv和env数组在内存中相邻 验证方法 : 使用/proc/[ pid ]/maps验证内存区域 通过打印变量地址定位内存区域 使用反汇编工具验证代码段内容 7. 进一步探索方向 堆分配的内部机制(malloc实现原理) 内存分页机制和页表 共享库的动态链接过程 内存映射文件(mmap)的实现 虚拟内存与物理内存的转换机制 通过本实验,我们深入了解了Linux进程的虚拟内存布局,掌握了通过编程和系统工具分析内存布局的方法,为后续深入研究操作系统内存管理机制打下了坚实基础。