Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Children Display
alltrue
depth2

Компиляция загружаемого кода

...

Для компиляции загружаемого кода используются компилятор и линкер GCC, стандартная библиотека С для встраиваемых систем newlib, утилита make и несколько сервисных утилит.
Существует два варианта использования GCC:

  1. Загрузить исходный код GCC, make, newlib из соответствующих репозиториев и самостоятельно скомпилировать.
  2. Воспользоваться готовым комплектом инструментов для разработки под ARM.

Для работы с ключами Guardant Code рекомендуется использовать свободно распространяемый инструментарий YAGARTO. Все примеры из комплекта разработчика, тестировались именно на нем.
Чтобы приступить к работе с YAGARTO требуется выполнить несколько простых шагов:

...

Все инструкции для компилятора и линкера, а также команды для обработки скомпилированного кода, содержатся в конфигурационном файле утилиты make (имя этого файла по умолчанию - makefile).
Соответственно, для компиляции приложения необходимо:

  1. Отредактировать настройки в секции Main Configuration внутри makefile:
  • Задать желаемое имя точки входа, адреса RAM, ROM
  • Задать требуемый размер стека
  • Задать имена и размеры буферов ввода-вывода
  • Задать путь к системным утилитам hex2bin.exe и map_parse.exe
  • Задать путь к папке с заголовочными файлами GrdAPI.h и GcaAPI.h
  • Задать путь к папке с файлом libgcaapi.lib
  1. Сгенерировать шаблон проекта: make template (для использования компиляторов, отличных от GCC, требуется ручная настройка проекта). Проект должен иметь определенный набор файлов и быть настроен для работы в среде Guardant Code
  2. Скопировать в папку проекта файлы с исходными текстами с модулями на C/C++ и добавить имена этих файлов в переменные makefile SRC и CPPSRC соответственно
  3. В скопированных модулях должна присутствовать функция с именем, совпадающим с именем заданной точки входа (и соответствующим прототипом)
  4. После этого можно выполнить команду: make и приложение будет собрано
  5. В папке проекта будет создана папка .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 all

или

make -f confname clean
make -f confname all

...

Универсальный 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-файлов, используемых в проекте

...

                  • begin --------*
                    make: *** No rule to make target `main.o', needed by `elf'. Stop.

...

                  • begin --------*
                    File main.c not found! Please check makefile (SRC, ASRC and CPPSRC values).

 

...

При старте приложения, в самом начале начинает исполняться код, находящийся в файле 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

LDR R4, =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
{
double x;

} iodata ALIGNED;

...

Размер программного стека для GCC указывается в makefile. За это отвечает параметр:

CFG_STACK_SIZE = 0x800;

Т. е., размер стека по умолчанию равен 2кБ (0x800 байтам).
Поскольку размер RAM достаточно сильно ограничен, рекомендуется небольшие и простые, но часто используемые функции оформлять как inline. Можно использовать макрос INLINE из syscalls_public.h. Например:

INLINE void add(int a, int b)
{
return a+b;
}

За счет этого происходит экономия памяти стека и увеличивается быстродействие кода.

...

Прямой перенос кода из исходного приложения может быть сопряжен с определенными трудностями. В общем случае, код, перенесенный в том же виде, как он существует в приложении, будет неработоспособен в электронном ключе. Поэтому код должен быть модифицирован и оптимизирован для выполнения на платформе CORTEX-M3. Желательно, чтобы этот код был написан заново и реализовывал функции, которых в ранних версиях приложения не было, либо эти функции должны быть видоизменены.

...

Прототип функции main() объявляется следующим образом:

DORD main(
DWORD dwInDataLng, DWORD dwOutDataLng, DWORD dwP1)

...

 

...

По возможности, переменные лучше объявлять глобально (хотя это и противоречит принципам функционального программирования), а не в теле функции, чтобы не передавать данные через стек. Этим экономится память стека и увеличивается быстродействие.
В загружаемом коде можно создать глобальные переменные, содержимое которых не будет обнуляться между вызовами. Такие переменные требуется объявлять с макросом 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)

Порядок работы с отладочной библиотекой таков:

  1. При помощи GrdUtil.exe создается файл маски, в котором один из алгоритмов представляет собой отладочный модуль загружаемого кода – DebugModule.bin. При этом нужно убедиться, что соответствующий модулю bmap-файл находится в той же директории. Можно взять готовый файл маски DebugMask.nsd из примера и изменить его для использования в собственном приложении. От файла маски зависит, какой номер будет у алгоритма, содержащего загружаемый код.
  2. Полученный файл маски с отладочным модулем прошивается в электронный ключ при помощи GrdUtil.
  3. К проекту загружаемого кода на PC подключается отладочная библиотека gcaapidll.dll. Для этого используется библиотека экспортов gcaapidll.lib. Файл GcaAPIdll.h содержит описание прототипов функций GcaXXX/GccaXXX.
  4. Перед вызовами функций GcaXXX/GccaXXX из gcaapidll.dll в исходном коде следует разместить вызов макроса DEBUGDLL_ INIT(hHandle, dwAlgoNum), который настраивает библиотеку для работы с текущим контекстом Guardant API. Макрос осуществляет привязку библиотеки к используемому контексту Guardant API и открытому ключу, также ему передается номер аппаратного алгоритма, в который был загружен отладочный модуль.
  5. Если код, предполагаемый для размещения в электронном ключе, использует вызовы внешнего Guardant API, то их требуется заменить на соответствующие вызовы Guardant Code API. В данном случае функции импортируются из отладочной библиотеки. Если же код содержит функции Guardant API, не имеющие прямых аналогов в Guardant Code API, требуется создать эквивалентные им конструкции из доступных функций.

...

Для загрузки кода в электронный ключ первоначально используется GrdUtil. При помощи этой утилиты создается дескриптор аппаратного алгоритма типа Загружаемый код.
В свойствах алгоритма указывается бинарный файл, который содержит скомпилированный загружаемый код. Этому файлу должен сопутствовать файл bmap, содержащий настройки адресов памяти.
Бинарный файл перед загрузкой должен быть преобразован в файл типа GCEXE (Guardant Code executable). Преобразование осуществляется в автоматическом режиме утилитой программирования ключей GrdUtil.
При выполнении преобразования GrdUtil генерирует ключевыепары:

  • Для зашифрования и расшифрования загружаемого кода

Зашифрование производится на открытом ключе, который хранится в маске и не записывается в электронный ключ.
Расшифрование – на закрытом ключе, который хранитьсяи в файле маски, и в дескрипторе алгоритма, записанногов электронный ключ

  • Для электронной цифровой подписи загружаемого кода Подписывание производится – на закрытом ключе, который хранится только в маске и не записывается в сам ключ. Проверка – на открытом, который будет храниться и в маске, и в дескрипторе алгоритма, который будет записан в ключ

...

Отладке приложений, использующих загружаемый код, следует уделить особое внимание, поскольку поиск ошибок при работе с «черным ящиком» является непростым делом.
Очень важным является итоговое быстродействие загруженного кода. Если оно получается неудовлетворительным, требуется принять меры по приведению кода к обозначенным в начале этой главы требованиям.

...

Проблема обновления информации в ключах, уже находящихся у пользователей приложения, актуальна и для загружаемого кода. Рано или поздно в этот код может потребоваться внести изменения или исправления.
Для успешного обновления загружаемого кода необходимо выполнение следующих условий:

  • У разработчика должна храниться прошивка (файл маски), содержащая ключевые пары для шифрования и подписи загружаемого кода,
  • У конечного пользователя должен находиться электронный ключ Guardant Code, содержащий дескриптор алгоритма с загружаемым кодом, а также закрытый ключ для расшифрования кода и открытый ключ для проверки ЭЦП
  • Ключевые пары в маске и ключе должны быть идентичны.

Для обновления загружаемого кода необходимо сгенерировать новый GCEXE-файл с обновленным кодом, зашифрованным и подписанным на соответствующих ключах.
Само обновление может производиться как при помощи технологии TRU, так и прямой загрузкой GCEXE-файла из защищаемого приложения функцией GrdCodeLoad().
При желании можно сделать процедуру обновления загружаемого кода «прозрачной» для пользователя. Тогда от него потребуется только получить обновление, поместить его рядом с исполняемым файлом приложения (или в специально для этого предназначенную директорию) и запустить приложение.