系统调用入门 — 先修一堵墙(特权级),再开一扇门(syscall)
系统调用入门 — 先修一堵墙(特权级),再开一扇门(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 印出 ##+。
- 特权级 = 同心圆:ring 0 内核、ring 3 应用;切权 = 换 CS(段描述符的 DPL 决定权限);
- 修墙三件套:DPL=3 段、retf 换栈换权、页表 user 位;
- argv 等给应用的数据必须放进应用页(new 出来的在内核堆里);
- CPL=3 中断时 CPU 强塞 TSS.RSP0 进 RSP——不设 TSS 就是拿地址 0 的值当栈;
- 多用户上下文共用 TSS 栈会互踩:上下文改由中断帧构建,恢复用 RestoreContext;
- 异常处理要直写帧缓冲,不依赖可能已毁的基础设施;
- syscall 三注册:EFER.SCE、LSTAR=入口、STAR=两侧段;RCX/R11 被指令占用,第 4 参走 R10;
- 系统调用号从高位起编,避开别家;入口先查参数再干活。
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(搜「##+」) |
Footnotes
-
出处:「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」))。 ↩ -
出处:「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」))。 ↩ -
出处:「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」))。 ↩ -
出处:「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(搜「保留一页」))。 ↩ -
出处:「20.3 设置TSS(osbook_day20c)」()。 ↩
-
出处:「20.3 设置TSS(osbook_day20c)」()。 ↩
-
出处:「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」))。 ↩ -
出处:「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(搜「帧缓冲区」))。 ↩ -
出处:「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」))。 ↩ -
出处:「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」))。 ↩ -
出处:「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(搜「##+」))。 ↩ -
出处:「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.4 帮助查找错误(osbook_day20d)」(
text/129-ch20-04-20-4-osbook-day20d.txt:24(搜「中断处理程序」);text/129-ch20-04-20-4-osbook-day20d.txt:36(搜「帧缓冲区」))。 ↩ -
出处:「20.3 设置TSS(osbook_day20c)」()。 ↩
-
出处:「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(搜「##+」))。 ↩