【问题标题】:Getting segmentation fault SIGSEGV in memcpy after mmap在 mmap 之后在 memcpy 中获取分段错误 SIGSEGV
【发布时间】:2012-10-27 13:35:22
【问题描述】:

我编写了一个简单的 Android 原生函数,它获取文件名和更多参数,并通过映射 (mmap) 它的内存来读取文件。

因为是mmap,所以我真的不需要调用“read()”,所以我只是从mmap()返回的地址中memcpy()。

但是,我在某个地方得到了 SIGSEGV,可能是因为我试图访问我不允许的内存。但是我不明白为什么,我已经要求映射所有文件的内存!

我附上了我的代码和我得到的错误:

编辑

我修复了终止循环,但在读取 25001984 个字节后仍然得到 SIGSEGV。 该函数适用于这些参数: jn_bytes = 100,000,000 jbuffer_size = 8192 jshared=jpopulate=jadvice=0

void Java_com_def_benchmark_Benchmark_testMmapRead(JNIEnv* env, jobject javaThis,
        jstring jfile_name, unsigned int jn_bytes, unsigned int jbuffer_size, jboolean jshared, jboolean jpopulate, jint jadvice) {
    const char *file_name = env->GetStringUTFChars(jfile_name, 0);

    /* *** start count  *** */
    int fd = open(file_name, O_RDONLY);
    //get the size of the file
    size_t length = lseek(fd, 0L, SEEK_END);
    lseek(fd, 0L, SEEK_SET);
    length = length>jn_bytes?jn_bytes:length;

    // man 2 mmap: MAP_POPULATE is only supported for private mappings since Linux 2.6.23
    int flags =  0;
    if (jshared) flags |= MAP_SHARED; else flags |= MAP_PRIVATE;
    if(jpopulate) flags |= MAP_POPULATE;
    //int flags = MAP_PRIVATE;
    int *  addr = reinterpret_cast<int *>(mmap(NULL, length , PROT_READ, flags , fd, 0));
    if (addr == MAP_FAILED) {
        __android_log_write(ANDROID_LOG_ERROR, "NDK_FOO_TAG", strerror(errno));
        return;
    }
    int * initaddr = addr;
    if(jadvice > 0)
        madvise(addr,length,jadvice==1?(MADV_SEQUENTIAL|MADV_WILLNEED):(MADV_DONTNEED));
    close(fd);

    char buffer[jbuffer_size];
    void *ret_val = buffer;
    int read_length = length;
    while(ret_val == buffer || read_length<jbuffer_size) {
/*****GETTING SIGSEGV SOMWHERE HERE IN THE WHILE************/
        ret_val = memcpy(buffer, addr,jbuffer_size);
        addr+=jbuffer_size;
        read_length -= jbuffer_size;
    }
    munmap(initaddr,length);
    /* stop count */
    env->ReleaseStringUTFChars(jfile_name, file_name);
}

和错误日志:

    15736^done
(gdb) 
15737 info signal SIGSEGV
&"info signal SIGSEGV\n"
~"Signal        Stop\tPrint\tPass to program\tDescription\n"
~"SIGSEGV       Yes\tYes\tYes\t\tSegmentation fault\n"
15737^done
(gdb) 
15738-stack-list-arguments 0 0 0
15738^done,stack-args=[frame={level="0",args=[]}]
(gdb) 
15739-stack-list-locals 0
15739^done,locals=[]
(gdb) 

【问题讨论】:

  • 文件有多大?系统可以在这么大的堆栈上创建 VLA 吗?使用malloc() 而不是char buffer[jbuffer_size];,但不要忘记也释放它。
  • 我要求 mmap 的长度为 100MB,我不明白为什么数组分配方法(堆栈/堆)会解决这个问题,因为几个第一个 memcpy 工作得很好(在调试时)但是之后(当我删除断点并让它运行时)我得到了 SIGSEGV。意味着我到达了一些我不应该访问的地址?
  • 在我工作的机器上,我可以通过 VLA 分配的内容肯定有上限,而且我认为我不会冒险尝试 100 MiB 范围内的 VLA — 至少,我会确保它可靠地工作。动态内存分配(通过malloc() 等)是另一回事,尽管即使在那里你也必须担心系统不会报告过度使用内存的错误,直到你去访问他们所说的可用但不是什么时候时候到了。
  • 现在我使用关于 read_length 的报告运行它,所以我看到我最后一次获得有关 read_length 的日志时始终为 74998016,这意味着如果我将它从 100000000(这是原始长度)减少) 我得到 25001984 (~ 24 MB) 这意味着我已经阅读了 ~ 24 MB 直到 SIGSEGV。它有帮助吗?我在 Android 2.3 上运行。关于你提到的 VLA,如果 ~100MB 太多了 - mmap 应该为我的请求返回一些错误,不是吗?如果您谈论我的缓冲区 - size=8196,为什么 memcpy 第一次成功?
  • VLA 是buffer,与mmap() 无关。我不是安卓开发者;我无法回答在任何版本的 Android 上哪些功能有效或无效。如果我在调试这个问题,我会去测试 VLA 是否在大尺寸下工作。例如,我可以分配一个 1 MiB、2 MiB、4 MiB、... 128 MiB VLA,然后安全地使用它(memset() 可以测试)。如果所有这些都很好,那么我对你的问题的猜测是错误的,你可以忽略我。如果其中任何一个失败了,那么也许我的问题是正确的。然后你会怎么做取决于你发现了什么。

标签: android c++ segmentation-fault mmap memcpy


【解决方案1】:

这里有个大问题:

    addr+=jbuffer_size;

您将addr 增加了sizeof(int) * jbuffer_size 字节,而您只想增加jbuffer_size 字节。

我的猜测是 sizeof(int) 在您的系统上是 4,因此您在循环的大约 25% 处崩溃,因为您在每次迭代中将 addr 增加了 4 倍太多。

【讨论】:

  • 你的意思是如果我做 addr+=1 它会增加 sizeof(int)?
  • 是的,这就是 C(和相关语言)的工作方式。要么使用 char * 指针而不是 int * 指针,要么显式处理地址算术。两个建议:(i) 至少阅读一本关于 C 的好书;(ii) 学习使用调试器(例如 gdb)——从长远来看,两者都会带来巨大的收益。
【解决方案2】:

while(ret_val == buffer || read_length&lt;jbuffer_size) 中的条件错误。 ret_val == buffer 将始终为 true,如果在到达循环时 read_length&lt;jbuffer_size 为 true,则它将始终保持为 true,因为 read_length 只会减少(嗯,直到它下溢 INT_MIN)。

【讨论】:

  • 这确实是个问题,但不是主要问题。见 cmets。
【解决方案3】:

while 循环是无限的:

while(ret_val == buffer || read_length<jbuffer_size) {
    ret_val = memcpy(buffer, addr,jbuffer_size);
    addr+=jbuffer_size;
    read_length -= jbuffer_size;
}

因为memcpy() 总是返回目标缓冲区,所以ret_val == buffer 总是true(因此作为终止条件的一部分是无用的)。这意味着addr 在循环的每次迭代中都会增加jbuffer_size 字节并传递给memcpy(),从而导致访问无效内存。

【讨论】:

  • 这确实是个问题,但不是主要问题。见 cmets。
【解决方案4】:

这个循环永远不会终止,因为ret_val 总是等于buffer

void *ret_val = buffer;
int read_length = length;
while(ret_val == buffer || read_length<jbuffer_size) {
    /*****GETTING SIGSEGV SOMWHERE HERE IN THE WHILE************/
    ret_val = memcpy(buffer, addr,jbuffer_size);
    addr+=jbuffer_size;
    read_length -= jbuffer_size;
}

memcpy 总是返回它的第一个参数,所以ret_val 永远不会改变。

【讨论】:

  • 这确实是个问题,但不是主要问题。见 cmets。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-24
  • 2020-06-29
  • 1970-01-01
  • 2018-06-06
  • 2018-04-25
相关资源
最近更新 更多