Intel Assembly 中的键盘缓冲区

Keyboard buffer in Intel Assembly

我在 Intel Assembly 上管理 "keyboard overflows" 时遇到问题。 主要问题是在读取了 read 调用指定的最大大小之后,剩余的数据被扔进了终端。 我在 x64 架构上使用 Linux。 这其实就是功课。 我的主要想法如下:

%define maxChars     10
%define maxChars_2   100

section .bss
   strLida  : resb maxChars
   strLidaL : resd 1

read:
   mov dword [strLidaL], maxChars

   mov rax, 0
   mov rdi, 1
   mov rsi, strLida
   mov rdx, [strLidaL]
   syscall

   mov [strLidaL], rax

size_compare:   
   cmp [strLidaL], maxChars
   jge overflow

overflow:
   mov dword [strLidaL_2], maxChars_2

   mov rax, 0
   mov rdi, 1
   mov rsi, strLida_2
   mov rdx, [strLidaL_2]
   syscall

这远不是一个好的解决方案,它会在达到最大字符数时跳转到另一个读取函数,以便它可以吞下剩余的溢出字符。 有一个系统调用吗?有更好的解决方案吗? 感谢您的输入。

您的解决方案一旦推广就非常好。

首先考虑这个C程序

cook.c

#include <stdio.h>

int main()
{
  char buffer[200];
  scanf("%s", buffer);

  return 0;
}

它很脆弱,return 是多余的,但请耐心等待。
该程序只是从输入中读取一个字符串,与您的输入非常相似。

如果你输入像 hello world 这样的短字符串,scanf 会将 hello 读入 buffer 但是 world 不会出现在终端中(与您的程序不同)。 那么 scanf 是如何做到这一点的呢?

无需逆向工程(或获取源代码)即可分析程序的简便方法是 strace
如果我在我的系统中 运行 strace ./cook 我可以看到 cook 执行 sys_read 系统调用 as

read(0, "hello world\n", 1024)          = 12

因此 scanf 简单地以 1024 字节的块读取,在这种情况下。
我不知道 libc 用来设置读取长度的逻辑,因为我认为它在这里不相关,所以我不会深入研究它。

如果我们输入超过 1024 个字符怎么办?
如果我键入 1 2 3 4 ... 1024(即所有 1024 以内的数字由 space 分隔)并按下结果是

manager@debian64-jboss:~$ ./cook
1 2 3 4 5 [... omitted]
manager@debian64-jboss:~$ 284 285 286 287 288 289 290 [... omitted]

显示部分输入进入终端提示。
如果我们进行数学计算,我们将得到预期的 9*2 + 90*3 + 184 * 4 = 1024。

长话短说:您并没有真正遇到问题 - 这是 Linux 下的预期行为。
在你的情况下,它更烦人,因为你读取的字节数很少。
说来话长the input processing mode: canonical or non-canonical.
默认的是 canonical,其中 OS 缓冲文本行以提供输入编辑功能。

如果您的程序要求 5 个字节,并且用户键入 hello world 并按下回车键,OS 将缓冲整个 "hello world\n" 字符串,但 sys_read 将只读到 space,为下一个 reader(shell)留下“world\n”。

您可以选择修复或缓解此问题。

读取更大的尺寸可以缓解问题 - 就像 C 示例一样。 由于您应该始终检查函数或系统调用的 return 值,因此这不会对您的程序布局产生重大影响。

或者,您可以按照 comp.lang.c 的建议阅读所有输入。
在汇编中,您可以使用

以一般方式执行此操作
 ;edi = file descriptor
emptyfd:
 lea rsi, [rsp-80h]     ;We use the redzone for the read buffer
 mov edx, 80h           ;Chunk length

.read_chunk:
 xor eax, eax       ;sys_read
 syscall

 ;We read all the buffer? (Note: this also check for errors as long as rdx != -1)
 cmp rdx, rax
 je .read_chunk

 ret

当心损坏的寄存器。

我不知道有任何系统调用这样做,但我不希望有任何系统调用 - 标准输入对内核没有特殊意义。


附带说明一下,将寄存器清零的好方法是
此外,移动或对 64 位寄存器的低 32 位部分执行操作会将高 32 位归零 - 因此 mov rdi, 1 可以写为 mov edi, 1.
NASM 无论如何都会将前者隐式转换为后者。