Coder
/* $Id$ */
/* Copyright (c) 2015-2018 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>
#ifdef __NetBSD__
# include <machine/reg.h>
#endif
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <libintl.h>
#include <glib.h>
#include "../debug.h"
#define _(string) gettext(string)
/* 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);
/* accessors */
static void _ptrace_get_registers(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 */
DebuggerDebugDefinition debug =
{
"ptrace",
NULL,
LICENSE_BSD3_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;
if((argv[0] = va_arg(argp, char *)) == NULL)
return -debug->helper->error(debug->helper->debugger, 1,
"%s", strerror(EINVAL));
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;
DebuggerDebugHelper const * helper = debug->helper;
errno = 0;
if(ptrace(PT_TRACE_ME, 0, (caddr_t)NULL, (ptrace_data_t)0) == -1
&& errno != 0)
{
helper->error(NULL, 1, "%s", strerror(errno));
_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(NULL),
}
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);
}
/* accessors */
/* ptrace_get_registers */
static void _ptrace_get_registers(PtraceDebug * debug)
{
#ifdef PT_GETREGS
DebuggerDebugHelper const * helper = debug->helper;
struct reg regs;
if(_ptrace_request(debug, PT_GETREGS, ®s, 0) != 0)
return;
# if defined(__amd64__)
/* XXX also support 32-bits on 64-bits */
helper->set_register(helper->debugger, "rax", regs.regs[_REG_RAX]);
helper->set_register(helper->debugger, "rcx", regs.regs[_REG_RCX]);
helper->set_register(helper->debugger, "rdx", regs.regs[_REG_RDX]);
helper->set_register(helper->debugger, "rbx", regs.regs[_REG_RBX]);
helper->set_register(helper->debugger, "r8", regs.regs[_REG_R8]);
helper->set_register(helper->debugger, "r9", regs.regs[_REG_R9]);
helper->set_register(helper->debugger, "r10", regs.regs[_REG_R10]);
helper->set_register(helper->debugger, "r11", regs.regs[_REG_R11]);
helper->set_register(helper->debugger, "r12", regs.regs[_REG_R12]);
helper->set_register(helper->debugger, "r13", regs.regs[_REG_R13]);
helper->set_register(helper->debugger, "r14", regs.regs[_REG_R14]);
helper->set_register(helper->debugger, "r15", regs.regs[_REG_R15]);
helper->set_register(helper->debugger, "rsi", regs.regs[_REG_RSI]);
helper->set_register(helper->debugger, "rdi", regs.regs[_REG_RDI]);
helper->set_register(helper->debugger, "rsp", regs.regs[_REG_RSP]);
helper->set_register(helper->debugger, "rbp", regs.regs[_REG_RBP]);
helper->set_register(helper->debugger, "rip", regs.regs[_REG_RIP]);
# elif defined(__i386__)
helper->set_register(helper->debugger, "eax", regs.r_eax);
helper->set_register(helper->debugger, "ecx", regs.r_ecx);
helper->set_register(helper->debugger, "edx", regs.r_edx);
helper->set_register(helper->debugger, "ebx", regs.r_ebx);
helper->set_register(helper->debugger, "esi", regs.r_esi);
helper->set_register(helper->debugger, "edi", regs.r_edi);
helper->set_register(helper->debugger, "esp", regs.r_esp);
helper->set_register(helper->debugger, "ebp", regs.r_ebp);
helper->set_register(helper->debugger, "eip", regs.r_eip);
# endif
#endif
}
/* 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(NULL));
}
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 */
return (_ptrace_request(debug, request, addr, data) == 0) ? 0 : -1;
}