Особенности генерации POSIX-сигналов, вызываемых аппаратными исключениями процессоров Elbrus
============================================================================================

.. contents::
   :local:

.. toctree::
   :hidden:

Общее описание
--------------

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

.. code-block:: bash

  SIGSEGV - нарушение сегментной защиты памяти
  SIGBUS - обращение по невалидному адресу
  SIGFPE - исключительная ситуация при исполнении арифметической операции

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

.. code-block:: bash

  SIGILL - нелегальная инструкция

Следует упомянуть, что для "обычных" арихитектур при "стандартном" программировании
на языках высокого уровня (т.е. без использования ассемблера и других видов
низкоуровневого программирования) с таким сигналом практически никто не сталкивается.
Для архитектуры Elbrus этот сигнал будет возникать в том числе и при "стандартном"
программировании из-за работы оптимизаций. Не тот сигнал приходит, как правило, либо
в результате работы оптимизаций, либо при ручном написании кода на ассемблере или
ассемблерных вставках. Если по каким-то причинам нужно поймать именно тот сигнал,
который бы поймался на других архитектурах, то следует использовать режим с отключением
оптимизаций, задействующих полуспекулятивный режим исполнения:

* **-O0** - режим компиляции без оптимизаций
* **-O1** - минимальный набор оптимизаций
* **-fcontrol-spec** - запрет полуспекулятивных обращений к памяти
  (для сохранения сингалов SIGSEGV и SIGBUS)
* **-fno-fp-spec** - запрет полуспекулятивных вещественных операций
  (для сохранения сигнала SIGFPE)

Таким образом выработка сигналов SIGBUS, SIGFPE, SIGSEGV может быть подменена выработкой
сигнала SIGILL. Это обстоятельство необходимо учитывать при написании пользовательских
обработчиков сигналов. Нужно также помнить, что, такие ситуации могут возникнуть в том
числе и в библиотечных процедурах.

Примеры
-------

Во всех примерах, кроме последнего, запуск без оптимизаций приводится в качестве аналога работы теста на "стандартных" архитектурах

Целочисленное деление на ноль
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: c

  int flag = 1;
  int a = 10;
  int b = 0;
  
  int main (void)
  {
    if (flag)
      a = a / b;
  
    return a + 1;
  }

.. code-block:: bash

  $ lcc t.c
  $ ./a.out 
  Floating point exception

  $ lcc t.c -O2
  $ ./a.out
  Illegal instruction

Обращение в недопустимую память
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: c

  int flag = 1;
  int *p = 0;

  int main (void)
  {
    if (flag)
      return *p;

    return 0;
  }

.. code-block:: bash

  $ lcc t.c
  $ ./a.out 
  Segmentation fault

  $ lcc t.c -O2
  $ ./a.out 
  Illegal instruction

Обращение по невыровненному адресу
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: c

  int flag = 1;
  long long val;
  int *p = (int*) ((char*)(&val) + 1);

  int main (void)
  {
    if (flag)
      return *p;

    return 0;
  }

.. code-block:: bash

  $ lcc t.c -faligned-check
  $ ./a.out 
  Bus error

  $ lcc t.c -faligned-check -O2
  $ ./a.out 
  Illegal instruction

Исключение стандарта IEEE-754
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: c

  #include <float.h>
  #include <fenv.h>

  int flag = 1;
  float a = FLT_MAX;

  int main (void)
  {
    feenableexcept (FE_ALL_EXCEPT);

    if (flag)
      a = a * a;

    return a + 1;
  }

.. code-block:: bash

  $ lcc t.c -lm
  $ ./a.out
  Floating point exception

  $ lcc t.c -lm -O2
  $ ./a.out
  Illegal instruction

Проявление ошибки в стандартной библиотеке
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: c

  #include <string.h>

  char *p1 = NULL;
  char *p2 = NULL;

  int main (void)
  {
    strcat (p1, p2);

    return 0;
  }

.. code-block:: bash

  $ lcc t.c
  $ ./a.out
  Illegal instruction

  $ gdb a.out
  (gdb) run
  Program received signal SIGILL, Illegal instruction.
  (gdb) bt
  #0  0x00002aaaaacd5f68 in strcat () from /lib64/libc.so.6
  #1  0x0000000000002540 in main ()

Сигнал SIGILL, пойманный внутри стандартной библиотеки для "обычных" архитектур означал бы некорректный код библиотеки. Для архитектуры Elbrus этот сигнал означает, что в реальности здесь должен быть один из сигналов SIGILL, SIGFPE, SIGBUS или SIGSEGV, который может быть вызван некорретными входными данными (т.е. ошибка на самом деле находится в пользовательской программе, а не в системной библиотеке)

