
本文介绍如何在不使用 eval 或 exec 的前提下,安全地对任意 Python 源码字符串(如用户提交的函数定义)启用 sys.settrace 进行细粒度执行跟踪,涵盖 AST 解析、动态编译、作用域隔离与 trace 函数集成等关键实践。
本文介绍如何在不使用 eval 或 exec 的前提下,安全地对任意 python 源码字符串(如用户提交的函数定义)启用 sys.settrace 进行细粒度执行跟踪,涵盖 ast 解析、动态编译、作用域隔离与 trace 函数集成等关键实践。
在实际开发中,我们常需对用户提交的 Python 代码(例如在线编程题评测、教学调试器或沙箱式分析工具)进行运行时行为追踪——记录变量赋值、函数调用、行执行顺序等。但直接使用 eval() 或 exec() 执行用户代码存在严重安全隐患(如任意系统调用、文件读写、远程请求),违背最小权限原则。
推荐方案:基于 compile() + exec() 的受控执行 + sys.settrace 动态注入
关键在于:避免 eval/exec 的裸用,转而通过 compile() 显式指定编译模式,并在受限命名空间中执行,同时在执行前启用自定义 trace 函数。以下是完整、安全、可落地的实现:
import sys
import ast
import functools
def trace_calls(frame, event, arg):
"""示例 trace 函数:仅打印函数调用与行号"""
if event == "call":
print(f"[CALL] {frame.f_code.co_name} at {frame.f_lineno}")
elif event == "line":
print(f"[LINE] {frame.f_code.co_name}:{frame.f_lineno}")
return trace_calls # 继续跟踪
def instrument_and_trace(source_code: str, global_ns: dict = None, local_ns: dict = None):
"""
安全地编译并执行用户源码,同时启用 sys.settrace
:param source_code: 用户提交的合法 Python 源码字符串(如函数定义)
:param global_ns: 可选的全局命名空间(建议传入空 dict 或最小化内置环境)
:param local_ns: 可选的局部命名空间
:return: 编译后的 code object(可用于后续多次执行)
"""
# 1. 静态语法检查(提前捕获 SyntaxError)
try:
tree = ast.parse(source_code, mode='exec')
except SyntaxError as e:
raise ValueError(f"Invalid Python syntax at line {e.lineno}: {e.text.strip() if e.text else ''}") from e
# 2. 编译为 code object(mode='exec' 支持多语句,包括 def)
try:
code_obj = compile(source_code, filename="<user_input>", mode='exec')
except Exception as e:
raise RuntimeError(f"Compilation failed: {e}") from e
# 3. 构建最小化、隔离的执行环境
safe_globals = global_ns or {
'__builtins__': {}, # 禁用所有内置函数(可按需显式开放如 print、len)
'__name__': '__user_module__',
'__doc__': None,
}
# 4. 启用 trace 并执行
sys.settrace(trace_calls)
try:
exec(code_obj, safe_globals, local_ns or {})
finally:
sys.settrace(None) # 确保无论成功失败都关闭 trace
return code_obj
# ✅ 使用示例:跟踪用户定义的 adder 函数
user_code = """
def adder(x, y):
z = x + y
return z
"""
# 执行并触发 trace(输出行号与调用信息)
instrument_and_trace(user_code)
# 此时 adder 已存在于 safe_globals 中,可安全调用
safe_env = {}
instrument_and_trace(user_code, global_ns=safe_env)
result = safe_env['adder'](3, 5) # 输出 trace 日志后返回 8
print("Result:", result)</user_input>
核心优势与注意事项:
- ✅ 无
eval/exec字符串执行风险:compile()仅做语法/字节码转换,不执行;exec()在严格控制的global_ns中运行,可禁用危险内置函数。 - ✅ 支持完整 Python 语法:
mode='exec'允许def、class、if、循环等任意合法语句块。 - ⚠️
sys.settrace是线程局部的:若需多线程跟踪,请在每个线程内单独设置;主线程 trace 不会自动传播到子线程。 - ⚠️ 性能开销显著:
settrace会使解释器在每行、每次调用时回调,生产环境慎用;调试/教育场景推荐。 - ? 进一步加固建议:
- 使用
ast.NodeVisitor对 AST 做白名单校验(如禁止Call节点含os.、subprocess.等危险模块); - 结合
resource.setrlimit()限制 CPU/内存; - 在
seccomp或容器沙箱中运行(Linux)。
- 使用
综上,通过 compile() + 受限 exec() + sys.settrace 的组合,你既能获得对用户代码的完整执行控制力,又能规避 eval 的安全陷阱,是构建可信赖 Python 动态分析工具的工业级实践路径。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











