在运行时查找输入字符串的字节数
Finding the number of bytes of entered string at runtime
我刚开始学习汇编 x86。我编写了一个程序,要求用户输入一个数字,然后检查它是偶数还是奇数,然后打印一条消息来显示此信息。
该代码工作正常,但有一个问题。它仅适用于 1 位数字:
; Ask the user to enter a number from the keyboard
; Check if this number is odd or even and display a message to say this
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
;Display 'Please enter a number'
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov ecx, msg1 ; message to be print
mov edx, len1 ; message length
int 80h ; perform system call
;Enter the number from the keyboard
mov eax, 3 ; sys_read
mov ebx, 2 ; file descriptor: stdin
mov ecx, myvariable ; destination (memory address)
mov edx, 4 ; size of the the memory location in bytes
int 80h ; perform system call
;Convert the variable to a number and check if even or odd
mov eax, [myvariable]
sub eax, '0' ;eax now has the number value
and eax, 01H
jz isEven
;Display 'The entered number is odd'
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov ecx, msg2 ; message to be print
mov edx, len2 ; message length
int 80h
jmp outProg
isEven:
;Display 'The entered number is even'
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov ecx, msg3 ; message to be print
mov edx, len3 ; message length
int 80h
outProg:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db "Please enter a number: ", 0xA,0xD
len1 equ $- msg1
msg2 db "The entered number is odd", 0xA,0xD
len2 equ $- msg2
msg3 db "The entered number is even", 0xA,0xD
len3 equ $- msg3
segment .bss
myvariable resb 4
对于超过 1 位的数字,它不能正常工作,因为它只考虑输入数字的第一个字节(第一个数字),所以它只检查那个。所以我需要一种方法来找出用户输入的值中有多少位数字(字节),这样我就可以做这样的事情:
;将变量转换为数字并检查偶数或奇数
mov eax, [myvariable+(number_of_digits-1)]
并且只检查包含最后一位数字的 eax,看它是偶数还是奇数。
问题是我不知道如何在用户输入后检查我的号码中有多少字节。
我确信这很容易,但我还没有弄明白,也没有在 google 上找到任何解决方案。请帮我解决一下这个。谢谢!
您实际上希望 movzx eax, byte [myvariable+(number_of_digits-1)]
只加载 1 个字节,而不是双字。或者直接用 test byte [...], 1
测试内存。您可以跳过 sub,因为 '0'
是偶数;减去从 ASCII 码转换为整数数字不会改变低位。
但是,是的,您需要最低有效数字,即打印/阅读顺序中的最后(最高地址)。
A read
系统调用 returns 在 EAX 中读取的字节数。(或负错误代码)。如果用户点击 return,这将包括换行符,但如果用户从不以换行符结尾的文件重定向,则不会。 (或者如果他们在输入一些数字后使用 control-d 在终端上提交输入)。最简单和可靠的方法是简单地循环查找缓冲区中的第一个非数字。
但是“聪明”/有趣的方法是检查 [mybuffer + eax - 1]
是否是数字,如果是则使用它。否则检查前一个字节。 (或者只是假设有一个换行符并始终检查 [mybuffer + eax - 2]
,已读取内容的倒数第二个字节。(或者如果用户刚刚按下 [,则关闭缓冲区的开头 return.)
(有效地检查 ASCII 数字;sub al, '0'
/ cmp al, 9
/ ja non_digit
。参见 double condition checking in assembly / What is the idea behind ^= 32, that converts lowercase letters to upper and vice versa?)
只是为了好玩,这里有一个更紧凑的版本,它总是只检查 read()
输入的倒数第二个字节。 (它不检查是否是数字,它会在缓冲区外读取输入长度 0 或 1,例如按 control-D 或 return。)还有读取错误,例如使用 strace ./oddeven <&-
重定向以关闭其标准输入。
注意有趣的部分:
; check if the low digit is even or odd
mov ecx, msg_even
mov edx, msg_odd ; these don't set flags and actually could be done after TEST
test byte [mybuf + eax - 2], 1 ; check the low bit of 2nd-last byte of the read input
cmovnz ecx, edx
;Display selected message
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov edx, msg_odd.len
int 80h ; write(1, digit&1 ? msg_odd : msg_even, msg_odd.len)
我使用了 cmov
,但是 mov ecx, msg_odd
上的一个简单分支就可以了。您不需要为系统调用复制整个设置,只需 运行 使用正确的指针和长度即可。 (ECX 和 EDX 值,我用 space 填充了奇数消息,所以我可以对两者使用相同的长度。)
这是自制的 static_assert(msg_odd.len == msg_even.len)
,使用 NASM 的条件指令 (https://nasm.us/doc/nasmdoc4.html)。它不仅仅是像 C 那样的单独预处理器,它还可以使用 NASM 数字等式表达式。
%if msg_odd.len != msg_even.len
; homebrew assert with NASM preprocessor, since I chose to skip doing a 2nd cmov for the length
%warn we assume both messages have the same length
%endif
完整的东西。我在上面显示的部分之外,我只是调整注释以有时在我认为它太多余时简化,并使用有意义的标签名称。
此外,我将 .rodata
和 .bss
放在顶部,因为 NASM 在定义之前抱怨引用 msg_odd.len
。 (您以前在 .data
中有您的字符串,但只读数据通常应该放在 .rodata 中,因此 OS 可以在同一程序的 运行 之间共享这些页面,因为它们保留干净。)
其他修复:
- Linux/Unix 使用
0xa
行结尾,\n
而不是 \n\r
.
- stdin 是 fd 0。2 是 stderr。 (2 恰好可以工作,因为终端仿真器通常 运行 shell 所有 3 个文件描述符都引用 tty 的相同读+写打开文件描述)。
; Ask the user to enter a number from the keyboard
; Check if this number is odd or even and display a message to say this
section .rodata
msg_prompt db "Please enter a number: ", 0xA
.len equ $- msg_prompt
msg_odd db "The entered number is odd ", 0xA ; padded with a space for same length as even
.len equ $- msg_odd
msg_even db "The entered number is even", 0xA
.len equ $- msg_even
section .bss
mybuf resb 128
.len equ $ - mybuf
section .text
global _start
_start: ; ld defaults to starting at the top of the .text section, but exporting a symbol silences the warning and can make GDB work more easily.
; Display prompt
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov ecx, msg_prompt
mov edx, msg_prompt.len
int 80h ; perform system call
mov eax, 3 ; sys_read
xor ebx, ebx ; file descriptor: stdin
mov ecx, mybuf
mov edx, mybuf.len
int 80h ; read(0, mybuf, len)
; return value in EAX: negative for error, 0 for EOF, or positive byte count
; for this toy program, lets assume valid input ending with digit\n
; the newline will be at [mybuf + eax - 1]. The digit before that, at [mybuf + eax - 2].
; If the user just presses return, we'll access before the end of mybuf, and may segfault if it's at the start of a page.
; check if the low digit is even or odd
mov ecx, msg_even
mov edx, msg_odd ; these don't set flags and actually could be done after TEST
test byte [mybuf + eax - 2], 1 ; check the low bit of 2nd-last byte of the read input
cmovnz ecx, edx
;Display selected message
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov edx, msg_odd.len
int 80h ; write(1, digit&1 ? msg_odd : msg_even, msg_odd.len)
%if msg_odd.len != msg_even.len
; homebrew assert with NASM preprocessor, since I chose to skip doing a 2nd cmov for the length
%warning we assume both messages have the same length
%endif
mov eax, 1 ;system call number (sys_exit)
xor ebx, ebx
int 0x80 ; _exit(0)
assemble + link 与 nasm -felf32 oddeven.asm && ld -melf_i386 -o oddeven oddeven.o
我刚开始学习汇编 x86。我编写了一个程序,要求用户输入一个数字,然后检查它是偶数还是奇数,然后打印一条消息来显示此信息。 该代码工作正常,但有一个问题。它仅适用于 1 位数字:
; Ask the user to enter a number from the keyboard
; Check if this number is odd or even and display a message to say this
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
;Display 'Please enter a number'
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov ecx, msg1 ; message to be print
mov edx, len1 ; message length
int 80h ; perform system call
;Enter the number from the keyboard
mov eax, 3 ; sys_read
mov ebx, 2 ; file descriptor: stdin
mov ecx, myvariable ; destination (memory address)
mov edx, 4 ; size of the the memory location in bytes
int 80h ; perform system call
;Convert the variable to a number and check if even or odd
mov eax, [myvariable]
sub eax, '0' ;eax now has the number value
and eax, 01H
jz isEven
;Display 'The entered number is odd'
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov ecx, msg2 ; message to be print
mov edx, len2 ; message length
int 80h
jmp outProg
isEven:
;Display 'The entered number is even'
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov ecx, msg3 ; message to be print
mov edx, len3 ; message length
int 80h
outProg:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db "Please enter a number: ", 0xA,0xD
len1 equ $- msg1
msg2 db "The entered number is odd", 0xA,0xD
len2 equ $- msg2
msg3 db "The entered number is even", 0xA,0xD
len3 equ $- msg3
segment .bss
myvariable resb 4
对于超过 1 位的数字,它不能正常工作,因为它只考虑输入数字的第一个字节(第一个数字),所以它只检查那个。所以我需要一种方法来找出用户输入的值中有多少位数字(字节),这样我就可以做这样的事情: ;将变量转换为数字并检查偶数或奇数
mov eax, [myvariable+(number_of_digits-1)]
并且只检查包含最后一位数字的 eax,看它是偶数还是奇数。 问题是我不知道如何在用户输入后检查我的号码中有多少字节。 我确信这很容易,但我还没有弄明白,也没有在 google 上找到任何解决方案。请帮我解决一下这个。谢谢!
您实际上希望 movzx eax, byte [myvariable+(number_of_digits-1)]
只加载 1 个字节,而不是双字。或者直接用 test byte [...], 1
测试内存。您可以跳过 sub,因为 '0'
是偶数;减去从 ASCII 码转换为整数数字不会改变低位。
但是,是的,您需要最低有效数字,即打印/阅读顺序中的最后(最高地址)。
A read
系统调用 returns 在 EAX 中读取的字节数。(或负错误代码)。如果用户点击 return,这将包括换行符,但如果用户从不以换行符结尾的文件重定向,则不会。 (或者如果他们在输入一些数字后使用 control-d 在终端上提交输入)。最简单和可靠的方法是简单地循环查找缓冲区中的第一个非数字。
但是“聪明”/有趣的方法是检查 [mybuffer + eax - 1]
是否是数字,如果是则使用它。否则检查前一个字节。 (或者只是假设有一个换行符并始终检查 [mybuffer + eax - 2]
,已读取内容的倒数第二个字节。(或者如果用户刚刚按下 [,则关闭缓冲区的开头 return.)
(有效地检查 ASCII 数字;sub al, '0'
/ cmp al, 9
/ ja non_digit
。参见 double condition checking in assembly / What is the idea behind ^= 32, that converts lowercase letters to upper and vice versa?)
只是为了好玩,这里有一个更紧凑的版本,它总是只检查 read()
输入的倒数第二个字节。 (它不检查是否是数字,它会在缓冲区外读取输入长度 0 或 1,例如按 control-D 或 return。)还有读取错误,例如使用 strace ./oddeven <&-
重定向以关闭其标准输入。
注意有趣的部分:
; check if the low digit is even or odd
mov ecx, msg_even
mov edx, msg_odd ; these don't set flags and actually could be done after TEST
test byte [mybuf + eax - 2], 1 ; check the low bit of 2nd-last byte of the read input
cmovnz ecx, edx
;Display selected message
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov edx, msg_odd.len
int 80h ; write(1, digit&1 ? msg_odd : msg_even, msg_odd.len)
我使用了 cmov
,但是 mov ecx, msg_odd
上的一个简单分支就可以了。您不需要为系统调用复制整个设置,只需 运行 使用正确的指针和长度即可。 (ECX 和 EDX 值,我用 space 填充了奇数消息,所以我可以对两者使用相同的长度。)
这是自制的 static_assert(msg_odd.len == msg_even.len)
,使用 NASM 的条件指令 (https://nasm.us/doc/nasmdoc4.html)。它不仅仅是像 C 那样的单独预处理器,它还可以使用 NASM 数字等式表达式。
%if msg_odd.len != msg_even.len
; homebrew assert with NASM preprocessor, since I chose to skip doing a 2nd cmov for the length
%warn we assume both messages have the same length
%endif
完整的东西。我在上面显示的部分之外,我只是调整注释以有时在我认为它太多余时简化,并使用有意义的标签名称。
此外,我将 .rodata
和 .bss
放在顶部,因为 NASM 在定义之前抱怨引用 msg_odd.len
。 (您以前在 .data
中有您的字符串,但只读数据通常应该放在 .rodata 中,因此 OS 可以在同一程序的 运行 之间共享这些页面,因为它们保留干净。)
其他修复:
- Linux/Unix 使用
0xa
行结尾,\n
而不是\n\r
. - stdin 是 fd 0。2 是 stderr。 (2 恰好可以工作,因为终端仿真器通常 运行 shell 所有 3 个文件描述符都引用 tty 的相同读+写打开文件描述)。
; Ask the user to enter a number from the keyboard
; Check if this number is odd or even and display a message to say this
section .rodata
msg_prompt db "Please enter a number: ", 0xA
.len equ $- msg_prompt
msg_odd db "The entered number is odd ", 0xA ; padded with a space for same length as even
.len equ $- msg_odd
msg_even db "The entered number is even", 0xA
.len equ $- msg_even
section .bss
mybuf resb 128
.len equ $ - mybuf
section .text
global _start
_start: ; ld defaults to starting at the top of the .text section, but exporting a symbol silences the warning and can make GDB work more easily.
; Display prompt
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov ecx, msg_prompt
mov edx, msg_prompt.len
int 80h ; perform system call
mov eax, 3 ; sys_read
xor ebx, ebx ; file descriptor: stdin
mov ecx, mybuf
mov edx, mybuf.len
int 80h ; read(0, mybuf, len)
; return value in EAX: negative for error, 0 for EOF, or positive byte count
; for this toy program, lets assume valid input ending with digit\n
; the newline will be at [mybuf + eax - 1]. The digit before that, at [mybuf + eax - 2].
; If the user just presses return, we'll access before the end of mybuf, and may segfault if it's at the start of a page.
; check if the low digit is even or odd
mov ecx, msg_even
mov edx, msg_odd ; these don't set flags and actually could be done after TEST
test byte [mybuf + eax - 2], 1 ; check the low bit of 2nd-last byte of the read input
cmovnz ecx, edx
;Display selected message
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor: stdout
mov edx, msg_odd.len
int 80h ; write(1, digit&1 ? msg_odd : msg_even, msg_odd.len)
%if msg_odd.len != msg_even.len
; homebrew assert with NASM preprocessor, since I chose to skip doing a 2nd cmov for the length
%warning we assume both messages have the same length
%endif
mov eax, 1 ;system call number (sys_exit)
xor ebx, ebx
int 0x80 ; _exit(0)
assemble + link 与 nasm -felf32 oddeven.asm && ld -melf_i386 -o oddeven oddeven.o