mirror of
https://github.com/Zenithsiz/ftmemsim-valgrind.git
synced 2026-02-03 18:13:01 +00:00
support to Memcheck for tracking the origin of uninitialised values, if you use the --track-origins=yes flag. This currently causes some Memcheck regression tests to fail, because they now print an extra line of advisory text in their output. This will be fixed. The core-tool interface is slightly changed. The version number for the interface needs to be incremented. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@7982
1236 lines
37 KiB
C
1236 lines
37 KiB
C
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- Management of error messages. m_errormgr.c ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|
|
/*
|
|
This file is part of Valgrind, a dynamic binary instrumentation
|
|
framework.
|
|
|
|
Copyright (C) 2000-2008 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 "pub_core_basics.h"
|
|
#include "pub_core_vki.h"
|
|
#include "pub_core_threadstate.h" // For VG_N_THREADS
|
|
#include "pub_core_debugger.h"
|
|
#include "pub_core_debuginfo.h"
|
|
#include "pub_core_errormgr.h"
|
|
#include "pub_core_execontext.h"
|
|
#include "pub_core_libcbase.h"
|
|
#include "pub_core_libcassert.h"
|
|
#include "pub_core_libcfile.h"
|
|
#include "pub_core_libcprint.h"
|
|
#include "pub_core_libcproc.h" // For VG_(getpid)()
|
|
#include "pub_core_mallocfree.h"
|
|
#include "pub_core_options.h"
|
|
#include "pub_core_stacktrace.h"
|
|
#include "pub_core_tooliface.h"
|
|
#include "pub_core_translate.h" // for VG_(translate)()
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Globals ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* After this many different unsuppressed errors have been observed,
|
|
be more conservative about collecting new ones. */
|
|
#define M_COLLECT_ERRORS_SLOWLY_AFTER 100
|
|
|
|
/* After this many different unsuppressed errors have been observed,
|
|
stop collecting errors at all, and tell the user their program is
|
|
evidently a steaming pile of camel dung. */
|
|
#define M_COLLECT_NO_ERRORS_AFTER_SHOWN 1000
|
|
|
|
/* After this many total errors have been observed, stop collecting
|
|
errors at all. Counterpart to M_COLLECT_NO_ERRORS_AFTER_SHOWN. */
|
|
#define M_COLLECT_NO_ERRORS_AFTER_FOUND 10000000
|
|
|
|
/* The list of error contexts found, both suppressed and unsuppressed.
|
|
Initially empty, and grows as errors are detected. */
|
|
static Error* errors = NULL;
|
|
|
|
/* The list of suppression directives, as read from the specified
|
|
suppressions file. Note that the list gets rearranged as a result
|
|
of the searches done by is_suppressible_error(). */
|
|
static Supp* suppressions = NULL;
|
|
|
|
/* Running count of unsuppressed errors detected. */
|
|
static UInt n_errs_found = 0;
|
|
|
|
/* Running count of suppressed errors detected. */
|
|
static UInt n_errs_suppressed = 0;
|
|
|
|
/* forwards ... */
|
|
static Supp* is_suppressible_error ( Error* err );
|
|
|
|
static ThreadId last_tid_printed = 1;
|
|
|
|
/* Stats: number of searches of the error list initiated. */
|
|
static UWord em_errlist_searches = 0;
|
|
|
|
/* Stats: number of comparisons done during error list
|
|
searching. */
|
|
static UWord em_errlist_cmps = 0;
|
|
|
|
/* Stats: number of searches of the suppression list initiated. */
|
|
static UWord em_supplist_searches = 0;
|
|
|
|
/* Stats: number of comparisons done during suppression list
|
|
searching. */
|
|
static UWord em_supplist_cmps = 0;
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Error type ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* Errors. Extensible (via the 'extra' field). Tools can use a normal
|
|
enum (with element values in the normal range (0..)) for 'ekind'.
|
|
Functions for getting/setting the tool-relevant fields are in
|
|
include/pub_tool_errormgr.h.
|
|
|
|
When errors are found and recorded with VG_(maybe_record_error)(), all
|
|
the tool must do is pass in the four parameters; core will
|
|
allocate/initialise the error record.
|
|
*/
|
|
struct _Error {
|
|
struct _Error* next;
|
|
// Unique tag. This gives the error a unique identity (handle) by
|
|
// which it can be referred to afterwords. Currently only used for
|
|
// XML printing.
|
|
UInt unique;
|
|
// NULL if unsuppressed; or ptr to suppression record.
|
|
Supp* supp;
|
|
Int count;
|
|
|
|
// The tool-specific part
|
|
ThreadId tid; // Initialised by core
|
|
ExeContext* where; // Initialised by core
|
|
ErrorKind ekind; // Used by ALL. Must be in the range (0..)
|
|
Addr addr; // Used frequently
|
|
Char* string; // Used frequently
|
|
void* extra; // For any tool-specific extras
|
|
};
|
|
|
|
|
|
ExeContext* VG_(get_error_where) ( Error* err )
|
|
{
|
|
return err->where;
|
|
}
|
|
|
|
ErrorKind VG_(get_error_kind) ( Error* err )
|
|
{
|
|
return err->ekind;
|
|
}
|
|
|
|
Addr VG_(get_error_address) ( Error* err )
|
|
{
|
|
return err->addr;
|
|
}
|
|
|
|
Char* VG_(get_error_string) ( Error* err )
|
|
{
|
|
return err->string;
|
|
}
|
|
|
|
void* VG_(get_error_extra) ( Error* err )
|
|
{
|
|
return err->extra;
|
|
}
|
|
|
|
UInt VG_(get_n_errs_found)( void )
|
|
{
|
|
return n_errs_found;
|
|
}
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Suppression type ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* Note: it is imperative this doesn't overlap with (0..) at all, as tools
|
|
* effectively extend it by defining their own enums in the (0..) range. */
|
|
typedef
|
|
enum {
|
|
// Nb: thread errors are a relic of the time when Valgrind's core
|
|
// could detect them. This example is left commented-out as an
|
|
// example should new core errors ever be added.
|
|
ThreadSupp = -1, /* Matches ThreadErr */
|
|
}
|
|
CoreSuppKind;
|
|
|
|
/* Max number of callers for context in a suppression. */
|
|
#define VG_MAX_SUPP_CALLERS 24
|
|
|
|
/* For each caller specified for a suppression, record the nature of
|
|
the caller name. Not of interest to tools. */
|
|
typedef
|
|
enum {
|
|
NoName, /* Error case */
|
|
ObjName, /* Name is of an shared object file. */
|
|
FunName /* Name is of a function. */
|
|
}
|
|
SuppLocTy;
|
|
|
|
typedef
|
|
struct {
|
|
SuppLocTy ty;
|
|
Char* name;
|
|
}
|
|
SuppLoc;
|
|
|
|
/* Suppressions. Tools can get/set tool-relevant parts with functions
|
|
declared in include/pub_tool_errormgr.h. Extensible via the 'extra' field.
|
|
Tools can use a normal enum (with element values in the normal range
|
|
(0..)) for 'skind'. */
|
|
struct _Supp {
|
|
struct _Supp* next;
|
|
Int count; // The number of times this error has been suppressed.
|
|
Char* sname; // The name by which the suppression is referred to.
|
|
|
|
// Length of 'callers'
|
|
Int n_callers;
|
|
// Array of callers, for matching stack traces. First one (name of fn
|
|
// where err occurs) is mandatory; rest are optional.
|
|
SuppLoc* callers;
|
|
|
|
/* The tool-specific part */
|
|
SuppKind skind; // What kind of suppression. Must use the range (0..).
|
|
Char* string; // String -- use is optional. NULL by default.
|
|
void* extra; // Anything else -- use is optional. NULL by default.
|
|
};
|
|
|
|
SuppKind VG_(get_supp_kind) ( Supp* su )
|
|
{
|
|
return su->skind;
|
|
}
|
|
|
|
Char* VG_(get_supp_string) ( Supp* su )
|
|
{
|
|
return su->string;
|
|
}
|
|
|
|
void* VG_(get_supp_extra) ( Supp* su )
|
|
{
|
|
return su->extra;
|
|
}
|
|
|
|
|
|
void VG_(set_supp_kind) ( Supp* su, SuppKind skind )
|
|
{
|
|
su->skind = skind;
|
|
}
|
|
|
|
void VG_(set_supp_string) ( Supp* su, Char* string )
|
|
{
|
|
su->string = string;
|
|
}
|
|
|
|
void VG_(set_supp_extra) ( Supp* su, void* extra )
|
|
{
|
|
su->extra = extra;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Helper fns ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
// Only show core errors if the tool wants to, we're not running with -q,
|
|
// and were not outputting XML.
|
|
Bool VG_(showing_core_errors)(void)
|
|
{
|
|
return VG_(needs).core_errors && VG_(clo_verbosity) >= 1 && !VG_(clo_xml);
|
|
}
|
|
|
|
/* Compare errors, to detect duplicates. */
|
|
static Bool eq_Error ( VgRes res, Error* e1, Error* e2 )
|
|
{
|
|
if (e1->ekind != e2->ekind)
|
|
return False;
|
|
if (!VG_(eq_ExeContext)(res, e1->where, e2->where))
|
|
return False;
|
|
|
|
switch (e1->ekind) {
|
|
//(example code, see comment on CoreSuppKind above)
|
|
//case ThreadErr:
|
|
// vg_assert(VG_(needs).core_errors);
|
|
// return <something>
|
|
default:
|
|
if (VG_(needs).tool_errors) {
|
|
return VG_TDICT_CALL(tool_eq_Error, res, e1, e2);
|
|
} else {
|
|
VG_(printf)("\nUnhandled error type: %u. VG_(needs).tool_errors\n"
|
|
"probably needs to be set.\n",
|
|
e1->ekind);
|
|
VG_(tool_panic)("unhandled error type");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void pp_Error ( Error* err )
|
|
{
|
|
if (VG_(clo_xml)) {
|
|
VG_(message)(Vg_UserMsg, "<error>");
|
|
VG_(message)(Vg_UserMsg, " <unique>0x%x</unique>",
|
|
err->unique);
|
|
VG_(message)(Vg_UserMsg, " <tid>%d</tid>", err->tid);
|
|
}
|
|
|
|
if (!VG_(clo_xml)) {
|
|
if (VG_(tdict).tool_show_ThreadIDs_for_errors
|
|
&& err->tid > 0 && err->tid != last_tid_printed) {
|
|
VG_(message)(Vg_UserMsg, "Thread %d:", err->tid );
|
|
last_tid_printed = err->tid;
|
|
}
|
|
}
|
|
|
|
switch (err->ekind) {
|
|
//(example code, see comment on CoreSuppKind above)
|
|
//case ThreadErr:
|
|
// vg_assert(VG_(needs).core_errors);
|
|
// VG_(tm_error_print)(err);
|
|
// break;
|
|
default:
|
|
if (VG_(needs).tool_errors)
|
|
VG_TDICT_CALL( tool_pp_Error, err );
|
|
else {
|
|
VG_(printf)("\nUnhandled error type: %u. VG_(needs).tool_errors\n"
|
|
"probably needs to be set?\n",
|
|
err->ekind);
|
|
VG_(tool_panic)("unhandled error type");
|
|
}
|
|
}
|
|
|
|
if (VG_(clo_xml))
|
|
VG_(message)(Vg_UserMsg, "</error>");
|
|
}
|
|
|
|
/* Figure out if we want to perform a given action for this error, possibly
|
|
by asking the user. */
|
|
Bool VG_(is_action_requested) ( Char* action, Bool* clo )
|
|
{
|
|
Char ch, ch2;
|
|
Int res;
|
|
|
|
if (*clo == False)
|
|
return False;
|
|
|
|
VG_(message)(Vg_UserMsg, "");
|
|
|
|
again:
|
|
VG_(printf)(
|
|
"==%d== "
|
|
"---- %s ? --- [Return/N/n/Y/y/C/c] ---- ",
|
|
VG_(getpid)(), action
|
|
);
|
|
|
|
res = VG_(read)(VG_(clo_input_fd), &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)(VG_(clo_input_fd), &ch2, 1);
|
|
if (res != 1) goto ioerror;
|
|
if (ch2 != '\n') goto again;
|
|
|
|
/* No, don't want to do action. */
|
|
if (ch == 'n' || ch == 'N') return False;
|
|
/* Yes, want to do action. */
|
|
if (ch == 'y' || ch == 'Y') return True;
|
|
/* No, don't want to do action, and don't ask again either. */
|
|
vg_assert(ch == 'c' || ch == 'C');
|
|
|
|
ioerror:
|
|
*clo = False;
|
|
return False;
|
|
}
|
|
|
|
|
|
/* Construct an error */
|
|
static __inline__
|
|
void construct_error ( Error* err, ThreadId tid, ErrorKind ekind, Addr a,
|
|
Char* s, void* extra, ExeContext* where )
|
|
{
|
|
/* DO NOT MAKE unique_counter NON-STATIC */
|
|
static UInt unique_counter = 0;
|
|
|
|
tl_assert(tid < VG_N_THREADS);
|
|
|
|
/* Core-only parts */
|
|
err->unique = unique_counter++;
|
|
err->next = NULL;
|
|
err->supp = NULL;
|
|
err->count = 1;
|
|
err->tid = tid;
|
|
if (NULL == where)
|
|
err->where = VG_(record_ExeContext)( tid, 0 );
|
|
else
|
|
err->where = where;
|
|
|
|
/* Tool-relevant parts */
|
|
err->ekind = ekind;
|
|
err->addr = a;
|
|
err->extra = extra;
|
|
err->string = s;
|
|
|
|
/* sanity... */
|
|
vg_assert( tid < VG_N_THREADS );
|
|
}
|
|
|
|
#define ERRTXT_LEN 4096
|
|
|
|
static void printSuppForIp(UInt n, Addr ip)
|
|
{
|
|
static UChar buf[ERRTXT_LEN];
|
|
|
|
if ( VG_(get_fnname_Z_demangle_only) (ip, buf, ERRTXT_LEN) ) {
|
|
VG_(printf)(" fun:%s\n", buf);
|
|
} else if ( VG_(get_objname)(ip, buf, ERRTXT_LEN) ) {
|
|
VG_(printf)(" obj:%s\n", buf);
|
|
} else {
|
|
VG_(printf)(" obj:*\n");
|
|
}
|
|
}
|
|
|
|
static void gen_suppression(Error* err)
|
|
{
|
|
ExeContext* ec = VG_(get_error_where)(err);
|
|
Int stop_at = VG_(clo_backtrace_size);
|
|
|
|
/* At most VG_MAX_SUPP_CALLERS names */
|
|
if (stop_at > VG_MAX_SUPP_CALLERS) stop_at = VG_MAX_SUPP_CALLERS;
|
|
vg_assert(stop_at > 0);
|
|
|
|
//(example code, see comment on CoreSuppKind above)
|
|
if (0) {
|
|
//if (0) ThreadErr == err->ekind) {
|
|
// VG_(printf)("{\n");
|
|
// VG_(printf)(" <insert a suppression name here>\n");
|
|
// VG_(printf)(" core:Thread\n");
|
|
|
|
} else {
|
|
Char* name = VG_TDICT_CALL(tool_get_error_name, err);
|
|
if (NULL == name) {
|
|
VG_(message)(Vg_UserMsg,
|
|
"(%s does not allow error to be suppressed)",
|
|
VG_(details).name);
|
|
return;
|
|
}
|
|
VG_(printf)("{\n");
|
|
VG_(printf)(" <insert a suppression name here>\n");
|
|
VG_(printf)(" %s:%s\n", VG_(details).name, name);
|
|
VG_TDICT_CALL(tool_print_extra_suppression_info, err);
|
|
}
|
|
|
|
// Print stack trace elements
|
|
VG_(apply_StackTrace)(printSuppForIp,
|
|
VG_(get_ExeContext_StackTrace)(ec), stop_at);
|
|
|
|
VG_(printf)("}\n");
|
|
}
|
|
|
|
static
|
|
void do_actions_on_error(Error* err, Bool allow_db_attach)
|
|
{
|
|
Bool still_noisy = True;
|
|
|
|
/* Perhaps we want a debugger attach at this point? */
|
|
if (allow_db_attach &&
|
|
VG_(is_action_requested)( "Attach to debugger", & VG_(clo_db_attach) ))
|
|
{
|
|
if (0) VG_(printf)("starting debugger\n");
|
|
VG_(start_debugger)( err->tid );
|
|
}
|
|
/* Or maybe we want to generate the error's suppression? */
|
|
if (VG_(clo_gen_suppressions) == 2
|
|
|| (VG_(clo_gen_suppressions) == 1
|
|
&& VG_(is_action_requested)( "Print suppression", &still_noisy ))
|
|
) {
|
|
gen_suppression(err);
|
|
}
|
|
if (VG_(clo_gen_suppressions) == 1 && !still_noisy)
|
|
VG_(clo_gen_suppressions) = 0;
|
|
}
|
|
|
|
/* Shared between VG_(maybe_record_error)() and VG_(unique_error)(),
|
|
just for pretty printing purposes. */
|
|
static Bool is_first_shown_context = True;
|
|
|
|
static Int n_errs_shown = 0;
|
|
|
|
/* 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) ( ThreadId tid,
|
|
ErrorKind ekind, Addr a, Char* s, void* extra )
|
|
{
|
|
Error err;
|
|
Error* p;
|
|
Error* p_prev;
|
|
UInt extra_size;
|
|
VgRes exe_res = Vg_MedRes;
|
|
static Bool stopping_message = False;
|
|
static Bool slowdown_message = False;
|
|
|
|
/* After M_COLLECT_NO_ERRORS_AFTER_SHOWN different errors have
|
|
been found, or M_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)
|
|
&& (n_errs_shown >= M_COLLECT_NO_ERRORS_AFTER_SHOWN
|
|
|| n_errs_found >= M_COLLECT_NO_ERRORS_AFTER_FOUND)
|
|
&& !VG_(clo_xml)) {
|
|
if (!stopping_message) {
|
|
VG_(message)(Vg_UserMsg, "");
|
|
|
|
if (n_errs_shown >= M_COLLECT_NO_ERRORS_AFTER_SHOWN) {
|
|
VG_(message)(Vg_UserMsg,
|
|
"More than %d different errors detected. "
|
|
"I'm not reporting any more.",
|
|
M_COLLECT_NO_ERRORS_AFTER_SHOWN );
|
|
} else {
|
|
VG_(message)(Vg_UserMsg,
|
|
"More than %d total errors detected. "
|
|
"I'm not reporting any more.",
|
|
M_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_COLLECT_ERRORS_SLOWLY_AFTER different errors have
|
|
been found, be much more conservative about collecting new
|
|
ones. */
|
|
if (n_errs_shown >= M_COLLECT_ERRORS_SLOWLY_AFTER
|
|
&& !VG_(clo_xml)) {
|
|
exe_res = Vg_LowRes;
|
|
if (!slowdown_message) {
|
|
VG_(message)(Vg_UserMsg, "");
|
|
VG_(message)(Vg_UserMsg,
|
|
"More than %d errors detected. Subsequent errors",
|
|
M_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, tid, ekind, a, s, extra, NULL );
|
|
|
|
/* First, see if we've got an error record matching this one. */
|
|
em_errlist_searches++;
|
|
p = errors;
|
|
p_prev = NULL;
|
|
while (p != NULL) {
|
|
em_errlist_cmps++;
|
|
if (eq_Error(exe_res, p, &err)) {
|
|
/* Found it. */
|
|
p->count++;
|
|
if (p->supp != NULL) {
|
|
/* Deal correctly with suppressed errors. */
|
|
p->supp->count++;
|
|
n_errs_suppressed++;
|
|
} else {
|
|
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 = errors;
|
|
errors = p;
|
|
}
|
|
|
|
return;
|
|
}
|
|
p_prev = p;
|
|
p = p->next;
|
|
}
|
|
|
|
/* Didn't see it. Copy and add. */
|
|
|
|
/* OK, we're really going to collect it. The context is on the stack and
|
|
will disappear shortly, so we must copy it. First do the main
|
|
(non-'extra') part.
|
|
|
|
Then VG_(tdict).tool_update_extra can update the 'extra' part. 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. Ugly.
|
|
|
|
Then, if there is an 'extra' part, copy it too, using the size that
|
|
VG_(tdict).tool_update_extra returned. Also allow for people using
|
|
the void* extra field for a scalar value like an integer.
|
|
*/
|
|
|
|
/* copy main part */
|
|
p = VG_(arena_malloc)(VG_AR_ERRORS, sizeof(Error));
|
|
*p = err;
|
|
|
|
/* update 'extra' */
|
|
switch (ekind) {
|
|
//(example code, see comment on CoreSuppKind above)
|
|
//case ThreadErr:
|
|
// vg_assert(VG_(needs).core_errors);
|
|
// extra_size = <something>
|
|
// break;
|
|
default:
|
|
vg_assert(VG_(needs).tool_errors);
|
|
extra_size = VG_TDICT_CALL(tool_update_extra, p);
|
|
break;
|
|
}
|
|
|
|
/* copy block pointed to by 'extra', if there is one */
|
|
if (NULL != p->extra && 0 != extra_size) {
|
|
void* new_extra = VG_(malloc)(extra_size);
|
|
VG_(memcpy)(new_extra, p->extra, extra_size);
|
|
p->extra = new_extra;
|
|
}
|
|
|
|
p->next = errors;
|
|
p->supp = is_suppressible_error(&err);
|
|
errors = p;
|
|
if (p->supp == NULL) {
|
|
n_errs_found++;
|
|
if (!is_first_shown_context)
|
|
VG_(message)(Vg_UserMsg, "");
|
|
pp_Error(p);
|
|
is_first_shown_context = False;
|
|
n_errs_shown++;
|
|
do_actions_on_error(p, /*allow_db_attach*/True);
|
|
} else {
|
|
n_errs_suppressed++;
|
|
p->supp->count++;
|
|
}
|
|
}
|
|
|
|
/* Second top-level entry point to the error management subsystem, for
|
|
errors that the tool wants to report immediately, eg. because they're
|
|
guaranteed to only happen once. This avoids all the recording and
|
|
comparing stuff. But they can be suppressed; returns True if it is
|
|
suppressed. Bool 'print_error' dictates whether to print the error.
|
|
Bool 'count_error' dictates whether to count the error in n_errs_found.
|
|
*/
|
|
Bool VG_(unique_error) ( ThreadId tid, ErrorKind ekind, Addr a, Char* s,
|
|
void* extra, ExeContext* where, Bool print_error,
|
|
Bool allow_db_attach, Bool count_error )
|
|
{
|
|
Error err;
|
|
Supp *su;
|
|
|
|
/* Build ourselves the error */
|
|
construct_error ( &err, tid, ekind, a, s, extra, where );
|
|
|
|
/* Unless it's suppressed, we're going to show it. Don't need to make
|
|
a copy, because it's only temporary anyway.
|
|
|
|
Then update the 'extra' part with VG_(tdict).tool_update_extra),
|
|
because that can have an affect on whether it's suppressed. Ignore
|
|
the size return value of VG_(tdict).tool_update_extra, because we're
|
|
not copying 'extra'. */
|
|
(void)VG_TDICT_CALL(tool_update_extra, &err);
|
|
|
|
su = is_suppressible_error(&err);
|
|
if (NULL == su) {
|
|
if (count_error)
|
|
n_errs_found++;
|
|
|
|
if (print_error) {
|
|
if (!is_first_shown_context)
|
|
VG_(message)(Vg_UserMsg, "");
|
|
pp_Error(&err);
|
|
is_first_shown_context = False;
|
|
n_errs_shown++;
|
|
do_actions_on_error(&err, allow_db_attach);
|
|
}
|
|
return False;
|
|
|
|
} else {
|
|
n_errs_suppressed++;
|
|
su->count++;
|
|
return True;
|
|
}
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Exported fns ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* Show the used suppressions. Returns False if no suppression
|
|
got used. */
|
|
static Bool show_used_suppressions ( void )
|
|
{
|
|
Supp *su;
|
|
Bool any_supp;
|
|
|
|
if (VG_(clo_xml))
|
|
VG_(message)(Vg_DebugMsg, "<suppcounts>");
|
|
|
|
any_supp = False;
|
|
for (su = suppressions; su != NULL; su = su->next) {
|
|
if (su->count <= 0)
|
|
continue;
|
|
any_supp = True;
|
|
if (VG_(clo_xml)) {
|
|
VG_(message)(Vg_DebugMsg,
|
|
" <pair>\n"
|
|
" <count>%d</count>\n"
|
|
" <name>%t</name>\n"
|
|
" </pair>",
|
|
su->count, su->sname);
|
|
} else {
|
|
VG_(message)(Vg_DebugMsg, "supp: %6d %s", su->count, su->sname);
|
|
}
|
|
}
|
|
|
|
if (VG_(clo_xml))
|
|
VG_(message)(Vg_DebugMsg, "</suppcounts>");
|
|
|
|
return any_supp;
|
|
}
|
|
|
|
|
|
/* Show all the errors that occurred, and possibly also the
|
|
suppressions used. */
|
|
void VG_(show_all_errors) ( void )
|
|
{
|
|
Int i, n_min;
|
|
Int n_err_contexts, n_supp_contexts;
|
|
Error *p, *p_min;
|
|
Supp *su;
|
|
Bool any_supp;
|
|
|
|
if (VG_(clo_verbosity) == 0)
|
|
return;
|
|
|
|
n_err_contexts = 0;
|
|
for (p = errors; p != NULL; p = p->next) {
|
|
if (p->supp == NULL)
|
|
n_err_contexts++;
|
|
}
|
|
|
|
n_supp_contexts = 0;
|
|
for (su = suppressions; su != NULL; su = su->next) {
|
|
if (su->count > 0)
|
|
n_supp_contexts++;
|
|
}
|
|
|
|
/* If we're printing XML, just show the suppressions and stop.
|
|
*/
|
|
if (VG_(clo_xml)) {
|
|
(void)show_used_suppressions();
|
|
return;
|
|
}
|
|
|
|
/* We only get here if not printing XML. */
|
|
VG_(message)(Vg_UserMsg,
|
|
"ERROR SUMMARY: "
|
|
"%d errors from %d contexts (suppressed: %d from %d)",
|
|
n_errs_found, n_err_contexts,
|
|
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 = 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_(tool_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_Error( p_min );
|
|
|
|
if ((i+1 == VG_(clo_dump_error))) {
|
|
StackTrace ips = VG_(get_ExeContext_StackTrace)(p_min->where);
|
|
VG_(translate) ( 0 /* dummy ThreadId; irrelevant due to debugging*/,
|
|
ips[0], /*debugging*/True, 0xFE/*verbosity*/,
|
|
/*bbs_done*/0,
|
|
/*allow redir?*/True);
|
|
}
|
|
|
|
p_min->count = 1 << 30;
|
|
}
|
|
|
|
if (n_supp_contexts > 0)
|
|
VG_(message)(Vg_DebugMsg, "");
|
|
any_supp = show_used_suppressions();
|
|
|
|
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)",
|
|
n_errs_found, n_err_contexts, n_errs_suppressed,
|
|
n_supp_contexts );
|
|
VG_(message)(Vg_UserMsg, "");
|
|
}
|
|
}
|
|
|
|
|
|
/* Show occurrence counts of all errors, in XML form. */
|
|
void VG_(show_error_counts_as_XML) ( void )
|
|
{
|
|
Error* err;
|
|
VG_(message)(Vg_UserMsg, "<errorcounts>");
|
|
for (err = errors; err != NULL; err = err->next) {
|
|
if (err->supp != NULL)
|
|
continue;
|
|
if (err->count <= 0)
|
|
continue;
|
|
VG_(message)(
|
|
Vg_UserMsg, " <pair>\n"
|
|
" <count>%d</count>\n"
|
|
" <unique>0x%x</unique>\n"
|
|
" </pair>",
|
|
err->count, err->unique
|
|
);
|
|
}
|
|
VG_(message)(Vg_UserMsg, "</errorcounts>");
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Standard suppressions ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* Get the next char from fd into *out_buf. Returns 1 if success,
|
|
0 if eof or < 0 if error. */
|
|
|
|
static Int get_char ( Int fd, Char* out_buf )
|
|
{
|
|
Int r;
|
|
static Char buf[64];
|
|
static Int buf_size = 0;
|
|
static Int buf_used = 0;
|
|
vg_assert(buf_size >= 0 && buf_size <= 64);
|
|
vg_assert(buf_used >= 0 && buf_used <= buf_size);
|
|
if (buf_used == buf_size) {
|
|
r = VG_(read)(fd, buf, 64);
|
|
if (r < 0) return r; /* read failed */
|
|
vg_assert(r >= 0 && r <= 64);
|
|
buf_size = r;
|
|
buf_used = 0;
|
|
}
|
|
if (buf_size == 0)
|
|
return 0; /* eof */
|
|
vg_assert(buf_size >= 0 && buf_size <= 64);
|
|
vg_assert(buf_used >= 0 && buf_used < buf_size);
|
|
*out_buf = buf[buf_used];
|
|
buf_used++;
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* 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.
|
|
*/
|
|
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 = get_char(fd, &ch);
|
|
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 = get_char(fd, &ch);
|
|
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 ( SuppLoc* p )
|
|
{
|
|
if (VG_(strncmp)(p->name, "fun:", 4) == 0) {
|
|
p->name += 4;
|
|
p->ty = FunName;
|
|
return True;
|
|
}
|
|
if (VG_(strncmp)(p->name, "obj:", 4) == 0) {
|
|
p->name += 4;
|
|
p->ty = ObjName;
|
|
return True;
|
|
}
|
|
VG_(printf)("location should start with fun: or obj:\n");
|
|
return False;
|
|
}
|
|
|
|
|
|
/* Look for "tool" in a string like "tool1,tool2,tool3" */
|
|
static __inline__
|
|
Bool tool_name_present(Char *name, Char *names)
|
|
{
|
|
Bool found;
|
|
Char *s = NULL; /* Shut gcc up */
|
|
Int len = VG_(strlen)(name);
|
|
|
|
found = (NULL != (s = VG_(strstr)(names, name)) &&
|
|
(s == names || *(s-1) == ',') &&
|
|
(*(s+len) == ',' || *(s+len) == '\0')
|
|
);
|
|
|
|
return found;
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
static void load_one_suppressions_file ( Char* filename )
|
|
{
|
|
# define N_BUF 200
|
|
SysRes sres;
|
|
Int fd, i;
|
|
Bool eof;
|
|
Char buf[N_BUF+1];
|
|
Char* tool_names;
|
|
Char* supp_name;
|
|
Char* err_str = NULL;
|
|
SuppLoc tmp_callers[VG_MAX_SUPP_CALLERS];
|
|
|
|
fd = -1;
|
|
sres = VG_(open)( filename, VKI_O_RDONLY, 0 );
|
|
if (sres.isError) {
|
|
if (VG_(clo_xml))
|
|
VG_(message)(Vg_UserMsg, "</valgrindoutput>\n");
|
|
VG_(message)(Vg_UserMsg, "FATAL: can't open suppressions file '%s'",
|
|
filename );
|
|
VG_(exit)(1);
|
|
}
|
|
fd = sres.res;
|
|
|
|
# define BOMB(S) { err_str = S; goto syntax_error; }
|
|
|
|
while (True) {
|
|
/* Assign and initialise the two suppression halves (core and tool) */
|
|
Supp* supp;
|
|
supp = VG_(arena_malloc)(VG_AR_CORE, sizeof(Supp));
|
|
supp->count = 0;
|
|
|
|
// Initialise temporary reading-in buffer.
|
|
for (i = 0; i < VG_MAX_SUPP_CALLERS; i++) {
|
|
tmp_callers[i].ty = NoName;
|
|
tmp_callers[i].name = NULL;
|
|
}
|
|
|
|
supp->string = supp->extra = NULL;
|
|
|
|
eof = VG_(get_line) ( fd, buf, N_BUF );
|
|
if (eof) break;
|
|
|
|
if (!VG_STREQ(buf, "{")) BOMB("expected '{' or end-of-file");
|
|
|
|
eof = VG_(get_line) ( fd, buf, N_BUF );
|
|
|
|
if (eof || VG_STREQ(buf, "}")) BOMB("unexpected '}'");
|
|
|
|
supp->sname = VG_(arena_strdup)(VG_AR_CORE, buf);
|
|
|
|
eof = VG_(get_line) ( fd, buf, N_BUF );
|
|
|
|
if (eof) BOMB("unexpected end-of-file");
|
|
|
|
/* Check it has the "tool1,tool2,...:supp" form (look for ':') */
|
|
i = 0;
|
|
while (True) {
|
|
if (buf[i] == ':') break;
|
|
if (buf[i] == '\0') BOMB("malformed 'tool1,tool2,...:supp' line");
|
|
i++;
|
|
}
|
|
buf[i] = '\0'; /* Replace ':', splitting into two strings */
|
|
|
|
tool_names = & buf[0];
|
|
supp_name = & buf[i+1];
|
|
|
|
if (VG_(needs).core_errors && tool_name_present("core", tool_names))
|
|
{
|
|
// A core suppression
|
|
//(example code, see comment on CoreSuppKind above)
|
|
//if (VG_STREQ(supp_name, "Thread"))
|
|
// supp->skind = ThreadSupp;
|
|
//else
|
|
BOMB("unknown core suppression type");
|
|
}
|
|
else if (VG_(needs).tool_errors &&
|
|
tool_name_present(VG_(details).name, tool_names))
|
|
{
|
|
// A tool suppression
|
|
if (VG_TDICT_CALL(tool_recognised_suppression, supp_name, supp)) {
|
|
/* Do nothing, function fills in supp->skind */
|
|
} else {
|
|
BOMB("unknown tool suppression type");
|
|
}
|
|
}
|
|
else {
|
|
// Ignore rest of suppression
|
|
while (True) {
|
|
eof = VG_(get_line) ( fd, buf, N_BUF );
|
|
if (eof) BOMB("unexpected end-of-file");
|
|
if (VG_STREQ(buf, "}"))
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (VG_(needs).tool_errors &&
|
|
!VG_TDICT_CALL(tool_read_extra_suppression_info, fd, buf, N_BUF, supp))
|
|
{
|
|
BOMB("bad or missing extra suppression info");
|
|
}
|
|
|
|
i = 0;
|
|
while (True) {
|
|
eof = VG_(get_line) ( fd, buf, N_BUF );
|
|
if (eof)
|
|
BOMB("unexpected end-of-file");
|
|
if (VG_STREQ(buf, "}")) {
|
|
if (i > 0) {
|
|
break;
|
|
} else {
|
|
BOMB("missing stack trace");
|
|
}
|
|
}
|
|
if (i == VG_MAX_SUPP_CALLERS)
|
|
BOMB("too many callers in stack trace");
|
|
if (i > 0 && i >= VG_(clo_backtrace_size))
|
|
break;
|
|
tmp_callers[i].name = VG_(arena_strdup)(VG_AR_CORE, buf);
|
|
if (!setLocationTy(&(tmp_callers[i])))
|
|
BOMB("location should start with 'fun:' or 'obj:'");
|
|
i++;
|
|
}
|
|
|
|
// If the num callers is >= VG_(clo_backtrace_size), ignore any extra
|
|
// lines and grab the '}'.
|
|
if (!VG_STREQ(buf, "}")) {
|
|
do {
|
|
eof = VG_(get_line) ( fd, buf, N_BUF );
|
|
} while (!eof && !VG_STREQ(buf, "}"));
|
|
}
|
|
|
|
// Copy tmp_callers[] into supp->callers[]
|
|
supp->n_callers = i;
|
|
supp->callers = VG_(arena_malloc)(VG_AR_CORE, i*sizeof(SuppLoc));
|
|
for (i = 0; i < supp->n_callers; i++) {
|
|
supp->callers[i] = tmp_callers[i];
|
|
}
|
|
|
|
supp->next = suppressions;
|
|
suppressions = supp;
|
|
}
|
|
VG_(close)(fd);
|
|
return;
|
|
|
|
syntax_error:
|
|
if (VG_(clo_xml))
|
|
VG_(message)(Vg_UserMsg, "</valgrindoutput>\n");
|
|
VG_(message)(Vg_UserMsg,
|
|
"FATAL: in suppressions file '%s': %s", filename, err_str );
|
|
|
|
VG_(close)(fd);
|
|
VG_(message)(Vg_UserMsg, "exiting now.");
|
|
VG_(exit)(1);
|
|
|
|
# undef BOMB
|
|
# undef N_BUF
|
|
}
|
|
|
|
|
|
void VG_(load_suppressions) ( void )
|
|
{
|
|
Int i;
|
|
suppressions = NULL;
|
|
for (i = 0; i < VG_(clo_n_suppressions); i++) {
|
|
if (VG_(clo_verbosity) > 1) {
|
|
VG_(message)(Vg_DebugMsg, "Reading suppressions file: %s",
|
|
VG_(clo_suppressions)[i] );
|
|
}
|
|
load_one_suppressions_file( VG_(clo_suppressions)[i] );
|
|
}
|
|
}
|
|
|
|
static
|
|
Bool supp_matches_error(Supp* su, Error* err)
|
|
{
|
|
switch (su->skind) {
|
|
//(example code, see comment on CoreSuppKind above)
|
|
//case ThreadSupp:
|
|
// return (err->ekind == ThreadErr);
|
|
default:
|
|
if (VG_(needs).tool_errors) {
|
|
return VG_TDICT_CALL(tool_error_matches_suppression, err, su);
|
|
} else {
|
|
VG_(printf)(
|
|
"\nUnhandled suppression type: %u. VG_(needs).tool_errors\n"
|
|
"probably needs to be set.\n",
|
|
err->ekind);
|
|
VG_(tool_panic)("unhandled suppression type");
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
Bool supp_matches_callers(Error* err, Supp* su)
|
|
{
|
|
Int i;
|
|
Char caller_name[ERRTXT_LEN];
|
|
StackTrace ips = VG_(get_ExeContext_StackTrace)(err->where);
|
|
|
|
for (i = 0; i < su->n_callers; i++) {
|
|
Addr a = ips[i];
|
|
vg_assert(su->callers[i].name != NULL);
|
|
// The string to be used in the unknown case ("???") can be anything
|
|
// that couldn't be a valid function or objname. --gen-suppressions
|
|
// prints 'obj:*' for such an entry, which will match any string we
|
|
// use.
|
|
switch (su->callers[i].ty) {
|
|
case ObjName:
|
|
if (!VG_(get_objname)(a, caller_name, ERRTXT_LEN))
|
|
VG_(strcpy)(caller_name, "???");
|
|
break;
|
|
|
|
case FunName:
|
|
// Nb: mangled names used in suppressions. Do, though,
|
|
// Z-demangle them, since otherwise it's possible to wind
|
|
// up comparing "malloc" in the suppression against
|
|
// "_vgrZU_libcZdsoZa_malloc" in the backtrace, and the
|
|
// two of them need to be made to match.
|
|
if (!VG_(get_fnname_Z_demangle_only)(a, caller_name, ERRTXT_LEN))
|
|
VG_(strcpy)(caller_name, "???");
|
|
break;
|
|
default: VG_(tool_panic)("supp_matches_callers");
|
|
}
|
|
if (0) VG_(printf)("cmp %s %s\n", su->callers[i].name, caller_name);
|
|
if (!VG_(string_match)(su->callers[i].name, caller_name))
|
|
return False;
|
|
}
|
|
|
|
/* 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 Supp record, otherwise NULL.
|
|
Tries to minimise the number of symbol searches since they are expensive.
|
|
*/
|
|
static Supp* is_suppressible_error ( Error* err )
|
|
{
|
|
Supp* su;
|
|
Supp* su_prev;
|
|
|
|
/* stats gathering */
|
|
em_supplist_searches++;
|
|
|
|
/* See if the error context matches any suppression. */
|
|
su_prev = NULL;
|
|
for (su = suppressions; su != NULL; su = su->next) {
|
|
em_supplist_cmps++;
|
|
if (supp_matches_error(su, err) && supp_matches_callers(err, su)) {
|
|
/* got a match. Move this entry to the head of the list
|
|
in the hope of making future searches cheaper. */
|
|
if (su_prev) {
|
|
vg_assert(su_prev->next == su);
|
|
su_prev->next = su->next;
|
|
su->next = suppressions;
|
|
suppressions = su;
|
|
}
|
|
return su;
|
|
}
|
|
su_prev = su;
|
|
}
|
|
return NULL; /* no matches */
|
|
}
|
|
|
|
/* Show accumulated error-list and suppression-list search stats.
|
|
*/
|
|
void VG_(print_errormgr_stats) ( void )
|
|
{
|
|
VG_(message)(Vg_DebugMsg,
|
|
" errormgr: %,lu supplist searches, %,lu comparisons during search",
|
|
em_supplist_searches, em_supplist_cmps
|
|
);
|
|
VG_(message)(Vg_DebugMsg,
|
|
" errormgr: %,lu errlist searches, %,lu comparisons during search",
|
|
em_errlist_searches, em_errlist_cmps
|
|
);
|
|
}
|
|
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- end ---*/
|
|
/*--------------------------------------------------------------------*/
|