libmemunreachble is loaded by zygote and can be triggered with dumpsys -t 600 meminfo --unreachable [process]
.
To enable malloc_debug backtraces on allocations for a single app process on a userdebug device, use:
adb root adb shell setprop libc.debug.malloc.program app_process adb shell setprop wrap.[process] "\$\@" adb shell setprop libc.debug.malloc.options backtrace=4
Kill and restart the app, trigger the leak, and then run dumpsys -t 600 meminfo --unreachable [process]
.
To disable malloc_debug:
adb shell setprop libc.debug.malloc.options "''" adb shell setprop libc.debug.malloc.program "''" adb shell setprop wrap.[process] "''"
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.