When debugging and profiling apps with native code, it’s often useful to be able to bundle command-line developer tools in your APK and run them automatically on the device when the app launches.
Beginning in Android 8.0, you can opt to create and package a user-defined shell script that the system automatically runs in a fresh process when you launch a debuggable app. This feature lets you write a script to invoke native developer tools to perform such operations as:
- Tracing system calls with strace.
- Ensuring memory integrity with malloc debug, Address Sanitizer (ASAN), and Valgrind.
- Enabling app-specific native memory tracking via malloc debug.
- Running Simpleperf against a specific app process, if you are profiling Java code.
Using the wrap shell script
To run native developer tools on the device using the wrap shell script, follow these steps:
- Compile a custom debuggable APK that packages the following:
- Install the debuggable APK on a device.
Depending on the Android platform version on the device, you may need to modify access control
over processes for the wrap shell script to run.
- Android 8.1 (API level 27) or higher: No such modification is needed.
- Android 8.0 (API level 26): The device must have
SELinux enforcement disabled.
You can disable SELinux enforcement via
adbon userdebug or eng builds. Supported devices with these permission levels are:
- An Android emulator with a userdebug system image, or
- A Google device flashed with an AOSP eng build or root image.
- Launch the app normally on your device, either via the app launcher or from the command line.
- It is strongly recommended to re-enable SELinux enforcement on your device when done.
Creating the wrap shell script
You can create a shell script named
wrap.sh to customize your runtime environment. In your
script, you can set environment variables and modify runtime arguments. The script should follow
MirBSD Korn shell (mksh) syntax.
When you launch a debuggable APK that contains
wrap.sh, the system executes the
script and grants it control over the app process. The system passes the command to start
the requested app as an argument to the
wrap.sh invocation. Note that your script is
responsible for starting the app.
The following snippet shows how to write a simple
wrap.sh file that just starts the app:
To start the
wrap.sh, you would include the following line:
#!/system/bin/sh LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper $@
To take advantage of
wrap.sh, you must build a debuggable APK. Make sure that the
android:debuggable=”true”setting is configured in the
element in your Android manifest.
You must package the
wrap.sh script with the native libraries of the app. If your app does not
contain native libraries, add the lib directory manually to your project directory. For all
architectures that your app supports, you must provide a copy of the wrap shell script under their
respective native-library architecture directories.
The following project directory example shows the file layout to support both the x86 and ARMv8 architectures:
# App Directory |- AndroidManifest.xml |- … |- lib |- x86 |- ... |- wrap.sh |- arm64-v8a |- ... |- wrap.sh
Make sure that your wrap.sh script is executable (that is, its executable bit is set). For example:
$ ls -l lib/x86/wrap.sh -rwxr-x--x 1 user user 0 Jan 01 1980 lib/x86/wrap.sh