You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hmz007 36ed224bac
Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a)
1 year ago
..
include/memunreachable Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
tests Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
.clang-format Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
Allocator.cpp Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
Allocator.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
Android.bp Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
AtomicState.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
Binder.cpp Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
Binder.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
HeapWalker.cpp Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
HeapWalker.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
Leak.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
LeakFolding.cpp Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
LeakFolding.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
LeakPipe.cpp Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
LeakPipe.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
LinkedList.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
MemUnreachable.cpp Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
OWNERS Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
PREUPLOAD.cfg Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
ProcessMappings.cpp Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
ProcessMappings.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
PtracerThread.cpp Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
PtracerThread.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
README.md Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
ScopedAlarm.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
ScopedDisableMalloc.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
ScopedPipe.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
ScopedSignalHandler.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
TEST_MAPPING Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
Tarjan.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
ThreadCapture.cpp Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
ThreadCapture.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
bionic.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
libmemunreachable.map Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago
log.h Rockchip Anroid14_SDK 20240628-rkr5 (2556df1a) 1 year ago

README.md

libmemunreachable

Introduction

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.


Usage

In Android apps

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]  "''"

Starting with Android U, new malloc debug options have been added that allow specific sized allocation to be backtraced. The three new options are:

  • backtrace_size
  • backtrace_min_size
  • backtrace_max_size

When enabling backtracing on all allocations, it is possible to have the process run so slowly that the app does not come up. Or the app runs so slowly that the leaks do not occur. The best way to avoid any slowdowns or timeouts is to first run libmemunreachable and look at the sizes of the leaking allocations. If there is only a single allocation size, then use backtrace_size which will indicate that backtraces should only be collected for that exact size. For example, if the output of dumpsys is:

 Unreachable memory
  24 bytes in 2 unreachable allocations
  ABI: 'arm64'

  24 bytes unreachable at 71d37787d0
   first 20 bytes of contents:
   71d37787d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
   71d37787e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

  24 bytes unreachable at 71d37797d0
   first 20 bytes of contents:
   71d37797d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
   71d37797e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

Then set the malloc debug options thusly:

adb shell setprop libc.debug.malloc.options "'backtrace backtrace_size=24'"

This will backtrace only 24 byte allocations.

If the output of libmemunreachable has multiple sized allocations, set the backtrace_min_size and backtrace_max_size options to cover all of the sizes. For example, if the output of dumpsys is:

 Unreachable memory
  512 bytes in 2 unreachable allocations
  ABI: 'arm64'

  320 bytes unreachable at 71d37787d0
   first 20 bytes of contents:
   71d37787d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
   71d37787e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

  192 bytes unreachable at 71b37b2f50
   first 20 bytes of contents:
   71b37b2f50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
   71b37b2f60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

Then set the malloc debug options thusly:

adb shell setprop libc.debug.malloc.options "'backtrace backtrace_min_size=192 backtrace_max_size=320'"

This will backtrace allocations of any size between 192 bytes and 320 bytes inclusively.

After setting the backtrace size options, restart the application so that running dumpsys again will include the actual backtrace of the leaking allocations.

C interface

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.

C++ interface

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.

Implementation

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.

  1. Original process: Leak detection is requested by calling GetUnreachableMemory()
  2. Allocations are disabled using malloc_disable()
  3. The collection process is spawned. The collection process, created using clone, is similar to a normal 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.
  4. Collection process: All threads in the original process are paused with ptrace().
  5. Registers contents, active stack areas, and memory mapping information are collected.
  6. Original process: Allocations are re-enabled using malloc_enable(), but all threads are still paused with ptrace().
  7. Collection process: The sweeper process is spawned using a normal fork(). The sweeper process has a copy of all memory from the original process, including all the data collected by the collection process.
  8. Collection process releases all threads from ptrace and exits
  9. Original process: All threads continue, the thread that called GetUnreachableMemory() blocks waiting for leak data over a pipe.
  10. Sweeper process: A list of all active allocations is produced by examining the memory mappings and calling malloc_iterate() on any heap mappings.
  11. A list of all roots is produced from globals (.data and .bss sections of binaries), and registers and stacks from each thread.
  12. The mark-and-sweep pass is performed starting from roots.
  13. Unmarked allocations are sent over the pipe back to the original process.

Components

  • 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.

Heap allocator requirements

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.