Net Yaroze For Linux

This is a tutorial that explains how to set up GCC 2.95 as a cross-compiler for the "Net Yaroze" PlayStation development environment under Linux.

Written by Christian Bauer Christian.Bauer@uni-mainz.de.


Node:Introduction, Next:, Previous:Top, Up:Top

Introduction

This document is a step-by-step explanation of how to set up GCC 2.95 as a cross-compiler under Linux for developing software for the "Net Yaroze" PlayStation development environment. The installation of the gdb debugger and graphics/sound conversion utilities are not covered.

We assume that you are familiar with the Linux shell and have some experience in developing software using GCC. No previous knowledge of installing GCC is required.

The examples in this tutorial were tested on a RedHat 6.1 system running on Intel hardware. They should, however, work on any Linux distribution that has the necessary tools for native software development installed (C compiler, headers/libraries, bison/flex etc.). This tutorial should also be applicable to other Unix systems, but the location of some files may differ from Linux.


Node:Getting the files, Next:, Previous:Introduction, Up:Top

Downloading the necessary software

First we have to download the source archives of the software we are going to install. We will need two packages from the GNU software collection:

The versions indicated are the latest versions available at the time of writing of this tutorial. Other versions have not been tested are not guaranteed to work.

binutils and GCC should be available from any GNU FTP mirror, for example ftp.gnu.org. Download these two files:

If you want to program in C++ (this is a bit complicated on the Net Yaroze (see Appendix B) so if you are a beginner you better stick with plain C), you have to get one more file:


Node:Pre-install, Next:, Previous:Getting the files, Up:Top

Preparing for the installation

Now there are some preparations we have to make before actually installing the GNU software we just downloaded.

First of all, we have to decide on the directory in which the software is going to be installed. This is called the PREFIX because the path of this directory is prefixed to the paths of every component to be installed to yield the path of the installed file. For example, if our PREFIX is /usr/local, the file bin/cpp would end up being installed in /usr/local/bin/cpp. A whole directory hierarchy is going to be created in the PREFIX directory, containing subdirectories like bin, include, lib, etc.

I suggest that you choose /usr/local as your PREFIX because most Linux systems are set up to automatically recognize stuff (binaries, includes, man pages etc.) that is put there. If you would like to keep the cross-development tools separated you may choose a different PREFIX, such as /usr/local/yaroze, but you will then have to manually add the path to the installed binaries to your $PATH environment variable (not to speak of paths for man pages and info files).

Also, we are going to create a subdirectory called yaroze in our home directory as a working directory for unpacking and compiling the sources.

So let's get going:

$ cd
$ mkdir yaroze

Now, we have to install the Net Yaroze header files and library in a place where binutils and GCC are able to find them during installation. This place has to be a directory called mipsel-ecoff (for reasons that will become appearent shortly) inside the PREFIX directory:

$ su
(enter root password)
# cd /usr/local
# mkdir mipsel-ecoff
# cd mipsel-ecoff
# mkdir include lib

We will copy the headers and library from the "software development disc" CD-ROM that comes with the Net Yaroze package (the silver one, not the blue-green PlayStation boot disc). The headers will be put in the include directory, the library (libps.a) in the lib directory:

(put CD in drive)
# mount /mnt/cdrom
# cp -R /mnt/cdrom/psx/include/* include
# cp /mnt/cdrom/psx/lib/libps.a lib
# ls -R
.:
include  lib

include:
abs.h     convert.h  libps.h   memory.h  rand.h    stddef.h  string.h
asm.h     ctype.h    limits.h  qsort.h   setjmp.h  stdio.h   strings.h
assert.h  fs.h       malloc.h  r3000.h   stdarg.h  stdlib.h  sys

include/sys:
errno.h  fcntl.h  file.h  ioctl.h  types.h

lib:
libps.a

Fine. Now the header files are formatted for MS-DOS which uses CR-LF to terminate lines, not a single LF as Linux does. This is generally not a problem but reading the headers is easier when the extra CR characters are removed so we are going to strip them:

# cd include
# for i in *.h sys/*.h; do tr -d '\r' <$i >/tmp/tr$$; mv /tmp/tr$$ $i; done
(you may now be asked a lot of questions like "mv: overwrite `abs.h'?";
answer y to all of them.)

You don't have to understand how the last command works but if you typed it exactly as displayed, all headers should now be converted to Linux format.

Now we are going to fix two problems which would appear later when compiling GCC. First, it expects a header file unistd.h which is not there. Fortunately it's sufficient to create an empty file with that name. Second, the file stddef.h defines some types that conflict with GCC-defined types during the GCC compilation, so we "protect" them with an #ifndef statement. In this tutorial, we will do this change with the "ed" text editor:

# touch unistd.h
# ed stddef.h
963
/_UCHAR_T/i
#ifndef IN_GCC
.
/u_long/a
#endif
.
w
985
q
# ls
abs.h      ctype.h   malloc.h  rand.h    stdio.h    sys
asm.h      fs.h      memory.h  setjmp.h  stdlib.h   unistd.h
assert.h   libps.h   qsort.h   stdarg.h  string.h
convert.h  limits.h  r3000.h   stddef.h  strings.h

If you are afraid of using ed, you may of course use any other text editor you like. Just insert a line

#ifndef IN_GCC

before the line #ifndef _UCHAR_T and a line

#endif

before the line #ifndef WEOF.

Ok, the headers are prepared. Now the last thing we are going to do is to extract one object file from the libps.a library that contains the startup code for PlayStation programs:

# cd ../lib
# ar x libps.a startup.o
# ls
libps.a  startup.o
# exit

Phew. We are done with the preparations and can now begin installing the GNU software. Don't worry, things are becoming easier now.


Node:Setting up binutils, Next:, Previous:Pre-install, Up:Top

Setting up binutils

This chapter explains how to compile and install the GNU binutils package to create the MIPS assembler, linker, and other utilities for handling object files produced by GCC.

Unpacking binutils

Unpacking the sources from the .tar.gz archive is simple. We are doing it in our yaroze directory:

$ cd ~/yaroze
$ tar xfz ../binutils-2.9.1.tar.gz       (or wherever you put the archive)
$ ls
binutils-2.9.1

The next step will be to configure the package for our desired target.

Configuring binutils

As with any GNU software, binutils has to be configured before compilation. This is done via a shell script called configure that figures out various details about the system it's running on, all by itself. This allows GNU software to be compiled on a variety of operating systems.

There are only two bits of information we have to supply to configure. The name of our PREFIX directory, and the desired target system (because we are building cross-development tools). In our case, the specification for the target system is mipsel-ecoff (which means "little-endian MIPS processor using the COFF object file format"). Now you know why we were putting the Net Yaroze headers and library in a directory called mipsel-ecoff: this is the default place in which compilers and linkers configured for mipsel-ecoff targets will look for them.

So let's configure binutils:

$ cd binutils-2.9.1
$ ./configure --prefix=/usr/local --target=mipsel-ecoff
(lots of output)

(We could actually have left out the --prefix=/usr/local argument because that is the default, but if you are using a different PREFIX you now know how to specify it.)

Compiling binutils

Now that binutils is configured, we can compile it:

$ make
(lots of output)

This will take a few minutes.

Installing binutils

If the compilation succeeded, we can now install the utilities. This will put everything in place in the PREFIX directory:

$ su
(enter root password)
# make install
(lots of output)
# exit
$ ls /usr/local/bin/mipsel*
/usr/local/bin/mipsel-ecoff-addr2line  /usr/local/bin/mipsel-ecoff-objcopy
/usr/local/bin/mipsel-ecoff-ar         /usr/local/bin/mipsel-ecoff-objdump
/usr/local/bin/mipsel-ecoff-as         /usr/local/bin/mipsel-ecoff-ranlib
/usr/local/bin/mipsel-ecoff-c++filt    /usr/local/bin/mipsel-ecoff-size
/usr/local/bin/mipsel-ecoff-gasp       /usr/local/bin/mipsel-ecoff-strings
/usr/local/bin/mipsel-ecoff-ld         /usr/local/bin/mipsel-ecoff-strip
/usr/local/bin/mipsel-ecoff-nm
$ cd ..

All these utilities have now been installed (along with man pages in /usr/local/man/man1 and some libraries in /usr/local/lib). Note that all programs have mipsel-ecoff in front of their name. That is to tell them apart from the native tools with the same name which are already installed on your system. So, for example, as is still the native assembler, but mipsel-ecoff-as is the MIPS assembler. (If you are curious, some of these programs have also been installed in /usr/local/mipsel-ecoff/bin, but without the mipsel-ecoff prefix. These are the actual programs that the compiler will use).

In theory, we could now start developing PlayStation software in assembly language. But it's easier in a higher-level language like C, so we are going to install GCC next (even for assembly-language programming GCC is a nice front-end to conveniently call the assembler and linker with nearly all the needed parameters).

Before proceeding, you should check that the utilities we just built are in the shell command search path because they will be needed by the GCC installation:

$ which mipsel-ecoff-as
/usr/local/bin/mipsel-ecoff-as

If you get a message like which: no mipsel-ecoff-as in in (...) instead, you have to add the PREFIX/bin directory to the search path:

$ PATH=$PATH:/usr/local/bin


Node:Setting up GCC, Next:, Previous:Setting up binutils, Up:Top

Setting up GCC

This chapter explains how to compile and install the GNU Compiler Collection (GCC) to create a C compiler that is capable of producing MIPS output for the PlayStation. It is assumed that the binutils for the mipsel-ecoff target have already been built and installed as described in the last chapter.

Unpacking GCC

As with binutils, we first unpack the source archive:

$ cd ~/yaroze
$ tar xfz ../gcc-core-2.95.2.tar.gz      (or wherever you put the archive)
$ tar xfz ../gcc-g++-2.95.2.tar.gz       (only if you downloaded this one)
$ ls
binutils-2.9.1  gcc-2.95.2

Configuring GCC

GCC is configured by exactly the same method as binutils (that's one of the nice things about GNU software: everything adheres to certain standards).

However, we are going to do it a little different now: we are setting up a separate directory for compilation. The binutils were configured and compiled in the same directory where the sources were unpacked. But the programmers of GCC recommend that it be built in a separate directory, so we follow their advice. We will create a new directory gcc-build and configure GCC there:

$ mkdir gcc-build
$ ls
binutils-2.9.1  gcc-2.95.2  gcc-build
$ cd gcc-build
$ ../gcc-2.95.2/configure --prefix=/usr/local --target=mipsel-ecoff \
  --with-gnu-as --with-gnu-ld --disable-nls --disable-multilib \
  --enable-languages=c
(lots of output)

That's a lot more arguments than we needed for binutils! We already know about --prefix and --target. --with-gnu-as and --with-gnu-ld specify that the assembler and linker used by the compiler front-end are the corresponding utilities from the GNU binutils package (in this case, GCC is able to use the special features of the GNU utilities). --disable-nls and --disable-multilib disable the native language support and the building of multiple versions of certain compiler-supplied libraries, because this will speed up the building process. Likewise, --enable-languages=c specifies that only the C compiler will be built. There are also compilers for Java, Fortran and Objective-C in the GCC package but a plain C compiler is probably all that you will need (if you also want a C++ compiler, specify --enable-languages=c,c++).

The configure script will have produced some files and directories:

$ ls
Makefile      config.status  gcc        mipsel-ecoff
config.cache  etc            libiberty  texinfo

Before compiling GCC, we will edit the Makefile to prevent the building of GCC-supplied libraries like libio because compilation of these requires things that the Net Yaroze environment doesn't provide, and libps.a and libgcc.a (which will be built) will be anything we need for PlayStation programming anyway.

So do this:

$ ed Makefile
56122
/^ALL_TARGET_MODULES/s//#&/
/^INSTALL_TARGET_MODULES/s//#&/
w
56124
q

(If you want to use a different text editor, that's fine. Just put a # in front of the lines ALL_TARGET_MODULES = \ and INSTALL_TARGET_MODULES = \ in the Makefile.)

[Note: there is probably a way to do this with certain arguments passed to configure instead of editing the Makefile, but I have not figured out how. If you know how to do it, please send me an email. Thanks.]

Compiling GCC

Now everything is prepared for compiling GCC:

$ make
(tons of output)

This will take quite a while. I suggest that you have a cup of coffee in the meantime. There is one part in the compilation where it appears to build the same thing over and over again (when it outputs lines like _muldi3). But don't worry, it doesn't hang. It's just making lots of different versions of the libgcc.a library for different MIPS architectures.

Installing GCC

If the compilation went without errors, GCC can now be installed. As with binutils, this will copy everything that is needed to the PREFIX directory:

$ su
(enter root password)
# make install
(lots of output)
# exit
$ ls /usr/local/bin/mipsel*
/usr/local/bin/mipsel-ecoff-ar       /usr/local/bin/mipsel-ecoff-objdump
/usr/local/bin/mipsel-ecoff-as       /usr/local/bin/mipsel-ecoff-protoize
/usr/local/bin/mipsel-ecoff-c++filt  /usr/local/bin/mipsel-ecoff-ranlib
/usr/local/bin/mipsel-ecoff-gasp     /usr/local/bin/mipsel-ecoff-size
/usr/local/bin/mipsel-ecoff-gcc      /usr/local/bin/mipsel-ecoff-strings
/usr/local/bin/mipsel-ecoff-ld       /usr/local/bin/mipsel-ecoff-strip
/usr/local/bin/mipsel-ecoff-nm       /usr/local/bin/mipsel-ecoff-unprotoize

If you compare this with the listing we got after installing binutils you will notice that there are now some new programs which also have the mipsel-ecoff prefix, the most important of which is mipsel-ecoff-gcc. That is the GCC compiler front-end that knows how to run the actual C preprocessor, compiler, assembler and linker to produce a MIPS binary for the PlayStation. Of course, man pages and info files for all programs will also have been installed.

Let's explore some more:

$ cd /usr/local/lib/gcc-lib
$ ls
mipsel-ecoff
$ cd mipsel-ecoff; ls
2.95.2
$ cd 2.95.2; ls
SYSCALLS.c.X  collect2  eb       libgcc.a  single      specs
cc1           cpp       include  mips3     soft-float

This is where the actual C preprocessor (cpp) and compiler (cc1) are located that get executed by the mipsel-ecoff-gcc front-end. The libgcc.a library contains software replacements for operations which are not directly supported by the MIPS processor. The specs file can be thought of as the configuration file for the GCC compiler. It contains the command line arguments with which the tools (preprocessor, compiler, aseembler, linker) will be called, as well as the names of standard libraries and predefined macros.

Final touches

As the last step of our GCC installation, we will make some small changes to the specs file.

$ pwd
/usr/local/lib/gcc-lib/mipsel-ecoff/2.95.2
$ su
(enter root password)
# ed specs
5919
/-lc/s//-lps/
/startfile/+1c
/usr/local/mipsel-ecoff/lib/startup.o
.
/-DR3000/s/$/ -Dpsx -Dunix/
w
5971
q
# exit

(If you are using a different text editor: change -lc to -lps in the line following *lib:, change the first empty line after the line *startfile: to /usr/local/mipsel-ecoff/startup.o (Caution: The number of lines in the file may not change. You really have to change the first empty line, not to insert a line), and add -Dpsx -Dunix to the list of predefined macros in the *predefines: section.)

This will tell GCC that the library that every program gets linked against is called libps.a (instead of libc.a), that the code in the startup.o file which we extracted from libps.a at the beginning of this tutorial will be put at the start of all programs, and that the macros psx and unix will be predefined.

Congratulations! You now have a working Linux cross-compiler for developing programs for your Net Yaroze system.


Node:Sample, Next:, Previous:Setting up GCC, Up:Top

A little example

Now that the compiler is installed, let's try compiling a little sample program to see if it everything works.

Setting up the sources

We will use one of the sample programs that come on the Net Yaroze software development CD-ROM:

$ cd ~/yaroze
$ mkdir sample
$ cd sample
$ cp /mnt/cdrom/psx/sample/check/* .
$ ls
auto  balltex.h  gdb.ini  main.c  main_j.c  makefile  pad.h

As was the case with the header files, all these files have MS-DOS-style line endings, which we will strip:

$ for i in *; do tr -d '\r' <$i >/tmp/tr$$; mv /tmp/tr$$ $i; done

If you are using a PAL display for your PlayStation you will probably also want to edit main.c to uncomment the SetVideoMode( MODE_NTSC ); line and activate the SetVideoMode( MODE_PAL ); line instead.

Next, edit the makefile so it looks like this:

CC      = mipsel-ecoff-gcc
CFLAGS  = -g -O2 -msoft-float
LDFLAGS = -Wl,-Ttext,0x80140000,-e,_start -msoft-float

PROG    = main
OBJS    = main.o

all: $(PROG)

$(PROG): $(OBJS)
        $(CC) $(LDFLAGS) -o $@ $(OBJS)

%.o: %.c
        $(CC) -c $(CFLAGS) -o $@ $<

clean:
        rm -f $(PROG) $(OBJS)

This makes the makefile a bit more Unix-like and also utilizes some of the new features of GCC and the make utility.

Caution: The indentations in the makefile have to be real <TAB> characters, not 8 spaces. It it's done with spaces, it won't work.

The first line specifies the compiler being used. We will of course use our freshly-created MIPS cross-compiler mipsel-ecoff-gcc. The -msoft-float flag in CFLAGS instructs the compiler that the Net Yaroze doesn't have an FPU (Floating Point Unit). If you are using the data types float or double in your programs without specifying -msoft-float, they will crash. The little sample program doesn't use floating point variables but I nevertheless put the -msoft-float in there for instructive purposes. The LDFLAGS line specifies flags for the linker: the start address of the program (0x80140000) and the name of the entry point at which program execution will start (_start, a symbol defined in the startup.o file). The C main() function will be called from the code in startup.o. Finally, LDFLAGS also has to contain the -msoft-float option so the program will get linked against the right version of libgcc.a.

Compiling the sample program

Now we can compile the sample program:

$ make
mipsel-ecoff-gcc -c -g -O2 -msoft-float -o main.o main.c
mipsel-ecoff-gcc -Wl,-Ttext,0x80140000,-e,_start -msoft-float -o main main.o
$ ls
auto  balltex.h  gdb.ini  main  main.c  main.o  main_j.c  makefile  pad.h
$ file main
main: MIPSEL-LE COFF executable (paged) not stripped - version 2.11
$ ./main
bash: ./main: cannot execute binary file

Wow. We have created a program that contains MIPS code for the PlayStation. This program will of course not run on our Linux system.

Note that you will have to use the new "mipsel-ecoff" commands for manipulating MIPS object files and executables:

$ objdump -h main
objdump: main: File format not recognized
$ mipsel-ecoff-objdump -h main

main:     file format ecoff-littlemips

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000af0  80140000  80140000  00001000  2**4
                  CONTENTS, ALLOC, LOAD, CODE
  1 .rdata        00000120  80140af0  80140af0  00001af0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .data         00000670  80140c10  80140c10  00001c10  2**4
                  CONTENTS, ALLOC, LOAD, DATA
  3 .sdata        00000080  80141280  80141280  00002280  2**4
                  CONTENTS, ALLOC, LOAD, DATA
  4 .sbss         00000010  80141300  80141300  00000000  2**4
                  ALLOC
  5 .bss          0001ec70  80141310  80141310  00000000  2**4
                  ALLOC

The output shows where in the PlayStation memory the different parts of our program will be loaded to. The .bss section is where global variables are stored. It is not actually part of the main executable file but a part of PlayStation RAM cleared to zero right after the program has been uploaded and started (some code in startup.o is doing this). This saves space in the executable file and keeps the uploading time down.

If you want to look what the compiled code looks like you can disassemble the program with the command mipsel-ecoff-objdump -d main. You can also get a nice mixed display of C source and compiled code with mipsel-ecoff-objdump -S main.

The next section will explain how to upload this program to the PlayStation.

Running the sample program

Under MS-DOS, uploading programs and data to the Net Yaroze PlayStation is done via the program siocons. There is also a Unix version of siocons available (your can get it from http://orion.identicalsoftware.com/yaroze/siocons.tgz). Unfortunately, this program doesn't seem to work well and produces a lot of transmission errors (at least on my system, Your Mileage May Vary). There is, however, an alternative called "hssutils". Download this file:

The hssutils package contains a program called psxfer that you can use to upload and start your programs on the Yaroze PlayStation. First, unpack the archive in your yaroze directory:

$ cd ~/yaroze
$ tar xfz ../hssutils-0.1.2.tar.gz       (or wherever you put the archive)
$ ls
binutils-2.9.1  gcc-2.95.2  gcc-build  hssutils-0.1.2  sample

hssutils is configured like binutils and GCC:

$ cd hssutils-0.1.2
$ ./configure --prefix=/usr/local --enable-siodev=/dev/ttyS1 \
  --enable-siospeed=115200
(some output)

The parameter given to --enable-siodev is the Unix device name of the serial port the Net Yaroze communications cable is plugged into. On my system, I use /dev/ttyS1 which is the second serial port because the first serial port (/dev/ttyS0) is connected to my modem. --enable-siospeed is used to set the default baud rate for communication with the PlayStation. It should be set to the same value that your Net Yaroze PlayStation displays after booting from the Net Yaroze boot CD-ROM (a message like TERMINAL SPEED xxx BPS). The default is 9600 baud and your Net Yaroze User's Manual explains how to set the PlayStation to a higher rate (= higher speed). I have set my PlayStation to 115200 before, so I specify this value to configure.

Both the serial device and baud rate you specify here can be overridden later with command line arguments to the psxfer program (-p device and -B baud).

Note that we didn't specify --target=mipsel-ecoff because we are building a plain Linux program here.

Unfortunately, we now have to fix a small bug in the hssutils sources that prevents compiling them under Linux as they are. The file hss/errlog.h defines an errlog structure that contains an errno member. This is however not allowed because errno is a global system variable that is defined in a very special way under Linux and you can't have a variable in your own programs with the same name. So we replace all instances of errno in the sources by err:

$ cd hss
$ chmod 644 *.c *.h
$ ed errlog.h
761
/errno/s//err/
w
759
q
$ ed errlog.c
2738
/errno/+1
#if HAVE_MEMORY_H
g/errno/s//err/g
w
2718
q
$ cd ..

(Note that the first instance of errno in errlog.c is the line #include <errno.h> which must not be changed.)

Now we can compile hssutils:

$ make
(some output)

And we install it in out PREFIX directory like the binutils and GCC:

$ su
(enter root password)
# make install
(some output)
# exit

This will install the program psxfer in /usr/local/bin.

Now we are ready to upload our sample program to the PlayStation and run it. First, boot your PlayStation with the blue-green Net Yaroze boot CD-ROM and wait until it has started up (the brick wall screen appears and the message TERMINAL SPEED xxx BPS is displayed). Then enter this on your Linux system:

$ cd ~/yaroze/sample
$ psxfer main -g
  entry: 80140000
     gp: 801492a0
  .text: 80140000..80140b10
 .rdata: 80140b10..80140c30
  .data: 80140c30..801412a0
 .sdata: 801412a0..80141320
  .sbss: 80141320..80141330
   .bss: 80141330..8015ffa0
main: 80141320 done
ResetGraph:jtb=8004829c,env=800482e4

The -g ("go") flag tells psxfer to not only upload the program but also immediately start it. It will take a few moments to establish the communication with the PlayStation and to upload the program. psxfer also displays a list of where the different program parts get loaded to.

After the program starts, you should see a screen with one bouncing ball on the display connected to your PlayStation and hear a music track. To end the program, press the Select button on the PlayStation controller. psxfer will then display the message program terminated. sigval=5 SIGTRAP(Bp) and exit.

Congratulations! You have reached the end of this little tutorial. I hope that it has been helpful to you. Happy programming!


Node:Modifying libps.a, Next:, Previous:Sample, Up:Top

Advanced topic: modifying libps.a

After completing this tutorial you have a compiler that will handle most of your PlayStation programming needs. But if you are using float, double or long long data types or programming in C++, you are probably going to run into trouble. Consider the following small program (which is just an illustration and serves no real purpose):

#include <libps.h>

int main(void)
{
  printf2("%f\n", sin(VSync(0)));
  return 0;
}

Compiling this program gives a linker error:

$ mipsel-ecoff-gcc -Wl,-Ttext,0x80090000,-e,_start -msoft-float \
  -o test test.c
/usr/local/mipsel-ecoff/lib/libps.a(symusr.o)(*ABS*+0x8003f108): multiple \
 definition of `__negdf2'
/usr/local/lib/gcc-lib/mipsel-ecoff/2.95.2/soft-float/libgcc.a(dp-bit.o) \
 (.text+0x12ac):/home/cebix/yaroze/gcc-build/gcc/dp-bit.c: first defined \
 here

This is because the Net Yaroze library libps.a contains functions that are also in the GCC support library libgcc.a which we have built. Technically, libps.a only contains the addresses of functions in the "System Area" of the PlayStation RAM, not the functions themselves (which are in the file mon/libps.exe on the blue-green Net Yaroze boot disc), but this makes little difference for us here.

We cannot drop libps.a because it contains all the standard C and PlayStation-specific functions, and we don't want to drop libgcc.a because it contains compiler-specific support functions that are necessary for certain data types or operations (like the int-to-float conversion above) to work (and if you are programming in C++, libgcc.a is absolutely required).

So what we are going to do now is to modify libps.a to remove the definition of __negdf2 from it. This will cause the linker to use the one from libgcc.a instead, which is fine. This is going to make the resulting program slightly larger (because more functions from libgcc.a will get linked into the program) but we'll have to live with that.

If the source of libps.a were available modifying it would be very easy, but unfortunately it isn't so we are going to decompile the library into its pieces, recreate the source for the "defective" part, and reassemble a new library. This is as complicated as it sounds, so you should not do this if you're a complete beginner.

Ok, let's start by breaking libps.a to pieces:

$ su
(enter root password)
# cd /usr/local/mipsel-ecoff/lib
# ls
ldscripts  libps.a  startup.o
# mipsel-ecoff-ar x libps.a
# ls
EQSF2.O     FLTSISF.O  LTSF2.O   krom2tm2.o  startup.o  stdef4.o  stdef8.o
FIXSFSI.O   GESF2.O    NEGSF2.O  ldscripts   stdef1.o   stdef5.o  symusr.o
FIXUDFSI.O  GTSF2.O    NESF2.O   libps.a     stdef2.o   stdef6.o  video.o
FIXUSFSI.O  LESF2.O    SUBSF3.O  puts.o      stdef3.o   stdef7.o

These are all the object files contained in libps.a. We are now going to remove the ones that are actually unusable for our cross-compiler and rename the original libps.a so we can restore it if something bad happens during our re-creation process.

# rm *.O puts.o
(probably lots of questions like "rm: remove `EQSF2.O'?"; answer y
to all of them.)
# mv libps.a libps.a.old
# ls
krom2tm2.o  libps.a.old  stdef1.o  stdef3.o  stdef5.o  stdef7.o  symusr.o
ldscripts   startup.o    stdef2.o  stdef4.o  stdef6.o  stdef8.o  video.o

It's a little more clearly arranged now. The who defines __negdf2 is symusr.o. This file contains no code but only symbol definitions.

# mipsel-ecoff-nm symusr.o | grep '__negdf2'
8003f108 A __negdf2

We are now going to recreate an assembler source file for symusr.o:

# mipsel-ecoff-nm symusr.o | \
  sed -e 's/\(.*\) A \(.*\)/\2 = 0x\1; .global \2/' >symusr.s
# ls
krom2tm2.o   startup.o  stdef3.o  stdef6.o  symusr.o
ldscripts    stdef1.o   stdef4.o  stdef7.o  symusr.s
libps.a.old  stdef2.o   stdef5.o  stdef8.o  video.o
# head symusr.s
ApplyMatrix = 0x8002d4d8; .global ApplyMatrix
ApplyMatrixLV = 0x8002cde0; .global ApplyMatrixLV
ApplyMatrixSV = 0x8002d528; .global ApplyMatrixSV
CLIP2 = 0x80063f9c; .global CLIP2
CdPlay = 0x8003c388; .global CdPlay
CdRead = 0x8003bf60; .global CdRead
CdReadExec = 0x8003c2b4; .global CdReadExec
CdReadFile = 0x8003c14c; .global CdReadFile
CdReadSync = 0x8003c068; .global CdReadSync
CdSearchFile = 0x8003b198; .global CdSearchFile

Isn't Unix wonderful? We only need a Makefile for assembling the new library (remember to use <TAB> characters for the indentations):

OBJS = symusr.o stdef1.o stdef2.o stdef3.o stdef4.o \
 stdef5.o stdef6.o stdef7.o stdef8.o krom2tm2.o video.o

libps.a: $(OBJS)
        mipsel-ecoff-ar r $@ $?
        mipsel-ecoff-ranlib $@

%.o: %.s
        mipsel-ecoff-as -o $@ $<

As a little test we will first create a new libps.a without making any changes:

# make
mipsel-ecoff-as -o symusr.o symusr.s
mipsel-ecoff-ar r libps.a symusr.o stdef1.o stdef2.o stdef3.o stdef4.o \
 stdef5.o stdef6.o stdef7.o stdef8.o krom2tm2.o video.o
mipsel-ecoff-ranlib libps.a
# ls -l lib*
-rw-rw-r--   1 root     root        26626 Feb 16 23:25 libps.a
-r-xr-xr-x   1 root     root        40216 Feb  5 21:34 libps.a.old

You will notice that the library got smaller. Now we can remove the __negdf2 definition from symusr.s and reassemble the library.

# ed symusr.s
11216
/__negdf2/d
w
11176
q
# make
mipsel-ecoff-as -o symusr.o symusr.s
mipsel-ecoff-ar r libps.a symusr.o
mipsel-ecoff-ranlib libps.a

And we are done. We have an updated libps.a that the sample program from above can be linked with. If you encounter more "multiple definition" problems in the future, you now know how to deal with them.


Node:Using C++, Previous:Modifying libps.a, Up:Top

Using C++

In this appendix I will talk about some of the pitfalls you will encounter when using C++ code on the Net Yaroze. I didn't do extensive tests with advanced C++ features like exceptions, templates and streams, but basic things like classes, new and delete, and virtual functions will work. For brevity, this appendix is not written in the step-by-step tutorial style of the rest of this article.

The first problem you will encounter is that not all of the Net Yaroze header files are C++ compliant. For example, libps.h is missing an extern "C" { ... } statement around its function definitions (strangely enough all the other header files have it), and it declares a function delete() which is a reserved keyword in C++ (I choose to rename that to delete_file()). Also, you might want to add const qualifiers to some of the char * arguments of the functions in the standard C header files to avoid getting lots of compiler warnings (C++ is much more strict with respect to const-ness than plain C). If in doubt, consult Unix man pages about these functions.

But wait, there's more! Consider the following program:

#include <stdio.h>

class hello {
public:
    hello(const char *name) : answer(42)
    {
     	printf("Constructor of (%s) was called\n", name);
    }

    void print()
    {
     	printf("The answer is %d.\n", answer);
    }

private:
    int answer;
};

static hello global_hello("global_hello");

int main(void)
{
    global_hello.print();

    hello local_hello("local_hello");
    local_hello.print();

    hello *dynamic_hello = new hello("dynamic_hello");
    dynamic_hello->print();
    delete dynamic_hello;

    return 0;
}

If you compile this, you will get lots of linker errors about the function abort() being missing. Uhm, but we are not calling abort() anywhere in our program!? Right, but we are using new, and if new fails it will throw an exception, and uncatched exceptions will abort the program using the function abort() (this is all done by code in libgcc.a). But abort() is defined neither in libgcc.a nor in libps.a, so we have to roll our own. Insert this in the program code:

extern "C" void abort()
{
    printf("aborted\n");
    exit(1);
}

The program will now compile. However, if you run it you will see this output from psxfer:

The answer is 0.
Constructor of (local_hello) was called
The answer is 42.
Constructor of (dynamic_hello) was called
The answer is 42.
program terminated. sigval=5 SIGTRAP(Bp)

Obviously, the constructor of global_hello is not being called. In fact, no constructors of any static objects are being called. This would be the job of a function called __main() in the libraries which gets called before main(). Unfortunately, the definition of __main() in libps.a is an empty function (libps.a was written with plain C in mind), so no constructors get called. A working version of __main() is instead provided by libgcc.a.

The easiest way (although a bit of a hack) to deactivate the bogus __main() in startup.o is to patch the object file to rename the symbol to something else:

# cd /usr/local/mipsel-ecoff/lib
# sed -e 's/__main/__xxxx/g' startup.o >startup.o.new
# mv startup.o.new startup.o

It's important that the number of characters in the symbol is not changed.

The sample program will now run correctly but mipsel-ecoff-objdump -h will show you that it has grown to over 16K of code, mostly exception-handling stuff from libgcc.a, so if you are concerned about code size, you should use plain C instead of C++.