VC debugging method Daquan

insert image description here

Welcome to follow the blogger Mindtechnist or join [ Linux C/C++/Python Community ] to learn and share Linux, C, C++, Python, Matlab, robot motion control, multi-robot collaboration, intelligent optimization algorithm, filter estimation, multi-sensor information fusion, Knowledge and technology in machine learning, artificial intelligence and other related fields.



Column: Common problems and solutions of development tools


1. Debugging basis

Debugging shortcut
F5: Start debugging
Shift+F5: Stop debugging
F10: Debug to the next sentence, here is a single-step trace
F11: Debug to the next sentence, follow up inside the function
Shift+F11: Jump out of the current function
Ctrl+F10: Debug Go to the cursor position
F9: Set (cancel) breakpoint
Alt+F9: Advanced breakpoint setting
Tracking debugging
1. Try to use shortcut keys to debug while running
2. Observe debugging information
3. Advanced interrupt setting
Abnormal debugging
retry -> cancel -> Debug
function stack, use variables or call stack window
Release to debug
1. Always test your Debug and Release versions
2. Do not remove the debug code, such as ASSERT, TRACE, etc.
3. Initialize variables, especially global variables, malloc memory, and new memory
4. When you remove a resource, make sure you remove all declarations related to this resource (mainly in resource.h)
5 , Compile your code with a warning level of 3 or 4, and make sure there are no warnings, project->setting->c/c+±>warninglevel (the Chinese version is project->properties->C/C+±>general->warning grade)
6. Change _debug to NDEBUG for debugging, project->setting->C/C+±>Preprocessordefinitions (the Chinese version is project->properties->C/C+±>preprocessor->preprocessordefinitions) (here are debug and One of the important differences of Release compilation)
7. Debug source code in Release, project->setting->C/C+±>debug info select programDataBase (Chinese version is project->properties->C/C+±>General-> Debug information format -> program database for "edit and continue"), project->setting->link select Generate debug info (Chinese version is project->properties->linker->debug->generate debug information)
8. Walk through the code, paying special attention to the stack and pointers

Two, TRACE macro

When the Debug target is selected and the afxTraceEnabled variable is set to TRUE, the TRACE macro is activated accordingly. But in the Release version of the program, they are completely prohibited. The following is a typical TRACE statement:
...
int nCount =9;
CString strDesc("total");
TRACE("Count =%d, Description =%s\n", nCount, strDesc);
...

It can be seen that the TRACE statement works a bit like the printf statement in C language. The number of TRACE macro parameters is variable, so it is very easy to use. If you look at the source code of MFC, you can't find the TRACE macro at all, but you can only see the TRACE0, TRACE1, TRACE2, and TRACE3 macros, and their parameters are 0, 1, 2, and 3, respectively. Personal summary: Recently, I encountered a TRACE statement in network programming. I don’t know
where to output it. I checked the information all night and couldn’t find it. Today I finally found it
.
>Select "ENABLE TRACING" in MFCTRACER and click OK
3. Debug and run, GO (F5) (Special attention: the reason why you can't see the TRACE content before is not to execute '!' is because it is not debug execution, but '!' , remember, remember)
4. Then you will see the TRACE content in the DEBUG window in OUTPUT, and the debugging execution will automatically jump from the BUILD window to the DEBUG window, where you will see the TRACE content.

The following is a detailed introduction to TRACE:

The TRACE macro is very useful for program debugging under VC, and has a function similar to printf; this macro only appears in the DEBUG version of the program, and it disappears completely when it is RELEASE, so as to help you debug in RELEASE When reducing the amount of code.

It is very simple to use, and the format is as follows:
TRACE("DDDDDDDDDDD");
TRACE("wewe%d",333);
There are also TRACE0, TRACE1, and TRACE2. . . Corresponding to 0, 1, 2 respectively. . The TRACE information of the first parameter
is output to the output window of the VC IDE environment (this window is the window where you compile the project error prompt), but it is only limited to running your DEBUG version of the program in VC.
TRACE information can also be captured using DEBUGVIEW. In this case, you cannot run your program in the VC IDE environment, but run the DEBUG version of the BUILD program separately. At this time, you can see the output in DEBUGVIEW format in the DEBUGVIEW window.
There are four usages of TRACE in VC as follows:
TRACE1 is to output a string without dynamic parameters, similar to C's printf("output string");
TRACE2: the string in it can be output with a parameter, similar to C's printf( "...%d", variable);
TRACE3: can output with two parameters, similar to C printf("...%d...%f", variable 1, variable 2);
TRACE4 can output with three parameters, similar to C printf("...%d, %d, %d", variable 1, variable 2, variable 3);
TRACE macro is a bit like the Printf function we used in C language before, so that the program can output some debugging information during running, Allows us to understand some of the state of the program. But there is one difference:
the TRACE macro has output only in the debugging state, while the previously used Printf function has output in any case. Like the Printf function, the TRACE function can accept multiple parameters such as:

int x = 1;
int y = 16;
float z = 32.0;
TRACE( "This is a TRACE statement\n" );
TRACE( "The value of x is %d\n", x );
TRACE( "x = %d and y = %d\n", x, y );
TRACE( "x = %d and y = %x and z = %f\n", x, y, z );

It should be noted that the TRACE macro only works on the Debug version of the project, and the TRACE macro will be ignored in the Release version of the project.

3. ASSERT macro

If you design a function that takes a pointer to a document object as a parameter, but you mistakenly call the function with a view pointer. This false address will result in the destruction of visual data. Now, this type of problem can be avoided entirely by implementing an ASSERT test at the beginning of the function to check whether the pointer actually points to a document object. In general, programmers should routinely use assertions at the beginning of every function. The ASSERT macro will evaluate expressions. If an expression is true, execution will continue. Otherwise, the program will display a message and halt. You can choose to ignore the error and continue, terminate the program, or jump into the Debugger. The following example demonstrates how to use an ASSERT macro to verify a statement.

void foo(char p, int size )   
{
    
    
     ASSERT( p != 0 ); //确认缓冲区的指针是有效的
     ASSERT( ( size >= 100  ); //确认缓冲区至少有100个字节
         // Do the foo calculation
}

These statements do not generate any code unless the --DEBUG processor flag is set. Visual C++ only sets these flags in the Debug version, but does not define these flags in the Release version. When --DEBUG is defined, the two assertions will produce the following code:

//ASSERT( p!= 0 );
do{
    
     
     if( !(p !=0) && AfxAssertFailedLine(—FILE—,—LINE—) )
         AfxDebugBreak();
      }while(0); 
      //ASSERT((size 〉= 100);
      do{
    
    
     if(!(size 〉= 100) &&AfxAssertFailedLine(—FILE—,—LINE—))
         AfxDebugBreak();
}while(0); 

The do-while loop encapsulates the entire assertion in a single program block, making it very comfortable for the compiler to compile. The If statement will evaluate the expression and call the AfxAssertFailedLine() function if the result is zero. This function will pop up a dialog box, which provides three options "Cancel, Retry or Ignore", when you choose "Retry", it will return TRUE. A retry will result in a call to the AfxDebugBreak() function, thereby activating the debugger.
AfxAssertFailedLine() is an unpublished function whose function is to display a message box. The source code for this function resides in afxasert.cpp. The --FILE- and --LINE- statements in the function are processor flags, which specify the source file name and the current line number, respectively.

AfxAssertFailedLine() is an unpublished function whose function is to display a message box. The source code for this function resides in afxasert.cpp. The --FILE- and --LINE- statements in the function are processor flags, which specify the source file name and the current line number, respectively.

Four, VERIFY macro

Because the assertion can only work in the Debug version of the program, the expression cannot contain assignment statements, increase statements (++) or decrease statements (--), because these statements actually change the data. But sometimes you may want to validate an active expression, using an assignment statement. Then it's time to replace ASSERT with the VERIFY macro. For example:

voidfoo(char p, int size )
{
    
    
	char q;
	VERIFY(q = p);
	ASSERT((size 〉= 100);
               //Do the foo calculation
               //Do the foo calculation
} 

In Debug mode, ASSERT and VERIFY are the same thing, but in Release mode, the VERIFY macro still tests the expression and the assertion does nothing. It can be said that in Release mode, the ASSERT statement is deleted.

Note that if you mistakenly use an active expression in an ASSERT statement, the compiler will ignore it without warning. In Release mode, the expression will be silently deleted, which will cause the program to run incorrectly. Since the Release version of the program usually does not contain Debug information, such errors will be difficult to find.

5. VC advanced debugging method - setting of conditions and data breakpoints

(1) Location breakpoint (LocationBreakpoint)
The most commonly used breakpoint is an ordinary location breakpoint. Press F9 on a certain line of the source program to set a location breakpoint. But for many problems, this naive breakpoint has limited effect. For example, the following code:

void CForDebugDlg::OnOK() 
{
    
    
       for(int i = 0; i < 1000; i++)    //A
       {
    
    
              intk = i * 10 - 2; //B
              SendTo(k);          //C
              inttmp = DoSome(i); //D
              Trace0("这里要输出的内容”);//在这里可以输出一些有用的信息,你也可以输出I的值,都是可以的
              intj = i / tmp;    //E
       }
}    

In fact, we can also use other methods to debug. You can use the TRACE0 macro to output each result in the loop. We can also see the output result in debug. When there is a problem, the output result may be different. , we can analyze the results in debug to find out the problem.
Executing this function, the program crashes at line E. It is found that tmp is 0 at this time. Assuming that tmp should not be 0, why is it 0 at this time? So it is best to be able to track how the DoSome function runs during this loop, but because it is in the loop body, if you set a breakpoint on line E, you may need to press F5 (GO) many times. It is very painful to keep pressing the hands like this. This problem can be easily solved by using VC6 breakpoint modification conditions. Proceed as follows.
1 Ctrl+B to open the breakpoint setting box, as shown below:
insert image description here

Figure 1 Set advanced position breakpoint
2 Then select the breakpoint where line D is located, then click the condition button, and enter a large number in the bottom edit box of the pop-up dialog box, depending on the application, 1000 is enough here .
3 Press F5 to re-run the program, the program is interrupted. Ctrl+B opens the breakpoint box, and a series of instructions follow after the breakpoint is found: ...487 times remaining. It means that there are 487 executions left, that is to say, an error occurs when the execution reaches 513 (1000-487) executions. Therefore, we change the number of skips of this breakpoint as mentioned in step 2, changing 1000 to 513.
4 Re-run the program again, the program executes 513 cycles, and then automatically stops at the breakpoint. At this point, we can take a closer look at how DoSome returns 0. This way, you avoid finger pain and save time.
Look at the other modification conditions of the position breakpoint. As shown in Figure 1, under "Enter the expression to be evaluated:", you can enter some conditions. When these conditions are met, the breakpoint will start. For example, in the program just now, we need the program to stop when i is 100, we can enter "i==100" in the edit box.
In addition, if only the variable name is entered in this edit box, the breakpoint will only be activated when the variable changes. This is handy for detecting when a variable has been modified, especially for large programs.
Using the modified condition of the position breakpoint can greatly facilitate the solution of certain problems.
(2) Data breakpoint (DataBreakpoint)
During the software debugging process, sometimes it is found that some data will be modified inexplicably (for example, some arrays are written out of bounds and cause other variables to be overwritten), and find out where the code causes this memory to be blocked. Changing is a tricky thing (without the help of a debugger). Proper use of data breakpoints can quickly help you locate when and where the data is modified. For example, the following program:
#include “stdafx.h”
#include <string.h>
int main(int argc, char* argv[])
{
charszName1[10];
charszName2[4];
strcpy(szName1,“shenzhen”);
printf(“%s\n”,szName1); //A

   strcpy(szName2,"vckbase");              //B
   printf("%s\n",szName1);
   printf("%s\n",szName2);
   return0;

}
The output of this program is
szName1: shenzhen
szName1:ase
szName2:vckbase
First, let me analyze why this is the result! First you set a breakpoint in strcpy(szName1,"shenzhen"); F9 here, and then F5 to run the program, this is the program will break to the breakpoint we set, as shown below
insert image description here

You see, the reason for the problem is here. The address allocated by the system to szName2 is 0x0012ff70, which is 4 bytes. Then, at the 4 bytes behind 0x0012ff70, the 10 bytes of szName1 are allocated, that is, Start to allocate 10 bytes at 0x0012ff74,
F10 single-step tracking, and come to the line printf(“%s\n”, szName1), as shown in the figure below
insert image description here

The space allocated by szName1 has already attached the value.
F10 goes to the next printf(“%s\n”, szName1) See the picture below,
insert image description here

Because the space allocated by szName1 and szName2 is continuous, the content of szName1 will be overwritten when the value assigned to szName2 exceeds the accommodated bytes, so when we output the result, there will be unexpected results, so how to do
it For debugging, when is the specific method
szName1 modified? Because there is no obvious modification of the szName1 code. We can first set a normal breakpoint at line A, F5 runs the program, and the program stops at line A. Then we set a data breakpoint. As shown below:
insert image description here

Figure 2 The data breakpoint
F5 continues to run, and the program stops at line B, indicating that the code at B has modified szName1. B obviously did not modify szName1? But the debugger pointed out that this line is generally not wrong, so calm down and look at the program, oh, you found out: szName2 has only 4 bytes, and strcpy has 7 bytes, so szName1 is overwritten.
Data breakpoints are not only valid for variable changes, you can also set whether the variable is equal to a certain value. For example, you can change the red circle in Figure 2 to the condition "szName2[0]==''''y''''", then the breakpoint will start when the first character of szName2 is y.
It can be seen that a big difference between data breakpoints and positional breakpoints is that there is no need to explicitly specify which line of code to set the breakpoint on.
(3) Others
1 Set a breakpoint in the call stack window, select a function, and press F9 to set a breakpoint. This allows a quick return to the desired function from a deep function call.
2 Set Next StateMent command (command in the right-click menu during the debug process)
The function of this command is to point the instruction pointer (EIP) of the program to different code lines. For example, if you are debugging the above code and run it on line A, but you don't want to run the code on lines B and C, then you can right click on line D and "Set Next StateMent". The debugger will not execute lines B and C. As long as it is within the same function, this instruction can be executed before or after jumping at will. Using this feature flexibly can save a lot of debugging time.
3 watch window
The watch window supports rich data formatting functions. If you input 0x65,u, it will display 101 in the right column.
Real-time display of errors in windows API calls: enter @err,hr in the left column.
Call the function in the watch window. As a reminder, clear it in the watch window immediately after calling the function, otherwise, the debugger will call this function at each step during single-step debugging.
4 messages breakpoint is not very practical. Basically, it can be replaced by the breakpoint described above.

Six, VC debugging environment settings

In order to debug a program, the program must first contain debugging information. In general, the Debug Configuration included in a project created from AppWizard automatically includes debugging information, but whether the Debug version is not the determining factor for the program to include debugging information, the programmer can add debugging information to any Configuration, including Release Version.
In order to add debugging information, you can follow the steps below:

Open the Projectsettings dialog box (it can be opened by the shortcut key ALT+F7, or it can be opened by the IDE menu Project/Settings),
select the C/C++ page, select general in the Category, and a Debug Info drop-down list box will appear for selection.
Select the Link page, select the check box "Generate DebugInfo", this option will make the linker write the debug information into the executable file and DLL.
If the option above Program Database is set in the C/C++ page, Link incrementally can be selected. Selecting this option will enable the program to be compiled on the basis of the previous compilation (that is, incremental compilation), without having to compile from scratch every time.

insert image description here


insert image description here
insert image description here


Guess you like

Origin blog.csdn.net/qq_43471489/article/details/130389361
Recommended