概述

WIN32程序每个进程都有4GB的虚拟空间,低2G为用户空间,是相互独立的,高2G为内核空间(从0x80000000开始的),所有进程共享。

x86架构将内存以每4KB为一页,称为页式管理,可供使用的物理内存:MmNumberOfPhysicalPages * 4 = 物理内存。用户空间不是真实存在的,只有需要使用它的时候操作系统才会在物理页上真正分配

对于不经常使用的物理页,操作系统会将其储存到虚拟内存(这个虚拟内存在硬盘上,不是用户的虚拟内存)上,再次需要访问时缺页异常,操作系统会处理异常为虚拟内存挂物理页。


私有内存

私有内存只有特定的进程可以访问,独享物理页

1
2
3
4
5
6
7
8
9
LPVOID VirtualAlloc(
//内存首地址
LPVOID lpAddress,
SIZE_T dwSize,
//类型
DWORD flAllocationType,
//初始保护属性
DWORD flProtect
);

内存大小一般分配为页的整数倍,一页为4KB,也就是0x1000,此处分配了2页内存。

当类型为MEM_RESERVE时,操作系统会分配线性地址,但不实际分配物理页,当为MEM_COMMIT时会分配物理页。保护属性可以允许内存是否可读,可写或可执行,具体宏可查看文档说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

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

#define BUF_SIZE 0x1000

int main() {

LPVOID p = VirtualAlloc(
NULL,
BUF_SIZE * 2,
MEM_COMMIT,
PAGE_READWRITE
);

//清空线性地址和物理页
VirtualFree(p, 0, MEM_RELEASE);

//保留线性地址,清空物理页
//VirtualFree(p, BUF_SIZE * 2, MEM_COMMIT);

system("pause");

return 0;
}


C语言中的malloc和C++里的new(new就是malloc加上一个构造函数),并没有真正申请内存,new也是调用malloc实现的,反汇编跟踪malloc会发现其最终会调用HeapAlloc,并没有进内核。不管是堆还是栈本质上都是私有内存。


映射内存

除了堆栈和VirtualAlloc分配的内存,其他的都是映射内存,所有进程都可以访问。

如果同名称内核对象已存在,其他进程通过OpenFileMapping也可以获取相同的mapfile内核对象,但关联线性地址之后返回的虚拟内存地址不一定相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

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

#define BUF_SIZE 0x1000
#define MAPFILENAME TEXT("共享内存")

HANDLE g_hMapFile;
LPTSTR g_lpBuff;

int main() {

/*
// 创建物理页
HANDLE CreateFileMappingA(
//文件句柄,当为INVALID_HANDLE_VALUE时不关联文件
HANDLE hFile,
//安全描述符
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
//物理页的保护模式
DWORD flProtect,
//大小,高32位,64位程序需要用到
DWORD dwMaximumSizeHigh,
//大小,底32位
DWORD dwMaximumSizeLow,
//内核对象的名称
LPCSTR lpName
);*/

/*
//将物理页与线性地址映射
LPVOID MapViewOfFile(
//fileMapping对象的句柄
HANDLE hFileMappingObject,
//线性地址的保护模式,只能比物理页的保护模式更严格,
//在内核调试工具中显示的属性
DWORD dwDesiredAccess,
//映射物理页的偏移,高32位,64位程序需要用到
DWORD dwFileOffsetHigh,
//映射物理页的偏移,底32位
DWORD dwFileOffsetLow,
//映射物理页的大小
SIZE_T dwNumberOfBytesToMap
); */

getchar();

//内核对象,物理页
g_hMapFile= CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUF_SIZE, MAPFILENAME);

//将物理页与线性地址进行关联
g_lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);

*(PDWORD)g_lpBuff = 0x12345678;

printf("%p", g_lpBuff);

getchar();

//关闭映射
UnmapViewOfFile(g_lpBuff);

//关闭句柄,当所有进程都不使用时,操作系统会将mapfile内核对象从物理页中清除。
CloseHandle(g_hMapFile);

return 0;
}



文件系统

相关API索引

卷相关

1
2
3
4
5
6
7
8
// 返回值二进制从右往左分别标识A开始的卷是否存在
GetLogIcalDrives();
//获取一个卷的盘符的字符串
GetLogIcalDriveStrings(buffsize,buf);
//获取卷的类型
GetDriveType()
//获取卷的详细信息
GetVolumeInformation();

文件夹相关

1
2
3
4
5
CreateDirectory();//创建目录
RemoveDirectory();//删除目录
MoveFile(); //修改目录名称.
GetCurrentDirectory(); //获取进程当前目录
SetCurrentDirectory(); //设置进程当前目录.

文件操作相关

1
2
3
4
5
6
7
8
9
CreateFile( ) //创建文件
DeleteFile(); //删除文件
CloseHandle(); //关闭文件句柄
GetFileSize(); //获取文件大小.
ReadFile(); //读文件
SetFilePointer(); //设置文件指针
WriteFile(); //写文件
CopyFile(); //拷贝文件
FindFirstFile(); //查找文件

内存映射文件

将该文件通过物理页映射到虚拟内存,用虚拟内存对文件进行操作,不直接对本体进行IO操作,就像读写内存一样读写文件。操作大型文件时性能会很高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

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

#define MAPPINGNAME TEXT("共享内存")

DWORD mappingFile(LPCWSTR lpcFile) {
HANDLE hFile;
HANDLE hMapFile;
DWORD dwFileMapSize;
LPVOID lpAddr;

//创建文件句柄
hFile = CreateFile(
lpcFile,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);

if (hFile == INVALID_HANDLE_VALUE) {
CloseHandle(hFile);
printf("创建失败:%d\n", GetLastError());
return 0;
}

//申请物理页
hMapFile = CreateFileMapping(
hFile,
NULL,
PAGE_READWRITE,
0,
0,
MAPPINGNAME
);

if (hMapFile == NULL) {
CloseHandle(hFile);
printf("映射内存失败:%d\n", GetLastError());
return 0;
}

//关联线性地址
lpAddr = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);

if (lpAddr == NULL) {
CloseHandle(hFile);
CloseHandle(hMapFile);
printf("关联线性地址失败:%d\n", GetLastError());
return 0;
}


//可以直接像读内存一样读取文件
DWORD dwTest1 = *(PDWORD)lpAddr;
DWORD dwTest2 = *(PDWORD)lpAddr + 0x1B8FC;
printf("%x %x", dwTest1, dwTest2);

//写入文件
*(PDWORD)lpAddr = 0x41414141;

/*
强制刷新缓存
否则只有在mapfile句柄关闭时才会改变文件,此处刷新4字节*/
//FlushViewOfFile(lpAddr, 4);

//关闭资源
UnmapViewOfFile(lpAddr);
CloseHandle(hMapFile);
CloseHandle(hFile);

}


int main() {

mappingFile(TEXT("C:\\t.exe"));

return 0;
}



写拷贝

MapViewOfFile关联线性地址时,如果第二个参数为FILE_MAP_COPY则为写拷贝,当写入文件时,操作系统会将一个副本放到一个新的物理页,从而不影响其他的进程。

调试工具下断点时本质上是将某一地址的指令修改为0xCC,如果不是写拷贝的话其他程序也会处于调试状态。