嵌入式Python解释器中跟踪代码执行

9
我希望创建一个应用程序,其中嵌入了Python解释器和基本的调试功能。现在我正在查找API函数,以便能够逐步运行代码并获取当前正在执行(或即将执行)的代码行号。
官方Python文档在跟踪和分析方面似乎有些不足。例如,没有关于Py_tracefunc返回值含义的信息。
到目前为止,我已经收集了以下内容:
#include <Python.h>

static int lineCounter = 0;

int trace(PyObject *obj, PyFrameObject *frame, int what, PyObject *arg)
{
    if(what == PyTrace_LINE)
    {
        lineCounter += 1;
        printf("line %d\n", lineCounter);
    }
    return 0;
}

int main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    if (program == NULL) {
        fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
        exit(1);
    }
    Py_SetProgramName(program);  /* optional but recommended */
    Py_Initialize();
    PyEval_SetTrace(trace, NULL);
    char *code = "def adder(a, b):\n"
                 " return a + b\n"
                 "x = 3\n"
                 "y = 4\n"
                 "print(adder(x, y))\n";
    PyRun_SimpleString(code);
    Py_Finalize();
    PyMem_RawFree(program);
    return 0;
}

然而,编译器输出以下错误:
hello.c:5:26: error: unknown type name ‘PyFrameObject’
 int trace(PyObject *obj, PyFrameObject *frame, int what, PyObject *arg)
                          ^

我正在使用ManjaroLinux操作系统,并使用以下内容进行编译:

gcc -o hello hello.c -I/usr/include/python3.5m  -Wno-unused-result -Wsign-compare -Wunreachable-code -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector-strong --param=ssp-buffer-size=4 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -L/usr/lib -lpython3.5m -lpthread -ldl  -lutil -lm  -Xlinker -export-dynamic

我发现我可以用struct _frame替换PyFrameObject,然后程序就可以编译了,但是大家都知道这只是一个肮脏的hack,而不是解决方案。
可执行文件输出如下:
line 1
line 2
line 3
line 4
line 5
7
但我希望跟踪脚本的执行流程(即:从第3行开始,然后是4,5,然后由于函数调用而到达2)。 我找不到关于逐步执行的任何信息。

您能推荐一些有关Python C API的其他资源,其中包含更多信息和该主题的一些介绍内容吗?

我奖励了这个答案,因为它无论如何都会过期。 但是,我仍在寻找,并将感激以上其他问题的答案。


2
这可能有助于跟踪函数:https://docs.python.org/3/library/sys.html#sys.settrace - Sven Marnach
2
除此之外,使用源代码吧,卢克! - Sven Marnach
@SvenMarnach: 你是指Python头文件吗? - Luke
1
这里涉及到CPython解释器的实现细节,因此需要查看整个CPython源代码而不仅仅是头文件。在这些情况下,除了文档之外,查看源代码通常是必要的(或者至少有帮助)。例如,查看Py_tracefunc的定义以获取返回值的含义,请参见此处 - Sven Marnach
@SvenMarnach: “当抛出异常时返回-1,成功时返回0” - 这是否意味着当我的回调函数实现引发异常时,我应该返回-1?为什么我要在那里引发异常? - Luke
1
好的,阅读源代码。这里是跟踪函数被调用的地方:https://github.com/python/cpython/blob/aed79b41a1fbcedd4697269e3fdd40af5ee82b14/Python/ceval.c#L4352。您可以查找此函数的调用者(在同一文件中),并查看如果返回错误它会如何行为(它不会进入帧)。如果您不希望发生这种情况,您可能不想抛出异常。 - Sven Marnach
4个回答

9
hello.c:5:26: error: unknown type name ‘PyFrameObject’
这个错误意味着PyFrameObject没有被声明。我进行了一次谷歌搜索,发现Python源代码树中的frameobject.h是声明该结构的地方。
我希望你能添加以下这行代码:
#include <frameobject.h>

为了解决这个问题。

1
你能提供任何证明需要单独包含frameobject.h的来源吗? - Luke
我从您发布的错误消息中推断出来。您可以通过阅读源代码自行确定。请阅读Python.h并跟随各种包含文件,以查看它是否包含frameobject.h。这甚至可能是基于定义的预处理器标记进行条件判断。 - dsh
就这么多,我可以相信你。可惜文档相当有选择性。但是那个简单的编译错误并不是我最大的担忧,你可能已经猜到了。 - Luke
一些其他的Python头文件包含了struct _frame的前向声明,以避免包含frameobject.h。你可以用struct _frame替换PyFrameObject。可以在Python的git历史记录中搜索原因。 - payload
我授予了你的答案赏金,因为它无论如何都会过期。然而,我仍在寻找并将感激对我的其他问题的回答。 - Luke
显示剩余2条评论

0
根据Python v3.10.8中Include/cpython/pystate.h的文档字符串:
/* Py_tracefunc return -1 when raising an exception, or 0 for success. */
typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *);

1
你的回答可以通过提供更多支持信息来改进。请[编辑]以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

0

pyFrameObject具有

int f_lineno;

field. 你可以使用它。但很显然,它并不总是存储正确的值。所以,你应该可能要使用这个函数:

/* Return the line of code the frame is currently executing. */
int PyFrame_GetLineNumber(PyFrameObject *);      

然后,您可以使用

frame->f_code->co_filename 

获取当前文件名

frame->f_code->co_name 

获取当前函数名

frame->f_back

获取调用栈中的下一级。


-1

PyFrameObject只是一个_frame结构体。在您的函数签名中将PyFrameObject替换为_frame,您就不必包含任何额外的Python头文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接