Unix сигнализирует о сомнении - о выполнении нижеприведенной программы
У меня есть эта программа ниже
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int x = 1;
void ouch(int sig) {
printf("OUCH! dividing by zero!\n");
x = 0;
}
void fpe(int sig) {
printf("FPE! I got a signal: %d\n",sig);
psignal(sig, "psignal");
x = 1;
}
int main(void) {
(void) signal(SIGINT, ouch);
(void) signal(SIGFPE, fpe);
while(1)
{
printf("Hello World: %d\n",1/x);
sleep(1);
}
}
Проблема: при выполнении этой программы - когда я передаю SIGINT из терминала в программу - "" ОЙ! деление на ноль! "выводится - как и ожидалось. Следующее сообщение"FPE! Я получил сигнал: 8 psignal: Исключение с плавающей запятой ". И это сообщение продолжается и не останавливается. Я сомневаюсь, что после вызова обработчика сигнала fpe я установил x равным 1. Я, следовательно, ожидаю, что Hello World должен быть отображается на выходе.
Ниже приведена расшифровка полученных данных:
Hello World: 1
Hello World: 1
^COUCH! dividing by zero!
FPE! I got a signal: 8
psignal: Floating point exception
FPE! I got a signal: 8
psignal: Floating point exception
FPE! I got a signal: 8
psignal: Floating point exception
^COUCH! dividing by zero!
.
.
.
.
3 ответа
Когда вводится обработчик сигнала, программный счетчик (регистр ЦП, указывающий на текущую выполняемую инструкцию) сохраняется там, где произошло деление на ноль. Игнорирование сигнала восстанавливает ПК в том же месте, на котором сигнал срабатывает снова (и снова, и снова).
Значение или изменчивость 'x' не имеет значения к этому моменту - ноль был передан в регистр CPU в готовности к выполнению деления.
сигнал человека 2 отмечает, что:
Согласно POSIX, поведение процесса не определено после того, как он игнорирует сигнал SIGFPE, SIGILL или SIGSEGV, который не был сгенерирован функциями kill(2) или Повышение (3). Целочисленное деление на ноль имеет неопределенный результат. На некоторых архитектурах он генерирует сигнал SIGFPE. (Также деление самого отрицательного целого числа на -1 может генерировать SIGFPE.) Игнорирование этого сигнала может привести к бесконечной петле.
Мы можем увидеть это в gdb, если вы скомпилируете с флагом отладки:
simon @ diablo: ~ $ gcc -g -o sigtest sigtest.c simon @ diablo: ~ $ gdb sigtest GNU GDB 6,8-Debian Copyright (C) 2008 Free Software Foundation, Inc. Лицензия GPLv3+: GNU GPL версии 3 или более поздней Это бесплатное программное обеспечение: вы можете изменять и распространять его. НЕ ПРЕДОСТАВЛЯЕТСЯ ГАРАНТИИ, если это разрешено законом. Тип "показать копирование" и "показать гарантию" для деталей. Этот GDB был настроен как "i486-linux-gnu"...
По умолчанию GDB не передает SIGINT процессу - измените его, чтобы он увидел первый сигнал:
(GDB) ручка SIGINT проход SIGINT используется отладчиком. Вы уверены, что хотите изменить это? (у или н) у Сигнал Стоп Печать Пропуск программы Описание SIGINT Да Да Да Прерывание
Поехали:
(GDB) запустить Стартовая программа: /home/simon/sigtest х = 1 Hello World: 1
Теперь давайте прервем это:
^ C Программа получила сигнал SIGINT, прерывание. 0xb767e17b в nanosleep () из /lib/libc.so.6
и далее к границе:
(GDB) продолжение Продолжение. ОЙ! деление на ноль! х = 0 Программа получила сигнал SIGFPE, Арифметическое исключение.0x0804853a в main () на sigtest.c:30 30 printf("Hello World: %d\n",1/x);
Проверьте значение "x" и продолжайте:
(GDB) печать х $1 = 0 (GDB) продолжение Продолжение. FPE! Я получил сигнал: 8 psignal: исключение с плавающей точкой Программа получила сигнал SIGFPE, Арифметическое исключение.0x0804853a в main () на sigtest.c:30 30 printf("Hello World: %d\n",1/x); (GDB) печать х $ 2 = 1
x явно равен 1, и мы все еще получили деление на ноль - что происходит? Давайте проверим основной ассемблер:
(GDB) разбирать Дамп кода ассемблера для функции main: 0x080484ca: lea 0x4(%esp),%ecx 0x080484ce: и $ 0xfffffff0,% esp... 0x08048533: mov% eax,% ecx 0x08048535: mov% edx,% eax 0x08048537: sar $ 0x1f,% edx0x0804853a: idiv% ecx << - адрес FPE произошел в0x0804853c: mov %eax,0x4(%esp) 0x08048540: movl $0x8048653,(%esp) 0x08048547: звонок 0x8048384 0x0804854c: jmp 0x8048503 Конец ассемблерной свалки.
Позже один поиск в Google сообщает нам, что IDIV делит значение в регистре EAX на операнд-источник (ECX). Вы, вероятно, можете угадать содержимое регистра:
(gdb) информационные регистры eax 0x1 1 ecx 0x0 0 ...
Вы должны использовать volatile int x
чтобы компилятор каждый раз перезагружал память из памяти через цикл. Учитывая, что ваш обработчик SIGINT работает, это, вероятно, не объясняет вашу конкретную проблему, но если вы попробуете более сложные примеры (или проведете оптимизацию), это в конечном итоге вас укусит.
После обработки сигнала, возникшего при выполнении инструкции, ПК может вернуться либо к этой инструкции, либо к следующей. То, что он делает, очень зависит от процессора + ОС. Кроме того, повышение SIGFPE при целочисленном делении на ноль также зависит от CPU + OS.
На уровне ЦП, после принятия исключения, наиболее целесообразно вернуться к некорректной инструкции после того, как ОС получит возможность сделать все, что ей нужно (подумать о сбоях страниц / пропусках TLB), и снова запустить эту инструкцию. (Операционная система, возможно, должна была выполнить некоторую коррекцию адреса, например, процессоры ARM указывают две инструкции после ошибочной инструкции в качестве свидетельства их первоначального трехэтапного конвейера, в то время как процессор MIPS указывает на переход при принятии исключения из инструкции по слот задержки прыжка).
На уровне ОС существует несколько способов обработки исключений:
- Выполните необходимую обработку (подкачка памяти, обновление таблиц страниц и т. Д.) И перезапустите инструкцию.
- Эмулируйте эту инструкцию, продвиньте ПК соответствующим образом и вернитесь к следующей инструкции. Это позволяет эмулировать невыполненные инструкции (процессоры без / с неполными FPU, LL/SC на процессорах MIPSI, ...) и неподдерживаемое выравнивание (после исключения исключения выравнивания ОС может принять решение об отправке SIGBUS процессу или эмуляции неподдерживаемый доступ, возможно, во время регистрации).
- Отправить фатальный сигнал процессу. Процесс может взять на себя роль ОС здесь при обработке исключения, используя зависящие от процессора + ОС методы, такие как метод siginfo, связанный с Simonj.
Непереносимый метод для работы с SIGFPE - вызов longjmp() из обработчика сигнала, как в моем ответе на аналогичный вопрос о SIGSEGV.
В n1318 есть больше подробностей о longjmp() из обработчика сигнала, который вы когда-либо хотели узнать. Также обратите внимание, что POSIX указывает, что longjmp() должен работать с не вложенными обработчиками сигналов.