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
.