Rus Eng
О компании
Технологии
Разработчикам ПО
Производителям микроконтроллеров
Партнеры / Клиенты
Перспективные проекты
| | | |
Главная страница    Статьи
Статьи

Система программирования процессора "Мультикор"


Автор: Д.Н. Бочарников, И.С. Замятин, Е.А. Зуев, С.А. Крысенков, М.Ю. Донорский, А.А. Чупринов

1. Введение. Инструментальное ПО для платформы Мультикор

В этой части статьи речь пойдет об инструментальном программном обеспечении процессора Мультикор. Под этим понимается комплекс средств разработки программ, который должен предоставить разработчику необходимую поддержку ключевых этапов и стадий разработки (прежде всего, процесс создания программ, их отладку и тестирование), обеспечив при этом современный уровень сервиса и достаточно высокую производительность труда.

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

Самая совершенная архитектура процессора не позволяет воспользоваться всеми ее преимуществами, если для нее отсутствует система программирования. В некотором смысле, система программирования - это "костный мозг" вычислительной системы, позволяющий "вырабатывать" качественное программное обеспечение, тем самым делая ее привлекательной для конечного пользователя.

Эволюция развития систем программирования от низкоуровневых средств (машинный код, ассемблеры) до высокоуровневых (С/С++, Pascal/Delphi, Ада и т.д.) показала, что для эффективного использования возможностей новых процессоров целесообразно предоставлять пользователям-разработчикам как низкоуровневые, так и высокоуровневые средства разработки программ.

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

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

  1. Компиляторы языков программирования Си/Си++.
  2. Ассемблер с встроенными макросредствами.
  3. Редактор связей (линкер) и библиотекарь.
  4. Отладчик.
  5. Интегрированная пользовательская среда, объединяющая перечисленные компоненты и функционирующая в двух основных режимах:
  • Режим разработки программ;
  • Режим отладки программ.

Последующие разделы статьи содержат краткое описание архитектуры компонент ПСРП и их основных особенностей.

2. Реализация компилятора C/C++ для платформы Мультикор

2.1. Основные проблемы при создании полного компилятора для языков C/C++

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

Очевидно, что добавление к стандартным средствам разработки для новой аппратной платформы (как-то ассемблер, линкер, отладчик) компилятора языка высокого уровня приводит к существенному удорожанию продукта для конечного пользователя. В связи с этим возникает вопрос о необходимости подобного добавления. В самом деле, давно известно, что объектный код, порождаемый любым компилятором, в любом случае уступает по эффективности ручному кодированию той же задачи на низкоуровневом языке ассемблера. Не вдаваясь подробно в дискуссию о преимуществах и недостатках языков высокого уровня в сравнении с языками ассемблера, отметим лишь некоторые моменты, доказывающие не только возможность, но и экономическую целесообразность разработки на языках высокого уровня.

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

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

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

2.2. Архитектура переносимых компиляторов C/C++ фирмы Интерстрон

Традиционная архитектура переносимых и перенацеливаемых компиляторов включает две основные подсистемы: компонента, ориентированная на конкретный входной язык и практически не зависимая от целевой аппаратной платформы, получила название front end (компилятор переднего плана, FE). Она производит полный синтаксический и семантический разбор программ на входном языке. Вторая компонента ориентирована на целевую платформу и не зависит от входного языка; она называется back end (BE) и выполняет генерацию целевого кода, предназначенного для выполнения. Взаимодействие между частями компилятора осуществляется посредством некоторого промежуточного представления исходного кода, создаваемого FE и используемого BE.

В компиляторах фирмы Интерстрон данная архитектура подверглась существенной модернизации. Наиболее радикальная модификация традиционной схемы заключалась в изменении роли промежуточного представления (ПП). Вместо низкоуровневой структуры, ориентированной исключительно на генерацию результирующего кода, традиционно присущей ПП, было разработано универсальное семантическое представление (СП) программ, которое содержит полное знание об исходной программе. Использование СП при генерации кода позволяет более полно учесть особенности исходной программы, что способствует большей эффективности кода. Принципиально важное обстоятельство заключается в том, что высокоуровневое СП пригодно не только для генерации машинного кода (что составляет традиционное и практически единственное применение ПП), но и для широго спектра операций над программами, допуская в том числе обратный инжиниринг и рефакторинг исходных программ (в частности, на основе методик и средств моделирования UML), статический анализ и генерацию отчетов, а также многие другие практически полезные операции, вплоть до потенциальной возможности автоматической верификации.

Вторая группа модернизаций традиционной схемы касается BE, который рассматривается как подсистема, организованная по модульному принципу. Первоначальная генерация кода из Семантического Представления происходит не в ассемблер целевой платформы, а в высокоуровневый абстрактный ассемблер. Это позволяет некоторую часть оптимизаций объектного кода производить одинаковым образом для различных целевых платформ - как существующих, так и перспективных.

Составной частью подсистемы BE служит инфраструктура оптимизаций. Наличие в архитектуре компиляторе такой компоненты позволяет значительно ускорить и удешевить разработку оптимизаций для перспективных аппаратных платформ - как реализацию традиционных, так и разработку специальных оптимизаций, учитывающих особенности конкретной платформы. Эта инфраструктура основана на традиционных методиках анализа потока управления и анализа потока данных. Кроме того, она включает и сравнительно новые методы, в частности, технологию представления программы в виде единичных статических присваиваний (Static Single Assignment Forms). Это позволяет применять достаточно агрессивные по отношению к получаемому результату и в тоже время консервативные по отношению к семантике программы способы оптимизации. Среди них можно отметить удаление избыточного кода (Dead Code Elimination), продвижение констант (Sparse Conditional Constant Propagation), глобальное распределение регистров (Global Register Allocation) и т.д.

2.3 Особенности реализации компилятора Си/Си++ для платформы Мультикор

Основной задачей при реализации компилятора языков C/C++ для платформы Мультикор было обеспечение прозрачного взаимодействия кода и данных, относящихся к разным процессорным ядрам. Данная задача осложняется тем фактом, что размер адресуемой единицы памяти для разных ядер различен: 8-битный байт для RISC-ядра и 32-битное слово для DSP-ядра. Один из наиболее очевидных результатов этого - необходимость одновременной поддержки более одной таблицы размеров типов с возможностью переключения между ними во время компиляции одного и того же модуля. Менее очевидное, но не менее значимое следствие состоит в том, что внутренние структуры, используемые компилятором для поддержки разнообразных свойств языка времени исполнения (например, таблицы виртуальных функций, указатели на функции-члены, структуры поддержки обработчиков исключений и т.д. и т.п.), также должны иметь разные размеры, в зависимости от того, с точки зрения какого процессорного ядра мы смотрим на них.

Кроме того, организация взаимодействия ядер процессора Мультикор требует различения функций ядра DSP, вызываемых из RISC-ядра, и функций, вызываемых из самого DSP-ядра.

Указанные особенности архитектуры Мультикор были поддержаны в компиляторе соответствующими директивами pragma. Такие конструкции являются рекомендованным стандартным способом расширения языка без модификации его семантики и без потери переносимости (компилятор может просто игнорировать неизвестные ему директивы pragma).

В компилятор были введены две директивы pragma с именами dsp и dsp_root, общий вид которых следующий:

#pragma dsp(a, b, c)
#pragma dsp_root(x, y)

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

Вторая директива отражает особенность организации взаимодействия ядер, которая требует различения функций для ядра DSP, вызываемых из RISC, от вызываемых из самого DSP-ядра. Единственное отличие второй формы заключается в том, что она используется для имен функций, предназначенных для выполнения DSP-ядром, но вызываемых из кода, написанного для RISC-ядра. Имена всех остальных функций DSP-ядра, вызываемых из других функций DSP-ядра, помещаются в директиву первой формы.

Использование директив

Директивы могут задаваться в программе на уровне пространстве имён (включая глобальное пространство имён). Считается, что все сущности из данного пространства имён, упомянутые в директиве, относятся к ядру DSP. Задание в директиве некоторого идентификатора должно предшествовать объявлению или описанию переменной или функции с этим именем.

Директива #pragma dsp_root может задавать только имена функций (включая имена глобальных функций и функций-членов классов) и имена классов. В последнем случае считается, что все методы класса являются dsp_root-методами, то есть могут вызываться только из RISC кода.

Классовые типы могут относиться только к одному ядру. Если идентификатор А объявлен как DSP-идентификатор и есть класс с именем A, то объекты этого класса не могут быть объявлены в режиме RISC, и наоборот. Конструкции typedef и enum, объявленные как относящиеся к DSP, могут также использоваться в режиме RISC.

Следующий пример иллюстрирует использование директив:

#pragma dsp (A, INT, CH)

struct A
{
    int x;
    int y;
};

typedef char CH;

enum E
{
    e1, e2, e3
};

void f ()
{
    A a;      // ошибка: объект DSP типа в стеке RISC функции
    E e;      // допустимо
    CH c;     // допустимо: c имеет тип char, заданный для RISC-ядра
    A *pa;    // допустимо: указатель на объекты типа A в DSP
}

В режиме DSP (в теле DSP-функции и в её заголовке, в определении DSP-класса и его методов и статических членов) не могут использоваться сущности из RISC. В режиме RISC переменные и типы из DSP доступны.

Заметим, что макроопределения, заданные с помощью директивы #define, действуют как в режиме RISC-ядра, так и в режиме DSP-ядра.

DSP-типы

Если сущности из DSP встречаются в режиме RISC-ядра, считается, что они имеют dsp-тип. Если это составной тип, все типы, из которых он составлен, также считаются dsp-типами. Однако типы, используемые при настройке шаблонов DSP, не считаются dsp-типами, за исключением классовых типов. Например:

#pragma dsp (df, di, dpi, dppi)

void df (int i, char c);
        // dsp-функция имеет dsp-тип, параметры также имеют dsp-типы

int   di;      // di имеет dsp-тип int
int*  dpi;     // dpi - dsp-указатель на dsp-тип int
int** dppi;    // dppi - dsp-указатель на dsp-указатель на dsp-тип int

Если в режиме RISC по стандарту должно быть выполнено продвижение (promotion) по отношению к выражению, имеющему арифметический dsp-тип, продвижение выполняется к RISC-типу.

Преобразования типов

В режиме RISC ядра любой арифметический dsp-тип может быть преобразован к любому арифметическому risc-типу и наоборот. Указатель на продвинутый арифметический dsp-тип может быть неявно преобразован к указателю на тот же risc-тип (если такое преобразование допускается стандартом) и наоборот. То же допустимо и для указателей на члены. Для указателей на другие типы такие преобразования не разрешены. Пример:

#pragma dsp (df, dc, di, dpi, dppi)

void df (int i, char c);     // dsp-функция

int ri,di;

char c, dc, *rpc;

int *rpi,*dpi;
int **rppi, **dppi;
void (*pf) (int, char);

void rf ()      // risc-функция
{
    ri = di;
    di = ri;
    ri = dc;
    rc = dc;       // допустимо
    rpi = dpi;     // допустимо: указатели на продвинутый тип
    dpi = &ri;     // допустимо, но возможно, нужно запретить (!)
    rpc = &dc;     // ошибка: указатели на непродвинутый тип
    rppi = dppi;   // ошибка
    pf = df;       // ошибка
}

Явные преобразования типов и операция sizeof

Типы, указанные в конструкциях явного преобразования типа в режиме RISC-ядра, считаются risc-типами, даже если преобразование применяется к выражению, имеющему dsp-тип. Результат операции sizeof, примененной к выражению dsp-типа, считается в адресуемых единицах dsp-ядра, например:

#pragma dsp (dc, di, dca)

char dc;
char dca[10];
int di;

void f ()        // risc-функция
{
    int i;
    i = sizeof (di);    // i = 1
    i = sizeof (dc);    // i = 1
    i = sizeof (+di);   // i = 4, т.к. произошло продвижение к risc-типу
    *((char *)dca) = 1; // dca преобразуется к указателю на risc char
}

2.4 Заключение

В результате был создан полный компилятор C/C++, с одной стороны, соответствующий международным стандартам, а с другой стороны использующий особенности архитектуры платформы Мультикор. Кроме того, в компиляторе реализованы некоторые наиболее распространенные оптимизации, такие как продвижение констант, удаление избыточного кода. В настоящее время компилятор проходит комплексное тестирование на соответствие стандартам и заявленным характеристикам на наборе тестов, разработанных фирмой Интерстрон.

В целом, работа по реализации полного компилятора C/C++ для платформы Мультикор показывает возможность создания компилятора для языка высокого уровня, отвечающего международным стандартам, и в то же время поддерживающего особенности архитектуры целевой платформы. Для конечных пользователей это означает возможность создания прикладных программ, которые с одной стороны могут эффективно использовать преимущества платформы Мультикор, а с другой стороны, предоставляет простую и надежную технологию адаптации для этой платформы уже существующих программных модулей на языках Си/Си++. Это обстоятельство может служить еще одним доводом в пользу использования ЯВУ (в частности, Си/Си++) в разработке прикладных программных продуктов для специализированных платформ.

3. Ассемблер, линкер, библиотекарь

В данном разделе кратко описаны важнейшие аспекты реализации системных утилит инструментального комплекса - ассемблера и линкера/библиотекаря. Будучи сравнительно низкоуровневыми компонентами, они должны в полной мере учитывать все аппаратные особенности целевой архитектуры с тем, чтобы обеспечить максимальное использование ее преимуществ.

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

При создании ассемблера и линкера преследовалась цель минимизировать количество информации, которое нужно знать пользователю, чтобы писать программы, в которых задействовано взаимодействие RISC- и DSP-ядер. Отличие секций RISC и DSP на синтаксическом уровне заключается в наличии специального атрибута в заголовке секции, который позволяет ассемблеру понять, что он имеет дело с секцией определённого типа. Так, объявление .section ".text" ro означает, что объявлена RISC-секция кода, а .section ".text" ro d - DSP-секция кода.

В результате работы ассемблера создаётся перемещаемый объектный файл, содержащий секции обоих типов и обращения к символам, которые надо разрешить. Задача линкера заключается в том, чтобы разместить нужным образом все секции в адресном пространстве и разрешить обращения к символам. Линкер помещает DSP-секции кода и данных в специально отведённую для этого область памяти. При разрешении перемещаемых выражений линкер учитывает, в какой секции определён тот или иной символ и в какой секции встречается обращение к нему. Каждому символу, определённому в DSP-памяти, соответствует два адресных значения: абсолютный виртуальный адрес RISC-памяти и внутренний относительный адрес DSP-памяти. Дело в том, что DSP-ядро имеет собственную внутреннюю адресацию и включает в себя два независимых пространства - память данных и память программ. Если линкер встречает обращение в RISC-секции к символу, определённому в DSP-памяти, то в качестве значения символа берётся его RISC-адрес, если же идёт обращение к этому символу из DSP-секции, то берётся его относительное значение.

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

Помимо размещения секций, конфигурационный файл поддерживает механизм оверлеев, который позволяет преодолеть ограниченность и небольшой размер памяти DSP-ядра по сравнению с RISC-памятью. Распространенная ситуация для многих классов задач заключается в том, что размер памяти, необходимой для кода и данных, превышает объем DSP-памяти. Каждая секция в программе может загружаться с одного адреса, а выполняться с другого. Линкер отслеживает такую двойственную логику распределения секций на этапах загрузки и исполнения и при разрешении перемещаемых выражений вместо символов подставляет адреса по исполнению. В конфигурационном файле для выделенных областей памяти, в которых будут размещаться определённые секции, можно прописать явные адреса размещения и исполнения. При анализе конфигурационного файла линкером будут сгенерированы специальные метки начала и конца таких областей по адресам размещения и исполнения. При помощи этих меток можно по мере надобности подгружать одни DSP-секции из RISC-памяти в DSP и отгружать другие из DSP в RISC, тем самым эффективно используя ограниченную память DSP-ядра.

В конфигурационном файле можно задать описания до 3-х разделов адресного пространства(RISC, DSP_PRAM, DSP_XYRAM), а в каждом из разделов - определить ряд областей, в которых будут располагаться секции. Каждая область характеризуется своим именем, адресом размещения, адресом исполнения и максимально возможным размером (два последних атрибута задавать необязательно). В описание области также входит набор признаков, по которым ту или иную секцию можно отнести именно к данной области определённого раздела. Секцию можно отнести в какую-то область либо по имени, либо по месторасположению. Для разделов RISC и DSP_XYRAM можно также задать размеры стека и кучи.

Ниже приведен пример конфигурационного файла:

RISC
stacksize 0x10000
heapsize  0x10000
area "Area1" Ar = 0xbfc00000 MaxSize = 0x500
".text1"
".data1" ("f1.obj" "lib1.lib":"f2","f3" "f4.obj")

area "Area2" Ar = 0xbfc00500
default

DSP_PRAM
area "Area3" Ar = 0xbfd00000  Ai = 0xb8440000
".textdsp1"

area "Area4" Ar = 0xbfd05000  Ai = 0xb8440000
".textdsp2"

area "Area5" Ar = 0xb8441000
default

В RISC-разделе выделены две области Area1 и Area2. В первую область попадут все секции с именем .text1, а также секции .data1 из модулей f1.obj, f4.obj и библиотечных модулей f2, f3. Во второй области размещаются все остальные RISC-секции. В разделе PRAM определено 3 области, при этом для Area3 и Area4 задан адрес размещения и исполнения. Эти области являются оверлейными, так как при исполнении секции каждой области (.textdsp1 и .textdsp2) будут располагаться в памяти последовательно друг за другом с одного и того же адреса 0xb8440000. Однако при начальной загрузке исполняемого файла и в те моменты времени, когда от секций не требуется находиться в DSP-памяти, секции каждой области размещаются в непересекающихся участках памяти. Сгенерированные линкером символы для обозначения начала и конца области Area3 по размещению и исполнению, будут следующими:

___areaDsp_r_Area3__base,
___areaDsp_r_Area3__limit,
___areaDsp_i_Area3__base,
___areaDsp_i_Area3__limit.

Линкер дает возможность анализа структуры сформированного исполняемого файла, в том числе просмотра состава секций и символов. Для этой цели при вызове линкера необходимо задать опцию, по которой будет создан специальный map-файл, содержащий информацию обо всех секциях и нелокальных символах исполняемого файла. Для каждой cекции указываются её тип, атрибуты, адрес размещения в памяти, размер и имя объектного модуля, из которого она была взята. Для символа указываются его тип и адрес.

Кроме основного режима работы - создания исполняемого файла - линкер также может работать и в режиме библиотекаря, то есть создавать и модифицировать библиотеки объектных модулей. В библиотеку могут входить как RISC-, так и DSP-секции.

4. Отладчик

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

Отладчик компании Интерстрон для платформы Мультикор предназначен для загрузки и отладки программ, полученных при помощи инструментальных средств (компилятора, ассемблера, линкера). Отладчик реализован в виде законченной самостоятельной программы, которая может работать как отдельная консольная утилита. Кроме того, отладчик интегрирован в среду разработки (см. разд 5). Пользовательским интерфейсом отладчика служит известный интерфейс GDB/MI, разработанный сообществом GNU (www.gnu.org) и к настоящему времени ставший стандартом де-факто. Привлекательной особенностью отладчика служит его сравнительно слабая зависимость от операционной системы инструментального компьютера, поэтому его портирование на другую ОС не представляет большой проблемы.

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

4.1 Формат отладочной информации

В качестве формата отладочной информации был выбран формат DWARF версии 3 (DWARF Working Group). На сегодняшний день этот формат является одним из лучших спецификаций отладочной информации для языков программирования высокого уровня. Он предоставляет возможность полностью описать все сущности языка Си++ - переменные, функции, классы, сложные структуры, указатели их областей видимости, а также задать их привязку к целевому коду. Дополнительным аргументом в пользу выбора формата DWARF служит его положение как фактического стандарта в своей области, что дает возможность при необходимости использовать отладочные средства сторонних производителей.

Генерация информации для отладчика реализована таким образом, что для отладки нет необходимости создавать особую "отладочную версию" программы. Генерация отладочной информации не влияет на размер конечного кода, так как отладочная информация помещается в промежуточные носители (в файл с ассемблерным кодом, в файл с объектным кодом, а также в специальный файл, содержащий собственно отладочную информацию). Отладка происходит непосредственно на том коде, который будет загружаться в целевой процессор.

4.2 Отладка оптимизированного кода

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

Так, отладочная информация позволяет отследить ситуацию с "поднятием" переменных в регистры, что показывает следующий пример:

int a[5] = {1, 2, 3, 4, 5};

int f()
{
    int i, sum;
    sum = 0;
    for (i = 0; i < 5; ++i) {
        sum += a[i];
    }
    return sum;
}

int main()
{
    return f();
}

В процессе выполнения цикла значения переменных i и sum находятся в регистрах, а не в памяти. Это можно проверить по ассемблерному тексту (см. 4.3). Тем не менее, в процессе отладки в интегрированной среде, значения переменных показываются правильно, так как отладчик сам "знает", откуда их брать.

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

int  f( int i )
{
    const int a=1;
    const int b=2;
    int c;

    c = a + b;
    return i+c;
}

Для этой функции фактически будет скомпилирована одна команда: return i+3. Так как переменная c реально не используется, то команды ее вычисления в объектном коде нет. Поэтому в процессе пошагового выполнения в отладчике при входе в данную функцию указатель выполняемой команды сразу будет установлен на строке return i+c, а переменную c нельзя будет посмотреть. Возможность подобных ситуаций следует учитывать при работе с оптимизированным кодом.

4.3 Отладка на уровне ассемблера

Как и большинство современных систем программирования, пакет средств разработки компании Интерстрон для платформы Мультикор позволяет вести отладку на уровне ассемблера, включая установку точек останова, пошаговую трассировку с привязкой к ассемблерному тексту, просмотр и изменение регистров и памяти. Отладочная информация для нее создается ассемблером. Исходный текст представлен в виде комментариев, что позволяет ориентироваться в тексте программы. При описании отладки оптимизированного кода мы уже обращались к отладке на уровне ассемблера, чтобы понять, как реализована программа. Например, фрагмент программы из 4.1 выглядит так (неоптимизированный код):

. . .
;   sum = 0;

   addiu $3, $0, 0
   sw $3, -4($30)

; for(i = 0; i < 5; ++i) {

L2:
   addiu $4, $0, 5
   lw $3, -4($30)
   nop
   sub $3, $3, $4
   bltz $3, L3
   nop
   j L4
   nop

; sum += a[i];

L3:
   lw $4, -4($30)
   nop
   la $3, _a
   addiu $5, $0, 4
   mul $4, $4, $5

. . .

   j L2
   nop

; }
; return sum;

L4:

. . .

Таким образом, разработчику предоставляется возможность производить отладку на любом уровне. Неоптимизированный код, полностью соответствующий исходному, можно использовать для отладки логики самой программы, не обращая внимания на особенности реализации. Оптимизированный код позволяет протестировать конечный результат. Отладка на уровне ассемблера позволяет обратить внимание на тонкости реализации программы для целевого процессора. Данные возможности позволяют разработчику значительно сократить время отладки и повысить безопасность разрабатываемого программного обеспечения.

5 Интегрированная среда

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

В качестве основы такой интегрированной среды была выбрана универсальная компонентная платформа Eclipse (www.eclipse.org). Эта платформа, разработкой и поддержкой которой в настоящее время занимается компания IBM, изначально задумывалась как всеобъемлющий платформенно-независимый инструментарий, который позволит объединить в одно целое все необходимые компоненты для разработки программного обеспечения, а также позволит производить их настройку под свои нужды. Сейчас платформа Eclipse активно развивается; в сообщество разработчиков Eclipse входят такие компании как Intel, Borland, QNX и другие.

Компания Интерстрон работает с Eclipse с 2002 года, став одним из первых в России пользователей этой платформы. За истекший период в компании разработана технология конструирования переносимых интегрированных средств различного назначения на основе платформы Eclipse. Пользовательская среда программирования для процессора Мультикор является одним из примеров использования этой технологии.

Главной особенностью и всместе с тем одним из принципиальных преимуществ технологии является ее открытость по отношению к реализации всевозможных особенностей процессоров. В примере с Мультикором можно отметить реализацию демонстрации всех групп регистров, реализацию DSP-дизассемблера. При этом не стоит забывать, что технология Eclipse ещё очень молода и очень перспективна, поэтому со временем в ней будут появляться новые решения, которые позволят ещё больше систематизировать и облегчить разаработку ПО.

Интегрированная среда разработки поддерживает следущие основные виды работ:

1. Разработка программных проектов на языках Си/Си++ и языке ассемблера. Поддерживается полнофункциональный текстовый редактор программ, включающий стандартный репертуар средств, в частности, синтаксическую подсветку (в том числе и для ассемблерного кода). Реализован менеджер проектов, обеспечивающий процесс автоматической сборки проектов с позиционированием ошибок и предупреждений по исходным текстам программ.

2. Компиляция программ и проектов на языках Си/Си++ и ассемблере. Менеджер проектов позволяет создавать различные конфигурации построения целевых программ, а также адекватно обрабатывать параметры компиляции, ассемблирования и линковки, в том числе опции, предназначенные специально для данного микропроцессора.

3. Отладка в графической среде с помощью отладчика Интерстрон. Поддерживается навигация по исходному коду в процессе отладким, просмотр и изменение значений ресурсов целевого процессора - памяти, переменных, регистров. Имеется возможность вычисления значения выражений, заданных в синтаксисе языков C/C++. Также реализованы установка точек останова, навигация по ассемблерному тексту, отображение стека вызовов и навигация по нему.

Учитывая вышесказанное, следует отметить, что на основе собственной технологии интеграции отладчика со средой разработки, компании Интерстрон удалось создать полнофункциональный инструмент для разработки и отладки программ на языках Си/Си++ для платформы Мультикор. Эта технология минимизирует время разработки среды, а также затраты, связанные с ее переносом на другие ОС.

4. Подсистема оперативной помощи. Высокая степень структурированности материала (а также его двуязычности) в сочетании со стандартными для оперативной помощи средствами навигации и поиска обеспечивают удобную и эффективную поддержку для разработчиков.

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

6. Заключение

Завершая краткий обзор инструментального ПО для платформы Мультикор,  следует отметить, что результатом совместной работы двух фирм является пакет средств разработки программ для процессора Мультикор, центральной частью которого служит компилятор полного стандарта для языка Си/Си++ фирмы "Интерстрон". Ключевой особенностью комплекса является реализация в рамках единого компилятора механизма создания выполняемых файлов кода для различных ядер, входящих в состав MIPS-архитектуры процессора Мультикор. В данной версии системы процесс загрузки ядер процессора осуществляется программистом-разработчиком на этапе создания программного проекта.

Пакет средств разработки состоит из следующих компонент:

  • Пользовательская среда разработки программ на Си/Си++, реализованная для режимов создания и отладки;
  • Компилятор программ на Си/Си++, реализующий стандарты ISO/IEC 9899:1999(C99) языка Си и ISO/IEC 14882:1998 языка Си++.
  • Объединенный ассемблер для RISC- и DSP-ядер процессора Мультикор.
  • Линкер выполняемого файла и библиотекаря для создания пользовательских библиотек.
  • Системные библиотеки языков Си и Си++.
  • Отладчик программ Си/Си++.


Назад

ЗАО "Интерстрон" 1998-08.06.2015, ООО "Интерстрон" 09.06.2015 по н.в. Все права защищены.
Москва, Дмитровское шоссе, 1/1
e-mail: interstron-info@mail.ru
web: www.interstron.ru
Тел.: +7 (495) 769-55-68