1

我正在编写一个基于多线程插件的应用程序。我不会成为插件作者。所以我希望避免主应用程序崩溃导致插件中的分段错误。是否可以?或者插件中的崩溃肯定也会影响主要应用程序状态?我使用 qt 编写了一个草图程序,因为我的“真实”应用程序强烈基于 qt 库。正如你所看到的,我强迫线程在未分配的 QString 上调用修剪函数时崩溃。信号处理程序被正确调用,但在线程被迫退出后,主应用程序也崩溃了。我做错什么了吗?或者就像我之前说的那样,我想做的事情是无法实现的?请注意,在这个简化版本的程序中,我避免使用插件,而只使用线程。引入插件将增加一个新的关键级别,我想。我想一步一步走下去。而且,总的来说,我想了解我的目标是否可行。非常感谢每个人都会尝试给我的任何帮助或建议。

#include <QString>
#include <QThread>
#include<csignal>
#include <QtGlobal>
#include <QtCore/QCoreApplication>


class MyThread : public QThread
{
public:
    static void sigHand(int sig)
    {
        qDebug("Thread crashed");
        QThread* th = QThread::currentThread();
        th->exit(1);
    }

    MyThread(QObject * parent = 0)
    :QThread(parent)
    {
        signal(SIGSEGV,sigHand);
    }

    ~MyThread()
    {
        signal(SIGSEGV,SIG_DFL);
        qDebug("Deleted thread, restored default signal handler");
    }

    void run()
    {
        QString* s;
        s->trimmed();
        qDebug("Should not reach this point");
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyThread th(&a);
    th.run();
    while (th.isRunning());
    qDebug("Thread died but main application still on");
    return a.exec();
}
4

1 回答 1

1

我目前正在处理同样的问题,并通过谷歌找到了这个问题。

您的来源无法正常工作有几个原因:

  • 没有新线程。只有调用 QThread::start 时才会创建线程。相反,您调用 MyThread::run,它在主线程中执行 run 方法。

  • 您调用 QThread::exit 来停止线程,这不应该直接停止线程,而是向线程事件循环发送一个 (qt) 信号,请求它停止。由于既没有线程也没有事件循环,因此该函数无效。即使你调用了 QThread::start,它也不起作用,因为编写一个 run 方法不会创建一个 qt 事件循环。为了能够在任何 QThread 中使用 exit,您需要先调用 QThread::exec。
    但是, QThread::exit 无论如何都是错误的方法。为了防止 SIGSEGV,必须立即调用线程,而不是在其事件循环中接收到 (qt) 信号之后。因此,尽管通常不赞成,但在这种情况下,必须调用 QThread::terminate

  • 但通常认为从信号处理程序调用复杂函数(如 QThread::currentThread、QThread::exit 或 QThread::terminate)是不安全的,因此您永远不应该在那里调用它们

  • 由于线程仍在信号处理程序之后运行(而且我不确定 QThread::terminate 是否会足够快地杀死它),信号处理程序退出到它被调用的位置,因此它重新执行导致 SIGSEGV 的指令,并且下一个 SIGSEGV 发生。

因此我使用了另一种方法,信号处理程序将包含指令地址的寄存器更改为另一个函数,然后在信号处理程序退出后运行该函数,而不是崩溃指令。像:

void signalHandler(int type, siginfo_t * si, void* ccontext){
    (static_cast<ucontext_t*>(ccontext))->Eip = &recoverFromCrash;
}

struct sigaction sa;
memset(&sa, 0, sizeof(sa)); sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = &signalHandler; 
sigaction(SIGSEGV, &sa, 0);

然后通常在导致 SIGSEGV 的线程中调用 recoverFromCrash 函数。由于从所有线程中为所有 SIGSEGV 调用信号处理程序,因此该函数必须检查它在哪个线程中运行。

但是,我认为简单地杀死线程并不安全,因为可能还有其他东西,这取决于正在运行的线程。因此,我没有杀死它,而是让它在无限循环中运行(调用 sleep 以避免浪费 CPU 时间)。然后,当程序关闭时,它会设置一个全局变量,并终止线程。(请注意,recover 函数永远不能返回,否则执行将返回到导致 SIGSEGV 的函数)

另一方面,从主线程调用,它启动一个新的事件循环,让程序运行。

if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
    //sub thread
    QThread* t = QThread::currentThread();
    while (programIsRunning) ThreadBreaker::sleep(1);
    ThreadBreaker::forceTerminate();
} else {
    //main thread
    while (programIsRunning) {
        QApplication::processEvents(QEventLoop::AllEvents);
        ThreadBreaker::msleep(1);
    }
    exit(0);
}

ThreadBreaker 是 QThread 的一个简单的包装类,因为 QThread 的 msleep、sleep 和 setTerminationEnabled(必须在终止之前调用)受到保护,无法从恢复函数中调用。

但这只是基本情况。还有很多其他的事情需要担心:捕捉 SIGFPE,捕捉堆栈溢出(检查 SIGSEGV 的地址,在备用堆栈中运行信号处理程序),有一堆平台独立性定义(64 位、arm、mac ),显示调试消息(尝试获取堆栈跟踪,想知道为什么调用 gdb 会导致 X 服务器崩溃,想知道为什么调用 glibc 回溯会导致程序崩溃)...

于 2012-12-21T12:56:27.457 回答