* Server-side debugger functions
*
* Copyright (C) 1999 Alexandre Julliard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#include "config.h"
+#include "wine/port.h"
+
#include <assert.h>
+#include <signal.h>
#include <string.h>
#include <stdio.h>
#include "process.h"
#include "thread.h"
#include "request.h"
+#include "console.h"
enum debug_event_state { EVENT_QUEUED, EVENT_SENT, EVENT_CONTINUED };
struct object obj; /* object header */
struct debug_event *event_head; /* head of pending events queue */
struct debug_event *event_tail; /* tail of pending events queue */
+ int kill_on_exit;/* kill debuggees on debugger exit ? */
};
no_get_fd, /* get_fd */
no_flush, /* flush */
no_get_file_info, /* get_file_info */
+ NULL, /* queue_async */
debug_event_destroy /* destroy */
};
no_get_fd, /* get_fd */
no_flush, /* flush */
no_get_file_info, /* get_file_info */
+ NULL, /* queue_async */
debug_ctx_destroy /* destroy */
};
{
struct process *debugger = event->debugger->process;
struct thread *thread = event->sender;
- handle_t handle;
+ obj_handle_t handle;
/* documented: THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME */
if (!(handle = alloc_handle( debugger, thread, THREAD_ALL_ACCESS, FALSE ))) return 0;
struct process *debugger = event->debugger->process;
struct thread *thread = event->sender;
struct process *process = thread->process;
- handle_t handle;
+ obj_handle_t handle;
/* documented: PROCESS_VM_READ | PROCESS_VM_WRITE */
if (!(handle = alloc_handle( debugger, process, PROCESS_ALL_ACCESS, FALSE ))) return 0;
{
struct process *debugger = event->debugger->process;
struct process_dll *dll = arg;
- handle_t handle = 0;
+ obj_handle_t handle = 0;
if (dll->file && !(handle = alloc_handle( debugger, dll->file, GENERIC_READ, FALSE )))
return 0;
/* alloc a debug event for a debugger */
static struct debug_event *alloc_debug_event( struct thread *thread, int code,
- void *arg, CONTEXT *context )
+ void *arg, const CONTEXT *context )
{
struct thread *debugger = thread->process->debugger;
struct debug_event *event;
struct thread *thread;
if (process->debugger) goto error; /* already being debugged */
- if (process->init_event) goto error; /* still starting up */
+ if (!is_process_init_done( process )) goto error; /* still starting up */
+ if (!process->thread_list) goto error; /* no thread running in the process */
/* make sure we don't create a debugging loop */
for (thread = debugger; thread; thread = thread->process->debugger)
if (thread->process == process) goto error;
+ /* don't let a debugger debug its console... won't work */
+ if (debugger->process->console && debugger->process->console->renderer->process == process)
+ goto error;
+
suspend_process( process );
/* we must have been able to attach all threads */
+ if (!process->thread_list) goto error2;
for (thread = process->thread_list; thread; thread = thread->proc_next)
- if (!thread->attached)
+ {
+ if (!thread->attached) goto error2;
+ }
+
+ if (set_process_debugger( process, debugger )) return 1;
+ resume_process( process );
+ return 0;
+
+ error2:
+ resume_process( process );
+ error:
+ set_error( STATUS_ACCESS_DENIED );
+ return 0;
+}
+
+
+/* detach a process from a debugger thread (and resume it ?) */
+int debugger_detach( struct process *process, struct thread *debugger )
+{
+ struct thread *thread;
+ struct debug_event *event;
+ struct debug_ctx *debug_ctx;
+
+ if (!process->debugger || process->debugger != debugger)
+ goto error; /* not currently debugged, or debugged by another debugger */
+ if (!debugger->debug_ctx ) goto error; /* should be a debugger */
+ /* init should be done, otherwise wouldn't be attached */
+ assert(is_process_init_done(process));
+
+ suspend_process( process );
+ /* send continue indication for all events */
+ debug_ctx = debugger->debug_ctx;
+
+ /* find the event in the queue
+ * FIXME: could loop on process' threads and look the debug_event field */
+ for (event = debug_ctx->event_head; event; event = event->next)
+ {
+ if (event->state != EVENT_QUEUED) continue;
+
+ if (event->sender->process == process)
{
+ assert( event->sender->debug_event == event );
+ event->status = DBG_CONTINUE;
+ event->state = EVENT_CONTINUED;
+ wake_up( &event->obj, 0 );
+ unlink_event( debug_ctx, event );
+ /* from queued debug event */
resume_process( process );
- goto error;
}
+ }
- if (set_process_debugger( process, debugger )) return 1;
+ /* remove relationships between process and its debugger */
+ process->debugger = NULL;
+ release_object( debugger->debug_ctx );
+ debugger->debug_ctx = NULL;
+
+ /* now detach all the threads */
+ for (thread = process->thread_list; thread; thread = thread->proc_next)
+ {
+ if (thread->attached)
+ {
+ detach_thread( thread, 0 );
+ }
+ }
+
+ /* from this function */
resume_process( process );
return 0;
if (!(debug_ctx = alloc_object( &debug_ctx_ops, -1 ))) return 0;
debug_ctx->event_head = NULL;
debug_ctx->event_tail = NULL;
+ debug_ctx->kill_on_exit = 1;
debugger->debug_ctx = debug_ctx;
}
process->debugger = debugger;
{
if (thread->debug_ctx) /* this thread is a debugger */
{
- /* kill all debugged processes */
- kill_debugged_processes( thread, thread->exit_code );
+ if (thread->debug_ctx->kill_on_exit)
+ {
+ /* kill all debugged processes */
+ kill_debugged_processes( thread, thread->exit_code );
+ }
+ else
+ {
+ detach_debugged_processes( thread );
+ }
release_object( thread->debug_ctx );
thread->debug_ctx = NULL;
}
set_error( STATUS_INVALID_HANDLE );
return;
}
- req->wait = 0;
+ reply->wait = 0;
if ((event = find_event_to_send( debug_ctx )))
{
- size_t size = get_req_data_size(req);
+ size_t size = get_reply_max_size();
event->state = EVENT_SENT;
event->sender->debug_event = event;
- req->pid = event->sender->process;
- req->tid = event->sender;
+ reply->pid = get_process_id( event->sender->process );
+ reply->tid = get_thread_id( event->sender );
if (size > sizeof(debug_event_t)) size = sizeof(debug_event_t);
- memcpy( get_req_data(req), &event->data, size );
- set_req_data_size( req, size );
+ set_reply_data( &event->data, size );
}
else /* no event ready */
{
- req->pid = 0;
- req->tid = 0;
- set_req_data_size( req, 0 );
+ reply->pid = 0;
+ reply->tid = 0;
if (req->get_handle)
- req->wait = alloc_handle( current->process, debug_ctx, SYNCHRONIZE, FALSE );
+ reply->wait = alloc_handle( current->process, debug_ctx, SYNCHRONIZE, FALSE );
}
}
/* Start debugging an existing process */
DECL_HANDLER(debug_process)
{
- struct debug_event_exception data;
struct process *process = get_process_from_id( req->pid );
if (!process) return;
- if (debugger_attach( process, current ))
+ if (!req->attach)
+ {
+ debugger_detach( process, current );
+ }
+ else if (debugger_attach( process, current ))
{
+ struct debug_event_exception data;
+
generate_startup_debug_events( process, NULL );
resume_process( process );
/* queue an exception event */
DECL_HANDLER(queue_exception_event)
{
- req->handle = 0;
+ reply->handle = 0;
if (current->process->debugger)
{
struct debug_event_exception data;
struct debug_event *event;
- CONTEXT *context = get_req_data( req );
+ const CONTEXT *context = get_req_data();
EXCEPTION_RECORD *rec = (EXCEPTION_RECORD *)(context + 1);
- if (get_req_data_size( req ) < sizeof(*rec) + sizeof(*context))
+ if (get_req_data_size() < sizeof(*rec) + sizeof(*context))
{
set_error( STATUS_INVALID_PARAMETER );
return;
data.first = req->first;
if ((event = alloc_debug_event( current, EXCEPTION_DEBUG_EVENT, &data, context )))
{
- if ((req->handle = alloc_handle( current->process, event, SYNCHRONIZE, FALSE )))
+ if ((reply->handle = alloc_handle( current->process, event, SYNCHRONIZE, FALSE )))
{
link_event( event );
suspend_process( current->process );
DECL_HANDLER(get_exception_status)
{
struct debug_event *event;
- size_t size = 0;
- req->status = 0;
+ reply->status = 0;
if ((event = (struct debug_event *)get_handle_obj( current->process, req->handle,
0, &debug_event_ops )))
{
if (event->state == EVENT_CONTINUED)
{
- req->status = event->status;
+ reply->status = event->status;
if (current->context == &event->context)
{
- size = min( sizeof(CONTEXT), get_req_data_size(req) );
- memcpy( get_req_data(req), &event->context, size );
+ size_t size = min( sizeof(CONTEXT), get_reply_max_size() );
+ set_reply_data( &event->context, size );
current->context = NULL;
}
}
else set_error( STATUS_PENDING );
release_object( event );
}
- set_req_data_size( req, size );
}
/* send an output string to the debugger */
data.length = req->length;
generate_debug_event( current, OUTPUT_DEBUG_STRING_EVENT, &data );
}
+
+/* simulate a breakpoint in a process */
+DECL_HANDLER(debug_break)
+{
+ struct process *process;
+
+ reply->self = 0;
+ if (!(process = get_process_from_handle( req->handle, PROCESS_SET_INFORMATION /*FIXME*/ )))
+ return;
+ if (process != current->process)
+ {
+ /* find a suitable thread to signal */
+ struct thread *thread;
+ for (thread = process->thread_list; thread; thread = thread->proc_next)
+ {
+ if (thread->unix_pid)
+ {
+ kill( thread->unix_pid, SIGTRAP );
+ break;
+ }
+ }
+ if (!thread) set_error( STATUS_ACCESS_DENIED );
+ }
+ else reply->self = 1;
+ release_object( process );
+}
+
+/* set debugger kill on exit flag */
+DECL_HANDLER(set_debugger_kill_on_exit)
+{
+ if (!current->debug_ctx)
+ {
+ set_error( STATUS_ACCESS_DENIED );
+ return;
+ }
+ current->debug_ctx->kill_on_exit = req->kill_on_exit;
+}