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
There are two important tools to detect these issues supported by the Android build system: AddressSanitizer (or ASan) and Valgrind. They work a bit differently and have their pros and cons.
- 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
/system
but/sbin
. - 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 (/system/bin
and/system/lib
).
Valgrind
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 /sbin/valgrind-lib
.
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
Make sure /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)
AddressSanitizer
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 Android.mk
:
LOCAL_SANITIZE:=address
LOCAL_CLANG:=true
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 /sbin
.
Now ASan, of course, expects them to be stored in /sytem/bin
and /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.
-
It has also been ported to GCC but I haven't seen it used in Android that way, and using Clang instead of GCC is pretty easy anyway. ↩︎
-
You can also try installing Valgrind/AddressSanitizer to the ROM and using them from there, but I haven't tried this. ↩︎