我故意造成EXC_BAD_ACCESS。通过触发对只读虚拟内存页面中的NSObject的写操作。理想情况下,我想捕获EXC_BAD_ACCESS,将虚拟内存页标记为可读写,并像往常一样继续执行。这有可能吗?我编写的导致EXC_BAD_ACCESS的代码如下。

WeakTargetObject.h(ARC)

@interface WeakTargetObject : NSObject
@property (nonatomic, weak) NSObject *target;
@end

WeakTargetObject.m(ARC)
@implementation WeakTargetObject
@end

main.m(MRR)
- (void)main {
  char *mem = NULL;
  vm_allocate(mach_task_self(), (vm_address_t *)&mem, vm_page_size, VM_FLAGS_ANYWHERE);
  NSLog(@"mem: %p", mem);
  WeakTargetObject *weakTargetObject = objc_constructInstance([WeakTargetObject class], (void *)mem);

  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSObject *target = [[NSObject alloc] init];
  weakTargetObject.target = target;
  [pool drain];
  pool = [[NSAutoreleasePool alloc] init];
  NSLog(@"expect non-nil. weakTargetObject.target: %@", weakTargetObject.target);
  [pool drain];

  vm_protect(mach_task_self(),
             (vm_address_t)mem,
             vm_page_size,
             1,
             VM_PROT_READ);

  // triggers EXC_BAD_ACCESS when objc runtime
  // tries to nil weakTargetObject.target
  [weakTargetObject release];
  NSLog(@"expect nil. weakTargetObject.target: %@", weakTargetObject.target);
}

最佳答案

我找到了有答案的darwin-dev post!

警告

这个答案有一个很大的缺点。除mach异常线程外,我的调试器无法在任何线程中工作。在其他任何线程中放置断点会导致Xcode5挂起。我不得不强行退出。在我的catch_exception_raise函数中,它运行良好。 I asked the LLDB folks about this.

END警告

此代码是答案的框架。它将无限循环,因为(根据follow-up)您需要做一些事情以使错误可恢复。就我而言,我需要将该页面标记为可读写。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <stdarg.h>
#include <pthread.h>
#include <assert.h>
#include <mach/mach.h>

kern_return_t
catch_exception_raise(mach_port_t exception_port,
                      mach_port_t thread,
                      mach_port_t task,
                      exception_type_t exception,
                      exception_data_t code_vector,
                      mach_msg_type_number_t code_count)
{
    fprintf(stderr, "catch_exception_raise %d\n", exception);
    return KERN_SUCCESS;  // loops infinitely...
}

void *exception_handler(void *arg)
{
extern boolean_t exc_server();
mach_port_t port = (mach_port_t) arg;
mach_msg_server(exc_server, 2048, port, 0);
abort(); // without this GCC complains (it doesn't know that mach_msg_server never returns)
}

void setup_mach_exception_port()
{
static mach_port_t exception_port = MACH_PORT_NULL;
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exception_port);
mach_port_insert_right(mach_task_self(), exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND);
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, exception_port, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE);
pthread_t returned_thread;
pthread_create(&returned_thread, NULL, exception_handler, (void*) exception_port);
}

void test_crash()
{
    id *obj = NULL;
    *obj = @"foo";
}

int main(int argc, char** argv)
{
    setup_mach_exception_port();
    test_crash();
    return 0;
}

这是我的新代码,可以正常工作:

WeakTargetObject.h(ARC)
@interface WeakTargetObject : NSObject
@property (nonatomic, weak) NSObject *target;
@end

WeakTargetObject.m(ARC)
@implementation WeakTargetObject
@end

main.m(MRR)
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <stdarg.h>
#include <pthread.h>
#include <assert.h>
#include <mach/mach.h>

static char * mem = NULL;

kern_return_t
catch_exception_raise(mach_port_t exception_port,
                      mach_port_t thread,
                      mach_port_t task,
                      exception_type_t exception,
                      exception_data_t code_vector,
                      mach_msg_type_number_t code_count)
{
  fprintf(stderr, "catch_exception_raise %d, mem: %p\n", exception, mem);
  kern_return_t success = vm_protect(mach_task_self(),
                                     (vm_address_t)mem,
                                     vm_page_size,
                                     0,
                                     VM_PROT_DEFAULT);
  fprintf(stderr, "switched to read-write: %d\n", success);
  return KERN_SUCCESS;
}

void *exception_handler(void *arg)
{
extern boolean_t exc_server();
mach_port_t port = (mach_port_t) arg;
mach_msg_server(exc_server, 2048, port, 0);
abort(); // without this GCC complains (it doesn't know that mach_msg_server never returns)
}

void setup_mach_exception_port()
{
static mach_port_t exception_port = MACH_PORT_NULL;
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exception_port);
mach_port_insert_right(mach_task_self(), exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND);
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, exception_port, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE);
pthread_t returned_thread;
pthread_create(&returned_thread, NULL, exception_handler, (void*) exception_port);
}

- (void)main {
  setup_mach_exception_port();
  vm_allocate(mach_task_self(), (vm_address_t *)&mem, vm_page_size, VM_FLAGS_ANYWHERE);
  NSLog(@"mem: %p", mem);
  WeakTargetObject *weakTargetObject = objc_constructInstance([WeakTargetObject class], (void *)mem);

  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSObject *target = [[NSObject alloc] init];
  weakTargetObject.target = target;
  [pool drain];
  pool = [[NSAutoreleasePool alloc] init];
  NSLog(@"expect non-nil. weakTargetObject.target: %@", weakTargetObject.target);
  [pool drain];

  vm_protect(mach_task_self(),
             (vm_address_t)mem,
             vm_page_size,
             // zero means don't set VM_PROT_READ as the maximum protection
             // one means DO set VM_PROT_READ as the maximum protection
             // we want zero because the if VM_PROT_READ is the maximum protection
             // we won't be able to set it to VM_PROT_DEFAULT later
             0,
             VM_PROT_READ);

  // triggers EXC_BAD_ACCESS when objc runtime
  // tries to nil weakTargetObject.target
  [weakTargetObject release];
  NSLog(@"expect nil. weakTargetObject.target: %@", weakTargetObject.target);
}

09-07 11:55