Spec-Zone.ru › GCC 13

10.6 Профилирование и покрытие тестами в автономных средах

Если ваше приложение работает в хостированной среде, например, GNU/Linux, то этот раздел, скорее всего, для вас неактуален. Этот раздел предназначен для разработчиков приложений, ориентированных на автономные среды (например, встраиваемые системы) с ограниченными ресурсами. В частности, для систем или тестовых случаев, которые не поддерживают конструкторы/деструкторы или ввод/вывод файлов библиотеки C. В этом разделе целевая система выполняет ваше приложение, снабженное инструментарием для профилирования или покрытия тестами. Вы разрабатываете и анализируете своё приложение на хостовой системе. Теперь мы приведём обзор того, как можно получить профилирование и покрытие тестами в данной ситуации, а затем предоставим учебник, который можно использовать на хостовой системе. Наконец, перечислены некоторые особенности начальной настройки системы.

  • Обзор
  • Учебник
  • Особенности начальной настройки системы

10.6.1 Обзор

Для приложения, снабжённого инструментарием для профилирования или покрытия тестами, компилятор генерирует некоторые глобальные структуры данных, которые обновляются кодом инструментации во время работы приложения. Эти структуры данных называются информацией gcov. Обычно, при завершении работы приложения информация gcov сохраняется в файлы .gcda. Один файл на каждую единицу трансляции, снабжённую инструментами для профилирования или покрытия тестами. Функция __gcov_exit(), которая сохраняет информацию gcov в файл, вызывается глобальной функцией-деструктором для каждой единицы трансляции, снабжённой инструментами для профилирования или покрытия тестами. Она выполняется при выходе процесса. В глобальной функции-конструкторе функция __gcov_init() вызывается для регистрации информации gcov единицы трансляции в глобальном списке. В некоторых ситуациях этот процесс не работает. Во-первых, если вы хотите профилировать глобальную конструкцию или обработку выхода операционной системы, сгенерированные компилятором функции могут конфликтовать с целями тестирования. Во-вторых, вы можете захотеть протестировать ранние части начальной настройки системы или ненормальное поведение программы, которое не позволяет использовать глобальную конструкцию или обработку выхода. В-третьих, вам необходима файловая система для сохранения файлов.

Опция GCC -fprofile-info-section позволяет использовать профилирование и покрытие тестами в автономных средах. Эта опция отключает использование глобальных конструкторов и деструкторов для информации gcov. Вместо этого указатель на информацию gcov сохраняется в специальном разделе входных данных компоновщика для каждой единицы трансляции, скомпилированной с этой опцией. По умолчанию имя раздела равно .gcov_info. Информация gcov инициализируется статически. Указатели на информацию gcov из всех единиц трансляции исполняемого файла могут быть собраны компоновщиком в непрерывный блок памяти. Для компоновщика GNU можно использовать нижеследующее определение раздела выходных данных скрипта компоновщика:

.gcov_info      :
{
  PROVIDE (__gcov_info_start = .);
  KEEP (*(.gcov_info))
  PROVIDE (__gcov_info_end = .);
}

Компоновщик предоставит две глобальные символы, __gcov_info_start и __gcov_info_end, которые определяют начало и конец массива указателей на блоки информации gcov соответственно. Директива KEEP () требуется для предотвращения сбора мусора указателей. Они не напрямую ссылаются на что-либо в исполняемом файле. Раздел может быть размещён в области памяти только для чтения.

Для передачи данных профилирования и покрытия тестами от целевой системы к хостовой системе, приложение должно предоставить функцию для создания надёжного потока байтов в нужном порядке от целевой к хостовой системе. Поток байтов может быть сжат и закодирован с использованием кодов обнаружения и исправления ошибок, чтобы удовлетворить требованиям, специфичным для приложения. Библиотека целевой системы libgcov, предоставляемая GCC, предоставляет две функции, __gcov_info_to_gcda() и __gcov_filename_to_gcfn(), для генерации потока байтов из блока информации gcov. Функции объявлены в #include <gcov.h>. Поток байтов может быть десериализован подкомандой merge-stream команд gcov-tool для создания или обновления файлов .gcda в файловой системе хостовой системы для приложения, снабжённого инструментами.

10.6.2 Учебник

Этот учебник должен быть выполнен на хостовой системе. Мы создадим программу, снабжённую инструментами для покрытия тестами. Программа запускает приложение и выводит информацию gcov в stderr в виде потока печатаемых символов. Приложение просто декодирует подобные потоки символов из stdin и записывает декодированный поток символов в stdout (предупреждение: это двоичные данные). Декодированный поток символов потребляется подкомандой merge-stream команд gcov-tool для создания или обновления файлов .gcda.

Для начала создайте пустой каталог. Перейдите в этот новый каталог. Затем вы создадите следующие три файла в этом каталоге

  1. app.h - заголовочный файл, используемый в app.c и main.c,
  2. app.c - исходный файл, содержащий пример приложения, и
  3. main.c - исходный файл, содержащий главную функцию программы и код для вывода информации gcov.

Сначала создайте заголовочный файл app.h со следующим содержимым:

static const unsigned char a = 'a';

static inline unsigned char *
encode (unsigned char c, unsigned char buf[2])
{
  buf[0] = c % 16 + a;
  buf[1] = (c / 16) % 16 + a;
  return buf;
}

extern void application (void);

Во-вторых, создайте исходный файл app.c со следующим содержимым:

#include "app.h"

#include <stdio.h>

/* The application reads a character stream encoded by encode() from stdin,
   decodes it, and writes the decoded characters to stdout.  Characters other
   than the 16 characters 'a' to 'p' are ignored.  */

static int can_decode (unsigned char c)
{
  return (unsigned char)(c - a) < 16;
}

void
application (void)
{
  int first = 1;
  int i;
  unsigned char c;

  while ((i = fgetc (stdin)) != EOF)
    {
      unsigned char x = (unsigned char)i;

      if (can_decode (x))
        {
          if (first)
            c = x - a;
          else
            fputc (c + 16 * (x - a), stdout);
          first = !first;
        }
      else
        first = 1;
    }
}

В-третьих, создайте исходный файл main.c со следующим содержимым:

#include "app.h"

#include <gcov.h>
#include <stdio.h>
#include <stdlib.h>

/* The start and end symbols are provided by the linker script.  We use the
   array notation to avoid issues with a potential small-data area.  */

extern const struct gcov_info *const __gcov_info_start[];
extern const struct gcov_info *const __gcov_info_end[];

/* This function shall produce a reliable in order byte stream to transfer the
   gcov information from the target to the host system.  */

static void
dump (const void *d, unsigned n, void *arg)
{
  (void)arg;
  const unsigned char *c = d;
  unsigned char buf[2];

  for (unsigned i = 0; i < n; ++i)
    fwrite (encode (c[i], buf), sizeof (buf), 1, stderr);
}

/* The filename is serialized to a gcfn data stream by the
   __gcov_filename_to_gcfn() function.  The gcfn data is used by the
   "merge-stream" subcommand of the "gcov-tool" to figure out the filename
   associated with the gcov information. */

static void
filename (const char *f, void *arg)
{
  __gcov_filename_to_gcfn (f, dump, arg);
}

/* The __gcov_info_to_gcda() function may have to allocate memory under
   certain conditions.  Simply try it out if it is needed for your application
   or not.  */

static void *
allocate (unsigned length, void *arg)
{
  (void)arg;
  return malloc (length);
}

/* Dump the gcov information of all translation units.  */

static void
dump_gcov_info (void)
{
  const struct gcov_info *const *info = __gcov_info_start;
  const struct gcov_info *const *end = __gcov_info_end;

  /* Obfuscate variable to prevent compiler optimizations.  */
  __asm__ ("" : "+r" (info));

  while (info != end)
  {
    void *arg = NULL;
    __gcov_info_to_gcda (*info, filename, dump, allocate, arg);
    fputc ('\n', stderr);
    ++info;
  }
}

/* The main() function just runs the application and then dumps the gcov
   information to stderr.  */

int
main (void)
{
  application ();
  dump_gcov_info ();
  return 0;
}

Если мы скомпилируем app.c с покрытием тестами и без дополнительных опций профилирования, то используется глобальный конструктор (_sub_I_00100_0 здесь, он может иметь другое имя в вашей среде) и деструктор (_sub_D_00100_1) для регистрации и вывода информации gcov соответственно. Мы также видим неопределённые ссылки на __gcov_init и __gcov_exit.

$ gcc --coverage -c app.c
$ nm app.o
0000000000000000 r a
0000000000000030 T application
0000000000000000 t can_decode
                 U fgetc
                 U fputc
0000000000000000 b __gcov0.application
0000000000000038 b __gcov0.can_decode
0000000000000000 d __gcov_.application
00000000000000c0 d __gcov_.can_decode
                 U __gcov_exit
                 U __gcov_init
                 U __gcov_merge_add
                 U stdin
                 U stdout
0000000000000161 t _sub_D_00100_1
0000000000000151 t _sub_I_00100_0

Скомпилируйте app.c и main.c с покрытием тестами и -fprofile-info-section. Теперь присутствует объект размера указателя только для чтения в разделе .gcov_info и нет неопределённых ссылок на __gcov_init и __gcov_exit.

$ gcc --coverage -fprofile-info-section -c main.c
$ gcc --coverage -fprofile-info-section -c app.c
$ objdump -h app.o

app.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000151  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000100  0000000000000000  0000000000000000  000001a0  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  2 .bss          00000040  0000000000000000  0000000000000000  000002a0  2**5
                  ALLOC
  3 .rodata       0000003c  0000000000000000  0000000000000000  000002a0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .gcov_info    00000008  0000000000000000  0000000000000000  000002e0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  5 .comment      0000004e  0000000000000000  0000000000000000  000002e8  2**0
                  CONTENTS, READONLY
  6 .note.GNU-stack 00000000  0000000000000000  0000000000000000  00000336  2**0
                  CONTENTS, READONLY
  7 .eh_frame     00000058  0000000000000000  0000000000000000  00000338  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

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

$ ld --verbose | sed '1,/^===/d' | sed '/^===/d' > linkcmds

Затем откройте файл linkcmds с помощью текстового редактора и поместите определение раздела выходных данных компоновщика из раздела «Обзор» после определения раздела .rodata. Скомпонуйте исполняемый файл программы, используя настроенный скрипт компоновщика:

$ gcc --coverage main.o app.o -T linkcmds -Wl,-Map,app.map

В файле карты компоновщика app.map мы видим, что компоновщик разместил объекты размера указателей только для чтения наших объектных файлов main.o и app.o в непрерывный блок памяти и предоставил символы __gcov_info_start и __gcov_info_end.

$ grep -C 1 "\.gcov_info" app.map

.gcov_info      0x0000000000403ac0       0x10
                0x0000000000403ac0                PROVIDE (__gcov_info_start = .)
 *(.gcov_info)
 .gcov_info     0x0000000000403ac0        0x8 main.o
 .gcov_info     0x0000000000403ac8        0x8 app.o
                0x0000000000403ad0                PROVIDE (__gcov_info_end = .)

Убедитесь, что файлы .gcda отсутствуют. Запустите программу без декодирования и выведите stderr в файл gcda-0.txt (первый запуск). Запустите программу для декодирования gcda-0.txt и отправьте её в gcov-tool с помощью подкоманды merge-stream для создания файлов .gcda (второй запуск). Запустите gcov для создания отчёта для app.c. Мы видим, что первый запуск без декодирования приводит к частично покрытому приложению:

$ rm -f app.gcda main.gcda
$ echo "" | ./a.out 2>gcda-0.txt
$ ./a.out <gcda-0.txt 2>gcda-1.txt | gcov-tool merge-stream
$ gcov -bc app.c
File 'app.c'
Lines executed:69.23% of 13
Branches executed:66.67% of 6
Taken at least once:50.00% of 6
Calls executed:66.67% of 3
Creating 'app.c.gcov'

Lines executed:69.23% of 13

Запустите программу для декодирования gcda-1.txt и отправьте её в gcov-tool с помощью подкоманды merge-stream для обновления файлов .gcda. Запустите gcov для создания отчёта для app.c. Поскольку второй запуск декодировал информацию gcov первого запуска, у нас теперь полностью покрыто приложение:

$ ./a.out <gcda-1.txt 2>gcda-2.txt | gcov-tool merge-stream
$ gcov -bc app.c
File 'app.c'
Lines executed:100.00% of 13
Branches executed:100.00% of 6
Taken at least once:100.00% of 6
Calls executed:100.00% of 3
Creating 'app.c.gcov'

Lines executed:100.00% of 13

10.6.3 Особенности начальной настройки системы

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

© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-13.3.0/gcc/Freestanding-Environments.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API