← 所有文章

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

現象

執行 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,會把所有使用者環境變數清除,只保留最小集合。被清除的關鍵變數包括:

變數 用途
DISPLAY X11 / XWayland 顯示編號(預設 :0
WAYLAND_DISPLAY Wayland socket 名稱(預設 wayland-0
XAUTHORITY X11 認證 cookie 檔案路徑
XDG_RUNTIME_DIR Wayland socket 所在的父目錄 (/run/user/1000)
DBUS_SESSION_BUS_ADDRESS D-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'

解法總結

步驟 變數 原因
1 XDG_RUNTIME_DIR Wayland socket 位置,缺失則 Wayland plugin 空指標崩潰
2 DISPLAY XWayland 顯示編號
3 WAYLAND_DISPLAY Wayland socket 名稱
4 XAUTHORITY XWayland 認證 cookie
5 DBUS_SESSION_BUS_ADDRESS D-Bus 整合
6 QT_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 表示修復成功。

留言

留言板載入中…