blob: 94652100274d4831638ef75738d8864511e1dea2 [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 implements Breakpoints service.
* The service maintains a list of breakpoints.
* Each breakpoint consists of one or more conditions that determine
* when a program's execution should be interrupted.
*/
#include "config.h"
#if SERVICE_Breakpoints
// TODO: breakpoint status reports
// TODO: replant breakpoints when shared lib is loaded or unloaded
#if defined(_WRS_KERNEL)
# include <vxWorks.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include "breakpoints.h"
#include "expressions.h"
#include "channel.h"
#include "protocol.h"
#include "errors.h"
#include "trace.h"
#include "runctrl.h"
#include "context.h"
#include "myalloc.h"
#include "exceptions.h"
#include "symbols.h"
#include "json.h"
#include "link.h"
#ifdef BREAK_INST
# undef BREAK_INST
#endif
#if defined(_WRS_KERNEL)
#include <private/vxdbgLibP.h>
#elif defined(__i386__)
static unsigned char BREAK_INST[] = { 0xcc }; /* breakpoint instruction */
#define BREAK_SIZE 1 /* breakpoint instruction size */
#else
#error "Unknown CPU"
#endif
typedef struct BreakpointRef BreakpointRef;
typedef struct BreakpointInfo BreakpointInfo;
typedef struct BreakInstruction BreakInstruction;
struct BreakpointRef {
LINK link_inp;
LINK link_bp;
InputStream * inp;
BreakpointInfo * bp;
};
struct BreakpointInfo {
LINK link_all;
LINK link_id;
LINK refs;
char id[64];
int enabled;
int unsupported;
int planted;
int deleted;
int error;
char * err_msg;
char * address;
char * condition;
};
struct BreakInstruction {
LINK link_all;
LINK link_adr;
Context * ctx;
int ctx_cnt;
unsigned long address;
#if defined(_WRS_KERNEL)
VXDBG_CTX vxdbg_ctx;
VXDBG_BP_ID vxdbg_id;
#else
char saved_code[BREAK_SIZE];
#endif
int error;
int skip;
BreakpointInfo ** refs;
int ref_size;
int ref_cnt;
int planted;
};
static const char * BREAKPOINTS = "Breakpoints";
#define ADDR2INSTR_HASH_SIZE 1023
#define addr2instr_hash(addr) (((addr) + ((addr) >> 8)) % ADDR2INSTR_HASH_SIZE)
#define link_all2bi(A) ((BreakInstruction *)((char *)(A) - (int)&((BreakInstruction *)0)->link_all))
#define link_adr2bi(A) ((BreakInstruction *)((char *)(A) - (int)&((BreakInstruction *)0)->link_adr))
#define ID2BP_HASH_SIZE 1023
#define link_all2bp(A) ((BreakpointInfo *)((char *)(A) - (int)&((BreakpointInfo *)0)->link_all))
#define link_id2bp(A) ((BreakpointInfo *)((char *)(A) - (int)&((BreakpointInfo *)0)->link_id))
#define INP2BR_HASH_SIZE 127
#define link_inp2br(A) ((BreakpointRef *)((char *)(A) - (int)&((BreakpointRef *)0)->link_inp))
#define link_bp2br(A) ((BreakpointRef *)((char *)(A) - (int)&((BreakpointRef *)0)->link_bp))
static LINK breakpoints;
static LINK id2bp[ID2BP_HASH_SIZE];
static LINK instructions;
static LINK addr2instr[ADDR2INSTR_HASH_SIZE];
static LINK inp2br[INP2BR_HASH_SIZE];
static int replanting = 0;
static Context * expression_context = NULL;
static int address_expression_identifier(char * name, Value * v);
static int condition_expression_identifier(char * name, Value * v);
static ExpressionContext bp_address_ctx = { address_expression_identifier, NULL };
static ExpressionContext bp_condition_ctx = { condition_expression_identifier, NULL };
static int id2bp_hash(char * id) {
unsigned hash = 0;
while (*id) hash = (hash >> 16) + hash + (unsigned char)*id++;
return hash % ID2BP_HASH_SIZE;
}
static void plant_instruction(BreakInstruction * bi) {
assert(!bi->planted);
#if defined(_WRS_KERNEL)
bi->vxdbg_ctx.ctxId = bi->ctx_cnt == 1 ? bi->ctx->pid : 0;
bi->vxdbg_ctx.ctxId = 0;
bi->vxdbg_ctx.ctxType = VXDBG_CTX_TASK;
if (vxdbgBpAdd(vxdbg_clnt_id,
&bi->vxdbg_ctx, 0, BP_ACTION_STOP | BP_ACTION_NOTIFY,
0, 0, (INSTR *)bi->address, 0, 0, &bi->vxdbg_id) != OK) {
bi->error = errno;
assert(bi->error != 0);
}
#else
if (context_read_mem(bi->ctx, bi->address, bi->saved_code, BREAK_SIZE) < 0) {
bi->error = errno;
}
else if (context_write_mem(bi->ctx, bi->address, &BREAK_INST, BREAK_SIZE) < 0) {
bi->error = errno;
}
#endif
bi->planted = bi->error == 0;
}
static int verify_instruction(BreakInstruction * bi) {
assert(bi->planted);
#if defined(_WRS_KERNEL)
return bi->vxdbg_ctx.ctxId == (bi->ctx_cnt == 1 ? bi->ctx->pid : 0) &&
bi->vxdbg_ctx.ctxType == VXDBG_CTX_TASK;
#else
return 1;
#endif
}
static void remove_instruction(BreakInstruction * bi) {
assert(bi->planted);
assert(!bi->error);
#ifdef _WRS_KERNEL
{
VXDBG_BP_DEL_INFO info;
memset(&info, 0, sizeof(info));
info.pClnt = vxdbg_clnt_id;
info.type = BP_BY_ID_DELETE;
info.info.id.bpId = bi->vxdbg_id;
if (vxdbgBpDelete(info) != OK) {
bi->error = errno;
assert(bi->error != 0);
}
}
#else
if (!bi->ctx->exited && bi->ctx->stopped) {
if (context_write_mem(bi->ctx, bi->address, bi->saved_code, BREAK_SIZE) < 0) {
bi->error = errno;
}
}
#endif
bi->planted = 0;
}
static BreakInstruction * add_instruction(Context * ctx, unsigned long address) {
int hash = addr2instr_hash(address);
BreakInstruction * bi = (BreakInstruction *)loc_alloc_zero(sizeof(BreakInstruction));
list_add_last(&bi->link_all, &instructions);
list_add_last(&bi->link_adr, addr2instr + hash);
context_lock(ctx);
bi->ctx = ctx;
bi->address = address;
return bi;
}
static void clear_instruction_refs(void) {
LINK * l = instructions.next;
while (l != &instructions) {
BreakInstruction * bi = link_all2bi(l);
bi->ctx_cnt = 1;
bi->ref_cnt = 0;
l = l->next;
}
}
static void delete_unused_instructions(void) {
LINK * l = instructions.next;
while (l != &instructions) {
BreakInstruction * bi = link_all2bi(l);
l = l->next;
if (bi->skip) continue;
if (bi->ref_cnt == 0) {
list_remove(&bi->link_all);
list_remove(&bi->link_adr);
if (bi->planted) {
if (bi->ctx->exited || !bi->ctx->stopped) {
LINK * qp = context_root.next;
while (qp != &context_root) {
Context * ctx = ctxl2ctxp(qp);
qp = qp->next;
if (ctx->mem == bi->ctx->mem && !ctx->exited && ctx->stopped) {
assert(bi->ctx != ctx);
context_unlock(bi->ctx);
context_lock(ctx);
bi->ctx = ctx;
break;
}
}
}
remove_instruction(bi);
}
context_unlock(bi->ctx);
loc_free(bi->refs);
loc_free(bi);
}
else if (!bi->planted) {
plant_instruction(bi);
}
else if (!verify_instruction(bi)) {
remove_instruction(bi);
plant_instruction(bi);
}
}
}
static BreakInstruction * find_instruction(Context * ctx, unsigned long address) {
int hash = addr2instr_hash(address);
LINK * l = addr2instr[hash].next;
assert(!ctx->exited && ctx->stopped);
while (l != addr2instr + hash) {
BreakInstruction * bi = link_adr2bi(l);
l = l->next;
if (bi->ctx->mem == ctx->mem && bi->address == address) {
if (bi->ctx->exited || !bi->ctx->stopped) {
assert(bi->ctx != ctx);
context_unlock(bi->ctx);
context_lock(ctx);
bi->ctx = ctx;
}
return bi;
}
}
return NULL;
}
static int address_expression_identifier(char * name, Value * v) {
Symbol sym;
if (v == NULL) return 0;
memset(v, 0, sizeof(Value));
if (expression_context == NULL) {
errno = ERR_INV_CONTEXT;
return -1;
}
if (strcmp(name, "$thread") == 0) {
string_value(v, thread_id(expression_context));
return 0;
}
if (find_symbol(expression_context, name, &sym) < 0) {
if (errno != ERR_SYM_NOT_FOUND) return -1;
}
else {
v->type = VALUE_UNS;
v->value = sym.value;
return 0;
}
errno = ERR_SYM_NOT_FOUND;
return -1;
}
static void address_expression_error(BreakpointInfo * bp, char * msg) {
// TODO: per-context address expression error report
int size;
if (bp->error) return;
bp->error = errno;
if (msg == NULL) msg = get_expression_error_msg();
assert(bp->err_msg == NULL);
size = strlen(msg) + strlen(bp->address) + 64;
bp->err_msg = loc_alloc(size);
snprintf(bp->err_msg, size, "Invalid breakpoint address '%s': %s", bp->address, msg);
}
static void plant_breakpoint(BreakpointInfo * bp) {
LINK * qp;
char * p = NULL;
Value v;
int context_sensitive = 0;
assert(!bp->planted);
assert(bp->enabled);
bp->error = 0;
if (bp->err_msg != NULL) loc_free(bp->err_msg);
if (bp->address == NULL) {
bp->error = ERR_INV_EXPRESSION;
trace(LOG_ALWAYS, "No breakpoint address");
return;
}
expression_context = NULL;
if (evaluate_expression(&bp_address_ctx, bp->address, &v) < 0) {
if (errno != ERR_INV_CONTEXT) {
address_expression_error(bp, NULL);
trace(LOG_ALWAYS, "Error: %s", bp->err_msg);
return;
}
context_sensitive = 1;
}
if (!context_sensitive && v.type != VALUE_INT && v.type != VALUE_UNS) {
errno = ERR_INV_EXPRESSION;
address_expression_error(bp, "Must be integer number");
trace(LOG_ALWAYS, "Error: %s", bp->err_msg);
return;
}
for (qp = context_root.next; qp != &context_root; qp = qp->next) {
BreakInstruction * bi = NULL;
Context * ctx = ctxl2ctxp(qp);
if (ctx->exited || ctx->exiting) continue;
assert(ctx->stopped);
if (context_sensitive) {
expression_context = ctx;
if (evaluate_expression(&bp_address_ctx, bp->address, &v) < 0) {
address_expression_error(bp, NULL);
trace(LOG_ALWAYS, "Error: %s", bp->err_msg);
continue;
}
if (v.type != VALUE_INT && v.type != VALUE_UNS) {
errno = ERR_INV_EXPRESSION;
address_expression_error(bp, "Must be integer number");
continue;
}
}
if (bp->condition != NULL) {
/* Optimize away the breakpoint if condition is always false for given context */
Value c;
expression_context = ctx;
if (evaluate_expression(&bp_address_ctx, bp->condition, &c) == 0) {
switch (c.type) {
case VALUE_INT:
case VALUE_UNS:
if (c.value == 0) continue;
break;
case VALUE_STR:
if (c.str == NULL) continue;
break;
}
}
}
bi = find_instruction(ctx, v.value);
if (bi == NULL) {
bi = add_instruction(ctx, v.value);
}
else if (bp->planted) {
int i = 0;
while (i < bi->ref_cnt && bi->refs[i] != bp) i++;
if (i < bi->ref_cnt) continue;
}
if (bi->ref_cnt >= bi->ref_size) {
bi->ref_size = bi->ref_size == 0 ? 8 : bi->ref_size * 2;
bi->refs = (BreakpointInfo **)loc_realloc(bi->refs, sizeof(BreakpointInfo *) * bi->ref_size);
}
bi->refs[bi->ref_cnt++] = bp;
if (bi->ctx != ctx) bi->ctx_cnt++;
if (bi->error) {
if (!bp->error) bp->error = bi->error;
}
else {
bp->planted = 1;
}
}
if (bp->planted) bp->error = 0;
}
static void event_replant_breakpoints(void *arg) {
LINK * l = breakpoints.next;
replanting = 0;
clear_instruction_refs();
while (l != &breakpoints) {
BreakpointInfo * bp = link_all2bp(l);
l = l->next;
if (bp->deleted) {
list_remove(&bp->link_all);
list_remove(&bp->link_id);
loc_free(bp->err_msg);
loc_free(bp->address);
loc_free(bp->condition);
loc_free(bp);
continue;
}
bp->planted = 0;
if (bp->enabled && !bp->unsupported) {
plant_breakpoint(bp);
}
}
delete_unused_instructions();
}
static void replant_breakpoints(void) {
if (list_is_empty(&breakpoints) && list_is_empty(&instructions)) return;
if (replanting) return;
replanting = 1;
post_safe_event(event_replant_breakpoints, NULL);
}
static int str_equ(char * x, char * y) {
if (x == y) return 1;
if (x == NULL) return 0;
if (y == NULL) return 0;
return strcmp(x, y) == 0;
}
static int copy_breakpoint_info(BreakpointInfo * dst, BreakpointInfo * src) {
int res = 0;
if (strcmp(dst->id, src->id) != 0) {
strcpy(dst->id, src->id);
res = 1;
}
if (dst->enabled != src->enabled) {
dst->enabled = src->enabled;
res = 1;
}
if (dst->unsupported != src->unsupported) {
dst->unsupported = src->unsupported;
res = 1;
}
if (!str_equ(dst->address, src->address)) {
loc_free(dst->address);
dst->address = src->address;
res = 1;
}
else {
loc_free(src->address);
}
src->address = NULL;
if (!str_equ(dst->condition, src->condition)) {
loc_free(dst->condition);
dst->condition = src->condition;
res = 1;
}
else {
loc_free(src->condition);
}
src->condition = NULL;
return res;
}
static BreakpointInfo * find_breakpoint(char * id) {
int hash = id2bp_hash(id);
LINK * l = id2bp[hash].next;
while (l != id2bp + hash) {
BreakpointInfo * bp = link_id2bp(l);
l = l->next;
if (strcmp(bp->id, id) == 0) return bp;
}
return NULL;
}
static BreakpointRef * find_breakpoint_ref(BreakpointInfo * bp, InputStream * inp) {
LINK * l;
if (bp == NULL) return NULL;
l = bp->refs.next;
while (l != &bp->refs) {
BreakpointRef * br = link_bp2br(l);
assert(br->bp == bp);
if (br->inp == inp) return br;
l = l->next;
}
return NULL;
}
static void add_breakpoint(InputStream * inp, BreakpointInfo * bp) {
BreakpointRef * r = NULL;
BreakpointInfo * i = NULL;
int chng = 0;
i = find_breakpoint(bp->id);
if (i == NULL) {
int hash = id2bp_hash(bp->id);
i = (BreakpointInfo *)loc_alloc_zero(sizeof(BreakpointInfo));
list_init(&i->refs);
list_add_last(&i->link_all, &breakpoints);
list_add_last(&i->link_id, id2bp + hash);
}
chng = copy_breakpoint_info(i, bp);
if (i->deleted) {
i->deleted = 0;
chng = 1;
}
r = find_breakpoint_ref(i, inp);
if (r == NULL) {
int inp_hash = (int)inp / 16 % INP2BR_HASH_SIZE;
r = (BreakpointRef *)loc_alloc_zero(sizeof(BreakpointRef));
list_add_last(&r->link_inp, inp2br + inp_hash);
list_add_last(&r->link_bp, &i->refs);
r->inp = inp;
r->bp = i;
}
else {
assert(r->bp == i);
assert(!list_is_empty(&i->refs));
}
if (chng) {
if (i->planted || i->enabled && !i->unsupported) replant_breakpoints();
}
}
static void remove_breakpoint(BreakpointInfo * bp) {
assert(list_is_empty(&bp->refs));
if (bp->planted) {
bp->deleted = 1;
replant_breakpoints();
}
else {
list_remove(&bp->link_all);
list_remove(&bp->link_id);
loc_free(bp->address);
loc_free(bp->condition);
loc_free(bp);
}
}
static void remove_ref(BreakpointRef * br) {
BreakpointInfo * bp = br->bp;
list_remove(&br->link_inp);
list_remove(&br->link_bp);
loc_free(br);
if (list_is_empty(&bp->refs)) remove_breakpoint(bp);
}
static void delete_breakpoint_refs(InputStream * inp) {
int hash = (int)inp / 16 % INP2BR_HASH_SIZE;
LINK * l = inp2br[hash].next;
while (l != &inp2br[hash]) {
BreakpointRef * br = link_inp2br(l);
l = l->next;
if (br->inp == inp) remove_ref(br);
}
}
static void read_breakpoint_properties(InputStream * inp, BreakpointInfo * bp) {
memset(bp, 0, sizeof(BreakpointInfo));
if (inp->read(inp) != '{') exception(ERR_JSON_SYNTAX);
if (inp->peek(inp) == '}') {
inp->read(inp);
}
else {
while (1) {
int ch;
char name[256];
json_read_string(inp, name, sizeof(name));
if (inp->read(inp) != ':') exception(ERR_JSON_SYNTAX);
if (strcmp(name, "ID") == 0) {
json_read_string(inp, bp->id, sizeof(bp->id));
}
else if (strcmp(name, "Address") == 0) {
bp->address = json_read_alloc_string(inp);
}
else if (strcmp(name, "Condition") == 0) {
bp->condition = json_read_alloc_string(inp);
}
else if (strcmp(name, "Enabled") == 0) {
bp->enabled = json_read_boolean(inp);
}
else {
bp->unsupported = 1;
json_skip_object(inp);
}
ch = inp->read(inp);
if (ch == ',') continue;
if (ch == '}') break;
exception(ERR_JSON_SYNTAX);
}
}
}
static void command_ini_bps(char * token, InputStream * inp, OutputStream * out) {
delete_breakpoint_refs(inp);
if (inp->read(inp) != '[') exception(ERR_PROTOCOL);
if (inp->peek(inp) == ']') {
inp->read(inp);
}
else {
while (1) {
int ch;
BreakpointInfo bp;
read_breakpoint_properties(inp, &bp);
add_breakpoint(inp, &bp);
ch = inp->read(inp);
if (ch == ',') continue;
if (ch == ']') break;
exception(ERR_JSON_SYNTAX);
}
}
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, MARKER_EOM);
}
static void command_get_bp_ids(char * token, InputStream * inp, OutputStream * out) {
// TODO: implement command_get_bp_ids()
exception(ERR_PROTOCOL);
}
static void command_get_properties(char * token, InputStream * inp, OutputStream * out) {
// TODO: implement command_get_properties()
exception(ERR_PROTOCOL);
}
static void command_get_status(char * token, InputStream * inp, OutputStream * out) {
// TODO: implement command_get_status()
exception(ERR_PROTOCOL);
}
static void command_bp_add(char * token, InputStream * inp, OutputStream * out) {
BreakpointInfo bp;
read_breakpoint_properties(inp, &bp);
add_breakpoint(inp, &bp);
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, MARKER_EOM);
}
static void command_bp_change(char * token, InputStream * inp, OutputStream * out) {
BreakpointInfo bp;
BreakpointInfo * p;
read_breakpoint_properties(inp, &bp);
p = find_breakpoint(bp.id);
if (p != NULL && copy_breakpoint_info(p, &bp)) {
if (p->planted || p->enabled && !p->unsupported) replant_breakpoints();
}
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, MARKER_EOM);
}
static void command_bp_enable(char * token, InputStream * inp, OutputStream * out) {
if (inp->read(inp) != '[') exception(ERR_PROTOCOL);
if (inp->peek(inp) == ']') {
inp->read(inp);
}
else {
while (1) {
int ch;
char id[256];
BreakpointInfo * bp;
json_read_string(inp, id, sizeof(id));
bp = find_breakpoint(id);
if (bp != NULL && !bp->enabled) {
bp->enabled = 1;
if (!bp->deleted && !bp->unsupported) replant_breakpoints();
}
ch = inp->read(inp);
if (ch == ',') continue;
if (ch == ']') break;
exception(ERR_JSON_SYNTAX);
}
}
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, MARKER_EOM);
}
static void command_bp_disable(char * token, InputStream * inp, OutputStream * out) {
if (inp->read(inp) != '[') exception(ERR_PROTOCOL);
if (inp->peek(inp) == ']') {
inp->read(inp);
}
else {
while (1) {
int ch;
char id[256];
BreakpointInfo * bp;
json_read_string(inp, id, sizeof(id));
bp = find_breakpoint(id);
if (bp != NULL && bp->enabled) {
bp->enabled = 0;
if (bp->planted) replant_breakpoints();
}
ch = inp->read(inp);
if (ch == ',') continue;
if (ch == ']') break;
exception(ERR_JSON_SYNTAX);
}
}
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, MARKER_EOM);
}
static void command_bp_remove(char * token, InputStream * inp, OutputStream * out) {
if (inp->read(inp) != '[') exception(ERR_PROTOCOL);
if (inp->peek(inp) == ']') {
inp->read(inp);
}
else {
while (1) {
int ch;
char id[256];
BreakpointRef * br;
json_read_string(inp, id, sizeof(id));
br = find_breakpoint_ref(find_breakpoint(id), inp);
if (br != NULL) remove_ref(br);
ch = inp->read(inp);
if (ch == ',') continue;
if (ch == ']') break;
exception(ERR_JSON_SYNTAX);
}
}
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, MARKER_EOM);
}
int is_stopped_by_breakpoint(Context * ctx) {
BreakInstruction * bi;
assert(!ctx->exited);
assert(ctx->stopped);
#ifdef _WRS_KERNEL
return ctx->stopped_by_bp;
#else
if (ctx->signal != SIGTRAP) return 0;
if (ctx->event != 0) return 0;
if (ctx->regs_error) return 0;
if (ctx->stopped_by_bp) return 1;
bi = find_instruction(ctx, get_regs_PC(ctx->regs) - BREAK_SIZE);
if (bi == NULL || bi->skip || bi->error) return 0;
set_regs_PC(ctx->regs, get_regs_PC(ctx->regs) - BREAK_SIZE);
ctx->regs_dirty = 1;
ctx->stopped_by_bp = 1;
return 1;
#endif
}
static int condition_expression_identifier(char * name, Value * v) {
return address_expression_identifier(name, v);
}
int evaluate_breakpoint_condition(Context * ctx) {
int i;
BreakInstruction * bi = find_instruction(ctx, get_regs_PC(ctx->regs));
if (bi == NULL) return 0;
expression_context = ctx;
for (i = 0; i < bi->ref_cnt; i++) {
Value v;
BreakpointInfo * bp = bi->refs[i];
assert(bp->planted);
assert(bp->error == 0);
if (bp->deleted) continue;
if (bp->unsupported) continue;
if (!bp->enabled) continue;
if (bp->condition == NULL) return 1;
if (evaluate_expression(&bp_condition_ctx, bp->condition, &v) < 0) {
trace(LOG_ALWAYS, "%s: %s", get_expression_error_msg(), bp->condition);
return 1;
}
switch (v.type) {
case VALUE_INT:
case VALUE_UNS:
if (v.value) return 1;
break;
case VALUE_STR:
if (v.str != NULL) return 1;
break;
}
}
return 0;
}
#ifndef _WRS_KERNEL
static void safe_restore_breakpoint(void * arg) {
SkipBreakpointInfo * sb = (SkipBreakpointInfo *)arg;
BreakInstruction * bi = find_instruction(sb->ctx, sb->address);
assert(sb->ctx->regs_error || get_regs_PC(sb->ctx->regs) != sb->address);
if (bi != NULL && bi->skip) {
assert(bi->error == 0);
bi->skip = 0;
plant_instruction(bi);
}
if (sb->done) sb->done(sb);
if (sb->out) stream_unlock(sb->out);
context_unlock(sb->ctx);
loc_free(sb);
}
static void safe_skip_breakpoint(void * arg) {
SkipBreakpointInfo * sb = (SkipBreakpointInfo *)arg;
assert(!sb->ctx->exited);
assert(sb->ctx->stopped);
assert(!sb->ctx->intercepted);
assert(!sb->ctx->regs_error);
assert(sb->address == get_regs_PC(sb->ctx->regs));
if (sb->error == 0) {
BreakInstruction * bi = find_instruction(sb->ctx, sb->address);
if (bi != NULL && !bi->skip) {
if (bi->error) {
sb->error = bi->error;
}
else {
remove_instruction(bi);
bi->skip = 1;
}
}
}
if (sb->error == 0) {
post_safe_event(safe_restore_breakpoint, sb);
if (context_single_step(sb->ctx) < 0) {
sb->error = errno;
}
else if (sb->pending_intercept) {
sb->ctx->pending_intercept = 1;
}
}
else {
if (sb->done) sb->done(sb);
if (sb->out) stream_unlock(sb->out);
context_unlock(sb->ctx);
loc_free(sb);
}
}
#endif
/*
* When a context is stopped by breakpoint, it is necessary to disable
* the breakpoint temporarily before the context can be resumed.
* This function function removes break instruction, then does single step
* over breakpoint location, then restores break intruction.
* Return: NULL if it is OK to resume context from current state,
* SkipBreakpointInfo pointer if context needs to step over a breakpoint.
*/
SkipBreakpointInfo * skip_breakpoint(Context * ctx) {
BreakInstruction * bi;
SkipBreakpointInfo * sb;
assert(!ctx->exited);
assert(ctx->stopped);
#ifdef _WRS_KERNEL
/* VxWork debug library can skip breakpoint when neccesary, no code is needed here */
return NULL;
#else
if (ctx->exited || ctx->exiting) return NULL;
assert(!ctx->regs_error);
bi = find_instruction(ctx, get_regs_PC(ctx->regs));
if (bi == NULL || bi->error) return NULL;
assert(!bi->skip);
sb = (SkipBreakpointInfo *)loc_alloc_zero(sizeof(SkipBreakpointInfo));
context_lock(ctx);
sb->ctx = ctx;
sb->address = get_regs_PC(ctx->regs);
post_safe_event(safe_skip_breakpoint, sb);
return sb;
#endif
}
static void event_context_created_or_exited(Context * ctx) {
if (ctx->parent == NULL) replant_breakpoints();
}
static void channel_close_listener(InputStream * inp, OutputStream * out) {
delete_breakpoint_refs(inp);
}
void ini_breakpoints_service(void) {
int i;
static ContextEventListener listener = {
event_context_created_or_exited,
event_context_created_or_exited,
NULL,
NULL,
NULL,
NULL
};
add_context_event_listener(&listener);
list_init(&breakpoints);
list_init(&instructions);
for (i = 0; i < ADDR2INSTR_HASH_SIZE; i++) list_init(addr2instr + i);
for (i = 0; i < ID2BP_HASH_SIZE; i++) list_init(id2bp + i);
for (i = 0; i < INP2BR_HASH_SIZE; i++) list_init(inp2br + i);
add_channel_close_listener(channel_close_listener);
add_command_handler(BREAKPOINTS, "set", command_ini_bps);
add_command_handler(BREAKPOINTS, "add", command_bp_add);
add_command_handler(BREAKPOINTS, "change", command_bp_change);
add_command_handler(BREAKPOINTS, "enable", command_bp_enable);
add_command_handler(BREAKPOINTS, "disable", command_bp_disable);
add_command_handler(BREAKPOINTS, "remove", command_bp_remove);
add_command_handler(BREAKPOINTS, "getBreakpointIDs", command_get_bp_ids);
add_command_handler(BREAKPOINTS, "getProperties", command_get_properties);
add_command_handler(BREAKPOINTS, "getStatus", command_get_status);
}
#endif