GCC Profile-guided optimization / Хабр

Что проверяется в таких случаях?

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

  1. Наличие организации в международных желтых страницах — проверяется не всеми центрами сертифации
  2. Наличие в whois домена названия вашей организации — а вот это уже проверят обязательно, и если такое название там не указано от вас скорей всего затребуют гарантийное письмо, в котором нужно указать, что домен действительно принадлежит организации, иногда могут затребовать подтверждение от регистратора
  3. Свидетельство о государственной регистрации — требуют все реже, чаще сейчас производится проверка через специальные компании, которые производят проверку существования организации по своим каналам. Например для Украины вас могут проверить по базе ЕДРПОУ
  4. Счет от телефонной компании, в которой содержится название вашей организации и ваш номер телефона, указанный в заказе — таким образом проверяется валидность вашего телефона. Требуют все реже.
  5. Проверочный звонок — все чаще правильность телефона проверяют осуществляя звонок, на номер телефона, указанный вами в заказе. При звонке спросят сотрудника, указанного в административном контакте. Не у всех центров сертификации есть русскоговорящие сотрудники, поэтому предупредите человека, который отвечает на телефон, что возможен звонок от англоязычной компании.

Что такое центры сертификации (ca)?

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

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

Говоря в общем, SSL сертификаты содержат и отображают (как минимум одно из) ваше доменное имя, ваше название организации, ваш адрес, город и страницу. Также сертификат всегда имеет дату окончания и данные о центре сертификации, ответственного за выпуск сертификата.

Браузер подключается к защищенному сайту, получает от него SSL сертификат и делает ряд проверок: он не просрочен ли сертификат, потом он проверяет, выпущен ли сертификат известным ему центром сертификации (CA) используется ли сертификат на сайте, для которого он был выпущен.

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

Центров сертификации существует достаточно много, вот перечень самых популярных:Comodo — работает с 1998 штабквартира в Jersey City, New Jersey, США.Geotrust — основан в 2001, в 2006 продан Verisign, штабквартира Mountain View, California, СШАSymantec — бывший Verisign в состав которого входит и Geotrust.

Как видим самый крупный игрок на рынке SSL сертификатов это Symantec, который владеет тремя крупнейшими центрами сертификации — Thawte, Verisgin и Geotrust.

Assert, который ничего не проверят

Неправильное условие, являющееся аргументом макроса

gcc_assert

не повлияет на корректность работы программы, но усложнит поиск ошибки, если таковая возникнет. Рассмотрим код:

static void
output_loc_operands (dw_loc_descr_ref loc, int for_eh_or_skip)
{
  unsigned long die_offset
    = get_ref_die_offset (val1->v.val_die_ref.die);
  ....
  gcc_assert (die_offset > 0
        && die_offset <= (loc->dw_loc_opc == DW_OP_call2)
             ? 0xffff
             : 0xffffffff);
  ....
}

Предупреждение анализатора PVS-Studio:

Perhaps the ‘?:’ operator works in a different way than it was expected. The ‘?:’ operator has a lower priority than the ‘<=’ operator. dwarf2out.c 2053

Приоритет тернарного оператора ?: ниже, чем у оператора сравнения <=. Это значит, что мы имеем дело с условием вида:

die_offset > 0 &&
  ((die_offset <= (loc->dw_loc_opc == DW_OP_call2)) ?
    0xffff : 0xffffffff);


Таким образом, второй операнд оператора

&&

может принимать значение

0xffff

или

0xffffffff

. Оба эти значения обозначают истину, поэтому выражение можно упростить до:

(die_offset > 0)


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

gcc_assert (die_offset > 0
      && die_offset <= ((loc->dw_loc_opc == DW_OP_call2)
           ? 0xffff
           : 0xffffffff));

Оператор

?:

очень коварен и его лучше не использовать в сложных выражениях. Уж очень легко допустить ошибку. У нас

большое количество примеров таких ошибок, найденных анализатором PVS-Studio в различных открытых проектах. Подробнее об операторе

?:

я писал в уже упомянутой ранее

(см. главу N4: Бойтесь оператора ?: и заключайте его в круглые скобки).

Где avr-gcc не прав

Я показал, что реальные пенальти по SRAM от активного использования виртуальных функций — это 2 байта на экземпляр. Очень адекватно за столь богатые возможности. Но что делает avr-gcc? Он пихает сами виртуальные таблицы в SRAM! Из-за этого появление каждого нового класса с виртуальными функциями, его наследника или даже интерфейса (pure abstract class) приводит к увеличению потребляемой SRAM.

Это совершенно не обоснованно, т.к. виртуальные таблицы не могут меняться по ходу исполнения программы. Им самое место в Flash-памяти, которая обычно «заканчивается» куда позже, чем SRAM. Это тема 100 раз поднималась в разныхсообществах.

Ирония в том, что эти таблицы и так уже размещаются в Flash, а в момент старта контроллера копируются ещё и в SRAM. В генерируемом ASM для получения адреса реализации функции нужно «просто» использовать не `ldd`, а `lpm`, т.е. ходить за адресом не в копию таблицы в SRAM, а в её оригинал на Flash.

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

Добавляем virtual


Теперь давайте добавим полиморфизма. Сделаем наши методы виртуальными:

volatile unsigned char var;

class Base
{
    public:
        virtual void foo() { var  = 19; }
        virtual void bar() { var  = 29; }
        virtual void baz() { var  = 39; }
};

class DerivedOne : public Base
{
    public:
        virtual void foo() { var  = 17; }
        virtual void bar() { var  = 27; }
        //virtual void baz() { var  = 37; }
};

class DerivedTwo : public Base
{
    public:
        virtual void foo() { var  = 18; }
        //virtual void bar() { var  = 28; }
        virtual void baz() { var  = 38; }
};

DerivedOne dOne = DerivedOne();
DerivedTwo dTwo = DerivedTwo();

int main()
{
    Base* b;
    if (var)
        b = &dOne;
    else
        b = &dTwo;

    asm("nop");
    b->foo();

    for (;;)
        ;

    return 0;
}

Проверяем:

AVR Memory Usage
----------------
Device: Unknown

Program:     312 bytes
(.text   .data   .bootloader)

Data:         25 bytes
(.data   .bss   .noinit)

Ого-го! 25 байт SRAM как не бывало. Легко проверить, что создание очередного экземпляра класса съест ещё 2 байта. Эти 2 байта — указатель на таблицу виртуальных функций, которая и позволяет при вызове метода по указателю на базовый класс исполнять конкретную реализацию номинального дочернего класса.

Но ведь у нас всего 2 глобальных объекта и одна несчастная переменная на 1 байт. Кто сожрал всю остальную память? Вот мы и подошли к сути проблемы. Это сами виртуальные таблицы. По штуке на каждый класс. Размер каждой линейно зависит от количества виртуальных функций.

Дубликаты присваиваний


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

Случай N1

type_p
find_structure (const char *name, enum typekind kind)
{
  ....
  structures = s;                   // <=
  s->kind = kind;
  s->u.s.tag = name;
  structures = s;                   // <=
  return s;
}

Предупреждение анализатора PVS-Studio:

V519 The ‘structures’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 842, 845. gengtype.c 845

Случай N2

static rtx
ix86_expand_sse_pcmpistr (....)
{
  unsigned int i, nargs;
  ....
    case V8DI_FTYPE_V8DI_V8DI_V8DI_INT_UQI:
    case V16SI_FTYPE_V16SI_V16SI_V16SI_INT_UHI:
    case V2DF_FTYPE_V2DF_V2DF_V2DI_INT_UQI:
    case V4SF_FTYPE_V4SF_V4SF_V4SI_INT_UQI:
    case V8SF_FTYPE_V8SF_V8SF_V8SI_INT_UQI:
    case V8SI_FTYPE_V8SI_V8SI_V8SI_INT_UQI:
    case V4DF_FTYPE_V4DF_V4DF_V4DI_INT_UQI:
    case V4DI_FTYPE_V4DI_V4DI_V4DI_INT_UQI:
    case V4SI_FTYPE_V4SI_V4SI_V4SI_INT_UQI:
    case V2DI_FTYPE_V2DI_V2DI_V2DI_INT_UQI:
      nargs = 5;         // <=
      nargs = 5;         // <=
      mask_pos = 1;
      nargs_constant = 1;
      break;
  ....
}

Предупреждение анализатора PVS-Studio:

V519 The ‘nargs’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 39951, 39952. i386.c 39952

Случай N3

Последний случай более странный, чем остальные. Возможно, тут есть какая-то ошибка. Переменной steptype значение присваивается 2 или 3 раза. Это подозрительно.

static void
cand_value_at (....)
{
  aff_tree step, delta, nit;
  struct iv *iv = cand->iv;
  tree type = TREE_TYPE (iv->base);
  tree steptype = type;                 // <=
  if (POINTER_TYPE_P (type))
    steptype = sizetype;                // <=
  steptype = unsigned_type_for (type);  // <=
  ....
}

Предупреждение анализатора PVS-Studio:

Про сертификаты:  Арболит: достоинства и недостатки — Реальное время

V519 The ‘steptype’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 5173, 5174. tree-ssa-loop-ivopts.c 5174

Есть ли разница в каком центре сертификации заказывать сертификат?

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

Что касается перечисленных выше центров сертификации, то их корневые сертификаты установлены в, пожалуй, 99,99% всех существующих браузеров.

Чтобы проверить, корневые сертификаты каких центров сертификации установлены в вашем браузере, достаточно в настройках вашего браузера найти такую опцию. (В Chrome Настройки -> показать дополнительные настройки -> управление сертификатами ->

Важный момент — частенько у клиентов возникала ситуация, когда SSL сертификат на серверe установлен, но при заходе на сайт браузер все равно выдает ошибку. Такая ситуация может возникнуть или из-за отсутствия в файле ca-bundle.crt корневого сертификата центра выдавшего сертификат или из-за того, что корневой сертификат устарел. Корневые сертификаты также имеют свой срок действия (в браузерах они обновляются при обновлении браузера).

Использование разрушенного массива

static void
dump_hsa_symbol (FILE *f, hsa_symbol *symbol)
{
  const char *name;
  if (symbol->m_name)
    name = symbol->m_name;
  else
  {
    char buf[64];
    sprintf (buf, "__%s_%i", hsa_seg_name (symbol->m_segment),
       symbol->m_name_number);
     name = buf;
  }
  fprintf (f, "align(%u) %s_%s %s",
           hsa_byte_alignment (symbol->m_align),
           hsa_seg_name(symbol->m_segment),
           hsa_type_name(symbol->m_type & ~BRIG_TYPE_ARRAY_MASK),
           name);
  ....
}

Предупреждение анализатора PVS-Studio:

Pointer to local array ‘buf’ is stored outside the scope of this array. Such a pointer will become invalid. hsa-dump.c 704

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

Использовать указатель на разрушенный буфер нельзя. Формально мы имеем дело с неопределённым поведением. На практике этот код может вполне успешно работать. Корректная работа программы — это один из вариантов проявления неопределенного поведения.

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

Чтобы исправить ошибку, достаточно объявить массив buf в той же области видимости, что и указатель name:

static void
dump_hsa_symbol (FILE *f, hsa_symbol *symbol)
{
  const char *name;
  char buf[64];
  ....
}

Кажется, забыли про «cost»

Структура

alg_hash_entry

объявлена следующим образом:

struct alg_hash_entry {
  unsigned HOST_WIDE_INT t;
  machine_mode mode;
  enum alg_code alg;
  struct mult_cost cost;
  bool speed;
};


В функции

synth_mult

программист решил проверить, тот ли это объект, который ему нужен. Для этого ему требуется сравнить поля структуры. Однако, кажется в этом месте допущена ошибка:

static void synth_mult (....)
{
  ....
  struct alg_hash_entry *entry_ptr;
  ....
  if (entry_ptr->t == t
      && entry_ptr->mode == mode
      && entry_ptr->mode == mode
      && entry_ptr->speed == speed
      && entry_ptr->alg != alg_unknown)
  {
  ....
}

Предупреждение анализатора PVS-Studio:

V501 There are identical sub-expressions ‘entry_ptr->mode == mode’ to the left and to the right of the ‘&&’ operator. expmed.c 2573

Два раза подряд проверяется mode, но зато нет проверки cost. Возможно, одно из сравнений нужно просто удалить, а возможно, нужно сравнивать cost. Мне сложно судить, но код явно стоит поправить.

Как получить максимальную производительность?

Определенного набора опций для получения максимальной проивзодительности не существует, однако в GCC есть много опций, которые стоит попробовать использовать. Ниже представлена таблица с рекомендуемыми опциями и прогнозами прироста для процессоров Intel Atom и 2nd Generation Intel Core i7 относительно опции “-O2”.

     Прогноз увеличения производительности на мобильных приложениях относительно “-O2” (только в 32 битном режиме, так как он основной для мобильного сегмента):

     Прогноз увеличения производительности на вычислительных задачах относительно “-O2” (в 32 битном режиме):

     Прогноз увеличения производительности на вычислительных задачах относительно “-O2” (в 64 битном режиме):


Преимущество 64 битного режима над 32 битным для вычислительных задач при опциях “-O2 -mfpmath=sse” составляет около

~5%Все данные в статье являются прогнозом, основанном на результатах определенного набора бенчмарков.

Ниже представлено описание используемых в статье опций. Полное описание (на английском):

Какие новые предупреждения будут?

Наряду с детектированием double-free, проводятся проверки на утечки

malloc fopen

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

void test(const char *filename)
{
  FILE *f = fopen(filename, "r");
  void *p = malloc(1024);
  /* do stuff */
}
$ gcc -c -fanalyzer leak.c
leak.c: In function ‘test’:
leak.c:9:1: warning: leak of ‘p’ [CWE-401] [-Wanalyzer-malloc-leak]
    9 | }
      | ^
  ‘test’: events 1-2
    |
    |    7 |   void *p = malloc(1024);
    |      |             ^~~~~~~~~~~~
    |      |             |
    |      |             (1) allocated here
    |    8 |   /* do stuff */
    |    9 | }
    |      | ~
    |      | |
    |      | (2) ‘p’ leaks here; was allocated at (1)
    |
leak.c:9:1: warning: leak of FILE ‘f’ [CWE-775] [-Wanalyzer-file-leak]
    9 | }
      | ^
  ‘test’: events 1-2
    |
    |    6 |   FILE *f = fopen(filename, "r");
    |      |             ^~~~~~~~~~~~~~~~~~~~
    |      |             |
    |      |             (1) opened here
    |......
    |    9 | }
    |      | ~
    |      | |
    |      | (2) ‘f’ leaks here; was opened at (1)
    |

Контроль использования памяти после ее освобождения:

#include <stdlib.h>

struct link { struct link *next; };

int free_a_list_badly(struct link *n)
{
  while (n) {
    free(n);
    n = n->next;
  }
}
$ gcc -c -fanalyzer use-after-free.c
use-after-free.c: In function ‘free_a_list_badly’:
use-after-free.c:9:7: warning: use after ‘free’ of ‘n’ [CWE-416] [-Wanalyzer-use-after-free]
    9 |     n = n->next;
      |     ~~^~~~~~~~~
  ‘free_a_list_badly’: events 1-4
    |
    |    7 |   while (n) {
    |      |         ^
    |      |         |
    |      |         (1) following ‘true’ branch (when ‘n’ is non-NULL)...
    |    8 |     free(n);
    |      |     ~~~~~~~
    |      |     |
    |      |     (2) ...to here
    |      |     (3) freed here
    |    9 |     n = n->next;
    |      |     ~~~~~~~~~~~
    |      |       |
    |      |       (4) use after ‘free’ of ‘n’; freed at (3)
    |


Контроль освобождения указателя не на кучу (heap):

#include <stdlib.h>

void test(int n)
{
  int buf[10];
  int *ptr;

  if (n < 10)
    ptr = buf;
  else
    ptr = (int *)malloc(sizeof (int) * n);

  /* do stuff.  */

  /* oops; this free should be conditionalized.  */
  free(ptr);
}
$ gcc -c -fanalyzer heap-vs-stack.c
heap-vs-stack.c: In function ‘test’:
heap-vs-stack.c:16:3: warning: ‘free’ of ‘ptr’ which points to memory not on the heap [CWE-590] [-Wanalyzer-free-of-non-heap]
   16 |   free(ptr);
      |   ^~~~~~~~~
  ‘test’: events 1-4
    |
    |    8 |   if (n < 10)
    |      |      ^
    |      |      |
    |      |      (1) following ‘true’ branch (when ‘n <= 9’)...
    |    9 |     ptr = buf;
    |      |     ~~~~~~~~~
    |      |         |
    |      |         (2) ...to here
    |      |         (3) pointer is from here
    |......
    |   16 |   free(ptr);
    |      |   ~~~~~~~~~
    |      |   |
    |      |   (4) call to ‘free’ here
    |

Контроль использования функции, которая, как известно, небезопасна для использования внутри обработчика

signal

#include <stdio.h>
#include <signal.h>

extern void body_of_program(void);

void custom_logger(const char *msg)
{
  fprintf(stderr, "LOG: %s", msg);
}

static void handler(int signum)
{
  custom_logger("got signal");
}

int main(int argc, const char *argv)
{
  custom_logger("started");

  signal(SIGINT, handler);

  body_of_program();

  custom_logger("stopped");

  return 0;
}
$ gcc -c -fanalyzer signal.c
signal.c: In function ‘custom_logger’:
signal.c:8:3: warning: call to ‘fprintf’ from within signal handler [CWE-479] [-Wanalyzer-unsafe-call-within-signal-handler]
    8 |   fprintf(stderr, "LOG: %s", msg);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ‘main’: events 1-2
    |
    |   16 | int main(int argc, const char *argv)
    |      |     ^~~~
    |      |     |
    |      |     (1) entry to ‘main’
    |......
    |   20 |   signal(SIGINT, handler);
    |      |   ~~~~~~~~~~~~~~~~~~~~~~~
    |      |   |
    |      |   (2) registering ‘handler’ as signal handler
    |
  event 3
    |
    |cc1:
    | (3): later on, when the signal is delivered to the process
    |
     --> ‘handler’: events 4-5
           |
           |   11 | static void handler(int signum)
           |      |             ^~~~~~~
           |      |             |
           |      |             (4) entry to ‘handler’
           |   12 | {
           |   13 |   custom_logger("got signal");
           |      |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
           |      |   |
           |      |   (5) calling ‘custom_logger’ from ‘handler’
           |
            --> ‘custom_logger’: events 6-7
                  |
                  |    6 | void custom_logger(const char *msg)
                  |      |      ^~~~~~~~~~~~~
                  |      |      |
                  |      |      (6) entry to ‘custom_logger’
                  |    7 | {
                  |    8 |   fprintf(stderr, "LOG: %s", msg);
                  |      |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                  |      |   |
                  |      |   (7) call to ‘fprintf’ from within signal handler
                  |

Наряду и с

Какие опции в gcc по умолчанию?

(1)

По умолчанию в GCC используется уровень оптимизаций “-O0”. Он явно не оптимален с точки зрения производительности и не рекомендуется для компиляции конечного продукта.


GCC не распознает архитектуру, на которой запускается компиляция, пока не передана опция ”-march=native”. По умолчанию GCC использует опцию, заданную при его конфигурации. Чтобы узнать конфигурацию GCC, достаточно запустить:

Это означает что GCC добавит “-march=corei7” к вашим опциям (если не будет указана другая архитектура).

Большинство GCC компиляторов для x86 (базовый для 64 битного Linux) добавляет: “-mtune=generic -march=x86-64” к заданным опциям, так как при конфигурации не были заданы опции, определяющие архитектуру. Вы всегда можете узнать все опции, передаваемые при запуске GCC, а также его внутренние опции при помощи команды:


В итоге, часто используемое:

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

Для указания текущей архитектуры надо компилировать так:

Указание используемой архитектуры важно для производительности.

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

Про сертификаты:  Об утверждении профессионального стандарта "Спасатель на акватории" от 07 декабря 2020 -

То есть динамическая сборка лучше, если важна быстрота GLIBC функций.(2)

По умолчанию большинство GCC компиляторов для x86 в 32 битном режиме используют x87 модель вычислений с плавающей точкой, так как были сконфигурированы без “-mfpmath=sse”. Только если GCC конфигурация содержит “–with-mfpmath=sse”:

компилятор будет использовать SSE модель по умолчанию.Во всех остальных случаях лучше добавлять опцию “-mfpmath=sse” к сборке в 32 битном режиме.

Так, часто используемое:

может привести к существенным потерям производительности в коде с вещественной арифметикой. Потому правильный вариант:

Добавление опции ”-mfpmath=sse” важно в 32 битном режиме! Исключением является компилятор, в конфигурации которого есть “–with-mfpmath=sse”.

Классика (copy-paste)


Начнем мы с самой классической и распространённой ошибки, которая выявляется с помощью диагностики

. Как правило, такие ошибки появляются из-за невнимательности при Copy-Paste или просто являются опечатками, допускаемыми при наборе нового кода.

static bool
dw_val_equal_p (dw_val_node *a, dw_val_node *b)
{
  ....
  case dw_val_class_vms_delta:
    return (!strcmp (a->v.val_vms_delta.lbl1,
                     b->v.val_vms_delta.lbl1)
            && !strcmp (a->v.val_vms_delta.lbl1,
                        b->v.val_vms_delta.lbl1));
  ....
}

Предупреждение анализатора PVS-Studio:

V501 There are identical sub-expressions ‘!strcmp(a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1)’ to the left and to the right of the ‘&&’ operator. dwarf2out.c 1428

Быстро увидеть ошибки проблематично и следует внимательно присмотреться. Именно поэтому ошибка и не была выявлена при обзорах кода и рефакторинге.

Функция strcmp дважды сравнивает одни и те же строки. Мне кажется, второй раз следовало сравнивать не члены класса lbl1, а lbl2. Тогда корректный код должен выглядеть так:

return (!strcmp (a->v.val_vms_delta.lbl1,
                 b->v.val_vms_delta.lbl1)
        && !strcmp (a->v.val_vms_delta.lbl2,
                    b->v.val_vms_delta.lbl2));

Хочу отметить, что код, приведённый в статье, немного отформатирован, чтобы он занимал мало места по оси X. На самом деле, код выглядит так:

Ошибки, возможно, удалось бы избежать, если использовать «табличное» выравнивание кода. Например, ошибку было бы легче заметить, если отформатировать код так:

Подробнее я рассматривал такой подход в электронной книге “Главный вопрос программирования, рефакторинга и всего такого” (см. главу N13: Выравнивайте однотипный код «таблицей»). Рекомендую всем, кто заботится о качестве своего кода, познакомиться с приведённой здесь ссылкой.

Давайте рассмотрим ещё одну ошибку, которая, я уверен, появилась из-за Copy-Paste:

const char *host_detect_local_cpu (int argc, const char **argv)
{
  unsigned int has_avx512vl = 0;
  unsigned int has_avx512ifma = 0;
  ....
  has_avx512dq = ebx & bit_AVX512DQ;
  has_avx512bw = ebx & bit_AVX512BW;
  has_avx512vl = ebx & bit_AVX512VL;       // <=
  has_avx512vl = ebx & bit_AVX512IFMA;     // <=
  ....
}

Предупреждение анализатора PVS-Studio:

The ‘has_avx512vl’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 500, 501. driver-i386.c 501

В переменную has_avx512vl дважды подряд записываются различные значения. Это не имеет смысла. Я изучил код и обнаружил переменную has_avx512ifma. Скорее всего, именно она и должна инициализироваться выражением ebx & bit_AVX512IFMA. Тогда корректный код должен быть таким:

has_avx512vl   = ebx & bit_AVX512VL;    
has_avx512ifma = ebx & bit_AVX512IFMA;

Опечатка


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

static bool
ubsan_use_new_style_p (location_t loc)
{
  if (loc == UNKNOWN_LOCATION)
    return false;

  expanded_location xloc = expand_location (loc);
  if (xloc.file == NULL || strncmp (xloc.file, "1", 2) == 0
      || xloc.file == '' || xloc.file[0] == 'xff'
      || xloc.file[1] == 'xff')
    return false;

  return true;
}

Предупреждение анализатора PVS-Studio:

It is odd that pointer to ‘char’ type is compared with the ” value. Probably meant: *xloc.file == ”. ubsan.c 1472

Здесь программист случайно забыл разыменовать указатель в выражении xloc.file == ”. В результате указатель просто сравнивается с 0, т.е. с NULL. Никакого эффекта это не имеет, так как ранее такая проверка уже выполнялась: xloc.file == NULL.

Правильный вариант кода:

if (xloc.file == NULL || strncmp (xloc.file, "1", 2) == 0
    || xloc.file[0] == '' || xloc.file[0] == 'xff'
    || xloc.file[1] == 'xff')
  return false;


Хотя, давайте ещё немного улучшим код. Я рекомендую отформатировать выражение так:

if (   xloc.file == NULL
    || strncmp (xloc.file, "1", 2) == 0
    || xloc.file[0] == ''
    || xloc.file[0] == 'xff'
    || xloc.file[1] == 'xff')
  return false;

Обратите внимание: теперь, если допустить ту же ошибку, шанс её заметить будет чуть-чуть выше:

if (   xloc.file == NULL
    || strncmp (xloc.file, "1", 2) == 0
    || xloc.file == ''
    || xloc.file[0] == 'xff'
    || xloc.file[1] == 'xff')
  return false;

По какому принципу работает ssl сертификат?

Итак для того, чтобы получить SSL сертификат самое первое, что нужно сделать, это сформировать специальный запрос на выпуск сертификата, так называемый (Certificate Signing Request). При формировании этого запроса вам будет задан ряд вопросов, для уточнения деталей о вашем домене и вашей компании. После завершения ваш веб сервер создаст 2 типа криптографических ключей — приватный ключ и публичный ключ.

Публичный ключ не является секретным и он помещается в запрос CSR.Вот пример такого запроса:—–BEGIN CERTIFICATE REQUEST—–MIIC3zCCAccCAQAwgZkxCzAJBgNVBAYTAlVBMQ0wCwYDVQQIEwRLaWV2MQ0wCwYDVQQHEwRLaWV2MRQwEgYDVQQKEwtIb3N0QXV0b21hdDEQMA4GA1UECxMHaG9zdGluZzEmMCQGCSqGSIb3DQEJARYXc3VwcG9ydEBob3N0YXV0b21hdC5jb20xHDAaBgNVBAMTE3d3dy5ob3N0YXV0b21hdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTg7iUv/iX SyZl74GcUVFHjFC5IqlTNEzWgLWrsSmxGxlGzXkUKidNyXWa0O3ayJHOiv1BSX1l672tTqeHxhGuM6F7l5FTRWUyFHUxSU2Kmci6vR6fw5ccgWOMMNdMg7V5bMOD8tfI74oBkVE7hV95Ds3c594u7kMLvHR xui2S3z2JJQEwChmflIojGnSCO/iv64RL9vjZ5B4jAWJwrruIXO5ILTdis41Z1nNIx3bBqkif0H/G4eO5WF6fFb7etm8M d8ebkqEztRAVdhXvTGBZ4Mt2DOV/bV4e/ffmQJxffTYEqWg8wb465GdAJcLhhiSaHgqRzrprKns7QSGjdAgMBAAGgADANBgkqhkiG9w0BAQUFAAOCAQEAuCfJKehyjt7N1IDv44dd V61MIqlDhna0LCXH1uT7R9H8mdlnuk8yevEcCRIkrnWAlA9GT3VkOY3Il4WTGg3wmtq6WAgLkVXQnhIpGDdYAflpAVeMKil8Z46BGIhKQGngL2PjWdhMVLlRTB/01nVSKSEk2jhO8 7yLOY1MoGIvwAEF4CL1lAjov8U4XGNfQldSWT1o8z9sDeGsGSf5DAXpcccx0gCyk90HFJxhbm/vTxjJgchUFro/0goVpBcredpKxtkwBMuCzeSyDnkQft0eLtZ9b9Q4 ZNDWsPPKxo/zWHm6Pa/4F4o2QKvPCPx9x4fm /xHqkhkR79LxJ EHzQ==—–END CERTIFICATE REQUEST—–

Данные которые содержатся в этом ключе можно легко проверить с помощью сервисов CSR Decoder. Как пример: CSR Decoder 1 или CSR Decoder 2. Второй сервис выдает больше информации о CSR и проверяет ее на валидность, поле Signature в результатах проверки.

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

Потенциальное разыменование нулевого указателя

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

Как мне показалось из недолгого общения с кодом GCC, его авторы очень любят макросы. Я замучался изучать, во что раскрывается тот или иной макрос и возможно поэтому пропустил немало интересных ошибок. Признаюсь, я иногда бываю ленив. Но пару ошибок, связанных с макросами, я всё-таки продемонстрирую.

odr_type
get_odr_type (tree type, bool insert)
{
  ....
  odr_types[val->id] = 0;
  gcc_assert (val->derived_types.length() == 0);
  if (odr_types_ptr)
    val->id = odr_types.length ();
  ....
}

Предупреждение анализатора PVS-Studio:

The ‘odr_types_ptr’ pointer was utilized before it was verified against nullptr. Check lines: 2135, 2139. ipa-devirt.c 2135

Видите здесь ошибку? Думаю, нет, и сообщение анализатора ясности не вносит. Всё дело в том, что odr_types — это не имя переменной, а макрос, объявленным следующим образом:

#define odr_types (*odr_types_ptr)

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

(*odr_types_ptr)[val->id] = 0;
if (odr_types_ptr)


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

nullptr

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

Рассмотрим ещё один аналогичный случай:

static inline bool
sd_iterator_cond (sd_iterator_def *it_ptr, dep_t *dep_ptr)
{
  ....
  it_ptr->linkp = &DEPS_LIST_FIRST (list);
  if (list)
    continue;
  ....
}

Предупреждение анализатора PVS-Studio:

V595 The ‘list’ pointer was utilized before it was verified against nullptr. Check lines: 1627, 1629. sched-int.h 1627

Чтобы увидеть ошибку, нам опять потребуется показать устройство макроса:

#define DEPS_LIST_FIRST(L) ((L)->first)

Раскрываем макрос и получаем:

it_ptr->linkp = &((list)->first);
if (list)
  continue;

И сейчас многие воскликнут: «Стоп, стоп! Здесь нет ошибки. Мы ведь просто получаем указатель на член класса. Никакого разыменования нулевого указателя здесь нет. Да, возможно код не аккуратен, но ошибки здесь нет!».

Всё не так просто. Здесь возникает неопределённое поведение. И то, что такой код может работать на практике, это просто везение. На самом деле, так писать нельзя. Например, оптимизирующий компилятор, увидев list->first, может удалить проверку if (list).

Я написал целую статью на эту тему: “Разыменовывание нулевого указателя приводит к неопределённому поведению”. Там как раз рассматривается аналогичный случай. Прежде чем спорить, прошу внимательно познакомиться с этой статьёй.

Пример без виртуальных функций

Давайте посмотрим на программу с одним базовым классом и двумя наследниками:

volatile unsigned char var;

class Base
{
    public:
        void foo() { var  = 19; }
        void bar() { var  = 29; }
        void baz() { var  = 39; }
};

class DerivedOne : public Base
{
    public:
        void foo() { var  = 17; }
        void bar() { var  = 27; }
        void baz() { var  = 37; }
};

class DerivedTwo : public Base
{
    public:
        void foo() { var  = 18; }
        void bar() { var  = 28; }
        void baz() { var  = 38; }
};

DerivedOne dOne = DerivedOne();
DerivedTwo dTwo = DerivedTwo();

int main()
{
    Base* b;
    if (var)
        b = &dOne;
    else
        b = &dTwo;

    asm("nop");
    b->foo();

    for (;;)
        ;

    return 0;
}

В функции `main` на основе значения `var`, которое компилятору заведомо не известно, мы назначаем указателю на базовый класс `b` ссылку либо на объект первого унаследованного класса, либо ссылку на объект второго. А затем вызываем метод `foo` по указателю на базовый класс.

Про сертификаты:  QR-код: как получить сертификат вакцинированного или переболевшего COVID-19 - Новости - Официальный портал Казани

Этот пример глуповат, т.к. вне зависимости от нашей возни с дочерними классами, будет вызвана реализация `foo` от базового класса `Base`. Пример полезен, как отправная точка.

$ avr-g   -O0 -c novirtual.cpp -o novirtual.o
$ avr-gcc -O0 novirtual.o -o novirtual.elf
$ avr-size -C --format=avr novirtual.elf
AVR Memory Usage
----------------
Device: Unknown

Program:     104 bytes
(.text   .data   .bootloader)

Data:          3 bytes
(.data   .bss   .noinit)

Итак, программа использует 104 байта Flash-памяти и 3 байта SRAM. 104 3 байт при использовании флагов оптимизации усыхают до 34 3, а при использовании флагов очистки мёртвого кода и вовсе — 16 0 байт.

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

	ldd r24,Y 1
	ldd r25,Y 2
	rcall _ZN4Base3fooEv

В регистры `r24:r25` загоняется значение `this` и делается непосредственный вызов `Base::foo`. Просто, эффективно. Конечно, оптимизатор заметит ненужность this и вообще узрит возможность inline’а, но мы давайте рассуждать на неоптимизированном уровне.

Пути диагностики

Вот самый простой пример ошибки double-free:

#include <stdlib.h>

void test(void *ptr)
{
  free(ptr);
  free(ptr);
}

GCC 10 с

-fanalyzer

сообщает об этом следующим образом:

$ gcc -c -fanalyzer double-free-1.c
double-free-1.c: In function ‘test’:
double-free-1.c:6:3: warning: double-‘free’ of ‘ptr’ [CWE-415] [-Wanalyzer-double-free]
    6 |   free(ptr);
      |   ^~~~~~~~~
  ‘test’: events 1-2
    |
    |    5 |   free(ptr);
    |      |   ^~~~~~~~~
    |      |   |
    |      |   (1) first ‘free’ here
    |    6 |   free(ptr);
    |      |   ~~~~~~~~~
    |      |   |
    |      |   (2) second ‘free’ here; first ‘free’ was at (1)
    |

Этот лог показывает, что GCC выучил несколько новых трюков; во-первых, возможность диагностики иметь идентификаторы

. В этом примере диагностика double-free помечена тегом

. Надеемся, что этот тег сделает вывод более понятным, повысит точность и даст вам что-то простое для ввода в поисковых системах. Пока что только диагностика от

-fanalyzer

маркируется идентификаторами уязвимости CWE.

Если Вы используете GCC 10 с подходящим терминалом (например, свежий gnome-terminal), то CWE-идентификатор — это гиперссылка, ведущая к описанию проблемы. Говоря о гиперссылках, для многих релизов, когда GCC выдает предупреждение, он печатает опцию, регулирующую это предупреждение.

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

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

Приведем более полный пример. Вы видите проблему в следующем коде? (Подсказка: на этот раз это не двойное освобождение):

#include <setjmp.h>
#include <stdlib.h>

static jmp_buf env;

static void inner(void)
{
  longjmp(env, 1);
}

static void middle(void)
{
  void *ptr = malloc(1024);
  inner();
  free(ptr);
}

void outer(void)
{
  int i;

  i = setjmp(env);
  if (i == 0)
    middle();
}

Вот что сообщает GCC

-fanalyzer

, который показывает межпроцедурный поток управления с помощью ASCII-вывода:

$ gcc -c -fanalyzer longjmp-demo.c
longjmp-demo.c: In function ‘inner’:
longjmp-demo.c:8:3: warning: leak of ‘ptr’ [CWE-401] [-Wanalyzer-malloc-leak]
    8 |   longjmp(env, 1);
      |   ^~~~~~~~~~~~~~~
  ‘outer’: event 1
    |
    |   18 | void outer(void)
    |      |      ^~~~~
    |      |      |
    |      |      (1) entry to ‘outer’
    |
  ‘outer’: event 2
    |
    |   22 |   i = setjmp(env);
    |      |       ^~~~~~
    |      |       |
    |      |       (2) ‘setjmp’ called here
    |
  ‘outer’: events 3-5
    |
    |   23 |   if (i == 0)
    |      |      ^
    |      |      |
    |      |      (3) following ‘true’ branch (when ‘i == 0’)...
    |   24 |     middle();
    |      |     ~~~~~~~~
    |      |     |
    |      |     (4) ...to here
    |      |     (5) calling ‘middle’ from ‘outer’
    |
     --> ‘middle’: events 6-8
           |
           |   11 | static void middle(void)
           |      |             ^~~~~~
           |      |             |
           |      |             (6) entry to ‘middle’
           |   12 | {
           |   13 |   void *ptr = malloc(1024);
           |      |               ~~~~~~~~~~~~
           |      |               |
           |      |               (7) allocated here
           |   14 |   inner();
           |      |   ~~~~~~~
           |      |   |
           |      |   (8) calling ‘inner’ from ‘middle’
           |
            --> ‘inner’: events 9-11
                  |
                  |    6 | static void inner(void)
                  |      |             ^~~~~
                  |      |             |
                  |      |             (9) entry to ‘inner’
                  |    7 | {
                  |    8 |   longjmp(env, 1);
                  |      |   ~~~~~~~~~~~~~~~
                  |      |   |
                  |      |   (10) ‘ptr’ leaks here; was allocated at (7)
                  |      |   (11) rewinding from ‘longjmp’ in ‘inner’...
                  |
    <------------- 
    |
  ‘outer’: event 12
    |
    |   22 |   i = setjmp(env);
    |      |       ^~~~~~
    |      |       |
    |      |       (12) ...to ‘setjmp’ in ‘outer’ (saved at (2))
    |


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

setjmp longjmp

. Я надеюсь, что описание достаточно понятно: происходит утечка памяти, когда вызов longjmp разворачивает стек обратно в

outer

мимо точки очистки в

middle

, не вызывая очистки.

Если вам не нравится ASCII-вывод, показанный выше, вы можете просматривать события как отдельную диагностику «ноты» при помощи -fdiagnostics-path-format=separate-events:

$ gcc -c -fanalyzer -fdiagnostics-path-format=separate-events longjmp-demo.c
longjmp-demo.c: In function ‘inner’:
longjmp-demo.c:8:3: warning: leak of ‘ptr’ [CWE-401] [-Wanalyzer-malloc-leak]
    8 |   longjmp(env, 1);
      |   ^~~~~~~~~~~~~~~
longjmp-demo.c:18:6: note: (1) entry to ‘outer’
   18 | void outer(void)
      |      ^~~~~
In file included from longjmp-demo.c:1:
longjmp-demo.c:22:7: note: (2) ‘setjmp’ called here
   22 |   i = setjmp(env);
      |       ^~~~~~
longjmp-demo.c:23:6: note: (3) following ‘true’ branch (when ‘i == 0’)...
   23 |   if (i == 0)
      |      ^
longjmp-demo.c:24:5: note: (4) ...to here
   24 |     middle();
      |     ^~~~~~~~
longjmp-demo.c:24:5: note: (5) calling ‘middle’ from ‘outer’
longjmp-demo.c:11:13: note: (6) entry to ‘middle’
   11 | static void middle(void)
      |             ^~~~~~
longjmp-demo.c:13:15: note: (7) allocated here
   13 |   void *ptr = malloc(1024);
      |               ^~~~~~~~~~~~
longjmp-demo.c:14:3: note: (8) calling ‘inner’ from ‘middle’
   14 |   inner();
      |   ^~~~~~~
longjmp-demo.c:6:13: note: (9) entry to ‘inner’
    6 | static void inner(void)
      |             ^~~~~
longjmp-demo.c:8:3: note: (10) ‘ptr’ leaks here; was allocated at (7)
    8 |   longjmp(env, 1);
      |   ^~~~~~~~~~~~~~~
longjmp-demo.c:8:3: note: (11) rewinding from ‘longjmp’ in ‘inner’...
In file included from longjmp-demo.c:1:
longjmp-demo.c:22:7: note: (12) ...to ‘setjmp’ in ‘outer’ (saved at (2))
   22 |   i = setjmp(env);
      |       ^~~~~~

или вообще выключить их с помощью

-fdiagnostics-path-format=none

. Есть также формат вывода JSON.

Все новые диагностики имеют название вида -Wanalyzer-SOMETHING: Мы уже видели -Wanalyzer-double-free и -Wanalyzer-malloc-leak выше. Все эти диагностики включаются, когда включен -fanalyzer, но их можно выборочно отключить с помощью вариантов -Wno-analyzer-SOMETHING (например, с помощью прагм).

Сертификаты с расширенной проверкой.

Это самые дорогие сертификаты и получить их сложнее всего. В таких сертификатах есть так называемый «green bar» — то есть при входе не сайт, где установлен такой сертификат в адресной строке браузера посетителя появится зеленая строка, в которой будет указано название организации, получившей сертификат.

Вот как это выглядит на сайте у Thawte.
GCC Profile-guided optimization / Хабр

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

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

Процесс выпуска сертификатов EV стандартизирован и должен строго соотвествовать правилам EV, которые были созданы на специализированном форуме CA/Browser Forum в 2007 году. Там указаны необходимые шаги, которые центр сертификации должен выполнить перед выпуском EV сертификата:

  1. Должен проверить правовую, физическую и операционную деятельности субъекта.
  2. Должен убедиться, что организация соответствует официальным документам.
  3. Необходимо убедиться, что организация имеет исключительное право на использование домена, указанного в сертификате EV.
  4. Необходимо убедиться, что организация полностью авторизована для выпуска EV сертификата.

Список того, что конкретно будут проверять такой же как и для сертификатов с проверкой организации.

EV сертификаты используются для всех типов бизнеса, в том числе для государственных и некоммерческих организаций. Для выпуска необходимо 10-14 дней.

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

Цена полиморфизма


Давайте схематично изобразим таблицы виртуальных функций. В нашем примере их 3, по одной на каждый класс:

vtable for Base:
  foo -> Base::foo
  bar -> Base::bar
  baz -> Base::baz

vtable for DerivedOne:
  foo -> DerivedOne::foo
  bar -> DerivedOne::bar
  baz -> Base::baz

vtable for DerivedTwo:
  foo -> DerivedTwo::foo
  bar -> Base::bar
  baz -> DerivedTwo::baz

Каждый указатель на 8-bit AVR — это 2 байта. Достаточно единожды создать такие таблицы для каждого класса в иерархии, а затем в конкретных экземплярах добавлять одно скрытое поле `__vtbl*`, которое указывает на конкретную таблицу. Так каждый экземпляр будет «знать кто он» вне зависимости от того, по указателю какого типа вызывают его методы. Т.е. оверхед полиморфизма для одного объекта — это лишь 2 байта на `__vtbl*` и затраты на косвенный вызов. Метод вызывается не напрямую, а сначала подтягивается его адрес из таблицы, а затем идёт вызов.

	ldd r24,Y 1
	ldd r25,Y 2
	mov r30,r24
	mov r31,r25
	ld r24,Z
	ldd r25,Z 1
	mov r30,r24
	mov r31,r25
	ld r18,Z
	ldd r19,Z 1
	ldd r24,Y 1
	ldd r25,Y 2
	mov r30,r18
	mov r31,r19
	icall

Дополнительные затраты на косвенный вызов важны, если речь идёт о многочисленных вызовах в коде, который очень критичен к времени исполнения. Но тогда возникает вопрос: что делает полиморфизм в таком коде? Каждой задаче — свой инструмент. Для решения задач высокого уровня ООП — благо.

Оцените статью
Мой сертификат
Добавить комментарий