Unix сигнализирует сомнение - о выполнении нижеприведенной программы - PullRequest
1 голос
/ 04 ноября 2010

У меня есть эта программа ниже

#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 ]

10 голосов
/ 04 ноября 2010

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

Значение или изменчивость 'x' не имеет значения в этом пункте - ноль имеетбыл переведен в регистр ЦП в готовности выполнить деление.

сигнал man 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.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...

По умолчанию GDB не передает процесс SIGINT - измените его так, чтобы он увидел первый сигнал:

(gdb) handle SIGINT pass
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) y

Signal        Stop  Print   Pass to program Description
SIGINT        Yes   Yes Yes     Interrupt

Выключено:

(gdb) run
Starting program: /home/simon/sigtest 
x = 1
Hello World: 1

Теперь давайте прервем его:

^C
Program received signal SIGINT, Interrupt.
0xb767e17b in nanosleep () from /lib/libc.so.6

и далее до деления:

(gdb) cont
Continuing.
OUCH!  dividing by zero!
x = 0

Program received signal SIGFPE, Arithmetic exception.
<b>0x0804853a</b> in main () at sigtest.c:30
30              printf("Hello World: %d\n",1/x);

Проверьте значение 'x' и продолжайте:

(gdb) print x
$1 = 0
(gdb) cont
Continuing.
FPE!  I got a signal: 8
psignal: Floating point exception

Program received signal SIGFPE, Arithmetic exception.
<b>0x0804853a</b> in main () at sigtest.c:30
30              printf("Hello World: %d\n",1/x);
(gdb) print x
$2 = 1

x явно равен 1, и мы все еще получили деление на ноль - что происходит?Давайте проверим базовый ассемблер:

(gdb) disassemble 
Dump of assembler code for function main:
0x080484ca :    lea    0x4(%esp),%ecx
0x080484ce :    and    $0xfffffff0,%esp
...
0x08048533 :  mov    %eax,%ecx
0x08048535 :  mov    %edx,%eax
0x08048537 :  sar    $0x1f,%edx
<b>0x0804853a :   idiv   %ecx           <<-- address FPE occurred at</b>
0x0804853c :  mov    %eax,0x4(%esp)
0x08048540 :  movl   $0x8048653,(%esp)
0x08048547 :  call   0x8048384 
0x0804854c :  jmp    0x8048503 
End of assembler dump.

Один поиск в Google позже скажет, что IDIV делит значение в регистре EAX на операнд-источник (ECX).Вы, вероятно, можете угадать содержимое регистра:

(gdb) info registers 
eax            0x1  1
ecx            0x0  0
...
1 голос
/ 06 ноября 2010

После обработки сигнала, возникшего при выполнении инструкции, ПК может вернуться либо к этой инструкции, либо к следующей. Какой из них это очень зависит от процессора + ОС. Кроме того, повышение SIGFPE при целочисленном делении на ноль также зависит от CPU + OS.

На уровне ЦП, после принятия исключения, наиболее целесообразно вернуться к инструкции, вызывающей сбой, после того, как ОС получит возможность сделать все, что ей нужно (подумать о сбоях страниц / пропусках TLB), и выполнить это Инструкция снова. (Операционная система, возможно, должна была выполнить некоторую коррекцию адреса, например, процессоры ARM указывают две инструкции после ошибочной инструкции как свидетельство их первоначального трехэтапного конвейера, в то время как процессор MIPS указывает на переход при принятии исключения из инструкции по слот задержки прыжка).

На уровне ОС существует несколько способов обработки исключений:

  • Выполните необходимую обработку (подкачка памяти, обновление таблиц страниц и т. Д.) И повторите команду.
  • Эмулируйте эту инструкцию, продвиньте ПК соответствующим образом и вернитесь к следующей инструкции. Это позволяет эмулировать невыполненные инструкции (процессоры без / с неполными FPU, LL / SC на процессорах MIPSI, ...) и неподдерживаемое выравнивание (после исключения исключения выравнивания ОС может принять решение об отправке SIGBUS процессу или эмуляции неподдерживаемый доступ, возможно, во время регистрации).
  • Отправить фатальный сигнал процессу. Процесс может взять на себя роль ОС здесь при обработке исключения, используя зависящие от процессора + ОС методы, такие как метод siginfo, связанный с Simonj.

Непереносимый метод для работы с SIGFPE - вызов longjmp () из обработчика сигнала, как в мой ответ на аналогичный вопрос о SIGSEGV .

n1318 содержит более подробную информацию о longjmp () из обработчика сигнала, который вы когда-либо хотели узнать. Также обратите внимание, что POSIX указывает, что longjmp () должен работать из не вложенных обработчиков сигналов.

1 голос
/ 04 ноября 2010

Вы должны использовать volatile int x, чтобы компилятор перезагружал x из памяти каждый раз через цикл.Учитывая, что ваш обработчик SIGINT работает, это, вероятно, не объясняет вашу конкретную проблему, но если вы попробуете более сложные примеры (или проведете оптимизацию), это в конечном итоге вас укусит.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...