做一个字节码追踪器,从内部理解 Python 的执行过程
admin
2023-07-30 22:08:05
0

最近我在研究 Python 的执行模型。我对 Python 内部的东西挺好奇,比如:类似 YIELDVALUE 和 YIELDFROM 此类操作码的实现;列表表达式、生成器表达式以及一些有趣的Python 特性是怎么编译的;异常触发之时,字节码层面发生了什么。

阅读 CPython 代码是相当有益的,但是我觉得要完全理解字节码的执行和堆栈的变化,光读源码是远远不够的。GDB 是个好选择,但我很懒,只想写一些高级的接口和 Python 代码。

因此我想做一个字节码级别的追踪 API,就像 sys.settrace 所提供的那样,但颗粒度更出色。这种练习完美地锻炼了我将 C 转化为 Python 的能力。我们所需的有以下几点:

  • 一个新的CPython解释器操作码
  • 一种将操作码注入Python字节码的方法
  • 一些Python代码,用于在Python的角度处理操作码

注:在这篇文章中,Python版本是3.5

一种新的CPython操作码

我们的新操作码:DEBUG_OP

这个新的操作码DEBUG_OP是我第一次尝试用C代码来实现CPython。我会尽量使之保持简洁。

我想要达到的目的是,无论我的操作码何时执行,都有一种方式调用一些Python代码,与此同时,我们也想能够追踪一些与执行上下文有关的数据。我们的操作码会把这些信息当作参数传递给我们的回调函数。我能辨识出的有用信息如下:

  • 堆栈的内容
  • 执行DEBUG_OP的帧对象信息

因此我们 DEBUG_OP 所需做的所有事情是:

  • 找到回调函数
  • 创建堆栈内容的列表
  • 调用回调函数,并将堆栈列表和当前帧作为参数传给它

听起来挺简单啊,让我们开始吧!

声明:以下的解释和代码都是经过大量段错误得到的。首先要做的事情,就是给我们的操作码命名并赋值,因此我们需要在Include/opcode.h中添加

123456789101112 /** My own comments begin by \’**\’ **//** From: Includes/opcode.h **/ /* Instruction opcodes for compiled code */ /** We just have to define our opcode with a free value    0 was the first one I found **/#define DEBUG_OP                0 #define POP_TOP                 1#define ROT_TWO                 2#define ROT_THREE               3

这简单的部分是完成了,现在我们必须真正去编写我们的操作码。

实现 DEBUG_OP

在考虑实现DEBUG_OP之前,我们需要问我们自己的第一个问题是:“我的接口应该是什么样的?”

拥有一个可以调用其他代码的新操作码是很酷的,但是它实际上会调用哪些代码呢?这个操作码怎么找到回调函数呢?我选择了一种看起来最简单的解决方案,在帧的全局区域写死函数名。

现在问题就变成了:“我怎么从一个字典中找到一个不变的C字符串?”

为了回答这个问题,我们可以寻找一些用在Python的main循环中的用到的和上下文管理相关的标识符**enter**和**exit**。

我们可以看到标识符被用在 SETUP_WITH 操作码中。

1234567 /** From: Python/ceval.c **/TARGET(SETUP_WITH) {_Py_IDENTIFIER(__exit__);_Py_IDENTIFIER(__enter__);PyObject *mgr = TOP();PyObject *exit = special_lookup(mgr, &PyId___exit__), *enter;PyObject *res;

现在,看一下_Py_IDENTIFIER 的宏定义:

12345678910111213141516171819202122232425262728293031 /** From: Include/object.h **/ /********************* String Literals ****************************************//* This structure helps managing static strings. The basic usage goes like this:   Instead of doing        r = PyObject_CallMethod(o, \”foo\”, \”args\”, ...);    do        _Py_IDENTIFIER(foo);       ...       r = _PyObject_CallMethodId(o, &PyId_foo, \”args\”, ...);    PyId_foo is a static variable, either on block level or file level. On first   usage, the string \”foo\” is interned, and the structures are linked. On interpreter   shutdown, all strings are released (through _PyUnicode_ClearStaticStrings).    Alternatively, _Py_static_string allows to choose the variable name.   _PyUnicode_FromId returns a borrowed reference to the interned string.   _PyObject_{Get,Set,Has}AttrId are __getattr__ versions using _Py_Identifier*.*/typedef struct _Py_Identifier {    struct _Py_Identifier *next;    const char* string;>。

最近我在研究 Python 的执行模型。我对 Python 内部的东西挺好奇,比如:类似 YIELDVALUE 和 YIELDFROM 此类操作码的实现;列表表达式、生成器表达式以及一些有趣的Python 特性是怎么编译的;异常触发之时,字节码层面发生了什么。

阅读 CPython 代码是相当有益的,但是我觉得要完全理解字节码的执行和堆栈的变化,光读源码是远远不够的。GDB 是个好选择,但我很懒,只想写一些高级的接口和 Python 代码。

因此我想做一个字节码级别的追踪 API,就像 sys.settrace 所提供的那样,但颗粒度更出色。这种练习完美地锻炼了我将 C 转化为 Python 的能力。我们所需的有以下几点:

  • 一个新的CPython解释器操作码
  • 一种将操作码注入Python字节码的方法
  • 一些Python代码,用于在Python的角度处理操作码

注:在这篇文章中,Python版本是3.5

一种新的CPython操作码

我们的新操作码:DEBUG_OP

这个新的操作码DEBUG_OP是我第一次尝试用C代码来实现CPython。我会尽量使之保持简洁。

我想要达到的目的是,无论我的操作码何时执行,都有一种方式调用一些Python代码,与此同时,我们也想能够追踪一些与执行上下文有关的数据。我们的操作码会把这些信息当作参数传递给我们的回调函数。我能辨识出的有用信息如下:

  • 堆栈的内容
  • 执行DEBUG_OP的帧对象信息

因此我们 DEBUG_OP 所需做的所有事情是:

  • 找到回调函数
  • 创建堆栈内容的列表
  • 调用回调函数,并将堆栈列表和当前帧作为参数传给它

听起来挺简单啊,让我们开始吧!

声明:以下的解释和代码都是经过大量段错误得到的。首先要做的事情,就是给我们的操作码命名并赋值,因此我们需要在Include/opcode.h中添加

123456789101112 /** My own comments begin by \’**\’ **//** From: Includes/opcode.h **/ /* Instruction opcodes for compiled code */ /** We just have to define our opcode with a free value    0 was the first one I found **/#define DEBUG_OP                0 #define POP_TOP                 1#define ROT_TWO                 2#define ROT_THREE               3

这简单的部分是完成了,现在我们必须真正去编写我们的操作码。

实现 DEBUG_OP

在考虑实现DEBUG_OP之前,我们需要问我们自己的第一个问题是:“我的接口应该是什么样的?”

拥有一个可以调用其他代码的新操作码是很酷的,但是它实际上会调用哪些代码呢?这个操作码怎么找到回调函数呢?我选择了一种看起来最简单的解决方案,在帧的全局区域写死函数名。

现在问题就变成了:“我怎么从一个字典中找到一个不变的C字符串?”

为了回答这个问题,我们可以寻找一些用在Python的main循环中的用到的和上下文管理相关的标识符**enter**和**exit**。

我们可以看到标识符被用在 SETUP_WITH 操作码中。

1234567 /** From: Python/ceval.c **/TARGET(SETUP_WITH) {_Py_IDENTIFIER(__exit__);_Py_IDENTIFIER(__enter__);PyObject *mgr = TOP();PyObject *exit = special_lookup(mgr, &PyId___exit__), *enter;PyObject *res;

现在,看一下_Py_IDENTIFIER 的宏定义:

12345678910111213141516171819202122232425262728293031 /** From: Include/object.h **/ /********************* String Literals ****************************************//* This structure helps managing static strings. The basic usage goes like this:   Instead of doing        r = PyObject_CallMethod(o, \”foo\”, \”args\”, ...);    do        _Py_IDENTIFIER(foo);       ...       r = _PyObject_CallMethodId(o, &PyId_foo, \”args\”, ...);    PyId_foo is a static variable, either on block level or file level. On first   usage, the string \”foo\” is interned, and the structures are linked. On interpreter   shutdown, all strings are released (through _PyUnicode_ClearStaticStrings).    Alternatively, _Py_static_string allows to choose the variable name.   _PyUnicode_FromId returns a borrowed reference to the interned string.   _PyObject_{Get,Set,Has}AttrId are __getattr__ versions using _Py_Identifier*.*/typedef struct _Py_Identifier {

相关内容

热门资讯

500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
Prometheus+Graf... 一,Prometheus概述 1,什么是Prometheus?Prometheus是最初在Sound...
python绘图库Matplo... 本文简单介绍了Python绘图库Matplotlib的安装,简介如下: matplotlib是pyt...