跳转至

系统调用入门 — 先修一堵墙(特权级),再开一扇门(syscall)

这一章讲什么: 上一章的应用已经是 ring 0 的公民:知道地址就能调内核函数、 读写内核内存。本章分两步收拾这个烂摊子: 第一步修墙——用 x86 的特权级(ring 0 内核 / ring 3 应用)把应用关进笼子, 配合页表的 user 位,应用碰内核区域直接页面故障; 第二步开门——墙内的应用总得有办法求系统办事,门就是 syscall: 一条从低权限进高权限的专用跳转,门口站一个检查参数的守门人。

1. 先看现象:一扇门是怎么立起来的

主线例子:

  修墙前:nm kernel.elf 查到 printk 在 0x10b000 → 应用直接调,控制台想印什么印什么
  修墙:  GDT 加两个 DPL=3 的段(代码 gdt[3]、数据 gdt[4]);
         应用页 user=1,内核页 user=0;
         CallApp: retf 一口气换 CS/RIP/SS/RSP → CPU 进入 CPL=3
         结果:rpn 能跑,但应用碰内核内存 = 页面故障,hlt 之类的特权指令 = 异常
  地雷:  rpn 跑在 ring 3,定时器中断一来,CPU 按规范把 TSS.RSP0 塞进 RSP ——
         TR 寄存器从没设置(=0)→ 拿内存地址 4 处的值当栈 → QEMU 里是 0 →
         中断帧写到 0xffffffffffffffd8 附近 → 恰好和 argv 页重叠 → 静默踩坏,看起来一切正常
  开门:  wrmsr 把 SyscallEntry 写进 IA32_LSTAR;应用 mov eax, 0x80000000; syscall
         → SyscallEntry 查 syscall_table[0] = LogString → 控制台左上角出现应用印的字符串

2. 顶层全景

  20.1 应用直接调内核函数(nm 查地址)——方便 = 零安全
  20.2 修墙:ring 0/3、DPL=3 段、retf 切权、页表 user 位、argv 挪进应用页
  20.3 排雷:TSS.RSP0(中断时 CPL0 的新栈);上下文改由中断帧构建;RestoreContext()
  20.4 调试基建:CPU 异常处理程序组 + 帧缓冲直绘 PrintHex(+ GDB 对照 RSP)
  20.5–20.7 开门:syscall 指令;MSR 三件套注册;SyscallEntry 汇编写成;
       系统调用号 0x80000000 起(避开 Linux);参数走 System V ABI(第 4 参用 R10)

  图说:墙(特权级)→ 雷(TSS)→ 门(syscall),三步都踩过坑才立得稳。

3. 核心原理

3.1 为什么必须修墙

应用能自由调内核函数意味着:恶意程序可毁系统、窃数据;就算只用自己写的应用, 应用的 bug 也会弄瘫 OS——「OS 不稳定,系统被迫关闭,连原因都找不到」1。 x86-64 有 4 级权限(0 最高 = 内核,3 最低 = 应用),画成同心圆叫保护环; Intel 规定只用两级就取 0 和 32。 修墙三件套:DPL=3 的代码段/数据段(GDT 加 gdt[3]/gdt[4]); 段寄存器切换(CS 指 gdt[3]、SS 指 gdt[4],retf 一口气换 CS+RIP+SS+RSP; 选择子写法 3<<3|3:高 3 位是 GDT 下标,低 2 位是 RPL=3)3; 页表 user 位(user=1 的页 CPL=3 也能访问,user=0 只有 CPL<3 能碰; 应用页 user=1、内核页 user=0——「这正是我们想做的」)4。 CS 的 RPL 有个专名 CPL(当前权限级),改写 CS 的瞬间模式切换完成5。

还有一个隐蔽工程:argv 原本由 new 分配在内核堆里,应用一读就页面故障—— MakeArgVector 改成在应用页内造 argv(指针数组放页顶,字符串数据跟在后面)6。

3.2 TSS 地雷:规范知道、你不知道

rpn 在 ring 3 里「看起来」正常运行,其实 CPU 规范藏着一刀: CPL=3 发生中断切到 CPL=0 时,TSS.RSP0 的值会被硬件写进 RSP,无法禁止7。 而 TR 寄存器从没设置过(=0)→ CPU 去地址 0 附近找 TSS → 把内存地址 4 处的值 (QEMU 里是 0)当内核栈。中断帧 40 字节往 RSP=0 一压,写到 0xffffffffffffffd8 附近; 恰好和 argv 那页尾部重叠(已映射、可写,不故障)——于是内存被静悄悄踩坏, 短的命令行还不受影响,「尽管看起来进展顺利」8。 排雷三件套:分配 32 KiB 中断栈写进 TSS.RSP0;TSS 注册进 GDT(TSS 段占两个条目, ltr 指令载 TR);上下文改从中断帧构建——多个 ring 3 上下文共用 TSS 栈会互相踩, 所以把「中断刚结束那一刻」的完整上下文存进 TaskContext,恢复时直接落在中断收尾处, 新写一个只恢复不保存的 RestoreContext()(SwitchContext 的后半段)9。 20.4 顺手立了调试基建:所有常见 CPU 异常都注册处理程序,异常时直写帧缓冲 (不依赖任何可能已坏的基础设施),打印栈帧和错误码;作者坦白:「读者看到的只是 我努力调试后的无异常程序」10。

3.3 syscall:一扇受控的门

系统调用 = 应用(call)系统,x86-64 最正统的门口指令是 syscall11。 它的机制:执行前用三个 MSR(模型专用寄存器)注册好—— IA32_EFER.SCE 置 1(启用 syscall);IA32_LSTAR = 内核入口 SyscallEntry 的地址; IA32_STAR 同时配置 syscall/sysret 换入的 CS/SS(内核段 RPL=0,经 sysret 回应用段 RPL=3)12。 syscall 的两个怪癖:返回地址进 RCX、RFLAGS 进 R11(不压栈), 所以第 4 个参数寄存器不能用 RCX,改用 R10(Linux 同款约定)13。 应用侧:汇编包装 SyscallLogString,mov eax, 系统调用号;syscall—— 号从 0x80000000 起,作者的理由:别和 Linux 的号(从 0 起)撞车, 万一哪天跑 Linux 应用14。 内核侧入口 SyscallEntry(纯汇编):保存 RBP/RCX/R11,mov rcx, r10 还原参数, and eax, 0x7fffffff 把号转成表下标,call syscall_table[eax], 收尾 sysret 回应用15。 守门:LogString 收到参数先检查有效性再输出——「参数检查对 OS 安全性至关重要」16。 验证:rpn 2 5 + 在控制台左上角印出 ##+——应用第一次隔着墙对系统喊话成功17。

4. 作者的判断与证据

判断:安全优先于方便,但门必须开在墙之后立刻。 直接调内核函数的便利只用了一节 就被没收;墙的代价(应用哑巴)在本章内就由 syscall 偿还。

判断:调试基建是给自己的,也是给读者的。 20.4 节的存在理由写得很诚实: 异常处理程序是因为「我开发时希望如此」;读者看到的是磨平 bug 的成果。

证据: 环 3 下 rpn 正常(文本框光标还在闪 = OS 活着); GDB 打断定时器中断处理程序,RSP 从 0xffffffffffffffd8 变成 0x8fd8(= TSS.RSP0 0x9000 附近); syscall 的 ##+ 上屏。

5. 边界与局限

  • 参数检查只查字符串指针,没有完备的用户内存验证(copy_from_user 式的边界检查 本书没有);
  • 中断帧上下文方案假设应用不会像内核那样共享栈;ring 3 的嵌套中断语义 (IST,第 21 章)还没登场;
  • syscall_table 是静态数组,系统调用号手工分配,没有版本协商;
  • 单应用约定:本章只保证一个应用在跑,多应用并发要等第 24 章修窗口 bug 时一起成熟。

6. 可带走的

本章主线例子,一行写完: nm 直调内核(零安全)→ DPL=3 段 + retf + user 位把应用关进 ring 3 → TSS.RSP0 地雷(argv 页被静默踩坏)→ TSS + 中断帧上下文排雷 → syscall/MSR/syscall_table 开门,rpn 印出 ##+。

  1. 特权级 = 同心圆:ring 0 内核、ring 3 应用;切权 = 换 CS(段描述符的 DPL 决定权限);
  2. 修墙三件套:DPL=3 段、retf 换栈换权、页表 user 位;
  3. argv 等给应用的数据必须放进应用页(new 出来的在内核堆里);
  4. CPL=3 中断时 CPU 强塞 TSS.RSP0 进 RSP——不设 TSS 就是拿地址 0 的值当栈;
  5. 多用户上下文共用 TSS 栈会互踩:上下文改由中断帧构建,恢复用 RestoreContext;
  6. 异常处理要直写帧缓冲,不依赖可能已毁的基础设施;
  7. syscall 三注册:EFER.SCE、LSTAR=入口、STAR=两侧段;RCX/R11 被指令占用,第 4 参走 R10;
  8. 系统调用号从高位起编,避开别家;入口先查参数再干活。

7. 原文地图

主题 原书章 原文位置
直接调内核函数 20.1 应用如何使用操作系统函数(osbook_day20a) text/126-ch20-01-20-1-osbook-day20a.txt:11(搜「nm」) · text/126-ch20-01-20-1-osbook-day20a.txt:17(搜「0x10b000」)
安全顾虑与保护环 20.2 保护操作系统(1)(osbook_day20b) text/127-ch20-02-20-2-1-osbook-day20b.txt:14(搜「安全角度」) · text/127-ch20-02-20-2-1-osbook-day20b.txt:31(搜「4个授权级别」) · text/127-ch20-02-20-2-1-osbook-day20b.txt:44(搜「环3」)
DPL=3 段与 retf 切权 20.2 保护操作系统(1)(osbook_day20b) text/127-ch20-02-20-2-1-osbook-day20b.txt:55(搜「gdt[3]」) · text/127-ch20-02-20-2-1-osbook-day20b.txt:80(搜「SS和RSP」) · text/127-ch20-02-20-2-1-osbook-day20b.txt:86(搜「CPL」)
user 位与页面故障防线 20.2 保护操作系统(1)(osbook_day20b) text/127-ch20-02-20-2-1-osbook-day20b.txt:98(搜「user标志」) · text/127-ch20-02-20-2-1-osbook-day20b.txt:110(搜「user位设置为1」)
argv 挪进应用页 20.2 保护操作系统(1)(osbook_day20b) text/127-ch20-02-20-2-1-osbook-day20b.txt:131(搜「argv的位置」) · text/127-ch20-02-20-2-1-osbook-day20b.txt:136(搜「保留一页」)
TSS.RSP0 地雷 20.3 设置TSS(osbook_day20c) text/128-ch20-03-20-3-tss-osbook-day20c.txt:24(搜「TSS.RSP0」;:29,搜「无法禁止」)
静默踩坏 argv 的分析 20.3 设置TSS(osbook_day20c) text/128-ch20-03-20-3-tss-osbook-day20c.txt:58(搜「因情况而异」;:65,搜「重叠」;:69,搜「悄然发生」)
中断帧上下文与 RestoreContext 20.3 设置TSS(osbook_day20c) text/128-ch20-03-20-3-tss-osbook-day20c.txt:145(搜「存储到上下文结构中」) · text/128-ch20-03-20-3-tss-osbook-day20c.txt:187(搜「RestoreContext」)
GDB 对照验证 20.4 帮助查找错误(osbook_day20d) text/129-ch20-04-20-4-osbook-day20d.txt:14(搜「0xffffffffffffffd8」)
异常处理组与直绘 20.4 帮助查找错误(osbook_day20d) text/129-ch20-04-20-4-osbook-day20d.txt:24(搜「中断处理程序」) · text/129-ch20-04-20-4-osbook-day20d.txt:36(搜「帧缓冲区」)
syscall 机制与寄存器怪癖 20.5 系统调用(osbook_day20e) text/130-ch20-05-20-5-osbook-day20e.txt:10(搜「syscall指令」) · text/131-ch20-06-20-6.txt:14(搜「R11」) · text/131-ch20-06-20-6.txt:17(搜「R10」)
MSR 三注册与号段选择 20.6 注册系统调用的过程 text/131-ch20-06-20-6.txt:26(搜「WriteMSR」) · text/131-ch20-06-20-6.txt:33(搜「IA32_LSTAR」) · text/131-ch20-06-20-6.txt:3(搜「0x80000000」)
SyscallEntry 本体与验证 20.7 系统调用本体 text/132-ch20-07-20-7.txt:18(搜「syscall_table」) · text/132-ch20-07-20-7.txt:48(搜「参数检查」) · text/132-ch20-07-20-7.txt:64(搜「##+」)

  1. 出处:「20.1 应用如何使用操作系统函数(osbook_day20a)」(text/126-ch20-01-20-1-osbook-day20a.txt:11(搜「nm」); text/126-ch20-01-20-1-osbook-day20a.txt:17(搜「0x10b000」))。 ↩

  2. 出处:「20.2 保护操作系统(1)(osbook_day20b)」(text/127-ch20-02-20-2-1-osbook-day20b.txt:14(搜「安全角度」); text/127-ch20-02-20-2-1-osbook-day20b.txt:31(搜「4个授权级别」); text/127-ch20-02-20-2-1-osbook-day20b.txt:44(搜「环3」))。 ↩

  3. 出处:「20.2 保护操作系统(1)(osbook_day20b)」(text/127-ch20-02-20-2-1-osbook-day20b.txt:55(搜「gdt[3]」); text/127-ch20-02-20-2-1-osbook-day20b.txt:80(搜「SS和RSP」); text/127-ch20-02-20-2-1-osbook-day20b.txt:86(搜「CPL」))。 ↩

  4. 出处:「20.2 保护操作系统(1)(osbook_day20b)」(text/127-ch20-02-20-2-1-osbook-day20b.txt:98(搜「user标志」); text/127-ch20-02-20-2-1-osbook-day20b.txt:110(搜「user位设置为1」))。 ↩

  5. 出处:「20.2 保护操作系统(1)(osbook_day20b)」(text/127-ch20-02-20-2-1-osbook-day20b.txt:131(搜「argv的位置」); text/127-ch20-02-20-2-1-osbook-day20b.txt:136(搜「保留一页」))。 ↩

  6. 出处:「20.3 设置TSS(osbook_day20c)」()。 ↩

  7. 出处:「20.3 设置TSS(osbook_day20c)」()。 ↩

  8. 出处:「20.3 设置TSS(osbook_day20c)」(text/128-ch20-03-20-3-tss-osbook-day20c.txt:145(搜「存储到上下文结构中」); text/128-ch20-03-20-3-tss-osbook-day20c.txt:187(搜「RestoreContext」))。 ↩

  9. 出处:「20.4 帮助查找错误(osbook_day20d)」(text/129-ch20-04-20-4-osbook-day20d.txt:14(搜「0xffffffffffffffd8」))。 ↩

  10. 出处:「20.4 帮助查找错误(osbook_day20d)」(text/129-ch20-04-20-4-osbook-day20d.txt:24(搜「中断处理程序」); text/129-ch20-04-20-4-osbook-day20d.txt:36(搜「帧缓冲区」))。 ↩

  11. 出处:「20.5 系统调用(osbook_day20e)」(text/130-ch20-05-20-5-osbook-day20e.txt:10(搜「syscall指令」); text/131-ch20-06-20-6.txt:14(搜「R11」); text/131-ch20-06-20-6.txt:17(搜「R10」))。 ↩

  12. 出处:「20.6 注册系统调用的过程」(text/131-ch20-06-20-6.txt:26(搜「WriteMSR」); text/131-ch20-06-20-6.txt:33(搜「IA32_LSTAR」); text/131-ch20-06-20-6.txt:3(搜「0x80000000」))。 ↩

  13. 出处:「20.7 系统调用本体」(text/132-ch20-07-20-7.txt:18(搜「syscall_table」); text/132-ch20-07-20-7.txt:48(搜「参数检查」); text/132-ch20-07-20-7.txt:64(搜「##+」))。 ↩

  14. 出处:「20.1 应用如何使用操作系统函数(osbook_day20a)」(text/126-ch20-01-20-1-osbook-day20a.txt:11(搜「nm」); text/126-ch20-01-20-1-osbook-day20a.txt:17(搜「0x10b000」))。 ↩

  15. 出处:「20.4 帮助查找错误(osbook_day20d)」(text/129-ch20-04-20-4-osbook-day20d.txt:24(搜「中断处理程序」); text/129-ch20-04-20-4-osbook-day20d.txt:36(搜「帧缓冲区」))。 ↩

  16. 出处:「20.3 设置TSS(osbook_day20c)」()。 ↩

  17. 出处:「20.7 系统调用本体」(text/132-ch20-07-20-7.txt:18(搜「syscall_table」); text/132-ch20-07-20-7.txt:48(搜「参数检查」); text/132-ch20-07-20-7.txt:64(搜「##+」))。 ↩