Hi all,
It sounds like there are a few folks who would be interested in a walkthrough of how to build a PDP11 cross compilation environment for Binutils and GCC, so here's a first draft of that. This example is specifically for 2.11BSD but can be used as a starting point for other targets. Feel free to ask as many clarifying questions as you need.
Prerequisites: not much, just a POSIX environment and standard C development tools. Any Linux or modern *BSD should work fine, as should MacOS. I bet you could also do this on WSL (Windows Subsystem for Linux) and maybe even Cygwin or MinGW if you're reasonably comfortable with development in those environments.
Note that the output of many commands are fairly
verbose and are not really helpful to duplicate here, hence liberal use
of "..."
First get
http://www.gnu.org/gnu/binutils/binutils-2.44.tar.xz (the latest version) or any other reasonably recent version of your choice, and unpack it anywhere writable. In the toplevel binutils directory create an "obj" directory where we will store our compilation. I don't know if you
have to use a separate directory but it's very strongly recommended; you could also call your directory whatever you want but for the purposes of these instructions I'm going to use obj.
--
(aelfric:/tmp) hbent% tar xf binutils-2.44.tar.xz
(aelfric:/tmp) hbent% cd binutils-2.44
(aelfric:/tmp/binutils-2.44) hbent% mkdir obj
(aelfric:/tmp/binutils-2.44) hbent% cd obj
--
Now we're going to run configure to set everything up for binutils. This will give us everything but the compiler itself - an assembler, linker, and various other handy utilities. By default they will be installed in /usr/local with a prefix equal to our target triple. In this example we're compiling for pdp11-dec-aout, so our assembler will be /usr/local/bin/pdp11-dec-aout-as, the linker pdp11-dec-aout-ld, etc. If you want the tools in a different directory just add "--prefix=/my/favorite/dir" to the end of the configure script.
--
(aelfric:/tmp/binutils-2.44/obj) hbent% ../configure --target=pdp11-dec-aout
checking build system type... x86_64-pc-linux-gnu
checking host system type... x86_64-pc-linux-gnu
checking target system type... pdp11-dec-aout
...
---
If things stop here you do not have a working development environment that can compile binutils, which is beyond the scope of this tutorial. If you want to target an OS other than 2.11BSD that's also beyond the scope of this particular tutorial but if you know what target triple you need, try using that as the argument to target and see what happens.
Now let's actually build and install everything:
---
(aelfric:/tmp/binutils-2.44/obj) hbent% make -j12
...
(aelfric:/tmp/binutils-2.44/obj) hbent% sudo make install
[actually installs everything into /usr/local/]
...
---
Feel free to substitute whatever number you want for make jobs; I have 8 logical cores so I use 1.5x that number of jobs in order to keep the machine as close to 100% occupied as I can during the build. If the "make" stops check your errors - the most common thing I see is overly aggressive use of -Werror by GCC, so try turning that off as a first step.
Now we have our tools ready. We have a directory tree /usr/local/pdp11-dec-aout that has all of the tools in it; the tools in /usr/local/bin are actually hard links. There isn't much else there yet but that's where we'll put everything from our pdp11 OS.
So now let's get our cross environment set up. What we need are the contents of /lib, /usr/lib, and /usr/include from 2.11BSD which we will put into /usr/local/pdp11-dec-aout/lib, /usr/local/pdp11-dec-aout/lib, and /usr/local/pdp11-dec-aout/include respectively. Getting those off of your installed OS is a little bit beyond the scope of this tutorial; there are lots of different options. You do not need to preserve file permissions.
In my case:
---
[start up 2.11BSD in SIMH]
[2bsd:/] % tar cvf ~hbent/lib.tar lib
a lib/c0 65 blocks
a lib/cpp 61 blocks
a lib/crt0.o 1 blocks
a lib/mcrt0.o 4 blocks
...
[2bsd:/] % cd usr
[2bsd:/usr] % tar cvf ~hbent/usrlib.tar lib
a lib/libc_p.a 524 blocks
a lib/libF77.a 102 blocks
...
[2bsd:/usr] % tar cvhf ~hbent/include.tar include
a include/arpa/ftp.h 6 blocks
a include/arpa/inet.h 3 blocks
a include/arpa/telnet.h 9 blocks
a include/arpa/tftp.h 4 blocks
...
[get .tar files onto local machine and now that we're back there:]
(aelfric:/usr/local/pdp11-dec-aout) hbent% sudo tar xf /tmp/lib.tar
[and repeat for usrlib.tar and include.tar]
---
Note that for tarring up /usr/include you do want to follow symbolic links. Make sure that all of what you have just unpacked is readable by any/all users you are using to run your compilation jobs.
Now we're ready to compile GCC. For the purposes of this compilation we're going to use GCC 12.4.0. There are two reasons for that: one, unlike more modern versions, it does not by default enable a number of warnings as errors that often frustrate 2.11BSD compilation; and two, it is the last version that supports STABS debugging which is nice to have.
---
(aelfric:/tmp) hbent% tar xf gcc-12.4.0.tar.xz
(aelfric:/tmp/gcc-12.4.0) hbent% mkdir obj
(aelfric:/tmp/gcc-12.4.0) hbent% cd obj
(aelfric:/tmp/gcc-12.4.0/obj) hbent% ../configure --target=pdp11-dec-aout --enable-languages=c --disable-libssp
checking build system type... x86_64-pc-linux-gnu
checking host system type... x86_64-pc-linux-gnu
checking target system type... pdp11-dec-aout
...
---
There are LOTS of options you could pass to GCC's configure here, including --prefix which you will almost certainly want to set to the same thing as binutils if you changed that above. Most of the other things you could set are well beyond the scope of this guide and are probably unnecessary unless you know for a fact that you need or want them. Also note that we're only setting up our cross-compiler for C; any other language will almost certainly not work and you're definitely on your own at that point. I don't even know if you strictly need to disable libssp but there's absolutely no reason to have it; if you're worried about stack smashing protection on 2.11BSD please message me off-list and we'll find you some psychological help.
If this does not complete successfully, check that the pdp11-dec-aout-* tools are actually in your $PATH and that you have development versions of GMP, MPFR, and MPC installed; hopefully any error you get will be at least partially self-explanatory.
Now let's actually compile GCC! This will take a while and is a great time to grab the beverage of your choice, or maybe even mow your lawn if you're on a slower machine.
--
(aelfric:/tmp/gcc-12.4.0/obj) hbent% make -j12
[LOTS of output removed...]
gmake[1]: Leaving directory '/tmp/gcc-12.4.0/obj'
(aelfric:/tmp/gcc-12.4.0/obj) hbent%
---
Holy cow, it completed successfully! Now let's install it and try a test compile. We'll do a verbose compile so that we can see what's being executed.
--
(aelfric:/tmp/gcc-12.4.0/obj) hbent% sudo make install
...
(aelfric:/tmp/gcc-12.4.0/obj) hbent% cd /tmp
(aelfric:/tmp) hbent% cat > blah.c
#include <stdio.h>
int main() { printf("Hello World!\n"); }
[CTRL-D]
(aelfric:/tmp) hbent% pdp11-dec-aout-gcc-12.4.0 -v blah.c -o blah
Using built-in specs.
COLLECT_GCC=pdp11-dec-aout-gcc-12.4.0
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/pdp11-dec-aout/12.4.0/lto-wrapper
Target: pdp11-dec-aout
Configured with: ../configure --target=pdp11-dec-aout --enable-languages=c --disable-libssp
Thread model: single
Supported LTO compression algorithms: zlib zstd
gcc version 12.4.0 (GCC)
COLLECT_GCC_OPTIONS='-v' '-o' 'blah'
/usr/local/libexec/gcc/pdp11-dec-aout/12.4.0/cc1 -quiet -v blah.c -quiet -dumpbase blah.c -dumpbase-ext .c -version -o /tmp/ccn8vX7q.s
GNU C17 (GCC) version 12.4.0 (pdp11-dec-aout)
compiled by GNU C version 14.1.1 20240720, GMP version 6.3.0, MPFR version 4.2.1, MPC version 1.3.1, isl version isl-0.27-GMP
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/lib/gcc/pdp11-dec-aout/12.4.0/../../../../pdp11-dec-aout/sys-include"
#include "..." search starts here:
#include <...> search starts here:
/usr/local/lib/gcc/pdp11-dec-aout/12.4.0/include
/usr/local/lib/gcc/pdp11-dec-aout/12.4.0/include-fixed
/usr/local/lib/gcc/pdp11-dec-aout/12.4.0/../../../../pdp11-dec-aout/include
End of search list.
GNU C17 (GCC) version 12.4.0 (pdp11-dec-aout)
compiled by GNU C version 14.1.1 20240720, GMP version 6.3.0, MPFR version 4.2.1, MPC version 1.3.1, isl version isl-0.27-GMP
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 0f551425efc4b9aa70f0bc4e9ea469e0
blah.c: In function 'main':
blah.c:2:14: warning: implicit declaration of function 'printf' [-Wimplicit-function-declaration]
2 | int main() { printf ("Hello world!\n"); }
| ^~~~~~
blah.c:2:1: note: include '<stdio.h>' or provide a declaration of 'printf'
1 | #include <stdio.h>
+++ |+#include <stdio.h>
2 | int main() { printf ("Hello world!\n"); }
blah.c:2:14: warning: incompatible implicit declaration of built-in function 'printf' [-Wbuiltin-declaration-mismatch]
2 | int main() { printf ("Hello world!\n"); }
| ^~~~~~
blah.c:2:14: note: include '<stdio.h>' or provide a declaration of 'printf'
COLLECT_GCC_OPTIONS='-v' '-o' 'blah'
/usr/local/lib/gcc/pdp11-dec-aout/12.4.0/../../../../pdp11-dec-aout/bin/as -o /tmp/ccrBy73J.o /tmp/ccn8vX7q.s
COMPILER_PATH=/usr/local/libexec/gcc/pdp11-dec-aout/12.4.0/:/usr/local/libexec/gcc/pdp11-dec-aout/12.4.0/:/usr/local/libexec/gcc/pdp11-dec-aout/:/usr/local/lib/gcc/pdp11-dec-aout/12.4.0/:/usr/local/lib/gcc/pdp11-dec-aout/:/usr/local/lib/gcc/pdp11-dec-aout/12.4.0/../../../../pdp11-dec-aout/bin/
LIBRARY_PATH=/usr/local/lib/gcc/pdp11-dec-aout/12.4.0/:/usr/local/lib/gcc/pdp11-dec-aout/12.4.0/../../../../pdp11-dec-aout/lib/
COLLECT_GCC_OPTIONS='-v' '-o' 'blah' '-dumpdir' 'blah.'
/usr/local/libexec/gcc/pdp11-dec-aout/12.4.0/collect2 -o blah /usr/local/lib/gcc/pdp11-dec-aout/12.4.0/../../../../pdp11-dec-aout/lib/crt0.o -L/usr/local/lib/gcc/pdp11-dec-aout/12.4.0 -L/usr/local/lib/gcc/pdp11-dec-aout/12.4.0/../../../../pdp11-dec-aout/lib /tmp/ccrBy73J.o -lgcc -lc -lgcc
COLLECT_GCC_OPTIONS='-v' '-o' 'blah' '-dumpdir' 'blah.'
(aelfric:/tmp) hbent% file blah
blah: PDP-11 executable not stripped
(aelfric:/tmp) hbent% pdp11-dec-aout-size blah
text data bss dec hex filename
2718 286 40 3044 be4 blah
(aelfric:/tmp) hbent% ls -l blah
-rwxr-xr-x 1 hbent hbent 5927 2025-05-03 19:07 blah
(aelfric:/tmp) hbent% pdp11-dec-aout-strip blah
(aelfric:/tmp) hbent% ls -l blah
-rwxr-xr-x 1 hbent hbent 3020 2025-05-03 19:13 blah
---
There's no need to strip the executable but it demonstrates the use of the tools and shows what kind of size savings you can expect.
If you want to save the intermediate files (preprocessed source, assembly, object file) use the --save-temps argument to GCC and you'll get blah.i, blah.s, and blah.o.
Note that stdio.h in 2.11BSD doesn't provide a definition of printf(), but for right now that isn't fatal. Various folks have been working to get those headers into a much more modern state.
Now let's move our executable over to 2.11BSD (which is once again beyond the scope of this tutorial) and see what happens...
--
[2bsd:/tmp] % ./blah
Hello world!
---
Holy cow, it worked! If you got this far, congratulations, you're done with the hardest part.
As I said earlier, please feel free to ask questions, and maybe in a little while I'll post a part two compiling and running something a little more non-trivial. If there's interest I can also cover topics like getting libgcc as small as possible, compiling split I+D executables, and more; I'm open to requests of what people would like me to cover.
-Henry