Pull to refresh

Bad Apple на телефоне Siemens CX75

Reading time5 min
Views21K
Увидел я, значит, пост про Bad Apple на MSX и подумал — а чем я хуже? Телефоны Siemens одно время были достаточно популярны на территории бывшего СССР, особенно среди моего поколения. А их прошивки были достаточно хакабельными. Патчи были всякие разные: от замены графики до добавления новой функциональности. И самый апогей патчестроения: т.н. эльфпак или эльфлоадер, он же загрузчик нативных приложений в формате ELF, превращающий обычный кнопочный телефон в, по сути, смартфон.

В этом посте я расскажу о том, как я в 2022 году смог написать и скомпилировать эльф на macOS на М1, и покажу, что из этого получилось.


Прямо сразу: видео с тем, что получилось



Коротко о телефонах Siemens x65 и x75


  • SoC Infineon PMB8870, также известная как SGold
  • Процессор ARM926EJ-S
  • 2 Мб оперативки В комментах пишут, что 8
  • 32 Мб флэш-памяти, из них ~10 отведено под доступную пользователю файловую систему, в остальном объёме находится прошивка
  • Экран 132*176, большую часть времени работает в режиме RGB565 (16 бит на пиксель)
  • В S65 и всей 75 серии есть слот для карты памяти RS-MMC (это как SD, только тоньше и в половину длины)
  • В 75 серии присутствует аппаратный декодер mp3
  • Связь с внешним миром: GSM/GPRS (без EDGE), последовательный порт в проприетарном разъёме, ИК-порт, в некоторых моделях bluetooth.

fdsafa
Структурная схема SoC из рекламных материалов производителя

Ядро процессора только одно, и оно занимается и GSM (как я понимаю, на достаточно высоком уровне), и рисованием интерфейса на экране.

Коротко об эльфах и их загрузчике


Строго говоря, полноценной операционной системы с настоящим API, динамическим линкером, виртуальной памятью и прочими современными радостями у этих телефонов нет. В процессоре есть MMU, но он не используется. Прошивка достаточно монолитна, и, по слухам, сделана на основе ОС реального времени Nucleus RTOS. Это всё оказывается проблемой, когда хочется динамически подгружать какой-то левый код — адреса нужных этому коду функций разные на разных моделях телефонов и на разных версиях прошивки для одной и той же модели. Разработчики загрузчика эльфов решили эту проблему оригинально: сделали интерфейс системных вызовов а-ля DOS. Раскладываешь аргументы функции по регистрам, вызываешь инструкцию swi XX, где XX — номер нужной функции, а добавленный патчем обработчик прерывания уже по таблице для конкретной прошивки прыгает в нужное место.

В оригинальном репозитории на тему эльфов, который каким-то чудом всё ещё онлайн, да и по моей собственной памяти, эльфы компилировались какой-то очень странной проприетарной микроконтроллерной штукой под названием IAR. У неё вот этот способ вызова функций через swi — как будто бы встроенная в компилятор фича. Я уже был готов сам начать разбираться с правильным ABI для этих «системных вызовов», писать куски ассемблера, отлаживать всё это дело без отладчика и даже логирования, но наткнулся на вот этот репозиторий, в котором те же самые эльфы сопровождаются файлами проектов для CodeBlocks и собираются нормальным человеческим gcc. Выглядит так, что самая сложная работа всё-таки уже была сделана за меня. Так что берём это всё и…

Пытаемся собрать эльф


И сразу вопрос — а чем собирать-то будем? Я уже когда-то пробовал извратиться с Android NDK, но ничего адекватно работающего из этого не получилось. К счастью, в Homebrew есть пакет gcc-arm-embedded с описанием «Pre-built GNU bare-metal toolchain for 32-bit Arm processors» — выглядит как именно то, что нам нужно!

Ставим, собираем минимальный пример эльфа из репозитория выше, elfloader3/examples/C/main.c:
arm-none-eabi-gcc main.c -o hello.elf -I../../dev/include -DSGOLD -O2 -std=gnu99 -D__NO_LIBC -lcrt -lcrt_helper -lgcc -L../../dev/lib/libs -Wl,--defsym,__ex=0 -Wl,--gc-section -nostartfiles
(не показано: с десяток неудачных попыток)

Заливаем на телефон, запускаем. Ничего не происходит. Осознаём, что эльфы, которые генерирует gcc, всё-таки не настолько же простые, как из IAR. Ставим имеющийся в репозитории более новый эльфлоадер. Поверх старого, надеясь, что ничего не сломается.


Возвращение 2007 выглядит примерно вот так

Хорошая новость: телефон после установки патча включается и работает. Плохая: эльф всё равно не запускается, но теперь хотя бы показывает ошибку.


Динамические библиотеки, щито?! В 2007 такого не было.

После чтения исходников стало понятно, что библиотека должна быть в папке /ZBin/lib либо в памяти телефона, либо на карте. Кладём её туда и запускаем ещё раз. Получаем ошибку «Incorrect elf». Находим в исходниках рядом с ней строку (орфография сохранена):
If you wont to use the shared libraries, you must add to linker option '--defsym __ex=0' add use elfclose function!

Не буду писать много букв о том, как я это долго и мучительно отлаживал, но в итоге у меня получилось скомпилировать работающий эльф. Благодаря этому экспириенсу я узнал много нового о том, как операционные системы загружают исполняемые файлы. В итоге:

  • Пришлось отказаться от предоставленной эльфлоадером «рантайм-библиотеки», потому что у меня так и не получилось заставить это работать. Видимо, GCC слишком новый.
  • Не обязательно вызывать все функции прошивки через программные прерывания. Этот новый эльфлоадер так и не делает — он получает указатель на таблицу указателей на функции через swi 0x80FF, а дальше уже вызывает из неё. Я у себя сделал так же, в swilib.h уже все нужные макросы прописаны, удобно.
  • Ни в коем случае нельзя инициализировать поля с указателями в структурах статически (на этапе компиляции). Эльф загружается в по сути случайный адрес в оперативке, а компилятор прописывает в эти поля адреса как будто эльф загружен по адресу 0x00000000. Результат предсказуем. Если инициализировать во время выполнения, такой проблемы нет, адрес высчитывается относительно pc и получается правильный.
  • Выгрузку эльфа из памяти я так и не смог сделать. Идея там такая, что нужно, чтобы эльф экспортировал (или импортировал?) символ __ex, в который эльфлоадер положит указатель на свою структуру с информацией про сам этот эльф. При выходе нужно передать этот указатель в функцию elfclose(). У меня так и не получилось заставить линкер экспортировать этот символ так, как оно ожидает, так что эта память просто течёт. Но для того, что мы тут пытаемся сделать, это вроде и не так уж страшно ¯\_(ツ)_/¯

Итоговый makefile
CC=arm-none-eabi-gcc
CFLAGS=-I../sie-dev/elfloader3/dev/include -DSGOLD -DX75 -D__NO_LIBC -O2 -std=gnu99 -Wl,-s -nostartfiles -nostdlib -fPIE -fshort-wchar
LIBS=-lgcc
DEPS=

%.o: %.c $(DEPS)
	$(CC) -c -o $@ $< $(CFLAGS)

BadApple: BadApple.o
	$(CC) -o BadApple.elf BadApple.o $(CFLAGS) $(LIBS)

.PHONY: clean

clean:
	rm -f *.o *.elf


Собственно сжатие и вывод видео


Изначально я хотел выводить картинку прямо в буфер экрана, но из этого ничего не получилось. Функция RamScreenBuffer() действительно возвращает указатель на буфер экрана. Можно прочитать 132*176*2 байт и получится скриншот. Но писать туда бесполезно — ничего из записанного почему-то нигде не отображается. Скорее всего, потому что сама прошивка его перезаписывает раньше, чем свежезаписанные данные попадут в местный аналог видеокарты.

В итоге остановился на менее оптимальном способе: сделать растр (т.н. IMGHDR), обновлять в нём пиксели каждый кадр и рисовать его на экран. Он бывает с 1, 8 или 16 битами на пиксель, я выбрал 8, чтобы были градации серого, но чтобы и не приходилось слишком много данных гонять туда-сюда (это ооооочень медленно). Получилось не очень оптимально, но 15 кадров в секунду вроде бы тянет.

Алгоритм сжатия максимально тупой, вариация на тему RLE. Есть команды «пропустить пиксели» (которые не поменялись с прошлого кадра), «перезаписать пиксели», «заполнить указанным значением» и «заполнить нулями». Всё видео, 3287 кадров, заняло около 13 Мб. Для облегчения чтения файл поделен на страницы по 40 Кб. Предполагается читать страницу целиком и держать её в памяти — каждый кадр гарантированно находится в пределах одной страницы.

Исходники самого эльфа и кодировщика

FAQ


Q: Зачем ты это сделал?
A: Потому что могу, потому что хотел выпендриться, и потому что у меня всё равно почти нет жизни.

Q: А на других телефонах заработает?
A: На 75 серии — скорее да, чем нет. На 65 серии — скорее нет, чем да, потому что во встроенную память файл с видео не влезет, нужна карта. На S65 может заработать, если сконвертировать музыку в amr или wav. А может и не заработать. Есть только один способ узнать!

Q: Но ведь на этих сименсах и так можно смотреть видео? Я помню, я смотрел!
A: В окошке на полэкрана, с битрейтом «шакалы 20й степени» и однозначным FPS.
Tags:
Hubs:
Total votes 104: ↑103 and ↓1+102
Comments89

Articles