Как работает отладчик?
Как технически реализованы функции остановки и дальнейшего исполнения нативной программы? Как реализован просмотр стека, регистров и памяти? В случае интерпретатора, понятно. Ему можно скомандовать «остановись», «покажи стек» потому что интерпретатор отдельная программа со своим интерфейсом. А вот при нативной отладке происходит какое-то волшебство, программа и отладчик работают на одном процессоре, но отладчик при этом управляет другой программой и видит все ее кишки. Как работает это волшебство?
Отслеживать
задан 12 сен 2019 в 8:19
6,853 2 2 золотых знака 23 23 серебряных знака 43 43 бронзовых знака
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
В процессорах есть аппаратная поддержка отладчика. Команды останова, бит пошагового выполнения программ и прочие чудеса. Все это обычно работает на системном уровне приоритета. В старых процессорах (до 80386) бит пошагового выполнения был, а аппаратных приоритетов еще не было. Там отладчики работали в режиме пользователя. Впрочем, там и ОС работала в режиме пользователя. 🙂 Если конечно DOS можно назвать ОС.
Давно это было. Подробнее Вам надо читать описание аппаратной архитектуры процессоров. Причем поддержка отладчика теперь есть во всех более-менее уважающих себя процессорах типа ARM и прочих. В старых процессорах типа однокристаллок 51 серии от INTEL этой поддержки нет.
Когда я этим занимался, то все работали под DOS и отладчики и программа работали в реальном режиме. Тогда в системе команд процессора была однобайтовая команда программного прерывания. Если пользователь ставил точку останова в программе, то отладчик модифицировал программу пользователя, записывая в начало ассемблерной последовательности (которая реализует Сишную команду) эту самую однобайтовую команду программного прерывания. Когда (и если) программа проходила через эту точку, возникало программное прерывание, которое обрабатывал отладчик. Он останавливал программу пользователя, считывал все регистры общего назначения, и другую информацию, интересную для программиста типа содержимого стека, сегментных регистров, сегментов памяти и прочего. И всю эту информацию распихивал по окнам и останавливался. Когда пользователь снимал точку останова, то отладчик восстанавливал программу пользователя, записывая вместо этой самой однобайтовой команды прерывания старую инструкцию.
Также был так называемый Т-бит в регистре флагов процессора. Если в этот Т-бит записать единицу, то процессор вырабатывал Т-прерывание со своим отдельным вектором после выполнения каждой инструкции. Это Т-прерывание тоже обрабатывалось отладчиком и с его помощью можно было реализовать пошаговое выполнение программы. По этому прерыванию отладчик тоже собирал всю информацию о состоянии процессора и представлял эту информацию в удобном для человека виде в разных окнах.
Как сейчас сделано я точно не знаю, но думаю что тоже что-то вроде этого. За исключением того, что теперь, конечно, отладчик работает в системном режиме процессора, а программа пользователя работает в пользовательском режиме процессора.
Отладка программ, дебаггер
До сих пор, если вы все внимательно повторяли за нами, вы скорее всего избежали серьезных проблем и ошибок. Но это только потому, что мы готовились к каждому уроку и тщательно проверяли каждую нашу программу.
В реальной работе никогда не бывает все так гладко. Пора уже научиться находить и уничтожать баги. Это неотъемлемая часть работы программиста, и для этого существуют специальные инструменты.

План урока
- Дебагер — что это и как работает?
- Как дебажить в RubyMine
- Как дебажить без RubyMine
Что такое дебаггер?
И вновь, вот уже в который раз вспомним нашу метафору с дорогой и машинкой.

Если наша машина вдруг заглохла, есть два варианта: либо проблему видно сразу, например, камень на дороге или уровень бензина на нуле, либо машина просто заглохла и всё. Жми педали, крути руль — не едет и всё тут. Приходится думать и искать поломку, залезать под капот.
Программы в компьютере обычно выполняются очень быстро, процессор старается выполнить код программы с максимальной скоростью, чтобы освободить ресурсы для следующей программы.
Эта скорость иногда мешает понять, в чём проблема и в какой момент случилась загвоздка. Тогда хочется программу остановить или замедлить.
Подобно тому, как спортивные машины на испытаниях обвешивают специальным оборудованием, которое в режиме реального времени снимает все возможные показания (температуру мотора, нагрузку на карданный вал, давление жидкостей и так далее), нам тоже хочется запустить нашу программу и в любой момент иметь возможность остановить её и посмотреть значения каких-то переменных и выражений.
Это можно сделать с помощью дебаггера — специальной программы, которая позволяет погрузиться внутрь работающей программы, наблюдать за ее выполнением и даже вмешиваться в ее выполнение.

Как работает дебаггер (отладчик)
Главная задача дебаггера — помочь найти баг (ошибку), поэтому он так и называется.
Если вы обнаружили ошибку, но не знаете ее причину, ваша задача — сделать какие-то предположения. И запустить программу с помощью дебаггера для проверки этих предположений.
Для дебага программа запускается в специальном режиме, в котором вы можете поставить в любой точке «брейкпоинт», инструкцию для дебаггера остановить выполнение программы на каком-то конкретном шаге и передать управление вам.
Как только программа дошла до брейкпойнта, она останавливается. Дальше вы можете рулить машиной «step-by-step», шаг за шагом выполняя программу строчку за строчкой и анализируя все ее переменные.
Давайте попробуем отдебажить что-нибудь из написанного нами ранее.
Установка дебаггера для RubyMine
В RubyMine довольно просто запустить программу в режиме отладки, для этого рядом с кнопкой Run всегда есть кнопка с зеленым жучком:

При первом запуске дебаггера RubyMine может попросить вас разрешения установить дополнительные гемы:

Гемы лучше установить предварительно самостоятельно. Давайте установим основной гем, нужный для дебага:
gem install ruby-debug-ide
(если это не получится в обычной консоли, запустите Dev-Kit-овскую, c:\dev\msys.bat , см. как мы это делаем на видео)
Теперь давайте напишем программу, которую мы будем дебажить.
Программа для решения квадратных уравнений
Пишем программу, которая решает квадратные уравнения.
equation.rb :
puts 'Solve equation: A * x^2 + B * x + C = 0' puts 'Enter A:' a = gets.to_f puts 'Enter B:' b = gets.to_f puts 'Enter C:' c = gets.to_f # считаем дискриминант discr = b*b - 4*a*c; # первый корень x1 = (-b + Math::sqrt(discr))/(2*a) # второй корень x2 = (-b - Math::sqrt(discr))/(2*a) puts 'Solution 1:' puts x1 puts 'Solution 2:' puts x2
Если мы попробуем запустить эту программу с параметрами a=0, b=4, c=2 или a=3, b=2, c=1 , то программа выдаст либо какую-то фигню, либо вообще упадет с ошибкой.
Давайте разбираться. Поставим брейкоинт вот на этой строчке:
discr = b*b - 4*a*c;
И запустим программу ещё раз:

Когда программа остановится, давайте попробуем вычислить дискриминант отдельно: нажмите Alt+F8 (или иконку с калькулятором), у вас появилось окно Evaluate expression.
Это окно, которое позволяет выполнять любой код (и видеть результат), как если бы он был написан перед той строчкой, на которой остановилось ваше приложение.

Если мы вычислим дискриминант, то увидим, что всё хорошо, дискриминант равен 4-м. Идём дальше (нажимаем F8 ). Это называется Step Over, означает «выполнить следующую строчку и остановиться». Так можно строчка за строчкой пройти всю программу в любом нужном вам темпе.
Давайте на следующем шаге вычислим первый корень, снова нажмите Alt+F8 и вычислите значение первого корня:

Оп-па! Результат выходит не числом. Тут уже легко разобраться, что раз у нас a=0 , то делить на эту переменную другие нельзя. Это исключение надо обработать отдельно.
Давайте разберёмся со вторым случаем. Снова запускаем нашу программу в режиме отладки, но уже с новыми значениями a=3, b=2, c=1 и снова останавливаем:

Мы попытались взять квадратный корень из отрицательного числа. В этом случае корней в действительных числах нет.
Можно написать в программе, что в таком случае корней нет и выйти. А можно сделать умней — добавить в программу поддержку комплексных чисел, тогда корень из отрицательного числа обретет новый смысл.
Вот так будет выглядеть наша исправленная программа:
# Метод, выводящий решение на экран def print_root(x_real, x_complex) puts 'Solution:' print x_real print ' +' if x_complex > 0 print " # * i" if x_complex != 0 puts end puts 'Solve equation: A * x^2 + B * x + C = 0' puts 'Enter A:' a = gets.to_f puts 'Enter B:' b = gets.to_f puts 'Enter C:' c = gets.to_f if a == 0 abort "It is linear equation! x = #" end # считаем дискриминант discr = b*b - 4*a*c; if discr < 0 # комплексные числа пошли x1_real = -b/(2*a) x1_complex = Math::sqrt(-discr)/(2*a) x2_real = -b/(2*a) x2_complex = -Math::sqrt(-discr)/(2*a) else # первый корень x1_real = (-b + Math::sqrt(discr))/(2*a) x1_complex = 0 # второй корень x2_real = (-b - Math::sqrt(discr))/(2*a) x2_complex = 0 end print_root(x1_real, x1_complex) print_root(x2_real, x2_complex)
Нюансы при отладке
Если наша программа работает с несколькими потоками, то при остановке одного из потоков, другие могут продолжить выполняться или остановиться в непредсказуемых местах.
Также во время остановки могут протухнуть внешние ресурсы (удаляться какие-нибудь временные файлы, пропадёт сеть и так далее), так что очень сложная программа в режиме отладки может вести себя не совсем так, как она ведёт себя в обычном режиме.
Ещё один нюанс: во время отладки может так выйти, что вы забредёте в код сторонних библиотек или языка руби. Не стоит бояться заглянуть и изучить чужой код, особенно если он хорош.
Как дебажить без RubyMine (byebug)
Если дебаг в RubyMine по каким-то причинам не для вас, то есть другие способы. Например с помощью специальной библиотеки byebug :
gem install byebug
Чтобы вашу программу можно было отдебажить с помощью «байбага», вам нужно добавить его в список подключённых библиотек в коде программы:
require byebug
А вместо брейкпоинта вам просто нужно написать в интересном вам месте волшебное слово
byebug
Дойдя до этой строчки, программа остановится и передаст вам управление прямо в консоли. Вы сможете выполнять там команды, также как в окне Evaluate Expression рубимайна, и смотреть значение выражений:

Зачем и когда дебажить?
Когда пользоваться дебагером, а когда нет, решать вам. В конечном итоге ошибку всё равно ищете вы, дебаггер лишь помогает проверять предположения.
Но вот несколько советов, когда вам стоит вспомнить про дебагер:
- Если вы столкнулись с неочевидным, сложным багом
- Нужно исследовать сложное и неочевидное поведение какой-то части программы
- Чтобы лучше понять как работает собственный код
- Чтобы наконец понять как работает чужой код 🙂
Мы очень надеемся, что теперь вы перестанете бояться ошибок в ваших приложениях, какими бы сложными они ни были.
Дальше научимся делать так, чтобы сложные ошибки появлялись в наших программах как можно реже!
Отладка в браузере
Давайте отвлечёмся от написания кода и поговорим о его отладке.
Отладка – это процесс поиска и исправления ошибок в скрипте. Все современные браузеры и большинство других сред разработки поддерживают инструменты для отладки – специальный графический интерфейс, который сильно упрощает отладку. Он также позволяет по шагам отследить, что именно происходит в нашем коде.
Мы будем использовать браузер Chrome, так как у него достаточно возможностей, в большинстве других браузеров процесс будет схожим.
Панель «Исходный код» («Sources»)
Версия Chrome, установленная у вас, может выглядеть немного иначе, однако принципиальных отличий не будет.
- Работая в Chrome, откройте тестовую страницу.
- Включите инструменты разработчика, нажав F12 (Mac: Cmd + Opt + I ).
- Щёлкните по панели Sources («исходный код»).
При первом запуске получаем следующее:
Кнопка-переключатель откроет вкладку со списком файлов.
Кликните на неё и выберите hello.js в дереве файлов. Вот что появится:
Интерфейс состоит из трёх зон:
- В зоне File Navigator (панель для навигации файлов) показаны файлы HTML, JavaScript, CSS, включая изображения, используемые на странице. Здесь также могут быть файлы различных расширений Chrome.
- Зона Code Editor (редактор кода) показывает исходный код.
- Наконец, зона JavaScript Debugging (панель отладки JavaScript) отведена для отладки, скоро мы к ней вернёмся.
Чтобы скрыть список ресурсов и освободить экранное место для исходного кода, щёлкните по тому же переключателю .
Консоль
При нажатии на клавишу Esc в нижней части экрана вызывается консоль, где можно вводить команды и выполнять их клавишей Enter .
Результат выполнения инструкций сразу же отображается в консоли.
Например, результатом 1+2 будет 3 , а вызов функции hello("debugger") ничего не возвращает, так что результатом будет undefined :
Точки останова (breakpoints)
Давайте разберёмся, как работает код нашей тестовой страницы. В файле hello.js щёлкните на номере строки 4 . Да-да, щёлкайте именно по самой цифре, не по коду.
Ура! Вы поставили точку останова. А теперь щёлкните по цифре 8 на восьмой линии.
Вот что в итоге должно получиться (синим это те места, по которым вы должны щёлкнуть):
Точка останова – это участок кода, где отладчик автоматически приостановит исполнение JavaScript.
Пока исполнение поставлено «на паузу», мы можем просмотреть текущие значения переменных, выполнить команды в консоли, другими словами, выполнить отладку кода.
В правой части графического интерфейса мы видим список точек останова. А когда таких точек выставлено много, да ещё и в разных файлах, этот список поможет эффективно ими управлять:
- Быстро перейдите к точке останова в коде (нажав на неё на правой панели).
- Временно отключите точку останова, сняв с неё галочку.
- Удалите точку останова, щёлкнув правой кнопкой мыши и выбрав Remove (Удалить).
- …и так далее.
Условные точки останова
Щелчок правой кнопкой мыши по номеру строки позволяет создать условную точку останова. Она сработает только в тот момент, когда выражение, которое вы должны указать при создании такой точки, истинно.
Это удобно, когда нам нужно остановиться только при определённом значении переменной или для определённых параметров функции.
Команда debugger
Выполнение кода можно также приостановить с помощью команды debugger прямо изнутри самого кода:
function hello(name) < let phrase = `Привет, $!`; debugger; //
Такая команда сработает только если открыты инструменты разработки, иначе браузер ее проигнорирует.
Остановимся и оглядимся
В нашем примере функция hello() вызывается во время загрузки страницы, поэтому для начала отладки (после того, как мы поставили точки останова) проще всего её перезагрузить. Нажмите F5 (Windows, Linux) или Cmd + R (Mac).
Выполнение прервётся на четвёртой строчке (где находится точка останова):
Чтобы понять, что происходит в коде, щёлкните по стрелочкам справа:
- Watch – показывает текущие значения для любых выражений. Вы можете нажать на + и ввести выражение. Отладчик покажет его значение, автоматически пересчитывая его в процессе выполнения.
- Call Stack – показывает цепочку вложенных вызовов. В текущий момент отладчик находится внутри вызова hello() , вызываемого скриптом в index.html (там нет функции, поэтому она называется “анонимной”). Если вы нажмёте на элемент стека (например, «anonymous»), отладчик перейдёт к соответствующему коду, и нам представляется возможность его проанализировать.
- Scope показывает текущие переменные. Local показывает локальные переменные функций, а их значения подсвечены прямо в исходном коде. В Global перечисляются глобальные переменные (то есть вне каких-либо функций). Там также есть ключевое слово this , которое мы ещё не изучали, но скоро изучим.
Пошаговое выполнение скрипта
А теперь давайте пошагаем по нашему скрипту.
Для этого есть кнопки в верхней части правой панели. Давайте рассмотрим их.
– «Resume»: продолжить выполнение, быстрая клавиша F8 .
Возобновляет выполнение кода. Если больше нет точек останова, то выполнение просто продолжается, без контроля отладчиком.
Вот, что мы увидим, кликнув на неё:
Выполнение кода возобновилось, дошло до другой точки останова внутри say() , и отладчик снова приостановил выполнение. Обратите внимание на пункт «Call stack» справа: в списке появился ещё один вызов. Сейчас мы внутри say() .
– «Step»: выполнить следующую команду, быстрая клавиша F9 .
Выполняет следующую инструкцию. Если мы нажмём на неё сейчас, появится alert .
Нажатие на эту кнопку снова и снова приведёт к пошаговому выполнению всех инструкций скрипта одного за другим.
– «Step over»: выполнить следующую команду, но не заходя внутрь функции, быстрая клавиша F10 .
Работает аналогично предыдущей команде «Step», но ведёт себя по-другому, если следующая инструкция является вызовом функции (имеется ввиду: не встроенная, как alert , а объявленная нами функция).
Если сравнить, то команда «Step» переходит во вложенный вызов функцию и приостанавливает выполнение в первой строке, в то время как «Step over» выполняет вызов вложенной функции незаметно для нас, пропуская её внутренний код.
Затем выполнение приостанавливается сразу после вызова функции.
Это хорошо, если нам не интересно видеть, что происходит внутри вызова функции.
– «Step into», быстрая клавиша F11 .
Это похоже на «Step», но ведёт себя по-другому в случае асинхронных вызовов функций. Если вы только начинаете изучать JavaScript, то можете не обращать внимания на разницу, так как у нас ещё нет асинхронных вызовов.
На будущее просто помните, что команда «Step» игнорирует асинхронные действия, такие как setTimeout (вызов функции по расписанию), которые выполняются позже. «Step into» входит в их код, ожидая их, если это необходимо. См. DevTools manual для получения более подробной информации.
– «Step out»: продолжить выполнение до завершения текущей функции, быстрая клавиша Shift + F11 .
Продолжает выполнение и останавливает его в самой последней строке текущей функции. Это удобно, когда мы случайно вошли во вложенный вызов, используя , но это нас не интересует, и мы хотим продолжить его до конца как можно скорее.
– активировать/деактивировать все точки останова(breakpoints).
Эта кнопка не влияет на выполнение кода, она лишь позволяет массово включить/отключить точки останова.
– включить/отключить автоматическую паузу в случае ошибки.
При включении, если открыты инструменты разработчика, ошибка при выполнении скрипта автоматически приостанавливает его. Затем мы можем проанализировать переменные в отладчике, чтобы понять, что пошло не так. Поэтому, если наш скрипт умирает с ошибкой, мы можем открыть отладчик, включить эту опцию и перезагрузить страницу, чтобы увидеть, где он умирает и каков контекст в этот момент.
Continue to here
Щелчок правой кнопкой мыши по строке кода открывает контекстное меню с отличной опцией под названием «Continue to here» («продолжить до этого места»).
Это удобно, когда мы хотим перейти на несколько шагов вперёд к строке, но лень устанавливать точку останова (breakpoint).
Логирование
Чтобы вывести что-то на консоль из нашего кода, существует функция console.log .
Например, это выводит в консоль значения от 0 до 4 :
Отладчик
Отладчик является встроенным в конфигуратор инструментом. Он помогает отлаживать программные модули, создаваемые в процессе разработки прикладного решения. Отладчик позволяет отслеживать последовательность выполнения операторов встроенного языка и просматривать значения переменных.
Основные возможности отладки
- отладка приложений, исполняемых на удаленных компьютерах, доступных по протоколу TCP/IP или HTTP,
- отладка кода, исполняемого рабочим процессом кластера серверов 1С:Предприятия 8; при этом поддерживается сквозной стек вызовов для клиента и сервера, и сквозная пошаговая отладка клиента и сервера;
- отладка кода, исполняемого в таких видах соединений как внешнее соединение, фоновое задание и WS-соединение?
- отладка мобильных приложений.
Точки останова
Отладчик позволяет установить на конкретную строку модуля специальный маркер — точку останова, — при достижении которой исполнение программного модуля останавливается и управление передается отладчику. Точки останова могут быть безусловными или с параметрами. При достижении безусловной точки останова исполнение программного модуля останавливается в любом случае:

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

Отладчик поддерживает возможность отключения точек останова. При этом строка модуля остается отмечена маркером, однако на ход исполнения модуля он никакого влияния не оказывает:

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

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

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

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

Текущее значение переменной также можно просмотреть, подведя указатель мыши к этой переменной. Текущее значение будет показано в виде короткой подсказки рядом с переменной.
Табло и локальные переменные
Результаты вычислений, которые требуется просматривать на протяжении выполнения некоторой части модуля, можно вывести в отдельное окно — табло, — в котором есть возможность распределить все просматриваемые выражения по четырем страницам:

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

В этом окне полностью функционирует контекстная подсказка.

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

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

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