System designTroubleshooting

Segfault when the camera opens: fix mismatched shared libraries

Your program compiles and links, then dies with a segfault when the camera opens. Use a gdb backtrace, ldd and md5sum to find the mixed library and fix it.

LumaSpec Vision10 min read Share on LinkedIn

On this page 9 sections
  1. 01Why the crash waits for the open call
  2. 02Three causes, ranked by probability
  3. 03Name the library before you rebuild anything
  4. 04The fix: one set, one tree, one cache refresh
  5. 05Worked example: two files, one soname, exit 139
  6. 06When it is not the libraries
  7. 07Prevention: pin the set, then let hardware carry it
  8. 08The two-minute gate before every deploy
  9. 09Questions

A segmentation fault on the first call that opens the camera is rarely a bug in your own code. The program compiled, it linked, and it dies at the device-open line with "Segmentation fault (core dumped)". On Linux that pattern points at the dynamic loader: your binary asked for a shared library by name, and the file it received does not match the one the SDK was built against. This page is for engineers building a camera SDK's C++ examples or their own capture program on Linux, on an x86 machine or an ARM board. You will read a backtrace that names the library that crashed, tell a mixed-version install from a mixed-ABI build, rebuild the dependency set as one consistent whole, and prove the result with ldd before the next run.

Why the crash waits for the open call

Everything before the open runs from headers and inline code compiled into your own binary. The open call is the first line that executes inside the SDK's transport library, where devices are enumerated and the connection is set up. If the loader served an older build of that library, every symbol still exists, so start-up succeeds, and the first mismatched call faults.

The shell reports the death as exit status 139. The convention is 128 plus the signal number, and signal 11 is SIGSEGV, so 128 + 11 = 139.

Two failures live at this layer, and they look nothing alike. A missing or renamed library fails loudly before main() with a "cannot open shared object file" message, which is the case covered in Linux vision shared library errors: diagnose missing dependencies. A library with the right name and the wrong content loads silently and segfaults on first use, which is what this page fixes.

Three causes, ranked by probability

  1. Two same-named builds on disk. You built the SDK's dependencies into /usr/local/lib while the distro package manager also installed older files with the same soname under /usr/lib. The loader cache hands your program the old one. This is the most common cause after a hand-built dependency set.
  2. A partially installed set. The SDK needs several third-party libraries. The install step copied some .so files but not all, so the fresh transport library loads beside an old dependency it was never built against.
  3. A mixed C++ ABI. Part of the stack was compiled with the old C++ string layout and part with the new one, the GCC dual ABI controlled by the documented _GLIBCXX_USE_CXX11_ABI build flag, set to 1 for the new layout and 0 for the old. ldd is completely clean here, and the crash lands on the first std::string or std::vector that crosses between your code and the library.

A stale loader cache belongs to the first cause: replace a file by hand and the cache keeps serving the previous path until ldconfig runs. If the project never got as far as linking, that is the earlier failure described in C++ camera SDK linking errors: diagnose CMake dependencies and CMake missing dependency diagnostics for vision builds. Fix that first, because this page starts where those end.

Name the library before you rebuild anything

Run the program under gdb and take a backtrace. It is the fastest route to the answer:

gdb -q ./camera_app
(gdb) run
Thread 1 "camera_app" received signal SIGSEGV, Segmentation fault.
#0  0x00007fb7a41230 in TransportOpen () from /usr/lib/libtransport.so.3.1.4
(gdb) bt

The names and addresses here are illustrative; yours will differ. The frame line ends with "from /usr/lib/libtransport.so.3.1.4", and that path is the diagnosis: it names the exact file that crashed. Confirm what the loader would serve and whether your two copies really differ:

ldd ./camera_app | grep transport
    libtransport.so.3 => /usr/lib/libtransport.so.3
md5sum /usr/lib/libtransport.so.3.1.4 /usr/local/lib/libtransport.so.3.2.0

If ldd resolves to a tree you did not choose and the two md5 digests differ, the split is confirmed. When ldd looks clean but the crash persists, test the ABI: nm -DC demangles a binary's symbols, and grep -c __cxx11 typically returns a few hundred for code built with the new C++ string ABI and zero for the old (an illustrative pair on our bench was 437 versus 0; your absolute counts will differ). Different counts between your binary and the SDK library is the ABI split. readelf -d ./camera_app | grep -E 'RPATH|RUNPATH' shows, in order, which custom search paths the binary carries.

The fix: one set, one tree, one cache refresh

  1. Decide the source of every dependency. List the third-party libraries the SDK requires and choose, for each one, the distro package or your own build. Never both.
  2. Build the set one way. Same compiler, same build flags (typically at least -O2 -fPIC for shared libraries), same C++ ABI flag, all shared or all static, whichever the SDK's build expects. A static copy of one library and a shared copy of another is the same trap in a different coat.
  3. Remove the loser. Uninstall the distro package that provides the old same-named file, or delete the stale copies from your build prefix. If other software needs the distro package, leave it alone and give only your program a run path to your tree: link with -Wl,-rpath,/usr/local/lib and check with readelf that RUNPATH is in the binary.
  4. Install and look. Run the documented install step for each dependency, then list the target directory, for example ls -l /usr/local/lib | grep transport, and confirm every soname you expect is present.
  5. Refresh the cache. Run sudo ldconfig so /etc/ld.so.cache and the soname links match the files actually on disk.
  6. Verify the whole program. ldd ./camera_app | grep 'not found' must print nothing, and every resolved path must sit inside the tree you chose.
  7. Rebuild clean. Delete the project's build directory, because object files remember old paths and flags. Rebuild and run once under gdb: the open call should return and stream.

Worked example: two files, one soname, exit 139

An ARM board, an SDK built from source, the vendor's example program. It compiles, links and dies at the open line.

  • echo $? after the crash prints 139: 128 + 11, so SIGSEGV, a memory fault rather than a normal error return.
  • The gdb frame reads #0 ... TransportOpen () from /usr/lib/libtransport.so.3.1.4.
  • ls -l /usr/local/lib | grep transport shows your own build, libtransport.so.3.2.0. Two files provide the soname libtransport.so.3 with different content.
  • md5sum on both files returns two different digests. Split confirmed.
  • ldd ./camera_app | grep transport resolves to /usr/lib/libtransport.so.3. The cache favours the distro copy, so the example was never running your build.

Decision: nothing else on the board uses the distro package, so remove it, run sudo ldconfig, and re-check. ldd now prints /usr/local/lib/libtransport.so.3. Delete the build directory, rebuild, run: the example opens the camera and streams.

If the distro package had to stay, the alternative decision is the run path from the fix section: -Wl,-rpath,/usr/local/lib, confirmed with readelf, after which ldd resolves to your tree. Both routes end in the same place: one file per soname, and every binary on the machine agrees on which one.

When it is not the libraries

  • Frame #0 is inside your own source file. Your bug: step through in gdb and check the pointer arithmetic on that line.
  • The fault address is tiny, 0x0 or a small offset such as 0x3, inside a callback. That is usually a null or failed handle: check the open call's return value before the handle is used.
  • The program prints an explicit error instead of crashing, such as a version string mismatch, device not found or a timeout. Version disagreements between the SDK library and the camera firmware announce themselves that way, as in 3D camera header version mismatch: fix firmware and SDK alignment. A first read that always fails with an I/O error is the separate case in First GenTL read always fails: fix the initial camera I/O error.
  • The crash arrives only after many streamed frames. That points at memory corruption elsewhere; rebuild everything with the address sanitizer and let it name the offending allocation.

Segfault at open, clean ldd, matching digests, matching ABI counts: past that point, treat the library set, not the code, as the problem.

Prevention: pin the set, then let hardware carry it

  • Keep a pin list in the repository: every dependency, its source, its version and the ABI flag. It is the same discipline as Docker build SDK version mismatch: pin one library version everywhere.
  • Let the distro package manager own as many versions as it can; build by hand only what it lacks.
  • Never leave two files with the same soname in two loader paths.
  • Add a build check: after linking, run ldd on the binary and fail the build if any dependency resolves outside the chosen prefix.

Hand-assembling a stack is also a hardware decision. A fanless vision controller replaces the bench-built board with a unit whose camera ports, USB ports, light-output channels and memory are specified together, so the environment stops shifting under your program:

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
$1,679.00View part
Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
$2,419.00View part

The vision controllers category page filters the range by LAN ports, USB 3 ports and memory, which are the three numbers that decide whether a whole bench collapses into one box:

ComputersVision controllers

Vision computers and fanless industrial PCs

190 models · from $1,119.00

Building against stereo cameras or laser profilers instead? The same fix applies, and the 3D sensors category is where those cameras live.

The two-minute gate before every deploy

  • ldd on the shipped binary: no "not found", every path inside the chosen prefix.
  • One file per soname across /usr/lib and /usr/local/lib; md5sum the suspects when unsure.
  • sudo ldconfig run on the target machine after any manual change to library files.
  • One gdb session from the clean build, backtrace saved with the source.
  • Pin list updated: name, source, version, ABI flag.
  • SDK library and camera firmware versions recorded together.

Skip an item and the split returns silently, usually on the machine you least suspect: the one imaged months ago.

Once the set is pinned, this crash class disappears rather than gets patched; more fixes at this layer are collected on the systems topic page. If you would rather consolidate the bench than maintain the pin list, the category page above sorts controllers by the ports and memory your cameras need, and we are reachable through the contact page for a compatibility check before you order. And if the crash is gone but the image stays black, the next stop is Camera window opens but no video: isolate capture from rendering.

Questions

Why does the linker accept the library and the program still crash?

Static linking checks every symbol at build time, but dynamic linking only checks that the requested soname exists when the program starts. A file with the right soname and older content passes both checks, then faults when the first call executes mismatched code. That is why a clean compile and a clean link prove nothing about which file the loader will actually serve at runtime.

What does exit status 139 mean?

Shells report a fatal signal as 128 plus the signal number. Signal 11 is SIGSEGV, so 128 + 11 = 139 is what you see after a segmentation fault. The same arithmetic explains 132 for SIGILL (signal 4) and 134 for SIGABRT (signal 6), which usually point at an assertion failure or a deliberate abort rather than a bad memory access.

Should I set LD_LIBRARY_PATH to my freshly built libraries?

Use it as a test, not a fix. Launching the app once with the variable set tells you whether the crash disappears, which confirms the diagnosis. As a permanent solution it is fragile: the variable, run paths and the loader cache interact in an order that is easy to get wrong, and a stale value in a service file silently changes behaviour for every program on the machine. Fix the installed set instead.

Do I need to rebuild the camera SDK itself, or only its dependency libraries?

Usually only the dependencies. Rebuild every third-party library the SDK links against with one compiler and one set of build flags, install them, run ldconfig, then delete your project's build directory and recompile the SDK and your code against that single set. Rebuilding only your own files leaves old objects in place, and those objects still carry the paths and flags of the mixed set.

Can I avoid all of this by linking everything statically?

Static linking pins each version at build time, so the loader has nothing to mix, and it removes this class of crash entirely. The trade-offs are a larger binary, a rebuild and redeploy of every consumer whenever one library changes, and SDKs that load plug-ins at runtime keep dynamic dependencies anyway. Mixing a static archive of one version with a shared copy of another still crashes.