Not stopping on EXC_BAD_ACCESS

(Sorry, I just discovered this list, and I don’t know how to properly jump into a thread I didn’t receive in email. I tried to replicate quoting one-level deep.)

I want to be notified when a __weak reference in one of my objects is about to be nil-ed. I accomplish this by allocating the object into a specific VM page and then marking the page as read-only. When ARC nils the __weak reference, I get an EXC_BAD_ACCESS, and I can mark the VM page as read-write, run my tear-down code while the object is still valid, and then return KERN_SUCCESS, which will allow ARC to continue on. However, as Richard described earlier, LLDB hangs when I set a breakpoint inside any code that isn’t my exception handler when I use task_set_exception_ports.

You suggested using thread_set_exception_ports to fix this issue. I assume that thread_set_exception_ports only takes over exceptions that occur on the thread given to thread_set_exception_ports, and not for the whole task, like task_set_exception_ports does. Thus, I would need to call thread_set_exception_ports on every thread since I care about getting an EXC_BAD_ACCESS exception that occurs on any thread, right? My handler isn’t called if I register for exceptions with a different thread.

Is there a way to be notified when a thread is created so I can subscribe to its messages?

Also, I still see LLDB hanging when a use thread_set_exception_ports. Am I doing something wrong? My code is below:

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) {
x86_exception_state32_t x86_exception_state32;
mach_msg_type_number_t sc = x86_EXCEPTION_STATE32_COUNT;

thread_get_state(thread,
x86_EXCEPTION_STATE32,
(thread_state_t)&x86_exception_state32,
&sc);

// pull fault address from x86_exception_state32
// check that it is an expected address
// make the page read-write
// do cleanup

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

// Does this need to be called on every thread?
// I assume exception_port can be reused. Is this true?

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);
// using task_set_exception_ports causes LLDB to hang…
// …whenever it hits breakpoint outside of catch_exception_raise

// task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, exception_port, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE);

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

(Sorry, I just discovered this list, and I don't know how to properly jump into a thread I didn't receive in email. I tried to replicate quoting one-level deep.)

I want to be notified when a __weak reference in one of my objects is about to be nil-ed. I accomplish this by allocating the object into a specific VM page and then marking the page as read-only. When ARC nils the __weak reference, I get an EXC_BAD_ACCESS, and I can mark the VM page as read-write, run my tear-down code while the object is still valid, and then return KERN_SUCCESS, which will allow ARC to continue on. However, as Richard described earlier, LLDB hangs when I set a breakpoint inside any code that isn't my exception handler when I use task_set_exception_ports.

If you were watching all the exception types on the task exception port, then it would make sense to me that we would hang trying to continue, since lldb uses EXC_BREAKPOINT for its own purposes and if it is not seeing that, it won't be able to run its execution logic. But if you are only watching for the EXC_BAD_ACCESS exception then lldb it shouldn't care about this (besides that lldb won't ever see real crashes.)

Anyway, check that you really are only registering for the EXC_BAD_ACCESS exception when you do the {task,thread}_set_exception_ports. Or, see below, maybe this is happening because you aren't replying to the exception message?

You suggested using thread_set_exception_ports to fix this issue. I assume that thread_set_exception_ports only takes over exceptions that occur on the thread given to thread_set_exception_ports, and not for the whole task, like task_set_exception_ports does. Thus, I would need to call thread_set_exception_ports on every thread since I care about getting an EXC_BAD_ACCESS exception that occurs on any thread, right? My handler isn't called if I register for exceptions with a different thread.

Is there a way to be notified when a thread is created so I can subscribe to its messages?

Not that I am aware of. There's no very good way to to watch for exceptions on threads you didn't create using the thread exception port, at least not one I know about.

Also, I still see LLDB hanging when a use thread_set_exception_ports. Am I doing something wrong? My code is below:

Is lldb hanging, or is your program hanging? If lldb is hanging, can you file a bug with the backtrace of lldb (and a sample program if you can easily whip one up...)

If it is your program that is hanging make sure you are sending on the reply message that you get when your catch_exception_raise returns KERN_SUCCESS. Maybe that is what mach_msg_server does, I've never used that function so I don't know. In lldb we call mach_exc_server to trigger the call to catch_exception_raise and that returns a reply right that you have to send on to the appropriate exception port for the process to continue. If you don't do that your program won't make progress. As I said, that may be already handled in mach_msg_server, I don't know, not having done it that way, but that's the only thing I can think of that might hang you up. You can check out how lldb does this here:

http://llvm.org/svn/llvm-project/lldb/trunk/tools/debugserver/source/MacOSX/MachException.cpp

Jim

Is lldb hanging, or is your program hanging? If lldb is hanging, can you

file a bug with the backtrace of lldb (and a sample program if you can
easily whip one up...)

You're right, it wasn't lldb's fault. I attached lldb to the simulator from
the command-line, and everything worked as expected. The problem must lie
somewhere in the bowels of Xcode.

-Heath Borders
heath.borders@gmail.com
Twitter: heathborders
http://heath-tech.blogspot.com