0/1 已展开

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() 为真时:

  1. end_of_stack(prev) 拿到栈末端哨兵的地址作为 dump 起点。
  2. 如果内核是 CONFIG_STACK_GROWSUP(栈向高地址增长),把指针前移 15 个 ulong,让 dump 范围覆盖到真正可读的破坏点;普通向下增长的栈 ptr 直接指向哨兵。
  3. print_hex_dump(KERN_ERR, "Corrupted Stack: ", DUMP_PREFIX_ADDRESS, 16, 1, ptr, 16 * sizeof(unsigned long), true) 把 16 个 ulong 按16 字节一行打到控制台,每行前缀打印地址。
  4. 沿用原 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-15ptr+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 缓冲区)。