5

所以我正在学习使用 NASM 语法的 x86 Linux 程序集(哦,天哪,又不是这个,你们都在想)。我正在尝试制作一个子程序,它将简单地将 EAX 中的值打印到标准输出。代码运行并退出没有错误,但没有打印。我不知道为什么。首先,这是我正在处理的文件:

segment .bss
    to_print:   resd 1

segment .text
    global print_eax_val

print_eax_val:                  ;       (top)
    push    dword ebx           ;Stack:  edx
    push    dword ecx           ;        ecx
    push    dword edx           ;        ebx
                                ;       (bot)

    mov     ecx,eax             ;ecx = eax

    mov     [to_print],ecx      ;to_print = ecx

    mov     eax, 4              ;sys_write
    mov     ebx, 1              ;to stdout
    add     ecx, 47             ;add 47 for ASCII numbers
    mov     edx, 2              ;double word = 2 bytes
    int     0x80

    mov     eax, [to_print]     ;eax = original val
    pop     edx                 ;pop the registers back from the stack
    pop     ecx
    pop     ebx                 ;Stack: empty

    ret

这是从我的主文件中调用的,看起来像这样(这可能无关紧要,除非我遗漏了一些激烈的东西)。

segment .data
        hello   db      "Hello world!", 0
        newline db      0xA
        len     equ $ - hello
        len2    equ $ - newline

segment .text
        extern print_nl
        extern print_eax_val
        global main

main:
        enter   0,0

        call    print_nl

        mov     eax, 1

        call    print_eax_val

        mov     ebx, 0          ;exit code = 0 (normal)
        mov     eax, 1          ;exit command
        int     0x80            ;ask kernel to quit

print_nl只是另一个定义和打印换行符的子程序。这成功运行并按预期打印新行。

问题是否与我的sys_write通话的长度参数有关?我给它 2,它是 a 的大小dword,它是EAX寄存器和我的to_print标签的大小,我用resd 1. 出于绝望,我尝试将长度更改为 1、4、8、16 和 32……没有任何效果。

编辑:对于任何想知道的人,这是我修复代码的方法:(我将在我更改的行上加上星号):

segment .bss
    to_print:   resd 1

segment .text
        global print_eax_val

print_eax_val:                      ;       (top)
        push    dword ebx           ;Stack:  edx
        push    dword ecx           ;        ecx
        push    dword edx           ;        ebx
                                    ;       (bot)

        mov     ecx,eax             ;ecx = eax

        mov     [to_print],ecx        ;to_print = ecx

****    add     dword [to_print], 48

        mov     eax, 4              ;sys_write
        mov     ebx, 1              ;to stdout
****    mov     ecx, to_print
        mov     edx, 2
        int     0x80

****    sub     dword [to_print], 48
        mov     eax, [to_print]     ;eax = original val
        pop     edx                 ;pop the registers back from the stack
        pop     ecx
        pop     ebx                 ;Stack: empty

        ret

基本上,ecx必须包含您要打印的块的地址,而不是值本身。正如所选答案中所指出的,这在 eax 在 0-9 范围内时才有效。

编辑 2:所以我对 sys_write 的第二个参数(存储在 中的那个)有点困惑edx。我认为它只是指一些字节。因此,对于 a dword,正如我使用的那样,在那里使用 4 是合适的,因为双字是 4 个字节或 32 位。我猜它起作用了,因为 x86 是小端的。所以在内存中, 的十六进制值to_print如下所示:

90 00 00 00

并且提供的长度为 2,sys_write 得到:

90 00

所以幸运的是价值没有被破坏。

后来我将代码更改为存储to_print为一个字节,使用resb 1和访问它byte而不是dword......一个字节在这里很好,因为我知道我不会给出to_print高于 9 的值。

4

2 回答 2

4

我对汇编程序非常生疏,但看起来 ECX 包含值 48,而它应该包含要写入的缓冲区的地址。

我想您打算print_eax_val采用 EAX 的二进制值,将 ASCII 偏移量添加到数字0(应该是 48,而不是 47),然后打印该单个字符。为此,在将值存储到 之前添加 48 to_print,将地址to_print放入 ECX,并将长度 (EDX) 设置为 1,因为您只写了一个字符。

现在请记住,这仅适用于 0x0000 和 0x0009 之间的 EAX 值。当您超过 9 时,您将获得其他 ASCII 字符。

解释如何获取 EAX 的任意二进制值并将其转换为可以打印的十进制字符串远远超出了 SO 的范围。

于 2013-07-02T22:32:20.247 回答
3

系统write调用需要一个字符缓冲区来打印,指向 ,%ecx长度为%edx%eax另一方面,像这样的寄存器包含一个 32 位整数。

所以如果你真的想打印%eax,你需要先将该整数转换为字符序列。您可以将其转换为 ASCII 十进制、十六进制或您喜欢的任何其他基数,但您需要将其转换为字符。

于 2013-07-02T22:33:45.983 回答