sched discussion
[PATCH v2 0/6] kcov: Suppress timer and scheduler coverage leaks
LLM 分析
kcov:抑制 timer 与 scheduler 的覆盖率泄漏
系列概况
- 标题:
[PATCH v2 0/6] kcov: Suppress timer and scheduler coverage leaks - 作者:Karl Mehltretter kmehltretter@gmail.com
- 版本:v2(共 6 个 patch)
- 规模:4 个文件改动,约 +66 / −6 行
- 修改文件:
include/linux/hrtimer_rearm.h、include/linux/kcov.h、kernel/kcov.c、kernel/sched/core.c - 代码统计:patch 1 净 0 行,patch 2 +38,patch 3 +13,patch 4/5/6 各 +3
- Message-ID(首封):
20260811154111.64669-1-kmehltretter@gmail.com - 完整性:完整,含 cover letter、6 个 patch 与 14 封回复(其中 Alexander Potapenko 已给出两个 Reviewed-by;Peter Zijlstra 提出 NAK 与替代方案)
补丁目的
KCOV 的初衷是让 syscall 覆盖率只随用户输入变化。但当前实现里,如果未插桩(KCOV)的 timer / scheduler 路径在 in_task() 为真时跑,它内部调用的已插桩 callee(如 sched_clock()、CPU capacity helper、profile_hits()、kthread_is_per_cpu())仍会被记录,并被错误归到当前任务上。三个典型症状:
__hrtimer_rearm_deferred()里的 timer-reprogramming 子树;__schedule()里被调用的arch_scale_cpu_capacity()等;- PREEMPT_RT 下 wakeup 路径里的
kthread_is_per_cpu()。
另外 task-context 的 wakeup 和 wake_up_new_task() 也会把调度器覆盖泄漏给普通 syscall。本系列新增可嵌套的 KCOV_PAUSED 位 + kcov_pause 守卫,让这四类关键路径调用 callee 时不再产生覆盖记录,但不屏蔽 callee 自身在 task-context 的覆盖。
旧流程的问题
- KCOV 仅把
KCOV_IN_CTXSW与KCOV_MODE_DISABLED视为不计数;其他所有模式(含 preempt_count 还未加上、已被减去的边界)都会被当作 task 上下文记录。 - 编译器在 callee 处自动插入
__sanitizer_cov_trace_pc(),而__schedule()/try_to_wake_up()/wake_up_new_task()/__hrtimer_rearm_deferred()是 noinstr 但会调到大量非 noinstr 函数,于是 KCOV 看到in_task()为真就把边角覆盖塞给 current。 - 结果:
CONFIG_KCOV_SELFTEST在x86_64 defconfig + QEMU下 fail;syzkaller 覆盖出现大量非确定性重复路径,corpus 增长变慢。
新流程
- 新增
KCOV_PAUSED(BIT(29))位,与KCOV_IN_CTXSW并列; kcov_mode_enabled()同时屏蔽这两个位;check_kcov_mode()用精确比较,带KCOV_PAUSED的 mode 直接被识别为关;- 提供基于
DEFINE_LOCK_GUARD_0的guard(kcov_pause)()作用域守卫,内部__kcov_pause / __kcov_resume操作current->kcov_mode并保存嵌套前的旧位; - 在四个调用点使用该 guard:deferred hrtimer rearm、
__schedule()、try_to_wake_up()主体、wake_up_new_task(); - KCOV 关闭时 guard 编译为空。
+-----------------------+
| sys_read() syscall |
| in_task() == true |
+----------+------------+
|
| instrumented .text runs
v
+-----------------------+ guard(kcov_pause)()
| try_to_wake_up() | ---> sets KCOV_PAUSED bit
| noinstr entry | in current->kcov_mode
+----------+------------+
|
| callees (instrumented):
| sched_clock()
| arch_scale_cpu_capacity()
| kthread_is_per_cpu()
v
+-----------------------+
| check_kcov_mode() |
| PAUSED set -> skip |
+----------+------------+
|
| scope exit -> __kcov_resume()
v
+-----------------------+
| KCOV_PAUSED cleared |
| syscall coverage OK |
+-----------------------+
Same guard pattern applied at:
- __schedule() (patch 4)
- try_to_wake_up() body (patch 5)
- wake_up_new_task() (patch 6)
- __hrtimer_rearm_deferred()(patch 3)
Patch 概览
kcov: Use unsigned int for kcov_start() mode parameter(cleanup):把kcov_start()的形参从enum kcov_mode改成unsigned int,与task_struct::kcov_mode字段及kcov_remote_softirq_stop()保存的原始位一致。无功能变化。kcov: Add a kcov_pause guard(feature):新增KCOV_PAUSED位、__kcov_pause / __kcov_resume两个__always_inline内部 helper、guard(kcov_pause)()宏,并修改kcov_mode_enabled()屏蔽位。#else CONFIG_KCOV分支提供空 stub。hrtimer: Pause KCOV during deferred rearm(bugfix):在include/linux/hrtimer_rearm.h增加hrtimer_rearm_deferred_kcov_paused()包装,调用__hrtimer_rearm_deferred();hrtick_schedule_exit()也改用新包装。Fixes: 15dd3a948855。sched/core: Pause KCOV in __schedule()(bugfix):在函数最前面加guard(kcov_pause)(),屏蔽sched_clock()、CPU capacity helper、profile_hits()等 callee。Fixes: 5c9a8750a640。sched/core: Pause KCOV in try_to_wake_up()(bugfix):在guard(preempt)()之后加guard(kcov_pause)(),覆盖kthread_is_per_cpu()、CPU capacity helper、SCHED_HRTICK arming 路径。Fixes: 5c9a8750a640。sched/core: Pause KCOV in wake_up_new_task()(bugfix):在wake_up_new_task()顶部加guard(kcov_pause)(),避免 CPU 选择和 enqueue 把覆盖泄漏给父进程。Fixes: 5c9a8750a640。
关键实现
/* include/linux/kcov.h (patch 2) */
#define KCOV_IN_CTXSW BIT(30)
#define KCOV_PAUSED BIT(29)
static __always_inline unsigned int __kcov_pause(void)
{
unsigned int paused = current->kcov_mode & KCOV_PAUSED;
current->kcov_mode |= KCOV_PAUSED;
return paused;
}
static __always_inline void __kcov_resume(unsigned int paused)
{
if (!paused)
current->kcov_mode &= ~KCOV_PAUSED;
}
DEFINE_LOCK_GUARD_0(kcov_pause,
_T->paused = __kcov_pause(),
__kcov_resume(_T->paused),
unsigned int paused)
/* kernel/kcov.c (patch 2) */
static inline bool kcov_mode_enabled(unsigned int mode)
{
return (mode & ~(KCOV_IN_CTXSW | KCOV_PAUSED)) != KCOV_MODE_DISABLED;
}
/* kernel/sched/core.c (patch 5) */
int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
guard(preempt)();
int cpu, success = 0;
/* Instrumented callees would leak coverage into current. */
guard(kcov_pause)();
...
}
Nesting semantics:
outer guard: paused = 0 -> save 0, set PAUSED
inner guard: paused = 1 -> save 1, PAUSED already set
inner exit : resumed=1 -> no clear
outer exit : resumed=0 -> clear PAUSED
kcov_remote_softirq_start/stop() saves/restores full mode
so KCOV_PAUSED survives remote-softirq round trips.
kcov_remote_softirq_start / stop() 仍保存并恢复完整 kcov_mode(含 KCOV_PAUSED),因此嵌套的 pause 段在 remote softirq 里行为正确。
类比
把 KCOV 想成小区门口的摄像头:本意是拍下居民(用户 syscall)进出大门时走过的路径。但调度器 / timer 像物业维修工,他们进出时也会经过同一条走廊,结果被摄像头误拍成居民行踪。
本系列做的事是给物业工人挂一块临时工牌(KCOV_PAUSED 位),进出时摄像头主动忽略;这条工牌只在工作时间佩戴,离开走廊就自动取下(guard 作用域结束),且可以多层嵌套(班长罩工人、工头罩班长)。
Highlight:风险与注意点
- Peter Zijlstra 强烈反对当前写法:他 NAK patch 3–6,认为既然
kernel/sched/是 noinstr,被 KCOV 注入本就不该发生,根因应是 KCOV 在 noinstr 区段发出了插桩 —— "kcov is broken and needs to die"。Karl 澄清 "instrumented" 特指 KCOV 插桩,承认__hrtimer_rearm_deferred()是普通.text,问题是__irq_exit_rcu()在减去HARDIRQ_OFFSET之后才调到它。 - Peter 提出替代方案(patch 15/16):直接修
kernel/softirq.c的softirq_handle_begin / end,让从 hardirq 退到 softirq 的路径显式覆盖 preempt_count;这样 KCOV 不再有机会被错误唤醒。Karl 同意这是更干净的根因修复方向。 - 架构差异:patch 3 依赖
HAVE_NOINSTR_HACK覆盖 GCC < 12 的 x86,其它架构用ARCH_WANTS_NO_INSTR限制 KCOV 到 GCC ≥ 12 或 Clang;否则旧 GCC 没有__no_sanitize_coverage支持。Karl 已在 GCC 8.1 / 15 / Clang 22.1 上测过。 - 路径选择疑问:patch 5 仅在
try_to_wake_up()主体上加 guard,而不是select_task_rq(),以避免漏掉 SCHED_HRTICK arming;后续可能需要把guard(kcov_pause)()移到guard(preempt)()之前(v3 Karl 自己提了)。 - 代码位移变化:guard 内联 + KCOV 关闭时空展开,导致不同 build 之间
__schedule()多 117 字节、try_to_wake_up()多 94 字节、wake_up_new_task()多 88 字节,要留意回归脚本基线。 - Sashiko 静态分析器报 "kcov_mode store 可能被重排 / 消除":Karl 认为在当前调用点不成立,但要继续观察。
版本变化
- v1 → v2:
- 新增 patch 1(cleanup:mode 参数 unsigned int);
- patch 2 改为 guard-only API,内部
__kcov_pause / __kcov_resume仅操作current(参考 Bradley Morgan 的意见); - patch 3–6 改用
guard(kcov_pause)()而非显式kcov_pause() / kcov_resume()对; - 改写所有 pause 注释。
一句话总结
本系列通过新增可嵌套的 KCOV_PAUSED 位和 guard(kcov_pause)(),抑制 __schedule()、try_to_wake_up()、wake_up_new_task() 与 deferred hrtimer rearm 对 KCOV 覆盖的污染;但维护者认为更彻底的修法是修正 softirq 边界上的 preempt_count 状态。