C++デバッグツールクラス

8682 ワード

未知のすべての例外をキャプチャし、プログラム名を含むCore dumpファイルを生成できます.

/*
 * DebugUtility.h
 *
 *  Created on: Jun 4, 2014
 *      Author: root
 *
 *  Use following two ways to help debugging application when application is crashed:
 *
 *    1 Turn on core dump and generate core dump file by forking child process and rename the child process dump
 *      file to new name. Including application name in core dump file is more easy to identify then default
 *      file name.
 *    2 Overwrite terminate() to handle un-handled exception and print backtrace before abort.
 *      Use gdb to get backtrace and save into log file /tmp/[app_name]_backtrace
 *
 *  Requriement:
 *    Linux, gcc, gdb
 *
 *  How to use it:
 *
 *    Add following lines at the beginning of main function to initialize:
 *    Be attention: app_name can't include path.
 *
 *    char * app_name;
 *	  if ( (app_name = strrchr(argv[0], '/')) == NULL )
 *	  {
 *		DebugUtility::GetInstance().Setup(argv[0], true);
 *	  }
 *	  else
 *	  {
 *		DebugUtility::GetInstance().Setup(app_name + 1, true);
 *	  }
 *
 *    To generate core dump file, you need to call DebugUtility::GetInstance().GenerateCoreDump() in signal
 *    handler code.
 *
 *    For example:
 *
 *    void signal_handler(int signal, siginfo_t * p_signinfo, void * p_context)
 *	  {
 *	    DebugUtility::GetInstance().GenerateCoreDump();
 *	    exit(0);
 *	  }
 *
 *    In main function:
 *
 *    	struct sigaction sa;
 *      sa.sa_sigaction = signal_handler;
 *      sigemptyset(&sa.sa_mask);
 *      sa.sa_flags = SA_RESTART | SA_SIGINFO;
 *      sigaction(SIGTERM, &sa, (struct sigaction *) NULL);
 *
 *    For application name test, if it crashed, you should be able to find core dump file core.test.[pid].
 *
 *    For application name test, if it quit because of raised exception, you should be able to find backtrace
 *    log file /tmp/test_backtrace
 */

#ifndef DEBUGUTILITY_H_
#define DEBUGUTILITY_H_

#include <stdlib.h>
#include <string>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#include <sys/wait.h>
#include <iostream>
#include <exception>

using namespace std;

class DebugUtility
{
public:

	// Overwrite default global terminate handler to handle all un-handled exceptions
	// Print backtrace before calling abort()
	static void my_terminate()
	{
		DebugUtility::GetInstance().PrintBacktrace();

		abort();
	}

	// Use singleton pattern to be sure only one DebugUtility object
	static DebugUtility & GetInstance()
	{
		static DebugUtility instance;

		return instance;
	}

	// Setup debug environment
	// Parameters:
	//   app_name: application name which will be part of core dump and backtrace file.
	//   core_dump_enabled: whether enable core dump, by default true.
	void Setup(string app_name, bool core_dump_enabled = true)
	{
		m_app_name = app_name;

		m_core_dump_enabled = core_dump_enabled;

		re_throw_flag = true;

		if (m_core_dump_enabled)
			EnableCoreDump();

		std::set_terminate(DebugUtility::my_terminate);
	}

	// Generate core dump file based on application name
	// For example application name test and pid is 12345, the core dump file is core.test.12345
	void GenerateCoreDump()
	{
		try
		{
			if (m_core_dump_enabled)
			{
				// Fork child process to generate core dump file
				// Then rename file name with application name

				pid_t childpid = fork();

				// child process generates core dump
				if (childpid == 0)
				{
					// Send SIGABRT signal to terminate child process and generate core dump
					abort();
				}

				if (childpid > 0)
				{
					waitpid(childpid, 0, 0);
				}

				char core_dump_file_name[1000];
				char child_core_dump_file_name[1000];

				// Rename the core dump name.
				sprintf(core_dump_file_name, "core.%s.%d", m_app_name.c_str(), getpid());
				sprintf(child_core_dump_file_name, "core.%d", childpid);

				// try with core.pid
				int rename_rval = rename(child_core_dump_file_name, core_dump_file_name);

				if (rename_rval == -1)
				{
					// try with just core which may happen on HP server
					rename_rval = rename("core", core_dump_file_name);
				}
			}
		} catch (...)
		{
			// Catch all exceptions
		}
	}

	// Try to re-throw exception again and print error message to std:cerr
	// Generate back trace by using gdb and save under /tmp
	// For example application name test, the backtrace file is /tmp/test_backtrace
	void PrintBacktrace()
	{
		try
		{
			// try once to re-throw currently active exception
			if (re_throw_flag)
			{
				re_throw_flag = false;
				throw;
			}
		} catch (const std::exception &e)
		{
			std::cerr << __FUNCTION__ << " caught unhandled exception. what(): " << e.what() << std::endl;
		} catch (...)
		{
			std::cerr << __FUNCTION__ << " caught unknown/unhandled exception." << std::endl;
		}

		try
		{
			char command_line[1000];
			snprintf(command_line, sizeof(command_line), "gdb 2>/dev/null --batch -n -ex thread -ex bt full --exec=%s --pid=%d > /tmp/%s_backtrace",
					m_app_name.c_str(), getpid(), m_app_name.c_str());
			system(command_line);
		} catch (...)
		{
			// Catch all exceptions
		}
	}

	~DebugUtility()
	{
	}

private:

	DebugUtility()
	{

	}

	// Enable core dump
	void EnableCoreDump()
	{
		m_core_dump_enabled = true;
		// Set core dump limit to unlimited to enable core dump
		rlimit core_limit =
		{ RLIM_INFINITY, RLIM_INFINITY };
		setrlimit(RLIMIT_CORE, &core_limit);
	}

	// Don't forget to declare these two. You want to make sure they
	// are unaccessable otherwise you may accidently get copies of
	// your singleton appearing.

	DebugUtility(DebugUtility const&); // Don't Implement
	void operator=(DebugUtility const&); // Don't implement

	// Application name which will be used in backtrace file name and core dump file name
	string m_app_name;

	// Whether generate core dump file
	bool m_core_dump_enabled;

	// Re-throw flag
	bool re_throw_flag;
};

#endif /* DEBUGUTILITY_H_ */


サンプルコードを使用:

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "DebugUtility.h"
#include <exception>
#include <stdexcept>
#include <iostream>

using namespace std;

void signal_handler(int signal, siginfo_t * p_signinfo, void * p_context)
{
	DebugUtility::GetInstance().GenerateCoreDump();
	exit(0);
}

void func1()
{

	throw std::runtime_error("test");
}

void func2()
{
	func1();
}

int main(int argc, char* argv[])
{
	cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!

	char * app_name;
	if ( (app_name = strrchr(argv[0], '/')) == NULL )
	{
		DebugUtility::GetInstance().Setup(argv[0], true);
	}
	else
	{
		DebugUtility::GetInstance().Setup(app_name + 1, true);
	}

	struct sigaction sa;
	sa.sa_sigaction = signal_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART | SA_SIGINFO;

	sigaction(SIGTERM, &sa, (struct sigaction *) NULL);
	sigaction(SIGINT, &sa, (struct sigaction *) NULL);

	func2();

	return 0;
}