Novus OS Debug 级接口手册 V1.0
Novus OS Debug 级接口手册
文档版本:V1.0(对应 Novus OS V1.6-FinalSpec)
用途:开发调试手册,每个接口展开到参数级、错误码级、前置/后置条件、调试检查点
配套:《全部接口总表》《NUC 格式规范》《NKM 格式规范》
状态:冻结
0. 阅读指南
每个接口条目包含:
- 原型:C 函数签名
- 所属子系统
- 调用方 / 被调用方
- 前置条件:调用前必须满足什么(锁、CAP、中断状态、内存)
- 参数表:逐字段类型、取值范围、单位、默认值、说明
- 返回值:逐情况说明
- 错误码对照表:什么情况返回什么错误码
- 后置条件:返回后系统状态变化
- 调试检查点:开发时应该打日志/断言的位置
- 使用示例:最小调用代码
- 常见 Bug:易犯错误
1. 通用错误码(全局复用)
| 值 |
宏名 |
含义 |
触发场景举例 |
| 0 |
NOVUS_OK |
成功 |
一切正常 |
| -1 |
NOVUS_EINVAL |
参数无效 |
指针为NULL、长度为0、枚举值越界 |
| -2 |
NOVUS_ENOMEM |
内存不足 |
页分配失败、缓冲区不够 |
| -3 |
NOVUS_ENOTFOUND |
对象不存在 |
路径不存在、符号未找到、设备不存在 |
| -4 |
NOVUS_EPERM |
权限不足 |
cap_check() 返回 false |
| -5 |
NOVUS_EBUSY |
资源忙 |
锁超时、文件被占用、模块被依赖 |
| -6 |
NOVUS_EIO |
IO错误 |
磁盘读失败、PCI读写错误 |
| -7 |
NOVUS_ECORRUPT |
数据损坏 |
SHA256不匹配、CRC32校验失败 |
| -8 |
NOVUS_EMODULE |
模块错误 |
签名校验失败、ABI不兼容 |
| -9 |
NOVUS_EFULL |
队列满 |
IPC队列满、WAL日志满 |
| -10 |
NOVUS_EAGAIN |
重试 |
资源暂时不可用 |
| -11 |
NOVUS_EOVERFLOW |
溢出 |
段大小越界、文件偏移超出文件长度 |
| -12 |
NOVUS_EDEP |
循环依赖 |
模块依赖图成环 |
2. SMP / AB 双核接口
2.1 smp_shmem_write
void smp_shmem_write(uint32_t offset, uint64_t val);
- 所属:SMP 启动
- 调用方:A核(BSP)
- 前置条件:
- 共享内存物理页已预留(A核早期分配)
- 调用者已持有
smp_shmem_lock()(如多核心并发写)
- 本地中断状态:允许开中断
- 参数:
| 参数 |
类型 |
范围 |
说明 |
| offset |
uint32_t |
0 ~ 4095 |
共享内存内偏移,必须 8 字节对齐 |
| val |
uint64_t |
任意 |
要写入的值 |
- 返回值:无(void)
- 后置条件:共享内存指定偏移处值更新;CPU 缓存一致性由
sfence 保证
- 调试检查点:
- 写入后立即读回比对:
assert(smp_shmem_read(offset) == val);
- 打印:
log_print(DEBUG, "shmem write off=%u val=%lx\n", offset, val);
- 常见 Bug:offset 未对齐导致原子性破坏;忘记持锁导致竞态
2.2 smp_shmem_read
uint64_t smp_shmem_read(uint32_t offset);
- 调用方:B核(AP)
- 前置条件:共享内存已映射到 AP 页表
- 参数:offset 同上
- 返回值:指定偏移处的值
- 调试检查点:读取
bsp_state 时循环等待,超时 200ms 判定启动失败
2.3 smp_shmem_lock / smp_shmem_unlock
int smp_shmem_lock(void);
void smp_shmem_unlock(void);
- 锁类型:自旋锁
- 自旋上限:1,000,000 次
- 超时返回:
NOVUS_EBUSY (-5)
- 调试检查点:
- 加锁前后打日志
- 超时触发时打印当前锁持有者信息(如实现了锁调试)
- 注意:锁内禁止调用可能睡眠的函数(此时调度器未启动)
2.4 acore_enter_sleep
void acore_enter_sleep(void);
- 调用方:A核自检完成后
- 前置条件:
- 所有硬件信息已写入共享内存
- IPI 已发送给所有 AP
bsp_state 已置 2
- 行为:进入
cli; hlt; 无限循环
- 后置条件:BSP 不再执行任何指令
- 调试检查点:进入前打印 "Acore entering sleep"
2.5 smp_wake_ap
int smp_wake_ap(uint64_t ap_id);
- 前置条件:
bsp_state 已置 2
- 已执行
sfence
- AP 启动栈地址已写入共享内存
- 参数:ap_id = AP 核心编号
- 行为:发送 IPI 向量 0x80
- 返回值:0 成功;-ENOMEM 失败
- 调试检查点:发送后等待
ap_ready 标志,超时 200ms → 重启
2.6 smp_ap_signal_ready
void smp_ap_signal_ready(void);
- 调用方:AP 完成 GDT/IDT/页表初始化后
- 后置条件:共享内存
ap_ready = 1;BSP 检测到后休眠
3. 内存管理接口
3.1 mm_alloc
void* mm_alloc(size_t pages);
- 所属:物理内存管理
- 前置条件:
- 物理内存管理器已初始化(M1 之后)
- 调用者不在中断上下文(或使用 GFP_ATOMIC 标志)
- 参数:
| 参数 |
类型 |
单位 |
说明 |
| pages |
size_t |
4KiB/页 |
要分配的连续物理页数 |
- 返回值:
- 错误码:返回 NULL 即失败(不通过返回值传错码)
- 后置条件:
- 物理页已保留(从空闲位图标记为已占用)
- 已映射到内核虚拟地址空间
- 调试检查点:
- 分配后打印:
log_print(DEBUG, "mm_alloc %zu pages -> %p\n", pages, ptr);
- 检查返回值是否对齐:
assert((uint64_t)ptr % 4096 == 0);
- 常见 Bug:
- pages=0 直接 panic
- 分配后忘记
mm_free → 内存泄漏
- 在中断上下文调用可睡眠版本 → 死锁
3.2 mm_free
void mm_free(void* vaddr, size_t pages);
- 参数:
- vaddr:
mm_alloc 返回的地址
- pages:必须与分配时一致
- 后置条件:物理页归还空闲位图;虚拟映射解除
- 调试检查点:释放后打印地址;检查双重释放(位图标记)
3.3 vm_map
int vm_map(uint64_t va, uint64_t pa, uint64_t len, uint32_t flags);
| 参数 |
类型 |
单位 |
说明 |
| va |
uint64_t |
字节 |
虚拟起始地址,4KiB 对齐 |
| pa |
uint64_t |
字节 |
物理起始地址,4KiB 对齐 |
| len |
uint64_t |
字节 |
长度,4KiB 对齐 |
| flags |
uint32_t |
位掩码 |
bit0=READ bit1=WRITE bit2=EXEC bit3=USER |
- 返回值:0 成功;负数错误码
- 错误码:
- -EINVAL:va/pa/len 未对齐
- -ENOMEM:页表分配失败
- 调试检查点:映射后读 CR3 验证 TLB;打印映射范围
- W^X 原则:flags 同时置 WRITE 和 EXEC → 拒绝(返回 -EINVAL)
3.4 vm_unmap
int vm_unmap(uint64_t va, uint64_t len);
- 解除映射,释放页表项
- 后置条件:访问该地址触发页错误
3.5 oom_scan
int oom_scan(proc_info_t* out);
- 前置条件:内存水位 < 10%
- 行为:遍历所有进程,按评分公式计算分数
- 评分公式(冻结):
score = rss_bytes * front_weight * age_weight * type_weight
front_weight: 前台=1.8, 后台=1.0
age_weight: <30s=1.0, 30s~5min=0.7, >5min=0.4
type_weight: 普通=1.0, 系统辅助=0.2
- 保护列表(永不参与评分):init、novus-wm、novus-settings、novus-taskmgr
- 返回值:0 找到候选进程,写入 out;-ENOMEM 无候选进程可杀
- 调试检查点:
- 打印每个进程的 score
- 打印选中的 victim
- 常见 Bug:误杀保护列表进程 → 系统崩溃
3.6 oom_reclaim
int oom_reclaim(uint32_t level);
- 参数:level
- 0 = 温和:只回收页缓存、目录项缓存
- 1 = 紧急:回收离屏 FB、NovusFS 冗余缓存
- 回收顺序(冻结):文件页缓存 → 目录项缓存 → 图形离屏缓冲 → NovusFS 缓存
- 防抖:1 秒内最多触发一次全局回收
4. 调度器接口
4.1 sched_spawn
int sched_spawn(const char* path, uint64_t caps, pid_t* out_pid);
- 前置条件:
- 调用者拥有
sys.proc.ctrl CAP(创建系统进程)
- path 指向的 .nuc 文件存在
- 参数:
| 参数 |
说明 |
| path |
.nuc 文件路径,如 "/system/bin/novus-settings" |
| caps |
进程初始 CAP 掩码(低64位) |
| out_pid |
输出参数,成功写入新 PID |
- 返回值:0 成功;负数错误码
- 错误码:
- -ENOENT:文件不存在
- -EINVAL:.nuc 头部校验失败
- -ENOMEM:无法分配进程地址空间
- 调试检查点:打印新进程 PID、入口地址、初始栈
- 后置条件:进程加入就绪队列,等待调度
4.2 sched_ctl
int sched_ctl(pid_t pid, uint32_t op, void* arg);
- 参数 op:
- 0 = 暂停进程(SIGSTOP)
- 1 = 恢复进程(SIGCONT)
- 2 = 终止进程(SIGKILL)
- 3 = 调试 attach
- CAP:
sys.proc.ctrl
- 调试检查点:打印 pid、op、返回值
4.3 sched_exception_report
int sched_exception_report(pid_t pid, exc_info_t* exc);
- 触发时机:用户态进程触发页错误/非法指令/除零
- 行为:
- 收集寄存器状态到 exc_info_t
- 不 panic,不杀进程
- 通过 IPC 发消息给 novus-wm
- WM 弹出错误对话框
- exc_info_t 字段:
typedef struct {
uint64_t pid;
uint64_t rip; // 出错指令地址
uint64_t cr2; // 页错误地址
uint32_t err_code; // 错误码
char proc_name[64];
uint8_t cause; // 0=页错误 1=非法指令 2=除零 3=断点
} exc_info_t;
5. 安全/CAP 接口
5.1 cap_check
int cap_check(proc_cap_t* p, uint32_t bit);
5.2 cap_drop
int cap_drop(proc_cap_t* p, uint32_t bit);
- 行为:清除指定位(不可逆)
- 用途:进程初始化完成后主动降权(最小权限)
- 返回值:0 成功;-EINVAL bit 越界
5.3 nkm_load
int nkm_load(const char* path);
- 前置条件:调用者拥有
mod.load CAP
- 执行步骤(debug 级):
- 打开文件,读前 128 字节 Header
- 校验 magic == "NKM!" → 失败返回 -ENOENT
- 校验 abi_version 主版本 == 1 → 否则 -EINVAL
- 校验 min_kernel_ver <= 当前内核版本 → 否则 -EMODULE
- 拷贝整个文件到内核内存
- 哈希校验:
- 临时副本中偏移 0x50 起 96 字节置 0
- 计算 SHA256
- 与头部 module_hash 比对 → 不匹配 -ECORRUPT
- 签名校验:
- 遍历 kernel_trusted_keys[0..key_count-1]
- 对 module_hash 做 Ed25519 verify
- 全部失败 → -EMODULE
- (管理员放行:传 NKM_FLAG_ALLOW_UNSIGNED 跳过此步)
- 递归加载依赖
- 映射段到内核地址空间
- 应用重定位
- 调用 nkm_init() → 非0返回则回滚
- 调试检查点:每一步打日志,失败时打印具体步骤
- 常见 Bug:
- 哈希计算时忘记置零 hash+sig 字段 → 永远不匹配
- 重定位时外部符号没找到 → 内核 oops
6. VFS 接口
6.1 vfs_open
int vfs_open(const char* path, uint32_t flags, uint32_t mode);
- 参数 flags:
- bit0 = O_RDONLY
- bit1 = O_WRONLY
- bit2 = O_RDWR
- bit3 = O_CREAT
- bit4 = O_TRUNC
- 路径检查:
/system 下写操作需要 fs.full CAP
- 路径解析失败 → -ENOENT
- 返回值:文件描述符(正整数);负数错误码
- 调试检查点:打印 path、flags、返回的 fd
6.2 vfs_read / vfs_write
int vfs_read(int fd, void* buf, uint64_t len);
int vfs_write(int fd, const void* buf, uint64_t len);
- 参数:
- fd:vfs_open 返回的描述符
- buf:缓冲区指针
- len:要读/写的字节数
- 返回值:实际读/写字节数;负数错误码
- 调试检查点:打印 fd、len、实际传输量
- 常见 Bug:buf 为 NULL;fd 越界;文件指针未推进
7. NovusFS 接口
7.1 novusfs_alloc_block
int novusfs_alloc_block(uint64_t* out_block);
- 前置条件:持有块分配器锁
- 行为:扫描块位图找第一个空闲位
- 返回值:0 成功,*out_block 填入块号
- 错误码:
- -ENOSPC:磁盘满
- -ECORRUPT:位图标记了坏块但块号仍被分配
- 调试检查点:打印分配的块号;验证位图已置 1
- 锁顺序:超级块锁 → 目录锁 → inode锁 → 块分配器锁(最内层)
7.2 WAL 事务
wal_begin_tx
int wal_begin_tx(uint32_t type, uint64_t block_no, wal_tx_t* out);
- type:0=块写 1=inode修改 2=目录修改
- 写入 PREP 记录到 WAL 区
- 返回 0 成功;-EFULL 日志区满
wal_commit
int wal_commit(wal_tx_t* tx, const void* data, uint32_t len);
wal_rollback
void wal_rollback(wal_tx_t* tx);
WAL 满判定
// 满条件:write_ptr == tail_ptr
if (wal_write_ptr == wal_tail_ptr) {
// 推进 tail 到下一个完整事务边界
wal_advance_tail();
}
8. Merkle 树接口
8.1 merkle_verify_root
int merkle_verify_root(dev_t dev, const uint8_t expect[32]);
- 调用方:A核开机自检
- 行为:
- 读取超级块中的 merkle_root_primary
- 如果 primary 损坏,读 backup
- 比对 expect(A核自己计算的哈希)与存储的根
- 不一致 → 返回 -ECORRUPT
- 错误码:
- -ECORRUPT:哈希不匹配
- -EIO:磁盘读失败
- 调试检查点:打印 expect 和 stored 两个哈希的前 8 字节
8.2 节点磁盘映射
// idx: 节点索引(根=1)
uint64_t node_to_block(uint64_t idx) {
return merkle_start_block + (idx / 64);
}
uint64_t node_offset_in_block(uint64_t idx) {
return (idx % 64) * 64;
}
- 边界检查:
- leaf_count == 0 → 拒绝挂载
- leaf_count == 1 → 树高=2,根=1,叶子起始=2
9. 图形栈系统调用
9.1 sys_gfx_create_surface
int sys_gfx_create_surface(uint32_t w, uint32_t h, uint32_t fmt, fb_handle_t* out);
- 参数:
- w/h:宽高(像素)
- fmt:0=BGRA8888, 1=BGR24
- out:输出帧缓冲句柄
- LowMem 限制:最多 4 个离屏 FB
- Full 限制:最多 16 个
- 调试检查点:打印 w、h、分配的 fb 地址
- 错误码:-ENOMEM 分配失败
9.2 sys_gfx_submit_dirty
int sys_gfx_submit_dirty(fb_handle_t h, const rect_t* rects, uint32_t n);
10. IPC 接口
10.1 ipc_send
int ipc_send(novus_ipc_hdr_t* hdr, const void* payload);
- 前置条件:
- 调用者已通过
ipc_bind 注册通道
- 拥有对应窗口的归属权(或 gui.override CAP)
- hdr 必须字段:
- magic = 0x4E574D49
- sender_pid = 当前进程 PID
- msg_type = 有效消息类型
- payload_len = 实际 payload 长度
- 错误码:
- -EFULL:目标队列满
- -EINVAL:magic 错误
- -EPERM:无权限
- 调试检查点:打印 msg_type、target_pid、payload_len
11. 系统调用 ABI(debug 要点)
11.1 寄存器约定
| 用途 |
寄存器 |
| 调用号 |
rax |
| 参数1 |
rdi |
| 参数2 |
rsi |
| 参数3 |
rdx |
| 参数4 |
rcx |
| 参数5 |
r8 |
| 参数6 |
r9 |
| 返回值 |
rax |
| 被调用者保存 |
rbp, rbx, r12, r13, r14, r15 |
11.2 常见调试技巧
- 系统调用号打印:在 syscall 入口打印 rax 和参数
- 返回值检查:用户态库包装后检查 rax 是否为负
- 栈对齐:进入内核时检查 rsp % 16 == 0
- 权限拒绝:syscall 入口先 cap_check,拒绝时打印 bit 号
12. 日志接口
12.1 log_print
void log_print(uint32_t level, const char* fmt, ...);
- level:
- 0 = DEBUG(开发调试)
- 1 = INFO(正常信息)
- 2 = WARN(警告)
- 3 = ERROR(错误)
- 行为:写入环形缓冲区 + 串口输出
- LowMem:DEBUG 级别不输出(节省串口带宽)
- 调试检查点:模块初始化函数开头打 INFO
12.2 panic_handler
void panic_handler(cpu_state_t* state);
- 触发时机:内核致命错误(双重缺页、致命锁死)
- 行为:
- 关中断
- VGA 文本模式打印寄存器转储
- HLT 死循环
- 绝不返回。用户态进程崩溃走
sched_exception_report,不走 panic。
13. 调试通用建议
13.1 打印策略
- 模块 init:打 INFO
- 关键错误:打 ERROR + 错误码
- 调试细节:打 DEBUG(LowMem 关闭)
- 不要在中断高频路径打日志(VSync 每帧)
13.2 断言
- 指针非 NULL:
assert(ptr != NULL);
- 对齐:
assert((uint64_t)addr % 16 == 0);
- 锁持有:调试时可记录当前锁持有者
- 返回值:
int ret = foo(); assert(ret == NOVUS_OK);
13.3 常见 Bug 清单
- 忘记持锁 → 多进程竞态
- 锁顺序错误 → 死锁(必须按 超级块→目录→inode→块分配)
- 双重释放 → 内存 corruption
- 偏移未对齐 → x86_64 严格对齐异常
- CAP 忘记检查 → 越权访问
- WAL 写了 PREP 没写 COMMIT → 崩溃后数据丢失
- Merkle 哈希包含了自身 hash 字段 → 校验永远失败
- NKM 签名时公钥不匹配 → 模块无法加载
文档状态
本文档为 Debug 级接口手册,开发时对照此文档逐行检查代码。
所有函数签名、参数、错误码、前置条件已冻结。
文档状态:✅ 冻结,可作为 M0–M10 开发调试基准。
No comments to display
No comments to display