Пошаговая отладка в visual studio. рассказываем и показываем от и до

Переполнение стека

Все компиляторы С используют стек для хранения локальных переменных, адресов возврата и передаваемых функциям параметров. Однако стек не безграничен, и, в конце концов, может быть исчерпан. Тогда попытка записи очередного элемента в него приведет к переполнению стека. Когда такое происходит, программа или полностью «умирает», или продолжает выполняться в ненормальном причудливом стиле. Самое неприятное в переполнении стека заключается в том, что оно в большинстве случаев происходит безо всякого предупреждения и оказывает на программу столь серьезное воздействие, что определить, что именно было сделано неправильно, иногда бывает невероятно трудно. Единственной приемлемой подсказкой может служить то, что в некоторых случаях переполнение стека вызвано выходом из-под контроля рекурсивных функций. Если в вашей программе используются рекурсивные функции и вы столкнулись с необъяснимыми сбоями в ее работе, проверьте условия завершения в рекурсивных функциях.

И еще одно замечание. Некоторые компиляторы позволяют увеличить объем памяти, резервируемой под стек. Если ваша программа во всем остальном не имеет ошибок, но быстро исчерпывает стековое пространство (возможно из-за глубокой степени вложенности или рекурсивности функций), необходимо просто увеличить размер стека.

Подключение Xdebug

Для работы Xdebug PHP должен быть в режиме CGI. Посмотрим на примере хостинга Timeweb, как его включить.

Подключаемся к серверу через SSH. Можно использовать консоль в панели управления Timeweb.

Переходим в папку cd-bin сайта:

// Вместо wordpress — директория вашего сайта

cd wordpress/public_html/cgi-bin/

Создаем символическую ссылку командой:

// Укажите нужную вам версию обработчика вместо php5.3

ln -s /opt/php5.3/bin/php-cgi php5.3.cgi

Остаемся в директории cgi-bin и копируем файл php.ini:

cp /etc/php53/cgi/php.ini ./

Добавляем в файл .htaccess сайта две строки:

Action php5.3-script /cgi-bin/php5.3.cgi

AddType php5.3-script .php

Теперь мы можем управлять параметрами PHP директивами в файле php.ini. Он находится в папке cgi-bin. Открываем его и вставляем в конце следующие строки:

zend_extension = xdebug.so

xdebug.remote_enable=1

xdebug.remote_host=127.0.0.1

xdebug.remote_port=3083

xdebug.idekey=VSCODE

xdebug.remote_autostart=1

Если указанный порт занят, укажите другой. Можно использовать стандартный для Xdebug — 9000. В качестве idekey я указал VSCODE. Если будете настраивать конфигурацию для PHPStorm, впишите его.

Чтобы проверить, работает ли Xdebug, создадим в корне сайта файл Myfile.php со следующим содержимым:

<?php

phpinfo();

?>

Открываем файл в браузере и проверяем, что все параметры указаны верно.

Возможности Xdebug

Xdebug — это расширение для PHP, которое позволяет использовать удаленный отладчик в IDE через брейк-пойнты. С его помощью вы можете отслеживать значения переменных. Как итог — ошибки в коде обнаруживаются быстрее, чем при использовании error_log, print_r и var_dump. 

Еще одна полезная функция — трассировка стека. Она выводит подробный путь, который привел приложение к ошибке. Он содержит параметры, переданные в функцию. Xdebug также можно использовать как профайлер для поиска узких мест кода. Если добавить внешние инструменты, то получится визуализировать графики производительности. Например, можно использовать WebGrind — набор PHP-скриптов для удобного вывода статистики прямо в браузере.

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

Ошибки при задании аргументов

Тип любого формального параметра, должен соответствовать типу фактического параметра. Хотя благодаря прототипам функций компиляторы могут обнаруживать многие несоответствия типов аргументов (параметров), они не могут обнаружить все. Более того, когда функция имеет переменное количество параметров, компилятор не может обнаружить несоответствие их типов. Например, рассмотрим функцию scanf(), которая принимает большое количество разнообразных аргументов. Не забывайте, что scanf() ожидает принять адреса своих аргументов, а не их значения. И никакая сила не сделает за вас правильную подстановку. Например, следующая последовательность операторов

int x;
scanf("%d", x);

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

scanf("%d", &x);

Сбор логов профилирования

Для получения возможности анализа логов профилирования их необходимо сначала собрать.

3.1. Сбор логов профилирования для веб-приложений

Для профилирования веб-приложений используй профайлер Xdebug глобально или запускай и останавливай его
по требованию. После включения профайлера открой приложение в браузере, чтобы начать сбор данных — логов
профилирования.

Заметка

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

3.2. Сбор логов профилирования для CLI приложений и юнит-тестов

Для профилирования CLI приложений и юнит-тестов используй профайлер Xdebug глобально или создай отдельную
конфигурацию запуска для включения профайлера с помощью окна
Run/Debug Configurations. После чего запускай CLI приложение или юнит-тесты для
сбора данных профилирования.

При анализе проблем производительности в юнит-тестах хорошим подходом является создание отдельной конфигурации
запуска только тех юнит-тестов, в которых есть подозрения на проблемы с производительностью. Это позволяет
собирать целевые логи профилирования.

Типичные сценарии отладки

С точки зрения прикладного разработчика типичные сценарии отладки не изменились. Единственным значительным отличием является то, что новый механизм отладки нужно включить. Потому что по-умолчанию он выключен.

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

Подключение предметов отладки

При запуске отладочных сеансов из конфигуратора, приложения выполняют автоматическое подключение предметов отладки (как клиентского, так и серверного) к серверу отладки.

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

Во-первых, теперь платформа предлагает вам для выбора все возможные предметы отладки.

А во-вторых, появился ещё один, более тонкий способ настройки. Это использование заранее созданных отборов.

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

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

Настройка VSCode

Чтобы работать с Xdebug в VSCode, установим два расширения: Sync-Rsync и PHP Debug. Первое нужно для работы с удаленным сервером, второе — для отладки скриптов.

  1. Открываем в VSCode раздел Extensions (можно использовать сочетание клавиш Ctrl+Shift+X).
  2. Находим и устанавливаем расширение Sync-Rsync.
  3. Находим и устанавливаем расширение PHP Debug.

После установки расширений создаем на локальной машине пустую папку и открываем ее через VSCode: «Файл» — «Открыть папку».

Добавляем подпапку .vscode, создаем внутри нее файл settings.json и прописываем в нем настройки для Sync-Rsync.

{

  "folders": [

    {

      "path": "/home/локальная папка"

    }

  ],

  "sync-rsync.onSaveIndividual": true,

  "sync-rsync.executableShell": "/bin/bash",

  "sync-rsync.shell": "ssh -p 1024 -i /home/user/.ssh/id_rsa",

  "sync-rsync.delete": true,

  "sync-rsync.sites": [

    {

      "localPath": "/home/локальная папка",

      "remotePath": "[email protected]:/home/xxxxx/domain.ru/htdocs/www/"

    }



}

Путь /home/user/.ssh/id_rsa — это место, где лежит файл с закрытой частью SSH-ключа.

После сохранения файла settings.json нажимаем в VSCode F1, выполняем команду Sync Remote to Local. В локальную папку, указанную в настройках, скопируются все файлы с сервера.

Затем нажимаем на иконку отладки и на шестеренку. В папке .vscode появится файл launch.json. В него тоже нужно внести изменения:

{

  "version": "0.2.0",

  "configurations": [

    {

      "name": "Listen for XDebug",

      "type": "php",

      "request": "launch",

      "port": 3083,

      "pathMappings": {

        "/home/xxxxx/domain.ru/htdocs/www": "${workspaceRoot}/"

      }

    },

    {

      "name": "Launch currently open script",

      "type": "php",

      "request": "launch",

      "program": "${file}",

      "cwd": "${fileDirname}",

      "port": 3083

    }



}

На этом настройка IDE завершена. Можно приступать к тестированию кода.

Изменение переменных, свойств объектов и асинхронные вычисления выражений

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

Для удобного просмотра и изменения локальных переменных, что представляется наиболее частой задачей, мы реализовали окно «Локальные переменные».

Внешне оно очень похоже на привычное вам «Табло». Но, во-первых, это окно уже автоматически заполнено всеми локальными переменными, а во-вторых, значения переменных вы можете теперь менять.

Значения примитивных типов вы можете изменить прямо в ячейке «Значение»:

А для изменения других значений вы можете воспользоваться окном ввода выражений:

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

Точно таким же образом вы можете изменять и значения любых (не только локальных) переменных, свойств, доступных для записи. В окне вычисления выражений (которое вызывается командой Shift+F9) вы можете менять значения переменных как в ячейке «Значение», так и с помощью отдельного диалога.

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

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

Многие компиляторы поставляются вместе с отладчиком, который представляет собой программу, помогающую отладить разрабатываемый код. В общем случае отладчики позволяют шаг за шагом исполнять код разрабатываемой программы, устанавливать точки останова и контролировать содержимое различных переменных. Современные отладчики, например такие, как поставляемые в составе пакета Visual C++, являются действительно замечательными инструментальными средствами, которые могут оказать существенную помощь в обнаружении ошибок в разрабатываемом коде. Хороший отладчик стоит дополнительного времени и усилий, которые необходимы на его изучение, чтобы в дальнейшем эффективно его применять. Как бы то ни было, хороший программист никогда не откажется от работы с отладчиком для реализации надежного проекта и выполнения тонких работ.

Организация удаленного подключения

Чтобы выполнять PHP Debug на локальной машине, нужно настроить связь IDE и сервера через SSH-туннель.

На Linux все выполняется парой команд.

// Генерируем пару ключей и сохраняем их в папке .ssh на локальной машине

ssh-keygen -t rsa

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

На Linux туннель создается командой:

ssh -R 3083:localhost:3083 адрес_сервера -p1024

На Windows туннель настраивается через утилиту PuTTY.

  1. На вкладке Session указываем имя сервера и номер порта 1024. Проверяем, чтобы был отмечен протокол SSH.
  2. Переходим в раздел Connection — Data. Указываем логин.
  3. Переходим в раздел Connection — SSH — Tunnels. Указываем параметры так, как указано на скриншоте. Номер порта пишем тот, который используется в конфигурации PHP на сервере.
  4. Возвращаемся в раздел Session и нажимаем на кнопку Open. Подтверждаем корректность ключей (только первый раз). 

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

Включение профайлера Xdebug

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

Конфигурирование Xdebug осуществляется через директивы в активном php.ini файле. При любом способе включения
профайлера необходимо настроить следующую директиву:

xdebug.profiler_output_dir = /path/to/store/snapshots

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

2.1. В глобальном масштабе

Чтобы включить профайлер Xdebug в глобальном масштабе необходимо использовать нижеуказанную директиву:

xdebug.profiler_enable = 1

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

2.2. С помощью дополнительных параметров интерпретатора

Включить профайлер с помощью дополнительных параметров интерпретатора можно в окне
Run/Debug Configurations. Для его открытия используй следующие пункты главного
меню IDE:

Опция (отмечена красным контуром на скриншоте выше) Interpreter options
(параметры интерпретатора) секции Command Line (командная строка) должна
содержать следующую строку:

-d xdebug.profiler_enable = 1

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

2.3. С помощью специальных GET/POST параметров или cookie файла

Для более управляемого профилирования необходимо использовать нижеуказанную директиву:

xdebug.profiler_enable_trigger = 1

Если значение директивы установлено в 1, то при выполнении сценария с GET/POST параметром или кукой с именем
XDEBUG_PROFILE профилирование будет выполнено вне зависимости от установки
xdebug.profiler_enable.

Данный вариант включения профайлера самый популярный.

Внимание

Ошибки очередности вычисления

В большинстве С-программ применяются операторы инкрементирования и декрементирования, а порядок следования этих операторов, как вы помните, имеет большое значение в зависимости от того, предшествуют они или следуют за переменной. Рассмотрим следующий случай:

y = 10;     y = 10;
x = y++;    x = ++y;

Приведенные две последовательности не эквивалентны. Та, что слева, присваивает переменной х значение 10, а затем инкрементирует у. В другой же последовательности (справа) у сначала инкрементируется, и в результате этого становится равным 11, и только затем значение 11 присваивается переменной х. Таким образом, в первом случае х равно 10, а во втором случае х11. В общем случае в сложных выражениях префиксная операция инкрементирования (или декрементирования) осуществляется перед вычислением значения операнда, используемого в последующих действиях. Постфиксный инкремент (или декремент) выполняется в сложных выражениях после того, как значение операнда вычислено. Если забыть об этих правилах, проблем не миновать.

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

/* первоначальный код */
x = a + b;
a = a + 1;

и представить ее в таком виде:

/* "усовершенствованный" код - ошибка! */
x = ++a + b;

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

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

Ошибки из-за нарушения границ

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

#include <stdio.h>

int main(void)
{
  int var1;
  char s;
  int var2;

  var1 = 10;  var2 = 10;
  gets(s);
  printf("%s %d %d", s, var1, var2);

  return 0;
}

В этом фрагменте нет очевидных ошибок кодирования. Тем не менее, вызов функции gets() с параметром s может косвенно привести к ошибке. В данной программе переменная s объявлена как массив символов (строка длиной в 10 знаков). Но что произойдет, если пользователь введет больше десяти знаков? Это приведет к выходу за границы массива s, и значение переменной var1 или var2, а возможно, и их обеих будет перезаписано. Следовательно, var1 и (или) var2 не будут содержать правильных значений. Это вызвано тем, что для хранения локальных переменных все С-компиляторы применяют стек. Переменные var1, var2, а также s могут располагаться в памяти так, как показано на рис. 28.1. (Ваш компилятор С может поменять порядок следования переменных var1, var2 и s.)

Предположим, что порядок распределения ячеек памяти совпадает с изображенным на рис. 28.1. Тогда, если произойдет выход за границы массива s, то дополнительные (лишние) символы будут помещены в область, в которой должна находиться переменная var2. Это практически уничтожит информацию, ранее записанную там.

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

Рис. 28.1. Размещение в памяти переменных var1, var2 и s (исходя из предположения, что на целое число выделяется 2 байта)

           Младшие  . 
           адреса   .
           памяти   .
      |-- +--------------------+ --|
var1 -|   +--------------------+   |- 2 байта
      |-- +--------------------+ --|
      |   +--------------------+   |
      |   +--------------------+   |
      |   +--------------------+   |
      |   +--------------------+   |
  s  -|   +--------------------+   |- 10 байтов
      |   +--------------------+   |
      |   +--------------------+   |
      |   +--------------------+   |
      |   +--------------------+   |
      |-- +--------------------+ --|
var2 -|   +--------------------+   |- 2 байта
      |-- +--------------------+ --|
           Старшие  . 
           адреса   .
           памяти   .

В рассмотренной программе потенциальная ошибка из-за выхода за границы может быть исключена за счет применения функции fgets() вместо gets(). Функция fgets() предоставляет возможность устанавливать максимальное количество считываемых символов. Единственная проблема состоит в том, что fgets() считывает и сохраняет еще и символ разделителя строк, поэтому в большинстве приложений его необходимо будет удалять.

Интерпритация синтаксических ошибок

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

С одной из особенно сногсшибательных ошибок можно познакомиться ближе, если вы попытаетесь скомпилировать следующий код:

char *myfunc(void);

int main(void)
{
  /* ... */
}

int myfunc(void) /* сообщение об ошибке указывает сюда */
{
  /* ... */
}

Ваш компилятор выдаст сообщение об ошибке вместе с таким вот разъяснением:

Type mismatch in redeclaration of myfunc(void)
(Несоответствие типов при повторном объявлении myfunc(void))

Это сообщение относится к строке листинга программы, которая помечена комментарием о наличии ошибки. Как такое возможно? Ведь в этой строке нет двух функций myfunc(). А разгадка состоит в том, что прототип в верхней строке программы показывает, что myfunc() возвращает значение типа указатель на символ. Это ведет к тому, что в таблице идентификаторов компилятор заполняет строку, содержащую эту информацию. Когда затем в программе компилятор встречает функцию myfunc(), то теперь тип результата указывается как int. Следовательно, вы «повторно объявили», другими словами «переопределили» функцию.

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

/* В тексте данной программы имеется синтаксическая ошибка. */
#include <stdio.h>

void func1(void);

int main(void)
{
  func1();

  return 0;
}

void func1(void);
{
  printf("Это в func1.\n");
}

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

bad declaration syntax (неправильный синтаксис объявления)

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

Архитектура процесса отладки

Новая архитектура отладки выглядит следующим образом:

В отладке участвуют отладчик, предметы отладки и новый элемент — сервер отладки.

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

И сам отладчик, и предметы отладки взаимодействуют с сервером отладки по протоколу HTTP

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

Для этого организуются дополнительные соединения. Их основное назначение — узнать, не появилась ли для них информация на сервере отладки. И если появилась, получить эту информацию.

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

Таким образом, взаимодействие получается одностороннее. Информация всё время передаётся с сервера отладки в отладчик, и в предметы отладки.

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Ваша ОС
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: