Terminate Application Properly and Collect Enough Debugging Data

As a programmer or a developer, error handling and bug fixing are what we deal with every day. When a critical error happens, what is a proper way to handle it? Different people may have different ways. Here I will talk about how to terminate an application properly and collect debugging data fully and elegantly in C++ applications on Windows platform. Same methodology should apply to other platforms or other programming languages.

When we talk about error handling, it covers many topics actually, including error code returning and checking among functions, exception throw and capture in code blocks, writing information to a log file, creating dump files, restarting the process, or switching to other active nodes, etc. In this article, I will talk about how to catch errors or exceptions at a global scope in a process, and the correspondent handling, separation of the code doing business logic, as the in place try-catch or __try-__except-__finally with code doing real things are usually distractions, though really needed sometimes. and the handling of it. Once captured, I can write the error and related information to console or a log file, or creating a dump file, or both.

All the test in this article were done on Windows 7 SP1 and Visual Studio 2015 Express with update 3.

1. Windows Error Reporting Service

First, let's start with a simple piece of code, which has an error causing different kinds of results based on system configuration or code. Here is the piece of code, code sample 1:

int main()
{
    int i = 10, j = 0;
    int k = i / j;
    cout << " k is: " << k << endl;
    return 0;
}

It is simple and obvious. A “division by zero” error or exception is thrown from line 4, to the system as it is not handled within the process yet. When running the program, this is prompted.

Error Window
Error Window

Error Report
Error Report

The exception causes the program crash, users are prompted with the above dialogues for a choice to send this information to Microsoft or cancel, which means these files are to be deleted and there are no data or files collected by Windows for developers to investigate.

A tiny dump file and program configuration information files are created by Windows by default in the user temporary directory. You can actually debugging it before you make a choice on the prompted dialogues, or copy the files to other placesfor later debugging.

Report Location
Report Location

It is the Windows Error Reporting Service who catches the exception and created these files and prompts the user for a choice. This service is mainly for windows technical analysis and support, to investigate errors/bugs in Windows Libraries and services. In most commercial applications these files are not sent due to possible confidential information leakage, at least they are not sent to Microsoft for every error.

More seriously, WerFault.exe, which is the process name of Windows Error Reporting Service, hangs the process there before the user makes a choice. If your application is a service, or a server application, usually it should be restarted as soon as possible to decrease the down time. So this service is not so attractive. If you search it on the Internet, you will find a lot of discussions and issues about it and most of the suggestions are to disable or remove it.

Briefly, to disable this service, go to the services panel and change it.

Service
Service

2. Application Error Message Box

Now you run the our first program again, an application error dialog or message box is prompted, not the ones from WerFault.exe, but another annoying one, which hangs the process also, to indicate application critical error. This is always terrifying to end users or customers, as they have no idea about what the problem is, but the red circle with white cross is pretty alarming. This message is from Windows too, as Windows handles critical application errors at the operating-system level by default.

Critical Application Error
Critical Application Error

Just like WerFault.exe prompted dialogues, you don't like this dialog either. How to get rid of it, so the program can restart, or shutdown not so noisily? Windows operating system needs to be informed not to show a message box for critical error messages.

There is a Windows API to set the error mode, which means, when unexpected error happens, how windows operating system should respond. The API is SetErrorMode. You can use this API to disable this dialog.

Here is the code about using SetErrorMode, code sample 2.

int main()
{
   ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
   int i = 10, j = 0;
   int k = i / j;
   cout << " k is: " << k << endl;
   return 0;
}

Now run this program, there is nothing prompted, the process just exits like nothing happened.

Users are not prompted, and the process is terminated and can be restarted now. But this silent error and exit pattern is annoying as you have no clues at all, you don't even know if a problem has happened or not and when, where and why the problem happens.

3. Capture Exceptions Within Your Application

So you need a kind of error and exception processing mechanisms within the program, which can terminate the process in time and collect enough data for developers to investigate. One way is using Windows API SetUnhandledExceptionFilter This API sets a filter function (UnhandledExceptionFilter) to receive and process the exception with an exception pointer, which contains all the information about this error. The simple way is to trace the error and then let the process die peacefully.

Here is the code, code sample 3.

LONG __stdcall DebugExceptionFilter(EXCEPTION_POINTERS* pExPtrs)
{
   cerr << "Exception captured, code: " << std::hex << pExPtrs->ExceptionRecord->ExceptionCode 
        << std::dec << endl;
   return 1;
}

int main()
{
   LPTOP_LEVEL_EXCEPTION_FILTER prev1 = SetUnhandledExceptionFilter(DebugExceptionFilter);
   int i = 10, j = 0;
   int k = i / j;
   cout << " k is: " << k << endl;
   cout << "continue processing!" << endl;
   return 0;
}

So this filter function gets the exception pointer and print out the exception code. Returning 1 in this function means to the system that if SetErrorMode is called properly, no applicator error message box is to be displayed. Running this program and you will get this in the console and nothing more.

Exception captured, code: c0000094

From this exception code, you can search MSDN to get the name of the exception and know what exception it is.

This is nice, but not good enough. You know the exception code, but you still need the call stack information, stack data, heap maybe, etc. To do that, using some third-party libraries is an option, but alternatively and less dependently, you can create a dump file in this filter function using Windows API MiniDumpWriteDump.

4. Create a Dump File When Exception Happens

It is not hard to write dump files in your own code, here is the code, code sample 4.

string PrintError()
{
   unsigned long errCode = ::GetLastError();
   std::stringstream ss;
   ss << "error code: " << errCode << endl;
   return ss.str();
}

bool CreateMiniDump(void * exceptionPtrs)
{
   auto now = std::chrono::system_clock::now();
   auto in_time_t = std::chrono::system_clock::to_time_t(now);

   std::stringstream ss;
   ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d-%H-%M-%S");
   string file = ss.str() + ".dmp";

   HANDLE hFile = ::CreateFileA(file.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
      cerr << "CreateFileA failed, " << PrintError() << endl;
   }

   MINIDUMP_EXCEPTION_INFORMATION mei;
   mei.ThreadId = GetCurrentThreadId();
   mei.ExceptionPointers = (PEXCEPTION_POINTERS)exceptionPtrs;
   mei.ClientPointers = FALSE;

   cout << "Creating MiniDump in file: " << file << ", exception pointer: " << exceptionPtrs << endl;
   if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpWithFullMemory, exceptionPtrs ? &mei : NULL, NULL, NULL))
   {
      cerr << "MiniDumpWriteDump failed, " << PrintError() << endl;
   }

   ::CloseHandle(hFile);

   return true;
}

LONG __stdcall DebugExceptionFilter(EXCEPTION_POINTERS* pExPtrs)
{
   cerr << "Exception captured, code: " << std::hex << pExPtrs->ExceptionRecord->ExceptionCode
      << std::dec << endl;
   
   CreateMiniDump(pExPtrs);

   return 0;
}

int main()
{
   ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
   LPTOP_LEVEL_EXCEPTION_FILTER prev1 = SetUnhandledExceptionFilter(DebugExceptionFilter);
   int i = 10, j = 0;
   int k = i / j;
   cout << " k is: " << k << endl;
   return 0;
}

In this example, when exception happens, the filter function prints the exception code, and then creates a dump file for the process. The dump file has all the process data at that time when problem happens, call stack, stack data, heap data, system resources, etc. It is much easier for developers to investigate later, and this way the program terminates peacefully also and can just be restarted and the service keeps running. Everybody wins!

Exception captured, code: c0000094
Creating MiniDump in file :2017-02-19-13-56-59.dmp, exception pointer: 0042F34C

In the current directory of the process, a dump file is created. The file name is the time stamp, of course, you can customize it to anything you like, maybe with process name, process id, user name, machine name, etc, especially with a time stamp is helpful for developers to know the time of the occurrence of the problem easily.

Coredump File Name
Coredump File Name

5. Capture C++ Typed Exception

In all above examples, the exception is Windows/C/Structured exception. What about C++/Typed exceptions? Does this filter function cover C++ exceptions also?

Let's try it. Here is the code with a C++ class simply throwing an exception, code sample 5.

class Foo
{
public:
   void ThrowIt()
   {
      throw std::runtime_error("just a test");
   }
};

int main()
{
   ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
   LPTOP_LEVEL_EXCEPTION_FILTER prev1 = SetUnhandledExceptionFilter(DebugExceptionFilter);
   Foo foo;
   foo.ThrowIt();
   return 0;
}

Running it you will get this in the output. The exception is captured by the filter function and a dump file is created. And process has died properly, same as the programs for Windows exceptions.

Exception captured, code: e06d7363
Creating MiniDump in file: 2017-02-19-14-24-17.dmp, exception pointer: 0022F134

Open the dump file in a Visual Studio to debug it, the call stack is as below.

Debugging Coredump
Debugging Coredump

So all the information are in the dump file and they are very help for the investigation. Once developers get this dump file, they are much closer to the fix of the problem.

6. Capture Exception in a thread

Now all we have tested is for a process with only 1 thread. How about multiple threads? Does this filter function cover other thread than the main thread?

Let's change the code to add a thread. Here is the code, code sample 6.

void foo()
{
   cout << "thread is started!" << endl;

   int i = 10, j = 0;
   int k = i / j;
   cout << " k is: " << k << endl;
}

int main()
{
   ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
   LPTOP_LEVEL_EXCEPTION_FILTER prev1 = ::SetUnhandledExceptionFilter(DebugExceptionFilter);
   
   std::thread first(foo);
   first.join();
   
   return 0;
}

Using C++ thread library, a simple thread is added, simply does a 'division-by-zero' operation. Running the program, the exception code is printed and a dump file is created, which means, the filter function works perfects in a multiple thread application.

thread is started!
Exception captured, code: c0000094
Creating MiniDump in file: 2017-02-19-16-34-33.dmp, exception pointer: 009AF114
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • **2014真题Directions:Read the following text. Choose the be...
    又是夜半惊坐起阅读 9,355评论 0 23
  • PLEASE READ THE FOLLOWING APPLE DEVELOPER PROGRAM LICENSE...
    念念不忘的阅读 13,430评论 5 6
  • 早 酱香饼 中午面条 晚上和同事逛街 黄山行去不了了,等下次。
    丽丽我我阅读 88评论 0 0
  • 现在是2017的第一天,很开心,没有出去浪,我感觉过得也是小幸福,和朋友出去吃了烤肉感觉很幸福 ,回来和老朋友聊天...
    蛋挞萌阅读 151评论 0 0
  • 一个成熟的人要承载自己的情绪,承载他人的情绪,同时不被他人的情绪所左右。可是做到这一点太难了。 和不成熟的人相处久...
    小流域阅读 274评论 0 0