?
The runtime environment of the C ++ language is a stack-based environment. The trace stack is used to track and print the called functions, variables, and return addresses when the program is running, the stack trace in the C ++ exception is to print out the file name and row number of the statement that causes the exception when the program throws an exception, and print out the information about the functions that call the statements that throw an exception and other upper-layer functions.
1. Why stack tracing?
When you are developing a program, have you ever encountered a problem with a line of code that suddenly becomes a machine while the program is running; have you ever encountered a sudden exception thrown during program debugging, but you don't know which line of code is wrong; have you ever encountered an exception when you suddenly throw an exception during the single-step debugging, but you forgot the exception that you throw when performing the single-step debugging, so you have to repeat it. The Beta program was put into trial run by the customer and suddenly becomes a machine. You cannot debug it. You can only find Bugs based on some information reported by the customer. Most of the customers are not familiar with program development, therefore, the amount of information they report is too small to make you feel helpless.
If you have encountered the above situations, You have to execute a statement in one step to check where the exception is thrown and where the statements that illegally access the memory are, the bad thing is that, based on the uncertainty principle of heenburg, sometimes you don't have any problems when debugging. So if you are lucky, you will soon find bugs. Unfortunately, you will not be able to find the problem within hours or days and it will become your nightmare. In the process of program development, I often encounter the above situations.
As we all know, it is much more difficult to find a bug in program development than to correct it. Therefore, if there is a way to print out the error information when a program fails, this will greatly facilitate the discovery of bugs, speed up program development, and improve the quality of the program. In this way, when the customer reports a program error, you only need to send the log to you, and you can easily find the problem based on the exception stack information in the log.
The stack tracking function is available in java. When a program throws an exception, it can print the file name and row number of the statement that can cause the exception to be thrown. This function is also available in C. Many people think that developing a program in java is faster than developing a program in C ++. I think it is an important reason to trace the stack when java throws an exception.
2. How to Implement stack tracking in C ++ exceptions
To implement stack tracing, you must rely on the underlying mechanism, namely the operating system or virtual platform. java is bound to the jvm virtual platform.. NET virtual platform binding, all of which provide the stack tracking function, and C ++ has nothing to do with the operating system or platform, so this function is not provided, but can I use the system functions of the operating system to implement this function? The following briefly introduces how to implement stack tracking for C ++ exceptions in Windows2000.
In Windows, the underlying implementation of C ++ exceptions is achieved through the structured exception SEH in Windows. Structural exceptions include, except for 0 overflow, illegal memory access, and stack overflow, although catch (... ) Can capture structural exceptions, but do not know which type of structural exceptions, so the first step is to convert a structural exception to a C ++ exception, in Windows _ set_se_translator () the function can implement this function. Create a conversion function: void _ cdecl TranslateSEHtoCE (UINT code, PEXCEPTION_POINTERS pep). In this conversion function, a class that inherits the C ++ standard exception is thrown, for example, CRecoverableSEHException (a recoverable structured exception class) and CUnRecoverableSEHException (a structure exception class that cannot be recovered). These two classes inherit CSEHException, and CSEHException inherits the Standard C ++ exception base class exception. Call _ set_se_translator (TranslateSEHtoCE) at the beginning of the main function to convert a structured exception to a C ++ exception.
In addition, because the default new in VC does not throw an exception when the new fails, you need to throw an exception when the new fails. In this way, you can use _ set_new_handler () in WINDOWS to convert it, an exception is thrown when new fails. Same as above, first create a conversion function int NewHandler (size_t size), in which the class bad_alloc of the c ++ standard exception is thrown, call _ set_new_handler (NewHandler) at the beginning of the main function ).
Then, trace the stack in the CSEHException constructor, print out the file name and row number of the statement that causes the structural exception, and call void ShowStack (HANDLE hThread, CONTEXT & c ). The ShowStack function encapsulates various system APIs required to trace stacks. Its function is to obtain the path of the current program based on the parameter c (thread context), and enumerate the called system dynamic Connection Library, then, the names of all executed functions and their file names and row numbers are printed in the order from the inside out.
Create your own exception classes so that they have the stack tracking function and define your own exception base classes, such as CMyException (you can change the name if you want ), it inherits the Standard C ++ exception class domain_error (exception can also be inherited), and then calls void ShowStack (HANDLE hThread, CONTEXT & c) in the constructor of CMyException ), in this way, stack tracing can be implemented. Other custom exceptions inherit CMyException, And the stack tracing function is automatically obtained. This forms a complete class hierarchy.
Exception
Logic_error runtime_error?
Length_error
Out_of_range bad_alloc bad_cast range_error
Invalid_argument bad_exception overflow_error
Domain_error ios_base: failure underflow_error
CMyException (custom exception base class) CSEHException (structured exception base class )?
CRecoverableSEHException CUnRecoverableSEHException
CSocketException (socket-related exception )?
CConfigException (configuration file-related exceptions)
Note: The exception classes above cmyexception are standard C ++ exception classes.
Note: The base classes of the preceding exception classes are exception.
Hosts file.
3. How to Use the stack trace class library in the C ++ exception
The downloaded files include exception. h exception. cpp (exception class library with Stack tracking), Main. cpp, test1.h, and test1.cpp (test code ).
Let's first take a look at the power of stack tracing. Running the sample program downloaded will print the following results (because the output is too long, only part of it is excerpted ). The main program is:
Void main (){
// Add the following statement to the entry of each thread function.
// Check for Memory leakage.
Cwinutil: vcheckmemoryleak ();
// Throw an exception when the new function fails.
Cwinutil: vsetthrownewexception ();?
// Converts a structured exception in windows to a C ++ exception.
Cwinutil: vmapsehtoce ();
// Initialization.
Cwinutil: vinitstackenviroment ();
Try {
// Capture the structure exception of the memory with invalid access.
Int * pint; // deliberately do not allocate memory
* Pint = 5; // This row should be displayed with an error.
}
// Capture recoverable structural exceptions.
Catch (const CRecoverableSEHException & bug ){
Cout}
// Capture unrecoverable structural exceptions.
Catch (const CUnRecoverableSEHException & bug ){
Cout}
// Capture Standard C ++ exceptions.
Catch (const exception & e ){
Cout}
// Catch other exceptions that do not inherit exception.
Catch (...){
Cout}
Try {
// Capture custom exceptions.
Throw CMyException ("my exception"); // the error message returned.
}
Catch (const CRecoverableSEHException & bug ){
Cout}
Catch (const CUnRecoverableSEHException & bug ){
Cout}
Catch (const exception & e ){
Cout}
Catch (...){
Cout}
Try {
// Catch exceptions in the function.
VDivideByZero (); // The exception thrown by this function is displayed.
Int I = 1;
}
Catch (const crecoverablesehexception & bug ){
Cout}
Catch (const cunrecoverablesehexception & bug ){
Cout}
Catch (const exception & E ){
Cout}
Catch (...){
Cout}
Try {
// Catch the exception thrown by the function in another source file test1.cpp.
Vtestvectorthrow (); // The exception thrown by this function is displayed.
Int I = 1;
}
Catch (const crecoverablesehexception & bug ){
Cout}
Catch (const cunrecoverablesehexception & bug ){
Cout}
Catch (const exception & E ){
Cout}
Catch (...){
Cout}
Int I;
Cin> I; // prevents accidental exit of the program by pressing a key.
}
Output of 1st exceptions is:
0. V 004066d5 0040779f 0012ff70 00000000 _ main + 85 bytes
Sig: _ main
Decl: _ main
Line: H:/C ++ test/stackwalk/test/Main. cpp (50) + 3 bytes
MoD: Test [h:/C ++ test/stackwalk/test/debug/test.exe], base: 0x00400000 H
Sym: Type: PDB, file: H:/C ++ test/stackwalk/test/debug/test.exe
From the above 4th rows, we can know that in main. the exception thrown by row 50th of the CPP file. Find this row as * pint = 5; then check the context. Oh, no memory is allocated, so the notorious illegal memory access was easily discovered! Is it powerful?
Output of 2nd exceptions is:
1. V 0040683d 0040779f 0012ff70 00000000 _ main + 445 bytes
Sig: _ main
Decl: _ main
Line: H:/C ++ test/stackwalk/test/Main. cpp (72) + 49 bytes
MoD: Test [h:/C ++ test/stackwalk/test/debug/test.exe], base: 0x00400000 H
Sym: Type: PDB, file: H:/C ++ test/stackwalk/test/debug/test.exe
The exception thrown by row 4th in the main. cpp file can be found from row 72nd above. This row is throw cmyexception ("My exception"); Oh, It is a custom exception.
Output of 3rd exceptions is:
0. V 004065f5 0040697c 0012fe98 00000000 void _ cdecl vdividebyzero (void) + 37 B
Ytes
Sig :? Vdividebyzero @ yaxxz
Decl: void _ cdecl vdividebyzero (void)
Line: H:/C ++ test/stackwalk/test/Main. cpp (26) + 6 bytes
MoD: Test [h:/C ++ test/stackwalk/test/debug/test.exe], base: 0x00400000 H
Sym: Type: PDB, file: H:/C ++ test/stackwalk/test/debug/test.exe
1. V 0040697c 0040779f 0012ff70 00000000 _ main + 764 bytes
Sig: _ main
Decl: _ main
Line: H:/C ++ test/stackwalk/test/Main. cpp (100) + 0 bytes
MoD: Test [h:/C ++ test/stackwalk/test/debug/test.exe], base: 0x00400000 H
Sym: Type: PDB, file: H:/C ++ test/stackwalk/test/debug/test.exe
The exception thrown by row 5th in the main. cpp file can be found from row 26th above, which is int iRet = 5/iZero; Oh, it is a division by zero exception. Then we can see from the first line that this function is called in main. the second line of the cpp file is the next line of vDivideByZero (); (Note that because vDivideByZero (); the function has been called, the number of lines displayed is the next line ). In this way, we can know the complete process of an exception.
Output of 4th exceptions is:
0. V 004070ca 00406ab9 0012fe98 00000000 void _ cdecl vTestVectorThrow (void) + 7
4 bytes
Sig :? VTestVectorThrow @ YAXXZ
Decl: void _ cdecl vTestVectorThrow (void)
Line: h:/c ++ test/stackwalk/test/test1.cpp (13) + 10 bytes
Mod: Test [H:/C ++ Test/StackWalk/Test/Debug/Test.exe], base: 0x00400000 h
Sym: type: PDB, file: H:/C ++ Test/StackWalk/Test/Debug/Test.exe
1. V 00406ab9 0040779f 0012ff70 00000000 _ main + 1081 bytes
Sig: _ main
Decl: _ main
Line: H:/C ++ Test/StackWalk/Test/main. cpp (118) + 0 bytes
Mod: Test [H:/C ++ Test/StackWalk/Test/Debug/Test.exe], base: 0x00400000 h
Sym: type: PDB, file: H:/C ++ Test/StackWalk/Test/Debug/Test.exe
From the above 5th rows, we can know the exception thrown in row 13th of the test1.cpp file. Find this row as vectInt [3] = 100; check the context and find that no space is allocated to the vectInt. Then we can see from the above 12th rows that this function is called in the 118th rows of the main. cpp file, that is, the next line of vTestVectorThrow.
So how to use this class library? For a new project, first set exception. h and exception. when cpp is added to the project, you need to inherit the custom exception class from CMyException, so that the custom exception class has the stack tracking function, second, add the following function calls to the entry function of each thread (Note: It must be called at the entry of each thread, for example, CWinUtil: vInitStackEnviroment (); no, you only need to call the main entry, but if you want to call the main entry in each thread, there will be no side effects ):
'// Add the following statement to the entry of each thread function.
// Check for Memory leakage.
CWinUtil: vCheckMemoryLeak ();
// Throw an exception when the new function fails.
CWinUtil: vSetThrowNewException ();?
// Converts a structured exception in WINDOWS to a C ++ exception.
CWinUtil: vMapSEHtoCE ();
// Initialization.
CWinUtil: vInitStackEnviroment ();
Capture exceptions as follows:
Try {
Vtest (); // assume that you want to catch exceptions that may be thrown by the vtest () function.
}
Catch (const crecoverablesehexception & bug) {// capture recoverable structural exceptions.
Cout}
Catch (const cunrecoverablesehexception & bug) {// used to capture irrecoverable structural exceptions.
Cout}
Catch (const exception & E) {// capture Standard C ++ exceptions and their subclasses.
Cout}
Catch (...) {// capture exceptions that throw unstructured exceptions and do not inherit exceptions.
Cout}
Of course, you do not have any special processing policies for structured exceptions, but can also be simplified:
Try {
Vtest (); // assume that you want to catch exceptions that may be thrown by the vtest () function.
}
// Capture Standard C ++ exceptions and their subclasses. Because structured exceptions inherit from exceptions, they can also be captured here.
Catch (const exception & bug ){
Cout}
For existing projects, first set exception. H and exception. CPP is added to the project, and the original custom exception class is inherited from cmyexception. Then the same method is used to capture exceptions. You can add initialization functions to each thread entry, it can be perfectly integrated with your original exception handling.
For the usage in MFC, you can capture exceptions as follows:
Try {
Vtest ();
}
Catch (const exception & e ){
Cout }?
// CException is the basic exception class in MFC. Exceptions in MFC are usually allocated from the heap. Therefore, the pointer should be captured. // after use, the delete function should be called to clear the memory.
Catch (CException * e ){
// Hadle exception
E-> delete ();?
}
That is, add a standard C ++ exception, a structured exception, and a custom exception to the MFC exception. In addition, because MFC already has an automatic mechanism to handle memory leaks, You need to delete the exception. lines 34th to 40th of the hfile (for details about memory leakage, see the following. stdafx must be included at the beginning of the cpp file. h, so the exception. add # include "stdafx. h ", otherwise the compilation will fail.
If you want to output stack information in a file to prevent loss, you can use the IO redirection function (For IO redirection, refer to Jim Hyslop and Herb Suter's article http://www.cuj.com/experts/1903/hyslop.htm), that is, in main () add the following statement at the beginning of the function:
Ofstream ofLog ("exception.txt", ios_base: app );
Streambuf * outbuf = cout. rdbuf (ofLog. rdbuf ());
In this case, all information output to the role is redirected to the prediction.txt file. If you want to restore the data, you can add the following statement:
// Restore the buffers
Cout. rdbuf (outbuf );
If you run the release version, you will find that the program cannot capture Invalid Memory Access, Division by zero, and other structural exceptions. This is because VC in the release version is a synchronization exception by default and does not capture structural exceptions, only C ++ exceptions can be captured. Therefore, you need to modify the compilation options and adopt the asynchronous exception model, add/EHa compilation options in project-> setting-> c/c ++-> project options. In addition, the release version does not generate a debugging symbol file by default, So that you cannot print information such as the row number of the Code that throws an exception, so you need to modify the compilation configuration as follows: select the program database item from the debug info list option on the Project-> Settings-> c/c ++ page. In this way, the release version can also implement stack tracing. Of course, this will slow down the release version and bring a debug info file, because some bugs will only appear in the release version, and the release version is actually used by the customer, therefore, you must test the release version. You can consider that the beta1 and beta2 versions of release contain the debugging information. In this case, both the debug and release versions pass the test, the final official version released to the customer can comment out the debugging information by setting a macro and restore it to the synchronization exception model, that is, it is restored to the default release version configuration of VC.
4. Other issues needing attention
This class library also has the function of checking for Memory leakage, as long as you add the following statement after all # include of each. cpp file:
// The following lines define the code line for Memory leakage. It should be declared in each. cpp file.
# Include "Exception. h"
# Ifdef _ DEBUG?
# Define new DEBUG_NEW
# Undef THIS_FILE
Static char THIS_FILE [] = _ FILE __;
# Endif
Then start the program in debug mode. When the program exits or is closed normally (note that the stop DEBUG command cannot be used to stop the program; otherwise, the memory leakage information will not be printed ), in the debug window of VC, the source code information that may cause memory leakage will be printed, including the file name and row number. Because the MFC program automatically generates the code, you do not need to manually add the code in the MFC program. For example, when you run the downloaded test program in debug mode, when the program Exits normally, the following statement is displayed in the debug window:
Detected memory leaks!
Dumping objects->
H:/C ++ test/stackwalk/test/Main. cpp (88): {184} normal block at 0x00632d50, 100 bytes long.
Data: CD?
Object dump complete.
From the above 3rd rows, we can know that in main. the 88th rows of the CPP file cause memory leakage. Find this row as char * pcleak = new char [100]; check the context and find that the memory has not been released.
Of course, just as you use tools such as purify and boundschecker to check for memory leaks, it will also falsely report military information, some places that won't leak memory, it also tells you that memory leaks, especially when you use a large number of STL class libraries, You need to carefully check the context to determine whether the memory is leaked.
This class library uses some special debugging symbols in VC, so it may not be passed in other compilers. In addition, the stack tracing Implementation discussed in this article is based on Windows 2000 and later. WIN98 and Win95 cannot output the file name and row number of the statement that causes the exception to be thrown. This class library cannot be run in Unix or Linux. If you are interested in working on Unix or Linux platforms, You can implement a C ++ exception stack tracking class library running on Unix or Linux platforms.
This class library cannot track standard C ++ exceptions and other stack information that you cannot control. That is, when these exceptions are thrown, the file name and row number information of the exception statement cannot be output. This is because the standard C ++ exception is built-in in the language, and the exception of other class libraries you cannot control its constructor. This is a small pity, but you can use exceptions to explain which exception it is, which C ++ functions may throw, in this way, you can quickly find the error.
With a complete exception level, you can do anything in the program. The exception filtering mechanism and stack trace will faithfully record any errors, except that you throw an exception in the Destructor and repeat the pointer that is not NULL, the program will still crash in both cases and cannot record stack information. Of course, this class library is powerless for situations such as buffer overflow and heap write overflow. However, you can use tools such as Purify and BoundsChecker to check whether such problems exist in the program running.
5. Summary
C ++ does not support stack tracing. With this exception class library with the stack tracing function, you will be able to speed up the process of developing programs.
Enjoy!
References:
1. Everett N. McKay Mike Woodring. WINDOWS program debugging Translator: He Jianhui and other China Power Press
2. Jeffrey Richter. WINDOWS core programming Translator: Wang Jianhua and other mechanical industry Press
3. Bjarne Stroustrup. Higher Education Press of The C ++ Programming Language, Special Edition
4. Jim Hyslop and Herb Suter Redirections, http://www.cuj.com/experts/1903/hyslop.htm