不能。uprobe 是内核态机制,仅支持基于二进制符号或内存偏移挂载,而 python 函数是解释执行的,无固定地址和 elf 符号;实际可挂点仅为 cpython 的 c 层函数(如 _pyeval_evalframedefault),且需动态解析地址、适配版本差异、手动解析 python 对象结构。

uprobe 能不能直接挂 Python 函数名?
不能。内核态 uprobe 只能基于二进制符号或内存偏移挂载,而 Python 函数在运行时是解释执行的,PyFunctionObject 对象地址不固定,也没有 ELF 符号表条目可查。你写 uprobe -p /usr/bin/python3 my_module.my_func 会直接失败 —— my_module.my_func 根本不是可执行文件里的符号。
实际能挂的只有 CPython 解释器的 C 层入口
真正可挂的点集中在 CPython 的 C 函数上,比如 PyEval_EvalFrameEx(旧版)、_PyEval_EvalFrameDefault(3.7+)、PyCFunction_Call、PyObject_Call。这些是 Python 字节码执行和函数调用的实际落点:
-
_PyEval_EvalFrameDefault是最常用入口,每次字节码帧执行都会过这里,但开销大、噪音多 -
PyCFunction_Call只捕获内置/扩展模块的 C 函数调用,不覆盖纯 Python 函数 - 想精准命中某个 Python 函数,得先用
gdb或py-bt确认它最终落入哪个 C 调用路径,再反推挂哪 - 注意:3.11+ 引入了“快速调用协议”,
PyObject_Call可能被绕过,_PyObject_FastCall成了新热点
挂载前必须解决符号解析和地址稳定性问题
CPython 可执行文件通常是 PIE(位置无关),每次启动基址都变,uprobe 需要动态计算真实地址。静态写死 -p /usr/bin/python3:0x123456 必然失效:
- 用
readelf -s /usr/bin/python3 | grep _PyEval_EvalFrameDefault查到的是 .text 段相对偏移,不是运行时地址 - 得配合
/proc/PID/maps找 python 进程的 text 段基址,再相加;或者用bpftrace的uretprobe自动解析符号(依赖 debuginfo) - 没装
python3-dbg或debuginfo包时,uprobe只能靠偏移硬挂,一升级 Python 就断 - 容器环境里,宿主机和容器内
/usr/bin/python3路径可能不同,uprobe路径必须匹配进程实际argv[0]或/proc/PID/exe
Python 层函数名只能在 probe 触发后动态提取
uprobe 本身不理解 Python 对象模型,所有“函数名”“参数值”都得在 eBPF 或用户态 handler 中手动解析:
立即学习“Python免费学习笔记(深入)”;
- 从
_PyEval_EvalFrameDefault的第一个参数(PyFrameObject*)开始,读f_code->co_name、f_code->co_filename等字段 - 字段偏移随 Python 版本剧烈变化:3.8 的
PyFrameObject和 3.12 差 3 个字段,硬编码 offset 必崩 - eBPF 里不能直接调用 Python C API,得用
bpf_probe_read_kernel多层解引用,每层都要判空,否则invalid mem access - 更稳妥的做法是用
libbpf+CO-RE,靠vmlinux.h类似机制生成适配结构体,但 Python 没官方python.hBTF,得自己 dump 和转换
真正的难点从来不在挂不挂得上,而在挂上之后——怎么从一堆指针跳转里,在不 crash 内核、不拖慢业务的前提下,把那个你想要的 def foo(): 给捞出来。这一步没现成轮子,每个 Python 版本都得重新对齐结构体。










