// Implementation of breakpoints. #include "zeek/zeek-config.h" #include "zeek/DbgBreakpoint.h" #include #include "zeek/Desc.h" #include "zeek/ID.h" #include "zeek/Queue.h" #include "zeek/Debug.h" #include "zeek/Scope.h" #include "zeek/Frame.h" #include "zeek/Func.h" #include "zeek/Val.h" #include "zeek/Stmt.h" #include "zeek/Timer.h" #include "zeek/Reporter.h" #include "zeek/module_util.h" namespace zeek::detail { // BreakpointTimer used for time-based breakpoints class BreakpointTimer final : public Timer { public: BreakpointTimer(DbgBreakpoint* arg_bp, double arg_t) : Timer(arg_t, TIMER_BREAKPOINT) { bp = arg_bp; } void Dispatch(double t, bool is_expire) override; protected: DbgBreakpoint* bp; }; void BreakpointTimer::Dispatch(double t, bool is_expire) { if ( is_expire ) return; bp->ShouldBreak(t); } DbgBreakpoint::DbgBreakpoint() { kind = BP_STMT; enabled = temporary = false; BPID = -1; at_stmt = nullptr; at_time = -1.0; repeat_count = hit_count = 0; description[0] = 0; source_filename = nullptr; source_line = 0; } DbgBreakpoint::~DbgBreakpoint() { SetEnable(false); // clean up any active state RemoveFromGlobalMap(); } bool DbgBreakpoint::SetEnable(bool do_enable) { bool old_value = enabled; enabled = do_enable; // Update statement counts. if ( do_enable && ! old_value ) AddToStmt(); else if ( ! do_enable && old_value ) RemoveFromStmt(); return old_value; } void DbgBreakpoint::AddToGlobalMap() { // Make sure it's not there already. RemoveFromGlobalMap(); g_debugger_state.breakpoint_map.insert(BPMapType::value_type(at_stmt, this)); } void DbgBreakpoint::RemoveFromGlobalMap() { std::pair p; p = g_debugger_state.breakpoint_map.equal_range(at_stmt); for ( BPMapType::iterator i = p.first; i != p.second; ) { if ( i->second == this ) { BPMapType::iterator next = i; ++next; g_debugger_state.breakpoint_map.erase(i); i = next; } else ++i; } } void DbgBreakpoint::AddToStmt() { if ( at_stmt ) at_stmt->IncrBPCount(); } void DbgBreakpoint::RemoveFromStmt() { if ( at_stmt ) at_stmt->DecrBPCount(); } bool DbgBreakpoint::SetLocation(ParseLocationRec plr, std::string_view loc_str) { if ( plr.type == PLR_UNKNOWN ) { debug_msg("Breakpoint specifier invalid or operation canceled.\n"); return false; } if ( plr.type == PLR_FILE_AND_LINE ) { kind = BP_LINE; source_filename = plr.filename; source_line = plr.line; if ( ! plr.stmt ) { debug_msg("No statement at that line.\n"); return false; } at_stmt = plr.stmt; snprintf(description, sizeof(description), "%s:%d", source_filename, source_line); debug_msg("Breakpoint %d set at %s\n", GetID(), Description()); } else if ( plr.type == PLR_FUNCTION ) { std::string loc_s(loc_str); kind = BP_FUNC; function_name = make_full_var_name(current_module.c_str(), loc_s.c_str()); at_stmt = plr.stmt; const Location* loc = at_stmt->GetLocationInfo(); snprintf(description, sizeof(description), "%s at %s:%d", function_name.c_str(), loc->filename, loc->last_line); debug_msg("Breakpoint %d set at %s\n", GetID(), Description()); } SetEnable(true); AddToGlobalMap(); return true; } bool DbgBreakpoint::SetLocation(Stmt* stmt) { if ( ! stmt ) return false; kind = BP_STMT; at_stmt = stmt; SetEnable(true); AddToGlobalMap(); const Location* loc = stmt->GetLocationInfo(); snprintf(description, sizeof(description), "%s:%d", loc->filename, loc->last_line); debug_msg("Breakpoint %d set at %s\n", GetID(), Description()); return true; } bool DbgBreakpoint::SetLocation(double t) { debug_msg("SetLocation(time) has not been debugged."); return false; kind = BP_TIME; at_time = t; timer_mgr->Add(new BreakpointTimer(this, t)); debug_msg("Time-based breakpoints not yet supported.\n"); return false; } bool DbgBreakpoint::Reset() { ParseLocationRec plr; switch ( kind ) { case BP_TIME: debug_msg("Time-based breakpoints not yet supported.\n"); break; case BP_FUNC: case BP_STMT: case BP_LINE: plr.type = PLR_FUNCTION; //### How to deal with wildcards? //### perhaps save user choices?--tough... break; } reporter->InternalError("DbgBreakpoint::Reset function incomplete."); // Cannot be reached. return false; } bool DbgBreakpoint::SetCondition(const std::string& new_condition) { condition = new_condition; return true; } bool DbgBreakpoint::SetRepeatCount(int count) { repeat_count = count; return true; } BreakCode DbgBreakpoint::HasHit() { if ( temporary ) { SetEnable(false); return BC_HIT_AND_DELETE; } if ( condition.size() ) { // TODO: ### evaluate using debugger frame too auto yes = dbg_eval_expr(condition.c_str()); if ( ! yes ) { debug_msg("Breakpoint condition '%s' invalid, removing condition.\n", condition.c_str()); SetCondition(""); PrintHitMsg(); return BC_HIT; } if ( ! IsIntegral(yes->GetType()->Tag()) && ! IsBool(yes->GetType()->Tag()) ) { PrintHitMsg(); debug_msg("Breakpoint condition should return an integral type"); return BC_HIT_AND_DELETE; } yes->CoerceToInt(); if ( yes->IsZero() ) { return BC_NO_HIT; } } int repcount = GetRepeatCount(); if ( repcount ) { if ( ++hit_count == repcount ) { hit_count = 0; PrintHitMsg(); return BC_HIT; } return BC_NO_HIT; } PrintHitMsg(); return BC_HIT; } BreakCode DbgBreakpoint::ShouldBreak(Stmt* s) { if ( ! IsEnabled() ) return BC_NO_HIT; switch ( kind ) { case BP_STMT: case BP_FUNC: if ( at_stmt != s ) return BC_NO_HIT; break; case BP_LINE: assert(s->GetLocationInfo()->first_line <= source_line && s->GetLocationInfo()->last_line >= source_line); break; case BP_TIME: assert(false); default: reporter->InternalError("Invalid breakpoint type in DbgBreakpoint::ShouldBreak"); } // If we got here, that means that the breakpoint could hit, // except potentially if it has a special condition or a repeat count. BreakCode code = HasHit(); if ( code ) g_debugger_state.BreakBeforeNextStmt(true); return code; } BreakCode DbgBreakpoint::ShouldBreak(double t) { if ( kind != BP_TIME ) reporter->InternalError("Calling ShouldBreak(time) on a non-time breakpoint"); if ( t < at_time ) return BC_NO_HIT; if ( ! IsEnabled() ) return BC_NO_HIT; BreakCode code = HasHit(); if ( code ) g_debugger_state.BreakBeforeNextStmt(true); return code; } void DbgBreakpoint::PrintHitMsg() { switch ( kind ) { case BP_STMT: case BP_FUNC: case BP_LINE: { ODesc d; Frame* f = g_frame_stack.back(); const ScriptFunc* func = f->GetFunction(); if ( func ) func->DescribeDebug (&d, f->GetFuncArgs()); const Location* loc = at_stmt->GetLocationInfo(); debug_msg("Breakpoint %d, %s at %s:%d\n", GetID(), d.Description(), loc->filename, loc->first_line); } return; case BP_TIME: assert(false); default: reporter->InternalError("Missed a case in DbgBreakpoint::PrintHitMsg\n"); } } } // namespace zeek::detail