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