Recovery operations for signals SIGBUS/SIGSEGV fail when the process intercepts Mach exceptions. Only the first recovery attempt succeeds, and subsequent Signal notifications are no longer received within the process.
I think this is a bug in XNU.
The test code main.c is:
If we comment out AddMachExceptionServer;, everything will return to normal.
#include <fcntl.h>
#include <mach/arm/kern_return.h>
#include <mach/kern_return.h>
#include <mach/mach.h>
#include <mach/message.h>
#include <mach/port.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/_types/_mach_port_t.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#pragma pack(4)
typedef struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCount;
integer_t code[2];
/** Padding to avoid RCV_TOO_LARGE. */
char padding[512];
} MachExceptionMessage;
typedef struct {
mach_msg_header_t header;
NDR_record_t NDR;
kern_return_t returnCode;
} MachReplyMessage;
#pragma pack()
static jmp_buf jump_buffer;
static void sigbus_handler(int signo, siginfo_t *info, void *context) {
printf("Caught SIGBUS at address: %p\n", info->si_addr);
longjmp(jump_buffer, 1);
}
static void *RunExcServer(void *userdata) {
kern_return_t kr = KERN_FAILURE;
mach_port_t exception_port = MACH_PORT_NULL;
kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE,
&exception_port);
if (kr != KERN_SUCCESS) {
printf("mach_port_allocate: %s", mach_error_string(kr));
return NULL;
}
kr = mach_port_insert_right(mach_task_self_, exception_port, exception_port,
MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
printf("mach_port_insert_right: %s", mach_error_string(kr));
return NULL;
}
kr = task_set_exception_ports(
mach_task_self_, EXC_MASK_ALL & ~(EXC_MASK_RPC_ALERT | EXC_MASK_GUARD),
exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,THREAD_STATE_NONE);
if (kr != KERN_SUCCESS) {
printf("task_set_exception_ports: %s", mach_error_string(kr));
return NULL;
}
MachExceptionMessage exceptionMessage = {{0}};
MachReplyMessage replyMessage = {{0}};
for (;;) {
printf("Wating for message\n");
// Wait for a message.
kern_return_t kr = mach_msg(&exceptionMessage.header, MACH_RCV_MSG, 0,
sizeof(exceptionMessage), exception_port,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (kr == KERN_SUCCESS) {
// Send a reply saying "I didn't handle this exception".
replyMessage.header = exceptionMessage.header;
replyMessage.NDR = exceptionMessage.NDR;
replyMessage.returnCode = KERN_FAILURE;
printf("Catch exception: %d codecnt:%d code[0]: %d, code[1]: %d\n",
exceptionMessage.exception, exceptionMessage.codeCount,
exceptionMessage.code[0], exceptionMessage.code[1]);
mach_msg(&replyMessage.header, MACH_SEND_MSG, sizeof(replyMessage), 0,
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
} else {
printf("Mach error: %s\n", mach_error_string(kr));
}
}
return NULL;
}
static bool AddMachExceptionServer(void) {
int error;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_t ptid = NULL;
error = pthread_create(&ptid, &attr, &RunExcServer, NULL);
if (error != 0) {
pthread_attr_destroy(&attr);
return false;
}
pthread_attr_destroy(&attr);
return true;
}
int main(int argc, char *argv[]) {
AddMachExceptionServer();
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = sigbus_handler;
sa.sa_flags = SA_SIGINFO;
// #if TARGET_OS_IPHONE
// sigaction(SIGSEGV, &sa, NULL);
// #else
sigaction(SIGBUS, &sa, NULL);
// #endif
int i = 0;
while (i++ < 3) {
printf("\nProgram start %d\n", i);
bzero(&jump_buffer, sizeof(jump_buffer));
if (setjmp(jump_buffer) == 0) {
int fd = open("tempfile", O_RDWR | O_CREAT | O_TRUNC, 0666);
ftruncate(fd, 0);
char *map =
(char *)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
unlink("tempfile");
printf("About to write to mmap of size 0 — should trigger SIGBUS...\n");
map[0] = 'X'; // ❌ triger a SIGBUS
munmap(map, 4096);
} else {
printf("Recovered from SIGBUS via longjmp!\n");
}
}
printf("_exit(0)\n");
_exit(0);
return 0;
}