blob: ac7874b5d2b83ac6113f4d53533a92647ab0864f [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 COPYING.
*/
#include "vg_include.h"
/*------------------------------------------------------------*/
/*--- Globals ---*/
/*------------------------------------------------------------*/
/* The list of error contexts found, both suppressed and unsuppressed.
Initially empty, and grows as errors are detected. */
static CoreError* vg_errors = NULL;
/* The list of suppression directives, as read from the specified
suppressions file. */
static CoreSupp* 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 CoreSupp* is_suppressible_error ( CoreError* err );
/*------------------------------------------------------------*/
/*--- Helper fns ---*/
/*------------------------------------------------------------*/
/* 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_CoreError ( VgRes res, CoreError* e1, CoreError* e2 )
{
if (e1->skin_err.ekind != e2->skin_err.ekind)
return False;
if (!VG_(eq_ExeContext)(res, e1->where, e2->where))
return False;
switch (e1->skin_err.ekind) {
case PThreadErr:
vg_assert(VG_(needs).core_errors);
if (e1->skin_err.string == e2->skin_err.string)
return True;
if (0 == VG_(strcmp)(e1->skin_err.string, e2->skin_err.string))
return True;
return False;
default:
if (VG_(needs).skin_errors)
return SK_(eq_SkinError)(res, &e1->skin_err, &e2->skin_err);
else {
VG_(printf)("\nUnhandled error type: %u. VG_(needs).skin_errors\n"
"probably needs to be set.\n",
e1->skin_err.ekind);
VG_(skin_error)("unhandled error type");
}
}
}
static void pp_CoreError ( CoreError* err, Bool printCount )
{
/* Closure for printing where the error occurred. Abstracts details
about the `where' field away from the skin. */
void pp_ExeContextClosure(void)
{
VG_(pp_ExeContext) ( err->where );
}
if (printCount)
VG_(message)(Vg_UserMsg, "Observed %d times:", err->count );
if (err->tid > 1)
VG_(message)(Vg_UserMsg, "Thread %d:", err->tid );
switch (err->skin_err.ekind) {
case PThreadErr:
vg_assert(VG_(needs).core_errors);
VG_(message)(Vg_UserMsg, "%s", err->skin_err.string );
VG_(pp_ExeContext)(err->where);
break;
default:
if (VG_(needs).skin_errors)
SK_(pp_SkinError)( &err->skin_err, &pp_ExeContextClosure );
else {
VG_(printf)("\nUnhandled error type: %u. VG_(needs).skin_errors\n"
"probably needs to be set?\n",
err->skin_err.ekind);
VG_(skin_error)("unhandled error type");
}
}
}
/* 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;
}
/* I've gone all object-oriented... initialisation depends on where the
error comes from:
- If from generated code (tst == NULL), 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).
- If not from generated code but in response to requests passed back to
the scheduler (tst != NULL), we pick up %EIP/%EBP values from the
stored thread state, not from VG_(baseBlock).
*/
static __inline__
void construct_error ( CoreError* err, ThreadState* tst,
ErrorKind ekind, Addr a, Char* s, void* extra )
{
/* CoreError parts */
err->next = NULL;
err->supp = NULL;
err->count = 1;
if (NULL == tst) {
err->tid = VG_(get_current_tid)();
err->where =
VG_(get_ExeContext2)( VG_(baseBlock)[VGOFF_(m_eip)],
VG_(baseBlock)[VGOFF_(m_ebp)],
VG_(baseBlock)[VGOFF_(m_esp)],
VG_(threads)[err->tid].stack_highest_word);
err->m_eip = VG_(baseBlock)[VGOFF_(m_eip)];
err->m_esp = VG_(baseBlock)[VGOFF_(m_esp)];
err->m_ebp = VG_(baseBlock)[VGOFF_(m_ebp)];
} else {
err->where = VG_(get_ExeContext) ( tst );
err->tid = tst->tid;
err->m_eip = tst->m_eip;
err->m_esp = tst->m_esp;
err->m_ebp = tst->m_ebp;
}
/* SkinError parts */
err->skin_err.ekind = ekind;
err->skin_err.addr = a;
err->skin_err.string = s;
err->skin_err.extra = extra;
/* sanity... */
vg_assert(err->tid >= 0 && err->tid < VG_N_THREADS);
}
/* 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. */
void VG_(maybe_record_error) ( ThreadState* tst,
ErrorKind ekind, Addr a, Char* s, void* extra )
{
CoreError err;
CoreError* p;
CoreError* p_prev;
VgRes exe_res = Vg_MedRes;
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_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_(clo_error_limit)
&& (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,
"Rerun with --error-limit=no to disable this cutoff. Note");
VG_(message)(Vg_UserMsg,
"that errors may occur in your program without prior warning from");
VG_(message)(Vg_UserMsg,
"Valgrind, because errors are no longer being displayed.");
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) {
exe_res = Vg_LowRes;
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;
}
}
/* Build ourselves the error */
construct_error ( &err, tst, ekind, a, s, extra );
/* First, see if we've got an error record matching this one. */
p = vg_errors;
p_prev = NULL;
while (p != NULL) {
if (eq_CoreError(exe_res, p, &err)) {
/* 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_errors;
vg_errors = p;
}
return;
}
p_prev = p;
p = p->next;
}
/* Didn't see it. Copy and add. */
/* OK, we're really going to collect it. First make a copy,
because the error context is on the stack and will disappear shortly.
We can duplicate the main part ourselves, but use
SK_(dup_extra_and_update) to duplicate the 'extra' part (unless it's
NULL).
SK_(dup_extra_and_update) can also update the SkinError. This is
for when there are more details to fill in which take time to work out
but don't affect our earlier decision to include the error -- by
postponing those details until now, we avoid the extra work in the
case where we ignore the error.
*/
p = VG_(arena_malloc)(VG_AR_ERRORS, sizeof(CoreError));
*p = err;
if (NULL != err.skin_err.extra)
SK_(dup_extra_and_update)(&p->skin_err);
p->next = vg_errors;
p->supp = is_suppressible_error(&err);
vg_errors = p;
if (p->supp == NULL) {
vg_n_errs_found++;
if (!is_first_shown_context)
VG_(message)(Vg_UserMsg, "");
pp_CoreError(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)(
err.m_eip, err.m_esp, err.m_ebp);
}
} else {
vg_n_errs_suppressed++;
p->supp->count++;
}
}
/*------------------------------------------------------------*/
/*--- Exported fns ---*/
/*------------------------------------------------------------*/
/* These are called not from generated code but from the scheduler */
void VG_(record_pthread_error) ( ThreadId tid, Char* msg )
{
if (! VG_(needs).core_errors) return;
VG_(maybe_record_error)( &VG_(threads)[tid], PThreadErr, /*addr*/0, msg,
/*extra*/NULL );
}
/*------------------------------*/
void VG_(show_all_errors) ( void )
{
Int i, n_min;
Int n_err_contexts, n_supp_contexts;
CoreError *p, *p_min;
CoreSupp *su;
Bool any_supp;
if (VG_(clo_verbosity) == 0)
return;
n_err_contexts = 0;
for (p = vg_errors; 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_errors; 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)("show_all_errors()");
VG_(message)(Vg_UserMsg, "");
VG_(message)(Vg_UserMsg, "%d errors in context %d of %d:",
p_min->count,
i+1, n_err_contexts);
pp_CoreError( 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'))
Bool VG_(get_line) ( 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, SuppLocTy* 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 void load_one_suppressions_file ( Char* filename )
{
# define N_BUF 200
Int fd, i;
Bool eof;
Bool is_unrecognised_suppressions = False;
Char buf[N_BUF+1];
fd = VG_(open)( filename, VKI_O_RDONLY, 0 );
if (fd == -1) {
VG_(message)(Vg_UserMsg, "FATAL: can't open suppressions file `%s'",
filename );
VG_(exit)(1);
}
while (True) {
/* Assign and initialise the two suppression halves (core and skin) */
CoreSupp* supp;
supp = VG_(arena_malloc)(VG_AR_CORE, sizeof(CoreSupp));
supp->count = 0;
for (i = 0; i < VG_N_SUPP_CALLERS; i++) supp->caller[i] = NULL;
supp->skin_supp.string = supp->skin_supp.extra = NULL;
eof = VG_(get_line) ( fd, buf, N_BUF );
if (eof) break;
if (!STREQ(buf, "{")) goto syntax_error;
eof = VG_(get_line) ( fd, buf, N_BUF );
if (eof || STREQ(buf, "}")) goto syntax_error;
supp->sname = VG_(arena_strdup)(VG_AR_CORE, buf);
eof = VG_(get_line) ( fd, buf, N_BUF );
if (eof) goto syntax_error;
/* Is it a core suppression? */
else if (VG_(needs).core_errors && STREQ(buf, "PThread"))
supp->skin_supp.skind = PThreadSupp;
/* Is it a skin suppression? */
else if (VG_(needs).skin_errors &&
SK_(recognised_suppression)(buf, &(supp->skin_supp.skind))) {
/* do nothing, function fills in supp->skin_supp.skind */
}
//else goto syntax_error;
else {
/* SSS: if we don't recognise the suppression name, ignore entire
* entry. Not sure if this is a good long-term approach -- makes
* it impossible to spot incorrect suppression names? (apart
* from the warning given) */
if (! is_unrecognised_suppressions) {
is_unrecognised_suppressions = True;
VG_(start_msg)(Vg_DebugMsg);
VG_(add_to_msg)("Ignoring unrecognised suppressions: ");
VG_(add_to_msg)("'%s'", buf);
} else {
VG_(add_to_msg)(", '%s'", buf);
}
while (True) {
eof = VG_(get_line) ( fd, buf, N_BUF );
if (eof) goto syntax_error;
if (STREQ(buf, "}"))
break;
}
continue;
}
if (VG_(needs).skin_errors &&
!SK_(read_extra_suppression_info)(fd, buf, N_BUF, &supp->skin_supp))
goto syntax_error;
/* "i > 0" ensures at least one caller read. */
for (i = 0; i < VG_N_SUPP_CALLERS; i++) {
eof = VG_(get_line) ( fd, buf, N_BUF );
if (eof) goto syntax_error;
if (i > 0 && STREQ(buf, "}"))
break;
supp->caller[i] = VG_(arena_strdup)(VG_AR_CORE, buf);
if (!setLocationTy(&(supp->caller[i]), &(supp->caller_ty[i])))
goto syntax_error;
}
supp->next = vg_suppressions;
vg_suppressions = supp;
}
if (is_unrecognised_suppressions) {
/* Print out warning about any ignored suppressions */
//VG_(end_msg)();
}
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] );
}
}
/* Return the name of an erring fn in a way which is useful
for comparing against the contents of a suppressions file.
Doesn't demangle the fn name, because we want to refer to
mangled names in the suppressions file.
*/
static
void get_objname_fnname ( Addr a,
Char* obj_buf, Int n_obj_buf,
Char* fun_buf, Int n_fun_buf )
{
(void)VG_(get_objname) ( a, obj_buf, n_obj_buf );
(void)VG_(get_fnname_nodemangle)( a, fun_buf, n_fun_buf );
}
static __inline__
Bool supp_matches_error(CoreSupp* su, CoreError* err)
{
switch (su->skin_supp.skind) {
case PThreadSupp:
return (err->skin_err.ekind == PThreadErr);
default:
if (VG_(needs).skin_errors) {
return (SK_(error_matches_suppression)(&err->skin_err,
&su->skin_supp));
} else {
VG_(printf)(
"\nUnhandled suppression type: %u. VG_(needs).skin_errors\n"
"probably needs to be set.\n",
err->skin_err.ekind);
VG_(skin_error)("unhandled suppression type");
}
}
}
static __inline__
Bool supp_matches_callers(CoreSupp* su, Char caller_obj[][M_VG_ERRTXT],
Char caller_fun[][M_VG_ERRTXT])
{
Int i;
for (i = 0; su->caller[i] != NULL; i++) {
switch (su->caller_ty[i]) {
case ObjName: if (VG_(string_match)(su->caller[i],
caller_obj[i])) break;
return False;
case FunName: if (VG_(string_match)(su->caller[i],
caller_fun[i])) break;
return False;
default: VG_(panic)("is_suppressible_error");
}
}
/* If we reach here, it's a match */
return True;
}
/* Does an error context match a suppression? ie is this a
suppressible error? If so, return a pointer to the CoreSupp
record, otherwise NULL.
Tries to minimise the number of symbol searches since they are expensive.
*/
static CoreSupp* is_suppressible_error ( CoreError* err )
{
# define STREQ(s1,s2) (s1 != NULL && s2 != NULL \
&& VG_(strcmp)((s1),(s2))==0)
Int i;
Char caller_obj[VG_N_SUPP_CALLERS][M_VG_ERRTXT];
Char caller_fun[VG_N_SUPP_CALLERS][M_VG_ERRTXT];
CoreSupp* su;
/* get_objname_fnname() writes the function name and object name if
it finds them in the debug info. so the strings in the suppression
file should match these.
*/
/* Initialise these strs so they are always safe to compare, even
if get_objname_fnname doesn't write anything to them. */
for (i = 0; i < VG_N_SUPP_CALLERS; i++)
caller_obj[i][0] = caller_fun[i][0] = 0;
for (i = 0; i < VG_N_SUPP_CALLERS && i < VG_(clo_backtrace_size); i++) {
get_objname_fnname ( err->where->eips[i],
caller_obj[i], M_VG_ERRTXT,
caller_fun[i], M_VG_ERRTXT );
}
/* See if the error context matches any suppression. */
for (su = vg_suppressions; su != NULL; su = su->next) {
if (supp_matches_error(su, err) &&
supp_matches_callers(su, caller_obj, caller_fun)) {
return su;
}
}
return NULL; /* no matches */
# undef STREQ
}
/*--------------------------------------------------------------------*/
/*--- end vg_errcontext.c ---*/
/*--------------------------------------------------------------------*/