mremap在两个连续的共享内存区域上操作导致SIGBUS的原因是什么?

huangapple go评论65阅读模式
英文:

Why does mremap on two consecutive shared memory regions result in SIGBUS?

问题

I am providing the translation of the text you provided:

作为背景:我正在实现一个x86 Linux模拟器,试图理解mremap系统调用的作用。目前唯一让我感到困惑的情况是,当old_len设置为0时。

据我理解文档,这可以用于创建已映射的共享内存区域的新映射,从而在同一物理内存上创建第二个虚拟地址映射。(即可以通过两个不同的虚拟地址范围访问相同的物理内存)

到目前为止一切顺利。当我尝试时,这确实按预期工作。然而,当存在两个连续的内存区域,例如0xf1000..0xf4000和0xf4000..0xf7000,写入跨越区域边界时(在mremapping之后)会导致SIGBUS。请参见下面的示例:

#define _GNU_SOURCE 1
#include <stdio.h>
#include <sys/mman.h>

int main() {
    char* mapping = mmap((void *) 0xf1000, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    char* mapping2 = mmap((void *) 0xf4000, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    char* mapping3 = mremap((void *) 0xf3000, 0x0, 0x3000, MREMAP_MAYMOVE | MREMAP_FIXED, 0xf9000);
    for (int i = 0; i < 0x3000; ++i) mapping3[i] = 'A';
    printf("%c\n", mapping[0x2000]);
    printf("Mapping pointer: %p\n", mapping);
    printf("Mapping2 pointer: %p\n", mapping2);
    printf("Mapping3 pointer: %p\n", mapping3);
    return 0;
}

如果循环仅迭代前0x1000个字符,则更改会在mapping中反映出我所期望的结果。

为什么会导致SIGBUS,为什么mremap调用不会失败,如果这是不允许的话?

英文:

For context: I am implementing an x86 linux emulator and am trying to wrap my head around understanding what the mremap syscall does. The only case that currently does not make much sense to me is the case where old_len is set to 0.

As far as i understand the docs this can be used to make a new mapping of an already mapped shared memory area, effectively creating a second virtual address mapping to the same physical memory. (I.e. it would be possible to access the same physical memory via the two different virtual address ranges)

So far so good. This works as expected when i try it. However, when there are two consecutive memory regions, e.g., 0xf1000..0xf4000 and 0xf4000..0xf7000 this results in a SIGBUS when writing across the boundary of the regions (after mremapping). See the example below:

#define _GNU_SOURCE 1
#include <stdio.h>
#include <sys/mman.h>

int main() {
    char* mapping = mmap((void *) 0xf1000, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    char* mapping2 = mmap((void *) 0xf4000, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    char* mapping3 = mremap((void *) 0xf3000, 0x0, 0x3000, MREMAP_MAYMOVE | MREMAP_FIXED, 0xf9000);
    for (int i = 0; i < 0x3000; ++i) mapping3[i] = 'A';
    printf("%c\n", mapping[0x2000]);
    printf("Mapping pointer: %p\n", mapping);
    printf("Mapping2 pointer: %p\n", mapping2);
    printf("Mapping3 pointer: %p\n", mapping3);
    return 0;
}

If the loop only iterates over the first 0x1000 characters, then the changes are reflected in mapping as i would expect.

Why does this result in a SIGBUS and why does the call to mremap not fail, if this is not allowed?

答案1

得分: 2

在调查后发现,似乎无法使用MAP_SHARED方式执行mremap跨映射操作,就像使用MAP_PRIVATE那样。

如果将mmap调用更改为使用MAP_PRIVATE,将会触发此错误

	if (!old_len && !(vma->vm_flags & (VM_SHARED | VM_MAYSHARE))) {
		pr_warn_once("%s (%d): attempted to duplicate a private mapping with mremap.  This is not supported.\n", current->comm, current->pid);
		return ERR_PTR(-EINVAL);
	}

... 我实际上可以验证这一点,因为这条消息出现在dmesg中:

[ 3566.135161] a.out (4234): attempted to duplicate a private mapping with mremap.  This is not supported.

如果你更改mremap调用,不使用old_len == 0的技巧:

char* mapping3 = mremap((void *) 0xf3000, 0x3000, 0x3000, MREMAP_MAYMOVE | MREMAP_FIXED, 0xf9000);

... 你将会得到EFAULT(错误的地址)的errno。这似乎会在此处被捕获:

	/* 我们不能在虚拟内存区域边界上重新映射 */
	if (old_len > vma->vm_end - addr)
		return ERR_PTR(-EFAULT);

正如你所见,如果old_len == 0,将会跳过此语句(假设是故意的)。

当使用MAP_SHARED时,这由shmem系统管理,它在映射边界方面似乎有些不同。然而,我无法确定在源代码中的具体位置。我认为这与MAP_SHARED | MAP_ANONYMOUS映射无法扩展有关。因此,如果你尝试扩展MAP_SHARED | MAP_ANONYMOUS映射,将会遇到相同的错误。它会在静默成功后,当你读/写新分配的部分时抛出总线错误。

总线错误发生在你尝试写入最初定义为mapping2的第一页时。因此,似乎这是你无法做到的事情。目前来看,在使用mremap时,最好将MAP_SHARED | MAP_ANONYMOUS映射视为独立的单元。

英文:

So after looking into this, it appears you cannot do this mremap across maps thing with MAP_SHARED the way you can with MAP_PRIVATE.

If you change the mmap calls to use MAP_PRIVATE, you hit this error:

	if (!old_len && !(vma->vm_flags & (VM_SHARED | VM_MAYSHARE))) {
		pr_warn_once("%s (%d): attempted to duplicate a private mapping with mremap.  This is not supported.\n", current->comm, current->pid);
		return ERR_PTR(-EINVAL);
	}

... which I can actually verify as that message appeared in dmesg:

[ 3566.135161] a.out (4234): attempted to duplicate a private mapping with mremap.  This is not supported.

If you change, the mremap call to not do the old_len == 0 trick:

char* mapping3 = mremap((void *) 0xf3000, 0x3000, 0x3000, MREMAP_MAYMOVE | MREMAP_FIXED, 0xf9000);

... you will get errno of EFAULT (bad address). This appears to get caught here:

	/* We can't remap across vm area boundaries */
	if (old_len > vma->vm_end - addr)
		return ERR_PTR(-EFAULT);

As you can see, the old_len == 0 will skip this statement (assuming intentionally).

When using MAP_SHARED, this is managed by the shmem system which appears to operate a bit differently in terms of map boundaries. However, I could not pinpoint exactly where in the source this is decided. I think this is related to the fact that MAP_SHARED | MAP_ANONYMOUS mappings cannot grow. So you hit the same error if you were to grow a MAP_SHARED | MAP_ANONYMOUS mapping. It will silently succeed, but then throw a bus error when you read/write into the newly allocated section.

Your bus error occurs when you try to write into the first page of what is originally defined as mapping2. So it would seem, this is just something you cannot do. As it stands, it would be better to think of MAP_SHARED | MAP_ANONYMOUS mappings as distinct units when using mremap.

huangapple
  • 本文由 发表于 2023年6月29日 20:05:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76580887.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定