Nsight Graphics 2026.2: 100% SIGSEGV intercepting GLFW OpenGL context creation (Linux/X11)

(Full disclosure - this issue was investigated and report created with help from Claude Code, with significant direction from me)

Nsight Graphics 2026.2: 100% SIGSEGV intercepting GLFW OpenGL context creation

Summary

Nsight Graphics 2026.2.0.0 crashes the target process 100% of the time when
it intercepts OpenGL/GLX context creation performed by GLFW. The fault is a race
in Nsight’s GL interception layer (libNvda.Graphics.Interception.so): during
glfwCreateWindow, libNvda’s make-context-current hook probes the driver with
glGetString(GL_VENDOR) through a per-context dispatch-table slot that has not
yet been populated, and jumps through the uninitialized slot.

A ~30-line pure-GLFW program reproduces it deterministically. Inserting a
debugger breakpoint at the faulting instruction drops the crash rate to 0%,
which pins it as a race. A Qt/QOpenGLWidget application on the same machine,
under the same Nsight build, does not crash — so this is specific to GLFW’s
context-creation path, not OpenGL-under-Nsight in general.

Environment

Component Version
Nsight Graphics 2026.2.0.0 (nsight-graphics-for-linux-2026.2.0.0)
NVIDIA driver 580.159.03
GPU NVIDIA RTX A1000 6GB Laptop GPU
OS Ubuntu 24.04.4 LTS, kernel 6.8.0-110-generic, X11
GLFW 3.3.10 (distro libglfw.so.3)
GL context (clean) 4.6.0 NVIDIA 580.159.03

Reproduced under both injection paths:

  • Nsight’s normal Launch (ptrace) attach, and
  • LD_PRELOAD=…/libNomad.Injection.so (used below — most convenient for a
    standalone report). The symmetry across ptrace and LD_PRELOAD shows the fault
    is in the interception logic, not the injection method.

Minimal reproducer

Links only GLFW. No GL loader (GLAD/GLEW), no application GL call, no Nsight SDK
usage. The crash happens entirely inside glfwCreateWindow.

#include <GLFW/glfw3.h>
#include <cstdio>

int main()
{
    if (!glfwInit()) { std::fprintf(stderr, "glfwInit failed\n"); return 1; }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* w = glfwCreateWindow(800, 600, "repro", nullptr, nullptr); // CRASH
    if (!w) { std::fprintf(stderr, "glfwCreateWindow failed\n"); glfwTerminate(); return 1; }

    std::fprintf(stderr, "context created — survived\n");
    glfwDestroyWindow(w);
    glfwTerminate();
    return 0;
}

Build and run:

g++ repro.cpp -o repro $(pkg-config --cflags --libs glfw3)

# Clean: prints "context created — survived", exit 0.
./repro

# Under Nsight interception: Segmentation fault, 100% of runs.
LD_PRELOAD=/opt/nvidia/nsight-graphics-for-linux/nsight-graphics-for-linux-2026.2.0.0/target/linux-desktop-nomad-x64/libNomad.Injection.so \
  ./repro

Measured: 30/30 crashes under interception, 0 without.

Crash details

Backtrace (gdb)

#0  0x0000000000000000        <jump through uninitialized slot>
#1  …  libNvda.Graphics.Interception.so + 0x2f30a27   ("get GL string" wrapper)
#2  …  libNvda.Graphics.Interception.so + 0x37a0511   (new-context GL-state init)
#3  …  libNvda.Graphics.Interception.so + 0x3796700   (Context::makeContextCurrent hook)
#4  …  libglfw.so.3 + 0x46e93                          (_glfwPlatformMakeContextCurrent)
#5  …  glfwCreateWindow
#6  …  main

GLFW, inside glfwCreateWindow, makes its platform context current; libNvda’s
make-current hook runs and, while initialising GL state for the new context,
probes the driver vendor.

Faulting instruction and registers

0x…2f30a21:  call *0x960(%rbp)        ; this->slot_0x960(GLenum)
0x…2f30a27:  mov  %rax,%rbx           ; (return address = frame #1)

rdi = rsi = r12 = 0x1f00   ; GL_VENDOR
rax = 0

%rbp is a heap-allocated libNvda dispatch object for the new context. The slot
at offset 0x960 should hold a trampoline to the driver’s glGetString. At the
moment of the call it is unpopulated, so the call jumps to garbage and faults on
instruction fetch (kernel error 14 = instruction fetch, user mode, page not
present).

It is a race (the decisive evidence)

Setup Crash rate
Free run 30/30
Same, with a gdb breakpoint at the call *0x960(%rbp) instruction 0/30

With the breakpoint, when gdb pauses at the call, the slot already reads a
valid trampoline and stepi executes cleanly. In a free run the slot is
unpopulated at call time; by the time gdb catches the SIGSEGV and pauses, the
slot has already been overwritten with the valid trampoline. So the slot value
at call time ≠ its value microseconds later. The breakpoint delay is enough for
the writer to finish first.

The uninitialized slot contents vary run to run (observed 0x0 and the
recurring 0x09691974), consistent with reading memory before the writer
populates it — not a fixed sentinel.

What does and doesn’t change it

  • GL profile/version: no effect. 3.3 core, 3.3 compat, 4.5 core, 4.6 core,
    3.3 forward-compatible, and the default (no hints) context all crash 100% at
    the identical instruction. The vendor probe runs for every context.
  • No application-side workaround. Pre-create glfwPollEvents(), invisible
    window, pre-warming proc addresses, and pre-create sleeps up to 500 ms all
    crash 100%. The race window is inside glfwCreateWindow’s make-current →
    vendor-probe chain (one call), and the probe dispatches through a private
    cached slot, so application or preload code cannot get between them.
  • Qt/QOpenGLWidget does not crash on the same machine/Nsight build —
    attach and frame capture both succeed. The difference is GLFW’s inline
    make-current->vendor-probe sequence during glfwCreateWindow.

Suspected root cause / suggested fix

libNvda’s per-context GL dispatch table is populated lazily/asynchronously, and
the make-context-current hook’s driver-vendor probe (glGetString(GL_VENDOR))
can execute before the slot it dispatches through is written. The fix is to
ensure the dispatch slot is populated before it is first called — populate
synchronously during context setup, or have the probe go through a path that
forces resolution, rather than racing a concurrent writer.

This report should be triageable without target-application source: the
reproducer is trivial and the faulting instruction, register state, and
free-run-vs-breakpoint contrast localize the defect to libNvda’s dispatch-table
initialization.

Thank you!

~Keith Kyzivat
Staff Software Engineer, Professional Services
The Qt Company

nsight-glfw-crash-bundle-nocore.tar.gz (5.0 KB)