Hack 虚拟内存系列(三):虚拟内存图解
字数 1601 2025-08-20 18:17:42
虚拟内存图解教学文档
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)的定位
栈用于存储局部变量、函数调用信息等。通过打印局部变量的地址可以定位栈的位置。
#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
内存布局顺序:
- 栈
- argv数组(命令行参数指针数组)
- env数组(环境变量指针数组)
- 实际的参数字符串
- 实际的环境变量字符串
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]
关键区域解释:
- 00400000-00401000:代码段,可执行权限
- 00600000-00601000:只读数据段
- 00601000-00602000:可读写数据段
- 02050000-02071000:堆区域
- 7fff16c62000-7fff16c83000:栈区域
5. 完整的虚拟内存布局图
基于实验结果,64位Linux系统的进程虚拟内存布局如下(从高地址到低地址):
+-----------------------+
| 内核空间 |
+-----------------------+
| 栈 (向下增长) | 0x7fff16c62000-7fff16c83000
| 环境变量和命令行参数 |
+-----------------------+
| 共享库映射区 |
+-----------------------+
| 堆 (向上增长) | 0x02050000-02071000
+-----------------------+
| 未初始化数据段 (bss) | 0x00601000-00602000
| 已初始化数据段 (data) | 0x00600000-00601000
+-----------------------+
| 代码段 (text) | 0x00400000-00401000
+-----------------------+
6. 关键发现与总结
-
栈和堆的位置:
- 栈位于高地址区域,向下增长
- 堆位于低地址区域,向上增长
- 两者之间有巨大的地址空间用于共享库映射等
-
内存分配细节:
- malloc分配的内存地址不是从堆的最开始处开始
- 堆管理器会使用前16字节左右的空间用于管理信息
-
可执行文件分段加载:
- 代码段(.text):只读可执行
- 数据段(.data):已初始化全局变量
- BSS段(.bss):未初始化全局变量
- 每个段加载到内存的不同区域,具有不同权限
-
命令行参数和环境变量:
- 存储在栈之上的区域
- 实际字符串存储在更高地址处
- argv和env数组在内存中相邻
-
验证方法:
- 使用/proc/[pid]/maps验证内存区域
- 通过打印变量地址定位内存区域
- 使用反汇编工具验证代码段内容
7. 进一步探索方向
- 堆分配的内部机制(malloc实现原理)
- 内存分页机制和页表
- 共享库的动态链接过程
- 内存映射文件(mmap)的实现
- 虚拟内存与物理内存的转换机制
通过本实验,我们深入了解了Linux进程的虚拟内存布局,掌握了通过编程和系统工具分析内存布局的方法,为后续深入研究操作系统内存管理机制打下了坚实基础。