Ho trovato un darwin-dev post che ha la risposta!
ATTENZIONE
Questa risposta ha un grave inconveniente. Il mio debugger non funzionerebbe in nessun thread diverso dal thread delle eccezioni mach. Mettere un punto di interruzione in qualsiasi altro thread ha causato l'interruzione di Xcode5. Ho dovuto forzare la chiusura. All'interno della mia funzione catch_exception_raise
, ha funzionato bene. I asked the LLDB folks about this.
FINE ATTENZIONE
Questo codice è lo scheletro della risposta. Avrà un ciclo infinito, perché (secondo lo follow-up) è necessario fare qualcosa per rendere l'errore recuperabile. Nel mio caso, ho bisogno di contrassegnare la pagina come lettura-scrittura.
#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;
}
Questo è il mio nuovo codice che funziona:
WeakTargetObject.h (ARC)
@interface WeakTargetObject : NSObject
@property (nonatomic, weak) NSObject *target;
@end
WeakTargetObject.m (ARC)
@implementation WeakTargetObject
@end
Mai n.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);
}