Особенности генерации POSIX-сигналов, вызываемых аппаратными исключениями процессоров Elbrus¶
Общее описание¶
Как известно, по стандарту POSIX в случае возникновения аппаратных исключительных ситуаций при исполнении программы вырабатываются некоторые сигналы, позволяющие обрабатывать такие ситуации и/или выдавать диагностические сообщения. Далее речь пойдет о следующих сигналах:
SIGSEGV - нарушение сегментной защиты памяти
SIGBUS - обращение по невалидному адресу
SIGFPE - исключительная ситуация при исполнении арифметической операции
Для ускорения получаемого кода компилятор широко использует (полу)спекулятивный режим исполнения операций, при котором операция исполняется прежде, чем выясняется корректность ее аргументов. При этом в случае некорректных аргументов операции (деление на 0, чтение по невалидному адресу, запись в защищенную страницу памяти и т.д.) результат операции помечается тэгом диагностики (иными словами, признаком отложенного прерывания). Диагностическое значение в дальнейшем может использоваться в других спекулятивных операциях, пересылаться в другой регистр, передаваться в качестве параметра в функцию и т.д. Иными словами, после момента отложения прерывания может быть исполнено достаточно много команд. Прерывание с выработкой сигнала произойдет только в момент завершения спекулятивной цепочки вычислений неспекулятивной операцией (чаще всего, операцией записи в память либо условного перехода). При таком прерывании вырабатывается только один вид сигнала:
SIGILL - нелегальная инструкция
Следует упомянуть, что для “обычных” арихитектур при “стандартном” программировании на языках высокого уровня (т.е. без использования ассемблера и других видов низкоуровневого программирования) с таким сигналом практически никто не сталкивается. Для архитектуры Elbrus этот сигнал будет возникать в том числе и при “стандартном” программировании из-за работы оптимизаций. Не тот сигнал приходит, как правило, либо в результате работы оптимизаций, либо при ручном написании кода на ассемблере или ассемблерных вставках. Если по каким-то причинам нужно поймать именно тот сигнал, который бы поймался на других архитектурах, то следует использовать режим с отключением оптимизаций, задействующих полуспекулятивный режим исполнения:
-O0 - режим компиляции без оптимизаций
-O1 - минимальный набор оптимизаций
-fcontrol-spec - запрет полуспекулятивных обращений к памяти (для сохранения сингалов SIGSEGV и SIGBUS)
-fno-fp-spec - запрет полуспекулятивных вещественных операций (для сохранения сигнала SIGFPE)
Таким образом выработка сигналов SIGBUS, SIGFPE, SIGSEGV может быть подменена выработкой сигнала SIGILL. Это обстоятельство необходимо учитывать при написании пользовательских обработчиков сигналов. Нужно также помнить, что, такие ситуации могут возникнуть в том числе и в библиотечных процедурах.
Примеры¶
Во всех примерах, кроме последнего, запуск без оптимизаций приводится в качестве аналога работы теста на “стандартных” архитектурах
Целочисленное деление на ноль¶
int flag = 1;
int a = 10;
int b = 0;
int main (void)
{
if (flag)
a = a / b;
return a + 1;
}
$ lcc t.c
$ ./a.out
Floating point exception
$ lcc t.c -O2
$ ./a.out
Illegal instruction
Обращение в недопустимую память¶
int flag = 1;
int *p = 0;
int main (void)
{
if (flag)
return *p;
return 0;
}
$ lcc t.c
$ ./a.out
Segmentation fault
$ lcc t.c -O2
$ ./a.out
Illegal instruction
Обращение по невыровненному адресу¶
int flag = 1;
long long val;
int *p = (int*) ((char*)(&val) + 1);
int main (void)
{
if (flag)
return *p;
return 0;
}
$ lcc t.c -faligned-check
$ ./a.out
Bus error
$ lcc t.c -faligned-check -O2
$ ./a.out
Illegal instruction
Исключение стандарта IEEE-754¶
#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;
}
$ lcc t.c -lm
$ ./a.out
Floating point exception
$ lcc t.c -lm -O2
$ ./a.out
Illegal instruction
Проявление ошибки в стандартной библиотеке¶
#include <string.h>
char *p1 = NULL;
char *p2 = NULL;
int main (void)
{
strcat (p1, p2);
return 0;
}
$ 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, который может быть вызван некорретными входными данными (т.е. ошибка на самом деле находится в пользовательской программе, а не в системной библиотеке)