是否存在一种万无一失的跨平台方式来重现 SIGBUS?

Does there exist a surefire, cross platform way to reproduce a SIGBUS?

这个问题纯属好奇;就我个人而言,我已经看到这个信号被发出,但很少见。

我问 the C chatroom whether there was a reliable way to reproduce it. And on this very room, user @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)

Java

$ 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 的系统上可重现,会发生什么?

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

$ cat t2.c
#include <signal.h>
int main(){raise(SIGBUS);}

SIGBUS 是使用内存映射文件的风险之一。 According to POSIX,在以下条件下,您将获得关于 mmap() 的 SIGBUS:

The system shall always zero-fill any partial page at the end of an object. Further, the system shall never write out any modified portions of the last page of an object which are beyond its end. References within the address range starting at pa and continuing for len bytes to whole pages following the end of an object shall result in delivery of a SIGBUS signal.

An implementation may generate SIGBUS signals when a reference would cause an error in the mapped object, such as out-of-space condition.

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

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

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