检查 mmap 的地址是否正确
我正在编写一个高负载守护进程,它应该在 FreeBSD 8.0 和 Linux 上运行。守护进程的主要目的是传递由其标识符请求的文件。通过向数据库请求将标识符转换为本地文件名/文件大小。然后我使用顺序 mmap()
调用通过 send()
传递文件块。
然而,有时数据库中的文件大小和文件系统上的文件大小不匹配(实际大小<数据库中的大小)。在这种情况下,我已经发送了所有实际数据块,并且当映射下一个数据块时 - mmap 没有返回错误,只是返回通常的地址(我也检查了 errno 变量,它在 mmap 之后等于零)。当守护进程尝试发送此块时,它会出现分段错误。 (此行为肯定在 FreeBSD 8.0 amd64 上发出)
我在打开之前使用安全检查来确保 stat()
调用的大小。然而现实生活向我表明,在极少数情况下仍然可能会出现段错误。
所以,我的问题是有没有办法在取消引用指针之前检查指针是否可访问?当我在 gdb 中打开 core 时,gdb 说给定的地址超出范围。 也许有人可以提出另一种解决方案。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#define FILENAME "./datafile"
int main()
{
unsigned long i, j;
srand(time(NULL));
unsigned long pagesize = sysconf(_SC_PAGESIZE);
unsigned long basesize = 4 * pagesize;
unsigned long cropsize = 2 * pagesize;
// create 4*pagesize sized file
int f = creat(FILENAME, 0644);
for (i = 0; i < basesize; i++) {
unsigned char c = (unsigned char)rand();
if (write(f, &c, 1) < 1) { perror("write"); break; }
}
close(f);
f = open(FILENAME, O_RDONLY);
// walk trough file
unsigned char xor = 0;
unsigned long offset = 0;
for (j = 0; j < 4; j++) {
// trunc file to 2*pagesize
if (j == 2) truncate(FILENAME, cropsize);
char *data = mmap(NULL, pagesize, PROT_READ, MAP_PRIVATE, f, offset);
if (data == MAP_FAILED) { perror("mmap"); break; }
printf("mmap: %lu@%lu for %i\n", pagesize, offset, f);
for (i = 0; i < pagesize; i++) xor ^= data[i];
offset += pagesize;
}
close(f);
return 0;
}
I'm writing a high-loaded daemon that should be run on the FreeBSD 8.0 and on Linux as well. The main purpose of daemon is to pass files that are requested by their identifier. Identifier is converted into local filename/file size via request to db. And then I use sequential mmap()
calls to pass file blocks with send()
.
However sometimes there are mismatch of filesize in db and filesize on filesystem (realsize < size in db). In this situation I've sent all real data blocks and when next data block is mapped -- mmap returns no errors, just usual address (I've checked errno variable also, it's equal to zero after mmap). And when daemon tries to send this block it gets Segmentation Fault. (This behaviour is guarantedly issued on FreeBSD 8.0 amd64)
I was using safe check before open to ensure size with stat()
call. However real life shows to me that segfault still can be raised in rare situtaions.
So, my question is there a way to check whether pointer is accessible before dereferencing it? When I've opened core in gdb, gdb says that given address is out of bound.
Probably there is another solution somebody can propose.
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#define FILENAME "./datafile"
int main()
{
unsigned long i, j;
srand(time(NULL));
unsigned long pagesize = sysconf(_SC_PAGESIZE);
unsigned long basesize = 4 * pagesize;
unsigned long cropsize = 2 * pagesize;
// create 4*pagesize sized file
int f = creat(FILENAME, 0644);
for (i = 0; i < basesize; i++) {
unsigned char c = (unsigned char)rand();
if (write(f, &c, 1) < 1) { perror("write"); break; }
}
close(f);
f = open(FILENAME, O_RDONLY);
// walk trough file
unsigned char xor = 0;
unsigned long offset = 0;
for (j = 0; j < 4; j++) {
// trunc file to 2*pagesize
if (j == 2) truncate(FILENAME, cropsize);
char *data = mmap(NULL, pagesize, PROT_READ, MAP_PRIVATE, f, offset);
if (data == MAP_FAILED) { perror("mmap"); break; }
printf("mmap: %lu@%lu for %i\n", pagesize, offset, f);
for (i = 0; i < pagesize; i++) xor ^= data[i];
offset += pagesize;
}
close(f);
return 0;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
当然,我无法从这里证明这一点,但我强烈怀疑您的代码中存在记账错误。如果您调用 mmap 并传入一个大小,并且成功,您不应该收到 SIGSEGV。
我建议您在调查中应用 valgrind。
在许多 Linux 系统上,/proc/PID/maps 将显示哪些区域映射了哪些访问权限。
Of course I can't prove it from here, but I strongly suspect that you just have a book-keeping bug in your code. If you call mmap and pass in a size, and it succeeds, you shouldn't get SIGSEGV.
I recommend that you apply valgrind to your investigation.
On many linux systems /proc/PID/maps will show you what regions are mapped with what access permissions.