System designTroubleshooting

ld file format not recognized: fix broken .so symlinks

Fix ld's 'file format not recognized' on a vendor .so: verify the file with ls and file, rebuild the versioned symlink chain with ln -s, then link again.

LumaSpec Vision9 min read Share on LinkedIn

On this page 11 sections
  1. 01What "file format not recognized" is telling you
  2. 02Why libfoo.so, libfoo.so.2 and libfoo.so.2.0.0 all exist
  3. 03Three causes, most likely first
  4. 04One-minute tests before you touch anything
  5. 05The fix: rebuild the chain with ln -s
  6. 06Worked example: a 15-byte libfoo.so against the 64-byte ELF floor
  7. 07When the rebuilt demo prints garbage instead of running
  8. 08When it is not the symlink
  9. 09Keep the chain intact on the next install
  10. 10The machine this build ends up on
  11. 11Questions

You link a vendor camera SDK demo on Linux and instead of a binary you get /usr/bin/ld: libfoo.so: file format not recognized; treating as linker script, then a syntax error and collect2: error: ld returned 1 exit status. The library itself is almost never corrupt. What broke is the versionless name: during download, extraction or install, the libfoo.so symlink was replaced by a small text or empty file, and ld read those bytes instead of an ELF shared object. This guide shows you how to confirm that in under a minute with ls, file and readelf, rebuild the libfoo.so → libfoo.so.2 → libfoo.so.2.0.0 chain with ln -s, and link again — on a desktop or on the vision controller that runs your cameras. libfoo here stands in for your vendor's library name.

What "file format not recognized" is telling you

ld reads every file on its input list and expects an ELF object, a static archive or a shared object. The GNU ld manual states the fallback: "If the linker cannot recognize the format of an object file, it will assume that it is a linker script." Your file holds text or nothing, so ld parses it as a script, fails on the first line, and prints syntax error. collect2 is gcc's wrapper passing on ld's exit code.

One subtlety sorts the cases. A file with readable text fails immediately, exactly as above. An empty file parses as an empty script, the link carries on, and the failure moves to undefined references further down — that variant is walked through in camera SDK undefined reference: diagnose C++ linker errors.

Why libfoo.so, libfoo.so.2 and libfoo.so.2.0.0 all exist

A Linux SDK ships each library under three names. libfoo.so.2.0.0 is the real ELF file. libfoo.so.2 is a symlink to it and carries the SONAME — the name the runtime loader opens when your program starts. libfoo.so is a symlink to the middle name — the one ld resolves when your build line says -lfoo. Check the contract on the payload:

$ readelf -d libfoo.so.2.0.0 | grep -i soname
0x000000000000000e (SONAME)  Library soname: [libfoo.so.2]

The middle link must carry exactly that name, because ldconfig indexes system libraries by SONAME. Replacing symlinks with copies also breaks the runtime side: a copy named libfoo.so may satisfy ld today, but the loader follows libfoo.so.2, so the demo dies at startup instead.

Three causes, most likely first

  1. The archive stripped the symlinks. The SDK zip stored the links, but the extractor wrote each link's target path out as a small text file — several GUI decompressors do this. The two versionless names become tiny text files (15 bytes for a libfoo chain), the payload arrives intact, and file answers ASCII text. This is the common case for vendor SDK archives.
  2. The download saved something else. A scripted fetch behind a login or a redirect writes the HTML page under the library's name. file answers HTML document; head libfoo.so shows tags in place of bytes.
  3. The link line, not the file. An option that consumes the next token swallows the library name, so ld reads a file it never should have; deleting one stray flag has fixed builds showing this exact error. A genuine linker script passed as a plain input — a version script or map with an internal error — fails the same way.

Cause 1 dominates for SDK zips, cause 2 shows up in scripted downloads, cause 3 is rare but real.

One-minute tests before you touch anything

Run these in the SDK's library directory. Sizes and dates in the listings are illustrative; the verdicts are what matter.

  1. ls -l libfoo* — a healthy directory shows l modes and -> arrows on the two versionless names. Regular-file modes with tiny byte counts point to cause 1.
  2. file libfoo.so libfoo.so.2.0.0 — a healthy payload reads ELF 64-bit LSB shared object, x86-64, dynamically linked. ASCII text maps to cause 1, HTML document to cause 2, empty to the silent variant.
  3. readelf -d libfoo.so.2.0.0 | grep -i soname — prints the SONAME the middle link must carry.
  4. ldd libfoo.so.2.0.0 — lists what the library really needs at runtime.

The last test kills the classic red herring: your distribution ships OpenCV 4.2, the SDK demo seems aimed at 3.2, and rebuilding OpenCV looks imminent. If no libopencv_* entry appears in the list, the vendor library never linked OpenCV at all, and the version question cannot explain this error.

The fix: rebuild the chain with ln -s

Delete the two broken names and re-create them as links to the payload:

cd /path/to/sdk/lib      # the directory holding libfoo.so.2.0.0
rm libfoo.so libfoo.so.2
ln -s libfoo.so.2.0.0 libfoo.so
ln -s libfoo.so.2.0.0 libfoo.so.2

If the SDK installed into a system directory, run sudo ldconfig afterwards: it rebuilds the loader cache from each file's SONAME and re-creates the middle link itself. Then rebuild clean:

cd ../demo
make clean && make

make clean is not decoration — the earlier failed run can leave a target that masquerades as a successful build.

Verify before you rerun the build:

$ file libfoo.so
libfoo.so: symbolic link to libfoo.so.2.0.0
$ ls -l libfoo.so
lrwxrwxrwx 1 dev dev 15 Jun  3 09:12 libfoo.so -> libfoo.so.2.0.0
$ file libfoo.so.2.0.0
libfoo.so.2.0.0: ELF 64-bit LSB shared object, x86-64, dynamically linked

Three green lines: the versionless name is a link again, the payload is ELF, and make will link.

Worked example: a 15-byte libfoo.so against the 64-byte ELF floor

Inputs. ls -l in the SDK library directory reports -rw-r--r-- 1 dev dev 15 ... libfoo.so — a regular file of 15 bytes. The chain's target string is libfoo.so.2.0.0. The ELF-64 header is 64 bytes on its own, per the ELF specification.

Rule. An extractor that turned the symlink into text writes the link's target path as the file's whole content, so the size in bytes equals the character count of that path. No genuine shared object can be smaller than its mandatory 64-byte header.

Arithmetic.

$ printf %s "libfoo.so.2.0.0" | wc -c
15

Counted by hand: 6 for libfoo, plus 3 for .so, plus 2 + 2 + 2 for .2.0.0 — 6 + 3 + 2 + 2 + 2 = 15 characters, matching ls -l exactly. And 15 < 64: the file is smaller than the header alone.

Result. file libfoo.so answers ASCII text — the link target written as text, not a damaged library.

Decision. Any versionless .so under 64 bytes is not a library. When its size equals the length of its own target string, rebuild the chain with ln -s as above, run make clean && make, and expect file to report ELF. A file of 64 bytes or more that still reads as text or HTML gets deleted and re-fetched from the vendor instead.

When the rebuilt demo prints garbage instead of running

You run the freshly built demo and the shell answers with binary noise — line 1 is a stream of random bytes ending in not found, followed by Syntax error: word unexpected (expecting \" )\"). The shell is reading a file that is neither ELF nor a script: the kernel refused to exec it, so the shell fell back to interpreting it line by line. The build did not produce the file you executed — a failed make left nothing behind, or you ran an old artifact.

file ./demo settles it. Expect ELF 64-bit LSB pie executable. If it answers text or data, run make clean && make, read the output for the real error, and only then execute. chmod +x on a non-ELF file changes nothing — permission bits do not turn text into a program.

file reports ELF on all three names and ld still errors — now read the exact path on the first line. A path into your own sources means a script or map you wrote has a syntax error; fix the script. A path into the toolchain's own directory means the compiler installation is damaged; reinstall that package rather than the SDK. A path that changes when you remove a flag identifies the flag: one build with this exact error was fixed by deleting a single option that consumed the library name as its own argument.

If the failure sits one stage earlier — the compiler cannot find OpenCV headers — the repair is different: camera SDK missing OpenCV headers: causes, tests and fixes sorts include paths, and CMake can't find OpenCV in SDK samples: fix the config path handles CMake's own search.

The bytes-first habit travels. When a robot controller rejects src, dat and sub program files it cannot recognize, diagnose the file before reflashing anything — the same identify-then-act order applies, as in when a robot controller cannot recognize src, dat and sub files.

Keep the chain intact on the next install

  • Transfer SDK trees with rsync -a or tar; both preserve symlinks. Plain cp -r and many file-manager and SFTP copies do not.
  • Gate every extraction with one line: file libfoo.so | grep -q ELF || echo "symlink lost". Run it as a post-install step or in CI, before make ever starts.
  • Take SDK binaries from the vendor or its authorized distributors only. If the link layout is not in the documentation, ask the vendor for the SONAME chain instead of reconstructing names by hand.
  • Carry the same discipline onto the controller when you ship models and configuration: exact path, exact file. Machine vision model file paths: fix deployment errors covers the deployment side.

More Linux-side build and integration fixes sit on the systems topic page.

The machine this build ends up on

Fixing the chain is a one-time job per install. The machine that reruns it sits in the cabinet next to the cameras, acquiring from GigE area scan cameras over its LAN ports while the same SDK build deploys onto it. Both controllers below carry 8 GB of memory and 4 built-in light channels, with multiple LAN ports for the cameras.

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

If neither card matches your port count or processing load, browse the full vision controllers range, or contact us with your SDK and camera count and we will spec the machine with you.

Questions

Can I copy the real library over the broken name instead of symlinking?

A copy with the right content will link today. It still breaks the contract: the loader matches the SONAME recorded in your binary, packages expect the three-name layout, and the next SDK update overwrites the payload while your copies go stale. The two ln -s commands cost seconds and survive updates.

Why does the error mention a linker script when I never wrote one?

Because ld's fallback for any input it cannot identify is to parse it as a linker script. A text file named libfoo.so gets script-parsed, fails on its first line, and reports a syntax error. The real problem — what the file contains — sits one line above in the message.

How do I know the OpenCV version is not the problem?

Ask the library itself. readelf -d libfoo.so.2.0.0 and ldd list what it actually needs. If no libopencv entry appears, the vendor library never linked OpenCV, so a mismatch between your distribution's build and the SDK's target cannot cause this linker error.

What should ls -l show for a healthy SDK library directory?

Three entries: libfoo.so and libfoo.so.2 with lrwxrwxrwx modes and arrows pointing at the payload, plus libfoo.so.2.0.0 as a regular file in the megabyte range on a typical SDK. readelf -d on the payload should print SONAME libfoo.so.2, matching the middle link's name exactly.

Does this change how I deploy to a vision controller?

Only in discipline, not method. Transfer SDK trees with rsync -a or tar so links survive, then gate the build with file libfoo.so | grep -q ELF before make. On a controller mounted in the cabinet, that one-line check saves a walk from the office to the line.