重新编译二进制文件时出现总线错误

4
有时,在各种Unix架构上,当程序正在运行时重新编译程序会导致程序崩溃并出现“总线错误”。有人能解释在哪些情况下会发生这种情况吗?首先,更新磁盘上的二进制文件如何影响内存中的代码?我唯一能想到的是某些系统将代码映射到内存中,当编译器重写磁盘镜像时,这会导致映射失效。这种方法的优势是什么?通过更改可执行文件来使正在运行的代码崩溃似乎非常不优秀。
2个回答

4
在本地文件系统上,所有主要的类Unix系统都支持解决这个问题,方法是删除文件。旧的vnode仍然保持打开状态,即使目录条目已经不存在并被重新用于新的镜像,旧文件仍然存在且未更改,现在没有名称,直到对它的最后引用(在这种情况下为内核)消失。
但如果您只是开始重写它,那么是的,它会被mmap(3)映射。当块被重写时,取决于动态链接器使用的哪些mmap(3)选项,会发生以下两种情况之一:
1.内核将使相应页面无效,或者
2.磁盘镜像将更改,但现有的内存页面不会更改。
无论哪种情况,运行中的程序都可能陷入麻烦。在第一种情况下,它基本上肯定会崩溃,在第二种情况下,除非所有页面都已被引用、分页并且永远不会被丢弃,否则它将被覆盖。
有两个mmap标志旨在解决此问题。一个是MAP_DENYWRITE(防止写入),另一个是MAP_COPY,它保留了原始版本并防止编写器更改映射的图像。
但由于安全原因,DENYWRITE已被禁用,而COPY在任何主要的类Unix系统中都没有实现。

编译器通常不会删除和重写,而是覆盖输出文件? - Lutorm

0

这是一个可能在您的情况下发生的复杂场景。这个错误的原因通常是内存对齐问题。总线错误在基于FreeBSD的系统中更为常见。考虑这样一种情况,你有一个结构体,像这样:

struct MyStruct { char ch[29]; // 29字节 int32 i; // 4字节 }

所以这个结构体的总大小是33字节。现在考虑一个有32字节缓存行的系统。这个结构体无法在单个缓存行中加载。现在考虑以下语句

Struct MyStruct abc; char *cptr = &abc // char point points at start of structure int32 *iptr = (cptr + 1) // iptr points at 2nd byte of structure.

现在总结构大小为33字节,您的int指针指向第2个字节,因此您可以从int指针读取32字节的数据(因为分配内存的总大小为33字节)。但是,当您尝试读取它时,如果该结构分配在缓存行的边界上,则操作系统无法在单个调用中读取32字节。因为当前缓存行仅包含31字节的数据,剩余的1字节位于下一个缓存行上。这将导致无效地址并出现“总线错误”。大多数操作系统通过在内部生成两个内存读取调用来处理此情况,但某些Unix系统不处理此情况。为避免此问题,建议注意内存对齐。当您尝试将结构强制转换为另一种数据类型并尝试读取该结构的内存时,通常会发生这种情况。

这种情况有点复杂,所以我不确定是否能以更简单的方式解释它。希望您能理解这种情况。


感谢您的回答,但我不明白这与更改磁盘上的图像有什么关系。除非在可执行文件正在运行时重新编译它,否则代码都可以正常运行。 - Lutorm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接