Содержание статьи
Введение
Как всем известно, Android имеет под собой фундамент в виде ядра Linux. Из этого следует, что в теории на смартфоне можно запустить все те приложения, что доступны на десктопном Linux. На практике же все сложнее. Поскольку набор Native-библиотек в Android отличается от такового на десктопе (не говоря уже об архитектуре платформы), приложения требуется компилировать статически. А иногда еще и патчить. Но и в этом случае работоспособность приложения не всегда гарантирована.
Что касается модулей ядра, которые могут быть весьма полезны на смартфоне (поддержка NTFS, например), то здесь все еще интереснее. Во многих ядрах от вендора отключена функция загрузки модулей (начиная с Android 4.3, это фактически требование Google. — Прим. ред.). Поэтому нам придется не только подобрать правильную версию ядра для сборки модулей, но и, возможно, пересобрать само ядро, включив такую поддержку, или просто добавить модуль в сам образ ядра.
Далее в статье мы рассмотрим, как побороть эти проблемы, и попробуем собрать модули Linux-ядра и несколько приложений.
Подготовка
Для сборки модулей (ядра) и приложений нам потребуется тулчейн, то есть связка из кросс‑компилятора и линковщика, плюс набор стандартных инструментов сборки, которые можно установить из репозитория (пример для Ubuntu):
$ sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl libc6-dev lib32ncurses5-dev x11proto-core-dev libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-glx:i386 libgl1-mesa-dev g++-multilib mingw32 openjdk-6-jdk tofrodos python-markdown libxml2-utils xsltproc zlib1g-dev:i386 git libtool
Теперь можно установить тулчейн. Их существует как минимум два — стандартный гугловский NDK и тулчейн от Linaro, куда сильнее оптимизирующий код. Различаются они еще и набором target-библиотек — если NDK содержит те библиотеки, которые имеются в Android и, соответственно, могут не подходить для сборки обычных POSIX-совместимых приложений, то Linaro включает минимальный набор стандартных POSIX-библиотек под ARM, для использования которых в гуглооси понадобится статическая линковка.
Для сборки ядра мы, в целях совместимости, будем использовать первый. Заходим на страничку https://developer.android.com/tools/sdk/ndk/index.html и, выбрав нужную версию NDK, скачиваем его. Затем устанавливаем на скачанный файл право исполнения и распаковываем:
$ chmod u+x android-ndk-r10c-linux-x86_64.bin
$ ./android-ndk-r10c-linux-x86_64.bin
А вот для сборки программ понадобится и тулчейн от Linaro. Для его получения проще всего зайти на forum.xda-developers.com/showthread.php?t=2098133 и выбрать билд. Лично я выбрал Linaro GCC 4.6.4-2013.05 (поскольку процессор у меня не Cortex, то и качал я arm-unknown-linux-gnueabi-linaro_4.6.4-2013.05-build_2013_05_18.tar.bz2). Распаковываем и для пущего удобства переименовываем:
$ tar xjvf arm-unknown-linux-gnueabi-linaro_4.6.4-2013.05-build_2013_05_18.tar.bz2
$ mv arm-unknown-linux-gnueabi-linaro_4.6.4-2013.05 linaro-toolchain-4.6
Добавим путь к тулчейну в ~/.bashrc (и заодно определим отдельные переменные, которые на время компиляции ядра, возможно, и не пригодятся, но в дальнейшем могут ой как понадобиться):
export PATH=$PATH:${HOME}/android-ndk-r10c/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86_64/bin:${HOME}/linaro-toolchain-4.6/bin
export NDKPATH=${HOME}/android-ndk-r10c
export ANDROID_SYSROOT=${HOME}/android-ndk-r10c/platforms/android-18/arch-arm
export LINARO_SYSROOT=${HOME}/linaro-toolchain-4.6/arm-unknown-linux-gnueabi/sysroot
export ARCH=arm
export CROSS_COMPILE_NDK=arm-linux-androideabi-
export CROSS_COMPILE_LINARO=arm-unknown-linux-gnueabi-
export CROSS_COMPILE=$CROSS_COMPILE_NDKexport CCOMPILE=$CROSS_COMPILE
Компиляция модулей и ядра
Для начала определим, поддерживает ли стоковое ядро модули. Для этого смотрим, есть ли на устройстве файл /proc/modules. В зависимости от этого мы поймем, что делать дальше. В случае если модули поддерживаются, мы берем ванильное ядро той же версии (но лучше, конечно, взять от вендора), конфигурируем его, компилируем только модули, закидываем их в /system/lib/modules и загружаем с помощью команды insmod на устройстве. Если же ядро модулей не поддерживает, можно либо взять готовое кастомное ядро с поддержкой модулей (об этом читай в статье «Выбираем кастомное ядро для своего Android-аппарата»), либо собрать свое, включив нужные модули в образ ядра.
В случае самсунговских устройств (у меня как раз такое) исходники ядер лежат на opensource.samsung.com. Для сборки ядра нам понадобится его конфиг. В некоторых устройствах он находится в файле /proc/config.gz, но, к сожалению, не во всех, поэтому разберем другой метод его получения. После скачивания и распаковки переходим в соответствующий каталог, смотрим файлы, находящиеся в arch/arm/configs/, и выбираем подходящий по архитектуре. В моем случае там был только один файл — n1a_00_defconfig. Переходим обратно в каталог, куда мы первоначально распаковали ядро, и набираем следующую команду:
$ make n1a_00_defconfig
Далее настраиваем ядро с помощью стандартной команды make menuconfig, чтобы включить нужные нам модули. Затем собираем:
$ make -j9 CFLAGS_MODULE=-fno-pic
Сборка модулей
Для сборки исключительно модулей без самого ядра можно использовать либо команду make modules, либо, если ты включил всего один модуль, следующие команды (вместо net/netfilter подставь каталог собираемого модуля):
$ make modules_prepare
$ make M=net/netfilter CFLAGS_MODULE=-fno-pic
После сборки нам нужно все получившиеся файлы скопировать в единый каталог:
$ mkdir final
$ cp arch/arm/boot/zImage final
$ find . -name '*ko' -exec cp '{}' final \;
Затем, в случае полной компиляции, нужно собрать файлы в ZIP-архив. Не абы какой, а сформированный определенным образом (речь идет о файле обновления для кастомной консоли восстановления. — Прим. ред.). Для этого клонируем с гитхаба шаблон для данного файла:
$ cd final
$ git clone https://github.com/koush/AnyKernel.git
$ cp ./*.ko ./AnyKernel/system/lib/modules/
$ cp ./zImage ./AnyKernel/kernel/
Поскольку те утилиты, что имеются в этом автоматическом апдейтере, немного устарели (во всяком случае, на моем планшете они завершались с сегфолтом), нужно их заменить рабочими, которые берем на d-h.st/RgI, и, распаковав, заменяем ими файлы с теми же названиями в каталоге AnyKernel/kernel/. Кроме того, нужно изменить скрипт для автоапдейтера, находящийся в AnyKernel/META-INF/com/google/android/updater-script. В конечном итоге должно получиться примерно следующее:
ui_print("Extracting System Files...");set_progress(1.000000);mount("ext4","MTD", "system", "/system");package_extract_dir("system", "/system");unmount("/system");ui_print("Extracting Kernel files...");package_extract_dir("kernel", "/tmp");ui_print("Installing kernel...");set_perm(0, 0, 0777, "/tmp/dump_image");set_perm(0, 0, 0777, "/tmp/mkbootimg.sh");set_perm(0, 0, 0777, "/tmp/mkbootimg");set_perm(0, 0, 0777, "/tmp/unpackbootimg");run_program("/sbin/busybox", "dd", "if=/dev/block/mmcblk0p9", "of=/tmp/boot.img");run_program("/tmp/unpackbootimg", "-i", "/tmp/boot.img", "-o", "/tmp/");run_program("/tmp/mkbootimg.sh");run_program("/sbin/busybox", "dd", "if=/tmp/newboot.img", "of=/dev/block/mmcblk0p9");ui_print("Done!");
Путь /dev/block/mmcblk0p9 здесь — та часть, которую необходимо изменить. Это раздел boot, и почти на всех устройствах он будет представлен разными файлами. Чтобы узнать имя файла на своем устройстве, выполни следующую команду:
$ for i in /dev/block/platform/*/by-name/boot; \do ls -l $i; done
После правки запаковываем каталог:
$ cd AnyKernel && zip -r AnyKernel.zip *
Затем кидаем файл на устройство и устанавливаем его с помощью кастомного рекавери (TWRP или CWM).
Сборка приложений
Модули ядра позволяют добавлять функциональность исключительно низкого уровня, которую в общем случае нельзя использовать напрямую. Для добавления же функциональности, которую можно использовать напрямую, нужно собирать программы, чем мы сейчас и займемся, — попробуем собрать несколько приложений. Перед сборкой почти всех приложений нужно экспортировать ряд переменных:
$ export CROSS_COMPILE=$CROSS_COMPILE_LINARO$ export CC=arm-unknown-linux-gnueabi-gcc
$ export CPP=arm-unknown-linux-gnueabi-cpp
$ export CXX=arm-unknown-linux-gnueabi-g++
$ export LD=arm-unknown-linux-gnueabi-ld
$ export AS=arm-unknown-linux-gnueabi-as
$ export AR=arm-unknown-linux-gnueabi-ar
$ export RANLIB=arm-unknown-linux-gnueabi-ranlib
$ export CPPFLAGS="--sysroot=$LINARO_SYSROOT"$ export CFLAGS="--static --sysroot=$LINARO_SYSROOT"$ export CXXFLAGS="--sysroot=$LINARO_SYSROOT"$ export LDFLAGS="--sysroot=$LINARO_SYSROOT"
И лишь затем можно приступать.
Bash
Bash собирать с помощью тулчейна Linaro очень легко — скачиваем исходники c официального FTP и распаковываем:
$ wget http://ftp.gnu.org/gnu/bash/bash-4.3.30.tar.gz
$ tar xzvf bash-4.2.53.tar.gz && cd bash-4.3.30
Выполняем скрипт configure и собираем:
$ ./configure --host=arm-linux --enable-static-link --without-bash-malloc --disable-rpath --disable-nls$ make
После сборки появится файл bash, который мы и копируем на устройство в /system/xbin.
Стоит дать комментарии, почему bash нужно компилировать с помощью тулчейна Linaro. В Bionic, стандартной реализации библиотеки libc в Android, отсутствуют некоторые POSIX-совместимые функции, используемые bash (такие, например, как mkfifo() или wctomb()). Соответственно, собрать bash с использованием этой библиотеки без танцев с бубном не выйдет. В Linaro же, с другой стороны, используется стандартная POSIX-совместимая библиотека glibc. Поскольку мы собираем bash статически, нам все равно, что используется в Android, — главное, чтобы версия glibc, с которой мы собираем, подошла к ядру. Впрочем, сегодня обратное маловероятно.
Lshw
Lshw — удобная консольная утилита, позволяющая быстро собрать информацию о доступном железе. Компилировать ее (опять же используя Linaro) достаточно просто. Скачиваем последнюю версию, распаковываем и заменяем в файлах src/Makefile и src/core/Makefile компилятор C++ на компилятор от Linaro (переменной CXX нужно присвоить значение arm-unknown-linux-gnueabi-g++), добавив также опцию --static в CXXFLAGS. Затем собираем обычным образом.
Htop
Это достаточно удобный консольный менеджер процессов для Linux. Для вывода информации он использует библиотеку ncurses, так что для его компиляции потребуется чуть больше времени. Создаем каталог htop, переходим в него и скачиваем ncurses:
$ mkdir htop && cd $_$ wget http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz
$ tar xzvf ncurses-5.9.tar.gz
$ cd ncurses-5.9
Устанавливаем переменную $SYSROOT_ADDITIONS, запускаем configure с нужными параметрами и собираем:
$ export SYSROOT_ADDITIONS=${HOME}/htop/rootdir
$ ./configure --with-normal --without-shared --without-cxx-binding --enable-root-environ --disable-widec --disable-GPM --without-ada --without-tests --host=arm-linux --prefix=$SYSROOT_ADDITIONS$ make && make install
Поскольку нам нет смысла включать поддержку юникода и мыши и мы не собираемся делать эту библиотеку динамической, отключаем эти опции (а также отключаем поддержку языка Ada).
Скачиваем и распаковываем сам htop, предварительно перейдя на уровень выше:
$ cd ..
$ wget http://hisham.hm/htop/releases/1.0.3/htop-1.0.3.tar.gz
$ tar xzvf htop-1.0.3.tar.gz
$ cd htop-1.0.3
Вновь переопределяем переменные окружения для сборки:
$ export CPPFLAGS="--sysroot=$LINARO_SYSROOT"$ export CFLAGS="--static -I${SYSROOT_ADDITIONS}/include --sysroot=$LINARO_SYSROOT"$ export CXXFLAGS="--sysroot=$LINARO_SYSROOT"$ export LDFLAGS="-L${SYSROOT_ADDITIONS}/ncurses-5.9/lib --sysroot=$LINARO_SYSROOT"$ export LIBS="${SYSROOT_ADDITIONS}/lib/libncurses.a"
Конфигурируем и собираем:
$ ./configure --host=arm --enable-static --disable-unicode$ make
Все бы хорошо, но после попытки запуска на устройстве вываливается ошибка «Error opening terminal: screen». Лечится это просто — достаем откуда‑нибудь каталог terminfo (я его выдернул из Terminal IDE, просто потому, что он оказался под рукой), копируем в /system/etc и выполняем в терминале на гаджете следующую команду:
# export TERMINFO=/system/etc/terminfo
После этого htop запустится без заморочек.
Tmux
Tmux — менеджер терминалов, более продвинутая альтернатива известного всем админам screen, созданная разработчиками OpenBSD. В случае с Android его удобно использовать для работы с устройством через adb shell или SSH (например, ходить на TV Box или HDMI-стик под управлением Android. — Прим. ред.).
Для компиляции tmux нам понадобится все та же ncurses — ее можно взять из предыдущей сборки, скопировав каталог rootdir. Помимо ncurses, потребуется библиотека libevent. Создаем каталог tmux, устанавливаем переменную $SYSROOT_ADDITIONS и скачиваем libevent и сам tmux:
$ export SYSROOT_ADDITIONS=${HOME}/tmux/rootdir
$ git clone https://github.com/libevent/libevent.git
$ git clone git://git.code.sf.net/p/tmux/tmux-code
Собираем libevent:
$ cd ../libevent
$ ./autogen.sh
$ ./configure --host=arm-linux --disable-shared --disable-openssl --disable-samples -prefix=$SYSROOT_ADDITIONS$ make && make install
Подготавливаемся к сборке tmux:
$ export CFLAGS="--static -I${SYSROOT_ADDITIONS}/include -I/${SYSROOT_ADDITIONS}/include/ncurses --sysroot=$LINARO_SYSROOT"$ export LDFLAGS=" -L${SYSROOT_ADDITIONS}/lib -L${SYSROOT_ADDITIONS}/include -L${SYSROOT_ADDITIONS}/include/ncurses --sysroot=$LINARO_SYSROOT"$ export LIBEVENT_CFLAGS="-I${SYSROOT_ADDITIONS}/include --sysroot=$LINARO_SYSROOT"$ export LIBEVENT_LIBS="-L${SYSROOT_ADDITIONS}/lib -levent --sysroot=$LINARO_SYSROOT"
И наконец, собираем сам tmux:
$ ./configure --enable-static --host=arm-linux && make
После сборки и заливки исполняемого файла перед запуском tmux, помимо переменной TERMINFO, нужно определить переменную TMPDIR — лично я использовал /data/local/tmp.
# export TERMINFO=/system/etc/terminfo# export TMPDIR=/data/local/tmp
Стоит заметить, что tmux может работать только от рута, потому что права доступа не позволяют писать в вышеуказанную папку кому попало.
Ngrep
А это крайне полезная утилита, позволяющая отлавливать пакеты с заданным паттерном (может быть нужно, например, для отладки RESTful-приложений). Для ее сборки потребуется собрать еще и libpcap. Как обычно, создаем каталог, куда и скачиваем libpcap, распаковываем его и собираем:
$ mkdir ngrep && cd $_$ wget http://www.tcpdump.org/release/libpcap-1.6.2.tar.gz
$ tar xzvf libpcap-1.6.2.tar.gz
$ cd libpcap-1.6.2
$ export SYSROOT_ADDITIONS=${HOME}/ngrep/rootdir
$ ./configure --host=arm-linux --disable-shared --with-pcap=linux --disable-dbus --prefix=$SYSROOT_ADDITIONS$ make && make install
Скачиваем сам ngrep, распаковываем, собираем:
$ export CFLAGS="--static -I${SYSROOT_ADDITIONS}/include -I${SYSROOT_ADDITIONS}/include/pcap --sysroot=$LINARO_SYSROOT"$ export LDFLAGS=" -L${SYSROOT_ADDITIONS}/lib -L${SYSROOT_ADDITIONS}/include -L${SYSROOT_ADDITIONS}/include/pcap --sysroot=$LINARO_SYSROOT"$ ./configure --enable-static --disable-dropprivs --host=arm-linux --with-pcap-includes=${SYSROOT_ADDITIONS}/include/pcap
$ make
Разберем опции обоих configure. В случае с libpcap мы отключили поддержку DBUS по понятной причине отсутствия его в Android и указали тип захвата пакетов (поскольку компилируем мы в конечном итоге под Linux, то и тип захвата ставим соответствующий). Для ngrep же мы указали путь к заголовочным файлам libpcap и отключили понижение привилегий по причине отсутствия файла /etc/passwd в Android, куда эта функциональность смотрит.
Linux Deploy
Компиляция программ может занять немало времени, да и не всякое приложение можно с легкостью собрать (например, текстовый torrent-клиент rtorrent потребует сборку libtorrent, а уж эта библиотека, в свою очередь, потянет за собой Boost). И если для пары‑тройки приложений это не столь критично, то в случае сборки большего количества трудозатраты становятся слишком велики. Да и сами приложения в случае статической компоновки могут раздуваться до невообразимых размеров. Однако есть решение и для этой ситуации — Linux Deploy, который с легкостью можно найти в Google Play.
Поскольку Android построен на базе ядра Linux, а изменения в нем не настолько сильны, чтобы мешать запуску обычных POSIX-приложений (что и было продемонстрировано выше), существует возможность развертывания chroot-окружения (с пробросом соответствующих псевдофайловых систем) и установки в нем userland-части дистрибутивов, поддерживающих архитектуру ARM. Приложение Linux Deploy делает именно это, создавая образ и монтируя его как loop-устройство.
Поддерживаются следующие дистрибутивы:
- Ubuntu;
- OpenSUSE;
- Fedora;
- Arch Linux;
- Gentoo;
- и, наконец, Kali Linux (его наличие, несомненно, обрадует пентестеров).
После развертывания и запуска системы в нее необходимо каким‑то образом войти. Для этого существует два метода: по SSH и через VNC. При наличии SSH-сервера в самом Android в Linux Deploy нужно либо его отключить, либо переопределить порт. А если использовать VNC, необходимо доустановить в Android VNC-клиент (рекомендую bVNC). Стандартные имя пользователя / пароль — android/changeme соответственно.
В данном контейнере можно производить практически те же действия, что и в обычном настольном дистрибутиве Linux, — со скидкой на поддерживаемую ядром функциональность. Замечу, что контейнер не изолирован от основной системы, и запуск служб в некоторых дистрибутивах не поддерживается по причине использования в них современных систем инициализации. Также стоит помнить, что приложения в контейнере нативные, — это изрядно кушает батарею.
Заключение
В статье были описаны два (а если считать компиляцию ядра с модулями, то три) метода расширения функциональности на Android. Подведем итоги.
Компиляция ядра и модулей имеет смысл только в тех случаях, когда тебе нужна низкоуровневая функциональность — будь то поддержка ФС или, допустим, модуль iptables. В современных стоковых ядрах поддержка загрузки модулей чаще всего отключена, так что для добавления функциональности всяко потребуется компиляция всего ядра.
В случае с компиляцией небольших POSIX-приложений можно попытаться использовать гугловский NDK, идущий с Bionic и практически несовместимый с POSIX, а можно использовать сторонний тулчейн для архитектуры ARM, в котором, как правило, есть библиотека glibc, и компилировать приложения статически. Однако следует помнить, что статически слинкованное приложение весит достаточно много, таким образом, нелишним будет еще раз подчеркнуть, что этот метод годится лишь для небольших приложений.
Для запуска крупных приложений можно использовать Linux Deploy, позволяющий развернуть на Android полноценную userland-часть популярных дистрибутивов. Однако и у него есть недостатки. Во‑первых, он изрядно кушает батарею, а во‑вторых, размер образа с данной userland-частью не может быть больше 4 Гб, так что, если раскатал губу на кучу приложений, закатай ее обратно.
В целом же запуск POSIX-приложений в Android вполне возможен — что и было показано в статье. А уж каким способом ты будешь что‑то делать, ты волен выбирать сам. Stay freedom.
Библиотеки, портированные на Android
Несмотря на то что Android не является в полной мере POSIX-совместимой ОС, под него все же были портированы некоторые из библиотек, доступных под десктопной Linux. Посмотрим, что это за библиотеки:
- SDL — удобная «обертка» вокруг низкоуровневых мультимедиафункций; используется в основном для разработки игр;
- FFmpeg — конвертация большинства аудио- и видеоформатов;
- Qt — начиная с пятой версии, Qt доступна и под Android;
- Unity — игровой движок;
- Ogre — «обертка» вокруг OpenGL для работы с 3D-графикой.
В общем, с точки зрения портирования приложений выбрать есть из чего.