| /* Everything about syscall catchpoints, for GDB. | 
 |  | 
 |    Copyright (C) 2009-2022 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 "defs.h" | 
 | #include <ctype.h> | 
 | #include "breakpoint.h" | 
 | #include "gdbcmd.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 (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 struct inferior_key<struct 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; | 
 |   struct breakpoint *b = bs->breakpoint_at; | 
 |   /* 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; | 
 |   struct gdbarch *gdbarch = b->gdbarch; | 
 |  | 
 |   get_last_target_status (nullptr, nullptr, &last); | 
 |  | 
 |   get_syscall_by_number (gdbarch, last.syscall_number (), &s); | 
 |  | 
 |   annotate_catchpoint (b->number); | 
 |   maybe_print_thread_hit_breakpoint (uiout); | 
 |  | 
 |   if (b->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 (b->disposition)); | 
 |     } | 
 |   uiout->field_signed ("bkptno", b->number); | 
 |  | 
 |   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 (bp_location **last_loc) const | 
 | { | 
 |   struct value_print_options opts; | 
 |   struct ui_out *uiout = current_uiout; | 
 |   struct gdbarch *gdbarch = loc->owner->gdbarch; | 
 |  | 
 |   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 | 
 | { | 
 |   struct gdbarch *gdbarch = loc->owner->gdbarch; | 
 |  | 
 |   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 | 
 | { | 
 |   struct gdbarch *gdbarch = loc->gdbarch; | 
 |  | 
 |   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; | 
 |   struct gdbarch *gdbarch = target_gdbarch (); | 
 |  | 
 |   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; | 
 | } | 
 |  | 
 | int | 
 | catch_syscall_enabled (void) | 
 | { | 
 |   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)) | 
 |     { | 
 |       struct syscall_catchpoint *c = (struct 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); | 
 | } |