This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] Add patch for debugging compiler ICEs.


Hi,

A years ago there was a discussion (https://gcc.gnu.org/ml/gcc-patches/2004-01/msg02437.html) about debugging compiler ICEs that resulted in a patch from Jakub, which dumps useful information into temporary file, but for some reasons this patch wasn't applied to trunk.

This is the resurrected patch with added GCC version information into generated repro file.

I've updated the patch that I've posted earlier (https://gcc.gnu.org/ml/gcc-patches/2014-07/msg01649.html ) according to recent upstream discussion (https://gcc.gnu.org/ml/gcc-patches/2014-08/msg00020.html).

The debugging functionality is disabled by default and can be enabled with adding -freport-bug into compile options. It can be also enabled by default with
--with-spec during GCC build.

There are several directions in which this can be improved e.g:

1) more user-friendly ways to report bugs (autosubmitting to Bugzilla, etc.)

2) generate repro in case of segfault.

but having basic functionality (autogenerating reprocase in temprorary file) already seems quite useful.

-Maxim
2014-08-04  Jakub Jelinek  <jakub@redhat.com>
	      Max Ostapenko  <m.ostapenko@partner.samsung.com>

	* common.opt: New option.
	* doc/invoke.texi: Describe new option.
	* diagnostic.c (diagnostic_action_after_output): Exit with
	ICE_EXIT_CODE instead of FATAL_EXIT_CODE.
	* gcc.c (execute): Don't free first string early, but at the end
	of the function.  Call retry_ice if compiler exited with
	ICE_EXIT_CODE.
	(main): Factor out common code.
	(print_configuration): New function.
	(try_fork): Likewise.
	(redirect_stdout_stderr): Likewise.
	(files_equal_p): Likewise.
	(check_repro): Likewise.
	(run_attempt): Likewise.
	(do_report_bug): Likewise.
	(append_text): Likewise.
	(try_generate_repro): Likewise

diff --git a/gcc/common.opt b/gcc/common.opt
index 0c4f86b..aa79250 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1120,6 +1120,11 @@ fdump-noaddr
 Common Report Var(flag_dump_noaddr)
 Suppress output of addresses in debugging dumps
 
+freport-bug
+Common Driver Var(flag_report_bug)
+Collect and dump debug information into temporary file if ICE in C/C++
+compiler occured.
+
 fdump-passes
 Common Var(flag_dump_passes) Init(0)
 Dump optimization passes
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index 0cc7593..67b8c5b 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -492,7 +492,7 @@ diagnostic_action_after_output (diagnostic_context *context,
 	real_abort ();
       diagnostic_finish (context);
       fnotice (stderr, "compilation terminated.\n");
-      exit (FATAL_EXIT_CODE);
+      exit (ICE_EXIT_CODE);
 
     default:
       gcc_unreachable ();
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 4f327df..dafb573 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -6271,6 +6271,11 @@ feasible to use diff on debugging dumps for compiler invocations with
 different compiler binaries and/or different
 text / bss / data / heap / stack / dso start locations.
 
+@item -freport-bug
+@opindex freport-bug
+Collect and dump debug information into temporary file if ICE in C/C++
+compiler occured.
+
 @item -fdump-unnumbered
 @opindex fdump-unnumbered
 When doing debugging dumps, suppress instruction numbers and address output.
diff --git a/gcc/gcc.c b/gcc/gcc.c
index 44d0416..f7a56d1 100644
--- a/gcc/gcc.c
+++ b/gcc/gcc.c
@@ -43,6 +43,13 @@ compilation is specified by a string called a "spec".  */
 #include "params.h"
 #include "vec.h"
 #include "filenames.h"
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if !(defined (__MSDOS__) || defined (OS2) || defined (VMS))
+#define RETRY_ICE_SUPPORTED
+#endif
 
 /* By default there is no special suffix for target executables.  */
 /* FIXME: when autoconf is fixed, remove the host check - dj */
@@ -253,6 +260,9 @@ static void init_gcc_specs (struct obstack *, const char *, const char *,
 static const char *convert_filename (const char *, int, int);
 #endif
 
+#ifdef RETRY_ICE_SUPPORTED
+static void try_generate_repro (const char *prog, const char **argv);
+#endif
 static const char *getenv_spec_function (int, const char **);
 static const char *if_exists_spec_function (int, const char **);
 static const char *if_exists_else_spec_function (int, const char **);
@@ -2849,7 +2859,7 @@ execute (void)
 	    }
 	}
 
-      if (string != commands[i].prog)
+      if (i && string != commands[i].prog)
 	free (CONST_CAST (char *, string));
     }
 
@@ -2902,6 +2912,17 @@ execute (void)
 	else if (WIFEXITED (status)
 		 && WEXITSTATUS (status) >= MIN_FATAL_STATUS)
 	  {
+#ifdef RETRY_ICE_SUPPORTED
+	    /* For ICEs in cc1, cc1obj, cc1plus see if it is
+	       reproducible or not.  */
+	    const char *p;
+	    if (flag_report_bug
+		&& WEXITSTATUS (status) == ICE_EXIT_CODE
+		&& i == 0
+		&& (p = strrchr (commands[0].argv[0], DIR_SEPARATOR))
+		&& ! strncmp (p + 1, "cc1", 3))
+	      try_generate_repro (commands[0].prog, commands[0].argv);
+#endif
 	    if (WEXITSTATUS (status) > greatest_status)
 	      greatest_status = WEXITSTATUS (status);
 	    ret_code = -1;
@@ -2959,6 +2980,9 @@ execute (void)
 	  }
       }
 
+   if (commands[0].argv[0] != commands[0].prog)
+     free (CONST_CAST (char *, commands[0].argv[0]));
+
     return ret_code;
   }
 }
@@ -6150,6 +6174,342 @@ give_switch (int switchnum, int omit_first_word)
   switches[switchnum].validated = true;
 }
 
+static void
+print_configuration (void)
+{
+  int n;
+  const char *thrmod;
+
+  fnotice (stderr, "Target: %s\n", spec_machine);
+  fnotice (stderr, "Configured with: %s\n", configuration_arguments);
+
+#ifdef THREAD_MODEL_SPEC
+  /* We could have defined THREAD_MODEL_SPEC to "%*" by default,
+  but there's no point in doing all this processing just to get
+  thread_model back.  */
+  obstack_init (&obstack);
+  do_spec_1 (THREAD_MODEL_SPEC, 0, thread_model);
+  obstack_1grow (&obstack, '\0');
+  thrmod = XOBFINISH (&obstack, const char *);
+#else
+  thrmod = thread_model;
+#endif
+
+  fnotice (stderr, "Thread model: %s\n", thrmod);
+
+  /* compiler_version is truncated at the first space when initialized
+  from version string, so truncate version_string at the first space
+  before comparing.  */
+  for (n = 0; version_string[n]; n++)
+    if (version_string[n] == ' ')
+      break;
+
+  if (! strncmp (version_string, compiler_version, n)
+      && compiler_version[n] == 0)
+    fnotice (stderr, "gcc version %s %s\n\n", version_string,
+             pkgversion_string);
+  else
+    fnotice (stderr, "gcc driver version %s %sexecuting gcc version %s\n\n",
+             version_string, pkgversion_string, compiler_version);
+}
+
+#ifdef RETRY_ICE_SUPPORTED
+#define RETRY_ICE_ATTEMPTS 3
+
+static int
+try_fork ()
+{
+  int pid, retries, sleep_interval;
+  sleep_interval = 1;
+  pid = -1;
+  for (retries = 0; retries < 4; retries++)
+    {
+      pid = fork ();
+      if (pid >= 0)
+        break;
+      sleep (sleep_interval);
+      sleep_interval *= 2;
+    }
+  return pid;
+}
+
+
+static void
+redirect_stdout_stderr (const char *out_temp, const char *err_temp,
+                        int append)
+{
+  int fd;
+  fd = open (out_temp, append ? O_RDWR | O_APPEND : O_RDWR);
+
+  if (fd < 0)
+    exit (-1);
+
+  close (STDOUT_FILENO);
+  dup (fd);
+  close (fd);
+
+  fd = open (err_temp, append ? O_RDWR | O_APPEND : O_RDWR);
+
+  if (fd < 0)
+    exit (-1);
+
+  close (STDERR_FILENO);
+  dup (fd);
+  close (fd);
+}
+
+static int
+files_equal_p (char *file1, char *file2, char *buf, const int bufsize)
+{
+  struct stat st1, st2;
+  size_t n, len;
+  int fd1, fd2;
+
+  fd1 = open (file1, O_RDONLY);
+  fd2 = open (file2, O_RDONLY);
+
+  if (fd1 < 0 || fd2 < 0)
+    goto error;
+
+  if (fstat (fd1, &st1) < 0 || fstat (fd2, &st2) < 0)
+    goto error;
+
+  if (st1.st_size != st2.st_size)
+    goto error;
+
+  for (n = st1.st_size; n; n -= len)
+    {
+      len = n;
+      if ((int) len > bufsize / 2)
+        len = bufsize / 2;
+
+      if (read (fd1, buf, len) != (int) len
+          || read (fd2, buf + bufsize / 2, len) != (int) len)
+        {
+          goto error;
+        }
+
+      if (memcmp (buf, buf + bufsize / 2, len) != 0)
+        goto error;
+    }
+
+  close (fd1);
+  close (fd2);
+
+  return 1;
+
+error:
+  close (fd1);
+  close (fd2);
+  return 0;
+}
+
+static int
+check_repro (char **temp_stdout_files, char **temp_stderr_files)
+{
+  int i;
+  const int ice_bufsize = 8192;
+  char *ice_buf = XNEWVEC (char, ice_bufsize);
+  for (i = 0; i < RETRY_ICE_ATTEMPTS - 2; ++i)
+    {
+     if (!files_equal_p (temp_stdout_files[i], temp_stdout_files[i + 1],
+                        ice_buf, ice_bufsize)
+         || !files_equal_p (temp_stderr_files[i], temp_stderr_files[i + 1],
+                          ice_buf, ice_bufsize))
+        {
+          fnotice (stderr, "The bug is not reproducible, so it is"
+                   " likely a hardware or OS problem.\n");
+          break;
+        }
+    }
+  free (ice_buf);
+  return i == RETRY_ICE_ATTEMPTS - 2;
+}
+
+enum attempt_status {
+  ATTEMPT_STATUS_FAIL_TO_RUN,
+  ATTEMPT_STATUS_SUCCESS,
+  ATTEMPT_STATUS_ICE
+};
+
+static enum attempt_status
+run_attempt (const char *prog, const char **new_argv, const char *out_temp,
+             const char *err_temp, int emit_system_info, int append)
+{
+  int status;
+  int pid = try_fork ();
+  if (pid < 0)
+    return ATTEMPT_STATUS_FAIL_TO_RUN;
+  else if (pid == 0)
+    {
+      redirect_stdout_stderr (out_temp, err_temp, append);
+
+      if (emit_system_info)
+        print_configuration ();
+
+      if (prog == new_argv[0])
+        execvp (prog, CONST_CAST2 (char *const *, const char **, new_argv));
+      else
+        execv (new_argv[0], CONST_CAST2 (char *const *, const char **,
+               new_argv));
+      exit (-1);
+    }
+
+  if (waitpid (pid, &status, 0) < 0)
+    return ATTEMPT_STATUS_FAIL_TO_RUN;
+
+  switch (WEXITSTATUS (status))
+    {
+      case ICE_EXIT_CODE:
+        return ATTEMPT_STATUS_ICE;
+
+      case SUCCESS_EXIT_CODE:
+        return ATTEMPT_STATUS_SUCCESS;
+
+      default:
+        return ATTEMPT_STATUS_FAIL_TO_RUN;
+    }
+}
+
+static void
+do_report_bug (const char *prog, const char **new_argv,
+                            const int nargs, char **out_file, char **err_file)
+{
+  int i, status;
+  int fd = open (*out_file, O_RDWR | O_APPEND);
+  if (fd < 0)
+    return;
+  write (fd, "\n//", 3);
+  for (i = 0; i < nargs; i++)
+    {
+      write (fd, " ", 1);
+      write (fd, new_argv[i], strlen (new_argv[i]));
+    }
+  write (fd, "\n\n", 2);
+  close (fd);
+  new_argv[nargs] = "-E";
+  new_argv[nargs + 1] = NULL;
+
+  status = run_attempt (prog, new_argv, *out_file, *err_file, 0, 1);
+
+  if (status == ATTEMPT_STATUS_SUCCESS)
+    {
+      fnotice (stderr, "Preprocessed source stored into %s file,"
+               " please attach this to your bugreport.\n",
+               *out_file);
+      /* Make sure it is not deleted.  */
+      free (*out_file);
+      *out_file = NULL;
+    }
+}
+
+static void
+append_text (char *file, const char *str)
+{
+  int fd = open (file, O_RDWR | O_APPEND);
+  if (fd < 0)
+    return;
+
+  write (fd, str, strlen (str));
+  close (fd);
+}
+
+static void
+try_generate_repro (const char *prog, const char **argv)
+{
+  int i, nargs, out_arg = -1, quiet = 0, attempt;
+  const char **new_argv;
+  char *temp_files[RETRY_ICE_ATTEMPTS * 2];
+  char **temp_stdout_files = &temp_files[0];
+  char **temp_stderr_files = &temp_files[RETRY_ICE_ATTEMPTS];
+
+  if (gcc_input_filename == NULL || ! strcmp (gcc_input_filename, "-"))
+    return;
+
+  for (nargs = 0; argv[nargs] != NULL; ++nargs)
+    /* Only retry compiler ICEs, not preprocessor ones.  */
+    if (! strcmp (argv[nargs], "-E"))
+      return;
+    else if (argv[nargs][0] == '-' && argv[nargs][1] == 'o')
+      {
+        if (out_arg == -1)
+          out_arg = nargs;
+        else
+          return;
+      }
+    /* If the compiler is going to output any time information,
+       it might varry between invocations.  */
+    else if (! strcmp (argv[nargs], "-quiet"))
+      quiet = 1;
+    else if (! strcmp (argv[nargs], "-ftime-report"))
+      return;
+
+  if (out_arg == -1 || !quiet)
+    return;
+
+  memset (temp_files, '\0', sizeof (temp_files));
+  new_argv = XALLOCAVEC (const char *, nargs + 4);
+  memcpy (new_argv, argv, (nargs + 1) * sizeof (const char *));
+  new_argv[nargs++] = "-frandom-seed=0";
+  new_argv[nargs++] = "-fdump-noaddr";
+  new_argv[nargs] = NULL;
+  if (new_argv[out_arg][2] == '\0')
+    new_argv[out_arg + 1] = "-";
+  else
+    new_argv[out_arg] = "-o-";
+
+  int status;
+  for (attempt = 0; attempt < RETRY_ICE_ATTEMPTS; ++attempt)
+    {
+      int emit_system_info = 0;
+      int append = 0;
+      temp_stdout_files[attempt] = make_temp_file (".out");
+      temp_stderr_files[attempt] = make_temp_file (".err");
+
+      if (attempt == RETRY_ICE_ATTEMPTS - 1)
+        {
+          append = 1;
+          emit_system_info = 1;
+        }
+
+      if (emit_system_info)
+        append_text (temp_stderr_files[attempt], "/*\n");
+
+      /* Fork a subprocess; wait and retry if it fails.  */
+      status = run_attempt (prog, new_argv, temp_stdout_files[attempt],
+                            temp_stderr_files[attempt], emit_system_info,
+                            append);
+
+      if (emit_system_info)
+        append_text (temp_stderr_files[attempt], "*/\n");
+
+      if (status != ATTEMPT_STATUS_ICE)
+        {
+          fnotice (stderr, "The bug is not reproducible, so it is"
+                   " likely a hardware or OS problem.\n");
+          goto out;
+        }
+    }
+
+  if (!check_repro (temp_stdout_files, temp_stderr_files))
+    goto out;
+
+  /* In final attempt we append cc1 options and preprocesssed code to last
+     generated .err file with configuration and backtrace.  */
+  do_report_bug (prog, new_argv, nargs,
+                              &temp_stderr_files[RETRY_ICE_ATTEMPTS - 1],
+                              &temp_stdout_files[RETRY_ICE_ATTEMPTS - 1]);
+
+out:
+  for (i = 0; i < RETRY_ICE_ATTEMPTS * 2; i++)
+    if (temp_files[i])
+      {
+        unlink (temp_stdout_files[i]);
+        free (temp_stdout_files[i]);
+      }
+}
+#endif
+
 /* Search for a file named NAME trying various prefixes including the
    user's -B prefix and some standard ones.
    Return the absolute file name found.  If nothing is found, return NAME.  */
@@ -6919,43 +7279,9 @@ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"
 
   if (verbose_flag)
     {
-      int n;
-      const char *thrmod;
-
-      fnotice (stderr, "Target: %s\n", spec_machine);
-      fnotice (stderr, "Configured with: %s\n", configuration_arguments);
-
-#ifdef THREAD_MODEL_SPEC
-      /* We could have defined THREAD_MODEL_SPEC to "%*" by default,
-	 but there's no point in doing all this processing just to get
-	 thread_model back.  */
-      obstack_init (&obstack);
-      do_spec_1 (THREAD_MODEL_SPEC, 0, thread_model);
-      obstack_1grow (&obstack, '\0');
-      thrmod = XOBFINISH (&obstack, const char *);
-#else
-      thrmod = thread_model;
-#endif
-
-      fnotice (stderr, "Thread model: %s\n", thrmod);
-
-      /* compiler_version is truncated at the first space when initialized
-	 from version string, so truncate version_string at the first space
-	 before comparing.  */
-      for (n = 0; version_string[n]; n++)
-	if (version_string[n] == ' ')
-	  break;
-
-      if (! strncmp (version_string, compiler_version, n)
-	  && compiler_version[n] == 0)
-	fnotice (stderr, "gcc version %s %s\n", version_string,
-		 pkgversion_string);
-      else
-	fnotice (stderr, "gcc driver version %s %sexecuting gcc version %s\n",
-		 version_string, pkgversion_string, compiler_version);
-
+      print_configuration ();
       if (n_infiles == 0)
-	return (0);
+        return (0);
     }
 
   if (n_infiles == added_libraries)

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]