]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-146636: Py_mod_abi mandatory for modules created from slots array (GH-146855)
authorPetr Viktorin <encukou@gmail.com>
Thu, 2 Apr 2026 11:54:21 +0000 (13:54 +0200)
committerGitHub <noreply@github.com>
Thu, 2 Apr 2026 11:54:21 +0000 (13:54 +0200)
Doc/c-api/module.rst
Doc/extending/first-extension-module.rst
Doc/includes/capi-extension/spammodule-01.c
Lib/test/test_capi/test_module.py
Lib/test/test_cext/extension.c
Lib/test/test_import/__init__.py
Misc/NEWS.d/next/C_API/2026-03-31-13-33-41.gh-issue-146636.5do3wt.rst [new file with mode: 0644]
Modules/_testcapi/module.c
Modules/_testmultiphase.c
Objects/moduleobject.c
Python/modsupport.c

index 39293b0fa228dfbfae1d7063818f25b34c69feb2..8b967c285ac865c842aab7563b4599912508fc0c 100644 (file)
@@ -230,6 +230,9 @@ Feature slots
    When creating a module, Python checks the value of this slot
    using :c:func:`PyABIInfo_Check`.
 
+   This slot is required, except for modules created from
+   :c:struct:`PyModuleDef`.
+
    .. versionadded:: 3.15
 
 .. c:macro:: Py_mod_multiple_interpreters
@@ -620,9 +623,9 @@ rather than from an extension's :ref:`export hook <extension-export-hook>`.
    and the :py:class:`~importlib.machinery.ModuleSpec` *spec*.
 
    The *slots* argument must point to an array of :c:type:`PyModuleDef_Slot`
-   structures, terminated by an entry slot with slot ID of 0
+   structures, terminated by an entry with slot ID of 0
    (typically written as ``{0}`` or ``{0, NULL}`` in C).
-   The *slots* argument may not be ``NULL``.
+   The array must include a :c:data:`Py_mod_abi` entry.
 
    The *spec* argument may be any ``ModuleSpec``-like object, as described
    in :c:macro:`Py_mod_create` documentation.
index f1ba0a3ceb7dbabda553585c0b94d054dd5d7c8c..cd755a98f7f5f4ca215f015b1c2faa739650b810 100644 (file)
@@ -265,12 +265,19 @@ Define this array just before your export hook:
 
 .. code-block:: c
 
+   PyABIInfo_VAR(abi_info);
+
    static PyModuleDef_Slot spam_slots[] = {
+      {Py_mod_abi, &abi_info},
       {Py_mod_name, "spam"},
       {Py_mod_doc, "A wonderful module with an example function"},
       {0, NULL}
    };
 
+The ``PyABIInfo_VAR(abi_info);`` macro and the :c:data:`Py_mod_abi` slot
+are a bit of boilerplate that helps prevent extensions compiled for
+a different version of Python from crashing the interpreter.
+
 For both :c:data:`Py_mod_name` and :c:data:`Py_mod_doc`, the values are C
 strings -- that is, NUL-terminated, UTF-8 encoded byte arrays.
 
index ac96f17f04712c0c221ba4a01a207cb4f787ba46..0bc34ef57445cb9ffc0f51c7754bddd816184d13 100644 (file)
@@ -35,7 +35,10 @@ static PyMethodDef spam_methods[] = {
 
 /// Module slot table
 
+PyABIInfo_VAR(abi_info);
+
 static PyModuleDef_Slot spam_slots[] = {
+    {Py_mod_abi, &abi_info},
     {Py_mod_name, "spam"},
     {Py_mod_doc, "A wonderful module with an example function"},
     {Py_mod_methods, spam_methods},
index 053e6709cda42e2c992504abb2664ee73700cd6a..c32ca1098edc56b4691e1b94a06e0a338f579b55 100644 (file)
@@ -25,9 +25,13 @@ def def_and_token(mod):
     )
 
 class TestModFromSlotsAndSpec(unittest.TestCase):
-    @requires_gil_enabled("empty slots re-enable GIL")
     def test_empty(self):
-        mod = _testcapi.module_from_slots_empty(FakeSpec())
+        with self.assertRaises(SystemError):
+            _testcapi.module_from_slots_empty(FakeSpec())
+
+    @requires_gil_enabled("minimal slots re-enable GIL")
+    def test_minimal(self):
+        mod = _testcapi.module_from_slots_minimal(FakeSpec())
         self.assertIsInstance(mod, types.ModuleType)
         self.assertEqual(def_and_token(mod), (0, 0))
         self.assertEqual(mod.__name__, 'testmod')
@@ -159,6 +163,16 @@ class TestModFromSlotsAndSpec(unittest.TestCase):
                 self.assertIn(name, str(cm.exception))
                 self.assertIn("NULL", str(cm.exception))
 
+    def test_bad_abiinfo(self):
+        """Slots that incompatible ABI is rejected"""
+        with self.assertRaises(ImportError) as cm:
+            _testcapi.module_from_bad_abiinfo(FakeSpec())
+
+    def test_multiple_abiinfo(self):
+        """Slots that Py_mod_abiinfo can be repeated"""
+        mod = _testcapi.module_from_multiple_abiinfo(FakeSpec())
+        self.assertEqual(mod.__name__, 'testmod')
+
     def test_def_multiple_exec(self):
         """PyModule_Exec runs all exec slots of PyModuleDef-defined module"""
         mod = _testcapi.module_from_def_multiple_exec(FakeSpec())
index 7555b78f18c2e64a4d08a04837cf82f1f12bedc6..a880cb82811f783d81e833e1ee1dc17d1e763ebd 100644 (file)
@@ -119,8 +119,10 @@ _Py_COMP_DIAG_PUSH
 #endif
 
 PyDoc_STRVAR(_testcext_doc, "C test extension.");
+PyABIInfo_VAR(abi_info);
 
 static PyModuleDef_Slot _testcext_slots[] = {
+    {Py_mod_abi, &abi_info},
     {Py_mod_name, STR(MODULE_NAME)},
     {Py_mod_doc, (void*)(char*)_testcext_doc},
     {Py_mod_exec, (void*)_testcext_exec},
index 437ab7031356b18065e4b7459829af07a6db7894..c905c0da0a12327faf1a5f5ca12f884af9efbd5a 100644 (file)
@@ -3499,12 +3499,20 @@ class ModexportTests(unittest.TestCase):
             pass
         self.assertEqual(_testcapi.pytype_getmodulebytoken(Sub, token), module)
 
-    @requires_gil_enabled("empty slots re-enable GIL")
     def test_from_modexport_empty_slots(self):
+        # Module to test that Py_mod_abi is mandatory for PyModExport
+        modname = '_test_from_modexport_empty_slots'
+        filename = _testmultiphase.__file__
+        with self.assertRaises(SystemError):
+            import_extension_from_file(
+                modname, filename, put_in_sys_modules=False)
+
+    @requires_gil_enabled("this module re-enables GIL")
+    def test_from_modexport_minimal_slots(self):
         # Module to test that:
-        # - no slots are mandatory for PyModExport
+        # - no slots except Py_mod_abi is mandatory for PyModExport
         # - the slots array is used as the default token
-        modname = '_test_from_modexport_empty_slots'
+        modname = '_test_from_modexport_minimal_slots'
         filename = _testmultiphase.__file__
         module = import_extension_from_file(
             modname, filename, put_in_sys_modules=False)
@@ -3516,7 +3524,7 @@ class ModexportTests(unittest.TestCase):
         smoke_mod = import_extension_from_file(
             '_test_from_modexport_smoke', filename, put_in_sys_modules=False)
         self.assertEqual(_testcapi.pymodule_get_token(module),
-                         smoke_mod.get_modexport_empty_slots())
+                         smoke_mod.get_modexport_minimal_slots())
 
 @cpython_only
 class TestMagicNumber(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/C_API/2026-03-31-13-33-41.gh-issue-146636.5do3wt.rst b/Misc/NEWS.d/next/C_API/2026-03-31-13-33-41.gh-issue-146636.5do3wt.rst
new file mode 100644 (file)
index 0000000..8f8b832
--- /dev/null
@@ -0,0 +1,3 @@
+The :c:data:`Py_mod_abi` slot is now mandatory for modules created from a
+slots array (using :c:func:`PyModule_FromSlotsAndSpec` or the
+:c:func:`PyModExport_* <PyModExport_modulename>` export hook).
index 3411b21e942a19b12682bbf4b414f18f32a14af4..52e1d6d94a3af7164624de51380a8684c2505e49 100644 (file)
@@ -8,6 +8,8 @@
  * Lib/test/test_capi/test_module.py
  */
 
+PyABIInfo_VAR(abi_info);
+
 static PyObject *
 module_from_slots_empty(PyObject *self, PyObject *spec)
 {
@@ -17,6 +19,16 @@ module_from_slots_empty(PyObject *self, PyObject *spec)
     return PyModule_FromSlotsAndSpec(slots, spec);
 }
 
+static PyObject *
+module_from_slots_minimal(PyObject *self, PyObject *spec)
+{
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
+        {0},
+    };
+    return PyModule_FromSlotsAndSpec(slots, spec);
+}
+
 static PyObject *
 module_from_slots_null(PyObject *self, PyObject *spec)
 {
@@ -27,6 +39,7 @@ static PyObject *
 module_from_slots_name(PyObject *self, PyObject *spec)
 {
     PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_name, "currently ignored..."},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -39,6 +52,7 @@ static PyObject *
 module_from_slots_doc(PyObject *self, PyObject *spec)
 {
     PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_doc, "the docstring"},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -51,6 +65,7 @@ static PyObject *
 module_from_slots_size(PyObject *self, PyObject *spec)
 {
     PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_state_size, (void*)123},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -78,6 +93,7 @@ static PyObject *
 module_from_slots_methods(PyObject *self, PyObject *spec)
 {
     PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_methods, a_methoddef_array},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -96,6 +112,7 @@ static PyObject *
 module_from_slots_gc(PyObject *self, PyObject *spec)
 {
     PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_state_traverse, noop_traverse},
         {Py_mod_state_clear, noop_clear},
         {Py_mod_state_free, noop_free},
@@ -128,6 +145,7 @@ static PyObject *
 module_from_slots_token(PyObject *self, PyObject *spec)
 {
     PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_token, (void*)&test_token},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -156,6 +174,7 @@ static PyObject *
 module_from_slots_exec(PyObject *self, PyObject *spec)
 {
     PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_exec, simple_exec},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -189,6 +208,7 @@ static PyObject *
 module_from_slots_create(PyObject *self, PyObject *spec)
 {
     PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_create, create_attr_from_spec},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -220,6 +240,7 @@ module_from_slots_repeat_slot(PyObject *self, PyObject *spec)
         return NULL;
     }
     PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {slot_id, "anything"},
         {slot_id, "anything else"},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
@@ -238,6 +259,7 @@ module_from_slots_null_slot(PyObject *self, PyObject *spec)
     }
     PyModuleDef_Slot slots[] = {
         {slot_id, NULL},
+        {Py_mod_abi, &abi_info},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_NOT_USED},
         {0},
@@ -254,6 +276,7 @@ module_from_def_slot(PyObject *self, PyObject *spec)
     }
     PyModuleDef_Slot slots[] = {
         {slot_id, "anything"},
+        {Py_mod_abi, &abi_info},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_NOT_USED},
         {0},
@@ -285,6 +308,7 @@ static PyModuleDef parrot_def = {
     .m_slots = NULL /* set below */,
 };
 static PyModuleDef_Slot parrot_slots[] = {
+    {Py_mod_abi, &abi_info},
     {Py_mod_name, (void*)parrot_name},
     {Py_mod_doc, (void*)parrot_doc},
     {Py_mod_state_size, (void*)123},
@@ -314,6 +338,43 @@ module_from_def_slot_parrot(PyObject *self, PyObject *spec)
     return module;
 }
 
+static PyObject *
+module_from_bad_abiinfo(PyObject *self, PyObject *spec)
+{
+    PyABIInfo bad_abi_info = {
+        1, 0,
+        .abi_version=0x02080000,
+    };
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
+        {Py_mod_abi, &bad_abi_info},
+        {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
+        {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+        {0},
+    };
+    return PyModule_FromSlotsAndSpec(slots, spec);
+}
+
+static PyObject *
+module_from_multiple_abiinfo(PyObject *self, PyObject *spec)
+{
+    PyABIInfo extra_abi_info = {
+        1, 0,
+        .flags=PyABIInfo_STABLE | PyABIInfo_FREETHREADING_AGNOSTIC,
+        .abi_version=0x03040000,
+    };
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
+        {Py_mod_abi, &abi_info},
+        {Py_mod_abi, &extra_abi_info},
+        {Py_mod_abi, &extra_abi_info},
+        {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
+        {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+        {0},
+    };
+    return PyModule_FromSlotsAndSpec(slots, spec);
+}
+
 static int
 another_exec(PyObject *module)
 {
@@ -344,6 +405,7 @@ static PyObject *
 module_from_def_multiple_exec(PyObject *self, PyObject *spec)
 {
     static PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_exec, simple_exec},
         {Py_mod_exec, another_exec},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
@@ -399,6 +461,7 @@ pymodule_get_state_size(PyObject *self, PyObject *module)
 
 static PyMethodDef test_methods[] = {
     {"module_from_slots_empty", module_from_slots_empty, METH_O},
+    {"module_from_slots_minimal", module_from_slots_minimal, METH_O},
     {"module_from_slots_null", module_from_slots_null, METH_O},
     {"module_from_slots_name", module_from_slots_name, METH_O},
     {"module_from_slots_doc", module_from_slots_doc, METH_O},
@@ -413,6 +476,8 @@ static PyMethodDef test_methods[] = {
     {"module_from_def_multiple_exec", module_from_def_multiple_exec, METH_O},
     {"module_from_def_slot", module_from_def_slot, METH_O},
     {"module_from_def_slot_parrot", module_from_def_slot_parrot, METH_O},
+    {"module_from_bad_abiinfo", module_from_bad_abiinfo, METH_O},
+    {"module_from_multiple_abiinfo", module_from_multiple_abiinfo, METH_O},
     {"pymodule_get_token", pymodule_get_token, METH_O},
     {"pymodule_get_def", pymodule_get_def, METH_O},
     {"pymodule_get_state_size", pymodule_get_state_size, METH_O},
index 4921dc90713dafdf9ef0e6b8e1d7751a473d1c09..54f53c899f5e394f2543b8877cdcf6e9324b1720 100644 (file)
@@ -1034,10 +1034,13 @@ PyInit__test_no_multiple_interpreter_slot(void)
 
 /* PyModExport_* hooks */
 
+PyABIInfo_VAR(abi_info);
+
 PyMODEXPORT_FUNC
 PyModExport__test_from_modexport(void)
 {
     static PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_name, "_test_from_modexport"},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -1050,6 +1053,7 @@ PyMODEXPORT_FUNC
 PyModExport__test_from_modexport_gil_used(void)
 {
     static PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_name, "_test_from_modexport_gil_used"},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
         {Py_mod_gil, Py_MOD_GIL_USED},
@@ -1100,6 +1104,7 @@ PyMODEXPORT_FUNC
 PyModExport__test_from_modexport_create_nonmodule(void)
 {
     static PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_name, "_test_from_modexport_create_nonmodule"},
         {Py_mod_create, modexport_create_string},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
@@ -1113,6 +1118,7 @@ PyMODEXPORT_FUNC
 PyModExport__test_from_modexport_create_nonmodule_gil_used(void)
 {
     static PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_name, "_test_from_modexport_create_nonmodule"},
         {Py_mod_create, modexport_create_string},
         {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
@@ -1132,6 +1138,18 @@ PyModExport__test_from_modexport_empty_slots(void)
     return modexport_empty_slots;
 }
 
+
+static PyModuleDef_Slot modexport_minimal_slots[] = {
+    {Py_mod_abi, &abi_info},
+    {0},
+};
+
+PyMODEXPORT_FUNC
+PyModExport__test_from_modexport_minimal_slots(void)
+{
+    return modexport_minimal_slots;
+}
+
 static int
 modexport_smoke_exec(PyObject *mod)
 {
@@ -1172,13 +1190,13 @@ modexport_smoke_get_test_token(PyObject *mod, PyObject *arg)
 }
 
 static PyObject *
-modexport_get_empty_slots(PyObject *mod, PyObject *arg)
+modexport_get_minimal_slots(PyObject *mod, PyObject *arg)
 {
     /* Get the address of modexport_empty_slots.
-     * This method would be in the `_test_from_modexport_empty_slots` module,
+     * This method would be in the `_test_from_modexport_minimal_slots` module,
      * if it had a methods slot.
      */
-    return PyLong_FromVoidPtr(&modexport_empty_slots);
+    return PyLong_FromVoidPtr(&modexport_minimal_slots);
 }
 
 static void
@@ -1198,10 +1216,11 @@ PyModExport__test_from_modexport_smoke(void)
     static PyMethodDef methods[] = {
         {"get_state_int", modexport_smoke_get_state_int, METH_NOARGS},
         {"get_test_token", modexport_smoke_get_test_token, METH_NOARGS},
-        {"get_modexport_empty_slots", modexport_get_empty_slots, METH_NOARGS},
+        {"get_modexport_minimal_slots", modexport_get_minimal_slots, METH_NOARGS},
         {0},
     };
     static PyModuleDef_Slot slots[] = {
+        {Py_mod_abi, &abi_info},
         {Py_mod_name, "_test_from_modexport_smoke"},
         {Py_mod_doc, "the expected docstring"},
         {Py_mod_exec, modexport_smoke_exec},
index e3868097c0ba9f9d9c2ee52b9fe69ba9d84e6131..8339e6b91a5e1641720ce04bc63ec1ceeef4dfe5 100644 (file)
@@ -446,6 +446,7 @@ module_from_def_and_spec(
     bool seen_m_traverse_slot = false;
     bool seen_m_clear_slot = false;
     bool seen_m_free_slot = false;
+    bool seen_m_abi_slot = false;
     for (cur_slot = def_like->m_slots; cur_slot && cur_slot->slot; cur_slot++) {
 
         // Macro to copy a non-NULL, non-repeatable slot.
@@ -555,6 +556,7 @@ module_from_def_and_spec(
                 if (PyABIInfo_Check((PyABIInfo *)cur_slot->value, name) < 0) {
                     goto error;
                 }
+                seen_m_abi_slot = true;
                 break;
             DEF_SLOT_CASE(Py_mod_name, char*, m_name)
             DEF_SLOT_CASE(Py_mod_doc, char*, m_doc)
@@ -587,6 +589,14 @@ module_from_def_and_spec(
 #undef COPY_NONDEF_SLOT
 #undef COPY_NONNULL_SLOT
     }
+    if (!original_def && !seen_m_abi_slot) {
+        PyErr_Format(
+            PyExc_SystemError,
+            "module %s does not define Py_mod_abi,"
+            " which is mandatory for modules defined from slots only.",
+            name);
+        goto error;
+    }
 
 #ifdef Py_GIL_DISABLED
     // For modules created directly from slots (not from a def), we enable
index 4624f326d17b89f69f6973ed2e87ea595cb10d52..bab21d1b2be5b518a1c653bc15d714e3d534bd3e 100644 (file)
@@ -735,15 +735,15 @@ int PyABIInfo_Check(PyABIInfo *info, const char *module_name)
                 return _abiinfo_raise(
                     module_name,
                     "incompatible future stable ABI version (%d.%d)",
-                    ((info->abi_version) >> 24) % 0xff,
-                    ((info->abi_version) >> 16) % 0xff);
+                    ((info->abi_version) >> 24) & 0xff,
+                    ((info->abi_version) >> 16) & 0xff);
             }
             if (info->abi_version < Py_PACK_VERSION(3, 2)) {
                 return _abiinfo_raise(
                     module_name,
                     "invalid stable ABI version (%d.%d)",
-                    ((info->abi_version) >> 24) % 0xff,
-                    ((info->abi_version) >> 16) % 0xff);
+                    ((info->abi_version) >> 24) & 0xff,
+                    ((info->abi_version) >> 16) & 0xff);
             }
         }
         if (info->flags & PyABIInfo_INTERNAL) {
@@ -758,8 +758,8 @@ int PyABIInfo_Check(PyABIInfo *info, const char *module_name)
                 return _abiinfo_raise(
                     module_name,
                     "incompatible ABI version (%d.%d)",
-                    ((info->abi_version) >> 24) % 0xff,
-                    ((info->abi_version) >> 16) % 0xff);
+                    ((info->abi_version) >> 24) & 0xff,
+                    ((info->abi_version) >> 16) & 0xff);
             }
         }
     }