在 Apple Silicon 上预期BUS_ADRERR时出现BUS_ADRALN错误

BUS_ADRALN error when BUS_ADRERR is expected on Apple Silicon

提问人:user46317 提问时间:11/16/2023 最后编辑:Peter Cordesuser46317 更新时间:11/17/2023 访问量:65

问:

在将大型 C++ 代码库移植到 Apple Silicon 时,我观察到信号处理在本机构建中的行为有所不同。具体来说,我正在写入 mmap 内存地址,并预计会出现BUS_ADRERR总线错误。Windows、Linux 和 Apple Silicon Mac 就是这种情况,这些 Mac 运行具有 Rosetta 仿真功能的 x86 代码,适用于以下代码。

但是,在本机构建中,BUS_ADRALN是我观察到的错误代码。这似乎完全出乎意料,因为地址是对齐的。下面我粘贴了我在 M1 Pro 机器上观察到的问题和输出的最小重现

/*
Reproducing the issue on M1 Pro laptop:

Clang version:
Apple clang version 15.0.0 (clang-1500.0.40.1)
Target: arm64-apple-darwin23.1.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

Observed output on M1 Mac:
clang++ repro.cpp && ./a.out
Obtained mmapped address: 0x102590000
Bus error (SIGBUS) occurred. Signal: 10
Siginfo code BUS_ADRALN -> Invalid address alignment
---------------------------------------------------------
On a Amazon Linux system on the other hand:

Compiler version:
clang version 11.1.0 (Amazon Linux 2 11.1.0-1.amzn2.0.2)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

Obtained mmapped address: 0x7facdaddf000
Bus error (SIGBUS) occurred. Signal: 7
Siginfo code BUS_ADRERR -> Non-existent physical address or invalid address
*/

#include <cassert>
#include <csignal>
#include <fcntl.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

using namespace std;

void sigbusHandler(int sig, siginfo_t* info, void*) {
   cerr << "Bus error (SIGBUS) occurred. Signal: " << sig << endl;
   switch (info->si_code) {
      case BUS_ADRALN:
         cerr << "Siginfo code BUS_ADRALN -> Invalid address alignment" << endl;
         break;
      case BUS_ADRERR:
         cerr << "Siginfo code BUS_ADRERR -> Non-existent physical address or invalid address" << endl;
         break;
      default:
         cerr << "Unknown bus error code: " << info->si_code << endl;
         break;
   }
   exit(sig);
}

int main() {
   // Install sighandler for SIGBUS
   struct sigaction sa;
   memset(&sa, 0, sizeof(sa));
   sa.sa_sigaction = sigbusHandler;
   sa.sa_flags = SA_SIGINFO;
   sigfillset(&sa.sa_mask);
   if (sigaction(SIGBUS, &sa, nullptr) == -1) {
      perror("sigaction");
      return EXIT_FAILURE;
   }  

   // mmaped file
   int fd;
   off_t filesize = sizeof(int);
   void *mapped;
   fd = open("example_file.txt", O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0660);
   if (fd == -1) {
      perror("open");
      return EXIT_FAILURE;
   }
   mapped = mmap(NULL, filesize, PROT_WRITE, MAP_PRIVATE, fd, 0);
   if (mapped == MAP_FAILED) {
      perror("mmap");
      close(fd);
      return EXIT_FAILURE;
   }


   // Write int to mmaped memory
   // (triggers bus error with signal code BUS_ADRALN, even though address seems to be properly aligned)
   int value = 42;
   // Make sure alignment is fine to write int
   assert((reinterpret_cast<uintptr_t>(mapped) & (alignment_of<decltype(value)>::value - 1)) == 0);
   cerr << "Obtained mmapped address: " << mapped << endl;
   memcpy(mapped, &value, sizeof(int));
   cerr << "Did not trigger an error, repro didn't work" << endl;

   // Unmap the file
   if (munmap(mapped, filesize) == -1) {
      perror("munmap");
      close(fd);
      return EXIT_FAILURE;
   }

   // Close the file descriptor
   close(fd);

   return EXIT_SUCCESS;
}
C++ macOS 信号 x86-64 Apple-Silicon

评论

2赞 zwol 11/16/2023
为什么报告SIGBUS的哪个子类别很重要?
1赞 Andrew Henle 11/16/2023
我强烈怀疑,并且在 Apple Silicon 系统上是异步信号安全的。我希望这只是提供一个示例,而不是来自您的实际代码库。cerrexit()
1赞 273K 11/16/2023
为什么大型 C++ 代码库用 C 标记?
0赞 user46317 11/17/2023
它只是测试代码,在生产中,它是哪个 sigbus 类别并不重要。但无论如何,我都想了解这里发生了什么。cerr/exit 用于提供最小重现,此代码不是来自我们的。是的,它是一个 C++ 代码库,我提供的代码是 C++,但仅使用 C 可以观察到相同的情况,所以我包含了 C 标签

答:

3赞 Siguza 11/16/2023 #1

XNU 似乎无条件地在 x86 和 ARM 上使用。BUS_ADRERRBUS_ADRALN

参见 bsd/dev/i386/unix_signal.c

case SIGBUS:
    sinfo64.si_code = BUS_ADRERR;
    sinfo64.si_addr = ua_cr2;
    break;

bsd/dev/arm/unix_signal.c

case SIGBUS:
    if (proc_is64bit_data(p)) {
#if defined(__arm64__)
        sinfo.si_addr = user_frame.uf64.mctx.es.far;
#else
#error Unsupported architecture
#endif
    } else {
        sinfo.si_addr = user_frame.uf32.mctx.es.far;
    }

    sinfo.si_code = BUS_ADRALN;
    break;

Rosetta 几乎可以肯定只是镜像了 x86_64 内核的功能,如果它没有完全包含相同的代码。

但是,由于这些无论如何都是硬编码的,因此我认为忽略它们是安全的。 是。SIGBUSSIGBUS

评论

0赞 user46317 11/17/2023
谢谢你的回答!帮助我理解我在信号处理程序中观察到的内容