assembly 一次读取1个字节的程序在调试器中工作,但在没有它的情况下会中断

but5z9lq  于 2022-11-24  发布在  其他
关注(0)|答案(1)|浏览(129)

下面是一个程序,它逐个获取十进制ASCII数字,并将它们转换为整数。结果存储在EDI寄存器中:

global _start
%macro kernel 4
    mov eax, %1
    mov ebx, %2
    mov ecx, %3
    mov edx, %4
    int 80h
%endmacro
section .bss
    symbol resb 1
section .text
_start:
    mov esi, 10
    xor edi, edi
.loop:
    kernel 3, 0, symbol, 1 ; load 1 char from STDIN into symbol
    test eax, eax          ; nothing loaded - EOF
    jz .quit
    xor ebx, ebx
    mov bl, [symbol]
    sub bl, '0'
    cmp bl, 9
    jg .quit               ; not a number
    mov eax, edi           ; previously accumulated number
    mul esi                ; eax *= 10
    lea edi, [eax + ebx]
    jmp .loop

.quit:
    mov eax, 1
    mov ebx, edi
    int 80h

我编译它:

$ nasm -g -f elf32 st3-18a.asm
$ ld -g -m elf_i386 st3-18a.o -o st3-18a
$ ./st3-18a
2[Enter]
Ctrl-d

当我在gdb中一步一步地运行这段代码时,一切都是正确的,最后存储在EDI中的结果是2。但是当我在没有调试器的情况下运行,并回显程序返回值时:

$ ./st3-18a
2[Enter]
Ctrl-d
$ echo $?
238

为什么输出0xEE?出了什么问题?

aiqt4smr

aiqt4smr1#

你的范围检查有缺陷,使用有符号比较(jg)而不是无符号比较(ja),所以你只在c - '0'从10..127开始时检测非数字字符,而不是当它回绕(即变成有符号负数)时,丢失了几乎一半你应该排除的字节值。包括ASCII范围低端的控制代码,如换行符。

那么,为什么广发银行会让它发挥作用呢?

你的程序只使用大小为1的read,在你按回车键后留下一个未读的换行符,这就是ASCII码0xa = '\n',所以你的下一个read(1, buf, 1)会得到它。
除非广发银行先拿到:在read(1,buf,1)之后,GDB接管终端读取更多命令,因此GDB在单步执行到下一个read系统调用之前获取剩余的终端输入并丢弃换行符。使用EOL(换行符)或EOF(ctrl-d)控制字符进行编辑。
这是因为你的程序与GDB共享一个终端,而不是将GDB附加到已经在另一个终端选项卡/窗口中运行的程序。例如,在不同的Unix TTY上。例如,gdb -p $(pidof st3-18a)
你也可以用strace来实现,或者只使用strace ./st3-18a,因为strace没有交互式输入。
在使用"熟" TTY输入的玩具程序中,通常会将read放到一个大小合适的缓冲区中,并忽略后面的字符。如果你从一个文件中重定向输入,以便一次准备好多行,这将中断,所以如果你想要一些健壮的东西,你可以使用libc中的fgets
但是,只要您意识到I/O过于简单且不健壮,那么在使用asm时,您就可以随心所欲,即使这意味着要对行和tty处理以及用户按Enter键而不是control-d进行假设。
在一个终端上运行cat,键入一个部分行并按下ctrl-D。您可以从另一个终端上运行strace -p $(pidof cat)来查看它的系统调用。
另请参阅How do I ignore line breaks in input using NASM Assembly?

相关问题