I do a lot of CPython debugging for better understanding the way it works. The lack of possibility to set a gdb breakpoint in Python source files I solved by writing a C extension module.
The idea: CPython is a big program written in C language. We can easy debug it as any C program - no problems here. If we want to stop execution when the _PyType_Lookup function is started, we just run a break _PyType_Lookup command. Thus, if we add our own C function into the CPython program, for example cbreakpoint, we can stop execution every time the cbreakpoint is called. And if we will find the way to insert this cbreakpoint function into the source.py, we will get the required functionality - every time the interpreter will see the cbreakpoint, it will be stopped (if we set break cbreakpoint before). We can do that by writing a C extension".
How I did that (I can miss something, because I am reproducing from memory):
- Downloaded a CPython source into the
~/learning_python/cpython-master directory and compiled it. There were some intricacies - Can't get rid of “value has been optimized out” in GDB.
- Created a module itself -
my_breakpoint.c.
- Created a setup file -
my_breakpoint_setup.py.
Run a
~/learning_python/cpython-master/python my_breakpoint_setup.py build
command. It created a my_breakpoint.cpython-38dm-x86_64-linux-gnu.so file.
Copied the shared object file from previous step into CPython's Lib directory:
cp -iv my_breakpoint.cpython-38dm-x86_64-linux-gnu.so ~/learning_python/cpython-master/Lib/
The copying is needed for convenience, otherwise we should have this .so file in any directory we want use (import) this module.
Now, we can make a following source.py:
#!/usr/bin/python3
from my_breakpoint import cbreakpoint
cbreakpoint(1)
a = 4
cbreakpoint(2)
b = 5
cbreakpoint(3)
c = a + b
To execute this file we must use our ~/learning_python/cpython-master interpreter, not a system's python3, because the system's python doesn't have the my_breakpoint module:
~/learning_python/cpython-master/python source.py
To debug this file do:
gdb --args ~/learning_python/cpython-master/python -B source.py
Then, inside 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
There is one problem. When you are pressing cont, gdb is stopped at the beginning of the cbreakpoint function and you are needing to do many next commands to skip this function and a CPython function calling code to achieve the beginning of the desired Python code execution. Or you can set a new breakpoint after cbreakpoint was hitted, like:
(gdb) break ceval.c:1080 ### The LOAD_CONST case beginning
(gdb) cont
But, after doing this many times I were automating these actions, so you can just add these lines into your ~/.gdbinit:
set breakpoint pending on
break cbreakpoint
command $bpnum
tbreak ceval.c:1098
command $bpnum
n
end
cont
end
set breakpoint pending off
Now, you just start gdb as in the 7 step and do:
(gdb) start
(gdb) cont
and you will jumped to the beginning of the source.py code execution.
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.
I asked the same question in the past, it can be useful for you: The optimal way to set a breakpoint in the Python source code while debugging CPython by GDB.