SIGSEGV в оптимизированной версии кода

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

Код C, составленный GCC 4.1.2.

Вот результат команды GDB "disas" при сбое:

   0x00000000004263e5 <+0>:     sub    $0x8,%rsp
   0x00000000004263e9 <+4>:     movsd  %xmm2,(%rsp)
   0x00000000004263ee <+9>:     divsd  %xmm1,%xmm0
   0x00000000004263f2 <+13>:    callq  0x60f098 <[email protected]>
=> 0x00000000004263f7 <+18>:    andpd  0x169529(%rip),%xmm0        
   0x00000000004263ff <+26>:    movsd  (%rsp),%xmm1
   0x0000000000426404 <+31>:    ucomisd %xmm0,%xmm1
   0x0000000000426408 <+35>:    seta   %al
   0x000000000042640b <+38>:    movzbl %al,%eax
   0x000000000042640e <+41>:    add    $0x8,%rsp
   0x0000000000426412 <+45>:    retq   

И здесь исходный источник функции:

char is_within_range(double a, double b, double range) {
  double ratio = a / b;
  double logRatio = fabs(log(ratio));
  return logRatio < range;
}

Для справки здесь не оптимизированная версия кода:

   0x00000000004263e5 <+0>: push   %rbp
   0x00000000004263e6 <+1>: mov    %rsp,%rbp
   0x00000000004263e9 <+4>: sub    $0x30,%rsp
   0x00000000004263ed <+8>: movsd  %xmm0,-0x18(%rbp)
   0x00000000004263f2 <+13>:    movsd  %xmm1,-0x20(%rbp)
   0x00000000004263f7 <+18>:    movsd  %xmm2,-0x28(%rbp)
   0x00000000004263fc <+23>:    movsd  -0x18(%rbp),%xmm0
   0x0000000000426401 <+28>:    divsd  -0x20(%rbp),%xmm0
   0x0000000000426406 <+33>:    movsd  %xmm0,-0x10(%rbp)
   0x000000000042640b <+38>:    mov    -0x10(%rbp),%rax
   0x000000000042640f <+42>:    mov    %rax,-0x30(%rbp)
   0x0000000000426413 <+46>:    movsd  -0x30(%rbp),%xmm0
   0x0000000000426418 <+51>:    callq  0x610608 <[email protected]>
   0x000000000042641d <+56>:    movapd %xmm0,%xmm1
   0x0000000000426421 <+60>:    movsd  0x16b6b7(%rip),%xmm0
   0x0000000000426429 <+68>:    andpd  %xmm1,%xmm0
   0x000000000042642d <+72>:    movsd  %xmm0,-0x8(%rbp)
   0x0000000000426432 <+77>:    movsd  -0x8(%rbp),%xmm1
   0x0000000000426437 <+82>:    movsd  -0x28(%rbp),%xmm0
   0x000000000042643c <+87>:    ucomisd %xmm1,%xmm0
   0x0000000000426440 <+91>:    seta   %al
   0x0000000000426443 <+94>:    movzbl %al,%eax
   0x0000000000426446 <+97>:    leaveq 
   0x0000000000426447 <+98>:    retq   

Ответ 1

=> 0x00000000004263f7 <+18>:    andpd  0x169529(%rip),%xmm0        
   0x00000000004263ff <+26>:    movsd  (%rsp),%xmm1

Когда команда andpd принимает операнд памяти, она должна быть выровнена с 16-байтной границей.

Для %rip -реляционной адресации смещение применяется к адресу следующей инструкции. Итак, здесь операнд памяти находится в 0x4263ff + 0x169529 = 0x58f928, который не выравнивается по 16 байт. Следовательно, segfault.

Компилятор напрямую генерирует код для fabs(), используя И с соответствующей битовой маской, чтобы очистить бит знака; постоянное значение битовой маски должно быть размещено на соответствующем смещении в достаточно выровненной секции данных, но не было. Это может быть ошибкой в ​​этой (старой) версии GCC или, возможно, может быть связанной с компоновкой проблемой где-то еще.

Ответ 2

Кажется, он сработал после вызова функции log:

callq  0x60f098 <[email protected]>

Таким образом, может возникнуть проблема с реализацией fabs, используя -O0.

Вы пробовали:

double logRatio = log(ratio);
logRatio = fabs(logRatio);

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

В качестве альтернативы вы можете заменить вызов fabs на что-то вроде:

double logRatio = log(ratio);
logRatio = (logRatio < 0) -logRatio : logRatio;

У вас могут быть проблемы с точностью, но это не главное...

Ответ 3

Я также использую gcc (GCC) 4.1.2 20070115 (SUSE Linux), здесь сгенерированная сборка:

Dump of assembler code for function is_within_range:
0x0000000000400580 <is_within_range+0>: divsd  %xmm1,%xmm0
0x0000000000400584 <is_within_range+4>: sub    $0x8,%rsp
0x0000000000400588 <is_within_range+8>: movsd  %xmm2,(%rsp)
0x000000000040058d <is_within_range+13>:        callq  0x400498 <[email protected]>
0x0000000000400592 <is_within_range+18>:        andpd  358(%rip),%xmm0        # 0x400700
0x000000000040059a <is_within_range+26>:        xor    %eax,%eax
0x000000000040059c <is_within_range+28>:        movsd  (%rsp),%xmm1
0x00000000004005a1 <is_within_range+33>:        ucomisd %xmm0,%xmm1
0x00000000004005a5 <is_within_range+37>:        seta   %al
0x00000000004005a8 <is_within_range+40>:        add    $0x8,%rsp
0x00000000004005ac <is_within_range+44>:        retq

Кажется, это почти то же самое, но я не получаю краха. Я думаю, вам нужно предоставить нам свои флагов компилятора, а также информацию о вашем процессоре и версии GLIBC, а также значения a, b и range для вас, поскольку проблема почти определенно с вызовом log.