这是一个程序,它一个接一个地接受十进制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?怎么了?
您的范围检查有bug,使用有符号比较(jg
)而不是无符号比较(ja
),所以当c - '0'
从10开始时,您只检测非数字字符。127,而不是当它环绕(即变成负符号),丢失几乎一半的字节值,你应该排除。在ASCII范围的低端包括像换行符这样的控制码。
- 装配双工况检查
- 装配中JA与JG的差异
- NASM程序集将输入转换为整数吗?显示了正确执行检查的循环。
那么为什么GDB使它工作呢?
您的程序只使用大小为1的read
,在按下return后留下一个未读换行符。这是ASCII0xa
='n'
,所以你的下一个read(1, buf, 1)
得到它。
除非GDB先获得它:GDB接管终端以在read(1,buf,1)
之后读取更多命令,因此GDB获得剩余的终端输入并在单步执行下一个read
系统调用之前丢弃换行符。或者,当GDB将终端从熟切换到生时,它可能会被丢弃,这样它就可以读取单个按键,而不必等待"提交";使用EOL(换行符)或EOF (ctrl-d)控制字符从内核的规范模式行编辑中删除。
这是因为您的程序与GDB共享一个终端,而不是将GDB附加到已经在另一个终端选项卡/窗口中运行的程序。例如,在不同的Unix TTY上。例如:与gdb -p $(pidof st3-18a)
.
您也可以使用strace
或strace ./st3-18a
,因为strace没有交互式输入。
在使用"cooked"的玩具程序中,通常将read
放入合适大小的缓冲区并忽略后面的字符。TTY输入。如果你从一个文件中重定向输入,那么多行同时准备好,所以如果你想要一些健壮的东西,你可以使用libc中的fgets
。
只要您意识到I/O过于简单且不健壮,那么在使用asm时就可以随心所欲,即使这意味着假设行和tty处理,以及用户按enter而不是control-d。
在终端中运行cat
,输入部分行并按control-D。您可以从另一个终端strace -p $(pidof cat)
查看它的系统调用。
参见如何使用NASM程序集忽略输入中的换行符?