Система Orphus

19 апреля 2009 г.

Настройки проектов в Delphi с точки зрения поиска ошибок

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

О чём идёт речь

Сначала, давайте посмотрим на них: открываем Project/Options. Нас будут интересовать вкладки Compiling и Linking (в старых версиях Delphi они назывались Compiler и Linker):

Вкладка Compiling
Вкладка Compiling в D2009

Вкладка Linking
Вкладка Linking в D2009

На вкладке «Compiler» нас будут интересовать опции «Stack Frames», группа «Debug information», «Local Symbols» и «Symbol reference info», «I/O Checking», «Overflow checking» и «Range checking». На «Linking» - «Map file», «Debug Information» (известная в предыдущих версиях Delphi как «Include TD32 debug info») и «Include remote debug symbols».

Давайте посмотрим, за что отвечают эти опции. А затем - как их лучше бы всего расставить. При этом мы будем рассматривать такие ситуации: обычное приложение и приложение с механизмом диагностики исключений.
Кроме того, настройки проекта могут отличаться, компилируете ли вы приложение для себя или для распространения. В новых версиях Delphi появились профили настроек (Debug и Release, соответственно). Вы можете задать свой набор настроек в каждом профиле, а затем переключаться между ними. В старых Delphi есть только один профиль настроек и вам нужно менять каждую настройку вручную.

Напомним, что при смене любой из опций необходимо сделать полный Build проекту (а не просто Compile).

Что означают эти опции?

Самыми важными настройками являются группа опций «Debug information», «Local Symbols» и «Symbol reference info».
Программа представляет собой набор машинных команд. Текст программы представляет собой текстовый файл. Вопрос: как отладчик узнаёт, когда надо остановиться, если вы поставили бряк на строку в тексте? Где же соответствие между текстовым файлом и набором байт в exe-файле? Вот для такой связи и служит отладочная информация. Это, грубо говоря, набор инструкций типа: «машинные коды с 1056 по 1059 относятся к строке 234 модуля Unit1.pas». Вот с помощью такой информации и работает отладчик. Указанные выше опции отвечают за генерацию отладочной информации для ваших модулей.

Отладочная информация сохраняется вместе с кодом модуля в dcu-файле. Т.е. один и тот же Unit1.pas может быть скомпилирован как с отладочной информацией, так и без неё - в разные dcu файлы. Отладочная информация увеличивает время компиляции, размер dcu-файлов, но не влияет на размер и скорость работы полученного exe-файла (т.е. отладочная информация не подключается к exe-файлу).

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

Подключение отладочной информации к приложению осуществляется несколькими способами: либо это опции проекта (а именно: «Map File», «Debug information» (Linker)/«Include TD32 Debug info» или «Include remote debug symbols»), либо это возможности всевозможных экспертов (типа EurekaLog, JCL или madExcept), которые добавляют отладочную информацию в программу в своём формате.
  • «Debug information» (директива {$D+} или {$D-}) – это собственно и есть отладочная информация. Т.е. соответствие между текстом программы и её машинным кодом. Вы должны включить эту опцию, если хотите ставить бряки, выполнять пошаговую отладку, а также иметь стек с именами для своего кода. Часто эту опцию включают автоматически различные эксперты типа EurekaLog.
  • «Local symbols» (директива {$L+} или {$L-}) – является дополнением к отладочной информации. Она отвечает за соответствие между данными в exe-файле и именами переменных. Если вы включаете эту опцию, то отладчик позволит вам просматривать и изменять переменные. Также окно «Call Stack» будет способно отражать переданные в процедуры параметры.
  • «Reference info» – это дополнительная информация для редактора кода, которая позволяет ему отображать более подробную информацию об идентификаторах. Например, где была объявлена переменная.
Эти три опции тесно связаны и обычно нет смысла включать/выключать их по одной.
  • «Use Debug DCUs» - эта опция переключает сборку вашей программы с отладочными модулями Delphi или с обычными. Если вы внимательно посмотрите на установку Delphi, то увидите, что pas файлы из папки Source никогда не используются для компиляции - а только для отладки. Для компиляции же используются уже готовые dcu-файлы из папки Lib. Это уменьшает время компиляции. Поскольку dcu могут быть скомпилированы с отладочной информацией и без неё, то в папке Lib есть два набора dcu-файлов: обычные и отладочные. Переключая эту опцию, вы указываете, какие из них использовать. Если вы выключите эту опцию, то не сможете заходить по F7 в стандартные функции и методы Delphi (т.к. для них будет отсутствовать отладочная информация). Также при выключенной опции вы не будете видеть информацию о стандартном коде в стеке вызовов.
  • «Stack Frames» - эта опция отвечает за генерацию стековых фреймов. Если опция выключена, то стековый фрейм не генерируется без необходимости. Если она включена -то фрейм генерируется всегда. Стековые фреймы используются при построении стека вызовов по фреймам (построение методом raw-сканирование не нуждается в стековых фреймах). В обычном приложении стековые фреймы генерируются практически всегда (*).
  • «Range checking» - служит помощником в поиске проблем при работе, например, с массивами. Если её включить, то для любого кода, который работает с массивами и строками, компилятор добавляет проверочный код, который следит за правильностью индексов. Если при проверке обнаруживается, что вы вылезаете за границы массива, то будет сгенерировано исключение класса ERangeError. При этом вы можете идентифицировать ошибку обычной отладкой. Если же опция выключена, то никакого дополнительного кода в программу не добавляется. Включение опции немного увеличивает размер программы и замедляет её выполнение. Рекомендуется включать эту опцию только в отладочной версии программы.
  • «Overflow checking» - похожа на опцию «Range checking», только проверочный код добавляется для всех арифметических целочисленных операций. Если результат выполнения такой операции выходит за размерность (происходит переполнение результата), то возбуждается исключение класса EIntOverflow. Пример – к байтовой переменной, равной 255, прибавляется 2. Должно получиться 257, но это число больше того, что помещается в байте, поэтому реальный результат будет равен 1. Это и есть переполнение. Эта опция используется редко по трём причинам. Во-первых, самый разный код может рассчитывать на то, что эта опция выключена (часто это различного рода криптографические операции, подсчёт контрольной суммы и т.п., но не только). В связи с этим при включении этой опции могут начаться совершенно различные проблемы. Во-вторых, в обычных ситуациях работают с четырёхбайтовыми знаковыми величинами, и работа около границ диапазонов представления происходит редко. В-третьих, арифметические операции с целыми – достаточно частый код (в отличие от операций с массивами), и добавление дополнительной работы на каждую операцию иногда может быть заметно (в смысле производительности).
  • «I/O Checking» - эта опция используется только при работе с файлами в стиле Паскаля, которые считаются устаревшими. По-хорошему, вы не должны использовать их и, соответственно, эту опцию.
  • «Map file» - включение опции заставляет линкёр Delphi создавать вместе с проектом map-файл. Различные установки опции отвечают за уровень детализации и обычно имеет смысл ставить только Off или Detailed. Map файл обычно используется всевозможными утилитами типа EurekaLog, JCL или madExcept в качестве первичного источника для создания отладочной информации в своём формате. Поэтому руками устанавливать эту опцию вам придётся крайне редко - эксперты включают её самостоятельно по необходимости.
  • «Debug Information» (Linker)/«Include TD32 debug info" - внедряет в приложение отладочную информацию для внешнего отладчика в формате TD32. Обычно эта опция включается, если вы отлаживаете проект через Attach to process и Delphi не может найти отладочную информацию. При включении этой опции размер приложения увеличивается в 5-10 раз. Поэтому, если вам нужна отладочная информация в распространяемом приложении - лучше рассмотреть другие варианты (лучше всего подходит отладочная информация в специализированных форматах - EurekaLog, JCL, madExcept).
  • «Include remote debug symbols" - заставляет линкёр создать rsm-файл вместе с проектом, в который записывается информация для удалённого отладчика Delphi. Вам нужно включать эту опцию, если вы хотите выполнить удалённую отладку. Полученный rsm-файлик нужно копировать вместе с приложением на удалённую машину.
Замечу также, что эти опции можно выставлять и локально - как для целого модуля, так и для отдельной функции/процедуры (а для некоторых опций - даже для участка кода). Делается это обычными директивами компилятора, узнать которые вы можете, нажав F1 в окне настроек. Например, «Stack Frames» регулируется {$W+} и {$W-}.

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

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

Обычное приложение без механизма диагностики исключений

Общие настройки для любых профилей

Все опции отладки («Debug information» (Compiler), «Local symbols», «Reference info») вообще на готовый модуль не влияют, т.к. отладочная информация в exe/DLL не хранится, ну и нам жить не мешают => поэтому смысла их выключать я не вижу («Use Debug DCUs» - устанавливайте по вкусу, смотря по тому, хотите вы отлаживаться в стандартных модулях или нет).

«Stack Frames» вообще включать незачем.

Генерацию map-файла выключаем.

Профиль Debug

Включаем «Range checking» и (по вкусу) «Overflow checking».

«Include TD32 debug info» - включаем только для отладки внешнего процесса.

Соответственно, «Include remote debug info» - включаем только для удалённой отладки.

Профиль Release

Выключаем «Range checking», «Overflow checking», «Include TD32 debug info» и «Include remote debug info».

Приложение с механизмом диагностики исключений (типа EurekaLog, JCL или madExcept)

Общие настройки любых профилей

Все опции отладки («Debug information» (Compiler), «Local symbols», «Reference info») держать включёнными, т.к. в противном случае не будет доступна отладочная информация. Соответственно, ваши информационные механизмы пойдут лесом.

«Stack Frames» - вообще вЫключать незачем.

Генерацию map-файла включаем, если об этом не позаботился эксперт (маловероятно).

Профиль Debug

«Use Debug DCUs» - по вкусу.
Включаем «Range checking» и (по вкусу) «Overflow checking».

«Include TD32 debug info» - включаем только для отладки внешнего процесса.

«Include remote debug info» - включаем только для удалённой отладки.

Профиль Release

Включаем «Use Debug DCUs».

Выключаем «Range checking», «Overflow checking», «Include TD32 debug info» и «Include remote debug info».

Примечание: если вы используете мало операций с индексами в своей программе (так что дополнительные проверки не замедлят её), то будет хорошей идеей всегда держать опцию «Range checking» включённой.

Debugging tips from Delphi 2009 Live!
Фото с Delphi 2009 Live!


Что может пойти не так, если настройки будут заданы неверно?

Ну, во-первых, это невозможность отладки (например, отсутствие информации для удалённого отладчика или выключенная опция «Debug information» (Compiler)), большой размер приложения (например, случайно забыли выключить «Debug information» (Linker)/«Include TD32 debug info»), медленная работа (например, компиляция с отладочным кодом), отсутствие или неполный стек вызовов в средствах диагностики исключений (например, выключили «Debug information» (Compiler)). В очень редких и запущенных случаях переключение опций может сказаться на работоспособности программы (например, установка Stack frames может снизить максимально возможную глубину рекурсии). Ну и недочёты по мелочи.

Кстати, если вы разрабатываете компоненты, то надо учитывать, что у Delphi есть именно два набора DCU-файлов, которые отличаются настройками компиляции. Вообще говоря, простое переключение опций, ответственных за генерацию отладочной информации никак не влияет на интерфейс и реализацию часть модуля. Но в общем случае код может использовать условные директивы. Поэтому может быть ситуация, когда два набора DCU файлов (скомпилированных с разными опциями) не совместимы друг с другом - потому что они использовали какую-либо директиву и содержат разный код (а вот и пример).
Поэтому вам тоже надо иметь два набора DCU-файлов: один - скомпилированный с опцией «Use Debug DCUs», другой - без. Причём, не важно включена или выключена опция «Debug information» (Compiler) в ваших настройках в обоих случаях.

Примечания:
(*) Например:
procedure TForm1.Button1Click(Sender: TObject);

  procedure A;

    procedure B;
    begin // синяя точка слева от "begin" =>
          // эта функция имеет стековый фрейм
      ShowMessage(IntToStr(Integer(nil^)));
    end;

  begin // нет синей точки слева от "begin" =>
        // это очень короткая процедура, поэтому
        // стековый фрейм не нужен =>
        // он не создаётся.
    B;
  end;

begin // нет синей точки слева от "begin" =>
    // это очень короткая процедура, поэтому
    // стековый фрейм не нужен =>
    // он не создаётся.
  A;
end;
Button1Click состоит всего из двух инструкций: "call A; ret;". Она очень короткая и не использует аргументы или локальные переменные. Поэтому, очевидно, что ей не нужен стековый фрейм. Когда опция "Stack frames" выключена, то для Button1Click стековый фрейм не создаётся (но он создаётся, если опция "Stack frames" будет включена).

Но, для более сложных процедур стековые фреймы будут генерироваться вне зависимости от установки опции "Stack frames".

Например, тоже очень короткая процедура B всегда имеет фрейм. Причина: использование типа String в ShowMessage. Компилятору нужно вставить неявную строковую переменную и неявный try/finally для её освобождения, поэтому процедуре нужен фрейм.

В реальных приложениях фреймы генерируются для 99% процедур.

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

Анонимный комментирует...

Я уже писал тебе, Alex, на форуме Eurekalog.

Ты для профиля Release с использованием иструментов на подобие Eurekalog советушеь отключать RangeCheck erorr и прочая.

Но после этого она (Eurekalog) не ловит эти исключения.

Она их ловит, только если соответствувующие опции включены при Build проекта.

GunSmoker комментирует...

>>> Но после этого она (Eurekalog) не ловит эти исключения.
Какие исключения?

P.S. Пожалуйста, обратите внимание, что это рекомендации. Это не универсальное решение, которое подойдёт во всех ситуациях (эй, если бы оно им было, то у нас попросту не было бы стольких опций!).
Если у вас есть конкретные ограничения/требования на настройки проекта, разумеется, вы должны использовать их.
В частности, Range Check Errors отключается для Release по той причине, что включение опции замедляет работу программы. Это может быть сильно заметно, если ваша программа много работает с массивами или иной индексацией. Если вас это мало заботит (или ваша программа практически не работает с индексацией) - то вы определённо должны включить эту опцию.
Но это же не подойдёт для всех! Именно поэтому общий сценарий таков: включаем опцию только на стадии отладки.

Fr0sT комментирует...

>>«Overflow checking» Эта опция используется редко по трём причинам.
>>Во-первых, самый разный код может рассчитывать на то, что эта опция выключена (часто это различного рода криптографические операции, подсчёт контрольной суммы и т.п., но не только). В связи с этим при включении этой опции могут начаться совершенно различные проблемы.

Не встречал пока, хотя не сильно юзаю такие алгоритмя - только base64, пожалуй.

>> Во-вторых, в обычных ситуациях работают с четырёхбайтовыми знаковыми величинами, и работа около границ диапазонов представления происходит редко.
Вот здесь основные возражения. Вовсе не всегда используется Integer, зачастую идёт Cardinal - просто чтобы обозначить, что применяются натуральные числа. В этом случае безобидный код
var
c: cardinal;
arr: array of byte;

for c := 0 to Length(arr) - 1 do
arr[c] := c;

при пустом массиве зашарашит перебор по 4 млрд элементов, что рано или поздно приведёт к выходу за границу или AV. Есть еще ряд случаев при операциях с беззнаковыми числами, когда range check нужен. Поэтому я его всегда включаю.


«Use Debug DCUs»
Добавлю: при включении этой опции отладка по F7 (c заходом в подпрограммы) становится практически невозможной. Ибо к примеру на строке
SomeOurProc(Trim(Memo.Lines[i])+DateToStr(Now))
(если надо войти в SomeOurProc) придется перелопатить чуть ли не половину RTL - отладка заходит даже в "подводные" функции вроде конкатенации строк.

Анонимный комментирует...

Столкнулся с проблемой: D2007, отладчик работает, но при использовании CallStack переходит только в окно процессора. Т.е. остановка по бряку-показывает в редакторе где встал, кликаю по последней строке каллстека (там-строка на которой бряк)и открывается окно CPU, показывающее на начало кода этой строки. НО! при включении DebugDCU отлично открывает из каллстека в редакторе стандартные процедуры.

GunSmoker комментирует...

Не понятно, в чём проблема. Вроде так и должно быть.

Если Use Debug DCUs выключено, то отладочной информации по системному коду нет - вот вы и получаете свой CPU отладчик (вы же переходите куда-то в середину процедуры).

Анонимный комментирует...

Проблема в том, что при включенном DebugDCU переходит на системные процедуры (открывает исходник) но мои процедуры открывает только в CPU.
Вопрос задал так же на королевстве, там чуть более написано:
http://www.delphikingdom.com/asp/answer.asp?IDAnswer=80042

Анонимный комментирует...

подскажите пожалуста что делать если мне пишут error enable debug privelege?


Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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