tl; dr: do xorl %eax, %eax
before call printf
.
printf
- это функция varargs. Вот что говорит System V AMD64 ABI о функциях varargs:
Для вызовов, которые могут вызывать функции, использующие varargs или stdargs (вызовы без прототипов или вызовы функций, содержащих многоточие (...) в объявлении) %al
18 используется как скрытый аргумент для указания количества используемых векторных регистров. Содержимое %al
не обязательно должно точно соответствовать количеству регистров, но должно быть верхней границей количества используемых векторных регистров и находиться в диапазоне 0–8 включительно.
Вы нарушил это правило. Вы увидите, что в первый раз, когда ваш код вызывает printf
, %al
равно 10, что превышает верхнюю границу 8. В вашей системе gNewSense вот дизассемблирование начала printf
:
printf:
sub $0xd8,%rsp
movzbl %al,%eax # rax = al;
mov %rdx,0x30(%rsp)
lea 0x0(,%rax,4),%rdx # rdx = rax * 4;
lea after_movaps(%rip),%rax # rax = &&after_movaps;
mov %rsi,0x28(%rsp)
mov %rcx,0x38(%rsp)
mov %rdi,%rsi
sub %rdx,%rax # rax -= rdx;
lea 0xcf(%rsp),%rdx
mov %r8,0x40(%rsp)
mov %r9,0x48(%rsp)
jmpq *%rax # goto *rax;
movaps %xmm7,-0xf(%rdx)
movaps %xmm6,-0x1f(%rdx)
movaps %xmm5,-0x2f(%rdx)
movaps %xmm4,-0x3f(%rdx)
movaps %xmm3,-0x4f(%rdx)
movaps %xmm2,-0x5f(%rdx)
movaps %xmm1,-0x6f(%rdx)
movaps %xmm0,-0x7f(%rdx)
after_movaps:
# nothing past here is relevant for your problem
Квази-C преобразование важных бит составляет goto *(&&after_movaps - al * 4);
. Для эффективности g cc и / или glib c не хотят сохранять больше векторных регистров, чем вы использовали, а также не хотят делать кучу условных переходов. Каждая инструкция по сохранению векторного регистра занимает 4 байта, поэтому она берет конец инструкции сохранения векторного регистра, вычитает al * 4
байт и переходит туда. В результате выполняется достаточное количество инструкций. Поскольку у вас их было больше 8, он в конечном итоге прыгнул слишком далеко назад и приземлился раньше, чем только что потребовалась инструкция прыжка, в результате чего получилось бесконечное l oop.
Что касается того, почему это не воспроизводится в современных системах, вот разборка начала их printf
:
printf:
sub $0xd8,%rsp
mov %rdi,%r10
mov %rsi,0x28(%rsp)
mov %rdx,0x30(%rsp)
mov %rcx,0x38(%rsp)
mov %r8,0x40(%rsp)
mov %r9,0x48(%rsp)
test %al,%al # if(!al)
je after_movaps # goto after_movaps;
movaps %xmm0,0x50(%rsp)
movaps %xmm1,0x60(%rsp)
movaps %xmm2,0x70(%rsp)
movaps %xmm3,0x80(%rsp)
movaps %xmm4,0x90(%rsp)
movaps %xmm5,0xa0(%rsp)
movaps %xmm6,0xb0(%rsp)
movaps %xmm7,0xc0(%rsp)
after_movaps:
# nothing past here is relevant for your problem
Квази- C перевод важных битов равен if(!al) goto after_movaps;
. Почему это изменилось? Думаю, это Спектр. Смягчения для Spectre делают непрямые прыжки очень медленными, так что больше не стоит проделывать этот трюк. Или нет; см. комментарии. Вместо этого они выполняют гораздо более простую проверку: если есть векторные регистры, сохраните их все. С этим кодом ваше неверное значение al
не является катастрофой, поскольку это просто означает, что векторные регистры будут копироваться без надобности.