Linux x86 NASM - 子程序:从EAX打印双字

时间:2013-07-02 22:17:59

标签: linux assembly x86 nasm

所以我正在学习使用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范围内,这将

编辑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的值。

2 个答案:

答案 0 :(得分:4)

我在汇编程序中非常生疏,但看起来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的范围。

答案 1 :(得分:3)

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

因此,如果您确实要打印%eax,则需要先将该整数转换为字符序列。您可以将其转换为ASCII十进制,十六进制或任何其他您喜欢的基础,但您需要将其转换为字符。