BACKWARDS BUILDS A nice warm day today [yesterday, by the time I finished writing this], at least compared against the endless cold dreary winter's days that have set the mood for months. Great for working on one of my outdoor projects, but I was rather unfocused and distractable. Ended up on my chair in the nice warm sun room, took off all my clothes, and grabbed my laptop for some hardcore... Linux! Ages ago I talked about battling with getting OpenSSH to build for the old version of Linux that I run on the old PCs like the one I'm posting from now. The problem was actually with the configure script because I wanted it to link against OpenSSL libraries in a custom directory to avoid messing with the old OpenSSL lib used by other programs, and the script just didn't want to play ball. I couldn't even manage to hack the configure script into submission, so I eventually gave up. Then I found PuTTY, which turned out to support Linux as well as Windows, didn't require any extra libraries, and had all the extras that come with openSSH, particularly an SFTP client. But that ran afoul of Tilde.Club when I signed up earlier this year, because it didn't support the latest RSA key magic using SHA-2 instead of SHA-1. Yes, it seems SSH software might only last a couple of years these days before becoming obsolete. Bloody internet. No problem though, PuTTY added support for that later, so I can just compile the latest version. But when I tried that about a week ago things took a turn full-circle. PuTTY has switched to using CMake as a build system. Whereas configure scripts and Makefiles at least try to run everywhere, CMake builds require a cmake program that's only a few years old at the latest. Building the latest CMake requires a C++11 compiler, but I found an earlier version that could still compile with old GCC, but the build failed early looking for a system header file that didn't exist. It didn't look hopeful. Like OpenSSH before, I've been roadblocked by the build system. So I gave up and built the last version of PuTTY before it switched to CMake, PuTTY 0.76, which Tilde.Club does like, for now. But clearly that solution is only going to last so long as well. What I need is to build from a modern system, with a modern CMake. But Linux, frustratingly, isn't kind to those attempting binary compatibility between systems of different ages. Actually that blame is largely misplaced. It's not actually Linux that's the problem, it's Glibc. Glibc, for the uninitiated, is the library that implements the standard C function routines (open, printf, etc.) of the C programming language. It talks between programs and the operating system kernel, and as a GNU project it's probably the overall most common GNU part of "GNU/Linux". Unfortunately programs compiled for new versions of Glibc generally won't run with older Glibc versions (although the Glibc developers do a great job at keeping old binaries working with newser Glibc versions). In theory static linking (GCC's "-static" option) solves this problem by bundling Glibc in with the program's binary, but in practice it doesn't because Glibc has some features which cause it to load extra files from the system. Glibc's not compatible with old versions of some of these files, particulary some which are actually object files (like mini libraries themselves) and don't get included in the static binary. Glibc's developers don't really like static linking, and they've basically willfully broken it, especially for building networking-related software like PuTTY. AppImages give the impression of having solved this, but they haven't. I built an AppImage for a complicated program a while ago. While in the (very slow) process of creating a monsterously long command line of manually-added rules to make the AppImage work successfully, I discovered that the standard solution to the Glibc compatibility problem is that developers simply pick the oldest Linux distro that they expect anyone to use, and run that to build the AppImage. As such, most users of the AppImage run later Glibc versions, which are designed to be compatible with such 'older' binaries, but it doesn't solve anything for going the other way and running 'new' binaries on old Glibc. It also means developers are tempted to bundle old support libraries, from the old Linux distros that they use, into the AppImages, which isn't good (there are a few solutions for building compatible AppImages on newer systems, but they're quite complicated and still require picking a relatively-recent minimum Glibc version). On top of that, Glibc since version 2.26 simply won't run on Linux kernel versions older than 3.2, which also rules it out for building a static binary to work for me on older Linux. So today's "hardcore Linux" task was to try an alternative to Glibc: Musl. This is designed as a more lightweight C library than Glibc, and has been adoped by some Linux distros instead of Glibc. Alpine Linux is particularly popular amongst these, and has amassed a surprisingly large number of programs in their package repo apparantly built against Musl. Even PuTTY! The Musl docs also don't mention a Linux kernel requirement beyond version 2.4 for "simple single-threaded applications" which _might_ include PuTTY? Plus it doesn't do any 'tricks' with object files like Glibc, so static builds work like they should. Musl _can_ be installed on Debian 10 Buster (or in my case the matching version of Devuan) too. Packages will still use Glibc because that's what they were compiled for, but you can compile your own software against Musl by setting the compiler to "musl-gcc" after installing "musl-tools". My Devuan system is actually x86_64 though, and I want to build for x86. I _could_ set up a separate x86 build system, but in theory GCC's x86_64 build target also supports building x86 binaries, even when it's not running on x86. Debian also has "multiarch" for installing 32bit libraries on 64-bit installations, which I already set up for running Wine. In theory this all goes together to mean that I can compile static, 32bit, Musl libC, builds of software on x86_64 Devuan! In practice everything fought against me on this, but after a couple of hours I'd worked out these steps to success (for C programs at least): Install these packages with their dependencies on multiarch-enabled Debian/Devuan: gcc-multiarch musl-tools musl-dev:i386 You need a GCC "specs" file for building against the 32-bit Musl library, but the Debian package system doesn't want to install it from musl-tools:i386 without replacing GCC with its 32-bit equivalent (damn obstinate package systems!), so make it from the 64-bit version (run as root): sed 's/x86_64/i386/g' /usr/lib/x86_64-linux-musl/musl-gcc.specs > \ /usr/lib/i386-linux-musl/musl-gcc.specs Set the CFLAGS environment variable with all the right magic words for CMake (or a 'configure' script) to whisper to GCC (Debian packages, presumably including Musl, are currently compiled for i686 arch.): export CFLAGS='-m32 -march=i686 -specs /usr/lib/i386-linux-musl/musl-gcc.specs -static -Xlinker -melf_i386' Run CMake (or ./configure): cmake -DCMAKE_BUILD_TYPE=Release . Edit the newly created CMakeCache.txt file as required (or run one of the GUI CMake interfaces).* I built just the command-line programs, not "putty" itself which uses GTK. Build with CMake (or "make"): cmake --build . Now you should have 32-bit i686 static binaries which will run on the x86_64 build system, and on older Linux distros, either x86_64 or x86. To be sure that it's really working, you can try building a test program described on this webpage (which otherwise stops a bit short of describing a complete solution for the task): https://www.geeksforgeeks.org/compile-32-bit-program-64-bit-gcc-c-c/ ------------------------------------------------------------------ // C program to demonstrate difference // in output in 32-bit and 64-bit gcc // File name: geek.c #include<stdio.h> int main() { printf("Size = %lu\n", sizeof(size_t)); } ------------------------------------------------------------------ When built as a 64-bit binary it prints "Size = 8" to the terminal when executed, and as a 32-bit binary it prints "Size = 4". Build it with dynamic linking first to confirm that the GCC command arguments are really causing it to link to Musl instead of Glibc: gcc -m32 -march=i686 \ -specs /usr/lib/i386-linux-musl/musl-gcc.specs \ -Xlinker -melf_i386 -o geek geek.c Now check with "file" to see the arch and "interpreter" used by the created binary: file geek geek: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-i386.so.1, with debug_info, not stripped So it's an x86 binary, and running it will therefore print "Speed = 4", plus "interpreter /lib/ld-musl-i386.so.1" means it's linked to Musl instead of Glibc! Now for the static binary: gcc -m32 -march=i686 \ -specs /usr/lib/i386-linux-musl/musl-gcc.specs -static \ -Xlinker -melf_i386 -o geek geek.c file geek geek: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, with debug_info, not stripped So after all that, did it make a PuTTY binary that worked on my old Linux distros on my old PCs? No. They just give me "segmentation fault". It does work on older 32-bit Linux distros. It worked on Debian 7 "Wheezy". But it doesn't go as far back as I needed it to. Musl isn't as compatible with old Linux kernels as I'd hoped, or maybe the problem is something else. Anyway it turned out to be pretty much a waste of time for me, but the technique might come in handy later on. I've also uploaded my i686 Musl static binaries of the PuTTY command-line tools here, for anyone who might want to give them a try. You're welcome to report back the earliest Linux that you can run them on. I'm too lazy to test with lots of old Linux kernels to find out where the cut-off point actually is: gopher://aussies.space/5/~freet/cupboard/putty_0.78_musl_static.tar.bz2 The next step for me might be to try and chroot into a copy of the root filesystem from my target Linux distro on a modern distro with modern CMake (accessed via a bind mount to the 'real' root FS and run with LD_LIBRARY_PATH). Then in theory I could run the CMake command where it thinks it's running in the old distro and generates corresponding makefiles. After that I could copy that PuTTY build directory to the real copy of the old distro and run "make" in there. Or maybe it's time to go back and have another go at OpenSSH? That's really getting desperate after the agony of last time though. - The Free Thinker * I really can't believe that the CMake developers haven't implemented an equivalent to "./configure --help", which has always been my starting point for building anything with a configure script. CMakeCache.txt is full of jumbled up options which makes it very hard to read (bare hand-edited makefiles are more readable!). Then, if you're trying to create a repeatable command to build later versions, you've got to then delete the CMakeCache.txt file and run the "cmake" command again with the changed options set as "-D[option]=[value]" arguments. What an awkward design! Neither PuTTY, nor the one other program I've compiled using CMake, bothered to document custom build options in their documentation either.