OpenWiki

Palm Native Arm Modules

Edit this page (last edited February 28, 2007)
Palm Notes | Recent Changes | Title Index | User Preferences | Random Page | Help
  • Введение
  • Взгляд сверху
  • Две части работы с модулями
  • Модули
  • Блок данных
  • Область системных данных
  • Область ссылок на массив SB
  • Область переходников на импортируемые модули
  • Область изменяемых данных модуля
  • Блок кода
  • Заголовок модуля
  • Таблица экспорта
  • Область кода
  • RAL
  • Структура базы программы
  • Функции из ядра PalmOS
  • Введение

    Настоящая статья описывает принципы построения нативных ARMовых модулей Palm OS 5.

    Взгляд сверху

    Для плавного перехода на современные процессоры, фирма Palm(Source) реализовала в 5 версии своей ОС два программных интерфейса: интерфейс, повторяющий архитектуру старых ОС на процессоре m68000, и новый, реализованный для процессоров семейства ARM. Новый интерфейс близок к старому, но некоторые детали изменились. Так, существенно изменилась работа с бинарными модулями, программами и библиотеками.

    На смену старым приложениям, хранящим свой код в ресурсах code#1, code#0 и data#0, пришли новые, с ресурсами amdc#0, amdi#0 и amdd#0. В чем отличие?

    Две части работы с модулями

    Palm OS 5 разделила работу с модулями на две части. Первая часть находится в модуле DAL и носит название RAL, вторая содержится в ядре ОС (boot) и реализует собственно программный интерфейс. Зачем потребовалось такое разделение?

    Дело в том, что Palm OS 5 существует в различных инкарнациях, как в виде железа ( на ARM процессоре), так и в виде симулятора ( на x86 с Win32 API слоем ниже). Все различия, связанные с нижележащими платформами вынесены в RAL, а системная часть исполняет общие вещи, в частности загрузку программы из базы.

    Модули

    Важно понимать, что RAL не оперирует с базами, он сразу работает с модулями в памяти. Palm OS работает в разделяемом адресном пространстве, так что все процесс, нити и прочая лабуда использует единый набор модулей.

    Модуль идентифицируется идентификатором, числом от 0 до 0x3FF. Этот идентификатор выдается модулю при создании, что резко ограничивает возможность написания своих модулей.

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

    Каждый загруженный модуль состоит из двух блоков памяти, read-only блока кода и read-write блока данных.

    Блок данных

    Указатель на блок данных является определяющим для модуля, этот так называемый static base pointer. По адресу static base ( дальше SB ) можно найти полную информацию о модуле. Факт загруженности модуля определяется по ненулевому SB. SB указывает не на начало блока, а где-то ближе к началу, так что некоторые данные адресуются по отрицательным смещениям от SB.

    Такой способ адресации был рекомендован компанией ARM как один из вариантов ABI. В регистре R9 хранится указатель на SB текущего модуля, все глобальные данные адресуются относительно SB. Также в любом SB[0] хранится указатель на массив всех SB, что позволяет быстро перегружать регистр при вызове функций другого модуля.

    Блок данных состоит из следующих частей:

    Область системных данных

    Эта область содержит различные системные данные, позволяющие использовать модуль. Название PreStaticBase? дано структуре из-за того, что она лежит по отрицательным смещениям от SB, который указывает после нее.

    typedef struct {
            UInt32                data0Size;
            void *                data0;
            void *                code0;
            void *                code1;
            LocalID                dbH;
    } RALCodeDescriptorType;
    typedef struct PreStaticBase{
            RALCodeDescriptorType codeDesc;
            UInt32 dbUseCount;
            void * lastDispatchTablePtr;
            void **(entryTables[3]); //-C UI, -8 Boot, -4 DAL
    }PreStaticBase;
    

    Структура RALCodeDescriptorType? содержит указатели на залоченные ресурсы базы программы и будет описана дальше. dbUseCount? содержит счетчик ссылок на модуль. Каждый модуль, вызывающий функции из нашего увеличивает этот счетчик. lastDispatchTablePtr? будет описана позже, в описании переходников на импортируемые модули. entryTables - это самый полезный элемент PreStaticBase?. Этот элемент в каждом модуле указывает на одно и то же - на массив адресов системных вызовов трех модулей: DAL, Boot и UI. Всеми любимый код
    LDR        R12, [R9, #-8]
    LDR        PC, [R12, #0x220]
    
    загружает из нужной таблицы указатель на системный вызов и переходит по нему. Это самый компактный способ вызова системного API. Еще раз замечу: в массивах лежат указатели на армовый код, исполняющий системные вызовы.

    Область ссылок на массив SB

    Непосредственно регистр SB указывает на 4 слова, содержащих указатели на массив SB для разных модулей. Получить конкретный SB можно по идентификатору модуля. Так, модуль ui с идентификатором 2 загружает свой SB так:
    STR     R9, [SP,#-4]!        // сохранить старый SB
    LDR     R9, [R9]        // загрузить указатель на массив всех SB
    LDR     R9, [R9,#8]        // загрузить "наш" SB. 8 = 2 * sizeof(void *)
    

    SB[0] указывает на начало массива. SB[1] = SB[0] + 0x80, SB[2] = SB[0] + 0x100, SB[3] = SB[0] + 0x180. В принципе три последних слова избыточны, но они позволяют генерировать короткий код загрузки SB для модулей с идентификаторами от 0x80 до 0x1FF.

    Область переходников на импортируемые модули

    typedef struct LinkDispatchEntry{
            void  **entries;
            UInt32 refNum;
    }LinkDispatchEntry;
    
    Дальше идет область переходников на импортируемые модули. Эта область используется при неявной загрузке модулей, используюшей функцию Sys Linker Stub. Такая загрузка предполагает, что требуемый модуль будет загружен в ответ на вызов первой функнции, которую мы из него импортируем. Неявная загрузка удобна при написании программы, поскольку она не требует вызывать SysLoadModule? и получать указатель на функцию. Неявность подразумевает, что загрузку модуля делает ОС, которая запоминает информацию о загруженном модуле в области переходников. Компилятор выделяет по одному переходнику на каждый используемый модуль, все вызовы функций одного модуля используют общий переходник. Перед использованием переходник обнулен. Первый вызов импортируемой функции видит 0 в поле entries, загружает требуемый модуль, и записывает его refNum и указатель на список экспортируемых функций в переходник. Последующие вызовы просто обращаются к функции через указатель в поле entries.

    При загрузке библиотеки увеличивается счетчик ее использований dbUseCount?. При выгрузке модуля ОС проходит по всем заполненным переходникам и уменьшает счетчик использования всех библиотек, указанных в refNum. Как найти где заканчивается массив переходников? Очень просто, при заполнении переходника модифицируется поле lastDispatchTablePtr? в PreStaticBase? так, что оно указывает на переходник с наибольшим адресом. Так что при выгрузке будут освобождены только те переходники, которые были инициализированы, то есть те, через которые происходили вызовы. Нюанс: если при инициализации переходника обнаружится перекрестные вызовы библиотек (например, на Treo650, HsNav? вызывает функции из HsExtensions2?.0 и наоборот), то у второго модуля поле entries будет заполнено, но поле refNum (указывающее на первый модуль) будет выставлено в ноль и счетчик ссылок на первый модуль не будет увеличено. Это позволит безопасно загружать библиотеки с перекрестными ссылками, и дает возможность правильной выгрузки библиотеки.

    Второй нюанс: поскольку инициализация переходника изменяет lastDispatchTablePtr?, то указание переходника в произвольном месте памяти (отличном от SB+0x16) может обрушить устройство при выгрузке, ибо цикл от SB+0x16 до lastDispatchTablePtr? захватит добрую область памяти, содержащей совсем другие данные...

    Область изменяемых данных модуля

    За переходниками лежит область глобальных данных модуля. Инициализированные данные заполняются из базы. С помощью регистра SB программа адресуется к своим глобальным данным. Такая адресация не требует изменения ссылок на данные при загрузке модуля в память. Заметим, что именно сюда возвращается указатель функциями типа PalmOSGetGlobalsPtr?.

    Блок кода

    Блок кода состоит из трех частей:

    Блок кода загружается из базы и не изменяется в процессе работы.

    Заголовок модуля

    Заголовок модуля описывает характеристики модуля.
    typedef struct AmdcHeader{
            UInt8        jumpToPilotMain[4];
            UInt32        exportTableOffsetFromHeader;
            UInt16        moduleID;
            UInt16        moduleIDEx;
            UInt32        revision;
            UInt32        exportEntriesCount;
            char        type[4];
            char        creator[4];
            char        amdcType[4];
            UInt16        flags1;
            UInt16        flags2;
    } AmdcHeader;
    

    Первые 4 байта занимает переход на функцию PilotMain?. Это не адрес, это инструкция, причем в ARMовой кодировке. PilotMain? сохраняет традиции классического Palm OS, кроме вызова для показа UI, эта функция получает управление для инициализации и деинициализации библиотеки, а еще один полезный код позволяет обрабатывать вызов функций библиотеки из m68k через PACE.

    Дальше идет смещение библиотеки экспортируемых функций от начала модуля. Обычно она располагается сразу после заголовка по смещению 0x24, но это необязательно. Вместе с полем exportEntriesCount?, которое описывает число экспортируемых функций они полностью описывают второй компонент блока кода.

    Дальше идут два поля, составляющих вместе идентификатор модуля. Причем modRefID = moduleID/4 + (moduleIDEx/4) * 32. У большинства модулей поле moduleIDEx обнулено, так что поле moduleID однозначно идентифицирует модуль. Так, у модуля CPM Library moduleID содержит 132, так что идентификатор у модуля 33. А вот у модуля SSL Crypto Library moduleID=0, а moduleIDEx=4, так что у модуля идентификатор 32. Зачем так сделано? Неизвестно. Важно понять, что результирующий идентификатор должен быть уникальным, а как он вычисляется из двух полей - неважно.

    Поле revision хранит ревизию модуля. На всякий случай.

    Поля type, creator и amdcType в принципе описывают соответствующие поля базы, откуда программа загрузилась. Если бы. Модуль MImgPlugIn?.prc с Treo 650 в базе имеет тип CdPl?, создателя ImCp?, а в модуле создателем почему-то указан Mjpg. Модуль boot.prc, вроде бы солидный модуль, а туда же: у базы тип rsrc, создатель psys, а у модуля тип boot. Используется ли это поле в заголовке при поиске библиотеки по типу, или нет - неизвестно. amdcType по идее хранит тип ресурса в базе, откуда загружается программа. Тоже нет, у boot.prc исполняемый код хранится в ресурсе boot#0x2713, а в поле amdcType хранится 'amdc'. Все поля приведены в little endian нотации, так что при просмотре ресурсов вы увидите все поля с перевернутыми байтами. Вообщем непонятные поля, скорее всего в настоящее время они не используются.

    В последних полях хранятся флаги, указывающие на специфические свойства модулей. Обычно первый флаг содержит 2, а второй 4. Для системных модулей во втором флаге хранится 7.

    #define RAL_AMDC_FLAG1_VALID_MODULE 2
    #define RAL_AMDC_FLAG2_VALID_MODULE 4
    #define RAL_AMDC_FLAG2_SYSTEM_MODULE 3
    
    RAL_AMDC_FLAG1_VALID_MODULE и RAL_AMDC_FLAG2_VALID_MODULE выставляется всем, а RAL_AMDC_FLAG2_SYSTEM_MODULE может стоять только у dal, boot, ui.

    Таблица экспорта

    Местоположение и размер таблицы экспорта указывается в полях exportTableOffsetFromHeader? и exportEntriesCount?. Таблица экспорта - это массив 4-байтовых инструкций перехода на экспортируемые функции. Не указатели, а сам армовый код перехода на функции.

    Область кода

    Сало - они и есть сало. Сюда компилятор кидает код функций, здесь осуществляются все переходы из arm в thumb и обратно. Здесь же лежат константные данные. По понятным причинам генерируемый код должен быть PIC ( position independent code), что для армового кода проблем не вызывает.

    RAL

    Как сказано выше, модуль RAL представляет самый нижний уровень работы с модулями. Он привязан к платформе и выполняет платформозависимый код. RAL API описывает абстрактный модуль и операции с ним.

    Что входит в RAL API? Все, что было описано выше про внутренности модулей скрыто в RAL API.
    typedef struct {
            UInt32                data0Size;        /* 'amdd' resource size */
            MemPtr                data0;                /* 'amdd' resource pointer */
            MemPtr                code0;                /* 'amdi' resource pointer */
            MemPtr                code1;                /* 'amdc' resource pointer */
            MemHandle        dbH;                /* program database memhandle */
    } RALCodeDescriptorType;
    
    typedef struct {
            UInt32                refNum;
            UInt32                dataSize;
            UInt32                revision;
            UInt32                entries; // entries count
            UInt8                sharingAppID;
    } RALModuleInfoType;
    
    Err RALCallWithNewStack(void *procP, void *procArg, void *newStack);
    Err RALGetStaticBase(UInt32 refNum, void **staticBaseP);
    Err RALGetEntryAddresses(UInt32 refNum, UInt32 startEntryNum, UInt32 endEntryNum, void **addressesPP);
    Err RALGetModuleInfo(RALCodeDescriptorType *codeDescriptorP, RALModuleInfoType *moduleInfoP);
    Err RALLinkClient(UInt32 refNum, UInt32 clientID, void **dispatchTablePP);
    Err RALLoadModule(RALCodeDescriptorType *codeDescriptorP, void *dataSegmentP, PalmOSMainFuncType **mainEntryPP, UInt32 *refNumP);
    Err RALPatchEntry(UInt32 refNum, UInt32 entryNum, void *procP, void **oldProcPP);
    Err RALSetA5(UInt32 newValue);
    Err RALUnloadModule(UInt32 refNum, RALCodeDescriptorType *codeDescriptorP, void **dataSegmentPP);
    Err RALUnloadNext(UInt32 refNum, RALCodeDescriptorType *codeDescriptorP, void **dataSegmentPP);
    

    Функция RALCallWithNewStack? позволяет вызывать функцию на новом стеке. Это действие абстрагирует для SysAppLaunch? запуск нового подпроцесса с новым стеком.

    Функция RALGetStaticBase? возвращает указатель на SB.

    Функция RALGetEntryAddresses? возвращает для загруженного модуля либо указатель на точку входа, если startEntryNum? равно -1, либо копирует в переданный массив адреса экспортируемых функций в диапазоне startEntryNum?..endEntryNum?.

    Функция RALGetModuleInfo? возвращает информацию, прочитанную из RALCodeDescriptorType? в структуре RALModuleInfoType?.

    Функция RALLinkClient? инициализирует переходник вызова из библиотеки с идентификатором clientID библиотеку с идентификатором refNum.

    Функция RALLoadModule? пользуясь структурой RALCodeDescriptorType?, указывающей на ресурсы из базы программы, осуществляет загрузку модуля. В качестве блоков данных и кода используются параметры codeDescriptorP? и dataSegmentP?. Функция возвращает указатель на точку входа и идентификатор модуля.

    Функция RALUnloadModule? выгружает модуль, заполняет структуру RALCodeDescriptorType? и возвращает указатель на блок данных для освобождения ресурсов.

    Функция RALPatchEntry? патчит обработчик функции в модуле. Работает только для модулей dal, boot, ui.

    RALSetA5?, RALUnloadNext? - ?

    Структура базы программы

    Армовая программа имеет тот же тип, что и классическая программа - 'appl'. Разные типы программ различает функция SysAppLaunch?, которая проверяет типы ресурсов. Аромвая программа обычно содержит три ресурса: amdc#0, amdi#0 и amdd#0.

    amdi#0 - это аналог code#0, правда из всех данных оттуда используется слово по смещению 4. Там лежит размер области данных выше SB (по умолчанию 0x10 - размер области указателей на SB) . Размер области ниже SB фиксирован и равен размеру структуры PreStaticBase? ( 0x28 байт).

    amdd#0 - традиционно компрессированный образ блока данных. Он разжимается в область после SB.

    amdc#0 - это ресурс с кодом данных. Он начинается с заголовка описанного выше. Обычно этот ресурс не копируется в память, то есть он исполняется из storage или dbcache.

    В армовой модели программ не предусмотрено никаких fixupов кода. Размер ресурса кода неограничен. Кстати из-за этого может вылезти проблема общения нативного приложения с hotsync, которые не переваривает ресурсов > 64К.

    Самое забавное, что типы и индексы ресурсов не фиксированы. У функции SysLoadModule? можно указать тип ресурса, отличный от amdc. При загрузке библиотеки через Sys Linker Stub вообще можно задать типы и индексы всез трех ресурсов в структуре SysModuleDescriptorType?. Так, системный модуль boot.prc почему-то предпочитает тип boot.

    Функции из ядра PalmOS

    Что же делают функции высокого уровня? Они оборачивают низкоуровневые функции, занимаясь грязной работой типа выделения памяти, поиска и открытия баз.

    typedef struct {
            UInt32                dbType;
            UInt32                dbCreator;
            UInt32                revision;
            UInt32                entries;
            UInt32                rsrcTypeData0;
            UInt32                rsrcTypeCode0;
            UInt32                rsrcTypeCode1;
            UInt16                rsrcIDData0;
            UInt16                rsrcIDCode0;
            UInt16                rsrcIDCode1;
            UInt16                reserved;
    } SysModuleDescriptorType;
    
    
    Err SysFindModule(UInt32 dbType, UInt32 dbCreator, UInt16 rsrcID, UInt32 flags, UInt32 *refNumP);
    Err SysPatchEntry(UInt32 refNum, UInt32 entryNum, void *procP, void **oldProcP);
    Err SysGetEntryAddresses(UInt32 refNum, UInt32 entryNumStart, UInt32 entryNumEnd, void **addressP);
    Err SysLoadModule(UInt32 dbType, UInt32 dbCreator, UInt16 rsrcID, UInt32 flags, UInt32 *refNumP);
    Err SysUnloadModule(UInt32 refNum);
    void SysLinkerStub(SysModuleDescriptorType *moduleP, UInt32 clientID, void **dispatchTablePP);
    

    Функции SysFindModule? и SysLoadModule? загружают модули. Отличие между функциями неизвестно. Функции открывают базы, лочат кодовые ресурсы, подсчитывают размер, выделяют область данных и вызывают RALLoadModule?

    Функция SysUnloadModule? выгружает модуль. RALUnloadModule? отвечает за низкий уровень, а SysUnloadModule? чистит память и закрывает базу.

    Функции SysPatchEntry? и SysGetEntryAddresses? - это аналоги соответствующих функций из RAL. Интересно, что у Zodiac вызов SysPatchEntry? был запрещен, в отличие от RALPatchEntry?.

    Функция Sys Linker Stub ( по ссылке можно найти документированный код, использующий эту функцию) вызывает RALLoadModule? по информации из SysModuleDescriptorType? и далее вызывает RALLinkClient?.

    P.S. Прототипы функций частично взяты из mobilestream sdk.

    Palm Notes | Recent Changes | Title Index | User Preferences | Random Page | Help
    Edit this page | View other revisions
    Print this page | View XML
    Find page by browsing, searching or an index
    Edited February 28, 2007 (diff)
    Valid XHTML 1.0!Valid CSS!