Tim Hentenaar's Blog

Jul 26, 2015 23:30

Building GCC for Solaris 2.5.1 (i386) in 10 Easy Steps

Having installed Solaris 2.5.1 in VirtualBox, I happened to recall the unfortunate fact that the Solaris of that era doesn't ship with a C compiler. Of course, you could always buy Sun's compiler, but in this day and age I haven't been able to find a copy, and I doubt Oracle would be willing to sell it to me. Thus, I set about to build one. Not the simplest task, as evidenced by the length of this post. Thank God Sun ships the C library, headers, and basic binary tools at least. :P

In this article, I cover the steps necessary to build the toolchain and build / package software under Solaris.

Let's get started...

Patches

Several patches are necessary in order to get the toolchain built and working. They can be downloaded here. The sources were otherwise unmodified.

Conventions

At each step, some commands will have to be run on your host, which I assume will be Linux, and some commands will have to be run on Solaris. The commands run on Linux will always begin with a bash-style user@host string before the prompt. The commands which should be run on Solaris will simply start with the prompt, as you'd expect.

The Solaris box has the hostname solaris251, and our host has the hostname cid.

The steps outlined here should also work for sparc Solaris 2.5.1 also, as long as you're using the proper target.

Initial Steps

You'll need to have the Solaris development packages installed. They should be included with your install media. Ensure you have at least the following packages installed. In Solaris 2.5.1 installing the "Development" system should include these packages.

  • SUNWhea
  • SUNWarc
  • SUNWbtool
  • SUNWlibm

Make a directory to hold the toolchain. Run the following command as root:

root@cid # mkdir -p /opt/cross-solaris-2.5.1

Now, you'll need to figure out your host tuple. On my machine, this is x86_64-pc-linux-gnu. To figure this out, just ask GCC what it is:

tim@cid $ gcc -v 2>&1 | grep 'Target:'
Target: x86_64-pc-linux-gnu

We'll be passing the host tuple to configure so that autoconf knows what type of system will host the toolchain.

NOTE: Commands preceeded by a # should be run as root.

Step 1: Get the Solaris headers and libc

You'll need to get the system headers and libraries. First, make an archive of them:

tim@cid $ telnet solaris251
Trying solaris251...
Connected to solaris251.
Escape character is '^]'.
UNIX(r) System V Release 4.0 (solaris251)
login: tim
Password:
Last login: Thu Jul 16 02:29:14 from 10.100.0.2
Sun Microsystems Inc.   SunOS 5.5.1     Generic May 1996
$ su
Password:
# tar -cf dev.tar /usr/ccs/lib/.[ao] /usr/include /usr/lib/.a /usr/lib/.so
# exit
$ exit
Connection closed by foreign host.

Second, we need to download and extract the archive:

tim@cid $ ftp solaris251
Connected to solaris251 (10.100.0.3).
220 solaris251 FTP server (UNIX(r) System V Release 4.0) ready.
Name (solaris251:tim): tim
500 'AUTH SSL': command not understood.
SSL not available
331 Password required for tim.
Password:
230 User tim logged in.
ftp> bin
200 Type set to I.
ftp> get dev.tar
local: dev.tar remote: dev.tar
200 PORT command successful.
150 Binary data connection for dev.tar (10.100.0.2,60471) (11768320 bytes).
226 Binary Transfer complete.
11768320 bytes received in 0.488 secs (2.3e+04 Kbytes/sec)
ftp> quit
221 Goodbye.

Extract it (using a tar that ignores leading slashes, like GNU tar),

tim@cid $ su
Password:
root@cid # tar -C /opt/cross-solaris-2.5.1 -xf dev.tar
root@cid # exit

Step 2: Setup PATH

In order to prevent repeatedly settings the path, as we'll often be switching back and fourth from our normal user to root on the Solaris box, let's go ahead and add the paths we need to our .profile.

tim@cid $ telnet solaris251
user: ...
$ su
Password:
# touch /.profile
# echo "PATH=/usr/local/bin:/usr/ccs/bin:/usr/sbin" >> /.profile
# echo "export PATH" >> /.profile
# exit
$ touch $HOME/.profile
$ echo "PATH=/usr/local/bin:/usr/ccs/bin:/usr/bin" >> $HOME/.profile
$ echo "export PATH" >> $HOME/.profile

Step 3: Binutils 2.25

First, you'll need to download the binutils source.

You can build it as follows:

tim@cid $ tar -xjf binutils-2.25.tar.bz2
tim@cid $ patch -p0 < binutils-2.25.patch
tim@cid $ mkdir binutils-build && cd binutils-build
tim@cid $ ../binutils-2.25/configure --prefix=/opt/cross-solaris-2.5.1 \
--with-sysroot=/opt/cross-solaris-2.5.1 \
--build=x86_64-pc-linux-gnu --target=i386-pc-solaris2.5.1 \
--disable-gold --disable-libstdcxx --disable-libssp --disable-nls
tim@cid $ make
tim@cid $ su
Password:
root@cid # make install
root@cid # exit
tim@cid $ cd .. && rm -rf binutils-build

Step 4: Cross-GCC 3.4.6

Next, we'll build a copy of GCC that can generate code for our Solaris box. You'll need to download the GCC source.

tim@cid $ export PATH=$PATH:/opt/cross-solaris-2.5.1/bin
tim@cid $ tar -xjf gcc-3.4.6.tar.bz2
tim@cid $ patch -p0 < gcc-3.4.patch ; cd ..
tim@cid $ mkdir gcc-build && cd gcc-build
tim@cid $ ../gcc-3.4.6/configure \
--prefix=/opt/cross-solaris-2.5.1 --with-sysroot=/opt/cross-solaris-2.5.1 \
--build=x8664-pc-linux-gnu --host=x8664-pc-linux-gnu\
--target=i386-pc-solaris2.5.1 --with-gnu-ld --with-gnu-as\
--enable-languages=c --enable-threads=posix --disable-multilib\
--disable-nls --disable-shared
tim@cid $ make
tim@cid $ sed -ie 's@/usr/ccs@../ccs@g' gcc/specs
tim@cid $ su
Password:
root@cid # export PATH=$PATH:/opt/cross-solaris-2.5.1/bin
root@cid # make install
root@cid # exit
tim@cid $ cd .. && rm -rf gcc-build

Now, let's test our compiler:

tim@cid $ i386-pc-solaris2.5.1-gcc -v
Reading specs from /opt/cross-solaris-2.5.1/lib/gcc/i386-pc-solaris2.5.1/3.4.6/specs
Configured with: ../gcc-3.4.6/configure --prefix=/opt/cross-solaris-2.5.1 --with-sysroot=/opt/cross-solaris-2.5.1 --build=x8664-pc-linux-gnu --host=x8664-pc-linux-gnu --target=i386-pc-solaris2.5.1 --with-gnu-ld --with-gnu-as --enable-languages=c --enable-threads=posix --disable-multilib --disable-nls --disable-shared
Thread model: posix
gcc version 3.4.6

Next, we'll to cross-compile this simple test program.

#include <stdio.h>
int main(int argc, char *argv[]) { puts("Hello, world!"); return 0; }

Here's how we build it, and test it on our Solaris box:

tim@cid $ i386-pc-solaris2.5.1-gcc -O2 -Wl,--no-check-sections -static -o hello hello.c
tim@cid $ file hello
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
tim@cid $ ftp solaris251
...
ftp> bin
200 Type set to I.
ftp> put hello
local: hello remote: hello
200 PORT command successful.
150 Binary data connection for hello (10.100.0.2,44065).
226 Transfer complete.
58964 bytes sent in 0.000199 secs (2.9e+05 Kbytes/sec)
ftp> quit
221 Goodbye.
tim@cid $ telnet solaris251
...
UNIX(r) System V Release 4.0 (solaris251)
login: tim
Password:
Last login: Thu Jul 16 04:19:21 from 10.100.0.2
Sun Microsystems Inc.   SunOS 5.5.1     Generic May 1996
$ chmod +x hello
$ ./hello
Hello, world!
$ exit

Step 5: Bootstrap GCC 3.4.6

Now we get to the tricky part. Cross-compiling GCC itself to produce a bootstrap compiler for building GCC on Solaris. We'll use the linker provided by Solaris, but we'll need the GNU assembler.

tim@cid $ mkdir cross-gcc-build && cd cross-gcc-build
tim@cid $ CFLAGS="-Wl,--no-check-sections -static" ../gcc-3.4.6/configure \
--prefix=/opt/cross-solaris-2.5.1/opt/gcc \
--with-sysroot=/opt/cross-solaris-2.5.1 \
--build=x86_64-pc-linux-gnu --host=i386-pc-solaris2.5.1 \
--target=i386-pc-solaris2.5.1 --with-gnu-as \
--with-ld=/usr/ccs/bin/ld --with-as=/opt/gcc/bin/as \
--enable-languages=c  --enable-threads=posix --disable-multilib \
--disable-nls --disable-shared
tim@cid $ make
tim@cid $ su
Password:
root@cid # export PATH=$PATH:/opt/cross-solaris-2.5.1/bin
root@cid # make install

Step 6: Cross-compile binutils 2.25 (gas only)

The assembler Solaris has sucks, and happens to lack some instructions used by gcc when optimizing. Thus, we'll need to make sure we have the GNU assembler handy.

tim@cid $ mkdir cross-binutils-build && cd cross-binutils-build
tim@cid $ LDFLAGS="-Wl,--no-check-sections -Wl,-Bstatic" \
CFLAGS="-O2 -I/opt/cross-solaris-2.5.1/usr/include" \
../binutils-2.25/configure --prefix=/opt/cross-solaris-2.5.1/opt/gcc \
--with-sysroot=/opt/cross-solaris-2.5.1 --build=x86_64-pc-linux-gnu \
--host=i386-pc-solaris2.5.1 --target=i386-pc-solaris2.5.1 \
--disable-nls --disable-libstdcxx --disable-plugins --disable-gold \
--disable-libssp
tim@cid $ make
tim@cid $ su
Password:
root@cid # export PATH=$PATH:/opt/cross-solaris-2.5.1/bin
root@cid # make -C gas install

Step 7: Test the bootstrap compiler

If tbe above steps were successful, you now have a Solaris-hosted GCC in /opt/cross-solaris-2.5.1/opt/gcc! We'll need to tar this up, and transfer it to the Solaris machine like so:

root@cid # cd /opt/cross-solaris-2.5.1
root@cid # tar -cf solaris-gcc-3.4.6.tar opt/gcc
root@cid # ftp solaris251
ftp> bin
ftp> put solaris-gcc-3.4.6.tar
ftp> quit
root@cid # exit
tim@cid $ ftp solaris251
ftp> ascii
ftp> put hello.c
ftp> quit
tim@cid $ telnet solaris251
user: ...
$ su
Password:
# tar -xf solaris-gcc-3.4.6.tar
# mv opt/gcc /opt/gcc && rm -rf opt
# exit
$ PATH=/opt/gcc/bin:$PATH
$ export PATH
$ gcc -v
Reading specs from /opt/gcc/bin/../lib/gcc/i386-pc-solaris2.5.1/3.4.6/specs
Configured with: ../gcc-3.4.6/configure --prefix=/opt/cross-solaris-2.5.1/opt/gcc --with-sysroot=/opt/cross-solaris-2.5.1 --build=x86_64-pc-linux-gnu --host=i386-pc-solaris2.5.1 --target=i386-pc-solaris2.5.1 --with-gnu-as --with-ld=/usr/ccs/bin/ld --with-as=/opt/gcc/bin/as --enable-languages=c --enable-threads=posix --disable-multilib --disable-nls --disable-shared
Thread model: posix
gcc version 3.4.6

Since we used --with-sysroot during our cross-compile, we need to create a symlink to mimic our setup on the Solaris box so that GCC can find our headers and libraries.

$ su
Password:
# mkdir -p /opt && ln -sf / cross-solaris-2.5.1
# exit
$

Now, let's build our hello world program:

$ gcc -O2 -o hello hello.c
$ ./hello
Hello, world!

Success! We can now compile C code on our target.

Step 8: Build GNU Make

Sun's make may not be suitable for building GCC, thus we'll need to use our almighty cross-compiled compiler to build GNU Make first.

$ bzip2 -d make-4.1.tar.bz2
$ ftp solaris251
ftp> bin
ftp> put make-4.1.tar
ftp> quit
$ telnet solaris251
user: ...
$ PATH=/opt/gcc/bin:$PATH
$ export PATH
$ tar -xf make-4.1.tar
$ cd make-4.1
$ ./configure --prefix=/usr/local --disable-nls
$ make
$ su
Password:
# . /.profile
# make install
# exit

Now, let's make sure we can execute it:

$ /usr/local/bin/make -v
GNU Make 4.1
Built for i386-pc-solaris2.5.1
Copyright (C) 1988-2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Step 9: Build GNU tar

The tar provided on Solaris always gives me issues with the tarred GCC source code. GNU tar will get the job done.

$ tar -xjf tar-1.28.tar.bz2
$ patch -p0 < tar-1.28.patch
$ tar -cf tar-1.28.tar tar-1.28
$ ftp solaris251
ftp> bin
ftp> put tar-1.28.tar
ftp> quit
$ telnet solaris251
user: ...
$ tar -xf tar-1.28.tar
$ cd tar-1.28
$ PATH=/opt/gcc/bin:$PATH
$ export PATH
$ ./configure --prefix=/usr/local --without-xattrs --without-selinux \
--without-posix-acls
$ make
$ su
Password:
# . /.profile
# make install
# exit
$ /usr/local/bin/tar --version
tar (GNU tar) 1.28
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by John Gilmore and Jay Fenlason.
$

Step 10: Build GCC on Solaris

Now, let's build our original objective, now that all the other stuff is out of the way.

tim@cid $ tar -cf gcc-3.4.6.tar gcc-3.4.6
tim@cid $ ftp solaris251
ftp> bin
ftp> put gcc-3.4.6.tar
ftp> quit
tim@cid $ telnet solaris251
user: ...
$ /usr/local/bin/tar -xf gcc-3.4.6.tar

Copy the assembler:

$ su
# cp /opt/gcc/bin/as /usr/local/bin/as
# exit

and build GCC:

$ PATH=/opt/gcc/bin:$PATH
$ export PATH
$ mkdir gcc-build && cd gcc-build
$ ../gcc-3.4.6/configure --prefix=/usr/local \
--with-gnu-as  --with-ld=/usr/ccs/bin/ld --with-as=/usr/local/bin/as \
--enable-languages=c,c++ --enable-threads=posix --disable-multilib \
--disable-nls
$ make
$ su
Password:
# . /.profile
# make install
# exit
$ cd ..

Now, let's test our compiler:

$ gcc -O2 -o hello hello.c
$ ./hello
Hello, World!
$

Now we can purge the bootstrap compiler:

$ su
Password:
# rm -rf /opt/gcc
# rm -f /opt/cross-solaris-2.5.1

Finally, we have a working compiler built in the target environment.

Bonus: Building gdb on Solaris

Let's install gdb also, so that we can have something a bit better than adb. Be sure to generate a tar file just like we did for tar. Then upload and build it as follows:

tim@cid $ telnet solaris251
user: ...
$ tar -xf gdb-7.9.tar ; cd gdb-7.9
$ MAKEINFO=false ./configure --prefix=/usr/local --disable-nls \
--disable-plugins --disable-gold --disable-libssp --enable-threads=posix
$ make
# . /.profile
# make install
# exit
$ cd .. ; rm -rf gdb-7.9

Now, let's test it:

$ gcc -g -O2 -o hello hello.c
$ gdb hello
GNU gdb (GDB) 7.9
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i386-pc-solaris2.5.1".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
http://www.gnu.org/software/gdb/bugs/.
Find the GDB manual and other documentation resources online at:
http://www.gnu.org/software/gdb/documentation/.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from hello...done.
(gdb) r
Starting program: /export/home/tim/hello
Hello, world!
Inferior 1 (process 26462) exited normally b main
Breakpoint 1 at 0x804896e: file hello.c, line 4.
(gdb) r
Starting program: /export/home/tim/hello
Breakpoint 1, main (argc=1, argv=0x8047e5c) at hello.c:4
4       {
(gdb) s
5               puts("Hello, world!");
(gdb) s
Hello, world!
7       }
(gdb) s
0x08048881 in _start ()
(gdb) c
Continuing.
[Inferior 1 (process 26463) exited normally]
(gdb) quit
$

Looks like the bastard works. :P

Creating Solaris Packages

If you're interested in checking out the source code for the System V packaging tools, or perhaps interested in playing with them on Linux, check out heirloom-pkgtools, which was initially released by Sun as part of OpenSolaris.

For this example, I'll demonstrate building a package for GNU Make using the System V package tools on Solaris.

First, take a "snapshot" of your current /usr/local.

$ find /usr/local -print | pkgproto > pre

Then, build your package normally, ensure it installs under the /usr/local prefix.

Next, take another "snapshot" of /usr/local, and diff them like so:

$ echo "i pkginfo" > prototype
$ find /usr/local -print | pkgproto > post
$ diff pre post | grep '^>' | sed -e 's/^> //g' | grep -v '^d' >> prototype

Your prototype file will now look something like this:

i pkginfo
f none /usr/local/bin/make 0755 root other
f none /usr/local/include/gnumake.h 0644 root other
f none /usr/local/share/info/make.info 0644 root other
f none /usr/local/share/info/make.info-1 0644 root other
f none /usr/local/share/info/make.info-2 0644 root other
f none /usr/local/share/man/man1/make.1 0644 root other

We don't need the info files, or gnumake.h, so remove them. After cleaning it up, your file will now look like this:

i pkginfo
f none /usr/local/bin/make 0755 root other
f none /usr/local/share/man/man1/make.1 0644 root other

Now, you'll want to create a pkginfo file, and upload it. Here's the one I made for GNU Make 4.1:

PKG="GNUmake"
NAME="GNU Make"
ARCH="i386"
VERSION="4.1"
CATEGORY="utility"
CLASSES="none"
VENDOR="Tim Hentenaar"
EMAIL="e@ma.il"
HOTLINE="http://hentenaar.com"

Most of the pkginfo fields should be self-explanatory. Next, we make the package, and transfer it to a package file on our filesystem.

$ su
Password:
# pkgmk -r /
## Building pkgmap from package prototype file.
## Processing pkginfo file.
WARNING: missing directory entry for 
WARNING: missing directory entry for 
...
WARNING: parameter  set to "solaris251150720132610"
## Attempting to volumize 2 entries in pkgmap.
part  1 -- 1850 blocks, 12 entries
## Packaging one part.
/var/spool/pkg/GNUmake/pkgmap
/var/spool/pkg/GNUmake/pkginfo
/var/spool/pkg/GNUmake/root/usr/local/bin/make
/var/spool/pkg/GNUmake/root/usr/local/share/man/man1/make.1
## Validating control scripts.
## Packaging complete.
# pkgtrans -s /var/spool/pkg /opt/packages/GNUmake.pkg GNUmake
Transferring  package instance
# ls -la /opt/packages
total 1828
drwxr-xr-x   2 root     other        512 Jul 20 13:40 .
drwxrwxr-x   6 root     sys          512 Jul 20 13:38 ..
-rw-r--r--   1 root     other     924672 Jul 20 13:40 GNUmake.pkg
# rm -rf /var/spool/pkg/GNUmake

Now we have our package as a datastream (file) in /opt/packages/GNUmake`.pkg. Let's test installing it:

# pkgadd -d /opt/packages/GNUmake.pkg
The following packages are available:
  1  GNUmake     GNU Make
                 (i386) 4.1
Select package(s) you wish to process (or 'all' to process
all packages). (default: all) [?,??,q]:
Processing package instance <GNUmake> from </opt/packages/GNUmake.pkg>
GNU Make
(i386) 4.1
Tim Hentenaar
## Processing package information.
## Processing system information.
## Verifying disk space requirements.
## Checking for conflicts with packages already installed.
## Checking for setuid/setgid programs.
Installing GNU Make 4.1 - i386 as <GNUmake>
## Installing part 1 of 1.
/usr/local/bin/make
/usr/local <implied directory>
/usr/local/bin <implied directory>
/usr/local/share/man/man1/make.1
/usr/local/share <implied directory>
/usr/local/share/man <implied directory>
/usr/local/share/man/man1 <implied directory>
[ verifying class <none> ]
Installation of <GNUmake> was successful.

You'll likely see some warnings about conflicting files, however I had removed the files that would've been installed by the package before installing it just to show how the installation would appear were the package not installed.

Now we can download the package, and distribute it.

Removing Solaris Packages

You can remove an installed package by:

# pkgrm GNUmake
The following package is currently installed:
   GNUmake         GNU Make
                   (i386) 4.1
Do you want to remove this package? y
## Removing installed package instance <GNUmake>
## Verifying package dependencies.
## Processing package information.
## Removing pathnames in class <none>
/usr/local/share/man/man1/make.1
/usr/local/bin/make
## Updating system information.
Removal of <GNUmake> was successful.