提问者:小点点

在分段错误处理程序中调用pthread_exit时,在没有活动异常的情况下调用terminate


你好吗?
我将修复Ubuntu 18.04上一个工作线程中的分段错误。
我的代码如下。

#include <thread>
#include <signal.h>
#include <string.h>
#include <pthread.h>
#include <opencv2/opencv.hpp>

void sigsegv_handler(int signum, siginfo_t *info, void *data)
{
    printf("The thread was crashed\n");
    pthread_exit(NULL);
}

void sleep_ms(int milliseconds)
{
#ifdef WIN32
    Sleep(milliseconds);
#elif _POSIX_C_SOURCE >= 199309L
    struct timespec ts;
    ts.tv_sec = milliseconds / 1000;
    ts.tv_nsec = (milliseconds % 1000) * 1000000;
    nanosleep(&ts, NULL);
#else
    usleep(milliseconds * 1000);
#endif
}

void thread_func(int i)
{
    if(i == 3)
    {
        int *p = 0;
        *p = 10;
    }
    printf("A new thread ran successfully\n");
}

int main()
{
    /* Set SIGSEGV handler. */
    struct sigaction  handler;
    sigemptyset(&handler.sa_mask);
    handler.sa_sigaction = &sigsegv_handler;
    handler.sa_flags = SA_SIGINFO;
    if (sigaction(SIGSEGV, &handler, NULL) == -1) 
        fprintf(stderr, "Cannot set SIGSEGV handler: %s.\n", strerror(errno));
    int i = 0;
    while(1)
    {
        std::thread writer_thread(thread_func, i);
        writer_thread.detach();
        sleep_ms(1000);
        printf("%d\n", i++);
    }
    return 0;
}

代码运行良好。
以下是此代码的输出。
新线程成功运行
0
新线程成功运行
1
新线程成功运行
2
线程崩溃
3
新线程成功运行
4
新线程成功运行
5
新线程成功运行
6
新线程成功运行
7

但是如果我按以下方式更改函数“thread_func”,程序就会崩溃。

void thread_func(int i)
{
    if(i == 3)
    {
        int *p = 0;
        *p = 10;
    }
    cv::Mat img(100, 100, CV_8UC3); // newly inserted
    cv::resize(img, img, cv::Size(200, 200)); //newly inserted
    printf("A new thread ran successfully\n");
}

错误消息如下。

已成功运行新线程
0
已成功运行新线程
1
已成功运行新线程
2
线程已崩溃
在没有活动异常的情况下调用终止
已中止(转储核心)

当然,我确信OpenCV模块中没有问题。
您可以帮助我解决此问题吗?
谢谢


共1个答案

匿名用户

简单的答案是你不能这样做:

void sigsegv_handler(int signum, siginfo_t *info, void *data)
{
    printf("The thread was crashed\n");
    pthread_exit(NULL);
}

首先,按照7.1.4库函数的使用,C11标准第4段:

标准库中的函数不保证是可重入的,可能修改具有静态或线程存储持续时间的对象。

或如脚注188所述:

因此,信号处理程序通常不能调用标准库函数。

因此,如果您的平台没有关于可以从信号处理程序中安全调用哪些函数的具体保证,您就不能从信号处理程序中进行任何函数调用。

但是,由于您调用的是pthread_exit(),假设您使用的是POSIX系统,POSIX确实为您可以调用的函数提供了一些保证,称为“async-signal-safe”,位于https://pubs.opengroup.org/onlinepubs/9699919799/functions/v2_chap02.html#tag_15_04_03.Linux特定列表可以在https://man7.org/linux/man-pages/man7/signal-safety7.html中找到

请注意,printf()pthreadexit()都不在这两个列表中。

sigsegv信号处理程序中调用printf()将是危险的--printf()的大多数实现将使用某种形式的malloc()/free(),而sigsegv通常是malloc()/new/free()操作遇到损坏的堆时的结果。堆操作往往在某种类型的锁下进行,以防止堆状态同时被修改,因此在所有事物的sigsegv处理程序中调用printf()会产生巨大的死锁风险。

而且pthread_exit()也会引起很大的问题--它不仅试图更改进程地址空间中的进程状态,还试图更改内核空间中的进程状态。在信号处理程序中,这是行不通的。