|  | /* Everything about syscall catchpoints, for GDB. | 
|  |  | 
|  | Copyright (C) 2009-2024 Free Software Foundation, Inc. | 
|  |  | 
|  | This file is part of GDB. | 
|  |  | 
|  | 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 <ctype.h> | 
|  | #include "breakpoint.h" | 
|  | #include "cli/cli-cmds.h" | 
|  | #include "inferior.h" | 
|  | #include "cli/cli-utils.h" | 
|  | #include "annotate.h" | 
|  | #include "mi/mi-common.h" | 
|  | #include "valprint.h" | 
|  | #include "arch-utils.h" | 
|  | #include "observable.h" | 
|  | #include "xml-syscall.h" | 
|  | #include "cli/cli-style.h" | 
|  | #include "cli/cli-decode.h" | 
|  |  | 
|  | /* An instance of this type is used to represent a syscall | 
|  | catchpoint.  */ | 
|  |  | 
|  | struct syscall_catchpoint : public catchpoint | 
|  | { | 
|  | syscall_catchpoint (struct gdbarch *gdbarch, bool tempflag, | 
|  | std::vector<int> &&calls) | 
|  | : catchpoint (gdbarch, tempflag, nullptr), | 
|  | syscalls_to_be_caught (std::move (calls)) | 
|  | { | 
|  | } | 
|  |  | 
|  | int insert_location (struct bp_location *) override; | 
|  | int remove_location (struct bp_location *, | 
|  | enum remove_bp_reason reason) override; | 
|  | int breakpoint_hit (const struct bp_location *bl, | 
|  | const address_space *aspace, | 
|  | CORE_ADDR bp_addr, | 
|  | const target_waitstatus &ws) override; | 
|  | enum print_stop_action print_it (const bpstat *bs) const override; | 
|  | bool print_one (const bp_location **) const override; | 
|  | void print_mention () const override; | 
|  | void print_recreate (struct ui_file *fp) const override; | 
|  |  | 
|  | /* Syscall numbers used for the 'catch syscall' feature.  If no | 
|  | syscall has been specified for filtering, it is empty. | 
|  | Otherwise, it holds a list of all syscalls to be caught.  */ | 
|  | std::vector<int> syscalls_to_be_caught; | 
|  | }; | 
|  |  | 
|  | struct catch_syscall_inferior_data | 
|  | { | 
|  | /* We keep a count of the number of times the user has requested a | 
|  | particular syscall to be tracked, and pass this information to the | 
|  | target.  This lets capable targets implement filtering directly.  */ | 
|  |  | 
|  | /* Number of times that "any" syscall is requested.  */ | 
|  | int any_syscall_count; | 
|  |  | 
|  | /* Count of each system call.  */ | 
|  | std::vector<int> syscalls_counts; | 
|  |  | 
|  | /* This counts all syscall catch requests, so we can readily determine | 
|  | if any catching is necessary.  */ | 
|  | int total_syscalls_count; | 
|  | }; | 
|  |  | 
|  | static const registry<inferior>::key<catch_syscall_inferior_data> | 
|  | catch_syscall_inferior_data; | 
|  |  | 
|  | static struct catch_syscall_inferior_data * | 
|  | get_catch_syscall_inferior_data (struct inferior *inf) | 
|  | { | 
|  | struct catch_syscall_inferior_data *inf_data; | 
|  |  | 
|  | inf_data = catch_syscall_inferior_data.get (inf); | 
|  | if (inf_data == NULL) | 
|  | inf_data = catch_syscall_inferior_data.emplace (inf); | 
|  |  | 
|  | return inf_data; | 
|  | } | 
|  |  | 
|  | /* Implement the "insert" method for syscall catchpoints.  */ | 
|  |  | 
|  | int | 
|  | syscall_catchpoint::insert_location (struct bp_location *bl) | 
|  | { | 
|  | struct inferior *inf = current_inferior (); | 
|  | struct catch_syscall_inferior_data *inf_data | 
|  | = get_catch_syscall_inferior_data (inf); | 
|  |  | 
|  | ++inf_data->total_syscalls_count; | 
|  | if (syscalls_to_be_caught.empty ()) | 
|  | ++inf_data->any_syscall_count; | 
|  | else | 
|  | { | 
|  | for (int iter : syscalls_to_be_caught) | 
|  | { | 
|  | if (iter >= inf_data->syscalls_counts.size ()) | 
|  | inf_data->syscalls_counts.resize (iter + 1); | 
|  | ++inf_data->syscalls_counts[iter]; | 
|  | } | 
|  | } | 
|  |  | 
|  | return target_set_syscall_catchpoint (inferior_ptid.pid (), | 
|  | inf_data->total_syscalls_count != 0, | 
|  | inf_data->any_syscall_count, | 
|  | inf_data->syscalls_counts); | 
|  | } | 
|  |  | 
|  | /* Implement the "remove" method for syscall catchpoints.  */ | 
|  |  | 
|  | int | 
|  | syscall_catchpoint::remove_location (struct bp_location *bl, | 
|  | enum remove_bp_reason reason) | 
|  | { | 
|  | struct inferior *inf = current_inferior (); | 
|  | struct catch_syscall_inferior_data *inf_data | 
|  | = get_catch_syscall_inferior_data (inf); | 
|  |  | 
|  | --inf_data->total_syscalls_count; | 
|  | if (syscalls_to_be_caught.empty ()) | 
|  | --inf_data->any_syscall_count; | 
|  | else | 
|  | { | 
|  | for (int iter : syscalls_to_be_caught) | 
|  | { | 
|  | if (iter >= inf_data->syscalls_counts.size ()) | 
|  | /* Shouldn't happen.  */ | 
|  | continue; | 
|  | --inf_data->syscalls_counts[iter]; | 
|  | } | 
|  | } | 
|  |  | 
|  | return target_set_syscall_catchpoint (inferior_ptid.pid (), | 
|  | inf_data->total_syscalls_count != 0, | 
|  | inf_data->any_syscall_count, | 
|  | inf_data->syscalls_counts); | 
|  | } | 
|  |  | 
|  | /* Implement the "breakpoint_hit" method for syscall catchpoints.  */ | 
|  |  | 
|  | int | 
|  | syscall_catchpoint::breakpoint_hit (const struct bp_location *bl, | 
|  | const address_space *aspace, | 
|  | CORE_ADDR bp_addr, | 
|  | const target_waitstatus &ws) | 
|  | { | 
|  | /* We must check if we are catching specific syscalls in this | 
|  | breakpoint.  If we are, then we must guarantee that the called | 
|  | syscall is the same syscall we are catching.  */ | 
|  | int syscall_number = 0; | 
|  |  | 
|  | if (ws.kind () != TARGET_WAITKIND_SYSCALL_ENTRY | 
|  | && ws.kind () != TARGET_WAITKIND_SYSCALL_RETURN) | 
|  | return 0; | 
|  |  | 
|  | syscall_number = ws.syscall_number (); | 
|  |  | 
|  | /* Now, checking if the syscall is the same.  */ | 
|  | if (!syscalls_to_be_caught.empty ()) | 
|  | { | 
|  | for (int iter : syscalls_to_be_caught) | 
|  | if (syscall_number == iter) | 
|  | return 1; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* Implement the "print_it" method for syscall catchpoints.  */ | 
|  |  | 
|  | enum print_stop_action | 
|  | syscall_catchpoint::print_it (const bpstat *bs) const | 
|  | { | 
|  | struct ui_out *uiout = current_uiout; | 
|  | /* These are needed because we want to know in which state a | 
|  | syscall is.  It can be in the TARGET_WAITKIND_SYSCALL_ENTRY | 
|  | or TARGET_WAITKIND_SYSCALL_RETURN, and depending on it we | 
|  | must print "called syscall" or "returned from syscall".  */ | 
|  | struct target_waitstatus last; | 
|  | struct syscall s; | 
|  |  | 
|  | get_last_target_status (nullptr, nullptr, &last); | 
|  |  | 
|  | get_syscall_by_number (gdbarch, last.syscall_number (), &s); | 
|  |  | 
|  | annotate_catchpoint (this->number); | 
|  | maybe_print_thread_hit_breakpoint (uiout); | 
|  |  | 
|  | if (this->disposition == disp_del) | 
|  | uiout->text ("Temporary catchpoint "); | 
|  | else | 
|  | uiout->text ("Catchpoint "); | 
|  | if (uiout->is_mi_like_p ()) | 
|  | { | 
|  | uiout->field_string ("reason", | 
|  | async_reason_lookup (last.kind () == TARGET_WAITKIND_SYSCALL_ENTRY | 
|  | ? EXEC_ASYNC_SYSCALL_ENTRY | 
|  | : EXEC_ASYNC_SYSCALL_RETURN)); | 
|  | uiout->field_string ("disp", bpdisp_text (this->disposition)); | 
|  | } | 
|  | print_num_locno (bs, uiout); | 
|  |  | 
|  | if (last.kind () == TARGET_WAITKIND_SYSCALL_ENTRY) | 
|  | uiout->text (" (call to syscall "); | 
|  | else | 
|  | uiout->text (" (returned from syscall "); | 
|  |  | 
|  | if (s.name == NULL || uiout->is_mi_like_p ()) | 
|  | uiout->field_signed ("syscall-number", last.syscall_number ()); | 
|  | if (s.name != NULL) | 
|  | uiout->field_string ("syscall-name", s.name); | 
|  |  | 
|  | uiout->text ("), "); | 
|  |  | 
|  | return PRINT_SRC_AND_LOC; | 
|  | } | 
|  |  | 
|  | /* Implement the "print_one" method for syscall catchpoints.  */ | 
|  |  | 
|  | bool | 
|  | syscall_catchpoint::print_one (const bp_location **last_loc) const | 
|  | { | 
|  | struct value_print_options opts; | 
|  | struct ui_out *uiout = current_uiout; | 
|  |  | 
|  | get_user_print_options (&opts); | 
|  | /* Field 4, the address, is omitted (which makes the columns not | 
|  | line up too nicely with the headers, but the effect is relatively | 
|  | readable).  */ | 
|  | if (opts.addressprint) | 
|  | uiout->field_skip ("addr"); | 
|  | annotate_field (5); | 
|  |  | 
|  | if (syscalls_to_be_caught.size () > 1) | 
|  | uiout->text ("syscalls \""); | 
|  | else | 
|  | uiout->text ("syscall \""); | 
|  |  | 
|  | if (!syscalls_to_be_caught.empty ()) | 
|  | { | 
|  | std::string text; | 
|  |  | 
|  | bool first = true; | 
|  | for (int iter : syscalls_to_be_caught) | 
|  | { | 
|  | struct syscall s; | 
|  | get_syscall_by_number (gdbarch, iter, &s); | 
|  |  | 
|  | if (!first) | 
|  | text += ", "; | 
|  | first = false; | 
|  |  | 
|  | if (s.name != NULL) | 
|  | text += s.name; | 
|  | else | 
|  | text += std::to_string (iter); | 
|  | } | 
|  | uiout->field_string ("what", text.c_str ()); | 
|  | } | 
|  | else | 
|  | uiout->field_string ("what", "<any syscall>", metadata_style.style ()); | 
|  | uiout->text ("\" "); | 
|  |  | 
|  | if (uiout->is_mi_like_p ()) | 
|  | uiout->field_string ("catch-type", "syscall"); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* Implement the "print_mention" method for syscall catchpoints.  */ | 
|  |  | 
|  | void | 
|  | syscall_catchpoint::print_mention () const | 
|  | { | 
|  | if (!syscalls_to_be_caught.empty ()) | 
|  | { | 
|  | if (syscalls_to_be_caught.size () > 1) | 
|  | gdb_printf (_("Catchpoint %d (syscalls"), number); | 
|  | else | 
|  | gdb_printf (_("Catchpoint %d (syscall"), number); | 
|  |  | 
|  | for (int iter : syscalls_to_be_caught) | 
|  | { | 
|  | struct syscall s; | 
|  | get_syscall_by_number (gdbarch, iter, &s); | 
|  |  | 
|  | if (s.name != NULL) | 
|  | gdb_printf (" '%s' [%d]", s.name, s.number); | 
|  | else | 
|  | gdb_printf (" %d", s.number); | 
|  | } | 
|  | gdb_printf (")"); | 
|  | } | 
|  | else | 
|  | gdb_printf (_("Catchpoint %d (any syscall)"), number); | 
|  | } | 
|  |  | 
|  | /* Implement the "print_recreate" method for syscall catchpoints.  */ | 
|  |  | 
|  | void | 
|  | syscall_catchpoint::print_recreate (struct ui_file *fp) const | 
|  | { | 
|  | gdb_printf (fp, "catch syscall"); | 
|  |  | 
|  | for (int iter : syscalls_to_be_caught) | 
|  | { | 
|  | struct syscall s; | 
|  |  | 
|  | get_syscall_by_number (gdbarch, iter, &s); | 
|  | if (s.name != NULL) | 
|  | gdb_printf (fp, " %s", s.name); | 
|  | else | 
|  | gdb_printf (fp, " %d", s.number); | 
|  | } | 
|  |  | 
|  | print_recreate_thread (fp); | 
|  | } | 
|  |  | 
|  | /* Returns non-zero if 'b' is a syscall catchpoint.  */ | 
|  |  | 
|  | static int | 
|  | syscall_catchpoint_p (struct breakpoint *b) | 
|  | { | 
|  | return dynamic_cast<syscall_catchpoint *> (b) != nullptr; | 
|  | } | 
|  |  | 
|  | static void | 
|  | create_syscall_event_catchpoint (int tempflag, std::vector<int> &&filter) | 
|  | { | 
|  | struct gdbarch *gdbarch = get_current_arch (); | 
|  |  | 
|  | std::unique_ptr<syscall_catchpoint> c | 
|  | (new syscall_catchpoint (gdbarch, tempflag, std::move (filter))); | 
|  |  | 
|  | install_breakpoint (0, std::move (c), 1); | 
|  | } | 
|  |  | 
|  | /* Splits the argument using space as delimiter.  */ | 
|  |  | 
|  | static std::vector<int> | 
|  | catch_syscall_split_args (const char *arg) | 
|  | { | 
|  | std::vector<int> result; | 
|  | gdbarch *gdbarch = current_inferior ()->arch (); | 
|  |  | 
|  | while (*arg != '\0') | 
|  | { | 
|  | int i, syscall_number; | 
|  | char *endptr; | 
|  | char cur_name[128]; | 
|  | struct syscall s; | 
|  |  | 
|  | /* Skip whitespace.  */ | 
|  | arg = skip_spaces (arg); | 
|  |  | 
|  | for (i = 0; i < 127 && arg[i] && !isspace (arg[i]); ++i) | 
|  | cur_name[i] = arg[i]; | 
|  | cur_name[i] = '\0'; | 
|  | arg += i; | 
|  |  | 
|  | /* Check if the user provided a syscall name, group, or a number.  */ | 
|  | syscall_number = (int) strtol (cur_name, &endptr, 0); | 
|  | if (*endptr == '\0') | 
|  | { | 
|  | if (syscall_number < 0) | 
|  | error (_("Unknown syscall number '%d'."), syscall_number); | 
|  | get_syscall_by_number (gdbarch, syscall_number, &s); | 
|  | result.push_back (s.number); | 
|  | } | 
|  | else if (startswith (cur_name, "g:") | 
|  | || startswith (cur_name, "group:")) | 
|  | { | 
|  | /* We have a syscall group.  Let's expand it into a syscall | 
|  | list before inserting.  */ | 
|  | const char *group_name; | 
|  |  | 
|  | /* Skip over "g:" and "group:" prefix strings.  */ | 
|  | group_name = strchr (cur_name, ':') + 1; | 
|  |  | 
|  | if (!get_syscalls_by_group (gdbarch, group_name, &result)) | 
|  | error (_("Unknown syscall group '%s'."), group_name); | 
|  | } | 
|  | else | 
|  | { | 
|  | /* We have a name.  Let's check if it's valid and fetch a | 
|  | list of matching numbers.  */ | 
|  | if (!get_syscalls_by_name (gdbarch, cur_name, &result)) | 
|  | /* Here we have to issue an error instead of a warning, | 
|  | because GDB cannot do anything useful if there's no | 
|  | syscall number to be caught.  */ | 
|  | error (_("Unknown syscall name '%s'."), cur_name); | 
|  | } | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /* Implement the "catch syscall" command.  */ | 
|  |  | 
|  | static void | 
|  | catch_syscall_command_1 (const char *arg, int from_tty, | 
|  | struct cmd_list_element *command) | 
|  | { | 
|  | int tempflag; | 
|  | std::vector<int> filter; | 
|  | struct syscall s; | 
|  | struct gdbarch *gdbarch = get_current_arch (); | 
|  |  | 
|  | /* Checking if the feature if supported.  */ | 
|  | if (gdbarch_get_syscall_number_p (gdbarch) == 0) | 
|  | error (_("The feature 'catch syscall' is not supported on \ | 
|  | this architecture yet.")); | 
|  |  | 
|  | tempflag = command->context () == CATCH_TEMPORARY; | 
|  |  | 
|  | arg = skip_spaces (arg); | 
|  |  | 
|  | /* We need to do this first "dummy" translation in order | 
|  | to get the syscall XML file loaded or, most important, | 
|  | to display a warning to the user if there's no XML file | 
|  | for his/her architecture.  */ | 
|  | get_syscall_by_number (gdbarch, 0, &s); | 
|  |  | 
|  | /* The allowed syntax is: | 
|  | catch syscall | 
|  | catch syscall <name | number> [<name | number> ... <name | number>] | 
|  |  | 
|  | Let's check if there's a syscall name.  */ | 
|  |  | 
|  | if (arg != NULL) | 
|  | filter = catch_syscall_split_args (arg); | 
|  |  | 
|  | create_syscall_event_catchpoint (tempflag, std::move (filter)); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Returns 0 if 'bp' is NOT a syscall catchpoint, | 
|  | non-zero otherwise.  */ | 
|  | static int | 
|  | is_syscall_catchpoint_enabled (struct breakpoint *bp) | 
|  | { | 
|  | if (syscall_catchpoint_p (bp) | 
|  | && bp->enable_state != bp_disabled | 
|  | && bp->enable_state != bp_call_disabled) | 
|  | return 1; | 
|  | else | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* See breakpoint.h.  */ | 
|  |  | 
|  | bool | 
|  | catch_syscall_enabled () | 
|  | { | 
|  | struct catch_syscall_inferior_data *inf_data | 
|  | = get_catch_syscall_inferior_data (current_inferior ()); | 
|  |  | 
|  | return inf_data->total_syscalls_count != 0; | 
|  | } | 
|  |  | 
|  | /* Helper function for catching_syscall_number.  return true if B is a syscall | 
|  | catchpoint for SYSCALL_NUMBER, else false.  */ | 
|  |  | 
|  | static bool | 
|  | catching_syscall_number_1 (struct breakpoint *b, int syscall_number) | 
|  | { | 
|  | if (is_syscall_catchpoint_enabled (b)) | 
|  | { | 
|  | syscall_catchpoint *c | 
|  | = gdb::checked_static_cast<syscall_catchpoint *> (b); | 
|  |  | 
|  | if (!c->syscalls_to_be_caught.empty ()) | 
|  | { | 
|  | for (int iter : c->syscalls_to_be_caught) | 
|  | if (syscall_number == iter) | 
|  | return true; | 
|  | } | 
|  | else | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool | 
|  | catching_syscall_number (int syscall_number) | 
|  | { | 
|  | for (breakpoint &b : all_breakpoints ()) | 
|  | if (catching_syscall_number_1 (&b, syscall_number)) | 
|  | return true; | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* Complete syscall names.  Used by "catch syscall".  */ | 
|  |  | 
|  | static void | 
|  | catch_syscall_completer (struct cmd_list_element *cmd, | 
|  | completion_tracker &tracker, | 
|  | const char *text, const char *word) | 
|  | { | 
|  | struct gdbarch *gdbarch = get_current_arch (); | 
|  | gdb::unique_xmalloc_ptr<const char *> group_list; | 
|  | const char *prefix; | 
|  |  | 
|  | /* Completion considers ':' to be a word separator, so we use this to | 
|  | verify whether the previous word was a group prefix.  If so, we | 
|  | build the completion list using group names only.  */ | 
|  | for (prefix = word; prefix != text && prefix[-1] != ' '; prefix--) | 
|  | ; | 
|  |  | 
|  | if (startswith (prefix, "g:") || startswith (prefix, "group:")) | 
|  | { | 
|  | /* Perform completion inside 'group:' namespace only.  */ | 
|  | group_list.reset (get_syscall_group_names (gdbarch)); | 
|  | if (group_list != NULL) | 
|  | complete_on_enum (tracker, group_list.get (), word, word); | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Complete with both, syscall names and groups.  */ | 
|  | gdb::unique_xmalloc_ptr<const char *> syscall_list | 
|  | (get_syscall_names (gdbarch)); | 
|  | group_list.reset (get_syscall_group_names (gdbarch)); | 
|  |  | 
|  | const char **group_ptr = group_list.get (); | 
|  |  | 
|  | /* Hold on to strings while we're using them.  */ | 
|  | std::vector<std::string> holders; | 
|  |  | 
|  | /* Append "group:" prefix to syscall groups.  */ | 
|  | for (int i = 0; group_ptr[i] != NULL; i++) | 
|  | holders.push_back (string_printf ("group:%s", group_ptr[i])); | 
|  |  | 
|  | for (int i = 0; group_ptr[i] != NULL; i++) | 
|  | group_ptr[i] = holders[i].c_str (); | 
|  |  | 
|  | if (syscall_list != NULL) | 
|  | complete_on_enum (tracker, syscall_list.get (), word, word); | 
|  | if (group_list != NULL) | 
|  | complete_on_enum (tracker, group_ptr, word, word); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | clear_syscall_counts (struct inferior *inf) | 
|  | { | 
|  | struct catch_syscall_inferior_data *inf_data | 
|  | = get_catch_syscall_inferior_data (inf); | 
|  |  | 
|  | inf_data->total_syscalls_count = 0; | 
|  | inf_data->any_syscall_count = 0; | 
|  | inf_data->syscalls_counts.clear (); | 
|  | } | 
|  |  | 
|  | void _initialize_break_catch_syscall (); | 
|  | void | 
|  | _initialize_break_catch_syscall () | 
|  | { | 
|  | gdb::observers::inferior_exit.attach (clear_syscall_counts, | 
|  | "break-catch-syscall"); | 
|  |  | 
|  | add_catch_command ("syscall", _("\ | 
|  | Catch system calls by their names, groups and/or numbers.\n\ | 
|  | Arguments say which system calls to catch.  If no arguments are given,\n\ | 
|  | every system call will be caught.  Arguments, if given, should be one\n\ | 
|  | or more system call names (if your system supports that), system call\n\ | 
|  | groups or system call numbers."), | 
|  | catch_syscall_command_1, | 
|  | catch_syscall_completer, | 
|  | CATCH_PERMANENT, | 
|  | CATCH_TEMPORARY); | 
|  | } |