Initial revision


git-svn-id: svn://svn.valgrind.org/valgrind/trunk@2 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/coregrind/vg_errcontext.c b/coregrind/vg_errcontext.c
new file mode 100644
index 0000000..42e09b5
--- /dev/null
+++ b/coregrind/vg_errcontext.c
@@ -0,0 +1,1070 @@
+
+/*--------------------------------------------------------------------*/
+/*--- Management of error messages.                vg_errcontext.c ---*/
+/*--------------------------------------------------------------------*/
+
+/*
+   This file is part of Valgrind, an x86 protected-mode emulator 
+   designed for debugging and profiling binaries on x86-Unixes.
+
+   Copyright (C) 2000-2002 Julian Seward 
+      jseward@acm.org
+      Julian_Seward@muraroa.demon.co.uk
+
+   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 2 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, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307, USA.
+
+   The GNU General Public License is contained in the file LICENSE.
+*/
+
+#include "vg_include.h"
+#include "vg_constants.h"
+
+
+/*------------------------------------------------------------*/
+/*--- Defns                                                ---*/
+/*------------------------------------------------------------*/
+
+/* Suppression is a type describing an error which we want to
+   suppress, ie, not show the user, usually because it is caused by a
+   problem in a library which we can't fix, replace or work around.
+   Suppressions are read from a file at startup time, specified by
+   vg_clo_suppressions, and placed in the vg_suppressions list.  This
+   gives flexibility so that new suppressions can be added to the file
+   as and when needed. 
+*/
+typedef 
+   enum { 
+      /* Bad syscall params */
+      Param, 
+      /* Use of invalid values of given size */
+      Value0, Value1, Value2, Value4, Value8, 
+      /* Invalid read/write attempt at given size */
+      Addr1, Addr2, Addr4, Addr8,
+      /* Invalid or mismatching free */
+      FreeS
+   } 
+   SuppressionKind;
+
+
+/* For each caller specified for a suppression, record the nature of
+   the caller name. */
+typedef
+   enum { 
+      /* Name is of an shared object file. */
+      ObjName,
+      /* Name is of a function. */
+      FunName 
+   }
+   SuppressionLocTy;
+
+
+/* A complete suppression record. */
+typedef
+   struct _Suppression {
+      struct _Suppression* next;
+      /* The number of times this error has been suppressed. */
+      Int count;
+      /* The name by which the suppression is referred to. */
+      Char* sname;
+      /* What kind of suppression. */
+      SuppressionKind skind;
+      /* Name of syscall param if skind==Param */
+      Char* param;
+      /* Name of fn where err occurs, and immediate caller (mandatory). */
+      SuppressionLocTy caller0_ty;
+      Char*            caller0;
+      SuppressionLocTy caller1_ty;
+      Char*            caller1;
+      /* Optional extra callers. */
+      SuppressionLocTy caller2_ty;
+      Char*            caller2;
+      SuppressionLocTy caller3_ty;
+      Char*            caller3;
+   } 
+   Suppression;
+
+
+/* ErrContext is a type for recording just enough info to generate an
+   error report for an illegal memory access.  The idea is that
+   (typically) the same few points in the program generate thousands
+   of illegal accesses, and we don't want to spew out a fresh error
+   message for each one.  Instead, we use these structures to common
+   up duplicates.  
+*/
+
+/* What kind of error it is. */
+typedef 
+   enum { ValueErr, AddrErr, 
+          ParamErr, UserErr, /* behaves like an anonymous ParamErr */
+          FreeErr, FreeMismatchErr }
+   ErrKind;
+
+/* What kind of memory access is involved in the error? */
+typedef
+   enum { ReadAxs, WriteAxs, ExecAxs }
+   AxsKind;
+
+/* Top-level struct for recording errors. */
+typedef
+   struct _ErrContext {
+      /* ALL */
+      struct _ErrContext* next;
+      /* ALL */
+      /* NULL if unsuppressed; or ptr to suppression record. */
+      Suppression* supp;
+      /* ALL */
+      Int count;
+      /* ALL */
+      ErrKind ekind;
+      /* ALL */
+      ExeContext* where;
+      /* Addr */
+      AxsKind axskind;
+      /* Addr, Value */
+      Int size;
+      /* Addr, Free, Param, User */
+      Addr addr;
+      /* Addr, Free, Param, User */
+      AddrInfo addrinfo;
+      /* Param */
+      Char* syscall_param;
+      /* Param, User */
+      Bool isWriteableLack;
+   } 
+   ErrContext;
+
+/* The list of error contexts found, both suppressed and unsuppressed.
+   Initially empty, and grows as errors are detected. */
+static ErrContext* vg_err_contexts = NULL;
+
+/* The list of suppression directives, as read from the specified
+   suppressions file. */
+static Suppression* vg_suppressions = NULL;
+
+/* Running count of unsuppressed errors detected. */
+static UInt vg_n_errs_found = 0;
+
+/* Running count of suppressed errors detected. */
+static UInt vg_n_errs_suppressed = 0;
+
+/* forwards ... */
+static Suppression* is_suppressible_error ( ErrContext* ec );
+
+
+/*------------------------------------------------------------*/
+/*--- Helper fns                                           ---*/
+/*------------------------------------------------------------*/
+
+
+static void clear_AddrInfo ( AddrInfo* ai )
+{
+   ai->akind      = Unknown;
+   ai->blksize    = 0;
+   ai->rwoffset   = 0;
+   ai->lastchange = NULL;
+}
+
+static void clear_ErrContext ( ErrContext* ec )
+{
+   ec->next    = NULL;
+   ec->supp    = NULL;
+   ec->count   = 0;
+   ec->ekind   = ValueErr;
+   ec->where   = NULL;
+   ec->axskind = ReadAxs;
+   ec->size    = 0;
+   ec->addr    = 0;
+   clear_AddrInfo ( &ec->addrinfo );
+   ec->syscall_param   = NULL;
+   ec->isWriteableLack = False;
+}
+
+
+static __inline__
+Bool vg_eq_ExeContext ( Bool top_2_only,
+                        ExeContext* e1, ExeContext* e2 )
+{
+   /* Note that frames after the 4th are always ignored. */
+   if (top_2_only) {
+      return VG_(eq_ExeContext_top2(e1, e2));
+   } else {
+      return VG_(eq_ExeContext_top4(e1, e2));
+   }
+}
+
+
+static Bool eq_AddrInfo ( Bool cheap_addr_cmp,
+                          AddrInfo* ai1, AddrInfo* ai2 )
+{
+   if (ai1->akind != ai2->akind) 
+      return False;
+   if (ai1->akind == Freed || ai1->akind == Mallocd) {
+      if (ai1->blksize != ai2->blksize)
+         return False;
+      if (!vg_eq_ExeContext(cheap_addr_cmp, 
+                            ai1->lastchange, ai2->lastchange))
+         return False;
+   }
+   return True;
+}
+
+/* Compare error contexts, to detect duplicates.  Note that if they
+   are otherwise the same, the faulting addrs and associated rwoffsets
+   are allowed to be different.  */
+
+static Bool eq_ErrContext ( Bool cheap_addr_cmp,
+                            ErrContext* e1, ErrContext* e2 )
+{
+   if (e1->ekind != e2->ekind) 
+      return False;
+   if (!vg_eq_ExeContext(cheap_addr_cmp, e1->where, e2->where))
+      return False;
+
+   switch (e1->ekind) {
+      case UserErr:
+      case ParamErr:
+         if (e1->isWriteableLack != e2->isWriteableLack) return False;
+         if (e1->ekind == ParamErr 
+             && 0 != VG_(strcmp)(e1->syscall_param, e2->syscall_param))
+            return False;
+         return True;
+      case FreeErr:
+      case FreeMismatchErr:
+         if (e1->addr != e2->addr) return False;
+         if (!eq_AddrInfo(cheap_addr_cmp, &e1->addrinfo, &e2->addrinfo)) 
+            return False;
+         return True;
+      case AddrErr:
+         if (e1->axskind != e2->axskind) return False;
+         if (e1->size != e2->size) return False;
+         if (!eq_AddrInfo(cheap_addr_cmp, &e1->addrinfo, &e2->addrinfo)) 
+            return False;
+         return True;
+      case ValueErr:
+         if (e1->size != e2->size) return False;
+         return True;
+      default: 
+         VG_(panic)("eq_ErrContext");
+   }
+}
+
+static void pp_AddrInfo ( Addr a, AddrInfo* ai )
+{
+   switch (ai->akind) {
+      case Stack: 
+         VG_(message)(Vg_UserMsg, "   Address 0x%x is on the stack", a);
+         break;
+      case Unknown:
+         VG_(message)(Vg_UserMsg, 
+                      "   Address 0x%x is not stack'd, malloc'd or free'd", a);
+         break;
+      case Freed: case Mallocd: case UserG: case UserS: {
+         UInt delta;
+         UChar* relative;
+         if (ai->rwoffset < 0) {
+            delta    = (UInt)(- ai->rwoffset);
+            relative = "before";
+         } else if (ai->rwoffset >= ai->blksize) {
+            delta    = ai->rwoffset - ai->blksize;
+            relative = "after";
+         } else {
+            delta    = ai->rwoffset;
+            relative = "inside";
+         }
+         if (ai->akind == UserS) {
+            VG_(message)(Vg_UserMsg, 
+               "   Address 0x%x is %d bytes %s a %d-byte stack red-zone created",
+               a, delta, relative, 
+               ai->blksize );
+	 } else {
+            VG_(message)(Vg_UserMsg, 
+               "   Address 0x%x is %d bytes %s a block of size %d %s",
+               a, delta, relative, 
+               ai->blksize,
+               ai->akind==Mallocd ? "alloc'd" 
+                  : ai->akind==Freed ? "free'd" 
+                                     : "client-defined");
+         }
+         VG_(pp_ExeContext)(ai->lastchange);
+         break;
+      }
+      default:
+         VG_(panic)("pp_AddrInfo");
+   }
+}
+
+static void pp_ErrContext ( ErrContext* ec, Bool printCount )
+{
+   if (printCount)
+      VG_(message)(Vg_UserMsg, "Observed %d times:", ec->count );
+   switch (ec->ekind) {
+      case ValueErr:
+         if (ec->size == 0) {
+             VG_(message)(Vg_UserMsg,
+                          "Use of uninitialised CPU condition code");
+         } else {
+             VG_(message)(Vg_UserMsg,
+                          "Use of uninitialised value of size %d",
+                          ec->size);
+         }
+         VG_(pp_ExeContext)(ec->where);
+         break;
+      case AddrErr:
+         switch (ec->axskind) {
+            case ReadAxs:
+               VG_(message)(Vg_UserMsg, "Invalid read of size %d", 
+                                        ec->size ); 
+               break;
+            case WriteAxs:
+               VG_(message)(Vg_UserMsg, "Invalid write of size %d", 
+                                        ec->size ); 
+               break;
+            case ExecAxs:
+               VG_(message)(Vg_UserMsg, "Jump to the invalid address "
+                                        "stated on the next line");
+               break;
+            default: 
+               VG_(panic)("pp_ErrContext(axskind)");
+         }
+         VG_(pp_ExeContext)(ec->where);
+         pp_AddrInfo(ec->addr, &ec->addrinfo);
+         break;
+      case FreeErr:
+         VG_(message)(Vg_UserMsg,"Invalid free() / delete / delete[]");
+         /* fall through */
+      case FreeMismatchErr:
+         if (ec->ekind == FreeMismatchErr)
+            VG_(message)(Vg_UserMsg, 
+                         "Mismatched free() / delete / delete []");
+         VG_(pp_ExeContext)(ec->where);
+         pp_AddrInfo(ec->addr, &ec->addrinfo);
+         break;
+      case ParamErr:
+         if (ec->isWriteableLack) {
+            VG_(message)(Vg_UserMsg, 
+               "Syscall param %s contains unaddressable byte(s)",
+                ec->syscall_param );
+         } else {
+            VG_(message)(Vg_UserMsg, 
+                "Syscall param %s contains uninitialised or "
+                "unaddressable byte(s)",
+            ec->syscall_param);
+         }
+         VG_(pp_ExeContext)(ec->where);
+         pp_AddrInfo(ec->addr, &ec->addrinfo);
+         break;
+      case UserErr:
+         if (ec->isWriteableLack) {
+            VG_(message)(Vg_UserMsg, 
+               "Unaddressable byte(s) found during client check request");
+         } else {
+            VG_(message)(Vg_UserMsg, 
+               "Uninitialised or "
+               "unaddressable byte(s) found during client check request");
+         }
+         VG_(pp_ExeContext)(ec->where);
+         pp_AddrInfo(ec->addr, &ec->addrinfo);
+         break;
+      default: 
+         VG_(panic)("pp_ErrContext");
+   }
+}
+
+
+/* Figure out if we want to attach for GDB for this error, possibly
+   by asking the user. */
+static
+Bool vg_is_GDB_attach_requested ( void )
+{
+   Char ch, ch2;
+   Int res;
+
+   if (VG_(clo_GDB_attach) == False)
+      return False;
+
+   VG_(message)(Vg_UserMsg, "");
+
+  again:
+   VG_(printf)(
+      "==%d== "
+      "---- Attach to GDB ? --- [Return/N/n/Y/y/C/c] ---- ", 
+      VG_(getpid)()
+   );
+
+   res = VG_(read)(0 /*stdin*/, &ch, 1);
+   if (res != 1) goto ioerror;
+   /* res == 1 */
+   if (ch == '\n') return False;
+   if (ch != 'N' && ch != 'n' && ch != 'Y' && ch != 'y' 
+      && ch != 'C' && ch != 'c') goto again;
+
+   res = VG_(read)(0 /*stdin*/, &ch2, 1);
+   if (res != 1) goto ioerror;
+   if (ch2 != '\n') goto again;
+
+   /* No, don't want to attach. */
+   if (ch == 'n' || ch == 'N') return False;
+   /* Yes, want to attach. */
+   if (ch == 'y' || ch == 'Y') return True;
+   /* No, don't want to attach, and don't ask again either. */
+   vg_assert(ch == 'c' || ch == 'C');
+
+  ioerror:
+   VG_(clo_GDB_attach) = False;
+   return False;
+}
+
+
+/* Top-level entry point to the error management subsystem.  All
+   detected errors are notified here; this routine decides if/when the
+   user should see the error. */
+static void VG_(maybe_add_context) ( ErrContext* ec )
+{
+   ErrContext* p;
+   ErrContext* p_prev;
+   Bool        cheap_addr_cmp         = False;
+   static Bool is_first_shown_context = True;
+   static Bool stopping_message       = False;
+   static Bool slowdown_message       = False;
+   static Int  vg_n_errs_shown        = 0;
+
+   /* After M_VG_COLLECT_NO_ERRORS_AFTER different errors have been
+      found, just refuse to collect any more. */
+   if (vg_n_errs_shown >= M_VG_COLLECT_NO_ERRORS_AFTER) {
+      if (!stopping_message) {
+         VG_(message)(Vg_UserMsg, "");
+         VG_(message)(Vg_UserMsg, 
+            "More than %d errors detected.  I'm not reporting any more.",
+            M_VG_COLLECT_NO_ERRORS_AFTER);
+         VG_(message)(Vg_UserMsg, 
+            "Final error counts may be inaccurate.  Go fix your program!");
+         VG_(message)(Vg_UserMsg, "");
+         stopping_message = True;
+      }
+      return;
+   }
+
+   /* After M_VG_COLLECT_ERRORS_SLOWLY_AFTER different errors have
+      been found, be much more conservative about collecting new
+      ones. */
+   if (vg_n_errs_shown >= M_VG_COLLECT_ERRORS_SLOWLY_AFTER) {
+      cheap_addr_cmp = True;
+      if (!slowdown_message) {
+         VG_(message)(Vg_UserMsg, "");
+         VG_(message)(Vg_UserMsg, 
+            "More than %d errors detected.  Subsequent errors",
+            M_VG_COLLECT_ERRORS_SLOWLY_AFTER);
+         VG_(message)(Vg_UserMsg, 
+            "will still be recorded, but in less detail than before.");
+         slowdown_message = True;
+      }
+   }
+
+
+   /* First, see if we've got an error record matching this one. */
+   p      = vg_err_contexts;
+   p_prev = NULL;
+   while (p != NULL) {
+      if (eq_ErrContext(cheap_addr_cmp, p, ec)) {
+         /* Found it. */
+         p->count++;
+	 if (p->supp != NULL) {
+            /* Deal correctly with suppressed errors. */
+            p->supp->count++;
+            vg_n_errs_suppressed++;	 
+         } else {
+            vg_n_errs_found++;
+         }
+
+         /* Move p to the front of the list so that future searches
+            for it are faster. */
+         if (p_prev != NULL) {
+            vg_assert(p_prev->next == p);
+            p_prev->next    = p->next;
+            p->next         = vg_err_contexts;
+            vg_err_contexts = p;
+	 }
+         return;
+      }
+      p_prev = p;
+      p      = p->next;
+   }
+
+   /* Didn't see it.  Copy and add. */
+
+   /* OK, we're really going to collect it. */
+
+   p = VG_(malloc)(VG_AR_ERRCTXT, sizeof(ErrContext));
+   *p = *ec;
+   p->next = vg_err_contexts;
+   p->supp = is_suppressible_error(ec);
+   vg_err_contexts = p;
+   if (p->supp == NULL) {
+      vg_n_errs_found++;
+      if (!is_first_shown_context)
+         VG_(message)(Vg_UserMsg, "");
+      pp_ErrContext(p, False);      
+      is_first_shown_context = False;
+      vg_n_errs_shown++;
+      /* Perhaps we want a GDB attach at this point? */
+      if (vg_is_GDB_attach_requested()) {
+         VG_(swizzle_esp_then_start_GDB)();
+      }
+   } else {
+      vg_n_errs_suppressed++;
+      p->supp->count++;
+   }
+}
+
+
+
+
+/*------------------------------------------------------------*/
+/*--- Exported fns                                         ---*/
+/*------------------------------------------------------------*/
+
+void VG_(record_value_error) ( Int size )
+{
+   ErrContext ec;
+   clear_ErrContext( &ec );
+   ec.count = 1;
+   ec.next  = NULL;
+   ec.where = VG_(get_ExeContext)( False );
+   ec.ekind = ValueErr;
+   ec.size  = size;
+   VG_(maybe_add_context) ( &ec );
+}
+
+void VG_(record_address_error) ( Addr a, Int size, Bool isWrite )
+{
+   ErrContext ec;
+
+   /* If this is caused by an access immediately below %ESP, and the
+      user asks nicely, we just ignore it. */
+   if (VG_(clo_workaround_gcc296_bugs) && VG_(is_just_below_ESP)(a))
+      return;
+
+   clear_ErrContext( &ec );
+   ec.count   = 1;
+   ec.next    = NULL;
+   ec.where   = VG_(get_ExeContext)( False );
+   ec.ekind   = AddrErr;
+   ec.axskind = isWrite ? WriteAxs : ReadAxs;
+   ec.size    = size;
+   ec.addr    = a;
+   VG_(describe_addr) ( a, &ec.addrinfo );
+   VG_(maybe_add_context) ( &ec );
+}
+
+void VG_(record_jump_error) ( Addr a )
+{
+   ErrContext ec;
+   clear_ErrContext( &ec );
+   ec.count   = 1;
+   ec.next    = NULL;
+   ec.where   = VG_(get_ExeContext)( False );
+   ec.ekind   = AddrErr;
+   ec.axskind = ExecAxs;
+   ec.addr    = a;
+   VG_(describe_addr) ( a, &ec.addrinfo );
+   VG_(maybe_add_context) ( &ec );
+}
+
+void VG_(record_free_error) ( Addr a )
+{
+   ErrContext ec;
+   clear_ErrContext( &ec );
+   ec.count   = 1;
+   ec.next    = NULL;
+   ec.where   = VG_(get_ExeContext)( True );
+   ec.ekind   = FreeErr;
+   ec.addr    = a;
+   VG_(describe_addr) ( a, &ec.addrinfo );
+   VG_(maybe_add_context) ( &ec );
+}
+
+void VG_(record_freemismatch_error) ( Addr a )
+{
+   ErrContext ec;
+   clear_ErrContext( &ec );
+   ec.count   = 1;
+   ec.next    = NULL;
+   ec.where   = VG_(get_ExeContext)( True );
+   ec.ekind   = FreeMismatchErr;
+   ec.addr    = a;
+   VG_(describe_addr) ( a, &ec.addrinfo );
+   VG_(maybe_add_context) ( &ec );
+}
+
+void VG_(record_param_err) ( Addr a, Bool isWriteLack, Char* msg )
+{
+   ErrContext ec;
+   clear_ErrContext( &ec );
+   ec.count   = 1;
+   ec.next    = NULL;
+   ec.where   = VG_(get_ExeContext)( False );
+   ec.ekind   = ParamErr;
+   ec.addr    = a;
+   VG_(describe_addr) ( a, &ec.addrinfo );
+   ec.syscall_param = msg;
+   ec.isWriteableLack = isWriteLack;
+   VG_(maybe_add_context) ( &ec );
+}
+
+
+void VG_(record_user_err) ( Addr a, Bool isWriteLack )
+{
+   ErrContext ec;
+   clear_ErrContext( &ec );
+   ec.count   = 1;
+   ec.next    = NULL;
+   ec.where   = VG_(get_ExeContext)( False );
+   ec.ekind   = UserErr;
+   ec.addr    = a;
+   VG_(describe_addr) ( a, &ec.addrinfo );
+   ec.isWriteableLack = isWriteLack;
+   VG_(maybe_add_context) ( &ec );
+}
+
+
+void VG_(show_all_errors) ( void )
+{
+   Int         i, n_min;
+   Int         n_err_contexts, n_supp_contexts;
+   ErrContext  *p, *p_min;
+   Suppression *su;
+   Bool        any_supp;
+
+   if (VG_(clo_verbosity) == 0)
+      return;
+
+   n_err_contexts = 0;
+   for (p = vg_err_contexts; p != NULL; p = p->next) {
+      if (p->supp == NULL)
+         n_err_contexts++;
+   }
+
+   n_supp_contexts = 0;
+   for (su = vg_suppressions; su != NULL; su = su->next) {
+      if (su->count > 0)
+         n_supp_contexts++;
+   }
+
+   VG_(message)(Vg_UserMsg,
+                "ERROR SUMMARY: "
+                "%d errors from %d contexts (suppressed: %d from %d)",
+                vg_n_errs_found, n_err_contexts, 
+                vg_n_errs_suppressed, n_supp_contexts );
+
+   if (VG_(clo_verbosity) <= 1)
+      return;
+
+   /* Print the contexts in order of increasing error count. */
+   for (i = 0; i < n_err_contexts; i++) {
+      n_min = (1 << 30) - 1;
+      p_min = NULL;
+      for (p = vg_err_contexts; p != NULL; p = p->next) {
+         if (p->supp != NULL) continue;
+         if (p->count < n_min) {
+            n_min = p->count;
+            p_min = p;
+         }
+      }
+      if (p_min == NULL) VG_(panic)("pp_AllErrContexts");
+
+      VG_(message)(Vg_UserMsg, "");
+      VG_(message)(Vg_UserMsg, "%d errors in context %d of %d:",
+                   p_min->count,
+                   i+1, n_err_contexts);
+      pp_ErrContext( p_min, False );
+
+      if ((i+1 == VG_(clo_dump_error))) {
+         VG_(translate) ( p_min->where->eips[0], NULL, NULL, NULL );
+      }
+
+      p_min->count = 1 << 30;
+   } 
+
+   if (n_supp_contexts > 0) 
+      VG_(message)(Vg_DebugMsg, "");
+   any_supp = False;
+   for (su = vg_suppressions; su != NULL; su = su->next) {
+      if (su->count > 0) {
+         any_supp = True;
+         VG_(message)(Vg_DebugMsg, "supp: %4d %s", su->count, 
+                                   su->sname);
+      }
+   }
+
+   if (n_err_contexts > 0) {
+      if (any_supp) 
+         VG_(message)(Vg_UserMsg, "");
+      VG_(message)(Vg_UserMsg,
+                   "IN SUMMARY: "
+                   "%d errors from %d contexts (suppressed: %d from %d)",
+                   vg_n_errs_found, n_err_contexts, 
+                   vg_n_errs_suppressed,
+                   n_supp_contexts );
+      VG_(message)(Vg_UserMsg, "");
+   }
+}
+
+/*------------------------------------------------------------*/
+/*--- Standard suppressions                                ---*/
+/*------------------------------------------------------------*/
+
+/* Get a non-blank, non-comment line of at most nBuf chars from fd.
+   Skips leading spaces on the line. Return True if EOF was hit instead. 
+*/
+
+#define VG_ISSPACE(ch) (((ch)==' ') || ((ch)=='\n') || ((ch)=='\t'))
+
+static Bool getLine ( Int fd, Char* buf, Int nBuf )
+{
+   Char ch;
+   Int  n, i;
+   while (True) {
+      /* First, read until a non-blank char appears. */
+      while (True) {
+         n = VG_(read)(fd, &ch, 1);
+         if (n == 1 && !VG_ISSPACE(ch)) break;
+         if (n == 0) return True;
+      }
+
+      /* Now, read the line into buf. */
+      i = 0;
+      buf[i++] = ch; buf[i] = 0;
+      while (True) {
+         n = VG_(read)(fd, &ch, 1);
+         if (n == 0) return False; /* the next call will return True */
+         if (ch == '\n') break;
+         if (i > 0 && i == nBuf-1) i--;
+         buf[i++] = ch; buf[i] = 0;
+      }
+      while (i > 1 && VG_ISSPACE(buf[i-1])) { 
+         i--; buf[i] = 0; 
+      };
+
+      /* VG_(printf)("The line is `%s'\n", buf); */
+      /* Ok, we have a line.  If a non-comment line, return.
+         If a comment line, start all over again. */
+      if (buf[0] != '#') return False;
+   }
+}
+
+
+/* *p_caller contains the raw name of a caller, supposedly either
+       fun:some_function_name   or
+       obj:some_object_name.
+   Set *p_ty accordingly and advance *p_caller over the descriptor
+   (fun: or obj:) part.
+   Returns False if failed.
+*/
+static Bool setLocationTy ( Char** p_caller, SuppressionLocTy* p_ty )
+{
+   if (VG_(strncmp)(*p_caller, "fun:", 4) == 0) {
+      (*p_caller) += 4;
+      *p_ty = FunName;
+      return True;
+   }
+   if (VG_(strncmp)(*p_caller, "obj:", 4) == 0) {
+      (*p_caller) += 4;
+      *p_ty = ObjName;
+      return True;
+   }
+   VG_(printf)("location should start with fun: or obj:\n");
+   return False;
+}
+
+
+/* Read suppressions from the file specified in vg_clo_suppressions
+   and place them in the suppressions list.  If there's any difficulty
+   doing this, just give up -- there's no point in trying to recover.  
+*/
+#define STREQ(s1,s2) (s1 != NULL && s2 != NULL \
+                      && VG_(strcmp)((s1),(s2))==0)
+
+static Char* copyStr ( Char* str )
+{
+   Int   n, i;
+   Char* str2;
+   n    = VG_(strlen)(str);
+   str2 = VG_(malloc)(VG_AR_PRIVATE, n+1);
+   vg_assert(n > 0);
+   for (i = 0; i < n+1; i++) str2[i] = str[i];
+   return str2;
+}
+
+static void load_one_suppressions_file ( Char* filename )
+{
+#  define N_BUF 200
+   Int  fd;
+   Bool eof;
+   Char buf[N_BUF+1];
+   fd = VG_(open_read)( filename );
+   if (fd == -1) {
+      VG_(message)(Vg_UserMsg, 
+                   "FATAL: can't open suppressions file `%s'", 
+                   filename );
+      VG_(exit)(1);
+   }
+
+   while (True) {
+      Suppression* supp;
+      supp = VG_(malloc)(VG_AR_PRIVATE, sizeof(Suppression));
+      supp->count = 0;
+      supp->param = supp->caller0 = supp->caller1 
+                  = supp->caller2 = supp->caller3 = NULL;
+
+      eof = getLine ( fd, buf, N_BUF );
+      if (eof) break;
+
+      if (!STREQ(buf, "{")) goto syntax_error;
+      
+      eof = getLine ( fd, buf, N_BUF );
+      if (eof || STREQ(buf, "}")) goto syntax_error;
+      supp->sname = copyStr(buf);
+
+      eof = getLine ( fd, buf, N_BUF );
+      if (eof) goto syntax_error;
+      else if (STREQ(buf, "Param"))  supp->skind = Param;
+      else if (STREQ(buf, "Value0")) supp->skind = Value0;
+      else if (STREQ(buf, "Value1")) supp->skind = Value1;
+      else if (STREQ(buf, "Value2")) supp->skind = Value2;
+      else if (STREQ(buf, "Value4")) supp->skind = Value4;
+      else if (STREQ(buf, "Value8")) supp->skind = Value8;
+      else if (STREQ(buf, "Addr1"))  supp->skind = Addr1;
+      else if (STREQ(buf, "Addr2"))  supp->skind = Addr2;
+      else if (STREQ(buf, "Addr4"))  supp->skind = Addr4;
+      else if (STREQ(buf, "Addr8"))  supp->skind = Addr8;
+      else if (STREQ(buf, "Free"))   supp->skind = FreeS;
+      else goto syntax_error;
+
+      if (supp->skind == Param) {
+         eof = getLine ( fd, buf, N_BUF );
+         if (eof) goto syntax_error;
+         supp->param = copyStr(buf);
+      }
+
+      eof = getLine ( fd, buf, N_BUF );
+      if (eof) goto syntax_error;
+      supp->caller0 = copyStr(buf);
+      if (!setLocationTy(&(supp->caller0), &(supp->caller0_ty)))
+         goto syntax_error;
+
+      eof = getLine ( fd, buf, N_BUF );
+      if (eof) goto syntax_error;
+      supp->caller1 = copyStr(buf);
+      if (!setLocationTy(&(supp->caller1), &(supp->caller1_ty)))
+         goto syntax_error;
+      
+      eof = getLine ( fd, buf, N_BUF );
+      if (eof) goto syntax_error;
+      if (!STREQ(buf, "}")) {
+         supp->caller2 = copyStr(buf);
+         if (!setLocationTy(&(supp->caller2), &(supp->caller2_ty)))
+            goto syntax_error;
+         eof = getLine ( fd, buf, N_BUF );
+         if (eof) goto syntax_error;
+         if (!STREQ(buf, "}")) {
+            supp->caller3 = copyStr(buf);
+            if (!setLocationTy(&(supp->caller3), &(supp->caller3_ty)))
+               goto syntax_error;
+            eof = getLine ( fd, buf, N_BUF );
+            if (eof || !STREQ(buf, "}")) goto syntax_error;
+         }
+      }
+
+      supp->next = vg_suppressions;
+      vg_suppressions = supp;
+   }
+
+   VG_(close)(fd);
+   return;
+
+  syntax_error:
+   if (eof) {
+      VG_(message)(Vg_UserMsg, 
+                   "FATAL: in suppressions file `%s': unexpected EOF", 
+                   filename );
+   } else {
+      VG_(message)(Vg_UserMsg, 
+                   "FATAL: in suppressions file `%s': syntax error on: %s", 
+                   filename, buf );
+   }
+   VG_(close)(fd);
+   VG_(message)(Vg_UserMsg, "exiting now.");
+    VG_(exit)(1);
+
+#  undef N_BUF   
+}
+
+
+void VG_(load_suppressions) ( void )
+{
+   Int i;
+   vg_suppressions = NULL;
+   for (i = 0; i < VG_(clo_n_suppressions); i++) {
+      if (VG_(clo_verbosity) > 1) {
+         VG_(message)(Vg_UserMsg, "Reading suppressions file: %s", 
+                                  VG_(clo_suppressions)[i] );
+      }
+      load_one_suppressions_file( VG_(clo_suppressions)[i] );
+   }
+}
+
+
+/* Does an error context match a suppression?  ie is this a
+   suppressible error?  If so, return a pointer to the Suppression
+   record, otherwise NULL.
+   Tries to minimise the number of calls to what_fn_is_this since they
+   are expensive.  
+*/
+static Suppression* is_suppressible_error ( ErrContext* ec )
+{
+#  define STREQ(s1,s2) (s1 != NULL && s2 != NULL \
+                        && VG_(strcmp)((s1),(s2))==0)
+
+   Char caller0_obj[M_VG_ERRTXT];
+   Char caller0_fun[M_VG_ERRTXT];
+   Char caller1_obj[M_VG_ERRTXT];
+   Char caller1_fun[M_VG_ERRTXT];
+   Char caller2_obj[M_VG_ERRTXT];
+   Char caller2_fun[M_VG_ERRTXT];
+   Char caller3_obj[M_VG_ERRTXT];
+   Char caller3_fun[M_VG_ERRTXT];
+
+   Suppression* su;
+   Int          su_size;
+
+   /* vg_what_fn_or_object_is_this returns:
+         <function_name>      or
+         <object_name>        or
+         ???
+      so the strings in the suppression file should match these.
+   */
+
+   /* Initialise these strs so they are always safe to compare, even
+      if what_fn_or_object_is_this doesn't write anything to them. */
+   caller0_obj[0] = caller1_obj[0] = caller2_obj[0] = caller3_obj[0] = 0;
+   caller0_fun[0] = caller1_fun[0] = caller2_obj[0] = caller3_obj[0] = 0;
+
+   VG_(what_obj_and_fun_is_this)
+      ( ec->where->eips[0], caller0_obj, M_VG_ERRTXT,
+                            caller0_fun, M_VG_ERRTXT );
+   VG_(what_obj_and_fun_is_this)
+      ( ec->where->eips[1], caller1_obj, M_VG_ERRTXT,
+                            caller1_fun, M_VG_ERRTXT );
+
+   if (VG_(clo_backtrace_size) > 2) {
+      VG_(what_obj_and_fun_is_this)
+         ( ec->where->eips[2], caller2_obj, M_VG_ERRTXT,
+                               caller2_fun, M_VG_ERRTXT );
+
+      if (VG_(clo_backtrace_size) > 3) {
+         VG_(what_obj_and_fun_is_this)
+            ( ec->where->eips[3], caller3_obj, M_VG_ERRTXT,
+                                  caller3_fun, M_VG_ERRTXT );
+      }
+   }
+
+   /* See if the error context matches any suppression. */
+   for (su = vg_suppressions; su != NULL; su = su->next) {
+      switch (su->skind) {
+         case FreeS:
+         case Param:  case Value0: su_size = 0; break;
+         case Value1: case Addr1:  su_size = 1; break;
+         case Value2: case Addr2:  su_size = 2; break;
+         case Value4: case Addr4:  su_size = 4; break;
+         case Value8: case Addr8:  su_size = 8; break;
+         default: VG_(panic)("errcontext_matches_suppression");
+      }
+      switch (su->skind) {
+         case Param:
+            if (ec->ekind != ParamErr) continue;
+            if (!STREQ(su->param, ec->syscall_param)) continue;
+            break;
+         case Value0: case Value1: case Value2: case Value4: case Value8:
+            if (ec->ekind != ValueErr) continue;
+            if (ec->size  != su_size)  continue;
+            break;
+         case Addr1: case Addr2: case Addr4: case Addr8:
+            if (ec->ekind != AddrErr) continue;
+            if (ec->size  != su_size) continue;
+            break;
+         case FreeS:
+            if (ec->ekind != FreeErr && ec->ekind != FreeMismatchErr) continue;
+            break;
+      }
+
+      switch (su->caller0_ty) {
+         case ObjName: if (!VG_(stringMatch)(su->caller0, 
+                                             caller0_obj)) continue;
+                       break;
+         case FunName: if (!VG_(stringMatch)(su->caller0, 
+                                             caller0_fun)) continue;
+                       break;
+         default: goto baaaad;
+      }
+
+      switch (su->caller1_ty) {
+         case ObjName: if (!VG_(stringMatch)(su->caller1, 
+                                             caller1_obj)) continue;
+                       break;
+         case FunName: if (!VG_(stringMatch)(su->caller1, 
+                                             caller1_fun)) continue;
+                       break;
+         default: goto baaaad;
+      }
+
+      if (VG_(clo_backtrace_size) > 2 && su->caller2 != NULL) {
+         switch (su->caller2_ty) {
+            case ObjName: if (!VG_(stringMatch)(su->caller2, 
+                                                caller2_obj)) continue;
+                          break;
+            case FunName: if (!VG_(stringMatch)(su->caller2, 
+                                                caller2_fun)) continue;
+                          break;
+            default: goto baaaad;
+         }
+      }
+
+      if (VG_(clo_backtrace_size) > 3 && su->caller3 != NULL) {
+         switch (su->caller3_ty) {
+            case ObjName: if (!VG_(stringMatch)(su->caller3,
+                                                caller3_obj)) continue;
+                          break;
+            case FunName: if (!VG_(stringMatch)(su->caller3, 
+                                                caller3_fun)) continue;
+                          break;
+            default: goto baaaad;
+         }
+      }
+
+      return su;
+   }
+
+   return NULL;
+
+  baaaad:
+   VG_(panic)("is_suppressible_error");
+
+#  undef STREQ
+}
+
+/*--------------------------------------------------------------------*/
+/*--- end                                          vg_errcontext.c ---*/
+/*--------------------------------------------------------------------*/