/* * Server-side debugger functions * * Copyright (C) 1999 Alexandre Julliard */ #include #include #include "winbase.h" #include "winerror.h" #include "handle.h" #include "process.h" #include "thread.h" #include "request.h" struct debug_event { struct debug_event *next; /* event queue */ struct debug_event *prev; struct thread *thread; /* thread which sent this event */ int sent; /* already sent to the debugger? */ int code; /* event code */ union debug_event_data data; /* event data */ }; struct debug_ctx { struct thread *owner; /* thread owning this debug context */ int waiting; /* is thread waiting for an event? */ struct timeout_user *timeout; /* timeout user for wait timeout */ struct debug_event *event_head; /* head of pending events queue */ struct debug_event *event_tail; /* tail of pending events queue */ }; /* size of the event data */ static const int event_sizes[] = { 0, sizeof(struct debug_event_exception), /* EXCEPTION_DEBUG_EVENT */ sizeof(struct debug_event_create_thread), /* CREATE_THREAD_DEBUG_EVENT */ sizeof(struct debug_event_create_process), /* CREATE_PROCESS_DEBUG_EVENT */ sizeof(struct debug_event_exit), /* EXIT_THREAD_DEBUG_EVENT */ sizeof(struct debug_event_exit), /* EXIT_PROCESS_DEBUG_EVENT */ sizeof(struct debug_event_load_dll), /* LOAD_DLL_DEBUG_EVENT */ sizeof(struct debug_event_unload_dll), /* UNLOAD_DLL_DEBUG_EVENT */ sizeof(struct debug_event_output_string), /* OUTPUT_DEBUG_STRING_EVENT */ sizeof(struct debug_event_rip_info) /* RIP_EVENT */ }; /* initialise the fields that do not need to be filled by the client */ static int fill_debug_event( struct thread *debugger, struct thread *thread, struct debug_event *event ) { int handle; /* some events need special handling */ switch(event->code) { case CREATE_THREAD_DEBUG_EVENT: if ((event->data.create_thread.handle = alloc_handle( debugger->process, thread, /* documented: THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME */ THREAD_ALL_ACCESS, FALSE )) == -1) return 0; break; case CREATE_PROCESS_DEBUG_EVENT: if ((handle = event->data.create_process.file) != -1) { if ((handle = duplicate_handle( thread->process, handle, debugger->process, GENERIC_READ, FALSE, 0 )) == -1) return 0; event->data.create_process.file = handle; } if ((event->data.create_process.process = alloc_handle( debugger->process, thread->process, /* documented: PROCESS_VM_READ | PROCESS_VM_WRITE */ PROCESS_ALL_ACCESS, FALSE )) == -1) { if (handle != -1) close_handle( debugger->process, handle ); return 0; } if ((event->data.create_process.thread = alloc_handle( debugger->process, thread, /* documented: THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME */ THREAD_ALL_ACCESS, FALSE )) == -1) { if (handle != -1) close_handle( debugger->process, handle ); close_handle( debugger->process, event->data.create_process.process ); return 0; } break; case LOAD_DLL_DEBUG_EVENT: if ((handle = event->data.load_dll.handle) != -1) { if ((handle = duplicate_handle( thread->process, handle, debugger->process, GENERIC_READ, FALSE, 0 )) == -1) return 0; event->data.load_dll.handle = handle; } break; } return 1; } /* free a debug event structure */ static void free_event( struct thread *debugger, struct debug_event *event ) { /* If the event has been sent already, the handles are now under the */ /* responsibility of the debugger process, so we don't touch them */ if (!event->sent) { switch(event->code) { case CREATE_THREAD_DEBUG_EVENT: close_handle( debugger->process, event->data.create_thread.handle ); break; case CREATE_PROCESS_DEBUG_EVENT: if (event->data.create_process.file != -1) close_handle( debugger->process, event->data.create_process.file ); close_handle( debugger->process, event->data.create_process.thread ); close_handle( debugger->process, event->data.create_process.process ); break; case LOAD_DLL_DEBUG_EVENT: if (event->data.load_dll.handle != -1) close_handle( debugger->process, event->data.load_dll.handle ); break; } } event->thread->debug_event = NULL; release_object( event->thread ); free( event ); } /* unlink the first event from the queue */ static void unlink_event( struct debug_ctx *debug_ctx, struct debug_event *event ) { if (event->prev) event->prev->next = event->next; else debug_ctx->event_head = event->next; if (event->next) event->next->prev = event->prev; else debug_ctx->event_tail = event->prev; event->next = event->prev = NULL; } /* link an event at the end of the queue */ static void link_event( struct debug_ctx *debug_ctx, struct debug_event *event ) { event->next = NULL; event->prev = debug_ctx->event_tail; if (event->prev) event->prev->next = event; else debug_ctx->event_head = event; debug_ctx->event_tail = event; } /* send the first queue event as a reply */ static void build_event_reply( struct debug_ctx *debug_ctx ) { struct debug_event *event = debug_ctx->event_head; struct thread *thread = event->thread; struct wait_debug_event_request *req = get_req_ptr( debug_ctx->owner ); assert( event ); assert( debug_ctx->waiting ); unlink_event( debug_ctx, event ); event->sent = 1; req->code = event->code; req->pid = thread->process; req->tid = thread; debug_ctx->waiting = 0; if (debug_ctx->timeout) { remove_timeout_user( debug_ctx->timeout ); debug_ctx->timeout = NULL; } debug_ctx->owner->error = 0; memcpy( req + 1, &event->data, event_sizes[event->code] ); } /* timeout callback while waiting for a debug event */ static void wait_event_timeout( void *ctx ) { struct debug_ctx *debug_ctx = (struct debug_ctx *)ctx; struct wait_debug_event_request *req = get_req_ptr( debug_ctx->owner ); assert( debug_ctx->waiting ); req->code = 0; req->pid = 0; req->tid = 0; debug_ctx->waiting = 0; debug_ctx->timeout = NULL; debug_ctx->owner->error = WAIT_TIMEOUT; send_reply( debug_ctx->owner ); } /* wait for a debug event (or send a reply at once if one is pending) */ static int wait_for_debug_event( int timeout ) { struct debug_ctx *debug_ctx = current->debug_ctx; struct timeval when; if (!debug_ctx) /* current thread is not a debugger */ { set_error( ERROR_INVALID_HANDLE ); return 0; } assert( !debug_ctx->waiting ); if (debug_ctx->event_head) /* already have a pending event */ { debug_ctx->waiting = 1; build_event_reply( debug_ctx ); return 1; } if (!timeout) /* no event and we don't want to wait */ { set_error( WAIT_TIMEOUT ); return 0; } if (timeout != -1) /* start the timeout */ { make_timeout( &when, timeout ); if (!(debug_ctx->timeout = add_timeout_user( &when, wait_event_timeout, debug_ctx ))) return 0; } debug_ctx->waiting = 1; current->state = SLEEPING; return 1; } /* continue a debug event */ static int continue_debug_event( struct process *process, struct thread *thread, int status ) { struct debug_event *event = thread->debug_event; if (process->debugger != current || !event || !event->sent) { /* not debugging this process, or no event pending */ set_error( ERROR_ACCESS_DENIED ); /* FIXME */ return 0; } if (thread->state != TERMINATED) { /* only send a reply if the thread is still there */ /* (we can get a continue on an exit thread/process event) */ struct send_debug_event_request *req = get_req_ptr( thread ); req->status = status; send_reply( thread ); } free_event( current, event ); resume_process( process ); return 1; } /* queue a debug event for a debugger */ static struct debug_event *queue_debug_event( struct thread *debugger, struct thread *thread, int code, void *data ) { struct debug_ctx *debug_ctx = debugger->debug_ctx; struct debug_event *event; assert( debug_ctx ); /* cannot queue a debug event for myself */ assert( debugger->process != thread->process ); /* build the event */ if (!(event = mem_alloc( sizeof(*event) - sizeof(event->data) + event_sizes[code] ))) return NULL; event->sent = 0; event->code = code; event->thread = (struct thread *)grab_object( thread ); memcpy( &event->data, data, event_sizes[code] ); if (!fill_debug_event( debugger, thread, event )) { release_object( event->thread ); free( event ); return NULL; } if (thread->debug_event) { /* only exit events can replace others */ assert( code == EXIT_THREAD_DEBUG_EVENT || code == EXIT_PROCESS_DEBUG_EVENT ); if (!thread->debug_event->sent) unlink_event( debug_ctx, thread->debug_event ); free_event( debugger, thread->debug_event ); } link_event( debug_ctx, event ); thread->debug_event = event; suspend_process( thread->process ); if (debug_ctx->waiting) { build_event_reply( debug_ctx ); send_reply( debug_ctx->owner ); } return event; } /* attach a process to a debugger thread */ int debugger_attach( struct process *process, struct thread *debugger ) { struct debug_ctx *debug_ctx; struct thread *thread; if (process->debugger) /* already being debugged */ { set_error( ERROR_ACCESS_DENIED ); return 0; } /* make sure we don't create a debugging loop */ for (thread = debugger; thread; thread = thread->process->debugger) if (thread->process == process) { set_error( ERROR_ACCESS_DENIED ); return 0; } if (!debugger->debug_ctx) /* need to allocate a context */ { assert( !debugger->debug_first ); if (!(debug_ctx = mem_alloc( sizeof(*debug_ctx) ))) return 0; debug_ctx->owner = current; debug_ctx->waiting = 0; debug_ctx->timeout = NULL; debug_ctx->event_head = NULL; debug_ctx->event_tail = NULL; debugger->debug_ctx = debug_ctx; } process->debugger = debugger; process->debug_prev = NULL; process->debug_next = debugger->debug_first; debugger->debug_first = process; return 1; } /* detach a process from its debugger thread */ static void debugger_detach( struct process *process ) { struct thread *debugger = process->debugger; assert( debugger ); if (process->debug_next) process->debug_next->debug_prev = process->debug_prev; if (process->debug_prev) process->debug_prev->debug_next = process->debug_next; else debugger->debug_first = process->debug_next; process->debugger = NULL; } /* a thread is exiting */ void debug_exit_thread( struct thread *thread, int exit_code ) { struct thread *debugger = current->process->debugger; struct debug_ctx *debug_ctx = thread->debug_ctx; if (debugger) /* being debugged -> send an event to the debugger */ { struct debug_event_exit event; event.exit_code = exit_code; if (!thread->proc_next && !thread->proc_prev) { assert( thread->process->thread_list == thread ); /* this is the last thread, send an exit process event and cleanup */ queue_debug_event( debugger, current, EXIT_PROCESS_DEBUG_EVENT, &event ); debugger_detach( thread->process ); } else queue_debug_event( debugger, current, EXIT_THREAD_DEBUG_EVENT, &event ); } if (debug_ctx) /* this thread is a debugger */ { struct debug_event *event; /* kill all debugged processes */ while (thread->debug_first) kill_process( thread->debug_first, exit_code ); /* free all pending events */ while ((event = debug_ctx->event_head) != NULL) { unlink_event( debug_ctx, event ); free_event( thread, event ); } /* remove the timeout */ if (debug_ctx->timeout) remove_timeout_user( debug_ctx->timeout ); thread->debug_ctx = NULL; free( debug_ctx ); } } /* Wait for a debug event */ DECL_HANDLER(wait_debug_event) { if (!wait_for_debug_event( req->timeout )) { req->code = 0; req->pid = NULL; req->tid = NULL; } } /* Continue a debug event */ DECL_HANDLER(continue_debug_event) { struct process *process = get_process_from_id( req->pid ); if (process) { struct thread *thread = get_thread_from_id( req->tid ); if (thread) { continue_debug_event( process, thread, req->status ); release_object( thread ); } release_object( process ); } } /* Start debugging an existing process */ DECL_HANDLER(debug_process) { struct process *process = get_process_from_id( req->pid ); if (process) { debugger_attach( process, current ); /* FIXME: should notice the debugged process somehow */ release_object( process ); } } /* Send a debug event */ DECL_HANDLER(send_debug_event) { struct thread *debugger = current->process->debugger; assert( !current->debug_event ); if ((req->code <= 0) || (req->code > RIP_EVENT)) { fatal_protocol_error( current, "send_debug_event: bad code %d\n", req->code ); return; } req->status = 0; if (debugger && queue_debug_event( debugger, current, req->code, req + 1 )) { /* wait for continue_debug_event */ current->state = SLEEPING; } }