3

这个问题纯粹是出于好奇。我个人已经看到这个信号被提出,但很少如此。

我在C 聊天室问是否有可靠的方法来重现它。在这个房间里,用户@Antti Haapala找到了一个。至少在 Linux x86_64 系统上……经过一番折腾,同样的模式可以用三种语言重现——但是,仅在基于 x86_64 Linux 的系统上,因为这些是唯一可以测试的系统……方法如下:

C

$ cat t.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

int main () {
        int fd = open ("empty", O_RDONLY);
        char *p = mmap (0, 40960, PROT_READ, MAP_SHARED, fd, 0);
        printf("%c\n", p[4096]);
}
$ :>empty
$ gcc t.c
$ ./a.out
Bus error (core dumped)

Python

$ cat t.py
import mmap
import re
import os

with open('empty', 'wb') as f:
    f.write(b'a' * 4096)

with open('empty', 'rb') as f:
    # memory-map the file, size 0 means whole file
    mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)

    os.system('truncate --size 0 empty')

    b'123' in mm
$ python t.py
Bus error (core dumped)

爪哇

$ cat Test.java
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Random;

public final class Test
{
    private static final int SIZE = 4096;
    private static final Path VICTIM = Paths.get("/tmp/somefile");

    public static void main(final String... args)
        throws IOException
    {
        // Create our victim; delete it first if it already exsists
        Files.deleteIfExists(VICTIM);
        Files.createFile(VICTIM);

        final Random rnd = new Random();
        final byte[] contents = new byte[SIZE];
        rnd.nextBytes(contents);
        Files.write(VICTIM, contents);

        try (
            final FileChannel channel = FileChannel.open(VICTIM,
                StandardOpenOption.READ, StandardOpenOption.WRITE);
        ) {
            final MappedByteBuffer buffer
                = channel.map(FileChannel.MapMode.READ_ONLY, 0L, SIZE);
            channel.truncate(0L);
            buffer.get(rnd.nextInt(SIZE));
        }
    }
}
$ javac Test.java
$ strace -ff -o TRACE java Test
Exception in thread "main" java.lang.InternalError: a fault occurred in a recent unsafe memory access operation in compiled Java code
    at Test.main(Test.java:35)
fge@erwin:~/tmp$ grep -w SIGBUS TRACE.*
TRACE.15850:rt_sigaction(SIGBUS, NULL, {SIG_DFL, [], 0}, 8) = 0
TRACE.15850:rt_sigaction(SIGBUS, {0x7fe3db71b480, ~[RTMIN RT_1], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x7fe3dc5d7d10}, {SIG_DFL, [], 0}, 8) = 0
TRACE.15850:--- SIGBUS {si_signo=SIGBUS, si_code=BUS_ADRERR, si_addr=0x7fe3dc9fb5aa} ---

再次重申:以上所有示例均仅适用于 Linux x86_64 系统;我没有别的东西可以支配。

有没有办法在其他系统上重现这个?

附带问题:如果上面的示例可以在没有 的系统上重现SIGBUS,会发生什么?

4

2 回答 2

3

也许不是您想要的,但可以完成工作。

$ cat t2.c
#include <signal.h>
int main(){raise(SIGBUS);}
于 2016-03-09T18:45:35.340 回答
3

SIGBUS是使用内存映射文件的危险之一。根据 POSIXmmap() ,您会在以下情况下获得 SIGBUS :

系统应始终在对象末尾对任何部分页面进行零填充。此外,系统绝不会写出对象最后一页超出其末尾的任何修改部分。地址范围内的引用从 pa 开始并持续 len 字节到对象结束后的整个页面,将导致 SIGBUS 信号的传递。

当引用将导致映射对象中的错误(例如空间不足的情况)时,实现可能会生成 SIGBUS 信号。

在您的示例中,引用的对象fd的长度为 0 个字节。因此,映射的所有页面都是“对象末尾之后的整个页面”,并在访问时生成 SIGBUS。

SIGBUS您可以通过不映射比对象长度更多的页面来避免 a 。可悲的是,存在一个固有的问题MAP_SHARED:即使映射的长度在 上被验证为正确mmap(),对象大小也可能在之后发生变化(例如,如果另一个进程调用truncate()该文件)。

通常,SIGBUS当您访问未映射的页面时,您总是会得到一个。LinuxSIGSEGV在其中一些情况下生成,因为语义重叠。SIGSEGV应在以禁止方式访问页面时生成。

于 2016-03-09T18:52:06.660 回答