ELF — приложение на телефоне LG?

в 17:39, , рубрики: elf, ненормальное программирование, Смартфоны и коммуникаторы, метки: ,

Первоначальное мнение

Я всегда думал, что телефоны от компании 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.

Вот так примерно выглядит код эльфа:

main.c

#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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js