Введение
Данная статья носит скорее просто познавательный характер, нежели практический, хотя знать то, о чем пойдет речь в этой статье желательно всем, кто намерен работать с QNX всерьез и надолго.
Стандарт исполняемых файлов Executable and Linking Format (ELF) был изначально разработан и опубликован UNIX System Laboratories (USL) как часть Application Binary Interface (ABI). После этого Tool Interface Standards (TIS) комитет выбрал этот стандарт для продвижения ELF в качестве формата для переносимых объектных файлов, которые могут работать на всех 32-ух битовых x86 процессорах фирмы Intel под любой операционной системой. Но это не значит, что ELF формат исполняемых файлов приемлем только для x86 архитектуры, просто для x86 архитектуры он стал de facto стандартом, а с самого начала он применялся и использовался для процессоров AT&T WE 32100. Потом Sun взяла его за основу исполняемых файлов под SunOS для своих станций и серверов на базе Sparc процессоров. А уж затем этот стандарт вовсю начал использоваться для Intel x86 архитектуры.
Стандартный исполняемый файл ELF формата содержит множество сегментов, например такие как: .text - сегмент кода, .data - сегмент инициализированных данных (обычно только для чтения, сюда попадают строки, константные массивы и т.п.), .bss - сегмент неинициализированных данных программы, .dynamic - сегмент служебных данных о подгружаемых библиотеках во время запуска программы, .comment - информация о версиях, .init - код инициализации процесса, .fini - код окончания выполнения процесса и т.д. Этих сегментов большое множество, но в основном они все стандартные и несут в себе информацию, необходимую для правильной загрузки исполняемого файла в память и требуемых им библиотек. Также есть сегменты, отвечающие за хранение отладочной информации (.debug) и прочих опциональных данных (.line, .p_paddr), которые не являются критическими для загрузки и выполнения исполняемого файла и могут быть безопасно удалены.
Стандарт оговаривает, что могут быть произвольные дополнительные сегменты в ELF файлах, которые каждая операционная система может использовать для своих служебных нужд, но с одним условием - эта информация не должна влиять на переносимость бинарного кода между операционными системами на базе одной архитектуры процессоров. Т.е. производителям операционных систем было не рекомендовано использовать дополнительные сегменты в качестве несущих основную информацию об исполняемых файлах.
Теперь мы вплотную подошли к вопросу: "Каким же образом QSSL использует эту возможность у себя в операционной системе QNX 6.x ?". Но … прежде чем начать это обсуждение, я хочу уделить еще немного вашего внимания компилятору GNU C/C++, а именно его языковым расширениям, благодаря которым мы можем управлять созданием дополнительных сегментов даже на этапе компиляции.
Создание дополнительных сегментов с помощью языковых расширений компилятора GNU C/C++
Для этих целей используется модификатор __attribute__ который может задавать условия сохранения структур, данных и т.п. в исполняемом файле. Модификатору __attribute__ передается в параметр сам атрибут, в нашем случае атрибут называется section, который принимает в параметр имя секции (сегмента). Имена сегментов принято писать с ведущей точки, например .data, .text и т.п., но далеко не все этого правила придерживаются. Сейчас я попробую привести в качестве примера строку, которая будет расположена в сегменте ".testseg" после линковки программы:
const char testarray[] __attribute__((section(".testseg"))) = "This is a test string !";
В этом примере хорошо видно, что это простое объявление открытого массива testchar, состоящего из элементов типа char. Точно таким же образом мы можем указать для всех нам необходимых данных, что они будут находиться в том сегменте, который мы хотим.
С атрибутом section разобрались, теперь рассмотрим еще несколько атрибутов, которые нам пригодятся в дальнейшем. Это атрибуты packed и aligned. Первый используется при необходимости объявить структуру без выравниваний мелких типов (char, short) на 4 байта (в QNX 6.2 уже по умолчанию стоит 8 байт выравнивания для некоторых системных вещей и для внутренних структур, используемых в Photon). Многие используют этот атрибут в виде записи #pragma pack(1) - для паковки структур. Эту запись использовать, на мой взгляд, будет правильнее, но мы уже используем атрибуты в программе, поэтому запись через #pragma будет особенно бросаться в глаза и для паковки структур мы будем тоже использовать атрибут.
struct str
{
char a;
char b;
} __attribute__ (( packed ));
Второй атрибут - aligned - используется для выравнивания данных на определенную границу, к примеру все процессоры Intel x86, начиная с первых MMX обращаясь к данным, которые выравнены на границу 32 байта имели превосходство по скорости доступа по сравнению с обращением к невыравненным данным. Этот атрибут будет также нам полезен далее.
struct str test __attribute__((aligned(16))) = {.a=5, .b=6};
Как работает команда use при показе помощи
Почти все, кто работают с QNX наверное очень много раз набирали команду use <имя файла> и просматривали помощь по программам. Некоторые пошли дальше и наверняка знают, как добавить эту помощь в свою программу, выводимую по команде use. Для этого существет утилита usemsg. Теперь давайте рассмотрим по-подробнее как она работает.
Эта утилита требует в командной строке исполняемый файл и просто текстовое сообщение, которое будет вставлено в исполняемый файл. Другой вариант - вместо текстового сообщения будет требоваться файл исходного текста, который содержит строки: #ifdef __USAGE / #endif /* __USAGE */ и между ними текстовое сообщение, которое будет изъято и помещено в бинарный исполняемый файл.
Теперь давайте рассмотрим, как же утилита usemsg добавляет текстовое сообщение в исполняемый файл и как команда use знает, где искать это текстовое сообщение.
На самом деле usemsg является всего лишь враппером (обверткой) для другой утилиты - ldrel, которая и выполняет всю работу. Утилита ldrel предназначена как раз для того, чтобы добавлять и изменять определенные сегменты исполняемых ELF файлов. После нехитрых манипуляций с исполняемыми файлами до и после добавления текстовых сообщений помощи, мы легко можем обнаружить, что в исполняемом файле после добавления помощи появился новый сегмент QNX_usage. Надо заметить что имя сегмента без ведущей точки в начале имени. Содержимое этого сегмента - один в один содержимое нашего файла с текстовым сообщением помощи - не больше и не меньше.
С использованием помощи в QNX 6.x есть некоторые проблемы. Рассмотрим одну из них: например, вам необходимо еще и выдавать помощь по ключу --help. Для того, чтобы это сделать необходимо дублировать текстовое сообщение о помощи как внутри программы, так и в сегменте QNX_usage. Это неудобно, но к сожалению неизбежно. В QNX 4.x была библиотечная функция print_usage(void), которая помогала решить эту проблему. Эта функция выполняла действия аналогичные тем, которые делает команда use при показе сообщений, таким образом не было необходимости в дублировании текстов помощи. В документации к библиотеке миграции с QNX 4.x к QNX 6.x mig4nto про эту функцию сказано коротко и ясно: no longer supported, т.е. ее больше нет и не будет. Эту функцию можно написать самому, вот один из ее вариантов. Надо сразу оговориться, что это не полный вариант этой функции, а всего лишь рабочий прототип, который достает информацию из сегмента QNX_usage и просто выводит ее в stderr поток.
/* Code by Colin Burgess , */
/* first published: 4 Dec 2000. */
#ifdef __USAGE
%C - test print_usage
#endif /* __USAGE */
#include
#include
#include
#include
#include
#include
#include
#include
void print_usage(void)
{
Elf32_Ehdr ehdr;
Elf32_Shdr *shdr = NULL;
char *usage = NULL, *shstrtab = NULL;
int fd, i;
/* get a file descriptor to our executable */
if (-1 == (fd = _cmdfd()))
{
return;
}
/* read Elf Header */
if (sizeof(ehdr) != read(fd, &ehdr, sizeof(ehdr)))
{
return;
}
/* Check ELF magic */
if (ehdr.e_ident[0] != 0x7f ||
ehdr.e_ident[1] != 'E' ||
ehdr.e_ident[2] != 'L' ||
ehdr.e_ident[3] != 'F')
{
return;
}
/* read section table */
if ((shdr = malloc(ehdr.e_shentsize * ehdr.e_shnum)) == NULL)
{
goto end;
}
if (-1 == lseek(fd, ehdr.e_shoff, SEEK_SET))
{
goto end;
}
if (ehdr.e_shentsize * ehdr.e_shnum != read(fd, shdr, ehdr.e_shentsize *
ehdr.e_shnum))
{
goto end;
}
/* read string index */
if ((shstrtab = malloc( shdr[ehdr.e_shstrndx].sh_size)) == NULL)
{
goto end;
}
if (-1 == lseek(fd, shdr[ehdr.e_shstrndx].sh_offset, SEEK_SET))
{
goto end;
}
if (shdr[ehdr.e_shstrndx].sh_size != read(fd, shstrtab,
shdr[ehdr.e_shstrndx].sh_size))
{
goto end;
}
/* look through each section until we find the "QNX_usage" section */
for (i = 1; i < ehdr.e_shnum; i++)
{
if (!strcmp(&shstrtab[shdr[i].sh_name], "QNX_usage"))
{
break;
}
}
if (i == ehdr.e_shnum)
{
goto end;
}
if ((usage = malloc(shdr[i].sh_size)) == NULL)
{
goto end;
}
if (-1 == lseek( fd, shdr[i].sh_offset, SEEK_SET ))
{
goto end;
}
if (shdr[i].sh_size != read(fd, usage, shdr[i].sh_size))
{
goto end;
}
write(STDERR_FILENO, usage, shdr[i].sh_size);
end:
if (shdr)
{
free(shdr);
}
if (shstrtab)
{
free(shstrtab);
}
if (usage)
{
free(usage);
}
}
Следует заметить, что данная функция не использует никаких внешних библиотек для работы с ELF файлами, а использует структуры ELF файлов, которые имеются в стандартной поставке QNX 6.x. Данную функцию можно также реализовать с помощью простого вызова команды use с параметром в виде имени нашего исполнямого файла. Т.е. решить эту проблему можно не одним способом. Кстати именно этот последний способ использовался в QNX 4.x, т.е. функция print_usage() просто вызывала команду use. В документации к этой функции, кстати, честно предупреждали о возможных проблемах с безопасностью в связи с этим способом.
Теперь вернемся к нашим сегментам. Ничто не мешает нам совсем отказаться от дополнительных трудностей с автоматизацией добавления сегмента QNX_usage в наши программы. Можно поступить, к примеру, вот так:
const char usage[] __attribute__((section("QNX_usage"))) = "%C - test print_usage";
Можно пойти дальше. Вы когда нибудь видели цветную помощь ? ;) Теперь увидите. Ниже приведен текст файла заголовка usgcol.h:
/* Code by Mike Gorchak */
#ifndef __USGCOL_H__
#define __USGCOL_H__
#define USG_BLACK "\x1B""[22;30m"
#define USG_BLUE "\x1B""[22;34m"
#define USG_GREEN "\x1B""[22;32m"
#define USG_CYAN "\x1B""[22;36m"
#define USG_RED "\x1B""[22;31m"
#define USG_MAGENTA "\x1B""[22;35m"
#define USG_BROWN "\x1B""[22;33m"
#define USG_LIGHTGRAY "\x1B""[22;37m"
#define USG_DARKGRAY "\x1B""[1;30m"
#define USG_LIGHTBLUE "\x1B""[1;34m"
#define USG_LIGHTGREEN "\x1B""[1;32m"
#define USG_LIGHTCYAN "\x1B""[1;36m"
#define USG_LIGHTRED "\x1B""[1;31m"
#define USG_PINK "\x1B""[1;35m"
#define USG_YELLOW "\x1B""[1;33m"
#define USG_WHITE "\x1B""[1;37m"
#endif // __USGCOL_H__
Это всего лишь строки с ANSI ESC-последовательностями для переключения текущего цвета выводимой информации. Подробнее о других ESC-последовательностях можно прочитать в помощи по драйверу консоли devc-con.
А вот это собственно текст программки usesection.c, которая ничего не делает, но из нее можно извлечь цветную помощь по команде use:
#include
#include "usgcol.h"
const char usage[] __attribute__((section("QNX_usage"))) =
"\n%C - Application for testing usage colors:\n\n"
" Dark colors: \n"
" " USG_BLUE "Blue\n"
" " USG_GREEN "Green\n"
" " USG_CYAN "Cyan\n"
" " USG_RED "Red\n"
" " USG_MAGENTA "Magenta\n"
" " USG_BROWN "Brown\n"
" " USG_LIGHTGRAY "Light Gray\n"
"\n"
" Light colors: \n"
" " USG_DARKGRAY "Dark Gray\n"
" " USG_LIGHTBLUE "Light Blue\n"
" " USG_LIGHTGREEN "Light Green\n"
" " USG_LIGHTCYAN "Light Cyan\n"
" " USG_LIGHTRED "Light Red\n"
" " USG_PINK "Pink\n"
" " USG_YELLOW "Yellow\n"
" " USG_WHITE "White\n"
USG_LIGHTGRAY "\n"; // return to default colors
int main()
{
return 0;
}
Попробовали ? :) Ну как смотрится ? К сожалению и тут не совсем все гладко - у команды use существует ограничение на длину выводимой строки равной размеру ширины экрана и, к сожалению, ANSI ESC-последовательности воспринимаются как самостоятельные символы, поэтому каждое переключение цвета забирает у нас 7 символов из строки.
Но на этом использование дополнительных сегментов в QNX 6.x совсем не ограничивается, существуют еще несколько их применений
Создание сегмента с информацией типа .pinfo
Многие наверное замечали такие странные файлы с расширением .pinfo, теперь поговорим об их использовании и необходимости в QNX 6.x. Вот пример файла .pinfo для драйвера звуковой платы:
STATE=Experimental
INSTALLDIR=x86/lib/dll/
INSTALLNAME=deva-ctrl-cmpci.so
NAME=deva-ctrl-cmpci.so
USER=root
HOST=deflektor
DATE=2002/08/21-17:55:01EEST
DESCRIPTION=CMI8738 audio driver
Эти файлы описывают данные, которые необходимы при создании своих инсталляционных пакетов. Подробнее о применении этих типов файлов можно почитать в статье, посвященной созданию своих пакетов, ссылка на которую указана в конце данной статьи в разделе рекомендуемой литературы и источников. Эта информация также может быть помещена в исполняемый файл, только в другой сегмент - QNX_info. Занести эту информацию внутрь исполняемого файла также можно с помощь usemsg, а прочитать с помощью команды use -i <имя файла>.
Содержимое этого сегмента также можно внести сразу в код, единственное, что два поля будет невозможно заполнить сразу, это HOST и USER, но эта информация вкладывается в этот сегмент не из надобности а скорее всего ради функции контроля за бинарными файлами, чтобы впоследствии можно было установить где и кем был скомпилирован файл. Эти два поля можно с чистой совестью опустить, либо выполнить их заполнение с помощью использования Makefile, приблизительно следующим образом (в этот образей Makefile включено также заполнение поля DATE, т.к. оно имеет немного нестандартный формат, который можно подготовить с помощью команды date, но нельзя выполнить с помощью стандартных определений __DATE__ и __TIME__):
COMPILEFLAGS = -O2 -Wall -I.
DATE_HOST = `/usr/bin/date +%Y/%m/%d-%T%Z`
USER_HOST = `/usr/bin/id -un`
HOST_HOST = `/bin/hostname`
CFLAG = $(COMPILEFLAGS) -DDATE_HOST=\"$(DATE_HOST)\" \
-DUSER_HOST=\"$(USER_HOST)\" -DHOST_HOST=\"$(HOST_HOST)\"
LDFLAGS =
OBJS = main.o
NAME = main
all: $(NAME)
clean:
rm -f *.o $(NAME)
$(NAME): $(OBJS)
$(CC) $(LDFLAGS) -o $(NAME) $(OBJS) -L.
strip $(NAME)
Если использовать подобный Makefile, то наш код для заполнения сегмента QNX_info будет выглядеть следующим образом:
const char info[] __attribute__((section("QNX_info"))) =
"STATE=Experimental\n"
"INSTALLDIR=x86/usr/bin/\n"
"INSTALLNAME=sectionstest\n"
"NAME=sections\n"
"USER="USER_HOST"\n"
"HOST="HOST_HOST"\n"
"DATE="DATE_HOST"\n"
"DESCRIPTION=Just a segments test program.\n";
В QNX также принято использовать механизм рекурсивных Makefile, схема которого была заимствована из xBSD систем. Суть ее в следующем: наш Makefile состоит всего лишь из единой строчки include xxx.mk (они находятся в /usr/include/mk) далее этот Makefile подгружает следующие .mk файлы, в которых описаны правила а затем компилирует все исходные файлы в нашем каталоге. Перед включением главного .mk файла мы может задать политику компиляции проекта. Это сделано так потому, что большинство Makefile содержат одинаковый код, который постоянно дублируется, использование рекурсивных make файлов позволяет уменьшить время написания таких Makefile, но зато необходимо дополнительное время на изучение всех возможностей этого механизма. Так вот, при использовании механизма рекурсивных make файлов, они сами генерируют файл с расширением <имя проекта>.pinfo, который далее можно использовать по назначению.
Создание сегмента с PhAB ресурсом
Многие, наверное, пробовали писать программы под Photon без помощи PhAB и в этом нет ничего сложного - можно делать все то же, что и в PhAB за одним исключением: такая приятная мелочь как иконка вашего приложения будет полностью отсутствовать как объект в вашей программе. К великому сожалению в API Photon'а отсутствуют механизмы задания иконки приложению, хотя бы для уже выполняющейся программы, я не говорю уже про помещение программы в Shelf или в меню запуска приложений. Тут мы обречены лицезреть иконку по умолчанию, которую любезно предоставил нам фотон для приложений без иконки. Есть еще одна не очень приятная особенность: иконки будут отображаться только в том случае, если исполняемый файл доступен по путям поиска переменных окружения.
Давайте разберемся как задается иконка для приложения написанного с использованием Photon Application Builder (PhAB). Вы создаете ресурс иконки PtIcon (PtIcon - это именно ресурс PhAB, а не фотона), имя ее должно быть только "Icon" - именно такое, и с большой буквы. В этом ресурсе есть две картинки "SIcon" и "LIcon" название этих ресурсов должно быть именно такое. "SIcon" - это маленькая иконка (15x15 пикселей) и "LIcon" - это, соответсвенно, большая иконка (43x43 пикселей). После сохранения этого ресурса, в папке проекта можно найти файл "Icon.wgti" - это и есть наш ресурс иконки, с которым мы и будет в дальнейшем работать. Не сложно заметить, что при компиляции проекта в PhAB, в конце запускается скрипт phappbind, который в свою очередь запускает файл bindres, а тот ldrel - вот такая несложная цепочка. Всю работу по изменению и модификации ресурса делает bindres. Например, нам надо добавить иконку к нашему проекту, для этого надо написать "bindres myprg Icon.wgti", после этого будет выведена надпись "Added Icon.wgti". При добавлении ресурса к бинарному файлу, он обрамляется еще и сопроводительной информацией, которую Shelf прочтет и на базе нее извлечет из исполняемого файла ресурс с иконкой. Все ресурсы PhAB добавляются в сегмент QNX_PhAB. Проанализировав готовый сегмент QNX_PhAB, можно восстановить его структуру:
unsigned long rescount;
Количество ресурсов, находящихся в одном сегменте (rescount).
unsigned char resname[48+4];
unsigned long resdisp;
unsigned long ressize;
unsigned long resmodtime;
Заголовок ресурса, повторяется столько раз, сколько имеется ресурсов, а именно столько, сколько находится в переменной rescount. resname - Имя файла, из которого был извлечен ресурс, например "Icon.wgti". resdisp - смещение относительно начала сегмента, где начинается данный ресурс. ressize - размер ресурса в байтах. resmodtime - timestamp последней модификации файла ресурса.
После этого идут в неизменном виде все подключаемые ресурсы, один за другим, разделенные терминаторами:
unsigned char terminator1;
unsigned char terminator2;
Где terminator1 - это символ 0x30 ('0'), а terminator2 - это значение 0x0A (LF), которого может не быть - он опционален.
Сегмент заканчивается таким образом:
unsigned char terminator;
Где terminator - это код признака конца строки с кодом 0x00.
Таким образом, зная структуру хранения сегмента QNX_PhAB, мы можем включить ресурс иконки прямо в исходный текст программы. Для этих целей была написана маленькая программа icongen, которая переводит ресурсы в формат сегмента QNX_PhAB, она находиться в архиве с исходными текстами, прилагаемом к статье. Запускаем программу ./icongen <имя файла ресурса.wgti> <имя файла исходного текста.c> [<имя файла образа.bin>]. Параметр <имя файла образа.bin> не обязателен, в файл с данным именем будет записано содержимое сегмента QNX_PhAB в бинарном виде, для последующего его использования с помощью утилиты ldrel. Вот образец преобразования ресурса в исходный текст на языке C:
/* This file was autogenerated by IconGen v1.0 */
struct PhABResource
{
unsigned long rescount;
unsigned char resname[48+4];
unsigned long resdisp;
unsigned long ressize;
unsigned long resmodtime;
unsigned char icondata[4441];
} icon __attribute__((section("QNX_PhAB"))) =
{
.rescount=1L,
.resname="Icon.wgti",
.resdisp=0x44L,
.ressize=4439,
.resmodtime=0x3D79ECD3L,
.icondata=
{
0x50, 0x68, 0x41, 0x42, 0x32, 0x30, 0x31, 0x2E, 0x30, 0x31, 0x0A, 0x50,
. . .
0x30, 0x0A, 0x6C, 0x6F, 0x63, 0x6B, 0x0A, 0x30, 0x0A, 0x30, 0x0A, 0x30,
0x00
}
};
Для простоты исполнения терминаторы ресурса и сегмента были включены в образ ресурса icondata, поэтому его размер на два байта больше, чем реальный размер ресурса, указываемый в поле ressize. Теперь этот файл с исходным текстом необходимо просто включить в ваш проект, а после его сборки исполняемый файл будет содержать в себе сегмент ресурсов QNX_PhAB, информация из которого будет доступна Shelf, который отобразит иконки, содержащиеся в данной программе. В архиве с исходными текстами также прилагается тестовый ресурс Icon.wgti, который содержит в себе две иконки размерами 15x15 и 43x43 с логотипом QNX Momentics®. Размер этого ресурса несколько великоват из-за того, что были использованы картинки логотипов с 32-х битовым цветом.
К сожалению идея использования иконок в абсолютно консольных приложениях, запускаемых под Photon'ом, потерпела крах. Shelf не хочет использовать ресурсы исполняемых файлов до тех пор, пока не будет проинициализирована библиотека виджетов с помощью PtInit() и не будет создано окно. Таким образом мы имеем возможность встраивать ресурсы только в приложения, использующие Photon.
Использование сегментов для других целей
На этом возможность использования дополнительных сегментов в исполняемых файлах ELF формата не ограничивается. Их можно использовать для хранения любых произвольных данных, необходимых вам в работе. К сожалению во время написания данной статьи выяснился не очень приятный момент в реализации поддержки исполняемых ELF файлов в QNX 6.x. При запуске приложения, содержащего в себе любые дополнительные сегменты, они грузятся в память все и сразу. К примеру в Linux и FreeBSD реализована возможность подкачки только тех страниц памяти сегментов, к которым производится обращение. Если обращения к данным сегмента не было, то сегмент не занимает оперативную память, а у программиста есть две возможности достать данные сегмента, либо обратившись к данным, либо, открыв исполняемый файл, достать от туда только необходимые данные. В QNX 6.x это ограничение скорей всего связано с отсутствием нормального менеджера памяти для виртуальных страниц, вследствие чего и не реализован нормально механизм файла подкачки. Таким образом в QNX 6.x использование больших дополнительных сегментов не является целесообразным, потому как возникает дублирование данных в памяти, либо все данные загружаются сразу, в независимости от их полезности. Остается только возможность использования небольших сегментов.
Самой привлекательной возможностью в QNX 6.x использование дополнительных сегментов является гибкий механизм plugin'ов в виде динамических библиотек. В дополнительных сегментах динамических библиотек можно хранить информацию об интерфейсах библиотек, их предназначении и т.п. информацию.
Список рекомендуемой литературы и источников
1. ELF (Executable and Linkable Format) Portable Formats Specification V1.1 by Tool Interface Standards (TIS). (Acrobat, PostScript - .ps) [for Intel processors] http://www.wotsit.org/download.asp?f=elf11g .
2. ELF (Executable and Linkable Format) Portable Formats Specification V1.1 by Tool Interface Standards (TIS). (Acrobat, .pdf формат). Dr. Dobb's Microprocessor Resources: http://x86.ddj.com/ftp/manuals/tools/elf.pdf .
3. Michael L. Haungs. The Construction of a Family of Simulators for the Intel Architecture with ELF Binary Input. Chapter: "The Executable and Linkable Format (ELF)" http://www.cs.ucdavis.edu/~haungs/paper/node10.html .
4. Документация QNX 6.2 по утилите usemsg.
5. Документация QNX 6.2 по утилите ldrel.
6. Jerry Chappell. QNX Package Generation (QPG) file structure (фрагмент статьи Packaging your QNX-based product http://www.qnx.com/developer/articles/sep1401/). http://www.qnx.com/developer/articles/sep1401/QPGtags.htm.
Все исходные тексты из этой статьи доступны в архиве files.tar.gz |