Simple Library Creation for the Amiga

Have you ever created a library for the Amiga? No? Reasonable, since it's a PITA. But sometimes you would like to have a shared library.
It happened then and now that it would have been comfortable if there was an easy way to create libraries for the Amiga. E.g. AmigaSSH contains now several programs which are all using the same crypto methods. That's a perfect candidate for a shared library. But I don't want the hazzle with FD, SFD, prototypes, etc.p.p.

What do I want? Some of these goals could be achieved with a normal library, but it's not code as usual when you have to name all those registers. You also have to maintain a FD/SFD file to maintain the order of the functions. It's not simple.

The idea and the plan

Libraries which utilize the A4 base register aren't new. Maybe my data layout goes a step further: I aligned the library base register A6 with the A4 register. To do so, the datasegment starts with the library structure:
struct Library __lib = {0};
And during library init, the library structure is copied into the datasegment. I also keep the pointer to the provided structure, since it's needed when the last instance gets closed.

Now each library instance needs an own data segment. And to supports trees of libraries, within a process there is one instance per library. The mechanism to duplicate the data segement is already there and used with resident programs. So I use the same mechanism: copy the data and apply the datadatarelocs.

All used functions are provided by a lookup per name. This also allows to implement dlfcn!

There is little more to do, like open/close counters but the most important thing is to set the A4 register properly:

LibInit and LibExpunge

Set a4 to the original data segment:
lea __a4_init,a4

LibOpen

This is a bit mor tricky. First I do the same as in LibInit
lea __a4_init,a4
But once the instance per process is created, I switch the data segment over to the library base in A6:
move.l a6,a4
add.l #32766,a4

LibClose

Switch the data segment over to the library base in A6:
move.l a6,a4
add.l #32766,a4
And this is also what I do for all other hard coded library functions... for the ONE other library function:

ResolveSymbols

This is the function which resolves the function names.
move.l a6,a4
add.l #32766,a4

The library functions

The stub

To add functions to a library, these needs to be compiled using -fbaserel to enable data access via a4. So each function will access the data segment for the calling process. To do so I create a stub with the same name which setups the A4 accordingly which works for most functions. That sub does I'll create a stub file per function.

Limitations

the init function

The init functions takes care to open the library and to initialize the function pointers. Since each function stub refers to the init function it is added automatically.

Simple Creation

To create such a simple library I added the bash script mkstub. There will be needs that are not covered by this script, take it as blueprint then.
The script takes the library name as first parameter and the object files do follow. So what does it do?
It filters the objects using m68k-amigaos-objdump -t to get all symbols, discards all non exported symbols, discards all starting with two underscores __
  1. an empty directory ${lib}-support. And there it creates
  2. the init-stub source file
  3. an export file
  4. a stub file
Then all these files are compiled and all files containg stub are put into the link library. The export files are added the library, which is not done in the script.

An Example

Let's write the library libfoo containing the file foo.c:
#include <amistdio.h>

int globalInt[] = {42};
char cccc[] = {'c'};

void foo() {
  puts("hello from the library");
}
We compile it using
m68k-amigaos-gcc -noixemul -resident -Os -c foo.c -o foo.o
Then run mkstub
mkstub libfoo foo.o
And we create the library:
m68k-amigaos-gcc -noixemul -resident -shared foo.o  libfoo-support/export-libfoo.o -o libfoo.library -Wl,-ulibVersionMajor=1,-ulibVersionMinor=42
Note that also setting a different name is supported using `-ulibName=differentName` which gets extended with .library.
Now we need test programs

A test program

The test program looks like
#include <amistdio.h>

extern void foo();
extern int * globalInt;
extern char * cccc;

int main() {
  foo();
  printf("%ld %c\n", *globalInt, *cccc);
  Flush(stdout);
  return 0;
}
Note that the variable types differ. We must use pointers here!
Compile it:
m68k-amigaos-gcc -Os -noixemul test.c -o test -lfoo -L.
And run it :-)

A DLFCN program

This opens the library dynamically! Note the A4 handling before calling into that library!
#include <amistdio.h>
#include <dlfcn.h>

int main() {
  void * handle = dlopen("libfoo.library", 0);
    printf("got handle=%08lx\n", handle);
  if (handle) {
    void (*foo)() = (void (*)())dlsym(handle, "foo");
    printf("got foo=%08lx\n", foo);
    if (foo) {
		register void * a4 asm("a4");
		asm volatile("move.l %1,%0;\nadd.l #32766,a4" : "=r"(a4) : "r"(handle));
        foo();
    }
    dlclose(handle);
  }
}
Compile it
m68k-amigaos-gcc -noixemul usedlfcn.c -o usedlfcn
And run it :-)