2025, Nov 23 03:02

Почему в GDB ломается readline в Python и как это исправить

Python под GDB теряет редактирование и историю readline. Поясняем причину (sys.stdout/sys.stderr) и показываем, как вернуть работу или обойтись без input().

Запуск Python под GDB удобен, когда нужно отлаживать стык нативного кода и Python, но есть тонкая ловушка: построчное редактирование и история в readline перестают работать. Привычные сочетания вроде Ctrl+P не срабатывают, а readline вообще не видит истории. Если вы запускаете скрипт командой gdb -x py_script.py и полагаетесь на input(), вероятно, уже сталкивались с этим.

Как воспроизвести проблему

Поведение легко воспроизвести. Минимальный скрипт ниже читает ввод в цикле и выводит текущую длину истории readline. Под GDB счётчик не растёт, а привычное редактирование ввода фактически отключено.

import readline
while True:
  line_buf = input("_in: ")
  print(readline.get_current_history_length())
  print('out:', line_buf)

Запущенный внутри сеанса GDB, readline показывает длину истории 0 вне зависимости от того, сколько строк вы ввели.

Что происходит на самом деле

Это связано с тем, как GDB при старте интегрируется с вводом-выводом Python. GDB заменяет стандартные потоки вывода Python собственными постраничными потоками. Официальное руководство говорит об этом напрямую:

При запуске GDB переопределяет sys.stdout и sys.stderr Python, чтобы печатать через собственные постраничные потоки вывода GDB. Программа на Python, которая пишет в один из этих потоков, может иметь вывод, прерванный пользователем (см. Screen Size). В такой ситуации возбуждается исключение Python KeyboardInterrupt.

Одного этого перенаправления достаточно, чтобы в такой конфигурации сломать редактирование и историю readline. Попытки обращаться напрямую к gdb.sys.stdin, gdb.sys.stdout или gdb.sys.stderr проблему не решают.

Решение

Верните sys.stdout и sys.stderr к встроенным исходным потокам до импорта readline. С этими двумя присваиваниями редактирование ввода и история в GDB работают как обычно.

import sys
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
import readline
while True:
  entry = input("_in: ")
  print(readline.get_current_history_length())
  print('out:', entry)

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

Альтернативный путь: вовсе не использовать input()

Во многих сценариях с GDB нет острой необходимости использовать input(). Типичный приём — реализовать собственную команду GDB на Python и передавать аргументы прямо из приглашения GDB. Ниже компактный пример:

import gdb
class PromptCmd(gdb.Command):
  def __init__(self):
    super().__init__("ask", gdb.COMMAND_USER)
  def invoke(self, arg, from_tty):
    print("You typed:", arg)
PromptCmd()

Такой подход полностью устраняет интерактивное редактирование строк в Python и органично вписывается в привычные рабочие процессы с GDB.

Почему это важно

Когда вы совмещаете Python и GDB, разбираться, почему ломается поведение input(), бывает утомительно — особенно когда readline тихо теряет историю и возможности редактирования. Понимание того, что GDB переопределяет sys.stdout и sys.stderr, а восстановление sys.__stdout__ и sys.__stderr__ устраняет симптомы, экономит время и избавляет от бесполезных приключений с подсистемой ввода.

Итоги

Если при запуске Python внутри GDB у вас исчезают редактирование и история readline, до импорта readline верните оригинальные стандартные потоки присваиваниями sys.stdout = sys.__stdout__ и sys.stderr = sys.__stderr__. Если это подходит под ваш сценарий, рассмотрите вариант обработки взаимодействия через gdb.Command вместо input(). В любом случае понимание того, как GDB влияет на I/O Python, помогает сохранить предсказуемое интерактивное поведение.