【C/C++基础进阶系列】实战记录 -- Dump 文件生成与分析
【1】Windows 端 Dump 文件的捕获与分析
【1.1】添加 Windows 端 Dump 捕获的代码
代码下载,window COREdump文件生成 c++代码
#ifndef MINIDUMPER_H
#define MINIDUMPER_H
#include <windows.h>
class CMiniDumper
{
public:
// 构造函数
CMiniDumper(bool bPromptUserForMiniDump = true);
// 析构函数
~CMiniDumper(void);
private:
// 自定义的异常处理,由异常过滤器回调
static LONG WINAPI unhandledExceptionHandler(struct _EXCEPTION_POINTERS *pExceptionInfo);
// 以下的方法目的在于,禁用 kernel32.dll 的 SetUnhandledExceptionFilter 方法
void disableSetUnhandledExceptionFilter();
static void __cdecl myInvalidParameterHandler(const wchar_t* expression,
const wchar_t* function,
const wchar_t* file,
unsigned int line,
uintptr_t pReserved);
static void __cdecl myPurecallHandler(void);
// 设置 Dump 记录的文件名
void setMiniDumpFileName(void);
// 该方法作为当前线程没有 token 签名时的替代方法
bool getImpersonationToken(HANDLE* phToken);
// 申请 Debug 权限以便于记录 Dump 信息
BOOL enablePrivilege(LPCTSTR pszPriv, HANDLE hToken, TOKEN_PRIVILEGES* ptpOld);
// 恢复权限信息
BOOL restorePrivilege(HANDLE hToken, TOKEN_PRIVILEGES* ptpOld);
// 记录 Dump 信息
LONG writeMiniDump(_EXCEPTION_POINTERS *pExceptionInfo );
// 保存异常信息的指针
_EXCEPTION_POINTERS *m_pExceptionInfo;
// 缓存 Dump 文件路径
TCHAR m_szMiniDumpPath[MAX_PATH];
// 缓存 App 应用文件路径
TCHAR m_szAppPath[MAX_PATH];
// 缓存 App 应用名称
TCHAR m_szAppBaseName[MAX_PATH];
// 控制是否打印错误信息的变量
bool m_bPromptUserForMiniDump;
// 单例,指向 CMiniDumper 实例用于记录 Dump 信息
static CMiniDumper* s_pMiniDumper;
// 临界区变量
static LPCRITICAL_SECTION s_pCriticalSection;
};
#endif // MINIDUMPER_H
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <tchar.h>
#include <dbghelp.h>
#include <stdlib.h>
#include "MiniDumper.h"
#pragma warning(disable:4996)
#ifdef UNICODE
#define _tcssprintf wsprintf
#define tcsplitpath _wsplitpath
#else
#define _tcssprintf sprintf
#define tcsplitpath _splitpath
#endif
#ifndef LogError
#define LogError printf
#endif
const int USER_DATA_BUFFER_SIZE = 4096;
//-----------------------------------------------------------------------------
// GLOBALS
//-----------------------------------------------------------------------------
CMiniDumper* CMiniDumper::s_pMiniDumper = NULL;
LPCRITICAL_SECTION CMiniDumper::s_pCriticalSection = NULL;
// Based on dbghelp.h
// 生命函数指针
typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess,
DWORD dwPid,
HANDLE hFile,
MINIDUMP_TYPE DumpType,
CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
//-----------------------------------------------------------------------------
// Name: CMiniDumper()
// Desc: Constructor
// 构造函数
//-----------------------------------------------------------------------------
CMiniDumper::CMiniDumper( bool bPromptUserForMiniDump )
{
// Our CMiniDumper should act alone as a singleton.
assert( !s_pMiniDumper );
s_pMiniDumper = this;
m_bPromptUserForMiniDump = bPromptUserForMiniDump;
// The SetUnhandledExceptionFilter function enables an application to
// supersede the top-level exception handler of each thread and process.
//
// SetUnhandledExceptionFilter 可以使得一个应用指定异常处理函数以替代线程/进程的顶层异常处理函数
//
// After calling this function, if an exception occurs in a process
// that is not being debugged, and the exception makes it to the
// unhandled exception filter, that filter will call the exception
// filter function specified by the lpTopLevelExceptionFilter parameter.
//
// 当调用了该函数,若进程中发生了异常(没有被调试),
// 该异常将会被过滤到 lpTopLevelExceptionFilter 指定的函数处理
::SetUnhandledExceptionFilter( unhandledExceptionHandler );
/*
* 为了安全起见(万一有某种新型的异常没有被事先安装的处理器过滤,UEF会被覆盖),
* 可以HOOK掉Kernel32.SetUnhandledExceptionFilter,从而禁止任何可能的覆盖。
*/
_set_abort_behavior(0, _CALL_REPORTFAULT);
_set_invalid_parameter_handler(myInvalidParameterHandler);
_set_purecall_handler(myPurecallHandler);
disableSetUnhandledExceptionFilter();
// Since DBGHELP.dll is not inherently thread-safe, making calls into it
// from more than one thread simultaneously may yield undefined behavior.
//
// DBGHELP.dll 并非内在线程安全的,同时从多个线程调用会触发未定义的行为
//
// This means that if your application has multiple threads, or is
// called by multiple threads in a non-synchronized manner, you need to
// make sure that all calls into DBGHELP.dll are isolated via a global
// critical section.
//
// 多线程调用时需要临界区保护
//
// 初始化临界区变量
s_pCriticalSection = new CRITICAL_SECTION;
if( s_pCriticalSection )
InitializeCriticalSection( s_pCriticalSection );
}
//-----------------------------------------------------------------------------
// Name: ~CMiniDumper()
// Desc: Destructor
// 析构函数
//-----------------------------------------------------------------------------
CMiniDumper::~CMiniDumper( void )
{
// 删除临界区变量
if( s_pCriticalSection )
{
DeleteCriticalSection( s_pCriticalSection );
delete s_pCriticalSection;
}
}
//-----------------------------------------------------------------------------
// Name: unhandledExceptionHandler()
// Desc: Call-back filter function for unhandled exceptions
// 由异常过滤器回调
//-----------------------------------------------------------------------------
LONG CMiniDumper::unhandledExceptionHandler( _EXCEPTION_POINTERS *pExceptionInfo )
{
if( !s_pMiniDumper )
return EXCEPTION_CONTINUE_SEARCH;
// 记录 Dump 信息
return s_pMiniDumper->writeMiniDump( pExceptionInfo );
}
//-----------------------------------------------------------------------------
// 以下的 disableSetUnhandledExceptionFilter / myInvalidParameterHandler / myPurecallHandler
// 目的都是禁用 kernel32.dll 的 SetUnhandledExceptionFilter 方法
//-----------------------------------------------------------------------------
void CMiniDumper::disableSetUnhandledExceptionFilter()
{
void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")),
"SetUnhandledExceptionFilter");
if (addr)
{
unsigned char code[16];
int size = 0;
code[size++] = 0x33;
code[size++] = 0xC0;
code[size++] = 0xC2;
code[size++] = 0x04;
code[size++] = 0x00;
DWORD dwOldFlag, dwTempFlag;
VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
}
}
void CMiniDumper::myInvalidParameterHandler(const wchar_t* expression,
const wchar_t* function,
const wchar_t* file,
unsigned int line,
uintptr_t pReserved)
{
//wprintf(L"Invalid parameter detected in function %s."
// L" File: %s Line: %d\n", function, file, line);
//wprintf(L"Expression: %s\n", expression);
throw 1;
}
void CMiniDumper::myPurecallHandler(void)
{
throw 1;
}
//-----------------------------------------------------------------------------
// Name: setMiniDumpFileName()
// Desc:
// 设置 Dump 记录的文件名
//-----------------------------------------------------------------------------
void CMiniDumper::setMiniDumpFileName( void )
{
time_t currentTime;
time( ¤tTime );
TCHAR chTmp[100];
memset(chTmp, 0, sizeof(chTmp));
struct tm *p;
p = localtime(¤tTime);
if(p == NULL) return;
p->tm_year = p->tm_year + 1900;
p->tm_mon = p->tm_mon + 1;
_tcssprintf(chTmp, _T("%04d-%02d-%02d_%02d-%02d-%02d"),
p->tm_year, p->tm_mon, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);
_tcssprintf( m_szMiniDumpPath,
_T("%s%s_%s.dmp"),
m_szAppPath,
m_szAppBaseName,
chTmp );
}
//-----------------------------------------------------------------------------
// Name: getImpersonationToken()
// Desc: The method acts as a potential workaround for the fact that the
// current thread may not have a token assigned to it, and if not, the
// process token is received.
// 该方法作为当前线程没有 token 签名时的替代方法
//-----------------------------------------------------------------------------
bool CMiniDumper::getImpersonationToken( HANDLE* phToken )
{
*phToken = NULL;
if( !OpenThreadToken( GetCurrentThread(),
TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
TRUE,
phToken) )
{
if( GetLastError() == ERROR_NO_TOKEN )
{
// No impersonation token for the current thread is available.
// Let's go for the process token instead.
if( !OpenProcessToken( GetCurrentProcess(),
TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
phToken) )
return false;
}
else
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Name: enablePrivilege()
// Desc: Since a MiniDump contains a lot of meta-data about the OS and
// application state at the time of the dump, it is a rather privileged
// operation. This means we need to set the SeDebugPrivilege to be able
// to call MiniDumpWriteDump.
// 申请 Debug 权限以便于记录 Dump 信息
//-----------------------------------------------------------------------------
BOOL CMiniDumper::enablePrivilege( LPCTSTR pszPriv, HANDLE hToken, TOKEN_PRIVILEGES* ptpOld )
{
BOOL bOk = FALSE;
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
bOk = LookupPrivilegeValue( 0, pszPriv, &tp.Privileges[0].Luid );
if( bOk )
{
DWORD cbOld = sizeof(*ptpOld);
bOk = AdjustTokenPrivileges( hToken, FALSE, &tp, cbOld, ptpOld, &cbOld );
}
return (bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()));
}
//-----------------------------------------------------------------------------
// Name: restorePrivilege()
// Desc:
// 恢复权限信息
//-----------------------------------------------------------------------------
BOOL CMiniDumper::restorePrivilege( HANDLE hToken, TOKEN_PRIVILEGES* ptpOld )
{
BOOL bOk = AdjustTokenPrivileges(hToken, FALSE, ptpOld, 0, NULL, NULL);
return ( bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()) );
}
//-----------------------------------------------------------------------------
// Name: writeMiniDump()
// Desc:
// 记录 Dump 信息
//-----------------------------------------------------------------------------
LONG CMiniDumper::writeMiniDump( _EXCEPTION_POINTERS *pExceptionInfo )
{
LONG retval = EXCEPTION_CONTINUE_SEARCH;
m_pExceptionInfo = pExceptionInfo;
// 1. 获取当前线程的 token 签名
HANDLE hImpersonationToken = NULL;
if( !getImpersonationToken( &hImpersonationToken ) )
return FALSE;
// You have to find the right dbghelp.dll.
// Look next to the EXE first since the one in System32 might be old (Win2k)
HMODULE hDll = NULL;
TCHAR szDbgHelpPath[MAX_PATH];
// 导入 DBGHELP.DLL 动态库
if( GetModuleFileName( NULL, m_szAppPath, _MAX_PATH ) )
{
TCHAR *pSlash = _tcsrchr( m_szAppPath, '\\' );
if( pSlash )
{
_tcscpy( m_szAppBaseName, pSlash + 1);
*(pSlash+1) = 0;
}
_tcscpy( szDbgHelpPath, m_szAppPath );
_tcscat( szDbgHelpPath, _T("DBGHELP.DLL") );
hDll = ::LoadLibrary( szDbgHelpPath );
}
if( hDll == NULL )
{
// If we haven't found it yet - try one more time.
hDll = ::LoadLibrary( _T("DBGHELP.DLL") );
}
LPCTSTR szResult = NULL;
if( hDll )
{
// Get the address of the MiniDumpWriteDump function, which writes
// user-mode mini-dump information to a specified file.
//
// 获取 MiniDumpWriteDump 函数地址
MINIDUMPWRITEDUMP MiniDumpWriteDump =
(MINIDUMPWRITEDUMP)::GetProcAddress( hDll, "MiniDumpWriteDump" );
if( MiniDumpWriteDump != NULL )
{
TCHAR szScratch[USER_DATA_BUFFER_SIZE];
// 设置 Dump 文件名称
setMiniDumpFileName();
// Ask the user if he or she wants to save a mini-dump file...
_tcssprintf( szScratch,
_T("There was an unexpected error:\n\nWould you ")
_T("like to create a mini-dump file?\n\n%s " ),
m_szMiniDumpPath);
// Create the mini-dump file...
// 创建 Dump 文件
HANDLE hFile = ::CreateFile( m_szMiniDumpPath,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL );
if( hFile != INVALID_HANDLE_VALUE )
{
_MINIDUMP_EXCEPTION_INFORMATION ExInfo;
// 记录当前线程 ID
ExInfo.ThreadId = ::GetCurrentThreadId();
// 记录异常信息
ExInfo.ExceptionPointers = pExceptionInfo;
ExInfo.ClientPointers = NULL;
// We need the SeDebugPrivilege to be able to run MiniDumpWriteDump
// 使能 SeDebugPrivilege 权限
TOKEN_PRIVILEGES tp;
BOOL bPrivilegeEnabled = enablePrivilege( SE_DEBUG_NAME, hImpersonationToken, &tp );
BOOL bOk;
// DBGHELP.dll is not thread-safe, so we need to restrict access...
// 进入临界区
EnterCriticalSection( s_pCriticalSection );
{
// Write out the mini-dump data to the file...
// 记录 Dump 信息到 Dump 文件中
bOk = MiniDumpWriteDump( GetCurrentProcess(),
GetCurrentProcessId(),
hFile,
MiniDumpNormal,
&ExInfo,
NULL,
NULL );
}
// 离开临界区
LeaveCriticalSection( s_pCriticalSection );
// Restore the privileges when done
// 恢复权限设置
if( bPrivilegeEnabled )
restorePrivilege( hImpersonationToken, &tp );
if( bOk )
{
szResult = NULL;
retval = EXCEPTION_EXECUTE_HANDLER;
LogError("Fatal error occurred. Wrote dump file: %s.", m_szMiniDumpPath);
}
else
{
_tcssprintf( szScratch,
_T("Failed to save the mini-dump file to '%s' (error %d)"),
m_szMiniDumpPath,
GetLastError() );
szResult = szScratch;
}
// 关闭文件
::CloseHandle( hFile );
}
else
{
_tcssprintf( szScratch,
_T("Failed to create the mini-dump file '%s' (error %d)"),
m_szMiniDumpPath,
GetLastError() );
szResult = szScratch;
}
}
else
{
szResult = _T( "Call to GetProcAddress failed to find MiniDumpWriteDump. ")
_T("The DBGHELP.DLL is possibly outdated." );
}
}
else
{
szResult = _T( "Call to LoadLibrary failed to find DBGHELP.DLL." );
}
if( szResult && m_bPromptUserForMiniDump )
{
LogError("%s", szResult);
}
// 终止当前进程
TerminateProcess( GetCurrentProcess(), 0 );
return retval;
}
【1.2】基于 Virtual Studio 2019 调试 Dump 文件
选择 "使用 仅限本机 进行调试",配合对应的程序即可定位到出现崩溃的代码段;
【2】Linx 端 Dump 文件的捕获与分析
【2.1】添加 Linux 端 Dump 捕获的代码
// 执行系统命令的函数
string System::execute(const string &cmd) {
FILE *fPipe = NULL;
// 函数说明
// 1. popen() 会调用 fork() 产生子进程,然后在子进程中调用 /bin/sh -c 来执行参数 command 的指令
// 2. 参数 type,“r” 代表读取,“w” 代表写入
// 依照 type 值,popen() 会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针,
// 随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中
// 3. 所有使用文件指针 (FILE*) 操作的函数也都可以使用,除了 fclose() 以外
// 4. 如果 type 为 r,那么调用进程读进 command 的标准输出
// 如果 type 为 w,那么调用进程写到 command 的标准输入
// 返回值 : 若成功则返回文件指针,否则返回 NULL,错误原因存于 errno 中
fPipe = popen(cmd.data(), "r");
if(!fPipe) {
return "";
}
string ret;
char buff[1024] = {0};
// 原型 char *fgets(char *str, int n, FILE *stream)
// 作用 : 从指定的流 stream 读取一行,并把读取的内容存储在 str 所指向的字符串内
// 当读取 (n - 1) 个字符,或读取到换行符,或者到达文件末尾,会停止,具体视情况而定
while(fgets(buff, sizeof(buff) - 1, fPipe)) {
ret.append(buff);
}
// 关闭 popen 函数打开的进程
pclose(fPipe);
return ret;
}
#if !defined(ANDROID) && !defined(_WIN32)
// 崩溃时的信号处理函数
// 对应于 SIGSEGV / SIGABRT 信号
// SIGSEGV : 当一个进程执行了一个无效的内存引用,或发生段错误时发送给的信号
// SIGABRT : 产生原因,1. double free/free 没有初始化的地址或者错误的地址; 2. 堆越界; 3. assert;
static void sig_crash(int sig) {
// SIG_DFL : 默认信号处理程序
signal(sig, SIG_DFL);
void *array[MAX_STACK_FRAMES];
// 原型 : int backtrace(void **buffer, int size);
// 说明 : 该函数获取当前线程的调用堆栈
// 获取的信息将会被存放在 buffer 中,buffer 是一个指针数组,
// 参数 size 用来指定 buffer 中可以保存多少个 void* 元素
// 函数的返回值是实际返回的 void* 元素个数,buffer 中的 void* 元素实际是从堆栈中获取的返回地址
// 参考博客 :
// backtrace 函数, https://www.cnblogs.com/fangyan5218/p/10686488.html
int size = backtrace(array, MAX_STACK_FRAMES);
// 原型 : char **backtrace_symbols(void *const *buffer, int size);
// 说明 : 该函数将 backtrace 函数获取的信息转化为一个字符串数组
// 参数 buffer 是 backtrace 获取的堆栈指针,size 是 backtrace 返回值
// 函数返回值是一个指向字符串数组的指针,它包含 char* 元素个数为 size
// 每个字符串包含了一个相对于 buffer 中对应元素的可打印信息,包括函数名、函数偏移地址和实际返回地址
// 注意 : backtrace_symbols 生成的字符串占用的内存是 malloc 出来的,
// 但是一次性 malloc 出来的,释放是只需要一次性释放返回的二级指针即可
// 参考博客 :
// backtrace 函数, https://www.cnblogs.com/fangyan5218/p/10686488.html
char ** strings = backtrace_symbols(array, size);
vector<vector<string> > stack(size);
for (int i = 0; i < size; ++i) {
auto &ref = stack[i];
std::string symbol(strings[i]);
ref.emplace_back(symbol);
#if defined(__linux) || defined(__linux__)
// 定义 lambda 表达式,用于执行命令将地址转换为程序中的行号
static auto addr2line = [](const string &address) {
// 调用 addr2line 命令
string cmd = StrPrinter << "addr2line -C -f -e " << exePath() << " " << address;
return System::execute(cmd);
};
size_t pos1 = symbol.find_first_of("(");
size_t pos2 = symbol.find_last_of(")");
std::string address = symbol.substr(pos1 + 1, pos2 - pos1 - 1);
ref.emplace_back(addr2line(address));
#endif//__linux
}
// 释放 backtrace_symbols 返回的信息占用的内存空间
free(strings);
// 触发 kBroadcastOnCrashDump 事件通知
// 监听 kBroadcastOnCrashDump 事件的处理器会将崩溃信息写入到 Dump 文件中
NoticeCenter::Instance().emitEvent(kBroadcastOnCrashDump,sig,stack);
}
#endif // !defined(ANDROID) && !defined(_WIN32)
// 系统设置函数
void System::systemSetup() {
#if !defined(_WIN32)
// 函数原型
// int getrlimit( int resource, struct rlimit *rlptr );
// int setrlimit( int resource, const struct rlimit *rlptr );
// 两个函数返回值 : 若成功则返回 0,若出错则返回非 0 值
//
// 在更改资源限制时,须遵循下列三条规则
// 1. 任何一个进程都可将一个软限制值更改为小于或等于其硬限制值
// 2. 任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值,这种降低对普通用户而言是不可逆的
// 3. 只有超级用户进程可以提高硬限制值
//
// 这两个函数的 resource 参数取下列值之一
// RLIMIT_AS 进程可用存储区的最大总长度(字节),这会影响 sbrk 函数和 mmap 函数
// RLIMIT_CORE core 文件的最大字节数,若其值为 0 则阻止创建 core 文件
// RLIMIT_CPU CPU 时间的最大量值(秒),当超过此软限制时,向该进程发送 SIGXCPU 信号
// RLIMIT_DATA 数据段的最大字节长度,这是初始化数据、非初始化数据以及堆的总和
// RLIMIT_FSIZE 可以创建的文件的最大字节长度,当超过此软限制时,则向该进程发送 SIGXFSZ 信号
// RLIMIT_LOCKS 一个进程可持有的文件锁的最大数(此数也包括 Linux 特有的文件租借数)
// RLIMIT_MEMLOCK 一个进程使用 mlock(2) 能够锁定在存储器中的最大字节长度
// RLIMIT_NOFILE 每个进程能打开的最大文件数,更改此限制将影响到 sysconf 函数在参数 _SC_OPEN_MAX 中的返回值
// RLIMIT_NPROC 每个实际用户 ID 可拥有的最大子进程数,
// 更改此限制将影响到 sysconf 函数在参数 _SC_CHILD_MAX 中返回的值
// RLIMIT_RSS 最大驻内存集的字节长度 (resident set size in bytes,RSS),
// 如果物理存储器供不应求,则内核将从进程处取回超过 RSS 的部分
// RLIMIT_SBSIZE 用户在任一给定时刻可以占用的套接字缓冲区的最大长度(字节) (Linux 2.4.22 不支持)
// RLIMIT_STACK 栈的最大字节长度
// RLIMIT_VMEM 与 RLIMIT_AS 的相同 (Linux 2.4.22 不支持)
//
// 常量 RLIM_INFINITY 指定了一个无限量的限制
//
// 参考博客,进程环境之getrlimit和setrlimit函数,https://www.cnblogs.com/nufangrensheng/p/3509262.html
//
// struct rlimit
// {
// /* The current (soft) limit. */
// rlim_t rlim_cur;(当前软限制)
// /* The hard limit. */
// rlim_t rlim_max;(硬限制)
// };
struct rlimit rlim,rlim_new;
// 设置 core 文件的最大字节数
if (getrlimit(RLIMIT_CORE, &rlim)==0) {
rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;
if (setrlimit(RLIMIT_CORE, &rlim_new)!=0) {
rlim_new.rlim_cur = rlim_new.rlim_max = rlim.rlim_max;
setrlimit(RLIMIT_CORE, &rlim_new);
}
InfoL << "core文件大小设置为:" << rlim_new.rlim_cur;
}
// 设置每个进程能打开的最大文件数
if (getrlimit(RLIMIT_NOFILE, &rlim)==0) {
rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;
if (setrlimit(RLIMIT_NOFILE, &rlim_new)!=0) {
rlim_new.rlim_cur = rlim_new.rlim_max = rlim.rlim_max;
setrlimit(RLIMIT_NOFILE, &rlim_new);
}
InfoL << "文件最大描述符个数设置为:" << rlim_new.rlim_cur;
}
#ifndef ANDROID
// 关联信号与信号处理函数
signal(SIGSEGV, sig_crash);
signal(SIGABRT, sig_crash);
signal(SIGFPE, sig_crash);
// 监听 kBroadcastOnCrashDump 信号,记录崩溃信息
NoticeCenter::Instance().addListener(nullptr,kBroadcastOnCrashDump,[](BroadcastOnCrashDumpArgs) {
// 格式化崩溃信息
stringstream ss;
ss << "## crash date:" << getTimeStr("%Y-%m-%d %H:%M:%S") << endl;
ss << "## exe: " << exeName() << endl;
ss << "## signal: " << sig << endl;
ss << "## stack: " << endl;
for (size_t i = 0; i < stack.size(); ++i) {
ss << "[" << i << "]: ";
for (auto &str : stack[i]){
ss << str << endl;
}
}
string stack_info = ss.str();
// 将崩溃信息写入文件
ofstream out(StrPrinter << exeDir() << "/crash." << getpid(), ios::out | ios::binary | ios::trunc);
out << stack_info;
out.flush();
cerr << stack_info << endl;
});
#endif// ANDROID
#endif//!defined(_WIN32)
}
【2.2】Core 文件生成与 gdb 分析
- linux 系统中生成 core 文件的条件
ulimit -c 0 不产生 core 文件
ulimit -c 100 设置 core 文件最大为 100k
ulimit -c unlimited 不限制 core 文件大小
- 基于 gdb 分析 core 文件
gdb <可执行程序路径> <core 文件路径>
从图示中可见,崩溃发生在 main 主函数的第 165 行处;
参考致谢
本博客为博主的学习实践总结,并参考了众多博主的博文,在此表示感谢,博主若有不足之处,请批评指正。