mirror of
https://github.com/Zenithsiz/ftmemsim-valgrind.git
synced 2026-02-03 10:05:29 +00:00
and stack address description.
* A race condition on an allocated block shows the stacktrace, but
does not show the thread # that allocated the block.
This patch adds the output of the thread # that allocated the block.
* The patch also fixes the confusion that might appear between
the core threadid and the helgrind thread nr in Stack address description:
A printed stack addrinfo was containing a thread id, while all other helgrind
messages are using (supposed to use) an 'helgrind thread #' which
is used in the thread announcement.
Basically, the idea is to let a tool set a "tool specific thread nr'
in an addrinfo.
The pretty printing of the addrinfo is then by preference showing this
thread nr (if it was set, i.e. different of 0).
Currently, only helgrind uses this addrinfo tnr.
Note: in xml mode, the output is matching the protocol description.
I.e., GUI should not be impacted by this change, if they properly implement
the xml protocol.
* Also, make the output produced by m_addrinfo consistent:
The message 'block was alloc'd at' is changed to be like all other
output : one character indent, and starting with an uppercase
git-svn-id: svn://svn.valgrind.org/valgrind/trunk@14175
1549 lines
55 KiB
C
1549 lines
55 KiB
C
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- Management, printing, etc, of errors and suppressions. ---*/
|
|
/*--- mc_errors.c ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|
|
/*
|
|
This file is part of MemCheck, a heavyweight Valgrind tool for
|
|
detecting memory errors.
|
|
|
|
Copyright (C) 2000-2013 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_tool_basics.h"
|
|
#include "pub_tool_gdbserver.h"
|
|
#include "pub_tool_poolalloc.h" // For mc_include.h
|
|
#include "pub_tool_hashtable.h" // For mc_include.h
|
|
#include "pub_tool_libcbase.h"
|
|
#include "pub_tool_libcassert.h"
|
|
#include "pub_tool_libcprint.h"
|
|
#include "pub_tool_machine.h"
|
|
#include "pub_tool_mallocfree.h"
|
|
#include "pub_tool_options.h"
|
|
#include "pub_tool_replacemalloc.h"
|
|
#include "pub_tool_tooliface.h"
|
|
#include "pub_tool_threadstate.h"
|
|
#include "pub_tool_debuginfo.h" // VG_(get_dataname_and_offset)
|
|
#include "pub_tool_xarray.h"
|
|
#include "pub_tool_addrinfo.h"
|
|
|
|
#include "mc_include.h"
|
|
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Error types ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* See comment in mc_include.h */
|
|
Bool MC_(any_value_errors) = False;
|
|
|
|
|
|
/* ------------------ Errors ----------------------- */
|
|
|
|
/* What kind of error it is. */
|
|
typedef
|
|
enum {
|
|
Err_Value,
|
|
Err_Cond,
|
|
Err_CoreMem,
|
|
Err_Addr,
|
|
Err_Jump,
|
|
Err_RegParam,
|
|
Err_MemParam,
|
|
Err_User,
|
|
Err_Free,
|
|
Err_FreeMismatch,
|
|
Err_Overlap,
|
|
Err_Leak,
|
|
Err_IllegalMempool,
|
|
Err_FishyValue,
|
|
}
|
|
MC_ErrorTag;
|
|
|
|
|
|
typedef struct _MC_Error MC_Error;
|
|
|
|
struct _MC_Error {
|
|
// Nb: we don't need the tag here, as it's stored in the Error type! Yuk.
|
|
//MC_ErrorTag tag;
|
|
|
|
union {
|
|
// Use of an undefined value:
|
|
// - as a pointer in a load or store
|
|
// - as a jump target
|
|
struct {
|
|
SizeT szB; // size of value in bytes
|
|
// Origin info
|
|
UInt otag; // origin tag
|
|
ExeContext* origin_ec; // filled in later
|
|
} Value;
|
|
|
|
// Use of an undefined value in a conditional branch or move.
|
|
struct {
|
|
// Origin info
|
|
UInt otag; // origin tag
|
|
ExeContext* origin_ec; // filled in later
|
|
} Cond;
|
|
|
|
// Addressability error in core (signal-handling) operation.
|
|
// It would be good to get rid of this error kind, merge it with
|
|
// another one somehow.
|
|
struct {
|
|
} CoreMem;
|
|
|
|
// Use of an unaddressable memory location in a load or store.
|
|
struct {
|
|
Bool isWrite; // read or write?
|
|
SizeT szB; // not used for exec (jump) errors
|
|
Bool maybe_gcc; // True if just below %esp -- could be a gcc bug
|
|
AddrInfo ai;
|
|
} Addr;
|
|
|
|
// Jump to an unaddressable memory location.
|
|
struct {
|
|
AddrInfo ai;
|
|
} Jump;
|
|
|
|
// System call register input contains undefined bytes.
|
|
struct {
|
|
// Origin info
|
|
UInt otag; // origin tag
|
|
ExeContext* origin_ec; // filled in later
|
|
} RegParam;
|
|
|
|
// System call memory input contains undefined/unaddressable bytes
|
|
struct {
|
|
Bool isAddrErr; // Addressability or definedness error?
|
|
AddrInfo ai;
|
|
// Origin info
|
|
UInt otag; // origin tag
|
|
ExeContext* origin_ec; // filled in later
|
|
} MemParam;
|
|
|
|
// Problem found from a client request like CHECK_MEM_IS_ADDRESSABLE.
|
|
struct {
|
|
Bool isAddrErr; // Addressability or definedness error?
|
|
AddrInfo ai;
|
|
// Origin info
|
|
UInt otag; // origin tag
|
|
ExeContext* origin_ec; // filled in later
|
|
} User;
|
|
|
|
// Program tried to free() something that's not a heap block (this
|
|
// covers double-frees). */
|
|
struct {
|
|
AddrInfo ai;
|
|
} Free;
|
|
|
|
// Program allocates heap block with one function
|
|
// (malloc/new/new[]/custom) and deallocates with not the matching one.
|
|
struct {
|
|
AddrInfo ai;
|
|
} FreeMismatch;
|
|
|
|
// Call to strcpy, memcpy, etc, with overlapping blocks.
|
|
struct {
|
|
Addr src; // Source block
|
|
Addr dst; // Destination block
|
|
SizeT szB; // Size in bytes; 0 if unused.
|
|
} Overlap;
|
|
|
|
// A memory leak.
|
|
struct {
|
|
UInt n_this_record;
|
|
UInt n_total_records;
|
|
LossRecord* lr;
|
|
} Leak;
|
|
|
|
// A memory pool error.
|
|
struct {
|
|
AddrInfo ai;
|
|
} IllegalMempool;
|
|
|
|
// A fishy function argument value
|
|
// An argument value is considered fishy if the corresponding
|
|
// parameter has SizeT type and the value when interpreted as a
|
|
// signed number is negative.
|
|
struct {
|
|
const HChar *function_name;
|
|
const HChar *argument_name;
|
|
SizeT value;
|
|
} FishyValue;
|
|
} Err;
|
|
};
|
|
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Printing errors ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* This is the "this error is due to be printed shortly; so have a
|
|
look at it any print any preamble you want" function. Which, in
|
|
Memcheck, we don't use. Hence a no-op.
|
|
*/
|
|
void MC_(before_pp_Error) ( Error* err ) {
|
|
}
|
|
|
|
/* Do a printf-style operation on either the XML or normal output
|
|
channel, depending on the setting of VG_(clo_xml).
|
|
*/
|
|
static void emit_WRK ( const HChar* format, va_list vargs )
|
|
{
|
|
if (VG_(clo_xml)) {
|
|
VG_(vprintf_xml)(format, vargs);
|
|
} else {
|
|
VG_(vmessage)(Vg_UserMsg, format, vargs);
|
|
}
|
|
}
|
|
static void emit ( const HChar* format, ... ) PRINTF_CHECK(1, 2);
|
|
static void emit ( const HChar* format, ... )
|
|
{
|
|
va_list vargs;
|
|
va_start(vargs, format);
|
|
emit_WRK(format, vargs);
|
|
va_end(vargs);
|
|
}
|
|
|
|
|
|
static const HChar* str_leak_lossmode ( Reachedness lossmode )
|
|
{
|
|
const HChar *loss = "?";
|
|
switch (lossmode) {
|
|
case Unreached: loss = "definitely lost"; break;
|
|
case IndirectLeak: loss = "indirectly lost"; break;
|
|
case Possible: loss = "possibly lost"; break;
|
|
case Reachable: loss = "still reachable"; break;
|
|
}
|
|
return loss;
|
|
}
|
|
|
|
static const HChar* xml_leak_kind ( Reachedness lossmode )
|
|
{
|
|
const HChar *loss = "?";
|
|
switch (lossmode) {
|
|
case Unreached: loss = "Leak_DefinitelyLost"; break;
|
|
case IndirectLeak: loss = "Leak_IndirectlyLost"; break;
|
|
case Possible: loss = "Leak_PossiblyLost"; break;
|
|
case Reachable: loss = "Leak_StillReachable"; break;
|
|
}
|
|
return loss;
|
|
}
|
|
|
|
Bool MC_(parse_leak_kinds) ( const HChar* str0, UInt* lks )
|
|
{
|
|
return VG_(parse_enum_set)("reachable,possible,indirect,definite",
|
|
str0, lks);
|
|
}
|
|
|
|
static const HChar* pp_Reachedness_for_leak_kinds(Reachedness r)
|
|
{
|
|
switch(r) {
|
|
case Reachable: return "reachable";
|
|
case Possible: return "possible";
|
|
case IndirectLeak: return "indirect";
|
|
case Unreached: return "definite";
|
|
default: tl_assert(0);
|
|
}
|
|
}
|
|
|
|
static void mc_pp_origin ( ExeContext* ec, UInt okind )
|
|
{
|
|
const HChar* src = NULL;
|
|
tl_assert(ec);
|
|
|
|
switch (okind) {
|
|
case MC_OKIND_STACK: src = " by a stack allocation"; break;
|
|
case MC_OKIND_HEAP: src = " by a heap allocation"; break;
|
|
case MC_OKIND_USER: src = " by a client request"; break;
|
|
case MC_OKIND_UNKNOWN: src = ""; break;
|
|
}
|
|
tl_assert(src); /* guards against invalid 'okind' */
|
|
|
|
if (VG_(clo_xml)) {
|
|
emit( " <auxwhat>Uninitialised value was created%s</auxwhat>\n",
|
|
src);
|
|
VG_(pp_ExeContext)( ec );
|
|
} else {
|
|
emit( " Uninitialised value was created%s\n", src);
|
|
VG_(pp_ExeContext)( ec );
|
|
}
|
|
}
|
|
|
|
HChar * MC_(snprintf_delta) (HChar * buf, Int size,
|
|
SizeT current_val, SizeT old_val,
|
|
LeakCheckDeltaMode delta_mode)
|
|
{
|
|
if (delta_mode == LCD_Any)
|
|
buf[0] = '\0';
|
|
else if (current_val >= old_val)
|
|
VG_(snprintf) (buf, size, " (+%'lu)", current_val - old_val);
|
|
else
|
|
VG_(snprintf) (buf, size, " (-%'lu)", old_val - current_val);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static void pp_LossRecord(UInt n_this_record, UInt n_total_records,
|
|
LossRecord* lr, Bool xml)
|
|
{
|
|
// char arrays to produce the indication of increase/decrease in case
|
|
// of delta_mode != LCD_Any
|
|
HChar d_bytes[20];
|
|
HChar d_direct_bytes[20];
|
|
HChar d_indirect_bytes[20];
|
|
HChar d_num_blocks[20];
|
|
|
|
MC_(snprintf_delta) (d_bytes, 20,
|
|
lr->szB + lr->indirect_szB,
|
|
lr->old_szB + lr->old_indirect_szB,
|
|
MC_(detect_memory_leaks_last_delta_mode));
|
|
MC_(snprintf_delta) (d_direct_bytes, 20,
|
|
lr->szB,
|
|
lr->old_szB,
|
|
MC_(detect_memory_leaks_last_delta_mode));
|
|
MC_(snprintf_delta) (d_indirect_bytes, 20,
|
|
lr->indirect_szB,
|
|
lr->old_indirect_szB,
|
|
MC_(detect_memory_leaks_last_delta_mode));
|
|
MC_(snprintf_delta) (d_num_blocks, 20,
|
|
(SizeT) lr->num_blocks,
|
|
(SizeT) lr->old_num_blocks,
|
|
MC_(detect_memory_leaks_last_delta_mode));
|
|
|
|
if (xml) {
|
|
emit(" <kind>%s</kind>\n", xml_leak_kind(lr->key.state));
|
|
if (lr->indirect_szB > 0) {
|
|
emit( " <xwhat>\n" );
|
|
emit( " <text>%'lu%s (%'lu%s direct, %'lu%s indirect) bytes "
|
|
"in %'u%s blocks"
|
|
" are %s in loss record %'u of %'u</text>\n",
|
|
lr->szB + lr->indirect_szB, d_bytes,
|
|
lr->szB, d_direct_bytes,
|
|
lr->indirect_szB, d_indirect_bytes,
|
|
lr->num_blocks, d_num_blocks,
|
|
str_leak_lossmode(lr->key.state),
|
|
n_this_record, n_total_records );
|
|
// Nb: don't put commas in these XML numbers
|
|
emit( " <leakedbytes>%lu</leakedbytes>\n",
|
|
lr->szB + lr->indirect_szB );
|
|
emit( " <leakedblocks>%u</leakedblocks>\n", lr->num_blocks );
|
|
emit( " </xwhat>\n" );
|
|
} else {
|
|
emit( " <xwhat>\n" );
|
|
emit( " <text>%'lu%s bytes in %'u%s blocks"
|
|
" are %s in loss record %'u of %'u</text>\n",
|
|
lr->szB, d_direct_bytes,
|
|
lr->num_blocks, d_num_blocks,
|
|
str_leak_lossmode(lr->key.state),
|
|
n_this_record, n_total_records );
|
|
emit( " <leakedbytes>%ld</leakedbytes>\n", lr->szB);
|
|
emit( " <leakedblocks>%d</leakedblocks>\n", lr->num_blocks);
|
|
emit( " </xwhat>\n" );
|
|
}
|
|
VG_(pp_ExeContext)(lr->key.allocated_at);
|
|
} else { /* ! if (xml) */
|
|
if (lr->indirect_szB > 0) {
|
|
emit(
|
|
"%'lu%s (%'lu%s direct, %'lu%s indirect) bytes in %'u%s blocks"
|
|
" are %s in loss record %'u of %'u\n",
|
|
lr->szB + lr->indirect_szB, d_bytes,
|
|
lr->szB, d_direct_bytes,
|
|
lr->indirect_szB, d_indirect_bytes,
|
|
lr->num_blocks, d_num_blocks,
|
|
str_leak_lossmode(lr->key.state),
|
|
n_this_record, n_total_records
|
|
);
|
|
} else {
|
|
emit(
|
|
"%'lu%s bytes in %'u%s blocks are %s in loss record %'u of %'u\n",
|
|
lr->szB, d_direct_bytes,
|
|
lr->num_blocks, d_num_blocks,
|
|
str_leak_lossmode(lr->key.state),
|
|
n_this_record, n_total_records
|
|
);
|
|
}
|
|
VG_(pp_ExeContext)(lr->key.allocated_at);
|
|
} /* if (xml) */
|
|
}
|
|
|
|
void MC_(pp_LossRecord)(UInt n_this_record, UInt n_total_records,
|
|
LossRecord* l)
|
|
{
|
|
pp_LossRecord (n_this_record, n_total_records, l, /* xml */ False);
|
|
}
|
|
|
|
void MC_(pp_Error) ( Error* err )
|
|
{
|
|
const Bool xml = VG_(clo_xml); /* a shorthand */
|
|
MC_Error* extra = VG_(get_error_extra)(err);
|
|
|
|
switch (VG_(get_error_kind)(err)) {
|
|
case Err_CoreMem:
|
|
/* What the hell *is* a CoreMemError? jrs 2005-May-18 */
|
|
/* As of 2006-Dec-14, it's caused by unaddressable bytes in a
|
|
signal handler frame. --njn */
|
|
// JRS 17 May 09: None of our regtests exercise this; hence AFAIK
|
|
// the following code is untested. Bad.
|
|
if (xml) {
|
|
emit( " <kind>CoreMemError</kind>\n" );
|
|
emit( " <what>%pS contains unaddressable byte(s)</what>\n",
|
|
VG_(get_error_string)(err));
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
} else {
|
|
emit( "%s contains unaddressable byte(s)\n",
|
|
VG_(get_error_string)(err));
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
}
|
|
break;
|
|
|
|
case Err_Value:
|
|
MC_(any_value_errors) = True;
|
|
if (xml) {
|
|
emit( " <kind>UninitValue</kind>\n" );
|
|
emit( " <what>Use of uninitialised value of size %ld</what>\n",
|
|
extra->Err.Value.szB );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
if (extra->Err.Value.origin_ec)
|
|
mc_pp_origin( extra->Err.Value.origin_ec,
|
|
extra->Err.Value.otag & 3 );
|
|
} else {
|
|
/* Could also show extra->Err.Cond.otag if debugging origin
|
|
tracking */
|
|
emit( "Use of uninitialised value of size %ld\n",
|
|
extra->Err.Value.szB );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
if (extra->Err.Value.origin_ec)
|
|
mc_pp_origin( extra->Err.Value.origin_ec,
|
|
extra->Err.Value.otag & 3 );
|
|
}
|
|
break;
|
|
|
|
case Err_Cond:
|
|
MC_(any_value_errors) = True;
|
|
if (xml) {
|
|
emit( " <kind>UninitCondition</kind>\n" );
|
|
emit( " <what>Conditional jump or move depends"
|
|
" on uninitialised value(s)</what>\n" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
if (extra->Err.Cond.origin_ec)
|
|
mc_pp_origin( extra->Err.Cond.origin_ec,
|
|
extra->Err.Cond.otag & 3 );
|
|
} else {
|
|
/* Could also show extra->Err.Cond.otag if debugging origin
|
|
tracking */
|
|
emit( "Conditional jump or move depends"
|
|
" on uninitialised value(s)\n" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
if (extra->Err.Cond.origin_ec)
|
|
mc_pp_origin( extra->Err.Cond.origin_ec,
|
|
extra->Err.Cond.otag & 3 );
|
|
}
|
|
break;
|
|
|
|
case Err_RegParam:
|
|
MC_(any_value_errors) = True;
|
|
if (xml) {
|
|
emit( " <kind>SyscallParam</kind>\n" );
|
|
emit( " <what>Syscall param %pS contains "
|
|
"uninitialised byte(s)</what>\n",
|
|
VG_(get_error_string)(err) );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
if (extra->Err.RegParam.origin_ec)
|
|
mc_pp_origin( extra->Err.RegParam.origin_ec,
|
|
extra->Err.RegParam.otag & 3 );
|
|
} else {
|
|
emit( "Syscall param %s contains uninitialised byte(s)\n",
|
|
VG_(get_error_string)(err) );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
if (extra->Err.RegParam.origin_ec)
|
|
mc_pp_origin( extra->Err.RegParam.origin_ec,
|
|
extra->Err.RegParam.otag & 3 );
|
|
}
|
|
break;
|
|
|
|
case Err_MemParam:
|
|
if (!extra->Err.MemParam.isAddrErr)
|
|
MC_(any_value_errors) = True;
|
|
if (xml) {
|
|
emit( " <kind>SyscallParam</kind>\n" );
|
|
emit( " <what>Syscall param %pS points to %s byte(s)</what>\n",
|
|
VG_(get_error_string)(err),
|
|
extra->Err.MemParam.isAddrErr
|
|
? "unaddressable" : "uninitialised" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)(VG_(get_error_address)(err),
|
|
&extra->Err.MemParam.ai, False);
|
|
if (extra->Err.MemParam.origin_ec
|
|
&& !extra->Err.MemParam.isAddrErr)
|
|
mc_pp_origin( extra->Err.MemParam.origin_ec,
|
|
extra->Err.MemParam.otag & 3 );
|
|
} else {
|
|
emit( "Syscall param %s points to %s byte(s)\n",
|
|
VG_(get_error_string)(err),
|
|
extra->Err.MemParam.isAddrErr
|
|
? "unaddressable" : "uninitialised" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)(VG_(get_error_address)(err),
|
|
&extra->Err.MemParam.ai, False);
|
|
if (extra->Err.MemParam.origin_ec
|
|
&& !extra->Err.MemParam.isAddrErr)
|
|
mc_pp_origin( extra->Err.MemParam.origin_ec,
|
|
extra->Err.MemParam.otag & 3 );
|
|
}
|
|
break;
|
|
|
|
case Err_User:
|
|
if (!extra->Err.User.isAddrErr)
|
|
MC_(any_value_errors) = True;
|
|
if (xml) {
|
|
emit( " <kind>ClientCheck</kind>\n" );
|
|
emit( " <what>%s byte(s) found "
|
|
"during client check request</what>\n",
|
|
extra->Err.User.isAddrErr
|
|
? "Unaddressable" : "Uninitialised" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)(VG_(get_error_address)(err), &extra->Err.User.ai,
|
|
False);
|
|
if (extra->Err.User.origin_ec && !extra->Err.User.isAddrErr)
|
|
mc_pp_origin( extra->Err.User.origin_ec,
|
|
extra->Err.User.otag & 3 );
|
|
} else {
|
|
emit( "%s byte(s) found during client check request\n",
|
|
extra->Err.User.isAddrErr
|
|
? "Unaddressable" : "Uninitialised" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)(VG_(get_error_address)(err), &extra->Err.User.ai,
|
|
False);
|
|
if (extra->Err.User.origin_ec && !extra->Err.User.isAddrErr)
|
|
mc_pp_origin( extra->Err.User.origin_ec,
|
|
extra->Err.User.otag & 3 );
|
|
}
|
|
break;
|
|
|
|
case Err_Free:
|
|
if (xml) {
|
|
emit( " <kind>InvalidFree</kind>\n" );
|
|
emit( " <what>Invalid free() / delete / delete[]"
|
|
" / realloc()</what>\n" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)( VG_(get_error_address)(err),
|
|
&extra->Err.Free.ai, False );
|
|
} else {
|
|
emit( "Invalid free() / delete / delete[] / realloc()\n" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)( VG_(get_error_address)(err),
|
|
&extra->Err.Free.ai, False );
|
|
}
|
|
break;
|
|
|
|
case Err_FreeMismatch:
|
|
if (xml) {
|
|
emit( " <kind>MismatchedFree</kind>\n" );
|
|
emit( " <what>Mismatched free() / delete / delete []</what>\n" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)(VG_(get_error_address)(err),
|
|
&extra->Err.FreeMismatch.ai, False);
|
|
} else {
|
|
emit( "Mismatched free() / delete / delete []\n" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)(VG_(get_error_address)(err),
|
|
&extra->Err.FreeMismatch.ai, False);
|
|
}
|
|
break;
|
|
|
|
case Err_Addr:
|
|
if (xml) {
|
|
emit( " <kind>Invalid%s</kind>\n",
|
|
extra->Err.Addr.isWrite ? "Write" : "Read" );
|
|
emit( " <what>Invalid %s of size %ld</what>\n",
|
|
extra->Err.Addr.isWrite ? "write" : "read",
|
|
extra->Err.Addr.szB );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)( VG_(get_error_address)(err),
|
|
&extra->Err.Addr.ai,
|
|
extra->Err.Addr.maybe_gcc );
|
|
} else {
|
|
emit( "Invalid %s of size %ld\n",
|
|
extra->Err.Addr.isWrite ? "write" : "read",
|
|
extra->Err.Addr.szB );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
|
|
VG_(pp_addrinfo_mc)( VG_(get_error_address)(err),
|
|
&extra->Err.Addr.ai,
|
|
extra->Err.Addr.maybe_gcc );
|
|
}
|
|
break;
|
|
|
|
case Err_Jump:
|
|
if (xml) {
|
|
emit( " <kind>InvalidJump</kind>\n" );
|
|
emit( " <what>Jump to the invalid address stated "
|
|
"on the next line</what>\n" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)( VG_(get_error_address)(err), &extra->Err.Jump.ai,
|
|
False );
|
|
} else {
|
|
emit( "Jump to the invalid address stated on the next line\n" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)( VG_(get_error_address)(err), &extra->Err.Jump.ai,
|
|
False );
|
|
}
|
|
break;
|
|
|
|
case Err_Overlap:
|
|
if (xml) {
|
|
emit( " <kind>Overlap</kind>\n" );
|
|
if (extra->Err.Overlap.szB == 0) {
|
|
emit( " <what>Source and destination overlap "
|
|
"in %pS(%#lx, %#lx)\n</what>\n",
|
|
VG_(get_error_string)(err),
|
|
extra->Err.Overlap.dst, extra->Err.Overlap.src );
|
|
} else {
|
|
emit( " <what>Source and destination overlap "
|
|
"in %pS(%#lx, %#lx, %lu)</what>\n",
|
|
VG_(get_error_string)(err),
|
|
extra->Err.Overlap.dst, extra->Err.Overlap.src,
|
|
extra->Err.Overlap.szB );
|
|
}
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
} else {
|
|
if (extra->Err.Overlap.szB == 0) {
|
|
emit( "Source and destination overlap in %s(%#lx, %#lx)\n",
|
|
VG_(get_error_string)(err),
|
|
extra->Err.Overlap.dst, extra->Err.Overlap.src );
|
|
} else {
|
|
emit( "Source and destination overlap in %s(%#lx, %#lx, %lu)\n",
|
|
VG_(get_error_string)(err),
|
|
extra->Err.Overlap.dst, extra->Err.Overlap.src,
|
|
extra->Err.Overlap.szB );
|
|
}
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
}
|
|
break;
|
|
|
|
case Err_IllegalMempool:
|
|
// JRS 17 May 09: None of our regtests exercise this; hence AFAIK
|
|
// the following code is untested. Bad.
|
|
if (xml) {
|
|
emit( " <kind>InvalidMemPool</kind>\n" );
|
|
emit( " <what>Illegal memory pool address</what>\n" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)( VG_(get_error_address)(err),
|
|
&extra->Err.IllegalMempool.ai, False );
|
|
} else {
|
|
emit( "Illegal memory pool address\n" );
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
VG_(pp_addrinfo_mc)( VG_(get_error_address)(err),
|
|
&extra->Err.IllegalMempool.ai, False );
|
|
}
|
|
break;
|
|
|
|
case Err_Leak: {
|
|
UInt n_this_record = extra->Err.Leak.n_this_record;
|
|
UInt n_total_records = extra->Err.Leak.n_total_records;
|
|
LossRecord* lr = extra->Err.Leak.lr;
|
|
pp_LossRecord (n_this_record, n_total_records, lr, xml);
|
|
break;
|
|
}
|
|
|
|
case Err_FishyValue:
|
|
if (xml) {
|
|
emit( " <kind>FishyValue</kind>\n" );
|
|
emit( " <what>");
|
|
emit( "Argument '%s' of function %s has a fishy "
|
|
"(possibly negative) value: %ld\n",
|
|
extra->Err.FishyValue.argument_name,
|
|
extra->Err.FishyValue.function_name,
|
|
(SSizeT)extra->Err.FishyValue.value);
|
|
emit( "</what>");
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
} else {
|
|
emit( "Argument '%s' of function %s has a fishy "
|
|
"(possibly negative) value: %ld\n",
|
|
extra->Err.FishyValue.argument_name,
|
|
extra->Err.FishyValue.function_name,
|
|
(SSizeT)extra->Err.FishyValue.value);
|
|
VG_(pp_ExeContext)( VG_(get_error_where)(err) );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
VG_(printf)("Error:\n unknown Memcheck error code %d\n",
|
|
VG_(get_error_kind)(err));
|
|
VG_(tool_panic)("unknown error code in mc_pp_Error)");
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Recording errors ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* These many bytes below %ESP are considered addressible if we're
|
|
doing the --workaround-gcc296-bugs hack. */
|
|
#define VG_GCC296_BUG_STACK_SLOP 1024
|
|
|
|
/* Is this address within some small distance below %ESP? Used only
|
|
for the --workaround-gcc296-bugs kludge. */
|
|
static Bool is_just_below_ESP( Addr esp, Addr aa )
|
|
{
|
|
esp -= VG_STACK_REDZONE_SZB;
|
|
if (esp > aa && (esp - aa) <= VG_GCC296_BUG_STACK_SLOP)
|
|
return True;
|
|
else
|
|
return False;
|
|
}
|
|
|
|
/* --- Called from generated and non-generated code --- */
|
|
|
|
void MC_(record_address_error) ( ThreadId tid, Addr a, Int szB,
|
|
Bool isWrite )
|
|
{
|
|
MC_Error extra;
|
|
Bool just_below_esp;
|
|
|
|
if (MC_(in_ignored_range)(a))
|
|
return;
|
|
|
|
if (VG_(is_watched)( (isWrite ? write_watchpoint : read_watchpoint), a, szB))
|
|
return;
|
|
|
|
just_below_esp = is_just_below_ESP( VG_(get_SP)(tid), a );
|
|
|
|
/* If this is caused by an access immediately below %ESP, and the
|
|
user asks nicely, we just ignore it. */
|
|
if (MC_(clo_workaround_gcc296_bugs) && just_below_esp)
|
|
return;
|
|
|
|
extra.Err.Addr.isWrite = isWrite;
|
|
extra.Err.Addr.szB = szB;
|
|
extra.Err.Addr.maybe_gcc = just_below_esp;
|
|
extra.Err.Addr.ai.tag = Addr_Undescribed;
|
|
VG_(maybe_record_error)( tid, Err_Addr, a, /*s*/NULL, &extra );
|
|
}
|
|
|
|
void MC_(record_value_error) ( ThreadId tid, Int szB, UInt otag )
|
|
{
|
|
MC_Error extra;
|
|
tl_assert( MC_(clo_mc_level) >= 2 );
|
|
if (otag > 0)
|
|
tl_assert( MC_(clo_mc_level) == 3 );
|
|
extra.Err.Value.szB = szB;
|
|
extra.Err.Value.otag = otag;
|
|
extra.Err.Value.origin_ec = NULL; /* Filled in later */
|
|
VG_(maybe_record_error)( tid, Err_Value, /*addr*/0, /*s*/NULL, &extra );
|
|
}
|
|
|
|
void MC_(record_cond_error) ( ThreadId tid, UInt otag )
|
|
{
|
|
MC_Error extra;
|
|
tl_assert( MC_(clo_mc_level) >= 2 );
|
|
if (otag > 0)
|
|
tl_assert( MC_(clo_mc_level) == 3 );
|
|
extra.Err.Cond.otag = otag;
|
|
extra.Err.Cond.origin_ec = NULL; /* Filled in later */
|
|
VG_(maybe_record_error)( tid, Err_Cond, /*addr*/0, /*s*/NULL, &extra );
|
|
}
|
|
|
|
/* --- Called from non-generated code --- */
|
|
|
|
/* This is for memory errors in signal-related memory. */
|
|
void MC_(record_core_mem_error) ( ThreadId tid, const HChar* msg )
|
|
{
|
|
VG_(maybe_record_error)( tid, Err_CoreMem, /*addr*/0, msg, /*extra*/NULL );
|
|
}
|
|
|
|
void MC_(record_regparam_error) ( ThreadId tid, const HChar* msg, UInt otag )
|
|
{
|
|
MC_Error extra;
|
|
tl_assert(VG_INVALID_THREADID != tid);
|
|
if (otag > 0)
|
|
tl_assert( MC_(clo_mc_level) == 3 );
|
|
extra.Err.RegParam.otag = otag;
|
|
extra.Err.RegParam.origin_ec = NULL; /* Filled in later */
|
|
VG_(maybe_record_error)( tid, Err_RegParam, /*addr*/0, msg, &extra );
|
|
}
|
|
|
|
void MC_(record_memparam_error) ( ThreadId tid, Addr a,
|
|
Bool isAddrErr, const HChar* msg, UInt otag )
|
|
{
|
|
MC_Error extra;
|
|
tl_assert(VG_INVALID_THREADID != tid);
|
|
if (!isAddrErr)
|
|
tl_assert( MC_(clo_mc_level) >= 2 );
|
|
if (otag != 0) {
|
|
tl_assert( MC_(clo_mc_level) == 3 );
|
|
tl_assert( !isAddrErr );
|
|
}
|
|
extra.Err.MemParam.isAddrErr = isAddrErr;
|
|
extra.Err.MemParam.ai.tag = Addr_Undescribed;
|
|
extra.Err.MemParam.otag = otag;
|
|
extra.Err.MemParam.origin_ec = NULL; /* Filled in later */
|
|
VG_(maybe_record_error)( tid, Err_MemParam, a, msg, &extra );
|
|
}
|
|
|
|
void MC_(record_jump_error) ( ThreadId tid, Addr a )
|
|
{
|
|
MC_Error extra;
|
|
tl_assert(VG_INVALID_THREADID != tid);
|
|
extra.Err.Jump.ai.tag = Addr_Undescribed;
|
|
VG_(maybe_record_error)( tid, Err_Jump, a, /*s*/NULL, &extra );
|
|
}
|
|
|
|
void MC_(record_free_error) ( ThreadId tid, Addr a )
|
|
{
|
|
MC_Error extra;
|
|
tl_assert(VG_INVALID_THREADID != tid);
|
|
extra.Err.Free.ai.tag = Addr_Undescribed;
|
|
VG_(maybe_record_error)( tid, Err_Free, a, /*s*/NULL, &extra );
|
|
}
|
|
|
|
void MC_(record_freemismatch_error) ( ThreadId tid, MC_Chunk* mc )
|
|
{
|
|
MC_Error extra;
|
|
AddrInfo* ai = &extra.Err.FreeMismatch.ai;
|
|
tl_assert(VG_INVALID_THREADID != tid);
|
|
ai->tag = Addr_Block;
|
|
ai->Addr.Block.block_kind = Block_Mallocd; // Nb: Not 'Block_Freed'
|
|
ai->Addr.Block.block_desc = "block";
|
|
ai->Addr.Block.block_szB = mc->szB;
|
|
ai->Addr.Block.rwoffset = 0;
|
|
ai->Addr.Block.allocated_at = MC_(allocated_at) (mc);
|
|
VG_(initThreadInfo) (&ai->Addr.Block.alloc_tinfo);
|
|
ai->Addr.Block.freed_at = MC_(freed_at) (mc);
|
|
VG_(maybe_record_error)( tid, Err_FreeMismatch, mc->data, /*s*/NULL,
|
|
&extra );
|
|
}
|
|
|
|
void MC_(record_illegal_mempool_error) ( ThreadId tid, Addr a )
|
|
{
|
|
MC_Error extra;
|
|
tl_assert(VG_INVALID_THREADID != tid);
|
|
extra.Err.IllegalMempool.ai.tag = Addr_Undescribed;
|
|
VG_(maybe_record_error)( tid, Err_IllegalMempool, a, /*s*/NULL, &extra );
|
|
}
|
|
|
|
void MC_(record_overlap_error) ( ThreadId tid, const HChar* function,
|
|
Addr src, Addr dst, SizeT szB )
|
|
{
|
|
MC_Error extra;
|
|
tl_assert(VG_INVALID_THREADID != tid);
|
|
extra.Err.Overlap.src = src;
|
|
extra.Err.Overlap.dst = dst;
|
|
extra.Err.Overlap.szB = szB;
|
|
VG_(maybe_record_error)(
|
|
tid, Err_Overlap, /*addr*/0, /*s*/function, &extra );
|
|
}
|
|
|
|
Bool MC_(record_leak_error) ( ThreadId tid, UInt n_this_record,
|
|
UInt n_total_records, LossRecord* lr,
|
|
Bool print_record, Bool count_error )
|
|
{
|
|
MC_Error extra;
|
|
extra.Err.Leak.n_this_record = n_this_record;
|
|
extra.Err.Leak.n_total_records = n_total_records;
|
|
extra.Err.Leak.lr = lr;
|
|
return
|
|
VG_(unique_error) ( tid, Err_Leak, /*Addr*/0, /*s*/NULL, &extra,
|
|
lr->key.allocated_at, print_record,
|
|
/*allow_GDB_attach*/False, count_error );
|
|
}
|
|
|
|
Bool MC_(record_fishy_value_error) ( ThreadId tid, const HChar *function_name,
|
|
const HChar *argument_name, SizeT value)
|
|
{
|
|
MC_Error extra;
|
|
|
|
tl_assert(VG_INVALID_THREADID != tid);
|
|
|
|
if ((SSizeT)value >= 0) return False; // not a fishy value
|
|
|
|
extra.Err.FishyValue.function_name = function_name;
|
|
extra.Err.FishyValue.argument_name = argument_name;
|
|
extra.Err.FishyValue.value = value;
|
|
|
|
VG_(maybe_record_error)(
|
|
tid, Err_FishyValue, /*addr*/0, /*s*/NULL, &extra );
|
|
|
|
return True;
|
|
}
|
|
|
|
void MC_(record_user_error) ( ThreadId tid, Addr a,
|
|
Bool isAddrErr, UInt otag )
|
|
{
|
|
MC_Error extra;
|
|
if (otag != 0) {
|
|
tl_assert(!isAddrErr);
|
|
tl_assert( MC_(clo_mc_level) == 3 );
|
|
}
|
|
if (!isAddrErr) {
|
|
tl_assert( MC_(clo_mc_level) >= 2 );
|
|
}
|
|
tl_assert(VG_INVALID_THREADID != tid);
|
|
extra.Err.User.isAddrErr = isAddrErr;
|
|
extra.Err.User.ai.tag = Addr_Undescribed;
|
|
extra.Err.User.otag = otag;
|
|
extra.Err.User.origin_ec = NULL; /* Filled in later */
|
|
VG_(maybe_record_error)( tid, Err_User, a, /*s*/NULL, &extra );
|
|
}
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Other error operations ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* 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. */
|
|
Bool MC_(eq_Error) ( VgRes res, Error* e1, Error* e2 )
|
|
{
|
|
MC_Error* extra1 = VG_(get_error_extra)(e1);
|
|
MC_Error* extra2 = VG_(get_error_extra)(e2);
|
|
|
|
/* Guaranteed by calling function */
|
|
tl_assert(VG_(get_error_kind)(e1) == VG_(get_error_kind)(e2));
|
|
|
|
switch (VG_(get_error_kind)(e1)) {
|
|
case Err_CoreMem: {
|
|
const HChar *e1s, *e2s;
|
|
e1s = VG_(get_error_string)(e1);
|
|
e2s = VG_(get_error_string)(e2);
|
|
if (e1s == e2s) return True;
|
|
if (VG_STREQ(e1s, e2s)) return True;
|
|
return False;
|
|
}
|
|
|
|
case Err_RegParam:
|
|
return VG_STREQ(VG_(get_error_string)(e1), VG_(get_error_string)(e2));
|
|
|
|
// Perhaps we should also check the addrinfo.akinds for equality.
|
|
// That would result in more error reports, but only in cases where
|
|
// a register contains uninitialised bytes and points to memory
|
|
// containing uninitialised bytes. Currently, the 2nd of those to be
|
|
// detected won't be reported. That is (nearly?) always the memory
|
|
// error, which is good.
|
|
case Err_MemParam:
|
|
if (!VG_STREQ(VG_(get_error_string)(e1),
|
|
VG_(get_error_string)(e2))) return False;
|
|
// fall through
|
|
case Err_User:
|
|
return ( extra1->Err.User.isAddrErr == extra2->Err.User.isAddrErr
|
|
? True : False );
|
|
|
|
case Err_Free:
|
|
case Err_FreeMismatch:
|
|
case Err_Jump:
|
|
case Err_IllegalMempool:
|
|
case Err_Overlap:
|
|
case Err_Cond:
|
|
return True;
|
|
|
|
case Err_FishyValue:
|
|
return VG_STREQ(extra1->Err.FishyValue.function_name,
|
|
extra2->Err.FishyValue.function_name) &&
|
|
VG_STREQ(extra1->Err.FishyValue.argument_name,
|
|
extra2->Err.FishyValue.argument_name);
|
|
|
|
case Err_Addr:
|
|
return ( extra1->Err.Addr.szB == extra2->Err.Addr.szB
|
|
? True : False );
|
|
|
|
case Err_Value:
|
|
return ( extra1->Err.Value.szB == extra2->Err.Value.szB
|
|
? True : False );
|
|
|
|
case Err_Leak:
|
|
VG_(tool_panic)("Shouldn't get Err_Leak in mc_eq_Error,\n"
|
|
"since it's handled with VG_(unique_error)()!");
|
|
|
|
default:
|
|
VG_(printf)("Error:\n unknown error code %d\n",
|
|
VG_(get_error_kind)(e1));
|
|
VG_(tool_panic)("unknown error code in mc_eq_Error");
|
|
}
|
|
}
|
|
|
|
/* Functions used when searching MC_Chunk lists */
|
|
static
|
|
Bool addr_is_in_MC_Chunk_default_REDZONE_SZB(MC_Chunk* mc, Addr a)
|
|
{
|
|
return VG_(addr_is_in_block)( a, mc->data, mc->szB,
|
|
MC_(Malloc_Redzone_SzB) );
|
|
}
|
|
static
|
|
Bool addr_is_in_MC_Chunk_with_REDZONE_SZB(MC_Chunk* mc, Addr a, SizeT rzB)
|
|
{
|
|
return VG_(addr_is_in_block)( a, mc->data, mc->szB,
|
|
rzB );
|
|
}
|
|
|
|
// Forward declarations
|
|
static Bool client_block_maybe_describe( Addr a, AddrInfo* ai );
|
|
static Bool mempool_block_maybe_describe( Addr a, AddrInfo* ai );
|
|
|
|
|
|
/* Describe an address as best you can, for error messages,
|
|
putting the result in ai. */
|
|
static void describe_addr ( Addr a, /*OUT*/AddrInfo* ai )
|
|
{
|
|
MC_Chunk* mc;
|
|
|
|
tl_assert(Addr_Undescribed == ai->tag);
|
|
|
|
/* -- Perhaps it's a user-named block? -- */
|
|
if (client_block_maybe_describe( a, ai )) {
|
|
return;
|
|
}
|
|
/* -- Perhaps it's in mempool block? -- */
|
|
if (mempool_block_maybe_describe( a, ai )) {
|
|
return;
|
|
}
|
|
/* Blocks allocated by memcheck malloc functions are either
|
|
on the recently freed list or on the malloc-ed list.
|
|
Custom blocks can be on both : a recently freed block might
|
|
have been just re-allocated.
|
|
So, first search the malloc-ed block, as the most recent
|
|
block is the probable cause of error.
|
|
We however detect and report that this is a recently re-allocated
|
|
block. */
|
|
/* -- Search for a currently malloc'd block which might bracket it. -- */
|
|
VG_(HT_ResetIter)(MC_(malloc_list));
|
|
while ( (mc = VG_(HT_Next)(MC_(malloc_list))) ) {
|
|
if (addr_is_in_MC_Chunk_default_REDZONE_SZB(mc, a)) {
|
|
ai->tag = Addr_Block;
|
|
ai->Addr.Block.block_kind = Block_Mallocd;
|
|
if (MC_(get_freed_block_bracketting)( a ))
|
|
ai->Addr.Block.block_desc = "recently re-allocated block";
|
|
else
|
|
ai->Addr.Block.block_desc = "block";
|
|
ai->Addr.Block.block_szB = mc->szB;
|
|
ai->Addr.Block.rwoffset = (Word)a - (Word)mc->data;
|
|
ai->Addr.Block.allocated_at = MC_(allocated_at)(mc);
|
|
VG_(initThreadInfo) (&ai->Addr.Block.alloc_tinfo);
|
|
ai->Addr.Block.freed_at = MC_(freed_at)(mc);
|
|
return;
|
|
}
|
|
}
|
|
/* -- Search for a recently freed block which might bracket it. -- */
|
|
mc = MC_(get_freed_block_bracketting)( a );
|
|
if (mc) {
|
|
ai->tag = Addr_Block;
|
|
ai->Addr.Block.block_kind = Block_Freed;
|
|
ai->Addr.Block.block_desc = "block";
|
|
ai->Addr.Block.block_szB = mc->szB;
|
|
ai->Addr.Block.rwoffset = (Word)a - (Word)mc->data;
|
|
ai->Addr.Block.allocated_at = MC_(allocated_at)(mc);
|
|
VG_(initThreadInfo) (&ai->Addr.Block.alloc_tinfo);
|
|
ai->Addr.Block.freed_at = MC_(freed_at)(mc);
|
|
return;
|
|
}
|
|
|
|
/* No block found. Search a non-heap block description. */
|
|
VG_(describe_addr) (a, ai);
|
|
}
|
|
|
|
void MC_(pp_describe_addr) ( Addr a )
|
|
{
|
|
AddrInfo ai;
|
|
|
|
ai.tag = Addr_Undescribed;
|
|
describe_addr (a, &ai);
|
|
VG_(pp_addrinfo_mc) (a, &ai, /* maybe_gcc */ False);
|
|
}
|
|
|
|
/* Fill in *origin_ec as specified by otag, or NULL it out if otag
|
|
does not refer to a known origin. */
|
|
static void update_origin ( /*OUT*/ExeContext** origin_ec,
|
|
UInt otag )
|
|
{
|
|
UInt ecu = otag & ~3;
|
|
*origin_ec = NULL;
|
|
if (VG_(is_plausible_ECU)(ecu)) {
|
|
*origin_ec = VG_(get_ExeContext_from_ECU)( ecu );
|
|
}
|
|
}
|
|
|
|
/* Updates the copy with address info if necessary (but not for all errors). */
|
|
UInt MC_(update_Error_extra)( Error* err )
|
|
{
|
|
MC_Error* extra = VG_(get_error_extra)(err);
|
|
|
|
switch (VG_(get_error_kind)(err)) {
|
|
// These ones don't have addresses associated with them, and so don't
|
|
// need any updating.
|
|
case Err_CoreMem:
|
|
//case Err_Value:
|
|
//case Err_Cond:
|
|
case Err_Overlap:
|
|
case Err_FishyValue:
|
|
// For Err_Leaks the returned size does not matter -- they are always
|
|
// shown with VG_(unique_error)() so they 'extra' not copied. But
|
|
// we make it consistent with the others.
|
|
case Err_Leak:
|
|
return sizeof(MC_Error);
|
|
|
|
// For value errors, get the ExeContext corresponding to the
|
|
// origin tag. Note that it is a kludge to assume that
|
|
// a length-1 trace indicates a stack origin. FIXME.
|
|
case Err_Value:
|
|
update_origin( &extra->Err.Value.origin_ec,
|
|
extra->Err.Value.otag );
|
|
return sizeof(MC_Error);
|
|
case Err_Cond:
|
|
update_origin( &extra->Err.Cond.origin_ec,
|
|
extra->Err.Cond.otag );
|
|
return sizeof(MC_Error);
|
|
case Err_RegParam:
|
|
update_origin( &extra->Err.RegParam.origin_ec,
|
|
extra->Err.RegParam.otag );
|
|
return sizeof(MC_Error);
|
|
|
|
// These ones always involve a memory address.
|
|
case Err_Addr:
|
|
describe_addr ( VG_(get_error_address)(err),
|
|
&extra->Err.Addr.ai );
|
|
return sizeof(MC_Error);
|
|
case Err_MemParam:
|
|
describe_addr ( VG_(get_error_address)(err),
|
|
&extra->Err.MemParam.ai );
|
|
update_origin( &extra->Err.MemParam.origin_ec,
|
|
extra->Err.MemParam.otag );
|
|
return sizeof(MC_Error);
|
|
case Err_Jump:
|
|
describe_addr ( VG_(get_error_address)(err),
|
|
&extra->Err.Jump.ai );
|
|
return sizeof(MC_Error);
|
|
case Err_User:
|
|
describe_addr ( VG_(get_error_address)(err),
|
|
&extra->Err.User.ai );
|
|
update_origin( &extra->Err.User.origin_ec,
|
|
extra->Err.User.otag );
|
|
return sizeof(MC_Error);
|
|
case Err_Free:
|
|
describe_addr ( VG_(get_error_address)(err),
|
|
&extra->Err.Free.ai );
|
|
return sizeof(MC_Error);
|
|
case Err_IllegalMempool:
|
|
describe_addr ( VG_(get_error_address)(err),
|
|
&extra->Err.IllegalMempool.ai );
|
|
return sizeof(MC_Error);
|
|
|
|
// Err_FreeMismatches have already had their address described; this is
|
|
// possible because we have the MC_Chunk on hand when the error is
|
|
// detected. However, the address may be part of a user block, and if so
|
|
// we override the pre-determined description with a user block one.
|
|
case Err_FreeMismatch: {
|
|
tl_assert(extra && Block_Mallocd ==
|
|
extra->Err.FreeMismatch.ai.Addr.Block.block_kind);
|
|
(void)client_block_maybe_describe( VG_(get_error_address)(err),
|
|
&extra->Err.FreeMismatch.ai );
|
|
return sizeof(MC_Error);
|
|
}
|
|
|
|
default: VG_(tool_panic)("mc_update_extra: bad errkind");
|
|
}
|
|
}
|
|
|
|
|
|
static Bool client_block_maybe_describe( Addr a,
|
|
/*OUT*/AddrInfo* ai )
|
|
{
|
|
UWord i;
|
|
CGenBlock* cgbs = NULL;
|
|
UWord cgb_used = 0;
|
|
|
|
MC_(get_ClientBlock_array)( &cgbs, &cgb_used );
|
|
if (cgbs == NULL)
|
|
tl_assert(cgb_used == 0);
|
|
|
|
/* Perhaps it's a general block ? */
|
|
for (i = 0; i < cgb_used; i++) {
|
|
if (cgbs[i].start == 0 && cgbs[i].size == 0)
|
|
continue;
|
|
// Use zero as the redzone for client blocks.
|
|
if (VG_(addr_is_in_block)(a, cgbs[i].start, cgbs[i].size, 0)) {
|
|
ai->tag = Addr_Block;
|
|
ai->Addr.Block.block_kind = Block_UserG;
|
|
ai->Addr.Block.block_desc = cgbs[i].desc;
|
|
ai->Addr.Block.block_szB = cgbs[i].size;
|
|
ai->Addr.Block.rwoffset = (Word)(a) - (Word)(cgbs[i].start);
|
|
ai->Addr.Block.allocated_at = cgbs[i].where;
|
|
VG_(initThreadInfo) (&ai->Addr.Block.alloc_tinfo);
|
|
ai->Addr.Block.freed_at = VG_(null_ExeContext)();;
|
|
return True;
|
|
}
|
|
}
|
|
return False;
|
|
}
|
|
|
|
|
|
static Bool mempool_block_maybe_describe( Addr a,
|
|
/*OUT*/AddrInfo* ai )
|
|
{
|
|
MC_Mempool* mp;
|
|
tl_assert( MC_(mempool_list) );
|
|
|
|
VG_(HT_ResetIter)( MC_(mempool_list) );
|
|
while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
|
|
if (mp->chunks != NULL) {
|
|
MC_Chunk* mc;
|
|
VG_(HT_ResetIter)(mp->chunks);
|
|
while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
|
|
if (addr_is_in_MC_Chunk_with_REDZONE_SZB(mc, a, mp->rzB)) {
|
|
ai->tag = Addr_Block;
|
|
ai->Addr.Block.block_kind = Block_MempoolChunk;
|
|
ai->Addr.Block.block_desc = "block";
|
|
ai->Addr.Block.block_szB = mc->szB;
|
|
ai->Addr.Block.rwoffset = (Word)a - (Word)mc->data;
|
|
ai->Addr.Block.allocated_at = MC_(allocated_at)(mc);
|
|
VG_(initThreadInfo) (&ai->Addr.Block.alloc_tinfo);
|
|
ai->Addr.Block.freed_at = MC_(freed_at)(mc);
|
|
return True;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return False;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Suppressions ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
typedef
|
|
enum {
|
|
ParamSupp, // Bad syscall params
|
|
UserSupp, // Errors arising from client-request checks
|
|
CoreMemSupp, // Memory errors in core (pthread ops, signal handling)
|
|
|
|
// Undefined value errors of given size
|
|
Value1Supp, Value2Supp, Value4Supp, Value8Supp, Value16Supp,
|
|
|
|
// Undefined value error in conditional.
|
|
CondSupp,
|
|
|
|
// Unaddressable read/write attempt at given size
|
|
Addr1Supp, Addr2Supp, Addr4Supp, Addr8Supp, Addr16Supp,
|
|
|
|
JumpSupp, // Jump to unaddressable target
|
|
FreeSupp, // Invalid or mismatching free
|
|
OverlapSupp, // Overlapping blocks in memcpy(), strcpy(), etc
|
|
LeakSupp, // Something to be suppressed in a leak check.
|
|
MempoolSupp, // Memory pool suppression.
|
|
FishyValueSupp,// Fishy value suppression.
|
|
}
|
|
MC_SuppKind;
|
|
|
|
Bool MC_(is_recognised_suppression) ( const HChar* name, Supp* su )
|
|
{
|
|
SuppKind skind;
|
|
|
|
if (VG_STREQ(name, "Param")) skind = ParamSupp;
|
|
else if (VG_STREQ(name, "User")) skind = UserSupp;
|
|
else if (VG_STREQ(name, "CoreMem")) skind = CoreMemSupp;
|
|
else if (VG_STREQ(name, "Addr1")) skind = Addr1Supp;
|
|
else if (VG_STREQ(name, "Addr2")) skind = Addr2Supp;
|
|
else if (VG_STREQ(name, "Addr4")) skind = Addr4Supp;
|
|
else if (VG_STREQ(name, "Addr8")) skind = Addr8Supp;
|
|
else if (VG_STREQ(name, "Addr16")) skind = Addr16Supp;
|
|
else if (VG_STREQ(name, "Jump")) skind = JumpSupp;
|
|
else if (VG_STREQ(name, "Free")) skind = FreeSupp;
|
|
else if (VG_STREQ(name, "Leak")) skind = LeakSupp;
|
|
else if (VG_STREQ(name, "Overlap")) skind = OverlapSupp;
|
|
else if (VG_STREQ(name, "Mempool")) skind = MempoolSupp;
|
|
else if (VG_STREQ(name, "Cond")) skind = CondSupp;
|
|
else if (VG_STREQ(name, "Value0")) skind = CondSupp; /* backwards compat */
|
|
else if (VG_STREQ(name, "Value1")) skind = Value1Supp;
|
|
else if (VG_STREQ(name, "Value2")) skind = Value2Supp;
|
|
else if (VG_STREQ(name, "Value4")) skind = Value4Supp;
|
|
else if (VG_STREQ(name, "Value8")) skind = Value8Supp;
|
|
else if (VG_STREQ(name, "Value16")) skind = Value16Supp;
|
|
else if (VG_STREQ(name, "FishyValue")) skind = FishyValueSupp;
|
|
else
|
|
return False;
|
|
|
|
VG_(set_supp_kind)(su, skind);
|
|
return True;
|
|
}
|
|
|
|
typedef struct _MC_LeakSuppExtra MC_LeakSuppExtra;
|
|
|
|
struct _MC_LeakSuppExtra {
|
|
UInt match_leak_kinds;
|
|
|
|
/* Maintains nr of blocks and bytes suppressed with this suppression
|
|
during the leak search identified by leak_search_gen.
|
|
blocks_suppressed and bytes_suppressed are reset to 0 when
|
|
used the first time during a leak search. */
|
|
SizeT blocks_suppressed;
|
|
SizeT bytes_suppressed;
|
|
UInt leak_search_gen;
|
|
};
|
|
|
|
typedef struct {
|
|
const HChar *function_name;
|
|
const HChar *argument_name;
|
|
} MC_FishyValueExtra;
|
|
|
|
Bool MC_(read_extra_suppression_info) ( Int fd, HChar** bufpp,
|
|
SizeT* nBufp, Int* lineno, Supp *su )
|
|
{
|
|
Bool eof;
|
|
Int i;
|
|
|
|
if (VG_(get_supp_kind)(su) == ParamSupp) {
|
|
eof = VG_(get_line) ( fd, bufpp, nBufp, lineno );
|
|
if (eof) return False;
|
|
VG_(set_supp_string)(su, VG_(strdup)("mc.resi.1", *bufpp));
|
|
} else if (VG_(get_supp_kind)(su) == LeakSupp) {
|
|
// We might have the optional match-leak-kinds line
|
|
MC_LeakSuppExtra* lse;
|
|
lse = VG_(malloc)("mc.resi.2", sizeof(MC_LeakSuppExtra));
|
|
lse->match_leak_kinds = RallS;
|
|
lse->blocks_suppressed = 0;
|
|
lse->bytes_suppressed = 0;
|
|
lse->leak_search_gen = 0;
|
|
VG_(set_supp_extra)(su, lse); // By default, all kinds will match.
|
|
eof = VG_(get_line) ( fd, bufpp, nBufp, lineno );
|
|
if (eof) return True; // old LeakSupp style, no match-leak-kinds line.
|
|
if (0 == VG_(strncmp)(*bufpp, "match-leak-kinds:", 17)) {
|
|
i = 17;
|
|
while ((*bufpp)[i] && VG_(isspace((*bufpp)[i])))
|
|
i++;
|
|
if (!MC_(parse_leak_kinds)((*bufpp)+i, &lse->match_leak_kinds)) {
|
|
return False;
|
|
}
|
|
} else {
|
|
return False; // unknown extra line.
|
|
}
|
|
} else if (VG_(get_supp_kind)(su) == FishyValueSupp) {
|
|
MC_FishyValueExtra *extra;
|
|
HChar *p;
|
|
|
|
eof = VG_(get_line) ( fd, bufpp, nBufp, lineno );
|
|
if (eof) return True;
|
|
|
|
extra = VG_(malloc)("mc.resi.3", sizeof *extra);
|
|
extra->function_name = VG_(strdup)("mv.resi.4", *bufpp);
|
|
|
|
// The suppression string is: function_name(argument_name)
|
|
p = VG_(strchr)(extra->function_name, '(');
|
|
if (p == NULL) return False; // malformed suppression string
|
|
*p++ = '\0';
|
|
extra->argument_name = p;
|
|
p = VG_(strchr)(p, ')');
|
|
if (p == NULL) return False; // malformed suppression string
|
|
*p = '\0';
|
|
|
|
VG_(set_supp_extra)(su, extra);
|
|
}
|
|
return True;
|
|
}
|
|
|
|
Bool MC_(error_matches_suppression) ( Error* err, Supp* su )
|
|
{
|
|
Int su_szB;
|
|
MC_Error* extra = VG_(get_error_extra)(err);
|
|
ErrorKind ekind = VG_(get_error_kind )(err);
|
|
|
|
switch (VG_(get_supp_kind)(su)) {
|
|
case ParamSupp:
|
|
return ((ekind == Err_RegParam || ekind == Err_MemParam)
|
|
&& VG_STREQ(VG_(get_error_string)(err),
|
|
VG_(get_supp_string)(su)));
|
|
|
|
case UserSupp:
|
|
return (ekind == Err_User);
|
|
|
|
case CoreMemSupp:
|
|
return (ekind == Err_CoreMem
|
|
&& VG_STREQ(VG_(get_error_string)(err),
|
|
VG_(get_supp_string)(su)));
|
|
|
|
case Value1Supp: su_szB = 1; goto value_case;
|
|
case Value2Supp: su_szB = 2; goto value_case;
|
|
case Value4Supp: su_szB = 4; goto value_case;
|
|
case Value8Supp: su_szB = 8; goto value_case;
|
|
case Value16Supp:su_szB =16; goto value_case;
|
|
value_case:
|
|
return (ekind == Err_Value && extra->Err.Value.szB == su_szB);
|
|
|
|
case CondSupp:
|
|
return (ekind == Err_Cond);
|
|
|
|
case Addr1Supp: su_szB = 1; goto addr_case;
|
|
case Addr2Supp: su_szB = 2; goto addr_case;
|
|
case Addr4Supp: su_szB = 4; goto addr_case;
|
|
case Addr8Supp: su_szB = 8; goto addr_case;
|
|
case Addr16Supp:su_szB =16; goto addr_case;
|
|
addr_case:
|
|
return (ekind == Err_Addr && extra->Err.Addr.szB == su_szB);
|
|
|
|
case JumpSupp:
|
|
return (ekind == Err_Jump);
|
|
|
|
case FreeSupp:
|
|
return (ekind == Err_Free || ekind == Err_FreeMismatch);
|
|
|
|
case OverlapSupp:
|
|
return (ekind == Err_Overlap);
|
|
|
|
case LeakSupp:
|
|
if (ekind == Err_Leak) {
|
|
MC_LeakSuppExtra* lse = (MC_LeakSuppExtra*) VG_(get_supp_extra)(su);
|
|
if (lse->leak_search_gen != MC_(leak_search_gen)) {
|
|
// First time we see this suppression during this leak search.
|
|
// => reset the counters to 0.
|
|
lse->blocks_suppressed = 0;
|
|
lse->bytes_suppressed = 0;
|
|
lse->leak_search_gen = MC_(leak_search_gen);
|
|
}
|
|
return RiS(extra->Err.Leak.lr->key.state, lse->match_leak_kinds);
|
|
} else
|
|
return False;
|
|
|
|
case MempoolSupp:
|
|
return (ekind == Err_IllegalMempool);
|
|
|
|
case FishyValueSupp: {
|
|
MC_FishyValueExtra *supp_extra = VG_(get_supp_extra)(su);
|
|
|
|
return (ekind == Err_FishyValue) &&
|
|
VG_STREQ(extra->Err.FishyValue.function_name,
|
|
supp_extra->function_name) &&
|
|
VG_STREQ(extra->Err.FishyValue.argument_name,
|
|
supp_extra->argument_name);
|
|
}
|
|
|
|
default:
|
|
VG_(printf)("Error:\n"
|
|
" unknown suppression type %d\n",
|
|
VG_(get_supp_kind)(su));
|
|
VG_(tool_panic)("unknown suppression type in "
|
|
"MC_(error_matches_suppression)");
|
|
}
|
|
}
|
|
|
|
const HChar* MC_(get_error_name) ( Error* err )
|
|
{
|
|
switch (VG_(get_error_kind)(err)) {
|
|
case Err_RegParam: return "Param";
|
|
case Err_MemParam: return "Param";
|
|
case Err_User: return "User";
|
|
case Err_FreeMismatch: return "Free";
|
|
case Err_IllegalMempool: return "Mempool";
|
|
case Err_Free: return "Free";
|
|
case Err_Jump: return "Jump";
|
|
case Err_CoreMem: return "CoreMem";
|
|
case Err_Overlap: return "Overlap";
|
|
case Err_Leak: return "Leak";
|
|
case Err_Cond: return "Cond";
|
|
case Err_FishyValue: return "FishyValue";
|
|
case Err_Addr: {
|
|
MC_Error* extra = VG_(get_error_extra)(err);
|
|
switch ( extra->Err.Addr.szB ) {
|
|
case 1: return "Addr1";
|
|
case 2: return "Addr2";
|
|
case 4: return "Addr4";
|
|
case 8: return "Addr8";
|
|
case 16: return "Addr16";
|
|
default: VG_(tool_panic)("unexpected size for Addr");
|
|
}
|
|
}
|
|
case Err_Value: {
|
|
MC_Error* extra = VG_(get_error_extra)(err);
|
|
switch ( extra->Err.Value.szB ) {
|
|
case 1: return "Value1";
|
|
case 2: return "Value2";
|
|
case 4: return "Value4";
|
|
case 8: return "Value8";
|
|
case 16: return "Value16";
|
|
default: VG_(tool_panic)("unexpected size for Value");
|
|
}
|
|
}
|
|
default: VG_(tool_panic)("get_error_name: unexpected type");
|
|
}
|
|
}
|
|
|
|
Bool MC_(get_extra_suppression_info) ( Error* err,
|
|
/*OUT*/HChar* buf, Int nBuf )
|
|
{
|
|
ErrorKind ekind = VG_(get_error_kind )(err);
|
|
tl_assert(buf);
|
|
tl_assert(nBuf >= 16); // stay sane
|
|
if (Err_RegParam == ekind || Err_MemParam == ekind) {
|
|
const HChar* errstr = VG_(get_error_string)(err);
|
|
tl_assert(errstr);
|
|
VG_(snprintf)(buf, nBuf-1, "%s", errstr);
|
|
return True;
|
|
} else if (Err_Leak == ekind) {
|
|
MC_Error* extra = VG_(get_error_extra)(err);
|
|
VG_(snprintf)
|
|
(buf, nBuf-1, "match-leak-kinds: %s",
|
|
pp_Reachedness_for_leak_kinds(extra->Err.Leak.lr->key.state));
|
|
return True;
|
|
} else if (Err_FishyValue == ekind) {
|
|
MC_Error* extra = VG_(get_error_extra)(err);
|
|
VG_(snprintf)
|
|
(buf, nBuf-1, "%s(%s)", extra->Err.FishyValue.function_name,
|
|
extra->Err.FishyValue.argument_name);
|
|
return True;
|
|
} else {
|
|
return False;
|
|
}
|
|
}
|
|
|
|
Bool MC_(print_extra_suppression_use) ( Supp *su,
|
|
/*OUT*/HChar *buf, Int nBuf )
|
|
{
|
|
if (VG_(get_supp_kind)(su) == LeakSupp) {
|
|
MC_LeakSuppExtra *lse = (MC_LeakSuppExtra*) VG_(get_supp_extra) (su);
|
|
|
|
if (lse->leak_search_gen == MC_(leak_search_gen)
|
|
&& lse->blocks_suppressed > 0) {
|
|
VG_(snprintf) (buf, nBuf-1,
|
|
"suppressed: %'lu bytes in %'lu blocks",
|
|
lse->bytes_suppressed,
|
|
lse->blocks_suppressed);
|
|
return True;
|
|
} else
|
|
return False;
|
|
} else
|
|
return False;
|
|
}
|
|
|
|
void MC_(update_extra_suppression_use) ( Error* err, Supp* su)
|
|
{
|
|
if (VG_(get_supp_kind)(su) == LeakSupp) {
|
|
MC_LeakSuppExtra *lse = (MC_LeakSuppExtra*) VG_(get_supp_extra) (su);
|
|
MC_Error* extra = VG_(get_error_extra)(err);
|
|
|
|
tl_assert (lse->leak_search_gen = MC_(leak_search_gen));
|
|
lse->blocks_suppressed += extra->Err.Leak.lr->num_blocks;
|
|
lse->bytes_suppressed
|
|
+= extra->Err.Leak.lr->szB + extra->Err.Leak.lr->indirect_szB;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- end mc_errors.c ---*/
|
|
/*--------------------------------------------------------------------*/
|