Release date: 9/9/2004 | update date: 9/9/2004
John RobbinsDownload the code in this article:Bugslayer0102.exe (42kb)
Content on this page
|
Debugger |
|
Conditional compilation |
|
Tracking and traceswitch |
|
Assertions |
|
Tracelistener-listeners is listening |
|
Bugslayertracelistener usage and implementation |
|
More new features |
|
Summary |
Microsoft has released Visual Studio. NET Beta 1, so many readers have begun to study. Net in depth. But do not make a mistake-. Net is a brand new platform. A lot of publicity has been made on key features such as ASP. NET and ADO. net. But for me, one of the biggest advantages of. NET is that it solves the most tricky problem of all programming-memory corruption and leakage.
With the help of the Common Language Runtime Library (CLR), you can focus on solving user problems rather than wasting time looking for memory corruption. In addition, Microsoft eventually had a new system-wide programming model for accessing the system. Consistent object-oriented base class library (BCL) can also eliminate a large number of errors.
Does this mean there is no need to write a bugslayer column? Before Visual Studio can implement your ideas, there are still many problems, such as logical errors and misunderstandings about the system, performance, and scalability.
This month, I plan to help you start correct. NET development. You may recall that the valid old assertions are very consistent with my mind. When I first started learning. net, I felt lost because my favorite assert and trace macros were gone. Therefore, I want to explain to you how to assert and trace in. net. To solve this problem, I first need to discuss the debugger provided in. Net SDK. Then, I will discuss assertion and Conditional compilation and provide you with a better assertion tool than Bcl provides.
All the code in this column is developed using. Net SDK Beta 1. Therefore, you do not need to use the complete Visual Studio. NET to compile and run the code. Unless Microsoft modified the interface in Beta 2, the Code continues to work normally. One thing you should know about the. net sdk is that it is extremely stable. I have been using it since the PDC version and have never encountered any problems.
Debugger
Generally, debugging is debugging. In other words, whatever operating system you use, you always need to set breakpoints, dump memory, and perform other common operations. For developers with Win32 backgrounds,. Net debugging has a slightly different main function. You can add and detach the debugger at will in the running. Net Process (that is, detach! Now, it is easier to debug running server applications than before.
The. net sdk contains two debuggers: cordbg. EXE and dbgurt. EXE. Among them, dbgurt. EXE is a good GUI, cordbg. EXE is a console-based version, it has several additional features, but it is relatively difficult to use. What's amazing in cordbg. EXE? The command is very important because you can use it to get help for all commands. Through in? Enter the command name. You can get more help about a single command. Generally, if you have used a debugger such as windbg, you should be able to deduce most commands. However, I plan to explain to you several interesting COMMANDS IN cordbg. EXE.
The first command that I want to draw your attention to is wt, which uses the application one by one and prints the call tree of each managed method called. I found it useful to use wt to understand the call relationships between various system classes. Sometimes, the wt command will display a lot of useless information, but it is very useful to check who performed what operations in the BCl. The wt command can trace the program stream from the current code line of the debugger to the end of the corresponding method.
Figure1The simple program in demonstrates the wt command. Once you start to execute cordbg. EXE on the program, the code line from main that calls Foo starts. If you type "WT", the output is as follows:
(cordbg) wt 1 HappyAppy::Main 6 HappyAppy::Foo 6 HappyAppy::Bar 10 HappyAppy::Baz 7 HappyAppy::Bar 7 HappyAppy::Foo 2 HappyAppy::Main 39 instructions total
In addition, you can see the Call Graph of each element called by foo. After the wt command is executed, the command Pointer Points to the code line after the call to foo.
Another useful command is F (funceval), which enables you to call Methods outside your class. For non-static methods, remember to pass "this" as the first parameter. There is also a group of commands that allow you to create objects, but I have not guessed how they work.
Cordbg. EXE is easy to use, while dbgurt is as good as a dream. According to the information I collected in PDC in last July, dbgurt and Visual Studio. NET use the same code base, so you can take a look at the final Visual Studio. NET debugger. From the perspective of Small previews In the. net sdk, I really like what I see. First, the dialog boxes (such as modules, threads, and breakpoints) that should be docked in the Visual C ++ 6.0 debugger can still be docked, making the debugger easier to use. In addition, to prevent all these docked windows from occupying too many screen areas and requiring 35 inch of the monitor, the debugger has a very intuitive mode in which, multiple docked windows can share the same window area with simulated labels.Figure2Various docked windows (such as modules, threads, and breakpoints) are displayed, and all Windows share the area at the bottom of the screen.
Not everything that the debugger UI promises exists in SDK Beta 1. Specifically, dbgurt lacks C # syntax coloring and some types of breakpoints. However, dbgurt has enough functions for you to have fun debugging. net. Dbgurt has several new breakpoint skipping counting locations which are very useful. Now, when the skipping count is exactly equal to a specific number, a multiple of a specific number, or greater than or equal to a specific number, you will be interrupted.
Now that I have discussed the main points of the debugger, I want to discuss Conditional compilation, because with it, the tracing and assertions will no longer exist!
Back to Top
Conditional compilation
All three language compilers (C #, Visual Basic, and C ++) in the. net sdk support standard # ifdef... # endif-style Conditional compilation. However, C # and Visual Basic now have a very good custom attribute, which supports Conditional compilation in another way. When declaring a C # or visual basic method, you can use this condition attribute to determine when the method can be called. The advantage of this condition attribute is that the caller does not have to perform any additional work when calling the method. If the Compilation instruction in the condition attribute specified by # define of C # is set, or the/d in the condition attribute specified by C # and Visual Basic is set, the method is called. Otherwise, the compiler will not generate the Microsoft intermediate language (msil) required for this call ).
The following program shows the usage of the condition attribute in the C # example:
Using system; Class happyappy {[conditional ("debug")] public static void debugonlymethod () {console. writeline ("Debug is active !! ") ;}Public static void main () {debugonlymethod () ;}} the same example written in Visual Basic: Imports systemimports system. diagnosticspublic module happyappy sub debugonlymethod () console. writeline ("Debug is active !! ") End sub main debugonlymethod () end subend Module
Debugonlymethod appears only when you define debug during compilation. The advantage of this method is that when debugonlymethod is called, a large number of # ifdef... # endif is not required. If you do not understand how condition attributes work, we recommend that you run the preceding program. As you can see at the beginning, the Conditional compilation functions in C # and Visual Basic make it easy to use excellent programming practices with a large number of assertions.
Back to Top
Tracking and traceswitch
Bcl provides two identical classes to process tracing and assertions: trace and debug, both of which are from the system. Diagnostics namespace. Interestingly, these two classes have identical attributes and methods, but they are not derived from each other, nor from any base class other than the object. The idea between these two classes is: When you define a debug class, the debug class is active, and when you define a trace, the trace class is active. According to the documentation, Microsoft wants you to use DEBUG for the debug version and trace for all versions .. Net, a function for network administrators is that you can enable light diagnostic tracing in the field, so you should always define trace. However, like tracking in the previous operating system, if you do not follow strict format settings and only output the minimum content that helps you understand the program flow, the output may be too large.
Trace methods in the trace and debug classes include write, writeif, writeline, and writelineif. The only difference between write and writeline is that writeline places a carriage return and a linefeed at the end of the output. The trail is executed only when the first parameter is calculated as true. This allows you to track conditions.
Although this sounds good, using writeif and writelineif may not be a good idea. Can you see any errors in the following code snippets?
Debug.WriteLineIf ( ShowTrace "Num = " + Num + " value out of range!" ) ;
The problem is that the string to be displayed is fully calculated and generated before calling Debug. writelineif. This means that you still have all system overhead for parameter generation even if showtrace is false every time you execute this row. Different from this, you should perform a normal condition check before calling, just like the following code snippet.
if ( true == bShowTrace ){ Debug.WriteLine ("Num = " + Num + " value out of range!" ) ; }
In this example, the system overhead generated by string parameters is avoided before the condition is calculated as true and the trail is executed. The disadvantage is that you must perform more typing operations.
Because tracing is a good method for finding problems in the field, Microsoft adds another class in system. diagnostics to help you determine the Trace Level: traceswitch. Traceswitch is a simple condition class that makes it easier to trace various sets, modules, and classes. Traceswitch enables you to easily determine the trace level so that your code can generate appropriate output instantly. The trace level can be determined through the traceswitch class attributes, because if the set level is correct, all these attributes will return true.Figure3Displays the trace level and their values.
The process of creating and using traceswitch is trivial. The following code snippet describes how to create and use traceswitch. I used writelineif to compress the code snippet.
public static void Main ( ){ TraceSwitch TheSwitch = new TraceSwitch ( "SwitchyTheSwitch", "Example Switch" ); Trace.WriteLineIf ( TheSwitch.TraceError , "Error tracing is on!" ) ; Trace.WriteLineIf ( TheSwitch.TraceWarning , "Warning tracing is on!" ) ; Trace.WriteLineIf ( TheSwitch.TraceInfo , "Info tracing is on!" ) ; Trace.WriteLineIf ( TheSwitch.TraceVerbose , "VerboseSwitching is on!" ) ;}
You may be wondering how to set the tracking level. The traceswitch constructor uses two parameters: Switch name and switch description. The important value is the switch name, because you must use the exact string to set the trace level. The first method is to use the Global Registry Key for all the switches in HKLM/software/Microsoft/complus/switches. You only need to create a DWORD value that matches the switch name and set the numberFigure3.
Another way to set a specific tracing level is to use the environment variable, that is, the name of the switch behind _ switch. The preceding code snippet is used as an example. The environment variable is _ switch_switchytheswitch. Set the environment variable to the tracing level you want to see. Remember that any environment variable will override the registry settings.
The idea of putting the tracing switch of all applications into a single environment variable seems like a waiting accident. I can easily see the very real possibility of name conflicts between enterprises. I think it would be much better to specify a trace switch from an input file. In this month's bugslayer code, I created a class named bugslayertraceswitch derived from traceswitch (all the relevant code can be found at the link at the top of this Article ). The bugslayertraceswitch constructor also uses the third parameter, which is used to read the file set at the tracking level. Assume that the file name of the constructor you pass in has sufficient information so that it can be found. Bugslayertraceswitch is part of my bugslayer assembly. Therefore, you only need to include the bugslayer as an import file. The file format is very simple, as shown in the following code snippet.
; The format is =HappyAppyClassSwitch=4Switcheroo=0
Note that rows with semicolons are treated as comment rows.
Now that you know how to use the tracking function in. net, let's discuss the output direction. By default, in addition to the traditional Win32 outputdebugstring call, the trace output goes to the added debugger. Remember that. NET is not a Win32-based traditional application, so the debugger output is different from what you are used. I will discuss the output in detail after this column.
At the beginning of this section, I mentioned that trace and debug classes both have trace methods. But which method should you use? I decided to only use trace for tracking. In this way, I have a consistent approach for tracking, instead of having to think about it before typing a trace statement. Now that you know how tracing works, let's discuss how assertions in. Net work.
Back to Top
Assertions
As I mentioned earlier, both the trace and debug classes have assert methods. I only use assert in the debug class. These methods are identical, but I do not want to bring up an unexpected message box when running the application, so I insist on using the debug version. The default assertion message box is shown in figureFigure4. Note that. Net assertions come with off-the-shelf stack Review (with fill source and row lookup ).
Figure4Debug assertions
Although the C ++ assert macro is easier to use, the Debug. Assert method in C # is also good. The overloaded assert method only uses different parameters. The first method uses a single Boolean condition. The second method uses a Boolean condition and a message string. The last method uses a Boolean condition, a message string, and a detailed message string. Using assert in. Net means that you must complete more typing operations than the traditional C ++ assert. Because there is no. Net macro, You need to input the string yourself to display the asserted string in the asserted message box. The following code snippet shows how all three types of C # assert work.
Debug.Assert ( i > 3 ) ;Debug.Assert ( i > 3 , "i > 3" ) ;Debug.Assert ( i > 3 , "i > 3" , "This means I got a bad parameter") ;
Of course, because C # supports condition attributes, you only need to define debug to enable the asserted code. For Visual Basic, you need to use True Conditional compilation to enclose various assertions, as shown below:
#If DEBUG ThenDebug.Assert ( i > 3 )#End If
As I mentioned earlier, if the code runs interactively, the assertions in. net will be displayed in the message box. I tried it in. NET and found that all application assertions can be globally redirected to the file. However, you are at your own risk to use these technologies. In addition, do not use them on any other computer except for the computer you use for development. With this premise, the following steps need to be followed. In HKLM/software/Microsoft/complus, you need to add two DWORD values (noguionassert and logtofile) and one string value (logfile ). Set noguionassert to 1 to disable the message box. Set logtofile to 1 to enable logging to a file. Set logfile to the full name and path of all assertions output. However, before changing the global settings, you should know how to better control the assertion output through tracelistener.
Back to Top
Tracelistener-listeners is listening
Tracing and assertions are unique in. Net because the control output is quite easy. Both the trace and debug classes have a member listeners, which is an array of tracelistener objects. Tracelistener sends the output of the trail and assertion. As you can imagine, you can have a trace listener that sends the output to outputdebugstring and a trace listener that sends the output to a file. The trace and debug functions enumerate the tracelistener classes in the listeners array one by one, and each class can process the output. This allows you to add or reduce the output. The default tracelistener (defaulttracelistener) sends the Trace Output to outputdebugstring through its write and writeline methods, and all the log Methods attached to the debugger. If you log on interactively, defaulttracelistener sends all assertions to the message box through its fail method.
Bcl comes with some predefined tracelistener that you can add to the listeners array as an additional output. The first is the eventlogtracelistener class, which can send the output to the specified event log. The second is textwritertracelistener, which can direct the output to textwriter or stream, such as the console. out function of filestream. The following code shows how to add a textwritertracelistener to the chain.
Debug.Listeners.Add(new TextWriterTraceListener ( "Trace.Log" ) );
Back to Top
Bugslayertracelistener usage and implementation
It is an interesting idea to replace the trace output at will. However, from the actual point of view, I would rather have a tracelistener. First, I can control it from any location in the application. If there are multiple independent tracelistener, it is more difficult to control each of them. Second, a tracelistener can handle all output needs of the entire application. Therefore, I wrote a bugslayertracelistener to simplify my work. It is the complete insertion replacement of all tracelistener. Trace output can be any combination of multiple locations (additional debuggers, files, and standard outputdebugstring calls. In addition to message boxes and Event Logs, assertions can also go to all the above locations. Adding a bugslayertracelistener to a debug or trace object is simple.
Debug.Listeners.Remove ( "Default" ) ;BugslayerTraceListener btl = new BugslayerTraceListener ( ) ;Debug.Listeners.Add ( btl ) ;
One thing you need to do is delete the defaulttracelistener so that the bugslayertracelistener can control the output.
If you check the code of the bugslayertracelistener, there is not much exciting content. The interesting part is in bugslayerwin32.cs (seeFigure5). Make sure that bugslayertracelistener checks whether there are interactive users before the pop-up message box. This requires that I use a special structure to call the Win32 API. Generally, it is confusing to call Win32 APIs from managed code. If you need to mention some code in. net, I hope that bugslayerwin32.cs will provide you with some tips on how to do this.
Back to Top
More new features
The unmanaged visual c ++ compiler and linker seem to have some interesting new features. After all the discussions on. NET, the new functions in Traditional Visual C ++ will receive less attention. Visual c ++. net will be a mandatory upgrade for all users who have installed the C/C ++ code base with a significantly improved debugger and new compiler flag. I read the Visual C ++ compiler reference in the. NET SDK documentation to learn about these marks.
My favorite new flag is/RTC used by Cl. EXE for runtime error check. Some errors it checks include local memory overflow and underflow, uninitialized memory access, and data truncation. Note that these checks occur at run time. The new/GL (full-program optimization) Logo of Cl. EXE and/ltcg (link generation) provides an unprecedented program optimization level. The most interesting optimization is cross-module inline, that is, inline functions in the module (even if the function is defined in another module ). Another optimization for the x86 CPU is the custom call Convention, which allows the compiler and the linker to use registers to pass parameters between function calls. The CL. EXE/GS (Generate security check) Option inserts code to check whether there is a buffer overflow that causes the returned address to rush out of the stack. After/Gs is enabled, any virus or fraud code that tries to take over your program will pop up a message box and terminate the process immediately.
The last interesting new sign/pdbstripped is a link. EXE sign, which will generate the second PDB file using only public symbols and framework Pointer Optimization (FPO) data. In this way, you can send the second PDB file to your customer so that you can obtain the complete call stack and information from the dr. Watson log. In general, there are some very good new features in Visual C ++. net, so I can't wait to migrate the existing code.
Back to Top
Summary
I hope this introduction to debugging on. NET will make your development easier. Specifically, bugslayertracelistener should make your tracking and assertions easier. When investigating. net, do not forget to look for new features in old places. In addition, you should always write better diagnostic code from the very beginning to make your work easier.
Because I think everyone should confess their mistakes, I must admit that there is a small error in the smooth working set utility in my December 2000 column. It is embarrassing that I have a reallocation problem in cfilebase: appendtodatabuffer of swsfile. cpp. The updated code is as follows:Figure6. Thanks to Eric patey and Richard Cooper for reporting this issue.
Tip 41 (from Ted Yu): In your April 2000 column, you complained that the STL bsearch function does not return values. The following is a bsearch function that can return the iterator corresponding to the found value. If this value cannot be found, the function returns end ().
template inline _FI bsearch ( _FI _F , _FI _L , const _Ty& _V ){ _FI _I = lower_bound(_F, _L, _V); if (_I == _L || _V < *_I) { return _L ; } return ( _I ) ; }
Tip 42 (from Patrick gautschi): Microsoft has released an interesting set of tools to help users track memory leaks called umdh (user mode dump heap. You can download these tools from how to use umdh.exe to find memory leaks. Be sure to read the entire article on how to use them.
John RobbinsOne of the founders of wintellect, the company is a software consulting, education and development company dedicated to Windows and COM programming. He isDebugging applications(Microsoft Press, 2000. To contact John, visit http://www.wintellect.com.
This article is taken from the February 2001 issue of msdn magazine.