下面是一个程序,它逐个获取十进制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?出了什么问题?
1条答案
按热度按时间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?