diff options
Diffstat (limited to 'coregrind/m_errormgr.c')
-rw-r--r-- | coregrind/m_errormgr.c | 1309 |
1 files changed, 1309 insertions, 0 deletions
diff --git a/coregrind/m_errormgr.c b/coregrind/m_errormgr.c new file mode 100644 index 0000000..ec4862a --- /dev/null +++ b/coregrind/m_errormgr.c @@ -0,0 +1,1309 @@ + +/*--------------------------------------------------------------------*/ +/*--- Management of error messages. m_errormgr.c ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2000-2009 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_seqmatch.h" +#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. */ + DotDotDot /* Frame-level wildcard */ + } + SuppLocTy; + +typedef + struct { + SuppLocTy ty; + Char* name; /* NULL for NoName and DotDotDot */ + } + 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_UMSG("<error>"); + VG_UMSG(" <unique>0x%x</unique>", err->unique); + VG_UMSG(" <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_UMSG("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_UMSG("</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_UMSG(""); + + 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_no_cxx_demangle) (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_UMSG("(%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_UMSG(""); + + if (n_errs_shown >= M_COLLECT_NO_ERRORS_AFTER_SHOWN) { + VG_UMSG( + "More than %d different errors detected. " + "I'm not reporting any more.", + M_COLLECT_NO_ERRORS_AFTER_SHOWN ); + } else { + VG_UMSG( + "More than %d total errors detected. " + "I'm not reporting any more.", + M_COLLECT_NO_ERRORS_AFTER_FOUND ); + } + + VG_UMSG("Final error counts will be inaccurate. Go fix your program!"); + VG_UMSG("Rerun with --error-limit=no to disable this cutoff. Note"); + VG_UMSG("that errors may occur in your program without prior warning from"); + VG_UMSG("Valgrind, because errors are no longer being displayed."); + VG_UMSG(""); + 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_UMSG(""); + VG_UMSG("More than %d errors detected. Subsequent errors", + M_COLLECT_ERRORS_SLOWLY_AFTER); + VG_UMSG("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, "errormgr.mre.1", 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)("errormgr.mre.2", 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_UMSG(""); + 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_UMSG(""); + 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_UMSG("<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_no_f_c)(Vg_DebugMsg, + " <pair>\n" + " <count>%d</count>\n" + " <name>%t</name>\n" + " </pair>", + su->count, su->sname); + } else { + VG_DMSG("supp: %6d %s", su->count, su->sname); + } + } + + if (VG_(clo_xml)) + VG_UMSG("</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_UMSG("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_UMSG(""); + VG_UMSG("%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_UMSG( ""); + any_supp = show_used_suppressions(); + + if (n_err_contexts > 0) { + if (any_supp) + VG_UMSG(""); + VG_UMSG("IN SUMMARY: " + "%d errors from %d contexts (suppressed: %d from %d)", + n_errs_found, n_err_contexts, n_errs_suppressed, + n_supp_contexts ); + VG_UMSG(""); + } +} + + +/* Show occurrence counts of all errors, in XML form. */ +void VG_(show_error_counts_as_XML) ( void ) +{ + Error* err; + VG_UMSG("<errorcounts>"); + for (err = errors; err != NULL; err = err->next) { + if (err->supp != NULL) + continue; + if (err->count <= 0) + continue; + VG_UMSG(" <pair>"); + VG_UMSG(" <count>%d</count>", err->count); + VG_UMSG(" <unique>0x%x</unique>", err->unique); + VG_UMSG(" </pair>"); + } + VG_UMSG("</errorcounts>"); +} + + +/*------------------------------------------------------------*/ +/*--- Suppression parsing ---*/ +/*------------------------------------------------------------*/ + +/* 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[256]; + static Int buf_size = 0; + static Int buf_used = 0; + vg_assert(buf_size >= 0 && buf_size <= 256); + vg_assert(buf_used >= 0 && buf_used <= buf_size); + if (buf_used == buf_size) { + r = VG_(read)(fd, buf, 256); + if (r < 0) return r; /* read failed */ + vg_assert(r >= 0 && r <= 256); + buf_size = r; + buf_used = 0; + } + if (buf_size == 0) + return 0; /* eof */ + vg_assert(buf_size >= 0 && buf_size <= 256); + 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; + } + if (VG_(strcmp)(p->name, "...") == 0) { + p->name = NULL; + p->ty = DotDotDot; + return True; + } + VG_(printf)("location should be \"...\", or should start " + "with \"fun:\" or \"obj:\"\n"); + return False; +} + + +/* Look for "tool" in a string like "tool1,tool2,tool3" */ +static 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, j, lineno = 0; + Bool eof; + Char buf[N_BUF+1]; + Char* tool_names; + Char* supp_name; + Char* err_str = NULL; + SuppLoc tmp_callers[VG_MAX_SUPP_CALLERS]; + + sres = VG_(open)( filename, VKI_O_RDONLY, 0 ); + if (sres.isError) { + if (VG_(clo_xml)) + VG_UMSG("</valgrindoutput>\n"); + VG_UMSG("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, "errormgr.losf.1", + 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 ); + lineno++; + if (eof) break; + + if (!VG_STREQ(buf, "{")) BOMB("expected '{' or end-of-file"); + + eof = VG_(get_line) ( fd, buf, N_BUF ); + lineno++; + + if (eof || VG_STREQ(buf, "}")) BOMB("unexpected '}'"); + + supp->sname = VG_(arena_strdup)(VG_AR_CORE, "errormgr.losf.2", buf); + + eof = VG_(get_line) ( fd, buf, N_BUF ); + lineno++; + + 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 ); + lineno++; + 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"); + } + + /* the main frame-descriptor reading loop */ + i = 0; + while (True) { + eof = VG_(get_line) ( fd, buf, N_BUF ); + lineno++; + 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, + "errormgr.losf.3", buf); + if (!setLocationTy(&(tmp_callers[i]))) + BOMB("location should be \"...\", or 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 ); + lineno++; + } while (!eof && !VG_STREQ(buf, "}")); + } + + // Reject entries which are entirely composed of frame + // level wildcards. + vg_assert(i > 0); // guaranteed by frame-descriptor reading loop + for (j = 0; j < i; j++) { + if (tmp_callers[j].ty == FunName || tmp_callers[j].ty == ObjName) + break; + vg_assert(tmp_callers[j].ty == DotDotDot); + } + vg_assert(j >= 0 && j <= i); + if (j == i) { + // we didn't find any non-"..." entries + BOMB("suppression must contain at least one location " + "line which is not \"...\""); + } + + // Copy tmp_callers[] into supp->callers[] + supp->n_callers = i; + supp->callers = VG_(arena_malloc)(VG_AR_CORE, "errormgr.losf.4", + 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_UMSG("</valgrindoutput>\n"); + VG_UMSG("FATAL: in suppressions file \"%s\" near line %d:", + filename, lineno ); + VG_UMSG(" %s", err_str ); + + VG_(close)(fd); + VG_UMSG("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_DMSG("Reading suppressions file: %s", VG_(clo_suppressions)[i] ); + } + load_one_suppressions_file( VG_(clo_suppressions)[i] ); + } +} + + +/*------------------------------------------------------------*/ +/*--- Matching errors to suppressions ---*/ +/*------------------------------------------------------------*/ + +/* Parameterising functions for the use of VG_(generic_match) in + suppression-vs-error matching. The suppression frames (SuppLoc) + play the role of 'pattern'-element, and the error frames (IPs, + hence simply Addrs) play the role of 'input'. In short then, we're + matching a sequence of Addrs against a pattern composed of a + sequence of SuppLocs. +*/ +static Bool supploc_IsStar ( void* supplocV ) +{ + SuppLoc* supploc = (SuppLoc*)supplocV; + return supploc->ty == DotDotDot; +} + +static Bool supploc_IsQuery ( void* supplocV ) +{ + return False; /* there's no '?' equivalent in the supp syntax */ +} + +static Bool supp_pattEQinp ( void* supplocV, void* addrV ) +{ + SuppLoc* supploc = (SuppLoc*)supplocV; /* PATTERN */ + Addr ip = *(Addr*)addrV; /* INPUT */ + + Char caller_name[ERRTXT_LEN]; + caller_name[0] = 0; + + /* So, does this IP address match this suppression-line? */ + switch (supploc->ty) { + case DotDotDot: + /* supp_pattEQinp is a callback from VG_(generic_match). As + per the spec thereof (see include/pub_tool_seqmatch.h), we + should never get called with a pattern value for which the + _IsStar or _IsQuery function would return True. Hence + this can't happen. */ + vg_assert(0); + case ObjName: + /* Get the object name into 'caller_name', or "???" + if unknown. */ + if (!VG_(get_objname)(ip, caller_name, ERRTXT_LEN)) + VG_(strcpy)(caller_name, "???"); + break; + case FunName: + /* Get the function name into 'caller_name', or "???" + if unknown. */ + // Nb: C++-mangled names are 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_no_cxx_demangle)(ip, caller_name, ERRTXT_LEN)) + VG_(strcpy)(caller_name, "???"); + break; + default: + vg_assert(0); + } + + /* So now we have the function or object name in caller_name, and + the pattern (at the character level) to match against is in + supploc->name. Hence (and leading to a re-entrant call of + VG_(generic_match)): */ + return VG_(string_match)(supploc->name, caller_name); +} + +///////////////////////////////////////////////////// + +static Bool supp_matches_callers(Error* err, Supp* su) +{ + /* Unwrap the args and set up the correct parameterisation of + VG_(generic_match), using supploc_IsStar, supploc_IsQuery and + supp_pattEQinp. */ + /* note, StackTrace === Addr* */ + StackTrace ips = VG_(get_ExeContext_StackTrace)(err->where); + UWord n_ips = VG_(get_ExeContext_n_ips)(err->where); + SuppLoc* supps = su->callers; + UWord n_supps = su->n_callers; + UWord szbPatt = sizeof(SuppLoc); + UWord szbInput = sizeof(Addr); + Bool matchAll = False; /* we just want to match a prefix */ + return + VG_(generic_match)( + matchAll, + /*PATT*/supps, szbPatt, n_supps, 0/*initial Ix*/, + /*INPUT*/ips, szbInput, n_ips, 0/*initial Ix*/, + supploc_IsStar, supploc_IsQuery, supp_pattEQinp + ); +} + +///////////////////////////////////////////////////// + +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"); + } + } +} + +///////////////////////////////////////////////////// + +/* 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_DMSG( + " errormgr: %'lu supplist searches, %'lu comparisons during search", + em_supplist_searches, em_supplist_cmps + ); + VG_DMSG( + " errormgr: %'lu errlist searches, %'lu comparisons during search", + em_errlist_searches, em_errlist_cmps + ); +} + +/*--------------------------------------------------------------------*/ +/*--- end ---*/ +/*--------------------------------------------------------------------*/ |