September 10, 2014

Because of a conversation I had on Twitter earlier today, I felt compelled to compile the oldest version of Vim I could find.

As it turns out, compiling a C program from more than 20 years ago is actually a lot easier than getting a Rails app from last year to work. If you want to try it yourself, these are the steps you need to take:

$ docker run -i -t mugen/ubuntu-build-essential bin/bash $ apt-get install -y wget gdb file $ wget ftp://ftp.vim.org/pub/vim/old/vim-1.24.tar.gz

According to Wikipedia, vim 1.22, released in 1992, was the first version of vim to run on Unix and thus compete with vi. I couldn’t find a tarball of that release, so I went with vim 1.24 instead which was released a year later.

$ tar -xf vim-1.24.tar.gz $ cd vim-1.24/src $ make -f makefile.unix unix.c: In function 'mch_settmode': unix.c:361:23: error: storage size of 'ttybold' isn't known unix.c:362:20: error: storage size of 'ttybnew' isn't known unix.c:366:12: error: 'TIOCGETP' undeclared (first use in this function) unix.c:366:12: note: each undeclared identifier is reported only once for each function it appears in unix.c:368:25: error: 'CRMOD' undeclared (first use in this function) unix.c:368:33: error: 'ECHO' undeclared (first use in this function) unix.c:369:23: error: 'RAW' undeclared (first use in this function) unix.c:370:12: error: 'TIOCSETP' undeclared (first use in this function)

Edit makefile.unix and change CC to cc -g so we can run gdb later. Remove -DTERMCAP from DEFS and -ltermcap from LIBS . A friend found a version of libtermcap that was almost working, but in the end I got a bunch of linking errors and decided to drop it altogether.

$ rm *.o # There’s no clean task. $ make -f makefile.unix unix.c: In function 'mch_settmode': unix.c:361:23: error: storage size of 'ttybold' isn't known unix.c:362:20: error: storage size of 'ttybnew' isn't known unix.c:366:12: error: 'TIOCGETP' undeclared (first use in this function) unix.c:366:12: note: each undeclared identifier is reported only once for each function it appears in unix.c:368:25: error: 'CRMOD' undeclared (first use in this function) unix.c:368:33: error: 'ECHO' undeclared (first use in this function) unix.c:369:23: error: 'RAW' undeclared (first use in this function) unix.c:370:12: error: 'TIOCSETP' undeclared (first use in this function) make: *** [unix.o] Error 1

A new error, this time in unix.c . It turns out that the preprocessor condition that switches between Linux/BSD and System V includes is the wrong way around. By changing #ifdef SYSV to #ifndef SYSV we can make it work.

$ rm *.o $ make -f makefile.unix cc -o mkcmdtab mkcmdtab.o mkcmdtab cmdtab.tab cmdtab.h make: mkcmdtab: Command not found make: *** [cmdtab.h] Error 127

Almost there! I didn’t know operating systems other than windows include the cwd in their PATH , but we can fix that easily:

$ PATH=.:$PATH make -f makefile.unix cc -o ../vim alloc.o unix.o buffers.o charset.o cmdline.o csearch.o digraph.o edit.o fileio.o help.o linefunc.o main.o mark.o message.o misccmds.o normal.o ops.o param.o quickfix.o regexp.o regsub.o screen.o script.o search.o storage.o tag.o term.o undo.o version.o $ file ../vim ../vim: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x4aed816b36b1b4b65cbbd3a599bd547a338f5b53, not stripped

Boom! We now have a 64bit binary of vim 1.24. However, the joy doesn’t last long, because as soon as we try to execute it, we see this:

./vim ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~Segmentation fault So close. — Pascal Hartig (@passy) September 10, 2014

Luckily, we compiled vim with debugging symbols, so lets turn to gdb for help:

$ gdb ./vim (gdb) r Program received signal SIGSEGV, Segmentation fault. 0x000000000040e326 in expand_env (src=0x42067e "$HOME/.vimrc", dst=0x62b010 "", dstlen=1025) at misccmds.c:694 694 *tail = NUL; (gdb) bt #0 0x000000000040e326 in expand_env (src=0x42067e "$HOME/.vimrc", dst=0x62b010 "", dstlen=1025) at misccmds.c:694 #1 0x00000000004079c7 in dosource (fname=0x420683 "/.vimrc") at cmdline.c:2121 #2 0x000000000040c016 in main (argc=1, argv=0x7fffffffed60) at main.c:278 (gdb)

Vim is trying to expand $HOME/.vimrc to an absolute path and attempts to overwrite a character of the string with \0 which leads to a crash. I don’t exactly know why. My uneducated guess would be that this area of the memory is marked as read-only, which we could certainly turn off through a GCC flag. But if we go up the stacktrace to the main function, we can spot a nice shortcut:

/* * Read the VIMINIT or EXINIT environment variable * (commands are to be separated with '|'). * If there is none, read initialization commands from "s:.vimrc" or "s:.exrc". */ if ((initstr = getenv("VIMINIT")) != NULL || (initstr = getenv("EXINIT")) != NULL) docmdline((u_char *)initstr); else if (dosource(SYSVIMRC_FILE)) /* main.c:278 */ dosource(SYSEXRC_FILE);

The crash happens when we source the SYSVIMRC_FILE which we can actually prevent from happening by setting the environment variable VIMINIT .

$ export VIMINIT="" $ ./vim ~ ~ ~ ~ Empty Buffer

That’s it! We have a running version of vim 1.24 on a modern Linux computer. You can convince yourself by typing :version and enjoying the fact that this version still went under the name of Vi IMitation before it was renamed to Vi IMproved with version 2.0. And hey, it can even load files bigger than 2MB.

255 Kudos