assembly 从STDIN阅读一个数字,将其转换为实际数字并将其打印回STDOUT

tpxzln5u  于 2023-02-08  发布在  其他
关注(0)|答案(1)|浏览(117)

我正在学习汇编。我正在编写一个简单的程序,它应该做以下事情:

  • 从STDIN读取2位数
  • 递减读取数
  • 将递减结果发送到STDOUT

这里的程序,我到目前为止:

section .data
    max_number_size: equ 2 ; max length of read input

section .bss
    input_buffer: resb max_number_size ; used to store input number

section .text
    global _start
    _start:
        mov eax, 3                  ; sys call read
        mov ebx, 0                  ; from FD 0
        mov ecx, input_buffer       ; indicate the adress of the memory buffer where the bytes will be stored
        mov edx, max_number_size    ; read this quantity of character
        int 80H                     ; store max_number_size to input_buffer from STDIN

    atoi:
        mov eax, 0                  ; Set initial total to 0
        mov ebx, 0                  ; keep track of nbr of char processed
        
        atoi_convert:
            mov esi, [ecx]              ; Get the current character
            test ebx, max_number_size   ; break the loop
            je _stdout

            cmp esi, 48                 ; Anything less than char 0 is invalid (check ASCII table)
            jl _exit_1

            cmp esi, 57                 ; Anything greater than char 9 is invalid (check ASCII table)
            jg _exit_1

            sub esi, 48                 ; Convert from ASCII to decimal (0 starts at 48d in ASCII)
            imul eax, 10                ; Multiply total by 10d
            add eax, esi                ; Add current digit to total

            inc ecx                     ; Get the address of the next character
            inc ebx                     ; keep track of nbr of char processed
            jmp atoi_convert

    _stdout:
        mov ecx, eax
        mov eax, 4
        mov ebx, 1
        mov edx, 32
        int 80h

    _exit_0:
        mov eax, 1
        mov ebx, 0
        int 80H

    _exit_1:
        mov eax, 1
        mov ebx, 1
        int 80H

注意,在上面的代码中,还没有递减。
我很难理解的是在这里的_stdout标签中实际发送到STDOUT的是什么。
我所理解的是,使用atoi,从stdin读取的ASCII字符被转换成实际的十进制值(如果input是12,则eax中atoi之后的值将是0000 1100(二进制))。
因此,当到达_stdout时,eax中有12d(0000 1100b)。为了将其发送到STDOUT,我将该值移动到ecx中,为系统调用配置eaxebxedx,然后,boom,系统调用。
但是,根本没有输出。
例如,参见:

/app # make
nasm -f elf -g -F stabs main.asm
ld -o main.exe main.o -m elf_i386
/app # ./main.exe > stdout
49
/app #
/app # ls -l | grep stdout
-rwxr-xr-x    1 root     root             0 Feb  7 12:05 stdout

stdout文件是空的。甚至没有一个字节。
我在这个程序中做错了什么,因此,我没有正确理解什么?

qvtsj1bj

qvtsj1bj1#

更新:如@Peter Cordes所言,第一段代码缺少输出ASCII数字的功能当前代码在@Peter Cordes给出的reference link的基础上增加了ASCII数字打印功能,谢谢!

使用像GDB这样的调试器来逐步执行并查看寄存器如何变化呢?
如果使用GDB在_stdoutint 0x80之前断点,您会注意到ecx为NULL。

(gdb) catch syscall 4
Catchpoint 1 (syscall 'write' [4])
(gdb) r
49 <- user's input

Catchpoint 1 (call to syscall write), 0x08049053 in _exit_0 ()
(gdb) p/x $ecx
$1 = 0x0

我们来看看为什么,_stdout的调用者是atoi_convert,我们把atoi_convert断点,一步一步的运行(使用si命令),你没注意到它总是在je _stdout中跳转_stdout吗?
test ebx, max_number_size实际上只是在执行and指令。它通常用于检测零。在这种情况下,cmp ebx, max_number_size是合适的。
atoi_convert中,mov esi, [ecx]ecx的4字节内容复制到esi

(gdb) b atoi_convert
Breakpoint 1 at 0x8049020
(gdb) r
49 <- user's input

Breakpoint 1, 0x08049020 in atoi_convert ()
(gdb) ni <- to run mov esi, [ecx]
0x08049022 in atoi_convert ()
(gdb) p/x $esi
$1 = 0x3934
(gdb) x/wx $ecx
0x804a000 <input_buffer>:       0x00003934

如您所见,当我输入49时,$esi0x3934。但是您假设esi是1字节(来自cmp esi, 48等)。正如@Jester所说,您应该使用movzx esi, byte [ecx]以确保内容是1字节,并且您应该使用read的返回值而不是max_number_size
而且,如果用户在终端上只输入一个字符,也会增加一个换行符,如果有无法识别的字符,我觉得调用_stdout比调用_exit_1更好。
让我们再次在int 0x80之前设置断点。

(gdb) catch syscall write
Catchpoint 1 (syscall 'write' [4])
(gdb) r
49 <- user's input

Catchpoint 1 (call to syscall write), 0x08049053 in _exit_0 ()
(gdb) p $ecx
$1 = 49

第二个参数应该是一个指针,但它是一个值,我需要把这个值转换成一个指向ASCII字符的指针,看看这个:How do I print an integer in Assembly Level Programming without printf from the c library?
不管怎样,下面的代码是可以工作的。我还添加了一些我没有提到的修正,比如对read的错误检查。另外,我还添加了一个基于reference link的ASCII数字打印函数。

section .data
    max_number_size: equ 2

section .bss
    input_buffer: resb max_number_size

section .text
    global _start
    _start:
        mov eax, 3
        mov ebx, 0
        mov ecx, input_buffer
        mov edx, max_number_size
        int 80H
        cmp eax, -1 ; error check
        jz _exit_1
        mov edi, eax

    atoi:
        mov eax, 0
        mov ebx, 0

        ; when input is completely wrong (like `AA`), don't show output.
        movzx esi, byte [ecx]
        cmp ebx, edi
        je _exit_1
        cmp esi, 48
        jl _exit_1
        cmp esi, 57
        jg _exit_1

        atoi_convert:
            movzx esi, byte [ecx]
            cmp ebx, edi
            je print_uint32

            cmp esi, 48
            jl print_uint32

            cmp esi, 57
            jg print_uint32

            sub esi, 48
            imul eax, 10
            add eax, esi

            inc ecx
            inc ebx
            jmp atoi_convert

    ; This code is referenced from <https://stackoverflow.com/a/46301894>.
    print_uint32:
        xor ebx, ebx
        mov ecx, 0xa
        push ecx
        mov esi, esp
        add esp, 4
    .toascii_digit:
        inc ebx ; Number of digits to output
        xor edx, edx
        div ecx
        add edx, '0'
        dec esi
        mov [esi], dl

        test eax, eax
        jnz .toascii_digit

        mov edx, ebx ; ebx will be overwritten. Move it before that.
        mov eax, 4
        mov ebx, 1
        mov ecx, esi
        int 80h

    _exit_0:
        mov eax, 1
        mov ebx, 0
        int 80H

    _exit_1:
        mov eax, 1
        mov ebx, 1
        int 80H

相关问题