这个问题纯粹是出于好奇;就我个人而言,我看到过这种信号被提出,但很少这样。
我在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("%cn", 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
的系统上是可重复的,会发生什么?
SIGBUS
是使用内存映射文件的危险之一。根据POSIX,在以下条件下,可以得到关于mmap()
的SIGBUS:
系统应始终零填充对象末尾的任何部分页面。此外,系统不得写出超出其末尾的对象最后一页的任何修改部分。从pa开始并持续len字节到对象结束后的整页的地址范围内的引用将导致SIGBUS信号的传递。
当引用将导致映射对象中的错误(例如空间外条件(时,实现可以生成SIGBUS信号。
在您的示例中,fd
引用的对象的长度为0字节。因此,映射的所有页面都是"对象结束后的整个页面",并在访问时生成SIGBUS。
您可以通过不映射超过对象长度的页面来避免SIGBUS
。遗憾的是,MAP_SHARED
有一个固有的问题:即使在mmap()
上验证了映射的长度是正确的,对象大小也可能在之后发生变化(例如,如果另一个进程在文件上调用truncate()
(。
通常,当您访问未映射的页面时,总会得到一个SIGBUS
。Linux在其中一些情况下生成SIGSEGV
,因为语义重叠。当以禁止的方式访问页面时,应该生成SIGSEGV
。
也许不是你想要的,但完成了任务。
$ cat t2.c
#include <signal.h>
int main(){raise(SIGBUS);}