Using AddressSanitizer and Valgrind on Android recovery
Debugging memory issues can be very hard or almost impossible when done by hand. Luckily it's relatively easy to detect memory issues with the proper tools. Unfortunately, using them while working on an Android recovery like TWRP is much harder, as the environment is very limited and a bit different from a normal Android system.
There are many kinds of memory errors, not all of which are detected. For example:
- NULL-pointer dereferences
- Buffer overflows (read/write)
- Reading from uninitialized memory
- Race conditions: reading from one thread while writing from another thread
- ...and many more
- AddressSanitizer is a simple compile flag (
-fsanitize=address) for Clang1 and a runtime library + linker. It is very fast as it inserts only simple checks in the output.
- Valgrind operates on unmodified binaries. It acts like a VM interpreting the whole binary but checks every memory operation for safety, thus using it incurs a big slowdown. The advantage is that you don't have to recompile binaries. In my limited experience it's more thorough but can sometimes change how the program behaves. It also needs a library to run but not a modified linker.
Which one you choose mostly depends on which is easier to setup or provides the required features.
NOTE: This guide only applies to Android recovery images (like TWRP), not to regular Android ROMs. Some of the information might still be useful for regular ROM development, though.
Differences in a recovery
I'm working with TWRP so your recovery might be different.
Recovery images are very limited and are a bit different from regular Android ROMs. The relevant differences are:
- The root directory is not
- Binaries and libraries are not in a separate directory. They are both stored directly inside
/sbin, and not in a subdirectory like in the full Android image (
Setting up Valgrind is actually pretty easy. First of all, build the binary:
$ mmma external/valgrind
You'll find the resulting binary in
out/target/product/<device>/system/bin/valgrind and the library in
out/target/product/<device>/system/lib/valgrind. What I have done is to copy the binary to
/sbin/valgrind and the library to
As the binary expects the library somewhere else (probably
/system/lib), you have to tell where it is. Luckily, that's very easy. Just set the
VALGRIND_LIB environment variable, like so:
$ VALGRIND_LIB=/sbin/valgrind-lib valgrind ls
But this won't work just yet. It'll give the confusing error
not found. It's not the Valgrind binary that isn't found, or the library, or the
ls binary it tries to run. It's something it expects in
/system/bin. Symlinking it to
/sbin solves the issue:
$ ln -s /sbin /system/bin
/system isn't mounted before you do this2.
And now you can see Valgrind in action in all it's glory:
~ # VALGRIND_LIB=/sbin/valgrind-lib valgrind ls ==2142== Memcheck, a memory error detector ==2142== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==2142== Using Valgrind-3.11.0.SVN.aosp and LibVEX; rerun with -h for copyright info ==2142== Command: ls ==2142== WARNING: linker: /sbin/valgrind-lib/vgpreload_core-arm-linux.so: unsupported flags DT_FLAGS_1=0x421 WARNING: linker: /sbin/valgrind-lib/vgpreload_memcheck-arm-linux.so: unsupported flags DT_FLAGS_1=0x421 acct etc oem sepolicy boot external_sd preload service_contexts bugreports file_contexts proc sideload cache file_contexts.bin property_contexts storage charger fstab.universal3470 recovery supersu config init res sys d init.rc root system data init.recovery.service.rc sbin tmp default.prop init.recovery.usb.rc sdcard twres dev license seapp_contexts ueventd.rc efs mnt selinux_version ueventd.universal3470.rc ==2142== ==2142== HEAP SUMMARY: ==2142== in use at exit: 17,686 bytes in 258 blocks ==2142== total heap usage: 547 allocs, 289 frees, 42,878 bytes allocated ==2142== ==2142== LEAK SUMMARY: ==2142== definitely lost: 11,280 bytes in 46 blocks ==2142== indirectly lost: 328 bytes in 2 blocks ==2142== possibly lost: 0 bytes in 0 blocks ==2142== still reachable: 6,078 bytes in 210 blocks ==2142== suppressed: 0 bytes in 0 blocks ==2142== Rerun with --leak-check=full to see details of leaked memory ==2142== ==2142== For counts of detected and suppressed errors, rerun with: -v ==2142== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Using AddressSanitizer is very similar. The big difference is that you have to add support for individual binaries. Most of the information from the official documentation still applies, but there are some extra things you need to set on a recovery image.
First, build AddressSanitizer:
$ mmma external/compiler-rt/lib/asan
And build your binary using ASan. This is simply a matter of adding this to your program's
I would recommend taking the binary somewhere inside
out/target/product/<phone>/obj/RECOVERY_EXECUTABLES as these aren't stripped of debug symbols. Use
find to well... find it.
Then copy the required runtime libraries to your phone. These are the library (
out/target/product/<phone>/system/lib/libclang_rt.asan-arm-android.so) and the special linker (
out/target/product/<phone>/system/bin/linker_asan). Store them both in
Now ASan, of course, expects them to be stored in
/system/lib, so we have to make two symlinks:
$ ln -s /sbin /system/bin $ ln -s /sbin /system/lib
Now you should be able to run the binary on your phone using AddressSanitizer. I don't have an example, unfortunately, as I don't want to recompile everything.
I hope you'll find this guide useful. It took me a while to figure this out so maybe it helps someone. Using these tools I've found a few memory issues in the new adb backup functionality of TWRP that caused errors during restore for me on the Samsung Galaxy S5 mini (but apparently nobody else). I hope these fixes will soon land in TWRP.