Исполняемый файл сборки ничего не показывает (x64) - PullRequest
2 голосов
/ 05 мая 2020

Очень простой вводный код сборки.
Кажется, компилируется нормально через gcc -o prog1 prog1.s, затем ./prog1 просто пропускает строку и ничего не показывает, как ожидание ввода, который код не запрашивает. Что не так?
Использование g cc (Debian 4.7.2-5) 4.7.2 в 64-разрядной версии gNewSense, работающей на VMware. Код:

/*
int nums[] = {10, -21, -30, 45};
int main() {
  int i, *p;
  for (i = 0, p = nums; i != 4; i++, p++)
    printf("%d\n", *p);
  return 0;
}
*/

.data
nums:  .int  10, -21, -30, 45
Sf:  .string "%d\n"    # string de formato para printf

.text
.globl  main
main:

/********************************************************/
/* mantenha este trecho aqui e nao mexa - prologo !!!   */
  pushq   %rbp
  movq    %rsp, %rbp
  subq    $16, %rsp
  movq    %rbx, -8(%rbp)
  movq    %r12, -16(%rbp)
/********************************************************/

  movl  $0, %ebx  /* ebx = 0; */
  movq  $nums, %r12  /* r12 = &nums */

L1:
  cmpl  $4, %ebx  /* if (ebx == 4) ? */
  je  L2          /* goto L2 */

  movl  (%r12), %eax    /* eax = *r12 */

/*************************************************************/
/* este trecho imprime o valor de %eax (estraga %eax)  */
  movq    $Sf, %rdi    /* primeiro parametro (ponteiro)*/
  movl    %eax, %esi   /* segundo parametro  (inteiro) */
  call  printf       /* chama a funcao da biblioteca */
/*************************************************************/

  addl  $1, %ebx  /* ebx += 1; */
  addq  $4, %r12  /* r12 += 4; */
  jmp  L1         /* goto L1; */

L2:  
/***************************************************************/
/* mantenha este trecho aqui e nao mexa - finalizacao!!!!      */
  movq  $0, %rax  /* rax = 0  (valor de retorno) */
  movq  -8(%rbp), %rbx
  movq  -16(%rbp), %r12
  leave
  ret      
/***************************************************************/

1 Ответ

3 голосов
/ 06 мая 2020

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 не является катастрофой, поскольку это просто означает, что векторные регистры будут копироваться без надобности.

...