Как пройти через опкод Python при отладке cpython?

Я хочу понять, как работает интерпретатор 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 также должен выполнять некоторые другие интерпретации. Итак, мой вопрос: как мне отлаживать только соответствующие инструкции кодов операций?

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

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


person hardik24    schedule 11.10.2019    source источник


Ответы (1)


Я много занимаюсь отладкой 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 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, потому что системный python не имеет модуля my_breakpoint:

    ~/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])

P.S.

Я задавал тот же вопрос в прошлом, это может быть полезно для вас: оптимальный способ установить точку останова в исходном коде Python при отладке CPython от GDB.

person MiniMax    schedule 11.10.2019
comment
после того, как я добрался до cbreakpoint, я обнаружил, что команда finish также вывела меня из функции расширения c - person Arjun; 22.05.2021