Рут даёт практически абсолютную власть над Android устройством. Сегодня я расскажу вам как получить еще больше имея склонность к программированию и желание исследовать систему на своём устройстве. Кто заинтересовался — прошу под кат.
Что же, начнём по порядку.
Стоит сказать что я использовал ОС Linux Ubuntu 11.10 и все примеры буду приводить для неё.
Первые 3 пункта очевидны, как добиться 4 и 5 легко найти в интернете. Последние два рассмотрим подробно.
В данной статье мы не рассматриваем возм��жность прошивки собственноручно собранного ядра на свой телефон поэтому мы должны придерживаться определённых правил.
Для того чтобы узнать каким компилятором собрано ядро на нашем устройстве выполняем команду:
c помощью любого эмулятора терминала или используя утилиту adb:
В результате получаем строку вроде этой:
Видим что у нас установлено ядро версии 3.0.69 локальная версия "-g26a847e" и собрано оно тулчейном Linaro GCC 4.7-2012.07. Зная версию находим необходимый тулчейн и распаковываем в любую папку. У меня путь выглядел так:
Для начала узнаем какое именно ядро использует наше устройство. Это можно сделать выполнив команду указанную выше или зайдя на устройстве в настройки, раздел «О телефоне».
Как было сказано выше в моём случае это 3.0.69-g26a847e. Немного поковырявшись на гитхабе прошивки (PACman for HTC Desire S) я определил что это ядро AndromadusMod.
Копируем найденные иходники себе на локальную машину (я предварительно форкнул необходимый репозиторий себе в гитхаб и выполнил git clone, производители вроде Google и изготовители кастомных прошивок держат исходники ядра в репозиториях с открытым доступом, некоторые просто позволяют скачать исходники в виде архива). Для меня это выглядело так:
Теперь нужно найти конфигурацию с которой собрано ядро нашего устройства. В большинстве случаев конфигурация лежит на самом устройстве и получить её можно с помощью adb, распаковать и скопировать в папку с исходниками ядра:
Необходимо также немного изменить конфигурацию — установить локальную версию на идентичную той что мы узнали ранее и выключить автоматическое назначение локальной версии. Сделать это можно с помощью любого текстового редактора:
После переходим в папку с исходниками, настраиваем переменные окружения для сборки и собственно собираем ядро:
Теперь можно перейти к программированию.
Учитывая огромное количество статей о написании Android приложения я рассмотрю только моменты связанные с задачей.
Наше приложение будет иметь всего 1 Activity:
Выглядит это в итоге вот так:

На кнопку мы назначаем событие которое получит информацию от нашего драйвера и запишет её в текстовое поле:
Теперь создадим класс обёртку для нашей jni библиотеки:
Создадим папку jni в корне проекта Android приложения.
Далее сгенерируем Си хедер для нашей нативной библиотеки:
Получаем хедер и копируем в ранее созданную папку, создадим соответствующий .c и конфигурацию сборки Android.mk:
Содержимое Android.mk:
Алгоритм работы библиотеки:
Полный код:
Полностью описывать процесс написания драйвера я не буду, сделаю лишь пару заметок:
Для начала зальём собранный драйвер на устройство, и установим его в ядро, заодно сделаем ноду драйвера доступной для всех:
Если версия ядра модифицирована правильно и ядро совпадает с тем которое было на устройстве ошибок быть не должно.
После можно запускать Android приложение напрямую через eclipse или установить его. Нажимаем единственную кнопку и получаем результат:

Логи ядра можно получить командой:
Показанное применение данной связки не единственное. Использование драйверов ядра позволяет напрямую работать с любыми интерфейсами устройства, влиять на работу любого приложения и системы в целом, также позволяет работать с интерфейсами которые спрятаны глубоко в системе за целой кучей API и фреймворков — например драйвер который будет писать необходимую вам информацию прямо в буфер видеопамяти устройства. Данное решение применимо не только для телефонов но и для любых устройств на базе Android.
Полные исходники лежат на GitHub.
На этом заканчиваю, спасибо за внимание. Надеюсь что данный материал окажется для кого-нибудь полезным.
1. developer.android.com — Android SDK/NDK и прочее.
2. www.vogella.com — довольно неплохие и понятные статьи по разработке Android приложений.
3. blog.edwards-research.com/2012/04/tutorial-android-jni — туториал по использованию JNI.
4. docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html — справочный материал по интерфейсу JNI.
5. Linux Device Drivers, 3ed — библия программиста Linux Kernel.
Поправил несколько опечаток, ошибок в коде. Спасибо: bmx666,Shirixae
Что же, начнём по порядку.
Что необходимо
- Минимальные знания C.
- Минимальные знания Java.
- Некоторое понимание того как взаимодействуют элементы системы Android.
- Рутованый Android телефон.
- IDE с поддержкой Android SDK/NDK (в моём случае eclipse, его очень легко настроить для работы с Android и описано это много раз).
- Тулчейн для кросс компиляции которым было собрано ядро на целевом устройстве.
- Собранное ядро для нашего устройства с правильной локальной версией.
Стоит сказать что я использовал ОС Linux Ubuntu 11.10 и все примеры буду приводить для неё.
Первые 3 пункта очевидны, как добиться 4 и 5 легко найти в интернете. Последние два рассмотрим подробно.
Выбор тулчейна для кросс компиляции модулей ядра (драйверов)
В данной статье мы не рассматриваем возм��жность прошивки собственноручно собранного ядра на свой телефон поэтому мы должны придерживаться определённых правил.
Для того чтобы узнать каким компилятором собрано ядро на нашем устройстве выполняем команду:
cat /proc/version
c помощью любого эмулятора терминала или используя утилиту adb:
adb shell "cat /proc/version"
В результате получаем строку вроде этой:
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
Видим что у нас установлено ядро версии 3.0.69 локальная версия "-g26a847e" и собрано оно тулчейном Linaro GCC 4.7-2012.07. Зная версию находим необходимый тулчейн и распаковываем в любую папку. У меня путь выглядел так:
/home/user/android/android_prebuilt_linux-x86_toolchain_arm-gnueabihf-linaro-4.7
Сборка ядра
Для начала узнаем какое именно ядро использует наше устройство. Это можно сделать выполнив команду указанную выше или зайдя на устройстве в настройки, раздел «О телефоне».
Информация о системе

Как было сказано выше в моём случае это 3.0.69-g26a847e. Немного поковырявшись на гитхабе прошивки (PACman for HTC Desire S) я определил что это ядро AndromadusMod.
Копируем найденные иходники себе на локальную машину (я предварительно форкнул необходимый репозиторий себе в гитхаб и выполнил git clone, производители вроде Google и изготовители кастомных прошивок держат исходники ядра в репозиториях с открытым доступом, некоторые просто позволяют скачать исходники в виде архива). Для меня это выглядело так:
/home/user/android/saga-3.0.69
Теперь нужно найти конфигурацию с которой собрано ядро нашего устройства. В большинстве случаев конфигурация лежит на самом устройстве и получить её можно с помощью adb, распаковать и скопировать в папку с исходниками ядра:
adb pull /proc/config.gz .
gunzip ./config.gz
cp ./config /home/user/android/saga-3.0.69/arch/arm/my_device_defconfig
Необходимо также немного изменить конфигурацию — установить локальную версию на идентичную той что мы узнали ранее и выключить автоматическое назначение локальной версии. Сделать это можно с помощью любого текстового редактора:
CONFIG_LOCAL_VERSION="-g26a847e"
CONFIG_LOCAL_VESION_AUTO=n
После переходим в папку с исходниками, настраиваем переменные окружения для сборки и собственно собираем ядро:
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
Теперь можно перейти к программированию.
Написание кода
Android приложение
Учитывая огромное количество статей о написании Android приложения я рассмотрю только моменты связанные с задачей.
Наше приложение будет иметь всего 1 Activity:
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="@string/btnText1"
android:onClick="onClick"/>
<EditText
android:id="@+id/editText1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/button1"
android:layout_marginTop="42dp"
android:ems="10"
android:textSize="16sp"
android:inputType="textMultiLine" />
</RelativeLayout>
Выглядит это в итоге вот так:

На кнопку мы назначаем событие которое получит информацию от нашего драйвера и запишет её в текстовое поле:
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());
}
}
}
Теперь создадим класс обёртку для нашей jni библиотеки:
public class IoctlWrapper {
public static native String getKData(); //Объявление нашего нативного метода, который будет общаться с драйвером
public static String getData() {
return getKData();
}
static {
System.loadLibrary("ioctlwrap");
}
}
JNI
Создадим папку jni в корне проекта Android приложения.
Далее сгенерируем Си хедер для нашей нативной библиотеки:
cd src
javac -d /tmp/ com/propheta13/amoduse/IoctlWrapper.java
cd /tmp
javah -jni com.propheta13.amoduse.IoctlWrapper
Получаем хедер и копируем в ранее созданную папку, создадим соответствующий .c и конфигурацию сборки Android.mk:
cd [PATH TO ANDROIDPROJ]/jni
cp /tmp/com_propheta13_amoduse_IoctlWrapper.h ./ioctlwrap.h
touch ./ioctlwrap.c
touch ./Android.mk
Содержимое Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ioctlwrap
LOCAL_SRC_FILES := ioctlwrap.c
Алгоритм работы библиотеки:
- Открыть ноду драйвера.
- Выделить буфер под информацию из драйвера
- Получить информацию с помощью ioctl запроса.
- Закрыть ноду.
- Преобразовать информацию в Java строку и передать в обёртку.
Полный код:
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;
}
Драйвер ядра
Полностью описывать процесс написания драйвера я не буду, сделаю лишь пару заметок:
- Драйвер написанный для этой статьи не делает ничего сверхъестественного — только возвращает список имён сетевых интерфейсов.
- Для общения с драйвером используется механизм ioctl.
- Makefile для сборки ��озволяет указывать ядро для которого требуется собрать данный драйвер, для этого нужно правильно указать переменные окружения и использовать команду:
make KMODDIR=[path to kernel]
Запуск
Для начала зальём собранный драйвер на устройство, и установим его в ядро, заодно сделаем ноду драйвера доступной для всех:
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
Если версия ядра модифицирована правильно и ядро совпадает с тем которое было на устройстве ошибок быть не должно.
После можно запускать Android приложение напрямую через eclipse или установить его. Нажимаем единственную кнопку и получаем результат:

Логи ядра можно получить командой:
root@android:/ # dmesg | grep [TEST_KMOD] # можно и не фильтровать но так лучше видно.
Лог ядра
<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.
Заключение
Показанное применение данной связки не единственное. Использование драйверов ядра позволяет напрямую работать с любыми интерфейсами устройства, влиять на работу любого приложения и системы в целом, также позволяет работать с интерфейсами которые спрятаны глубоко в системе за целой кучей API и фреймворков — например драйвер который будет писать необходимую вам информацию прямо в буфер видеопамяти устройства. Данное решение применимо не только для телефонов но и для любых устройств на базе Android.
Полные исходники лежат на GitHub.
На этом заканчиваю, спасибо за внимание. Надеюсь что данный материал окажется для кого-нибудь полезным.
Использованные ресурсы:
1. developer.android.com — Android SDK/NDK и прочее.
2. www.vogella.com — довольно неплохие и понятные статьи по разработке Android приложений.
3. blog.edwards-research.com/2012/04/tutorial-android-jni — туториал по использованию JNI.
4. docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html — справочный материал по интерфейсу JNI.
5. Linux Device Drivers, 3ed — библия программиста Linux Kernel.
UPD
Поправил несколько опечаток, ошибок в коде. Спасибо: bmx666,Shirixae