OpenWiki

Writing Armlets With Gcc

Palm Notes | Recent Changes | Title Index | User Preferences | Random Page | Help
Difference from prior major revision.
minor diff author diff hide diff

<TableOfContents/>
  
  
=== Введение ===
Статья 
описывает 
специфику 
программирования армлетов с помощью GCC. Предполагается, что читатель знаком с пальмовой документацией по армлетам и функции ~PceNativeCall
  
=== Теория ===
Когда 
говорят о 
достижениях 
компьютерной мысли 5-60 годов, то, вспоминая про компиляторы и линкеры, часто забывают про еще один модуль: загрузчик. Загрузчик (loader) - это модуль, который преобразовывает программу на внешнем носителе в блок памяти с исполняемыми кодами, готовый к исполнению. 
  
В функции 
загрузчика 
входят:
  * Выделение 
памяти для 
программного кода и копирование его с внешнего носителя
  * Выделение 
памяти для 
инициализированных данных и инициализация (int x = 3;)
  * Выделение 
памяти для 
неинициализированных данных (int y);
  * Выделение 
памяти под 
стек.
  * Коррекция 
адресов 
памяти
  
Я вспомнил 
про 
загрузчики 
по одной 
простой 
причине: 
компания PalmSource 
совершенно 
не описала в 
каком виде и 
как хранить 
армлет. Таким 
образом 
задачи 
загрузчика 
перекладываются на программиста.
  
=== Gcc, 
компилирующий в ARM-код ===
  
Использовать Gcc для компиляции армлетов выглядит вполне логично, ведь к анонсированию технологии армлетов уже существовал кодогенератор Gcc в ARM-код. Попробуем понять, насколько он пригоден.
  
Gcc - компилятор, 
который 
достаточно 
сильно 
привязан к 
юниксоподобным ОС. И любимый файл, получающийся после сборки линкером - это файл формата ELF. Такие файлы содержат внутри себя именованные сегменты (секции) с кодом и данными. Секция с кодом традиционно называется .text . Сразу возникает мысль - скомпилировать программу в ELF и извлечь оттуда эту секцию. Секцию поместить в ресурс и в нужное время залочить ресурс и передать на него управление.
  
Пример 
вызова:
<code>
UInt32 CallARM(UInt32 id, void *param){
        MemHandle h;
        UInt32 res;
        void *p;
        h = DmGet1Resource('armc', id);
        p = MemHandleLock(h);
        res = PceNativeCall(p, param);
        MemHandleUnlock(h);
        DmReleaseResource(h);
        return res;
}
</code>
  
  
Для самых 
простых 
случаев 
такой метод 
годится. 
Секцию можно 
извлечть с 
помощью 
утилиты obj-copy.
  
======= Опции
компилятора
для простых
армлетов === ====
  
Для самых 
простых 
армлетов 
достаточно 
трех 
специфических опции gcc:
  
  * -fPIC - 
генерировать позиционно-независимый код (position independent code). Эту опцию нужно включать для того, чтобы код мог работать с любого адреса.
  * -ffixed-r9 - PalmOS 
использует 
регистр R9 для 
системных 
данных. Опция 
запрещает 
компилятору 
использовать регистр.
  * -nostartfiles - Опция 
указывает 
линкеру, что 
startup-библиотека 
не нужна.
  
  
=== 
Позиционно-независимый
код ===
  
Очень быстро 
мы упираемся 
в то, что 
такой 
технологии 
недостаточно. Почему?
  
  * Нельзя 
использовать глобальные данные.
  * Не работает 
получение 
адресов 
функций (<code>void (*pfn)(int) 
= foo;</code>).
  * не работают 
константные 
строки (<code>strcpy(sz, 
"test");</code>).
  
  
Попытки 
использовать все это приводит к краху программы.
  
В чем 
проблема? 
  
  * Во-первых мы 
не берем 
глобальные 
данные из 
секции .data
  * Во-вторых 
даже если мы 
их возьмем, 
то мы не 
знаем как их 
подсунуть 
бинарному 
коду
  * В-третьих 
компилятор и 
линкер в 
принципе не 
может знать с 
какого 
адреса будет 
загружена 
наша 
программа и 
данные. А без 
знания этого 
он не может 
вычислить 
адрес 
функции или 
строки.
  
Как решают 
проблему с 
неизвестным 
адресом 
загрузки в 
других ОС? 
Обычно 
программа 
линкуется 
как 
начинающаяся с адреса 0, а загрузчик прибавляет реальный стартовый адрес ко всем ссылкам, которые требуют настоящего адреса. Такие ссылки называются relocation или fixup, а создаваемый список носит имя relocation list.
  
Поскольку у
нас
юниксовый GCC,
то 
перемещаемый
код 
ассоциируется
прежде всего
с 
динамическими 
библиотеками
(shared libraries). И
технология 
используется 
соответствующая
- GOT (global offset table).
Технология 
использования
GOT такая: все
секции
программы 
рассматриваются
как один
монолитный
блок памяти,
загрузчик
проходит по
секции .got,
считывает
4-байтовые
слова .got и по
указанным в
словах
смещениям 
относительно
начала блока
прибавляет
ко всем
4-байтовым
словам
стартовый
адрес.
Поскольку
блок
монолитный,
то все виды
ссылок (
код-код,
код-данные,
данные-код и 
данные-данные) 
корректируются
такой
операцией.
Все
фрагменты
кода, которые
требуют
абсолютного
адреса
просто
считывают
его из GOT.
  
Пример:
<code>
Example:
===
void foo(void);
  
  
void bar(void){
        void *p;
        p = &foo;        
}
  
void foo(void){
}
===
bar:
        LDR     R3, =0                @ load index of
загрузить в R3
индекс
ссылки на foo in GOT
        LDR     R3, [R10,R3]        @ load foo address
загрузить в R3
адрес
функции. R10 -
указатель на
GOT
        STR     R3, [R11,#-0x10]        @ store &foo in local variable
[R11,#-0x10]@ запомнить
адрес в
локальной
переменной. R11 -
указатель на
фрейм
локальных
переменных
  
.got:
        DCD foo-base_address                @ initially        @
слово из GOT со
смещением foo offset from
program base address
  
  
Simple program with empty GOT table.
  
It's the simplest case. '.text' section  of ELF program should be copied to
application database as separate resource. In run time just lock this resource
and pass resource pointer to PceNativeCall. Specific compiler options:
  
  
How to avoid GOT table generation
If your program uses function pointers or const global data, then you have two
choices: handle GOT table or remove code, which require GOT table. Common hints:
- Remove global variables. If you still need them, you can merge all global
variables into structure and point it with register. Don't forget to disable all
other access to this register for compiler with option  -ffixed-r8
</code>
  
=== Как
избежать
генерации GOT? ===
  
Ответ
простой: не 
использовать
то, что
генерирует
записи для GOT.
То есть не 
использовать
глобальных
данных,
указателей
на функции,
константных
строк итд.
Весело, да?
  
==== Вынос
глобальных
данных в
структуру ====
  
Если без
глобальных
данных не
получается,
то можно
поступить
следующим
образом:
собрать все
глобальные
данные и
вынести их в
структуру.
Все ссылки на 
псевдо-глобальные
данные
пойдут через
ссылку. Для
скорости
можно
указать, что
указатель
всегда
содержится в
регистре.
Стоит
запретить
использование
регистра 
компилятором
с помощью
опции -ffixed-r8.
  
Например,
<code>
// структура с
глобальными
данными
struct MyGlobals{
        int x;
        // ...
};
  
// Глобальная 
переменная!!! 
Компилятор 
вместо нее 
будет 
использовать регистр R8.
register struct MyGlobals *global asm ("r8");
  
// 
инициализация R8 в начале кода
// 
предполагается, что блок глобалных данных выделяет основной код и передает параметром.
unsigned long NativeFunc
                (const void *emulStateP, 
                char *userData68KP, 
                Call68KFuncType *call68KFuncP)
{
        asm("ldr r8,%0" : "=m" (userData68KP));
        //...
        // пример 
использования
        // int z = x + 3; 
        int z = global->x + 3; 
}
  
</code>
  
==== Безопасное
объявление
константных
строк ====
  
Я разработал
макрос CONST_TEXT,
который
объявляет
мини-функцию, 
возвращающую
указатель на
строку. В
отличии от
простого 
использования
константной
строки, мой
метод
позволяет
обойтись без
GOT.
  
<code>
// global
register struct MyGlobals *global asm ("r8");
  
определение
макроса
#define CONST_TEXT(name, str) \
static const char * const name(void){ \
asm("ADR R0, 1f; B 2f; 1: .ASCIZ \"" str "\"; .ALIGN
4; 2: ;"); \
}
  
// code
        int z
объявление
переменной
CONST_TEXT(getNote, "Enter custom note: ");
  
// 
использование
const char *const prompt = global->x + 3;
  
- Remove function pointers if possible. Sometimes you can move functions to
another arm resource and pass pointer from PceNativeCall
  
GOT handling
If you can't avoid getNote();
</code>
  
==== Как
избежать
указателей
на функции ====
  
Единственный
хороший
метод
избежать GOT generation, you can
handle it. Main idea: save - вынести
функции в
отдельные
ресурсы и
передавать
указатели на
них.
  
==== армлеты и C++ ====
  
Рецепт
простой: не
используйте
C++, если вы не
понимаете
как
реализованы
конструкции
этого языка.
Если вы не
можете
навскидку
сказать где
хранятся VMT, то
забудьте об 
использовании
плюсов в
армлетах.
  
=== Ручное
создание GOT ===
  
Если вы не
можете 
использовать
не GOT,
научитесь ее 
обрабатывать.
Это несложно.
Поместите
секцию .got section to separate resource and
make it available and fixed in runtime. GCC use following options for в
отдельный
ресурс,
склейте .text и .got
при
исполнении и
прибавьте
адрес блока
ко всем
записям в GOT.
Патченый build-prc
можно взять в
YAHM SDK по адресу
http://yahm.palmoid.com/yahmv.htm . Патч
позволяет 
автоматически
заносить .got
секцию в
получаемый
prc-файл
  
==== Опции,
связанные с GOT
handling:
        -mpic-register=r10  - register with pointer to  ====
  * -mpic-register=r10 - опция
указывает на
регистр, в
котором
хранится
указатель на
GOT. Обратите
внимание, что
разные
армлеты
могут иметь
разные GOT.
  * -msingle-pic-base - опция
говорит, что
установкой
указателя на
GOT table
        -msingle-pic-base - assumes that startup code load pic register with
GOT table address. If you don't use this option each GOT-used function starts
with loading pic-register like this:
                LDR
занимается startup.
Если опция не
указана, то
каждая
функция,
использующая
его 
устанавливает
этот регистр.
  
Вот какой
год будет 
генерироваться
при
отсутствии
-msingle-pic-base:
<code>
        LDR     R10, =_got_start - . - 8        @ load offset between current
instruction and  GOT table
                ADD 8@
загрузить в R10
разницу
между
адресом GOT и
текущим
адресом.
        ADD     R10, PC, R10                @ add current PC to offset and
obtain прибавить
значение
текущего
адреса
</code>
  
Код, который
нужно
исполнить
при старте
очевиден: <code>
asm("ldr r10,%0" : "=m" (gotPtr)); </code>.
  
==== Загрузчик GOT
address
  
This code assumes that  .got section follows .text section in runtime. Even
though -msingle-pic-base option used, GCC can generate code, based on this
assumption (http://www.escribe.com/computing/poaf/m911.html). To make such code
working you should copy merge .text and .got sections. One of possible ways is
copying .text into one resource and .got in another resource. Below is YAHM code
for fixing ====
  
Приведу
пример
загрузчика GOT
из [YAHMа http://yahm.palmoid.com].
Функция
принимает
указатель на
код и номер
ресурса с GOT. YAHM assumes that
На выходе мы
получаем
указатель на
новый 
откорректированный
блок памяти
или на старый
код, если GOT table can be found in
.got' resource with the same resource number. Note that GOT section should be
aligned on 4-byte bound.
===
отсутствует.
  
<code>
inline UInt32 RoundCeil4(UInt32 x){
        return ((x + 3) / 4) * 4;
}
void *FixupCode(void *codeInResource, UInt16 resNo, UInt32 *pGotPtr){
        MemHandle hGot;
        UInt32 *pFixups;
        void *pChunk;
        UInt32 codeSize, gotSize;
        int i;
  
        *pGotPtr = 0;
        hGot = DmGet1Resource('.got', resNo);
        if (hGot == NULL){
                // no fixup section
                return codeInResource;
        }
        codeSize = MemPtrSize(codeInResource);
        gotSize = MemHandleSize(hGot);
  
        pChunk = MemPtrNew(RoundCeil4(codeSize) + gotSize);
        if (pChunk == NULL) return NULL;
        pFixups = (UInt32 *)(pChunk + RoundCeil4(codeSize));
        *pGotPtr = (UInt32)pFixups;
        MemMove(pChunk, codeInResource, codeSize);
        MemMove(pFixups, MemHandleLock(hGot), gotSize);
        MemHandleUnlock(hGot);
        DmReleaseResource(hGot);
        for(i = 0; i < gotSize/sizeof(UInt32); ++i){
                UInt32 x = ByteSwap32(pFixups[i]) + (UInt32)pChunk;
                pFixups[i] = ByteSwap32(x);
        }
        return pChunk;        
}
===
Next problem is big .text section with size more than 64K. There are two
solutions for this problem. First is using of MemGluePtrNew function. This
function can allocate more than 64K in dynamic heap. Second solution is
FtrPtrNew. This function unconsciously can allocate big chunks in storage
memory. Don't forget to use DmWrite for write access to this memory. The better
way is smart combination of these solutions:
 * for TT1 with 1Mb of dynamic heap allocation with FtrPtrNew is preferable
 * NX-60 with 3mb of dynamic heap and 11Mb of storage the best method is
allocation with MemGluePtrNew
  
  
With resource copying we miss one important PalmOS feature: execution in
place.
  
Global data
  
With GOT support you can use initialized global data. You can place global
data into .text section with custom linker script. Use  -Xlinker</code>
  
==== Известные
ловушки .got ====
  
Ловушка #1:
компилятор
полагает, что
.got идет в
порядке,
указанном в
скрипте
линкера (то
есть после .text).
Наличие 
дополнительных
секций типа .disposn
может
сдвинуть GOT. В
этом случае
следует 
использовать
свой скрипт
для линкера.
Скрипт
задается
параметром -Xlinker
-T -Xlinker globalscript.ls options for passing linker script from GCC to LD.
  
===globalscript.ls myscript.ls.
<code>
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm",
               "elf32-littlearm")
OUTPUT_ARCH ("arm")
  
SEARCH_DIR("/usr/arm-palmos/lib");
  
/* This is a pathetically simple linker script that is only of use for
   building Palm OS 5 armlets, namely stand-alone code that has no global
   data or other complications.  */
  
SECTIONS
{
    .text :
    {
        *(.text .rodata)
        *(.glue_7t) *(.glue_7)
    }
        .got : {*(.got) }
        .got.plt : {*(.got.plt) }
    .disposn : { *(.disposn) }
    .data : {*(.data)}
    .bss : {*(.bss)}
}
</code>
  
Обратите 
внимание, что 
даже при 
наличии 
опции -msingle-pic-base GCC 
может 
адресоваться к GOT не по регистру, а через смещение от начала кода ( например, http://www.escribe.com/computing/poaf/m911.html ). Это разбивает в прах светлую идею выделять память только для GOT, с адресацией через R10.
  
  
Проблема #2: 
что делать, 
если объем 
секции .text 
больше 64K. 
Понятно, что 
большой 
кусок нужно 
распилить на 
несколько 
ресурсов и в 
процессе 
исполнения 
склеить. Для 
выделения 
большого 
блока можно 
использовать функцию MemGluePtrNew или выделить через FtrPtrNew. Первая функция выделяет в динамической памяти, а вторая в памяти баз (и записывать в нее нужно через DmWrite). Для выделения лучше использовать комбинированное решение:
  * Для TT с 1Мб 
кучи лучше 
записывать с 
помощью FtrPtrNew
  * Для NX-60 с 3Мб 
кучи запись с 
помощью MemGluePtrNew 
логичнее
  
Обратите 
внимание, что 
из-за 
необходимости склеивания мы потеряли преимущество PalmOS - исполнение кода без копирования.
  
  
=== Глобальные 
данные ===
  
Ниже 
приведен 
скрипт, 
позволяющий 
использовать инициализированные глобальные данные в армлетах. При этом генерируется GOT и получаемый образ должен быть в куче.
  
Используйте 
опции -Xlinker -T -Xlinker globalscript.ls 
для указания 
скрипта.
  
<code>
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm",
               "elf32-littlearm")
OUTPUT_ARCH ("arm")
  
SEARCH_DIR("/usr/arm-palmos/lib");
  
/* This is a pathetically simple linker script that is only of use for
   building Palm OS 5 armlets, namely stand-alone code that has no global
   data or other complications.  */
  
SECTIONS
{
    .text :
    {
        *(.text .rodata)
        *(.glue_7t) *(.glue_7)
        *(.data)
    }
        .got : {*(.got) }
        .got.plt : {*(.got.plt) }
    .disposn : { *(.disposn) }
    .data : {LONG(1)}
    .bss : {*(.bss)}
}
===
</code>
  
  

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 October 14, 2005 (hide diff)