Copyright (C) А.Гавва V-0.4w май 2004

4. Вспомогательные утилиты

4.1 Уменьшение затрат времени с помощью утилиты gnatstub

Начиная с версии 3.11p, система компилятора GNAT предусматривает утилиту gnatstub. Утилита gnatstub может быть использована для построения файла прототипа тела пакета в соответствии с предоставленным файлом спецификации пакета. В результате обработки файла спецификации пакета, в котором указаны спецификации подпрограмм, утилита gnatstub создает файл тела пакета, в котором располагаются "пустые" тела подпрограмм, соответствующие их спецификациям. Такие "пустые" подпрограммы часто называют "заглушками" ("stubs").

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

Предположим, что у нас есть файл с исходным текстом спецификации пакета tiny.ads, содержимое которого имеет следующий вид:


package Tiny is

    procedure Simple_Procedure;
    function Simple_Function return Boolean;

end Tiny;

Шаблон тела пакета может быть создан с помощью команды:


gnatstub tiny.ads

В результате, утилита gnatstub генерирует файл tiny.adb, который имеет следующий вид:


package body Tiny is

---------------------
-- Simple_Function --
---------------------

function Simple_Function return Boolean is
begin
    return Simple_Function;
end Simple_Function;

----------------------
-- Simple_Procedure --
----------------------

procedure Simple_Procedure is
begin
    null;
end Simple_Procedure;

end Tiny;

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

4.2 Утилита перекрестных ссылок gnatxref

Утилита gnatxref (или gnatf для GNAT 3.10) - это утилита которая генерирует индексы для каждого появления идентификатора в программе, включая все идентификаторы использованные в пакетах от которых данная программа зависит.

Опция -v позволяет генерировать листинг в формате tag-файла редактора vi.

Для программы hello.adb, показанной ранее, утилита gnatxref генерирует следующее:


  Text_IO U a-textio.ads:51:13 {} {hello.adb:1:10 4:7 }
  Put_Line U a-textio.ads:260:14 {} {hello.adb:4:15 }
  Ada U ada.ads:18:9 {} {hello.adb:1:6 4:3 }
  hello U hello.adb:2:11 {} {}

Каждая строка начинается с указания имени индексируемого идентификатора. Далее следует имя файла в котором данный идентификатор объявлен с указанием расположения этого объявления в файле. В заключение, следует секция в которой перечислены все появления идентификатора в программе.

В данном примере, идентификатор Text_IO появляется в первой строке (с учетом спецификатора with) и в четвертой строке (Put_Line).

4.3 Оценка "мертвого" кода с помощью утилиты gnatelim

Утилита gnatelim, основанная на ASIS (Ada Semantic Interface Specification), может быть использована для поиска неиспользуемых программой частей в объектных файлах и удаления их из финального исполняемого файла. В результате ее работы, создается файл перечисляющий подпрограммы которые компилятор не должен компилировать. При сохранении этого списка не используемых подпрограмм в файле gnat.adc, утилита gnatmake будет автоматически читать этот файл и отбрасывать указанные подпрограммы при компиляции.

Согласно рекомендаций руководства пользователя по компилятору GNAT, для использования утилиты gnatelim, необходимо сгенерировать дерево файлов, используя опцию -gnatt. Предположим, что главной программой является файл main.adb, тогда мы можем выполнить следующее:


gnatmake -c main
gnatbind main
gnatmake -f -c -gnatc -gnatt main
gnatelim main > gnat.adc
gnatmake -f main

Эти команды сгенерируют полный набор дерева файлов для проекта, отбросят все не используемые в проекте подпрограммы, и, затем, перекомпилируют весь проект как финальный исполняемый файл.

4.4 Отслеживание состояния стека и обнаружение утечек памяти
      во время выполнения программы

Начиная с версии 3.12 GNAT предусматривает средства обратной трассировки, которые позволяют получить информацию об источнике исключения и состоянии стека времени выполнения в случае возникновения исключения. Эти средства предоставляются пакетами Gnat.Traceback и Gnat.Traceback.Symbolic (за более подробной информацией следует обратиться к спецификациям этих пакетов), которые позволяют точно идентифицировать место возникновения исключения, вплоть до определения файла с исходным текстом и строки в результате выполнения которой было возбуждено исключение. Для того, чтобы активировать использование этих средств необходимо при компиляции программы указать опцию -funwind-tables, а при связывании программы - опцию -E.

4.4.1 Утилита gnatmem

Для мониторинга программы, которая выполняется под управлением отладчика gdb (поставляемого вместе с GNAT), может быть использована утилита gnatmem (начиная с версии 3.12 GNAT). После того как выполнение программы завершено, утилита gnatmem отображает общую информацию о динамическом распределении памяти, в процессе работы программы. Эта информация может быть использована для поиска "утечек" памяти, то есть, мест где программа осуществляет динамическое распределение памяти и не возвращает распределенное пространство памяти системе. Поскольку утилита gnatmem использует отладчик gdb, программа должна быть скомпилирована с подключением поддержки отладки под управлением gdb, что выполняется указанием опции -g при компиляции программы.

Для запуска программы program под управлением утилиты gnatmem можно сделать следующее:


gnatmem program

Утилита gnatmem может принимать следующие опции командной строки:

-q  -  Активирует "молчаливый" режим - выводится не вся статистика, а только информация о потенциально возможных утечках памяти.
n  -  Число в интервале от 1 до 10 указывающее глубину вложенности информации обратной трассировки.
-o file  -  Сохранить вывод отладчика gdb в указанный файл file. Скрипт отладчика gdb сохраняется как файл gnatmem.tmp.
-i file  -  Осуществить мониторинг используя первоначально сохраненный с помощью опции -o файл file. Удобно использовать для проверки программы выполнение которой под управлением утилиты gnatmem было неожиданно прервано в результате возникновения каких-либо ошибок.

4.4.2 Средства GNAT.Debug_Pools

Использование Unchecked_Deallocation и/или Unchecked_Conversion может легко привести к некорректным ссылкам памяти. От проблем, порождаемых подобными ссылками, достаточно сложно избавиться поскольку проявляемые симптомы, как правило, далеки от реального источника проблемы. В подобных случаях, очень важно обнаружить наличие подобной проблемы как можно раньше. Именно такая задача возлагается на Storage_Pool предусмотренный в GNAT.Debug_Pools.

Чтобы воспользоваться средствами отладочного пула динамической памяти GNAT, пользователь должен ассоциировать объект отладочного пула с каждым ссылочным типом, который потенциально способен быть источником проблем.


type Ptr is access Some_Type;
Pool : GNAT.Debug_Pools.Debug_Pool;
for Ptr'Storage_Pool use Pool;

GNAT.Debug_Pool является производным от Checked_Pool, который является характерным для GNAT пулом динамической памяти. Такие пулы динамической памяти, подобно стандартным пулам динамической памяти Ады, позволяют пользователю переопределять стратегию распределения (аллокацию) и освобождения (деаллокацию) динамической памяти. Они также предусматривают контрольные точки, для каждого разыменования (расшифровки) ссылок посредством операции Dereference, которая неявно вызывается в процессе разыменования каждого значения ссылочного типа.

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

Для типов, ассоциированных с отладочным пулом Debug_Pool динамическое распределение выполняется с помощью использования стандартных подпрограмм размещения динамической памяти GNAT. Ссылки ко всем фрагментам распределенной памяти сохраняются во внутреннем каталоге. Стратегия освобождения состоит не в освобождении памяти используемой системы, а в заполнении памяти шаблоном, который может быть легко распознан в процессе отладочной сессии. Шаблон соответствует старому двоично десятичному соглашению IBM и имеет значение 16#DEADBEEF#. При каждом разыменовании, осуществляется проверка того, что ссылочное значение указывает на правильно распределенное место памяти. Ниже показан полный пример использования отладочного пула Debug_Pool, который содержит типичный образец разрушения памяти:


@leftskip=0cm
with Gnat.Io; use Gnat.Io;
with Unchecked_Deallocation;
with Unchecked_Conversion;
with GNAT.Debug_Pools;
with System.Storage_Elements;
with Ada.Exceptions; use Ada.Exceptions;
procedure Debug_Pool_Test is

   type T is access Integer;
   type U is access all T;

   P : GNAT.Debug_Pools.Debug_Pool;
   for T'Storage_Pool use P;

   procedure Free is new Unchecked_Deallocation (Integer, T);
   function UC is new Unchecked_Conversion (U, T);
   A, B : aliased T;

   procedure Info is new GNAT.Debug_Pools.Print_Info(Put_Line);

begin
   Info (P);
   A := new Integer;
   B := new Integer;
   B := A;
   Info (P);
   Free (A);
   begin
      Put_Line (Integer'Image(B.all));
   exception
      when E : others => Put_Line ("raised: " & Exception_Name (E));
   end;
   begin
      Free (B);
   exception
      when E : others => Put_Line ("raised: " & Exception_Name (E));
   end;
   B := UC(A'Access);
   begin
      Put_Line (Integer'Image(B.all));
   exception
      when E : others => Put_Line ("raised: " & Exception_Name (E));
   end;
   begin
      Free (B);
   exception
      when E : others => Put_Line ("raised: " & Exception_Name (E));
   end;
   Info (P);
end Debug_Pool_Test;

Механизм отладочного пула предусматривает отображение следующей диагностики, при выполнении показанной выше программы с ошибками:


Debug Pool info:
  Total allocated bytes :  0
  Total deallocated bytes :  0
  Current Water Mark:  0
  High Water Mark:  0

Debug Pool info:
  Total allocated bytes :  8
  Total deallocated bytes :  0
  Current Water Mark:  8
  High Water Mark:  8

raised: GNAT.DEBUG_POOLS.ACCESSING_DEALLOCATED_STORAGE
raised: GNAT.DEBUG_POOLS.FREEING_DEALLOCATED_STORAGE
raised: GNAT.DEBUG_POOLS.ACCESSING_NOT_ALLOCATED_STORAGE
raised: GNAT.DEBUG_POOLS.FREEING_NOT_ALLOCATED_STORAGE
Debug Pool info:
  Total allocated bytes :  8
  Total deallocated bytes :  4
  Current Water Mark:  4
  High Water Mark:  8

4.5 Условная компиляция с помощью препроцессора gnatprep

Хотя разработчики Ады решили не включать в язык директивы поддержки препроцессора, подобные используемым в языке программирования C, GNAT предусматривает препроцессор gnatprep, который позволяет осуществлять условную компиляцию Ада-программ.

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

Следует обратить внимание на то, что директивы условной компиляции не допускают использования выражений (в отличие от препроцессора языка C). Таким образом, в директивах условной компиляции могут использоваться только переменные, которые могут принимать значения "истина" (True) или "ложь" (False).

Предположим, что у нас есть файл prepvalues со следующими определениями для препроцессора:


ALPHAVERSION    := True
BETAVERSION     := False
RELEASEVERSION  := False
TRANSLATION     := English

Предположим также, что у нас есть небольшая программа, текст которой содержит директивы препроцессора gnatprep:


with Text_IO;
use  Text_IO;

procedure Preptest is

    -- включить только ту часть кода, которая уместна для этой версии

    #if ALPHAVERSION
        S_Version : String := "Alpha";
    #elsif BETAVERSION
        S_Version : String := "Beta";
    #elsif RELEASEVERSION
        S_Version : String := "Release";
    #else
        S_Version : String := "Unknown";
    #end if;


    -- строковой переменной S_Translation будет присваиваться
    -- значение переменной препроцессора $TRANSLATION

    S_Translation : String := "$TRANSLATION";

begin

    Put_Line( "This is the " & S_Version & " edition" );
    Put_Line( "This is the " & S_Translation & " translation" );

end Preptest;

В результате обработки показанной выше программы препроцессором gnatprep, с учетом значений указанных в файле prepvalues, будет получен следующий исходный текст программы:


with Text_IO;
use  Text_IO;

procedure Preptest is

    -- включить только ту часть кода, которая уместна для этой версии

        S_Version : String := "Beta";

    -- строковой переменной S_Translation будет присваиваться
    -- значение переменной препроцессора $TRANSLATION

    S_Translation : String := "English";


begin

    Put_Line( "This is the " & S_Version & " edition" );
    Put_Line( "This is the " & S_Translation & " translation" );

end Preptest;

Препроцессор gnatprep может принимать следующие опции командной строки:

-Dsymbol=value  -  Позволяет объявить значение value для символа symbol в командной строке запуска препроцессора gnatprep, а не в файле (аналогично опции -D для препроцессора языка C). Например, -DMacintosh=FALSE.
-b  -  Заменить в выходном файле с исходным текстом команды препроцессора gnatprep пустыми строками (вместо -c).
-c  -  Закомментировать в выходном файле с исходным текстом команды препроцессора gnatprep вместо -b).
-r  -  Сгенерировать в выходном файле с исходным текстом директиву компилятора Source_Reference.
-s  -  Распечатать отсортированный список символов и их значений.
-u  -  Трактовать необъявленные символы как символы имеющие значение FALSE.

Следует заметить, что препроцессор gnatprep не имеет конструкций, эквивалентных __FILE__ (name of current source file) или __LINE__ (number of current line), которые поддерживаются препроцессором языка C.

4.6 Утилиты gnatpsys и gnatpsta

Множество описаний стандартных пакетов System и Standard зависят от конкретной реализации системы и используемой платформы. Исходный текст пакета Standard не доступен, а доступный исходный текст пакета System использует множество специальных атрибутов для параметризации различных критических значений и в некоторых случаях может оказаться не достаточно информативным.

Для подобных случаев предусматриваются специальные утилиты gnatpsys и gnatpsta (начиная с версии 3.15, утилита gnatpsys - не поставляется). Эти утилиты осуществляют динамическое определение значений параметров, которые зависят от конкретной реализации системы и используются пакетами System и Standard, после чего, отображают их в виде листингов с исходными текстами этих пакетов, показывая все фактические значения, которые могут быть интересны программисту.

Обе утилиты осуществляют вывод на стандартное устройство вывода используемой системы и не требуют указания каких-либо параметров в командной строке. Утилита gnatpsys предназначена для отображения листинга пакета System, а утилита gnatpsta отображает исходный текст пакета Standard.

4.7 Произвольное именование файлов, утилита gnatname

Как уже указывалось, компилятор должен обладать возможностью определить имя файла с исходным текстом компилируемого модуля. При использовании принимаемых по умолчанию, стандартных для GNAT соглашений именования (".ads" - для спецификаций, ".adb" - для тел), компилятор GNAT не нуждается в какой-либо дополнительной информации.

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

Ранее, при рассмотрении альтернативных схем именования, указывалось, что в случае хорошо организованной нестандартной схемы именования, для указания правил именования файлов с исходными текстами необходимо незначительное количество директив Source_File_Name. Однако, когда используемая схема именования не регулярна или произвольна, для указания имен файлов с исходными текстами может потребоваться значительное число директив Source_File_Name. Для облегчения поддержки взаимосвязи между именами компилируемых модулей и именами файлов с исходными текстами, система компилятора GNAT (версия 3.15 и более новые версии) предусматривает утилиту gnatname, которая предназначена для генерации необходимых директив для набора файлов.

Обычно запуск утилиты gnatname осуществляется с помощью команды, которая имеет следующий вид:


$ gnatname [опции] [шаблоны_имен]

Следует заметить, что все аргументы командной строки запуска gnatname не являются обязательными.

При использовании без аргументов, gnatname создаст в текущем каталоге файл gnat.adc, который будет содержать директивы конфигурации для всех компилируемых модулей расположенных в текущем каталоге. Для обнаружения всех компилируемых модулей, gnatname, для всех обычных файлов расположенных в текущем каталоге, использует запуск компилятора GNAT в режиме проверки синтаксиса. В результате, для файлов, которые содержат компилируемые модули Ады, будет осуществляться генерация директивы Source_File_Name.

В качестве аргументов gnatname могут быть указаны один или более шаблонов имен. Каждый шаблон имен должен быть заключен в двойные кавычки. Шаблон имен является регулярным выражением, которое используется для указания шаблона имен командными интерпретаторами UNIX (shell) или DOS. Ниже показаны примеры указания шаблонов имен:


   "*.[12].ada"
   "*.ad[sb]*"
   "body_*"    "spec_*"

Более полное описание синтаксиса, который используется для указания шаблонов имен, приводится при описании второго вида регулярных выражений, описанных в файле исходного текста GNAT g-regexp.ads (регулярные выражения "Glob").

Отсутствие в командной строке gnatname аргументов, которые указывают шаблоны имен, является эквивалентным указанию единственного шаблона имен "*".

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

В командной строке запуска gnatname могут быть указаны следующие опции:

-cfile   Создать файл директив конфигурации "file" (вместо создаваемого по умолчанию файла gnat.adc). Между "-c" и "file" допускается как отсутствие, так и присутствие пробелов (один и более). Указание файла "file" может содержать информацию о каталоге. Пользователь должен обладать правами записи в указываемый файл "file". В командной строке может присутствовать только одна опция "-c". При указании опции "-c" не допускается указание опции "-P".
-ddir   Осуществлять поиск файлов с исходными текстами в каталоге "dir". Между "-d" и "dir" допускается как отсутствие, так и присутствие пробелов (один и более). При указании опции "-d", поиск исходных файлов в текущем рабочем каталоге не осуществляется за исключением случаев когда текущий каталог явно указан в опции "-d" или "-D". В командной строке допускается указание нескольких опций "-d". Когда каталог "dir" указывается как относительный путь, то его расположение выбирается относительно каталога в котором располагается файл с директивами конфигурации указываемый с помощью опции "-c", или относительно каталога содержащего файл проекта указываемый с помощью опции "-P", или, при отсутствии опций "-c" и "-P", относительно текущего рабочего каталога. Каталог, указываемый опцией "-d", должен существовать и должен быть доступен для чтения.
-Dfile   Осуществлять поиск файлов с исходными текстами в каталогах, которые перечислены в текстовом файле "file". Между "-D" и "file" допускается как отсутствие, так и присутствие пробелов (один и более). Текстовый файл "file" должен существовать и должен быть доступен для чтения. В файле "file", каждая непустая строка должна указывать каталог. Указание опции "-D" является эквивалентом указания в командной строке такого же количества опций "-d", сколько непустых строк содержится в файле "file".
-h   Выводит информацию подсказки (help) об использовании. Вывод направляется в стандартное устройство вывода stdout.
-Pproj   Создать или обновить файл проекта "proj". Между "-P" и "proj" допускается как отсутствие, так и присутствие пробелов (один и более). Указание файла проекта "proj" может содержать информацию о каталоге. Файл "proj" должен быть доступен по записи. В командной строке может присутствовать только одна опция "-P". При указании опции "-P" не допускается указание опции "-c".
-v   "Многословный" (verbose) режим. Вывод детального объяснения поведения на устройство стандартного вывода stdout. Это включает: имя записонного файла; имена каталогов поиска и имена каждого файла, в этих каталогах поиска, которые соответствуют хотябы одному из указанных шаблонов имен; индикацию - является ли файл компилируемым модулем, и, если да, то вывод имени модуля.
-v -v   Очень "многословный" (verbose) режим. В дополнение к выводу информации генерируемой в многословном режиме, для каждого файла, который обнаружен в указанных каталогах поиска, имя которого не совпадает ни с одним из заданных шаблонов имен, выводится индикация о несовпадении имени файла.

В заключение рассмотрим несколько простых примеров команд запуска gnatname.

Команда запуска gnatname без параметров


$ gnatname

будет эквивалентна команде


$ gnatname -d. "*"

В следующем примере


$ gnatname -c /home/me/names.adc -d sources "[a-z]*.ada*"

каталог "/home/me" должен существовать и быть доступным для записи. Кроме того, каталог "/home/me/sources" (указанный как "-d sources") должен существовать и быть доступным для чтения. Следует заметить наличие необязательных пробелов после опций "-c" и "-d".

Еще один пример:


$ gnatname -P/home/me/proj -dsources -dsources/plus -Dcommon_dirs.txt "body_*" "spec_*"

Здесь следует обратить внимание на возможность одновременного использования нескольких опций "-d" совместно с одной (или более) опцией "-D". Кроме того, в этом примере используются несколько шаблонов имен.


Copyright (C) А.Гавва V-0.4w май 2004