guides / wrong-elf-class troubleshootingupdated 2026-10-25 readelf -h ./app

wrong ELF class: ELFCLASS32 or ELFCLASS64 — what it means

2026-10-25 By luckyray Debian 12 · Ubuntu 24.04 · Alpine 3.20
$ python3 -c "import ctypes; ctypes.CDLL('./libsensor.so')"
OSError: ./libsensor.so: wrong ELF class: ELFCLASS32

or, at link time:

$ gcc main.o sensor.o -o app
/usr/bin/ld: sensor.o: relocations in generic ELF (EM: 3)
/usr/bin/ld: sensor.o: error adding symbols: file in wrong format

Both are the same underlying fact: an ELF file’s class — 32-bit or 64-bit — is fixed in the file header, and it cannot be mixed within one address space.

Class is not architecture

These are two independent header fields, and confusing them sends people down the wrong path:

FieldValuesAnswers
EI_CLASSELFCLASS32, ELFCLASS64how wide are pointers and offsets in this file
e_machineEM_X86_64, EM_AARCH64, EM_386, …which instruction set the code is for

An EM_X86_64 binary is always ELFCLASS64. An EM_386 binary is always ELFCLASS32. But EM_MIPS and EM_PPC exist in both classes, which is why “32-bit MIPS” and “64-bit MIPS” are different targets with incompatible ABIs. Checking file output handles both fields at once:

$ file libsensor.so
libsensor.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked

ELF 32-bit is the class; Intel 80386 is the machine.

Why this is a different failure from Exec format error

Both end with the same practical result — the code does not run — but they fail at different layers:

  • Exec format error is the kernel rejecting an executable. Nothing was loaded, no loader ran.
  • wrong ELF class is reported after something already accepted the file. At link time it is the linker refusing to combine objects of different widths. At run time it is dlopen() refusing to map a library whose class differs from the calling process.

That second case is the one that confuses people, because the error arrives from application code — Python’s ctypes, a Java JNI loader, a plugin system — not from the shell:

$ python3 -c "import platform; print(platform.architecture())"
('64bit', 'ELF')

$ file ./vendor/lib/liblegacy.so
./vendor/lib/liblegacy.so: ELF 32-bit LSB shared object, Intel 80386

A 64-bit Python process cannot dlopen() a 32-bit library. There is no flag that changes this; the pointer sizes in the two pieces of code disagree, so the call ABI cannot be reconciled at all.

The four cases, and which fix applies

Mixing object files of different classes

$ gcc -c -m32 lib.c -o lib.o
$ gcc -c main.c -o main.o
$ gcc main.o lib.o -o app
/usr/bin/ld: lib.o: error adding symbols: file in wrong format

Compile the whole program with the same -m32/-m64 flag. Partial application does not work at the object level. If you need 32-bit builds on a 64-bit host, install multilib:

sudo apt install gcc-multilib g++-multilib

Loading a 64-bit library into a 32-bit process

The mirror image of the ctypes example above, common when a Python or Node installation is 32-bit (the default on some Windows-derived distributions and on some embedded boards) while the library vendor ships only x86-64 builds. Check the interpreter’s word size, not the CPU’s — a 64-bit CPU runs 32-bit userlands happily.

A prebuilt wheel or binary for the wrong target

$ pip install somepkg
$ python3 -c "import somepkg"
ImportError: .../somepkg/_core.cpython-311-x86_64-linux-gnu.so: wrong ELF class: ELFCLASS64

The filename is the giveaway: x86_64 in a wheel tag on an aarch64 host, or an i686 tag on x86_64. pip normally selects the right wheel; it does not when the tag was mis-declared, when a wheel was built locally with the wrong flags, or when the platform tag was overridden with --platform. Verify with pip debug --verbose | grep -A20 'Compatible tags'.

NOBITS and linker scripts — a false alarm

Occasionally wrong ELF class appears for a linker script rather than an object:

/usr/bin/ld: /usr/lib/libc.so: invalid ELF header

On glibc systems /usr/lib/libc.so is a text linker script, not an ELF file. This is expected; if you are passing it explicitly to a custom link command, use -lc and let the driver resolve it instead.

Diagnosing quickly

$ readelf -h libsensor.so | grep -E 'Class|Machine|Type'
  Class:                             ELF32
  Type:                              DYN (Shared object file)
  Machine:                           Intel 80386

Then compare against the consumer:

$ uname -m                 # kernel architecture
$ getconf LONG_BIT         # userland width: 32 or 64
$ python3 -c "import platform; print(platform.architecture()[0])"

uname -m tells you the CPU, not the userland. That gap is where most of these incidents live: a 64-bit kernel with a 32-bit userland answers x86_64 to uname and 32 to getconf LONG_BIT.

Should you fix it or work around it?

The honest answer is usually neither — use the correct build. Multilib exists and works, but it doubles the dependency surface: a 32-bit binary needs 32-bit versions of every library it links, and on modern distributions those packages are increasingly unavailable or unmaintained. If a vendor only ships a 32-bit library in 2026, that is a signal about the vendor.

When you have no choice, a container with the matching userland is cleaner than installing multilib across the host. It also makes the constraint explicit and reproducible.

The ELF Inspector reports class, machine and type for any binary without a shell, which is handy when the machine that has the problem is the one you cannot install binutils on. For the case where the classes match but the flat binary still will not execute, see Exec format error.


Every command in this guide was run against the distribution named in the output. If your output differs, the mechanism section explains why — and the ELF Inspector shows the same fields without needing a shell.

Related

guides
  1. cannot execute binary file: Exec format error — causes and fixes Why Linux refuses to run a binary with Exec format error, how to confirm the cause in three commands, and the five fixes that actually apply.
  2. version `GLIBC_2.34' not found — why copying libc never works How glibc symbol versioning works, why a binary built on a newer distribution refuses to start on an older one, and the four legitimate fixes.
  3. error while loading shared libraries: cannot open shared object file How the dynamic loader searches for libraries, why a library can be installed and still not found, and the five fixes that work in order of preference.