Self Relocating Program
Once upon a time, I wrote a reasonably-strict-C89 C language program that “relocates” a function in memory and then runs it. That relocated function can relocate itself again, and run that copy, and so on and so forth.
I wrote it (as I recall) on a Sun 3/260 workstation in about 1989 or 1990. I know I wrote it on a Motorola 68K Sun, because the group I was in had 2 workstations, a 68K and a nearly new SPARC based system, I think it was a Sun 4-110.
I had the inspiration at work, tried it, the program just crashed,
but as I was driving home, I realized that I had to call functions in
the relocated function via function pointers.
Next day at work, I could change my code, and wonder of wonders, it worked!
I think my very first version actually copied itself on to the stack,
using the now scorned alloca()
C library function,
but I soon modified it to use malloc()
, and hence execute from the heap.
I’ve been able to get it to run on all kinds of CPU architectures, M68020, SPARC, Mips R2000, R3000, R4000, x86, x86_64. Big-endian, little-endian, RISC and CISC. All of these CPUs run the code unmodified. DEC Alpha and M68040 took a little extra work, putting an embedded assembly instruction in the code to flush the instruction cache. The Alpha CPU as a DEC “UDB”, a fairly early iteration of that CPU architecture. I recall it having memory barrier instructions, and I believe that’s what I used. Alas, I’ve lost the Alpha and M68040 versions long ago.
The idea of “portability” to different architectures has gotten lost in these latter days. “Portability” used to mean a program could work with big- or little-endian CPUs, did not violate alignment restrictions, and could be compiled with variant C compilers. Sun had it’s own C compiler for Solaris, which did not descend from BSD’s “PCC” compiler or AT&T’s C compiler. Really portable programs could even be compiled and run under non-Unix, non-POSIX operating systems.
Now, something’s portable if it compiles for 32- or 64-bit x86 CPUs on Linux and Windows. If you’re lucky, it works on an ARM-based RaspberryPi.
I don’t really know if the changed connotation of “portability” is good or bad. It took some discipline to write a portable program, maybe that discipline is best used elsewhere.
People have learned along the way.
“Portable” C programs were typically filled with conditional compilation constructs,
maybe redefining a particular type,
or renaming functions.
Sometimes a “portable” program was actually 2 different programs,
where there was one #ifdef
construct with one program in the true-branch,
and an entirely different program in the false-branch.
Nowadays, conditional compilation is considered to be bad form.
You can see this reflected in programming language design. C and C++ had a pre-processor that could make code OS- CPU- or compiler-specific. Java, a language from the early/mid 1990s, does not have conditional compilation, and it’s compiled to bytecode, which turned out to get used as a semi-portable intermediate representation of machine code. But most architectures and OSes fell by the wayside, so a machine-independent, intermediate representation isn’t so attractive.
Go from 2010 compiles to machine code, has file-level conditional compilation, rather than redefining lexical tokens and doing expression-level conditional compilation. The entire Go compiler toolchain explicitly does cross-compilation. All the OS- and CPU-level portability concerns get moved into the runtime and the extensive standard library.
What next for portability? Probably it will disappear. We appear to be making ARM CPUs a de facto standard, and Linux the de facto standard operating system.