Tag Archives: tcsh

Cross Building tcsh with ELLCC: Revisted

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.