你是否好奇 Python 程式碼在 CPython 虛擬機器中究竟如何一步步執行?YIELD_VALUE、LIST_COMP、異常處理等 bytecode 指令背後的堆疊變化是什麼?本文將帶你深入 CPython 3.5 內核,從零實作一個自訂 opcode(DEBUG_OP),並示範如何在執行期將其注入任意函數的 bytecode,打造一個指令級追蹤器,可即時觀測堆疊、幀物件與控制流跳轉。
Include/opcode.h 定義 DEBUG_OP = 0,並在 Python/ceval.c 實作對應的 TARGET(DEBUG_OP)。op_target(stack_list, frame) 並呼叫。MutableCodeObject 解決 code object 不可變問題,並提供 insert_op_debug() 處理絕對/相對跳轉偏移修正。Trace 類別封裝自動追蹤、巢狀呼叫跟蹤與報表生成。#define DEBUG_OP 0
#define POP_TOP 1
#define ROT_TWO 2
/* ...既有 opcode 依序後移... */
static void *opcode_targets[256] = {
&&TARGET_DEBUG_OP,
&&TARGET_POP_TOP,
&&TARGET_ROT_TWO,
/* ... */
};
TARGET(DEBUG_OP) {
PyObject *target = NULL, *value = NULL, *res = NULL, **sp = NULL, *tmp;
int i;
_Py_IDENTIFIER(op_target);
target = _PyDict_GetItemId(f->f_globals, &PyId_op_target);
if (target == NULL && _PyErr_OCCURRED()) {
if (!PyErr_ExceptionMatches(PyExc_KeyError)) goto error;
PyErr_Clear();
DISPATCH();
}
/* 收集堆疊內容 */
value = PyList_New(0);
for (i = 1; i <= STACK_LEVEL(); i++) {
tmp = PEEK(i);
if (tmp == NULL) tmp = Py_None;
PyList_Append(value, tmp);
}
/* 呼叫 op_target(stack_list, frame) */
Py_INCREF(target);
PUSH(target);
PUSH(value);
Py_INCREF(f);
PUSH((PyObject *)f);
sp = stack_pointer;
res = call_function(&sp, 2);
stack_pointer = sp;
if (res == NULL) goto error;
Py_DECREF(res);
DISPATCH();
}
關鍵點:
_Py_IDENTIFIER 與 _PyDict_GetItemId 高效查找全域字典。STACK_LEVEL()、PEEK(i) 存取運算堆疊。PUSH(target)、PUSH(value)、PUSH(frame) → call_function(&sp, 3)。
class MutableCodeObject:
args_name = ("co_argcount", "co_kwonlyargcount", "co_nlocals", "co_stacksize",
"co_flags", "co_code", "co_consts", "co_names", "co_varnames",
"co_filename", "co_name", "co_firstlineno", "co_lnotab",
"co_freevars", "co_cellvars")
def __init__(self, code_obj):
self.initial_code = 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)
opcode_jump_rel = {'FOR_ITER', 'JUMP_FORWARD', 'SETUP_LOOP', 'SETUP_WITH', 'SETUP_EXCEPT', 'SETUP_FINALLY'}
opcode_jump_abs = {'POP_JUMP_IF_TRUE', 'POP_JUMP_IF_FALSE', 'JUMP_ABSOLUTE'}
def insert_op_debug(code, offset):
res = b""
inserted = False
for instr in dis.Bytecode(code):
if instr.offset == offset:
res += b"\x00" # DEBUG_OP opcode = 0
inserted = True
# 相對跳轉:目標在插入點之後 → arg + 1
if instr.opname in opcode_jump_rel and not inserted:
if offset < instr.offset + 3 + instr.arg: # 3 = opcode(1) + arg(2)
res += struct.pack(" 插入點 → arg + 1
if instr.opname in opcode_jump_abs:
if instr.arg > offset:
res += struct.pack("
def lol(x):
for i in range(10):
if x == i:
break
# 注入 DEBUG_OP 到每條指令
lol.__code__ = transform_code(lol.__code__, add_debug_op_everywhere, add_stacksize=3)
def op_target(stack, frame):
print(stack)
lol(2)
[]
[]
[10, ]
[range(0, 10)]
[]
[0, ]
[]
[2, ]
[0, 2, ]
[False, ] # COMPARE_OP 結果
[]
[1, ]
...
[2, 2, ]
[True, ] # 條件成立
[]
[None] # RETURN_VALUE
清晰可見:FOR_ITER 產生 iterator、COMPARE_OP 推入布林值、POP_JUMP_IF_FALSE 依條件跳轉。
class Trace:
def __init__(self, func):
self.func = func
def call(self, *args, **kwargs):
self.add_func_to_trace(self.func)
op_target.callback = self.callback
try:
return self.func(*args, **kwargs)
finally:
op_target.callback = None
def add_func_to_trace(self, f):
if not hasattr(f, "op_debug") and hasattr(f, "__code__"):
f.__code__ = transform_code(f.__code__, add_everywhere, add_stacksize=ADD_STACK)
f.__globals__["op_target"] = op_target
f.op_debug = True
def do_auto_follow(self, stack, frame):
next_instr = FrameAnalyser(frame).next_instr()
if "CALL" in next_instr.opname:
f_index = (next_instr.arg & 0xff) + (2 * (next_instr.arg >> 8))
called_func = stack[f_index]
if not hasattr(called_func, "op_debug"):
self.add_func_to_trace(called_func)
op_target 中直接操作 stack_list 實現熱修補(需極度謹慎)。專案完整程式碼見文末連結。編譯 CPython 3.5:
./configure --with-pydebug
make -j$(nproc)
./python your_script.py
透過自訂 opcode 與 bytecode 注入,我們獲得了前所未有的執行時可觀測性。這不僅是理解 CPython 內核的最佳途徑,更為進階除錯、效能調優與教學工具開發奠定了基礎。歡迎 Fork 擴充,打造屬於你的 Python 運行時探測器!