|  | /* Skipping uninteresting files and functions while stepping. | 
|  |  | 
|  | Copyright (C) 2011-2024 Free Software Foundation, Inc. | 
|  |  | 
|  | This program is free software; you can redistribute it and/or modify | 
|  | it under the terms of the GNU General Public License as published by | 
|  | the Free Software Foundation; either version 3 of the License, or | 
|  | (at your option) any later version. | 
|  |  | 
|  | This program is distributed in the hope that it will be useful, | 
|  | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | GNU General Public License for more details. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License | 
|  | along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  | #include "skip.h" | 
|  | #include "event-top.h" | 
|  | #include "value.h" | 
|  | #include "valprint.h" | 
|  | #include "ui-out.h" | 
|  | #include "symtab.h" | 
|  | #include "cli/cli-cmds.h" | 
|  | #include "command.h" | 
|  | #include "completer.h" | 
|  | #include "stack.h" | 
|  | #include "cli/cli-utils.h" | 
|  | #include "arch-utils.h" | 
|  | #include "linespec.h" | 
|  | #include "objfiles.h" | 
|  | #include "breakpoint.h" | 
|  | #include "source.h" | 
|  | #include "filenames.h" | 
|  | #include "fnmatch.h" | 
|  | #include "gdbsupport/gdb_regex.h" | 
|  | #include <optional> | 
|  | #include <list> | 
|  | #include "cli/cli-style.h" | 
|  | #include "gdbsupport/buildargv.h" | 
|  |  | 
|  | /* True if we want to print debug printouts related to file/function | 
|  | skipping. */ | 
|  | static bool debug_skip = false; | 
|  |  | 
|  | class skiplist_entry | 
|  | { | 
|  | public: | 
|  | /* Create a skiplist_entry object and add it to the chain.  */ | 
|  | static void add_entry (bool file_is_glob, | 
|  | std::string &&file, | 
|  | bool function_is_regexp, | 
|  | std::string &&function); | 
|  |  | 
|  | /* Return true if the skip entry has a file or glob-style file | 
|  | pattern that matches FUNCTION_SAL.  */ | 
|  | bool skip_file_p (const symtab_and_line &function_sal) const; | 
|  |  | 
|  | /* Return true if the skip entry has a function or function regexp | 
|  | that matches FUNCTION_NAME.  */ | 
|  | bool skip_function_p (const char *function_name) const; | 
|  |  | 
|  | /* Getters.  */ | 
|  | int number () const { return m_number; }; | 
|  | bool enabled () const { return m_enabled; }; | 
|  | bool file_is_glob () const { return m_file_is_glob; } | 
|  | const std::string &file () const { return m_file; } | 
|  | const std::string &function () const { return m_function; } | 
|  | bool function_is_regexp () const { return m_function_is_regexp; } | 
|  |  | 
|  | /* Setters.  */ | 
|  | void enable () { m_enabled = true; }; | 
|  | void disable () { m_enabled = false; }; | 
|  |  | 
|  | /* Disable copy.  */ | 
|  | skiplist_entry (const skiplist_entry &) = delete; | 
|  | void operator= (const skiplist_entry &) = delete; | 
|  |  | 
|  | private: | 
|  | /* Key that grants access to the constructor.  */ | 
|  | struct private_key {}; | 
|  | public: | 
|  | /* Public so we can construct with container::emplace_back.  Since | 
|  | it requires a private class key, it can't be called from outside. | 
|  | Use the add_entry static factory method to construct instead.  */ | 
|  | skiplist_entry (bool file_is_glob, std::string &&file, | 
|  | bool function_is_regexp, std::string &&function, | 
|  | private_key); | 
|  |  | 
|  | private: | 
|  | /* Return true if we're stopped at a file to be skipped.  */ | 
|  | bool do_skip_file_p (const symtab_and_line &function_sal) const; | 
|  |  | 
|  | /* Return true if we're stopped at a globbed file to be skipped.  */ | 
|  | bool do_skip_gfile_p (const symtab_and_line &function_sal) const; | 
|  |  | 
|  | private: /* data */ | 
|  | int m_number = -1; | 
|  |  | 
|  | /* True if FILE is a glob-style pattern. | 
|  | Otherwise it is the plain file name (possibly with directories).  */ | 
|  | bool m_file_is_glob; | 
|  |  | 
|  | /* The name of the file or empty if no name.  */ | 
|  | std::string m_file; | 
|  |  | 
|  | /* True if FUNCTION is a regexp. | 
|  | Otherwise it is a plain function name (possibly with arguments, | 
|  | for C++).  */ | 
|  | bool m_function_is_regexp; | 
|  |  | 
|  | /* The name of the function or empty if no name.  */ | 
|  | std::string m_function; | 
|  |  | 
|  | /* If this is a function regexp, the compiled form.  */ | 
|  | std::optional<compiled_regex> m_compiled_function_regexp; | 
|  |  | 
|  | /* Enabled/disabled state.  */ | 
|  | bool m_enabled = true; | 
|  | }; | 
|  |  | 
|  | static std::list<skiplist_entry> skiplist_entries; | 
|  | static int highest_skiplist_entry_num = 0; | 
|  |  | 
|  | skiplist_entry::skiplist_entry (bool file_is_glob, | 
|  | std::string &&file, | 
|  | bool function_is_regexp, | 
|  | std::string &&function, | 
|  | private_key) | 
|  | : m_file_is_glob (file_is_glob), | 
|  | m_file (std::move (file)), | 
|  | m_function_is_regexp (function_is_regexp), | 
|  | m_function (std::move (function)) | 
|  | { | 
|  | gdb_assert (!m_file.empty () || !m_function.empty ()); | 
|  |  | 
|  | if (m_file_is_glob) | 
|  | gdb_assert (!m_file.empty ()); | 
|  |  | 
|  | if (m_function_is_regexp) | 
|  | { | 
|  | gdb_assert (!m_function.empty ()); | 
|  | m_compiled_function_regexp.emplace (m_function.c_str (), | 
|  | REG_NOSUB | REG_EXTENDED, | 
|  | _("regexp")); | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | skiplist_entry::add_entry (bool file_is_glob, std::string &&file, | 
|  | bool function_is_regexp, std::string &&function) | 
|  | { | 
|  | skiplist_entries.emplace_back (file_is_glob, | 
|  | std::move (file), | 
|  | function_is_regexp, | 
|  | std::move (function), | 
|  | private_key {}); | 
|  |  | 
|  | /* Incremented after push_back, in case push_back throws.  */ | 
|  | skiplist_entries.back ().m_number = ++highest_skiplist_entry_num; | 
|  | } | 
|  |  | 
|  | static void | 
|  | skip_file_command (const char *arg, int from_tty) | 
|  | { | 
|  | struct symtab *symtab; | 
|  | const char *filename = NULL; | 
|  |  | 
|  | /* If no argument was given, try to default to the last | 
|  | displayed codepoint.  */ | 
|  | if (arg == NULL) | 
|  | { | 
|  | symtab = get_last_displayed_symtab (); | 
|  | if (symtab == NULL) | 
|  | error (_("No default file now.")); | 
|  |  | 
|  | /* It is not a typo, symtab_to_filename_for_display would be needlessly | 
|  | ambiguous.  */ | 
|  | filename = symtab_to_fullname (symtab); | 
|  | } | 
|  | else | 
|  | filename = arg; | 
|  |  | 
|  | skiplist_entry::add_entry (false, std::string (filename), | 
|  | false, std::string ()); | 
|  |  | 
|  | gdb_printf (_("File %s will be skipped when stepping.\n"), filename); | 
|  | } | 
|  |  | 
|  | /* Create a skiplist entry for the given function NAME and add it to the | 
|  | list.  */ | 
|  |  | 
|  | static void | 
|  | skip_function (const char *name) | 
|  | { | 
|  | skiplist_entry::add_entry (false, std::string (), false, std::string (name)); | 
|  |  | 
|  | gdb_printf (_("Function %s will be skipped when stepping.\n"), name); | 
|  | } | 
|  |  | 
|  | static void | 
|  | skip_function_command (const char *arg, int from_tty) | 
|  | { | 
|  | /* Default to the current function if no argument is given.  */ | 
|  | if (arg == NULL) | 
|  | { | 
|  | frame_info_ptr fi = get_selected_frame (_("No default function now.")); | 
|  | struct symbol *sym = get_frame_function (fi); | 
|  | const char *name = NULL; | 
|  |  | 
|  | if (sym != NULL) | 
|  | name = sym->print_name (); | 
|  | else | 
|  | error (_("No function found containing current program point %s."), | 
|  | paddress (get_current_arch (), get_frame_pc (fi))); | 
|  | skip_function (name); | 
|  | return; | 
|  | } | 
|  |  | 
|  | skip_function (arg); | 
|  | } | 
|  |  | 
|  | /* Process "skip ..." that does not match "skip file" or "skip function".  */ | 
|  |  | 
|  | static void | 
|  | skip_command (const char *arg, int from_tty) | 
|  | { | 
|  | const char *file = NULL; | 
|  | const char *gfile = NULL; | 
|  | const char *function = NULL; | 
|  | const char *rfunction = NULL; | 
|  | int i; | 
|  |  | 
|  | if (arg == NULL) | 
|  | { | 
|  | skip_function_command (arg, from_tty); | 
|  | return; | 
|  | } | 
|  |  | 
|  | gdb_argv argv (arg); | 
|  |  | 
|  | for (i = 0; argv[i] != NULL; ++i) | 
|  | { | 
|  | const char *p = argv[i]; | 
|  | const char *value = argv[i + 1]; | 
|  |  | 
|  | if (strcmp (p, "-fi") == 0 | 
|  | || strcmp (p, "-file") == 0) | 
|  | { | 
|  | if (value == NULL) | 
|  | error (_("Missing value for %s option."), p); | 
|  | file = value; | 
|  | ++i; | 
|  | } | 
|  | else if (strcmp (p, "-gfi") == 0 | 
|  | || strcmp (p, "-gfile") == 0) | 
|  | { | 
|  | if (value == NULL) | 
|  | error (_("Missing value for %s option."), p); | 
|  | gfile = value; | 
|  | ++i; | 
|  | } | 
|  | else if (strcmp (p, "-fu") == 0 | 
|  | || strcmp (p, "-function") == 0) | 
|  | { | 
|  | if (value == NULL) | 
|  | error (_("Missing value for %s option."), p); | 
|  | function = value; | 
|  | ++i; | 
|  | } | 
|  | else if (strcmp (p, "-rfu") == 0 | 
|  | || strcmp (p, "-rfunction") == 0) | 
|  | { | 
|  | if (value == NULL) | 
|  | error (_("Missing value for %s option."), p); | 
|  | rfunction = value; | 
|  | ++i; | 
|  | } | 
|  | else if (*p == '-') | 
|  | error (_("Invalid skip option: %s"), p); | 
|  | else if (i == 0) | 
|  | { | 
|  | /* Assume the user entered "skip FUNCTION-NAME". | 
|  | FUNCTION-NAME may be `foo (int)', and therefore we pass the | 
|  | complete original arg to skip_function command as if the user | 
|  | typed "skip function arg".  */ | 
|  | skip_function_command (arg, from_tty); | 
|  | return; | 
|  | } | 
|  | else | 
|  | error (_("Invalid argument: %s"), p); | 
|  | } | 
|  |  | 
|  | if (file != NULL && gfile != NULL) | 
|  | error (_("Cannot specify both -file and -gfile.")); | 
|  |  | 
|  | if (function != NULL && rfunction != NULL) | 
|  | error (_("Cannot specify both -function and -rfunction.")); | 
|  |  | 
|  | /* This shouldn't happen as "skip" by itself gets punted to | 
|  | skip_function_command.  */ | 
|  | gdb_assert (file != NULL || gfile != NULL | 
|  | || function != NULL || rfunction != NULL); | 
|  |  | 
|  | std::string entry_file; | 
|  | if (file != NULL) | 
|  | entry_file = file; | 
|  | else if (gfile != NULL) | 
|  | entry_file = gfile; | 
|  |  | 
|  | std::string entry_function; | 
|  | if (function != NULL) | 
|  | entry_function = function; | 
|  | else if (rfunction != NULL) | 
|  | entry_function = rfunction; | 
|  |  | 
|  | skiplist_entry::add_entry (gfile != NULL, std::move (entry_file), | 
|  | rfunction != NULL, std::move (entry_function)); | 
|  |  | 
|  | /* I18N concerns drive some of the choices here (we can't piece together | 
|  | the output too much).  OTOH we want to keep this simple.  Therefore the | 
|  | only polish we add to the output is to append "(s)" to "File" or | 
|  | "Function" if they're a glob/regexp.  */ | 
|  | { | 
|  | const char *file_to_print = file != NULL ? file : gfile; | 
|  | const char *function_to_print = function != NULL ? function : rfunction; | 
|  | const char *file_text = gfile != NULL ? _("File(s)") : _("File"); | 
|  | const char *lower_file_text = gfile != NULL ? _("file(s)") : _("file"); | 
|  | const char *function_text | 
|  | = rfunction != NULL ? _("Function(s)") : _("Function"); | 
|  |  | 
|  | if (function_to_print == NULL) | 
|  | { | 
|  | gdb_printf (_("%s %s will be skipped when stepping.\n"), | 
|  | file_text, file_to_print); | 
|  | } | 
|  | else if (file_to_print == NULL) | 
|  | { | 
|  | gdb_printf (_("%s %s will be skipped when stepping.\n"), | 
|  | function_text, function_to_print); | 
|  | } | 
|  | else | 
|  | { | 
|  | gdb_printf (_("%s %s in %s %s will be skipped" | 
|  | " when stepping.\n"), | 
|  | function_text, function_to_print, | 
|  | lower_file_text, file_to_print); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | info_skip_command (const char *arg, int from_tty) | 
|  | { | 
|  | int num_printable_entries = 0; | 
|  | struct value_print_options opts; | 
|  |  | 
|  | get_user_print_options (&opts); | 
|  |  | 
|  | /* Count the number of rows in the table and see if we need space for a | 
|  | 64-bit address anywhere.  */ | 
|  | for (const skiplist_entry &e : skiplist_entries) | 
|  | if (arg == NULL || number_is_in_list (arg, e.number ())) | 
|  | num_printable_entries++; | 
|  |  | 
|  | if (num_printable_entries == 0) | 
|  | { | 
|  | if (arg == NULL) | 
|  | current_uiout->message (_("Not skipping any files or functions.\n")); | 
|  | else | 
|  | current_uiout->message ( | 
|  | _("No skiplist entries found with number %s.\n"), arg); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | ui_out_emit_table table_emitter (current_uiout, 6, num_printable_entries, | 
|  | "SkiplistTable"); | 
|  |  | 
|  | current_uiout->table_header (5, ui_left, "number", "Num");   /* 1 */ | 
|  | current_uiout->table_header (3, ui_left, "enabled", "Enb");  /* 2 */ | 
|  | current_uiout->table_header (4, ui_right, "regexp", "Glob"); /* 3 */ | 
|  | current_uiout->table_header (20, ui_left, "file", "File");   /* 4 */ | 
|  | current_uiout->table_header (2, ui_right, "regexp", "RE");   /* 5 */ | 
|  | current_uiout->table_header (40, ui_noalign, "function", "Function"); /* 6 */ | 
|  | current_uiout->table_body (); | 
|  |  | 
|  | for (const skiplist_entry &e : skiplist_entries) | 
|  | { | 
|  | QUIT; | 
|  | if (arg != NULL && !number_is_in_list (arg, e.number ())) | 
|  | continue; | 
|  |  | 
|  | ui_out_emit_tuple tuple_emitter (current_uiout, "blklst-entry"); | 
|  | current_uiout->field_signed ("number", e.number ()); /* 1 */ | 
|  |  | 
|  | if (e.enabled ()) | 
|  | current_uiout->field_string ("enabled", "y"); /* 2 */ | 
|  | else | 
|  | current_uiout->field_string ("enabled", "n"); /* 2 */ | 
|  |  | 
|  | if (e.file_is_glob ()) | 
|  | current_uiout->field_string ("regexp", "y"); /* 3 */ | 
|  | else | 
|  | current_uiout->field_string ("regexp", "n"); /* 3 */ | 
|  |  | 
|  | current_uiout->field_string ("file", | 
|  | e.file ().empty () ? "<none>" | 
|  | : e.file ().c_str (), | 
|  | e.file ().empty () | 
|  | ? metadata_style.style () | 
|  | : file_name_style.style ()); /* 4 */ | 
|  | if (e.function_is_regexp ()) | 
|  | current_uiout->field_string ("regexp", "y"); /* 5 */ | 
|  | else | 
|  | current_uiout->field_string ("regexp", "n"); /* 5 */ | 
|  |  | 
|  | current_uiout->field_string ("function", | 
|  | e.function ().empty () ? "<none>" | 
|  | : e.function ().c_str (), | 
|  | e.function ().empty () | 
|  | ? metadata_style.style () | 
|  | : function_name_style.style ()); /* 6 */ | 
|  |  | 
|  | current_uiout->text ("\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | skip_enable_command (const char *arg, int from_tty) | 
|  | { | 
|  | bool found = false; | 
|  |  | 
|  | for (skiplist_entry &e : skiplist_entries) | 
|  | if (arg == NULL || number_is_in_list (arg, e.number ())) | 
|  | { | 
|  | e.enable (); | 
|  | found = true; | 
|  | } | 
|  |  | 
|  | if (!found) | 
|  | error (_("No skiplist entries found with number %s."), arg); | 
|  | } | 
|  |  | 
|  | static void | 
|  | skip_disable_command (const char *arg, int from_tty) | 
|  | { | 
|  | bool found = false; | 
|  |  | 
|  | for (skiplist_entry &e : skiplist_entries) | 
|  | if (arg == NULL || number_is_in_list (arg, e.number ())) | 
|  | { | 
|  | e.disable (); | 
|  | found = true; | 
|  | } | 
|  |  | 
|  | if (!found) | 
|  | error (_("No skiplist entries found with number %s."), arg); | 
|  | } | 
|  |  | 
|  | static void | 
|  | skip_delete_command (const char *arg, int from_tty) | 
|  | { | 
|  | bool found = false; | 
|  |  | 
|  | for (auto it = skiplist_entries.begin (), | 
|  | end = skiplist_entries.end (); | 
|  | it != end;) | 
|  | { | 
|  | const skiplist_entry &e = *it; | 
|  |  | 
|  | if (arg == NULL || number_is_in_list (arg, e.number ())) | 
|  | { | 
|  | it = skiplist_entries.erase (it); | 
|  | found = true; | 
|  | } | 
|  | else | 
|  | ++it; | 
|  | } | 
|  |  | 
|  | if (!found) | 
|  | error (_("No skiplist entries found with number %s."), arg); | 
|  | } | 
|  |  | 
|  | bool | 
|  | skiplist_entry::do_skip_file_p (const symtab_and_line &function_sal) const | 
|  | { | 
|  | if (debug_skip) | 
|  | gdb_printf (gdb_stdlog, | 
|  | "skip: checking if file %s matches non-glob %s...", | 
|  | function_sal.symtab->filename, m_file.c_str ()); | 
|  |  | 
|  | bool result; | 
|  |  | 
|  | /* Check first sole SYMTAB->FILENAME.  It may not be a substring of | 
|  | symtab_to_fullname as it may contain "./" etc.  */ | 
|  | if (compare_filenames_for_search (function_sal.symtab->filename, | 
|  | m_file.c_str ())) | 
|  | result = true; | 
|  |  | 
|  | /* Before we invoke realpath, which can get expensive when many | 
|  | files are involved, do a quick comparison of the basenames.  */ | 
|  | else if (!basenames_may_differ | 
|  | && filename_cmp (lbasename (function_sal.symtab->filename), | 
|  | lbasename (m_file.c_str ())) != 0) | 
|  | result = false; | 
|  | else | 
|  | { | 
|  | /* Note: symtab_to_fullname caches its result, thus we don't have to.  */ | 
|  | const char *fullname = symtab_to_fullname (function_sal.symtab); | 
|  |  | 
|  | result = compare_filenames_for_search (fullname, m_file.c_str ()); | 
|  | } | 
|  |  | 
|  | if (debug_skip) | 
|  | gdb_printf (gdb_stdlog, result ? "yes.\n" : "no.\n"); | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | bool | 
|  | skiplist_entry::do_skip_gfile_p (const symtab_and_line &function_sal) const | 
|  | { | 
|  | if (debug_skip) | 
|  | gdb_printf (gdb_stdlog, | 
|  | "skip: checking if file %s matches glob %s...", | 
|  | function_sal.symtab->filename, m_file.c_str ()); | 
|  |  | 
|  | bool result; | 
|  |  | 
|  | /* Check first sole SYMTAB->FILENAME.  It may not be a substring of | 
|  | symtab_to_fullname as it may contain "./" etc.  */ | 
|  | if (gdb_filename_fnmatch (m_file.c_str (), function_sal.symtab->filename, | 
|  | FNM_NOESCAPE) == 0) | 
|  | result = true; | 
|  |  | 
|  | /* Before we invoke symtab_to_fullname, which is expensive, do a quick | 
|  | comparison of the basenames. | 
|  | Note that we assume that lbasename works with glob-style patterns. | 
|  | If the basename of the glob pattern is something like "*.c" then this | 
|  | isn't much of a win.  Oh well.  */ | 
|  | else if (!basenames_may_differ | 
|  | && gdb_filename_fnmatch (lbasename (m_file.c_str ()), | 
|  | lbasename (function_sal.symtab->filename), | 
|  | FNM_NOESCAPE) != 0) | 
|  | result = false; | 
|  | else | 
|  | { | 
|  | /* Note: symtab_to_fullname caches its result, thus we don't have to.  */ | 
|  | const char *fullname = symtab_to_fullname (function_sal.symtab); | 
|  |  | 
|  | result = gdb_filename_fnmatch (m_file.c_str (), fullname, FNM_NOESCAPE); | 
|  | } | 
|  |  | 
|  | if (debug_skip) | 
|  | gdb_printf (gdb_stdlog, result ? "yes.\n" : "no.\n"); | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | bool | 
|  | skiplist_entry::skip_file_p (const symtab_and_line &function_sal) const | 
|  | { | 
|  | if (m_file.empty ()) | 
|  | return false; | 
|  |  | 
|  | if (function_sal.symtab == NULL) | 
|  | return false; | 
|  |  | 
|  | if (m_file_is_glob) | 
|  | return do_skip_gfile_p (function_sal); | 
|  | else | 
|  | return do_skip_file_p (function_sal); | 
|  | } | 
|  |  | 
|  | bool | 
|  | skiplist_entry::skip_function_p (const char *function_name) const | 
|  | { | 
|  | if (m_function.empty ()) | 
|  | return false; | 
|  |  | 
|  | bool result; | 
|  |  | 
|  | if (m_function_is_regexp) | 
|  | { | 
|  | if (debug_skip) | 
|  | gdb_printf (gdb_stdlog, | 
|  | "skip: checking if function %s matches regex %s...", | 
|  | function_name, m_function.c_str ()); | 
|  |  | 
|  | gdb_assert (m_compiled_function_regexp); | 
|  | result | 
|  | = (m_compiled_function_regexp->exec (function_name, 0, NULL, 0) == 0); | 
|  | } | 
|  | else | 
|  | { | 
|  | if (debug_skip) | 
|  | gdb_printf (gdb_stdlog, | 
|  | ("skip: checking if function %s matches non-regex " | 
|  | "%s..."), | 
|  | function_name, m_function.c_str ()); | 
|  | result = (strcmp_iw (function_name, m_function.c_str ()) == 0); | 
|  | } | 
|  |  | 
|  | if (debug_skip) | 
|  | gdb_printf (gdb_stdlog, result ? "yes.\n" : "no.\n"); | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /* See skip.h.  */ | 
|  |  | 
|  | bool | 
|  | function_name_is_marked_for_skip (const char *function_name, | 
|  | const symtab_and_line &function_sal) | 
|  | { | 
|  | if (function_name == NULL) | 
|  | return false; | 
|  |  | 
|  | for (const skiplist_entry &e : skiplist_entries) | 
|  | { | 
|  | if (!e.enabled ()) | 
|  | continue; | 
|  |  | 
|  | bool skip_by_file = e.skip_file_p (function_sal); | 
|  | bool skip_by_function = e.skip_function_p (function_name); | 
|  |  | 
|  | /* If both file and function must match, make sure we don't errantly | 
|  | exit if only one of them match.  */ | 
|  | if (!e.file ().empty () && !e.function ().empty ()) | 
|  | { | 
|  | if (skip_by_file && skip_by_function) | 
|  | return true; | 
|  | } | 
|  | /* Only one of file/function is specified.  */ | 
|  | else if (skip_by_file || skip_by_function) | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* Completer for skip numbers.  */ | 
|  |  | 
|  | static void | 
|  | complete_skip_number (cmd_list_element *cmd, | 
|  | completion_tracker &completer, | 
|  | const char *text, const char *word) | 
|  | { | 
|  | size_t word_len = strlen (word); | 
|  |  | 
|  | for (const skiplist_entry &entry : skiplist_entries) | 
|  | { | 
|  | gdb::unique_xmalloc_ptr<char> name = xstrprintf ("%d", entry.number ()); | 
|  | if (strncmp (word, name.get (), word_len) == 0) | 
|  | completer.add_completion (std::move (name)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void _initialize_step_skip (); | 
|  | void | 
|  | _initialize_step_skip () | 
|  | { | 
|  | static struct cmd_list_element *skiplist = NULL; | 
|  | struct cmd_list_element *c; | 
|  |  | 
|  | add_prefix_cmd ("skip", class_breakpoint, skip_command, _("\ | 
|  | Ignore a function while stepping.\n\ | 
|  | \n\ | 
|  | Usage: skip [FUNCTION-NAME]\n\ | 
|  | skip [FILE-SPEC] [FUNCTION-SPEC]\n\ | 
|  | If no arguments are given, ignore the current function.\n\ | 
|  | \n\ | 
|  | FILE-SPEC is one of:\n\ | 
|  | -fi|-file FILE-NAME\n\ | 
|  | -gfi|-gfile GLOB-FILE-PATTERN\n\ | 
|  | FUNCTION-SPEC is one of:\n\ | 
|  | -fu|-function FUNCTION-NAME\n\ | 
|  | -rfu|-rfunction FUNCTION-NAME-REGULAR-EXPRESSION"), | 
|  | &skiplist, 1, &cmdlist); | 
|  |  | 
|  | c = add_cmd ("file", class_breakpoint, skip_file_command, _("\ | 
|  | Ignore a file while stepping.\n\ | 
|  | Usage: skip file [FILE-NAME]\n\ | 
|  | If no filename is given, ignore the current file."), | 
|  | &skiplist); | 
|  | set_cmd_completer (c, deprecated_filename_completer); | 
|  |  | 
|  | c = add_cmd ("function", class_breakpoint, skip_function_command, _("\ | 
|  | Ignore a function while stepping.\n\ | 
|  | Usage: skip function [FUNCTION-NAME]\n\ | 
|  | If no function name is given, skip the current function."), | 
|  | &skiplist); | 
|  | set_cmd_completer (c, location_completer); | 
|  |  | 
|  | c = add_cmd ("enable", class_breakpoint, skip_enable_command, _("\ | 
|  | Enable skip entries.\n\ | 
|  | Usage: skip enable [NUMBER | RANGE]...\n\ | 
|  | You can specify numbers (e.g. \"skip enable 1 3\"),\n\ | 
|  | ranges (e.g. \"skip enable 4-8\"), or both (e.g. \"skip enable 1 3 4-8\").\n\n\ | 
|  | If you don't specify any numbers or ranges, we'll enable all skip entries."), | 
|  | &skiplist); | 
|  | set_cmd_completer (c, complete_skip_number); | 
|  |  | 
|  | c = add_cmd ("disable", class_breakpoint, skip_disable_command, _("\ | 
|  | Disable skip entries.\n\ | 
|  | Usage: skip disable [NUMBER | RANGE]...\n\ | 
|  | You can specify numbers (e.g. \"skip disable 1 3\"),\n\ | 
|  | ranges (e.g. \"skip disable 4-8\"), or both (e.g. \"skip disable 1 3 4-8\").\n\n\ | 
|  | If you don't specify any numbers or ranges, we'll disable all skip entries."), | 
|  | &skiplist); | 
|  | set_cmd_completer (c, complete_skip_number); | 
|  |  | 
|  | c = add_cmd ("delete", class_breakpoint, skip_delete_command, _("\ | 
|  | Delete skip entries.\n\ | 
|  | Usage: skip delete [NUMBER | RANGES]...\n\ | 
|  | You can specify numbers (e.g. \"skip delete 1 3\"),\n\ | 
|  | ranges (e.g. \"skip delete 4-8\"), or both (e.g. \"skip delete 1 3 4-8\").\n\n\ | 
|  | If you don't specify any numbers or ranges, we'll delete all skip entries."), | 
|  | &skiplist); | 
|  | set_cmd_completer (c, complete_skip_number); | 
|  |  | 
|  | add_info ("skip", info_skip_command, _("\ | 
|  | Display the status of skips.\n\ | 
|  | Usage: info skip [NUMBER | RANGES]...\n\ | 
|  | You can specify numbers (e.g. \"info skip 1 3\"),\n\ | 
|  | ranges (e.g. \"info skip 4-8\"), or both (e.g. \"info skip 1 3 4-8\").\n\n\ | 
|  | If you don't specify any numbers or ranges, we'll show all skips.")); | 
|  | set_cmd_completer (c, complete_skip_number); | 
|  |  | 
|  | add_setshow_boolean_cmd ("skip", class_maintenance, | 
|  | &debug_skip, _("\ | 
|  | Set whether to print the debug output about skipping files and functions."), | 
|  | _("\ | 
|  | Show whether the debug output about skipping files and functions is printed."), | 
|  | _("\ | 
|  | When non-zero, debug output about skipping files and functions is displayed."), | 
|  | NULL, NULL, | 
|  | &setdebuglist, &showdebuglist); | 
|  | } |