| /******************************************************************************* |
| * Copyright (c) 2007, 2016 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 <tcf/config.h> |
| |
| #if SERVICE_StackTrace |
| |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <tcf/framework/myalloc.h> |
| #include <tcf/framework/protocol.h> |
| #include <tcf/framework/trace.h> |
| #include <tcf/framework/context.h> |
| #include <tcf/framework/json.h> |
| #include <tcf/framework/cache.h> |
| #include <tcf/framework/exceptions.h> |
| #include <tcf/services/registers.h> |
| #include <tcf/services/symbols.h> |
| #include <tcf/services/linenumbers.h> |
| #include <tcf/services/memorymap.h> |
| #include <tcf/services/stacktrace.h> |
| |
| #define MAX_FRAMES 1000 |
| |
| static const char * STACKTRACE = "StackTrace"; |
| |
| typedef struct StackTrace { |
| int inlined; |
| int complete; |
| int frame_cnt; |
| int frame_max; |
| StackFrame * frames; /* ordered top (current) to bottom */ |
| } 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) { |
| frame->frame = stack->frame_cnt; |
| 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 free_frame(StackFrame * frame) { |
| int error = errno; |
| if (frame->area != NULL) { |
| loc_free(frame->area->directory); |
| loc_free(frame->area->file); |
| loc_free(frame->area); |
| frame->area = NULL; |
| } |
| loc_free(frame->regs); |
| loc_free(frame->func_id); |
| frame->regs = NULL; |
| frame->func_id = NULL; |
| errno = error; |
| } |
| |
| static int get_frame_debug_info(StackFrame * frame, StackTracingInfo ** info) { |
| uint64_t ip = 0; |
| Context * ctx = frame->ctx; |
| if (read_reg_value(frame, get_PC_definition(ctx), &ip) < 0) { |
| if (frame->is_top_frame) return -1; |
| return 0; |
| } |
| if (ip > 0 && !frame->is_top_frame) { |
| StackTrace * stk = EXT(ctx); |
| if (frame->frame > stk->frame_cnt) { |
| ip--; |
| } |
| else { |
| StackFrame * up = stk->frames + (frame->frame - 1); |
| /* Walked reg values are saved before call, use (return address) - 1 to search next frame info. |
| * Stack crawl regs are computed after return, use return address */ |
| if (up->is_walked) { |
| ip--; |
| } |
| #if ENABLE_Symbols |
| else { |
| /* Workaround for missing frame info for return address of a function that never returns */ |
| Symbol * sym = NULL; |
| int sym_class = SYM_CLASS_UNKNOWN; |
| ContextAddress sym_addr = 0; |
| ContextAddress sym_size = 0; |
| if (find_symbol_by_addr(ctx, STACK_NO_FRAME, ip - 1, &sym) == 0 && |
| get_symbol_class(sym, &sym_class) == 0 && sym_class == SYM_CLASS_FUNCTION && |
| get_symbol_size(sym, &sym_size) == 0 && sym_size != 0 && |
| get_symbol_address(sym, &sym_addr) == 0 && sym_addr != 0 && |
| sym_addr + sym_size > sym_addr && sym_addr + sym_size <= ip) { |
| ip--; |
| } |
| } |
| #endif |
| } |
| } |
| return get_stack_tracing_info(ctx, (ContextAddress)ip, info); |
| } |
| |
| int get_next_stack_frame(StackFrame * frame, StackFrame * down) { |
| #if ENABLE_Symbols |
| int error = 0; |
| Context * ctx = frame->ctx; |
| StackTracingInfo * info = NULL; |
| int frame_idx = frame->frame; |
| |
| memset(down, 0, sizeof(StackFrame)); |
| |
| if (get_frame_debug_info(frame, &info) < 0) { |
| error = errno; |
| } |
| else if (info != NULL) { |
| Trap trap; |
| if (set_trap(&trap)) { |
| int i; |
| LocationExpressionState * state; |
| state = evaluate_location_expression(ctx, frame, info->fp->cmds, info->fp->cmds_cnt, NULL, 0); |
| if (state->stk_pos != 1) str_exception(ERR_OTHER, "Invalid stack trace expression"); |
| frame->fp = (ContextAddress)state->stk[0]; |
| if (info->sub_cnt > 0) { |
| size_t buf_size = 8; |
| uint8_t * buf = (uint8_t *)tmp_alloc(buf_size); |
| StackTrace * stk = EXT(ctx); |
| #if ENABLE_StackRegisterLocations |
| LocationExpressionCommand cmd; |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd = SFT_CMD_RD_REG; |
| #endif |
| frame->inlined = info->sub_cnt; |
| for (i = 0; i < info->sub_cnt; i++) { |
| RegisterDefinition * def = get_reg_definitions(ctx); |
| StackFrame * prev = stk->frames + stk->frame_cnt - 1; |
| down->ctx = ctx; |
| down->fp = frame->fp; |
| while (def->name != NULL) { |
| if (def->dwarf_id >= 0) { |
| #if ENABLE_StackRegisterLocations |
| cmd.args.reg = def; |
| if (write_reg_location(down, def, &cmd, 1) == 0) { |
| down->has_reg_data = 1; |
| def++; |
| continue; |
| } |
| #endif |
| if (buf_size < def->size) buf = (uint8_t *)tmp_realloc(buf, buf_size = def->size); |
| if (read_reg_bytes(prev, def, 0, def->size, buf) == 0) { |
| write_reg_bytes(down, def, 0, def->size, buf); |
| down->has_reg_data = 1; |
| } |
| } |
| def++; |
| } |
| down->is_walked = 1; |
| down->inlined = info->sub_cnt - i - 1; |
| down->area = (CodeArea *)loc_alloc(sizeof(CodeArea)); |
| *down->area = info->subs[down->inlined]->area; |
| if (down->area->directory) down->area->directory = loc_strdup(down->area->directory); |
| if (down->area->file) down->area->file = loc_strdup(down->area->file); |
| prev->func_id = loc_strdup(info->subs[down->inlined]->func_id); |
| add_frame(stk, down); |
| frame = stk->frames + frame_idx; |
| memset(down, 0, sizeof(StackFrame)); |
| } |
| } |
| down->ctx = ctx; |
| for (i = 0; i < info->reg_cnt; i++) { |
| RegisterDefinition * reg_def = info->regs[i]->reg; |
| uint8_t * buf = NULL; |
| size_t size = 0; |
| Trap trap_reg; |
| #if ENABLE_StackRegisterLocations |
| if (write_reg_location(down, reg_def, info->regs[i]->cmds, info->regs[i]->cmds_cnt) == 0) { |
| down->has_reg_data = 1; |
| continue; |
| } |
| #endif |
| if (set_trap(&trap_reg)) { |
| /* If a saved register value cannot be evaluated - ignore it */ |
| state = evaluate_location_expression(ctx, frame, info->regs[i]->cmds, info->regs[i]->cmds_cnt, NULL, 0); |
| if (state->stk_pos == 1) { |
| unsigned j; |
| uint64_t v = state->stk[0]; |
| buf = (uint8_t *)tmp_alloc_zero(reg_def->size); |
| for (j = 0; j < reg_def->size; j++) { |
| buf[reg_def->big_endian ? reg_def->size - j - 1 : j] = (uint8_t)v; |
| v = v >> 8; |
| } |
| size = reg_def->size; |
| } |
| else if (state->stk_pos == 0 && state->pieces_cnt > 0) { |
| read_location_pieces(state->ctx, state->stack_frame, |
| state->pieces, state->pieces_cnt, state->reg_id_scope.big_endian, (void **)&buf, &size); |
| } |
| clear_trap(&trap_reg); |
| } |
| if (buf != NULL && size > 0) { |
| if (size > reg_def->size) size = reg_def->size; |
| if (write_reg_bytes(down, reg_def, 0, size, buf) < 0) exception(errno); |
| down->has_reg_data = 1; |
| } |
| } |
| clear_trap(&trap); |
| frame->is_walked = 1; |
| } |
| else { |
| error = trap.error; |
| frame->fp = 0; |
| } |
| } |
| if (error) { |
| free_frame(down); |
| errno = error; |
| return -1; |
| } |
| #else |
| memset(down, 0, sizeof(StackFrame)); |
| #endif |
| return 0; |
| } |
| |
| static void invalidate_stack_trace(StackTrace * stack) { |
| int i; |
| for (i = 0; i < stack->frame_cnt; i++) { |
| free_frame(stack->frames + i); |
| } |
| stack->frame_cnt = 0; |
| stack->complete = 0; |
| } |
| |
| static void trace_stack(Context * ctx, StackTrace * stack, int max_frames) { |
| StackFrame down; |
| |
| if (stack->frame_cnt == 0) { |
| memset(&down, 0, sizeof(down)); |
| down.is_top_frame = 1; |
| down.ctx = ctx; |
| add_frame(stack, &down); |
| } |
| |
| trace(LOG_STACK, "Stack trace, ctx %s", ctx->id); |
| while (stack->frame_cnt < max_frames) { |
| int frame_idx = stack->frame_cnt - 1; |
| StackFrame * frame = stack->frames + frame_idx; |
| #if ENABLE_Trace |
| if (LOG_STACK & log_mode) { |
| uint64_t v; |
| RegisterDefinition * def; |
| trace(LOG_STACK, "Frame %d", stack->frame_cnt - 1); |
| for (def = get_reg_definitions(ctx); def->name != NULL; def++) { |
| if (def->no_read || def->read_once || def->bits) continue; |
| if (read_reg_value(frame, def, &v) != 0) continue; |
| trace(LOG_STACK, " %-8s %16" PRIX64, def->name, v); |
| } |
| } |
| #endif |
| if (get_next_stack_frame(frame, &down) < 0) { |
| if (cache_miss_count() > 0) break; |
| trace(LOG_ALWAYS, "Stack trace error: %s", errno_to_str(errno)); |
| } |
| frame = stack->frames + frame_idx; /* stack->frames might be realloc-ed */ |
| if (frame->is_walked == 0) { |
| trace(LOG_STACK, " *** frame info not available ***"); |
| free_frame(&down); |
| memset(&down, 0, sizeof(down)); |
| down.ctx = ctx; |
| if (crawl_stack_frame(frame, &down) < 0) { |
| free_frame(&down); |
| if (cache_miss_count() > 0) break; |
| trace(LOG_STACK, " crawl error: %s", errno_to_str(errno)); |
| stack->complete = 1; |
| break; |
| } |
| } |
| assert(down.area == NULL); |
| trace(LOG_STACK, " cfa %16" PRIX64, (uint64_t)frame->fp); |
| if (!down.has_reg_data) { |
| stack->complete = 1; |
| free_frame(&down); |
| break; |
| } |
| if (stack->frame_cnt > 1 && frame->fp == stack->frames[stack->frame_cnt - 2].fp) { |
| /* Compare registers in current and next frame */ |
| int equ = 1; |
| size_t buf_size = 8; |
| uint8_t * buf0 = (uint8_t *)tmp_alloc(buf_size); |
| uint8_t * buf1 = (uint8_t *)tmp_alloc(buf_size); |
| RegisterDefinition * def; |
| for (def = get_reg_definitions(ctx); def->name != NULL; def++) { |
| int f0, f1; |
| if (buf_size < def->size) { |
| buf_size = def->size; |
| buf0 = (uint8_t *)tmp_realloc(buf0, buf_size); |
| buf1 = (uint8_t *)tmp_realloc(buf1, buf_size); |
| } |
| f0 = read_reg_bytes(frame, def, 0, def->size, buf0) == 0; |
| f1 = read_reg_bytes(&down, def, 0, def->size, buf1) == 0; |
| if (f0 != f1 || (f0 && memcmp(buf0, buf1, def->size) != 0)) { |
| equ = 0; |
| break; |
| } |
| } |
| if (equ) { |
| /* All registers are same - stop tracing */ |
| stack->complete = 1; |
| free_frame(&down); |
| break; |
| } |
| } |
| #ifdef TRACE_STACK_BOTTOM_CHECK |
| TRACE_STACK_BOTTOM_CHECK; |
| #endif |
| add_frame(stack, &down); |
| } |
| } |
| |
| static StackTrace * create_stack_trace(Context * ctx, int max_frames) { |
| StackTrace * stack = EXT(ctx); |
| max_frames++; /* Frame pointer and return address calculation needs one more frame */ |
| if (!stack->complete && stack->frame_cnt < max_frames) { |
| trace_stack(ctx, stack, max_frames); |
| if (cache_miss_count() > 0) { |
| errno = ERR_CACHE_MISS; |
| return NULL; |
| } |
| } |
| return stack; |
| } |
| |
| typedef struct CommandGetContextData { |
| Context * ctx; |
| int frame; |
| StackTrace * stack; |
| StackFrame * info; |
| StackFrame * down; |
| uint64_t ip; |
| uint64_t rp; |
| int ip_error; |
| int rp_error; |
| } CommandGetContextData; |
| |
| static void write_context(OutputStream * out, char * id, CommandGetContextData * d) { |
| 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, d->ctx->id); |
| |
| write_stream(out, ','); |
| json_write_string(out, "ProcessID"); |
| write_stream(out, ':'); |
| json_write_string(out, context_get_group(d->ctx, CONTEXT_GROUP_PROCESS)->id); |
| |
| write_stream(out, ','); |
| json_write_string(out, "Index"); |
| write_stream(out, ':'); |
| json_write_long(out, d->frame); |
| |
| if (d->stack->complete) { |
| write_stream(out, ','); |
| json_write_string(out, "Level"); |
| write_stream(out, ':'); |
| json_write_long(out, d->stack->frame_cnt - d->frame - 1); |
| } |
| |
| if (d->info->is_top_frame) { |
| write_stream(out, ','); |
| json_write_string(out, "TopFrame"); |
| write_stream(out, ':'); |
| json_write_boolean(out, 1); |
| } |
| |
| if (d->info->is_walked) { |
| write_stream(out, ','); |
| json_write_string(out, "Walk"); |
| write_stream(out, ':'); |
| json_write_boolean(out, 1); |
| } |
| |
| if (d->info->fp) { |
| write_stream(out, ','); |
| json_write_string(out, "FP"); |
| write_stream(out, ':'); |
| json_write_uint64(out, d->info->fp); |
| } |
| |
| if (d->info->inlined) { |
| write_stream(out, ','); |
| json_write_string(out, "Inlined"); |
| write_stream(out, ':'); |
| json_write_long(out, d->info->inlined); |
| } |
| |
| if (d->info->func_id != NULL) { |
| write_stream(out, ','); |
| json_write_string(out, "FuncID"); |
| write_stream(out, ':'); |
| json_write_string(out, d->info->func_id); |
| } |
| |
| if (d->info->area != NULL) { |
| write_stream(out, ','); |
| json_write_string(out, "CodeArea"); |
| write_stream(out, ':'); |
| write_code_area(out, d->info->area, NULL); |
| } |
| |
| if (d->ip_error == 0) { |
| write_stream(out, ','); |
| json_write_string(out, "IP"); |
| write_stream(out, ':'); |
| json_write_uint64(out, d->ip); |
| } |
| |
| if (d->rp_error == 0) { |
| write_stream(out, ','); |
| json_write_string(out, "RP"); |
| write_stream(out, ':'); |
| json_write_uint64(out, d->rp); |
| } |
| |
| write_stream(out, '}'); |
| } |
| |
| typedef struct CommandGetContextArgs { |
| char token[256]; |
| int id_cnt; |
| char ** ids; |
| } CommandGetContextArgs; |
| |
| static void command_get_context_cache_client(void * x) { |
| int i; |
| int err = 0; |
| Channel * c = cache_channel(); |
| CommandGetContextArgs * args = (CommandGetContextArgs *)x; |
| CommandGetContextData * data = (CommandGetContextData *) |
| tmp_alloc_zero(sizeof(CommandGetContextData) * args->id_cnt); |
| |
| for (i = 0; i < args->id_cnt; i++) { |
| StackTrace * stack = NULL; |
| CommandGetContextData * d = data + i; |
| RegisterDefinition * reg_ip = NULL; |
| if (id2frame(args->ids[i], &d->ctx, &d->frame) < 0) { |
| err = errno; |
| break; |
| } |
| if (!d->ctx->stopped) { |
| err = ERR_IS_RUNNING; |
| break; |
| } |
| assert(d->frame >= 0); |
| stack = create_stack_trace(d->ctx, d->frame + 1); |
| if (stack == NULL) { |
| err = errno; |
| break; |
| } |
| if (d->frame >= stack->frame_cnt) { |
| assert(stack->complete); |
| err = ERR_INV_CONTEXT; |
| break; |
| } |
| d->stack = stack; |
| d->info = stack->frames + d->frame; |
| d->down = d->frame < stack->frame_cnt - 1 ? d->info + 1 : NULL; |
| |
| reg_ip = get_PC_definition(d->ctx); |
| if (reg_ip == NULL || d->info == NULL) d->ip_error = ERR_OTHER; |
| else if (read_reg_value(d->info, reg_ip, &d->ip) < 0) d->ip_error = errno; |
| if (reg_ip == NULL || d->down == NULL) d->rp_error = ERR_OTHER; |
| else if (read_reg_value(d->down, reg_ip, &d->rp) < 0) d->rp_error = errno; |
| } |
| |
| 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 = 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); |
| } |
| } |
| write_stream(&c->out, ']'); |
| write_stream(&c->out, 0); |
| write_errno(&c->out, err); |
| write_stream(&c->out, MARKER_EOM); |
| |
| loc_free(args->ids); |
| } |
| |
| static void command_get_context(char * token, Channel * c) { |
| CommandGetContextArgs args; |
| |
| args.ids = json_read_alloc_string_array(&c->inp, &args.id_cnt); |
| json_test_char(&c->inp, MARKER_EOA); |
| json_test_char(&c->inp, MARKER_EOM); |
| |
| 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]; |
| int min_frame; |
| int max_frame; |
| int all_frames; |
| } 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 if (args->all_frames) { |
| stack = create_stack_trace(ctx, MAX_FRAMES); |
| if (stack == NULL) err = errno; |
| } |
| else { |
| stack = create_stack_trace(ctx, EXT(ctx)->inlined + args->max_frame + 1); |
| if (stack == NULL) err = errno; |
| } |
| |
| cache_exit(); |
| |
| write_stringz(&c->out, "R"); |
| write_stringz(&c->out, args->token); |
| |
| write_errno(&c->out, err); |
| |
| if (stack == NULL) { |
| write_stringz(&c->out, "null"); |
| } |
| else { |
| int i; |
| int j = 0; |
| int inlined = EXT(ctx)->inlined; |
| write_stream(&c->out, '['); |
| if (args->all_frames) { |
| for (i = 0; i < stack->frame_cnt - inlined; i++) { |
| if (j > 0) write_stream(&c->out, ','); |
| json_write_string(&c->out, frame2id(ctx, stack->frame_cnt - i - 1)); |
| j++; |
| } |
| } |
| else { |
| for (i = inlined + args->min_frame; i <= inlined + args->max_frame && i < stack->frame_cnt; i++) { |
| if (j > 0) write_stream(&c->out, ','); |
| json_write_string(&c->out, frame2id(ctx, i)); |
| j++; |
| } |
| } |
| 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; |
| |
| memset(&args, 0, sizeof(args)); |
| json_read_string(&c->inp, args.id, sizeof(args.id)); |
| json_test_char(&c->inp, MARKER_EOA); |
| json_test_char(&c->inp, MARKER_EOM); |
| |
| args.all_frames = 1; |
| strlcpy(args.token, token, sizeof(args.token)); |
| cache_enter(command_get_children_cache_client, c, &args, sizeof(args)); |
| } |
| |
| static void command_get_children_range(char * token, Channel * c) { |
| CommandGetChildrenArgs args; |
| |
| memset(&args, 0, sizeof(args)); |
| json_read_string(&c->inp, args.id, sizeof(args.id)); |
| json_test_char(&c->inp, MARKER_EOA); |
| args.min_frame = (int)json_read_long(&c->inp); |
| json_test_char(&c->inp, MARKER_EOA); |
| args.max_frame = (int)json_read_long(&c->inp); |
| json_test_char(&c->inp, MARKER_EOA); |
| json_test_char(&c->inp, MARKER_EOM); |
| |
| strlcpy(args.token, token, sizeof(args.token)); |
| cache_enter(command_get_children_cache_client, c, &args, sizeof(args)); |
| } |
| |
| int get_top_frame(Context * ctx) { |
| |
| if (!ctx->stopped) { |
| errno = ERR_IS_RUNNING; |
| return STACK_TOP_FRAME; |
| } |
| |
| return 0; |
| } |
| |
| int get_bottom_frame(Context * ctx) { |
| StackTrace * stack; |
| |
| if (!ctx->stopped) { |
| errno = ERR_IS_RUNNING; |
| return STACK_BOTTOM_FRAME; |
| } |
| |
| stack = create_stack_trace(ctx, MAX_FRAMES); |
| if (stack == NULL) return STACK_BOTTOM_FRAME; |
| |
| assert(stack->frame_cnt > 0); |
| return stack->frame_cnt - 1; |
| } |
| |
| int get_prev_frame(Context * ctx, int frame) { |
| StackTrace * stack; |
| |
| if (frame == STACK_TOP_FRAME) { |
| frame = get_top_frame(ctx); |
| if (frame < 0) return frame; |
| } |
| |
| if (frame < 0) { |
| set_errno(ERR_OTHER, "No previous stack frame"); |
| return STACK_NO_FRAME; |
| } |
| |
| stack = create_stack_trace(ctx, frame + 2); |
| if (stack == NULL) return STACK_NO_FRAME; |
| |
| if (frame + 1 >= stack->frame_cnt) { |
| set_errno(ERR_OTHER, "No previous stack frame"); |
| return STACK_NO_FRAME; |
| } |
| |
| return frame + 1; |
| } |
| |
| int get_next_frame(Context * ctx, int frame) { |
| |
| if (frame == STACK_BOTTOM_FRAME) { |
| frame = get_bottom_frame(ctx); |
| if (frame < 0) return frame; |
| } |
| |
| if (frame <= 0) { |
| set_errno(ERR_OTHER, "No next stack frame"); |
| return STACK_NO_FRAME; |
| } |
| |
| return frame - 1; |
| } |
| |
| int get_frame_info(Context * ctx, int frame, StackFrame ** info) { |
| StackTrace * stack; |
| int max_frames = 0; |
| |
| *info = NULL; |
| if (ctx == NULL || !context_has_state(ctx)) { |
| errno = ERR_INV_CONTEXT; |
| return -1; |
| } |
| if (!ctx->stopped) { |
| errno = ERR_IS_RUNNING; |
| return -1; |
| } |
| |
| if (frame >= 0) max_frames = frame + 1; |
| else if (frame == STACK_TOP_FRAME) max_frames = 1; |
| else if (frame == STACK_BOTTOM_FRAME) max_frames = MAX_FRAMES; |
| |
| stack = create_stack_trace(ctx, max_frames); |
| if (stack == NULL) return -1; |
| |
| if (frame == STACK_TOP_FRAME) { |
| frame = 0; |
| } |
| else if (frame == STACK_BOTTOM_FRAME) { |
| if (!stack->complete) { |
| set_errno(ERR_INV_CONTEXT, "No such stack frame"); |
| return -1; |
| } |
| frame = stack->frame_cnt - 1; |
| } |
| else if (frame < 0 || frame >= stack->frame_cnt) { |
| set_errno(ERR_INV_CONTEXT, "No such stack frame"); |
| return -1; |
| } |
| |
| *info = stack->frames + frame; |
| return 0; |
| } |
| |
| int get_cached_frame_info(Context * ctx, int frame, StackFrame ** info) { |
| StackTrace * stack = EXT(ctx); |
| |
| *info = NULL; |
| if (ctx == NULL || !context_has_state(ctx)) { |
| errno = ERR_INV_CONTEXT; |
| return -1; |
| } |
| if (!ctx->stopped) { |
| errno = ERR_IS_RUNNING; |
| return -1; |
| } |
| |
| if (frame == STACK_TOP_FRAME) { |
| frame = 0; |
| } |
| else if (frame == STACK_BOTTOM_FRAME) { |
| if (!stack->complete) { |
| set_errno(ERR_INV_CONTEXT, "No such stack frame"); |
| return -1; |
| } |
| frame = stack->frame_cnt - 1; |
| } |
| else if (frame < 0 || frame >= stack->frame_cnt) { |
| set_errno(ERR_INV_CONTEXT, "No such stack frame"); |
| return -1; |
| } |
| |
| *info = stack->frames + frame; |
| return 0; |
| } |
| |
| void set_inlined_frame_level(Context * ctx, int level) { |
| EXT(ctx)->inlined = level; |
| } |
| |
| int get_inlined_frame_level(Context * ctx) { |
| return EXT(ctx)->inlined; |
| } |
| |
| static void flush_stack_trace(Context * ctx, void * args) { |
| invalidate_stack_trace(EXT(ctx)); |
| EXT(ctx)->inlined = 0; |
| } |
| |
| #if SERVICE_Registers |
| static void flush_on_register_change(Context * ctx, int frame, RegisterDefinition * def, void * args) { |
| invalidate_stack_trace(EXT(ctx)); |
| } |
| #endif |
| |
| 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)); |
| } |
| |
| static void event_map_changed(Context * ctx, void * client_data) { |
| if (ctx->mem_access && context_get_group(ctx, CONTEXT_GROUP_PROCESS) == ctx) { |
| /* If the context is a memory space, we need to invalidate |
| * stack traces on all members of the group, since they can |
| * dependent on the symbol information. */ |
| LINK * l = context_root.next; |
| while (l != &context_root) { |
| Context * x = ctxl2ctxp(l); |
| l = l->next; |
| if (x->exited) continue; |
| if (context_get_group(x, CONTEXT_GROUP_PROCESS) != ctx) continue; |
| invalidate_stack_trace(EXT(x)); |
| } |
| } |
| } |
| |
| 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 |
| }; |
| #if SERVICE_Registers |
| static RegistersEventListener registers_listener = { |
| flush_on_register_change, |
| }; |
| #endif |
| #if SERVICE_MemoryMap |
| static MemoryMapEventListener map_listener = { |
| NULL, |
| NULL, |
| NULL, |
| event_map_changed, |
| }; |
| #endif |
| add_context_event_listener(&context_listener, bcg); |
| #if SERVICE_Registers |
| add_registers_event_listener(®isters_listener, bcg); |
| #endif |
| #if SERVICE_MemoryMap |
| add_memory_map_event_listener(&map_listener, NULL); |
| #endif |
| add_command_handler(proto, STACKTRACE, "getContext", command_get_context); |
| add_command_handler(proto, STACKTRACE, "getChildren", command_get_children); |
| add_command_handler(proto, STACKTRACE, "getChildrenRange", command_get_children_range); |
| context_extension_offset = context_extension(sizeof(StackTrace)); |
| } |
| |
| #endif |