sched discussion
[PATCH v4] sched/debug: Dump end of stack when stack corruption detected
LLM 分析
sched/debug:检测到栈破坏时转储栈末端内存
系列概况
- 标题:
[PATCH v4] sched/debug: Dump end of stack when stack corruption detected - 作者:Feng Tang feng.tang@linux.alibaba.com
- 版本:v4(单封 patch,无多 patch 系列)
- 规模:1 文件,+12/-1 行(13 行 diff)
- 修改文件:
kernel/sched/core.c - 代码统计:
1 file changed, 12 insertions(+), 1 deletion(-) - Message-ID:
20260714111345.50501-1-feng.tang@linux.alibaba.com - 完整性:单封 patch,包含 diff、commit message、Signed-off-by、Reviewed-by、Changelog;标题无
[1]链接,但 commit message 引用 bugzilla 217854 作为[1]
补丁目的
增强 __schedule_bug() 在检测到内核栈末端哨兵(STACK_END_MAGIC)被破坏时的诊断能力。旧逻辑只 BUG()/panic 一行 "corrupted stack end detected inside scheduler" 就死;本 patch 在 panic 前先把栈末端附近 16 个 ulong 的原始内存用 hexdump 形式打到 KERN_ERR 日志,便于和 KASAN、slub_debug、链表破坏等其他受害者 dump 做交叉比对,从而定位是谁在写这些内存。
旧流程的问题
旧 __schedule_bug() 在 task_stack_end_corrupted(prev) 为真时直接走 BUG 路径,只输出 "corrupted stack end detected inside scheduler" 一行。给真实调试带来的痛点:
-多个受害者同时报告内存破坏(slub、KASAN、调度器栈哨兵、链表破坏)时,只有"栈末端被破坏"这条线索,没有破坏点的字节内容,无法和其他 dump 对位。
- 调试一次 suspend/resume hang,需要在多份 dump 之间人工匹配字节模式,效率极低,也容易漏掉真正的根因。
新流程
新流程在 task_stack_end_corrupted() 为真时:
- 用
end_of_stack(prev)拿到栈末端哨兵的地址作为 dump 起点。 - 如果内核是
CONFIG_STACK_GROWSUP(栈向高地址增长),把指针前移 15 个 ulong,让 dump 范围覆盖到真正可读的破坏点;普通向下增长的栈ptr直接指向哨兵。 - 用
print_hex_dump(KERN_ERR, "Corrupted Stack: ", DUMP_PREFIX_ADDRESS, 16, 1, ptr, 16 * sizeof(unsigned long), true)把 16 个 ulong 按16 字节一行打到控制台,每行前缀打印地址。 - 沿用原 BUG/panic 收尾,不改变错误严重性判断。
关键实现
static noinline void __schedule_bug(struct task_struct *prev)
{
if (task_stack_end_corrupted(prev)) {
unsigned long *ptr = end_of_stack(prev);
/* Dump 16 ulong words around the corruption point */
#ifdef CONFIG_STACK_GROWSUP
ptr -= 15;
#endif
print_hex_dump(KERN_ERR, "Corrupted Stack: ",
DUMP_PREFIX_ADDRESS, 16, 1, ptr,
16 * sizeof(unsigned long), true);
}
/* ... 后续保持原 BUG()/panic 行为 ... */
}
要点:
end_of_stack(prev)取的就是栈末端的 STACK_END_MAGIC 哨兵位置,紧贴破坏现场。#ifdef CONFIG_STACK_GROWSUP处理罕见的向上增长栈配置;测试需要,但生产内核几乎都用向下增长栈。print_hex_dump复用内核统一 dump 工具,避免手写 printk 循环;DUMP_PREFIX_ADDRESS让每行带地址前缀,方便按地址与其他 dump 对位。- dump 范围是16 个 ulong(128 字节),受控,不会让 panic 控制台刷屏。
ASCII 流程图
+--------------------------+
| __schedule_bug(prev) |
+--------------------------+
|
v
+-------------------------------+
| task_stack_end_corrupted()? |
+-------------------------------+
| |
no | | yes
v v
+-----------------+ +--------------------------------+
| fall through, | | ptr = end_of_stack(prev) |
| BUG()/panic | +--------------------------------+
+-----------------+ |
v
+-------------------------------+
| #ifdef CONFIG_STACK_GROWSUP? |
+-------------------------------+
| |
yes | | no
v v
ptr -= 15 ptr (unchanged)
| |
+-------+--------+
v
+----------------------------------------+
| print_hex_dump(KERN_ERR, "...", |
| DUMP_PREFIX_ADDRESS, 16, 1, ptr, |
| 16 * sizeof(unsigned long), true) |
+----------------------------------------+
|
v
(continue to BUG()/panic)
类比
把内核栈末端哨兵想象成公寓大门口的"封条"。保安(__schedule_bug)每次巡逻发现封条被撕(旧流程)只会大喊"封条坏了",却没说门里外发生了啥。新流程相当于保安撕门前先拍一段门廊监控(hexdump 16 个 ulong),让刑侦(开发者)能把这段画面和其他监控探头(slub/KASAN/链表破坏)的时间线拼起来。
换个角度:栈末端像水杯最后一颗弹珠的位置——弹珠(哨兵)被推走说明水面(栈)出问题了;新流程不仅记录"弹珠动了",还把水面附近 16 个格子的波纹一起记录下来,方便和别的"水面"的波纹比对。
Highlight:风险与注意点
- 打印量受控:只 dump 16 个 ulong(128 字节),对 panic 控制台安全,不会刷屏冲掉其他线索。
CONFIG_STACK_GROWSUP分支只-15而非-16,dump 范围从ptr-15到ptr+0共 16 个 ulong;阅读 up-growing 栈的 dump 时要意识到"破坏点靠近尾巴"。- 仍依赖原 BUG/panic 行为,没有把"栈末端破坏"变成可恢复错误;这是排查工具,不是容错开关。
- 只在调度上下文检测到 STACK_END_MAGIC 被踩时触发;栈破坏发生在异常表、softirq、中断栈等路径时不会被这个 patch 覆盖。
- 单独看这段 dump 没有意义,必须和 KASAN/slub_debug 的其他 dump 配合交叉比对;它本身不告诉你"是谁写的"。
- patch头部 bugzilla 217854 的链接没有出现在标题里只出现在 commit message,自动化追踪工具可能漏抓。
版本变化
- v1 → v2:细化 commit log,补上 suspend/resume + DMA 的具体故事,rebase 到 6.8-rc3。
- v2 → v3:根据 Adrian 反馈调整代码格式(更紧凑的 if 体、注释排版),新增
Reviewed-by: John Paul Adrian Glaubitz。 - v3 → v4:再次重写 commit log 描述,rebase 到 7.2-rc1,更新作者邮箱(从 intel 转到 alibaba),主体逻辑未变。
一句话总结
v4 在调度器检测到栈末端哨兵被踩时,于 panic 前多打 16 个 ulong 的 hexdump,方便把栈破坏现场和 KASAN 等其他 dump 对齐,顺藤摸瓜找到 DMA 这类异步写穿的真实凶手(本案为以太网驱动 suspend hook 提前释放 RX 缓冲区)。