Added page to website describing setup of cross compiler for PDP-11.
authorAaron Taylor <ataylor@subgeniuskitty.com>
Thu, 7 Jan 2021 12:00:33 +0000 (04:00 -0800)
committerAaron Taylor <ataylor@subgeniuskitty.com>
Thu, 7 Jan 2021 12:00:33 +0000 (04:00 -0800)
data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler-libgcc-errormsg.txt [new file with mode: 0644]
data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler.md [new file with mode: 0644]
data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler.metadata [new file with mode: 0644]

diff --git a/data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler-libgcc-errormsg.txt b/data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler-libgcc-errormsg.txt
new file mode 100644 (file)
index 0000000..d80771a
--- /dev/null
@@ -0,0 +1,118 @@
+The following error occurs when attempting to build libgcc with the following
+sequence of commands.
+
+    setenv PREFIX "$HOME/cross-compiler/pdp11-gcc10.2.0"
+    setenv PATH "$PREFIX/bin:$PATH"
+    setenv TARGET pdp11-aout
+
+    cd $HOME/cross-compiler/pdp11-gcc10.2.0
+    mkdir workdir-binutils
+    mkdir workdir-gcc
+
+       cd $HOME/cross-compiler/pdp11-gcc10.2.0
+    tar xzf binutils-2.35.1.tar.gz
+    cd workdir-binutils
+
+    ../binutils-2.35.1/configure --target=$TARGET --prefix="$PREFIX" \
+            --with-sysroot --disable-nls --disable-werror
+    gmake
+    gmake install
+
+    cd $HOME/cross-compiler/pdp11-gcc10.2.0
+    tar xzf gcc-10.2.0.tar.gz
+    cd gcc-10.2.0
+    ./contrib/download-prerequisites
+    cd ../workdir-gcc
+
+    ../gcc-10.2.0/configure --target=$TARGET --prefix="$PREFIX" \
+            --disable-nls --enable-languages=c --without-headers \
+            --with-gnu-as --with-gnu-ld --disable-libssp
+    gmake all-gcc
+    gmake all-target-libgcc
+
+================================================================================
+
+Checking multilib configuration for libgcc...
+gmake[2]: Entering directory '/usr/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc/pdp11-aout/libgcc'
+# If this is the top-level multilib, build all the other
+# multilibs.
+gmake[3]: Entering directory '/usr/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc/pdp11-aout/libgcc'
+if [ -z "msoft-float" ]; then \
+  true; \
+else \
+  rootpre=`${PWDCMD-pwd}`/; export rootpre; \
+  srcrootpre=`cd ../../../gcc-9.3.0/libgcc; ${PWDCMD-pwd}`/; export srcrootpre; \
+  lib=`echo "${rootpre}" | sed -e 's,^.*/\([^/][^/]*\)/$,\1,'`; \
+  compiler="/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc/./gcc/xgcc -B/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc/./gcc/ -B/home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/bin/ -B/home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/lib/ -isystem /home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/include -isystem /home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/sys-include   "; \
+  for i in `${compiler} --print-multi-lib 2>/dev/null`; do \
+    dir=`echo $i | sed -e 's/;.*$//'`; \
+    if [ "${dir}" = "." ]; then \
+      true; \
+    else \
+      if [ -d ../${dir}/${lib} ]; then \
+        flags=`echo $i | sed -e 's/^[^;]*;//' -e 's/@/ -/g'`; \
+        if (cd ../${dir}/${lib}; gmake "AR=/home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/bin/ar" "AR_FLAGS=rc" "CC=/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc/./gcc/xgcc -B/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc/./gcc/ -B/home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/bin/ -B/home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/lib/
+-isystem /home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/include -isystem /home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/sys-include   " "CFLAGS=-g -O2" "DESTDIR=" "EXTRA_OFILES=" "HDEFINES=" "INSTALL=/usr/bin/install -c" "INSTALL_DATA=/usr/bin/install -c -m 644" "INSTALL_PROGRAM=/usr/bin/install -c" "LDFLAGS=" "LOADLIBES=" "RANLIB=/home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/bin/ranlib" "SHELL=/bin/sh" "prefix=/home/ataylor/cross-compiler/pdp11-gcc9.3.0" "exec_prefix=/home/ataylor/cross-compiler/pdp11-gcc9.3.0" "libdir=/home/ataylor/cross-compiler/pdp11-gcc9.3.0/lib" "libsubdir=/home/ataylor/cross-compiler/pdp11-gcc9.3.0/lib/gcc/pdp11-aout/9.3.0" "tooldir=/home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout" \
+                        CFLAGS="-g -O2 ${flags}" \
+                        CCASFLAGS=" ${flags}" \
+                        FCFLAGS=" ${flags}" \
+                        FFLAGS=" ${flags}" \
+                        ADAFLAGS=" ${flags}" \
+                        prefix="/home/ataylor/cross-compiler/pdp11-gcc9.3.0" \
+                        exec_prefix="/home/ataylor/cross-compiler/pdp11-gcc9.3.0" \
+                        GOCFLAGS="-O2 -g ${flags}" \
+                        GDCFLAGS="-O2 -g ${flags}" \
+                        CXXFLAGS="-g -O2 ${flags}" \
+                        LIBCFLAGS="-g -O2 ${flags}" \
+                        LIBCXXFLAGS="-g -O2 -fno-implicit-templates ${flags}" \
+                        LDFLAGS=" ${flags}" \
+                        MULTIFLAGS="${flags}" \
+                        DESTDIR="" \
+                        INSTALL="/usr/bin/install -c" \
+                        INSTALL_DATA="/usr/bin/install -c -m 644" \
+                        INSTALL_PROGRAM="/usr/bin/install -c" \
+                        INSTALL_SCRIPT="/usr/bin/install -c" \
+                        all); then \
+          true; \
+        else \
+          exit 1; \
+        fi; \
+      else true; \
+      fi; \
+    fi; \
+  done; \
+fi
+gmake[4]: Entering directory '/usr/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc/pdp11-aout/msoft-float/libgcc'
+# If this is the top-level multilib, build all the other
+# multilibs.
+CONFIG_FILES= CONFIG_HEADERS=auto-target.h:../../../../gcc-9.3.0/libgcc/config.in /bin/sh ./config.status
+config.status: creating auto-target.h
+config.status: auto-target.h is unchanged
+config.status: executing default commands
+# Early copyback; see "all" above for the rationale.  The
+# early copy is necessary so that the gcc -B options find
+# the right startup files when linking shared libgcc.
+/bin/sh ../../../../gcc-9.3.0/libgcc/../mkinstalldirs ../../.././gcc/msoft-float
+parts="";                                       \
+for file in $parts; do                                  \
+  rm -f ../../.././gcc/msoft-float/$file;               \
+  /usr/bin/install -c -m 644 $file ../../.././gcc/msoft-float/; \
+  case $file in                                         \
+    *.a)                                                \
+      /home/ataylor/cross-compiler/pdp11-gcc9.3.0/pdp11-aout/bin/ranlib ../../.././gcc/msoft-float/$file ;;     \
+  esac;                                                 \
+done
+dest=../../.././gcc/include/tmp$$-unwind.h; \
+cp unwind.h $dest; \
+chmod a+r $dest; \
+sh ../../../../gcc-9.3.0/libgcc/../move-if-change $dest ../../.././gcc/include/unwind.h
+#: not found
+gmake[4]: *** [Makefile:1109: install-unwind_h-forbuild] Error 127
+gmake[4]: Leaving directory '/usr/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc/pdp11-aout/msoft-float/libgcc'
+gmake[3]: *** [Makefile:1210: multi-do] Error 1
+gmake[3]: Leaving directory '/usr/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc/pdp11-aout/libgcc'
+gmake[2]: *** [Makefile:127: all-multi] Error 2
+gmake[2]: Leaving directory '/usr/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc/pdp11-aout/libgcc'
+gmake[1]: *** [Makefile:12488: all-target-libgcc] Error 2
+gmake[1]: Leaving directory '/usr/home/ataylor/cross-compiler/pdp11-gcc9.3.0/workdir-gcc'
+gmake: *** [Makefile:941: all] Error 2
diff --git a/data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler.md b/data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler.md
new file mode 100644 (file)
index 0000000..965b242
--- /dev/null
@@ -0,0 +1,324 @@
+# Overview #
+
+TODO: Write introduction. Goal is to build a cross compiler targeting pdp11-aout.
+
+TODO: What kind of joint header do I want across all the articles in a set, linking them together?
+
+This document guides you through building a cross compiler using GCC on
+FreeBSD. This cross compiler will run on a modern AMD64 machine but emit code
+which runs on a DEC PDP-11. In addition to the compiler, these instructions
+also build associated tooling like an assembler, linker, etc.
+
+In this manner, modern programming tools like `make`, `git`, `vi`, and more can
+be used to write modern C in your usual style while targeting the PDP-11.
+
+
+# Installation #
+
+These instructions were tested on FreeBSD 12 with GCC 7.3.0 from ports as the
+host compiler. The cross compiler was built from the GCC 10.2.0 and Binutils
+2.35.1 source code.
+
+Building GCC requires GNU Make. On FreeBSD either install via `pkg install
+gmake` or build from ports under `devel/gmake`. On Linux your `make` command is
+probably `gmake` in disguise. Run `make --version` and see if the first line is
+something like `GNU Make 4.2.1`.
+
+In addition to GCC, we will also need to compile GNU Binutils since it contains
+the assembler, linker, and other necessary tools.
+
+Obtain suitable source code tarballs from these links.
+
+  - <https://www.gnu.org/software/binutils/>
+
+  - <https://www.gnu.org/software/gcc/>
+
+I like to build all my cross compilers under one folder in my home directory,
+each with a version specific sub-folder.
+
+    setenv PREFIX "$HOME/cross-compiler/pdp11-gcc10.2.0"
+
+Remember to make any `$PATH` changes permanent. For `tcsh` on FreeBSD, this
+means editing `~/.cshrc`. To set the `$PATH` for this session, execute the
+following.
+
+    setenv PATH "$PREFIX/bin:$PATH"
+
+The `$TARGET` environment variable is critical as it tells GCC what kind of
+cross compiler we desire. In our case, this [target
+triplet](https://wiki.osdev.org/Target_Triplet) is requesting code for the
+PDP-11 architecture, wrapped in an `a.out` container, with no hosted
+environment. That means this is a bare-metal target. There will be no C
+standard library, only the C language itself.
+
+    setenv TARGET pdp11-aout
+
+Both GCC and binutils are best built from outside the source tree. Make two
+directories to hold the build detritus. Use a clean build directory each time
+you reconfigure or rebuild.
+
+    cd $HOME/cross-compiler/pdp11-gcc10.2.0
+    mkdir workdir-binutils
+    mkdir workdir-gcc
+
+Build binutils first. Assuming you saved the source code in
+`~/cross-compiler/pdp11-gcc10.2.0/`, simply do the following.
+
+       cd $HOME/cross-compiler/pdp11-gcc10.2.0
+    tar xzf binutils-2.35.1.tar.gz
+    cd workdir-binutils
+
+Now configure, build and install binutils.
+
+    ../binutils-2.35.1/configure --target=$TARGET --prefix="$PREFIX" \
+            --with-sysroot --disable-nls --disable-werror
+    gmake
+    gmake install
+
+Verify that you can access a series of files in your `$PATH` named
+`pdp11-aout-*` (e.g. `pdp11-aout-as`), and that checking their version with
+`pdp11-aout-as --version` results in something like `GNU Binutils 2.35.1`.
+
+With binutils built and installed, now it's time to build GCC.
+
+Follow a similar process to unpack the source code, but note the new
+requirement to download dependencies. In older versions of GCC this command was
+`./contrib/download-dependencies` instead of
+`./contrib/download-prerequisites`.
+
+    cd $HOME/cross-compiler/pdp11-gcc10.2.0
+    tar xzf gcc-10.2.0.tar.gz
+    cd gcc-10.2.0
+    ./contrib/download-prerequisites
+    cd ../workdir-gcc
+
+Configuring GCC proceeds similarly to binutils. Both GNU `as` and GNU `ld` are
+part of binutils, hence the directive informing GCC to use them.
+
+    ../gcc-10.2.0/configure --target=$TARGET --prefix="$PREFIX" \
+            --disable-nls --enable-languages=c --without-headers \
+            --with-gnu-as --with-gnu-ld --disable-libssp
+    gmake all-gcc
+    gmake install-gcc
+
+Verify that `pdp11-aout-gcc --version` from your `$PATH` reports something like
+`pdp11-aout-gcc 10.2.0`.
+
+That's it, you're done. You now have a cross compiler that will run on your
+workstation and output PDP-11 compatible binaries in `a.out` format.
+
+At this point you can [skip ahead to the next section](TODO) or continue
+reading about some potential pitfalls of the cross compiler we've just built.
+
+
+# Potential Pitfalls #
+
+Below are a few problems I ran into while using my cross compiler, some of
+which may apply when compiling your own code for the PDP-11. I hope that by
+mentioning the problems here, along with symptoms and workarounds, you might be
+saved some time when encountering them.
+
+## Compiling libgcc ##
+
+Our newly built cross compiler expects `libgcc` to exist at link time, but we
+didn't build it. So what is `libgcc` anyway? Quoting from the [GCC
+manual](https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html):
+
+    GCC provides a low-level runtime library, libgcc.a or libgcc_s.so.1 on some
+    platforms. GCC generates calls to routines in this library automatically,
+    whenever it needs to perform some operation that is too complicated to emit
+    inline code for.
+    
+    Most of the routines in libgcc handle arithmetic operations that the target
+    processor cannot perform directly. This includes integer multiply and divide on
+    some machines, and all floating-point and fixed-point operations on other
+    machines. libgcc also includes routines for exception handling, and a handful
+    of miscellaneous operations.
+    
+    Some of these routines can be defined in mostly machine-independent C. Others
+    must be hand-written in assembly language for each processor that needs them. 
+
+Why didn't we build `libgcc`? Because we encountered this [error
+message](./pdp11-cross-compiler-libgcc-errormsg.txt).
+
+
+### Problem ###
+
+Consider the following C code which performs division and modulus operations on
+16-bit unsigned integers.
+
+    #include "pdp11.h"
+    #include <stdint.h>
+    
+    uint16_t a=8, b=64;
+    printf("b \% a = %o\n", b % a);
+    printf("b / a = %o\n", b / a);
+
+If we try to compile this code, we receive two errors from the linker.
+    
+    pdp11-aout-ld: example.o:example.o:(.text+0x8e): undefined reference to `__umodhi3'
+    pdp11-aout-ld: example.o:example.o:(.text+0xac): undefined reference to `__udivhi3'
+
+The two functions referenced, `__umodhi3` and `__udivhi3` are part of `libgcc`.
+The names reference the **u**nsigned **mod**ulo or **div**ision on
+**h**alf-**i**teger types. Per the [GCC
+manual](https://gcc.gnu.org/onlinedocs/gccint/Machine-Modes.html#Machine-Modes),
+the half-integer mode uses a two-byte integer.
+
+
+### Solution ###
+
+There are two ways around this problem.
+
+The first (and superior) option is figuring out how to build `libgcc`. The
+command to initiate the build is `gmake all-target-libgcc`, executed under the
+same environment in which `gmake all-gcc` was executed earlier in this guide.
+If you figure out what I'm doing wrong, let me know.
+
+The second option is to implement your own functions for `__umodhi3()`,
+`__udivhi3()`, and whatever else might come up. It's not hard to make something
+functional, though catching all the edge cases could be challenging. 
+
+
+## uint32 ##
+
+Although the PDP-11 utilizes a 16-bit word, GCC is clever enough to allow
+operations on 32-bit words by breaking them up into smaller operations. For
+example, in the following assembly code generated by GCC, note how the 32-bit
+word is pushed onto the stack as two separate words.
+
+    uint32_t a=0710004010          uint16_t a=010;
+
+    add     $-4, sp                add     $-2, sp
+    mov     $3440, (sp)            mov     $10, (sp)
+    mov     $4010, 2(sp)
+
+
+### Problem ###
+
+Whenever I try to make real use of code with `uint32_t`, I encounter internal
+compiler errors like the following.
+
+    memtest.c:119:1: error: insn does not satisfy its constraints:
+     }
+     ^
+    (insn 95 44 45 (set (reg:HI 1 r1)
+            (reg/f:HI 16 virtual-incoming-args)) "memtest.c":114 14 {movhi}
+         (nil))
+    memtest.c:119:1: internal compiler error: in extract_constrain_insn_cached, at recog.c:2225
+    no stack trace because unwind library not available
+    Please submit a full bug report,
+    with preprocessed source if appropriate.
+    See <https://gcc.gnu.org/bugs/> for instructions.
+    *** Error code 1
+
+In each case, adding a single `uint32_t` operation in one spot in the code
+resulted in a compiler error in a completely different part of the code.
+Removing the offending `uint32_t` line caused the program to again compile and
+execute normally. In each case, I already had `uint32_t` related code working
+elsewhere in the program.
+
+
+### Solution ###
+
+Until I track down the bug causing these errors, I've been using structs
+containing pairs of `uint16_t` words and writing helper functions to perform
+operations on them.
+
+
+## GNU Assembler Bug ##
+
+If you're stuck using an older version of GNU binutils, as I was while cross
+compiling from a SPARCstation 20, there is a bug in the GNU assembler that
+crops up whenever double-indirection is used in GCC.  It was present until at
+least GNU Binutil 2.28 but appears to be fixed no later than 2.32 per the
+following code snippet in `binutils-2.32/gas/config/tc-pdp11.c`.
+
+    if (*str == '@' || *str == '*')
+    {
+        /* @(Rn) == @0(Rn): Mode 7, Indexed deferred.
+        Check for auto-increment deferred.  */
+        if ( ...
+
+
+### Problem ###
+
+One of the addressing modes supported by the PDP-11 is 'index deferred',
+represented by `@X(Rn)`. This operand indicates that `Rn` contains a pointer
+which should be dereferenced and the result added to `X` to generate a new
+pointer to the final location. For example, consider the following four values,
+one stored in a register and the other three in memory. Then `@2(R1)` is the
+value `222`.
+
+    R1: 1000
+    1000: 2000
+    2000: 111
+    2002: 222
+
+Similarly, `@0(R1)` is the value `111`. In most PDP-11 assemblers, including
+DEC's MACRO-11 assembler, the string `@(Rn)` is an alias to `@0(Rn)`. But when
+the GNU assembler encounters `@(Rn)` it assembles it as though it were `(Rn)`,
+a single level of indirection instead of two levels!
+
+If we're only writing assembly then we can work around this bug by always using
+the form `@0(Rn)`. But what if we're writing C and using GCC to compile it?
+Consider the following C code example, taken directly from some stack-based
+debugger code written for the PDP-11.
+
+    uint16_t ** csp = (uint16_t **) 070000;
+    *csp = (uint16_t *) 060000;
+    **csp = 0;
+
+When GCC compiles this to assembly it generates code of the form `@(Rn)` when
+assigning a value to `**csp` thus causing the value `0` to overwrite the value
+`060000` at `*csp` if GNU `as` is used to assemble the code.
+
+
+### Solution ###
+
+The following patch, tested on GNU binutils 2.28, fixes the bug. It's a little
+hacky since it overloads the `operand->code` variable to pass unrelated state
+information to `parse_reg()`.
+
+    --- tc-pdp11.c     2017-06-24 22:33:00.260210000 -0700
+    +++ tc-pdp11.c.fixed       2017-06-24 22:32:12.455205000 -0700
+    @@ -431,6 +431,9 @@
+     {
+       LITTLENUM_TYPE literal_float[2];
+     
+    +  /* Store the value (if any) passed by parse_op_noreg() before parse_reg() overwrites it. */
+    +  int deferred = operand->code;
+    +
+       str = skip_whitespace (str);
+     
+       switch (*str)
+    @@ -451,6 +454,15 @@
+         operand->code |= 020;
+         str++;
+       }
+    +      /*
+    +       * This catches the case where @(Rn) is interpreted as (Rn) rather than @0(Rn)
+    +       */
+    +      else if (deferred)
+    +        {
+    +          operand->additional = 1;
+    +          operand->word = 0;
+    +          operand->code |= 060;
+    +        }
+           else
+       {
+         operand->code |= 010;
+    @@ -581,6 +593,12 @@
+     
+       if (*str == '@' || *str == '*')
+         {
+    +      /*
+    +       * operand->code is overwritten by parse_reg() inside parse_op_no_deferred()
+    +       * We use it to temporarily catch the alias @(Rn) -> @0(Rn) since
+    +       *   parse_op_no_deferred() starts at str+1 and thus misses the '@'.
+    +       */
+    +      operand->code |= 010;
+           str = parse_op_no_deferred (str + 1, operand);
+           if (operand->error)
+    return str;
+
diff --git a/data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler.metadata b/data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler.metadata
new file mode 100644 (file)
index 0000000..2956b17
--- /dev/null
@@ -0,0 +1,6 @@
+[DEFAULT]
+page_title = PDP-11 Cross-Compiling - Building a cross compiler with GCC for pdp11-aout.
+meta_keywords = 
+meta_description = 
+menu_text = Cross Compiler
+menu_priority = 8000