Qt + Display 造成 Segmentation Fault 的原因與解法

June 23, 2026

現象

執行 PyQt5 應用程式時加上 sudo

sudo venv/bin/python3 sparrow-wifi.py

直接 Segmentation Fault (SIGSEGV),沒有任何錯誤訊息,或出現:

qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized.

環境

  • Ubuntu 24.04 / GNOME Wayland
  • Python 3.12 + PyQt5
  • ASUS Zenbook UM5302TA

原因:三個層級的失效

1. sudo 清空 Display 環境變數

Ubuntu 的 sudo 預設啟用 env_reset,會把所有使用者環境變數清除,只保留最小集合。被清除的關鍵變數包括:

變數用途
DISPLAYX11 / XWayland 顯示編號(預設 :0
WAYLAND_DISPLAYWayland socket 名稱(預設 wayland-0
XAUTHORITYX11 認證 cookie 檔案路徑
XDG_RUNTIME_DIRWayland socket 所在的父目錄 (/run/user/1000)
DBUS_SESSION_BUS_ADDRESSD-Bus session bus 位址

沒有這些變數,Qt5 的 QApplication() 無法找到顯示伺服器而直接崩潰。

2. XDG_RUNTIME_DIR 缺失導致 Wayland plugin 初始化失敗

即使手動設了 DISPLAY=:0,如果 XDG_RUNTIME_DIR 未設定,Qt5 的 Wayland platform plugin 在嘗試 wl_display_connect() 時會因為找不到 socket 路徑而回傳 NULL。隨後 Qt5 的 plugin 載入器在迭代可用 plugin 清單時,因為 Wayland plugin 返回了未預期的空指標而觸發 SIGSEGV

測試驗證:

  • XDG_RUNTIME_DIRQApplication() 成功 ✅
  • XDG_RUNTIME_DIRQApplication() SIGSEGV ❌

3. GNOME Wayland 下 QT_QPA_PLATFORM 預設行為導致 XCB 路徑崩潰

GNOME 的 Qt5 整合層有一個特殊邏輯:當偵測到 XDG_SESSION_TYPE=wayland 時,Qt5 會忽略 Wayland 並顯示以下警告:

Warning: Ignoring XDG_SESSION_TYPE=wayland on Gnome. Use QT_QPA_PLATFORM=wayland to run on Wayland anyway.

接著 Qt5 改載入 xcb (XWayland) plugin。但在 root 權限下,XWayland 的連線同樣會因權限問題失敗而 segfault。

必須明確設定 QT_QPA_PLATFORM=wayland 才能強制使用 Wayland plugin,而這個 plugin 在 GNOME Mutter 下允許 root 連線(無 SO_PEERCRED 阻擋)。


解法

QApplication(sys.argv) 之前,偵測是否為 root 執行,並從原使用者的 session 還原關鍵環境變數:

if os.geteuid() == 0: orig_uid = os.environ.get('SUDO_UID', os.environ.get('PKEXEC_UID', '')) orig_user = os.environ.get('SUDO_USER', '') if not orig_uid and not orig_user: for entry in os.listdir('/run/user/'): if entry.isdigit(): uid_path = f'/run/user/{entry}' if os.path.isdir(uid_path) and os.access(uid_path, os.R_OK): wl_test = os.path.join(uid_path, 'wayland-0') if os.path.exists(wl_test): orig_uid = entry break if orig_uid and 'XDG_RUNTIME_DIR' not in os.environ: xdg_rt = f'/run/user/{orig_uid}' if os.path.isdir(xdg_rt): os.environ['XDG_RUNTIME_DIR'] = xdg_rt if 'DISPLAY' not in os.environ: os.environ['DISPLAY'] = ':0' if orig_uid and 'WAYLAND_DISPLAY' not in os.environ: wl_socket = f'/run/user/{orig_uid}/wayland-0' if os.path.exists(wl_socket): os.environ['WAYLAND_DISPLAY'] = 'wayland-0' if 'XAUTHORITY' not in os.environ and orig_uid: xauth_dir = f'/run/user/{orig_uid}' if os.path.isdir(xauth_dir): for entry in os.listdir(xauth_dir): if entry.startswith('.mutter-Xwaylandauth'): os.environ['XAUTHORITY'] = os.path.join(xauth_dir, entry) break if 'DBUS_SESSION_BUS_ADDRESS' not in os.environ and orig_uid: bus_path = f'/run/user/{orig_uid}/bus' if os.path.exists(bus_path): os.environ['DBUS_SESSION_BUS_ADDRESS'] = f'unix:path={bus_path}' if 'WAYLAND_DISPLAY' in os.environ and 'QT_QPA_PLATFORM' not in os.environ: os.environ['QT_QPA_PLATFORM'] = 'wayland'

解法總結

步驟變數原因
1XDG_RUNTIME_DIRWayland socket 位置,缺失則 Wayland plugin 空指標崩潰
2DISPLAYXWayland 顯示編號
3WAYLAND_DISPLAYWayland socket 名稱
4XAUTHORITYXWayland 認證 cookie
5DBUS_SESSION_BUS_ADDRESSD-Bus 整合
6QT_QPA_PLATFORM=wayland★ 防止 GNOME 自動降級到 XCB(XCB 在 root 下會 segfault)

附:快速測試指令

pkexec env DISPLAY=:0 XDG_RUNTIME_DIR=/run/user/1000 \ WAYLAND_DISPLAY=wayland-0 \ XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.* \ QT_QPA_PLATFORM=wayland \ python3 -c " from PyQt5.QtWidgets import QApplication import sys app = QApplication(sys.argv) print('QApp OK') app.quit() "

預期輸出:

QStandardPaths: runtime directory '/run/user/1000' is not owned by UID 0, ...
QApp OK

QStandardPaths 的警告是良性的(不影響功能),看到 QApp OK 表示修復成功。