Index: third_party/tcmalloc/chromium/src/heap-checker.cc |
diff --git a/third_party/tcmalloc/chromium/src/heap-checker.cc b/third_party/tcmalloc/chromium/src/heap-checker.cc |
index 3f5765e71408ba6df08b01799d640d4cb70778cf..c53646b43ccc31d6e8c5470cd83ff91ca5d95548 100644 |
--- a/third_party/tcmalloc/chromium/src/heap-checker.cc |
+++ b/third_party/tcmalloc/chromium/src/heap-checker.cc |
@@ -52,7 +52,7 @@ |
#include <time.h> |
#include <assert.h> |
-#ifdef HAVE_LINUX_PTRACE_H |
+#if defined(HAVE_LINUX_PTRACE_H) |
#include <linux/ptrace.h> |
#endif |
#ifdef HAVE_SYS_SYSCALL_H |
@@ -73,20 +73,20 @@ |
#include <algorithm> |
#include <functional> |
-#include <google/heap-checker.h> |
+#include <gperftools/heap-checker.h> |
#include "base/basictypes.h" |
#include "base/googleinit.h" |
#include "base/logging.h" |
-#include <google/stacktrace.h> |
+#include <gperftools/stacktrace.h> |
#include "base/commandlineflags.h" |
#include "base/elfcore.h" // for i386_regs |
#include "base/thread_lister.h" |
#include "heap-profile-table.h" |
#include "base/low_level_alloc.h" |
#include "malloc_hook-inl.h" |
-#include <google/malloc_hook.h> |
-#include <google/malloc_extension.h> |
+#include <gperftools/malloc_hook.h> |
+#include <gperftools/malloc_extension.h> |
#include "maybe_threads.h" |
#include "memory_region_map.h" |
#include "base/spinlock.h" |
@@ -144,7 +144,7 @@ DEFINE_string(heap_check, |
"\"minimal\", \"normal\", \"strict\", " |
"\"draconian\", \"as-is\", and \"local\" " |
" or the empty string are the supported choices. " |
- "(See HeapLeakChecker::InternalInitStart for details.)"); |
+ "(See HeapLeakChecker_InternalInitStart for details.)"); |
DEFINE_bool(heap_check_report, true, "Obsolete"); |
@@ -282,10 +282,6 @@ static bool constructor_heap_profiling = false; |
static const int heap_checker_info_level = 0; |
//---------------------------------------------------------------------- |
-// Cancel our InitialMallocHook_* if present. |
-static void CancelInitialMallocHooks(); // defined below |
- |
-//---------------------------------------------------------------------- |
// HeapLeakChecker's own memory allocator that is |
// independent of the normal program allocator. |
//---------------------------------------------------------------------- |
@@ -553,6 +549,23 @@ inline static uintptr_t AsInt(const void* ptr) { |
//---------------------------------------------------------------------- |
+// We've seen reports that strstr causes heap-checker crashes in some |
+// libc's (?): |
+// http://code.google.com/p/gperftools/issues/detail?id=263 |
+// It's simple enough to use our own. This is not in time-critical code. |
+static const char* hc_strstr(const char* s1, const char* s2) { |
+ const size_t len = strlen(s2); |
+ RAW_CHECK(len > 0, "Unexpected empty string passed to strstr()"); |
+ for (const char* p = strchr(s1, *s2); p != NULL; p = strchr(p+1, *s2)) { |
+ if (strncmp(p, s2, len) == 0) { |
+ return p; |
+ } |
+ } |
+ return NULL; |
+} |
+ |
+//---------------------------------------------------------------------- |
+ |
// Our hooks for MallocHook |
static void NewHook(const void* ptr, size_t size) { |
if (ptr != NULL) { |
@@ -560,6 +573,11 @@ static void NewHook(const void* ptr, size_t size) { |
const bool ignore = (counter > 0); |
RAW_VLOG(16, "Recording Alloc: %p of %"PRIuS "; %d", ptr, size, |
int(counter)); |
+ |
+ // Fetch the caller's stack trace before acquiring heap_checker_lock. |
+ void* stack[HeapProfileTable::kMaxStackDepth]; |
+ int depth = HeapProfileTable::GetCallerStackTrace(0, stack); |
+ |
{ SpinLockHolder l(&heap_checker_lock); |
if (size > max_heap_object_size) max_heap_object_size = size; |
uintptr_t addr = AsInt(ptr); |
@@ -567,7 +585,7 @@ static void NewHook(const void* ptr, size_t size) { |
addr += size; |
if (addr > max_heap_address) max_heap_address = addr; |
if (heap_checker_on) { |
- heap_profile->RecordAlloc(ptr, size, 0); |
+ heap_profile->RecordAlloc(ptr, size, depth, stack); |
if (ignore) { |
heap_profile->MarkAsIgnored(ptr); |
} |
@@ -770,6 +788,8 @@ static void MakeDisabledLiveCallbackLocked( |
} |
} |
+static const char kUnnamedProcSelfMapEntry[] = "UNNAMED"; |
+ |
// This function takes some fields from a /proc/self/maps line: |
// |
// start_address start address of a memory region. |
@@ -787,7 +807,9 @@ static void RecordGlobalDataLocked(uintptr_t start_address, |
RAW_DCHECK(heap_checker_lock.IsHeld(), ""); |
// Ignore non-writeable regions. |
if (strchr(permissions, 'w') == NULL) return; |
- if (filename == NULL || *filename == '\0') filename = "UNNAMED"; |
+ if (filename == NULL || *filename == '\0') { |
+ filename = kUnnamedProcSelfMapEntry; |
+ } |
RAW_VLOG(11, "Looking into %s: 0x%" PRIxPTR "..0x%" PRIxPTR, |
filename, start_address, end_address); |
(*library_live_objects)[filename]. |
@@ -799,7 +821,7 @@ static void RecordGlobalDataLocked(uintptr_t start_address, |
// See if 'library' from /proc/self/maps has base name 'library_base' |
// i.e. contains it and has '.' or '-' after it. |
static bool IsLibraryNamed(const char* library, const char* library_base) { |
- const char* p = strstr(library, library_base); |
+ const char* p = hc_strstr(library, library_base); |
size_t sz = strlen(library_base); |
return p != NULL && (p[sz] == '.' || p[sz] == '-'); |
} |
@@ -911,11 +933,11 @@ HeapLeakChecker::ProcMapsResult HeapLeakChecker::UseProcMapsLocked( |
if (inode != 0) { |
saw_nonzero_inode = true; |
} |
- if ((strstr(filename, "lib") && strstr(filename, ".so")) || |
- strstr(filename, ".dll") || |
+ if ((hc_strstr(filename, "lib") && hc_strstr(filename, ".so")) || |
+ hc_strstr(filename, ".dll") || |
// not all .dylib filenames start with lib. .dylib is big enough |
// that we are unlikely to get false matches just checking that. |
- strstr(filename, ".dylib") || strstr(filename, ".bundle")) { |
+ hc_strstr(filename, ".dylib") || hc_strstr(filename, ".bundle")) { |
saw_shared_lib = true; |
if (inode != 0) { |
saw_shared_lib_with_nonzero_inode = true; |
@@ -1391,6 +1413,31 @@ static SpinLock alignment_checker_lock(SpinLock::LINKER_INITIALIZED); |
} |
} |
if (size < sizeof(void*)) continue; |
+ |
+#ifdef NO_FRAME_POINTER |
+ // Frame pointer omission requires us to use libunwind, which uses direct |
+ // mmap and munmap system calls, and that needs special handling. |
+ if (name2 == kUnnamedProcSelfMapEntry) { |
+ static const uintptr_t page_mask = ~(getpagesize() - 1); |
+ const uintptr_t addr = reinterpret_cast<uintptr_t>(object); |
+ if ((addr & page_mask) == 0 && (size & page_mask) == 0) { |
+ // This is an object we slurped from /proc/self/maps. |
+ // It may or may not be readable at this point. |
+ // |
+ // In case all the above conditions made a mistake, and the object is |
+ // not related to libunwind, we also verify that it's not readable |
+ // before ignoring it. |
+ if (msync(const_cast<char*>(object), size, MS_ASYNC) != 0) { |
+ // Skip unreadable object, so we don't crash trying to sweep it. |
+ RAW_VLOG(0, "Ignoring inaccessible object [%p, %p) " |
+ "(msync error %d (%s))", |
+ object, object + size, errno, strerror(errno)); |
+ continue; |
+ } |
+ } |
+ } |
+#endif |
+ |
const char* const max_object = object + size - sizeof(void*); |
while (object <= max_object) { |
// potentially unaligned load: |
@@ -1463,7 +1510,7 @@ void HeapLeakChecker::DisableChecksIn(const char* pattern) { |
} |
// static |
-void HeapLeakChecker::IgnoreObject(const void* ptr) { |
+void HeapLeakChecker::DoIgnoreObject(const void* ptr) { |
SpinLockHolder l(&heap_checker_lock); |
if (!heap_checker_on) return; |
size_t object_size; |
@@ -1477,7 +1524,7 @@ void HeapLeakChecker::IgnoreObject(const void* ptr) { |
IgnoredObjectsMap; |
} |
if (!ignored_objects->insert(make_pair(AsInt(ptr), object_size)).second) { |
- RAW_LOG(FATAL, "Object at %p is already being ignored", ptr); |
+ RAW_LOG(WARNING, "Object at %p is already being ignored", ptr); |
} |
} |
} |
@@ -1867,16 +1914,18 @@ void HeapCleaner::RunHeapCleanups() { |
heap_cleanups_ = NULL; |
} |
-// Program exit heap cleanup registered with atexit(). |
+// Program exit heap cleanup registered as a module object destructor. |
// Will not get executed when we crash on a signal. |
// |
-/*static*/ void HeapLeakChecker::RunHeapCleanups() { |
+void HeapLeakChecker_RunHeapCleanups() { |
+ if (FLAGS_heap_check == "local") // don't check heap in this mode |
+ return; |
{ SpinLockHolder l(&heap_checker_lock); |
// can get here (via forks?) with other pids |
if (heap_checker_pid != getpid()) return; |
} |
HeapCleaner::RunHeapCleanups(); |
- if (!FLAGS_heap_check_after_destructors) DoMainHeapCheck(); |
+ if (!FLAGS_heap_check_after_destructors) HeapLeakChecker::DoMainHeapCheck(); |
} |
static bool internal_init_start_has_run = false; |
@@ -1891,21 +1940,26 @@ static bool internal_init_start_has_run = false; |
// We can't hold heap_checker_lock throughout because it would deadlock |
// on a memory allocation since our new/delete hooks can be on. |
// |
-/*static*/ void HeapLeakChecker::InternalInitStart() { |
+void HeapLeakChecker_InternalInitStart() { |
{ SpinLockHolder l(&heap_checker_lock); |
RAW_CHECK(!internal_init_start_has_run, |
"Heap-check constructor called twice. Perhaps you both linked" |
" in the heap checker, and also used LD_PRELOAD to load it?"); |
internal_init_start_has_run = true; |
+#ifdef ADDRESS_SANITIZER |
+ // AddressSanitizer's custom malloc conflicts with HeapChecker. |
+ FLAGS_heap_check = ""; |
+#endif |
+ |
if (FLAGS_heap_check.empty()) { |
// turns out we do not need checking in the end; can stop profiling |
- TurnItselfOffLocked(); |
+ HeapLeakChecker::TurnItselfOffLocked(); |
return; |
} else if (RunningOnValgrind()) { |
// There is no point in trying -- we'll just fail. |
RAW_LOG(WARNING, "Can't run under Valgrind; will turn itself off"); |
- TurnItselfOffLocked(); |
+ HeapLeakChecker::TurnItselfOffLocked(); |
return; |
} |
} |
@@ -1914,7 +1968,7 @@ static bool internal_init_start_has_run = false; |
if (!FLAGS_heap_check_run_under_gdb && IsDebuggerAttached()) { |
RAW_LOG(WARNING, "Someone is ptrace()ing us; will turn itself off"); |
SpinLockHolder l(&heap_checker_lock); |
- TurnItselfOffLocked(); |
+ HeapLeakChecker::TurnItselfOffLocked(); |
return; |
} |
@@ -1965,15 +2019,25 @@ static bool internal_init_start_has_run = false; |
RAW_LOG(FATAL, "Unsupported heap_check flag: %s", |
FLAGS_heap_check.c_str()); |
} |
+ // FreeBSD doesn't seem to honor atexit execution order: |
+ // http://code.google.com/p/gperftools/issues/detail?id=375 |
+ // Since heap-checking before destructors depends on atexit running |
+ // at the right time, on FreeBSD we always check after, even in the |
+ // less strict modes. This just means FreeBSD is always a bit |
+ // stricter in its checking than other OSes. |
+#ifdef __FreeBSD__ |
+ FLAGS_heap_check_after_destructors = true; |
+#endif |
+ |
{ SpinLockHolder l(&heap_checker_lock); |
RAW_DCHECK(heap_checker_pid == getpid(), ""); |
heap_checker_on = true; |
RAW_DCHECK(heap_profile, ""); |
- ProcMapsResult pm_result = UseProcMapsLocked(DISABLE_LIBRARY_ALLOCS); |
+ HeapLeakChecker::ProcMapsResult pm_result = HeapLeakChecker::UseProcMapsLocked(HeapLeakChecker::DISABLE_LIBRARY_ALLOCS); |
// might neeed to do this more than once |
// if one later dynamically loads libraries that we want disabled |
- if (pm_result != PROC_MAPS_USED) { // can't function |
- TurnItselfOffLocked(); |
+ if (pm_result != HeapLeakChecker::PROC_MAPS_USED) { // can't function |
+ HeapLeakChecker::TurnItselfOffLocked(); |
return; |
} |
} |
@@ -2027,8 +2091,6 @@ static bool internal_init_start_has_run = false; |
"-- Performance may suffer"); |
if (FLAGS_heap_check != "local") { |
- // Schedule registered heap cleanup |
- atexit(RunHeapCleanups); |
HeapLeakChecker* main_hc = new HeapLeakChecker(); |
SpinLockHolder l(&heap_checker_lock); |
RAW_DCHECK(main_heap_checker == NULL, |
@@ -2061,7 +2123,20 @@ static bool internal_init_start_has_run = false; |
// We want this to run early as well, but not so early as |
// ::BeforeConstructors (we want flag assignments to have already |
// happened, for instance). Initializer-registration does the trick. |
-REGISTER_MODULE_INITIALIZER(init_start, HeapLeakChecker::InternalInitStart()); |
+REGISTER_MODULE_INITIALIZER(init_start, HeapLeakChecker_InternalInitStart()); |
+REGISTER_MODULE_DESTRUCTOR(init_start, HeapLeakChecker_RunHeapCleanups()); |
+ |
+// static |
+bool HeapLeakChecker::NoGlobalLeaksMaybeSymbolize( |
+ ShouldSymbolize should_symbolize) { |
+ // we never delete or change main_heap_checker once it's set: |
+ HeapLeakChecker* main_hc = GlobalChecker(); |
+ if (main_hc) { |
+ RAW_VLOG(10, "Checking for whole-program memory leaks"); |
+ return main_hc->DoNoLeaks(should_symbolize); |
+ } |
+ return true; |
+} |
// static |
bool HeapLeakChecker::DoMainHeapCheck() { |
@@ -2074,7 +2149,13 @@ bool HeapLeakChecker::DoMainHeapCheck() { |
do_main_heap_check = false; // will do it now; no need to do it more |
} |
- if (!NoGlobalLeaks()) { |
+ // The program is over, so it's safe to symbolize addresses (which |
+ // requires a fork) because no serious work is expected to be done |
+ // after this. Symbolizing is really useful -- knowing what |
+ // function has a leak is better than knowing just an address -- |
+ // and while we can only safely symbolize once in a program run, |
+ // now is the time (after all, there's no "later" that would be better). |
+ if (!NoGlobalLeaksMaybeSymbolize(SYMBOLIZE)) { |
if (FLAGS_heap_check_identify_leaks) { |
RAW_LOG(FATAL, "Whole-program memory leaks found."); |
} |
@@ -2094,19 +2175,8 @@ HeapLeakChecker* HeapLeakChecker::GlobalChecker() { |
// static |
bool HeapLeakChecker::NoGlobalLeaks() { |
- // we never delete or change main_heap_checker once it's set: |
- HeapLeakChecker* main_hc = GlobalChecker(); |
- if (main_hc) { |
- RAW_VLOG(10, "Checking for whole-program memory leaks"); |
- // The program is over, so it's safe to symbolize addresses (which |
- // requires a fork) because no serious work is expected to be done |
- // after this. Symbolizing is really useful -- knowing what |
- // function has a leak is better than knowing just an address -- |
- // and while we can only safely symbolize once in a program run, |
- // now is the time (after all, there's no "later" that would be better). |
- return main_hc->DoNoLeaks(SYMBOLIZE); |
- } |
- return true; |
+ // symbolizing requires a fork, which isn't safe to do in general. |
+ return NoGlobalLeaksMaybeSymbolize(DO_NOT_SYMBOLIZE); |
} |
// static |
@@ -2124,6 +2194,10 @@ void HeapLeakChecker::BeforeConstructorsLocked() { |
RAW_DCHECK(heap_checker_lock.IsHeld(), ""); |
RAW_CHECK(!constructor_heap_profiling, |
"BeforeConstructorsLocked called multiple times"); |
+#ifdef ADDRESS_SANITIZER |
+ // AddressSanitizer's custom malloc conflicts with HeapChecker. |
+ return; |
+#endif |
// Set hooks early to crash if 'new' gets called before we make heap_profile, |
// and make sure no other hooks existed: |
RAW_CHECK(MallocHook::AddNewHook(&NewHook), ""); |