A long time ago, I wrote a post about building the tcsh shell with ELLCC. The subject came up again recently, so I thought that a revisit might be in order.
First, I downloaded the latest tcsh sources with wget and unpacked them with tar:
rich@dev:~$ wget ftp://ftp.astron.com/pub/tcsh/tcsh-6.19.00.tar.gz --2016-08-31 16:16:59-- ftp://ftp.astron.com/pub/tcsh/tcsh-6.19.00.tar.gz => ‘tcsh-6.19.00.tar.gz’ Resolving ftp.astron.com (ftp.astron.com)... 38.117.134.18 Connecting to ftp.astron.com (ftp.astron.com)|38.117.134.18|:21... connected. Logging in as anonymous ... Logged in! ==> SYST ... done. ==> PWD ... done. ==> TYPE I ... done. ==> CWD (1) /pub/tcsh ... done. ==> SIZE tcsh-6.19.00.tar.gz ... 947135 ==> PASV ... done. ==> RETR tcsh-6.19.00.tar.gz ... done. Length: 947135 (925K) (unauthoritative) tcsh-6.19.00.tar.gz 100%[===================>] 924.94K 2.17MB/s in 0.4s 2016-08-31 16:17:00 (2.17 MB/s) - ‘tcsh-6.19.00.tar.gz’ saved [947135] rich@dev:~$ tar xfp tcsh-6.19.00.tar.gz rich@dev:~$
I used wget to get tcsh, but you could also use a web browser.
Next I went into the source directory and ran configure to set up for an ELLCC build:
rich@dev:~$ cd tcsh-6.19.00/ rich@dev:~/tcsh-6.19.00$ CC="/home/rich/ellcc/bin/ecc -target x86_64-linux" CPP="${CC} -E" ./configure checking for a BSD-compatible install... /usr/bin/install -c [snip lot's of configure output] config.status: creating config.h config.status: executing ./atconfig commands rich@dev:~/tcsh-6.19.00$
In this case I configured for an x86_64-linux host. A little later I’ll configure the build for a MIPS.
Now for the build:
rich@dev:~/tcsh-6.19.00$ make grep 'ERR_' ./sh.err.c | grep '^#define' >> sh.err.h.tmp sh.err.h recreated. /home/rich/ellcc/bin/ecc -target x86_64-linux -E -I. -I. -D_PATH_TCSHELL='"/usr/local/bin/tcsh"' -D_h_tc_const\ ./tc.const.c | \ sed -n -e 's/^\(Char STR[a-zA-Z0-9_]*\) *\[ *\].*/extern \1[];/p' | \ sort >> tc.const.h.tmp tc.const.h recreated. /home/rich/ellcc/bin/ecc -target x86_64-linux -c -g -O2 -I. -I. -D_PATH_TCSHELL='"/usr/local/bin/tcsh"' sh.c /home/rich/ellcc/bin/ecc -target x86_64-linux -c -g -O2 -I. -I. -D_PATH_TCSHELL='"/usr/local/bin/tcsh"' [snip lots of files compiled] /home/rich/ellcc/bin/ecc -target x86_64-linux -c -g -O2 -I. -I. -D_PATH_TCSHELL='"/usr/local/bin/tcsh"' sh.proc.c sh.proc.c:155:16: error: variable has incomplete type 'union wait' union wait w; ^ sh.proc.c:155:11: note: forward declaration of 'union wait' union wait w; ^ 1 error generated. Makefile:465: recipe for target 'sh.proc.o' failed make: *** [sh.proc.o] Error 1 rich@dev:~/tcsh-6.19.00$
It turns out the ELLCC’s standard C library doesn’t support a BSD style wait, so I added a conditional compilation around lin 50 of sh.proc.c:
#if defined(_BSD) || (defined(IRIS4D) && __STDC__) || defined(__lucid) || defined(__linux__) || defined(__GNU__) || defined(__GLIBC__) # if !defined(__ANDROID__) && !defined(__ELLCC__) # define BSDWAIT # endif #endif /* _BSD || (IRIS4D && __STDC__) || __lucid || glibc */
Now the make succeeded, but when I tried to run the shell I got:
rich@dev:~/tcsh-6.19.00$ ./tcsh (nil) current memory allocation: free: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 used: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Total in use: 0, total free: 0 Allocated memory from 0x1a00000 to 0xffffffffffffffff. Real top at 0x1a00000 nbytes=56: Out of memory Aborted (core dumped) rich@dev:~/tcsh-6.19.00$
Not good! It turned out that tcsh tries to to its own memory allocation using sbrk() rather than malloc() by default. A quick edit of config_f.h (around line 213) adding another conditional compilation fixed it:
#if defined(__MACHTEN__) || defined(PURIFY) || defined(MALLOC_TRACE) || defined(_OSD_POSIX) || defined(__MVS__) || defined (__CYGWIN__) || defined(__GLIBC__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__ELLCC__) # define SYSMALLOC #else # undef SYSMALLOC #endif
You have to manually delete tc.alloc.o and run make again for this change to take affect:
rich@dev:~/tcsh-6.19.00$ rm tc.alloc.o rich@dev:~/tcsh-6.19.00$ make /home/rich/ellcc/bin/ecc -target x86_64-linux -E -I. -I. -D_PATH_TCSHELL='"/usr/local/bin/tcsh"' -D_h_tc_const\ ./tc.const.c | \ sed -n -e 's/^\(Char STR[a-zA-Z0-9_]*\) *\[ *\].*/extern \1[];/p' | \ sort >> tc.const.h.tmp tc.const.h unchanged. /home/rich/ellcc/bin/ecc -target x86_64-linux -c -g -O2 -I. -I. -D_PATH_TCSHELL='"/usr/local/bin/tcsh"' tc.alloc.c rm -f tcsh core /home/rich/ellcc/bin/ecc -target x86_64-linux -o tcsh -g -O2 -I. -I. sh.o sh.dir.o sh.dol.o sh.err.o sh.exec.o sh.char.o sh.exp.o sh.file.o sh.func.o sh.glob.o sh.hist.o sh.init.o sh.lex.o sh.misc.o sh.parse.o sh.print.o sh.proc.o sh.sem.o sh.set.o sh.time.o glob.o dotlock.o mi.termios.o ma.setp.o vms.termcap.o tw.help.o tw.init.o tw.parse.o tw.spell.o tw.comp.o tw.color.o ed.chared.o ed.refresh.o ed.screen.o ed.init.o ed.inputl.o ed.defns.o ed.xmap.o ed.term.o tc.alloc.o tc.bind.o tc.const.o tc.defs.o tc.disc.o tc.func.o tc.nls.o tc.os.o tc.printf.o tc.prompt.o tc.sched.o tc.sig.o tc.str.o tc.vers.o tc.who.o -lncurses clang-3.9: warning: argument unused during compilation: '-g' make[1]: Entering directory '/home/rich/tcsh-6.19.00/nls' make[1]: Nothing to be done for 'catalogs'. make[1]: Leaving directory '/home/rich/tcsh-6.19.00/nls'
Now when I run tcsh I get:
rich@dev:~/tcsh-6.19.00$ ./tcsh [~/tcsh-6.19.00] dev% set _ addsuffix anyerror arch Linux-x86_64 arch1 Linux arch2 x86_64 arch3 argv () autolist cdpath (.. /home/rich /usr/rich/src /usr/src /sys/arch/i386 /usr/local/etc/httpd/htdocs) cdtohome csubstnonl cwd /home/rich/tcsh-6.19.00 dirstack /home/rich/tcsh-6.19.00 echo_style both edit elegant /home/rich/elegant euid 1000 euser rich gid 1000 group rich histdup erase history 1000 home /home/rich iarch Linux-x86_64 killring 30 notify owd path (/opt/microchip/mplabc32/v2.01/bin /opt/jdk1.5.0_06/jre/bin /usr/local/bin /sbin /usr/sbin /usr/lib64/qt-3.3/bin /usr/lib64/ccache /usr/local/bin /usr/bin /bin /usr/games /usr/local/sbin /usr/sbin /home/rich/.local/bin /home/rich/bin) prompt [%~] %m% prompt2 %R? prompt3 CORRECT>%R (y|n|e|a)? promptchars $# savehist (1024 merge) shell /usr/local/bin/tcsh shlvl 4 sourced 0 status 0 tcsh 6.19.00 term xterm uid 1000 user rich version tcsh 6.19.00 (Astron) 2015-05-21 (x86_64-unknown-linux) options wide,nls,dl,al,kan,rh,color,filec [~/tcsh-6.19.00] dev% exit exit rich@dev:~/tcsh-6.19.00$
Nice! Now what about the MIPS? I cleaned up and did a new configure, but it failed:
rich@dev:~/tcsh-6.19.00$ make distclean make[1]: Entering directory '/home/rich/tcsh-6.19.00/nls' rm -f C.cat et.cat finnish.cat french.cat german.cat greek.cat italian.cat ja.cat pl.cat russian.cat spanish.cat ukrainian.cat make[1]: Leaving directory '/home/rich/tcsh-6.19.00/nls' rm -f a.out strings x.c xs.c tcsh tcsh.a _MAKE_LOG gethost rm -f *.o *.i *.s rm -f sh.prof.c ed.defns.h tc.const.h sh.err.h tc.defs.c rm -f tcsh.*.m tcsh.*.cat rm -f Makefile config.h config_p.h rm -f config.status config.cache config.log tcsh.ps rm -f missing rm -rf autom4te.cache rm -f *~ #* rich@dev:~/tcsh-6.19.00$ CC="/home/rich/ellcc/bin/ecc -target mips32r2el-linux" CPP="${CC} -E" ./configure checking for a BSD-compatible install... /usr/bin/install -c checking build system type... x86_64-unknown-linux-gnu checking host system type... x86_64-unknown-linux-gnu checking cached host tuple... ok Tcsh will use configuration file `linux'. checking for gcc... /home/rich/ellcc/bin/ecc -target mips32r2el-linux checking whether the C compiler works... yes checking for C compiler default output file name... a.out checking for suffix of executables... checking whether we are cross compiling... configure: error: in `/home/rich/tcsh-6.19.00': configure: error: cannot run C compiled programs. If you meant to cross compile, use `--host'. See `config.log' for more details rich@dev:~/tcsh-6.19.00$
Not a big deal, though. All I have to do is tell configure that I’m cross compiling:
rich@dev:~/tcsh-6.19.00$ CC="/home/rich/ellcc/bin/ecc -target mips32r2el-linux" CPP="${CC} -E" ./configure -host=x86_64-linux checking for a BSD-compatible install... /usr/bin/install -c [snip lots of configure output] config.status: executing ./atconfig commands rich@dev:~/tcsh-6.19.00$ make grep 'ERR_' ./sh.err.c | grep '^#define' >> sh.err.h.tmp sh.err.h recreated. /home/rich/ellcc/bin/ecc -target mips32r2el-linux -E -I. -I. -D_PATH_TCSHELL='"/usr/local/bin/tcsh"' -D_h_tc_const\ ./tc.const.c | \ sed -n -e 's/^\(Char STR[a-zA-Z0-9_]*\) *\[ *\].*/extern \1[];/p' | \ sort >> tc.const.h.tmp tc.const.h recreated. /home/rich/ellcc/bin/ecc -target mips32r2el-linux -c -g -O2 -I. -I. -D_PATH_TCSHELL='"/usr/local/bin/tcsh"' sh.c [snip lots of make output] make[1]: Leaving directory '/home/rich/tcsh-6.19.00/nls' rich@dev:~/tcsh-6.19.00$
Now I can run file on the tcsh executable to see what it is, and then try to run it:
ich@dev:~/tcsh-6.19.00$ file tcsh tcsh: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1, statically linked, BuildID[sha1]=778f05c34a07a583f3e56ad979235b0bbcb55279, not stripped rich@dev:~/tcsh-6.19.00$ ~/ellcc/bin/qemu-mipsel tcsh exit rich@dev:~/tcsh-6.19.00$
The good news is that the MIPS executable runs. The bad news is that it exits immediately. A little poking around finds that the MIPS tcsh can run single commands:
rich@dev:~/tcsh-6.19.00$ ~/ellcc/bin/qemu-mipsel tcsh -c set _ addsuffix anyerror arch Linux-x86_64 arch1 Linux arch2 x86_64 arch3 argv () cdpath (.. /home/rich /usr/rich/src /usr/src /sys/arch/i386 /usr/local/etc/httpd/htdocs) cdtohome command set csubstnonl cwd /home/rich/tcsh-6.19.00 dirstack /home/rich/tcsh-6.19.00 echo_style both edit elegant /home/rich/elegant euid 1000 euser rich gid 1000 group rich histdup erase history 1000 home /home/rich iarch Linux-x86_64 killring 30 notify owd path (/opt/microchip/mplabc32/v2.01/bin /opt/jdk1.5.0_06/jre/bin /usr/local/bin /sbin /usr/sbin /usr/lib64/qt-3.3/bin /usr/lib64/ccache /usr/local/bin /usr/bin /bin /usr/games /usr/local/sbin /usr/sbin /home/rich/.local/bin /home/rich/bin) prompt [%~] %m% savehist (1024 merge) shell /usr/local/bin/tcsh shlvl 4 sourced 0 status 0 tcsh 6.19.00 term xterm uid 1000 user rich version tcsh 6.19.00 (Astron) 2015-05-21 (mips-unknown-linux) options wide,nls,dl,al,kan,sm,rh,color,filec rich@dev:~/tcsh-6.19.00$
I tried mips32r2-linux (big endian) and it failed exactly the same way. I also tried a similar cross build for arm32v7-linux and it worked fine. My guess is that there is a bug in QEMU that isn’t handling a MIPS system call correctly, but that investigation will have to be on another day. I’d love to test the binary on a real MIPS system, but I don’t have one. I suspect it might just work.