| /******************************************************************************* |
| * Copyright (c) 2007, 2010 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 |
| * 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: |
| * Wind River Systems - initial API and implementation |
| *******************************************************************************/ |
| |
| /* |
| * Target service implementation: stack trace (TCF name StackTrace) |
| */ |
| |
| #include <config.h> |
| |
| #if SERVICE_StackTrace |
| |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <framework/myalloc.h> |
| #include <framework/protocol.h> |
| #include <framework/trace.h> |
| #include <framework/context.h> |
| #include <framework/json.h> |
| #include <framework/cache.h> |
| #include <framework/exceptions.h> |
| #include <services/registers.h> |
| #include <services/stacktrace.h> |
| #include <services/symbols.h> |
| |
| #define MAX_FRAMES 1000 |
| |
| static const char * STACKTRACE = "StackTrace"; |
| |
| typedef struct StackTrace { |
| ErrorReport * error; |
| int valid; |
| int frame_cnt; |
| int frame_max; |
| StackFrame * frames; /* ordered bottom to top */ |
| } StackTrace; |
| |
| static size_t context_extension_offset = 0; |
| |
| #define EXT(ctx) ((StackTrace *)((char *)(ctx) + context_extension_offset)) |
| |
| static void add_frame(StackTrace * stack, StackFrame * frame) { |
| if (stack->frame_cnt >= stack->frame_max) { |
| stack->frame_max += 32; |
| stack->frames = (StackFrame *)loc_realloc(stack->frames, |
| stack->frame_max * sizeof(StackFrame)); |
| } |
| stack->frames[stack->frame_cnt++] = *frame; |
| } |
| |
| static void invalidate_stack_trace(StackTrace * stack) { |
| int i; |
| release_error_report(stack->error); |
| for (i = 0; i < stack->frame_cnt; i++) { |
| loc_free(stack->frames[i].regs); |
| stack->frames[i].regs = NULL; |
| } |
| stack->error = NULL; |
| stack->frame_cnt = 0; |
| stack->valid = 0; |
| } |
| |
| static void trace_stack(Context * ctx, StackTrace * stack) { |
| int i; |
| int error = 0; |
| StackFrame frame; |
| ContextAddress prev_fp = 0; |
| |
| stack->frame_cnt = 0; |
| memset(&frame, 0, sizeof(frame)); |
| frame.is_top_frame = 1; |
| frame.ctx = ctx; |
| trace(LOG_STACK, "Stack trace, ctx %s", ctx->id); |
| while (stack->frame_cnt < MAX_FRAMES) { |
| StackFrame down; |
| memset(&down, 0, sizeof(down)); |
| down.ctx = ctx; |
| trace(LOG_STACK, "Frame %d", stack->frame_cnt); |
| #if ENABLE_Trace |
| if (LOG_STACK & log_mode) { |
| uint64_t v; |
| RegisterDefinition * r; |
| for (r = get_reg_definitions(ctx); r->name != NULL; r++) { |
| if (read_reg_value(&frame, r, &v) == 0) { |
| trace(LOG_STACK, " %-8s %16"PRIX64, r->name, v); |
| } |
| } |
| } |
| #endif |
| #if ENABLE_Symbols |
| if (get_next_stack_frame(&frame, &down) < 0) { |
| error = errno; |
| trace(LOG_STACK, " trace error: %s", errno_to_str(errno)); |
| loc_free(down.regs); |
| break; |
| } |
| #endif |
| if (frame.fp == 0) { |
| trace(LOG_STACK, " *** frame info not available ***"); |
| loc_free(down.regs); |
| memset(&down, 0, sizeof(down)); |
| down.ctx = ctx; |
| if (crawl_stack_frame(&frame, &down) < 0) { |
| error = errno; |
| trace(LOG_STACK, " crawl error: %s", errno_to_str(errno)); |
| loc_free(down.regs); |
| break; |
| } |
| } |
| trace(LOG_STACK, " cfa %16"PRIX64, (uint64_t)frame.fp); |
| if (stack->frame_cnt > 0 && frame.fp == prev_fp) { |
| loc_free(down.regs); |
| break; |
| } |
| add_frame(stack, &frame); |
| prev_fp = frame.fp; |
| frame = down; |
| } |
| |
| if (frame.has_reg_data) add_frame(stack, &frame); |
| else loc_free(frame.regs); |
| |
| if (get_error_code(error) == ERR_CACHE_MISS) { |
| invalidate_stack_trace(stack); |
| stack->error = get_error_report(ERR_CACHE_MISS); |
| } |
| else if (error && stack->frame_cnt == 0) { |
| stack->error = get_error_report(error); |
| } |
| for (i = 0; i < stack->frame_cnt / 2; i++) { |
| StackFrame f = stack->frames[i]; |
| stack->frames[i] = stack->frames[stack->frame_cnt - i - 1]; |
| stack->frames[stack->frame_cnt - i - 1] = f; |
| } |
| } |
| |
| static StackTrace * create_stack_trace(Context * ctx) { |
| StackTrace * stack = EXT(ctx); |
| if (!stack->valid) { |
| assert(stack->frame_cnt == 0); |
| release_error_report(stack->error); |
| stack->error = NULL; |
| stack->valid = 1; |
| trace_stack(ctx, stack); |
| } |
| return stack; |
| } |
| |
| static void write_context(OutputStream * out, char * id, Context * ctx, int level, StackFrame * frame, StackFrame * down) { |
| uint64_t v; |
| RegisterDefinition * reg_def = get_PC_definition(ctx); |
| |
| write_stream(out, '{'); |
| |
| json_write_string(out, "ID"); |
| write_stream(out, ':'); |
| json_write_string(out, id); |
| |
| write_stream(out, ','); |
| json_write_string(out, "ParentID"); |
| write_stream(out, ':'); |
| json_write_string(out, ctx->id); |
| |
| write_stream(out, ','); |
| json_write_string(out, "Level"); |
| write_stream(out, ':'); |
| json_write_long(out, level); |
| |
| write_stream(out, ','); |
| json_write_string(out, "ProcessID"); |
| write_stream(out, ':'); |
| json_write_string(out, context_get_group(ctx, CONTEXT_GROUP_PROCESS)->id); |
| |
| if (frame->is_top_frame) { |
| write_stream(out, ','); |
| json_write_string(out, "TopFrame"); |
| write_stream(out, ':'); |
| json_write_boolean(out, 1); |
| } |
| |
| if (frame->fp) { |
| write_stream(out, ','); |
| json_write_string(out, "FP"); |
| write_stream(out, ':'); |
| json_write_uint64(out, frame->fp); |
| } |
| |
| if (read_reg_value(frame, reg_def, &v) == 0) { |
| write_stream(out, ','); |
| json_write_string(out, "IP"); |
| write_stream(out, ':'); |
| json_write_uint64(out, v); |
| } |
| |
| if (down != NULL && read_reg_value(down, reg_def, &v) == 0) { |
| write_stream(out, ','); |
| json_write_string(out, "RP"); |
| write_stream(out, ':'); |
| json_write_uint64(out, v); |
| } |
| |
| write_stream(out, '}'); |
| } |
| |
| typedef struct CommandGetContextData { |
| Context * ctx; |
| int frame; |
| StackFrame * info; |
| StackFrame * down; |
| } CommandGetContextData; |
| |
| typedef struct CommandGetContextArgs { |
| char token[256]; |
| int id_cnt; |
| char ** ids; |
| CommandGetContextData * data; |
| } CommandGetContextArgs; |
| |
| static void command_get_context_cache_client(void * x) { |
| int i; |
| int err = 0; |
| CommandGetContextArgs * args = (CommandGetContextArgs *)x; |
| Channel * c = cache_channel(); |
| |
| memset(args->data, 0, sizeof(CommandGetContextData) * args->id_cnt); |
| for (i = 0; i < args->id_cnt; i++) { |
| StackTrace * stack = NULL; |
| CommandGetContextData * d = args->data + i; |
| if (id2frame(args->ids[i], &d->ctx, &d->frame) < 0) { |
| err = errno; |
| break; |
| } |
| if (!d->ctx->stopped) { |
| err = ERR_IS_RUNNING; |
| break; |
| } |
| stack = create_stack_trace(d->ctx); |
| if (stack->error) { |
| err = set_error_report_errno(stack->error); |
| break; |
| } |
| if (d->frame >= stack->frame_cnt) { |
| err = ERR_INV_CONTEXT; |
| break; |
| } |
| d->info = stack->frames + d->frame; |
| d->down = d->frame > 0 ? d->info - 1 : NULL; |
| } |
| |
| cache_exit(); |
| |
| write_stringz(&c->out, "R"); |
| write_stringz(&c->out, args->token); |
| write_stream(&c->out, '['); |
| for (i = 0; i < args->id_cnt; i++) { |
| CommandGetContextData * d = args->data + i; |
| if (i > 0) write_stream(&c->out, ','); |
| if (d->info == NULL) { |
| write_string(&c->out, "null"); |
| } |
| else { |
| write_context(&c->out, args->ids[i], d->ctx, d->frame, d->info, d->down); |
| } |
| } |
| write_stream(&c->out, ']'); |
| write_stream(&c->out, 0); |
| write_errno(&c->out, err); |
| write_stream(&c->out, MARKER_EOM); |
| |
| loc_free(args->ids); |
| loc_free(args->data); |
| } |
| |
| static void command_get_context(char * token, Channel * c) { |
| CommandGetContextArgs args; |
| |
| args.ids = json_read_alloc_string_array(&c->inp, &args.id_cnt); |
| if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX); |
| if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX); |
| |
| args.data = (CommandGetContextData *)loc_alloc(sizeof(CommandGetContextData) * args.id_cnt); |
| strlcpy(args.token, token, sizeof(args.token)); |
| cache_enter(command_get_context_cache_client, c, &args, sizeof(args)); |
| } |
| |
| typedef struct CommandGetChildrenArgs { |
| char token[256]; |
| char id[256]; |
| } CommandGetChildrenArgs; |
| |
| static void command_get_children_cache_client(void * x) { |
| int err = 0; |
| Context * ctx = NULL; |
| StackTrace * stack = NULL; |
| CommandGetChildrenArgs * args = (CommandGetChildrenArgs *)x; |
| Channel * c = cache_channel(); |
| |
| ctx = id2ctx(args->id); |
| if (ctx == NULL || !context_has_state(ctx)) { |
| /* no children */ |
| } |
| else if (!ctx->stopped) { |
| err = ERR_IS_RUNNING; |
| } |
| else { |
| stack = create_stack_trace(ctx); |
| } |
| |
| cache_exit(); |
| |
| write_stringz(&c->out, "R"); |
| write_stringz(&c->out, args->token); |
| |
| write_errno(&c->out, stack != NULL ? set_error_report_errno(stack->error) : err); |
| |
| if (stack == NULL) { |
| write_stringz(&c->out, "null"); |
| } |
| else { |
| int i; |
| write_stream(&c->out, '['); |
| for (i = 0; i < stack->frame_cnt; i++) { |
| if (i > 0) write_stream(&c->out, ','); |
| json_write_string(&c->out, frame2id(ctx, i)); |
| } |
| write_stream(&c->out, ']'); |
| write_stream(&c->out, 0); |
| } |
| |
| write_stream(&c->out, MARKER_EOM); |
| } |
| |
| static void command_get_children(char * token, Channel * c) { |
| CommandGetChildrenArgs args; |
| |
| json_read_string(&c->inp, args.id, sizeof(args.id)); |
| if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX); |
| if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX); |
| |
| strlcpy(args.token, token, sizeof(args.token)); |
| cache_enter(command_get_children_cache_client, c, &args, sizeof(args)); |
| } |
| |
| int get_top_frame(Context * ctx) { |
| StackTrace * stack; |
| |
| if (!ctx->stopped) { |
| errno = ERR_IS_RUNNING; |
| return STACK_TOP_FRAME; |
| } |
| |
| stack = create_stack_trace(ctx); |
| if (stack->error != NULL) { |
| set_error_report_errno(stack->error); |
| return STACK_TOP_FRAME; |
| } |
| |
| assert(stack->frame_cnt > 0); |
| return stack->frame_cnt - 1; |
| } |
| |
| int get_frame_info(Context * ctx, int frame, StackFrame ** info) { |
| StackTrace * stack; |
| |
| *info = NULL; |
| if (ctx == NULL || !context_has_state(ctx)) { |
| errno = ERR_INV_CONTEXT; |
| return -1; |
| } |
| if (!ctx->stopped) { |
| errno = ERR_IS_RUNNING; |
| return -1; |
| } |
| |
| stack = create_stack_trace(ctx); |
| if (stack->error != NULL) { |
| set_error_report_errno(stack->error); |
| return -1; |
| } |
| |
| if (frame == STACK_TOP_FRAME) { |
| if (stack->frame_cnt == 0) { |
| errno = set_errno(ERR_INV_CONTEXT, "No such stack frame"); |
| return -1; |
| } |
| frame = stack->frame_cnt - 1; |
| } |
| else if (frame < 0 || frame >= stack->frame_cnt) { |
| errno = set_errno(ERR_INV_CONTEXT, "No such stack frame"); |
| return -1; |
| } |
| |
| *info = stack->frames + frame; |
| return 0; |
| } |
| |
| int is_top_frame(Context * ctx, int frame) { |
| StackTrace * stack; |
| |
| if (ctx == NULL || !context_has_state(ctx)) return 0; |
| if (frame == STACK_TOP_FRAME) return 1; |
| if (!ctx->stopped) return 0; |
| stack = create_stack_trace(ctx); |
| if (stack->error != NULL) return 0; |
| return frame == stack->frame_cnt - 1; |
| } |
| |
| static void flush_stack_trace(Context * ctx, void * args) { |
| invalidate_stack_trace(EXT(ctx)); |
| } |
| |
| static void flush_on_register_change(Context * ctx, int frame, RegisterDefinition * def, void * args) { |
| invalidate_stack_trace(EXT(ctx)); |
| } |
| |
| static void delete_stack_trace(Context * ctx, void * args) { |
| invalidate_stack_trace(EXT(ctx)); |
| loc_free(EXT(ctx)->frames); |
| memset(EXT(ctx), 0, sizeof(StackTrace)); |
| } |
| |
| void ini_stack_trace_service(Protocol * proto, TCFBroadcastGroup * bcg) { |
| static ContextEventListener context_listener = { |
| NULL, |
| flush_stack_trace, |
| NULL, |
| flush_stack_trace, |
| flush_stack_trace, |
| delete_stack_trace |
| }; |
| static RegistersEventListener registers_listener = { |
| flush_on_register_change, |
| }; |
| add_context_event_listener(&context_listener, bcg); |
| add_registers_event_listener(®isters_listener, bcg); |
| add_command_handler(proto, STACKTRACE, "getContext", command_get_context); |
| add_command_handler(proto, STACKTRACE, "getChildren", command_get_children); |
| context_extension_offset = context_extension(sizeof(StackTrace)); |
| } |
| |
| #endif |