Simple Library Creation for the Amiga (2)

Recently I wrote about simple library creation for the Amiga. And even dlfcn is supported. But it's not yet good enough. Consider the program:
#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));
Where you have to set the register a4 correctly. That's ugly!
Isn't there a way to provide a direct usable function?

Moving the stub into the library

If the stub could be moved into the library, we could get rid of that a4 setting. Let's have a look at the current generated stub:
extern void * libcryptosshBase;
extern void * __libcryptossh_sp;
extern void * __libcryptossh_a4[2];

long (*__ptr_log)();
void log() {
    asm volatile("move.l (sp)+,%0" : "=m"(__libcryptossh_sp));
    asm volatile("movem.l a5/a6,%0\nmove.l a4,a6" : "=m"(__libcryptossh_a4));
    asm volatile("move.l %0,a5" :: "m"(__ptr_log));
    asm volatile("move.l %0,a4\nadd.l #32766,a4" :: "m"(libcryptosshBase));
    asm volatile("jsr (a5)" :: "m"(__ptr_log));
    asm volatile("move.l a6,a4\nmovem.l %0,a5/a6" : "=m"(__libcryptossh_a4));
    asm volatile("move.l %0,-(sp)" : "=m"(__libcryptossh_sp));

char const * __name_log = "log";
void ** __to_ptr_log = (void**)&__ptr_log;
We still need the library base or the a4 value per opener.

Move the code into the data segment

But if the code gets moved into the libraries data segment, we'll get a copy per instance. To spare registers, we can place the wanted a4 value in front of the function and use it's value using the pc:
	.section .data.stub_log
	.long 0 | <-- contains the a4 value to use
	move.l a4,-(sp)
	move.l -8(pc),a4 | <-- read the correct a4 value
	move.l (sp)+,(___save_a4:W,a4)
	move.l (sp)+,(___save_sp:W,a4)
	jsr    _log
	move.l (___save_sp:W,a4),-(sp)
	move.l (___save_a4:W,a4),a4
The drawback is, that this will increase the size of the data segment, but if the stubs are placed at the end if the data segment and the initialization code doesn't use base relative addressing, it won't disturb.

Coding starts... and a bit later...

The new library works. Patching the a4 into the data segment can be done while symbols are resolved:
   // v is pointer to the function
   register long * l asm("a2") = (long *)v - 1;
   asm volatile("move.l a4,(%0)" :: "r"(l));
The library size increased by 1.8kB and all 4 referencing programs shrinked by 200bytes to 1.8kB, overall it's smaller now, since the trampoline code in each program is now only something like
_${n}: .globl _${n}	
	.short 0x4ef9 | jmp
	.long	_${out}Base | dummy to load the lib init code
These are the pointer and the hex bytes for the jump instruction.

Back to the dlfcn program

Now the program works without any hacks:
#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) {
Mission accomplished!

DEF file


To control which functions of the library are visible a def file can be used, which is a simple text file:
  .text    _functionName
This makes the function functionName exported. The scipr mkstub creates the stub for the link library and the code to export.


But wait: What if a function doesn't access the libraries data segment?
For that purpose I added the directive .direct to the def-file handling code:
  .direct    _funcNoData
Using this the caller gets the direct pointer to the function and since the stub is now a simple jmp, only this jmp instruction remains as overhead. Sometimes it's tricky to find out if a function can be exported .direct, but it can be handled.


Exporting variables is the last issue and also there I found a working approach:
In the def-file use:
  .data _some_level
In the header file
  extern long * some_level;
It can point to any type, but it must be a pointer. And in the libraries source file:
long some_level__data;
long * some_level = &some_level_data;
In the source file you assign the pointer yourself. In the export stubs the mkstub script handles this for you and a program usging that libraries will get a pointer to some_level_data too.

Is it usable? YES!

The libcryptossh.library of the amigassh programs are made using mkstub. There also member functions of C++ classes are exported from the library. The part of the Makefile looks like
$(OUTDIR)/libcryptossh.a: $(LIB_OBJECTS) src/libcryptossh.def
	cd $(OUTDIR); \
	mkstub libcryptossh ../src/libcryptossh.def

$(OUTDIR)/libcryptossh.library: $(OUTDIR)/libcryptossh.a 
	m68k-amigaos-gcc -shared -noixemul $(LIB_OBJECTS) $(OUTDIR)/libcryptossh-support/export*.o -o $@ \
	-Wl,-ulibVersionMajor=$(MAJOR),-ulibVersionMinor=$(MINOR),-ulibName=libcryptossh \