深入Linux 0.11内核:我是如何通过GDB跟踪键盘中断拿到‘密码’的
2026/6/3 1:31:25 网站建设 项目流程

深入Linux 0.11内核:我是如何通过GDB跟踪键盘中断拿到‘密码’的

记得第一次在终端输入passwd命令时,屏幕突然停止回显字符的瞬间——那种既熟悉又陌生的体验,像触碰到了操作系统深处的隐秘开关。今天,我将用GDB调试器带您穿越到1991年的Linux 0.11内核,一起揭开键盘输入背后的魔法帷幕。这不是普通的操作指南,而是一次从按键触发到屏幕显示的完整数据流探险,适合已经熟悉C语言和基本汇编的开发者。

1. 实验环境搭建与调试起手式

在开始追踪键盘中断之前,我们需要一个能运行Linux 0.11的Bochs虚拟机环境。与直接使用预编译镜像不同,我更喜欢从源码构建调试版本:

# 获取特定调试版内核 git clone https://github.com/oldlinux/linux-0.11-lab.git cd linux-0.11-lab make debug

启动时需要特别注意两个终端窗口的配合:

  • 终端A运行Bochs虚拟机:./run
  • 终端B启动GDB调试器:./mygdb

提示:如果遇到段错误,尝试在Makefile中添加-g编译选项重新构建内核

调试器的第一个断点应该设在键盘中断入口。通过查阅keyboard.S源码,我发现0x21号中断的处理函数地址存储在中断描述符表(IDT)中。用GDB验证这个关键地址:

(gdb) x/8x 0x00000000 0x0: 0x00eb8e00 0x00080000 0x0000ea00 0x00080000 0x10: 0x00eb8e00 0x00080000 0x0000ea00 0x00080000 (gdb) break *0x9470 # keyboard_interrupt的物理地址

2. 从物理按键到扫描码的奇幻漂流

当我在Bochs窗口按下'a'键时,GDB立即在断点处暂停。此时查看CPU寄存器,能看到键盘控制器通过I/O端口0x60发送的原始扫描码:

eax 0x1 1 ecx 0x0 0 edx 0x60 96

通过keyboard.S中的转换表,这个扫描码会被映射为ASCII字符。但有趣的是,Linux 0.11采用了一种双层缓冲机制

  1. 原始扫描码队列:存储在keyboard.S定义的keyboard_buffer数组
  2. 处理后字符队列:位于tty_io.ctty_table结构体

用GDB观察这个转换过程:

(gdb) x/16b 0x9020 # 查看键盘缓冲区 0x9020: 0x1e 0x9e 0x00 0x00 0x00 0x00 0x00 0x00 0x9028: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

这里0x1e对应'a'键按下事件,0x9e则是释放事件。转换后的字符最终会进入tty设备的secondary队列,这就是getchar()函数读取的数据源。

3. 终端回显的秘密:为什么第二次输入不可见

当实验要求输入passwd后接着输入secret时,第二个字符串的神秘消失其实与termios结构体中的c_lflag字段有关。通过GDB我们可以捕捉这个状态变化:

(gdb) p *(struct termios*)0x5a00 $1 = { c_iflag = 1280, c_oflag = 5, c_cflag = 191, c_lflag = 35387, # 包含ECHO标志位 c_line = 0 '\000' }

tty_ioctl.c中,当检测到TCSETSW操作时,内核会修改这个标志位。具体到密码输入场景:

  1. 第一次passwd输入后,tty_ioctl()清除了ECHO标志
  2. secret输入时,con_write()函数检查到无回显标志,直接丢弃显示输出
  3. 数据仍存在于tty缓冲区,可以被read()系统调用获取

这个机制解释了为什么密码输入时看不到字符,但程序仍能获取到正确内容。通过反汇编sys_read系统调用,我们能看到它最终调用tty_read()secondary队列提取数据。

4. 字符设备驱动全景:从键盘到控制台的数据管道

Linux 0.11的字符设备驱动架构比想象中精巧。通过分析chr_dev数组,我发现键盘输入最终流向控制台设备的过程涉及多个关键函数:

函数名所在文件作用
keyboard_interruptkeyboard.S处理硬件中断,填充扫描码缓冲区
copy_to_cookedtty_io.c转换扫描码为ASCII,处理特殊控制符
tty_readtty_io.c从secondary队列读取处理后的字符
con_writeconsole.c控制台输出(受c_lflag影响)

用GDB跟踪gets()函数的完整调用链时,我设置了一组精妙的断点:

(gdb) break *0x2e3a # sys_read入口 (gdb) break *0x4d7c # tty_read (gdb) break *0x4a9a # copy_to_cooked

当在Bochs输入abc时,能看到数据流经这些函数的完整轨迹。特别值得注意的是copy_to_cooked中的这段汇编:

movb %al, (%edi) # 存储处理后的字符 cmpl $0xa, %eax # 检查是否是回车 je 1f # 如果是则跳转处理

这解释了为什么输入需要以回车结束——内核在收到0x0a(换行符)时才认为一行输入完成。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询