8

这有点奇怪,但我今天正在研究 GNU 汇编器(我希望至少能够阅读语法),并试图让我这个人为的小例子工作。也就是说,我只想从 0 到 100,一直打印数字。所以几分钟后我想出了这个:

# count.s: print the numbers from 0 to 100. 
    .text
string: .asciz "%d\n"
    .globl _main

_main:
    movl    $0, %eax # The starting point/current value.
    movl    $100,   %ebx # The ending point.

_loop:
    # Display the current value.
    pushl   %eax
    pushl   $string
    call     _printf
    addl     $8, %esp

    # Check against the ending value.
    cmpl    %eax, %ebx
    je    _end

    # Increment the current value.
    incl    %eax
    jmp _loop   

_end:

我从中得到的只是 3 一遍又一遍地打印。就像我说的,只是一个人为的例子,所以不要太担心,这不是生死攸关的问题。

(格式有点乱,但没什么大不了的)。

4

6 回答 6

12

您不能相信任何被调用的过程对任何寄存器所做的事情。要么将寄存器压入堆栈并在调用 printf 后将它们弹出,要么将增量和终点值保存在内存中,并根据需要读取/写入寄存器。

我希望以下工作。我假设 pushl 有一个等效的 popl 并且您可以将额外的几个数字推入堆栈。

# count.s: print the numbers from 0 to 100. 
    .text
string: .asciz "%d\n"
    .globl _main

_main:
    movl    $0, %eax # The starting point/current value.
    movl    $100,       %ebx # The ending point.

_loop:
    # Remember your registers.
    pushl   %eax
    pushl   %ebx

    # Display the current value.
    pushl   %eax
    pushl   $string
    call     _printf
    addl     $8, %esp

    # reinstate registers.
    popl   %ebx
    popl   %eax

    # Check against the ending value.
    cmpl    %eax, %ebx
    je    _end

    # Increment the current value.
    incl    %eax
    jmp _loop   

_end:
于 2008-08-21T07:47:00.007 回答
6

我对_printf不太熟悉,但是会不会是它修改了eax?Printf 应该返回打印的字符数,在这种情况下是两个:'0' 和 '\n'。我认为它在 eax 中返回这个,当你增加它时,你会得到 3,这就是你继续打印的内容。您最好为计数器使用不同的寄存器。

于 2008-08-21T06:28:08.917 回答
5

您可以安全地使用“被调用者保存”的寄存器,而不必自己保存它们。在 x86 上,它们是 edi、esi 和 ebx;其他架构有更多。

这些都记录在 ABI 参考资料中:http: //math-atlas.sourceforge.net/devel/assembly/

于 2008-09-04T02:32:11.960 回答
3

编写良好的函数通常会将所有寄存器压入堆栈,然后在它们完成时将它们弹出,以便它们在函数期间保持不变。例外是包含返回值的 eax。像 printf 这样的库函数很可能是这样写的,所以我不会像 Wedge 建议的那样做:

您需要对您拥有的任何其他变量执行相同的操作。使用寄存器存储局部变量几乎只保留给有足够寄存器支持它的架构(例如 EPIC、amd64 等)。

事实上,据我所知,编译器通常以这种方式编译函数来准确处理这个问题。

@seanyboy,您的解决方案太过分了。所需要的只是将 eax 替换为 ecx 之类的其他寄存器。

于 2008-08-21T09:46:17.583 回答
1

内森走在正确的轨道上。您不能假设在调用子程序后寄存器值不会被修改。事实上,最好假设它们会被修改,否则子程序将无法完成它的工作(至少对于像 x86 这样的低寄存器计数架构)。如果你想保留一个值,你应该将它存储在内存中(例如将它压入堆栈并跟踪它的位置)。

您需要对您拥有的任何其他变量执行相同的操作。使用寄存器存储局部变量几乎只保留给有足够寄存器支持它的架构(例如 EPIC、amd64 等)。

于 2008-08-21T08:00:47.960 回答
-1

您可以重写它,以便使用不应该更改的寄存器,例如%ebp. 只需确保在开始时将它们压入堆栈,并在例程结束时将它们弹出即可。

# count.s: print the numbers from 0 to 100. 
    .text
string: .asciz "%d\n"
    .globl _main

_main:
    push    %ecx
    push    %ebp
    movl    $0, %ecx # The starting point/current value.
    movl    $100,       %ebp # The ending point.

_loop:
    # Display the current value.
    pushl   %ecx
    pushl   $string
    call     _printf
    addl     $8, %esp

    # Check against the ending value.
    cmpl    %ecx, %ebp
    je    _end

    # Increment the current value.
    incl    %ecx
    jmp _loop   

_end:
    pop     %ebp
    pop     %ecx
于 2008-08-21T20:48:29.033 回答