2013-10-18 46 views
5

我故意造成EXC_BAD_ACCESS。通過觸發在只讀虛擬內存頁面中寫入NSObject。理想情況下,我想趕上EXC_BAD_ACCESS,將虛擬內存頁面標記爲讀寫,並且像通常那樣繼續執行。這甚至有可能嗎?我寫的代碼如下: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); 
} 

回答

2

我發現有一個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 

邁(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); 
}