x86-64 汇编器中的无限循环

Posted

技术标签:

【中文标题】x86-64 汇编器中的无限循环【英文标题】:An infinite loop in x86-64 assembler 【发布时间】:2013-11-07 17:27:50 【问题描述】:
#include <stdio.h>
#include <math.h>

int main(int argc, const char *argv[])

  long i, max;
  long sum = 0;
  max = (long)pow(2,32);

  for (i = 0; i < max; i++) 
    sum += i; 
  
  printf("%ld\n", sum);
  return 0;

$gcc -S main.c

问题是:在下面的.L2 代码中,-8(%rbp) 始终等于零,而%rax 始终大于零。所以这是一个无限循环?如果我用gcc -S -O1 main.c 编译,那就很清楚了。我真的很困扰!

只是汇编代码的一小部分:

main:   
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $48, %rsp
    movl    %edi, -36(%rbp)
    movq    %rsi, -48(%rbp)
    movq    $0, -16(%rbp)
    movl    $0, -8(%rbp)
    movl    $2, -4(%rbp)
    movq    $0, -24(%rbp)
    jmp .L2

.L3:
    movq    -24(%rbp), %rax
    addq    %rax, -16(%rbp)
    addq    $1, -24(%rbp)

.L2:
    movq    -24(%rbp), %rax     
    cmpq    -8(%rbp), %rax       
    jl  .L3                      

.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"

【问题讨论】:

long 的大小是多少?如果它是 32 位(4 字节),那么 pow(2, 32) 将溢出。如果您想要 long 的最大值,那么您应该在 C++ 中使用 std::numeric_limits,并在 C 中使用 &lt;limits.h&gt; 头文件中的 LONG_MAX -8(%rbp)max 的值,因此它显然不应该为零。也许你可以展示更多它被设置的汇编器。 当您检查它是否为零时,您从 -8(%rbp) 读取了多少字节?它的值应该是0x100000000,它的低 32 位为零。 @JoachimPileborg Pileborg 很抱歉没有明确说明,我使用 ubuntu 12.04 x86_64。所以long的大小是8字节,那么pow(2,32)就不会溢出了。 @mbratch 但-8(%rbp) 被初始化为零。 cmpq -8(%rbx), %rax 的第一次是真的,然后总是真的。 【参考方案1】:

真正的循环计数器 (i) 位于 -24(%rbp)。在第三行,它增加了。在第 4 行,它被加载到rax。所以rax 不是一个常数零,它与i 一起贯穿值。

-8(%rbp),一个假设是max 所在的位置。所以i 的值与那个值进行比较,这就是你的循环退出条件。 -8(%rbp) 不应为零。如果是的话,我会闻到流氓 32 位算法的味道。

编辑:我想我知道是怎么回事。常量 2 和 32 是 int,而不是 long,因此假定为 32 位。 int 的大小取决于平台;甚至 GCC 的约定也可能会有所不同。 pow(int, int) 被实现为内在的。当参数为 32 位时,2^32 为 0。

替换

max = (long)pow(2,32);

max = pow(2l, 32l);

或者使用常量更好:

max = 0x100000000l;

正如我和其他人所怀疑的那样,混合中存在 32 位。

【讨论】:

【参考方案2】:

不,这不是无限循环。首先,%rax 并不总是大于 0,在该行中,它从 -24(%rbp) 中获取值,这显然是变量 i。当它进入循环时,它将 -24(%rbp) 设置为零,然后跳转到 .L2 它发生的部分原因是您没有显示。如果 max 的变量值 -8(%rbp) 等于零(在溢出的情况下)jl 将不会跳转到 .L3 并且循环将在第一次检查后终止。 我不太明白为什么你需要为此阅读汇编,这从 C++ 源代码中很明显。

【讨论】:

另外,通过运行代码,我们可以看到它结束了。即使只是一些额外的printf() 语句也必须有所帮助。 不,-8(%rbx) 被初始化为零,在此之上我添加了额外的代码。并且cmpq -8(%rbx), %rax 的第一次为真,然后始终为真,除非%rax 会溢出。 是的,我就是想看看用-O1编译的代码与不编译的汇编代码的区别。因为我发现用-O1编译的代码比平时花的时间少。跨度> @xuefu 正如我之前所说,-24(%rbp) 被初始化为 0。然后它跳转到 .L2,然后比较 -8(%rbp)-24(%rbp),因为它们都等于 0 jl 将不跳转到 .L3 所以循环退出。哪一部分你不明白?

以上是关于x86-64 汇编器中的无限循环的主要内容,如果未能解决你的问题,请参考以下文章

X.86 X64 汇编器中的正确堆栈操作

为什么用cycle()创建的迭代器中的可变值没有更新,即使有停止条件,也会导致无限循环?

Saxonica EE Xslt 转换处理无限循环

如何保护C文件在ubuntu中进入无限循环

8086汇编将小写字母转换为大写字母(含提示信息,无限循环,按“!”退出程序)

8086汇编将小写字母转换为大写字母(含提示信息,无限循环,按“!”退出程序)