/* * %CopyrightBegin% * * Copyright Ericsson AB 2000-2012. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * %CopyrightEnd% */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "sys.h" #include "erl_vm.h" #include "global.h" #include "erl_process.h" #include "beam_load.h" #include "bif.h" #include "error.h" #include "erl_binary.h" #include "beam_bp.h" #include "erl_term.h" /* ************************************************************************* ** Macros */ /* ** Memory allocation macros */ /* Breakpoint data */ #define Alloc(SZ) erts_alloc(ERTS_ALC_T_BPD, (SZ)) #define ReAlloc(P, SIZ) erts_realloc(ERTS_ALC_T_BPD, (P), (SZ)) #define Free(P) erts_free(ERTS_ALC_T_BPD, (P)) #if defined(ERTS_ENABLE_LOCK_CHECK) && defined(ERTS_SMP) # define ERTS_SMP_REQ_PROC_MAIN_LOCK(P) \ if ((P)) erts_proc_lc_require_lock((P), ERTS_PROC_LOCK_MAIN) # define ERTS_SMP_UNREQ_PROC_MAIN_LOCK(P) \ if ((P)) erts_proc_lc_unrequire_lock((P), ERTS_PROC_LOCK_MAIN) #else # define ERTS_SMP_REQ_PROC_MAIN_LOCK(P) # define ERTS_SMP_UNREQ_PROC_MAIN_LOCK(P) #endif #define BREAK_IS_BIF (1) #define BREAK_IS_ERL (0) #define ERTS_BPF_LOCAL_TRACE 0x01 #define ERTS_BPF_META_TRACE 0x02 #define ERTS_BPF_COUNT 0x04 #define ERTS_BPF_COUNT_ACTIVE 0x08 #define ERTS_BPF_DEBUG 0x10 #define ERTS_BPF_TIME_TRACE 0x20 #define ERTS_BPF_TIME_TRACE_ACTIVE 0x40 #define ERTS_BPF_ALL 0x7F extern Eterm beam_return_to_trace[1]; /* OpCode(i_return_to_trace) */ extern Eterm beam_return_trace[1]; /* OpCode(i_return_trace) */ extern Eterm beam_exception_trace[1]; /* OpCode(i_exception_trace) */ extern Eterm beam_return_time_trace[1]; /* OpCode(i_return_time_trace) */ /* ************************************************************************* ** Local prototypes */ /* ** Helpers */ static Eterm do_call_trace(Process* c_p, BeamInstr* I, Eterm* reg, Binary* ms, Eterm tracer_pid); static int set_break(Eterm mfa[3], int specified, Binary *match_spec, Uint break_flags, enum erts_break_op count_op, Eterm tracer_pid); static int set_module_break(Module *modp, Eterm mfa[3], int specified, Binary *match_spec, Uint break_flags, enum erts_break_op count_op, Eterm tracer_pid); static int set_function_break(Module *modp, BeamInstr *pc, int bif, Binary *match_spec, Uint break_flags, enum erts_break_op count_op, Eterm tracer_pid); static int clear_break(Eterm mfa[3], int specified, Uint break_flags); static int clear_module_break(Module *modp, Eterm mfa[3], int specified, Uint break_flags); static int clear_function_break(Module *modp, BeamInstr *pc, int bif, Uint break_flags); static BpDataTime* get_time_break(BeamInstr *pc); static GenericBpData* check_break(BeamInstr *pc, Uint break_flags); static void bp_time_diff(bp_data_time_item_t *item, process_breakpoint_time_t *pbt, Uint ms, Uint s, Uint us); static void bp_count_unref(BpCount* bcp); static void bp_time_unref(BpDataTime* bdt); /* bp_hash */ #define BP_TIME_ADD(pi0, pi1) \ do { \ Uint r; \ (pi0)->count += (pi1)->count; \ (pi0)->s_time += (pi1)->s_time; \ (pi0)->us_time += (pi1)->us_time; \ r = (pi0)->us_time / 1000000; \ (pi0)->s_time += r; \ (pi0)->us_time = (pi0)->us_time % 1000000; \ } while(0) static void bp_hash_init(bp_time_hash_t *hash, Uint n); static void bp_hash_rehash(bp_time_hash_t *hash, Uint n); static ERTS_INLINE bp_data_time_item_t * bp_hash_get(bp_time_hash_t *hash, bp_data_time_item_t *sitem); static ERTS_INLINE bp_data_time_item_t * bp_hash_put(bp_time_hash_t *hash, bp_data_time_item_t *sitem); static void bp_hash_delete(bp_time_hash_t *hash); /* ************************************************************************* ** External interfaces */ void erts_bp_init(void) { } int erts_set_trace_break(Eterm mfa[3], int specified, Binary *match_spec) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return set_break(mfa, specified, match_spec, ERTS_BPF_LOCAL_TRACE, 0, am_true); } int erts_set_mtrace_break(Eterm mfa[3], int specified, Binary *match_spec, Eterm tracer_pid) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return set_break(mfa, specified, match_spec, ERTS_BPF_META_TRACE, 0, tracer_pid); } /* set breakpoint data for on exported bif entry */ void erts_set_mtrace_bif(BeamInstr *pc, Binary *match_spec, Eterm tracer_pid) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); set_function_break(NULL, pc, BREAK_IS_BIF, match_spec, ERTS_BPF_META_TRACE, 0, tracer_pid); } void erts_set_time_trace_bif(BeamInstr *pc, enum erts_break_op count_op) { set_function_break(NULL, pc, BREAK_IS_BIF, NULL, ERTS_BPF_TIME_TRACE|ERTS_BPF_TIME_TRACE_ACTIVE, count_op, NIL); } void erts_clear_time_trace_bif(BeamInstr *pc) { clear_function_break(NULL, pc, BREAK_IS_BIF, ERTS_BPF_TIME_TRACE|ERTS_BPF_TIME_TRACE_ACTIVE); } int erts_set_debug_break(Eterm mfa[3], int specified) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return set_break(mfa, specified, NULL, ERTS_BPF_DEBUG, 0, NIL); } int erts_set_count_break(Eterm mfa[3], int specified, enum erts_break_op count_op) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return set_break(mfa, specified, NULL, ERTS_BPF_COUNT|ERTS_BPF_COUNT_ACTIVE, count_op, NIL); } int erts_set_time_break(Eterm mfa[3], int specified, enum erts_break_op count_op) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return set_break(mfa, specified, NULL, ERTS_BPF_TIME_TRACE|ERTS_BPF_TIME_TRACE_ACTIVE, count_op, NIL); } int erts_clear_trace_break(Eterm mfa[3], int specified) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return clear_break(mfa, specified, ERTS_BPF_LOCAL_TRACE); } int erts_clear_mtrace_break(Eterm mfa[3], int specified) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return clear_break(mfa, specified, ERTS_BPF_META_TRACE); } void erts_clear_mtrace_bif(BeamInstr *pc) { clear_function_break(NULL, pc, BREAK_IS_BIF, ERTS_BPF_META_TRACE); } int erts_clear_debug_break(Eterm mfa[3], int specified) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return clear_break(mfa, specified, ERTS_BPF_DEBUG); } int erts_clear_count_break(Eterm mfa[3], int specified) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return clear_break(mfa, specified, ERTS_BPF_COUNT|ERTS_BPF_COUNT_ACTIVE); } int erts_clear_time_break(Eterm mfa[3], int specified) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return clear_break(mfa, specified, ERTS_BPF_TIME_TRACE|ERTS_BPF_TIME_TRACE_ACTIVE); } int erts_clear_break(Eterm mfa[3], int specified) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); return clear_break(mfa, specified, ERTS_BPF_ALL); } int erts_clear_module_break(Module *modp) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); ASSERT(modp); return clear_module_break(modp, NULL, 0, ERTS_BPF_ALL); } int erts_clear_function_break(Module *modp, BeamInstr *pc) { ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking()); ASSERT(modp); return clear_function_break(modp, pc, BREAK_IS_ERL, ERTS_BPF_ALL); } BeamInstr erts_generic_breakpoint(Process* c_p, BeamInstr* I, Eterm* reg) { GenericBp* g; GenericBpData* bp; Uint bp_flags; g = (GenericBp *) I[-4]; bp = &g->data[0]; bp_flags = bp->flags; ASSERT((bp_flags & ~ERTS_BPF_ALL) == 0); if (bp_flags & (ERTS_BPF_LOCAL_TRACE|ERTS_BPF_TIME_TRACE_ACTIVE) && !IS_TRACED_FL(c_p, F_TRACE_CALLS)) { bp_flags &= ~(ERTS_BPF_LOCAL_TRACE| ERTS_BPF_TIME_TRACE| ERTS_BPF_TIME_TRACE_ACTIVE); if (bp_flags == 0) { /* Quick exit */ return g->orig_instr; } } if (bp_flags & ERTS_BPF_LOCAL_TRACE) { (void) do_call_trace(c_p, I, reg, bp->local_ms, am_true); } if (bp_flags & ERTS_BPF_META_TRACE) { Eterm pid; pid = (Eterm) erts_smp_atomic_read_nob(&bp->tracer_pid); pid = do_call_trace(c_p, I, reg, bp->meta_ms, pid); erts_smp_atomic_set_nob(&bp->tracer_pid, pid); } if (bp_flags & ERTS_BPF_COUNT_ACTIVE) { erts_smp_atomic_inc_nob(&bp->count->acount); } if (bp_flags & ERTS_BPF_TIME_TRACE_ACTIVE) { Eterm w; erts_trace_time_call(c_p, I, bp->time); w = (BeamInstr) *c_p->cp; if (! (w == (BeamInstr) BeamOp(op_i_return_time_trace) || w == (BeamInstr) BeamOp(op_return_trace) || w == (BeamInstr) BeamOp(op_i_return_to_trace)) ) { Eterm* E = c_p->stop; ASSERT(c_p->htop <= E && E <= c_p->hend); if (E - 2 < c_p->htop) { (void) erts_garbage_collect(c_p, 2, reg, I[-1]); ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p); } E = c_p->stop; ASSERT(c_p->htop <= E && E <= c_p->hend); E -= 2; E[0] = make_cp(I); E[1] = make_cp(c_p->cp); /* original return address */ c_p->cp = beam_return_time_trace; c_p->stop = E; } } if (bp_flags & ERTS_BPF_DEBUG) { return (BeamInstr) BeamOp(op_i_debug_breakpoint); } else { return g->orig_instr; } } static Eterm do_call_trace(Process* c_p, BeamInstr* I, Eterm* reg, Binary* ms, Eterm tracer_pid) { Eterm* cpp; int return_to_trace = 0; BeamInstr w; BeamInstr *cp_save; Uint32 flags; Uint need = 0; Eterm* E = c_p->stop; w = *c_p->cp; if (w == (BeamInstr) BeamOp(op_return_trace)) { cpp = &E[2]; } else if (w == (BeamInstr) BeamOp(op_i_return_to_trace)) { return_to_trace = 1; cpp = &E[0]; } else if (w == (BeamInstr) BeamOp(op_i_return_time_trace)) { cpp = &E[0]; } else { cpp = NULL; } if (cpp) { for (;;) { BeamInstr w = *cp_val(*cpp); if (w == (BeamInstr) BeamOp(op_return_trace)) { cpp += 3; } else if (w == (BeamInstr) BeamOp(op_i_return_to_trace)) { return_to_trace = 1; cpp += 1; } else if (w == (BeamInstr) BeamOp(op_i_return_time_trace)) { cpp += 2; } else { break; } } cp_save = c_p->cp; c_p->cp = (BeamInstr *) cp_val(*cpp); ASSERT(is_CP(*cpp)); } ERTS_SMP_UNREQ_PROC_MAIN_LOCK(c_p); flags = erts_call_trace(c_p, I-3, ms, reg, 1, &tracer_pid); ERTS_SMP_REQ_PROC_MAIN_LOCK(c_p); if (cpp) { c_p->cp = cp_save; } ASSERT(!ERTS_PROC_IS_EXITING(c_p)); if ((flags & MATCH_SET_RETURN_TO_TRACE) && !return_to_trace) { need += 1; } if (flags & MATCH_SET_RX_TRACE) { need += 3; } if (need) { ASSERT(c_p->htop <= E && E <= c_p->hend); if (E - need < c_p->htop) { (void) erts_garbage_collect(c_p, need, reg, I[-1]); ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p); E = c_p->stop; } } if (flags & MATCH_SET_RETURN_TO_TRACE && !return_to_trace) { E -= 1; ASSERT(c_p->htop <= E && E <= c_p->hend); E[0] = make_cp(c_p->cp); c_p->cp = (BeamInstr *) beam_return_to_trace; } if (flags & MATCH_SET_RX_TRACE) { E -= 3; ASSERT(c_p->htop <= E && E <= c_p->hend); ASSERT(is_CP((Eterm) (UWord) (I - 3))); ASSERT(am_true == tracer_pid || is_internal_pid(tracer_pid) || is_internal_port(tracer_pid)); E[2] = make_cp(c_p->cp); E[1] = tracer_pid; E[0] = make_cp(I - 3); /* We ARE at the beginning of an instruction, the funcinfo is above i. */ c_p->cp = (flags & MATCH_SET_EXCEPTION_TRACE) ? beam_exception_trace : beam_return_trace; erts_smp_proc_lock(c_p, ERTS_PROC_LOCKS_ALL_MINOR); c_p->trace_flags |= F_EXCEPTION_TRACE; erts_smp_proc_unlock(c_p, ERTS_PROC_LOCKS_ALL_MINOR); } c_p->stop = E; return tracer_pid; } void erts_trace_time_call(Process* c_p, BeamInstr* I, BpDataTime* bdt) { Uint ms,s,us; process_breakpoint_time_t *pbt = NULL; bp_data_time_item_t sitem, *item = NULL; bp_time_hash_t *h = NULL; BpDataTime *pbdt = NULL; ASSERT(c_p); ASSERT(erts_smp_atomic32_read_acqb(&c_p->state) & ERTS_PSFLG_RUNNING); /* get previous timestamp and breakpoint * from the process psd */ pbt = ERTS_PROC_GET_CALL_TIME(c_p); get_sys_now(&ms, &s, &us); /* get pbt * timestamp = t0 * lookup bdt from code * set ts0 to pbt * add call count here? */ if (pbt == 0) { /* First call of process to instrumented function */ pbt = Alloc(sizeof(process_breakpoint_time_t)); (void *) ERTS_PROC_SET_CALL_TIME(c_p, ERTS_PROC_LOCK_MAIN, pbt); } else { ASSERT(pbt->pc); /* add time to previous code */ bp_time_diff(&sitem, pbt, ms, s, us); sitem.pid = c_p->id; sitem.count = 0; /* previous breakpoint */ pbdt = get_time_break(pbt->pc); /* if null then the breakpoint was removed */ if (pbdt) { h = &(pbdt->hash[bp_sched2ix_proc(c_p)]); ASSERT(h); ASSERT(h->item); item = bp_hash_get(h, &sitem); if (!item) { item = bp_hash_put(h, &sitem); } else { BP_TIME_ADD(item, &sitem); } } } /* Add count to this code */ sitem.pid = c_p->id; sitem.count = 1; sitem.s_time = 0; sitem.us_time = 0; /* this breakpoint */ ASSERT(bdt); h = &(bdt->hash[bp_sched2ix_proc(c_p)]); ASSERT(h); ASSERT(h->item); item = bp_hash_get(h, &sitem); if (!item) { item = bp_hash_put(h, &sitem); } else { BP_TIME_ADD(item, &sitem); } pbt->pc = I; pbt->ms = ms; pbt->s = s; pbt->us = us; } void erts_trace_time_return(Process *p, BeamInstr *pc) { Uint ms,s,us; process_breakpoint_time_t *pbt = NULL; bp_data_time_item_t sitem, *item = NULL; bp_time_hash_t *h = NULL; BpDataTime *pbdt = NULL; ASSERT(p); ASSERT(erts_smp_atomic32_read_acqb(&p->state) & ERTS_PSFLG_RUNNING); /* get previous timestamp and breakpoint * from the process psd */ pbt = ERTS_PROC_GET_CALL_TIME(p); get_sys_now(&ms,&s,&us); /* get pbt * lookup bdt from code * timestamp = t1 * get ts0 from pbt * get item from bdt->hash[bp_hash(p->id)] * ack diff (t1, t0) to item */ if (pbt) { /* might have been removed due to * trace_pattern(false) */ ASSERT(pbt->pc); bp_time_diff(&sitem, pbt, ms, s, us); sitem.pid = p->id; sitem.count = 0; /* previous breakpoint */ pbdt = get_time_break(pbt->pc); /* beware, the trace_pattern might have been removed */ if (pbdt) { h = &(pbdt->hash[bp_sched2ix_proc(p)]); ASSERT(h); ASSERT(h->item); item = bp_hash_get(h, &sitem); if (!item) { item = bp_hash_put(h, &sitem); } else { BP_TIME_ADD(item, &sitem); } } pbt->pc = pc; pbt->ms = ms; pbt->s = s; pbt->us = us; } } /* * SMP NOTE: Process p may have become exiting on return! */ Uint32 erts_bif_mtrace(Process *p, BeamInstr *pc, Eterm *args, int local, Eterm *tracer_pid) { GenericBp* g; ASSERT(tracer_pid); g = (GenericBp *) pc[-4]; if (g) { Eterm tpid1, tpid2; Uint32 flags; GenericBpData* bp; bp = &g->data[0]; tpid1 = tpid2 =(Eterm) erts_smp_atomic_read_nob(&bp->tracer_pid); flags = erts_call_trace(p, pc-3/*mfa*/, bp->meta_ms, args, local, &tpid2); *tracer_pid = tpid2; if (tpid1 != tpid2) { erts_smp_atomic_set_nob(&bp->tracer_pid, tpid2); } return flags; } *tracer_pid = NIL; return 0; } int erts_is_trace_break(BeamInstr *pc, Binary **match_spec_ret) { GenericBpData* bp = check_break(pc, ERTS_BPF_LOCAL_TRACE); if (bp) { if (match_spec_ret) { *match_spec_ret = bp->local_ms; } return 1; } return 0; } int erts_is_mtrace_break(BeamInstr *pc, Binary **match_spec_ret, Eterm *tracer_pid_ret) { GenericBpData* bp = check_break(pc, ERTS_BPF_META_TRACE); if (bp) { if (match_spec_ret) { *match_spec_ret = bp->meta_ms; } if (tracer_pid_ret) { *tracer_pid_ret = (Eterm) erts_smp_atomic_read_nob(&bp->tracer_pid); } return 1; } return 0; } int erts_is_native_break(BeamInstr *pc) { #ifdef HIPE ASSERT(pc[-5] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); return pc[0] == (BeamInstr) BeamOp(op_hipe_trap_call) || pc[0] == (BeamInstr) BeamOp(op_hipe_trap_call_closure); #else return 0; #endif } int erts_is_count_break(BeamInstr *pc, Uint *count_ret) { GenericBpData* bp = check_break(pc, ERTS_BPF_COUNT); if (bp) { if (count_ret) { *count_ret = (Uint) erts_smp_atomic_read_nob(&bp->count->acount); } return 1; } return 0; } int erts_is_time_break(Process *p, BeamInstr *pc, Eterm *retval) { Uint i, ix; bp_time_hash_t hash; Uint size; Eterm *hp, t; bp_data_time_item_t *item = NULL; BpDataTime *bdt = get_time_break(pc); if (bdt) { if (retval) { /* collect all hashes to one hash */ bp_hash_init(&hash, 64); /* foreach threadspecific hash */ for (i = 0; i < bdt->n; i++) { bp_data_time_item_t *sitem; /* foreach hash bucket not NIL*/ for(ix = 0; ix < bdt->hash[i].n; ix++) { item = &(bdt->hash[i].item[ix]); if (item->pid != NIL) { sitem = bp_hash_get(&hash, item); if (sitem) { BP_TIME_ADD(sitem, item); } else { bp_hash_put(&hash, item); } } } } /* *retval should be NIL or term from previous bif in export entry */ if (hash.used > 0) { size = (5 + 2)*hash.used; hp = HAlloc(p, size); for(ix = 0; ix < hash.n; ix++) { item = &(hash.item[ix]); if (item->pid != NIL) { t = TUPLE4(hp, item->pid, make_small(item->count), make_small(item->s_time), make_small(item->us_time)); hp += 5; *retval = CONS(hp, t, *retval); hp += 2; } } } bp_hash_delete(&hash); } return 1; } return 0; } BeamInstr * erts_find_local_func(Eterm mfa[3]) { Module *modp; BeamInstr** code_base; BeamInstr* code_ptr; Uint i,n; if ((modp = erts_get_module(mfa[0], erts_active_code_ix())) == NULL) return NULL; if ((code_base = (BeamInstr **) modp->curr.code) == NULL) return NULL; n = (BeamInstr) code_base[MI_NUM_FUNCTIONS]; for (i = 0; i < n; ++i) { code_ptr = code_base[MI_FUNCTIONS+i]; ASSERT(((BeamInstr) BeamOp(op_i_func_info_IaaI)) == code_ptr[0]); ASSERT(mfa[0] == ((Eterm) code_ptr[2]) || is_nil((Eterm) code_ptr[2])); if (mfa[1] == ((Eterm) code_ptr[3]) && ((BeamInstr) mfa[2]) == code_ptr[4]) { return code_ptr + 5; } } return NULL; } static void bp_hash_init(bp_time_hash_t *hash, Uint n) { Uint size = sizeof(bp_data_time_item_t)*n; Uint i; hash->n = n; hash->used = 0; hash->item = (bp_data_time_item_t *)Alloc(size); sys_memzero(hash->item, size); for(i = 0; i < n; ++i) { hash->item[i].pid = NIL; } } static void bp_hash_rehash(bp_time_hash_t *hash, Uint n) { bp_data_time_item_t *item = NULL; Uint size = sizeof(bp_data_time_item_t)*n; Uint ix; Uint hval; item = (bp_data_time_item_t *)Alloc(size); sys_memzero(item, size); for( ix = 0; ix < n; ++ix) { item[ix].pid = NIL; } /* rehash, old hash -> new hash */ for( ix = 0; ix < hash->n; ix++) { if (hash->item[ix].pid != NIL) { hval = ((hash->item[ix].pid) >> 4) % n; /* new n */ while (item[hval].pid != NIL) { hval = (hval + 1) % n; } item[hval].pid = hash->item[ix].pid; item[hval].count = hash->item[ix].count; item[hval].s_time = hash->item[ix].s_time; item[hval].us_time = hash->item[ix].us_time; } } Free(hash->item); hash->n = n; hash->item = item; } static ERTS_INLINE bp_data_time_item_t * bp_hash_get(bp_time_hash_t *hash, bp_data_time_item_t *sitem) { Eterm pid = sitem->pid; Uint hval = (pid >> 4) % hash->n; bp_data_time_item_t *item = NULL; item = hash->item; while (item[hval].pid != pid) { if (item[hval].pid == NIL) return NULL; hval = (hval + 1) % hash->n; } return &(item[hval]); } static ERTS_INLINE bp_data_time_item_t * bp_hash_put(bp_time_hash_t *hash, bp_data_time_item_t* sitem) { Uint hval; float r = 0.0; bp_data_time_item_t *item; /* make sure that the hash is not saturated */ /* if saturated, rehash it */ r = hash->used / (float) hash->n; if (r > 0.7f) { bp_hash_rehash(hash, hash->n * 2); } /* Do hval after rehash */ hval = (sitem->pid >> 4) % hash->n; /* find free slot */ item = hash->item; while (item[hval].pid != NIL) { hval = (hval + 1) % hash->n; } item = &(hash->item[hval]); item->pid = sitem->pid; item->s_time = sitem->s_time; item->us_time = sitem->us_time; item->count = sitem->count; hash->used++; return item; } static void bp_hash_delete(bp_time_hash_t *hash) { hash->n = 0; hash->used = 0; Free(hash->item); hash->item = NULL; } static void bp_time_diff(bp_data_time_item_t *item, /* out */ process_breakpoint_time_t *pbt, /* in */ Uint ms, Uint s, Uint us) { int ds,dus; #ifdef DEBUG int dms; dms = ms - pbt->ms; #endif ds = s - pbt->s; dus = us - pbt->us; /* get_sys_now may return zero difftime, * this is ok. */ #ifdef DEBUG ASSERT(dms >= 0 || ds >= 0 || dus >= 0); #endif if (dus < 0) { dus += 1000000; ds -= 1; } if (ds < 0) { ds += 1000000; } item->s_time = ds; item->us_time = dus; } void erts_schedule_time_break(Process *p, Uint schedule) { Uint ms, s, us; process_breakpoint_time_t *pbt = NULL; bp_data_time_item_t sitem, *item = NULL; bp_time_hash_t *h = NULL; BpDataTime *pbdt = NULL; ASSERT(p); pbt = ERTS_PROC_GET_CALL_TIME(p); if (pbt) { switch(schedule) { case ERTS_BP_CALL_TIME_SCHEDULE_EXITING : break; case ERTS_BP_CALL_TIME_SCHEDULE_OUT : /* When a process is scheduled _out_, * timestamp it and add its delta to * the previous breakpoint. */ pbdt = get_time_break(pbt->pc); if (pbdt) { get_sys_now(&ms,&s,&us); bp_time_diff(&sitem, pbt, ms, s, us); sitem.pid = p->id; sitem.count = 0; h = &(pbdt->hash[bp_sched2ix_proc(p)]); ASSERT(h); ASSERT(h->item); item = bp_hash_get(h, &sitem); if (!item) { item = bp_hash_put(h, &sitem); } else { BP_TIME_ADD(item, &sitem); } } break; case ERTS_BP_CALL_TIME_SCHEDULE_IN : /* When a process is scheduled _in_, * timestamp it and remove the previous * timestamp in the psd. */ get_sys_now(&ms,&s,&us); pbt->ms = ms; pbt->s = s; pbt->us = us; break; default : ASSERT(0); /* will never happen */ break; } } /* pbt */ } /* ************************************************************************* ** Local helpers */ static int set_break(Eterm mfa[3], int specified, Binary *match_spec, Uint break_flags, enum erts_break_op count_op, Eterm tracer_pid) { Module *modp; int num_processed = 0; ErtsCodeIndex code_ix = erts_active_code_ix(); if (!specified) { /* Find and process all modules in the system... */ int current; int last = module_code_size(code_ix); for (current = 0; current < last; current++) { modp = module_code(current, code_ix); ASSERT(modp != NULL); num_processed += set_module_break(modp, mfa, specified, match_spec, break_flags, count_op, tracer_pid); } } else { /* Process a single module */ if ((modp = erts_get_module(mfa[0], code_ix)) != NULL) { num_processed += set_module_break(modp, mfa, specified, match_spec, break_flags, count_op, tracer_pid); } } return num_processed; } static int set_module_break(Module *modp, Eterm mfa[3], int specified, Binary *match_spec, Uint break_flags, enum erts_break_op count_op, Eterm tracer_pid) { BeamInstr** code_base; BeamInstr* code_ptr; int num_processed = 0; Uint i,n; ASSERT(break_flags); ASSERT(modp); code_base = (BeamInstr **) modp->curr.code; if (code_base == NULL) { return 0; } n = (BeamInstr) code_base[MI_NUM_FUNCTIONS]; for (i = 0; i < n; ++i) { code_ptr = code_base[MI_FUNCTIONS+i]; ASSERT(code_ptr[0] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); if ((specified < 2 || mfa[1] == ((Eterm) code_ptr[3])) && (specified < 3 || ((int) mfa[2]) == ((int) code_ptr[4]))) { BeamInstr *pc = code_ptr+5; num_processed += set_function_break(modp, pc, BREAK_IS_ERL, match_spec, break_flags, count_op, tracer_pid); } } return num_processed; } static int set_function_break(Module *modp, BeamInstr *pc, int bif, Binary *match_spec, Uint break_flags, enum erts_break_op count_op, Eterm tracer_pid) { GenericBp* g; GenericBpData* bp; Uint common; BeamInstr **code_base = NULL; if (bif == BREAK_IS_ERL) { code_base = (BeamInstr **)modp->curr.code; ASSERT(code_base); ASSERT(code_base <= (BeamInstr **)pc); ASSERT((BeamInstr **)pc < code_base + (modp->curr.code_length/sizeof(BeamInstr *))); } else { ASSERT(*pc == (BeamInstr) em_apply_bif); ASSERT(modp == NULL); } /* * Currently no trace support for native code. */ if (erts_is_native_break(pc)) { return 0; } /* * Initialize the breakpoint data for this breakpoint (if needed). */ g = (GenericBp *) pc[-4]; if (g == 0) { if (count_op == erts_break_reset || count_op == erts_break_stop) { /* Do not insert a new breakpoint */ return 1; } g = Alloc(sizeof(GenericBp)); g->data[0].flags = 0; erts_smp_atomic_init_nob(&g->data[0].tracer_pid, 0); pc[-4] = (BeamInstr) g; } bp = &g->data[0]; /* * If we are changing an existing breakpoint, clean up old data. */ common = break_flags & bp->flags; if (common & ERTS_BPF_LOCAL_TRACE) { MatchSetUnref(bp->local_ms); } else if (common & ERTS_BPF_META_TRACE) { MatchSetUnref(bp->meta_ms); } else if (common & ERTS_BPF_COUNT) { if (count_op == erts_break_stop) { bp->flags &= ~ERTS_BPF_COUNT_ACTIVE; } else { bp->flags |= ERTS_BPF_COUNT_ACTIVE; erts_smp_atomic_set_nob(&bp->count->acount, 0); } ASSERT((bp->flags & ~ERTS_BPF_ALL) == 0); return 1; } else if (common & ERTS_BPF_TIME_TRACE) { BpDataTime* bdt = bp->time; Uint i = 0; if (count_op == erts_break_stop) { bp->flags &= ~ERTS_BPF_TIME_TRACE_ACTIVE; } else { bp->flags |= ERTS_BPF_TIME_TRACE_ACTIVE; for (i = 0; i < bdt->n; i++) { bp_hash_delete(&(bdt->hash[i])); bp_hash_init(&(bdt->hash[i]), 32); } } ASSERT((bp->flags & ~ERTS_BPF_ALL) == 0); return 1; } /* * Initialize the new breakpoint data. */ if (break_flags & ERTS_BPF_LOCAL_TRACE) { MatchSetRef(match_spec); bp->local_ms = match_spec; } else if (break_flags & ERTS_BPF_META_TRACE) { MatchSetRef(match_spec); bp->meta_ms = match_spec; erts_smp_atomic_set_nob(&bp->tracer_pid, tracer_pid); } else if (break_flags & ERTS_BPF_COUNT) { BpCount* bcp; ASSERT((bp->flags & ERTS_BPF_COUNT) == 0); bcp = Alloc(sizeof(BpCount)); erts_refc_init(&bcp->refc, 1); erts_smp_atomic_init_nob(&bcp->acount, 0); bp->count = bcp; } else if (break_flags & ERTS_BPF_TIME_TRACE) { BpDataTime* bdt; int i; ASSERT((bp->flags & ERTS_BPF_TIME_TRACE) == 0); bdt = Alloc(sizeof(BpDataTime)); erts_refc_init(&bdt->refc, 1); bdt->n = erts_no_schedulers; bdt->hash = Alloc(sizeof(bp_time_hash_t)*(bdt->n)); for (i = 0; i < bdt->n; i++) { bp_hash_init(&(bdt->hash[i]), 32); } bp->time = bdt; } bp->flags |= break_flags; if (bif == BREAK_IS_ERL && *pc != (BeamInstr) BeamOp(op_i_generic_breakpoint)) { g->orig_instr = *pc; *pc = (BeamInstr) BeamOp(op_i_generic_breakpoint); modp->curr.num_breakpoints++; } ASSERT((bp->flags & ~ERTS_BPF_ALL) == 0); return 1; } static int clear_break(Eterm mfa[3], int specified, Uint break_flags) { ErtsCodeIndex code_ix = erts_active_code_ix(); int num_processed = 0; Module *modp; if (!specified) { /* Iterate over all modules */ int current; int last = module_code_size(code_ix); for (current = 0; current < last; current++) { modp = module_code(current, code_ix); ASSERT(modp != NULL); num_processed += clear_module_break(modp, mfa, specified, break_flags); } } else { /* Process a single module */ if ((modp = erts_get_module(mfa[0], code_ix)) != NULL) { num_processed += clear_module_break(modp, mfa, specified, break_flags); } } return num_processed; } static int clear_module_break(Module *m, Eterm mfa[3], int specified, Uint break_flags) { BeamInstr** code_base; BeamInstr* code_ptr; int num_processed = 0; Uint i; BeamInstr n; ASSERT(m); code_base = (BeamInstr **) m->curr.code; if (code_base == NULL) { return 0; } n = (BeamInstr) code_base[MI_NUM_FUNCTIONS]; for (i = 0; i < n; ++i) { code_ptr = code_base[MI_FUNCTIONS+i]; if ((specified < 2 || mfa[1] == ((Eterm) code_ptr[3])) && (specified < 3 || ((int) mfa[2]) == ((int) code_ptr[4]))) { BeamInstr *pc = code_ptr + 5; num_processed += clear_function_break(m, pc, BREAK_IS_ERL, break_flags); } } return num_processed; } static int clear_function_break(Module *m, BeamInstr *pc, int bif, Uint break_flags) { BeamInstr **code_base = NULL; GenericBp* g; GenericBpData* bp; Uint common; if (bif == BREAK_IS_ERL) { code_base = (BeamInstr **)m->curr.code; ASSERT(code_base); ASSERT(code_base <= (BeamInstr **)pc); ASSERT((BeamInstr **)pc < code_base + (m->curr.code_length/sizeof(BeamInstr *))); } else { ASSERT(*pc == (BeamInstr) em_apply_bif); ASSERT(m == NULL); } if (erts_is_native_break(pc)) { return 0; } if ((g = (GenericBp *) pc[-4]) == 0) { return 1; } ASSERT(bif == BREAK_IS_BIF || *pc == (BeamInstr) BeamOp(op_i_generic_breakpoint)); bp = &g->data[0]; ASSERT((bp->flags & ~ERTS_BPF_ALL) == 0); common = bp->flags & break_flags; bp->flags &= ~break_flags; if (common & ERTS_BPF_LOCAL_TRACE) { MatchSetUnref(bp->local_ms); } if (common & ERTS_BPF_META_TRACE) { MatchSetUnref(bp->meta_ms); } if (common & ERTS_BPF_COUNT) { ASSERT((bp->flags & ERTS_BPF_COUNT_ACTIVE) == 0); bp_count_unref(bp->count); } if (common & ERTS_BPF_TIME_TRACE) { ASSERT((bp->flags & ERTS_BPF_TIME_TRACE_ACTIVE) == 0); bp_time_unref(bp->time); } ASSERT((bp->flags & ~ERTS_BPF_ALL) == 0); if (bp->flags == 0) { pc[-4] = 0; if (bif == BREAK_IS_ERL) { *pc = g->orig_instr; ASSERT(m->curr.num_breakpoints > 0); m->curr.num_breakpoints--; } Free(g); } return 1; } static void bp_count_unref(BpCount* bcp) { if (erts_refc_dectest(&bcp->refc, 0) <= 0) { Free(bcp); } } static void bp_time_unref(BpDataTime* bdt) { if (erts_refc_dectest(&bdt->refc, 0) <= 0) { Uint i = 0; Uint j = 0; Process *h_p = NULL; bp_data_time_item_t* item = NULL; process_breakpoint_time_t* pbt = NULL; /* remove all psd associated with the hash * and then delete the hash. * ... sigh ... */ for (i = 0; i < bdt->n; ++i) { if (bdt->hash[i].used) { for (j = 0; j < bdt->hash[i].n; ++j) { item = &(bdt->hash[i].item[j]); if (item->pid != NIL) { h_p = erts_pid2proc(NULL, 0, item->pid, ERTS_PROC_LOCK_MAIN); if (h_p) { pbt = ERTS_PROC_SET_CALL_TIME(h_p, ERTS_PROC_LOCK_MAIN, NULL); if (pbt) { Free(pbt); } erts_smp_proc_unlock(h_p, ERTS_PROC_LOCK_MAIN); } } } } bp_hash_delete(&(bdt->hash[i])); } Free(bdt->hash); Free(bdt); } } static BpDataTime* get_time_break(BeamInstr *pc) { GenericBpData* bp = check_break(pc, ERTS_BPF_TIME_TRACE); return bp ? bp->time : 0; } BpDataTime* erts_get_active_time_break(BeamInstr *pc) { GenericBpData* bp = check_break(pc, ERTS_BPF_TIME_TRACE_ACTIVE); return bp ? bp->time : 0; } static GenericBpData* check_break(BeamInstr *pc, Uint break_flags) { GenericBp* g = (GenericBp *) pc[-4]; ASSERT(pc[-5] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); if (erts_is_native_break(pc)) { return 0; } if (g) { GenericBpData* bp = &g->data[0]; ASSERT((bp->flags & ~ERTS_BPF_ALL) == 0); if (bp->flags & break_flags) { return bp; } } return 0; }