本文記錄作者基於 CPython 3.5 實作一個位元碼級追蹤器,目標為:
DEBUG_OP在 Include/opcode.h 註冊操作碼值(此處使用 0):
#define DEBUG_OP 0 #define POP_TOP 1 #define ROT_TWO 2 // ...
核心任務:從幀全域變數查找 op_target 回呼、收集當前堆疊內容、呼叫回呼函數。
TARGET(DEBUG_OP) {
_Py_IDENTIFIER(op_target);
PyObject *target = _PyDict_GetItemId(f->f_globals, &PyId_op_target);
if (!target) { PyErr_Clear(); DISPATCH(); }
// 收集堆疊
PyObject *stack_list = PyList_New(0);
for (int i = 1; i <= STACK_LEVEL(); i++) {
PyObject *tmp = PEEK(i) ? : Py_None;
PyList_Append(stack_list, tmp);
}
// 呼叫回呼:op_target(stack_list, frame)
PUSH(target); PUSH(stack_list); PUSH(f);
PyObject *res = call_function(&stack_pointer, 2);
// ... 錯誤處理與清理
}
修改 Python/opcode_targets.h 將 DEBUG_OP 加入分派表:
static void *opcode_targets[256] = {
&&TARGET_DEBUG_OP, // opcode 0
&&TARGET_POP_TOP,
// ...
};
class MutableCodeObject:
args_name = ("co_argcount", "co_kwonlyargcount", ..., "co_code", "co_consts", ...)
def __init__(self, code_obj):
for attr in self.args_name:
val = getattr(code_obj, attr)
setattr(self, attr, list(val) if isinstance(val, tuple) else val)
def get_code(self):
args = [tuple(getattr(self, a)) if isinstance(getattr(self, a), list) else getattr(self, a)
for a in self.args_name]
return self.initial_code.__class__(*args)
注入 DEBUG_OP 會改變位元碼長度,需同步調整絕對/相對跳躍指令的引數:
FOR_ITER, JUMP_FORWARD 等):目標位址 > 插入點時,引數 +1JUMP_ABSOLUTE, POP_JUMP_IF_TRUE 等):目標位址 > 插入點時,引數 +1實作 Trace 基類與 DummyTrace 示例,支援:
do_auto_follow)=== Entering New Frame lol (0x7fde9eaebdb0) === [0,] [2, ] [False, ] ... 0 LOAD_FAST 0 x 1 DEBUG_OP None ...
此專案深度揭示 CPython 主迴圈、C API 程式設計、位元碼結構。後續可延伸:修改堆疊值實現熱修復、建立視覺化除錯器、分析生成器/協程/上下文管理器的位元碼行為。
完整程式碼:文末附有專案完整原始碼連結。