Writing

Feed Software, technology, sysadmin war stories, and more.

Thursday, November 30, 2023

clang now makes binaries an original Pi B+ can't run

I have a bunch of Raspberry Pi systems all over the place, goofy things that they are. They do dumb and annoying jobs in strange locations. I even have one of the older models, which is called just the B+. You can think of it as the "1B+" but apparently it was never officially branded the 1.

If you have one of these, or perhaps an original Pi Zero hanging around, you might find that C++ programs built with clang don't work any more. I ran into this as soon as I started trying to take binaries from my "build host" (a much faster Pi 4B) to run them on this original beast. It throws an illegal instruction.

This used to work in the old version (bullseye). It now breaks in the current one (bookworm). I figured, okay, maybe it's doing some optimization because it was built on the 4B. So, I went and did a build on the B+ natively. It also broke.

So I backed off another level to a much simpler reproduction case: just declare main() and return. That still broke.

Looking this up, there are a bunch of screwy dead-end forum posts where people go back and forth asserting this package is installed and that's making the compiler go stupid, or it's because they did the "lite" install vs. the "recommended" install, or who knows what.

I wanted to do better than that, so this afternoon I picked up a brand new SD card, blew the whole "desktop + recommended" OS image onto it, booted *that*, then installed clang, and...

raspberrypi:~/prog$ cat t.cc
#include <stdio.h>

int main() {
  return 0;
}
raspberrypi:~/prog$ clang++ -Wall -o t t.cc
raspberrypi:~/prog$ ./t
Illegal instruction

Awesome. It can compile something it can't even run. What's the bad instruction? gdb will answer that in a jiffy.

(gdb) disassemble
Dump of assembler code for function main:
   0x004005a4 <+0>:	sub	sp, sp, #4
=> 0x004005a8 <+4>:	movw	r0, #0

movw. That's not in armv6l, apparently. So yeah, this compiler is effectively cross-compiling for armv7 (or something) by default. That's not very useful.

You can work around this by grabbing the compiler by the lapels and saying "build for armv6, punk", and it will give you a working binary:

raspberrypi:~/prog$ clang++ --target=armv6-unknown-linux-gnueabihf -Wall -o t t.cc
raspberrypi:~/prog$ ./t
raspberrypi:~/prog$ 

How and why did it get to that point? I can only imagine it's some default that got bumped from version 11 to version 12, and somehow nobody noticed? I guess nobody still runs these old things anywhere?

So weird.