#include #include #include #include #include #include #include #include #if defined(__linux__) #include #include #include #include #include #include #include #include #include #include #include #include #endif #include "DroneScrewServerPresenter.h" #include "DroneScrewZmqProtocol.h" #include "Version.h" #include "VrLog.h" #include "VrError.h" #if defined(__linux__) // ========== exit() 拦截 ========== // libff_media.so 内部线程在错误条件下会直接调用 exit() 导致整个进程崩溃。 // 我们拦截 exit() 调用:主线程的 exit 正常放行,子线程的 exit 改为只结束该线程。 namespace { // 真正的 exit 函数指针,启动时初始化 typedef void (*real_exit_t)(int); real_exit_t g_real_exit = nullptr; bool g_exitHookInstalled = false; } // namespace extern "C" void exit(int status) { // 递归保护:如果 exit hook 内部再次调用 exit,直接走 _exit static thread_local bool g_inExitHook = false; if (g_inExitHook) _exit(status); g_inExitHook = true; // 首次调用时获取真正的 exit 地址 if (!g_real_exit) { g_real_exit = reinterpret_cast(dlsym(RTLD_NEXT, "exit")); if (!g_real_exit) _exit(status); // 极端情况:拿不到真 exit,直接用 _exit } // 判断是否为主线程(主线程的 tid == pid) const pid_t tid = static_cast(syscall(SYS_gettid)); const pid_t pid = getpid(); // 获取线程 ID 字符串 std::ostringstream oss; oss << std::this_thread::get_id(); if (tid == pid) { // 主线程调用 exit() — 正常退出流程 LOG_ERROR("========================================\n"); LOG_ERROR("[EXIT-HOOK] exit(%d) called from MAIN thread tid=%d, passing through to real exit\n", status, static_cast(tid)); LOG_ERROR("========================================\n"); g_real_exit(status); } else { // 子线程调用 exit() — 记录 backtrace 并仅结束该线程 LOG_ERROR("========================================\n"); LOG_ERROR("[EXIT-HOOK] exit(%d) called from NON-MAIN thread! tid=%d c++id=%s\n", status, static_cast(tid), oss.str().c_str()); LOG_ERROR("[EXIT-HOOK] Preventing process exit — terminating this thread only.\n"); // 打印调用栈 void* array[30]; const int size = backtrace(array, 30); LOG_ERROR("[EXIT-HOOK-BACKTRACE] %d frames:\n", size); char** messages = backtrace_symbols(array, size); if (messages) { for (int i = 0; i < size; i++) LOG_ERROR(" [%d] %s\n", i, messages[i]); free(messages); } LOG_ERROR("========================================\n"); // 仅退出当前线程,进程继续运行 pthread_exit(nullptr); } } namespace { QString procStatusValue(const char* key) { #if defined(__linux__) QFile f(QStringLiteral("/proc/self/status")); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return QString(); const QByteArray prefix = QByteArray(key) + ':'; while (!f.atEnd()) { const QByteArray line = f.readLine().trimmed(); if (line.startsWith(prefix)) return QString::fromLatin1(line.mid(prefix.size()).trimmed()); } #else Q_UNUSED(key); #endif return QString(); } #if defined(__linux__) std::string getThreadIdStr() { std::ostringstream oss; oss << std::this_thread::get_id(); return oss.str(); } void terminateHandler() { LOG_ERROR("========================================\n"); LOG_ERROR("[TERMINATE] std::terminate() called! thread=%s\n", getThreadIdStr().c_str()); LOG_ERROR("========================================\n"); // 尝试获取未捕获的异常信息 auto eptr = std::current_exception(); if (eptr) { try { std::rethrow_exception(eptr); } catch (const std::exception& e) { int status = 0; char* demangled = abi::__cxa_demangle(typeid(e).name(), nullptr, nullptr, &status); LOG_ERROR("[TERMINATE] uncaught exception type: %s\n", (status == 0 && demangled) ? demangled : typeid(e).name()); LOG_ERROR("[TERMINATE] what(): %s\n", e.what()); if (demangled) free(demangled); } catch (...) { LOG_ERROR("[TERMINATE] uncaught exception (unknown type, not std::exception)\n"); } } else { LOG_ERROR("[TERMINATE] no active exception (terminate called without exception)\n"); } // 打印调用栈 void* array[30]; const int size = backtrace(array, 30); LOG_ERROR("[TERMINATE-BACKTRACE] Stack trace (%d frames):\n", size); char** messages = backtrace_symbols(array, size); if (messages) { for (int i = 0; i < size; i++) LOG_ERROR(" [%d] %s\n", i, messages[i]); free(messages); } else { LOG_ERROR(" (backtrace_symbols failed)\n"); } LOG_ERROR("========================================\n"); LOG_ERROR("[TERMINATE] aborting...\n"); LOG_ERROR("========================================\n"); // 刷新日志后调用默认 abort std::abort(); } void atexitHandler() { LOG_ERROR("========================================\n"); LOG_ERROR("[ATEXIT] Program is exiting normally!\n"); LOG_ERROR("========================================\n"); // 打印调用栈看是谁调用了 exit void* array[30]; const int size = backtrace(array, 30); LOG_ERROR("[ATEXIT-BACKTRACE] Exit call stack (%d frames):\n", size); char** messages = backtrace_symbols(array, size); if (messages) { for (int i = 0; i < size; i++) LOG_ERROR(" [%d] %s\n", i, messages[i]); free(messages); } else { LOG_ERROR(" (backtrace_symbols failed)\n"); } LOG_ERROR("========================================\n"); } void signalHandler(int sig) { const char* sigName = "UNKNOWN"; switch (sig) { case SIGSEGV: sigName = "SIGSEGV (Segmentation Fault)"; break; case SIGABRT: sigName = "SIGABRT (Abort)"; break; case SIGTERM: sigName = "SIGTERM (Termination)"; break; case SIGINT: sigName = "SIGINT (Interrupt)"; break; case SIGILL: sigName = "SIGILL (Illegal Instruction)"; break; case SIGFPE: sigName = "SIGFPE (Floating Point Exception)"; break; case SIGBUS: sigName = "SIGBUS (Bus Error)"; break; case SIGPIPE: sigName = "SIGPIPE (Broken Pipe)"; break; default: break; } LOG_ERROR("========================================\n"); LOG_ERROR("[SIGNAL] Caught signal %d: %s\n", sig, sigName); LOG_ERROR("========================================\n"); // 打印调用栈 void* array[30]; const int size = backtrace(array, 30); LOG_ERROR("[BACKTRACE] Stack trace (%d frames):\n", size); char** messages = backtrace_symbols(array, size); if (messages) { for (int i = 0; i < size; i++) LOG_ERROR(" [%d] %s\n", i, messages[i]); free(messages); } else { LOG_ERROR(" (backtrace_symbols failed)\n"); } LOG_ERROR("========================================\n"); // 对于致命信号,调用默认处理器 signal(sig, SIG_DFL); raise(sig); } #endif } int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); app.setApplicationName(DRONESCREWSERVER_PRODUCT_NAME); app.setApplicationVersion(DRONESCREWSERVER_VERSION_STRING); app.setOrganizationName(DRONESCREWSERVER_COMPANY_NAME); VrLogUtils::InitLog(); VrLogUtils::EnableTime(false); #if defined(__linux__) // 注册 std::terminate 处理器(捕获未处理 C++ 异常,优先级最高) std::set_terminate(terminateHandler); LOG_INFO("[TERMINATE] std::terminate handler registered, main thread=%s\n", getThreadIdStr().c_str()); // 注册 atexit 处理器(捕获正常退出) atexit(atexitHandler); LOG_INFO("[ATEXIT] Exit handler registered\n"); // 注册信号处理器 signal(SIGSEGV, signalHandler); // 段错误 signal(SIGABRT, signalHandler); // 异常终止 signal(SIGTERM, signalHandler); // 终止信号 signal(SIGINT, signalHandler); // 中断信号 (Ctrl+C) signal(SIGILL, signalHandler); // 非法指令 signal(SIGFPE, signalHandler); // 浮点异常 signal(SIGBUS, signalHandler); // 总线错误 signal(SIGPIPE, signalHandler); // 管道破裂 LOG_INFO("[SIGNAL] Signal handlers registered\n"); // 屏蔽 ffmedia 库的 WARN 日志(仅在 ERROR 时打印) extern unsigned int ff_log_level; ff_log_level = 0; // 0=ERROR only, 1=WARN // exit() 拦截已在链接时生效(本可执行文件定义的 exit 符号优先级高于 libc) // 子线程调用 exit() 时仅结束该线程,不会杀死整个进程 LOG_INFO("[EXIT-HOOK] exit() interceptor active: main-thread exit passes through, child-thread exit converts to pthread_exit\n"); #endif LOG_DEBUG("===========================================\n"); LOG_INFO(" %s\n", DRONESCREWSERVER_PRODUCT_NAME); LOG_INFO(" V%s_%s built %s %s\n", DRONESCREWSERVER_VERSION_STRING, DRONESCREWSERVER_VERSION_BUILD, __DATE__, __TIME__); LOG_DEBUG("===========================================\n"); qRegisterMetaType("DroneScrewResult"); qRegisterMetaType("DroneScrewAlgoParams"); qRegisterMetaType("MvsImageData"); DroneScrewServerPresenter presenter; DroneScrewZmqProtocol zmq; // 信号槽:协议 → presenter QObject::connect(&zmq, &DroneScrewZmqProtocol::rawImagePublishEnabledRequested, &presenter, &DroneScrewServerPresenter::setRawPubEnabled, Qt::DirectConnection); QObject::connect(&zmq, &DroneScrewZmqProtocol::detectModeRequested, &presenter, &DroneScrewServerPresenter::setDetectMode, Qt::DirectConnection); QObject::connect(&zmq, &DroneScrewZmqProtocol::setExposureRequested, &presenter, &DroneScrewServerPresenter::handleSetExposure); QObject::connect(&zmq, &DroneScrewZmqProtocol::setGainRequested, &presenter, &DroneScrewServerPresenter::handleSetGain); QObject::connect(&zmq, &DroneScrewZmqProtocol::updateAlgoParamsRequested, &presenter, &DroneScrewServerPresenter::handleUpdateAlgoParams); // presenter → 协议(发布检测结果) QObject::connect(&presenter, &DroneScrewServerPresenter::detectionResult, &zmq, &DroneScrewZmqProtocol::publishDetectionResult); // presenter → 协议(原始图像 PUB)— 同步连接,回调内立即拷贝完成 QObject::connect(&presenter, &DroneScrewServerPresenter::rawImageReady, &zmq, &DroneScrewZmqProtocol::publishRawImage, Qt::DirectConnection); zmq.setSingleDetectionHandler([&presenter]() { return presenter.runSingleDetection(); }); zmq.setStartWorkHandler([&presenter]() { return presenter.startDetectionWork(); }); zmq.setStopWorkHandler([&presenter]() { return presenter.stopDetectionWork(); }); zmq.setStartLiveStreamHandler([&presenter]() { return presenter.startLiveStream(); }); zmq.setStopLiveStreamHandler([&presenter]() { return presenter.stopLiveStream(); }); zmq.setSwapCameraRolesHandler([&presenter]() { return presenter.swapCameraRoles(); }); zmq.setServerInfoProvider([&presenter]() { return presenter.getRuntimeInfo(); }); zmq.setCalibrationInfoProvider([&presenter]() { return presenter.getCalibrationInfo(); }); // 配置文件 —— 运行时配置优先(runtime_config.xml 不会被部署覆盖), // config/config.xml 仅作为出厂默认值。 const QString appDir = QCoreApplication::applicationDirPath(); const QString runtimeCfgPath = QDir(appDir).filePath("runtime_config.xml"); auto findDefaultConfig = [&]() -> QString { const QString a = QDir(appDir).filePath("config/config.xml"); if (QFileInfo::exists(a)) return a; const QString b = QDir::current().filePath("config/config.xml"); if (QFileInfo::exists(b)) return b; return QString(); }; // 之后所有 saveConfiguration 调用都写到 runtime_config.xml presenter.setConfigFilePath(runtimeCfgPath); if (QFileInfo::exists(runtimeCfgPath)) { // 正常路径:加载持久化的运行时配置 LOG_INFO("[CONFIG] loading runtime config: %s\n", runtimeCfgPath.toStdString().c_str()); if (!presenter.loadConfiguration(runtimeCfgPath)) { LOG_WARN("[CONFIG] runtime config load failed, falling back to defaults\n"); const QString fallback = findDefaultConfig(); if (!fallback.isEmpty()) presenter.loadConfiguration(fallback); // loadConfiguration 会覆盖 m_configFilePath,需要重新指回 runtime 路径 presenter.setConfigFilePath(runtimeCfgPath); } } else { // 首次部署:加载出厂默认配置,然后直接复制到 runtime_config.xml const QString defaultCfg = findDefaultConfig(); if (!defaultCfg.isEmpty()) { LOG_INFO("[CONFIG] first run, loading default: %s\n", defaultCfg.toStdString().c_str()); presenter.loadConfiguration(defaultCfg); // loadConfiguration 会覆盖 m_configFilePath,需要重新指回 runtime 路径 presenter.setConfigFilePath(runtimeCfgPath); // 直接复制默认配置文件到运行时位置,保留所有元素 if (QFile::copy(defaultCfg, runtimeCfgPath)) LOG_INFO("[CONFIG] default config copied to: %s\n", runtimeCfgPath.toStdString().c_str()); else LOG_WARN("[CONFIG] failed to copy default config to: %s\n", runtimeCfgPath.toStdString().c_str()); } else { LOG_INFO("[CONFIG] no config file found, using defaults\n"); } } // 初始化各子系统 presenter.initAll(); // 启动 ZMQ zmq.setRtspUrl(presenter.getRtspUrl()); if (!zmq.startServer(presenter.getZmqControlPort(), presenter.getZmqResultPort(), presenter.getZmqRawImagePort())) { LOG_ERROR("ZMQ start fail\n"); return ERR_CODE(NET_ERR_CREAT_INIT); } // raw 图像发布由 start.mode 控制:pull_stream 关闭,subscribe 开启。 presenter.setRawPubEnabled(false); LOG_INFO("=== DroneScrewServer started, ZMQ ctrl=%d result=%d raw=%d, RTSP=%s ===\n", presenter.getZmqControlPort(), presenter.getZmqResultPort(), presenter.getZmqRawImagePort(), presenter.getRtspUrl().toStdString().c_str()); const auto startedAt = std::chrono::steady_clock::now(); QTimer heartbeatTimer; heartbeatTimer.setInterval(60000); QObject::connect(&heartbeatTimer, &QTimer::timeout, [&presenter, startedAt]() { const auto now = std::chrono::steady_clock::now(); const auto uptimeSec = std::chrono::duration_cast(now - startedAt).count(); const QJsonObject info = presenter.getRuntimeInfo(); const QString mode = info["mode"].toString(); const QString rss = procStatusValue("VmRSS"); const QString vmPeak = procStatusValue("VmPeak"); const QString vmSize = procStatusValue("VmSize"); const QString threads = procStatusValue("Threads"); LOG_INFO("[HEARTBEAT] uptime=%llds mode=%s rss=%s vmPeak=%s vmSize=%s threads=%s\n", static_cast(uptimeSec), mode.toStdString().c_str(), rss.isEmpty() ? "N/A" : rss.toStdString().c_str(), vmPeak.isEmpty() ? "N/A" : vmPeak.toStdString().c_str(), vmSize.isEmpty() ? "N/A" : vmSize.toStdString().c_str(), threads.isEmpty() ? "N/A" : threads.toStdString().c_str()); }); heartbeatTimer.start(); return app.exec(); } #else int main() { return -1; } #endif