Here's my first cut at trying to tell how well or how bad we perform
in terms of debug info, that can be dropped into the GCC run-time test
infrastructure and used by means of #include in new tests that add
GUALCHK* annotations (or with separate compilation, if some stuff is
moved into a separate header).

Thoughts, comments, suggestions, tomatoes, eggs? :-)

/* Infrastructure to test the quality of debug information.
   Copyright (C) 2008 Free Software Foundation, Inc.
   Contributed by Alexandre Oliva <[EMAIL PROTECTED]>.

This file is part of GCC.

GCC 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, or (at your option)
any later version.

GCC 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 GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

/* This is a first cut at checking that debug information matches
   run-time.  The idea is to annotate programs with GUALCHK* macros
   that guide the tests.

   In the current implementation, all of the macros expand to function
   calls.  On the one hand, this interferes with optimizations; on the
   other hand, it establishes an optimization barrier and a clear
   inspection point, where previous operations (as in the abstract
   machine) should have been completed and have their effects visible,
   and future operations shouldn't have started yet.

   In the current implementation of guality_check(), we fork a child
   process that runs gdb, attaches to the parent process (the one that
   called guality_check), moves up one stack frame (to the caller of
   guality_check) and then examines the given expression.

   If it matches the expected value, we have a PASS.  If it differs,
   we have a FAILure.  If it is missing, we'll have a FAIL or an
   UNRESOLVED depending on whether the variable or expression might be
   unavailable at that point, as indicated by the third argument.

   We envision a future alternate implementation with two compilation
   and execution cycles, say one that runs the program and uses the
   macros to log expressions and expected values, another in which the
   macros expand to nothing and the logs are used to guide a debug
   session that tests the values.  How to identify the inspection
   points in the second case is yet to be determined.  It is
   recommended that GUALCHK* macros be by themselves in source lines,
   so that __FILE__ and __LINE__ will be usable to identify them.
*/

/* Attach a debugger to the current process and verify that the string
   EXPR, evaluated by the debugger, yields the long long number VAL.
   If the debugger cannot compute the expression, say because the
   variable is unavailable, this will count as an error, unless unkok
   is nonzero.  */

#define GUALCHKXPRVAL(expr, val, unkok) \
  guality_check ((expr), (val), (unkok))

/* Check that a debugger knows that EXPR evaluates to the run-time
   value of EXPR.  Unknown values are marked as acceptable,
   considering that EXPR may die right after this call.  This will
   affect the generated code in that EXPR will be evaluated and forced
   to remain live at least until right before the call to
   guality_check, although not necessarily after the call.  */

#define GUALCHKXPR(expr) \
  GUALCHKXPRVAL (#expr, (long long)(expr), 1)

/* Check that a debugger knows that EXPR evaluates to the run-time
   value of EXPR.  Unknown values are marked as errors, because the
   value of EXPR is forced to be available right after the call, for a
   range of at least one instruction.  This will affect the generated
   code, in that EXPR *will* be evaluated both before and after the
   call to guality_check.  */

#define GUALCHKFLA(expr) do {                           \
    GUALCHKXPRVAL (#expr, (long long)(expr), 0);        \
    guality_forced_live = (long long)(expr);            \
  } while (0)

/* GUALCHK is the simplest way to assert that debug information for an
   expression matches its run-time value.  Whether to force the
   expression live after the call, so as to flag incompleteness
   errors, can be disabled by defining DONT_FORCE_LIVE_AFTER.  */

#ifndef DONT_FORCE_LIVE_AFTER
#define GUALCHK(var) GUALCHKFLA(var)
#else
#define GUALCHK(var) GUALCHKXPR(var)
#endif

/* The program that GDB is going to be told to attach to.  */

static char *guality_program_name;

/* The pid of the process it should attach to, as a string.  */

char guality_pid_str[30];

/* The name of the GDB program.  This is gdb by default, but it can be
   overridden by setting the GUALITY_GDB environment variable.  */

static const char *guality_gdb_name;

/* Kinds of results communicated as exit status from child process
   that runs gdb to the parent process that's being monitored.  */

enum guality_counter { PASS, INCORRECT, INCOMPLETE };

/* Count of passes and errors.  */

static int guality_count[INCOMPLETE+1];

/* If --guality-skip is given in the command line, all the monitoring,
   forking and debugger-attaching action will be disabled.  This is
   useful to run the monitor program within a debugger.  */

static int guality_skip;

/* This variable is holds the last value that was forced live after a
   call.  */

static long long guality_forced_live;

/* This function is the main guality program.  It may actually be
   defined as main, because we #define main to it afterwards.  Because
   of this wrapping, guality_main may not have an empty argument
   list.  */

extern int guality_main (int argc, char *argv[]);

/* Set things up, run guality_main, then print a summary and quit.  */

int
main (int argc, char *argv[])
{
  int i;

  guality_program_name = argv[0];
  sprintf (guality_pid_str, "%i", getpid ());
  guality_gdb_name = getenv ("GUALITY_GDB");
  if (!guality_gdb_name)
    guality_gdb_name = "gdb";

  for (i = 1; i < argc; i++)
    if (strcmp (argv[i], "--guality-skip") == 0)
      guality_skip = 1;
    else
      break;

  argv[--i] = guality_program_name;

  guality_main (argc - i, argv + i);

  i = guality_count[INCORRECT];

  printf ("%s: %i PASS, %i FAIL, %i UNRESOLVED\n",
          i ? "FAIL" : "PASS",
          guality_count[PASS], guality_count[INCORRECT],
          guality_count[INCOMPLETE]);

  return i;
}

#define main guality_main

/* This is run in the child process, to attach gdb to the parent and
   verify that name evaluates to value or unknown.  */

static void
attach (const char *name, long long value, int unknown_ok)
{
  FILE *p;
  const char *cmd_arr[] = {
    guality_gdb_name,
    " -nx -q --batch"
    " --eval-command 'set var loop=0' --eval-command 'up'"
    " --eval-command 'p ", name,
    "' --eval-command detach --eval-command quit ",
    guality_program_name, " ", guality_pid_str
  };
  int i, len;
  long long value_in;
  int base;
  int sign;

  for (i = 0, len = 1; i < sizeof (cmd_arr) / sizeof(*cmd_arr); i++)
    len += strlen (cmd_arr[i]);

  {
    char cmd[len];

    cmd[0] = '\0';
    for (i = 0; i < sizeof (cmd_arr) / sizeof(*cmd_arr); i++)
      strcat (cmd, cmd_arr[i]);

    p = popen (cmd, "r");
  }

  /* Look for "$1 = ".  Assume unknown if we don't find it.  */
  sign = 0;

  while ((i = getc (p)) != '$')
    if (i == EOF)
      goto reached_eof;
  
  if ((i = getc (p)) != '1')
    abort ();

  if ((i = getc (p)) != ' ')
    abort ();

  if ((i = getc (p)) != '=')
    abort ();

  if ((i = getc (p)) != ' ')
    abort ();

  /* Parse the value.  We support decimal, hex led by "0x", with zero
     or more leading "-" signs.  "<unavailable>" is recognized by the
     leading '<'.  "(type)" is recognized by the leading paren, and
     skipped up to a matching paren, that must be followed by a
     blank.  */

  value_in = 0;
  base = 0;
  sign = 1;
  while ((i = getc (p)) != '\n')
    {
      int zero;

      if (i == EOF)
        abort ();

      if (base == 0)
        {
          if (i == '<')
            {
              sign = 0;
              break;
            }
      
          if (i == '(')
            {
              int unmatched = 1;
              while (unmatched && (i = getc (p)) != EOF)
                if (i == '(')
                  unmatched++;
                else if (i == ')')
                  unmatched--;

              if (i == EOF || (i = getc (p)) != ' ')
                abort ();
              continue;
            }

          if (i == '-')
            {
              sign = -sign;
              continue;
            }

          if (i == 'x')
            {
              base = 16;
              continue;
            }

          if (i != '0')
            base = 10;
        }

      zero = '0';

      if (i < '0' || i > '9')
        {
          if (base != 16)
            abort ();

          if (i >= 'A' && i <= 'F')
            zero = 'A' - 10;
          else if (i >= 'a' && i <= 'f')
            zero = 'a' - 10;
          else
            abort ();
        }

      value_in *= base;
      value_in += i - zero;
    }
        
  /* Skip any remaining junk.  */

  while ((i = getc (p)) != EOF)
    ;

 reached_eof:
  pclose (p);

  if (!sign)
    {
      printf ("%s: %s is unavailable, expected %lli\n",
              unknown_ok ? "UNRESOLVED" : "FAIL", name, value);
      exit (unknown_ok ? INCOMPLETE : INCORRECT);
    }

  value_in *= sign;

  if (value != value_in)
    {
      printf ("FAIL: %s is %lli, expected %lli\n", name, value_in, value);
      exit (INCORRECT);
    }

  printf ("PASS: %s is %lli, as expected\n", name, value);
  exit (PASS);
}

/* Fork a child process and attach a debugger to the parent to
   evaluate NAME in the caller.  If it matches VALUE, we have a PASS;
   if it's unknown and UNKNOWN_OK, we have an UNRESOLVED.  Otherwise,
   it's a FAIL.  */

static void __attribute__((noinline))
guality_check (const char *name, long long value, int unknown_ok)
{
  pid_t pid;
  volatile int loop;
  int status;
  
  if (guality_skip)
    return;

  switch ((pid = fork ()))
    {
    case 0:
      attach (name, value, unknown_ok);

    case -1:
      abort ();

    default:
      break;
    }

  loop = 1;
  while (loop)
    ;

  if (wait (&status) != pid)
    abort ();

  if (!WIFEXITED (status))
    abort ();

  switch (WEXITSTATUS (status))
    {
    case PASS:
    case INCORRECT:
    case INCOMPLETE:
      ++guality_count[WEXITSTATUS (status)];
      break;

    default:
      abort ();
    }
}

#ifdef GUALCHK_SELF_TEST
/* Some silly sanity checking.  */

int
main (int argc, char *argv[])
{
  int i = argc+1;
  int j = argc-2;
  int k = 5;

  GUALCHK (argc);
  GUALCHK (&i);
  GUALCHK (i);
  GUALCHK (j);
  GUALCHKXPRVAL ("0x40", 64, 0);
  GUALCHKXPRVAL ("k", 5, 1);
}
#endif
-- 
Alexandre Oliva         http://www.lsd.ic.unicamp.br/~oliva/
Free Software Evangelist  [EMAIL PROTECTED], gnu.org}
FSFLA Board Member       ¡Sé Libre! => http://www.fsfla.org/
Red Hat Compiler Engineer   [EMAIL PROTECTED], gcc.gnu.org}

Reply via email to