Spec-Zone.ru › GCC 15

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

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

  • Обзор
  • Руководство
  • Особенности инициализации системы

11.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 () необходима, чтобы предотвратить удаление указателей при сборке мусора. На них нет прямых ссылок из исполняемого файла. Секцию можно разместить в области памяти только для чтения.

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

11.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 для сбора данных о покрытии кода без дополнительных параметров профилирования, то для регистрации и вывода данных gcov используются глобальный конструктор (здесь это _sub_I_00100_0; в вашей среде его имя может отличаться) и деструктор (_sub_D_00100_1) соответственно. Также видны неопределённые ссылки на __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

11.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-15.3.0/gcc/Freestanding-Environments.html

Spec-Zone.ru

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