blob: d5e0abb43dfd12bebbf5a043e8f4190ec7523105 [file] [log] [blame]
/*--------------------------------------------------------------------*/
/*--- 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
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,
/* Pthreading error */
PThread
}
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,
PThreadErr /* pthread API error */
}
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; hijacked for PThread as a description */
Char* syscall_param;
/* Param, User */
Bool isWriteableLack;
/* ALL */
ThreadId tid;
/* ALL */
/* These record %EIP, %ESP and %EBP at the error point. They
are only used to make GDB-attaching convenient; there is no
other purpose; specifically they are not used to do
comparisons between errors. */
UInt m_eip;
UInt m_esp;
UInt m_ebp;
}
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;
/* Used to disable further error reporting once some huge number of
errors have already been logged. */
static Bool vg_ignore_errors = False;
/* 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;
ai->stack_tid = VG_INVALID_THREADID;
ai->maybe_gcc = False;
}
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;
ec->m_eip = 0xDEADB00F;
ec->m_esp = 0xDEADBE0F;
ec->m_ebp = 0xDEADB0EF;
ec->tid = VG_INVALID_THREADID;
}
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 != Undescribed
&& ai2->akind != Undescribed
&& 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 PThreadErr:
if (e1->syscall_param == e2->syscall_param)
return True;
if (0 == VG_(strcmp)(e1->syscall_param, e2->syscall_param))
return True;
return False;
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 thread %d's stack",
a, ai->stack_tid);
break;
case Unknown:
if (ai->maybe_gcc) {
VG_(message)(Vg_UserMsg,
" Address 0x%x is just below %%esp. Possibly a bug in GCC/G++",
a);
VG_(message)(Vg_UserMsg,
" v 2.96 or 3.0.X. To suppress, use: --workaround-gcc296-bugs=yes");
} else {
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 );
if (ec->tid > 1)
VG_(message)(Vg_UserMsg, "Thread %d:", ec->tid );
switch (ec->ekind) {
case ValueErr:
if (ec->size == 0) {
VG_(message)(
Vg_UserMsg,
"Conditional jump or move depends on uninitialised value(s)");
} 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;
case PThreadErr:
VG_(message)(Vg_UserMsg, "%s", ec->syscall_param );
VG_(pp_ExeContext)(ec->where);
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;
vg_assert(ec->tid >= 0 && ec->tid < VG_N_THREADS);
/* After M_VG_COLLECT_NO_ERRORS_AFTER_SHOWN different errors have
been found, or M_VG_COLLECT_NO_ERRORS_AFTER_FOUND total errors
have been found, just refuse to collect any more. This stops
the burden of the error-management system becoming excessive in
extremely buggy programs, although it does make it pretty
pointless to continue the Valgrind run after this point. */
if (vg_n_errs_shown >= M_VG_COLLECT_NO_ERRORS_AFTER_SHOWN
|| vg_n_errs_found >= M_VG_COLLECT_NO_ERRORS_AFTER_FOUND) {
if (!stopping_message) {
VG_(message)(Vg_UserMsg, "");
if (vg_n_errs_shown >= M_VG_COLLECT_NO_ERRORS_AFTER_SHOWN) {
VG_(message)(Vg_UserMsg,
"More than %d different errors detected. "
"I'm not reporting any more.",
M_VG_COLLECT_NO_ERRORS_AFTER_SHOWN );
} else {
VG_(message)(Vg_UserMsg,
"More than %d total errors detected. "
"I'm not reporting any more.",
M_VG_COLLECT_NO_ERRORS_AFTER_FOUND );
}
VG_(message)(Vg_UserMsg,
"Final error counts will be inaccurate. Go fix your program!");
VG_(message)(Vg_UserMsg, "");
stopping_message = True;
vg_ignore_errors = 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. First, describe any addr
info in the error. */
if (ec->addrinfo.akind == Undescribed)
VG_(describe_addr) ( ec->addr, &ec->addrinfo );
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)(
ec->m_eip, ec->m_esp, ec->m_ebp);
}
} else {
vg_n_errs_suppressed++;
p->supp->count++;
}
}
/*------------------------------------------------------------*/
/*--- Exported fns ---*/
/*------------------------------------------------------------*/
/* These two are called from generated code, so that the %EIP/%EBP
values that we need in order to create proper error messages are
picked up out of VG_(baseBlock) rather than from the thread table
(vg_threads in vg_scheduler.c). */
void VG_(record_value_error) ( Int size )
{
ErrContext ec;
if (vg_ignore_errors) return;
clear_ErrContext( &ec );
ec.count = 1;
ec.next = NULL;
ec.where = VG_(get_ExeContext)( False, VG_(baseBlock)[VGOFF_(m_eip)],
VG_(baseBlock)[VGOFF_(m_ebp)] );
ec.ekind = ValueErr;
ec.size = size;
ec.tid = VG_(get_current_tid)();
ec.m_eip = VG_(baseBlock)[VGOFF_(m_eip)];
ec.m_esp = VG_(baseBlock)[VGOFF_(m_esp)];
ec.m_ebp = VG_(baseBlock)[VGOFF_(m_ebp)];
VG_(maybe_add_context) ( &ec );
}
void VG_(record_address_error) ( Addr a, Int size, Bool isWrite )
{
ErrContext ec;
Bool just_below_esp;
if (vg_ignore_errors) return;
just_below_esp
= VG_(is_just_below_ESP)( VG_(baseBlock)[VGOFF_(m_esp)], a );
/* 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) && just_below_esp)
return;
clear_ErrContext( &ec );
ec.count = 1;
ec.next = NULL;
ec.where = VG_(get_ExeContext)( False, VG_(baseBlock)[VGOFF_(m_eip)],
VG_(baseBlock)[VGOFF_(m_ebp)] );
ec.ekind = AddrErr;
ec.axskind = isWrite ? WriteAxs : ReadAxs;
ec.size = size;
ec.addr = a;
ec.tid = VG_(get_current_tid)();
ec.m_eip = VG_(baseBlock)[VGOFF_(m_eip)];
ec.m_esp = VG_(baseBlock)[VGOFF_(m_esp)];
ec.m_ebp = VG_(baseBlock)[VGOFF_(m_ebp)];
ec.addrinfo.akind = Undescribed;
ec.addrinfo.maybe_gcc = just_below_esp;
VG_(maybe_add_context) ( &ec );
}
/* These five are called not from generated code but in response to
requests passed back to the scheduler. So we pick up %EIP/%EBP
values from the stored thread state, not from VG_(baseBlock). */
void VG_(record_free_error) ( ThreadState* tst, Addr a )
{
ErrContext ec;
if (vg_ignore_errors) return;
clear_ErrContext( &ec );
ec.count = 1;
ec.next = NULL;
ec.where = VG_(get_ExeContext)( False, tst->m_eip, tst->m_ebp );
ec.ekind = FreeErr;
ec.addr = a;
ec.tid = tst->tid;
ec.m_eip = tst->m_eip;
ec.m_esp = tst->m_esp;
ec.m_ebp = tst->m_ebp;
ec.addrinfo.akind = Undescribed;
VG_(maybe_add_context) ( &ec );
}
void VG_(record_freemismatch_error) ( ThreadState* tst, Addr a )
{
ErrContext ec;
if (vg_ignore_errors) return;
clear_ErrContext( &ec );
ec.count = 1;
ec.next = NULL;
ec.where = VG_(get_ExeContext)( False, tst->m_eip, tst->m_ebp );
ec.ekind = FreeMismatchErr;
ec.addr = a;
ec.tid = tst->tid;
ec.m_eip = tst->m_eip;
ec.m_esp = tst->m_esp;
ec.m_ebp = tst->m_ebp;
ec.addrinfo.akind = Undescribed;
VG_(maybe_add_context) ( &ec );
}
void VG_(record_jump_error) ( ThreadState* tst, Addr a )
{
ErrContext ec;
if (vg_ignore_errors) return;
clear_ErrContext( &ec );
ec.count = 1;
ec.next = NULL;
ec.where = VG_(get_ExeContext)( False, tst->m_eip, tst->m_ebp );
ec.ekind = AddrErr;
ec.axskind = ExecAxs;
ec.addr = a;
ec.tid = tst->tid;
ec.m_eip = tst->m_eip;
ec.m_esp = tst->m_esp;
ec.m_ebp = tst->m_ebp;
ec.addrinfo.akind = Undescribed;
VG_(maybe_add_context) ( &ec );
}
void VG_(record_param_err) ( ThreadState* tst, Addr a, Bool isWriteLack,
Char* msg )
{
ErrContext ec;
if (vg_ignore_errors) return;
clear_ErrContext( &ec );
ec.count = 1;
ec.next = NULL;
ec.where = VG_(get_ExeContext)( False, tst->m_eip, tst->m_ebp );
ec.ekind = ParamErr;
ec.addr = a;
ec.tid = tst->tid;
ec.m_eip = tst->m_eip;
ec.m_esp = tst->m_esp;
ec.m_ebp = tst->m_ebp;
ec.addrinfo.akind = Undescribed;
ec.syscall_param = msg;
ec.isWriteableLack = isWriteLack;
VG_(maybe_add_context) ( &ec );
}
void VG_(record_user_err) ( ThreadState* tst, Addr a, Bool isWriteLack )
{
ErrContext ec;
if (vg_ignore_errors) return;
clear_ErrContext( &ec );
ec.count = 1;
ec.next = NULL;
ec.where = VG_(get_ExeContext)( False, tst->m_eip, tst->m_ebp );
ec.ekind = UserErr;
ec.addr = a;
ec.tid = tst->tid;
ec.m_eip = tst->m_eip;
ec.m_esp = tst->m_esp;
ec.m_ebp = tst->m_ebp;
ec.addrinfo.akind = Undescribed;
ec.isWriteableLack = isWriteLack;
VG_(maybe_add_context) ( &ec );
}
void VG_(record_pthread_err) ( ThreadId tid, Char* msg )
{
ErrContext ec;
if (vg_ignore_errors) return;
if (!VG_(clo_instrument)) return;
clear_ErrContext( &ec );
ec.count = 1;
ec.next = NULL;
ec.where = VG_(get_ExeContext)( False, VG_(threads)[tid].m_eip,
VG_(threads)[tid].m_ebp );
ec.ekind = PThreadErr;
ec.tid = tid;
ec.syscall_param = msg;
ec.m_eip = VG_(threads)[tid].m_eip;
ec.m_esp = VG_(threads)[tid].m_esp;
ec.m_ebp = VG_(threads)[tid].m_ebp;
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) ( 0 /* dummy ThreadId; irrelevant due to below NULLs */,
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; /* backwards compat */
else if (STREQ(buf, "Cond")) 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 if (STREQ(buf, "PThread")) supp->skind = PThread;
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;
if (!STREQ(buf, "}")) {
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 PThread:
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;
case PThread:
if (ec->ekind != PThreadErr) 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;
}
if (su->caller1 != NULL) {
vg_assert(VG_(clo_backtrace_size) >= 2);
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 ---*/
/*--------------------------------------------------------------------*/