Linux x86 NASM-子例程:从EAX打印一个双字



所以我正在学习使用NASM语法的x86 Linux汇编(哦,天哪,不要再这样了,你们都在想)。我正在尝试制作一个子程序,只需将EAX中的值打印到stdout。代码运行并退出时没有出现错误,但没有打印任何内容。我不明白为什么。首先,这是我正在处理的文件:

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,这是一个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范围内时,才有效。

EDIT 2:所以我对sys_write的第二个参数(存储在edx中的参数)有点困惑。我认为它只是指一些字节。因此,对于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的值。

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

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

现在请记住,这只适用于0x0000和0x0009之间的EAX值。当你超过9时,你会得到其他ASCII字符。

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

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

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