Children Display all true depth 2
Компиляция загружаемого кода
...
Для компиляции загружаемого кода используются компилятор и линкер GCC, стандартная библиотека С для встраиваемых систем newlib, утилита make и несколько сервисных утилит.
Существует два варианта использования GCC:
- Загрузить исходный код GCC, make, newlib из соответствующих репозиториев и самостоятельно скомпилировать.
- Воспользоваться готовым комплектом инструментов для разработки под ARM.
Для работы с ключами Guardant Code рекомендуется использовать свободно распространяемый инструментарий YAGARTO. Все примеры из комплекта разработчика, тестировались именно на нем.
Чтобы приступить к работе с YAGARTO требуется выполнить несколько простых шагов:
- Загрузить набор утилит YAGARTO с сайта разработчика (http://www.yagarto.de/download/yagarto/yagarto-tools-20100703-setup.exe )
- Загрузить компилятор с сайта разработчика (http://www.yagarto.de/download/yagarto/yagarto-bu-2.21_gcc-4.6.0-c-c++_nl-1.19.0_gdb-7.2_eabi_20110429.html )
- Установить загруженные пакеты в директорию C:\YAGARTO
...
Все инструкции для компилятора и линкера, а также команды для обработки скомпилированного кода, содержатся в конфигурационном файле утилиты make (имя этого файла по умолчанию - makefile).
Соответственно, для компиляции приложения необходимо:
- Отредактировать настройки в секции Main Configuration внутри makefile:
- Задать желаемое имя точки входа, адреса RAM, ROM
- Задать требуемый размер стека
- Задать имена и размеры буферов ввода-вывода
- Задать путь к системным утилитам hex2bin.exe и map_parse.exe
- Задать путь к папке с заголовочными файлами GrdAPI.h и GcaAPI.h
- Задать путь к папке с файлом libgcaapi.lib
- Сгенерировать шаблон проекта: make template (для использования компиляторов, отличных от GCC, требуется ручная настройка проекта). Проект должен иметь определенный набор файлов и быть настроен для работы в среде Guardant Code
- Скопировать в папку проекта файлы с исходными текстами с модулями на C/C++ и добавить имена этих файлов в переменные makefile SRC и CPPSRC соответственно
- В скопированных модулях должна присутствовать функция с именем, совпадающим с именем заданной точки входа (и соответствующим прототипом)
- После этого можно выполнить команду: make и приложение будет собрано
- В папке проекта будет создана папка .out с двумя файлами, имеющими расширения bin и bmap. Эти файлы требуются для генерации GCEXE-файла и загрузки его в электронный ключ при помощи утилиты GrdUtil.exe
...
Приложение собирается при помощи GNU-утилиты make, которая использует конфигурационный файл с именем makefile.
Для утилиты make доступны следующие команды:
1. Сборка проекта
Если конфигурационный файл имеет имя по умолчанию (makefile):
make |
Если конфигурационный файл имеет имя, отличное от имени по умолчанию:
make –f confname |
2. Удаление всех файлов, создаваемых при сборке(файлы, создаваемые при make template не удаляются)
make clean |
или
make -f confname clean |
3. Создание шаблона приложения
make template |
или
make -f confname template |
...
Полная пересборка приложения
make clean |
или
make -f confname clean |
...
Универсальный makefile содержит секции настроек с параметрами:
- Генерации шаблона проекта командой make template
- Сборки приложения по команде make/make all
При внесении изменений в первую секцию требуется перегенерация шаблона проекта (см. соответствующий раздел). При внесении изменений во вторую секцию требуется пересборка проекта путем подачи команды make clean и затем make all.
Настройки секции генерации шаблона:
Имя параметра | Значение |
---|---|
CFG_ENTRYPOINT_NAME | Имя точки входа (по умолчанию функция main) |
CFG_PROGRAM_ADDR *) | Адрес Flash-памяти, по которому располагается приложение |
CFG_PROGRAM_SIZE *) | Размер приложения во Flash-памяти |
CFG_RAM_ADDR *) | Адрес начала RAM, резервированной для загружаемого кода |
CFG_RAM_SIZE *) | Размер RAM, зарезервированной для загружаемого кода |
CFG_INPUT_BUFFER_NAME | Имя буфера ввода, через который данные передаются в загружаемый код |
CFG_INPUT_BUFFER_SIZE | Размер буфера ввода |
CFG_OUTPUT_BUFFER_NAME | Имя буфера вывода, данные из которого возвращаются вызывающему приложению |
CFG_OUTPUT_BUFFER_SIZE | Размер буфера вывода |
CFG_STACK_SIZE | Размер программного стека |
CFG_INCLUDE_DIR | Путь до директории, содержащей заголовочные файлы GcaAPI.h и GrdAPI.h |
CFG_SYS_DIR | Путь до директории, содержащей служебные утилиты |
CFG_TARGET_NAME | Имя двоичного bin-файла, получаемого при компиляции |
...
Имя параметра | Значение |
OPT | Уровень оптимизации. Рекомендуемые значения 2 или s (так же допустимые значения 0 и 1, значение 3 крайне не рекомендуется) |
SRC | Набор С-файлов, используемых в проекте |
ASRC | Набор ASM-файлов, используемых в проекте |
...
|
...
|
...
При старте приложения, в самом начале начинает исполняться код, находящийся в файле Startup.S. Он инициализирует стек и C-окружение (предварительно инициализированные переменные) и обнуляет неинициализированные переменные и область стека, при необходимости вызывает конструкторы глобальных объектов C. После этого он передает управление в приложение на C. Этот файл генерируется автоматически из универсального makefile.
По умолчанию точка входа в C-приложении на GCC имеет стандартное имя main. Прототип, однако, отличается от стандартного ANSI C и имеет следующий вид:
int main(DWORD dwInDataLng, DWORD dwOutDataLng, DWORD dwP1); |
Где:
dwInDataLng – размер данных поступивших из PC,
dwOutDataLng – размер данных, который PC запрашивает назад,
dwP1 – параметр dwP1, переданный функции GrdCodeRun().
Если требуется изменить адрес точки входа, то в файле Startup.S требуется исправить строчки:
.global main |
...
В микроконтроллерах на основе ядра CORTEX-M3, на которых построен ключ Guardant Code, имеется единое адресное пространство в 4Гб. Для загружаемого кода доступны следующие диапазоны адресов:
Адреса | Назначение |
00020000h-0003FFFFh | Flash-память для размещения загружаемого кода и ROM-секции микропрограммы (для варианта с 128 кб Flash-памяти) |
00020000h-00077FFFh | Flash-память для размещения загружаемого кода и ROM-секции микропрограммы (для варианта с 352 кб Flash-памяти) |
40003000h-40007FDFh | RAM (ОЗУ), доступная загружаемому коду. Тут размещаются: стек, буфер ввода-вывода, переменные загружаемого кода |
...
Имена буферов ввода и вывода в makefile могут быть разными, а могут и совпадать.
В случае, когда они совпадают, выделяется один буфер, который работает одновременно и на ввод, и на вывод. В C-коде буфер ввода-вывода может быть объявлен так:
extern BYTE iodata[]; |
...
extern struct |
...
Размер программного стека для GCC указывается в makefile. За это отвечает параметр:
CFG_STACK_SIZE = 0x800; |
Т. е., размер стека по умолчанию равен 2кБ (0x800 байтам).
Поскольку размер RAM достаточно сильно ограничен, рекомендуется небольшие и простые, но часто используемые функции оформлять как inline. Можно использовать макрос INLINE из syscalls_public.h. Например:
INLINE void add(int a, int b) |
За счет этого происходит экономия памяти стека и увеличивается быстродействие кода.
...
Прямой перенос кода из исходного приложения может быть сопряжен с определенными трудностями. В общем случае, код, перенесенный в том же виде, как он существует в приложении, будет неработоспособен в электронном ключе. Поэтому код должен быть модифицирован и оптимизирован для выполнения на платформе CORTEX-M3. Желательно, чтобы этот код был написан заново и реализовывал функции, которых в ранних версиях приложения не было, либо эти функции должны быть видоизменены.
...
Прототип функции main() объявляется следующим образом:
DORD main( |
...
...
По возможности, переменные лучше объявлять глобально (хотя это и противоречит принципам функционального программирования), а не в теле функции, чтобы не передавать данные через стек. Этим экономится память стека и увеличивается быстродействие.
В загружаемом коде можно создать глобальные переменные, содержимое которых не будет обнуляться между вызовами. Такие переменные требуется объявлять с макросом NO_INIT:
DWORD buffer[100] NO_INIT |
Эти переменные являются аналогами статических переменных.
Польза от них может заключаться в возможности запоминания некоторых состояний загружаемого кода. Это делает анализ «черного ящика» гораздо более сложным.
...
Выход из приложения можно осуществлять следующим образом:
- Возврат из main при помощи return. Код возврата будет помещен в параметр dwRetCode функции GrdCodeRun()
- Вызов функции GcaExit(). Ей также передается код возврата
Кроме того, принудительное завершение приложения происходит в следующих случаях:
- Наступление таймаута времени выполнения загружаемого кода (3 секунды)
- Попытка выполнения приложением недопустимого действия (обращение к недопустимым адресам памяти и т.д.)
В примерах в качестве кода возврата с ошибкой используется значение -1. Для упрощения отладки можно возвращать значения макроса _LINE или пользоваться вызовом GcaExit(0, __LINE_). Этот способ поможет определить строку, на которой произошел выход из приложения. Оставлять в конечных версиях возвраты в данном виде нежелательно, так как это может дать дополнительную информацию для злоумышленника.
Для отладки можно, к примеру, использовать следующий макрос:
#define ASSERT(cond){if(cond)GcaExit(0,_LINE_);} |
...
За вычисления с плавающей точкой отвечает библиотека libm из комплекта GCC. Полное описание математических функций, доступных в ней, можно найти в документации к данной библиотеке. Для каждой функции имеется 2 варианта: обычный, для вычислений с двойной точностью (тип double), а также с приставкой «f», для вычислений с половинной точностью (тип float).
...
Разработка и первоначальная отладка загружаемого кода производится на компьютере. Для этого можно использовать любую IDE и отладчик языка С.
Основная проблема состоит в том, что отлаживать уже загруженный код затруднительно, поскольку нет возможности «залезть» отладчиком в контроллер ключа. Поэтому первоначально отлаживают сам алгоритм загружаемого кода.
Загруженный в ключ код имеет ограниченные возможности для трассировки. Например, нельзя вывести трассу на консоль или записать в файл. Однако некоторые средства все же есть. Для этой цели можно использовать функции управления светодиодом. Сигналы, подаваемые с его помощью, можно применять в качестве признаков прохождения тех или иных веток кода.
Как вариант, можно использовать принудительный возврат из загружаемого кода с соответствующим кодом возврата и передачей необходимых для отладки данных через буфер вывода.
Методы отладки кода такие же, как и при разработке на PC. Если в загружаемом коде используются вызовы Guardant Code API, то для отладки не нужно загружать этот код в ключ: можно использовать входящую в комплект разработчика отладочную библиотеку.
...
Отладочная библиотека представлена двумя частями:
- Модуль для загрузки в электронный ключ,
- Динамическая библиотека, содержащая функции, прототипы которых аналогичны тем, что доступны для загружаемого кода внутри электронного ключа (GcaXXX и GccaXXX)
Порядок работы с отладочной библиотекой таков:
- При помощи GrdUtil.exe создается файл маски, в котором один из алгоритмов представляет собой отладочный модуль загружаемого кода – DebugModule.bin. При этом нужно убедиться, что соответствующий модулю bmap-файл находится в той же директории. Можно взять готовый файл маски DebugMask.nsd из примера и изменить его для использования в собственном приложении. От файла маски зависит, какой номер будет у алгоритма, содержащего загружаемый код.
- Полученный файл маски с отладочным модулем прошивается в электронный ключ при помощи GrdUtil.
- К проекту загружаемого кода на PC подключается отладочная библиотека gcaapidll.dll. Для этого используется библиотека экспортов gcaapidll.lib. Файл GcaAPIdll.h содержит описание прототипов функций GcaXXX/GccaXXX.
- Перед вызовами функций GcaXXX/GccaXXX из gcaapidll.dll в исходном коде следует разместить вызов макроса DEBUGDLL_ INIT(hHandle, dwAlgoNum), который настраивает библиотеку для работы с текущим контекстом Guardant API. Макрос осуществляет привязку библиотеки к используемому контексту Guardant API и открытому ключу, также ему передается номер аппаратного алгоритма, в который был загружен отладочный модуль.
- Если код, предполагаемый для размещения в электронном ключе, использует вызовы внешнего Guardant API, то их требуется заменить на соответствующие вызовы Guardant Code API. В данном случае функции импортируются из отладочной библиотеки. Если же код содержит функции Guardant API, не имеющие прямых аналогов в Guardant Code API, требуется создать эквивалентные им конструкции из доступных функций.
...
Для загрузки кода в электронный ключ первоначально используется GrdUtil. При помощи этой утилиты создается дескриптор аппаратного алгоритма типа Загружаемый код.
В свойствах алгоритма указывается бинарный файл, который содержит скомпилированный загружаемый код. Этому файлу должен сопутствовать файл bmap, содержащий настройки адресов памяти.
Бинарный файл перед загрузкой должен быть преобразован в файл типа GCEXE (Guardant Code executable). Преобразование осуществляется в автоматическом режиме утилитой программирования ключей GrdUtil.
При выполнении преобразования GrdUtil генерирует ключевыепары:
- Для зашифрования и расшифрования загружаемого кода
Зашифрование производится на открытом ключе, который хранится в маске и не записывается в электронный ключ.
Расшифрование – на закрытом ключе, который хранитьсяи в файле маски, и в дескрипторе алгоритма, записанногов электронный ключ
- Для электронной цифровой подписи загружаемого кода Подписывание производится – на закрытом ключе, который хранится только в маске и не записывается в сам ключ. Проверка – на открытом, который будет храниться и в маске, и в дескрипторе алгоритма, который будет записан в ключ
...
Отладке приложений, использующих загружаемый код, следует уделить особое внимание, поскольку поиск ошибок при работе с «черным ящиком» является непростым делом.
Очень важным является итоговое быстродействие загруженного кода. Если оно получается неудовлетворительным, требуется принять меры по приведению кода к обозначенным в начале этой главы требованиям.
...
Проблема обновления информации в ключах, уже находящихся у пользователей приложения, актуальна и для загружаемого кода. Рано или поздно в этот код может потребоваться внести изменения или исправления.
Для успешного обновления загружаемого кода необходимо выполнение следующих условий:
- У разработчика должна храниться прошивка (файл маски), содержащая ключевые пары для шифрования и подписи загружаемого кода,
- У конечного пользователя должен находиться электронный ключ Guardant Code, содержащий дескриптор алгоритма с загружаемым кодом, а также закрытый ключ для расшифрования кода и открытый ключ для проверки ЭЦП
- Ключевые пары в маске и ключе должны быть идентичны.
Для обновления загружаемого кода необходимо сгенерировать новый GCEXE-файл с обновленным кодом, зашифрованным и подписанным на соответствующих ключах.
Само обновление может производиться как при помощи технологии TRU, так и прямой загрузкой GCEXE-файла из защищаемого приложения функцией GrdCodeLoad().
При желании можно сделать процедуру обновления загружаемого кода «прозрачной» для пользователя. Тогда от него потребуется только получить обновление, поместить его рядом с исполняемым файлом приложения (или в специально для этого предназначенную директорию) и запустить приложение.