【问题标题】:Finding the number of bytes of entered string at runtime在运行时查找输入字符串的字节数
【发布时间】:2021-06-03 02:03:06
【问题描述】:

我是学习汇编 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 以查看它是偶数还是奇数。 问题是我不知道如何在用户输入号码后检查我的号码中有多少字节。 我确信这很容易,但我无法弄清楚,也没有找到任何关于如何在谷歌上做到这一点的解决方案。请帮我解决一下这个。谢谢!

【问题讨论】:

  • 实际上,对于这个玩具示例,您只需要最后一位即可知道整个值是奇数还是偶数。
  • 注意stdin是0。2是stderr。 (它恰好可以工作,因为终端仿真器通常使用所有 3 个文件描述符来运行 shell,这些文件描述符引用 tty 的相同读写打开文件 description)。

标签: linux assembly x86 system-calls


【解决方案1】:

您实际上希望 movzx eax, byte [myvariable+(number_of_digits-1)] 仅加载 1 个字节,而不是 dword。或者直接用test byte [...], 1 测试内存。您可以跳过子,因为'0' 是偶数;从 ASCII 码转换为整数的减法不会改变低位。

但是是的,您需要最低有效位,即打印/阅读顺序中的最后一个(最高地址)。

read 系统调用返回在 EAX 中读取的字节数。(或负错误代码)。如果用户点击返回,这将包括一个换行符,但如果用户从一个不以换行符结尾的文件重定向,则不包括一个换行符。 (或者如果他们在输入一些数字后使用 control-d 在终端上提交了输入)。最简单和稳健的方法是简单地循环查找缓冲区中的第一个非数字。

但“聪明”/有趣的方法是检查 [mybuffer + eax - 1] 是否为数字,如果是则使用它。否则检查前一个字节。 (或者只是假设有一个换行符并始终检查[mybuffer + eax - 2],即所读取内容的倒数第二个字节。(或者如果用户刚刚按下回车,则从缓冲区的开头开始。)

(为了有效地检查 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 或返回。)同样适用于读取错误,例如使用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 值,并且我用空格填充了奇数消息,因此我可以对两者使用相同的长度。)

这是一个自制的 static_assert(msg_odd.len == msg_even.len),使用 NASM 的条件指令 (https://nasm.us/doc/nasmdoc4.html)。它不像 C 那样只是一个单独的预处理器,它可以使用 NASM 数字 equ 表达式。

%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

完整的东西。我在上面显示的部分之外,我只是调整了 cmets 以有时在我认为它过于多余时进行简化,并使用有意义的标签名称。

另外,我将.rodata.bss 放在顶部,因为NASM 抱怨在定义之前引用msg_odd.len。 (您之前在 .data 中保存了字符串,但只读数据通常应该放在 .rodata 中,因此操作系统可以在同一程序的运行之间共享这些页面,因为它们保持干净。)

其他修复:

  • Linux/Unix 使用0xa 行尾,\n 不是\n\r
  • stdin 是 fd 0。2 是 stderr。 (2 恰好可以工作,因为终端仿真器通常使用所有 3 个文件描述符来运行 shell,这些文件描述符引用 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)

组装+链接nasm -felf32 oddeven.asm && ld -melf_i386 -o oddeven oddeven.o

【讨论】:

  • 确实,很好的解释现在它起作用了,而且我记得并且通过阅读你的答案也学到了一些东西。你能告诉我“%”在汇编中做了什么吗?这个断言有什么意义?是否类似于 C/C++ 中的 #define is 在预编译(在这种情况下为组装)进行评估?
  • @Cantaff0rd:我链接 NASM 手册 nasm.us/doc/nasmdoc4.html 是有原因的。就像我在回答那段中所说的那样,不,它只是一个预处理器;正如您在此处看到的,它正在比较源自$ - symbol EQU 定义的值。我的回答中也解释了 static_assert 的要点:警告如果您将一个字符串更改为与另一个不同的长度,因为我省略了 cmov 为 EDX 选择 2 个不同长度之一。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-16
  • 1970-01-01
  • 2020-02-14
  • 2020-10-13
  • 2023-03-16
相关资源
最近更新 更多