Using drivers from an Android application
- From the sandbox
- Tutorial
Ruth gives almost absolute power over the Android device. Today I will tell you how to get even more with a penchant for programming and a desire to explore the system on your device. Who is interested - please, under cat.
Well, let's start in order.
It is worth saying that I used the Linux Ubuntu 11.10 OS and I will give all the examples for it.
The first 3 points are obvious how to achieve 4 and 5 are easy to find on the Internet. The last two will be considered in detail.
In this article, we do not consider the possibility of flashing the kernel with your own hands on your phone, so we must adhere to certain rules.
In order to find out which compiler compiled the kernel on our device, we execute the command:
using any terminal emulator or using the adb utility:
As a result, we get a line like this:
We see that we have the kernel version 3.0.69 installed, the local version is "-g26a847e" and it is compiled by the Linaro GCC 4.7-2012.07 toolchain. Knowing the version, we find the necessary toolchain and unpack it into any folder. My path looked like this:
First, find out which core our device is using. This can be done by executing the command indicated above or by going to the settings on the device, section “About the phone”.
As mentioned above in my case it is 3.0.69-g26a847e. By digging a bit on the github of the firmware (PACman for HTC Desire S), I determined that this is the core of AndromadusMod.
We copy the found sources to my local machine (I previously forked the necessary repository into my github and executed a git clone, manufacturers like Google and manufacturers of custom firmware keep the kernel sources in open access repositories, some just allow you to download the sources as an archive). For me it looked like this:
Now you need to find the configuration with which the kernel of our device is assembled. In most cases, the configuration lies on the device itself and you can get it using adb, unpack and copy it to the folder with the kernel sources:
You also need to slightly change the configuration - set the local version to the identical one that we learned earlier and turn off the automatic assignment of the local version. You can do this using any text editor:
After that, we go to the source folder, configure the environment variables for the assembly, and actually build the kernel:
Now you can move on to programming.
Given the huge number of articles about writing an Android application, I will consider only the moments associated with the task.
Our application will have only 1 Activity:
It looks like this in the end:
On the button, we assign an event that will receive information from our driver and write it in the text box:
Now create a wrapper class for our jni library:
Create the jni folder in the root of the Android application project.
Next, we will generate a C header for our native library:
We get the header and copy it to the previously created folder, create the corresponding .c and Android.mk build configuration:
Android.mk content:
The library operation algorithm:
Full code:
I will not completely describe the process of writing a driver, I will only make a couple of notes:
First, fill in the assembled driver on the device, and install it in the kernel, at the same time make the driver node available to everyone:
If the kernel version is modified correctly and the kernel matches the one that was on the device, there should be no errors.
After that, you can launch the Android application directly through eclipse or install it. We press a single button and get the result: The
kernel logs can be obtained with the command:
The shown application of this ligament is not the only one. Using kernel drivers allows you to directly work with any device interface, affect the operation of any application and the system as a whole, and also allows you to work with interfaces that are hidden deep in the system behind a whole bunch of APIs and frameworks - for example, a driver that will write the information you need directly into the video memory buffer devices. This solution is applicable not only for phones but also for any Android devices.
Full source code is on GitHub .
This concludes, thanks for your attention. I hope that this material will be useful to someone.
1. developer.android.com - Android SDK / NDK and more.
2. www.vogella.com - pretty good and understandable articles on the development of Android applications.
3. blog.edwards-research.com/2012/04/tutorial-android-jni - Tutorial on using JNI.
4. docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html - reference material on the JNI interface.
5. Linux Device Drivers, 3ed - Bible programmer Linux Kernel.
Corrected several typos, errors in the code. Thanks: bmx666 , Shirixae
Well, let's start in order.
What is necessary
- Minimum knowledge C.
- Minimal knowledge of Java.
- Some understanding of how the elements of the Android system interact.
- Rooted Android phone.
- IDE with Android SDK / NDK support (in my case eclipse, it is very easy to configure it to work with Android and it is described many times).
- A cross-compilation toolchain which built the kernel on the target device.
- The assembled kernel for our device with the correct local version.
It is worth saying that I used the Linux Ubuntu 11.10 OS and I will give all the examples for it.
The first 3 points are obvious how to achieve 4 and 5 are easy to find on the Internet. The last two will be considered in detail.
Choosing a toolchain for cross-compiling kernel modules (drivers)
In this article, we do not consider the possibility of flashing the kernel with your own hands on your phone, so we must adhere to certain rules.
In order to find out which compiler compiled the kernel on our device, we execute the command:
cat /proc/version
using any terminal emulator or using the adb utility:
adb shell "cat /proc/version"
As a result, we get a line like this:
Linux version 3.0.69-g26a847e (blindnumb@iof303) (gcc version 4.7.2 20120701 (prerelease) (Linaro GCC 4.7-2012.07)) #1 PREEMPT Mon Mar 18 12:19:10 MST 2013
We see that we have the kernel version 3.0.69 installed, the local version is "-g26a847e" and it is compiled by the Linaro GCC 4.7-2012.07 toolchain. Knowing the version, we find the necessary toolchain and unpack it into any folder. My path looked like this:
/home/user/android/android_prebuilt_linux-x86_toolchain_arm-gnueabihf-linaro-4.7
Kernel assembly
First, find out which core our device is using. This can be done by executing the command indicated above or by going to the settings on the device, section “About the phone”.
System Information
As mentioned above in my case it is 3.0.69-g26a847e. By digging a bit on the github of the firmware (PACman for HTC Desire S), I determined that this is the core of AndromadusMod.
We copy the found sources to my local machine (I previously forked the necessary repository into my github and executed a git clone, manufacturers like Google and manufacturers of custom firmware keep the kernel sources in open access repositories, some just allow you to download the sources as an archive). For me it looked like this:
/home/user/android/saga-3.0.69
Now you need to find the configuration with which the kernel of our device is assembled. In most cases, the configuration lies on the device itself and you can get it using adb, unpack and copy it to the folder with the kernel sources:
adb pull /proc/config.gz .
gunzip ./config.gz
cp ./config /home/user/android/saga-3.0.69/arch/arm/my_device_defconfig
You also need to slightly change the configuration - set the local version to the identical one that we learned earlier and turn off the automatic assignment of the local version. You can do this using any text editor:
CONFIG_LOCAL_VERSION="-g26a847e"
CONFIG_LOCAL_VESION_AUTO=n
After that, we go to the source folder, configure the environment variables for the assembly, and actually build the kernel:
cd /home/user/android/saga-3.0.69
export ARCH=arm
export CROSS_COMPILE=/home/user/android/android_prebuilt_linux-x86_toolchain_arm-gnueabihf-linaro-4.7/bin/arm-eabi-
export LOCALVERSION= all
make my_device_defconfig
make
Now you can move on to programming.
Code writing
Android app
Given the huge number of articles about writing an Android application, I will consider only the moments associated with the task.
Our application will have only 1 Activity:
activity_main.xml
It looks like this in the end:
On the button, we assign an event that will receive information from our driver and write it in the text box:
MainActivity class
public class MainActivity extends Activity {
private EditText text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (EditText)findViewById(R.id.editText1);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.button1:
text.setText(IoctlWrapper.getData());
}
}
}
Now create a wrapper class for our jni library:
public class IoctlWrapper {
public static native String getKData(); //Объявление нашего нативного метода, который будет общаться с драйвером
public static String getData() {
return getKData();
}
static {
System.loadLibrary("ioctlwrap");
}
}
Jni
Create the jni folder in the root of the Android application project.
Next, we will generate a C header for our native library:
cd src
javac -d /tmp/ com/propheta13/amoduse/IoctlWrapper.java
cd /tmp
javah -jni com.propheta13.amoduse.IoctlWrapper
We get the header and copy it to the previously created folder, create the corresponding .c and Android.mk build configuration:
cd [PATH TO ANDROIDPROJ]/jni
cp /tmp/com_propheta13_amoduse_IoctlWrapper.h ./ioctlwrap.h
touch ./ioctlwrap.c
touch ./Android.mk
Android.mk content:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ioctlwrap
LOCAL_SRC_FILES := ioctlwrap.c
The library operation algorithm:
- Open the driver node.
- Allocate a buffer for information from the driver
- Get information using ioctl request.
- Close the node.
- Convert information to Java string and pass to wrapper.
Full code:
ioctlwrap.c
const char string[] = "Driver open failed.";
#define BUF_SIZE 4096
JNIEXPORT jstring JNICALL Java_com_propheta13_amoduse_IoctlWrapper_getKData
(JNIEnv *env, jclass jcl)
{
char *info_buf = NULL;
int dfd = 0, rc = 0;
dfd = open(TKMOD_DEV_PATH, O_RDONLY);
if(dfd < 0)
{
jstring RetString = (*env)->NewStringUTF(env, string);
goto exit;
}
info_buf = malloc(BUF_SIZE);
rc = ioctl(dfd, TKMOD_IOCTL_GET_DATA, info_buf);
if(rc < 0)
{
strerror_r(rc, info_buf, BUF_SIZE);
}
jstring RetString = (*env)->NewStringUTF(env, info_buf);
free(info_buf);
close(dfd);
exit:
return RetString;
}
Kernel driver
I will not completely describe the process of writing a driver, I will only make a couple of notes:
- The driver written for this article does nothing supernatural - it only returns a list of network interface names.
- To communicate with the driver, the ioctl mechanism is used.
- The build makefile allows you to specify the kernel for which you want to build this driver, for this you need to correctly specify the environment variables and use the command:
make KMODDIR=[path to kernel]
Launch
First, fill in the assembled driver on the device, and install it in the kernel, at the same time make the driver node available to everyone:
adb push ./test_kmod.ko /data/local/tmp
root@android:/ # rmmod test_kmod
root@android:/ # insmod /data/local/tmp/test_kmod.ko
root@android:/ # chmod 777 /dev/tkmod_device
If the kernel version is modified correctly and the kernel matches the one that was on the device, there should be no errors.
After that, you can launch the Android application directly through eclipse or install it. We press a single button and get the result: The
kernel logs can be obtained with the command:
root@android:/ # dmesg | grep [TEST_KMOD] # можно и не фильтровать но так лучше видно.
Kernel log
<6>[ 8695.448028] [TEST_KMOD] == Module init ==
<7>[ 8775.583587] [TEST_KMOD] tkmod opened. Descriptor: 0xc2e98e00.
<7>[ 8775.583770] [TEST_KMOD] TKMOD_IOCTL_GET_DATA request.
<6>[ 8775.583923] [TEST_KMOD] name = lo
<6>[ 8775.584167] [TEST_KMOD] name = dummy0
<6>[ 8775.584259] [TEST_KMOD] name = rmnet0
<6>[ 8775.584320] [TEST_KMOD] name = rmnet1
<6>[ 8775.584503] [TEST_KMOD] name = rmnet2
<6>[ 8775.584564] [TEST_KMOD] name = rmnet3
<6>[ 8775.584655] [TEST_KMOD] name = rmnet4
<6>[ 8775.584777] [TEST_KMOD] name = rmnet5
<6>[ 8775.584930] [TEST_KMOD] name = rmnet6
<6>[ 8775.585021] [TEST_KMOD] name = rmnet7
<6>[ 8775.585113] [TEST_KMOD] name = gre0
<6>[ 8775.585266] [TEST_KMOD] name = sit0
<6>[ 8775.585357] [TEST_KMOD] name = ip6tnl0
<7>[ 8775.585479] [TEST_KMOD] tkmod_ 0xc2e98e00 closed successfuly.
Conclusion
The shown application of this ligament is not the only one. Using kernel drivers allows you to directly work with any device interface, affect the operation of any application and the system as a whole, and also allows you to work with interfaces that are hidden deep in the system behind a whole bunch of APIs and frameworks - for example, a driver that will write the information you need directly into the video memory buffer devices. This solution is applicable not only for phones but also for any Android devices.
Full source code is on GitHub .
This concludes, thanks for your attention. I hope that this material will be useful to someone.
Resources used:
1. developer.android.com - Android SDK / NDK and more.
2. www.vogella.com - pretty good and understandable articles on the development of Android applications.
3. blog.edwards-research.com/2012/04/tutorial-android-jni - Tutorial on using JNI.
4. docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html - reference material on the JNI interface.
5. Linux Device Drivers, 3ed - Bible programmer Linux Kernel.
UPD
Corrected several typos, errors in the code. Thanks: bmx666 , Shirixae