remote_debugging.md
1> **Источник:** https://python-all.ru/3.14/howto/remote_debugging.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Протокол подключения удаленной отладки89Этот протокол позволяет внешним инструментам подключаться к работающему процессу CPython и удаленно выполнять код Python.1011На большинстве платформ для подключения к другому процессу Python требуются повышенные привилегии.1213## Отключение удаленной отладки1415Чтобы отключить поддержку удаленной отладки, используйте любой из следующих способов:1617- Установите переменную окружения [`PYTHON_DISABLE_REMOTE_DEBUG`](https://python-all.ru/3.14/using/cmdline.html#envvar-PYTHON_DISABLE_REMOTE_DEBUG) в `1` перед запуском интерпретатора.18- Используйте параметр командной строки [`-X disable_remote_debug`](https://python-all.ru/3.14/using/cmdline.html#cmdoption-X).19- Скомпилируйте Python с флагом сборки [`--without-remote-debug`](https://python-all.ru/3.14/using/configure.html#cmdoption-without-remote-debug).2021# Требования к разрешениям2223Подключение к работающему процессу Python для удаленной отладки требует повышенных привилегий на большинстве платформ. Конкретные требования и шаги по устранению неполадок зависят от вашей операционной системы:2425Linux2627Процесс-трассировщик должен иметь возможность `CAP_SYS_PTRACE` или эквивалентные привилегии. Вы можете трассировать только те процессы, которыми владеете и которым можете отправлять сигналы. Трассировка может не удаться, если процесс уже трассируется, или если он выполняется с set-user-ID или set-group-ID. Модули безопасности, такие как Yama, могут дополнительно ограничивать трассировку.2829Чтобы временно ослабить ограничения ptrace (до перезагрузки), выполните:3031> `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope`3233> **Примечание**34>35> Отключение `ptrace_scope` снижает защиту системы и должно выполняться только в доверенных средах.3637Если запуск производится внутри контейнера, используйте `--cap-add=SYS_PTRACE` или `--privileged`, и при необходимости запустите от root.3839Попробуйте повторно выполнить команду с повышенными привилегиями:4041> `sudo -E !!`4243macOS4445Чтобы подключиться к другому процессу, обычно требуется запустить инструмент отладки с повышенными привилегиями. Это можно сделать, используя `sudo` или запустив от root.4647Даже при подключении к собственным процессам macOS может блокировать отладку, если отладчик не запущен с правами root из-за ограничений безопасности системы.4849Windows5051Чтобы подключиться к другому процессу, обычно требуется запустить инструмент отладки с правами администратора. Запустите командную строку или терминал от имени Администратора.5253Некоторые процессы могут оставаться недоступными даже с правами Администратора, если у вас не включена привилегия `SeDebugPrivilege`.5455Чтобы решить проблемы с доступом к файлу или папке, измените разрешения безопасности:5657> 1. Щёлкните правой кнопкой мыши по файлу или папке и выберите **Свойства**.58> 2. Перейдите на вкладку **Безопасность**, чтобы просмотреть пользователей и группы, имеющие доступ.59> 3. Нажмите **Изменить**, чтобы изменить разрешения.60> 4. Выберите свою учётную запись.61> 5. В разделе **Разрешения** отметьте **Чтение** или **Полный доступ** по мере необходимости.62> 6. Нажмите **Применить**, затем **ОК** для подтверждения.6364> **Примечание**65>66> Убедитесь, что выполнены все [требования к разрешениям](https://python-all.ru/3.14/howto/remote_debugging.html#permission-requirements), прежде чем продолжить.6768В этом разделе описывается низкоуровневый протокол, который позволяет внешним инструментам внедрять и выполнять скрипт Python внутри работающего процесса CPython.6970Этот механизм лежит в основе функции [`sys.remote_exec()`](https://python-all.ru/3.14/library/sys.html#sys.remote_exec), которая инструктирует удалённый процесс Python выполнить файл `.py`. Однако в данном разделе не документируется использование этой функции. Вместо этого даётся подробное описание базового протокола, который принимает на вход `pid` целевого процесса Python и путь к исходному файлу Python для выполнения. Эта информация позволяет независимо перереализовать протокол, независимо от языка программирования.7172> **Предупреждение**73>74> Выполнение внедрённого скрипта зависит от того, достигнет ли интерпретатор безопасной точки вычислений. В результате выполнение может быть отложено в зависимости от состояния выполнения целевого процесса.7576После внедрения скрипт выполняется интерпретатором в целевом процессе при следующем достижении безопасной точки вычислений. Этот подход обеспечивает возможность удалённого выполнения без изменения поведения или структуры работающего приложения Python.7778Последующие разделы содержат пошаговое описание протокола, включая методы поиска структур интерпретатора в памяти, безопасного доступа к внутренним полям и запуска выполнения кода. Учитываются платформозависимые вариации, где это применимо, и включены примеры реализаций для пояснения каждой операции.7980# Поиск структуры PyRuntime8182CPython помещает структуру `PyRuntime` в специальный раздел двоичного файла, чтобы помочь внешним инструментам найти её во время выполнения. Имя и формат этого раздела зависят от платформы. Например, на системах ELF используется `.PyRuntime`, а на macOS – `__DATA,__PyRuntime`. Инструменты могут найти смещение этой структуры, изучив двоичный файл на диске.8384Структура `PyRuntime` содержит глобальное состояние интерпретатора CPython и предоставляет доступ к другим внутренним данным, включая список интерпретаторов, состояния потоков и поля поддержки отладчика.8586Для работы с удалённым процессом Python отладчик должен сначала найти адрес памяти структуры `PyRuntime` в целевом процессе. Этот адрес нельзя жёстко задать или вычислить по имени символа, потому что он зависит от того, куда операционная система загрузила двоичный файл.8788Метод поиска `PyRuntime` зависит от платформы, но шаги в целом одинаковы:89901. Найдите базовый адрес, по которому двоичный файл Python или общая библиотека были загружены в целевой процесс.912. Используйте двоичный файл на диске, чтобы найти смещение раздела `.PyRuntime`.923. Прибавьте смещение раздела к базовому адресу, чтобы вычислить адрес в памяти.9394Разделы ниже объясняют, как это сделать на каждой поддерживаемой платформе, и содержат примеры кода.9596Linux (ELF)9798Чтобы найти структуру `PyRuntime` в Linux:991001. Прочитайте карту памяти процесса (например, `/proc/<pid>/maps`), чтобы найти адрес, по которому был загружен исполняемый файл Python или `libpython`.1012. Проанализируйте заголовки разделов ELF в двоичном файле, чтобы получить смещение раздела `.PyRuntime`.1023. Прибавьте это смещение к базовому адресу из шага 1, чтобы получить адрес памяти `PyRuntime`.103104Ниже приведён пример реализации:105106```python107def find_py_runtime_linux(pid: int) -> int:108 # Шаг 1: Попытаться найти исполняемый файл Python в памяти109 binary_path, base_address = find_mapped_binary(110 pid, name_contains="python"111 )112113 # Шаг 2: Запасной вариант – разделяемая библиотека, если исполняемый файл не найден114 if binary_path is None:115 binary_path, base_address = find_mapped_binary(116 pid, name_contains="libpython"117 )118119 # Шаг 3: Разобрать заголовки ELF, чтобы получить смещение секции .PyRuntime120 section_offset = parse_elf_section_offset(121 binary_path, ".PyRuntime"122 )123124 # Шаг 4: Вычислить адрес PyRuntime в памяти125 return base_address + section_offset126```127128В системах Linux есть два основных подхода к чтению памяти из другого процесса. Первый – через файловую систему `/proc`, а именно чтение из `/proc/[pid]/mem`, что обеспечивает прямой доступ к памяти процесса. Это требует соответствующих разрешений – либо быть тем же пользователем, что и целевой процесс, либо иметь права root. Второй подход – использование системного вызова `process_vm_readv()`, который предоставляет более эффективный способ копирования памяти между процессами. Хотя операция `PTRACE_PEEKTEXT` ptrace также может использоваться для чтения памяти, она значительно медленнее, так как читает только одно слово за раз и требует множества переключений контекста между трассирующим и трассируемым процессами.129130Для анализа разделов ELF процесс включает чтение и интерпретацию структур формата файла ELF из двоичного файла на диске. Заголовок ELF содержит указатель на таблицу заголовков разделов. Каждый заголовок раздела содержит метаданные о разделе, включая его имя (хранящееся в отдельной таблице строк), смещение и размер. Чтобы найти конкретный раздел, например .PyRuntime, необходимо пройтись по этим заголовкам и сопоставить имя раздела. Затем заголовок раздела предоставляет смещение, по которому этот раздел находится в файле, которое можно использовать для вычисления его адреса во время выполнения при загрузке двоичного файла в память.131132Подробнее о формате файла ELF можно узнать в [спецификации ELF](https://python-all.ru/3.14/howto/remote_debugging.html).133134macOS (Mach-O)135136Чтобы найти структуру `PyRuntime` в macOS:1371381. Вызовите `task_for_pid()`, чтобы получить порт задачи `mach_port_t` для целевого процесса. Этот дескриптор необходим для чтения памяти с помощью таких API, как `mach_vm_read_overwrite` и `mach_vm_region`.1392. Просканируйте области памяти, чтобы найти ту, которая содержит исполняемый файл Python или `libpython`.1403. Загрузите бинарный файл с диска и проанализируйте заголовки Mach-O, чтобы найти секцию с именем `PyRuntime` в сегменте `__DATA`. В macOS имена символов автоматически получают префикс с подчёркиванием, поэтому символ `PyRuntime` отображается как `_PyRuntime` в таблице символов, но на имя секции это не влияет.141142Ниже приведён пример реализации:143144```python145def find_py_runtime_macos(pid: int) -> int:146 # Шаг 1: Получить доступ к памяти процесса147 handle = get_memory_access_handle(pid)148149 # Шаг 2: Попытаться найти исполняемый файл Python в памяти150 binary_path, base_address = find_mapped_binary(151 handle, name_contains="python"152 )153154 # Шаг 3: Запасной вариант – libpython, если исполняемый файл не найден155 if binary_path is None:156 binary_path, base_address = find_mapped_binary(157 handle, name_contains="libpython"158 )159160 # Шаг 4: Разобрать заголовки Mach-O, чтобы получить смещение секции __DATA,__PyRuntime161 section_offset = parse_macho_section_offset(162 binary_path, "__DATA", "__PyRuntime"163 )164165 # Шаг 5: Вычислить адрес PyRuntime в памяти166 return base_address + section_offset167```168169В macOS для доступа к памяти другого процесса требуется использовать специфичные для Mach-O API и форматы файлов. Первый шаг – получение дескриптора `task_port` через `task_for_pid()`, который предоставляет доступ к пространству памяти целевого процесса. Этот дескриптор позволяет выполнять операции с памятью через такие API, как `mach_vm_read_overwrite()`.170171Память процесса можно исследовать с помощью `mach_vm_region()` для сканирования виртуального адресного пространства, а `proc_regionfilename()` помогает определить, какие бинарные файлы загружены в каждой области памяти. Когда бинарный файл Python или библиотека найдены, необходимо проанализировать их заголовки Mach-O, чтобы найти структуру `PyRuntime`.172173Формат Mach-O организует код и данные в сегменты и секции. Структура `PyRuntime` находится в секции с именем `__PyRuntime` внутри сегмента `__DATA`. Вычисление фактического адреса во время выполнения включает поиск сегмента `__TEXT`, который служит базовым адресом бинарного файла, а затем определение сегмента `__DATA`, содержащего целевую секцию. Итоговый адрес вычисляется путём комбинации базового адреса с соответствующими смещениями секций из заголовков Mach-O.174175Обратите внимание, что доступ к памяти другого процесса в macOS обычно требует повышенных привилегий – либо прав root, либо специальных разрешений безопасности, предоставленных отладочному процессу.176177Windows (PE)178179Чтобы найти структуру `PyRuntime` в Windows:1801811. Используйте API ToolHelp для перечисления всех модулей, загруженных в целевой процесс. Это делается с помощью таких функций, как [CreateToolhelp32Snapshot](https://python-all.ru/3.14/howto/remote_debugging.html), [Module32First](https://python-all.ru/3.14/howto/remote_debugging.html) и [Module32Next](https://python-all.ru/3.14/howto/remote_debugging.html).1822. Определите модуль, соответствующий `python.exe` или `pythonXY.dll`, где `X` и `Y` – старший и младший номера версии Python, и запишите его базовый адрес.1833. Найдите секцию `PyRuntim`. Из-за ограничения формата PE в 8 символов на имена секций (определено как `IMAGE_SIZEOF_SHORT_NAME`), исходное имя `PyRuntime` усекается. Эта секция содержит структуру `PyRuntime`.1844. Получите относительный виртуальный адрес (RVA) секции и добавьте его к базовому адресу модуля.185186Ниже приведён пример реализации:187188```python189def find_py_runtime_windows(pid: int) -> int:190 # Шаг 1: Попытаться найти исполняемый файл Python в памяти191 binary_path, base_address = find_loaded_module(192 pid, name_contains="python"193 )194195 # Шаг 2: Запасной вариант – общая pythonXY.dll, если исполняемый файл не196 # найден197 if binary_path is None:198 binary_path, base_address = find_loaded_module(199 pid, name_contains="python3"200 )201202 # Шаг 3: Разобрать заголовки секций PE, чтобы получить RVA с секции PyRuntime203 # секции. Имя секции отображается как "PyRuntim" из-за204 # ограничения в 8 символов, установленного форматом PE (IMAGE_SIZEOF_SHORT_NAME).205 section_rva = parse_pe_section_offset(binary_path, "PyRuntim")206207 # Шаг 4: Вычислить адрес PyRuntime в памяти208 return base_address + section_rva209```210211В Windows для доступа к памяти другого процесса требуется использовать функции Windows API, такие как `CreateToolhelp32Snapshot()` и `Module32First()/Module32Next()`, для перечисления загруженных модулей. Функция `OpenProcess()` предоставляет дескриптор для доступа к пространству памяти целевого процесса, позволяя выполнять операции с памятью через `ReadProcessMemory()`.212213Память процесса можно исследовать, перечисляя загруженные модули, чтобы найти бинарный файл Python или DLL. Когда найдено, необходимо проанализировать его заголовки PE, чтобы найти структуру `PyRuntime`.214215Формат PE организует код и данные в секции. Структура `PyRuntime` находится в секции с именем «PyRuntim» (усечено из «PyRuntime» из-за ограничения PE в 8 символов на имя). Вычисление фактического адреса во время выполнения включает поиск базового адреса модуля из записи модуля, а затем определение целевой секции в заголовках PE. Итоговый адрес вычисляется путём комбинации базового адреса с виртуальным адресом секции из заголовков секций PE.216217Обратите внимание, что доступ к памяти другого процесса в Windows обычно требует соответствующих привилегий – либо административного доступа, либо привилегии `SeDebugPrivilege`, предоставленной отладочному процессу.218219# Чтение \_Py\_DebugOffsets220221После того как адрес структуры `PyRuntime` определён, следующим шагом является чтение структуры `_Py_DebugOffsets`, расположенной в начале блока `PyRuntime`.222223Эта структура предоставляет версионно-зависимые смещения полей, которые необходимы для безопасного чтения памяти интерпретатора и состояния потоков. Эти смещения различаются между версиями CPython и должны быть проверены перед использованием, чтобы убедиться в их совместимости.224225Для чтения и проверки отладочных смещений выполните следующие шаги:2262271. Прочитайте память целевого процесса, начиная с адреса `PyRuntime`, покрывая то же количество байт, что и структура `_Py_DebugOffsets`. Эта структура находится в самом начале блока памяти `PyRuntime`. Её расположение определено во внутренних заголовках CPython и остаётся неизменным в пределах одной минорной версии, но может меняться в мажорных версиях.2282. Проверьте, что структура содержит корректные данные:229230 - Поле `cookie` должно соответствовать ожидаемой отладочной метке.231 - Поле `version` должно соответствовать версии интерпретатора Python, используемого отладчиком.232 - Если отладчик или целевой процесс использует предрелизную версию (например, альфа, бета или кандидат на выпуск), версии должны совпадать в точности.233 - Поле `free_threaded` должно иметь одинаковое значение как в отладчике, так и в целевом процессе.2343. Если структура корректна, содержащиеся в ней смещения можно использовать для поиска полей в памяти. Если какая-либо проверка не пройдена, отладчик должен остановить операцию, чтобы избежать чтения памяти в неверном формате.235236Ниже приведён пример реализации для чтения и проверки `_Py_DebugOffsets`:237238```python239def read_debug_offsets(pid: int, py_runtime_addr: int) -> DebugOffsets:240 # Шаг 1: Прочитать память из целевого процесса по адресу PyRuntime241 data = read_process_memory(242 pid, address=py_runtime_addr, size=DEBUG_OFFSETS_SIZE243 )244245 # Шаг 2: Десериализовать сырые байты в структуру _Py_DebugOffsets246 debug_offsets = parse_debug_offsets(data)247248 # Шаг 3: Проверить содержимое структуры249 if debug_offsets.cookie != EXPECTED_COOKIE:250 raise RuntimeError("Invalid or missing debug cookie")251 if debug_offsets.version != LOCAL_PYTHON_VERSION:252 raise RuntimeError(253 "Mismatch between caller and target Python versions"254 )255 if debug_offsets.free_threaded != LOCAL_FREE_THREADED:256 raise RuntimeError("Mismatch in free-threaded configuration")257258 return debug_offsets259```260261> **Предупреждение**262>263> **Рекомендуется приостановка процесса**264>265> Чтобы избежать состояний гонки и обеспечить согласованность памяти, настоятельно рекомендуется приостановить целевой процесс перед выполнением любых операций, которые читают или записывают внутреннее состояние интерпретатора. Среда выполнения Python может одновременно изменять структуры данных интерпретатора – например, создавать или уничтожать потоки – в ходе нормального выполнения. Это может привести к недопустимым чтениям или записям в память.266>267> Отладчик может приостановить выполнение, присоединившись к процессу с помощью `ptrace` или отправив сигнал `SIGSTOP`. Выполнение следует возобновлять только после завершения операций с памятью на стороне отладчика.268>269> > **Примечание**270> >271> > Некоторые инструменты, такие как профилировщики или семплирующие отладчики, могут работать с запущенным процессом без приостановки. В таких случаях инструменты должны быть явно спроектированы для обработки частично обновлённой или несогласованной памяти. Для большинства реализаций отладчиков приостановка процесса остаётся самым безопасным и надёжным подходом.272273# Поиск интерпретатора и состояния потока274275Прежде чем код можно будет внедрить и выполнить в удалённом процессе Python, отладчик должен выбрать поток, в котором запланировать выполнение. Это необходимо, потому что управляющие поля, используемые для удалённого внедрения кода, находятся в структуре `_PyRemoteDebuggerSupport`, которая встроена в объект `PyThreadState`. Эти поля изменяются отладчиком для запроса выполнения внедрённых сценариев.276277Структура `PyThreadState` представляет поток, выполняющийся внутри интерпретатора Python. Она поддерживает контекст выполнения потока и содержит поля, необходимые для координации отладчика. Поиск допустимого `PyThreadState` является, таким образом, ключевым предварительным условием для удалённого запуска выполнения.278279Поток обычно выбирается на основе его роли или идентификатора. В большинстве случаев используется главный поток, но некоторые инструменты могут нацеливаться на конкретный поток по его нативному идентификатору. После выбора целевого потока отладчик должен найти в памяти как интерпретатор, так и соответствующие структуры состояния потока.280281Соответствующие внутренние структуры определены следующим образом:282283- `PyInterpreterState` представляет изолированный экземпляр интерпретатора Python. Каждый интерпретатор поддерживает свой собственный набор импортированных модулей, встроенное состояние и список состояний потоков. Хотя большинство приложений Python используют один интерпретатор, CPython поддерживает несколько интерпретаторов в одном процессе.284- `PyThreadState` представляет поток, выполняющийся в рамках интерпретатора. Он содержит состояние выполнения и управляющие поля, используемые отладчиком.285286Для поиска потока:2872881. Используйте смещение `runtime_state.interpreters_head` для получения адреса первого интерпретатора в структуре `PyRuntime`. Это точка входа в связанный список активных интерпретаторов.2892. Используйте смещение `interpreter_state.threads_main` для доступа к состоянию главного потока, связанному с выбранным интерпретатором. Обычно это самый надёжный поток для целевого воздействия.2903. При необходимости используйте смещение `interpreter_state.threads_head` для итерации по связанному списку всех состояний потоков. Каждая структура `PyThreadState` содержит поле `native_thread_id`, которое можно сравнить с идентификатором целевого потока, чтобы найти конкретный поток.2914. После того как найден корректный `PyThreadState`, его адрес может быть использован на последующих этапах протокола, таких как запись управляющих полей отладчика и планирование выполнения.292293Ниже приведён пример реализации, который находит состояние главного потока:294295```python296def find_main_thread_state(297 pid: int, py_runtime_addr: int, debug_offsets: DebugOffsets,298) -> int:299 # Шаг 1: Прочитать interpreters_head из PyRuntime300 interp_head_ptr = (301 py_runtime_addr + debug_offsets.runtime_state.interpreters_head302 )303 interp_addr = read_pointer(pid, interp_head_ptr)304 if interp_addr == 0:305 raise RuntimeError("No interpreter found in the target process")306307 # Шаг 2: Прочитать указатель threads_main из интерпретатора308 threads_main_ptr = (309 interp_addr + debug_offsets.interpreter_state.threads_main310 )311 thread_state_addr = read_pointer(pid, threads_main_ptr)312 if thread_state_addr == 0:313 raise RuntimeError("Main thread state is not available")314315 return thread_state_addr316```317318Следующий пример демонстрирует, как найти поток по его нативному идентификатору потока:319320```python321def find_thread_by_id(322 pid: int,323 interp_addr: int,324 debug_offsets: DebugOffsets,325 target_tid: int,326) -> int:327 # Начать с threads_head и пройти по связному списку328 thread_ptr = read_pointer(329 pid,330 interp_addr + debug_offsets.interpreter_state.threads_head331 )332333 while thread_ptr:334 native_tid_ptr = (335 thread_ptr + debug_offsets.thread_state.native_thread_id336 )337 native_tid = read_int(pid, native_tid_ptr)338 if native_tid == target_tid:339 return thread_ptr340 thread_ptr = read_pointer(341 pid,342 thread_ptr + debug_offsets.thread_state.next343 )344345 raise RuntimeError("Thread with the given ID was not found")346```347348После того как найдено корректное состояние потока, отладчик может приступить к изменению его управляющих полей и планированию выполнения, как описано в следующем разделе.349350# Запись управляющей информации351352После идентификации корректной структуры `PyThreadState` отладчик может изменить управляющие поля внутри неё, чтобы запланировать выполнение указанного скрипта Python. Эти управляющие поля периодически проверяются интерпретатором, и при правильной установке они запускают выполнение удалённого кода в безопасной точке цикла вычислений.353354Каждый `PyThreadState` содержит структуру `_PyRemoteDebuggerSupport`, используемую для связи между отладчиком и интерпретатором. Расположение её полей определяется структурой `_Py_DebugOffsets` и включает следующее:355356- `debugger_script_path`: Буфер фиксированного размера, содержащий полный путь к исходному файлу Python (`.py`). Этот файл должен быть доступен и читаем целевым процессом при запуске выполнения.357- `debugger_pending_call`: Целочисленный флаг. Установка этого значения в `1` сообщает интерпретатору, что скрипт готов к выполнению.358- `eval_breaker`: Поле, проверяемое интерпретатором во время выполнения. Установка бита 5 (`_PY_EVAL_PLEASE_STOP_BIT`, значение `1U << 5`) в этом поле заставляет интерпретатор приостановиться и проверить активность отладчика.359360Для завершения внедрения отладчик должен выполнить следующие шаги:3613621. Запишите полный путь скрипта в буфер `debugger_script_path`.3632. Устанавливает `debugger_pending_call` в `1`.3643. Прочитайте текущее значение `eval_breaker`, установите бит 5 (`_PY_EVAL_PLEASE_STOP_BIT`) и запишите обновлённое значение обратно. Это сигнализирует интерпретатору о необходимости проверить активность отладчика.365366Ниже приведён пример реализации:367368```python369def inject_script(370 pid: int,371 thread_state_addr: int,372 debug_offsets: DebugOffsets,373 script_path: str374) -> None:375 # Вычислить базовое смещение _PyRemoteDebuggerSupport376 support_base = (377 thread_state_addr +378 debug_offsets.debugger_support.remote_debugger_support379 )380381 # Шаг 1: Записать путь к скрипту в debugger_script_path382 script_path_ptr = (383 support_base +384 debug_offsets.debugger_support.debugger_script_path385 )386 write_string(pid, script_path_ptr, script_path)387388 # Шаг 2: Установить debugger_pending_call в 1389 pending_ptr = (390 support_base +391 debug_offsets.debugger_support.debugger_pending_call392 )393 write_int(pid, pending_ptr, 1)394395 # Шаг 3: Установить _PY_EVAL_PLEASE_STOP_BIT (бит 5, значение 1 << 5) в396 # eval_breaker397 eval_breaker_ptr = (398 thread_state_addr +399 debug_offsets.debugger_support.eval_breaker400 )401 breaker = read_int(pid, eval_breaker_ptr)402 breaker |= (1 << 5)403 write_int(pid, eval_breaker_ptr, breaker)404```405406После установки этих полей отладчик может возобновить процесс (если он был приостановлен). Интерпретатор обработает запрос в следующей безопасной точке вычислений, загрузит скрипт с диска и выполнит его.407408Отладчик обязан убедиться, что файл скрипта остаётся доступным и читаемым для целевого процесса во время выполнения.409410> **Примечание**411>412> Выполнение скрипта асинхронно. Файл скрипта нельзя удалять сразу после внедрения. Отладчик должен дождаться, пока внедрённый скрипт не произведёт наблюдаемый эффект, прежде чем удалять файл. Этот эффект зависит от того, что должен делать скрипт. Например, отладчик может ждать, пока удалённый процесс не подключится обратно к сокету, прежде чем удалить скрипт. Как только такой эффект наблюдается, можно с уверенностью считать, что файл больше не нужен.413414# Сводка415416Чтобы внедрить и выполнить Python-скрипт в удаленном процессе:4174181. Найдите структуру `PyRuntime` в памяти целевого процесса.4192. Прочитайте и проверьте структуру `_Py_DebugOffsets` в начале `PyRuntime`.4203. С помощью смещений найдите корректный `PyThreadState`.4214. Запишите путь к Python-скрипту в `debugger_script_path`.4225. Установите флаг `debugger_pending_call` в значение `1`.4236. Установите `_PY_EVAL_PLEASE_STOP_BIT` в поле `eval_breaker`.4247. Возобновите процесс (если он был приостановлен). Скрипт выполнится в следующей безопасной точке выполнения.425426# Безопасность и модель угроз427428Протокол удаленной отладки опирается на те же примитивы операционной системы, которые используют нативные отладчики, такие как GDB и LLDB. Подключение к процессу требует **тех же привилегий**, что и эти отладчики, например `ptrace` / Yama LSM в Linux, `task_for_pid` в macOS и `SeDebugPrivilege` в Windows. Python не создает новых путей повышения привилегий; если злоумышленник уже обладает необходимыми разрешениями для подключения к процессу, он с тем же успехом может использовать GDB для чтения памяти или внедрения кода.429430Следующие принципы определяют, что считается уязвимостью безопасности в этой функции, а что нет:431432**Подключение требует привилегий уровня ОС**433434На всех поддерживаемых платформах операционная система ограничивает межпроцессный доступ к памяти проверками привилегий (`CAP_SYS_PTRACE`, root или права администратора). Сообщение о проблеме, проявившейся только после получения этих привилегий, – это **не** уязвимость в CPython, поскольку граница безопасности ОС уже была пересечена.435436**Сбои и ошибки памяти при чтении скомпрометированного процесса не считаются уязвимостями**437438Инструмент, читающий внутреннее состояние интерпретатора из целевого процесса, должен доверять корректности этой памяти. Если целевой процесс поврежден или находится под контролем злоумышленника, отладчик или профилировщик может упасть, выдать мусор или вести себя непредсказуемо. Это тот же риск, который принимает любой отладчик на основе `ptrace`. Ошибки этой категории (переполнения буфера, ошибки сегментации или неопределенное поведение, вызванное чтением поврежденного состояния) **не** рассматриваются как проблемы безопасности, хотя исправления, повышающие надежность, приветствуются.439440**Уязвимости в целевом процессе не входят в область ответственности**441442Если отлаживаемый процесс Python уже скомпрометирован, злоумышленник управляет выполнением в этом процессе. Демонстрация дальнейшего воздействия, исходя из этого состояния, не является уязвимостью в протоколе удаленной отладки.443444## Когда использовать `PYTHON_DISABLE_REMOTE_DEBUG`445446Переменная окружения [`PYTHON_DISABLE_REMOTE_DEBUG`](https://python-all.ru/3.14/using/cmdline.html#envvar-PYTHON_DISABLE_REMOTE_DEBUG) (и эквивалентный флаг [`-X disable_remote_debug`](https://python-all.ru/3.14/using/cmdline.html#cmdoption-X)) позволяет операторам отключить внутрипроцессную сторону протокола в качестве меры **защиты в глубину**. Это может быть полезно в усиленных или изолированных средах, где не ожидается отладка или профилирование процесса, а уменьшение поверхности атаки является приоритетом, хотя проверки привилегий уровня ОС уже предотвращают непривилегированный доступ.447448Установка этой переменной **не** затрагивает другие интерфейсы отладки уровня ОС (`ptrace`, `/proc`, `task_for_pid` и т.д.), которые остаются доступными согласно их собственным моделям разрешений.449