Debug the Crash error of the Release version program (transfer)

http://blog.sina.com.cn/s/blog_48f93b530100fsln.html

 

When developing an application with C++ under the Windows platform, the last thing you want to see is the program crash, and to solve the bug that caused the problem, the most difficult thing is to debug the release version. Because the release version lacks a lot of debugging information, not to mention that it is generally released for users to use, and the crash scene is difficult to retain and reproduce. This article will give several solutions to debug the crash error of the release version application. (This article only discusses the debugging in the MSVC environment of the Windows platform, and does not pay attention to other platforms and development environments. Please learn from and try it yourself.)

 

    Option 1: Crash address  + MAP file

    This solution can only be used for programs developed in previous versions of VC7. 

    1. Crash address

     The so-called crash address is the memory address that causes the program to crash. The dialog box of the application crash under WinXP is as follows:

 

 

 

    The value of the red line in the second picture above is the code offset address of the crash, and the third picture is the absolute address of the crash; generally the cause of the crash is the memory operation error, we can use these two addresses and the MAP file to locate the error. line of code.

    2. MAP file

    A MAP file is a file (text file) that records application information, which probably contains information such as the program's global symbols, source code module names, source code files and line numbers, and these information can help us locate the wrong code line.

    How to generate a MAP file? Take VC6 as an example, in Project Settings -> C/C++ -> Debug info, select Line Numbers Only; in Project Settings -> Link, select Generate mapfile, and enter /MAPINFO:LINES and /MAPINFO in Project Options :EXPORTS, recompiling the program will generate a .map file.

    The compile link options corresponding to the above settings are divided into:

    /Zi  — means to generate pdb debugging information;

    /MAP[:filename]  — indicates the name of the generated map file;

    /MAPINFO:EXPORTS  — Indicates that exported functions are added to the generated map file (when generating a DLL file);

    /MAPINFO:LINES  — Indicates that code line information is added to the generated map file.

    Since the /MAPINFO:LINES option is no longer supported in VC8 and later versions, it is difficult to locate the error code line through the information and crash address in the MAP file, so this scheme can only be used in VC7 and previous versions.

    An example of a MAP file fragment is as follows: 

      

    

    The address in the Rva+Base column in the figure is the absolute address of the function corresponding to the function in this row, and the address after the colon in the Address column is the relative offset address of the function.   

    3. Locate crash code

    With the above introduction, locating the crash code is very simple. Use the following formula for positioning:

    Crash line offset = crash address - crash function absolute address + function relative offset

    We first find the crashed function based on the crash address (absolute address) and the address in the Rva+Base column in the second picture (that is, the crash address is greater than the Rva+Base address of the function row and smaller than the address of the next function), and then find The relative offset address of the function corresponding to this line is brought into the formula to get the crash line offset, which represents the offset of the code of the crash line relative to the function where the code is located. Use this value to compare with the offset after the colon of the corresponding function in the third picture. The decimal number before the closest value is the line number in the function where the code is located.

    Ok, so far we have successfully found the crashing code line, but this method is still laborious and has many restrictions. Let's take a look at the following scheme.

There are a few things to add to the first plan given in the previous article. Although the location of the error code is located by "crash address + MAP file", although it needs to go through a more complicated address calculation, it is the easiest way to implement it. It is more convenient if you just want to locate the faulty function by the crash address. I found a small tool for parsing MAP files on the Internet, which can list the address of each function very clearly, and can export the analysis table as an Excel file. Tool download address: http://e.ys168.com/?tinyfun , VCMapper.exe in the tool directory.

    In addition, the previous article mainly refers to two articles:

    http://www.vckbase.com/document/viewdoc/?id=908

    http://www.vckbase.com/document/viewdoc/?id=1473

 

    Option 2: Crash address + MAP file + COD file

    Since VC8 and later versions no longer support the generation of code line information in MAP files, we look for another positioning method: COD files.

    1. COD file

    A COD file is a file that contains the corresponding information of assembly code, binary machine code and source code. Each cpp corresponds to a COD file. Through this file, we can locate it very easily.

    The setting method for generating COD files in VC6 is: Project Settings -> C/C++, select Listing Files in Category, and select Assembly, Machine code, and source in the Listing file type combo box. The setting method to generate COD files in VC8 is: Project Properties -> C/C++ -> Output Files -> Assembler Output item, select Assembly, Machine code, and Source(/Facs).

   

    2. Locate the crash line

    The following is an example to illustrate. Now I have a dialog-based MFC application CrashTest, in the CCrashTestDlg::OnInitDialog function, write the code statement that causes the crash (line 99), the source file is as follows:

    

    According to the crash address (0x004012A3) and the MAP file (locating the fragment picture below), the crash function is OnInitDialog; and we can easily calculate the offset of the crash address relative to the crash function as 0x004012A3 - 0x004011E0 = 0xC3.

    

    Let's look at the CrashTestDlg.cod file again. We find the OnInitDialog function information fragment according to the source code information in the file:

    

    You can see that the first line in the picture is the starting line of the assembly code of the OnInitDialog function; find the source code of "int * p = NULL;", the preceding 98 indicates the line number of this line of code in the source file, and the following 000c1 indicates The offset relative to the starting position of the function, the following "33 c0" is the machine code, and "xor eax, eax" is the assembly code. Then we find the corresponding error statement is line 99 according to the offset 0xC3 calculated earlier: "*p = 5;".

    To summarize the positioning steps:

 1) Calculate the offset X in the function     according to the formula  crash statement = crash address - crash function address ;

    2) According to the formula  , the address of the crash statement in the COD file = the address of the crash function in the COD file + X  to calculate the address Y. The address of the crash function in the COD file is the address indicated after the function start bracket "{" in the COD file, generally 0x0000;

    3) Find the corresponding code line in the COD file according to Y.

   

    ok, the introduction of the second plan is over. The biggest advantage of this method is that there is no VC development environment version limit, and the information contained in the COD file is more abundant, which can not only help us locate the crash, but also help us analyze many things. Of course, this also resulted in the compilation of a lot of information files.

According to the previous two blog posts, if we want to locate the crash line code, we must calculate it according to the relevant information files. If the amount to be processed is relatively large, I am afraid it will be very laborious. Is there a simpler and faster way?

    The most direct idea is to write a small tool to automatically locate according to rules and information, but it takes a lot of effort to develop. Happily, we can find similar tools that are open source and free! The world of programmers may be so simple and willing to share many times!

   

    Option 3: Crash address + PDB file + CrashFinder

    CrashFinder is an open source tool, the author is John Robbin, you can go to his blog to find information about CrashFinder. Here we take CrashFinder 2.5 as an example, and the relevant article links are: http://www.wintellect.com/CS/blogs/jrobbins/archive/2006/04/19/crashfinder-returns.aspx

    1. PDB file

    The PDB (Program Database) file contains all the debugging related information of the exe program. For details, please refer to MSDN. When the compile option is set to /Zi, the link option is set to /DEBUG, /OPT:REF, the .pdb file of the project will be generated. Specifically in VC2005, the Project Propertise -> C/C++ -> General -> Debug Information Format item is set to Program Database (/Zi), the Linker -> Debugging -> Generate Debug Info item is set to Yes (/Debug), and the Linker -> Optimization -> References item is set to Eliminate Unreferenced Data (/OPT:REF).

    As long as the above options are set, the release version can also generate PDB files. Of course, the corresponding application will also be slightly larger.

    2、CrashFinder

    Two conditions are required for CrashFinder to run: one is that the system must have the dbghelp.dll file; the other is that the PDB file must be in the same path as the exe file. For dbghelp.dll, it is generally available in the system system32 path. If you don't download one, you can put it in this directory.

    Let's take a look at the interface of CrashFinder.

   

 

    It is also very simple to use. First select File->New or click the New button on the toolbar, select the exe file to be debugged to open, and you will find that the exe and the dependent dll file information have been loaded. Enter the crash address (hexadecimal) in the edit box in the lower half, click the "Find" button on the right, and the crash source file path, name and line number of the crash will be displayed below, as shown in the following figure.

 

    It is really convenient to use CrashFinder for crash location. But I found a bug in the process of using it. Every time I start the program, the loaded exe module will show a fork when I start the program directly, indicating that the debug symbols cannot be found. However, after opening a file with the Open button fails, it will be successful to create a new one. Guessing that it may be caused by the direct new creation and the wrong path when locating the PDB file. There is the source code, but I am lazy to read it. If you are interested, you can try it.

    Well, the third plan is introduced here, and there are more powerful solutions later :)

The previous solutions are to directly locate the code location of the crash, but in a relatively large program, only knowing this information is not enough. We want to know more about the order of calling functions and variable values, that is, when the crash occurs. Call stack information.

 

    Option 4: SetUnhandledExceptionFilter + StackWalker

    This solution requires you to add code to the project yourself. To realize the above idea, two things need to be done: 1. You need to have the opportunity to process the program stack when it crashes; 2. Collect stack information.

    1. SetUnhandleExceptionFilter function

    C++ program exceptions under the Windows platform can usually be divided into two types: structured exceptions (Structured Exceptions, which can be understood as operating system-related exceptions) and C++ exceptions. For Structured Exception Handling (SEH), you can find a lot of information, and I won't go into details here. Crash errors are generally caused by exceptions that are not normally caught. The Windows operating system provides an API function that can handle these exceptions before the program crashes, which is the SetUnhandleExceptionFilter function. (C++ also has a similar function set_terminate that handles uncaught C++ exceptions.)

    The SetUnhandleExceptionFilter function is declared as follows:

    LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
      __in          LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
    );

    where LPTOP_LEVEL_EXCEPTION_FILTER is defined as follows:

    typedef LONG (WINAPI *PTOP_LEVEL_EXCEPTION_FILTER)(
        __in struct _EXCEPTION_POINTERS *ExceptionInfo
    );
    typedef PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER;

    Simply put, SetUnhandleExceptionFilter allows us to set our own function as a global SEH filter function, and our function will be called for processing before the program crashes. What we can use is the variable ExceptionInfo of the _EXCEPTION_POINTERS structure type, which contains the description of the exception and the thread state where the exception occurred. The filter function can return different values ​​to let the system continue to run or exit the application.

    For specific usage and examples of the SetUnhandleExceptionFilter function, please refer to MSDN.

 

    2. StackWalker
    Now we have the opportunity to process the program state information before the crash, just generate and save the stack information and you're done. The Windows dbghelp.dll library provides a function to get the current stack information: StackWalk64 (StackWalk in previous versions of Win2K). The function is declared as follows:

    BOOL WINAPI StackWalk64(
      __in          DWORD MachineType,
      __in          HANDLE hProcess,
      __in          HANDLE hThread,
      __in_out      LPSTACKFRAME64 StackFrame,
      __in_out      PVOID ContextRecord,
      __in          PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
      __in          PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
      __in          PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
      __in          PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
    );
    The specific usage of this function can refer to MSDN. Here I recommend a StackWalker written by a great man, which can be used directly and is open source. StackWalker provides a base class with several simple interfaces, which can easily generate stack information, and supports a series of VC versions, which is very easy to use. We can write a subclass ourselves and overload the virtual function OnOutput to output the stack information in a specific format. The address of StackWalker is: http://www.codeproject.com/KB/threads/StackWalker.aspx .

    However, for the Release version, the stack information obtained by the StackWalk64 function may be incomplete. If the exception is thrown by an MFC module, the obtained stack may lack information about the previous calling module. In addition, StackWalk64 needs the latest dbghelp.dll file support to work; to correctly output the crash function name and line number, it needs pdb file support. The above deficiencies may affect the integrity and effect of the output information, and it is almost impossible to bring the pdb file for the program released outside, so this solution still has shortcomings, and it is more suitable for the debugging of the local release version.

    Next we will introduce a more complete solution

When we release our release version of the program, it usually runs on the user's machine. In this case, for the fourth scheme, because the pdb file is required to correctly generate the function line number and code line number of the stack call, the fourth scheme is only suitable for the debugging of the local release version, otherwise only an incomplete stack can be generated. information. For the first three solutions, in fact, the user only needs to inform the crash address, and then find the crash address locally, but the process of locating the crash is very inconvenient. If there are many crashes, the first three solutions are not suitable. Moreover, none of the first three schemes can generate stack call information, which has limited effect on debugging.

    Let's take a look at a more complete solution.

 

    Option 5: SetUnhandledExceptionFilter + Minidump

    We have already introduced the SetUnhandleExceptionFilter function. The idea of ​​this solution is to use our own exception handling function to generate a minidump file.

    1. Minidump concept

    minidump (small memory dump) can be understood as a dump file, which records the smallest useful information that can help debug a crash. In fact, if you select "Small memory dump (64 KB)" in System Properties -> Advanced -> Startup and Recovery -> Settings -> Write Debug Information, when the system stops unexpectedly, it will be in C:\ A file with a .dmp suffix is ​​generated under the Windows\Minidump\ path. This file is a minidump file, but this is a kernel-mode minidump.

   What we want to generate is the minidump of the user mode, which contains the module information, thread information, stack call information, etc. of the program running. And in order to conform to its mini characteristics, the dump file is compressed.

    2. Generate minidump file

    The API function for generating minidump files is MiniDumpWriteDump. This function requires the support of dbghelp.lib. Its prototype is as follows:

    BOOL WINAPI MiniDumpWriteDump(
      __in          HANDLE hProcess,
      __in          DWORD ProcessId,
      __in          HANDLE hFile,
      __in          MINIDUMP_TYPE DumpType,
      __in          PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
      __in          PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
      __in          PMINIDUMP_CALLBACK_INFORMATION CallbackParam
    );

    Add the following code to our exception handler:

    HANDLE hFile = ::CreateFile( _T("E:\\dumpfile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
     if( hFile != INVALID_HANDLE_VALUE)
     {
         MINIDUMP_EXCEPTION_INFORMATION einfo;
         einfo.ThreadId = ::GetCurrentThreadId();
         einfo.ExceptionPointers = pExInfo;
         einfo.ClientPointers = FALSE;

        ::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpNormal, &einfo, NULL, NULL);
        ::CloseHandle(hFile);
     }

    Among them, the pExInfo variable is a parameter of the PEXCEPTION_POINTERS type of the exception handling function. Please refer to MSDN for details.

    3. Debug minidump

    To debug the dump file, the pdb file is needed first, so we need to set the Debug Infomation Format to "Program Database (/Zi)" when we build the program. Secondly, we also need to ensure that the dump file used is consistent with the source code, exe, and pdb file version, which requires us to maintain the program version information.

    The most convenient environment for debugging minidump is VS. We only need to put the .dmp, .exe, and .pdb files in one path, and make sure that the path of the source code file is the same as the path when compiling. The rest is VS help we are done. Double-click the .dmp file or select "dump files" in the file open project, load the dump file, and then press F5 to run to restore the crash scene directly. You can locate the crash code, view the call stack, and view the thread and Module information... everything is the same as you set breakpoint debugging, so powerful! Check out a screenshot.

 

    It should be noted that for the release version of the program, many codes are optimized by the compiler, so there may be deviations in the positioning, you can consider setting options to remove code optimization.

    Other tools that can debug minidump include WinDbg, etc. You can refer to relevant information.

    This article mainly refers to this article: http://vicchina.51.net/research/other/seh/minidumps/intro.htm .

    In the next article, we will give a perfect solution for debugging the release program, which is suitable for the debugging of the application release program with a large number of users.

In the previous article, we have given a solution, which can be very convenient to debug and locate the crash error through the dump file; from the perspective of the whole process, there is still one last step, that is, how to get the dump file generated when the crash occurs. It would be nice if you could let users send files over, but what about programs like free shareware that are distributed on the Internet? Our users are uncertain, and the number of users may be very large. Even if we can find a way to contact users, we can't collect crash information one by one.

    We need a solution that can provide crash information reporting.

    We can set up a server for information collection, as long as the client reports correctly when it crashes, but the corresponding maintenance cost and development difficulty cannot be ignored. Is there an easier way? Remember my blog post " Add Auto-Email to Programs "? This is the simple and effective method!

 

    Option 6: minidump + email

    We only need to generate a minidump information file first, and then send the file to the specified mailbox by email. The rest is that we check the mailbox every day and extract the dump file for debugging.

    1. Email function

    First, let's take a look at what information is required to send an email.

    a. The email account of the sender;

    b. The recipient's email account;

    c, email title, generally should have software name and version information;

    d. The body of the email should generally have a simple crash information prompt to distinguish crashes caused by different reasons;

    e. Email attachments, of course, are our dump files, and log files generated by the software can also be added.

    Of course, you should add as much information as possible to the title to distinguish the cause of the crash, such as adding the address information of the crash to the title; because when hundreds or thousands of crashes are reported every day, repeated crashes account for most of them, and it takes time It's a bit too wasteful to differentiate them. From this point of view, the StackWalker mentioned in the previous scheme is still useful. We can use it to generate some crash text description information and write it in the title or text.

    Is the size of the dump file suitable as an email attachment? In fact, the files generated by minidump are generally between a few K and dozens of K, and there is no problem as an email attachment.

    Regarding the technical details of sending email, it has been introduced in the article " Adding automatic email sending function to the program ", you can refer to it. In fact, it is still time-consuming and labor-intensive to process emails received in the mailbox. You can consider writing some scripts to automate the processing process and improve efficiency.

    2、google breakpad

    Google breakpad is an open source cross-platform crash report system. From the perspective of open source and cross-platform, it is enough to be called a complete and effective tool. In fact, breakpad provides a system-level solution at the entire crash report level, which means that it can almost adapt to the application requirements of various software and various platforms.

    The overall idea of ​​breakpad is similar to the solution introduced above, but the way to submit the dump file at the end is more complete. If you are interested, you can go to its official website to check related information: http://code.google.com/p/google-breakpad/ .

 

    ok, the series of articles about debugging the crash error of the release release program is finished. The solutions given in these articles range from simple to complex, from simple to perfect, and have a more comprehensive summary of crash debugging. Of course, there are still many concepts and technologies involved, we need to continue to learn and comprehend, and I hope everyone can communicate with each other.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325691031&siteId=291194637