Bug 61138
| Summary: | 256-check_python3_abi.brp переименовывает в .abi3.so расширения, собранные без limited API | ||
|---|---|---|---|
| Product: | Sisyphus | Reporter: | Stanislav Levin <slev> |
| Component: | rpm-build-python3 | Assignee: | Ivan Zakharyaschev <imz> |
| Status: | NEW --- | QA Contact: | qa-sisyphus |
| Severity: | normal | ||
| Priority: | P5 | CC: | darktemplar, george, glebfm, imz, kotopesutility, ldv, slev, vitty |
| Version: | unstable | ||
| Hardware: | x86_64 | ||
| OS: | Linux | ||

Description
Stanislav Levin 2026-10-20 11:10:16 MSK256-check_python3_abi.brp переименовывает модуль расширения из .cpython-3XX.so в .abi3.so, если все функции Python C API, которые он использует, есть в списке stable ABI. Но по символам этого не узнать. Модуль работает с объектами Python двумя способами: - вызывает функции Python C API - их имена записаны в .so как неразрешённые символы, при загрузке модуля они берутся из интерпретатора python3; brp их видит; - сам читает поля структур объектов - это делают макросы и inline- функции из заголовков Python. Компилятор заменяет имя поля на его адрес внутри структуры (смещение) из заголовков той версии Python, с которой идёт сборка, и записывает это число прямо в машинный код. Имени не остаётся, поэтому brp этого не видит. Без Py_LIMITED_API многие макросы читают поля напрямую. С Py_LIMITED_API те же макросы становятся вызовами функций или недоступны совсем. Stable ABI обещает сохранять только часть структур и полей (см. python3/Misc/stable_abi.toml). Например, что читают макросы без limited API: - PyUnicode_Check читает поле tp_flags структуры PyTypeObject. PyTypeObject в stable ABI - 'opaque': "No members are part of the ABI, nor is the size". Нарушение stable ABI. - PyTuple_GET_ITEM читает поле ob_item структуры PyTupleObject. Этой структуры в stable ABI нет. Нарушение stable ABI. - PyUnicode_GET_LENGTH читает поле length структуры PyASCIIObject. Этой структуры в stable ABI нет. Нарушение stable ABI. Подробные ссылки на исходники - в конце. Шаги для воспроизведения: $ cat > foo.c <<'EOF' #include <Python.h> static PyObject *is_str(PyObject *m, PyObject *obj) { return PyBool_FromLong(PyUnicode_Check(obj)); } static PyMethodDef methods[] = {{"is_str", is_str, METH_O, NULL}, {NULL}}; static struct PyModuleDef module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "foo", .m_methods = methods, }; PyMODINIT_FUNC PyInit_foo(void) { return PyModuleDef_Init(&module); } EOF $ F="$(python3-config --cflags)" $ gcc -shared -fPIC $F foo.c -o foo.cpython-314.so $ gcc -shared -fPIC $F -DPy_LIMITED_API=0x030E0000 foo.c -o foo.abi3.so Один и тот же исходный код даёт два разных модуля: $ nm -D --undefined-only foo.cpython-314.so | grep Py U PyBool_FromLong U PyModuleDef_Init $ nm -D --undefined-only foo.abi3.so | grep Py U PyBool_FromLong U PyModuleDef_Init U Py_TYPE U PyType_GetFlags В foo.abi3.so PyUnicode_Check - это вызовы Py_TYPE и PyType_GetFlags. В foo.cpython-314.so вместо них встроен код, который сам читает поле tp_flags структуры PyTypeObject. Лог brp для foo.cpython-314.so: process_so.py: .../site-packages/foo.cpython-314.so requires only stable API from python3: {'PyBool_FromLong', 'PyModuleDef_Init'} 256-check_python3_abi.brp: Rename .../site-packages/foo.cpython-314.so to .../site-packages/foo.abi3.so Фактический результат: foo.cpython-314.so переименован в foo.abi3.so. Теперь он выглядит так же, как настоящая сборка с limited API, но в нём по-прежнему зашиты смещения полей структур Python 3.14. Предлагаемое исправление: Согласно документации (Py_LIMITED_API, Py_TARGET_ABI3T (3.15+)): https://docs.python.org/3/c-api/stable.html#compiling-for-stable-abi > To compile for a Stable ABI, define one or both of the following macros to the lowest Python version your extension should support, in Py_PACK_VERSION format Нет обещания, что наличие *только* abi3 символов делает модуль abi3 совместимым. Проверка символов может доказать, что модуль НЕ совместим с abi3, но не может доказать, что он совместим. Это решает только сборка. Ссылки на исходники https://git.altlinux.org/gitoskop/#/gears/p/python3.git/-/blob/sisyphus/python3/Include/ PyUnicode_Check(op): 1. unicodeobject.h:106-107 - макрос: PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_UNICODE_SUBCLASS). 2. Py_TYPE(op): без limited API (или limited < 3.14) - inline _Py_TYPE, return ob->ob_type (object.h:268-275); с limited API >= 3.14 - функция Py_TYPE (object.h:262). 3. PyType_FastSubclass - макрос, вызывает PyType_HasFeature (object.h:798). 4. PyType_HasFeature (object.h:782-796): с limited API - flags = PyType_GetFlags(type) (функция, объявлена в object.h:431; комментарий "PyTypeObject is opaque in the limited C API"); без limited API - flags = type->tp_flags (object.h:792). PyTuple_GET_ITEM(op, index): 1. cpython/tupleobject.h подключается только при #ifndef Py_LIMITED_API (tupleobject.h:37-41); с limited API макроса нет, есть только функция PyTuple_GetItem (tupleobject.h:32). 2. cpython/tupleobject.h:29 - макрос: (_PyTuple_CAST(op)->ob_item[(index)]). 3. _PyTuple_CAST (cpython/tupleobject.h:18-19) - приведение к PyTupleObject*. 4. PyTupleObject (cpython/tupleobject.h:5-13): PyObject_VAR_HEAD, затем массив ob_item (строка 12). PyUnicode_GET_LENGTH(op): 1. cpython/unicodeobject.h подключается только при #ifndef Py_LIMITED_API (unicodeobject.h:1020-1024); с limited API макроса нет. Есть только функция PyUnicode_GetLength (unicodeobject.h:158-163), и то лишь при limited API >= 3.3: #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03030000. 2. cpython/unicodeobject.h:301 - макрос, вызывает static inline функцию PyUnicode_GET_LENGTH (cpython/unicodeobject.h:298-300). 3. Функция: return _PyASCIIObject_CAST(op)->length. 4. _PyASCIIObject_CAST (cpython/unicodeobject.h:187-189) - приведение к PyASCIIObject*. 5. PyASCIIObject (cpython/unicodeobject.h:54-163), поле length (строка 100).