blob: f833ed0e3645b217aa5759425b3207dc15fa703c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2014 Xilinx, 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
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* Xilinx - initial API and implementation
*******************************************************************************/
/*
* "dynamic printf" service
*/
#include <tcf/config.h>
#if SERVICE_DPrintf && SERVICE_Expressions && SERVICE_Streams
#include <assert.h>
#include <tcf/framework/trace.h>
#include <tcf/framework/cache.h>
#include <tcf/framework/json.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/exceptions.h>
#include <tcf/services/streamsservice.h>
#include <tcf/services/runctrl.h>
#include <tcf/services/dprintf.h>
static const char * DPRINTF = "DPrintf";
typedef struct Buffer Buffer;
typedef struct Client Client;
struct Buffer {
LINK link;
char * buf;
size_t done;
size_t size;
};
struct Client {
LINK link;
LINK bufs;
Channel * channel;
VirtualStream * vstream;
Buffer * queue;
char * tmp_buf;
unsigned tmp_pos;
unsigned tmp_max;
};
#define link2buf(x) ((Buffer *)((char *)(x) - offsetof(Buffer, link)))
#define link2client(x) ((Client *)((char *)(x) - offsetof(Client, link)))
static LINK clients;
static Client * find_client(Channel * channel) {
LINK * l;
if (channel == NULL) return NULL;
for (l = clients.next; l != &clients; l = l->next) {
Client * client = link2client(l);
if (client->channel == channel) return client;
}
return NULL;
}
static void add_ch(Client * client, char ch) {
if (client->tmp_pos >= client->tmp_max) {
client->tmp_max += 256;
client->tmp_buf = (char *)loc_realloc(client->tmp_buf, client->tmp_max);
}
client->tmp_buf[client->tmp_pos++] = ch;
}
static const void * load_remote_string(Context * ctx, Value * arg_val) {
int error = 0;
size_t sbf_pos = 0;
size_t sbf_max = 128;
char * sbf = (char *)tmp_alloc(sbf_max);
ContextAddress addr = 0;
if (ctx == NULL) error = ERR_INV_CONTEXT;
if (!error && ctx->mem_access == 0) ctx = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
if (!error && ctx->mem_access == 0) error = ERR_INV_CONTEXT;
if (!error && value_to_address(arg_val, &addr) < 0) error = errno;
while (!error) {
char ch = 0;
if (sbf_pos >= sbf_max) {
sbf_max *= 2;
sbf = (char *)tmp_realloc(sbf, sbf_max);
}
if (context_read_mem(ctx, addr, &ch, 1) < 0) {
error = errno;
}
else {
sbf[sbf_pos++] = ch;
if (ch == 0) return sbf;
addr++;
}
}
return "???";
}
void dprintf_expression_ctx(Context * ctx, const char * fmt, Value * args, unsigned args_cnt) {
unsigned fmt_pos = 0;
unsigned arg_pos = 0;
Client * client = find_client(cache_channel());
if (client == NULL) return;
while (fmt[fmt_pos]) {
char ch = fmt[fmt_pos];
if (ch == 0) break;
if (ch == '%' && fmt[fmt_pos + 1] == '%') {
fmt_pos++;
}
else if (ch == '%' && arg_pos < args_cnt) {
char arg_buf[256];
char arg_fmt[256];
unsigned pos = fmt_pos++;
unsigned arg_len = 0;
unsigned flag_l = 0;
unsigned flag_h = 0;
unsigned flag_L = 0;
unsigned flag_j = 0;
unsigned flag_z = 0;
unsigned flag_t = 0;
char fmt_ch = 0;
Value * arg_val = args + arg_pos++;
while (fmt[fmt_pos] && fmt_pos - pos < sizeof(arg_fmt)) {
ch = fmt[fmt_pos];
if (ch == 0) break;
fmt_pos++;
switch (ch) {
case 'l': flag_l++; continue;
case 'L': flag_L++; continue;
case 'h': flag_h++; continue;
case 'j': flag_j++; continue;
case 'z': flag_z++; continue;
case 't': flag_t++; continue;
}
if (ch == '%' || ch >= 'A') {
fmt_ch = ch;
break;
}
}
if (fmt_ch != '%') {
int64_t n = 0;
uint64_t u = 0;
double d = 0;
memcpy(arg_fmt, fmt + pos, fmt_pos - pos);
arg_fmt[fmt_pos - pos] = 0;
arg_buf[0] = 0;
switch (fmt_ch) {
case 'd':
case 'i':
if (arg_val->type_class == TYPE_CLASS_INTEGER || arg_val->type_class == TYPE_CLASS_ENUMERATION) {
if (value_to_signed(arg_val, &n) < 0) exception(errno);
}
else {
if (value_to_unsigned(arg_val, &u) < 0) exception(errno);
n = u;
}
if (flag_l > 1) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (long long)n);
else if (flag_l) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (long)n);
else if (flag_j) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (long long)n);
else if (flag_z) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (size_t)n);
else if (flag_t) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (ptrdiff_t)n);
else if (flag_h > 1) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (char)n);
else if (flag_h) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (short)n);
else snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (int)n);
break;
case 'o':
case 'u':
case 'x':
case 'X':
if (arg_val->type_class == TYPE_CLASS_INTEGER || arg_val->type_class == TYPE_CLASS_ENUMERATION) {
if (value_to_signed(arg_val, &n) < 0) exception(errno);
}
else {
if (value_to_unsigned(arg_val, &u) < 0) exception(errno);
n = u;
}
if (flag_l > 1) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (unsigned long long)n);
else if (flag_l) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (unsigned long)n);
else if (flag_j) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (unsigned long long)n);
else if (flag_z) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (size_t)n);
else if (flag_t) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (ptrdiff_t)n);
else if (flag_h > 1) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (unsigned char)n);
else if (flag_h) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (unsigned short)n);
else snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (unsigned int)n);
break;
case 'c':
case 'C':
if (value_to_signed(arg_val, &n) < 0) exception(errno);
snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (int)n);
break;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
case 'a':
case 'A':
if (value_to_double(arg_val, &d) < 0) exception(errno);
if (flag_L) snprintf(arg_buf, sizeof(arg_buf), arg_fmt, (long double)d);
else snprintf(arg_buf, sizeof(arg_buf), arg_fmt, d);
break;
case 's':
{
const void * value = arg_val->value;
if (arg_val->type_class == TYPE_CLASS_POINTER) {
value = load_remote_string(ctx, arg_val);
}
snprintf(arg_buf, sizeof(arg_buf), arg_fmt, value);
}
break;
default:
snprintf(arg_buf, sizeof(arg_buf), arg_fmt, arg_val->value);
break;
}
arg_len = strlen(arg_buf);
if (client->tmp_pos + arg_len >= client->tmp_max) {
client->tmp_max += arg_len + 256;
client->tmp_buf = (char *)loc_realloc(client->tmp_buf, client->tmp_max);
}
memcpy(client->tmp_buf + client->tmp_pos, arg_buf, arg_len);
client->tmp_pos += arg_len;
continue;
}
}
add_ch(client, ch);
fmt_pos++;
}
}
static void streams_callback(VirtualStream * stream, int event_code, void * args) {
Client * client = (Client *)args;
assert(stream == client->vstream);
if (event_code == VS_EVENT_SPACE_AVAILABLE && !list_is_empty(&client->bufs)) {
size_t done = 0;
Buffer * b = link2buf(client->bufs.next);
virtual_stream_add_data(stream, b->buf + b->done, b->size - b->done, &done, 0);
b->done += done;
if (b->done >= b->size) {
list_remove(&b->link);
if (list_is_empty(&client->bufs)) run_ctrl_unlock();
loc_free(b->buf);
loc_free(b);
}
}
}
static void read_open_args(InputStream * inp, const char * name, void * x) {
json_skip_object(inp);
}
static void command_open(char * token, Channel * c) {
char id[256];
Client * client = find_client(c);
json_read_struct(&c->inp, read_open_args, NULL);
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
if (client == NULL) {
client = (Client *)loc_alloc_zero(sizeof(Client));
virtual_stream_create(DPRINTF, NULL, 0x1000,
VS_ENABLE_REMOTE_READ, streams_callback, client, &client->vstream);
list_add_first(&client->link, &clients);
list_init(&client->bufs);
client->channel = c;
}
virtual_stream_get_id(client->vstream, id, sizeof(id));
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, 0);
json_write_string(&c->out, id);
write_stream(&c->out, MARKER_EOA);
write_stream(&c->out, MARKER_EOM);
}
static void free_client(Client * client) {
virtual_stream_delete(client->vstream);
list_remove(&client->link);
if (!list_is_empty(&client->bufs)) {
do {
Buffer * bf = link2buf(client->bufs.next);
list_remove(&bf->link);
loc_free(bf->buf);
loc_free(bf);
}
while (!list_is_empty(&client->bufs));
run_ctrl_unlock();
}
loc_free(client->tmp_buf);
loc_free(client);
}
static void command_close(char * token, Channel * c) {
Client * client = find_client(c);
json_test_char(&c->inp, MARKER_EOM);
if (client != NULL) free_client(client);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, 0);
write_stream(&c->out, MARKER_EOM);
}
static void cache_transaction_listener(int evt) {
LINK * l;
switch (evt) {
case CTLE_START:
case CTLE_RETRY:
for (l = clients.next; l != &clients; l = l->next) {
Client * client = link2client(l);
client->tmp_pos = 0;
}
break;
case CTLE_ABORT:
break;
case CTLE_COMMIT:
for (l = clients.next; l != &clients; l = l->next) {
Client * client = link2client(l);
if (client->tmp_pos > 0) {
size_t done = 0;
virtual_stream_add_data(client->vstream, client->tmp_buf, client->tmp_pos, &done, 0);
if (done < client->tmp_pos) {
Buffer * b = (Buffer *)loc_alloc_zero(sizeof(Buffer));
b->size = client->tmp_pos - done;
b->buf = (char *)loc_alloc(b->size);
memcpy(b->buf, client->tmp_buf + done, b->size);
if (list_is_empty(&client->bufs)) run_ctrl_lock();
list_add_last(&b->link, &client->bufs);
}
}
}
break;
}
}
static void channel_close_listener(Channel * c) {
Client * client = find_client(c);
if (client != NULL) free_client(client);
}
static void function_callback(int mode, Value * v, Value * args, unsigned args_cnt) {
if (args_cnt == 0) {
str_exception(ERR_INV_EXPRESSION, "$printf mush have at least one argument");
}
if (mode != EXPRESSION_MODE_SKIP && args[0].type_class != TYPE_CLASS_ARRAY) {
str_exception(ERR_INV_EXPRESSION, "$printf first argument must be a string");
}
if (mode == EXPRESSION_MODE_NORMAL) {
dprintf_expression_ctx(v->ctx, (char *)args[0].value, args + 1, args_cnt - 1);
}
set_value(v, NULL, 0, 0);
}
static int identifier_callback(Context * ctx, int frame, char * name, Value * v) {
if (strcmp(name, "$printf") == 0) {
v->func_cb = function_callback;
v->type_class = TYPE_CLASS_FUNCTION;
return 1;
}
return 0;
}
void ini_dprintf_service(Protocol * p) {
list_init(&clients);
add_command_handler(p, DPRINTF, "open", command_open);
add_command_handler(p, DPRINTF, "close", command_close);
add_cache_transaction_listener(cache_transaction_listener);
add_channel_close_listener(channel_close_listener);
add_identifier_callback(identifier_callback);
}
#endif /* SERVICE_DPrintf */