Глава 14. Отладка

CPython поставляется со встроенным отладчиком, pdb, для отладки приложений Python. Этот отладчик pdb исключителен при отладке крушений внутри некого приложения Python, а также для написания тестов и инспектирования локальных переменных.

Когда же дело касается CPython, однако, вам требуется второй отладчик, тот, который понимает C.

В этой главе вы изучите как:

  • Подключать отладчик к своему интерпретатору CPython

  • Применять этот отладчик чтобы заглянуть вовнутрь выполнения процесса CPython

Существует два типа отладчиков: консольный и наглядный. Консольные отладчики (такие как pdb) снабжают вас неким приглашением на ввод команд и собственные команды для изучения переменных и стека. Наглядные отладчики это приложения с графическим интерфейсом, которые представляют данные в координатной сетке.

В этой главе рассматриваются следующие отладчики:

Таблица 14-1. Рассматриваемые отладчики
Отладчик Тип Платформа

LLDB

консольный

macOS

GDB

консольный

Linux

Отладчик Visual Studio

наглядный

Windows

Отладчик CLion

наглядный

Windows, macOS, Linux

Отладчик VS Code

наглядный

Windows, macOS, Linux

Применение обработчика крушений

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

Если CPython вызывает ошибку сегментации, тогда вы получите очень мало сведений о том что произошло:


[1]    63476 segmentation fault  ./python portscanner.py
 	   

CPython поставляется со встроенным обработчиком отказов. Если вы запускаете CPython с -X faulthandler или -X dev, тогда вместо выдачи на печать системного сообщения ошибки сегментации, имеющийся обработчик ошибок выведет на печать значение исполнявшегося потока и трассировку стека самого Python где произошла эта ошибка:


Fatal Python error: Segmentation fault
Thread 0x0000000119021dc0 (most recent call first):
  File "/cpython/Lib/threading.py", line 1039 in _wait_for_tstate_lock
  File "/cpython/Lib/threading.py", line 1023 in join
  File "/cpython/portscanner.py", line 26 in main
  File "/cpython/portscanner.py", line 32 in <module>
[1]    63540 segmentation fault  ./python -X dev portscanner.py
 	   

Эта функциальная возможность также полезна при разработке и тестированию расширения C CPython.

Компиляция поддержки отладки

Для получения существенных сведений из своего отладчика, вам следует компилировать необходимые символы отладки в CPython. Без таких символов, тот стек отладки, которй пребывает внутри сеанса отладки не будет содержать верных названий функций, имён переменных или названий файлов.

  Windows

Следуйте тем же самым шагам, которые вы предпринимали в разделе Компиляция CPython в Windows из Главы 3, Компиляция CPython, при этом обеспечьте компиляцию в конфигурации отладки для получения символов отладки:


> build.bat -p x64 -c Debug
		

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

  macOS или Linux

Соответствующие шаги из Главы 3, Компиляция CPython предписывают запуск сценария ./configure с флагом --with-pydebug. Если вы не включили этот флаг, тогда вернитесь обратно и снова запустите ./configure с первонаяальными параметрами и этим флагом --with-pydebug. Это произведёт правильный исполняемый файл и символы для отладки.

Применение LLDB для macOS

Отладчик LLDB поставляется с инструментами разработки Xcode, поэтому вы должны иметь их уже установленными.

Запустите LLDB и загрузите скомпилированный исполняемый файл CPython в качестве его цели:


$ lldb ./python.exe
(lldb) target create "./python.exe"
Current executable set to './python.exe' (x86_64).
		

Теперь вы будете обладать приглашением на ввод, в котором вы можете вводить некоторые команды для отладки.

  Создание точек прерывания

Для создания точки прерывания воспользуйтесь командой break set с необходимым файлом (относительно вашего корня) и значением номера строки:


lldb) break set --file Objects/floatobject.c --line 532
Breakpoint 1: where = python.exe`float_richcompare + 2276 at 
    floatobject.c:532:26, address = 0x000000010006a974
 	   
[Замечание]Замечание

Существует также условное обозначение для настройки точек прерывания: (lldb) b Objects/floatobject.c:532

Вы можете добавлять множество точек прерывания при помощи команды break set. Для просмотра перечня утановленных в данный момент точек прерывания воспользуйтесь командой break list:


(lldb) break list
Current breakpoints:
1: file = 'Objects/floatobject.c', line = 532, exact_match = 0, locations = 1
  1.1: where = python.exe`float_richcompare + 2276 at floatobject.c:532:26, 
            address = python.exe[...], unresolved, hit count = 0
 	   

  Запуск CPython

Для запуска CPython воспользуйтесь командой process launch -- параметрами командной строки которые вы обычно применяете с Python.

Для запуска Python с некой строкой, например, python -c "print(1)" воспользуйтесь следующей командой:


(lldb) process launch -- -c "print(1)"
 	   

Для запуска Python с неким сценарием применяйте такую команду:


(lldb) process launch -- my_script.py
 	   

  Подключение к исполняемому интерпретатору CPython

Если вы уже обладаете запущенным интерпретатором CPython, тогда вы можете подключаться к нему.

Изнутри своего сеанса LLDB исполните process attach --pid с соответствующим идентификатором процесса:


(lldb) process attach --pid 123
 	   

Вы можете получить идентификатор процесса из монитора активности или воспользовавшись os.getpid() в Python.

Все настроенные до или после этой точки точки прерывания остановят этот процесс.

  Обработка точки прерывания

Чтобы посмотреть как обрабатываются точки прерывания, установите некую точку прерывания в функции float_richcompare() Objects/floatobject.c.

Затем запустите этот процесс и сравните два плавающих значения при помощи своего оператора почти равенства, который вы разрабатывали в предыдущих главах:


(lldb) process launch -- -c "1.0~=1.1"
Process 64421 launched: '/cpython/python.exe' (x86_64)
Process 64421 stopped
* thread #1, queue = '...', stop reason = breakpoint 1.1
    frame #0: 0x000000010006a974 python.exe`float_richcompare(v=1.0,
        w=1.1, op=6) at floatobject.c:532:26
   529          break;
   530      case Py_AlE: {
   531              double diff = fabs(i - j);
-> 532              const double rel_tol = 1e-9;
   533              const double abs_tol = 0.1;
   534              r = (((diff <= fabs(rel_tol * j)) ||
Target 0: (python.exe) stopped.
 	   

LLDB выдаст вам приглашение на ввод снова. Вы можете просмотреть локальные переменные при помощи команды v:


(lldb) v
(PyObject *) v = 0x000000010111b370 1.0
(PyObject *) w = 0x000000010111b340 1.1
(int) op = 6
(double) i = 1
(double) j = 1.1000000000000001
(int) r = 0
(double) diff = 0.10000000000000009
(const double) rel_tol = 2.1256294105914498E-314
(const double) abs_tol = 0
 	   

Вы можете вычислять выражение C при помощи команды expr с любой допустимой командой C. Могут применяться любые переменные из области действия. Например, чтобы вызвать fabs(rel_tol) и привести его к double выполните такую команду:


(lldb) expr (double)fabs(rel_tol)
(double) $1 = 2.1256294105914498E-314
 	   

Это выдаст на печать получаемые в результате переменные и назначать их некому идентификатору ($1). Вы можете повторно применять этот идентификатор в качестве временной переменной.

Вы можете также пожелать изучать экземпляры PyObject:


(lldb) expr v->ob_type->tp_name
(const char *) $6 = 0x000000010034fc26 "float"
 	   

Для отслеживания хода исполнения с этой точки прерывания применяйте команду bt:


(lldb) bt
* thread #1, queue = '...', stop reason = breakpoint 1.1
  * frame #0: ... 
        python.exe`float_richcompare(...) at floatobject.c:532:26
    frame #1: ... 
        python.exe`do_richcompare(...) at object.c:796:15
    frame #2: ...
        python.exe`PyObject_RichCompare(...) at object.c:846:21
    frame #3: ... 
        python.exe`cmp_outcome(...) at ceval.c:4998:16
 	   

Чтобы вмешиваться, применяйте команду step или s.

Чтобы перешагнуть или продолжить со следующего предложения, воспользуйтесь командой next или n.

Для продолжения исполнения употребляйте команду continue или c.

Для выхода из сеанса пользуйтесь командой quit или q.

[Совет]Смотри также

LLDB Documentation Tutorial содержит наиболее исчерпывающий список команд.

  Применение расширения cpython_lldb

LLDB поддерживает расширения, написанные на Python. Имеется некое расширение открытого исходного кода, cpython_lldb, которая выводит на печать дополнительные сведения в вашем сеансе LLDB для естественных объектов CPython/

Для его установки запустите такие команды:


$ mkdir -p ~/.lldb
$ cd ~/.lldb && git clone https://github.com/malor/cpython-lldb
$ echo "command script import ~/.lldb/cpython-lldb/cpython_lldb.py" \
  >> ~/.lldbinit
$ chmod +x ~/.lldbinit 
		

Теперь, всякий раз когда вы наблюдаете переменные в LLDB, вы также видите некоторые дополнительные сведения справа, такие как численное значение для целых чисел и чисел с плавающей запятой или текст для строк Unicode. Внутри консоли LLDB у вас также имеется некая дополнительная команда, py-bt, которая выводит на печать значение отслеживания стека для кадров Python.

Применение GDB

GDB это отладчик, обычно применяемый для приложений C/C++, написанных для платформ Linux. Он также очень популярен в команде разработчиков CPython.

При компиляции CPython он вырабатывает некий сценарий, python-gdb.py. Не исполняйте этот сценарий напрямую. Вместо этого GDB обнаружит и исполнит его автоматически после настройки.

Для настройки этого этапа отредактируйте файл .gdbinit внутри своего домашнего пути (~/.gdbinit) и добавьте приводимую ниже строку, в которой /path/to/checkout это путь к проверке git cpython:


add-auto-load-safe-path /path/to/checkout
 	   

Для запуска GDB запустите его с аргументом, указывающим на ваш скомпилированный исполняемый файл CPython:


$ gdb ./python
		

GDB загрузит все символы для скомпилированного исполняемого файла и предоставит вам приглашение командной строки. GDB обладает набором встроенных команд, а пакет расширений CPython некоторыми дополнительными командами.

  Создание точек прерывания

Для установки точки прерывания воспользуйтесь командой b <file>:<line> относительно пути этого исполняемого файла:


(gdb) b Objects/floatobject.c:532
Breakpoint 1 at 0x10006a974: file Objects/floatobject.c, line 532.
 	   

Вы можете устнаовить столько точек прерывания, сколько пожелаете.

  Запуск CPython

Для запуска конкретного процесса применяйте команду run с идущими далее аргументами для запуска интерпретатора Python.

Например, для запуска с некой строкой воспользуйтесь такой строкой:


(gdb) run -c "print(1)"
 	   

Чтобы запустить Python со сценарием употребляйте такую команду:


(gdb) run my_script.py
 	   

  Подключение к исполняемому интерпретатору CPython

Когда вы уже имеете запущенным интерпретатор CPython, вы можете присоединиться к нему.

Изнутри сеанса GDB запустите команду attach с идентификатором нужного процесса:


(gdb) attach 123
 	   

Вы можете получить значение идентификатора процесса из монитора активности или воспользовавшись os.getpid() в Python.

Все установленные до и после этой точки будут останавливать этот процесс.

  Обработка точки прерывания

Когда GDB попадает в эту точку прерывания, вы можете пользоваться командой print или p для вывода на печать переменной:


(gdb) p *(PyLongObject*)v
$1 = {ob_base = {ob_base = {ob_refcnt = 8, ob_type = ...}, ob_size = 1}, ob_digit = {42}}
 	   

Чтобы вмешиваться, применяйте команду step или s.

Чтобы перешагнуть или продолжить со следующего предложения, воспользуйтесь командой next или n.

  Применение расширения python-gdb

Расширение python-gdb загрузит некоторый дополнительный набор команд в имеющуюся консоль GDB:

Таблица 14-2. Дополнительные команды python-gdb
Команда Назначение

py-print

Отыскивает переменную Python и выводит её на печать

py-bt

Выводит её на печать отслеживание стека Python

py-locals

Выводит на печать результат locals()

py-up

Спускаемся вниз на один кадр Python

py-down

Поднимаемсяна один кадр Python вверх

py-list

Выводит её на печать исходный код Python для текущего кадра

Применение отладчика Visual Studio

Microsoft Visual Studio поступает в пакете с визуальным отладчиком. Этот отладчик является мощным и поддерживает визуализатор стека кадра, список отслеживаемого, а также возможность вычисления выражений.

Для его применения откройте Visual Studio и соответствующий файл решения PCBuild/pcbuild.sln.

  Создание точек прерывания

Для добавления новой точки прерывания, проследуйте в тот файл, в который вы желаете в своём окне решения, затем кликните в соответствующем внутреннем поле слева от номера вашей строки.

Это добавит красный кружок для указания того что вы установили в этой строке точку прерывания:

 

Рисунок 14-1



Когда вы наводите курсор на красный кружок, появляется зубец Кликните по этому зубцу для настройки условных точек прерываний. Добавьте одно или более условных выражений, которые должны быть вычислены до попадания в эту точку прерывания:

 

Рисунок 14-2



  Запуск отладчика

Из верхнего меню выберите Debug/Start Debugger или нажмите F5.

Visual Studio запустит новую среду исполнения Pythob и REPL.

  Обработка точки прерывания

Когда вы попадаете в некую точку прерывания вы можете пройти вперёд по предложениям применяя кнопки навигации или следующих горячих клавиш:

  • Для вмешательства: F11

  • Для продвижения: F10

  • Чтобы покинуть: Shift + F11

Снизу вы увидите стек вызова. Вы можете выбирать в этом стеке кадры для изменения навигации и инспектирования переменных в прочих кадрах:

 

Рисунок 14-3



В своём редакторе кода вы можете выделить любую переменную или выражение чтобы увидеть их значение. Вы также можете кликнуть правой кнопкой мыши и выбрать Add Watch. Это добавляет соответствующую переменную в окно просмотра, в котором вы сможете быстро увидеть значения переменных, которые требуются вам для отладки:

 

Рисунок 14-4



Применение отладчика CLion

IDE CLion оборудован мощным визуальным отладчиком. В macOS он работает с LLDB и с GDB в macOS, Windows и Linux.

Для настройки этого отладчика пройдите в Preferences и выберите Build, Execution, Deployment/Toolchains:

 

Рисунок 14-5



Имеется блок выбора для целевого отладчика. Выберите подходящие параметры для своей операционной системы:

  • macOS: Снабжён LLDB

  • Windows или Linux: Снабжаются GDB

[Замечание]Важно

И параметры LLDB, и параметры GDB приносят выгоду из расширений cpython_lldb и python-gdb, соответственно. Прочтите разделы LLDB и GDB из этой главы для получения сведений относительно установки и включения этих расширений.

  Отладка приложения Make

Из CLion 2020.2 вы можете компилировать и отлаживать все основывающиеся на makefile проекты, в том числе и CPython.

Для запуска отладки выполните шаги из раздела Монтаж JetBrains CLion Главы 2, Настройка вашей среды разработки. После выполнения этих шагов вам придётся Make Application цели. Выберите Run/Debug из верхнего меню чтобы запустить необходимый процесс и приступить к отладке.

В качестве альтернативы вы можете подключить необходимый отладчик для исполнения процесса CPython.

  Подключение отладчика

Для подключения к исполняющему процесс CPython отладчику CLion выберите Run/Attach to Process.

Всплывёт список запущенных процессов. Отыщите процесс Python, к которому вы желаете присоединиться и выберите Attach. Начнётся сеанс отладки.

[Предостережение]Важно

Когда у вас имеется установленным подключаемый модуль Python, это покажет в самом верху процесс Python. Не выбирайте его!.

Он использует отладчик Python, не отладчик C:

 

Рисунок 14-6



Вместо этого прокрутите далее вниз до списка Native и отыщите правильный процесс Python.

  Создание точек прерывания

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

 

Рисунок 14-7



Кликните правой кнопкой для подключения условий:

 

Рисунок 14-8



Чтобы просматривать и управлять всеми текущими точками прерывания переместитесь из самого верхнего меню к Run/View Breakpoints :

 

Рисунок 14-9



Теперь вы можете включать и отключать точки прерывания, а также отключать их при попадании в иную точку прерывания.

  Обработка точек прерывания

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

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

 

Рисунок 14-10



Внутри прерывания вы можете вычислять выражения для получения дополнительных сведений о локальных переменных. Вы можете отыскать окно Evaliate в Run/Debugging Actions/Evaluate Expression или иконку закладки в окне отладки.

Внутри окна Evaluation вы можете набрать выражение и CLion наберёт далее названия свойств и типы:

 

Рисунок 14-11



Вы также можете преобразовывать выражения, что полезно для приведения PyObject* в реальный тип, например в PyFloatObject*:

 

Рисунок 14-12



Выводы

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

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