排查nvim异常退出方法

我在 容器化lazy.nvim 遇到一个非常奇怪的问题,就是开始使用nvim,刚打开过了几秒钟就自动退出,也没有提示任何错误。

  • 检查Neovim退出时状态码:

检查退出时
echo $?

返回码显示是 132

Linux 的信号退出码计算公式是 128 + 信号编号132 - 128 = 4 ,而内核信号 4 正好就是 SIGILL

SIGILL 是Linux的一个进程终止信号,缩写自英文 illegal instruction ( 非法指令 )。当一个进程尝试执行以下操作时,CPU无法识别该指令并向进程发送此信号:

  • 损坏的二进制文件 : 程序文件本身损坏或由于传输错误导致指令格式错乱

  • 架构不兼容 : 运行为特定CPU(如ARM或x86)编译的二进制文件,而当前硬件不支持该指令集

  • 执行了非代码数据 : 代码段被意外修改,或由于栈溢出、指针越界,导致程序将普通数据或内存地址误当作机器指令来执行

  • 特权指令异常 : 在普通用户态下执行了仅内核态允许执行的指令

由于我是在 使用OCLP(OpenCore Legacy Patcher)安装最新macOS 环境下使用 Colima ,并且自己构建了 Colima镜像Debian 镜像,涉及的链路很复杂,需要进一步排查。

究竟是nvim存在问题还是(第三方)插件问题

  • 首先需要确认是不是 nvim 自身程序坏了,所以执行以下命令,不加载任何LazyVim配置和插件,干净启动:

不加载任何插件干净启动nvim
nvim -u NONE

经过简单测试,发现没有出现闪退现象,这说明至少neovim自身是好的,怀疑对象转为配置或插件

  • 通过clean方式启动nvim,这样就能加载nvim的基础内置行为,但不加载任何第三方自建插件:

不加载第三方插件启动nvim
nvim --clean

经过验证,依然没有出现闪退,那么证明nvim的基本配置和内置功能都正常,怀疑进一步缩小到第三方插件

  • (这步没有执行)另外一个可能是LSP语言服务器导致的崩溃,所以需要检查 :LspLog 看看是否存在日志报错。或者检查 ~/.local/state/nvim/log 全局日志

  • 由于neovim闪退太快,所以需要通过Linux标准错误重定向和系统核心日志来确定最后一个的报错:

记录crash日志方式启动nvim
# 用最高级别的调试日志启动,并把标准错误抓取到 crash.log 中
nvim -V9crash.log +q

我在nvim异常退出后检查 crash.log 发现有如下错误:

crash日志错误
...
Executing FileType Autocommands for "*"
autocommand if !exists('b:ts_highlight') | 0verbose exe "set syntax=" . expand("<amatch>") | endif

finished sourcing /home/admin/.local/share/mise/installs/neovim/stable/share/nvim/runtime/syntax/syntax.vim
Reading ShaDa file "/home/admin/.local/state/nvim/shada/main.shada" info marks oldfiles
                                                                                       E40: Can't open errorfile errors.err

Executing UILeave Autocommands for "*"

看来就是在访问 /home/admin/.local/state/nvim/shada/main.shada 触发了异常。这个 main.shada 是上一次编辑文件留下的历史记录和光标位置,但是为何处理这个 目录 会导致闪退?

Colima cpuType

我忽然想到我的一个特殊操作,我的 Colima配置 采用了一个非常特殊的 Colima mountType 9p ,为了避免OCLP伪装的内存页表和QEMU冲突,特别将 cpuType 从默认的 cpuType: host 修订为 cpuType: qemu64 。但是为了能够继续使用一些CPU硬件特性,所以将 cpuType 附加了一些指令集特性:

设置qemu64作为CPU类型,针对i7优化
# 强行切到 qemu64 保证不与 OCLP 冲突,但通过逗号追加 Haswell 核心最精髓的加速指令集
cpuType: "qemu64,+ssse3,+sse4.1,+sse4.2,+avx,+avx2,+aes,+popcnt"

这样在Colima VM中看到的CPU是一个特殊规格的 qemu64 处理器。由于gcc编译时默认会自动检测CPU架构和类型,并针对硬件做加速优化。这就有可能触发了误判( gcc -march=native ),由于指令集中包含了 +avx+avx2 ,可能会导致误判并编译出越界的硬件加速码。

备注

实际上我在 Colima镜像 中Dockerfile中使用了 mise 来安装开发软件环境,这导致很多软件包可能采用了错误的优化编译

修正

  • 比较简单的修正是在环境变量中使用比较通用的编译参数:

比较通用的编译参数,使用x86-64基础指令集
# 强制 GCC 只编译最纯粹、没有附加脑补的 x86-64 基础指令集
export CFLAGS="-march=x86-64 -O2"
export CXXFLAGS="-march=x86-64 -O2"
export RUSTFLAGS="-C target-cpu=generic"

这样对于Dockerfile,则使用 ENV 参数命令:

在Dockerfile中配置编译参数
# ==========================================
# 针对特殊虚拟化 CPU (qemu64+custom flags) 的全局安全保障
# ==========================================
# 强制 C/C++ 编译器和 Rust 编译器只生成标准通用的机器码,杜绝激进的硬件加速脑补
ENV CFLAGS="-march=x86-64 -O2" \
    CXXFLAGS="-march=x86-64 -O2" \
    RUSTFLAGS="-C target-cpu=generic"
  • 然后重新编译nvim插件:

重新编译
# 1. 刷新环境变量,确保 CFLAGS 生效
source ~/.bashrc

# 2. 让 mise 强制卸载并重新源码编译
mise uninstall neovim@stable
mise use --global neovim@stable

# 3. 重新编译插件
# 彻底删除 lazy.nvim 下载的所有插件源码及 C 扩展编译产物
rm -rf ~/.local/share/nvim/lazy/

# 彻底删除所有旧的、可能中毒的 Tree-sitter 二进制高亮解析器 (.so)
rm -rf ~/.local/share/nvim/tree-sitter-*

# 清空 Neovim 的状态机和缓存
rm -rf ~/.local/state/nvim/
rm -rf ~/.cache/nvim/

# 让 lazy.nvim 重新下载并触发所有插件(如 blink.cmp 等带 C 扩展的组件)的 Build 脚本
nvim --headless "+Lazy! sync" +qa

备注

经过验证,确实解决了nvim闪退问题。那么意味着之前没有正确配置的 Colima镜像 需要重新再构建一遍,以免编译程序中包含了错误的CPU指令集再导致指令错误。

进一步优化

按照之前 Colima mountType 9pcpuType ,其中手工拼接的参数(SSE4.2 + AVX + AVX2 + POPCNT),恰好完整覆盖了现代Linux社区为解决虚拟化兼容问题,定义的通用的 微架构级别(Microarchitecture Levels) x86-64-v3 ,所以上述编译参数可以在 ~/.bashrc/etc/profile.d/compiler.sh 定义:

自定义cpuType对应的微架构级别 x86-64-v3
# 显式告诉 GCC:不要瞎猜型号,老老实实按标准的 x86-64-v3 规范编译
export CFLAGS="-march=x86-64-v3 -O2"
export CXXFLAGS="-march=x86-64-v3 -O2"

如果发现某些极度激进的编译器(或者旧版本GCC)在 -march=x86-64-v3 下依然会使用 BMI2FMA 指令集,则可以直接使用最高优先级的特征掩码(Feature Masking)明确指定:

明确指定编译参数
# 允许使用 AVX2 和 SSE4.2,但死死锁住引发 132 报错的几个高危脑补盲区
export CFLAGS="-march=x86-64 -msse4.2 -mavx -mavx2 -maes -mpopcnt -mno-bmi -mno-bmi2 -mno-fma -O2"
export CXXFLAGS="-march=x86-64 -msse4.2 -mavx -mavx2 -maes -mpopcnt -mno-bmi -mno-bmi2 -mno-fma -O2"

这里 -mno-bmi -mno-bmi2 -mno-fma 表示即使GCC推测硬件支持 BMI 等指令集,也绝对禁止生成这些指令。

参考

  • gemini