// See the file "COPYING" in the main distribution directory for copyright. // Support routines to help deal with Zeek debugging commands and // implementation of most commands. #include "zeek/DebugCmds.h" #include #include #include #include #include "zeek/DbgBreakpoint.h" #include "zeek/Debug.h" #include "zeek/DebugCmdInfoConstants.cc" // NOLINT(bugprone-suspicious-include) #include "zeek/Desc.h" #include "zeek/Frame.h" #include "zeek/Func.h" #include "zeek/ID.h" #include "zeek/PolicyFile.h" #include "zeek/Reporter.h" #include "zeek/Scope.h" #include "zeek/Val.h" #include "zeek/util.h" using namespace std; namespace zeek::detail { DebugCmdInfoQueue g_DebugCmdInfos; // // Helper routines // static bool string_is_regex(const string& s) { return strpbrk(s.data(), "?*\\+"); } static void lookup_global_symbols_regex(const string& orig_regex, vector& matches, bool func_only = false) { if ( util::streq(orig_regex.c_str(), "") ) return; string regex = "^"; int len = orig_regex.length(); for ( int i = 0; i < len; ++i ) { if ( orig_regex[i] == '*' ) regex.push_back('.'); regex.push_back(orig_regex[i]); } regex.push_back('$'); regex_t re; if ( regcomp(&re, regex.c_str(), REG_EXTENDED | REG_NOSUB) ) { debug_msg("Invalid regular expression: %s\n", regex.c_str()); return; } auto global = global_scope(); const auto& syms = global->Vars(); ID* nextid; for ( const auto& sym : syms ) { ID* nextid = sym.second.get(); if ( ! func_only || nextid->GetType()->Tag() == TYPE_FUNC ) if ( ! regexec(&re, nextid->Name(), 0, nullptr, 0) ) matches.push_back(nextid); } } static void choose_global_symbols_regex(const string& regex, vector& choices, bool func_only = false) { lookup_global_symbols_regex(regex, choices, func_only); if ( choices.size() <= 1 ) return; while ( true ) { debug_msg("There were multiple matches, please choose:\n"); for ( size_t i = 0; i < choices.size(); i++ ) debug_msg("[%zu] %s\n", i + 1, choices[i]->Name()); debug_msg("[a] All of the above\n"); debug_msg("[n] None of the above\n"); debug_msg("Enter your choice: "); char charinput[256]; if ( ! fgets(charinput, sizeof(charinput) - 1, stdin) ) { choices.clear(); return; } if ( charinput[strlen(charinput) - 1] == '\n' ) charinput[strlen(charinput) - 1] = 0; string input = charinput; if ( input == "a" ) return; if ( input == "n" ) { choices.clear(); return; } int option = atoi(input.c_str()); if ( option > 0 && option <= (int)choices.size() ) { ID* choice = choices[option - 1]; choices.clear(); choices.push_back(choice); return; } } } // // DebugCmdInfo implementation // DebugCmdInfo::DebugCmdInfo(const DebugCmdInfo& info) : cmd(info.cmd), helpstring(nullptr) { num_names = info.num_names; names = info.names; resume_execution = info.resume_execution; repeatable = info.repeatable; } DebugCmdInfo::DebugCmdInfo(DebugCmd arg_cmd, const char* const* arg_names, int arg_num_names, bool arg_resume_execution, const char* const arg_helpstring, bool arg_repeatable) : cmd(arg_cmd), helpstring(arg_helpstring) { num_names = arg_num_names; resume_execution = arg_resume_execution; repeatable = arg_repeatable; for ( int i = 0; i < num_names; ++i ) names.push_back(arg_names[i]); } const DebugCmdInfo* get_debug_cmd_info(DebugCmd cmd) { if ( (int)cmd < g_DebugCmdInfos.size() ) return g_DebugCmdInfos[(int)cmd]; else return nullptr; } int find_all_matching_cmds(const string& prefix, const char* array_of_matches[]) { // Trivial implementation for now (### use hashing later). unsigned int arglen = prefix.length(); int matches = 0; for ( int i = 0; i < num_debug_cmds(); ++i ) { array_of_matches[g_DebugCmdInfos[i]->Cmd()] = nullptr; for ( int j = 0; j < g_DebugCmdInfos[i]->NumNames(); ++j ) { const char* curr_name = g_DebugCmdInfos[i]->Names()[j]; if ( strncmp(curr_name, prefix.c_str(), arglen) != 0 ) continue; // If exact match, then only return that one. if ( ! prefix.compare(curr_name) ) { for ( int k = 0; k < num_debug_cmds(); ++k ) array_of_matches[k] = nullptr; array_of_matches[g_DebugCmdInfos[i]->Cmd()] = curr_name; return 1; } array_of_matches[g_DebugCmdInfos[i]->Cmd()] = curr_name; ++matches; } } return matches; } // // ------------------------------------------------------------ // Implementation of some debugger commands // Start, end bounds of which frame numbers to print static int dbg_backtrace_internal(int start, int end) { if ( start < 0 || end < 0 || (unsigned)start >= g_frame_stack.size() || (unsigned)end >= g_frame_stack.size() ) reporter->InternalError("Invalid stack frame index in DbgBacktraceInternal\n"); if ( start < end ) { int temp = start; start = end; end = temp; } for ( int i = start; i >= end; --i ) { const Frame* f = g_frame_stack[i]; const Stmt* stmt = f ? f->GetNextStmt() : nullptr; string context = get_context_description(stmt, f); debug_msg("#%d %s\n", int(g_frame_stack.size() - 1 - i), context.c_str()); }; return 1; } // Returns 0 for illegal arguments, or 1 on success. int dbg_cmd_backtrace(DebugCmd cmd, const vector& args) { assert(cmd == dcBacktrace); assert(g_frame_stack.size() > 0); unsigned int start_iter; int end_iter; if ( args.size() > 0 ) { int how_many; // determines how we traverse the frames int valid_arg = sscanf(args[0].c_str(), "%i", &how_many); if ( ! valid_arg ) { debug_msg("Argument to backtrace '%s' invalid: must be an integer\n", args[0].c_str()); return 0; } if ( how_many > 0 ) { // innermost N frames start_iter = g_frame_stack.size() - 1; end_iter = start_iter - how_many + 1; if ( end_iter < 0 ) end_iter = 0; } else { // outermost N frames start_iter = how_many - 1; if ( start_iter + 1 > g_frame_stack.size() ) start_iter = g_frame_stack.size() - 1; end_iter = 0; } } else { start_iter = g_frame_stack.size() - 1; end_iter = 0; } return dbg_backtrace_internal(start_iter, end_iter); } // Returns 0 if invalid args, else 1. int dbg_cmd_frame(DebugCmd cmd, const vector& args) { assert(cmd == dcFrame || cmd == dcUp || cmd == dcDown); if ( cmd == dcFrame ) { int idx = 0; if ( args.size() > 0 ) { if ( args.size() > 1 ) { debug_msg("Too many arguments: expecting frame number 'n'\n"); return 0; } if ( ! sscanf(args[0].c_str(), "%d", &idx) ) { debug_msg("Argument to frame must be a positive integer\n"); return 0; } if ( idx < 0 || (unsigned int)idx >= g_frame_stack.size() ) { debug_msg("No frame %d", idx); return 0; } } g_debugger_state.curr_frame_idx = idx; } else if ( cmd == dcDown ) { if ( g_debugger_state.curr_frame_idx == 0 ) { debug_msg("Innermost frame already selected\n"); return 0; } g_debugger_state.curr_frame_idx--; } else if ( cmd == dcUp ) { if ( (unsigned int)(g_debugger_state.curr_frame_idx + 1) == g_frame_stack.size() ) { debug_msg("Outermost frame already selected\n"); return 0; } ++g_debugger_state.curr_frame_idx; } int user_frame_number = g_frame_stack.size() - 1 - g_debugger_state.curr_frame_idx; // Set the current location to the new frame being looked at // for 'list', 'break', etc. const Stmt* stmt = g_frame_stack[user_frame_number]->GetNextStmt(); if ( ! stmt ) reporter->InternalError("Assertion failed: stmt is null"); const Location loc = *stmt->GetLocationInfo(); g_debugger_state.last_loc = loc; g_debugger_state.already_did_list = false; return dbg_backtrace_internal(user_frame_number, user_frame_number); } int dbg_cmd_help(DebugCmd cmd, const vector& args) { assert(cmd == dcHelp); debug_msg("Help summary: \n\n"); for ( int i = 1; i < num_debug_cmds(); ++i ) { const DebugCmdInfo* info = get_debug_cmd_info(DebugCmd(i)); debug_msg("%s -- %s\n", info->Names()[0], info->Helpstring()); } return -1; } int dbg_cmd_break(DebugCmd cmd, const vector& args) { assert(cmd == dcBreak); vector bps; int cond_index = -1; // at which argument pos. does bp condition start? if ( args.empty() || args[0] == "if" ) { // break on next stmt int user_frame_number = g_frame_stack.size() - 1 - g_debugger_state.curr_frame_idx; Stmt* stmt = g_frame_stack[user_frame_number]->GetNextStmt(); if ( ! stmt ) reporter->InternalError("Assertion failed: stmt is null"); DbgBreakpoint* bp = new DbgBreakpoint(); bp->SetID(g_debugger_state.NextBPID()); if ( ! bp->SetLocation(stmt) ) { debug_msg("Breakpoint not set.\n"); delete bp; return 0; } if ( args.size() > 0 && args[0] == "if" ) cond_index = 1; bps.push_back(bp); } else { vector locstrings; if ( string_is_regex(args[0]) ) { vector choices; choose_global_symbols_regex(args[0], choices, true); for ( const auto& choice : choices ) locstrings.emplace_back(choice->Name()); } else locstrings.push_back(args[0]); for ( const auto& loc_str : locstrings ) { debug_msg("Setting breakpoint on %s:\n", loc_str.c_str()); vector plrs = parse_location_string(loc_str); for ( const auto& plr : plrs ) { DbgBreakpoint* bp = new DbgBreakpoint(); bp->SetID(g_debugger_state.NextBPID()); if ( ! bp->SetLocation(plr, loc_str) ) { debug_msg("Breakpoint not set.\n"); delete bp; } else bps.push_back(bp); } } if ( args.size() > 1 && args[1] == "if" ) cond_index = 2; } // Is there a condition specified? if ( cond_index >= 0 && ! bps.empty() ) { // ### Implement conditions string cond; for ( const auto& arg : args ) { cond += arg; cond += " "; } bps[0]->SetCondition(cond); } for ( auto& bp : bps ) { bp->SetTemporary(false); g_debugger_state.breakpoints[bp->GetID()] = bp; } return 0; } // Set a condition on an existing breakpoint. int dbg_cmd_break_condition(DebugCmd cmd, const vector& args) { assert(cmd == dcBreakCondition); if ( args.size() < 2 ) { debug_msg("Arguments must specify breakpoint number and condition.\n"); return 0; } int idx = atoi(args[0].c_str()); DbgBreakpoint* bp = g_debugger_state.breakpoints[idx]; string expr; for ( int i = 1; i < int(args.size()); ++i ) { expr += args[i]; expr += " "; } bp->SetCondition(expr); return 1; } // Change the state of a breakpoint. int dbg_cmd_break_set_state(DebugCmd cmd, const vector& args) { assert(cmd == dcDeleteBreak || cmd == dcClearBreak || cmd == dcDisableBreak || cmd == dcEnableBreak || cmd == dcIgnoreBreak); if ( cmd == dcClearBreak || cmd == dcIgnoreBreak ) { debug_msg("'clear' and 'ignore' commands not currently supported\n"); return 0; } if ( g_debugger_state.breakpoints.empty() ) { debug_msg("No breakpoints currently set.\n"); return -1; } vector bps_to_change; if ( args.empty() ) { BPIDMapType::iterator iter; for ( iter = g_debugger_state.breakpoints.begin(); iter != g_debugger_state.breakpoints.end(); ++iter ) bps_to_change.push_back(iter->second->GetID()); } else { for ( const auto& arg : args ) if ( int idx = atoi(arg.c_str()) ) bps_to_change.push_back(idx); } for ( auto bp_change : bps_to_change ) { BPIDMapType::iterator result = g_debugger_state.breakpoints.find(bp_change); if ( result != g_debugger_state.breakpoints.end() ) { switch ( cmd ) { case dcDisableBreak: g_debugger_state.breakpoints[bp_change]->SetEnable(false); debug_msg("Breakpoint %d disabled\n", bp_change); break; case dcEnableBreak: g_debugger_state.breakpoints[bp_change]->SetEnable(true); debug_msg("Breakpoint %d enabled\n", bp_change); break; case dcDeleteBreak: delete g_debugger_state.breakpoints[bp_change]; g_debugger_state.breakpoints.erase(bp_change); debug_msg("Breakpoint %d deleted\n", bp_change); break; default: reporter->InternalError("Invalid command in DbgCmdBreakSetState\n"); } } else debug_msg("Breakpoint %d does not exist\n", bp_change); } return -1; } // Evaluate an expression and print the result. int dbg_cmd_print(DebugCmd cmd, const vector& args) { assert(cmd == dcPrint); // ### TODO: add support for formats // Just concatenate all the 'args' into one expression. string expr; for ( size_t i = 0; i < args.size(); ++i ) { expr += args[i]; if ( i < args.size() - 1 ) expr += " "; } auto val = dbg_eval_expr(expr.c_str()); if ( val ) { ODesc d; val->Describe(&d); debug_msg("%s\n", d.Description()); } else { debug_msg("\n"); } return 1; } // Get the debugger's state. // Allowed arguments are: break (breakpoints), watch, display, source. int dbg_cmd_info(DebugCmd cmd, const vector& args) { assert(cmd == dcInfo); if ( args.empty() ) { debug_msg("Syntax: info info-command\n"); debug_msg("List of info-commands:\n"); debug_msg("info breakpoints -- List of breakpoints and watches\n"); return 1; } if ( ! strncmp(args[0].c_str(), "breakpoints", args[0].size()) || ! strncmp(args[0].c_str(), "watch", args[0].size()) ) { debug_msg("Num Type Disp Enb What\n"); BPIDMapType::iterator iter; for ( iter = g_debugger_state.breakpoints.begin(); iter != g_debugger_state.breakpoints.end(); ++iter ) { DbgBreakpoint* bp = (*iter).second; debug_msg("%-4d%-15s%-5s%-4s%s\n", bp->GetID(), "breakpoint", bp->IsTemporary() ? "del" : "keep", bp->IsEnabled() ? "y" : "n", bp->Description()); } } else debug_msg("I don't have info for that yet.\n"); return 1; } int dbg_cmd_list(DebugCmd cmd, const vector& args) { assert(cmd == dcList); // The constant 4 is to match the GDB behavior. const unsigned int CENTER_IDX = 4; // 5th line is the 'interesting' one int pre_offset = 0; if ( args.size() > 1 ) { debug_msg("Syntax: list [file:]line OR list function_name\n"); return 0; } if ( args.empty() ) { // Special case: if we just hit a breakpoint, then show // that line without advancing first. if ( g_debugger_state.already_did_list ) pre_offset = 10; } else if ( args[0] == "-" ) // Why -10 ? Because that's what GDB does. pre_offset = -10; else if ( args[0][0] == '-' || args[0][0] == '+' ) { int offset; if ( ! sscanf(args[0].c_str(), "%d", &offset) ) { debug_msg("Offset must be a number\n"); return false; } pre_offset = offset; } else { vector plrs = parse_location_string(args[0]); ParseLocationRec plr = plrs[0]; if ( plr.type == PLR_UNKNOWN ) { debug_msg("Invalid location specifier\n"); return false; } g_debugger_state.last_loc.SetFile(plr.filename); g_debugger_state.last_loc.SetFirstLine(plr.line); pre_offset = 0; } if ( (int)pre_offset + (int)g_debugger_state.last_loc.FirstLine() - (int)CENTER_IDX < 0 ) pre_offset = CENTER_IDX - g_debugger_state.last_loc.FirstLine(); g_debugger_state.last_loc.IncrementLine(pre_offset); int last_line_in_file = how_many_lines_in(g_debugger_state.last_loc.FileName()); if ( g_debugger_state.last_loc.FirstLine() > last_line_in_file ) g_debugger_state.last_loc.SetLine(last_line_in_file); PrintLines(g_debugger_state.last_loc.FileName(), g_debugger_state.last_loc.FirstLine() - CENTER_IDX, 10, true); g_debugger_state.already_did_list = true; return 1; } int dbg_cmd_trace(DebugCmd cmd, const vector& args) { assert(cmd == dcTrace); if ( args.empty() ) { debug_msg("Execution tracing is %s.\n", g_trace_state.DoTrace() ? "on" : "off"); return 1; } if ( args[0] == "on" ) { g_trace_state.TraceOn(); return 1; } if ( args[0] == "off" ) { g_trace_state.TraceOff(); return 1; } debug_msg("Invalid argument"); return 0; } int num_debug_cmds() { return static_cast(g_DebugCmdInfos.size()); } } // namespace zeek::detail