blob: affdfde22993c408c39350a21c21268f773e91c3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
/*
* This module handles process/thread OS contexts and their state machine.
*/
#if defined(_WRS_KERNEL)
# include <vxWorks.h>
#endif
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include "context.h"
#include "events.h"
#include "errors.h"
#include "trace.h"
#include "myalloc.h"
#define CONTEXT_PID_ROOT_SIZE 1024
#define CONTEXT_PID_HASH(PID) ((PID) % CONTEXT_PID_ROOT_SIZE)
static LINK context_pid_root[CONTEXT_PID_ROOT_SIZE];
static ContextEventListener * event_listeners = NULL;
LINK context_root = { NULL, NULL };
#define CASE(var) case var: return ""#var;
char * signal_name(int signal) {
#ifndef WIN32
switch (signal) {
CASE(SIGHUP)
CASE(SIGINT)
CASE(SIGQUIT)
CASE(SIGILL)
CASE(SIGTRAP)
CASE(SIGABRT)
CASE(SIGBUS)
CASE(SIGFPE)
CASE(SIGKILL)
CASE(SIGUSR1)
CASE(SIGSEGV)
CASE(SIGUSR2)
CASE(SIGPIPE)
CASE(SIGALRM)
CASE(SIGTERM)
#ifndef _WRS_KERNEL
CASE(SIGSTKFLT)
#endif
CASE(SIGCHLD)
CASE(SIGCONT)
CASE(SIGSTOP)
CASE(SIGTSTP)
CASE(SIGTTIN)
CASE(SIGTTOU)
CASE(SIGURG)
CASE(SIGXCPU)
CASE(SIGXFSZ)
CASE(SIGVTALRM)
CASE(SIGPROF)
#ifndef _WRS_KERNEL
CASE(SIGWINCH)
CASE(SIGIO)
CASE(SIGPWR)
#endif
CASE(SIGSYS)
}
#endif
return NULL;
}
#undef CASE
Context * context_find_from_pid(pid_t pid) {
LINK * qhp = &context_pid_root[CONTEXT_PID_HASH(pid)];
LINK * qp;
assert(is_dispatch_thread());
for (qp = qhp->next; qp != qhp; qp = qp->next) {
Context * ctx = pidl2ctxp(qp);
if (ctx->pid == pid && !ctx->exited) return ctx;
}
return NULL;
}
static Context * create_context(pid_t pid) {
LINK * qhp = &context_pid_root[CONTEXT_PID_HASH(pid)];
Context * ctx = (Context *)loc_alloc_zero(sizeof(Context));
assert(context_find_from_pid(pid) == NULL);
ctx->pid = pid;
ctx->ref_count = 1;
list_init(&ctx->children);
list_add_first(&ctx->ctxl, &context_root);
list_add_first(&ctx->pidl, qhp);
return ctx;
}
char * pid2id(pid_t pid, pid_t parent) {
static char s[64];
char * p = s + sizeof(s);
unsigned long n = (long)pid;
*(--p) = 0;
do {
*(--p) = (char)(n % 10 + '0');
n = n / 10;
}
while (n != 0);
if (parent != 0) {
n = (long)parent;
*(--p) = '.';
do {
*(--p) = (char)(n % 10 + '0');
n = n / 10;
}
while (n != 0);
}
*(--p) = 'P';
return p;
}
char * thread_id(Context * ctx) {
if (ctx->parent == NULL) return pid2id(ctx->pid, ctx->pid);
assert(ctx->parent->parent == NULL);
return pid2id(ctx->pid, ctx->parent->pid);
}
char * container_id(Context * ctx) {
if (ctx->parent != NULL) ctx = ctx->parent;
assert(ctx->parent == NULL);
return pid2id(ctx->pid, 0);
}
pid_t id2pid(char * id, pid_t * parent) {
pid_t pid = 0;
if (parent != NULL) *parent = 0;
if (id == NULL) return 0;
if (id[0] != 'P') return 0;
if (id[1] == 0) return 0;
pid = (pid_t)strtol(id + 1, &id, 10);
if (id[0] == '.') {
if (id[1] == 0) return 0;
if (parent != NULL) *parent = pid;
pid = (pid_t)strtol(id + 1, &id, 10);
}
if (id[0] != 0) return 0;
return pid;
}
Context * id2ctx(char * id) {
pid_t pid = id2pid(id, NULL);
if (pid == 0) return NULL;
return context_find_from_pid(pid);
}
void context_lock(Context * ctx) {
assert(ctx->ref_count > 0);
ctx->ref_count++;
}
void context_unlock(Context * ctx) {
if (--(ctx->ref_count) == 0) {
assert(list_is_empty(&ctx->children));
assert(ctx->parent == NULL);
list_remove(&ctx->ctxl);
list_remove(&ctx->pidl);
loc_free(ctx);
}
}
char * context_state_name(Context * ctx) {
if (ctx->exited) return "exited";
if (ctx->intercepted) return "intercepted";
if (ctx->stopped) return "stopped";
return "running";
}
static void event_context_created(Context * ctx) {
ContextEventListener * listener = event_listeners;
while (listener != NULL) {
if (listener->context_created != NULL) listener->context_created(ctx);
listener = listener->next;
}
}
static void event_context_changed(Context * ctx) {
ContextEventListener * listener = event_listeners;
while (listener != NULL) {
if (listener->context_changed != NULL) listener->context_changed(ctx);
listener = listener->next;
}
}
static void event_context_stopped(Context * ctx) {
ContextEventListener * listener = event_listeners;
while (listener != NULL) {
if (listener->context_stopped != NULL) listener->context_stopped(ctx);
listener = listener->next;
}
}
static void event_context_started(Context * ctx) {
ContextEventListener * listener = event_listeners;
while (listener != NULL) {
if (listener->context_started != NULL) listener->context_started(ctx);
listener = listener->next;
}
}
static void event_context_exited(Context * ctx) {
ContextEventListener * listener = event_listeners;
while (listener != NULL) {
if (listener->context_exited != NULL) listener->context_exited(ctx);
listener = listener->next;
}
}
#ifdef WIN32
/*
* On Windows context management is not supported yet.
*/
char * event_name(int event) {
return "Unknown";
}
int context_attach(pid_t pid, Context ** res) {
errno = EINVAL;
return -1;
}
int context_stop(Context * ctx) {
errno = EINVAL;
return -1;
}
int context_continue(Context * ctx) {
errno = EINVAL;
return -1;
}
int context_single_step(Context * ctx) {
errno = EINVAL;
return -1;
}
int context_read_mem(Context * ctx, unsigned long address, void * buf, size_t size) {
errno = EINVAL;
return -1;
}
int context_write_mem(Context * ctx, unsigned long address, void * buf, size_t size) {
errno = EINVAL;
return -1;
}
static void init(void) {
}
#elif defined(_WRS_KERNEL)
/* TODO: RTP support */
#include <taskHookLib.h>
#include <private/vxdbgLibP.h>
#define TRACE_EVENT_STEP 2
#define EVENT_HOOK_IGNORE 1
#define EVENT_HOOK_BREAKPOINT 2
#define EVENT_HOOK_STEP_DONE 3
#define EVENT_HOOK_STOP 4
#define EVENT_HOOK_TASK_ADD 5
#define EVENT_HOOK_TASK_DEL 6
struct event_info {
int event;
VXDBG_CTX current_ctx; /* context that hit breakpoint */
VXDBG_CTX stopped_ctx; /* context stopped by the breakpoint */
REG_SET regs; /* task registers before exception */
UINT32 addr; /* breakpoint addr */
int bp_info_ok; /* breakpoint information available */
VXDBG_BP_INFO bp_info; /* breakpoint information */
SEM_ID delete_signal;
};
VXDBG_CLNT_ID vxdbg_clnt_id = 0;
#define MAX_EVENTS 64
static struct event_info events[MAX_EVENTS];
static int events_inp = 0;
static int events_out = 0;
static int events_buf_overflow = 0;
static spinlockIsr_t events_lock;
static VX_COUNTING_SEMAPHORE(events_signal_mem);
static SEM_ID events_signal;
static pthread_t events_thread;
static WIND_TCB * main_thread;
char * event_name(int event) {
switch (event) {
case 0: return "none";
case TRACE_EVENT_STEP: return "Single Step";
}
return NULL;
}
static struct event_info * event_info_alloc(int event) {
int nxt;
struct event_info * info;
SPIN_LOCK_ISR_TAKE(&events_lock);
if (events_buf_overflow) {
SPIN_LOCK_ISR_GIVE(&events_lock);
return NULL;
}
info = events + events_inp;
nxt = (events_inp + 1) % MAX_EVENTS;
if (nxt == events_out) {
events_buf_overflow = 1;
semGive(events_signal);
SPIN_LOCK_ISR_GIVE(&events_lock);
return NULL;
}
memset(info, 0, sizeof(struct event_info));
info->event = event;
events_inp = nxt;
return info;
}
static void event_info_post(struct event_info * info) {
assert(info != NULL);
semGive(events_signal);
SPIN_LOCK_ISR_GIVE(&events_lock);
}
int context_attach(pid_t pid, Context ** res) {
struct event_info * info;
Context * ctx = create_context(pid);
ctx->mem = taskIdSelf();
assert(ctx->ref_count == 1);
event_context_created(ctx);
if (taskIsStopped(pid)) {
ctx->pending_intercept = 1;
info = event_info_alloc(EVENT_HOOK_STOP);
if (info != NULL) {
info->stopped_ctx.ctxId = pid;
event_info_post(info);
}
}
if (res != NULL) *res = ctx;
return 0;
}
int context_stop(Context * ctx) {
struct event_info * info;
VXDBG_CTX vxdbg_ctx;
assert(is_dispatch_thread());
assert(!ctx->stopped);
assert(!ctx->exited);
assert(!ctx->regs_dirty);
assert(!ctx->intercepted);
if (ctx->pending_intercept) {
trace(LOG_CONTEXT, "context: stop ctx %#x pid %d", ctx, ctx->pid);
}
else {
trace(LOG_CONTEXT, "context: temporary stop ctx %#x pid %d", ctx, ctx->pid);
}
vxdbg_ctx.ctxId = ctx->pid;
vxdbg_ctx.ctxType = VXDBG_CTX_TASK;
if (vxdbgStop(vxdbg_clnt_id, &vxdbg_ctx) != OK) return -1;
assert(taskIsStopped(ctx->pid));
info = event_info_alloc(EVENT_HOOK_STOP);
if (info != NULL) {
info->stopped_ctx.ctxId = ctx->pid;
event_info_post(info);
}
return 0;
}
static int kill_context(Context * ctx) {
ctx->pending_signals &= ~(1 << SIGKILL);
if (taskDelete(ctx->pid) != OK) return -1;
ctx->stopped = 0;
event_context_started(ctx);
ctx->exiting = 0;
ctx->exited = 1;
event_context_exited(ctx);
if (ctx->parent != NULL) {
list_remove(&ctx->cldl);
context_unlock(ctx->parent);
ctx->parent = NULL;
}
context_unlock(ctx);
return 0;
}
int context_continue(Context * ctx) {
VXDBG_CTX vxdbg_ctx;
assert(is_dispatch_thread());
assert(ctx->stopped);
assert(!ctx->pending_intercept);
assert(!ctx->exited);
assert(!ctx->pending_step);
trace(LOG_CONTEXT, "context: resume ctx %#x, pid %d", ctx, ctx->pid);
if (ctx->regs_dirty) {
if (taskRegsSet(ctx->pid, &ctx->regs) != OK) return -1;
ctx->regs_dirty = 0;
}
if (ctx->pending_signals & (1 << SIGKILL)) {
return kill_context(ctx);
}
vxdbg_ctx.ctxId = ctx->pid;
vxdbg_ctx.ctxType = VXDBG_CTX_TASK;
if (vxdbgCont(vxdbg_clnt_id, &vxdbg_ctx) != OK) return -1;
ctx->stopped = 0;
event_context_started(ctx);
return 0;
}
int context_single_step(Context * ctx) {
VXDBG_CTX vxdbg_ctx;
struct event_info * info;
assert(is_dispatch_thread());
assert(ctx->stopped);
assert(!ctx->pending_intercept);
assert(!ctx->pending_step);
assert(!ctx->exited);
trace(LOG_CONTEXT, "context: single step ctx %#x, pid %d", ctx, ctx->pid);
if (ctx->regs_dirty) {
if (taskRegsSet(ctx->pid, &ctx->regs) != OK) return -1;
ctx->regs_dirty = 0;
}
if (ctx->pending_signals & (1 << SIGKILL)) {
return kill_context(ctx);
}
vxdbg_ctx.ctxId = ctx->pid;
vxdbg_ctx.ctxType = VXDBG_CTX_TASK;
if (vxdbgStep(vxdbg_clnt_id, &vxdbg_ctx, NULL, NULL) != OK) return -1;
ctx->pending_step = 1;
ctx->stopped = 0;
event_context_started(ctx);
return 0;
}
int context_read_mem(Context * ctx, unsigned long address, void * buf, size_t size) {
#ifdef _WRS_PERSISTENT_SW_BP
vxdbgMemRead((void *)address, buf, size);
#else
bcopy((void *)address, buf, size);
#endif
return 0;
}
int context_write_mem(Context * ctx, unsigned long address, void * buf, size_t size) {
#ifdef _WRS_PERSISTENT_SW_BP
vxdbgMemWrite((void *)address, buf, size);
#else
bcopy(buf, (void *)address, size);
#endif
return 0;
}
static void event_handler(void * arg) {
struct event_info * info = (struct event_info *)arg;
Context * current_ctx = context_find_from_pid(info->current_ctx.ctxId);
Context * stopped_ctx = context_find_from_pid(info->stopped_ctx.ctxId);
switch (info->event) {
case EVENT_HOOK_BREAKPOINT:
if (stopped_ctx == NULL) break;
assert(!stopped_ctx->stopped);
assert(!stopped_ctx->regs_dirty);
assert(!stopped_ctx->intercepted);
stopped_ctx->regs_error = 0;
stopped_ctx->regs = info->regs;
stopped_ctx->signal = SIGTRAP;
assert(get_regs_PC(stopped_ctx->regs) == info->addr);
stopped_ctx->event = 0;
stopped_ctx->stopped_by_bp = info->bp_info_ok;
stopped_ctx->bp_info = info->bp_info;
if (current_ctx != NULL) stopped_ctx->bp_pid = current_ctx->pid;
stopped_ctx->pending_step = 0;
stopped_ctx->stopped = 1;
event_context_stopped(stopped_ctx);
break;
case EVENT_HOOK_STEP_DONE:
if (current_ctx == NULL) break;
assert(!current_ctx->stopped);
assert(!current_ctx->regs_dirty);
assert(!current_ctx->intercepted);
current_ctx->regs_error = 0;
current_ctx->regs = info->regs;
current_ctx->signal = SIGTRAP;
current_ctx->event = TRACE_EVENT_STEP;
current_ctx->pending_step = 0;
current_ctx->stopped = 1;
event_context_stopped(current_ctx);
break;
case EVENT_HOOK_STOP:
if (stopped_ctx == NULL) break;
assert(!stopped_ctx->stopped);
stopped_ctx->regs_error = 0;
if (taskRegsGet(stopped_ctx->pid, &stopped_ctx->regs) != OK) {
stopped_ctx->regs_error = errno;
assert(stopped_ctx->regs_error != 0);
}
stopped_ctx->signal = SIGSTOP;
stopped_ctx->event = 0;
stopped_ctx->pending_step = 0;
stopped_ctx->stopped = 1;
event_context_stopped(stopped_ctx);
break;
case EVENT_HOOK_TASK_ADD:
if (current_ctx == NULL) break;
assert(stopped_ctx == NULL);
stopped_ctx = create_context((pid_t)info->stopped_ctx.ctxId);
assert(stopped_ctx->ref_count == 1);
stopped_ctx->mem = current_ctx->mem;
stopped_ctx->parent = current_ctx->parent != NULL ? current_ctx->parent : current_ctx;
stopped_ctx->parent->ref_count++;
list_add_first(&stopped_ctx->cldl, &stopped_ctx->parent->children);
event_context_created(stopped_ctx);
break;
case EVENT_HOOK_TASK_DEL:
if (stopped_ctx != NULL) {
assert(!stopped_ctx->stopped);
assert(!stopped_ctx->intercepted);
assert(!stopped_ctx->exited);
stopped_ctx->pending_step = 0;
stopped_ctx->exiting = 0;
stopped_ctx->exited = 1;
event_context_exited(stopped_ctx);
if (stopped_ctx->parent != NULL) {
list_remove(&stopped_ctx->cldl);
context_unlock(stopped_ctx->parent);
stopped_ctx->parent = NULL;
}
context_unlock(stopped_ctx);
}
semGive(info->delete_signal);
break;
default:
assert(0);
break;
}
loc_free(info);
}
static void event_error(void * arg) {
trace(LOG_ALWAYS, "Fatal error: VXDBG events buffer overflow");
exit(1);
}
static void * event_thread_func(void * arg) {
struct event_info * info;
taskPrioritySet(0, VX_TASK_PRIORITY_MIN);
for (;;) {
semTake(events_signal, WAIT_FOREVER);
info = (struct event_info *)loc_alloc(sizeof(struct event_info));
SPIN_LOCK_ISR_TAKE(&events_lock);
if (events_buf_overflow && events_inp == events_out) {
SPIN_LOCK_ISR_GIVE(&events_lock);
break;
}
assert(events_inp != events_out);
*info = events[events_out];
events_out = (events_out + 1) % MAX_EVENTS;
SPIN_LOCK_ISR_GIVE(&events_lock);
if (info->event != EVENT_HOOK_IGNORE) {
post_event(event_handler, info);
}
}
post_event(event_error, NULL);
}
static void vxdbg_event_hook(
VXDBG_CTX * current_ctx, /* context that hit breakpoint */
VXDBG_CTX * stopped_ctx, /* context stopped by the breakpoint */
REG_SET * regs, /* task registers before exception */
UINT32 addr, /* breakpoint addr */
VXDBG_BP_INFO * bp_info) { /* breakpoint information */
struct event_info * info = event_info_alloc(EVENT_HOOK_BREAKPOINT);
if (info != NULL) {
if (stopped_ctx == NULL) info->event = EVENT_HOOK_STEP_DONE;
if (current_ctx != NULL) info->current_ctx = *current_ctx;
if (stopped_ctx != NULL) info->stopped_ctx = *stopped_ctx;
if (regs != NULL) info->regs = *regs;
info->addr = addr;
if (bp_info != NULL) {
info->bp_info_ok = 1;
info->bp_info = *bp_info;
}
event_info_post(info);
}
}
static void task_create_hook(WIND_TCB * tcb) {
struct event_info * info = event_info_alloc(EVENT_HOOK_TASK_ADD);
if (info != NULL) {
info->current_ctx.ctxId = taskIdSelf();
info->stopped_ctx.ctxId = (UINT32)tcb;
event_info_post(info);
}
}
static void task_delete_hook(WIND_TCB * tcb) {
if (tcb != main_thread && taskIdCurrent != main_thread) {
struct event_info * info = event_info_alloc(EVENT_HOOK_TASK_DEL);
if (info != NULL) {
VX_COUNTING_SEMAPHORE(signal_mem);
SEM_ID signal = info->delete_signal = semCInitialize(signal_mem, SEM_Q_FIFO, 0);
info->current_ctx.ctxId = taskIdSelf();
info->stopped_ctx.ctxId = (UINT32)tcb;
event_info_post(info);
semTake(signal, WAIT_FOREVER);
semTerminate(signal);
}
}
}
static void init(void) {
SPIN_LOCK_ISR_INIT(&events_lock, 0);
main_thread = taskIdCurrent;
if ((events_signal = semCInitialize(events_signal_mem, SEM_Q_FIFO, 0)) == NULL) {
perror("semCInitialize");
exit(1);
}
vxdbg_clnt_id = vxdbgClntRegister(EVT_BP);
if (vxdbg_clnt_id == NULL) {
perror("vxdbgClntRegister");
exit(1);
}
taskCreateHookAdd((FUNCPTR)task_create_hook);
taskDeleteHookAdd((FUNCPTR)task_delete_hook);
vxdbgHookAdd(vxdbg_clnt_id, EVT_BP, vxdbg_event_hook);
vxdbgHookAdd(vxdbg_clnt_id, EVT_TRACE, vxdbg_event_hook);
if (pthread_create(&events_thread, &pthread_create_attr, event_thread_func, NULL) != 0) {
perror("pthread_create");
exit(1);
}
}
#else
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sched.h>
#define PTRACE_SETOPTIONS 0x4200
#define PTRACE_GETEVENTMSG 0x4201
#define PTRACE_GETSIGINFO 0x4202
#define PTRACE_SETSIGINFO 0x4203
#define PTRACE_O_TRACESYSGOOD 0x00000001
#define PTRACE_O_TRACEFORK 0x00000002
#define PTRACE_O_TRACEVFORK 0x00000004
#define PTRACE_O_TRACECLONE 0x00000008
#define PTRACE_O_TRACEEXEC 0x00000010
#define PTRACE_O_TRACEVFORKDONE 0x00000020
#define PTRACE_O_TRACEEXIT 0x00000040
#define PTRACE_EVENT_FORK 1
#define PTRACE_EVENT_VFORK 2
#define PTRACE_EVENT_CLONE 3
#define PTRACE_EVENT_EXEC 4
#define PTRACE_EVENT_VFORK_DONE 5
#define PTRACE_EVENT_EXIT 6
#define USE_ESRCH_WORKAROUND 1
#define WORD_SIZE 4
#define PTRACE_FLAGS ( \
PTRACE_O_TRACEFORK | \
PTRACE_O_TRACEVFORK | \
PTRACE_O_TRACECLONE | \
PTRACE_O_TRACEEXEC | \
PTRACE_O_TRACEVFORKDONE | \
PTRACE_O_TRACEEXIT)
struct pid_exit_info {
pid_t pid;
int value;
};
struct pid_stop_info {
pid_t pid;
int signal;
int event;
};
static pthread_t wpid_thread;
static pid_t my_pid = 0;
static pthread_mutex_t waitpid_lock;
static pthread_cond_t waitpid_cond;
static int attach_flag = 0;
char * event_name(int event) {
switch (event) {
case 0: return "none";
case PTRACE_EVENT_FORK: return "fork";
case PTRACE_EVENT_VFORK: return "vfork";
case PTRACE_EVENT_CLONE: return "clone";
case PTRACE_EVENT_EXEC: return "exec";
case PTRACE_EVENT_VFORK_DONE: return "vfork-done";
case PTRACE_EVENT_EXIT: return "exit";
default:
trace(LOG_ALWAYS, "event_name() called with unexpected event code %d", event);
return "unknown";
}
}
int context_attach(pid_t pid, Context ** res) {
Context * ctx = NULL;
pthread_mutex_lock(&waitpid_lock);
if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) {
int err = errno;
trace(LOG_ALWAYS, "error: ptrace(PTRACE_ATTACH) failed: pid %d, error %d %s",
pid, err, errno_to_str(err));
pthread_mutex_unlock(&waitpid_lock);
errno = err;
return -1;
}
ctx = create_context(pid);
/* TODO: context_attach works only for main task in a process */
ctx->mem = pid;
assert(ctx->ref_count == 1);
event_context_created(ctx);
attach_flag = 1;
pthread_cond_signal(&waitpid_cond);
pthread_mutex_unlock(&waitpid_lock);
if (res != NULL) *res = ctx;
return 0;
}
int context_stop(Context * ctx) {
assert(is_dispatch_thread());
assert(!ctx->exited);
assert(!ctx->stopped);
assert(!ctx->regs_dirty);
assert(!ctx->intercepted);
if (ctx->pending_intercept) {
trace(LOG_CONTEXT, "context: suspending ctx %#x pid %d", ctx, ctx->pid);
}
else {
trace(LOG_CONTEXT, "context: temporary suspending ctx %#x pid %d", ctx, ctx->pid);
}
if (tkill(ctx->pid, SIGSTOP) < 0) {
int err = errno;
trace(LOG_ALWAYS, "error: tkill(SIGSTOP) failed: ctx %#x, pid %d, error %d %s",
ctx, ctx->pid, err, errno_to_str(err));
errno = err;
return -1;
}
return 0;
}
int context_continue(Context * ctx) {
int signal = 0;
if (ctx->pending_signals != 0) {
while ((ctx->pending_signals & (1 << signal)) == 0) signal++;
}
assert(signal != SIGSTOP);
assert(signal != SIGTRAP);
assert(is_dispatch_thread());
assert(ctx->stopped);
assert(!ctx->pending_intercept);
assert(!ctx->pending_step);
assert(!ctx->exited);
trace(LOG_CONTEXT, "context: resuming ctx %#x, pid %d, with signal %d", ctx, ctx->pid, signal);
#ifdef __i386__
/* Bug in ptrace: trap flag is not cleared after single step */
if (ctx->regs.eflags & 0x100) {
ctx->regs.eflags &= ~0x100;
ctx->regs_dirty = 1;
}
#endif
if (ctx->regs_dirty) {
if (ptrace(PTRACE_SETREGS, ctx->pid, 0, &ctx->regs) < 0) {
int err = errno;
#if USE_ESRCH_WORKAROUND
if (err == ESRCH) {
ctx->regs_dirty = 0;
ctx->stopped = 0;
event_context_started(ctx);
return 0;
}
#endif
trace(LOG_ALWAYS, "error: ptrace(PTRACE_SETREGS) failed: ctx %#x, pid %d, error %d %s",
ctx, ctx->pid, err, errno_to_str(err));
errno = err;
return -1;
}
ctx->regs_dirty = 0;
}
if (ptrace(PTRACE_CONT, ctx->pid, 0, signal) < 0) {
int err = errno;
#if USE_ESRCH_WORKAROUND
if (err == ESRCH) {
ctx->stopped = 0;
event_context_started(ctx);
return 0;
}
#endif
trace(LOG_ALWAYS, "error: ptrace(PTRACE_CONT, ...) failed: ctx %#x, pid %d, error %d %s",
ctx, ctx->pid, err, errno_to_str(err));
errno = err;
return -1;
}
ctx->pending_signals &= ~(1 << signal);
ctx->stopped = 0;
event_context_started(ctx);
return 0;
}
int context_single_step(Context * ctx) {
assert(is_dispatch_thread());
assert(ctx->stopped);
assert(!ctx->pending_intercept);
assert(!ctx->pending_step);
assert(!ctx->exited);
trace(LOG_CONTEXT, "context: single step ctx %#x, pid %d", ctx, ctx->pid);
if (ctx->regs_dirty) {
if (ptrace(PTRACE_SETREGS, ctx->pid, 0, &ctx->regs) < 0) {
int err = errno;
#if USE_ESRCH_WORKAROUND
if (err == ESRCH) {
ctx->regs_dirty = 0;
ctx->pending_step = 1;
ctx->stopped = 0;
event_context_started(ctx);
return 0;
}
#endif
trace(LOG_ALWAYS, "error: ptrace(PTRACE_SETREGS) failed: ctx %#x, pid %d, error %d %s",
ctx, ctx->pid, err, errno_to_str(err));
errno = err;
return -1;
}
ctx->regs_dirty = 0;
}
if (ptrace(PTRACE_SINGLESTEP, ctx->pid, 0, 0) < 0) {
int err = errno;
#if USE_ESRCH_WORKAROUND
if (err == ESRCH) {
ctx->stopped = 0;
ctx->pending_step = 1;
event_context_started(ctx);
return 0;
}
#endif
trace(LOG_ALWAYS, "error: ptrace(PTRACE_SINGLESTEP, ...) failed: ctx %#x, pid %d, error %d %s",
ctx, ctx->pid, err, errno_to_str(err));
errno = err;
return -1;
}
ctx->pending_step = 1;
ctx->stopped = 0;
event_context_started(ctx);
return 0;
}
int context_write_mem(Context * ctx, unsigned long address, void * buf, size_t size) {
unsigned long word_addr;
assert(is_dispatch_thread());
assert(!ctx->exited);
assert(ctx->stopped);
trace(LOG_CONTEXT, "context: write memory ctx %#x, pid %d, address 0x%08x, size %d", ctx, ctx->pid, address, size);
for (word_addr = address & ~3ul; word_addr < address + size; word_addr += WORD_SIZE) {
int i;
unsigned int word = 0;
if (word_addr < address || word_addr + WORD_SIZE > address + size) {
errno = 0;
word = ptrace(PTRACE_PEEKDATA, ctx->pid, word_addr, 0);
if (errno != 0) {
int err = errno;
trace(LOG_ALWAYS, "error: ptrace(PTRACE_PEEKDATA, ...) failed: ctx %#x, pid %d, error %d %s",
ctx, ctx->pid, err, errno_to_str(err));
errno = err;
return -1;
}
}
for (i = 0; i < WORD_SIZE; i++) {
if (word_addr + i >= address && word_addr + i < address + size) {
// TODO: big endian support
((unsigned char *)&word)[i] = ((unsigned char *)buf)[word_addr + i - address];
}
}
if (ptrace(PTRACE_POKEDATA, ctx->pid, word_addr, word) < 0) {
int err = errno;
trace(LOG_ALWAYS, "error: ptrace(PTRACE_POKEDATA, ...) failed: ctx %#x, pid %d, error %d %s",
ctx, ctx->pid, err, errno_to_str(err));
errno = err;
return -1;
}
}
return 0;
}
int context_read_mem(Context * ctx, unsigned long address, void * buf, size_t size) {
unsigned long word_addr;
assert(is_dispatch_thread());
assert(!ctx->exited);
assert(ctx->stopped);
trace(LOG_CONTEXT, "context: read memory ctx %#x, pid %d, address 0x%08x, size %d", ctx, ctx->pid, address, size);
for (word_addr = address & ~3ul; word_addr < address + size; word_addr += WORD_SIZE) {
int i;
unsigned int word = 0;
errno = 0;
word = ptrace(PTRACE_PEEKDATA, ctx->pid, word_addr, 0);
if (errno != 0) {
int err = errno;
trace(LOG_ALWAYS, "error: ptrace(PTRACE_PEEKDATA, ...) failed: ctx %#x, pid %d, error %d %s",
ctx, ctx->pid, err, errno_to_str(err));
errno = err;
return -1;
}
for (i = 0; i < WORD_SIZE; i++) {
if (word_addr + i >= address && word_addr + i < address + size) {
// TODO: big endian support
((unsigned char *)buf)[word_addr + i - address] = ((unsigned char *)&word)[i];
}
}
}
return 0;
}
static void event_pid_exited(void *arg) {
struct pid_exit_info *eap = arg;
Context * ctx;
ctx = context_find_from_pid(eap->pid);
if (ctx == NULL) {
trace(LOG_EVENTS, "event: ctx not found, pid %d, exit status %d", eap->pid, eap->value);
}
else {
trace(LOG_EVENTS, "event: ctx %#x, pid %d, exit status %d", ctx, eap->pid, eap->value);
assert(!ctx->stopped);
assert(!ctx->intercepted);
assert(!ctx->exited);
/* Note: ctx->exiting should be 1 here. However, PTRACE_EVENT_EXIT can be lost by PTRACE because of racing
* between PTRACE_CONT and SIGTRAP/PTRACE_EVENT_EXIT. So, ctx->exiting can be 0.
*/
ctx->exiting = 0;
ctx->exited = 1;
event_context_exited(ctx);
if (ctx->parent != NULL) {
list_remove(&ctx->cldl);
context_unlock(ctx->parent);
ctx->parent = NULL;
}
context_unlock(ctx);
}
loc_free(eap);
}
static void event_pid_stopped(void * arg) {
unsigned long msg = 0;
Context * ctx = NULL;
Context * ctx2 = NULL;
struct pid_stop_info * eap = arg;
trace(LOG_EVENTS, "event: pid %d stopped, signal %d, event %s",
eap->pid, eap->signal, event_name(eap->event));
ctx = context_find_from_pid(eap->pid);
if (ctx == NULL) {
trace(LOG_ALWAYS, "error: invalid event: pid %d is not traced", eap->pid);
return;
}
assert(!ctx->exited);
assert(!ctx->stopped || eap->event == 0 || eap->event == PTRACE_EVENT_EXIT);
if (ctx->trace_flags != PTRACE_FLAGS) {
if (ptrace(PTRACE_SETOPTIONS, ctx->pid, 0, PTRACE_FLAGS) < 0) {
int err = errno;
trace(LOG_ALWAYS, "error: ptrace(PTRACE_SETOPTIONS) failed: pid %d, error %d %s",
ctx->pid, err, errno_to_str(err));
}
else {
ctx->trace_flags = PTRACE_FLAGS;
}
}
switch (eap->event) {
case PTRACE_EVENT_FORK:
case PTRACE_EVENT_VFORK:
case PTRACE_EVENT_CLONE:
if (ptrace(PTRACE_GETEVENTMSG, eap->pid, 0, &msg) < 0) {
trace(LOG_ALWAYS, "error: ptrace(PTRACE_GETEVENTMSG) failed; pid %d, error %d %s",
eap->pid, errno, errno_to_str(errno));
break;
}
assert(msg != 0);
ctx2 = create_context(msg);
assert(ctx2->parent == NULL);
if (eap->event == PTRACE_EVENT_CLONE) {
ctx2->mem = ctx->mem;
ctx2->parent = ctx->parent != NULL ? ctx->parent : ctx;
ctx2->parent->ref_count++;
list_add_first(&ctx2->cldl, &ctx2->parent->children);
}
else {
ctx2->mem = ctx2->pid;
}
assert(ctx2->mem != 0);
event_context_created(ctx2);
break;
case PTRACE_EVENT_EXEC:
event_context_changed(ctx);
break;
}
if (eap->signal != SIGSTOP && eap->signal != SIGTRAP) {
ctx->pending_signals |= 1 << eap->signal;
}
if (eap->signal == SIGTRAP && eap->event == PTRACE_EVENT_EXIT) {
ctx->exiting = 1;
ctx->regs_dirty = 0;
}
if (!ctx->stopped || !ctx->intercepted) {
unsigned long pc0 = get_regs_PC(ctx->regs);
assert(!ctx->regs_dirty);
assert(!ctx->intercepted);
ctx->regs_error = 0;
if (ptrace(PTRACE_GETREGS, ctx->pid, 0, &ctx->regs) < 0) {
#if USE_ESRCH_WORKAROUND
if (errno == ESRCH) {
/* Racing condition: somebody resumed this context while we are handling stop event.
*
* One possible cause: main thread has exited forcing children to exit too.
* I beleive it is a bug in PTRACE implementation - PTRACE should delay exiting of
* a context while it is stopped, but it does not, which causes a nasty racing.
*
* Workaround: Ignore current event, assume context is running.
*/
loc_free(eap);
return;
}
#endif
ctx->regs_error = errno;
trace(LOG_ALWAYS, "error: ptrace(PTRACE_GETREGS) failed; pid %d, error %d %s",
ctx->pid, errno, errno_to_str(errno));
}
trace(LOG_EVENTS, "event: pid %d stopped at PC = %d (0x%08x)",
ctx->pid, get_regs_PC(ctx->regs), get_regs_PC(ctx->regs));
if (eap->signal == SIGSTOP && ctx->pending_step && ctx->regs_error == 0 && pc0 == get_regs_PC(ctx->regs)) {
trace(LOG_EVENTS, "event: pid %d, single step failed because of pending SIGSTOP, retrying");
ptrace(PTRACE_SINGLESTEP, ctx->pid, 0, 0);
}
else {
ctx->signal = eap->signal;
ctx->event = eap->event;
ctx->pending_step = 0;
ctx->stopped = 1;
event_context_stopped(ctx);
}
}
loc_free(eap);
}
static void *wpid_handler(void *x) {
pid_t pid;
int status;
struct timeval timeout;
for (;;) {
attach_flag = 0;
if ((pid = waitpid(-1, &status, WUNTRACED | __WALL)) == (pid_t)-1) {
if (errno == ECHILD) {
pthread_mutex_lock(&waitpid_lock);
if (!attach_flag) pthread_cond_wait(&waitpid_cond, &waitpid_lock);
pthread_mutex_unlock(&waitpid_lock);
continue;
}
perror("waitpid");
exit(1);
}
trace(LOG_WAITPID, "waitpid: pid %d status %#x", pid, status);
if (WIFEXITED(status) || WIFSIGNALED(status)) {
struct pid_exit_info *eap;
eap = loc_alloc(sizeof *eap);
eap->pid = pid;
eap->value = WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status);
post_event(event_pid_exited, eap);
}
else if (WIFSTOPPED(status)) {
struct pid_stop_info *eap;
eap = loc_alloc(sizeof *eap);
eap->pid = pid;
eap->signal = WSTOPSIG(status);
eap->event = status >> 16;
post_event(event_pid_stopped, eap);
}
else {
trace(LOG_ALWAYS, "unexpected status (0x%x) from waitpid (pid %d)", status, pid);
}
}
}
static void init(void) {
pthread_mutex_init(&waitpid_lock, NULL);
pthread_cond_init(&waitpid_cond, NULL);
my_pid = getpid();
/* Create thread to get process events using waitpid() */
if (pthread_create(&wpid_thread, &pthread_create_attr, wpid_handler, NULL) != 0) {
perror("pthread_create");
exit(1);
}
}
#endif
void add_context_event_listener(ContextEventListener * listener) {
listener->next = event_listeners;
event_listeners = listener;
}
void ini_contexts(void) {
int i;
list_init(&context_root);
for (i = 0; i < CONTEXT_PID_ROOT_SIZE; i++) {
list_init(&context_pid_root[i]);
}
init();
}