21 April, 2009

Optimal project settings

Today I want to discuss various project's options, related to debugging.

What will we talk about?

Ok, let's see, what options do I mean: open your project's options and take a look at "Compiling" and "Linking" pages (those pages are called "Compiler" and "Linker" in old Delphi versions):

Compiling page in project's options (D2009)
"Compiling" page in project's options (D2009)
Linking page
"Linking" page in project's options (D2009)

On "Compiler" page we are interested in "Stack Frames", group of "Debug information", "Local Symbols" and "Symbol reference info", "I/O Checking", "Overflow checking" and "Range checking". On "Linking" page - "Map file", "Debug Information" (this option is known as "Include TD32 debug info" in previous Delphi versions) and "Include remote debug symbols".

Let's see what these options do. And then - what are the best settings for them and why. We will have in mind the scenario of usual application and EurekaLog-enabled application.

Besides, the project settings can differs for debug and release targets - i.e.: do you compile application for your self (for debugging) or for end-user deployment. There are settings profiles (Debug and Release) in new Delphi versions. You can specify individual settings for each profile and just toggle between them before compilation. Older Delphi version have only one global profile, so you have to change every single option manually.

Please, remember, that you need to make a full build (and not just compile), if you've changed one of those options.

What do these options mean?

The most important settings are set of options "Debug information" ("Compiling" page), "Local Symbols" and "Symbol reference info".

The program is set of machine's (CPU) instructions - which are just numbers. The source code is a text file. The question: how does the debugger know, when he needs to stop, when you are setting a breakpoint in your source? Where is correspondence between raw numbers and human-readable text?
This correspondence is a debug information. Roughly speaking, the debug information is set of instructions like: "the machine codes no. 1056-1059 correspond to line 234 of Unit1". The debugger works thanks to such debug info.

And these options? They controls the generation of debug information for your units.
The debug information is stored in dcu-files together with its compiled code. I.e. the very same Unit1.pas can be compiled into different dcu-files (with or without debug information). The debug information increases compilation time, size of dcu-files, but it does not affect size or speed of resulting application (i.e. debug information is not included into application).
There are cases, when you want to have debug information in your files or (at least) near them. For example: if you are going to do remote debugging or debugging of external process. OR if you want to have human-readable call-stack in your exception diagnostic tool (EurekaLog).

You can embed debug information into your files by two ways: either you play with project's options (using "Map File", "Debug information" (Linker)/"Include TD32 Debug info" or "Include remote debug symbols" options) OR you use some sort of expert (for example, EurekaLog or JCL), which injects debug information in his own format into your executables.
  • "Debug information" – that is the debug info itself. You should enable this option if you want to do step-by-step debugging or have call stacks with names. EurekaLog enables this option automatically for you. But if you mess with your project's settings...
  • "Local symbols" – it is "addon" for usual debug information. This is correspondence between program's data and variables names in source code. You need to enable this option if you want to see and change variables. Also, call stack window in Delphi can display function's arguments with this option.
  • "Reference info" – this is additional information for code editor, which allows him to display detailed information about identificators. For example: where were a variable declared.
Those options are very close related and usually there is no need to enable or disable only one of them - they are switched together.
  • "Use Debug DCUs" - this very important option switches compilation between using debug and release versions of standard Delphi's units. If you were attentive, then you could notice that real pas-files in Source folder are never used during compilation. Instead, the precompiled files (in dcu) are used. They are taken from Lib or Lib\Debug folders. This trick greatly decreases compilation time. Because dcu can be compiled with and without debug information - there are two sets of dcus in Lib folder. By toggling this option you'll specify which one Delphi should use for you. If you switch this option off - then you won't be able to debug standard Delphi code or see detailed call stack for it.
  • "Stack Frames" - this option controls stack frames generation. If the option is off then stack frames won't be generated unless they are needed. If the option is on - then stack frames will be generated always. Stack frames are used for frame-based stack-tracing method (the raw-tracing method do not require stack frames). I.e. it is used for building call stack. In usual application stack frames are generated almost everywhere.
  • "Range checking" - this is a very useful helper for debugging problems with array-based structures. With it, compiler will insert additional checks (for strings, arrays, etc), which checks the correctness of indexes. If you (by mistake) pass an invalid index - the exception of type ERangeError will be generated. And you can find your error. If the option is off then there is no additional code. Enabling this option slightly increases size of your application and slows down it execution. It is recommended to turn this option for debugging only.
  • "Overflow checking" - it is somehow similar to "Range checking", except checking code checks overflows in arithmetic operations. If result of operation is not suitable for storage variable - then exception EIntOverflow will be raised. For example: we have a byte variable, which holds 255 now. And we add 2 to it. There should be 257, but it can not be stored in byte variable, so real result will be 1. That is integer overflow. This option is actually rarely used. There are three reasons for it: different code often depends on it to be turned off. That means that enabling this option will break such code. Secondly: usually you work with Integer, and your logic rarely involves dealing with values on Integer's range border. And third: arithmetic operations is very common code, so adding additional checks to every operation can slow down your code significantly.
  • "I/O Checking" - this option is used for working with "files in Pascal-style" (AssignFile, Reset, etc), This feature is deprecated and you shouldn't use these routines and therefore you don't need to touch this option.
  • "Map file" - by enabling this option you tell the Delphi's linker to create a separate map-file along with your executable. Map file contains human-readable representation of debug information. Different settings for this option controls the detalization level of output. Usually, there is no need to change it to anything, which differs from "Off" or "Detailed". The map-file is used by various tools as primary source of debug information. For example, EurekaLog automatically turns this option on and uses map-file to create a debug information in its own format and then injects it into application. That is why you rarely need to change this option manually.
  • "Debug Information" (Linker)/"Include TD32 debug info" - this option embeds debug information for external debugger in TD32 format into your application. You may need this option if you use "Run"/"Attach to process" and Delphi can not find debug information. Note, that size of your application can increase 5-10 times by enabling this option. So, it is not good choice for storing debug info in release version - it is better to use EurekaLog (or any other source of debug information, which is supported by EL; for example - JCL).
  • "Include remote debug symbols" - very similar to previous option, but this creates a rsm-file with debug information for Delphi remote debugger. You need this option, if you want to do remote debugging.
Note, that these options can be enabled not only globally, but also separately for each unit (several options can affect single routines or, even, lines of code). This is done by using usual compiler directives (you can see them in help - by pressing F1 while you stay in project's options dialog). For example, "Stack Frames" is controlled by {$W+} and {$W-}.

So, by summarizing all this info, we can give a recommendations for different cases. They are written down below. Settings, which differs from defaults are marked in bold (i.e. you should toggle them manually).

Please note, that external tools may also have options, which affects your application. For example, we already have discussed such options for EurekaLog.

Usual application, without EurekaLog

Base settings for each profile

All debug options ("Debug information" (Compiler), "Local symbols", "Reference info") does not affect the resulting application and do not disturb us - so you usually should keep them always on.
"Use Debug DCUs" - set it as you like (depending on: "do you want to debug standard Delphi code or not?").
There is no need to turn on "Stack Frames" option.
There is no need for map-files.

Debug profile

Turn on "Range checking" and (optionally) "Overflow checking".
"Include TD32 debug info" - enable it only if you use "Attach to process" while debugging.
"Include remote debug info" - enable it only if you want to use remote debugger.

Release profile

Turn off "Range checking", "Overflow checking", "Include TD32 debug info" and "Include remote debug info".

EurekaLog-enabled application

Base settings for each profile

All debug options ("Debug information" (Compiler), "Local symbols", "Reference info") should be definitely turned on. Otherwise, call stack functionality won't be working.
There is no need to turn OFF "Stack Frames" option.
Generation of map-file should be turned on, but EurekaLog's expert takes cares of it.

Debug profile

"Use Debug DCUs" - set it as you like.
Turn on "Range checking" and (optionally) "Overflow checking".
"Include TD32 debug info" - enable it only if you use "Attach to process" while debugging.
"Include remote debug info" - enable it only if you want to use remote debugger.

Release profile

Turn on "Use Debug DCUs" option.
Turn off "Range checking", "Overflow checking", "Include TD32 debug info" and "Include remote debug info".

Note: if you don't do many index-based operations (so additional checks will not slow down your code) - then it may be a good idea to always keep "Range checking" on.

Debugging tips from Delphi 2009 Live!
Shot from Delphi 2009 Live!

What can go wrong if I mess up with my settings?

Well, that means, for example, impossibility of debugging (say, missed information for remote debugger or turned off "Debug information" (Compiler) option), large size of application (for example - you forgot to turn off "Debug information" (Linker)/"Include TD32 debug info"), slow execution (for example: application was compiled with debug code), missed or partial call stacks in EurekaLog (for example, you managed to turn off "Debug information" (Compiler) or you do not use "Use Debug DCUs"). In rare cases there can be work/not-work difference (for example, swithing on "Stack frames" can slightly decrease maximum depth of recursive algorithm). There are few other minor issues.

BTW, if you develop a component - do not forget that Delphi have two sets of dcus. Generally, toggling debug options does not affect interface and implementation parts of dcus. So, different versions of dcus are compatitible with each other. But it is not always so - code may use conditional defines. That is why it is possible to have binary incompatible versions of dcu-files of the same source code (and here is a real-life example with D2009 and InstanceSize method).

For this reason, you should also create a two sets of dcu-files: first one should be compiled against usual dcu ("Use Debug DCUs" turned off) and the other one - with debug dcu-files ("Use Debug DCUs" turned on). BTW, it is not important, how you'll set "Debug information" (Compiler) option for your component (of course, it is only from POV of binary compatibility).