本节我们来看看如何使用 Systemtap 来追踪 Python 程序的执行。
1. 环境配置
从 Python 3.6 开始,CPython 可以使用嵌入式“标记”,也称为“探测器”,使得可以通过 DTrace 或 SystemTap 来追踪 Cpython。
在 Linux 上,为了Systemtap 能够动态追踪 Cpython 的执行,必须按照如下步骤配置系统环境:
- 必须安装 SystemTap 开发工具
- CPython 必须启用 –with-dtrace 编译选项
1.1 安装 SystemTap 开发工具
1
|
yum install systemtap-sdt-devel
|
1.2 Cpython 启用 –with-dtrace
默认情况下,通过 yum 安装的 Python 都已启用 –with-dtrace 编译选项。可使用如下方式进行确认
1
2
3
|
> import sysconfig
> sysconfig.get_config_vars()
> sysconfig.get_config_var('WITH_DTRACE')
|
1.3 验证 Cpython 支持 Systemtap
在 Linux 上,可以通过查看程序是否包含“.note.stapsdt”部分来验证构建的二进制文件中是否存在 SystemTap 静态标记。
如果 Cpython 未启用 –enable-shared 选项,可使用如下两种方式进行确认:
1
2
3
4
5
|
> readelf -S ./python | grep .note.stapsdt
[30] .note.stapsdt NOTE 0000000000000000 00308d78
> readelf -n ./python
# 显示的元数据或包含 SystemTap 的信息 stapsdt
|
通常情况下 yum 安装的 python 都会启用 –enable-shared 编译选项,因此需要通过下面的方式进行验证:
1
2
|
> readelf -S /usr/lib64/libpython3.6m.so.1.0 |grep -i .note.stapsdt
[28] .note.stapsdt NOTE 0000000000000000 002f5bcc
|
2. 使用 Systemtap 追踪 Python
2.1 直接使用 Python 的静态标记
使用 Systemtap 动态追踪 Python 的第一种方式是直接使用 Python 的静态标记。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# Python 未启用 --enable-shared 时
probe process("python").mark("function__entry") {
filename = user_string($arg1);
funcname = user_string($arg2);
lineno = $arg3;
printf("%s => %s in %s:%d\\n",
thread_indent(1), funcname, filename, lineno);
}
# Python 启用 --enable-shared 时,静态标记包含在 libpython shared library 中
probe process("python").library("libpython3.6m.so.1.0").mark("function__entry") {
filename = user_string($arg1);
funcname = user_string($arg2);
lineno = $arg3;
printf("%s => %s in %s:%d\\n",
thread_indent(1), funcname, filename, lineno);
}
|
Python 为 Systemtap 提供了以下静态标记:
function__entry(str filename,str funcname,int lineno)
- 作用: 表示开始 Python 函数调用
- 说明: 这个静态标记,等同于内核函数,可以通过目标变量访问静态标记内的变量
- 参数: filename,funcname,lineno,必须使用$arg1,$arg2,$arg3访问
- $arg1:(const char *) filename,使用user_string($arg1)获取 filename 的值
- $arg2:(const char *) function name,使用user_string($arg2)获取funcname的值
- $arg3:int 行号
function__return(str filename,str funcname,int lineno)
- 作用: 表示Python 函数调用结束,即return 或 exception
- 参数: 同 function__entry
line(str filename,str funcname,int lineno)
- 作用: 此标记表示即将执行 Python 脚本一行,相当于使用 Python 探查器进行 line-by-line 跟踪
- 参数: 同 function__entry
gc__start(int generation)
- 作用: Python interpreter 启动垃圾回收周期时触发
gc__done(long collected)
- 作用: Python interpreter 完成垃圾回收周期时触发
- 参数:
import__find__load__start(str modulename)
- 作用: 在importlib尝试查找并加载模块之前触发
- 参数:
- $arg0: (const char *) modulename,使用user_string($arg0)获取modulename的值
import__find__load__done(str modulename,int found)
- 作用: 调用importlib的 find_and_load function 后触发。
- 参数:
- $arg0: (const char *) modulename,使用user_string($arg0)获取modulename的值
- $arg1: int 表示模块是否已成功加载
追踪示例
追踪Python调用的脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# stap 脚本
probe process("python3.6").library("/usr/lib64/libpython3.6m.so.1.0").mark("function__entry") {
filename = user_string($arg1);
funcname = user_string($arg2);
lineno = $arg3;
printf("%s => %s in %s:%d\n",
thread_indent(1), funcname, filename, lineno);
}
probe process("python3.6").library("/usr/lib64/libpython3.6m.so.1.0").mark("function__return") {
filename = user_string($arg1);
funcname = user_string($arg2);
lineno = $arg3;
printf("%s <= %s in %s:%d\n",
thread_indent(-1), funcname, filename, lineno);
}
|
试验的 Python 脚本
1
2
3
4
5
6
7
8
9
10
11
|
# test.py
def two():
c = 1 + 2
return c
def one():
d = two()
return d
one()
|
执行 Python 动态追踪
1
2
3
4
5
6
7
8
9
10
11
|
# stap 监测
> stap stap_test.stp -c "python3.6 test.py"
......
0 python3.6(29732): => __init__ in <frozen importlib._bootstrap_external>:800
4 python3.6(29732): <= __init__ in <frozen importlib._bootstrap_external>:804
0 python3.6(29732): => <module> in test.py:2
5 python3.6(29732): => one in test.py:6
8 python3.6(29732): => two in test.py:2
10 python3.6(29732): <= two in test.py:4
13 python3.6(29732): <= one in test.py:8
16 python3.6(29732): <= <module> in test.py:11
|
2.2 使用 Systemtap 提供的 typeset
typeset 提供的函数库,可以帮助我们隐藏一些Python 静态标记的细节。从目前提供的 typeset 来看,提供的库还是很低级。
1
2
3
4
|
ll /usr/share/systemtap/tapset/|grep python
-rw-r--r--. 1 root root 522 8月 7 2019 libpython2.7-64.stp
-rw-r--r--. 1 root root 31021 10月 19 00:12 python2.stp
-rw-r--r--. 1 root root 30405 10月 19 00:12 python3.stp
|