Как пройти через код операции Python при отладке cpython? - PullRequest
2 голосов
/ 11 октября 2019

Я хочу понять, как работает интерпретатор Python. Я понимаю процесс генерации кода операции и хочу лучше понять часть интерпретатора. Для этого я много читал в Интернете и узнал о цикле for (;;) в файле ceval.c в интерпретаторе Python (Cpython).

Теперь я хочу интерпретировать следующий код Python a.py:

a = 4
b = 5
c = a + b

когда я делаю python -m dis a.py

  1           0 LOAD_CONST               0 (4)
              2 STORE_NAME               0 (a)

  2           4 LOAD_CONST               1 (5)
              6 STORE_NAME               1 (b)

  3           8 LOAD_NAME                0 (a)
             10 LOAD_NAME                1 (b)
             12 BINARY_ADD
             14 STORE_NAME               2 (c)
             16 LOAD_CONST               2 (None)
             18 RETURN_VALUE

Теперь я поместил точку отладки в строку switch(opcode) в ceval.c. И теперь, когда я запускаю отладчик, он доходит до этой позиции более 2000 раз. Я думаю, это потому, что перед запуском python должен сделать еще кое-что по интерпретации. Итак, мой вопрос: как мне отладить только соответствующие инструкции кодов операций?

По сути, как я узнаю, что команда, которую я отлаживаю, на самом деле из созданной мной программы?

Пожалуйста, помогите мнес тем же. Заранее спасибо.

1 Ответ

0 голосов
/ 11 октября 2019

Я много отлаживаю CPython , чтобы лучше понять, как это работает. Отсутствие возможности установить точку останова gdb в исходных файлах Python Я решил, написав модуль расширения C .

Идея : CPython - большая программа, написанная на языке C . Мы можем легко отладить его как любую C программу - здесь никаких проблем. Если мы хотим остановить выполнение при запуске функции _PyType_Lookup, мы просто запускаем команду break _PyType_Lookup. Таким образом, если мы добавим нашу собственную функцию C в программу CPython , например cbreakpoint, мы можем остановить выполнение при каждом вызове cbreakpoint. И если мы найдем способ вставить эту cbreakpoint функцию в source.py, мы получим требуемую функциональность - каждый раз, когда интерпретатор увидит cbreakpoint, она будет остановлена ​​(если мы установим break cbreakpoint до того, как). Мы можем сделать это, написав расширение C .

Как я это сделал (я могу что-то упустить, потому что я воспроизводлю по памяти):

  1. Загрузил исходный код CPython в каталог ~/learning_python/cpython-master и скомпилировал его. Были некоторые сложности - Не удалось избавиться от «значения были оптимизированы» в GDB .
  2. Создание самого модуля - my_breakpoint.c.
  3. Создание файла установки - my_breakpoint_setup.py.
  4. Выполнение команды

    ~/learning_python/cpython-master/python my_breakpoint_setup.py build
    

    . создал файл my_breakpoint.cpython-38dm-x86_64-linux-gnu.so.

  5. Скопировал файл общего объекта из предыдущего шага в CPython's Lib каталог:

    cp -iv my_breakpoint.cpython-38dm-x86_64-linux-gnu.so ~/learning_python/cpython-master/Lib/
    

    копирование необходимо для удобства, в противном случае мы должны иметь этот файл .so в любом каталоге, в котором мы хотим использовать (импортировать) этот модуль.

  6. Теперь мы можем сделать следующее source.py:

    #!/usr/bin/python3
    
    from my_breakpoint import cbreakpoint
    
    cbreakpoint(1)
    a = 4
    
    cbreakpoint(2)
    b = 5
    
    cbreakpoint(3)
    c = a + b
    

    Чтобы выполнить этот файл, мы должны использовать наш интерпретатор ~/learning_python/cpython-master, а не системный python3, потому что системный питон не имеет модуля my_breakpointle:

    ~/learning_python/cpython-master/python source.py
    
  7. Для отладки этого файла выполните:

    gdb --args ~/learning_python/cpython-master/python -B source.py
    

    Затем внутри gdb:

    (gdb) start
    
    (gdb) break cbreakpoint
    Function "cbreakpoint" not defined.
    Make breakpoint pending on future shared library load? (y or [n]) y
    Breakpoint 2 (cbreakpoint) pending.
    
    (gdb) cont
    

    Естьодна проблема. Когда вы нажимаете cont, gdb останавливается в начале функции cbreakpoint, и вам нужно выполнить много команд next, чтобы пропустить эту функцию, и код CPython , вызывающий функцию длядобиться начала желаемого выполнения кода Python. Или вы можете установить новую точку останова после нажатия cbreakpoint, например:

    (gdb) break ceval.c:1080 ### The LOAD_CONST case beginning
    (gdb) cont
    

    Но, после этого я автоматизировал эти действия, так что вы можете просто добавить эти строки в ~ / .gdbinit :

    set breakpoint pending on
    break cbreakpoint
        command $bpnum
        tbreak ceval.c:1098
            command $bpnum
            n
            end
        cont
        end
    set breakpoint pending off
    

    Теперь вы просто запускаете gdb , как в 7 шаге, и делаете:

    (gdb) start
    (gdb) cont
    

    и вы прыгаетек началу выполнения кода source.py.

my_breakpoint.c

#include <Python.h>

static PyObject* cbreakpoint(PyObject *self, PyObject *args){
    int breakpoint_id;

    if(!PyArg_ParseTuple(args, "i", &breakpoint_id))
        return NULL;

    return Py_BuildValue("i", breakpoint_id);
}

static PyMethodDef my_methods[] = { 
    {"cbreakpoint", cbreakpoint, METH_VARARGS, "breakpoint function"},  
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef my_breakpoint = { 
    PyModuleDef_HEAD_INIT,  
    "my_breakpoint",
    "the module for setting C breakpoint in the Python source",
    -1, 
    my_methods
};

PyMODINIT_FUNC PyInit_my_breakpoint(void){
    return PyModule_Create(&my_breakpoint);
}

my_breakpoint_setup.py

from distutils.core import setup, Extension

module = Extension('my_breakpoint', sources = ['my_breakpoint.c'])

setup (name = 'PackageName',
       version = '1.0',
       description = 'This is a package for my_breakpoint module',
       ext_modules = [module])

PS

В прошлом я задавал один и тот же вопрос, он может быть полезен для вас: Оптимальный способ установки точки останова в исходном коде Python при отладке CPythonпо ГБД .

...