汇编可执行文件不显示任何内容(x64)

问题描述 投票:2回答:1

非常简单的汇编介绍代码。似乎可以通过 gcc -o prog1 prog1.s那么 ./prog1 只是跳过一行,什么都不显示,就像在等待一个代码不要求的输入。有什么问题吗?使用gcc(Debian 4.7.2-5)4.7.2在VMware.Code上运行的64位gNewSense。

/*
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      
/***************************************************************/
gcc assembly x86-64 executable debian-based
1个回答
3
投票

tl;dr: 做 xorl %eax, %eax 之前 call printf.

printf 是一个varargs函数。下面是System V AMD64 ABI对varargs函数的说明。

对于可能调用使用varargs或stdargs的函数的调用(无原型调用或调用声明中包含省略号(. . . )的函数) %al18 作为隐藏参数,用于指定使用的向量寄存器的数量。寄存器的内容是 %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);. 为了提高效率,gcc和or glibc不想保存比你用的更多的向量寄存器,它也不想做一堆条件分支。每条保存向量寄存器的指令都是4个字节,所以它把向量寄存器保存指令的末尾,减去 al * 4 字节,并跳转到那里。这样的结果是,执行的指令刚好够用。由于你的指令超过了8条,结果跳得太靠后,落在了刚才的跳转指令之前,从而形成了一个无限循环。

至于为什么在现代系统上无法重现,下面是其开头的拆解。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 并不是一场灾难,因为这只是意味着向量寄存器将被不必要地复制。

© www.soinside.com 2019 - 2024. All rights reserved.