Skip to content

Fix startup segfault and NVIDIA detection under WSL2 - #506

Open
zzccppp wants to merge 2 commits into
Syllo:masterfrom
zzccppp:bugfix/wsl-gpu-detection
Open

Fix startup segfault and NVIDIA detection under WSL2#506
zzccppp wants to merge 2 commits into
Syllo:masterfrom
zzccppp:bugfix/wsl-gpu-detection

Conversation

@zzccppp

@zzccppp zzccppp commented Aug 25, 2026

Copy link
Copy Markdown

Two independent bugs, both visible on WSL2. The first crashes nvtop at startup on any
system where a DRM device's parent has no bound driver; the second makes nvtop report
No GPU to monitor on WSL2 systems where nvidia-smi works fine. They are unrelated,
so they are split into one commit each.

Tested on Ubuntu 24.04 under WSL2 (kernel 5.15.167.4-microsoft-standard-WSL2),
RTX 4090, Windows driver 596.49, built from 3d4a953 with default backend options.


1. Segfault at startup when a DRM device has no driver

nvtop segfaults before drawing anything:

$ nvtop
Segmentation fault (core dumped)
#0  __strcmp_evex ()                       <- rdi = 0x0
#1  add_v3d_cards (devname="/dev/dri/card0") at src/extract_gpuinfo_v3d.c:199
#2  gpuinfo_v3d_get_device_handles ()
#3  gpuinfo_init_info_extraction ()        at src/extract_gpuinfo.c:64
#4  main ()                                at src/nvtop.c:229

add_v3d_cards() discards the return value of nvtop_device_get_driver():

const char *driver;
nvtop_device_get_driver(parent, &driver);
if (strcmp(driver, "v3d"))
  return;

but that function explicitly reports failure by writing NULL and returning -ENOENT
(src/device_discovery_linux.c:73):

int nvtop_device_get_driver(nvtop_device *device, const char **driver) {
  *driver = udev_device_get_driver((struct udev_device *)device);
  return *driver ? 0 : -ENOENT;
}

so strcmp(NULL, "v3d") dereferences a null pointer.

On WSL2 this triggers every time: /dev/dri/card0 is backed by the vgem platform
device, which has no driver symlink in sysfs:

$ readlink -f /sys/class/drm/card0/device
/sys/devices/platform/vgem
$ ls /sys/devices/platform/vgem/
driver_override  drm  modalias  subsystem  uevent      # no 'driver'

Because every compiled-in backend enumerates all DRM devices and filters them by the
parent's driver name, and V3D_SUPPORT defaults to ON on Linux, this null deref in
the v3d backend takes down the whole process during gpuinfo_init_info_extraction() --
including on machines whose actual GPU is a working NVIDIA or AMD card. A default
Linux build of nvtop therefore cannot start at all under WSL2.

The Intel backend already handles this correctly in src/extract_gpuinfo_intel.c:101;
this change makes the v3d backend do the same.

2. No GPU to monitor on WSL2 while nvidia-smi works

With the crash fixed, nvtop still found no GPU, even though nvidia-smi reported the
RTX 4090 normally. gpuinfo_nvidia_init() loaded the wrong NVML:

libnvidia_ml_handle = dlopen("libnvidia-ml.so", RTLD_LAZY);
if (!libnvidia_ml_handle)
  libnvidia_ml_handle = dlopen("libnvidia-ml.so.1", RTLD_LAZY);

Two problems:

  • The unversioned libnvidia-ml.so is a development symlink, while the SONAME
    libnvidia-ml.so.1 is what the runtime driver installs. The SONAME should be tried
    first.
  • The fallback only covers a failing dlopen. When dlopen succeeds but nvmlInit()
    fails, gpuinfo_nvidia_init() just returns false and never tries the other name.

On WSL2 the two names resolve to different files, because /etc/ld.so.conf.d/ld.wsl.conf
puts /usr/lib/wsl/lib on the search path but that directory only ships the SONAME:

dlopen name resolves to nvmlInit_v2()
libnvidia-ml.so.1 /usr/lib/wsl/lib/libnvidia-ml.so.1 (WSL-provided) 0 — success, 1 device
libnvidia-ml.so /usr/lib/x86_64-linux-gnu/libnvidia-ml.so (native driver) 9NVML_ERROR_DRIVER_NOT_LOADED

The native library fails because it wants /dev/nvidiactl, which does not exist under
WSL2 (/dev/dxg is used instead). nvidia-smi works on the same system precisely
because it dlopens the SONAME.

I am aware the README already advises against installing the native Linux driver inside
WSL2, and that is still the right advice. But nvtop does not have to be fatally confused
by it when a usable library is present: trying candidates until one initializes is
strictly more robust, and the dlopen-succeeded-but-nvmlInit-failed case is worth
handling on its own regardless of WSL.

This moves the load-symbols-and-init sequence into gpuinfo_nvidia_init_with_lib() and
tries each candidate in turn, so a failure at any step moves on to the next name. It
also records the NVML error string and dlclose()s the handle when nvmlInit() fails,
which previously leaked the handle and left local_error_string stale.


Testing

  • Both commits build cleanly; each was compiled standalone to keep the history bisectable.
  • Before: Segmentation fault (core dumped).
  • After commit 1 only: starts, prints No GPU to monitor.
  • After both: GPU detected correctly.
$ nvtop -s
[ { "device_name": "NVIDIA GeForce RTX 4090", "gpu_clock": "210MHz", "temp": "33C",
    "power_draw": "34W", "gpu_util": "34%", "mem_total": "51527024640", ... } ]

The interactive TUI renders correctly as well (device bar, PCIe RX/TX, clocks, plots).

I do not have a Raspberry Pi to verify the v3d path still matches on real v3d hardware,
but the change only adds an error check ahead of the existing strcmp, so a device that
previously compared equal to "v3d" still does.

nvtop_device_get_driver() sets *driver to NULL and returns -ENOENT when
the device has no driver bound. add_v3d_cards() ignored the return value
and passed the result straight to strcmp(), which segfaults whenever a
DRM device's parent has no bound driver.

This is hit on every WSL2 system: /dev/dri/card0 is backed by the vgem
platform device, which has no driver symlink in sysfs. Since every
compiled-in backend enumerates all DRM devices and filters them by the
parent's driver name, this NULL deref in the v3d backend crashes nvtop
during gpuinfo_init_info_extraction(), before any interface is set up --
even on machines with a perfectly working NVIDIA or AMD GPU.

Check the return value first, matching what the Intel backend already
does in extract_gpuinfo_intel.c.
gpuinfo_nvidia_init() called dlopen("libnvidia-ml.so") first and only
fell back to "libnvidia-ml.so.1" when that dlopen itself failed. Two
problems with this:

- The unversioned libnvidia-ml.so is a development symlink, whereas the
  SONAME libnvidia-ml.so.1 is the name the runtime driver installs. The
  SONAME is the one that should be tried first.
- The fallback never triggered when dlopen succeeded but nvmlInit()
  failed, so a loadable-but-unusable library was fatal.

Under WSL2 the two names can resolve to different files:
libnvidia-ml.so.1 finds the WSL-provided library in /usr/lib/wsl/lib
which talks to the host GPU, while the unversioned libnvidia-ml.so only
exists in the native Linux driver packages installed inside the distro,
whose nvmlInit() returns NVML_ERROR_DRIVER_NOT_LOADED because
/dev/nvidiactl is absent. nvtop consequently reported "No GPU to
monitor" on systems where nvidia-smi -- which dlopens the SONAME --
works fine.

The README already advises not to install the native driver inside WSL2,
but nvtop can simply pick the library that works instead of failing.

Move the load-symbols-and-init sequence into gpuinfo_nvidia_init_with_lib()
and try each candidate name in turn, so a failure at any step moves on to
the next one. Also record the NVML error string and dlclose the handle
when nvmlInit() fails, which previously leaked.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant