blob: 34f52b20bd1c8b613049d245b23cb23feacf7248 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2014 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
*******************************************************************************/
/*
* 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 <tcf/config.h>
#if SERVICE_Breakpoints
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <tcf/framework/channel.h>
#include <tcf/framework/protocol.h>
#include <tcf/framework/errors.h>
#include <tcf/framework/trace.h>
#include <tcf/framework/context.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/cache.h>
#include <tcf/framework/json.h>
#include <tcf/framework/link.h>
#include <tcf/services/symbols.h>
#include <tcf/services/runctrl.h>
#include <tcf/services/contextquery.h>
#include <tcf/services/breakpoints.h>
#include <tcf/services/expressions.h>
#include <tcf/services/linenumbers.h>
#include <tcf/services/stacktrace.h>
#include <tcf/services/memorymap.h>
#include <tcf/services/pathmap.h>
typedef struct BreakpointRef BreakpointRef;
typedef struct InstructionRef InstructionRef;
typedef struct BreakInstruction BreakInstruction;
typedef struct EvaluationArgs EvaluationArgs;
typedef struct EvaluationRequest EvaluationRequest;
typedef struct LocationEvaluationRequest LocationEvaluationRequest;
typedef struct ConditionEvaluationRequest ConditionEvaluationRequest;
typedef struct ContextExtensionBP ContextExtensionBP;
typedef struct BreakpointHitCount BreakpointHitCount;
struct BreakpointRef {
LINK link_inp;
LINK link_bp;
Channel * channel; /* NULL means API client */
BreakpointInfo * bp;
};
struct BreakpointInfo {
Context * ctx; /* NULL means all contexts */
LINK link_all;
LINK link_id;
LINK link_clients;
char id[256];
int enabled;
int client_cnt;
int instruction_cnt;
ErrorReport * error;
char * type;
char * location;
char * condition;
char * context_query;
char ** context_ids;
char ** context_names;
char ** stop_group;
char * file;
char * client_data;
int temporary;
int access_mode;
int access_size;
int line;
int column;
unsigned ignore_count;
BreakpointAttribute * attrs;
EventPointCallBack * event_callback;
void * event_callback_args;
int attrs_changed;
int status_changed;
LINK link_hit_count;
};
struct BreakpointHitCount {
LINK link_bp;
LINK link_ctx;
Context * ctx;
unsigned count;
};
struct InstructionRef {
BreakpointInfo * bp;
Context * ctx; /* "breakpoint" group context, see CONTEXT_GROUP_BREAKPOINT */
ContextAddress addr;
unsigned cnt;
};
#define MAX_BI_SIZE 16
struct BreakInstruction {
LINK link_all;
LINK link_adr;
LINK link_lst;
ContextBreakpoint cb; /* cb.ctx is "canonical" context, see context_get_canonical_addr() */
char saved_code[MAX_BI_SIZE];
char planted_code[MAX_BI_SIZE];
size_t saved_size;
ErrorReport * planting_error;
ErrorReport * address_error;
int stepping_over_bp;
InstructionRef * refs;
unsigned ref_size;
unsigned ref_cnt;
uint8_t no_addr;
uint8_t virtual_addr;
uint8_t hardware;
uint8_t valid; /* 1 if 'refs' array is valid */
uint8_t planted;
uint8_t dirty; /* the instruction is planted, but planting data is obsolete */
uint8_t unsupported; /* context_plant_breakpoint() returned ERR_UNSUPPORTED */
uint8_t planted_as_sw_bp;
};
struct EvaluationArgs {
BreakpointInfo * bp;
Context * ctx;
unsigned index;
};
struct ConditionEvaluationRequest {
Context * ctx;
BreakpointInfo * bp;
int condition_ok;
int triggered;
};
struct LocationEvaluationRequest {
# define LOC_EVALUATION_BP_MAX 8
# define LOC_EVALUATION_BP_ALL 9
BreakpointInfo * bp_arr[LOC_EVALUATION_BP_MAX];
unsigned bp_cnt; /* bp_cnt > LOC_EVALUATION_BP_MAX means all breakpoints */
};
struct EvaluationRequest {
Context * ctx; /* Must be breakpoints group context */
LINK link_posted;
LINK link_active;
LocationEvaluationRequest loc_posted;
LocationEvaluationRequest loc_active;
ConditionEvaluationRequest * bp_arr;
unsigned bp_cnt;
unsigned bp_max;
};
struct ContextExtensionBP {
int step_over_bp_cnt;
BreakInstruction * stepping_over_bp; /* if not NULL, the context is stepping over a breakpoint instruction */
char ** bp_ids; /* if stopped by breakpoint, contains NULL-terminated list of breakpoint IDs */
EvaluationRequest * req;
Context * bp_grp;
int empty_bp_grp;
int instruction_cnt;
LINK link_hit_count;
};
static const char * BREAKPOINTS = "Breakpoints";
static size_t context_extension_offset = 0;
typedef struct Listener {
BreakpointsEventListener * listener;
void * args;
} Listener;
static Listener * listeners = NULL;
static unsigned listener_cnt = 0;
static unsigned listener_max = 0;
#define EXT(ctx) ((ContextExtensionBP *)((char *)(ctx) + context_extension_offset))
#define is_disabled(bp) (bp->enabled == 0 || bp->client_cnt == 0)
#define ADDR2INSTR_HASH_SIZE (32 * MEM_USAGE_FACTOR - 1)
#define addr2instr_hash(ctx, addr) ((unsigned)((uintptr_t)(ctx) + (uintptr_t)(addr) + ((uintptr_t)(addr) >> 8)) % ADDR2INSTR_HASH_SIZE)
#define link_all2bi(A) ((BreakInstruction *)((char *)(A) - offsetof(BreakInstruction, link_all)))
#define link_adr2bi(A) ((BreakInstruction *)((char *)(A) - offsetof(BreakInstruction, link_adr)))
#define link_lst2bi(A) ((BreakInstruction *)((char *)(A) - offsetof(BreakInstruction, link_lst)))
#define ID2BP_HASH_SIZE (32 * MEM_USAGE_FACTOR - 1)
#define link_all2bp(A) ((BreakpointInfo *)((char *)(A) - offsetof(BreakpointInfo, link_all)))
#define link_id2bp(A) ((BreakpointInfo *)((char *)(A) - offsetof(BreakpointInfo, link_id)))
#define INP2BR_HASH_SIZE (4 * MEM_USAGE_FACTOR - 1)
#define link_inp2br(A) ((BreakpointRef *)((char *)(A) - offsetof(BreakpointRef, link_inp)))
#define link_bp2br(A) ((BreakpointRef *)((char *)(A) - offsetof(BreakpointRef, link_bp)))
#define link_posted2erl(A) ((EvaluationRequest *)((char *)(A) - offsetof(EvaluationRequest, link_posted)))
#define link_active2erl(A) ((EvaluationRequest *)((char *)(A) - offsetof(EvaluationRequest, link_active)))
#define link_bcg2chnl(A) ((Channel *)((char *)(A) - offsetof(Channel, bclink)))
#define link_bp2hcnt(A) ((BreakpointHitCount *)((char *)(A) - offsetof(BreakpointHitCount, link_bp)))
#define link_ctx2hcnt(A) ((BreakpointHitCount *)((char *)(A) - offsetof(BreakpointHitCount, link_ctx)))
static LINK breakpoints = TCF_LIST_INIT(breakpoints);
static LINK id2bp[ID2BP_HASH_SIZE];
static LINK instructions = TCF_LIST_INIT(instructions);
static LINK addr2instr[ADDR2INSTR_HASH_SIZE];
static LINK inp2br[INP2BR_HASH_SIZE];
static LINK evaluations_posted = TCF_LIST_INIT(evaluations_posted);
static LINK evaluations_active = TCF_LIST_INIT(evaluations_active);
static uintptr_t generation_posted = 0;
static uintptr_t generation_active = 0;
static uintptr_t generation_done = 0;
static int planting_instruction = 0;
static int cache_enter_cnt = 0;
static int bp_location_error = 0;
#if ENABLE_LineNumbers
static int bp_line_cnt = 0;
#endif
static TCFBroadcastGroup * broadcast_group = NULL;
static unsigned id2bp_hash(char * id) {
unsigned hash = 0;
while (*id) hash = (hash >> 16) + hash + (unsigned char)*id++;
return hash % ID2BP_HASH_SIZE;
}
static unsigned get_bp_access_types(BreakpointInfo * bp, int virtual_addr) {
char * type = bp->type;
unsigned access_types = bp->access_mode;
if (access_types == 0 && (bp->file != NULL || bp->location != NULL)) access_types |= CTX_BP_ACCESS_INSTRUCTION;
if (type != NULL && strcmp(type, "Software") == 0) access_types |= CTX_BP_ACCESS_SOFTWARE;
if (virtual_addr) access_types |= CTX_BP_ACCESS_VIRTUAL;
return access_types;
}
#ifndef NDEBUG
static int print_not_stopped_contexts(Context * ctx) {
LINK * l;
Context * grp;
if (is_all_stopped(ctx)) return 1;
grp = context_get_group(ctx, CONTEXT_GROUP_STOP);
for (l = context_root.next; l != &context_root; l = l->next) {
Context * ctx = ctxl2ctxp(l);
if (context_get_group(ctx, CONTEXT_GROUP_STOP) != grp) continue;
printf("ID %s, stopped %d, exiting %d, exited %d, signal %d\n",
ctx->id, ctx->stopped, ctx->exiting, ctx->exited, ctx->signal);
}
return 0;
}
#endif
static void plant_instruction(BreakInstruction * bi) {
int error = 0;
size_t saved_size = bi->saved_size;
ErrorReport * rp = NULL;
assert(!bi->stepping_over_bp);
assert(!bi->planted);
assert(!bi->dirty);
assert(!bi->cb.ctx->exited);
assert(!bi->cb.ctx->exiting);
assert(bi->valid);
assert(bi->address_error == NULL);
assert(print_not_stopped_contexts(bi->cb.ctx));
bi->saved_size = 0;
if (context_plant_breakpoint(&bi->cb) < 0) error = errno;
bi->unsupported = error && get_error_code(error) == ERR_UNSUPPORTED;
if (bi->unsupported && !bi->virtual_addr && !bi->hardware) {
uint8_t * break_inst = get_break_instruction(bi->cb.ctx, &bi->saved_size);
if (break_inst == NULL) {
error = set_errno(ERR_OTHER, "The context does not support software breakpoints");
}
else {
assert(bi->saved_size > 0);
assert(sizeof(bi->saved_code) >= bi->saved_size);
assert(!bi->virtual_addr);
error = 0;
planting_instruction = 1;
memcpy(bi->planted_code, break_inst, bi->saved_size);
if (context_read_mem(bi->cb.ctx, bi->cb.address, bi->saved_code, bi->saved_size) < 0) {
error = errno;
}
else if (context_write_mem(bi->cb.ctx, bi->cb.address, break_inst, bi->saved_size) < 0) {
error = errno;
}
planting_instruction = 0;
}
}
else if (error == ERR_UNSUPPORTED) {
error = set_errno(ERR_OTHER, "Unsupported set of breakpoint attributes");
}
rp = get_error_report(error);
if (saved_size != bi->saved_size || !compare_error_reports(bi->planting_error, rp)) {
unsigned i;
release_error_report(bi->planting_error);
bi->planting_error = rp;
for (i = 0; i < bi->ref_cnt; i++) {
bi->refs[i].bp->status_changed = 1;
}
}
else {
release_error_report(rp);
}
bi->planted = bi->planting_error == NULL;
}
static int remove_instruction(BreakInstruction * bi) {
assert(bi->planted);
assert(bi->planting_error == NULL);
assert(bi->address_error == NULL);
assert(print_not_stopped_contexts(bi->cb.ctx));
if (bi->saved_size) {
if (!bi->cb.ctx->exited) {
int r = 0;
char buf[MAX_BI_SIZE];
planting_instruction = 1;
r = context_read_mem(bi->cb.ctx, bi->cb.address, buf, bi->saved_size);
if (r >= 0 && memcmp(buf, bi->planted_code, bi->saved_size) == 0) {
r = context_write_mem(bi->cb.ctx, bi->cb.address, bi->saved_code, bi->saved_size);
}
planting_instruction = 0;
if (r < 0) return -1;
}
}
else {
if (context_unplant_breakpoint(&bi->cb) < 0) return -1;
if (bi->cb.ctx->stopped_by_cb != NULL) {
ContextBreakpoint ** p = bi->cb.ctx->stopped_by_cb;
while (*p != NULL && *p != &bi->cb) p++;
while (*p != NULL && (*p = *(p + 1)) != NULL) p++;
}
}
bi->planted = 0;
bi->dirty = 0;
return 0;
}
#ifndef NDEBUG
static int is_canonical_addr(Context * ctx, ContextAddress address) {
Context * mem = NULL;
ContextAddress mem_addr = 0;
if (context_get_canonical_addr(ctx, address, &mem, &mem_addr, NULL, NULL) < 0) return 0;
return mem == ctx && address == mem_addr;
}
#endif
static BreakInstruction * find_instruction(Context * ctx, int virtual_addr,
ContextAddress address, unsigned access_types, ContextAddress access_size) {
int hash = addr2instr_hash(ctx, address);
LINK * l = addr2instr[hash].next;
assert(virtual_addr || is_canonical_addr(ctx, address));
while (l != addr2instr + hash) {
BreakInstruction * bi = link_adr2bi(l);
if (bi->cb.ctx == ctx &&
bi->cb.address == address &&
bi->cb.length == access_size &&
bi->cb.access_types == access_types &&
bi->virtual_addr == virtual_addr &&
bi->no_addr == 0)
{
return bi;
}
l = l->next;
}
return NULL;
}
static BreakInstruction * add_instruction(Context * ctx, int virtual_addr,
ContextAddress address, unsigned access_types, ContextAddress access_size) {
int hash = addr2instr_hash(ctx, address);
BreakInstruction * bi = (BreakInstruction *)loc_alloc_zero(sizeof(BreakInstruction));
assert(find_instruction(ctx, virtual_addr, address, access_types, access_size) == NULL);
list_add_last(&bi->link_all, &instructions);
list_add_last(&bi->link_adr, addr2instr + hash);
context_lock(ctx);
bi->cb.ctx = ctx;
bi->cb.address = address;
bi->cb.length = access_size;
bi->cb.access_types = access_types;
bi->virtual_addr = (uint8_t)virtual_addr;
return bi;
}
static void clear_instruction_refs(Context * ctx, BreakpointInfo * bp) {
LINK * l = instructions.next;
assert(print_not_stopped_contexts(ctx));
while (l != &instructions) {
unsigned i;
BreakInstruction * bi = link_all2bi(l);
for (i = 0; i < bi->ref_cnt; i++) {
InstructionRef * ref = bi->refs + i;
if (ref->ctx != ctx) continue;
if (bp != NULL && ref->bp != bp) continue;
ref->cnt = 0;
bi->valid = 0;
}
l = l->next;
}
}
static void free_instruction(BreakInstruction * bi) {
assert(bi->dirty == 0);
assert(bi->planted == 0);
assert(bi->ref_cnt == 0);
assert(bi->stepping_over_bp == 0);
list_remove(&bi->link_all);
list_remove(&bi->link_adr);
context_unlock(bi->cb.ctx);
release_error_report(bi->address_error);
release_error_report(bi->planting_error);
loc_free(bi->refs);
loc_free(bi);
}
static BreakInstruction * plant_at_canonical_address(BreakInstruction * v_bi);
static void validate_bi_refs(BreakInstruction * bi) {
unsigned i = 0;
assert(!bi->valid);
while (i < bi->ref_cnt) {
InstructionRef * ref = bi->refs + i;
if (ref->cnt == 0 || ref->ctx->exiting || ref->ctx->exited) {
ref->bp->instruction_cnt--;
ref->bp->status_changed = 1;
EXT(ref->ctx)->instruction_cnt--;
context_unlock(ref->ctx);
memmove(ref, ref + 1, sizeof(InstructionRef) * (bi->ref_cnt - i - 1));
if (bi->planted) bi->dirty = 1;
bi->ref_cnt--;
}
else {
if (ref->bp->attrs_changed && bi->planted) bi->dirty = 1;
i++;
}
}
bi->valid = 1;
}
static void flush_instructions(void) {
LINK lst;
LINK * l;
list_init(&lst);
/* Validate references */
l = instructions.next;
while (l != &instructions) {
BreakInstruction * bi = link_all2bi(l);
list_init(&bi->link_lst);
l = l->next;
if (!bi->valid) {
bi->planted_as_sw_bp = 0;
bi->hardware = 1;
if (bi->no_addr == 0) {
static const unsigned mask = ~(unsigned)(CTX_BP_ACCESS_VIRTUAL | CTX_BP_ACCESS_SOFTWARE);
if ((bi->cb.access_types & mask) == CTX_BP_ACCESS_INSTRUCTION && bi->cb.length == 1) {
unsigned i;
bi->hardware = 0;
for (i = 0; i < bi->ref_cnt; i++) {
char * type = bi->refs[i].bp->type;
if (bi->refs[i].cnt == 0) continue;
if (type == NULL) continue;
if (strcmp(type, "Hardware") == 0) {
bi->hardware = 1;
break;
}
}
}
}
if (bi->hardware || bi->virtual_addr) {
validate_bi_refs(bi);
}
list_add_last(&bi->link_lst, &lst);
}
}
/* Unplant breakpoints */
l = lst.next;
while (l != &lst) {
BreakInstruction * bi = link_lst2bi(l);
l = l->next;
if (!bi->valid) {
assert(!bi->hardware);
assert(!bi->virtual_addr);
continue;
}
if (bi->stepping_over_bp) continue;
if (bi->planted) {
if (bi->dirty || bi->address_error) {
remove_instruction(bi);
}
else if (bi->ref_cnt == 0 && bi->virtual_addr) {
remove_instruction(bi);
}
else if (bi->saved_size == 0 && !bi->hardware) {
/* Free space for hardware breakpoints */
remove_instruction(bi);
}
}
if (bi->planted || bi->ref_cnt == 0 || bi->address_error ||
bi->cb.ctx->exiting || bi->cb.ctx->exited) {
list_remove(&bi->link_lst);
}
}
/* Plant hardware breakpoints first */
l = lst.next;
while (l != &lst) {
BreakInstruction * bi = link_lst2bi(l);
l = l->next;
if (bi->hardware) {
if (!bi->planted) plant_instruction(bi);
list_remove(&bi->link_lst);
}
}
/* Plant the rest of virtual address breakpoints */
l = lst.next;
while (l != &lst) {
BreakInstruction * bi = link_lst2bi(l);
l = l->next;
if (bi->virtual_addr) {
if (!bi->planted) plant_instruction(bi);
if (!bi->planted && bi->unsupported) {
BreakInstruction * ca = plant_at_canonical_address(bi);
if (ca != NULL) {
bi->planted_as_sw_bp = 1;
assert(!ca->hardware);
assert(!ca->virtual_addr);
if (ca->address_error) {
if (!ca->valid) validate_bi_refs(ca);
}
else if (list_is_empty(&ca->link_lst)) {
list_add_last(&ca->link_lst, &lst);
}
}
}
list_remove(&bi->link_lst);
}
}
/* Validate and plant canonical address breakpoints */
l = lst.next;
while (l != &lst) {
BreakInstruction * bi = link_lst2bi(l);
l = l->next;
assert(!bi->no_addr);
assert(!bi->hardware);
assert(!bi->virtual_addr);
assert(!bi->address_error);
if (!bi->valid) validate_bi_refs(bi);
if (bi->stepping_over_bp) continue;
if (bi->ref_cnt == 0) continue;
if (!bi->planted) plant_instruction(bi);
}
/* Free unused break instructions */
l = instructions.next;
while (l != &instructions) {
BreakInstruction * bi = link_all2bi(l);
l = l->next;
list_init(&bi->link_lst);
if (bi->ref_cnt > 0) continue;
if (bi->stepping_over_bp) continue;
if (bi->planted && is_all_stopped(bi->cb.ctx)) remove_instruction(bi);
if (!bi->planted) free_instruction(bi);
}
}
static unsigned get_bp_hit_count(BreakpointInfo * bp, Context * ctx) {
unsigned count = 0;
LINK * l = bp->link_hit_count.next;
while (l != &bp->link_hit_count) {
BreakpointHitCount * c = link_bp2hcnt(l);
if (c->ctx == ctx) count += c->count;
l = l->next;
}
return count;
}
static unsigned inc_bp_hit_count(BreakpointInfo * bp, Context * ctx) {
unsigned count = 0;
LINK * l = bp->link_hit_count.next;
while (l != &bp->link_hit_count) {
BreakpointHitCount * c = link_bp2hcnt(l);
if (c->ctx == ctx) {
c->count++;
count += c->count;
break;
}
l = l->next;
}
if (count == 0) {
BreakpointHitCount * c = (BreakpointHitCount *)loc_alloc_zero(sizeof(BreakpointHitCount));
list_add_first(&c->link_bp, &bp->link_hit_count);
list_add_first(&c->link_ctx, &(EXT(ctx))->link_hit_count);
c->count = count = 1;
c->ctx = ctx;
}
bp->status_changed = 1;
return count;
}
static void reset_bp_hit_count(BreakpointInfo * bp) {
LINK * l = bp->link_hit_count.next;
while (l != &bp->link_hit_count) {
BreakpointHitCount * c = link_bp2hcnt(l);
l = l->next;
list_remove(&c->link_bp);
list_remove(&c->link_ctx);
loc_free(c);
bp->status_changed = 1;
}
}
void clone_breakpoints_on_process_fork(Context * parent, Context * child) {
Context * mem = context_get_group(parent, CONTEXT_GROUP_PROCESS);
LINK * l = instructions.next;
assert(child == context_get_group(child, CONTEXT_GROUP_PROCESS));
assert(child != mem);
while (l != &instructions) {
unsigned i;
BreakInstruction * ci = NULL;
BreakInstruction * bi = link_all2bi(l);
l = l->next;
if (!bi->planted) continue;
if (!bi->saved_size) continue;
if (bi->cb.ctx != mem) continue;
ci = add_instruction(child, bi->virtual_addr, bi->cb.address, bi->cb.access_types, bi->cb.length);
memcpy(ci->saved_code, bi->saved_code, bi->saved_size);
ci->saved_size = bi->saved_size;
ci->ref_size = bi->ref_size;
ci->ref_cnt = bi->ref_cnt;
ci->refs = (InstructionRef *)loc_alloc_zero(sizeof(InstructionRef) * ci->ref_size);
for (i = 0; i < bi->ref_cnt; i++) {
BreakpointInfo * bp = bi->refs[i].bp;
ci->refs[i] = bi->refs[i];
ci->refs[i].ctx = child;
context_lock(child);
EXT(child)->instruction_cnt++;
bp->instruction_cnt++;
bp->status_changed = 1;
}
ci->valid = 1;
ci->planted = 1;
}
}
int unplant_breakpoints(Context * ctx) {
int error = 0;
LINK * l = instructions.next;
/* Note: the function can be called for a fork child process
* that we don't want to attach. All references to such process
* should be removed immediately, we cannot rely on
* event_replant_breakpoints() to do that. */
while (l != &instructions) {
unsigned i;
BreakInstruction * bi = link_all2bi(l);
l = l->next;
if (bi->cb.ctx != ctx) continue;
if (bi->planted && remove_instruction(bi) < 0) {
error = errno;
continue;
}
for (i = 0; i < bi->ref_cnt; i++) {
BreakpointInfo * bp = bi->refs[i].bp;
Context * bx = bi->refs[i].ctx;
assert(bp->instruction_cnt > 0);
bp->instruction_cnt--;
bp->status_changed = 1;
EXT(bx)->instruction_cnt--;
context_unlock(bx);
}
bi->ref_cnt = 0;
free_instruction(bi);
}
if (!error) return 0;
errno = error;
return -1;
}
int check_breakpoints_on_memory_read(Context * ctx, ContextAddress address, void * p, size_t size) {
if (!planting_instruction) {
while (size > 0) {
size_t sz = size;
uint8_t * buf = (uint8_t *)p;
LINK * l = instructions.next;
Context * mem = NULL;
ContextAddress mem_addr = 0;
ContextAddress mem_base = 0;
ContextAddress mem_size = 0;
while (l != &instructions) {
BreakInstruction * bi = link_all2bi(l);
size_t i;
l = l->next;
if (!bi->planted) continue;
if (!bi->saved_size) continue;
if (mem == NULL) {
if (context_get_canonical_addr(ctx, address, &mem, &mem_addr, &mem_base, &mem_size) < 0) return -1;
if ((size_t)(mem_base + mem_size - mem_addr) < sz) sz = (size_t)(mem_base + mem_size - mem_addr);
}
if (bi->cb.ctx != mem) continue;
if (bi->cb.address + bi->saved_size <= mem_addr) continue;
if (bi->cb.address >= mem_addr + sz) continue;
for (i = 0; i < bi->saved_size; i++) {
if (bi->cb.address + i < mem_addr) continue;
if (bi->cb.address + i >= mem_addr + sz) continue;
buf[bi->cb.address + i - mem_addr] = bi->saved_code[i];
}
}
p = (uint8_t *)p + sz;
address += sz;
size -= sz;
}
}
return 0;
}
int check_breakpoints_on_memory_write(Context * ctx, ContextAddress address, void * p, size_t size) {
if (!planting_instruction) {
while (size > 0) {
size_t sz = size;
uint8_t * buf = (uint8_t *)p;
LINK * l = instructions.next;
Context * mem = NULL;
ContextAddress mem_addr = 0;
ContextAddress mem_base = 0;
ContextAddress mem_size = 0;
while (l != &instructions) {
BreakInstruction * bi = link_all2bi(l);
l = l->next;
if (!bi->planted) continue;
if (!bi->saved_size) continue;
if (mem == NULL) {
if (context_get_canonical_addr(ctx, address, &mem, &mem_addr, &mem_base, &mem_size) < 0) return -1;
if ((size_t)(mem_base + mem_size - mem_addr) < sz) sz = (size_t)(mem_base + mem_size - mem_addr);
}
if (bi->cb.ctx != mem) continue;
if (bi->cb.address + bi->saved_size <= mem_addr) continue;
if (bi->cb.address >= mem_addr + sz) continue;
{
size_t i;
uint8_t * break_inst = get_break_instruction(bi->cb.ctx, &i);
assert(i == bi->saved_size);
for (i = 0; i < bi->saved_size; i++) {
if (bi->cb.address + i < mem_addr) continue;
if (bi->cb.address + i >= mem_addr + sz) continue;
bi->saved_code[i] = buf[bi->cb.address + i - mem_addr];
buf[bi->cb.address + i - mem_addr] = break_inst[i];
}
}
}
p = (uint8_t *)p + sz;
address += sz;
size -= sz;
}
}
return 0;
}
static void write_breakpoint_status(OutputStream * out, BreakpointInfo * bp) {
write_stream(out, '{');
if (bp->instruction_cnt) {
int cnt = 0;
LINK * l = instructions.next;
json_write_string(out, "Instances");
write_stream(out, ':');
write_stream(out, '[');
while (l != &instructions) {
unsigned i;
BreakInstruction * bi = link_all2bi(l);
l = l->next;
if (bi->planted_as_sw_bp) continue;
for (i = 0; i < bi->ref_cnt; i++) {
if (bi->refs[i].bp != bp) continue;
if (cnt > 0) write_stream(out, ',');
write_stream(out, '{');
json_write_string(out, "LocationContext");
write_stream(out, ':');
json_write_string(out, bi->refs[i].ctx->id);
if (bi->address_error != NULL) {
write_stream(out, ',');
json_write_string(out, "Error");
write_stream(out, ':');
json_write_string(out, errno_to_str(set_error_report_errno(bi->address_error)));
}
else {
write_stream(out, ',');
json_write_string(out, "HitCount");
write_stream(out, ':');
json_write_ulong(out, get_bp_hit_count(bp, bi->refs[i].ctx));
if (!bi->no_addr) {
write_stream(out, ',');
json_write_string(out, "Address");
write_stream(out, ':');
json_write_uint64(out, bi->refs[i].addr);
}
if (bi->cb.length > 0) {
write_stream(out, ',');
json_write_string(out, "Size");
write_stream(out, ':');
json_write_uint64(out, bi->cb.length);
}
if (bi->planting_error != NULL) {
write_stream(out, ',');
json_write_string(out, "Error");
write_stream(out, ':');
json_write_string(out, errno_to_str(set_error_report_errno(bi->planting_error)));
}
else if (bi->planted) {
int bp_type_is_set = 0;
#if ENABLE_ExtendedBreakpointStatus
if (bi->saved_size == 0) {
/* Back-end context breakpoint status */
int cnt = 0;
const char ** names = NULL;
const char ** values = NULL;
if (context_get_breakpoint_status(&bi->cb, &names, &values, &cnt) == 0) {
while (cnt > 0) {
if (*values != NULL) {
if (strcmp (*names, "BreakpointType") == 0) bp_type_is_set = 1;
write_stream(out, ',');
json_write_string(out, *names);
write_stream(out, ':');
write_string(out, *values);
}
names++;
values++;
cnt--;
}
}
}
#endif
if (bp_type_is_set == 0) {
write_stream(out, ',');
json_write_string(out, "BreakpointType");
write_stream(out, ':');
json_write_string(out, bi->saved_size ? "Software" : "Hardware");
}
}
}
write_stream(out, '}');
cnt++;
}
}
write_stream(out, ']');
assert(generation_done != generation_active || cnt > 0);
}
else if (bp->error) {
json_write_string(out, "Error");
write_stream(out, ':');
json_write_string(out, errno_to_str(set_error_report_errno(bp->error)));
}
write_stream(out, '}');
}
static void send_event_breakpoint_status(Channel * channel, BreakpointInfo * bp) {
OutputStream * out = channel ? &channel->out : &broadcast_group->out;
unsigned i;
assert(*bp->id);
write_stringz(out, "E");
write_stringz(out, BREAKPOINTS);
write_stringz(out, "status");
json_write_string(out, bp->id);
write_stream(out, 0);
write_breakpoint_status(out, bp);
write_stream(out, 0);
write_stream(out, MARKER_EOM);
if (channel) return;
for (i = 0; i < listener_cnt; i++) {
Listener * l = listeners + i;
if (l->listener->breakpoint_status_changed == NULL) continue;
l->listener->breakpoint_status_changed(bp, l->args);
}
}
static BreakInstruction * link_breakpoint_instruction(
BreakpointInfo * bp, Context * ctx,
ContextAddress ctx_addr, ContextAddress size,
Context * mem, int virtual_addr, ContextAddress mem_addr,
ErrorReport * address_error) {
BreakInstruction * bi = NULL;
InstructionRef * ref = NULL;
if (mem == NULL) {
/* Breakpoint does not have an address, e.g. breakpoint on a signal or I/O event */
int hash = addr2instr_hash(ctx, bp);
LINK * l = addr2instr[hash].next;
assert(ctx_addr == 0);
assert(mem_addr == 0);
assert(virtual_addr == 0);
while (l != addr2instr + hash) {
BreakInstruction * i = link_adr2bi(l);
if (i->cb.ctx == ctx && i->no_addr && i->ref_cnt == 1 &&
i->refs[0].ctx == ctx && i->refs[0].bp == bp &&
compare_error_reports(address_error, i->address_error)) {
release_error_report(address_error);
i->refs[0].cnt++;
return i;
}
l = l->next;
}
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->cb.ctx = ctx;
bi->no_addr = 1;
bi->cb.access_types = CTX_BP_ACCESS_NO_ADDRESS;
bi->address_error = address_error;
}
else {
unsigned access_types = get_bp_access_types(bp, virtual_addr);
bi = find_instruction(mem, virtual_addr, mem_addr, access_types, size);
if (bi == NULL) {
bi = add_instruction(mem, virtual_addr, mem_addr, access_types, size);
}
else {
unsigned i = 0;
while (i < bi->ref_cnt) {
ref = bi->refs + i;
if (ref->bp == bp && ref->ctx == ctx) {
assert(!bi->valid || !bi->virtual_addr);
ref->addr = ctx_addr;
ref->cnt++;
return bi;
}
i++;
}
}
}
if (bi->ref_cnt >= bi->ref_size) {
bi->ref_size = bi->ref_size == 0 ? 8 : bi->ref_size * 2;
bi->refs = (InstructionRef *)loc_realloc(bi->refs, sizeof(InstructionRef) * bi->ref_size);
}
bi->valid = 0;
ref = bi->refs + bi->ref_cnt++;
memset(ref, 0, sizeof(InstructionRef));
ref->bp = bp;
ref->ctx = ctx;
ref->addr = ctx_addr;
ref->cnt = 1;
context_lock(ctx);
EXT(ctx)->instruction_cnt++;
bp->instruction_cnt++;
bp->status_changed = 1;
return bi;
}
static BreakInstruction * address_expression_error(Context * ctx, BreakpointInfo * bp, int error) {
ErrorReport * rp = NULL;
assert(error != 0);
assert(bp->error == NULL);
if (get_error_code(error) == ERR_CACHE_MISS) return NULL;
rp = get_error_report(error);
assert(rp != NULL);
return link_breakpoint_instruction(bp, ctx, 0, 0, NULL, 0, 0, rp);
}
static void plant_breakpoint(Context * ctx, BreakpointInfo * bp, ContextAddress addr, ContextAddress size) {
link_breakpoint_instruction(bp, ctx, addr, size, ctx, 1, addr, NULL);
}
static BreakInstruction * plant_at_canonical_address(BreakInstruction * v_bi) {
Context * ctx = v_bi->cb.ctx;
ContextAddress addr = v_bi->cb.address;
ContextAddress size = v_bi->cb.length;
BreakInstruction * c_bi = NULL;
ContextAddress mem_addr = 0;
Context * mem = NULL;
int error = 0;
unsigned i;
if (context_get_canonical_addr(ctx, addr, &mem, &mem_addr, NULL, NULL) < 0) error = errno;
for (i = 0; i < v_bi->ref_cnt; i++) {
BreakpointInfo * bp = v_bi->refs[i].bp;
assert(v_bi->refs[i].ctx == ctx);
if (error) {
c_bi = address_expression_error(ctx, bp, error);
}
else {
c_bi = link_breakpoint_instruction(bp, ctx, addr, size, mem, 0, mem_addr, NULL);
assert(c_bi->ref_cnt > 0);
assert(c_bi->virtual_addr == 0);
assert(c_bi->ref_cnt > 1 || c_bi->refs[0].bp == bp);
}
}
return c_bi;
}
static void event_replant_breakpoints(void * arg);
static EvaluationRequest * create_evaluation_request(Context * ctx) {
EvaluationRequest * req = EXT(ctx)->req;
if (req == NULL) {
req = (EvaluationRequest *)loc_alloc_zero(sizeof(EvaluationRequest));
req->ctx = ctx;
list_init(&req->link_posted);
list_init(&req->link_active);
EXT(ctx)->req = req;
}
assert(req->ctx == ctx);
return req;
}
static ConditionEvaluationRequest * add_condition_evaluation_request(EvaluationRequest * req, Context * ctx, BreakpointInfo * bp) {
unsigned i;
ConditionEvaluationRequest * c = NULL;
assert(bp->instruction_cnt);
assert(bp->error == NULL);
assert(ctx->stopped_by_bp || ctx->stopped_by_cb);
for (i = 0; i < req->bp_cnt; i++) {
if (req->bp_arr[i].ctx == ctx && req->bp_arr[i].bp == bp) return NULL;
}
if (req->bp_max <= req->bp_cnt) {
req->bp_max = req->bp_cnt + 4;
req->bp_arr = (ConditionEvaluationRequest *)loc_realloc(req->bp_arr, sizeof(ConditionEvaluationRequest) * req->bp_max);
}
c = req->bp_arr + req->bp_cnt++;
context_lock(c->ctx = ctx);
c->bp = bp;
c->condition_ok = 0;
c->triggered = 0;
return c;
}
static void post_evaluation_request(EvaluationRequest * req) {
if (list_is_empty(&req->link_posted)) {
context_lock(req->ctx);
list_add_last(&req->link_posted, &evaluations_posted);
if (generation_posted == generation_done) run_ctrl_lock();
post_event(event_replant_breakpoints, (void *)++generation_posted);
}
}
static int check_context_ids_location(BreakpointInfo * bp, Context * ctx);
static void post_location_evaluation_request(Context * ctx, BreakpointInfo * bp) {
ContextExtensionBP * ext = EXT(ctx);
Context * grp = context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT);
assert(ctx->exited || id2ctx(ctx->id) == ctx);
if (ext->bp_grp != NULL && ext->bp_grp != grp && !ext->bp_grp->exited) {
/* The context has migrated into another breakpoint group.
* If the old group became empty, we need to remove breakpoints in it.
*/
int ctx_found = 0;
LINK * l = context_root.next;
while (l != &context_root) {
Context * c = ctxl2ctxp(l);
l = l->next;
if (c->exited) continue;
if (context_get_group(c, CONTEXT_GROUP_BREAKPOINT) == ext->bp_grp) {
ctx_found = 1;
break;
}
}
if (!ctx_found) {
EvaluationRequest * req = create_evaluation_request(ext->bp_grp);
req->loc_posted.bp_cnt = LOC_EVALUATION_BP_ALL;
post_evaluation_request(req);
EXT(ext->bp_grp)->empty_bp_grp = 1;
}
}
else if (bp == NULL && grp != NULL && EXT(grp)->instruction_cnt == 0) {
EvaluationRequest * req = EXT(grp)->req;
if (req == NULL || list_is_empty(&req->link_posted)) {
/* Optimization: if no breakpoints to replant, ignore the request */
int bp_found = 0;
LINK * l = breakpoints.next;
while (l != &breakpoints) {
BreakpointInfo * bp = link_all2bp(l);
if (!is_disabled(bp) && (bp->context_query || check_context_ids_location(bp, grp))) {
bp_found = 1;
break;
}
l = l->next;
}
if (!bp_found) return;
}
}
ext->bp_grp = grp;
if (grp != NULL) {
EvaluationRequest * req = create_evaluation_request(grp);
if (bp != NULL && req->loc_posted.bp_cnt < LOC_EVALUATION_BP_MAX) {
req->loc_posted.bp_arr[req->loc_posted.bp_cnt++] = bp;
}
else {
req->loc_posted.bp_cnt = LOC_EVALUATION_BP_ALL;
}
post_evaluation_request(req);
EXT(grp)->empty_bp_grp = 0;
}
}
static void run_bp_evaluation(CacheClient * client, BreakpointInfo * bp, Context * ctx, int index) {
int cnt = 0;
EvaluationArgs args;
if (*bp->id && list_is_empty(&bp->link_clients)) return;
args.bp = bp;
args.ctx = ctx;
args.index = index;
if (*bp->id) {
LINK * l = bp->link_clients.next;
while (l != &bp->link_clients) {
BreakpointRef * br = link_bp2br(l);
Channel * c = br->channel;
assert(br->bp == bp);
if (c != NULL) {
assert(!is_channel_closed(c));
cache_set_def_channel(c);
client(&args);
cnt++;
}
l = l->next;
}
}
if (cnt == 0) {
LINK * l = channel_root.next;
while (l != &channel_root) {
Channel * c = chanlink2channelp(l);
if (!is_channel_closed(c)) {
cache_set_def_channel(c);
client(&args);
cnt++;
}
l = l->next;
}
}
cache_set_def_channel(NULL);
if (cnt == 0) client(&args);
}
static void free_bp(BreakpointInfo * bp) {
assert(list_is_empty(&evaluations_posted));
assert(list_is_empty(&evaluations_active));
assert(list_is_empty(&bp->link_clients));
assert(bp->instruction_cnt == 0);
assert(bp->client_cnt == 0);
reset_bp_hit_count(bp);
list_remove(&bp->link_all);
if (*bp->id) list_remove(&bp->link_id);
if (bp->ctx) context_unlock(bp->ctx);
release_error_report(bp->error);
loc_free(bp->type);
loc_free(bp->location);
loc_free(bp->context_ids);
loc_free(bp->context_names);
loc_free(bp->context_query);
loc_free(bp->stop_group);
loc_free(bp->file);
loc_free(bp->condition);
loc_free(bp->client_data);
while (bp->attrs != NULL) {
BreakpointAttribute * attr = bp->attrs;
bp->attrs = attr->next;
loc_free(attr->name);
loc_free(attr->value);
loc_free(attr);
}
loc_free(bp);
}
static void remove_ref(BreakpointRef * br);
static void send_event_context_removed(BreakpointInfo * bp);
static void notify_breakpoint_status(BreakpointInfo * bp) {
assert(generation_done == generation_posted);
#ifndef NDEBUG
{
/* Verify breakpoints data structure */
LINK * m = NULL;
int instruction_cnt = 0;
int planted_as_sw_cnt = 0;
for (m = instructions.next; m != &instructions; m = m->next) {
unsigned i;
BreakInstruction * bi = link_all2bi(m);
assert(bi->valid);
assert(bi->ref_cnt <= bi->ref_size);
assert(bi->cb.ctx->ref_count > 0);
for (i = 0; i < bi->ref_cnt; i++) {
assert(bi->refs[i].cnt > 0);
assert(bi->refs[i].ctx->ref_count > 0);
assert(!bi->refs[i].ctx->exited);
assert(!bi->cb.ctx->exited);
if (bi->virtual_addr || bi->address_error || bi->no_addr) {
assert(bi->refs[i].ctx == bi->cb.ctx);
}
if (bi->refs[i].bp == bp) {
instruction_cnt++;
if (bi->planted_as_sw_bp) {
assert(bi->virtual_addr);
planted_as_sw_cnt++;
}
assert(id2ctx(bi->refs[i].ctx->id) == NULL ||
check_context_ids_location(bp, bi->refs[i].ctx));
}
}
}
assert(bp->enabled || instruction_cnt == 0);
assert(bp->instruction_cnt == instruction_cnt);
assert(planted_as_sw_cnt == 0 || planted_as_sw_cnt < instruction_cnt);
if (*bp->id) {
int i;
int client_cnt = 0;
for (i = 0; i < INP2BR_HASH_SIZE; i++) {
for (m = inp2br[i].next; m != &inp2br[i]; m = m->next) {
BreakpointRef * br = link_inp2br(m);
if (br->bp == bp) client_cnt++;
}
}
assert(bp->client_cnt == client_cnt);
}
else {
assert(list_is_empty(&bp->link_clients));
}
}
#endif
if (bp->client_cnt == 0) {
if (bp->instruction_cnt == 0) {
if (*bp->id) send_event_context_removed(bp);
free_bp(bp);
}
}
else if (bp->status_changed) {
if (*bp->id) send_event_breakpoint_status(NULL, bp);
bp->status_changed = 0;
}
}
static void done_replanting_breakpoints(void) {
LINK * l = NULL;
assert(list_is_empty(&evaluations_posted));
assert(list_is_empty(&evaluations_active));
assert(generation_done == generation_active);
for (l = breakpoints.next; l != &breakpoints;) {
BreakpointInfo * bp = link_all2bp(l);
l = l->next;
bp->attrs_changed = 0;
notify_breakpoint_status(bp);
}
}
static void done_condition_evaluation(EvaluationRequest * req) {
Context ** trig_ctx = (Context **)tmp_alloc_zero(sizeof(Context *) * req->bp_cnt);
size_t * trig_size = (size_t *)tmp_alloc_zero(sizeof(size_t) * req->bp_cnt);
unsigned trig_cnt = 0;
unsigned i, j;
for (i = 0; i < req->bp_cnt; i++) {
Context * ctx = req->bp_arr[i].ctx;
BreakpointInfo * bp = req->bp_arr[i].bp;
assert(ctx->stopped);
if (!req->bp_arr[i].condition_ok) continue;
if (inc_bp_hit_count(bp, req->ctx) <= bp->ignore_count) continue;
if (bp->event_callback != NULL) {
bp->event_callback(ctx, bp->event_callback_args);
}
else {
j = 0;
assert(bp->id[0] != 0);
req->bp_arr[i].triggered = 1;
while (j < trig_cnt && trig_ctx[j] != ctx) j++;
if (j == trig_cnt) trig_ctx[trig_cnt++] = ctx;
trig_size[j] += sizeof(char *) + strlen(bp->id) + 1;
}
}
for (j = 0; j < trig_cnt; j++) {
/* Create list of triggered breakpoint IDs */
Context * ctx = trig_ctx[j];
size_t mem_size = trig_size[j] + sizeof(char *);
char ** bp_id_list = (char **)loc_alloc(mem_size);
char * pool = (char *)bp_id_list + mem_size;
ContextExtensionBP * ext = EXT(ctx);
assert(ext->bp_ids == NULL);
ext->bp_ids = bp_id_list;
for (i = 0; i < req->bp_cnt; i++) {
if (req->bp_arr[i].triggered && req->bp_arr[i].ctx == ctx) {
BreakpointInfo * bp = req->bp_arr[i].bp;
size_t n = strlen(bp->id) + 1;
pool -= n;
memcpy(pool, bp->id, n);
*bp_id_list++ = pool;
}
}
*bp_id_list++ = NULL;
assert((char *)bp_id_list == pool);
}
/* Intercept contexts */
for (i = 0; i < req->bp_cnt; i++) {
if (req->bp_arr[i].triggered && req->bp_arr[i].bp->stop_group == NULL) {
suspend_debug_context(req->bp_arr[i].ctx);
}
}
}
static void done_all_evaluations(void) {
LINK * l;
l = evaluations_active.next;
while (l != &evaluations_active) {
EvaluationRequest * req = link_active2erl(l);
l = l->next;
if (req->bp_cnt) done_condition_evaluation(req);
}
l = evaluations_active.next;
while (l != &evaluations_active) {
EvaluationRequest * req = link_active2erl(l);
unsigned i;
l = l->next;
for (i = 0; i < req->bp_cnt; i++) {
if (req->bp_arr[i].triggered) {
BreakpointInfo * bp = req->bp_arr[i].bp;
if (bp->stop_group != NULL) {
/* Intercept contexts in BP stop groups */
char ** ids = bp->stop_group;
while (*ids) {
Context * c = id2ctx(*ids++);
if (c != NULL) suspend_debug_context(c);
}
}
if (bp->temporary) {
LINK * m = bp->link_clients.next;
while (m != &bp->link_clients) {
BreakpointRef * br = link_bp2br(m);
m = m->next;
assert(br->bp == bp);
remove_ref(br);
}
}
}
context_unlock(req->bp_arr[i].ctx);
}
req->bp_cnt = 0;
list_remove(&req->link_active);
context_unlock(req->ctx);
}
}
static void plant_at_address_expression(Context * ctx, ContextAddress ip, BreakpointInfo * bp) {
ContextAddress addr = 0;
ContextAddress size = 1;
int error = 0;
Value v;
if (evaluate_expression(ctx, STACK_NO_FRAME, ip, bp->location, 1, &v) < 0) error = errno;
if (!error && value_to_address(&v, &addr) < 0) error = errno;
if (bp->access_mode & (CTX_BP_ACCESS_DATA_READ | CTX_BP_ACCESS_DATA_WRITE)) {
if (bp->access_size > 0) {
size = bp->access_size;
}
else {
size = context_word_size(ctx);
#if ENABLE_Symbols
if (v.type != NULL) {
Symbol * type = v.type;
int type_class = 0;
if (!error && get_symbol_type_class(type, &type_class) < 0) error = errno;
if (!error && type_class == TYPE_CLASS_POINTER) {
Symbol * base_type = NULL;
if (!error && get_symbol_base_type(type, &base_type) < 0) error = errno;
if (!error && base_type != NULL && get_symbol_size(base_type, &size) < 0) error = errno;
}
}
#endif
}
}
if (error) {
address_expression_error(ctx, bp, error);
}
else {
plant_breakpoint(ctx, bp, addr, size);
#if ENABLE_Symbols
/* If the expression returns multiple symbols, plant multiple breakpoints */
if (v.sym_list != NULL) {
unsigned n = 0;
while (v.sym_list[n] != NULL) {
Symbol * sym = v.sym_list[n++];
if (get_symbol_address(sym, &addr) == 0) {
plant_breakpoint(ctx, bp, addr, size);
}
}
}
#endif
}
}
#if ENABLE_LineNumbers
static void plant_breakpoint_address_iterator(CodeArea * area, void * x) {
EvaluationArgs * args = (EvaluationArgs *)x;
bp_line_cnt++;
if (args->bp->location == NULL) {
ContextAddress addr = area->start_address;
if ((addr == area->end_address || area->start_line != args->bp->line) &&
area->next_address != 0) addr = area->next_address;
plant_breakpoint(args->ctx, args->bp, addr, 1);
}
else {
plant_at_address_expression(args->ctx, area->start_address, args->bp);
}
}
#endif
static int check_context_ids_location(BreakpointInfo * bp, Context * ctx) {
/* Check context IDs attribute and return 1 if the breakpoint should be planted in 'ctx' */
assert(ctx == context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT));
if (bp->ctx != NULL) {
if (context_get_group(bp->ctx, CONTEXT_GROUP_BREAKPOINT) != ctx) return 0;
}
if (bp->context_ids != NULL) {
int ok = 0;
char ** ids = bp->context_ids;
while (!ok && *ids != NULL) {
Context * c = id2ctx(*ids++);
if (c == NULL) continue;
ok = context_get_group(c, CONTEXT_GROUP_BREAKPOINT) == ctx;
}
if (!ok) return 0;
}
if (bp->context_names != NULL) {
int ok = 0;
char ** names = bp->context_names;
while (!ok && *names != NULL) {
char * name = *names++;
LINK * l = context_root.next;
while (!ok && l != &context_root) {
Context * c = ctxl2ctxp(l);
l = l->next;
if (c->exited) continue;
if (c->name == NULL) continue;
if (context_get_group(c, CONTEXT_GROUP_BREAKPOINT) != ctx) continue;
ok = strcmp(c->name, name) == 0;
}
}
if (!ok) return 0;
}
if (bp->context_query != NULL) {
int ok = 0;
LINK * l = context_root.next;
if (parse_context_query(bp->context_query) < 0) {
bp_location_error = errno;
return 0;
}
while (!ok && l != &context_root) {
Context * c = ctxl2ctxp(l);
l = l->next;
if (c->exited) continue;
if (context_get_group(c, CONTEXT_GROUP_BREAKPOINT) != ctx) continue;
ok = run_context_query(c);
}
if (!ok) return 0;
}
return 1;
}
static int check_context_ids_condition(BreakpointInfo * bp, Context * ctx) {
/* Check context IDs attribute and return 1 if the breakpoint should be triggered by 'ctx' */
assert(context_has_state(ctx));
if (bp->ctx != NULL) {
if (bp->ctx != ctx) return 0;
}
if (bp->context_ids != NULL) {
int ok = 0;
char ** ids = bp->context_ids;
Context * prs = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
while (!ok && *ids != NULL) {
char * id = *ids++;
ok = strcmp(id, ctx->id) == 0 || (prs && strcmp(id, prs->id) == 0);
}
if (!ok) return 0;
}
if (bp->context_names != NULL) {
int ok = 0;
if (ctx->name) {
char * name = ctx->name;
char ** names = bp->context_names;
while (!ok && *names != NULL) {
ok = strcmp(name, *names++) == 0;
}
}
if (!ok) {
Context * prs = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
if (prs && prs->name) {
char * name = prs->name;
char ** names = bp->context_names;
while (!ok && *names != NULL) {
ok = strcmp(name, *names++) == 0;
}
}
}
if (!ok) return 0;
}
if (bp->context_query != NULL) {
parse_context_query(bp->context_query);
if (!run_context_query(ctx)) return 0;
}
return 1;
}
static void evaluate_condition(void * x) {
EvaluationArgs * args = (EvaluationArgs *)x;
EvaluationRequest * req = EXT(args->ctx)->req;
ConditionEvaluationRequest * ce = req->bp_arr + args->index;
BreakpointInfo * bp = ce->bp;
assert(req != NULL);
assert(req->bp_cnt > 0);
assert(args->index >= 0);
assert(args->index < req->bp_cnt);
assert(cache_enter_cnt > 0);
assert(args->bp == ce->bp);
if (!is_disabled(bp)) {
Context * ctx = ce->ctx;
assert(ctx->stopped);
assert(ctx->stopped_by_bp || ctx->stopped_by_cb);
if (check_context_ids_condition(bp, ctx)) {
if (bp->condition != NULL) {
Value v;
int b = 0;
if (evaluate_expression(ctx, STACK_TOP_FRAME, 0, bp->condition, 1, &v) < 0 ||
(v.size > 0 && value_to_boolean(&v, &b) < 0)) {
switch (get_error_code(errno)) {
case ERR_CACHE_MISS:
case ERR_CHANNEL_CLOSED:
break;
default:
trace(LOG_ALWAYS, "%s: %s", errno_to_str(errno), bp->condition);
ce->condition_ok = 1;
break;
}
}
else if (b) {
ce->condition_ok = 1;
}
}
else {
ce->condition_ok = 1;
}
}
}
}
static void evaluate_bp_location(void * x) {
EvaluationArgs * args = (EvaluationArgs *)x;
BreakpointInfo * bp = args->bp;
Context * ctx = args->ctx;
assert(cache_enter_cnt > 0);
bp_location_error = 0;
if (!ctx->exited && !ctx->exiting && !EXT(ctx)->empty_bp_grp && !is_disabled(bp) &&
bp->error == NULL && check_context_ids_location(bp, ctx) && bp_location_error == 0) {
if (bp->file != NULL) {
#if ENABLE_LineNumbers
bp_line_cnt = 0;
if (line_to_address(ctx, bp->file, bp->line, bp->column, plant_breakpoint_address_iterator, args) < 0) {
bp_location_error = errno;
}
else if (bp_line_cnt == 0) {
bp_location_error = set_errno(ERR_OTHER, "Unresolved source line information");
}
#else
bp_location_error = set_errno(ERR_UNSUPPORTED, "LineNumbers service not available");
#endif
}
else if (bp->location != NULL) {
plant_at_address_expression(ctx, 0, bp);
}
else {
link_breakpoint_instruction(bp, ctx, 0, bp->access_size, NULL, 0, 0, NULL);
}
}
if (bp_location_error) address_expression_error(ctx, bp, bp_location_error);
}
static void validate_bp_attrs(void) {
LINK * l = breakpoints.next;
while (l != &breakpoints) {
BreakpointInfo * bp = link_all2bp(l);
if (bp->instruction_cnt == 0) {
ErrorReport * error = NULL;
if (bp->context_query != NULL) {
if (parse_context_query(bp->context_query) < 0) {
error = get_error_report(errno);
}
}
if (compare_error_reports(bp->error, error)) {
release_error_report(error);
}
else {
bp->status_changed = 1;
release_error_report(bp->error);
bp->error = error;
}
}
l = l->next;
}
}
static void replant_breakpoints_cache_client(void * args) {
EvaluationRequest * req = *(EvaluationRequest **)args;
Context * ctx = req->ctx;
unsigned i;
check_all_stopped(ctx);
if (req->loc_active.bp_cnt > LOC_EVALUATION_BP_MAX) {
clear_instruction_refs(ctx, NULL);
if (!ctx->exiting && !ctx->exited && !EXT(ctx)->empty_bp_grp) {
LINK * l = breakpoints.next;
while (l != &breakpoints) {
run_bp_evaluation(evaluate_bp_location, link_all2bp(l), ctx, -1);
l = l->next;
}
}
}
else if (req->loc_active.bp_cnt > 0) {
for (i = 0; i < req->loc_active.bp_cnt; i++) {
BreakpointInfo * bp = req->loc_active.bp_arr[i];
clear_instruction_refs(ctx, bp);
if (!ctx->exiting && !ctx->exited && !EXT(ctx)->empty_bp_grp) {
run_bp_evaluation(evaluate_bp_location, bp, ctx, -1);
}
}
}
if (req->bp_cnt > 0) {
for (i = 0; i < req->bp_cnt; i++) {
ConditionEvaluationRequest * r = req->bp_arr + i;
r->condition_ok = 0;
if (is_disabled(r->bp)) continue;
run_bp_evaluation(evaluate_condition, r->bp, ctx, i);
}
}
cache_exit();
cache_enter_cnt--;
assert(cache_enter_cnt >= 0);
if (cache_enter_cnt == 0) {
done_all_evaluations();
assert(list_is_empty(&evaluations_active));
if (list_is_empty(&evaluations_posted)) {
flush_instructions();
}
if (!list_is_empty(&evaluations_posted)) {
post_event(event_replant_breakpoints, (void *)++generation_posted);
}
else {
assert(generation_done != generation_active);
generation_done = generation_active;
assert(generation_posted == generation_done);
done_replanting_breakpoints();
run_ctrl_unlock();
}
}
}
static void event_replant_breakpoints(void * arg) {
LINK * q;
assert(!list_is_empty(&evaluations_posted));
if ((uintptr_t)arg != generation_posted) return;
if (cache_enter_cnt > 0) return;
validate_bp_attrs();
generation_active = generation_posted;
while (!list_is_empty(&evaluations_posted)) {
EvaluationRequest * req = link_posted2erl(evaluations_posted.next);
req->loc_active = req->loc_posted;
memset(&req->loc_posted, 0, sizeof(LocationEvaluationRequest));
assert(list_is_empty(&req->link_active));
list_add_last(&req->link_active, &evaluations_active);
list_remove(&req->link_posted);
cache_enter_cnt++;
}
q = evaluations_active.next;
while (q != &evaluations_active) {
EvaluationRequest * req = link_active2erl(q);
q = q->next;
cache_enter(replant_breakpoints_cache_client, NULL, &req, sizeof(req));
}
}
static void replant_breakpoint(BreakpointInfo * bp) {
int check_intsructions = 0;
if (bp->client_cnt == 0) {
check_intsructions = 1;
}
else if (bp->ctx != NULL) {
post_location_evaluation_request(bp->ctx, bp);
}
else if (bp->context_ids) {
char ** ids = bp->context_ids;
while (*ids != NULL) {
Context * ctx = id2ctx(*ids++);
if (ctx == NULL) continue;
if (ctx->exited) continue;
post_location_evaluation_request(ctx, bp);
}
check_intsructions = 1;
}
else if (bp->context_names) {
char ** names = bp->context_names;
while (*names != NULL) {
char * name = *names++;
LINK * l = context_root.next;
while (l != &context_root) {
Context * ctx = ctxl2ctxp(l);
l = l->next;
if (ctx->exited) continue;
if (ctx->name == NULL) continue;
if (strcmp(ctx->name, name)) continue;
post_location_evaluation_request(ctx, bp);
}
}
check_intsructions = 1;
}
else if (bp->context_query && parse_context_query(bp->context_query) == 0) {
LINK * l = context_root.next;
while (l != &context_root) {
Context * ctx = ctxl2ctxp(l);
l = l->next;
if (ctx->exited) continue;
if (!run_context_query(ctx)) continue;
post_location_evaluation_request(ctx, bp);
}
check_intsructions = 1;
}
else {
LINK * l = context_root.next;
while (l != &context_root) {
Context * ctx = ctxl2ctxp(l);
l = l->next;
if (ctx->exited) continue;
post_location_evaluation_request(ctx, bp);
}
}
if (check_intsructions && bp->instruction_cnt > 0) {
LINK * l = instructions.next;
while (l != &instructions) {
unsigned i;
BreakInstruction * bi = link_all2bi(l);
for (i = 0; i < bi->ref_cnt; i++) {
InstructionRef * ref = bi->refs + i;
if (ref->bp != bp) continue;
if (ref->ctx->exited) continue;
/* Check for fork child that is going to be detached */
if (id2ctx(ref->ctx->id) == NULL) continue;
post_location_evaluation_request(ref->ctx, bp);
}
l = l->next;
}
}
}
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, Channel * channel) {
LINK * l;
if (bp == NULL) return NULL;
l = bp->link_clients.next;
while (l != &bp->link_clients) {
BreakpointRef * br = link_bp2br(l);
assert(br->bp == bp);
if (br->channel == channel) return br;
l = l->next;
}
return NULL;
}
static void read_breakpoint_property(InputStream * inp, const char * name, void * args) {
BreakpointAttribute *** p = (BreakpointAttribute ***)args;
BreakpointAttribute * attr = (BreakpointAttribute *)loc_alloc_zero(sizeof(BreakpointAttribute));
attr->name = loc_strdup(name);
attr->value = json_read_object(inp);
**p = attr;
*p = &attr->next;
}
static BreakpointAttribute * read_breakpoint_properties(InputStream * inp) {
BreakpointAttribute * attrs = NULL;
BreakpointAttribute ** p = &attrs;
json_read_struct(inp, read_breakpoint_property, &p);
return attrs;
}
static void read_id_attribute(BreakpointAttribute * attrs, char * id, size_t id_size) {
while (attrs != NULL) {
if (strcmp(attrs->name, BREAKPOINT_ID) == 0) {
ByteArrayInputStream buf;
InputStream * inp = create_byte_array_input_stream(&buf, attrs->value, strlen(attrs->value));
json_read_string(inp, id, id_size);
json_test_char(inp, MARKER_EOS);
return;
}
attrs = attrs->next;
}
str_exception(ERR_OTHER, "Breakpoint must have an ID");
}
static void set_breakpoint_attribute(BreakpointInfo * bp, const char * name, const char * value) {
BreakpointAttribute * attr = bp->attrs;
BreakpointAttribute ** ref = &bp->attrs;
while (attr != NULL) {
if (strcmp(attr->name, name) == 0) {
loc_free(attr->value);
attr->value = loc_strdup(value);
return;
}
ref = &attr->next;
attr = attr->next;
}
attr = (BreakpointAttribute *)loc_alloc_zero(sizeof(BreakpointAttribute));
attr->name = loc_strdup(name);
attr->value = loc_strdup(value);
*ref = attr;
}
static int set_breakpoint_attributes(BreakpointInfo * bp, BreakpointAttribute * new_attrs) {
int diff = 0;
BreakpointAttribute * old_attrs = bp->attrs;
BreakpointAttribute ** new_ref = &bp->attrs;
bp->attrs = NULL;
while (new_attrs != NULL) {
BreakpointAttribute * new_attr = new_attrs;
BreakpointAttribute * old_attr = old_attrs;
BreakpointAttribute ** old_ref = &old_attrs;
InputStream * buf_inp = NULL;
ByteArrayInputStream buf;
int unsupported_attr = 0;
char * name = new_attr->name;
new_attrs = new_attr->next;
new_attr->next = NULL;
while (old_attr && strcmp(old_attr->name, name)) {
old_ref = &old_attr->next;
old_attr = old_attr->next;
}
if (old_attr != NULL) {
assert(old_attr == *old_ref);
*old_ref = old_attr->next;
old_attr->next = NULL;
if (strcmp(old_attr->value, new_attr->value) == 0) {
*new_ref = old_attr;
new_ref = &old_attr->next;
loc_free(new_attr->value);
loc_free(new_attr->name);
loc_free(new_attr);
continue;
}
loc_free(old_attr->value);
loc_free(old_attr->name);
loc_free(old_attr);
old_attr = NULL;
}
diff++;
*new_ref = new_attr;
new_ref = &new_attr->next;
buf_inp = create_byte_array_input_stream(&buf, new_attr->value, strlen(new_attr->value));
if (strcmp(name, BREAKPOINT_ID) == 0) {
json_read_string(buf_inp, bp->id, sizeof(bp->id));
}
else if (strcmp(name, BREAKPOINT_TYPE) == 0) {
loc_free(bp->type);
bp->type = json_read_alloc_string(buf_inp);
}
else if (strcmp(name, BREAKPOINT_LOCATION) == 0) {
loc_free(bp->location);
bp->location = json_read_alloc_string(buf_inp);
}
else if (strcmp(name, BREAKPOINT_ACCESSMODE) == 0) {
bp->access_mode = json_read_long(buf_inp);
}
else if (strcmp(name, BREAKPOINT_SIZE) == 0) {
bp->access_size = json_read_long(buf_inp);
}
else if (strcmp(name, BREAKPOINT_CONDITION) == 0) {
loc_free(bp->condition);
bp->condition = json_read_alloc_string(buf_inp);
}
else if (strcmp(name, BREAKPOINT_CONTEXTIDS) == 0) {
loc_free(bp->context_ids);
bp->context_ids = json_read_alloc_string_array(buf_inp, NULL);
}
else if (strcmp(name, BREAKPOINT_CONTEXTNAMES) == 0) {
loc_free(bp->context_names);
bp->context_names = json_read_alloc_string_array(buf_inp, NULL);
}
else if (strcmp(name, BREAKPOINT_CONTEXT_QUERY) == 0) {
loc_free(bp->context_query);
bp->context_query = json_read_alloc_string(buf_inp);
}
else if (strcmp(name, BREAKPOINT_STOP_GROUP) == 0) {
loc_free(bp->stop_group);
bp->stop_group = json_read_alloc_string_array(buf_inp, NULL);
}
else if (strcmp(name, BREAKPOINT_TEMPORARY) == 0) {
bp->temporary = json_read_boolean(buf_inp);
}
else if (strcmp(name, BREAKPOINT_FILE) == 0) {
loc_free(bp->file);
bp->file = json_read_alloc_string(buf_inp);
}
else if (strcmp(name, BREAKPOINT_LINE) == 0) {
bp->line = json_read_long(buf_inp);
}
else if (strcmp(name, BREAKPOINT_COLUMN) == 0) {
bp->column = json_read_long(buf_inp);
}
else if (strcmp(name, BREAKPOINT_IGNORECOUNT) == 0) {
bp->ignore_count = json_read_ulong(buf_inp);
}
else if (strcmp(name, BREAKPOINT_ENABLED) == 0) {
bp->enabled = json_read_boolean(buf_inp);
}
else {
unsupported_attr = 1;
}
if (!unsupported_attr) json_test_char(buf_inp, MARKER_EOS);
}
while (old_attrs != NULL) {
BreakpointAttribute * old_attr = old_attrs;
char * name = old_attr->name;
old_attrs = old_attr->next;
if (strcmp(name, BREAKPOINT_ID) == 0) {
bp->id[0] = 0;
}
else if (strcmp(name, BREAKPOINT_TYPE) == 0) {
loc_free(bp->type);
bp->type = NULL;
}
else if (strcmp(name, BREAKPOINT_LOCATION) == 0) {
loc_free(bp->location);
bp->location = NULL;
}
else if (strcmp(name, BREAKPOINT_ACCESSMODE) == 0) {
bp->access_mode = 0;
}
else if (strcmp(name, BREAKPOINT_SIZE) == 0) {
bp->access_size = 0;
}
else if (strcmp(name, BREAKPOINT_CONDITION) == 0) {
loc_free(bp->condition);
bp->condition = NULL;
}
else if (strcmp(name, BREAKPOINT_CONTEXTIDS) == 0) {
loc_free(bp->context_ids);
bp->context_ids = NULL;
}
else if (strcmp(name, BREAKPOINT_CONTEXTNAMES) == 0) {
loc_free(bp->context_names);
bp->context_names = NULL;
}
else if (strcmp(name, BREAKPOINT_CONTEXT_QUERY) == 0) {
loc_free(bp->context_query);
bp->context_query = NULL;
}
else if (strcmp(name, BREAKPOINT_STOP_GROUP) == 0) {
loc_free(bp->stop_group);
bp->stop_group = NULL;
}
else if (strcmp(name, BREAKPOINT_TEMPORARY) == 0) {
bp->temporary = 0;
}
else if (strcmp(name, BREAKPOINT_FILE) == 0) {
loc_free(bp->file);
bp->file = NULL;
}
else if (strcmp(name, BREAKPOINT_LINE) == 0) {
bp->line = 0;
}
else if (strcmp(name, BREAKPOINT_COLUMN) == 0) {
bp->column = 0;
}
else if (strcmp(name, BREAKPOINT_IGNORECOUNT) == 0) {
bp->ignore_count = 0;
}
else if (strcmp(name, BREAKPOINT_ENABLED) == 0) {
bp->enabled = 0;
}
loc_free(old_attr->value);
loc_free(old_attr->name);
loc_free(old_attr);
diff++;
}
return diff;
}
static void write_breakpoint_properties(OutputStream * out, BreakpointInfo * bp) {
int cnt = 0;
BreakpointAttribute * attr = bp->attrs;
write_stream(out, '{');
while (attr != NULL) {
if (cnt > 0) write_stream(out, ',');
json_write_string(out, attr->name);
write_stream(out, ':');
write_string(out, attr->value);
attr = attr->next;
cnt++;
}
write_stream(out, '}');
}
static void send_event_context_added(Channel * channel, BreakpointInfo * bp) {
OutputStream * out = channel ? &channel->out : &broadcast_group->out;
unsigned i;
assert(bp->id[0] != 0);
write_stringz(out, "E");
write_stringz(out, BREAKPOINTS);
write_stringz(out, "contextAdded");
write_stream(out, '[');
write_breakpoint_properties(out, bp);
write_stream(out, ']');
write_stream(out, 0);
write_stream(out, MARKER_EOM);
if (channel) return;
for (i = 0; i < listener_cnt; i++) {
Listener * l = listeners + i;
if (l->listener->breakpoint_created == NULL) continue;
l->listener->breakpoint_created(bp, l->args);
}
}
static void send_event_context_changed(BreakpointInfo * bp) {
OutputStream * out = &broadcast_group->out;
unsigned i;
assert(bp->id[0] != 0);
write_stringz(out, "E");
write_stringz(out, BREAKPOINTS);
write_stringz(out, "contextChanged");
write_stream(out, '[');
write_breakpoint_properties(out, bp);
write_stream(out, ']');
write_stream(out, 0);
write_stream(out, MARKER_EOM);
for (i = 0; i < listener_cnt; i++) {
Listener * l = listeners + i;
if (l->listener->breakpoint_changed == NULL) continue;
l->listener->breakpoint_changed(bp, l->args);
}
}
static void send_event_context_removed(BreakpointInfo * bp) {
OutputStream * out = &broadcast_group->out;
unsigned i;
write_stringz(out, "E");
write_stringz(out, BREAKPOINTS);
write_stringz(out, "contextRemoved");
write_stream(out, '[');
json_write_string(out, bp->id);
write_stream(out, ']');
write_stream(out, 0);
write_stream(out, MARKER_EOM);
for (i = 0; i < listener_cnt; i++) {
Listener * l = listeners + i;
if (l->listener->breakpoint_deleted == NULL) continue;
l->listener->breakpoint_deleted(bp, l->args);
}
}
static BreakpointInfo * add_breakpoint(Channel * c, BreakpointAttribute * attrs) {
char id[256];
BreakpointRef * r = NULL;
BreakpointInfo * bp = NULL;
int ref_added = 0;
int added = 0;
int chng = 0;
read_id_attribute(attrs, id, sizeof(id));
bp = find_breakpoint(id);
if (bp == NULL) {
int hash = id2bp_hash(id);
bp = (BreakpointInfo *)loc_alloc_zero(sizeof(BreakpointInfo));
list_init(&bp->link_clients);
list_init(&bp->link_hit_count);
list_add_last(&bp->link_all, &breakpoints);
list_add_last(&bp->link_id, id2bp + hash);
set_breakpoint_attributes(bp, attrs);
}
else {
chng = set_breakpoint_attributes(bp, attrs);
if (chng) bp->attrs_changed = 1;
}
if (list_is_empty(&bp->link_clients)) added = 1;
else r = find_breakpoint_ref(bp, c);
if (r == NULL) {
unsigned inp_hash = (unsigned)(uintptr_t)c / 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, &bp->link_clients);
r->channel = c;
r->bp = bp;
bp->client_cnt++;
ref_added = 1;
}
assert(r->bp == bp);
assert(!list_is_empty(&bp->link_clients));
if (chng || added || ref_added) replant_breakpoint(bp);
if (added) send_event_context_added(NULL, bp);
else if (chng) send_event_context_changed(bp);
return bp;
}
static void remove_ref(BreakpointRef * br) {
BreakpointInfo * bp = br->bp;
bp->client_cnt--;
list_remove(&br->link_inp);
list_remove(&br->link_bp);
loc_free(br);
replant_breakpoint(bp);
if (list_is_empty(&bp->link_clients)) {
assert(bp->client_cnt == 0);
if (generation_done == generation_posted) notify_breakpoint_status(bp);
}
}
static void delete_breakpoint_refs(Channel * c) {
unsigned hash = (unsigned)(uintptr_t)c / 16 % INP2BR_HASH_SIZE;
LINK * l = inp2br[hash].next;
while (l != &inp2br[hash]) {
BreakpointRef * br = link_inp2br(l);
l = l->next;
if (br->channel == c) remove_ref(br);
}
}
static void command_set_array_cb(InputStream * inp, void * args) {
add_breakpoint((Channel *)args, read_breakpoint_properties(inp));
}
static void command_set(char * token, Channel * c) {
LINK * l = NULL;
/* Delete all breakpoints of this channel */
delete_breakpoint_refs(c);
/* Report breakpoints from other channels */
l = breakpoints.next;
while (l != &breakpoints) {
BreakpointInfo * bp = link_all2bp(l);
l = l->next;
if (list_is_empty(&bp->link_clients)) continue;
assert(*bp->id);
send_event_context_added(c, bp);
send_event_breakpoint_status(c, bp);
}
/* Add breakpoints for this channel */
json_read_array(&c->inp, command_set_array_cb, c);
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, 0);
write_stream(&c->out, MARKER_EOM);
}
static void command_get_ids(char * token, Channel * c) {
LINK * l = breakpoints.next;
int cnt = 0;
json_test_char(&c->inp, MARKER_EOM);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, 0);
write_stream(&c->out, '[');
while (l != &breakpoints) {
BreakpointInfo * bp = link_all2bp(l);
l = l->next;
if (list_is_empty(&bp->link_clients)) continue;
assert(*bp->id);
if (cnt > 0) write_stream(&c->out, ',');
json_write_string(&c->out, bp->id);
cnt++;
}
write_stream(&c->out, ']');
write_stream(&c->out, 0);
write_stream(&c->out, MARKER_EOM);
}
static void command_get_properties(char * token, Channel * c) {
char id[256];
BreakpointInfo * bp = NULL;
int err = 0;
json_read_string(&c->inp, id, sizeof(id));
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
bp = find_breakpoint(id);
if (bp == NULL || list_is_empty(&bp->link_clients)) err = ERR_INV_CONTEXT;
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, err);
if (err) {
write_stringz(&c->out, "null");
}
else {
write_breakpoint_properties(&c->out, bp);
write_stream(&c->out, 0);
}
write_stream(&c->out, MARKER_EOM);
}
static void command_get_status(char * token, Channel * c) {
char id[256];
BreakpointInfo * bp = NULL;
int err = 0;
json_read_string(&c->inp, id, sizeof(id));
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
bp = find_breakpoint(id);
if (bp == NULL || list_is_empty(&bp->link_clients)) err = ERR_INV_CONTEXT;
assert(*bp->id);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, err);
if (err) {
write_stringz(&c->out, "null");
}
else {
write_breakpoint_status(&c->out, bp);
write_stream(&c->out, 0);
}
write_stream(&c->out, MARKER_EOM);
}
static void command_add(char * token, Channel * c) {
BreakpointAttribute * props = read_breakpoint_properties(&c->inp);
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
add_breakpoint(c, props);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, 0);
write_stream(&c->out, MARKER_EOM);
}
static void command_change(char * token, Channel * c) {
BreakpointAttribute * props = read_breakpoint_properties(&c->inp);
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
add_breakpoint(c, props);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, 0);
write_stream(&c->out, MARKER_EOM);
}
static void command_enable_array_cb(InputStream * inp, void * args) {
char id[256];
BreakpointInfo * bp;
json_read_string(inp, id, sizeof(id));
bp = find_breakpoint(id);
if (bp != NULL && !list_is_empty(&bp->link_clients) && !bp->enabled) {
bp->enabled = 1;
set_breakpoint_attribute(bp, BREAKPOINT_ENABLED, "true");
replant_breakpoint(bp);
send_event_context_changed(bp);
}
}
static void command_enable(char * token, Channel * c) {
json_read_array(&c->inp, command_enable_array_cb, NULL);
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, 0);
write_stream(&c->out, MARKER_EOM);
}
static void command_disable_array_cb(InputStream * inp, void * args) {
char id[256];
BreakpointInfo * bp;
json_read_string(inp, id, sizeof(id));
bp = find_breakpoint(id);
if (bp != NULL && !list_is_empty(&bp->link_clients) && bp->enabled) {
bp->enabled = 0;
set_breakpoint_attribute(bp, BREAKPOINT_ENABLED, "false");
replant_breakpoint(bp);
send_event_context_changed(bp);
}
}
static void command_disable(char * token, Channel * c) {
json_read_array(&c->inp, command_disable_array_cb, NULL);
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, 0);
write_stream(&c->out, MARKER_EOM);
}
static void command_remove_array_cb(InputStream * inp, void * args) {
char id[256];
BreakpointRef * br;
json_read_string(inp, id, sizeof(id));
br = find_breakpoint_ref(find_breakpoint(id), (Channel *)args);
if (br != NULL) remove_ref(br);
}
static void command_remove(char * token, Channel * c) {
json_read_array(&c->inp, command_remove_array_cb, c);
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, 0);
write_stream(&c->out, MARKER_EOM);
}
static void command_get_capabilities(char * token, Channel * c) {
char id[256];
Context * ctx;
OutputStream * out = &c->out;
int err = 0;
json_read_string(&c->inp, id, sizeof(id));
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
ctx = id2ctx(id);
if ((strlen(id)>0) && !ctx) err = ERR_INV_CONTEXT;
write_stringz(out, "R");
write_stringz(out, token);
write_errno(out, err);
if (err) {
write_stringz(&c->out, "null");
}
else {
write_stream(out, '{');
json_write_string(out, "ID");
write_stream(out, ':');
json_write_string(out, id);
write_stream(out, ',');
json_write_string(out, "BreakpointType");
write_stream(out, ':');
json_write_boolean(out, 1);
write_stream(out, ',');
json_write_string(out, "Location");
write_stream(out, ':');
json_write_boolean(out, 1);
write_stream(out, ',');
json_write_string(out, "FileLine");
write_stream(out, ':');
json_write_boolean(out, ENABLE_LineNumbers);
write_stream(out, ',');
json_write_string(out, "FileMapping");
write_stream(out, ':');
json_write_boolean(out, SERVICE_PathMap);
write_stream(out, ',');
json_write_string(out, "IgnoreCount");
write_stream(out, ':');
json_write_boolean(out, 1);
write_stream(out, ',');
json_write_string(out, "Condition");
write_stream(out, ':');
json_write_boolean(out, 1);
if (ctx != NULL) {
int md = CTX_BP_ACCESS_INSTRUCTION;
md |= context_get_supported_bp_access_types(ctx);
md &= ~CTX_BP_ACCESS_VIRTUAL;
write_stream(out, ',');
json_write_string(out, "AccessMode");
write_stream(out, ':');
json_write_long(out, md);
}
write_stream(out, ',');
json_write_string(out, "ContextIds");
write_stream(out, ':');
json_write_boolean(out, 1);
write_stream(out, ',');
json_write_string(out, "ContextNames");
write_stream(out, ':');
json_write_boolean(out, 1);
#if SERVICE_ContextQuery
write_stream(out, ',');
json_write_string(out, "ContextQuery");
write_stream(out, ':');
json_write_boolean(out, 1);
#endif
write_stream(out, ',');
json_write_string(out, "StopGroup");
write_stream(out, ':');
json_write_boolean(out, 1);
write_stream(out, ',');
json_write_string(out, "ClientData");
write_stream(out, ':');
json_write_boolean(out, 1);
write_stream(out, ',');
json_write_string(out, "Temporary");
write_stream(out, ':');
json_write_boolean(out, 1);
#if ENABLE_ContextBreakpointCapabilities
{
/* Back-end context breakpoint capabilities */
int cnt = 0;
const char ** names = NULL;
const char ** values = NULL;
if (context_get_breakpoint_capabilities(ctx, &names, &values, &cnt) == 0) {
while (cnt > 0) {
if (*values != NULL) {
write_stream(out, ',');
json_write_string(out, *names);
write_stream(out, ':');
write_string(out, *values);
}
names++;
values++;
cnt--;
}
}
}
#endif
write_stream(out, '}');
write_stream(out, 0);
}
write_stream(out, MARKER_EOM);
}
void add_breakpoint_event_listener(BreakpointsEventListener * listener, void * args) {
if (listener_cnt >= listener_max) {
listener_max += 8;
listeners = (Listener *)loc_realloc(listeners, listener_max * sizeof(Listener));
}
listeners[listener_cnt].listener = listener;
listeners[listener_cnt].args = args;
listener_cnt++;
}
void rem_breakpoint_event_listener(BreakpointsEventListener * listener) {
unsigned i = 0;
while (i < listener_cnt) {
if (listeners[i++].listener == listener) {
while (i < listener_cnt) {
listeners[i - 1] = listeners[i];
i++;
}
listener_cnt--;
break;
}
}
}
void iterate_breakpoints(IterateBreakpointsCallBack * callback, void * args) {
LINK * l = breakpoints.next;
while (l != &breakpoints) {
BreakpointInfo * bp = link_all2bp(l);
l = l->next;
callback(bp, args);
}
}
BreakpointAttribute * get_breakpoint_attributes(BreakpointInfo * bp) {
return bp->attrs;
}
BreakpointInfo * create_breakpoint(BreakpointAttribute * attrs) {
return add_breakpoint(NULL, attrs);
}
void change_breakpoint_attributes(BreakpointInfo * bp, BreakpointAttribute * attrs) {
int chng = set_breakpoint_attributes(bp, attrs);
assert(!list_is_empty(&bp->link_clients));
if (chng) {
bp->attrs_changed = 1;
replant_breakpoint(bp);
if (bp->id[0] != 0) send_event_context_changed(bp);
}
}
char * get_breakpoint_status(BreakpointInfo * bp) {
char * res = NULL;
ByteArrayOutputStream buf;
OutputStream * out = create_byte_array_output_stream(&buf);
write_breakpoint_status(out, bp);
write_stream(out, 0);
get_byte_array_output_stream_data(&buf, &res, NULL);
return res;
}
void delete_breakpoint(BreakpointInfo * bp) {
BreakpointRef * br = find_breakpoint_ref(bp, NULL);
assert(br != NULL && br->channel == NULL);
remove_ref(br);
}
void iterate_context_breakpoint_links(Context * ctx, ContextBreakpoint * cb, IterateCBLinksCallBack * callback, void * args) {
unsigned i;
Context * grp = context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT);
BreakInstruction * bi = (BreakInstruction *)((char *)cb - offsetof(BreakInstruction, cb));
for (i = 0; i < bi->ref_cnt; i++) {
if (bi->refs[i].ctx == grp) callback(bi->refs[i].bp, args);
}
}
int is_breakpoint_address(Context * ctx, ContextAddress address) {
Context * mem = NULL;
ContextAddress mem_addr = 0;
BreakInstruction * bi = NULL;
if (context_get_canonical_addr(ctx, address, &mem, &mem_addr, NULL, NULL) < 0) return 0;
bi = find_instruction(mem, 0, mem_addr, CTX_BP_ACCESS_INSTRUCTION, 1);
return bi != NULL && bi->planted;
}
void evaluate_breakpoint(Context * ctx) {
unsigned i;
Context * mem = NULL;
ContextAddress mem_addr = 0;
BreakInstruction * bi = NULL;
Context * grp = context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT);
EvaluationRequest * req = create_evaluation_request(grp);
int already_posted = !list_is_empty(&req->link_posted) || !list_is_empty(&req->link_active);
int need_to_post = already_posted;
assert(context_has_state(ctx));
assert(ctx->stopped);
assert(ctx->stopped_by_bp || ctx->stopped_by_cb);
assert(ctx->exited == 0);
assert(EXT(ctx)->bp_ids == NULL);
if (ctx->stopped_by_bp) {
if (context_get_canonical_addr(ctx, get_regs_PC(ctx), &mem, &mem_addr, NULL, NULL) < 0) return;
bi = find_instruction(mem, 0, mem_addr, CTX_BP_ACCESS_INSTRUCTION, 1);
if (bi != NULL && bi->planted) {
assert(bi->valid);
for (i = 0; i < bi->ref_cnt; i++) {
if (bi->refs[i].ctx == grp) {
BreakpointInfo * bp = bi->refs[i].bp;
ConditionEvaluationRequest * c = add_condition_evaluation_request(req, ctx, bp);
if (c == NULL) continue;
if (need_to_post) continue;
if (is_disabled(bp)) continue;
if (bp->condition != NULL || bp->stop_group != NULL || bp->temporary) {
need_to_post = 1;
continue;
}
if (!check_context_ids_condition(bp, ctx)) continue;
c->condition_ok = 1;
}
}
}
}
if (ctx->stopped_by_cb) {
int j;
assert(ctx->stopped_by_cb[0] != NULL);
for (j = 0; ctx->stopped_by_cb[j]; j++) {
bi = (BreakInstruction *)((char *)ctx->stopped_by_cb[j] - offsetof(BreakInstruction, cb));
assert(bi->planted);
for (i = 0; i < bi->ref_cnt; i++) {
if (bi->refs[i].ctx == grp) {
BreakpointInfo * bp = bi->refs[i].bp;
ConditionEvaluationRequest * c = add_condition_evaluation_request(req, ctx, bp);
if (c == NULL) continue;
if (need_to_post) continue;
if (is_disabled(bp)) continue;
if (bp->condition != NULL || bp->stop_group != NULL || bp->temporary) {
need_to_post = 1;
continue;
}
if (!check_context_ids_condition(bp, ctx)) continue;
c->condition_ok = 1;
}
}
}
}
if (need_to_post) {
if (!already_posted) post_evaluation_request(req);
}
else {
done_condition_evaluation(req);
for (i = 0; i < req->bp_cnt; i++) {
ConditionEvaluationRequest * c = req->bp_arr + i;
if (c->bp->status_changed && generation_done == generation_posted) {
assert(c->bp->client_cnt > 0);
notify_breakpoint_status(c->bp);
}
context_unlock(c->ctx);
}
req->bp_cnt = 0;
}
}
char ** get_context_breakpoint_ids(Context * ctx) {
return EXT(ctx)->bp_ids;
}
static void safe_skip_breakpoint(void * arg);
static void safe_restore_breakpoint(void * arg) {
Context * ctx = (Context *)arg;
ContextExtensionBP * ext = EXT(ctx);
BreakInstruction * bi = ext->stepping_over_bp;
assert(!bi->virtual_addr);
assert(bi->stepping_over_bp > 0);
assert(find_instruction(bi->cb.ctx, 0, bi->cb.address, bi->cb.access_types, bi->cb.length) == bi);
if (!ctx->exiting && ctx->stopped && !ctx->stopped_by_exception) {
Context * mem = NULL;
ContextAddress mem_addr = 0;
ContextAddress pc = get_regs_PC(ctx);
if (context_get_canonical_addr(ctx, pc, &mem, &mem_addr, NULL, NULL) == 0 &&
bi->cb.ctx == mem && bi->cb.address == mem_addr) {
if (ext->step_over_bp_cnt < 100) {
ext->step_over_bp_cnt++;
safe_skip_breakpoint(arg);
return;
}
trace(LOG_ALWAYS, "Skip breakpoint error: wrong PC %#lx", pc);
}
}
ext->stepping_over_bp = NULL;
ext->step_over_bp_cnt = 0;
bi->stepping_over_bp--;
if (bi->stepping_over_bp == 0 && bi->valid && bi->ref_cnt > 0 &&
!bi->cb.ctx->exited && !bi->cb.ctx->exiting && !bi->planted) {
plant_instruction(bi);
}
context_unlock(ctx);
}
static void safe_skip_breakpoint(void * arg) {
Context * ctx = (Context *)arg;
ContextExtensionBP * ext = EXT(ctx);
BreakInstruction * bi = ext->stepping_over_bp;
int error = 0;
#ifndef NDEBUG
Context * mem = NULL;
ContextAddress mem_addr = 0;
#endif
assert(bi != NULL);
assert(bi->stepping_over_bp > 0);
assert(find_instruction(bi->cb.ctx, 0, bi->cb.address, bi->cb.access_types, bi->cb.length) == bi);
post_safe_event(ctx, safe_restore_breakpoint, ctx);
if (ctx->exited || ctx->exiting) return;
assert(ctx->stopped);
assert(!is_intercepted(ctx));
assert(context_get_canonical_addr(ctx, get_regs_PC(ctx), &mem, &mem_addr, NULL, NULL) == 0);
assert(bi->cb.address == mem_addr);
if (bi->planted && remove_instruction(bi) < 0) error = errno;
if (error == 0 && safe_context_single_step(ctx) < 0) error = errno;
if (error) {
error = set_errno(error, "Cannot step over breakpoint");
ctx->signal = 0;
ctx->stopped = 1;
ctx->stopped_by_bp = 0;
ctx->stopped_by_cb = NULL;
ctx->stopped_by_exception = 1;
ctx->pending_intercept = 1;
loc_free(ctx->exception_description);
ctx->exception_description = loc_strdup(errno_to_str(error));
send_context_changed_event(ctx);
}
}
/*
* When a context is stopped by breakpoint, it is necessary to disable
* the breakpoint temporarily before the context can be resumed.
* This function removes break instruction, then does single step
* over breakpoint location, then restores break intruction.
* Return: 0 if it is OK to resume context from current state,
* return 1 if context needs to step over a breakpoint.
*/
int skip_breakpoint(Context * ctx, int single_step) {
ContextExtensionBP * ext = EXT(ctx);
Context * mem = NULL;
ContextAddress mem_addr = 0;
BreakInstruction * bi;
assert(ctx->stopped);
assert(!ctx->exited);
assert(single_step || ext->stepping_over_bp == NULL);
if (ext->stepping_over_bp != NULL) return 0;
if (!ctx->stopped_by_bp && ctx->stopped_by_cb == NULL) return 0;
if (ctx->exited || ctx->exiting) return 0;
if (context_get_canonical_addr(ctx, get_regs_PC(ctx), &mem, &mem_addr, NULL, NULL) < 0) return -1;
bi = find_instruction(mem, 0, mem_addr, CTX_BP_ACCESS_INSTRUCTION, 1);
if (bi == NULL || bi->planting_error) return 0;
bi->stepping_over_bp++;
ext->stepping_over_bp = bi;
ext->step_over_bp_cnt = 1;
assert(bi->stepping_over_bp > 0);
context_lock(ctx);
post_safe_event(ctx, safe_skip_breakpoint, ctx);
return 1;
}
int is_skipping_breakpoint(Context * ctx) {
ContextExtensionBP * ext = EXT(ctx);
return ext->stepping_over_bp != NULL;
}
BreakpointInfo * create_eventpoint_ext(BreakpointAttribute * attrs, Context * ctx, EventPointCallBack * callback, void * callback_args) {
BreakpointInfo * bp = (BreakpointInfo *)loc_alloc_zero(sizeof(BreakpointInfo));
bp->client_cnt = 1;
if (ctx != NULL) context_lock(bp->ctx = ctx);
bp->event_callback = callback;
bp->event_callback_args = callback_args;
list_init(&bp->link_clients);
list_init(&bp->link_hit_count);
list_add_last(&bp->link_all, &breakpoints);
set_breakpoint_attributes(bp, attrs);
replant_breakpoint(bp);
return bp;
}
BreakpointInfo * create_eventpoint(const char * location, Context * ctx, EventPointCallBack * callback, void * callback_args) {
static const char * attr_list[] = { BREAKPOINT_ENABLED, BREAKPOINT_LOCATION };
BreakpointAttribute * attrs = NULL;
BreakpointAttribute ** ref = &attrs;
unsigned i;
/* Create attributes to allow get_breakpoint_attributes() and change_breakpoint_attributes() calls */
for (i = 0; i < sizeof(attr_list) / sizeof(char *); i++) {
ByteArrayOutputStream buf;
BreakpointAttribute * attr = (BreakpointAttribute *)loc_alloc_zero(sizeof(BreakpointAttribute));
OutputStream * out = create_byte_array_output_stream(&buf);
attr->name = loc_strdup(attr_list[i]);
switch (i) {
case 0:
json_write_boolean(out, 1);
break;
case 1:
json_write_string(out, location);
break;
}
write_stream(out, 0);
get_byte_array_output_stream_data(&buf, &attr->value, NULL);
*ref = attr; ref = &attr->next;
}
return create_eventpoint_ext(attrs, ctx, callback, callback_args);
}
void destroy_eventpoint(BreakpointInfo * bp) {
assert(bp->id[0] == 0);
assert(bp->client_cnt == 1);
assert(list_is_empty(&bp->link_clients));
bp->client_cnt = 0;
replant_breakpoint(bp);
}
static void event_context_created(Context * ctx, void * args) {
post_location_evaluation_request(ctx, NULL);
list_init(&EXT(ctx)->link_hit_count);
}
static void event_context_changed(Context * ctx, void * args) {
if (ctx->mem_access && context_get_group(ctx, CONTEXT_GROUP_PROCESS) == ctx) {
/* If the context is a memory space, we need to update
* breakpoints on all members of the group */
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;
post_location_evaluation_request(x, NULL);
}
}
else {
post_location_evaluation_request(ctx, NULL);
}
}
static void event_context_started(Context * ctx, void * args) {
ContextExtensionBP * ext = EXT(ctx);
if (ext->bp_ids != NULL) {
loc_free(ext->bp_ids);
ext->bp_ids = NULL;
}
}
static void event_context_exited(Context * ctx, void * args) {
post_location_evaluation_request(ctx, NULL);
}
static void event_context_disposed(Context * ctx, void * args) {
LINK * l = NULL;
ContextExtensionBP * ext = EXT(ctx);
EvaluationRequest * req = ext->req;
if (req != NULL) {
assert(list_is_empty(&req->link_posted));
assert(list_is_empty(&req->link_active));
loc_free(req->bp_arr);
loc_free(req);
ext->req = NULL;
}
if (ext->bp_ids != NULL) {
loc_free(ext->bp_ids);
ext->bp_ids = NULL;
}
l = ext->link_hit_count.next;
if (l != NULL) { /* link_hit_count can be uninitialized */
while (l != &ext->link_hit_count) {
BreakpointHitCount * c = link_ctx2hcnt(l);
l = l->next;
list_remove(&c->link_bp);
list_remove(&c->link_ctx);
loc_free(c);
}
}
}
#if SERVICE_MemoryMap
static void event_code_unmapped(Context * ctx, ContextAddress addr, ContextAddress size, void * args) {
/* Unmapping a code section unplants all breakpoint instructions in that section as side effect.
* This function udates service data structure to reflect that.
*/
int cnt = 0;
while (size > 0) {
ContextAddress sz = size;
LINK * l = instructions.next;
Context * mem = NULL;
ContextAddress mem_addr = 0;
ContextAddress mem_base = 0;
ContextAddress mem_size = 0;
if (context_get_canonical_addr(ctx, addr, &mem, &mem_addr, &mem_base, &mem_size) < 0) break;
if (mem_base + mem_size - mem_addr < sz) sz = mem_base + mem_size - mem_addr;
while (l != &instructions) {
unsigned i;
BreakInstruction * bi = link_all2bi(l);
l = l->next;
if (bi->cb.ctx != mem) continue;
if (!bi->planted) continue;
if (bi->cb.address < mem_addr || bi->cb.address >= mem_addr + sz) continue;
for (i = 0; i < bi->ref_cnt; i++) {
bi->refs[i].bp->status_changed = 1;
cnt++;
}
bi->planted = 0;
}
addr += sz;
size -= sz;
}
if (cnt > 0 && generation_done == generation_posted) done_replanting_breakpoints();
}
#endif
#if SERVICE_PathMap
static void event_path_map_changed(Channel * c, void * args) {
unsigned hash = (unsigned)(uintptr_t)c / 16 % INP2BR_HASH_SIZE;
LINK * l = inp2br[hash].next;
while (l != &inp2br[hash]) {
BreakpointRef * br = link_inp2br(l);
l = l->next;
if (br->channel == c && br->bp->file != NULL) replant_breakpoint(br->bp);
}
}
#endif
static void channel_close_listener(Channel * c) {
delete_breakpoint_refs(c);
}
void ini_breakpoints_service(Protocol * proto, TCFBroadcastGroup * bcg) {
int i;
broadcast_group = bcg;
{
static ContextEventListener listener = {
event_context_created,
event_context_exited,
NULL,
event_context_started,
event_context_changed,
event_context_disposed
};
add_context_event_listener(&listener, NULL);
}
#if SERVICE_MemoryMap
{
static MemoryMapEventListener listener = {
event_context_changed,
event_code_unmapped,
event_context_changed,
event_context_changed,
};
add_memory_map_event_listener(&listener, NULL);
}
#endif
#if SERVICE_PathMap
{
static PathMapEventListener listener = {
event_path_map_changed,
};
add_path_map_event_listener(&listener, NULL);
}
#endif
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(proto, BREAKPOINTS, "set", command_set);
add_command_handler(proto, BREAKPOINTS, "add", command_add);
add_command_handler(proto, BREAKPOINTS, "change", command_change);
add_command_handler(proto, BREAKPOINTS, "enable", command_enable);
add_command_handler(proto, BREAKPOINTS, "disable", command_disable);
add_command_handler(proto, BREAKPOINTS, "remove", command_remove);
add_command_handler(proto, BREAKPOINTS, "getIDs", command_get_ids);
add_command_handler(proto, BREAKPOINTS, "getProperties", command_get_properties);
add_command_handler(proto, BREAKPOINTS, "getStatus", command_get_status);
add_command_handler(proto, BREAKPOINTS, "getCapabilities", command_get_capabilities);
context_extension_offset = context_extension(sizeof(ContextExtensionBP));
}
#endif /* SERVICE_Breakpoints */