| Index: third_party/tcmalloc/chromium/src/heap-checker.cc
|
| ===================================================================
|
| --- third_party/tcmalloc/chromium/src/heap-checker.cc (revision 88335)
|
| +++ third_party/tcmalloc/chromium/src/heap-checker.cc (working copy)
|
| @@ -106,6 +106,28 @@
|
| using std::less;
|
| using std::char_traits;
|
|
|
| +// If current process is being ptrace()d, 'TracerPid' in /proc/self/status
|
| +// will be non-zero.
|
| +static bool IsDebuggerAttached(void) { // only works under linux, probably
|
| + char buf[256]; // TracerPid comes relatively earlier in status output
|
| + int fd = open("/proc/self/status", O_RDONLY);
|
| + if (fd == -1) {
|
| + return false; // Can't tell for sure.
|
| + }
|
| + const int len = read(fd, buf, sizeof(buf));
|
| + bool rc = false;
|
| + if (len > 0) {
|
| + const char *const kTracerPid = "TracerPid:\t";
|
| + buf[len - 1] = '\0';
|
| + const char *p = strstr(buf, kTracerPid);
|
| + if (p != NULL) {
|
| + rc = (strncmp(p + strlen(kTracerPid), "0\n", 2) != 0);
|
| + }
|
| + }
|
| + close(fd);
|
| + return rc;
|
| +}
|
| +
|
| // This is the default if you don't link in -lprofiler
|
| extern "C" {
|
| ATTRIBUTE_WEAK PERFTOOLS_DLL_DECL bool ProfilingIsEnabledForAllThreads();
|
| @@ -163,12 +185,6 @@
|
| // use 1 if any alignment is ok.
|
| // heap_check_test_pointer_alignment flag guides if we try the value of 1.
|
| // The larger it can be, the lesser is the chance of missing real leaks.
|
| -//
|
| -// sizeof(void)* is correct. However gold (the new linker) has a bug where it
|
| -// sometimes places global pointers on 4-byte boundaries, even when pointers
|
| -// are 8 bytes long. While we are fixing the linker, degrade to 4-byte
|
| -// alignment on all targets. http://b/1226481
|
| -//
|
| static const size_t kPointerSourceAlignment = sizeof(void*);
|
| DEFINE_int32(heap_check_pointer_source_alignment,
|
| EnvToInt("HEAP_CHECK_POINTER_SOURCE_ALIGNMENT",
|
| @@ -304,6 +320,9 @@
|
| if (p) alloc_count_ -= 1;
|
| LowLevelAlloc::Free(p);
|
| }
|
| + static void Free(void* p, size_t /* n */) {
|
| + Free(p);
|
| + }
|
| // destruct, free, and make *p to be NULL
|
| template<typename T> static void DeleteAndNull(T** p) {
|
| (*p)->~T();
|
| @@ -795,7 +814,7 @@
|
| // pthread_setspecific (which can be the only pointer to a heap object).
|
| IsLibraryNamed(library, "/libdl") ||
|
| // library loaders leak some "system" heap that we don't care about
|
| - IsLibraryNamed(library, "/libcrypto")
|
| + IsLibraryNamed(library, "/libcrypto") ||
|
| // Sometimes libcrypto of OpenSSH is compiled with -fomit-frame-pointer
|
| // (any library can be, of course, but this one often is because speed
|
| // is so important for making crypto usable). We ignore all its
|
| @@ -803,6 +822,10 @@
|
| // to ignore allocations done in files/symbols that match
|
| // "default_malloc_ex|default_realloc_ex"
|
| // but that doesn't work when the end-result binary is stripped.
|
| + IsLibraryNamed(library, "/libjvm") ||
|
| + // JVM has a lot of leaks we don't care about.
|
| + IsLibraryNamed(library, "/libzip")
|
| + // The JVM leaks java.util.zip.Inflater after loading classes.
|
| ) {
|
| depth = 1; // only disable allocation calls directly from the library code
|
| } else if (IsLibraryNamed(library, "/ld")
|
| @@ -859,6 +882,8 @@
|
| int64 inode;
|
| char *permissions, *filename;
|
| bool saw_shared_lib = false;
|
| + bool saw_nonzero_inode = false;
|
| + bool saw_shared_lib_with_nonzero_inode = false;
|
| while (it.Next(&start_address, &end_address, &permissions,
|
| &file_offset, &inode, &filename)) {
|
| if (start_address >= end_address) {
|
| @@ -874,10 +899,25 @@
|
| // do things in this loop.
|
| continue;
|
| }
|
| - // Determine if any shared libraries are present.
|
| - if (inode != 0 && strstr(filename, "lib") && strstr(filename, ".so")) {
|
| + // Determine if any shared libraries are present (this is the same
|
| + // list of extensions as is found in pprof). We want to ignore
|
| + // 'fake' libraries with inode 0 when determining. However, some
|
| + // systems don't share inodes via /proc, so we turn off this check
|
| + // if we don't see any evidence that we're getting inode info.
|
| + if (inode != 0) {
|
| + saw_nonzero_inode = true;
|
| + }
|
| + if ((strstr(filename, "lib") && strstr(filename, ".so")) ||
|
| + 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")) {
|
| saw_shared_lib = true;
|
| + if (inode != 0) {
|
| + saw_shared_lib_with_nonzero_inode = true;
|
| + }
|
| }
|
| +
|
| switch (proc_maps_task) {
|
| case DISABLE_LIBRARY_ALLOCS:
|
| // All lines starting like
|
| @@ -895,6 +935,12 @@
|
| RAW_CHECK(0, "");
|
| }
|
| }
|
| + // If /proc/self/maps is reporting inodes properly (we saw a
|
| + // non-zero inode), then we only say we saw a shared lib if we saw a
|
| + // 'real' one, with a non-zero inode.
|
| + if (saw_nonzero_inode) {
|
| + saw_shared_lib = saw_shared_lib_with_nonzero_inode;
|
| + }
|
| if (!saw_shared_lib) {
|
| RAW_LOG(ERROR, "No shared libs detected. Will likely report false leak "
|
| "positives for statically linked executables.");
|
| @@ -962,7 +1008,8 @@
|
| // specially via self_thread_stack, not here:
|
| if (thread_pids[i] == self_thread_pid) continue;
|
| RAW_VLOG(11, "Handling thread with pid %d", thread_pids[i]);
|
| -#if defined(HAVE_LINUX_PTRACE_H) && defined(HAVE_SYS_SYSCALL_H) && defined(DUMPER)
|
| +#if (defined(__i386__) || defined(__x86_64)) && \
|
| + defined(HAVE_LINUX_PTRACE_H) && defined(HAVE_SYS_SYSCALL_H) && defined(DUMPER)
|
| i386_regs thread_regs;
|
| #define sys_ptrace(r, p, a, d) syscall(SYS_ptrace, (r), (p), (a), (d))
|
| // We use sys_ptrace to avoid thread locking
|
| @@ -1633,6 +1680,13 @@
|
| return true;
|
| }
|
|
|
| + // Update global_region_caller_ranges. They may need to change since
|
| + // e.g. initialization because shared libraries might have been loaded or
|
| + // unloaded.
|
| + Allocator::DeleteAndNullIfNot(&global_region_caller_ranges);
|
| + ProcMapsResult pm_result = UseProcMapsLocked(DISABLE_LIBRARY_ALLOCS);
|
| + RAW_CHECK(pm_result == PROC_MAPS_USED, "");
|
| +
|
| // Keep track of number of internally allocated objects so we
|
| // can detect leaks in the heap-leak-checket itself
|
| const int initial_allocs = Allocator::alloc_count();
|
| @@ -1645,18 +1699,6 @@
|
| MemoryRegionMap::LockHolder ml;
|
| int a_local_var; // Use our stack ptr to make stack data live:
|
|
|
| - // Sanity check that nobody is messing with the hooks we need:
|
| - // Important to have it here: else we can misteriously SIGSEGV
|
| - // in IgnoreLiveObjectsLocked inside ListAllProcessThreads's callback
|
| - // by looking into a region that got unmapped w/o our knowledge.
|
| - MemoryRegionMap::CheckMallocHooks();
|
| - if (MallocHook::GetNewHook() != NewHook ||
|
| - MallocHook::GetDeleteHook() != DeleteHook) {
|
| - RAW_LOG(FATAL, "Had our new/delete MallocHook-s replaced. "
|
| - "Are you using another MallocHook client? "
|
| - "Use --heap_check=\"\" to avoid this conflict.");
|
| - }
|
| -
|
| // Make the heap profile, other threads are locked out.
|
| HeapProfileTable::Snapshot* base =
|
| reinterpret_cast<HeapProfileTable::Snapshot*>(start_snapshot_);
|
| @@ -1833,9 +1875,6 @@
|
| if (!FLAGS_heap_check_after_destructors) DoMainHeapCheck();
|
| }
|
|
|
| -// defined below
|
| -static int GetCommandLineFrom(const char* file, char* cmdline, int size);
|
| -
|
| static bool internal_init_start_has_run = false;
|
|
|
| // Called exactly once, before main() (but hopefully just before).
|
| @@ -1859,29 +1898,20 @@
|
| // turns out we do not need checking in the end; can stop profiling
|
| 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();
|
| + return;
|
| }
|
| }
|
|
|
| // Changing this to false can be useful when debugging heap-checker itself:
|
| - if (!FLAGS_heap_check_run_under_gdb) {
|
| - // See if heap checker should turn itself off because we are
|
| - // running under gdb (to avoid conflicts over ptrace-ing rights):
|
| - char name_buf[15+15];
|
| - snprintf(name_buf, sizeof(name_buf),
|
| - "/proc/%d/cmdline", static_cast<int>(getppid()));
|
| - char cmdline[1024*8]; // /proc/*/cmdline is at most 4Kb anyway usually
|
| - int size = GetCommandLineFrom(name_buf, cmdline, sizeof(cmdline)-1);
|
| - cmdline[size] = '\0';
|
| - // look for "gdb" in the executable's name:
|
| - const char* last = strrchr(cmdline, '/');
|
| - if (last) last += 1;
|
| - else last = cmdline;
|
| - if (strncmp(last, "gdb", 3) == 0) {
|
| - RAW_LOG(WARNING, "We seem to be running under gdb; will turn itself off");
|
| - SpinLockHolder l(&heap_checker_lock);
|
| - TurnItselfOffLocked();
|
| - return;
|
| - }
|
| + 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();
|
| + return;
|
| }
|
|
|
| { SpinLockHolder l(&heap_checker_lock);
|
| @@ -2084,98 +2114,15 @@
|
| }
|
| }
|
|
|
| -//----------------------------------------------------------------------
|
| -// HeapLeakChecker global constructor/destructor ordering components
|
| -//----------------------------------------------------------------------
|
| -
|
| -static bool in_initial_malloc_hook = false;
|
| -
|
| -#ifdef HAVE___ATTRIBUTE__ // we need __attribute__((weak)) for this to work
|
| -#define INSTALLED_INITIAL_MALLOC_HOOKS
|
| -
|
| -void HeapLeakChecker_BeforeConstructors(); // below
|
| -
|
| -// Helper for InitialMallocHook_* below
|
| -static inline void InitHeapLeakCheckerFromMallocHook() {
|
| - { SpinLockHolder l(&heap_checker_lock);
|
| - RAW_CHECK(!in_initial_malloc_hook,
|
| - "Something did not reset initial MallocHook-s");
|
| - in_initial_malloc_hook = true;
|
| - }
|
| - // Initialize heap checker on the very first allocation/mmap/sbrk call:
|
| - HeapLeakChecker_BeforeConstructors();
|
| - { SpinLockHolder l(&heap_checker_lock);
|
| - in_initial_malloc_hook = false;
|
| - }
|
| -}
|
| -
|
| -// These will owerwrite the weak definitions in malloc_hook.cc:
|
| -
|
| -// Important to have this to catch the first allocation call from the binary:
|
| -extern void InitialMallocHook_New(const void* ptr, size_t size) {
|
| - InitHeapLeakCheckerFromMallocHook();
|
| - // record this first allocation as well (if we need to):
|
| - MallocHook::InvokeNewHook(ptr, size);
|
| -}
|
| -
|
| -// Important to have this to catch the first mmap call (say from tcmalloc):
|
| -extern void InitialMallocHook_MMap(const void* result,
|
| - const void* start,
|
| - size_t size,
|
| - int protection,
|
| - int flags,
|
| - int fd,
|
| - off_t offset) {
|
| - InitHeapLeakCheckerFromMallocHook();
|
| - // record this first mmap as well (if we need to):
|
| - MallocHook::InvokeMmapHook(
|
| - result, start, size, protection, flags, fd, offset);
|
| -}
|
| -
|
| -// Important to have this to catch the first sbrk call (say from tcmalloc):
|
| -extern void InitialMallocHook_Sbrk(const void* result, std::ptrdiff_t increment) {
|
| - InitHeapLeakCheckerFromMallocHook();
|
| - // record this first sbrk as well (if we need to):
|
| - MallocHook::InvokeSbrkHook(result, increment);
|
| -}
|
| -
|
| // static
|
| -void CancelInitialMallocHooks() {
|
| - if (MallocHook::GetNewHook() == InitialMallocHook_New) {
|
| - MallocHook::SetNewHook(NULL);
|
| - }
|
| - RAW_DCHECK(MallocHook::GetNewHook() == NULL, "");
|
| - if (MallocHook::GetMmapHook() == InitialMallocHook_MMap) {
|
| - MallocHook::SetMmapHook(NULL);
|
| - }
|
| - RAW_DCHECK(MallocHook::GetMmapHook() == NULL, "");
|
| - if (MallocHook::GetSbrkHook() == InitialMallocHook_Sbrk) {
|
| - MallocHook::SetSbrkHook(NULL);
|
| - }
|
| - RAW_DCHECK(MallocHook::GetSbrkHook() == NULL, "");
|
| -}
|
| -
|
| -#else
|
| -
|
| -// static
|
| -void CancelInitialMallocHooks() {}
|
| -
|
| -#endif
|
| -
|
| -// static
|
| void HeapLeakChecker::BeforeConstructorsLocked() {
|
| RAW_DCHECK(heap_checker_lock.IsHeld(), "");
|
| RAW_CHECK(!constructor_heap_profiling,
|
| "BeforeConstructorsLocked called multiple times");
|
| - CancelInitialMallocHooks();
|
| // Set hooks early to crash if 'new' gets called before we make heap_profile,
|
| // and make sure no other hooks existed:
|
| - if (MallocHook::SetNewHook(NewHook) != NULL ||
|
| - MallocHook::SetDeleteHook(DeleteHook) != NULL) {
|
| - RAW_LOG(FATAL, "Had other new/delete MallocHook-s set. "
|
| - "Somehow leak checker got activated "
|
| - "after something else have set up these hooks.");
|
| - }
|
| + RAW_CHECK(MallocHook::AddNewHook(&NewHook), "");
|
| + RAW_CHECK(MallocHook::AddDeleteHook(&DeleteHook), "");
|
| constructor_heap_profiling = true;
|
| MemoryRegionMap::Init(1);
|
| // Set up MemoryRegionMap with (at least) one caller stack frame to record
|
| @@ -2198,12 +2145,9 @@
|
| RAW_CHECK(heap_checker_on, "");
|
| RAW_VLOG(heap_checker_info_level, "Turning perftools heap leak checking off");
|
| heap_checker_on = false;
|
| - // Unset our hooks checking they were the ones set:
|
| - if (MallocHook::SetNewHook(NULL) != NewHook ||
|
| - MallocHook::SetDeleteHook(NULL) != DeleteHook) {
|
| - RAW_LOG(FATAL, "Had our new/delete MallocHook-s replaced. "
|
| - "Are you using another MallocHook client?");
|
| - }
|
| + // Unset our hooks checking they were set:
|
| + RAW_CHECK(MallocHook::RemoveNewHook(&NewHook), "");
|
| + RAW_CHECK(MallocHook::RemoveDeleteHook(&DeleteHook), "");
|
| Allocator::DeleteAndNull(&heap_profile);
|
| // free our optional global data:
|
| Allocator::DeleteAndNullIfNot(&ignored_objects);
|
| @@ -2215,57 +2159,13 @@
|
| RAW_CHECK(!heap_checker_on, "");
|
| }
|
|
|
| -// Read in the command line from 'file' into 'cmdline' and return the size read
|
| -// 'size' is the space available in 'cmdline'.
|
| -// We need this because we don't yet have argv/argc.
|
| -// CAVEAT: 'file' (some /proc/*/cmdline) usually contains the command line
|
| -// already truncated (to 4K on Linux).
|
| -// Arguments in cmdline will be '\0'-terminated,
|
| -// the first one will be the binary's name.
|
| -static int GetCommandLineFrom(const char* file, char* cmdline, int size) {
|
| - // This routine is only used to check if we're running under gdb, so
|
| - // it's ok if this #if fails and the routine is a no-op.
|
| - //
|
| - // This function is called before memory allocation hooks are set up
|
| - // so we must not have any memory allocations in it. We use syscall
|
| - // versions of open/read/close here because we don't trust the non-syscall
|
| - // versions: they might 'accidentally' cause a memory allocation.
|
| - // Here's a real-life problem scenario we had:
|
| - // 1) A program LD_PRELOADed a library called list_file_used.a
|
| - // 2) list_file_used intercepted open/read/close and called dlsym()
|
| - // 3) dlsym() called pthread_setspecific() which called malloc().
|
| - // This malloced memory is 'hidden' from the heap-checker. By
|
| - // definition, this thread-local data is live, and everything it points
|
| - // to is live (not a memory leak) as well. But because this memory
|
| - // was hidden from the heap-checker, everything it points to was
|
| - // taken to be orphaned, and therefore, a memory leak.
|
| -#if defined(_WIN32) || defined(__CYGWIN__) || defined(__CYGWIN32__) || defined(__MINGW32__)
|
| - // Use a win32 call to get the command line.
|
| - const char* command_line = ::GetCommandLine();
|
| - strncpy(cmdline, command_line, size);
|
| - cmdline[size - 1] = '\0';
|
| - return strlen(cmdline);
|
| -#elif defined(HAVE_SYS_SYSCALL_H)
|
| - int fd = syscall(SYS_open, file, O_RDONLY);
|
| - int result = 0;
|
| - if (fd >= 0) {
|
| - ssize_t r;
|
| - while ((r = syscall(SYS_read, fd, cmdline + result, size)) > 0) {
|
| - result += r;
|
| - size -= r;
|
| - }
|
| - syscall(SYS_close, fd);
|
| - }
|
| - return result;
|
| -#else
|
| - return 0;
|
| -#endif
|
| -}
|
| -
|
| extern bool heap_leak_checker_bcad_variable; // in heap-checker-bcad.cc
|
|
|
| static bool has_called_before_constructors = false;
|
|
|
| +// TODO(maxim): inline this function with
|
| +// MallocHook_InitAtFirstAllocation_HeapLeakChecker, and also rename
|
| +// HeapLeakChecker::BeforeConstructorsLocked.
|
| void HeapLeakChecker_BeforeConstructors() {
|
| SpinLockHolder l(&heap_checker_lock);
|
| // We can be called from several places: the first mmap/sbrk/alloc call
|
| @@ -2304,11 +2204,19 @@
|
| #endif
|
| if (need_heap_check) {
|
| HeapLeakChecker::BeforeConstructorsLocked();
|
| - } else { // cancel our initial hooks
|
| - CancelInitialMallocHooks();
|
| }
|
| }
|
|
|
| +// This function overrides the weak function defined in malloc_hook.cc and
|
| +// called by one of the initial malloc hooks (malloc_hook.cc) when the very
|
| +// first memory allocation or an mmap/sbrk happens. This ensures that
|
| +// HeapLeakChecker is initialized and installs all its hooks early enough to
|
| +// track absolutely all memory allocations and all memory region acquisitions
|
| +// via mmap and sbrk.
|
| +extern "C" void MallocHook_InitAtFirstAllocation_HeapLeakChecker() {
|
| + HeapLeakChecker_BeforeConstructors();
|
| +}
|
| +
|
| // This function is executed after all global object destructors run.
|
| void HeapLeakChecker_AfterDestructors() {
|
| { SpinLockHolder l(&heap_checker_lock);
|
|
|