Первоначальное мнение
Я всегда думал, что телефоны от компании LG для «домохозяек». И когда мне попал в руки аппарат LG KP500 я своё мнение особо не изменил, хотя в нём было много чего весьма интересного. Я имею ввиду это диспетчер задач (даже есть специальная кнопка), который может вызывать и закрывать другие свёрнутые родные и Java — приложения, в Java доступна работа с файловой системой (JSR — 75), хоть и не полностью, на этом функционал Java машины заканчивается. В этом телефоне (это я подчёркиваю, он позиционируется как «звонилка») даже есть свой формат исполнительных файлов — PXE (*.pxo), но правда он скрыт для пользователя и запускается из конкретной папки диска, имеющий атрибут только чтение. Данный аппарат имеет неплохое «железо». Это Nand Flash 256 Мб / SDRAM 128 Мб, TFT сенсорный резистивный дисплей c разрешением 400x240 и 262 тысяч цветов, 3-х осевой акселерометр и бейзбенд-процессор Infineon SGold-3 (PMB8878), ну и стандартный набор: камера, BlueTooth, радио и т.д. Операционной системы такой как Android, Windows, iOS — там нет, зато есть свой закрытый «велосипед» на ядре Nucleus RTOS древней версии. Такое железо, на мой взгляд, к подпольным «эльфописателям» не попадалось, что даёт определённый стимул.
PXE — формат
PXE это формат исполнительных файлов на телефонах LG, начиная с KP500. Он, как вы догадались, закрыт, на него нет документаций, про SDK я вообще молчу. В телефоне запускается только из каталога системного диска прошивкой или другими родными приложениями этого формата:
"/cus/lgapp/Pxo/*.pxo"
Так как процессор в телефоне ARM926EJ-S, то и в них встречается ARM / Thumb инструкции.
Эти файлы имеют «Place Independed Code», т.е. не зависят от адреса загрузки, то бишь проецирования на память. Это говорит о том, что имеется таблица релокаций. Написаны, естественно, на ARM C/C++, но всё-таки разработчики отдали предпочтение Thumb набору инструкций. Имеются 2 секции: данных и кода. Конечно же, PXE — приложения используют некоторое API. Оно представлено 2-уровневой библиотекой функций, ну группой таблиц указателей на процедуры прошивки. Указатель на эту таблицу сообщается приложению при создании. А вот сама программа построена на главном обработчике событий, т.е. в неё поступают различные события: создания, выхода, остановки, активации, перерисовки, таймера и т.д.
ELF — загрузчик
Можно было и создавать программы в файлах PXE, но для этого нужен, как минимум, компоновщик, умеющий собирать его из объектных файлов. где такой достать неизвестно, а самому писать — смерть. Второй фактор это ограничение места запуска, не просто ограничение, а целая проблема, ибо просто так добавить его в каталог системного диска задача утомительная и диск не резиновый, правда с этим можно справится без затруднений и рвания на себе волос.
Остается оригинальный способ — делать свои программы в формате ELF. Написать для него загрузчик задача простая, а компиляторов которые его могут собирать хоть отбавляй.
Так я и решил написать загрузчик эльфов для данного телефона, проблем с внесением своего кода в прошивку особых не было. Ах да, по поводу модификации прошивки, это очень странно, что этим никто не занимался за 4 года сообщество создавало / разблокировало / доставало какие-угодно flash — темы, программы для распаковки / собирания прошивок, официальные прошивальщики, утилиты, даже флешер для слития / залития ФС (к сожалению, запись / чтение областей кода, производилось некорректно, поэтому и бесполезно). Т.е. было очень много достижений, но в плане патчинга и исследования кода прошивки было сделано чуть больше, чем ничего. Пришлось самостоятельно изучать программу для теста процессора, в ней я нашёл секретный протокол (DWD) для работы с телефоном, написал программу, попутно исправляя глюки этого протокол, и наконец-таки слил все нужные дампы адресного пространства. Итак, вернёмся к нашим эльфам. Модифицированная прошивка умеет запускать один эльф, а он в свою очередь загружает остальные эльфы и библиотеку, а так же патчит Java — машину для расширения её возможностей. Всё это хорошо, но загруженные и исполненные эльфы это просто голый код, проецированный на память. И чтобы телефон видел в них нормальную программу принято решение использовать метод «паразитирования» PXE — файла. Для этого был пропатчен маленький файл этого формата, т.е. он стал перенаправлять все события в ELF, который его загрузил, и очищал его из памяти при событии выхода. От эльфа требовалось загрузить этот «donor.pxo» с параметрами — указателями на процедуру обработки событий и базу загрузки ну и копировать код, который представлен в исполнительных файлах PXE. У эльфов есть ещё возможность использовать вызов процедур через SWI, т.е. отдельная библиотека функций и SWI обработчик, как в ElfPack на Siemens.
Вот так примерно выглядит код эльфа:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "elf/elf.h"
extern int thing_w;
extern int thing_h;
extern int thing_d;
extern unsigned char thing_bitmap[];
extern int star_w;
extern int star_h;
extern int star_d;
extern unsigned char star_bitmap[];
/* =================================== GUI ================================== */
#define WINDOW_ID_SCREEN 0x5001
int Screen_EventHandler(int event_id, int item_num, int param);
void Screen_OnInit();
void Screen_Close(int action);
void Screen_OnExit();
void Screen_OnKeyDown(int key);
void Screen_OnKeyUp(int key);
void Screen_OnDraw();
void Screen_OnIndicatorDraw();
void Screen_OnTimer();
void Screen_OnPointing(int action, int position);
void Screen_OnAwake();
void Screen_OnSleep();
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
int w1 = 0, h1 = 0, w2 = 0, h2 = 0;
int z = 0, d = 0;
unsigned char *b1 = 0, *b2 = 0;
int de1 = 0, de2 = 0;
void Draw()
{
char ascii_text_buffer[256];
unsigned short u16_text_buffer[256];
drw_fillrect(0, GUI_STATUSBAR_HEIGHT, DISPLAY_WITDH, DISPLAY_HEIGHT, drw_color_make_rgb(0, 0, 0));
drw_string_setoutline(1);
drw_string_setcolor(drw_color_make_rgb(200, 0, 0));
cp1251_2_utf16(u16_text_buffer, "Тест тачскрина");
drw_string(0, GUI_STATUSBAR_HEIGHT + 16 * 0, u16_text_buffer, 16);
sprintf(ascii_text_buffer, "Координаты #1: X: %d / Y: %d", x1 + w1/2, y1 + h1/2);
cp1251_2_utf16(u16_text_buffer, ascii_text_buffer);
drw_string(0, GUI_STATUSBAR_HEIGHT + 16 * 1, u16_text_buffer, 16);
sprintf(ascii_text_buffer, "Координаты #2: X: %d / Y: %d", x2 + w2/2, y2 + h2/2);
cp1251_2_utf16(u16_text_buffer, ascii_text_buffer);
drw_string(0, GUI_STATUSBAR_HEIGHT + 16 * 2, u16_text_buffer, 16);
if (z == 1) {
drw_bitmap(x1, y1, w1, h1, de1, b1);
drw_bitmap(x2, y2, w2, h2, de2, b2);
} else
{
drw_bitmap(x2, y2, w2, h2, de2, b2);
drw_bitmap(x1, y1, w1, h1, de1, b1);
}
gui_redraw();
}
//Действие при создании окна
void Screen_OnInit()
{
printf("Screen_OnInitrn");
x1 = 50;
x2 = 80;
y1 = 200;
y2 = 50;
w1 = thing_w;
h1 = thing_h;
w2 = star_w;
h2 = star_h;
b1 = thing_bitmap;
b2 = star_bitmap;
de1 = thing_d;
de2 = star_d;
z = 0;
d = 0;
Draw();
}
//Действие при уничтожении окна
void Screen_OnExit()
{
printf("Screen_OnExitrn");
}
//Действие при зажатии настоящей кнопки или рабочей области тачскрина
void Screen_OnKeyDown(int key)
{
printf("Screen_OnKeyDown key = %drn", key);
switch (key)
{
case KEY_MULTI:
__taskapi_call_taskman();
break;
case KEY_END:
__taskapi_app_exit(0, 0, 0);
break;
}
Draw();
}
//Действие при отпускании настоящей кнопки
void Screen_OnKeyUp(int key)
{
printf("Screen_OnKeyUp key = %drn", key);
}
//Действие при отрисовке окна
void Screen_OnDraw()
{
//printf("Screen_OnDraw()rn");
Draw();
}
//Действие при отрисовке статус-бара
void Screen_OnIndicatorDraw()
{
//printf("Screen_OnIndicatorDraw()rn");
Draw();
}
//Действие при срабатывании таймеров
void Screen_OnTimer(int timer_id, int param)
{
//printf("Screen_OnTimer: %d / %drn", timer_id, param);
}
//Действие при манипуляциях с тачскрином
void Screen_OnPointing(int action, int position)
{
int x, y;
x = PXE_LOWORD(position);
y = PXE_HIWORD(position);
switch (action)
{
case TOUCH_ACTION_PRESS:
{
//Если 1-ый объект наверху
if (z == 0)
{
if (x >= x1 && x < (x1 + w1) && y >= y1 && y < (y1 + h1)) d = 1;
else
{
if (x >= x2 && x < (x2 + w2) && y >= y2 && y < (y2 + h2))
{
z = 1;
d = 1;
}
}
//Если 2-ой объект наверху
} else
{
if (x >= x2 && x < (x2 + w2) && y >= y2 && y < (y2 + h2)) d = 1;
else
{
if (x >= x1 && x < (x1 + w1) && y >= y1 && y < (y1 + h1))
{
z = 0;
d = 1;
}
}
}
break;
}
case TOUCH_ACTION_PRESSED:
{
if (d == 1)
{
if (z == 0)
{
x1 = x;
y1 = y;
} else
{
x2 = x;
y2 = y;
}
}
break;
}
case TOUCH_ACTION_RELEASE:
{
d = 0;
break;
}
}
Draw();
}
//Действие при активации
void Screen_OnAwake()
{
printf("Screen_OnAwake()rn");
}
//Действие при сворачивании
void Screen_OnSleep()
{
printf("Screen_OnSleep()rn");
}
//Главный обработчик окна WINDOW_ID_MAINMENU от приложения
int Window_EventHandler(int cmd, int subcmd, int status)
{
switch (cmd)
{
case Window_OnInit:
Screen_OnInit();
break;
case Window_OnExit:
Screen_OnExit();
break;
case Window_OnAwake:
Screen_OnAwake();
break;
case Window_OnSleep:
Screen_OnSleep();
break;
case Window_OnKeyDown:
Screen_OnKeyDown(subcmd);
break;
case Window_OnKeyUp:
Screen_OnKeyUp(subcmd);
break;
case Window_OnDraw:
Screen_OnDraw();
break;
case Window_OnTimer:
Screen_OnTimer(subcmd, status);
break;
case Window_OnPointing:
Screen_OnPointing(subcmd, status);
break;
case Window_OnIndicatorDraw:
Screen_OnIndicatorDraw();
break;
default:
break;
}
return 1;
}
/* ---------------------- Обработчик событий приложения --------------------- */
int elf_run(int event_id, int wparam, int lparam)
{
//printf("elf_run = %d / %d / 0x%08Xrn", event_id, wparam, lparam);
switch (event_id)
{
//Событие при создании приложения
case PXE_RUN_CREATE_EVENT:
//Устанавливаем имя приложения в Диспетчере задач
__taskapi_app_setname(app_handle, 0, 0, 0);
//Создаём окно
gui_window_create(WINDOW_ID_SCREEN, Window_EventHandler);
//Запускаем инициализацию окна
gui_window_init(WINDOW_ID_SCREEN);
printf("PXE_RUN_CREATE_EVENTrn");
return 1;
//Событие при создании приложения
case PXE_RUN_DESTROY_EVENT:
//Уничтожаем окно
gui_window_destroy_all();
printf("PXE_RUN_DESTROY_EVENTrn");
return 1;
//Событие при активации приложения
case PXE_RUN_RESUME_EVENT:
printf("PXE_RUN_RESUME_EVENTrn");
//Отправим команду на перерисовку
gui_window_trans_event(PXE_RUN_PAINT_EVENT, 0, 0);
return 1;
//Событие при сворачивании приложения
case PXE_RUN_SUSPEND_EVENT:
printf("PXE_RUN_SUSPEND_EVENTrn");
return 1;
default:
//Конвертируем остальные события приложения для окна
gui_window_trans_event(event_id, wparam, lparam);
return 1;
}
}
Первый не тестовый эльф, который я публично представил, это эмулятор приставки Sega.
И всё остальное по теме:
Наработки для KP500
Автор: Dimadze