commit | 495a383b71969fec9ee56cae3894db5935bf1110 | [log] [tgz] |
---|---|---|
author | Colin Cross <ccross@android.com> | Mon May 14 14:55:39 2018 -0700 |
committer | Colin Cross <ccross@android.com> | Mon May 14 22:17:44 2018 -0700 |
tree | 8c9b311c146ad400f7248740305289c13a50b455 | |
parent | 9fe39ae42541d7116150d3f3a709770a5753a870 [diff] |
Make memunreachable_test more robust against false negative leaks For some reason, the memunreachable tests are rock solid on the devices covered by APCT, but catch a ton of false-negatives on hikey960, which show up as failures that look like: system/core/libmemunreachable/tests/MemUnreachable_test.cpp:200: Failure Expected equality of these values: 1U Which is: 1 info.leaks.size() Which is: 0 These happen when a stray copy of a pointer is lying around that points to the memory it is expected to leak. The stray pointers can be on the stack or in the jemalloc thread cache of freed allocations, which is always considered active memory. Add some extra cleanups to get rid of old pointers. 1. Clear the tcache when destructing UnreachableMemoryInfo 2. Clear the stack and tcache before and after each test 3. Make MemunreachbleTest.twice match MemunreachableTest.stack Also fix MemunreachableTest.notdumpable, which was only passing when run as root, which was bypassing what the test was trying to cover. Make the test pass when run as non-root, and skip when the test is running as root. Bug: 79701104 Test: memunreachable_test Test: memunreachable_test as root Change-Id: Ia6c6df11e76405d08118afcc19c1fe80a6684c56 Merged-In: Ia6c6df11e76405d08118afcc19c1fe80a6684c56 (cherry picked from commit ca71f170b78e4707dbaa5bf4ddace47a455b4699)
libmemunreachable is a zero-overhead native memory leak detector. It uses an imprecise mark-and-sweep garbage collector pass over all native memory, reporting any unreachable blocks as leaks. It is similar to the Heap Checker from tcmalloc, but with a few key differences to remove the overhead. Instead of instrumenting every call to malloc and free, it queries the allocator (jemalloc) for active allocations when leak detection is requested. In addition, it performs a very short stop-the-world data collection on the main process, and then forks a copy of the process to perform the mark-and-sweep, minimizing disruption to the original process.
In the default (zero-overhead) mode, the returned data on leaks is limited to the address, approximate (upper bound) size, and the the first 32 bytes of the contents of the leaked allocation. If malloc_debug backtraces are enabled they will be included in the leak information, but backtracing allocations requires significant overhead.
bool LogUnreachableMemory(bool log_contents, size_t limit)
Writes a description of leaked memory to the log. A summary is always written, followed by details of up to limit
leaks. If log_contents
is true
, details include up to 32 bytes of the contents of each leaked allocation. Returns true if leak detection succeeded.
bool NoLeaks()
Returns true
if no unreachable memory was found.
####bool GetUnreachableMemory(UnreachableMemoryInfo& info, size_t limit = 100)
#### Updates an UnreachableMemoryInfo
object with information on leaks, including details on up to limit
leaks. Returns true if leak detection succeeded.
std::string GetUnreachableMemoryString(bool log_contents = false, size_t limit = 100)
Returns a description of leaked memory. A summary is always written, followed by details of up to limit
leaks. If log_contents
is true
, details include up to 32 bytes of the contents of each leaked allocation. Returns true if leak detection succeeded.
The sequence of steps required to perform a leak detection pass is divided into three processes - the original process, the collection process, and the sweeper process.
GetUnreachableMemory()
malloc_disable()
fork()
child process, except that it shares the address space of the parent - any writes by the original process are visible to the collection process, and vice-versa. If we forked instead of using clone, the address space might get out of sync with observed post-ptrace thread state, since it takes some time to pause the parent.ptrace()
.malloc_enable()
, but all threads are still paused with ptrace()
.fork()
. The sweeper process has a copy of all memory from the original process, including all the data collected by the collection process.ptrace
and exitsGetUnreachableMemory()
blocks waiting for leak data over a pipe.malloc_iterate()
on any heap mappings.MemUnreachable.cpp
: Entry points, implements the sequencing described above.PtracerThread.cpp
: Used to clone the collection process with shared address space.ThreadCapture.cpp
: Pauses threads in the main process and collects register contents.ProcessMappings.cpp
: Collects snapshots of /proc/pid/maps
.HeapWalker.cpp
: Performs the mark-and-sweep pass over active allocations.LeakPipe.cpp
: transfers data describing leaks from the sweeper process to the original process.libmemunreachable requires a small interface to the allocator in order to collect information about active allocations.
malloc_disable()
: prevent any thread from mutating internal allocator state.malloc enable()
: re-enable allocations in all threads.malloc_iterate()
: call a callback on each active allocation in a given heap region.malloc_backtrace()
: return the backtrace from when the allocation at the given address was allocated, if it was collected.