ArchLinux 下 mpv: undefined symbol: vkCreateWaylandSurfaceKHR 问题解决

ArchLinux 下 mpv: undefined symbol: vkCreateWaylandSurfaceKHR 问题解决

问题现象

  1. 在Gnome Terminal 直接执行 mpv filename.mp4 报错:

mpv: symbol lookup error: mpv: undefined symbol: vkCreateWaylandSurfaceKHR

  1. 使用 smplayer 打开同样的文件, 也是同样的报错.

解决办法

这个解决办法很简单.

但是我要说的是, 这个问题其实我花了很长时间才解决, 并且不是我自己想到的.

产生这个问题的时候, 一般是更新了某些软件之后.

当时我想, 这个是视频相关的, 可能跟显卡驱动相关. 但是后面发现, 即使重启系统, 或者重新编译mpv, 这个问题还是不能解决. 但是有时候重启系统后又能成功打开文件.

报错是 undefined symbol: vkCreateWaylandSurfaceKHR , 但是其实这个问题跟 wayland 无关, 因为我用的是 N 卡 + X11.

搜索了一下, 得知 vkCreateWaylandSurfaceKHR 这个符号来自 libvulkan, 但是本机有正常安装 libvulkan.

❯ paru -Ss libvulkan | rg installed
extra/vulkan-icd-loader 1.2.203-1 [0B 481.45KiB] [Installed]


ls -l /usr/lib/libvulkan* 
lrwxrwxrwx root root  14 B  Fri Jan 21 23:07:49 2022  /usr/lib/libvulkan.so ⇒ libvulkan.so.1
lrwxrwxrwx root root  20 B  Fri Jan 21 23:07:49 2022  /usr/lib/libvulkan.so.1 ⇒ libvulkan.so.1.2.203
.rwxr-xr-x root root 470 KB Fri Jan 21 23:07:49 2022  /usr/lib/libvulkan.so.1.2.203
.rwxr-xr-x root root  11 MB Sat Mar  5 08:14:57 2022  /usr/lib/libvulkan_intel.so


 ❯ strings /usr/lib/libvulkan.so.1 | rg vkCreateWaylandSurfaceKHR  
vkCreateWaylandSurfaceKHR
vkCreateWaylandSurfaceKHR
VK_KHR_wayland_surface extension not enabled. vkCreateWaylandSurfaceKHR not executed!
vkCreateWaylandSurfaceKHR: Invalid instance [VUID-vkCreateWaylandSurfaceKHR-instance-parameter]
  1. 重新编译 mpv 不能解决
  2. 问题只在有时候出现(条件不明确)
  3. undefined symbol 这种错误按以往的经常是特别容易解决的, 安装好对应的lib就好了, 但是现在的情况是, 有正确的 lib 安装了, 依旧报错

所以, 一时间找不到思路了.

直到有一天, 通过搜索看到了reddit 上一个回答. 终于找到了正确的答案.

This is because chrome provides a broken copy of vulkan which is loaded while it tries to execute mpv. The fix is to make a little wrapper at /usr/local/bin/mpv that preloads the system version of vulkan:

#!/bin/sh
export LD_PRELOAD=/usr/lib/libvulkan.so.1
exec /usr/bin/mpv "$@"

简单来说就是, Google Chrome 自带了一个 没有 vkCreateWaylandSurfaceKHR 符号的 libvulkan.so, 并且优先于系统的 libvulkan.so 加载了.

解决办法是通过一个脚本wrapper来启动mpv. 然后设置 smplayer 调用这个wrapper.

未解之迷

这个方法可以解决问题, 但是有一点老灯还没想明白.

为什么 Chrome 会影响整个系统的 LD_LIBRARY_PATH 环境变量 ? 一般来说, 通过一个 shell 脚本设置的变量, 不会影响其它 shell 的.

通过 kitty 执行 /usr/bin/env | rg LD_LIBRARY_PATH 无结果.

在 kitty 下面:

❯ ldd /usr/bin/mpv | rg libvulkan.so.1
	libvulkan.so.1 => /usr/lib/libvulkan.so.1 (0x00007fa58880a000)

唯独在 gnome terminal 下得到的结果是 LD_LIBRARY_PATH=/opt/google/chrome:/opt/google/chrome/lib

自然而然, 加载的是 Google 的 /opt/google/chrome/libvulkan.so.1

❯ /usr/bin/env | rg LD_LIBRARY_PATH
LD_LIBRARY_PATH=/opt/google/chrome:/opt/google/chrome/lib


❯ ldd /usr/bin/mpv | rg libvulkan.so.1
	libvulkan.so.1 => /opt/google/chrome/libvulkan.so.1 (0x00007fd41628b000)

所以, 现在的问题是, 为什么Chrome 启动脚本里的 LD_LIBRARY_PATH 会对 Gome Terminal 或者 smplayer 有效,

而在 kitty 等其它终端里面却获取不到?

尝试重启一次系统, 这次发现, 连 gnome terminal 下也没有 LD_LIBRARY_PATH 这个值了.

所以, 当gnome terminal 下能取到 LD_LIBRARY_PATH 这个值的时候, 发生了什么?

由于无法复现, 这里老灯也已经无法继续探索了.

/usr/lib/gnome-terminal-server (gnome terminal实际干活的进程) 和 smplayer 获取环境变量的方式, 和 kitty 有什么不同?

是否跟安装了 chrome-gnome-shell 有关系?

在 Linux 下面的 Google Chrome 启动实际上是通过 google-chrome 这个shell 脚本启动的 (edge 也一样, 只是重命名为了 microsoft-edge ), 在 ArchLinux 下这个路径是 /opt/google/chrome/google-chrome

其内容如下:

#!/bin/bash
#
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Let the wrapped binary know that it has been run through the wrapper.
export CHROME_WRAPPER="`readlink -f "$0"`"

HERE="`dirname "$CHROME_WRAPPER"`"

# We include some xdg utilities next to the binary, and we want to prefer them
# over the system versions when we know the system versions are very old. We
# detect whether the system xdg utilities are sufficiently new to be likely to
# work for us by looking for xdg-settings. If we find it, we leave $PATH alone,
# so that the system xdg utilities (including any distro patches) will be used.
if ! command -v xdg-settings &> /dev/null; then
  # Old xdg utilities. Prepend $HERE to $PATH to use ours instead.
  export PATH="$HERE:$PATH"
else
  # Use system xdg utilities. But first create mimeapps.list if it doesn't
  # exist; some systems have bugs in xdg-mime that make it fail without it.
  xdg_app_dir="${XDG_DATA_HOME:-$HOME/.local/share/applications}"
  mkdir -p "$xdg_app_dir"
  [ -f "$xdg_app_dir/mimeapps.list" ] || touch "$xdg_app_dir/mimeapps.list"
fi

# Always use our versions of ffmpeg libs.
# This also makes RPMs find the compatibly-named library symlinks.
if [[ -n "$LD_LIBRARY_PATH" ]]; then
  LD_LIBRARY_PATH="$HERE:$HERE/lib:$LD_LIBRARY_PATH"
else
  LD_LIBRARY_PATH="$HERE:$HERE/lib"
fi
export LD_LIBRARY_PATH

export CHROME_VERSION_EXTRA="stable"

# We don't want bug-buddy intercepting our crashes. http://crbug.com/24120
export GNOME_DISABLE_CRASH_DIALOG=SET_BY_GOOGLE_CHROME


# Sanitize std{in,out,err} because they'll be shared with untrusted child
# processes (http://crbug.com/376567).
exec < /dev/null
exec > >(exec cat)
exec 2> >(exec cat >&2)

# Note: exec -a below is a bashism.
exec -a "$0" "$HERE/chrome" "$@"

搜索了系统其它地方(如 /etc/environment, /etc/profile.d 等), 也没有发现设置 LD_LIBRARY_PATH 的, 唯一的只有这个脚本有 export LD_LIBRARY_PATH, 而这个脚本只有在Chrome 启动的时候才会执行, 并且, 这样执行 export LD_LIBRARY_PATH 不应该影响到 gnome terminal 或 smplayer 的环境变量啊?