Interactive Debugging

Information the Debugger Needs

The debugger needs certain information in order to perform tasks such as setting breakpoints and displaying the call stack. This information comes from three primary sources: the metadata contained within the assembly, the program database, and the JIT compiler tracking information.

In this section, I explain what types of information the debugger needs and how it uses the information. I also explain how to ensure that the information is available for debugging a Web service. Finally I offer recommendations for creating release and debug builds for Web service projects. The goal for release builds is to create the information that the debugger needs in order to effectively diagnose problems that might emerge in the production environment.

Assembly Metadata

From the .NET assembly's metadata, the debugger needs information about the types defined within the assembly. The debugger uses this information to display the friendly name of types, the methods they expose, and the names of instances of types and to populate the call stack, local watch windows, and so on. This metadata is always contained within a .NET assembly, so the debugger will always have enough information to display a call stack composed of friendly names.

Program Database

Some debugging features require more information than what is provided by the metadata contained within an assembly. For example, the assembly's metadata does not contain enough information to allow you to interactively step through the source code that implements the Web service.

To facilitate source code–level debugging, the debugger needs information about how to map the program image to its original source code. The program database, which can be optionally generated by the compiler, contains a mapping between the Microsoft intermediate language (MSIL) instructions within the assembly and the lines in the source code to which they relate.

The program database is in a separate file with a .pdb file extension and typically has the same name as the executable (.dll or .exe) with which it is associated. The .pdb file often resides in the same directory as its associated .dll or .exe.

The executable and the associated .pdb file generated by the compiler are considered a matched pair. The debugger will not let you use a .pdb file that is either newer or older than the executable running in the targeted process. When the compiler generates the executable and its associated .pdb file, it stamps both of them with a GUID, which the debugger uses to make sure that the correct .pdb file is loaded.

There is no equivalent mechanism for associating the .pdb file with the version of the source code from which it was created, so it is possible to interactively debug your application using an incorrect version of the source code. To avoid this situation, you should maintain tight version control over the executable, the .pdb file, and source control. At the very least, you should check all three into your source control database before deploying the database on an external machine.

The Visual C# compiler (csc.exe) generates a .pdb file if you specify the /debug switch. Table 11-1 describes all the variations of the Visual C# compiler /debug switch.

The first two items in the table are pretty straightforward. The third item requires further explanation. In the next section, I discuss why the .pdb file generated by the /debug:pdbonly switch cannot be used for source-level debugging by default.

Table 11-1  Visual C# Compiler Debugging Switches

Switch

Description

/debug, /debug+, or /debug:full

Specifies that the compiler will generate a .pdb file.

/debug-

Specifies that the compiler will not generate a .pdb file. This is the default setting.

/debug:pdbonly

Specifies that the compiler will generate a .pdb file. However, source-level debugging will be disabled by default.

You can also use the /optimize switch to specify whether your code will be optimized before being executed. By default, optimization is disabled—the same as specifying the /optimize- switch. However, this results in significant performance penalties.

You can enable optimization by specifying the /optimize+ switch. Doing so reduces the fidelity of source-code debugging, however. For example, code might appear to execute out of order or not at all. As a result, optimization is often disabled during development and then enabled before the application ships.

You can specify whether optimization is enabled or whether a .pdb file will be created for a Visual Studio .NET project by modifying the Generate Debugging Information and Optimize Code project settings in the Project Settings dialog box. To open this dialog box, select a project in the Solution Explorer and then choose Project, Properties, or right-click on the project and choose Properties.

Visual Studio .NET will automatically create two configurations for your project, Debug and Release. For the Debug configuration, Generate Debugging Information is set to true and Optimize Code is set to false. For the Release configuration, Generate Debugging Information is set to false and Optimize Code is set to true.

You will find that .pdb files can be invaluable for diagnosing problems, especially those that appear only in production. I strongly encourage you to generate .pdb files for every assembly you release to production. However, before I make recommendations about specific build settings, I need to paint a more complete picture.

Tracking Information

So far, I have told you only half the story. In the previous section, I discussed the behavior of the Visual C# complier as it relates to debugging. However, the Visual C# compiler does not generate the code that is ultimately executed and therefore debugged. It generates MSIL, and the resulting MSIL is compiled by the JIT compiler to native code before being executed by the processor.

When you debug a Web service, you attach your debugger to the process that is executing the output of the JIT compiler. The JIT compiler thus has just as much influence as the Visual C# compiler does over your ability to interactively debug the code for a Web service.

Recall that the program database generated by the Visual C# compiler maps the generated MSIL to the original source code. But because the MSIL is compiled by the JIT compiler before it is executed, the program database does not contain enough information to facilitate interactive debugging.

To facilitate interactive debugging, the debugger must be able to map the native code executing within the process to the MSIL and then to the source code. Half of the mapping, from the MSIL to the source code, is provided by the .pdb file. The other half, from the native machine code instructions to the MSIL, must be created by the JIT compiler at run time.

The mapping created by the JIT compiler is referred to as tracking information. Tracking information is generated whenever MSIL is compiled to native code by the JIT compiler. The debugger uses the combination of the information in the .pdb file and the tracking information generated by the JIT compiler to facilitate interactive source-code debugging.

With tracking disabled, you cannot perform source-level debugging on the targeted executable. When source code is compiled using the /debug switch, the resulting assembly will be marked to enable tracking. The JIT compiler learns of this because the assembly is decorated with the Debuggable attribute, whose IsJITTrackingEnabled property is set to true. When the JIT compiler loads the assembly, it looks for this attribute; the value of true for its IsJITTrackingEnabled property overrides the default behavior.

So why should you care whether tracking is enabled? Because when tracking is enabled, it imposes a slight performance penalty when your application is executed. Specifically, application warm-up is slightly slower because the JIT compiler has to generate the tracking information in addition to compiling the MSIL the first time a method is called.

Once a method has been JIT compiled, no additional costs are associated with tracking. Therefore, in most cases the benefits of improved debugging support for the Web service will outweigh the costs associated with tracking, especially for Web services. An instance of a Web service usually supports multiple requests from multiple clients, so the costs associated with generating the tracking information are quickly amortized away.

In some situations, however, you might not want to incur the costs associated with tracking unless the application is experiencing a problem. You can compile your application using the /debug:pdbonly switch so that the resulting assembly will have an associated .pdb file generated for it but will not have the Debuggable attribute's IsJITTrackingEnabled property set to true.

Note that you cannot configure the Visual Studio .NET build properties to invoke the same behavior that the /debug:pdbonly switch does. If you want to generate a .pdb file and not set the IsJITTrackingEnabled property within the assembly, you must use some other means of building the application.

If you suspect a problem with an application that was compiled using the /debug:pdbonly switch, you must enable tracking at run time. The two primary ways to enable tracking at run time are by using the debugger and by configuring an .ini file. Note that with the current version of .NET, modifications to the IsJITTrackingEnabled property take effect only when the application is reloaded by the common language runtime. Both methods of configuring tracking at run time require you to restart your application.

The first method of enabling tracking at run time is by creating an .ini file that is used to set the JIT compiler debugging options. The .ini file should have the same name as the application and should reside in the same directory. For example, the .ini file for MyRemotingWebService.exe would be named MyRemotingWebService.ini. The contents of the .ini file would look something like this:

[.NET Framework Debugging Control] GenerateTrackingInfo=1 AllowOptimize=0

This example configures the JIT compiler to generate tracking information for the application. As you can see, you can use the .ini file to control whether the JIT compiler generates optimized code. This example does not allow the JIT compiler to generate optimized native code.

The second method of enabling tracking at run time is by using a debugger. If the executable is launched within a debugger such as Visual Studio .NET, the debugger will ensure that tracking is enabled and optimization is disabled.

You can launch an executable in Visual Studio .NET by opening an existing project of type Executable Files (*.exe). Select the executable you want to launch within the debugger. When you start debugging, you will be required to save the newly created Visual Studio .NET solutions file. Then Visual Studio .NET will launch the application with tracking enabled.

The two methods of enabling tracking at run time are effective for .NET .exe applications such as those that host Remoting Web services and clients that interact with Web services. However, they do not work for applications hosted by ASP.NET, primarily because ASP.NET applications are hosted within a worker process (aspnet_wp.exe). This worker process is unmanaged and hosts the common language runtime.

The common language runtime host processes, such as ASP.NET, can programmatically set the debugging options for the JIT compiler. But the current version of ASP.NET does not provide a means of setting the debugging options at run time, so if you want to interactively debug your ASP.NET-hosted Web service, you must build the component using the /debug option.

The good news is that the performance costs associated with generating the tracking information are much less relevant with respect to ASP.NET-hosted Web services. Methods exposed by the Web service tend to be JIT compiled once and then executed many times. The amortized cost of generating the tracking information becomes insignificant.

I encourage you to compile the release version of your Web services using the /debug switch. You will not incur a performance penalty once your code has been JIT compiled. And, in most cases, the ability to perform interactive source-level debugging will far outweigh the slight performance penalty that tracking incurs during warm-up.

If the overhead related to tracking is a concern for your ASP.NET-hosted Web services, consider building two release versions of your DLL, one using /debug:pdbonly and one using /debug. The reason to build a .pdb file for both DLLs is in case future versions of the ASP.NET runtime allow you to enable tracking at run time.

In general, you should compile the release version of your application using the /optimize+ switch. The optimizations performed by the JIT compiler will reduce the fidelity of interactive source-level debugging. However, the performance costs associated with disabling optimization are significant and span the entire lifetime of your application.

Debugging Dynamically Compiled Source Code

Recall that the implementation of a Web service can also be contained in the .asmx file itself. In this case, the ASP.NET runtime generates the MSIL; you must tell the ASP.NET runtime to generate the information needed to facilitate interactive source-code debugging.

You can enable support for debugging for a particular .asmx page, an entire directory, or an entire application. Doing so will cause a program database and tracking information to be generated at run time. In addition, optimization will be disabled.

You can enable debugging at the page level by setting the Debug attribute in the @ WebService directive. Here is an example:

<@ WebService Debug="true" Language="C#"  > using System; using System.Web.Service; public class MyWebService {     [WebMethod]     public string Hello()     {         return "Hello world.";     } }

You can also enable debugging using the web.config file. Depending on where it is located, you can use the web.config file to configure files either within a specific directory or within the entire application, as shown here:

<configuration>   <system.web>     <compilation debug="true"/>   </system.web> </configuration>

Enabling debugging also disables optimization, so the Web service will incur a performance penalty. You should therefore disable debugging in production whenever possible.



Building XML Web Services for the Microsoft  .NET Platform
Building XML Web Services for the Microsoft .NET Platform
ISBN: 0735614067
EAN: 2147483647
Year: 2002
Pages: 94
Authors: Scott Short

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net