Skip to content

一次艰难的 cpython issue 排查过程,以及我学到了什么 #325

@yihong0618

Description

@yihong0618

引子

最近依然在尝试做一些大模型目前做不到的事儿,去满足一些存在感。但是 TiDB 的 issues 没有以前多了,恰好某一天 @kemingy 发现 cpython 源码里有些写的不好的地方,我想 cpython 也许也可以?但我万万没想到一个 patch 进入到 cpython 是多么的难,我已经前前后后修了 4 个 cpython 的 bug 但是每个都在过程中,过程中学到了不少东西,但更开心的是认识到了目前即使最强的大模型的局限。

bug ?

尝试的过程

下面是我尝试的过程,如果能同样帮到喜欢 bug 的朋友就更好了

  1. 当然是尝试复现,3.13 在 repl 使用 issue 中的语句 hang, 3.14 不 hang 住,是 3.13 only 的问题
  2. Claude Code 启动,先让大模型帮我定位下,大模型前前后后走了几圈,都是错的,而且大模型的思路有问题,它认为 3.14 修了就是有的 commit 修了,再不停的查 commit
  3. 自己来吧,首先先缩小范围
    • 在 repl 里必复现
    • 直接使用 ./python xxx.py 没事
    • pty 没事儿
    • subprocess 没事
    • 卧槽只有 repl 里?
    • 强制使用旧 repl PYTHON_BASIC_REPL=1 ./python 也没事儿!
  4. OK 那是 new repl only, 定位好了问题

能复现就是成功的一半?

  1. no 这个 issue 并不是
  2. 这个是 c 层面的还是 python 层面的?因为只有在 new repl 里我开始以为是 python 层面的
  3. 在所有可能是 memory error 的地方接上 except: nothing work
  4. 在源码里加 print 卧槽加上 print bug 消失了,妈的
  5. 因为上条我怀疑我走错路了,大概率是 c 层面的,但是我得定位到哪一行造成 c 层面的错误
  6. 再一顿 print debug 之后发现是 console.py 里的这一行 exec(code, self.locals)
  7. OK 找到这个 bug 就可以简化为在 new repl 里执行 exec("_testcapi.set_nomemory(0)")

但是但是

  1. 我在 c 层面一路找 memroy 相关的没头绪,能改的地方都改了
  2. 过程中还熟悉了一点 cpython 的代码
  3. 但是还是不行

先放弃了,但还想着

  • 然后我就把问题先搁置了去忙别的了
  • 但是跑步的过程中突然有了一点想法也许不是 3.13 only 的
  • git branch 启动!对不是!发现 3.14 第一个版本仍然有这个 bug
  • 那么是某个小 branch 给修了
  • branch 二分法启动!最后发现是 3.14a2 -> 3.14a3 之间某个 commit 给意外的修复了?
  • 既然 branch 能二分法我突然悟到了可以 bisect
  • 第一次用 bisect 在大模型的教学下一路找(这里有个坑就是 good 和 bad 是反的)最后发现是这个 commit 意外的给修了 5fc6bb2754a25157575efc0b37da78c629fea46e
  • 修的人好强啊
  • 能 cherry-pick 么?不能!因为改动太大了这之间的里
  • 能借住他修的一部分修复么?一路尝试,是不能的,因为本质上不是一个问题
  • 又尝试了好多。。。也不行,确性了 cpython 遗留的 bug 都是有难度的

找朋友吧

  • 当然是先找@kemingy 帮忙,他比我多走了两步但是也卡住了
  • @frostming 也试试
  • 暂时没结果,早上突然想到我要去 pycon 的机票还在拖延,不想麻烦 gray 帮忙了 ,等下 @Graymon 的演讲是 gdb 相关也许可以试试
  • gdb 启动
  • 这里也有个坑好像 gdb 会改变内存
  • 但是最后还是找到了是在 /Python/ceval.c b/Python/ceval.c 的 goto exception_unwind; 里陷入了无限的递归
  • 修复如果递归直接 pyerr clear 卧槽不 hang 了但是是 Segmentation fault
  • 继续,成功了!于是有了如下修复
  • 感谢朋友们的帮忙,开心

修复

diff --git a/Python/ceval.c b/Python/ceval.c
index 8c0cb29863c..3fe97a2c74c 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -912,7 +912,13 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
                 int frame_lasti = _PyInterpreterFrame_LASTI(frame);
                 PyObject *lasti = PyLong_FromLong(frame_lasti);
                 if (lasti == NULL) {
-                    goto exception_unwind;
+                    // If we can't allocate memory for lasti during exception handling,
+                    // this likely means we're in a severe memory shortage situation.
+                    // Instead of going back to exception_unwind (which would cause
+                    // infinite recursion), directly exit to let the original exception
+                    // propagate up and hopefully be handled at a higher level.
+                    _PyFrame_SetStackPointer(frame, stack_pointer);
+                    goto exit_unwind;
                 }
                 PUSH(lasti);
             }

修复的对么?

  • 不知道,先跑测试 3.13 都通过了
  • main 有这个问题么?没有,代码结构都改了
  • 对不对我先把过程分享出来,能解决一大部分也是开心的

感想

  • 谢谢朋友们
  • 大模型还是有局限的但还是能帮不少忙的
  • gray 好厉害
  • cpython 能遗留下来的 bug 都非常难
  • 还是开心

---- update ----

https://t.me/c/1459082815/900

和 gray 学习。

以防有人没有 tg

Image Image

以及 gray 非常精彩的 pycon

GDB for CPython - PyCon 25.pdf

ppt 在附件中

Metadata

Metadata

Assignees

No one assigned

    Labels

    TopTop label of gitblog技术文章技术文章

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions