如何逐个字符地从汇编中的标准输入读取输入
How to read input from stdin in assembly, character by character
我希望下面的程序从 stdin
中读取一些字符(最多 9 个),并将它们放在内存中的指定位置。
实际发生了什么:当我按 Enter 时,如果我的字符少于 9 个,它就会转到下一行;这将一直发生,直到我输入 9 个字符。如果我输入超过 9 个,多余的字符将被解释为 shell 命令。为什么当我按 Enter 时它没有终止?
在 Ubuntu 上使用 nasm
2.14.02。
global _start
section .bss
buf resb 10
section .text
; Read a word from stdin, terminate it with a 0 and place it at the given address.
; - , rdi: *buf - where to place read bytes
; - , rsi: max_count, including the NULL terminator
; Returns in rax:
; - *buf - address of the first byte where the NULL-terminated string was placed
; - 0, if input too big
read_word: ; (rdi: *buf, rsi: max_count) -> *buf, or 0 if input too big
mov r8, 0 ; current count
mov r9, rsi ; max count
dec r9 ; one char will be occupied by the terminating 0
; read a char into the top of the stack, then pop it into rax
.read_char:
push rdi ; save; will be clobbered by syscall
mov rax, 0 ; syscall id = 0 (read)
mov rdi, 0 ; syscall , fd = 0 (stdin)
push 0 ; top of the stack will be used to place read byte
mov rsi, rsp ; syscall , *buf = rsp (addr where to put read byte)
mov rdx, 1 ; syscall , count (how many bytes to read)
syscall
pop rax
pop rdi
; if read character is Enter (aka carriage-return, CR) - null-terminate the string and exit
cmp rax, 0x0d ; Enter
je .exit_ok
; not enter ⇒ place it in the buffer, and read another one
mov byte [rdi+r8], al ; copy character into output buffer
inc r8 ; inc number of collected characters
cmp r8, r9 ; make sure number doesn't exceed maximum
je .exit_ok ; if we have the required number of chars, exit
jb .read_char ; if it's not greater, read another char
.exit_ok: ; add a null to the end of the string and return address of buffer (same as input)
add r8, 1
mov byte [rdi+r8], 0
mov rax, rdi
ret
.exit_err: ; return 0 (error)
mov rax, 0
ret
_start:
mov rdi, buf ; - *buf
mov rsi, 10 ; - uint count
call read_word
mov rax, 60 ; exit syscall
mov rdi, 0 ; exit code
syscall
首先,当用户按下 Enter 键时,您将看到 LF(\n
、0xa
),而不是 CR(\r
、0xd
)。这可以解释为什么您的程序在您认为应该退出的时候没有退出。
至于为什么额外的字符进入 shell,这是关于 OS 如何进行终端输入的。它会将来自终端的击键累积到内核缓冲区中,直到按下 Enter,然后使整个缓冲区可供 read()
读取。这允许像退格键这样的东西透明地工作而不需要应用程序明确编码,但这确实意味着你不能一次真正地读取一个击键,正如你所注意到的那样。
如果您的程序在缓冲区仍包含字符时退出,那么这些字符将由下一个尝试从设备读取的程序读取,在您的情况下将是 shell。大多数读取 stdin 的程序通过继续读取和处理数据直到看到文件末尾(read()
返回 0)来避免这种情况,当用户按下 Ctrl-D 时终端会发生这种情况。
如果你真的需要逐个字符地处理输入,你需要将终端设置为non-canonical mode,但在这种情况下很多事情会有所不同。
我希望下面的程序从 stdin
中读取一些字符(最多 9 个),并将它们放在内存中的指定位置。
实际发生了什么:当我按 Enter 时,如果我的字符少于 9 个,它就会转到下一行;这将一直发生,直到我输入 9 个字符。如果我输入超过 9 个,多余的字符将被解释为 shell 命令。为什么当我按 Enter 时它没有终止?
在 Ubuntu 上使用 nasm
2.14.02。
global _start
section .bss
buf resb 10
section .text
; Read a word from stdin, terminate it with a 0 and place it at the given address.
; - , rdi: *buf - where to place read bytes
; - , rsi: max_count, including the NULL terminator
; Returns in rax:
; - *buf - address of the first byte where the NULL-terminated string was placed
; - 0, if input too big
read_word: ; (rdi: *buf, rsi: max_count) -> *buf, or 0 if input too big
mov r8, 0 ; current count
mov r9, rsi ; max count
dec r9 ; one char will be occupied by the terminating 0
; read a char into the top of the stack, then pop it into rax
.read_char:
push rdi ; save; will be clobbered by syscall
mov rax, 0 ; syscall id = 0 (read)
mov rdi, 0 ; syscall , fd = 0 (stdin)
push 0 ; top of the stack will be used to place read byte
mov rsi, rsp ; syscall , *buf = rsp (addr where to put read byte)
mov rdx, 1 ; syscall , count (how many bytes to read)
syscall
pop rax
pop rdi
; if read character is Enter (aka carriage-return, CR) - null-terminate the string and exit
cmp rax, 0x0d ; Enter
je .exit_ok
; not enter ⇒ place it in the buffer, and read another one
mov byte [rdi+r8], al ; copy character into output buffer
inc r8 ; inc number of collected characters
cmp r8, r9 ; make sure number doesn't exceed maximum
je .exit_ok ; if we have the required number of chars, exit
jb .read_char ; if it's not greater, read another char
.exit_ok: ; add a null to the end of the string and return address of buffer (same as input)
add r8, 1
mov byte [rdi+r8], 0
mov rax, rdi
ret
.exit_err: ; return 0 (error)
mov rax, 0
ret
_start:
mov rdi, buf ; - *buf
mov rsi, 10 ; - uint count
call read_word
mov rax, 60 ; exit syscall
mov rdi, 0 ; exit code
syscall
首先,当用户按下 Enter 键时,您将看到 LF(\n
、0xa
),而不是 CR(\r
、0xd
)。这可以解释为什么您的程序在您认为应该退出的时候没有退出。
至于为什么额外的字符进入 shell,这是关于 OS 如何进行终端输入的。它会将来自终端的击键累积到内核缓冲区中,直到按下 Enter,然后使整个缓冲区可供 read()
读取。这允许像退格键这样的东西透明地工作而不需要应用程序明确编码,但这确实意味着你不能一次真正地读取一个击键,正如你所注意到的那样。
如果您的程序在缓冲区仍包含字符时退出,那么这些字符将由下一个尝试从设备读取的程序读取,在您的情况下将是 shell。大多数读取 stdin 的程序通过继续读取和处理数据直到看到文件末尾(read()
返回 0)来避免这种情况,当用户按下 Ctrl-D 时终端会发生这种情况。
如果你真的需要逐个字符地处理输入,你需要将终端设置为non-canonical mode,但在这种情况下很多事情会有所不同。