Gcc 程序集可执行文件不';不显示任何内容(x64)

Gcc 程序集可执行文件不';不显示任何内容(x64),gcc,assembly,x86-64,calling-convention,abi,Gcc,Assembly,X86 64,Calling Convention,Abi,非常简单的汇编介绍代码。 似乎通过gcc-o prog1 prog1.s编译正常,然后/prog1跳过一行,什么也不显示,就像等待代码不要求的输入一样。怎么了? 在VMware上运行的64位gNewSense中使用gcc(Debian 4.7.2-5)4.7.2。 代码: tl;dr:doxorl%eax,%eax在调用printf之前 printf是一个varargs函数。以下是System V AMD64 ABI对varargs函数的看法: 对于可能调用使用varargs或stdargs(原

非常简单的汇编介绍代码。
似乎通过
gcc-o prog1 prog1.s
编译正常,然后
/prog1
跳过一行,什么也不显示,就像等待代码不要求的输入一样。怎么了?
在VMware上运行的64位gNewSense中使用gcc(Debian 4.7.2-5)4.7.2。 代码:


tl;dr:do
xorl%eax,%eax
在调用printf之前

printf
是一个varargs函数。以下是System V AMD64 ABI对varargs函数的看法:

对于可能调用使用varargs或stdargs(原型较少)的函数的调用 调用或调用声明中包含省略号(…)的函数)
%al
18 作为隐藏参数,指定使用的向量寄存器的数量。内容 of
%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
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翻译是
goto*(&&after_movaps-al*4)。为了提高效率,gcc和/或glibc不希望保存比您使用的更多的向量寄存器,也不希望执行一系列条件分支。保存向量寄存器的每条指令有4个字节,因此它取向量寄存器保存指令的末尾,减去
al*4
字节,然后跳到那里。这导致执行的指令数量刚好足够。因为你有超过8个,它最终跳得太远了,在它刚刚接受的跳转指令之前着陆,从而创建了一个无限循环

至于为什么它在现代系统上不可复制,这里是对它们的
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
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。为什么会发生这种变化?我猜是幽灵。幽灵的缓解措施使间接跳跃变得非常缓慢,因此不再值得再这样做。或否;见评论。相反,他们做了一个简单得多的检查:如果有任何向量寄存器,那么保存它们。有了这段代码,你的
al
的坏值并不是一场灾难,因为它只是意味着向量寄存器将被不必要地复制。

如果你能将注释翻译成英语并解释你期望的输出类型(我假设与上面列出的C程序的输出相同),事情会变得容易得多,它的工作原理与注释中的C代码类似。您确定正在编译和运行您认为是的吗?在调用printf
之前,您应该将
%al
归零,因为您不使用任何SSE寄存器作为参数。不过,这不太可能导致这个问题。您可以尝试通过
strace
运行程序,当然也可以使用调试器。等等,我刚刚在gNewSense 4虚拟机中尝试过,我可以在那里重现问题。说到底,我也许能弄明白这一点。@Jester关于需要归零
%al
的说法是对的。这样做,它的工作。完整的答案和解释马上就要来了。幽灵的缓解措施会让间接跳跃变得非常缓慢——只有在你用
lfence
或其他什么装备它们时才会缓慢,而GCC在默认情况下通常不会这样做。我认为这种变化早于幽灵;可能只是因为间接分支更难预测,而且FP printf比在只有一个FP arg的情况下转储额外寄存器的情况要少,成本也不高。(特别是在具有良好OoO exec和大型存储缓冲区的现代CPU上)有趣的发现;我不知道gcc变量代码gen除了check
AL之外做了什么=0
@Ajna因为问题是它跳得很厉害,值不是10,它很可能跳到某条指令的一半,而该指令恰好不是其他有效指令,因此得到的是
SIGILL
非法指令。@Ajna不,这不是gNewSense的问题。你的代码有问题。你的代码违反了ABI的一条规则,而且新系统对你违反的规则比旧系统更宽容(也就是说,在新系统上,它只是稍微慢一点,而不是完全被破坏)。@Ajna:有缺陷的asm代码意外/碰巧工作的情况并不罕见。其他ABI冲突,如修改保留调用的寄存器,通常不会对简单调用方造成问题,但会破坏其他代码。把代码扔到墙上,看看什么棒在asm中比在其他语言中工作得更差。不要依赖试错。(虽然它可以找到一些肯定不起作用的东西,例如,在这里它在一个测试系统上出现故障。)@Ajna:这就是问题中违反ABI的代码的来源吗?直到现在你才这么说,但我想这解释了为什么你一直认为这一定是gNewSense中的一个bug,即使在解释了代码中的bug之后。即使你知道自己在做什么,只是忘记了一些事情,bug也确实是偶然发生的。出于同样的原因,故意的尝试和错误是不安全的,在系统上测试时很容易忽略这些错误。通常最好从C编译器输出开始,或与C编译器输出进行比较;编译器在遵循调用约定时不会出错。