blob: e88fce99c99ffc22529a2d891fa6a14201ff9790 [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
*******************************************************************************/
/*
* Target service implementation: run control (TCF name RunControl)
*/
#include "config.h"
#if SERVICE_RunControl
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <assert.h>
#include "runctrl.h"
#include "protocol.h"
#include "channel.h"
#include "json.h"
#include "context.h"
#include "myalloc.h"
#include "trace.h"
#include "events.h"
#include "exceptions.h"
#include "breakpoints.h"
#define RM_RESUME 0
#define RM_STEP_OVER 1
#define RM_STEP_INTO 2
#define RM_STEP_OVER_LINE 3
#define RM_STEP_INTO_LINE 4
#define RM_STEP_OUT 5
#define STOP_ALL_TIMEOUT 1000000
#define STOP_ALL_MAX_CNT 20
static const char RUN_CONTROL[] = "RunControl";
typedef struct SafeEvent SafeEvent;
struct SafeEvent {
void (*done)(void *);
void * arg;
SafeEvent * next;
};
typedef struct GetContextArgs GetContextArgs;
struct GetContextArgs {
OutputStream * out;
char token[256];
Context * ctx;
pid_t parent;
};
static SafeEvent * safe_event_list = NULL;
static int safe_event_pid_count = 0;
static int safe_event_generation = 0;
#if !defined(WIN32) && !defined(_WRS_KERNEL)
static char *get_executable(pid_t pid) {
static char s[FILE_PATH_SIZE + 1];
char tmpbuf[100];
int sz;
snprintf(tmpbuf, sizeof(tmpbuf), "/proc/%d/exe", pid);
if ((sz = readlink(tmpbuf, s, FILE_PATH_SIZE)) < 0) {
trace(LOG_ALWAYS, "error: readlink() failed; pid %d, error %d %s",
pid, errno, errno_to_str(errno));
return NULL;
}
s[sz] = 0;
return s;
}
#endif
static void write_context(OutputStream * out, Context * ctx, int is_thread) {
assert(!ctx->exited);
out->write(out, '{');
json_write_string(out, "ID");
out->write(out, ':');
json_write_string(out, is_thread ? thread_id(ctx) : container_id(ctx));
if (is_thread) {
out->write(out, ',');
json_write_string(out, "ParentID");
out->write(out, ':');
json_write_string(out, container_id(ctx));
}
#if !defined(_WRS_KERNEL)
out->write(out, ',');
json_write_string(out, "ProcessID");
out->write(out, ':');
json_write_string(out, pid2id(ctx->mem, 0));
#endif
#if !defined(WIN32) && !defined(_WRS_KERNEL)
if (!ctx->exiting && !is_thread) {
out->write(out, ',');
json_write_string(out, "File");
out->write(out, ':');
json_write_string(out, get_executable(ctx->pid));
}
#endif
if (is_thread) {
out->write(out, ',');
json_write_string(out, "CanSuspend");
out->write(out, ':');
json_write_boolean(out, 1);
out->write(out, ',');
json_write_string(out, "CanResume");
out->write(out, ':');
json_write_long(out, (1 << RM_RESUME) | (1 << RM_STEP_INTO));
out->write(out, ',');
json_write_string(out, "HasState");
out->write(out, ':');
json_write_boolean(out, 1);
}
out->write(out, '}');
}
static void write_context_state(OutputStream * out, Context * ctx) {
char reason[128];
assert(!ctx->exited);
if (!ctx->intercepted) {
write_stringz(out, "0");
write_stringz(out, "null");
write_stringz(out, "null");
return;
}
/* Number: PC */
json_write_ulong(out, get_regs_PC(ctx->regs));
out->write(out, 0);
/* String: Reason */
if (ctx->event != 0) {
assert(ctx->signal == SIGTRAP);
snprintf(reason, sizeof(reason), "Event: %s", event_name(ctx->event));
}
else if (is_stopped_by_breakpoint(ctx)) {
strcpy(reason, "Breakpoint");
}
else if (ctx->signal == SIGSTOP || ctx->signal == SIGTRAP) {
strcpy(reason, "Suspended");
}
else if (signal_name(ctx->signal)) {
snprintf(reason, sizeof(reason), "Signal %d %s", ctx->signal, signal_name(ctx->signal));
}
else {
snprintf(reason, sizeof(reason), "Signal %d", ctx->signal);
}
json_write_string(out, reason);
out->write(out, 0);
/* Object: Aditional info */
out->write(out, '{');
json_write_string(out, "Event");
out->write(out, ':');
json_write_long(out, ctx->event);
out->write(out, ',');
json_write_string(out, "Signal");
out->write(out, ':');
json_write_long(out, ctx->signal);
if (signal_name(ctx->signal)) {
out->write(out, ',');
json_write_string(out, "SignalName");
out->write(out, ':');
json_write_string(out, signal_name(ctx->signal));
}
out->write(out, '}');
out->write(out, 0);
}
static void event_get_context(void * arg) {
GetContextArgs * s = (GetContextArgs *)arg;
OutputStream * out = s->out;
Context * ctx = s->ctx;
if (!is_stream_closed(out)) {
int err = 0;
write_stringz(out, "R");
write_stringz(out, s->token);
if (ctx->exited) err = ERR_ALREADY_EXITED;
write_errno(out, err);
if (err == 0) {
write_context(out, ctx, s->parent != 0);
out->write(out, 0);
}
else {
write_stringz(out, "null");
}
out->write(out, MARKER_EOM);
out->flush(out);
}
stream_unlock(out);
context_unlock(ctx);
loc_free(s);
}
static void command_get_context(char * token, InputStream * inp, OutputStream * out) {
int err = 0;
char id[256];
Context * ctx = NULL;
json_read_string(inp, id, sizeof(id));
if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX);
if (inp->read(inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
ctx = id2ctx(id);
if (ctx == NULL) err = ERR_INV_CONTEXT;
else if (ctx->exited) err = ERR_ALREADY_EXITED;
if (err) {
write_stringz(out, "R");
write_stringz(out, token);
write_errno(out, err);
write_stringz(out, "null");
out->write(out, MARKER_EOM);
}
else {
/* Need to stop everything to access context properties.
* In particular, proc FS access can fail when process is running.
*/
GetContextArgs * s = loc_alloc_zero(sizeof(GetContextArgs));
s->out = out;
stream_lock(out);
strcpy(s->token, token);
s->ctx = ctx;
context_lock(ctx);
id2pid(id, &s->parent);
post_safe_event(event_get_context, s);
}
}
static void command_get_children(char * token, InputStream * inp, OutputStream * out) {
char id[256];
json_read_string(inp, id, sizeof(id));
if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX);
if (inp->read(inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
write_stringz(out, "R");
write_stringz(out, token);
write_errno(out, 0);
out->write(out, '[');
if (id[0] == 0) {
LINK * qp;
int cnt = 0;
for (qp = context_root.next; qp != &context_root; qp = qp->next) {
Context * ctx = ctxl2ctxp(qp);
if (ctx->exited) continue;
if (ctx->parent != NULL) continue;
if (cnt > 0) out->write(out, ',');
json_write_string(out, container_id(ctx));
cnt++;
}
}
else if (id[0] == 'P') {
LINK * qp;
int cnt = 0;
pid_t ppd = 0;
pid_t pid = id2pid(id, &ppd);
Context * parent = id2ctx(id);
if (parent != NULL && parent->parent == NULL && ppd == 0) {
if (!parent->exited) {
if (cnt > 0) out->write(out, ',');
json_write_string(out, thread_id(parent));
cnt++;
}
for (qp = parent->children.next; qp != &parent->children; qp = qp->next) {
Context * ctx = cldl2ctxp(qp);
if (ctx->exited) continue;
assert(ctx->parent == parent);
if (cnt > 0) out->write(out, ',');
json_write_string(out,thread_id(ctx));
cnt++;
}
}
}
out->write(out, ']');
out->write(out, 0);
out->write(out, MARKER_EOM);
}
static void command_get_state(char * token, InputStream * inp, OutputStream * out) {
char id[256];
Context * ctx;
int err = 0;
json_read_string(inp, id, sizeof(id));
if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX);
if (inp->read(inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
ctx = id2ctx(id);
write_stringz(out, "R");
write_stringz(out, token);
if (ctx == NULL) err = ERR_INV_CONTEXT;
else if (ctx->exited) err = ERR_ALREADY_EXITED;
write_errno(out, err);
json_write_boolean(out, ctx != NULL && ctx->intercepted);
out->write(out, 0);
if (err) {
write_stringz(out, "0");
write_stringz(out, "null");
write_stringz(out, "null");
}
else {
write_context_state(out, ctx);
}
out->write(out, MARKER_EOM);
}
static void send_simple_result(OutputStream * out, char * token, int err) {
write_stringz(out, "R");
write_stringz(out, token);
write_errno(out, err);
out->write(out, MARKER_EOM);
}
static void send_event_context_resumed(OutputStream * out, Context * ctx);
static void done_skip_breakpoint(SkipBreakpointInfo * s) {
OutputStream * out = s->out;
if (!is_stream_closed(out)) {
send_simple_result(out, s->token, s->error);
out->flush(out);
}
}
static void command_resume(char * token, InputStream * inp, OutputStream * out) {
char id[256];
long mode;
long count;
Context * ctx;
int err = 0;
json_read_string(inp, id, sizeof(id));
if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX);
mode = json_read_long(inp);
if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX);
count = json_read_long(inp);
if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX);
if (inp->read(inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
ctx = id2ctx(id);
assert(safe_event_list == NULL);
if (ctx == NULL) {
err = ERR_INV_CONTEXT;
}
else if (ctx->exited) {
err = ERR_ALREADY_EXITED;
}
else if (!ctx->intercepted) {
err = ERR_ALREADY_RUNNING;
}
else if (ctx->regs_error) {
err = ctx->regs_error;
}
else if (count != 1) {
err = EINVAL;
}
else if (mode == RM_RESUME || mode == RM_STEP_INTO) {
SkipBreakpointInfo * sb = skip_breakpoint(ctx);
send_event_context_resumed(&broadcast_stream, ctx);
if (sb != NULL) {
if (mode == RM_STEP_INTO) sb->pending_intercept = 1;
sb->done = done_skip_breakpoint;
sb->out = out;
stream_lock(out);
strcpy(sb->token, token);
return;
}
if (mode == RM_STEP_INTO) {
if (context_single_step(ctx) < 0) {
err = errno;
}
else {
ctx->pending_intercept = 1;
}
}
else {
if (context_continue(ctx) < 0) err = errno;
}
}
else {
err = EINVAL;
}
send_simple_result(out, token, err);
}
static void send_event_context_suspended(OutputStream * out, Context * ctx);
static void command_suspend(char * token, InputStream * inp, OutputStream * out) {
char id[256];
Context * ctx;
int err = 0;
json_read_string(inp, id, sizeof(id));
if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX);
if (inp->read(inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
ctx = id2ctx(id);
if (ctx == NULL) {
err = ERR_INV_CONTEXT;
}
else if (ctx->exited) {
err = ERR_ALREADY_EXITED;
}
else if (ctx->intercepted) {
err = ERR_ALREADY_STOPPED;
}
else if (ctx->stopped) {
send_event_context_suspended(&broadcast_stream, ctx);
}
else {
ctx->pending_intercept = 1;
if (context_stop(ctx) < 0) err = errno;
}
send_simple_result(out, token, err);
}
static void command_not_supported(char * token, InputStream * inp, OutputStream * out) {
char id[256];
json_read_string(inp, id, sizeof(id));
if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX);
if (inp->read(inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
send_simple_result(out, token, ENOSYS);
}
static void send_event_context_added(OutputStream * out, Context * ctx) {
write_stringz(out, "E");
write_stringz(out, RUN_CONTROL);
write_stringz(out, "contextAdded");
/* <array of context data> */
out->write(out, '[');
if (ctx->parent == NULL) {
write_context(out, ctx, 0);
out->write(out, ',');
}
write_context(out, ctx, 1);
out->write(out, ']');
out->write(out, 0);
out->write(out, MARKER_EOM);
}
static void send_event_context_changed(OutputStream * out, Context * ctx) {
write_stringz(out, "E");
write_stringz(out, RUN_CONTROL);
write_stringz(out, "contextChanged");
/* <array of context data> */
out->write(out, '[');
if (ctx->parent == NULL) {
write_context(out, ctx, 0);
out->write(out, ',');
}
write_context(out, ctx, 1);
out->write(out, ']');
out->write(out, 0);
out->write(out, MARKER_EOM);
}
static void send_event_context_removed(OutputStream * out, Context * ctx) {
write_stringz(out, "E");
write_stringz(out, RUN_CONTROL);
write_stringz(out, "contextRemoved");
/* <array of context IDs> */
out->write(out, '[');
json_write_string(out, thread_id(ctx));
if (ctx->parent == NULL && list_is_empty(&ctx->children)) {
out->write(out, ',');
json_write_string(out, container_id(ctx));
}
out->write(out, ']');
out->write(out, 0);
out->write(out, MARKER_EOM);
}
static void send_event_context_suspended(OutputStream * out, Context * ctx) {
assert(!ctx->exited);
assert(!ctx->intercepted);
ctx->intercepted = 1;
ctx->pending_intercept = 0;
write_stringz(out, "E");
write_stringz(out, RUN_CONTROL);
write_stringz(out, "contextSuspended");
/* String: Context ID */
json_write_string(out, thread_id(ctx));
out->write(out, 0);
write_context_state(out, ctx);
out->write(out, MARKER_EOM);
}
static void send_event_context_resumed(OutputStream * out, Context * ctx) {
assert(ctx->intercepted);
assert(!ctx->pending_intercept);
ctx->intercepted = 0;
write_stringz(out, "E");
write_stringz(out, RUN_CONTROL);
write_stringz(out, "contextResumed");
/* String: Context ID */
json_write_string(out, thread_id(ctx));
out->write(out, 0);
out->write(out, MARKER_EOM);
}
static void send_event_context_exception(OutputStream * out, Context * ctx) {
char buf[128];
write_stringz(out, "E");
write_stringz(out, RUN_CONTROL);
write_stringz(out, "contextException");
/* String: Context ID */
json_write_string(out, thread_id(ctx));
out->write(out, 0);
/* String: Human readable description of the exception */
snprintf(buf, sizeof(buf), "Signal %d", ctx->signal);
json_write_string(out, buf);
out->write(out, 0);
out->write(out, MARKER_EOM);
}
int is_all_stopped(void) {
LINK * qp;
for (qp = context_root.next; qp != &context_root; qp = qp->next) {
Context * ctx = ctxl2ctxp(qp);
if (ctx->exited || ctx->exiting) continue;
if (!ctx->stopped) return 0;
}
return are_channels_suspended();
}
static void continue_temporary_stopped(void * arg) {
LINK * qp;
if ((int)arg != safe_event_generation) return;
assert(safe_event_list == NULL);
if (channels_get_message_count() > 0) {
post_event(continue_temporary_stopped, (void *)safe_event_generation);
return;
}
for (qp = context_root.next; qp != &context_root; qp = qp->next) {
Context * ctx = ctxl2ctxp(qp);
if (ctx->exited) continue;
if (!ctx->stopped) continue;
if (ctx->intercepted) continue;
if (ctx->pending_step) continue;
context_continue(ctx);
}
}
static void run_safe_events(void * arg) {
LINK * qp;
if ((int)arg != safe_event_generation) return;
assert(safe_event_list != NULL);
assert(are_channels_suspended());
for (qp = context_root.next; qp != &context_root; qp = qp->next) {
Context * ctx = ctxl2ctxp(qp);
if (ctx->exited || ctx->exiting) continue;
if (!ctx->pending_step) {
int error = 0;
if (ctx->stopped) continue;
if (context_stop(ctx) < 0) {
error = errno;
#ifdef _WRS_KERNEL
if (error == S_vxdbgLib_INVALID_CTX) {
/* Most often this means that context has exited,
* but exit event is not delivered yet.
* Not an error. */
error = 0;
}
#endif
}
if (error) {
trace(LOG_ALWAYS, "error: can't temporary stop pid %d; error %d: %s",
ctx->pid, error, errno_to_str(error));
}
}
if (!ctx->pending_safe_event) {
ctx->pending_safe_event = 1;
safe_event_pid_count++;
}
else if (ctx->pending_safe_event == STOP_ALL_MAX_CNT) {
trace(LOG_ALWAYS, "error: can't temporary stop pid %d; error: timeout", ctx->pid);
ctx->exiting = 1;
ctx->pending_safe_event = 0;
safe_event_pid_count--;
}
else {
ctx->pending_safe_event++;
}
}
if ((int)arg != safe_event_generation) return;
while (safe_event_list) {
SafeEvent * i = safe_event_list;
if (safe_event_pid_count > 0) {
post_event_with_delay(run_safe_events, (void *)++safe_event_generation, STOP_ALL_TIMEOUT);
return;
}
assert(is_all_stopped());
safe_event_list = i->next;
i->done(i->arg);
loc_free(i);
if ((int)arg != safe_event_generation) return;
}
channels_resume();
/* Lazily continue execution of temporary stopped contexts */
post_event(continue_temporary_stopped, (void *)safe_event_generation);
}
static void check_safe_events(Context * ctx) {
assert(ctx->stopped || ctx->exited);
assert(ctx->pending_safe_event);
assert(safe_event_list != NULL);
assert(safe_event_pid_count > 0);
ctx->pending_safe_event = 0;
safe_event_pid_count--;
if (safe_event_pid_count == 0) {
post_event(run_safe_events, (void *)++safe_event_generation);
}
}
void post_safe_event(void (*done)(void *), void * arg) {
SafeEvent * i = (SafeEvent *)loc_alloc(sizeof(SafeEvent));
i->done = done;
i->arg = arg;
if (safe_event_list == NULL) {
assert(safe_event_pid_count == 0);
channels_suspend();
post_event(run_safe_events, (void *)++safe_event_generation);
}
assert(are_channels_suspended());
i->next = safe_event_list;
safe_event_list = i;
}
static void event_context_created(Context * ctx) {
assert(!ctx->exited);
assert(!ctx->intercepted);
assert(!ctx->stopped);
send_event_context_added(&broadcast_stream, ctx);
broadcast_stream.flush(&broadcast_stream);
}
static void event_context_changed(Context * ctx) {
send_event_context_changed(&broadcast_stream, ctx);
broadcast_stream.flush(&broadcast_stream);
}
static void event_context_stopped(Context * ctx) {
assert(ctx->stopped);
assert(!ctx->intercepted);
assert(!ctx->exited);
if (ctx->pending_safe_event) check_safe_events(ctx);
if (is_stopped_by_breakpoint(ctx)) {
if (evaluate_breakpoint_condition(ctx)) {
ctx->pending_intercept = 1;
}
else {
skip_breakpoint(ctx);
}
}
else if (ctx->signal != SIGSTOP && ctx->signal != SIGTRAP) {
send_event_context_exception(&broadcast_stream, ctx);
ctx->pending_intercept = 1;
}
if (ctx->pending_intercept) {
send_event_context_suspended(&broadcast_stream, ctx);
broadcast_stream.flush(&broadcast_stream);
}
if (!ctx->intercepted && safe_event_list == NULL) {
context_continue(ctx);
}
}
static void event_context_started(Context * ctx) {
assert(!ctx->stopped);
assert(!ctx->intercepted);
ctx->stopped_by_bp = 0;
if (safe_event_list) {
if (!ctx->pending_step) {
context_stop(ctx);
}
if (!ctx->pending_safe_event) {
ctx->pending_safe_event = 1;
safe_event_pid_count++;
}
}
}
static void event_context_exited(Context * ctx) {
assert(!ctx->stopped);
assert(!ctx->intercepted);
if (ctx->pending_safe_event) check_safe_events(ctx);
send_event_context_removed(&broadcast_stream, ctx);
broadcast_stream.flush(&broadcast_stream);
}
void ini_run_ctrl_service(void) {
static ContextEventListener listener = {
event_context_created,
event_context_exited,
event_context_stopped,
event_context_started,
event_context_changed,
NULL
};
add_context_event_listener(&listener);
add_command_handler(RUN_CONTROL, "getContext", command_get_context);
add_command_handler(RUN_CONTROL, "getChildren", command_get_children);
add_command_handler(RUN_CONTROL, "getState", command_get_state);
add_command_handler(RUN_CONTROL, "resume", command_resume);
add_command_handler(RUN_CONTROL, "suspend", command_suspend);
add_command_handler(RUN_CONTROL, "terminate", command_not_supported);
}
#endif