由 mmap() 分配的内存 space 如何防止被 'new' 调用分配?
How can a memory space allocated by mmap() protected from getting allocated by 'new' calls?
我正在使用
在特定地址分配 space
mmap(...,PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,...)
但是,如果我调用类似 new char[large_number]
的内容,则创建的数组会与先前分配的内存段重叠。所以我的问题是我应该怎么做才能保护我之前尝试分配的内存段。
编辑:
对于以下代码,
char* base_address = (char*)malloc(sizeof(long));
int page_size = getpagesize();
size_t length = page_size;
char* selected_address = (char*)base_address + (page_size - ((long)base_address % page_size));
char* allocated_address = (char*) mmap(selected_address, page_size * 4, PROT_WRITE | PROT_READ
, MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
int* a = new int[page_size / 4];
// mprotect(allocated_address, page_size * 4, PROT_READ | PROT_WRITE);
int* b = new (allocated_address) int[page_size / 2];
int* c = new (allocated_address + page_size / 2 ) int[page_size/2];
int* d = (int*)malloc(page_size);
cout << "base_address:" << (int*)base_address << " selected_address:" << (int*)selected_address << " allocated_address:" << (int*)allocated_address << endl;
cout << "a=" << a << " end=" << &a[page_size/4 - 1] << endl;
cout << "b=" << b << " end=" << &b[page_size/2 - 1] << endl;
cout << "c=" << c << " end=" << &c[page_size/2 - 1] << endl;
cout << "d=" << d << " end=" << &d[page_size - 1] << endl;
我正在
base_address:0x603010 selected_address:0x604000 allocated_address:0x604000
a=0x603030 end=0x60402c
b=0x604000 end=0x605ffc
c=0x604800 end=0x6067fc
d=0x604040 end=0x60803c
不应该在 mmap 之外分配的 a 和 d 段分配 space?
mmap(...,PROT_WRITE | PROT_READ, MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED,...)
内存映射只能是 MAP_SHARED
或 MAP_ANONYMOUS
,但不能同时是两者。
But if I call something like new char[large_number]
that array is created overlapping the previously allocated memory segment.
这是不可能的。您忽略 mmap
调用的 return 值,调用很可能会失败。
来自我系统上 mmap 的手册页 (OS X)
If MAP_FIXED is specified in flags, the system will try to place the mapping at the specified address, possibly removing a mapping that already exists at that location.
你强迫 mmap()
在第一页映射到 malloc()
交给你的地址上方。 new
很可能调用 malloc()
来获取内存块,并且内存分配器可能认为它拥有您尝试映射到的页面。您没有告诉分配器它不能使用该页面,所以它会继续使用。
不指定MAP_FIXED
。只需使用它返回给您的地址即可,无需指定 MAP_FIXED
。
您的实施平台可能通过跟踪堆使用的最高页面地址,然后在需要时简单地增加它来隐式地增加堆。第一次访问以前未使用的页面会产生页面错误,然后 O/S 从错误地址开始自动增加进程的虚拟内存地址 space。
在 Linux 上,请参阅 sbrk(2) 手册页以获得此过程的更完整描述。
所以这里发生的事情是,您明确请求了一些 mmap()ed 到 C 库接下来将使用的地址,用于分配的内存。 C 库愉快地继续增加数据段,侵入你的 mmap-ed space.
这是"Doctor, it hurts when I wave my arm like this."的一个简单案例; "Well, don't do wave your arm like that."
Linux 上的 mmap() 手册页对 mmap() 的第一个参数提出了警告:
If addr is NULL, then the kernel chooses the address at which to create
the mapping; this is the most portable method of creating a new map‐ping.
NULL 地址导致内核将您的新内存段映射到一个地址,该地址远离任何其他地址,也远离进程的数据段,并且两者不会相互干扰。
如果像您的情况一样,您在虚拟地址 space 中明确 mmap()ing 了一个特定地址,您有责任避免 运行 陷入麻烦,并且知道您的流程中到底发生了什么,包括 C 库正在做什么。
而且我还应该注意,为了计算新的映射地址,您正在计算一个指针地址,它超过了当前分配的数组的末尾。
严格来说,这是未定义的行为。在这一点上,就严格 C/C++ 合规性而言,所有的赌注都落空了。
您的密码是 undefined behavior。您正在 mmap
-ing 由 new
或 malloc
给出的地址。你不应该这样做(并且它通常应该失败并显示 MAP_FIXED
因为 new
或 malloc
返回的地址 很少 页对齐)。
我猜你正在使用 Linux。所以代码:
char* allocated_address
= (char*) mmap(NULL, page_size * 4,
PROT_WRITE | PROT_READ,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (allocated_address == MMAP_FAILED)
{ perror("mmap"); exit(EXIT_FAILURE); };
int* b = new ((void*)allocated_address) int[page_size / 2];
int* c = new ((void*)(allocated_address + (page_size / 2)*sizeof(int)))
int[page_size/2];
注意数组 int[page_size/2]
的字节大小是 sizeof(int)*(page_size/2)
。在我的系统上(Linux/x86-64/Debian/Sid)sizeof(int)
是 4,page_size
是 4096。
仔细阅读mmap(2). Because of ASLRmmap
的结果(没有MAP_FIXED
,你几乎不应该使用它,除非映射再次一个以前映射的地址段)通常无法从一个运行下一个复制。
顺便说一句,当您的程序 运行ning 为 pid 1234 时,运行 cat /proc/1234/maps
在单独的终端中(来自 inside 您的程序您甚至可以逐行顺序阅读并解析 /proc/self/maps
);它会显示进程 1234 的地址 space。阅读 proc(5) and Advanced Linux Programming
我正在使用
在特定地址分配 spacemmap(...,PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,...)
但是,如果我调用类似 new char[large_number]
的内容,则创建的数组会与先前分配的内存段重叠。所以我的问题是我应该怎么做才能保护我之前尝试分配的内存段。
编辑: 对于以下代码,
char* base_address = (char*)malloc(sizeof(long));
int page_size = getpagesize();
size_t length = page_size;
char* selected_address = (char*)base_address + (page_size - ((long)base_address % page_size));
char* allocated_address = (char*) mmap(selected_address, page_size * 4, PROT_WRITE | PROT_READ
, MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
int* a = new int[page_size / 4];
// mprotect(allocated_address, page_size * 4, PROT_READ | PROT_WRITE);
int* b = new (allocated_address) int[page_size / 2];
int* c = new (allocated_address + page_size / 2 ) int[page_size/2];
int* d = (int*)malloc(page_size);
cout << "base_address:" << (int*)base_address << " selected_address:" << (int*)selected_address << " allocated_address:" << (int*)allocated_address << endl;
cout << "a=" << a << " end=" << &a[page_size/4 - 1] << endl;
cout << "b=" << b << " end=" << &b[page_size/2 - 1] << endl;
cout << "c=" << c << " end=" << &c[page_size/2 - 1] << endl;
cout << "d=" << d << " end=" << &d[page_size - 1] << endl;
我正在
base_address:0x603010 selected_address:0x604000 allocated_address:0x604000
a=0x603030 end=0x60402c
b=0x604000 end=0x605ffc
c=0x604800 end=0x6067fc
d=0x604040 end=0x60803c
不应该在 mmap 之外分配的 a 和 d 段分配 space?
mmap(...,PROT_WRITE | PROT_READ, MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED,...)
内存映射只能是 MAP_SHARED
或 MAP_ANONYMOUS
,但不能同时是两者。
But if I call something like
new char[large_number]
that array is created overlapping the previously allocated memory segment.
这是不可能的。您忽略 mmap
调用的 return 值,调用很可能会失败。
来自我系统上 mmap 的手册页 (OS X)
If MAP_FIXED is specified in flags, the system will try to place the mapping at the specified address, possibly removing a mapping that already exists at that location.
你强迫 mmap()
在第一页映射到 malloc()
交给你的地址上方。 new
很可能调用 malloc()
来获取内存块,并且内存分配器可能认为它拥有您尝试映射到的页面。您没有告诉分配器它不能使用该页面,所以它会继续使用。
不指定MAP_FIXED
。只需使用它返回给您的地址即可,无需指定 MAP_FIXED
。
您的实施平台可能通过跟踪堆使用的最高页面地址,然后在需要时简单地增加它来隐式地增加堆。第一次访问以前未使用的页面会产生页面错误,然后 O/S 从错误地址开始自动增加进程的虚拟内存地址 space。
在 Linux 上,请参阅 sbrk(2) 手册页以获得此过程的更完整描述。
所以这里发生的事情是,您明确请求了一些 mmap()ed 到 C 库接下来将使用的地址,用于分配的内存。 C 库愉快地继续增加数据段,侵入你的 mmap-ed space.
这是"Doctor, it hurts when I wave my arm like this."的一个简单案例; "Well, don't do wave your arm like that."
Linux 上的 mmap() 手册页对 mmap() 的第一个参数提出了警告:
If addr is NULL, then the kernel chooses the address at which to create the mapping; this is the most portable method of creating a new map‐ping.
NULL 地址导致内核将您的新内存段映射到一个地址,该地址远离任何其他地址,也远离进程的数据段,并且两者不会相互干扰。
如果像您的情况一样,您在虚拟地址 space 中明确 mmap()ing 了一个特定地址,您有责任避免 运行 陷入麻烦,并且知道您的流程中到底发生了什么,包括 C 库正在做什么。
而且我还应该注意,为了计算新的映射地址,您正在计算一个指针地址,它超过了当前分配的数组的末尾。
严格来说,这是未定义的行为。在这一点上,就严格 C/C++ 合规性而言,所有的赌注都落空了。
您的密码是 undefined behavior。您正在 mmap
-ing 由 new
或 malloc
给出的地址。你不应该这样做(并且它通常应该失败并显示 MAP_FIXED
因为 new
或 malloc
返回的地址 很少 页对齐)。
我猜你正在使用 Linux。所以代码:
char* allocated_address
= (char*) mmap(NULL, page_size * 4,
PROT_WRITE | PROT_READ,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (allocated_address == MMAP_FAILED)
{ perror("mmap"); exit(EXIT_FAILURE); };
int* b = new ((void*)allocated_address) int[page_size / 2];
int* c = new ((void*)(allocated_address + (page_size / 2)*sizeof(int)))
int[page_size/2];
注意数组 int[page_size/2]
的字节大小是 sizeof(int)*(page_size/2)
。在我的系统上(Linux/x86-64/Debian/Sid)sizeof(int)
是 4,page_size
是 4096。
仔细阅读mmap(2). Because of ASLRmmap
的结果(没有MAP_FIXED
,你几乎不应该使用它,除非映射再次一个以前映射的地址段)通常无法从一个运行下一个复制。
顺便说一句,当您的程序 运行ning 为 pid 1234 时,运行 cat /proc/1234/maps
在单独的终端中(来自 inside 您的程序您甚至可以逐行顺序阅读并解析 /proc/self/maps
);它会显示进程 1234 的地址 space。阅读 proc(5) and Advanced Linux Programming