⭐⭐⭐⭐⭐Linux C/C++ 进程崩溃诊断以及有效数据收集:解锁代码问题快速定位与修复的方法

简介: ⭐⭐⭐⭐⭐Linux C/C++ 进程崩溃诊断以及有效数据收集:解锁代码问题快速定位与修复的方法

引言

软件开发的过程中,进程崩溃是一个常见的问题。进程崩溃通常是由于程序中的错误或异常引起的,如内存泄漏、空指针解引用、数组越界访问等。这些问题可能会导致程序在运行时突然终止,给用户带来不便并影响软件的稳定性。

进程崩溃后,开发者需要对问题进行调查和诊断,以便找出问题的根源并修复它。在这种情况下,收集崩溃时的信息变得至关重要。这些信息可以帮助开发者了解崩溃发生的上下文,缩小问题范围,加快定位和解决问题的速度。

崩溃信息包括堆栈跟踪、系统信息、线程信息等,这些信息可以帮助开发者快速地找到问题所在。例如,堆栈跟踪可以显示函数调用的顺序,从而让开发者了解导致崩溃的函数调用路径。系统信息可以帮助开发者判断是否是由于某些特定的硬件或操作系统引起的问题。线程信息有助于了解多线程程序中可能存在的竞争条件或死锁等问题。

为了有效地收集崩溃信息,开发者可以使用信号处理函数来捕获进程中的异常信号。通过在信号处理函数中收集和记录崩溃时的各种信息,开发者可以更容易地分析和解决问题。

总之,进程崩溃后收集信息是软件开发和维护过程中的关键步骤。通过对崩溃信息的分析和理解,开发者可以更快地识别问题、修复错误并提高软件的稳定性和可靠性。


崩溃信息的类型

  • 堆栈跟踪(Stack trace)
    堆栈跟踪提供了在崩溃发生时函数调用的顺序,包括函数名、参数和返回地址。这有助于开发者了解导致崩溃的函数调用路径,以及问题可能发生的位置。
  • 系统信息(System information)
    系统信息包括操作系统版本、硬件配置、已安装的软件以及其他环境信息。这些信息有助于判断问题是否由特定的硬件、操作系统或软件引起,并有助于重现问题。
  • 线程信息(Thread information)
    线程信息可以帮助了解多线程程序中可能存在的竞争条件、死锁等问题。这些信息包括线程的状态、标识符、优先级以及线程栈等。
  • 内存使用情况(Memory usage)
    内存使用情况信息包括进程在崩溃时分配的内存大小、已使用内存、内存泄漏等。分析这些信息可以帮助开发者找到潜在的内存问题,如内存泄漏或分配失败。
  • 信号来源和上下文信息(Signal source and context information)
    当异常信号触发时,可以获取信号的来源和触发原因。此外,上下文信息包括寄存器值、指令地址等,有助于进一步分析崩溃原因。

设置信号处理函数(Setting up signal handlers)

为了有效地收集崩溃信息,开发者需要设置信号处理函数来捕获进程中的异常信号。信号处理函数可以在异常发生时被自动调用,并收集和记录崩溃时的各种信息。信号处理函数的设置通常涉及以下步骤:

a. 定义信号处理函数,用于在接收到异常信号时执行相应的操作,如收集崩溃信息。

b. 注册信号处理函数,将其与特定的异常信号关联起来。

c. 在程序运行过程中,信号处理函数会自动捕获异常信号并执行相应操作。

通过设置信号处理函数并收集崩溃信息,开发者可以更容易地分析和解决问题,从而提高软件的稳定性和可靠性。

信号来源和上下文信息

使用 siginfo_t 结构体获取信号来源信息

当信号处理函数被调用时,操作系统会将一个 siginfo_t 结构体传递给处理函数。siginfo_t 结构体包含了关于信号的详细信息,如信号类型、发送信号的进程或线程 ID、触发信号的原因等。通过分析 siginfo_t 结构体,可以了解信号的来源和触发原因。

使用 ucontext 结构体获取上下文信息

信号处理函数除了可以接收 siginfo_t 结构体外,还可以接收一个 ucontext 结构体。ucontext 结构体包含了信号发生时的上下文信息,如寄存器值、指令地址等。通过分析 ucontext 结构体,可以了解崩溃发生时的程序状态,从而更准确地定位问题。

将崩溃信息写入日志

收集到信号来源和上下文信息后,将这些信息写入日志文件或发送给开发者是一种有效的诊断方法。可以使用以下方法将崩溃信息写入日志:

使用标准 I/O 函数,如 fprintf,将信号来源和上下文信息写入文件。需要确保文件 I/O 操作在信号处理函数中是安全的。

使用 syslog(POSIX)或 Event Log(Windows)将崩溃信息记录到系统日志中,以便在远程或集中式日志系统中查看。

使用网络通信库,如 libcurl,将崩溃信息发送给开发者或崩溃报告服务器,以便实时收集和分析崩溃数据。

通过收集信号来源和上下文信息,并将这些信息写入日志或发送给开发者,可以更快地分析和解决崩溃问题,从而提高软件的稳定性和可靠性。


标准的信号处理函数示例

关于信号的相关资料可以看这几篇文章

Linux之信号介绍/列表:列举Linux系统中常见的信号及其含义和用途

Linux系统编程之信号使用:介绍信号的基本概念、用法和实现方式

Linux系统编程之信号集:介绍信号集的基本概念、用法和实现方式

static void staticFailureSignalHandler(int signum, siginfo_t *signal_info, void *ucontext) {
    std::cout << "Signal number: " << signum << std::endl;
    // 使用 siginfo_t 结构体中的信息
    if (signal_info) {
        std::cout << "Signal code: " << signal_info->si_code << std::endl;
        std::cout << "Fault address: " << signal_info->si_addr << std::endl;
    }
    // 使用 ucontext 结构体中的信息
    if (ucontext) {
        ucontext_t *uc = static_cast<ucontext_t *>(ucontext);
#ifdef __linux__
        std::cout << "Instruction pointer: 0x" << std::hex << uc->uc_mcontext.gregs[REG_RIP] << std::endl;
#elif __APPLE__
        std::cout << "Instruction pointer: 0x" << std::hex << uc->uc_mcontext->__ss.__rip << std::endl;
#endif
    }
    _Exit(EXIT_FAILURE);
}

辅助信息(打印信号用途)

#include <string>
std::string getSignalDescription(int signal) {
    std::string signalDescription;
    switch (signal) {
        case SIGSEGV:
            signalDescription = "Segmentation fault";
            break;
        case SIGABRT:
            signalDescription = "Aborted";
            break;
        case SIGFPE:
            signalDescription = "Floating point exception";
            break;
        // Add other signal cases here
        default:
            signalDescription = "Unknown signal";
            break;
    }
    return signalDescription;
}

获取堆栈跟踪

关于堆栈的作用: 此文章有介绍 堆栈的作用

获取堆栈跟踪是分析崩溃信息的关键步骤。下面介绍两种获取堆栈跟踪的方法:使用 POSIX 的 backtrace 函数和使用跨平台的 libunwind 库。

使用 backtrace 函数(POSIX)

在 POSIX 兼容的系统(如 Linux 和 macOS)上,可以使用 backtrace 函数来获取当前线程的堆栈跟踪。首先,需要包含头文件 ,然后调用 backtrace 函数:

#include <execinfo.h>
#include <stdio.h>
void print_stack_trace() {
    void *buffer[100];
    int size = backtrace(buffer, sizeof(buffer) / sizeof(void *));
    char **symbols = backtrace_symbols(buffer, size);
    for (int i = 0; i < size; i++) {
        printf("%d: %s\n", i, symbols[i]);
    }
    free(symbols);
}

使用 libunwind(跨平台)

libunwind 是一个跨平台的库,用于获取堆栈跟踪。首先,需要安装 libunwind,然后包含头文件 ,并使用相应的 API

#include <libunwind.h>
#include <stdio.h>
void print_stack_trace() {
    unw_cursor_t cursor;
    unw_context_t context;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    while (unw_step(&cursor) > 0) {
        unw_word_t offset;
        char symbol[256] = {"<unknown>"};
        unw_word_t ip;
        unw_get_reg(&cursor, UNW_REG_IP, &ip);
        if (unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset) == 0) {
            printf("0x%lx: %s (0x%lx)\n", ip, symbol, offset);
        } else {
            printf("0x%lx: %s\n", ip, symbol);
        }
    }
}

core dump 文件

此文查看 :Linux 中 core dump 文件的作用和使用方法

在进程崩溃时,捕获崩溃信号并获取堆栈信息与生成 core dump 文件之间不会冲突。您可以在信号处理函数中获取堆栈信息并执行您需要的操作。同时,您可以通过设置 RLIMIT_CORE 资源限制来保证在进程崩溃时仍然生成 core dump 文件。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <execinfo.h>
#include <sys/resource.h>
void signal_handler(int sig) {
    void *buffer[30];
    size_t size;
    // 获取堆栈信息
    size = backtrace(buffer, sizeof(buffer) / sizeof(void *));
    fprintf(stderr, "Error: signal %d:\n", sig);
    backtrace_symbols_fd(buffer, size, STDERR_FILENO);
    exit(1);
}
int main() {
    // 设置资源限制以便在进程崩溃时生成 core dump 文件
    struct rlimit rlim;
    rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
    setrlimit(RLIMIT_CORE, &rlim);
    signal(SIGSEGV, signal_handler); // 捕获 SIGSEGV 信号(例如空指针解引用)
    // 引发一个空指针解引用错误
    int *ptr = NULL;
    *ptr = 42;
    return 0;
}

获取 CPU 使用情况

获取 CPU 使用情况:记录崩溃时的 CPU 使用情况,以确定是否存在性能问题。

bool getProcessCPUUsage(std::string &cpuUsageInfo) {
    std::ifstream stat("/proc/self/stat");
    if (!stat.is_open()) {
        cpuUsageInfo = "Error: Unable to open /proc/self/stat.";
        return false;
    }
    std::string line;
    std::getline(stat, line);
    stat.close();
    std::istringstream iss(line);
    std::vector<std::string> tokens{std::istream_iterator<std::string>{iss},
                                    std::istream_iterator<std::string>{}};
    if (tokens.size() < 17) {
        cpuUsageInfo = "Error: Unexpected format in /proc/self/stat.";
        return false;
    }
    long utime = std::stol(tokens[13]);
    long stime = std::stol(tokens[14]);
    long cutime = std::stol(tokens[15]);
    long cstime = std::stol(tokens[16]);
    cpuUsageInfo = "User time: " + std::to_string(utime) + " ticks\n";
    cpuUsageInfo += "System time: " + std::to_string(stime) + " ticks\n";
    cpuUsageInfo += "Children user time: " + std::to_string(cutime) + " ticks\n";
    cpuUsageInfo += "Children system time: " + std::to_string(cstime) + " ticks\n";
    return true;
}

结果示例

User time: 0 ticks
System time: 1 ticks
Children user time: 0 ticks
Children system time: 0 ticks

这意味着进程在系统模式下花费了一点时间,但在用户模式下几乎没有花费时间。同时,它的子进程(如果有的话)没有花费任何用户或系统时间。下面是每种类型的时间的简要解释:

1. User Time (0 ticks)

这表示进程在用户模式下运行的时间。用户模式是一个受限制的处理模式,用于应用程序和一些系统进程。在这种模式下,进程无法直接访问硬件或引用受保护的内存地址。0 ticks 表示进程还没有在用户模式下运行,或者运行的时间很短。

2. System Time (1 tick)

这表示进程在内核模式下运行的时间。内核模式不受用户模式的限制,进程可以直接访问硬件和受保护的内存地址。1 tick 表示进程在内核模式下运行了一小段时间。

3. Children User Time (0 ticks) 和 Children System Time (0 ticks)

这些值表示所有已终止的子进程在用户模式和内核模式下分别运行的时间。这两个值都是 0,意味着要么没有子进程,要么子进程还没有花费任何时间在这两种模式下运行。

原因和分析

  • 进程刚启动:如果进程刚刚启动,这些值可能很小,因为进程还没有机会执行太多操作。
  • 轻量级进程:对于不执行密集计算或系统调用的轻量级进程,这些值也可能很小。
  • 采样时间点:这些值取决于你检查 /proc/self/stat 的具体时间。如果你在进程刚启动时立即检查,那么这些值可能会很小。

要获得更详细的信息,你可能需要让进程运行更长时间,或者对进程进行更复杂的操作,然后再检查这些值。

获取操作系统和硬件信息

获取操作系统和硬件信息对于诊断和解决问题非常重要,因为某些问题可能与特定的操作系统或硬件有关。可以通过以下方法收集操作系统和硬件信息:

操作系统:

使用 uname 函数(POSIX)或 GetVersionEx 函数(Windows)获取操作系统名称、版本、内核版本等信息。

硬件信息:

使用 sysctl 函数(POSIX)或 GetSystemInfo 函数(Windows)获取处理器类型、内存大小、虚拟内存等硬件信息。

示例

#include <string>
#include <fstream>
#include <unistd.h>
#include <sys/utsname.h>
//获取硬件和操作系统信息的函数(仅适用于 Linux):
bool getSystemInfo(std::string &systemInfo) {
    struct utsname sysinfo;
    if (uname(&sysinfo) == -1) {
        return false;
    }
    systemInfo = "Operating System: ";
    systemInfo += sysinfo.sysname;
    systemInfo += "\n";
    systemInfo += "Node name: ";
    systemInfo += sysinfo.nodename;
    systemInfo += "\n";
    systemInfo += "Release: ";
    systemInfo += sysinfo.release;
    systemInfo += "\n";
    systemInfo += "Version: ";
    systemInfo += sysinfo.version;
    systemInfo += "\n";
    systemInfo += "Machine: ";
    systemInfo += sysinfo.machine;
    systemInfo += "\n";
    return true;
}

获取内存使用情况

获取崩溃时的内存使用情况,可以帮助确定是否存在内存泄漏或内存不足的问题。

#include <string>
#include <fstream>
#include <unistd.h>
//获取内存使用情况的函数(仅适用于 Linux)
bool getMemoryUsage(std::string &memoryUsageInfo) {
    std::ifstream meminfo("/proc/self/status");
    if (!meminfo.is_open()) {
        return false;
    }
    std::string line;
    while (std::getline(meminfo, line)) {
        if (line.find("VmRSS") != std::string::npos || line.find("VmSize") != std::string::npos) {
            memoryUsageInfo += line + "\n";
        }
    }
    meminfo.close();
    return true;
}

获取进程资源使用情况

收集进程的资源使用情况可以帮助开发者找到潜在的性能问题或资源泄漏。可以通过以下方法获取进程资源使用情况:

使用 getrusage 函数(POSIX)或 GetProcessMemoryInfo 函数(Windows)获取进程的 CPU 时间、内存使用、页面错误等资源使用情况。

使用 getrlimit 函数(POSIX)或 GetProcessWorkingSetSize 函数(Windows)获取进程的资源限制,如最大内存使用量、最大文件描述符数量等。

getursage示例

#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
/**
 * @brief 获取当前进程的资源使用情况
 * 
 * @param result 存储结果的vector,每条消息用string存储
 * @return bool 成功获取返回true,否则返回false
 */
bool getProcessResourceUsage(std::vector<std::string>& result)
{
    // 获取进程资源使用情况
    struct rusage usage;
    if (getrusage(RUSAGE_SELF, &usage) != 0)
    {
        std::cerr << "Error: Failed to get resource usage." << std::endl;
        return false;
    }
    std::stringstream ss;
    // 用户态CPU时间
    ss << "User CPU time: " << usage.ru_utime.tv_sec << "s " << usage.ru_utime.tv_usec << "us";
    result.push_back(ss.str());
    ss.str("");
    // 系统态CPU时间
    ss << "System CPU time: " << usage.ru_stime.tv_sec << "s " << usage.ru_stime.tv_usec << "us";
    result.push_back(ss.str());
    ss.str("");
    // 页面重映射次数
    ss << "Page reclaims (soft page faults): " << usage.ru_minflt;
    result.push_back(ss.str());
    ss.str("");
    // 页面错误次数
    ss << "Page faults (hard page faults): " << usage.ru_majflt;
    result.push_back(ss.str());
    ss.str("");
    // 发出的阻塞I/O操作次数
    ss << "Block input operations: " << usage.ru_inblock;
    result.push_back(ss.str());
    ss.str("");
    // 已完成的阻塞I/O操作次数
    ss << "Block output operations: " << usage.ru_oublock;
    result.push_back(ss.str());
    ss.str("");
    // IPC消息发送次数
    ss << "IPC messages sent: " << usage.ru_msgsnd;
    result.push_back(ss.str());
    ss.str("");
    // IPC消息接收次数
    ss << "IPC messages received: " << usage.ru_msgrcv;
    result.push_back(ss.str());
    ss.str("");
    // 信号量操作次数
    ss << "Semaphore operations: " << usage.ru_nvcsw + usage.ru_nivcsw;
    result.push_back(ss.str());
    ss.str("");
    // 上下文切换次数
    ss << "Context switches: " << usage.ru_nswap;
    result.push_back(ss.str());
    return true;
}
int main()
{
    std::vector<std::string> resource_usage_info;
    if (getProcessResourceUsage(resource_usage_info))
    {
        for (const auto& info : resource_usage_info)
        {
            std::cout << info << std::endl;
        }
    }
    return 0;
}

这个函数的主要目的是获取当前进程的资源使用情况,并将结果存储到一个 std::vectorstd::string 中。它使用 getrusage 系统调用来获取 rusage 结构,该结构包含了进程的资源使用统计信息。

ru_utime 和 ru_stime 分别表示用户态和系统态的 CPU 时间。

ru_minflt 和 ru_majflt 分别表示软、硬页面错误的次数。

ru_inblock 和 ru_oublock 表示发出和完成的阻塞 I/O 操作次数。

ru_msgsnd 和 ru_msgrcv 表示 IPC 消息的发送和接收次数。

ru_nvcsw 和 ru_nivcsw 表示自愿和非自愿的上下文切换次数。

getrlimit示例

#include <sys/resource.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <limits>
/**
 * @brief 获取当前进程的资源限制
 * 
 * @param result 存储结果的vector,每条消息用string存储
 * @return bool 成功获取返回true,否则返回false
 */
bool getProcessResourceLimits(std::vector<std::string>& result)
{
    // 定义资源类型数组
    const std::vector<int> resource_types = {
        RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE,
        RLIMIT_LOCKS, RLIMIT_MEMLOCK, RLIMIT_MSGQUEUE, RLIMIT_NICE,
        RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_RSS, RLIMIT_RTPRIO, RLIMIT_RTTIME,
        RLIMIT_SIGPENDING, RLIMIT_STACK};
    // 定义资源类型名称数组
    const std::vector<std::string> resource_names = {
        "Address space limit", "Core file size limit", "CPU time limit",
        "Data segment limit", "File size limit", "File lock limit",
        "Locked memory limit", "Message queue limit", "Nice priority limit",
        "File descriptor limit", "Number of processes limit", "Resident set size limit",
        "Real-time priority limit", "Real-time timeout limit", "Pending signals limit",
        "Stack size limit"};
    struct rlimit limit;
    std::stringstream ss;
    for (size_t i = 0; i < resource_types.size(); ++i)
    {
        if (getrlimit(resource_types[i], &limit) == 0)
        {
            ss << resource_names[i] << ": ";
            // 获取软限制
            if (limit.rlim_cur == RLIM_INFINITY)
            {
                ss << "soft limit: unlimited, ";
            }
            else
            {
                ss << "soft limit: " << limit.rlim_cur << ", ";
            }
            // 获取硬限制
            if (limit.rlim_max == RLIM_INFINITY)
            {
                ss << "hard limit: unlimited";
            }
            else
            {
                ss << "hard limit: " << limit.rlim_max;
            }
            result.push_back(ss.str());
            ss.str("");
        }
        else
        {
            std::cerr << "Error: Failed to get resource limit for " << resource_names[i] << std::endl;
            return false;
        }
    }
    return true;
}
int main()
{
    std::vector<std::string> resource_limits_info;
    if (getProcessResourceLimits(resource_limits_info))
    {
        for (const auto& info : resource_limits_info)
        {
            std::cout << info << std::endl;
        }
    }
    return 0;
}

从 /proc 获取线程信息(Linux):

在 Linux 系统中,/proc 文件系统提供了丰富的进程和线程信息。通过读取 /proc 文件系统中的相关文件,可以收集到线程的详细信息。例如:

/proc/[pid]/task:该目录包含进程中每个线程的详细信息。可以遍历此目录以获取进程中所有线程的标识符。

/proc/[pid]/task/[tid]/status:该文件提供了特定线程的状态信息,如线程状态、优先级等。

/proc/[pid]/task/[tid]/stat:该文件包含了特定线程的详细统计信息,如 CPU 使用时间、上下文切换次数等。

关于更加详细的介绍,可以看我这篇文章:

深度剖析Linux进程的内部机制:一探/proc/pid的奥秘


优化信号处理函数

避免信号处理函数中的不安全操作:

信号处理函数可能在程序的任何时刻被调用,因此在编写信号处理函数时应避免使用可能导致竞争条件、死锁或其他未定义行为的操作。以下是一些建议:

避免在信号处理函数中使用全局变量或共享资源,因为这可能导致竞争条件。如果必须使用全局变量,请确保使用原子操作或其他同步机制来保护数据。

避免在信号处理函数中调用可能阻塞的函数,如文件 I/O、内存分配等,因为这可能导致死锁或崩溃。

避免在信号处理函数中修改信号掩码或重新注册信号处理函数,因为这可能导致信号丢失或循环调用。

避免在信号处理函数中使用 exit()失效

使用 siglongjmp 和 sigsetjmp 函数

设置一个非局部跳转标记,然后在信号处理函数中执行 siglongjmp,以便从信号处理函数返回到设置跳转标记的地方。

#include <csetjmp>
#include <csignal>
#include <iostream>
std::sigjmp_buf jumpBuffer;
void signalHandler(int signal) {
    // Call existing functions: getStackTrace, getSysinfo, etc.
    // ...
    // Perform a non-local jump to the point where the jump buffer was set
    std::siglongjmp(jumpBuffer, 1);
}
int main() {
    std::signal(SIGABRT, signalHandler);
    if (std::sigsetjmp(jumpBuffer, 1) == 0) {
        // This block is executed when the program is first run
        // or when returning from the signal handler using siglongjmp
        std::cout << "Running before assertion..." << std::endl;
        assert(false);
    } else {
        // This block is executed after returning from the signal handler
        std::cout << "Recovered from assertion failure." << std::endl;
    }
    return 0;
}

使用 C++11 或更高版本的 std::thread 类

std::thread 类来处理异常不需要使用信号处理函数。可以通过捕获线程抛出的异常来处理断言失败。

#include <cassert>
#include <iostream>
#include <thread>
void worker() {
    std::cout << "Running before assertion..." << std::endl;
    assert(false);
}
int main() {
    try {
        std::thread t(worker);
        t.join();
    } catch (const std::exception& e) {
        // Handle the exception here
        std::cerr << "Caught an exception: " << e.what() << std::endl;
    }
    return 0;
}

获取线程信息

线程信息有助于了解多线程程序中可能存在的竞争条件、死锁等问题。

可以通过以下方法获取线程信息:

线程列表:使用 pthread 库(POSIX)或 CreateToolhelp32Snapshot 函数(Windows)获取进程中所有线程的列表。

线程状态:使用 pthread_attr_getschedparam 函数(POSIX)或 GetThreadPriority 函数(Windows)获取线程的状态、优先级等信息。

线程栈:使用 backtrace 函数(POSIX)或 CaptureStackBackTrace 函数(Windows)获取线程的堆栈跟踪。

通过收集系统信息、进程资源使用情况和线程信息,开发者可以更全面地了解崩溃发生的环境和上下文,从而更快地定位和解决问题。

使用线程库获取线程信息(跨平台)

跨平台的线程库,如 Boost.Thread 或 C++11标准库,提供了一些函数和类来获取线程信息。使用这些库可以在不同操作系统上以统一的方式收集线程信息。例如:

使用 std::thread::get_id 函数(C++11)或 boost::thread::get_id 函数(Boost.Thread)获取线程标识符。

使用 std::thread::hardware_concurrency 函数(C++11)或 boost::thread::hardware_concurrency 函数(Boost.Thread)获取可用的处理器数量,以评估线程并发能力。

通过解析 /proc 目录中的信息来获取线程状态

查看: 深度剖析Linux进程的内部机制:一探/proc/pid的奥秘

#include <dirent.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
struct ThreadInfo {
    int tid;
    char state;
    std::string comm;
};
bool getThreadStatus(std::vector<ThreadInfo>& threadStatus) {
    const std::string proc_path = "/proc";
    DIR* dir = opendir(proc_path.c_str());
    if (dir == nullptr) {
        std::cerr << "Failed to open /proc directory." << std::endl;
        return false;
    }
    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type == DT_DIR) {
            int tid = -1;
            try {
                tid = std::stoi(entry->d_name);
            } catch (const std::invalid_argument&) {
                continue;
            } catch (const std::out_of_range&) {
                continue;
            }
            if (tid > 0) {
                std::string status_file = proc_path + "/" + entry->d_name + "/status";
                std::ifstream in(status_file);
                if (in.is_open()) {
                    ThreadInfo thread_info;
                    thread_info.tid = tid;
                    std::string line;
                    while (std::getline(in, line)) {
                        if (line.substr(0, 5) == "Name:") {
                            thread_info.comm = line.substr(6);
                        } else if (line.substr(0, 6) == "State:") {
                            thread_info.state = line[7];
                            break;
                        }
                    }
                    threadStatus.push_back(thread_info);
                }
            }
        }
    }
    closedir(dir);
    return true;
}

示例用法:

void signalHandler(int signal) {
    // Call existing functions: getStackTrace, getSysinfo, etc.
    // ...
    std::vector<ThreadInfo> threadStatus;
    if (getThreadStatus(threadStatus)) {
        for (const auto& thread_info : threadStatus) {
            std::cout << "TID: " << thread_info.tid
                      << ", Name: " << thread_info.comm
                      << ", State: " << thread_info.state << std::endl;
        }
    } else {
        std::cerr << "Failed to get thread status." << std::endl;
    }
    // Save or print the collected information
    // ...
}

使用异步信号安全的函数

异步信号安全(Async-signal-safe)函数是指可以在信号处理函数中安全调用的函数,因为它们不会被中断或产生竞争条件。以下是一些建议:

使用 write 函数(POSIX)或 WriteFile 函数(Windows)将崩溃信息写入文件,而不是使用标准 I/O 函数,如 fprintf。

使用 sig_atomic_t 类型来存储信号处理函数中使用的变量,以确保对这些变量的操作是原子的。

使用 sigprocmask 函数(POSIX)或 SetConsoleCtrlHandler 函数(Windows)来阻塞或解除阻塞信号,而不是在信号处理函数中直接修改信号掩码。

通过优化信号处理函数,避免不安全操作并使用异步信号安全的函数,可以降低崩溃时产生的风险,提高信号处理函数的可靠性。这将有助于更准确地收集和分析崩溃信息,从而加快问题定位和解决的速度。


总结

在本文中,我们探讨了如何在软件崩溃时收集关键信息,以便在之后进行诊断和问题解决。我们介绍了信号处理函数的概念,并讨论了如何使用 sigaction 函数注册信号处理函数。我们还详细了解了如何获取堆栈跟踪、收集系统信息、获取线程信息,以及如何获取信号来源和上下文信息。

为了确保有效地记录这些信息,我们讨论了如何选择一个日志库,并在信号处理函数中将收集到的信息写入日志。此外,我们还强调了在编写信号处理函数时遵循的最佳实践,包括避免不安全操作和使用异步信号安全的函数。

收集崩溃信息的重要性不言而喻,它能帮助开发人员更快地定位问题,从而加快修复速度。掌握本文中介绍的方法和技巧,将有助于您更好地应对软件崩溃问题,提高软件的稳定性和可靠性。

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
11月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
378 67
|
10月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
284 16
|
10月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
203 20
|
9月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
215 0
|
9月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
285 0
|
9月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
170 0
|
9月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
231 0
|
6月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
741 2
二、Linux文本处理与文件操作核心命令
|
6月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
435 137
|
6月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
1154 58