README.md
1libmemunreachable
2================
3
4Introduction
5--------------
6libmemunreachable 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](http://htmlpreview.github.io/?https://github.com/gperftools/gperftools/blob/master/doc/heap_checker.html), 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.
7
8In 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.
9
10----------
11
12Usage
13-------
14
15### In Android apps ###
16
17libmemunreachble is loaded by zygote and can be triggered with `dumpsys -t 600 meminfo --unreachable [process]`.
18
19To enable malloc\_debug backtraces on allocations for a single app process on a userdebug device, use:
20```
21adb root
22adb shell setprop libc.debug.malloc.program app_process
23adb shell setprop wrap.[process] "\$\@"
24adb shell setprop libc.debug.malloc.options backtrace=4
25```
26
27Kill and restart the app, trigger the leak, and then run `dumpsys -t 600 meminfo --unreachable [process]`.
28
29To disable malloc\_debug:
30```
31adb shell setprop libc.debug.malloc.options "''"
32adb shell setprop libc.debug.malloc.program "''"
33adb shell setprop wrap.[process] "''"
34```
35
36Starting with Android U, new malloc debug options have been added
37that allow specific sized allocation to be backtraced. The three
38new options are:
39
40- backtrace\_size
41- backtrace\_min\_size
42- backtrace\_max\_size
43
44When enabling backtracing on all allocations, it is possible to have
45the process run so slowly that the app does not come up. Or the app
46runs so slowly that the leaks do not occur. The best way to avoid any
47slowdowns or timeouts is to first run libmemunreachable and look at
48the sizes of the leaking allocations. If there is only a single
49allocation size, then use backtrace\_size which will indicate that
50backtraces should only be collected for that exact size. For example,
51if the output of dumpsys is:
52
53```
54 Unreachable memory
55 24 bytes in 2 unreachable allocations
56 ABI: 'arm64'
57
58 24 bytes unreachable at 71d37787d0
59 first 20 bytes of contents:
60 71d37787d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
61 71d37787e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
62
63 24 bytes unreachable at 71d37797d0
64 first 20 bytes of contents:
65 71d37797d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
66 71d37797e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
67```
68
69Then set the malloc debug options thusly:
70
71```
72adb shell setprop libc.debug.malloc.options "'backtrace backtrace_size=24'"
73```
74
75This will backtrace only 24 byte allocations.
76
77If the output of libmemunreachable has multiple sized allocations, set
78the backtrace\_min\_size and backtrace\_max\_size options to cover all
79of the sizes. For example, if the output of dumpsys is:
80
81```
82 Unreachable memory
83 512 bytes in 2 unreachable allocations
84 ABI: 'arm64'
85
86 320 bytes unreachable at 71d37787d0
87 first 20 bytes of contents:
88 71d37787d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
89 71d37787e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
90
91 192 bytes unreachable at 71b37b2f50
92 first 20 bytes of contents:
93 71b37b2f50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
94 71b37b2f60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
95```
96
97Then set the malloc debug options thusly:
98
99```
100adb shell setprop libc.debug.malloc.options "'backtrace backtrace_min_size=192 backtrace_max_size=320'"
101```
102
103This will backtrace allocations of any size between 192 bytes and 320 bytes
104inclusively.
105
106After setting the backtrace size options, restart the application so
107that running dumpsys again will include the actual backtrace of the
108leaking allocations.
109
110### C interface ###
111
112#### `bool LogUnreachableMemory(bool log_contents, size_t limit)` ####
113Writes 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.
114Returns true if leak detection succeeded.
115
116#### `bool NoLeaks()` ####
117Returns `true` if no unreachable memory was found.
118
119### C++ interface ###
120
121#### `bool GetUnreachableMemory(UnreachableMemoryInfo& info, size_t limit = 100)` ####
122Updates an `UnreachableMemoryInfo` object with information on leaks, including details on up to `limit` leaks. Returns true if leak detection succeeded.
123
124#### `std::string GetUnreachableMemoryString(bool log_contents = false, size_t limit = 100)` ####
125Returns 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.
126Returns true if leak detection succeeded.
127
128Implementation
129-------------------
130The 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.
131
132 1. *Original process*: Leak detection is requested by calling `GetUnreachableMemory()`
133 2. Allocations are disabled using `malloc_disable()`
134 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.
135 4. *Collection process*: All threads in the original process are paused with `ptrace()`.
136 5. Registers contents, active stack areas, and memory mapping information are collected.
137 6. *Original process*: Allocations are re-enabled using `malloc_enable()`, but all threads are still paused with `ptrace()`.
138 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.
139 8. Collection process releases all threads from `ptrace` and exits
140 9. *Original process*: All threads continue, the thread that called `GetUnreachableMemory()` blocks waiting for leak data over a pipe.
141 10. *Sweeper process*: A list of all active allocations is produced by examining the memory mappings and calling `malloc_iterate()` on any heap mappings.
142 11. A list of all roots is produced from globals (.data and .bss sections of binaries), and registers and stacks from each thread.
143 12. The mark-and-sweep pass is performed starting from roots.
144 13. Unmarked allocations are sent over the pipe back to the original process.
145
146----------
147
148
149Components
150---------------
151- `MemUnreachable.cpp`: Entry points, implements the sequencing described above.
152- `PtracerThread.cpp`: Used to clone the collection process with shared address space.
153- `ThreadCapture.cpp`: Pauses threads in the main process and collects register contents.
154- `ProcessMappings.cpp`: Collects snapshots of `/proc/pid/maps`.
155- `HeapWalker.cpp`: Performs the mark-and-sweep pass over active allocations.
156- `LeakPipe.cpp`: transfers data describing leaks from the sweeper process to the original process.
157
158
159Heap allocator requirements
160----------------------------------
161libmemunreachable requires a small interface to the allocator in order to collect information about active allocations.
162
163 - `malloc_disable()`: prevent any thread from mutating internal allocator state.
164 - `malloc enable()`: re-enable allocations in all threads.
165 - `malloc_iterate()`: call a callback on each active allocation in a given heap region.
166 - `malloc_backtrace()`: return the backtrace from when the allocation at the given address was allocated, if it was collected.
167