From: Aaron Taylor Date: Thu, 7 Jan 2021 12:00:33 +0000 (-0800) Subject: Added page to website describing setup of cross compiler for PDP-11. X-Git-Url: http://git.subgeniuskitty.com/website_subgeniuskitty.com/.git/commitdiff_plain/4d61aa4e9bd86c3c01f1902615a2b0914282a85a Added page to website describing setup of cross compiler for PDP-11. --- 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 index 0000000..d80771a --- /dev/null +++ b/data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler-libgcc-errormsg.txt @@ -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 index 0000000..965b242 --- /dev/null +++ b/data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler.md @@ -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. + + - + + - + +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 + + 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 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 index 0000000..2956b17 --- /dev/null +++ b/data/development/pdp-11/modern_c_software_development/pdp11-cross-compiler.metadata @@ -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