How to Test and Debug NDK-based Android* Applications

This article is an overview on how to test and debug NDK-based Android applications for the x86 platform. Creating a sample application will be shown from start to finish, and testing and debugging will be demonstrated.

1. Development Environment

Please make sure that the Android application development environment has been installed already. If not, please check the instructions on the Android developer site [1]. Here we simply list the main components we need.

  • Android SDK: The Android Software Development Kit provides the tools and libraries necessary to begin developing applications that run on Android-powered devices.
  • Android NDK: The Android NDK is a toolset that lets you embed components that make use of native code in your Android applications.

In this example, we just need these two components, but if you want to use IDE, you need to download and install the following:

Eclipse*: the popular IDE for many projects, including Android projects by installing the ADT plugin.

ADT: the Android Development Toolkit, a plugin for Eclipse.

CDT: C/C++ Development Toolkit, if you want to build C/C++ code in Eclipse.

Cygwin*: Cygwin is a collection of tools that provide a Linux* look-and-feel environment for Windows*. In the Windows OS, we need it to run “ndk-build” for building native code.

2. Testing Environment

For x86 platform target, an x86-based device or x86 simulator is needed for testing. With the IA phone launches, like the Lava Xolo X900 and Lenovo K800, in April this year, x86-based tablets and phones with Android will become more and more common. For those who don’t have x86 devices for testing, the x86 simulator works just fine. Android SDK Manager can be used to install the x86 simulator, and images of Android 4.0.3 and Android 2.3.3 for the Intel® Atom™ platform have been included in the official software release. See Figures 1 and 2.


Figure 1. Intel® Atom™ x86 System Image for Android* 4.0.3


Figure 2. Intel® Atom™ x86 System Image for Android* 2.3.3

For other specific versions of Android that cannot be found in the official release, please try to build it from the Android source code and put it into the corresponding folder (such as $ANDROID_NDK_ROOT/platforms/android-10/images/ for Android 2.3.3).

Intel® Hardware Accelerated Execution Manager (Intel® HAXM) [3] can also be installed to help developers gain faster Android emulation. HAXM is a hardware-assisted virtualization engine (hypervisor) that uses Intel Virtualization Technology (Intel® VT) to speed up Android app emulation on a host machine. In combination with Android x86 emulator images provided by Intel and the official Android SDK Manager, HAXM allows for faster Android emulation on Intel VT-enabled systems.


Figure 3. Intel® Hardware Accelerated Execution Manager

3. Write our sample code

Now we are going to create a simple application called CPUIdApp [3] that can return the main features of the host’s CPU. There are two major functions in the native code—one is for getting CPU information and the other is for parsing the string. It can be written into cpuid.c file and built by gcc for the Linux OS or in Cygwin for the Windows OS. After running the sample application, the parsed CPU ID information will be displayed on the screen. Our purpose is to show the parsed CPU ID information in a simple Activity. For more detailed information about how to create this project, please refer to [1, 4].

The first step is to create an Android project. Please see the source code from cupid.c in the attachment.

$mkdir jni-cpuid && cd jni-cpuid

This command will create a simple “Hello World” Android project.

$android create project –package “com.test.cpuid” –activity “CPUIdApp” –path . –target “android-9”

For our purpose, we need to change src/com/test/cupid/CPUIdApp.java to the following:

……
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
		TextView tv = new TextView(this);   //create a TextView as a container for the result
		tv.setText(cpuid_get());  //get parsed CPU ID information from native function and set to tv
		setContentView(tv);
    }
 
	private static native String cpuid_get();  //declare a native function but do not implement
	static { System.loadLibrary("cpuid"); }   //load the native library contains the native function we need
}

The next step is to implement the cupid_get function in the native code.

$ant debug
$javah –d jni –classpath bin/classes com.test.cpuid.CPUIdApp

Then the jni directory with the com_test_cpuid_CPUIdApp.h in it are created automatically, and the function that needs to be implemented is declared in the header file.

JNIEXPORT  jstring  JNICALL  Java_com_test_cpuid_CPUIdApp_cpuid_1get
  (JNIEnv *, jclass);

After that, move cupid.c to jni and create a com_test_cpuid_CPUIdApp.c file to implement the Java_com_test_cpuid_CPUIdApp_cpuid_1get function.

#include "com_test_cpuid_CPUIdApp.h"
extern void cpuid_parse(char *buf);
 
JNIEXPORT jstring JNICALL Java_com_example_cpuid_CPUIdApp_cpuid_1get(JNIEnv *env, jclass jc)
{
        char buf[1024];
        cpuid_parse(buf);
        return (*env)->NewStringUTF(env, buf);
}
The last step is to create Android.mk file for building the native code in the next session.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := cpuid
LOCAL_SRC_FILES := cpuid.c com_test_cpuid_CPUIdApp.c
include $(BUILD_SHARED_LIBRARY)

4. Build, deploy, and test on the x86 simulator

It’s time to run “ndk-build” to build native code. To build for x86 architecture, “APP_ABI=x86” needs to be added as arguments or on the top of Android.mk file.

$ndk-build APP_ABI=x86

Compile x86    : cpuid <= cpuid.c
Compile x86    : cpuid <= com_test_cpuid_CPUIdApp.c
SharedLibrary  : libcpuid.so
Install        : libcpuid.so => libs/x86/libcpuid.so
 

Then use “ant” to build the whole project. Cleaning the project before building it is always a good habit.

$ant clean && ant debug

The CPUIdApp-debug.apk will be created in the “bin” directory, and it could be installed via “adb install” command.

$adb install bin/CPUIdApp-debug.apk

Finally, run CPUIdApp in the emulator to see the output.

5. Debug for errors

There are two ways to debug Android NDK applications: adb logcat and ndk-gdb. Let’s talk about these two methods in this session.

5.1 Using adb logcat to get log output

For a Java*-based application, log information can be obtained by running “adb logcat” if it uses Log.d(). Logs from other applications and the system are also shown when running “adb logcat” command.

After running CPUIdApp that we built previously on the emulator, an error dialog will appear, and the application crashes. Use “adb logcat” to check what’s wrong.

$adb logcat

…
D/dalvikvm( 1616): No JNI_OnLoad found in /data/data/com.test.cpuid/lib/libcpuid.so 0xb49300f8, skipping init
W/dalvikvm( 1616): No implementation found for native Lcom/test/cpuid/CPUIdApp;.cpuid_get ()Ljava/lang/String;
…

The log says that the Lcom/test/cupid/CPUIdApp.cpuid_get function is not implemented. After checking the code in jni/com_test_cpuid_CPUIdApp.c, we find that Java_com_example_cpuid_CPUIdApp_cpuid_1get should be named Java_com_test_cpuid_CPUIdApp_cpuid_1get. Oh, no! Typos are always a problem for development. So rename Java_com_example_cpuid_CPUIdApp_cpuid_1get as Java_com_test_cpuid_CPUIdApp_cpuid_1get in jni/com_test_cpuid_CPUIdApp.c and rebuild the project.

$ndk-build APP_ABI=x86
$ant clean && ant debug
$adb install –r bin/CPUIdApp-debug.apk

Then re-run CPUIdApp in the emulator. The normal output is shown in the next figure. Please refer to [4, 10] if you want the detailed information of these CPUID operation codes.


Figure 4. Result of CPUIdApp

For some other information, a series functions of Log.*() provided by android.util.Log can be used to print information as needed. It is very simple, in the Java file:

…
import android.util.Log;
…
Log.d(“CPUIdApp”, “try to get cupid_get…”);  //print useful information as needed
…

After rebuilding and running CPUIdApp, get the log output by using “adb logcat” as well:

D/CPUIdApp( 2327): try to get cpuid_get...

In fact, it not only works in Java code, it works in C/C++ code as well. Modify com_test_cpuid_CPUIdApp.c as follows:

…
#include <android> 
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, “CPUIdApp”, __VA_ARGS__)) 
… 
JNIEXPORT jstring JNICALL Java_com_test_cpuid_CPUIdApp_cpuid_1get(JNIEnv *env, jclass jc) 
{ 
        char buf[1024]; 
        cpuid_parse(buf); 
        LOGI("cpuid_1get buf value: "); //the same way in native side to print useful information 
        LOGI(buf); 
        return (*env)->NewStringUTF(env, buf); 
} 
To build this, we need to modify Android.mk, by adding: 
LOCAL_LDLIBS := -llog 
LOCAL_C_INCLUDES += system/core/include/cutils 
LOCAL_SHARED_LIBRARIES := libcutils 
</android>

Then:

$ndk-build APP_ABI=x86 && ant clean && ant debug && adb install –r bin/CPUIdApp-debug.apk

Run and you’ll get the following log information:

I/CPUIdApp( 2475): cpuid_1get buf value:
I/CPUIdApp( 2475): CPUID Features(EAX=1): ECX = 0x00000201 EDX = 0x0781abfd
I/CPUIdApp( 2475): HT - NO
I/CPUIdApp( 2475): VT - NO
I/CPUIdApp( 2475): SSE - YES
I/CPUIdApp( 2475): SSE2 - YES
I/CPUIdApp( 2475): SSE3 - YES
I/CPUIdApp( 2475): SSSE3 - YES
I/CPUIdApp( 2475): SSE41 - NO
I/CPUIdApp( 2475): SSE42 - NO
I/CPUIdApp( 2475): AES - NO
I/CPUIdApp( 2475): AVX - NO

5.2 Using ndk-gdb to debug C/C++ code

Android NDK r4 introduced a helper shell script named 'ndk-gdb' to easily launch a native debugging session for NDK-generated machine code. The script is located at the top-level directory of the NDK and can be invoked from the command line from your application project directory or any of its subdirectories.

Let’s try:

In the root of the project directory, run “ndk-gdb --start”, and the following output will be displayed on the terminal:

Found debuggable flag: false
ERROR: Package com.test.cpuid is not debuggable ! You can fix that in two ways:
- Rebuilt with the NDK_DEBUG=1 option when calling 'ndk-build'.
- Modify your manifest to set android:debuggable attribute to "true", then rebuild normally.

It’s better to do both: 1) modify manifest, and add android:debuggable=”true” in the application label and 2) rebuild by running “ndk-build APP_ABI=x86 NDK_DEBUG=1”.

$ ndk-build APP_ABI=x86 NDK_DEBUG=1
$ ant clean && ant debug && adb install –r bin/CPUIdApp-debug.apk
$ndk-gdb –start com.test.cpuid.CPUIdApp –force –verbose

The application is started, and it will show:

…
…
and track explicitly loaded dynamic code.
0xb7eb2ff9 in __futex_syscall4 ()
   from /home/user/labs/cpuid/obj/local/x86/libc.so
(gdb)

Then all “gdb” commands can be used to debug the native code. Sometimes, the native part has been finished before we see the “gdb” prompt. This time, try to add “android.os.Debug.waitForDebugger()” at src/com/test/cpuid/CPUIdApp.java:

…
import android.os.Debug;
…
        Log.d("CPUIdApp", "waiting for debug ...");
        android.os.Debug.waitForDebugger();  //make app pause
        Log.d("CPUIdApp", "try to get cpuid_get...");
        tv.setText(cpuid_get());
…

This way, we can debug into cupid_get() function and get more detailed information.

6. Conclusion

In this article, Android NDK-based application creation, installation, testing, and debugging are introduced by using a simple sample application. Applications can be tested on either real x86 devices or x86 emulators, and can be debugged via “adb logcat” and “ndk-gdb” commands provided by Android SDK/NDK tools.

Use “adb logcat” to get some simple information or system-related information.

For more detailed information, “ndk-gdb” is needed.

We only showed how to do these steps from the command line; for those who would like to use Eclipse, please refer to [7 and 8] .

References:

[1] Android developer site. http://developer.android.com/index.html

[2] Sample NDK applications. http://developer.android.com/sdk/ndk/overview.html#samples

[3] Intel® Hardware Accelerated Execution Manager installation instructions. http://redfort-software.intel.com/en-us/articles/installation-instructions-for-intel-hardware-accelerated-execution-manager-windows/

[4] Wiki on CPUID opcode. http://en.wikipedia.org/wiki/CPUID

[5] NDK-based Android Apps for IA. http://redfort-software.intel.com/en-us/articles/creating-and-porting-ndk-based-android-apps-for-ia/

[6] Using cgdb with ndk-debug. http://mhandroid.wordpress.com/2011/01/23/using-cgdb-with-ndk-debug-and-cgdb-tutorial/

[7] Using Eclipse for Android C/C++ Debugging. http://mhandroid.wordpress.com/2011/01/23/using-eclipse-for-android-cc-debugging/

[8] Using Eclipse for Android C/C++ Development. http://mhandroid.wordpress.com/2011/01/23/using-eclipse-for-android-cc-development/

[9] Android NDK for Intel Architecture. http://redfort-software.intel.com/en-us/articles/ndk-for-ia/

[10] Intel® 64 and IA-32 Architectures Developer's Manual: Vol. 2A. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-2a-manual.html

[11] NDK-GDB introduction. http://source-android.frandroid.com/ndk/docs/NDK-GDB.html

Notices

INFORMATION IN THIS DOCUMENT IS PROVIDED IN CONNECTION WITH INTEL PRODUCTS. NO LICENSE, EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, TO ANY INTELLECTUAL PROPERTY RIGHTS IS GRANTED BY THIS DOCUMENT. EXCEPT AS PROVIDED IN INTEL'S TERMS AND CONDITIONS OF SALE FOR SUCH PRODUCTS, INTEL ASSUMES NO LIABILITY WHATSOEVER AND INTEL DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY, RELATING TO SALE AND/OR USE OF INTEL PRODUCTS INCLUDING LIABILITY OR WARRANTIES RELATING TO FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR INFRINGEMENT OF ANY PATENT, COPYRIGHT OR OTHER INTELLECTUAL PROPERTY RIGHT.

UNLESS OTHERWISE AGREED IN WRITING BY INTEL, THE INTEL PRODUCTS ARE NOT DESIGNED NOR INTENDED FOR ANY APPLICATION IN WHICH THE FAILURE OF THE INTEL PRODUCT COULD CREATE A SITUATION WHERE PERSONAL INJURY OR DEATH MAY OCCUR.

Intel may make changes to specifications and product descriptions at any time, without notice. Designers must not rely on the absence or characteristics of any features or instructions marked "reserved" or "undefined." Intel reserves these for future definition and shall have no responsibility whatsoever for conflicts or incompatibilities arising from future changes to them. The information here is subject to change without notice. Do not finalize a design with this information.

The products described in this document may contain design defects or errors known as errata which may cause the product to deviate from published specifications. Current characterized errata are available on request.

Contact your local Intel sales office or your distributor to obtain the latest specifications and before placing your product order.

Copies of documents which have an order number and are referenced in this document, or other Intel literature, may be obtained by calling 1-800-548-4725, or go to: http://www.intel.com/design/literature.htm

Software and workloads used in performance tests may have been optimized for performance only on Intel microprocessors. Performance tests, such as SYSmark and MobileMark, are measured using specific computer systems, components, software, operations, and functions. Any change to any of those factors may cause the results to vary. You should consult other information and performance tests to assist you in fully evaluating your contemplated purchases, including the performance of that product when combined with other products.

Any software source code reprinted in this document is furnished under a software license and may only be used or copied in accordance with the terms of that license.

Intel, Atom, and the Intel logo are trademarks of Intel Corporation in the US and/or other countries.

Copyright © 2012 Intel Corporation. All rights reserved.
*Other names and brands may be claimed as the property of others.