373 lines
8.7 KiB
C
373 lines
8.7 KiB
C
/* $Id$ */
|
|
/* Copyright (c) 2015 Pierre Pronchery <khorben@defora.org> */
|
|
/* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the authors nor the names of the contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
* THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE. */
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <glib.h>
|
|
#include "../debugger.h"
|
|
|
|
|
|
/* ptrace */
|
|
/* private */
|
|
typedef struct _DebuggerDebug PtraceDebug;
|
|
|
|
/* platform */
|
|
#ifdef __NetBSD__
|
|
typedef int ptrace_data_t;
|
|
#endif
|
|
|
|
struct _DebuggerDebug
|
|
{
|
|
DebuggerDebugHelper const * helper;
|
|
GPid pid;
|
|
guint source;
|
|
gboolean running;
|
|
|
|
/* events */
|
|
ptrace_event_t event;
|
|
|
|
/* deferred requests */
|
|
int request;
|
|
void * addr;
|
|
ptrace_data_t data;
|
|
};
|
|
|
|
|
|
/* prototypes */
|
|
/* plug-in */
|
|
static PtraceDebug * _ptrace_init(DebuggerDebugHelper const * helper);
|
|
static void _ptrace_destroy(PtraceDebug * debug);
|
|
static int _ptrace_start(PtraceDebug * debug, va_list argp);
|
|
static int _ptrace_pause(PtraceDebug * debug);
|
|
static int _ptrace_stop(PtraceDebug * debug);
|
|
static int _ptrace_continue(PtraceDebug * debug);
|
|
static int _ptrace_next(PtraceDebug * debug);
|
|
static int _ptrace_step(PtraceDebug * debug);
|
|
|
|
/* useful */
|
|
static void _ptrace_exit(PtraceDebug * debug);
|
|
static int _ptrace_request(PtraceDebug * debug, int request, void * addr,
|
|
ptrace_data_t data);
|
|
static int _ptrace_schedule(PtraceDebug * debug, int request, void * addr,
|
|
ptrace_data_t data);
|
|
|
|
|
|
/* constants */
|
|
static DebuggerDebugDefinition _ptrace_definition =
|
|
{
|
|
"ptrace",
|
|
NULL,
|
|
LICENSE_GNU_LGPL3_FLAGS,
|
|
_ptrace_init,
|
|
_ptrace_destroy,
|
|
_ptrace_start,
|
|
_ptrace_pause,
|
|
_ptrace_stop,
|
|
_ptrace_continue,
|
|
_ptrace_next,
|
|
_ptrace_step
|
|
};
|
|
|
|
|
|
/* protected */
|
|
/* functions */
|
|
/* plug-in */
|
|
/* ptrace_init */
|
|
static PtraceDebug * _ptrace_init(DebuggerDebugHelper const * helper)
|
|
{
|
|
PtraceDebug * debug;
|
|
|
|
if((debug = object_new(sizeof(*debug))) == NULL)
|
|
return NULL;
|
|
debug->helper = helper;
|
|
debug->pid = -1;
|
|
debug->source = 0;
|
|
debug->running = FALSE;
|
|
/* events */
|
|
memset(&debug->event, 0, sizeof(debug->event));
|
|
#ifdef PTRACE_FORK
|
|
debug->event.pe_set_event = PTRACE_FORK;
|
|
#endif
|
|
/* deferred requests */
|
|
debug->request = -1;
|
|
debug->addr = NULL;
|
|
debug->data = 0;
|
|
return debug;
|
|
}
|
|
|
|
|
|
/* ptrace_destroy */
|
|
static void _ptrace_destroy(PtraceDebug * debug)
|
|
{
|
|
_ptrace_exit(debug);
|
|
object_delete(debug);
|
|
}
|
|
|
|
|
|
/* ptrace_start */
|
|
static int _start_parent(PtraceDebug * debug);
|
|
/* callbacks */
|
|
static void _start_on_child_setup(gpointer data);
|
|
static void _start_on_child_watch(GPid pid, gint status, gpointer data);
|
|
|
|
static int _ptrace_start(PtraceDebug * debug, va_list argp)
|
|
{
|
|
char * argv[3] = { NULL, NULL, NULL };
|
|
const unsigned int flags = G_SPAWN_DO_NOT_REAP_CHILD
|
|
| G_SPAWN_FILE_AND_ARGV_ZERO;
|
|
GError * error = NULL;
|
|
|
|
argv[0] = va_arg(argp, char *);
|
|
argv[1] = argv[0];
|
|
if(g_spawn_async(NULL, argv, NULL, flags, _start_on_child_setup,
|
|
debug, &debug->pid, &error) == FALSE)
|
|
{
|
|
error_set_code(-errno, "%s", error->message);
|
|
g_error_free(error);
|
|
return -debug->helper->error(debug->helper->debugger, 1,
|
|
"%s", "Could not start execution");
|
|
}
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s() %d\n", __func__, debug->pid);
|
|
#endif
|
|
return _start_parent(debug);
|
|
}
|
|
|
|
static int _start_parent(PtraceDebug * debug)
|
|
{
|
|
debug->source = g_child_watch_add(debug->pid, _start_on_child_watch,
|
|
debug);
|
|
#ifdef PTRACE_FORK
|
|
_ptrace_schedule(debug, PT_SET_EVENT_MASK, &debug->event,
|
|
sizeof(debug->event));
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/* callbacks */
|
|
static void _start_on_child_setup(gpointer data)
|
|
{
|
|
PtraceDebug * debug = data;
|
|
|
|
errno = 0;
|
|
if(ptrace(PT_TRACE_ME, 0, (caddr_t)NULL, (ptrace_data_t)0) == -1
|
|
&& errno != 0)
|
|
{
|
|
debugger_error(NULL, strerror(errno), 1);
|
|
_exit(125);
|
|
}
|
|
}
|
|
|
|
static void _start_on_child_watch(GPid pid, gint status, gpointer data)
|
|
{
|
|
PtraceDebug * debug = data;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s(%d, %d)\n", __func__, pid, status);
|
|
#endif
|
|
#ifdef G_OS_UNIX
|
|
if(debug->pid != pid)
|
|
return;
|
|
if(WIFSTOPPED(status))
|
|
{
|
|
# ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s() stopped\n", __func__);
|
|
# endif
|
|
debug->running = FALSE;
|
|
if(debug->request >= 0)
|
|
{
|
|
if(_ptrace_request(debug, debug->request,
|
|
debug->addr, debug->data)
|
|
!= 0)
|
|
{
|
|
debug->request = -1;
|
|
return;
|
|
}
|
|
debug->running = TRUE;
|
|
debug->request = -1;
|
|
}
|
|
}
|
|
else if(WIFSIGNALED(status))
|
|
{
|
|
# ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s() signal %d\n", __func__,
|
|
WTERMSIG(status));
|
|
# endif
|
|
debug->source = 0;
|
|
_ptrace_exit(debug);
|
|
}
|
|
else if(WIFEXITED(status))
|
|
{
|
|
# ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s() error %d\n", __func__,
|
|
WEXITSTATUS(status));
|
|
# endif
|
|
debug->source = 0;
|
|
_ptrace_exit(debug);
|
|
}
|
|
#else
|
|
GError * error = NULL;
|
|
|
|
if(debug->pid != pid)
|
|
return;
|
|
if(g_spawn_check_exit_status(status, &error) == FALSE)
|
|
{
|
|
error_set_code(WEXITSTATUS(status), "%s", error->message);
|
|
g_error_free(error);
|
|
debug->helper->error(debug->helper->debugger,
|
|
WEXITSTATUS(status), "%s", error_get(),
|
|
}
|
|
debug->source = 0;
|
|
_ptrace_exit(debug);
|
|
#endif
|
|
}
|
|
|
|
|
|
/* ptrace_pause */
|
|
static int _ptrace_pause(PtraceDebug * debug)
|
|
{
|
|
return _ptrace_schedule(debug, -1, NULL, 0);
|
|
}
|
|
|
|
|
|
/* ptrace_stop */
|
|
static int _ptrace_stop(PtraceDebug * debug)
|
|
{
|
|
return _ptrace_schedule(debug, PT_KILL, NULL, 0);
|
|
}
|
|
|
|
|
|
/* ptrace_continue */
|
|
static int _ptrace_continue(PtraceDebug * debug)
|
|
{
|
|
return _ptrace_schedule(debug, PT_CONTINUE, (caddr_t)1, 0);
|
|
}
|
|
|
|
|
|
/* ptrace_next */
|
|
static int _ptrace_next(PtraceDebug * debug)
|
|
{
|
|
return _ptrace_schedule(debug, PT_SYSCALL, (caddr_t)1, 0);
|
|
}
|
|
|
|
|
|
/* ptrace_step */
|
|
static int _ptrace_step(PtraceDebug * debug)
|
|
{
|
|
return _ptrace_schedule(debug, PT_STEP, (caddr_t)1, 0);
|
|
}
|
|
|
|
|
|
/* useful */
|
|
/* ptrace_exit */
|
|
static void _ptrace_exit(PtraceDebug * debug)
|
|
{
|
|
if(debug->source != 0)
|
|
g_source_remove(debug->source);
|
|
debug->source = 0;
|
|
if(debug->pid > 0)
|
|
g_spawn_close_pid(debug->pid);
|
|
debug->pid = -1;
|
|
debug->running = FALSE;
|
|
debug->request = -1;
|
|
debug->addr = NULL;
|
|
debug->data = 0;
|
|
}
|
|
|
|
|
|
/* ptrace_request */
|
|
static int _ptrace_request(PtraceDebug * debug, int request, void * addr,
|
|
int data)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s(%d, %p, %d) %d\n", __func__, request, addr,
|
|
data, debug->pid);
|
|
#endif
|
|
if(debug->pid <= 0)
|
|
return -1;
|
|
errno = 0;
|
|
if(ptrace(request, debug->pid, addr, data) == -1 && errno != 0)
|
|
{
|
|
error_set_code(-errno, "%s: %s", "ptrace", strerror(errno));
|
|
if(errno == ESRCH)
|
|
_ptrace_exit(debug);
|
|
return -debug->helper->error(debug->helper->debugger, 1,
|
|
"%s", error_get());
|
|
}
|
|
debug->running = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ptrace_schedule */
|
|
static int _ptrace_schedule(PtraceDebug * debug, int request, void * addr,
|
|
int data)
|
|
{
|
|
DebuggerDebugHelper const * helper = debug->helper;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s(%d, %p, %d)\n", __func__, request, addr,
|
|
data);
|
|
#endif
|
|
if(debug->running)
|
|
{
|
|
/* stop the traced process */
|
|
if(kill(debug->pid, SIGSTOP) != 0)
|
|
{
|
|
error_set_code(-errno, "%s: %s", "kill",
|
|
strerror(errno));
|
|
if(errno == ESRCH)
|
|
_ptrace_exit(debug);
|
|
return -helper->error(helper->debugger, 1,
|
|
"%s", "Could not schedule command"
|
|
" (could not stop the traced process)");
|
|
}
|
|
debug->running = FALSE;
|
|
wait(NULL);
|
|
if(request < 0)
|
|
return 0;
|
|
/* schedule the request */
|
|
debug->request = request;
|
|
debug->addr = addr;
|
|
debug->data = data;
|
|
return 0;
|
|
}
|
|
if(request < 0)
|
|
return 0;
|
|
/* we can issue the request directly */
|
|
if(_ptrace_request(debug, request, addr, data) != 0)
|
|
return -1;
|
|
return 0;
|
|
}
|