Почему ctypes также загружает функции стандартной библиотеки C и как я могу этого избежать?

Вопрос или проблема

  1. Загрузка самодельной общей библиотеки с помощью ctypes кажется предоставляет доступ к функциям стандартной библиотеки C.

    Почему?

    Я ожидал бы получить какую-то ошибку AttributeError при попытке запустить приведенный ниже минимальный воспроизводимый пример.

  2. Как я могу ограничить это поведение (если это возможно)?

  3. [разрешающий вопрос]: Как разрешается имя функции при попытке доступа к ней, если моя самодельная версия переопределяет стандартную функцию библиотеки C?


Из того, что я понял из документации, это должно возвращать представление загруженной общей библиотеки. Но похоже, что оно ведет себя как-то иначе.


Минимальный воспроизводимый пример:

  • test.py
#!/usr/bin/env python3

import ctypes
import os

my_lib = ctypes.CDLL(os.path.abspath("libfoo.so"))

# Я ожидаю, что это сработает (это так).
print(f"{my_lib.foo = }")
# Я ожидаю, что это не сработает (но это так).
print(f"{my_lib.strncmp = }")
# Я ожидаю, что это не сработает (это так).
print(f"{my_lib.bar = }")
  • foo.s:
    global foo

foo:
    ret
  • сессия оболочки:
$ make
nasm -f elf64 -o foo.o foo.s
gcc -shared -o libfoo.so foo.o
$ nm -a libfoo.so
0000000000000000 a 
                 w __cxa_finalize@GLIBC_2.2.5
0000000000004000 d __dso_handle
0000000000003e48 d _DYNAMIC
00000000000010f4 t _fini
00000000000010f0 T foo
0000000000000000 a foo.s
0000000000003fe8 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000001000 t _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000004008 d __TMC_END__
$ ./test.py
my_lib.foo = <_FuncPtr object at 0x75a09cb3e140>
my_lib.strncmp = <_FuncPtr object at 0x75a09cb3e200>
Traceback (most recent call last):
  File "/home/vmonteco/code/MREs/MRE_ctypes_give_access_to_stdlib/./test.py", line 13, in <module>
    print(f"{my_lib.bar = }")
  File "/home/vmonteco/.pyenv/versions/3.10.14/lib/python3.10/ctypes/__init__.py", line 387, in __getattr__
    func = self.__getitem__(name)
  File "/home/vmonteco/.pyenv/versions/3.10.14/lib/python3.10/ctypes/__init__.py", line 392, in __getitem__
    func = self._FuncPtr((name_or_ordinal, self))
AttributeError: /home/vmonteco/code/MREs/MRE_ctypes_give_access_to_stdlib/libfoo.so: undefined symbol: bar
$ 

Ответ или решение

Ответ на вопрос о ctypes и загрузке стандартных функций библиотеки C

1. Почему при загрузке пользовательской библиотеки с помощью ctypes доступны функции стандартной библиотеки C?

Когда вы загружаете свою пользовательскую библиотеку с помощью ctypes.CDLL, процесс загрузки загружает не только вашу библиотеку, но и стандартные зависимости, в том числе стандартную библиотеку C (например, libc). Это происходит потому, что ваша библиотека может зависеть от функций, определённых в стандартной библиотеке (например, такие функции, как strncmp, используются часто и могут быть необходимы в вашем коде, даже если вы их не вызывали напрямую).

При этом Python, используя ctypes, работает с динамическими библиотеками, что означает, что он будет разрешать имена функций, вызываемых из вашей библиотеки, и будет искать их в загруженных библиотеках, начиная с вашей библиотеки и заканчивая стандартными библиотеками.

Во время выполнения, если система не может найти функцию в вашей библиотеке, она будет продолжать искать в других загруженных библиотеках, включая стандартную библиотеку C. Поэтому вы можете получить доступ к стандартным функциям, таким как strncmp, даже если ваша библиотека не объявляет их явно.

2. Как можно предотвратить это поведение?

К сожалению, предотвратить доступ к стандартным функциям C не представляется возможным, так как это поведение обусловлено динамической моделью разрешения символов в операционных системах. Однако, если вы хотите избежать конфликтов имен, вы можете использовать следующие подходы:

  • Изменение имен функций вашей библиотеки: Убедитесь, что ваши функции имеют уникальные имена, чтобы избежать путаницы с функциями стандартной библиотеки.
  • Использование статической компоновки: Если вы создадите статическую библиотеку (.a), в которой будут собраны все необходимые функции, это поможет избежать неявного использования стандартной библиотеки, хотя может быть сложно разделить функциональность.
  • Изоляция окружения: Вы можете использовать контейнеризацию или виртуализацию, чтобы минимизировать влияние стандартных библиотек. Например, вы можете использовать контейнер Docker для изоляции вашего приложения.

3. Как происходит разрешение имен функций, если моя библиотека переопределяет стандартную функцию C?

Когда происходит вызов функции в вашей библиотеке, система сначала ищет эту функцию в вашей библиотеке. Если функция не найдена, она продолжает искать в зависимостях и стандартных библиотеках, загруженных в процессе. Это связано с тем, как работает динамическая линковка.

Если вы переопределяете стандартную функцию, то при наличии вашей библиотеки в памяти система будет использовать реализацию этой функции из вашей библиотеки. Однако, если ваша библиотека не загружена или не содержит нужного определения, будет использована стандартная версия.

Примерно это выглядит так:

  1. Вы вызываете my_lib.strncmp().
  2. Если strncmp определен в вашей библиотеке, система использует вашу версию.
  3. Если не определён, система использует стандартную версию из нагрузочной библиотеки (например, libc).

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

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...