IT-Блог о программировании и операционных системах

Отладка в Delphi 2010. Точки останова.

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

Средства отладки в Delphi

Точки останова на код

Точки останова (Breakpoint) – это прерывание хода выполнения программы в том месте, где была установлена точка останова. На этом этапе, если программа запущена под отладчиком (из среды), она будет продолжать выполнение, пока не дойдёт до точки останова. После этого выполнение программы останавливается (но не завершается). Теперь можно посмотреть значения переменных, например, или продолжить выполнение программы пошагово (трассировать).
Интегрированный отладчик Delphi использует два типа точек останова (далее по тексту: BP ) – условные и безусловные. На безусловных BP отладчик будет останавливаться всегда, в то время как на условных остановится только в том случае, если заданное условие выполнится.

Установка безусловных точек останова

Устанавливать точки останова можно различными методами. Можно установить безусловный BP простым кликом по полоске в редакторе кода, левее требуемой инструкции (рис. 1). Строка, на которую был поставлен BP окрасится в другой цвет, а на месте клика появится красная точка, которая символизирует точку останова. Другим способом установки безусловного BP является нажатие клавиши F5, будучи на той инструкции, на которой планируется останавливать отладчик.


Установка breakpoint

Установка условных точек останова

Как уже было сказано, кроме безусловных точек останова, можно использовать так называемые условные BP. Условные точки устанавливаются из главного меню, а именно Run | Add Breakpoint | Source Breakpoint. После, появится диалоговое окно, в котором, в поле “Condition” нужно указать условие останова в виде логического выражения.


Диалог установки точки прерывания на код

После этого, программа прервётся на указанном месте только тогда, когда переменная “i” примет значение “3”. В окне установки BP есть дополнительные поля, введённые для других нужд:
Filename – поле, в котором указывается, в каком файле устанавливается BP.
Line number – номер строки, на которой нужно установить BP.
Thread – здесь можно указать поток, к которому будет применена точка останова. Можно задать номер потока или если он именован, задать его имя.
Pass count – количество холостых проходов программы через точку без прерывания на ней.
Group – можно указать имя для группы, к которой будет относиться точка прерывания. Точки останова можно разделять на логические группы и затем активировать или деактивировать их все вместе. Например, очень удобно, если у вас все BP которые тормозят циклы, находятся например в группе “Cycle”, а точки, которые навешаны на вызовы WinAPI-фунций, в группе “API”.
Группа Actions:
Break – Определяет, будет ли точка останова, тормозить программу (как классический BP)
Ignore subsequent exceptions – если включено, то отладчик перестанет всплывать на любом последующем исключении. Я использую эту опцию для того, чтобы не брякаться второй раз на одном и том же месте, при обработке исключений.
Handle subsequent exceptions – действие, обратное предыдущему.
Log message – здесь можно указать текст, который появится в окне “Event log” (Ctrl+Alt+V) вашего IDE Delphi, когда сработает BP. В конце этой строки отладчик автоматически добавит имя процесса и его дескриптор. Полезность сомнительная.
Eval expression – отладчик вычислит выражение из этого поля и покажет его в “Event log”. Очень полезное поле. Можно например быстро посмотреть значение переменной или результат выражения на момент срабатывания точки останова. “Птичка Log result” при этом должна быть “чекнута”, иначе результат выражения нигде не показывается. Другими словами, без этого чекбокса, поле “Eval expression” абсолютно бесполезно.
Enable group – активирует все точки останова выбранной группы, после того как настраиваемый BP сработал.
Disable group – деактивирует все точки останова выбранной группы.
Последние два поля используются для того, чтобы например если сработал один BP, а следом за ним стоит ещё несколько, из этой же или из другой группы, можно было бы их отключить автоматически, чтобы при продолжении выполнения программы, не брякнуться ещё раз где-то рядом.
Log call stack – включает логирование всего стека вызовов или нескольких фреймов на момент срабатывания точки. Собственно говоря, эта опция – синоним отладочному окну “Call stack” (Ctrl+Alt+S).

Точки останова на адрес и данные

Такие точки останова возможно устанавливать только в процессе прогона программы под отладчиком. Они позволяют останавливать выполнение в тех случаях, если программа обращается по адресу (Address breakpoint) или если программа читает изменяет данные (переменные, константы и записи. Data Breakpoint). Когда срабатывает такой BP, отладчик автоматически вываливается на следующую инструкцию после той, которая выполнила обращение к адресу или данным. Установить такие BP можно из главного меню Run | Add breakpoint | Address / Data breakpoint. К слову, точки останова такого вида удобнее всего использовать, когда отладка протекает непосредственно с использованием окна низкоуровневой отладки – CPU (Ctrl+Alt+C). BP на адрес мало чем интересна, а вот на точки останова да запись данных мы рассмотрим.


Для этого нам понадобится небольшой пример кода, который мы будем отлаживать. Мы создадим его позже. Сначала нужно отключить оптимизацию, чтобы отладчик не был слишком самостоятельным и не выключал наши точки останова. Выключить оптимизацию можно так: Project | Options | Compiling | Optimization > False. Если отладочная информация не включена в проект, то нужно включить, но обычно по дефолту она всё же включена. Если нет, то в том же окне настроек проекта включите “Debug information”, “Local symbols” и “Refference Info”. Я думаю все знают, за что означают эти опции, так как у меня нет ни малейшего желания расписывать их (лень-матушка обуяла). Тем более, их уже расписал Александр Алексеев в своём переводе “Настройки проектов в Delphi с точки зрения поиска ошибок”, очень советую, если возникают вопросы.
Итак, пример. Он будет тривиальным, может быть даже глупым, но он всё же достаточно наглядным, так что сделайте ему “скидку”:
  1. Определите глобальные переменные “i” и “j” типа “Integer”.
  2. Создайте на форме две управляющие кнопки.
  3. На обработчики OnClick для каждой кнопки напишите инструкции: “i:=1;” и “j:=1;”.
  4. PROFIT.


Нам нужно установить точку останова на запись в переменные “i” и “j”. Для этого запустите проект на выполнение и вызовите диалог “Add data breakpoint” так, как я описал выше. Хоть и выглядит он довольно уныло и очень похожим на диалог установки “Source breakpoint”, в нём есть некоторые моменты, о которых стоит сказать пару слов.


Диалог установки бряка на запись данных
Address – адрес переменной, на который нужно установить точку останова. Следует заметить тот факт, что если отладчик сможет “сопоставить” адрес и строку исходника, он автоматом изменит тип BP на Source breakpoint.
Length – размер данных. Автоматически рассчитывается отладчиком для всех стандартных типов, но можно указать и свой размер.
В остальном, все поля идентичны тем, что в диалоге установки бряков на исходник. Теперь в поле адреса введите или непосредственный адрес самой переменной (если вдруг знаете) или для того чтобы отладчик вычислил его автоматически введите “@i” или “addr(i)” и нажмите “OK”. Повторите эти манипуляции и с переменной “j”. Теперь попробуйте нажать на кнопки, отладчик будет вываливаться после каждой операции присваивания переменным “i” и “j”.
Даже если переменную будет писать функция или процедура из другого модуля, отладчик всё равно это “увидит” и покажет нам место, где произошла запись. Кстати о модулях!

Точки останова “на модули”

Собственно, ничего в них интересного нет. Срабатывают тогда, когда подгружаются различные модули. Например BPL, DLL, EXE или OCX. Устанавливаются из главного меню Run | Breakpoint | Module load breakpoint. В диалоговом окне нужно указать (или выбрать) тот модуль, на загрузку которого вы хотите поставить точку останова.

Установка BP програмно

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

procedure foo;
 var i:integer;
begin
 asm
  db $CC ; int 3 также будет работать.
 end;
 i:=i+1; // вывалимся в отладчик на этом месте
end;

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

7 комментариев:

  1. А не подскажете каким образом можно например поставить брэкпойнты во всех методах в модуле?

    ОтветитьУдалить
  2. Если я вас правильно понял, вам нужно одним "махом" поставить бряк на все методы класса? Так не получится. Можно устанавливать бряки на методы класса таким образом:

    1) Непосредственно из окна редактора. Встаём на строку "begin" любого класса и жмём F5 (Source breakpoint)
    2) Запускаем программу под отладчиком. Run | Add breakpoint | Address breakpoint | Вводим адрес метода класса, или "@TMyClass.MyMethod".
    3) Брейк на изменение полей класса производится также, как и на обычные переменные, только перед названием поля обязательно ставят имя объекта. Например: "@Form2.FMyField".

    C уважением, Егор.

    ОтветитьУдалить
  3. Ну, можно попробовать написать IDE эксперт, который это делает :)

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

    ОтветитьУдалить
  4. Да скорее напишу тогда отдельный тул.
    Просто когда хочу весь модуль отладить лень по всем методам ходить ставить бряки. А тем более когда надо еще все убрать но запускать под отладчиком а потом все заново расставлять...
    А так сделаю чтобы
    procedure А;
    begin
    ...........
    end;
    заменялось на:
    procedure А;
    begin {$IFDEF BRKP} asm db $CC end; {$ENDIF}
    ...........
    end;
    тогда можно управлять включением/отключением с помощью условной компиляции
    За программный способ установки спасибо - не знал.
    >>Но польза сомнительна... ибо это вам наверняка надо только один раз - можно и руками поставить.
    Да, мы программисты такие - готовы несколько часов потратить на написание чего-либо что по нашему мнению избавляет от часа(в общей сложности) монотонной работы =)

    ОтветитьУдалить
  5. >>> А тем более когда надо еще все убрать

    А зачем убирать? Breakpoints | Disable all.

    ОтветитьУдалить
  6. >>> А тем более когда надо еще все убрать
    Ещё можно использовать группы.

    ОтветитьУдалить
  7. >Breakpoints | Disable all.
    Это вроде на весь проект работает?
    Группы - ну тоже самое, расставлять то в ручную все.
    В общем это из той же оперы как рефакторинг - можно сделать вручную, а можно автоматизировать.

    ОтветитьУдалить