如何从EXC_BAD_ACCESS恢复? [英] How do I recover from EXC_BAD_ACCESS?

查看:235
本文介绍了如何从EXC_BAD_ACCESS恢复?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm intentionally causing an EXC_BAD_ACCESS. By triggering a write to an NSObject in a read-only virtual memory page. Ideally, I'd like to catch EXC_BAD_ACCESS, mark the virtual memory page as read-write and have execution continue as it normally would have. Is this even possible? The code I've written to cause the EXC_BAD_ACCESS is below.

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帖子有答案!

警告

这个答案有一个很大的缺点。我的调试器不能在mach异常线程以外的任何线程中工作。在任何其他线程中放置断点会导致Xcode5挂起。我不得不强行退出它。在我的 catch_exception_raise 函数中,它工作正常。 我向LLDB人员询问了这个问题。

This answer has a major downside. My debugger wouldn't work in any thread other than the mach exception thread. Putting a breakpoint in any other thread caused Xcode5 to hang. I had to force-quit it. Inside my catch_exception_raise function, it worked fine. I asked the LLDB folks about this.

结束警告

此代码是答案的骨架。它将无限循环,因为(根据后续行动)您需要做一些事情才能使错误恢复。就我而言,我需要将页面标记为可读写。

This code is the skeleton of the answer. It will infinite loop, because (according to the follow-up) you need to do something to make the error recoverable. In my case, I need to mark the page as read-write.

#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);
}

这篇关于如何从EXC_BAD_ACCESS恢复?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆