在运行时查找输入字符串的字节数

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