reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
# Unikraft / MirageOS:库操作系统与 unikernel
<CORE RULE>
这一族的模型是:
```
应用 + 被选中的 OS 库 + 驱动 + 运行时
→ 链接成单一 image / 单一地址空间
```
于是:
```
不要硬找 "user → syscall → kernel process" 边界
```
**"系统调用"可能只是函数调用,"内核"可能就在同一个保护域里。**
第二类坑:**同一份应用代码可以构建成完全不同的目标**,运行时的 I/O、boot、设备模型、地址结构随之全变——看到同一个应用名,**不能假定 binary/运行时架构相同**。
</CORE RULE>
## 一、Unikraft:库操作系统
- 把 OS 功能拆成细粒度**微库**,用 Kconfig 式构建系统选择,**与应用链接成单一二进制**;支持 POSIX 兼容
- 单地址空间、单一保护域,**组件之间没有保护边界**;内核函数可以被**直接执行**——专门的调用机制使进入"内核"**不需要 trap/中断/特权级切换**
- **调度是可选的**:可以构建单线程或 run-to-completion 的 unikernel
两条构建路线(区别对 RE 很关键):
| 路线 | 做法 | 结果 |
|---|---|---|
| **Native(API 兼容)** | 应用**源码**与 Unikraft 一起编译链接;系统调用经 shim 微库解析为处理函数 | 运行期 shim"让开",**syscall 实际变成廉价函数调用** |
| **Binary-compatible(ABI 兼容)** | 直接运行**未修改的 Linux ELF**,无需重编译 | 系统调用被**捕获**并经 shim 重定向到各子系统;缺的 syscall 默认落到 ENOSYS 桩 |
- 二进制兼容路线需要:ELF 格式、二进制兼容的函数接口与数据表示、Linux 系统调用、以及兼容的运行时环境
- **PIE 与否影响地址空间布局**:非 PIE 可执行文件**决定布局并限制为单个非 PIE 应用**;PIE 提供布局灵活性、允许单地址空间内多应用、且**上下文切换时不需要切换地址空间**
**RE 含义**:
- 看到"系统调用"别自动假设特权级切换——**同一保护域里可能只是函数调用**
- 看到"内核函数"与"应用函数"混在一起也正常
- 先判是 native 还是 binary-compatible 构建:两者的调用路径不同
## 二、MirageOS:把配置当接线图
- 用 OCaml 编写,构建成**独立的 unikernel**;同一份源码可构建成多种目标:**Unix 进程**(本地开发)、**Xen**(PVH 模式)、**Solo5 系列**(hvt 硬件虚拟化、spt 严格 seccomp 沙箱、virtio、muen、genode)、Unikraft 目标等
- 构建流程是"**先写配置清单,再生成构建**":
1. 写配置描述组件(**接线图,不是应用本身**)——这里表达的是"应用 functor 的类型"与"把哪个具体实现插进去"
2. 按目标执行配置,生成 Makefile/Dune 文件、**主胶水代码**与**按目标区分的包描述**
3. 构建
- **同一份源码换目标,差别只在一个被替换的模块**(平台主模块)+ 目标对应的包集合;应用代码不变
**关键推论**:
```
源码里没有明确的 "new Driver()"
但 runtime 里出现了设备
→ 不奇怪:设备接线由构建期配置决定
```
- 设备驱动按目标打包(网络、控制台、块设备、时钟各有对应包);某些目标支持多网络/多块设备,另一些限于单个
- 代码跨目标的行为差异(I/O、boot、设备模型、地址结构)来自这一层,而不是应用逻辑
## 三、Unikraft 的启动表与 syscall 符号
**多级初始化表**(这是"0 xref 却在启动时执行"的正解):
| 表 | 阶段 | 说明 |
|---|---|---|
| 早期表 | 最早 | 在**分配器可用之前**,处理 bootinfo |
| 构造表 | 紧随其后 | 同样在分配器之前,库级构造函数 |
| 初始化表 | 之后 | **调度器可用之后**的标准库初始化 |
**初始化层级(按序)**:构造 → 早期 → 平台 → 库 → rootfs → 系统 → 晚期;**每级内有 10 个优先级(0 最高)**,条目放在**按名字排序的专用链接段**里,由起止符号界定。
**所以**:`init 函数 0 xref` **不等于 dead code**。
**syscall shim 的多个符号**:同一操作往往同时存在 **libc 风格**与 **raw-error 风格**的包装(外加 shim 本体)——**不要判"三份重复实现"**,它们是 ABI 包装层。
**native 构建里没有特权转换**:syscall 样式的 API **直接落进库实现**(例如最终走到 VFS 写路径),`write() → 普通调用` 完全可能。
**binary-compatible 构建里真的会出现 Linux syscall ABI**:所以要**先用"native 链接还是加载 Linux ELF"分流**——两者不能共用同一套 CFG 心智模型。
**缺 syscall 返回 ENOSYS 而应用照常工作**:文档明确指出这种情况存在——不少应用会调用自己实际不依赖的 syscall。**"syscall 缺失"不能直接判"应用不兼容"。**
## 四、MirageOS 的 Lwt 与目标指纹
- **没有 OS 线程却像有几百个"线程"**:Mirage 大量使用 **Lwt**——**协作式轻量线程 + 事件循环**,不是抢占式原生线程
- **一个函数进入死循环会让整个 unikernel 像死了一样**:Lwt 线程会一直执行直到主动让出;CPU 密集路径不让出就能**饿死同一事件循环上的全部任务**——**不需要互斥量,也不需要内核死锁**:
```
所有网络定时器停 / 所有异步任务停 / CPU 100%
→ 先查有没有协作式让出
```
- **后台任务失败但主程序行为奇怪**:Lwt 的后台异常交给 **async 异常处理器**,是否终止 unikernel 取决于该处理器的配置——**既不是必然崩溃,也不是必然被忽略**;必须恢复 async 异常策略
- **目标指纹**:不同目标的隔离机制完全不同(硬件虚拟化 / 沙箱进程 / virtio 兼容 hypervisor / Xen PVHv2)——**"这是 MirageOS unikernel"不足以决定你该找什么**(VM-exit?Linux syscall?Xen grant?virtio 队列?),**必须先确定目标**
## 五、这一族的共同 RE 策略
1. **先确认构建目标/构建类型**(native vs binary;Unix vs hvt vs Xen…)——它决定 syscall 路径、地址布局与设备模型
2. **不要假设存在特权边界**:单一地址空间是设计
3. **设备的出现与接线来自构建期配置**,不在应用源码里
4. 现象"应用跑得好好的但找不到它调用内核的痕迹"——在 native 构建里是正常的
## 决策树
```
拿到 unikernel 目标
├─ 先判构建类型/目标
│ ├─ Unikraft:native(源码编译)还是 binary-compatible(跑 Linux ELF)
│ ├─ MirageOS:unix / xen / hvt / spt / virtio / …
│ └─ 目标决定 I/O、boot、设备模型、地址布局
├─ 找不到 syscall 的 trap/特权切换
│ └─ 单保护域下可能就是函数调用(native 构建)
├─ 设备的接线找不到来源
│ └─ 来自构建期配置(配置清单/DSL),不是应用源码
├─ 地址布局相关
│ └─ 非 PIE 决定布局且限单应用;PIE 允许单地址空间内多应用
└─ 同一个应用名、行为不同
└─ 别假定 binary/运行时架构相同,先确认目标
```
## 工具与验证
- 构建类型:构建系统的配置项、是否有 ELF 加载器(binary-compatible 路线)、包集合
- 调用路径:shim 微库的使用点;native 路线的"让开"行为
- 设备接线:构建期配置清单/DSL 与生成的主胶水代码
- 验证:能同时说清「这是哪种构建/哪个目标 + 调用进入内核走的是函数调用还是捕获 + 设备接线来自哪份配置」
## 该平台的坑(汇总)
- **硬找 user/kernel 边界**:单地址空间、单保护域是设计
- **把"syscall"默认当特权级切换**:native 构建下可能只是函数调用
- **假定同一应用名对应同一运行时架构**:构建目标决定一切
- **在应用源码里找设备初始化**:接线来自构建期配置
- **忽略 PIE 与否对地址布局的限制**:非 PIE 决定布局并限单应用
- **忽略 binary-compatible 路线的桩语义**:缺失的系统调用可能落到 ENOSYS 桩,行为与会返回错误不同
- **按"动态库/动态链接器"模型理解 image**:这一族通常是静态整链
- **把 0 xref 的启动函数当死代码**:多级初始化表通过专用链接段注册
- **把 shim 的多个符号当重复实现**:libc 风格 / raw-error 风格 / shim 本体是三层包装
- **把 ENOSYS 当"应用不兼容"**:不少应用会调用自己并不依赖的 syscall
- **把"所有任务一起卡死"当内核死锁**:协作式线程不让出就会饿死整个事件循环
- **把后台任务异常当必然崩溃或必然被忽略**:取决于 async 异常处理策略
- **不确认目标就去找 VM-exit / syscall**:不同构建目标的隔离机制完全不同