Copyright (c) Hyperion Entertainment and contributors.

How to Build Stubs for 68k Libraries

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

Author

Original written by Roman Kargin

Introduction

The idea for this tutorial came from when I was playing with Dopus5 code and want to make a one single function from the 68k version of dopus5.library from native AmigaOS 4 code. While we have all kinds of auto generation tools to build all kinds of protos/interfaces/inlines/stubs/etc. it still wasn't clear to me how to do that what I need.

From the beginning a very helpful article was Thomas Rapp's [How to create PPC stubs for M68K libraries] where he shows how with using of fd2pragma and IDLTool to build a jpeg.l.main stub from a 68k jpeg.library, as well as helpful answers from Fredrik Wikstrom. In the end, all of it seemed important for me to note so I just put everything into this tutorial so that I hope others will find useful.

68k->ppc glue stubs

While 68k libraries is something old, and on os4 today almost all of the libraries done in native form, still from time to time some old 68k libraries happen to be necessary, have no replacements and some 68k-ppc glue stubs together with aos4 includes for it must to be done to continue use of library.

If you do native code _yourself_ which will use some old 68k library (i.e native aos4-ppc binary with avail source code which will use 68k library), there is not only 68k-ppc glue stubs can help, but you can use EmulateTags() instead and call your library functions through that function. This was will make easy for your code to call both PPC and 68k native libs with only small overhead: just few instructions. But it will reduces amount of work and will supports both PPC and 68k modules transparently and no new stub-files is need it. But, its only when you ppc-native code is _yours_, if you write it from scratch, if there is jus few functions in library and so on. Quite offten you just do not want to deal with all of this because of big fat amount of functions in a library, different ways of how them used from the code, and you just need to make 68k library works from os4 binary with no problems: then 68k-ppc stubs is only one and easy way.

For better explain of what is 68k-ppc stubs is it, you can read Migration Guide (see links), but in brief: if you have let's say 68k version of dopus5.library and want to use it from native aos4 binaries, you need to create dopus5.l.main and when code of your os4 binary will call for interface on a 68k library the system (ramlib, to be more exact) will automatically scan its library search path for the file "dopus5.l.main" and if found, will try to open it and obtain the interface. So, that l.main file is pure nonstartup code which contain necessary stubs, and you _not_ need to write it from scratch manually as for that purposes different kind of tools is present (fd2pragma, fdtrans and idltool). That library.l.main code have 2 files usually if you auto generate them: ppc->68k crosscall stubs and vectors. You of course can write them from scratch manually, but its a pain and there is already tools which do it for you.

The name of the file in general not so matter (i.e. library not necessary should be called like ".library", but it can be ".module" or whatever). Ramlib just will find the first "." in the library name, starting from the end, the first part up to and including the dot plus one more character, usually the 'l' in library), is used, another dot is appended, then the interface name is added. It then calls the exec function.

eg: If the interface name is 'main' and the library name is 'foobar', this is what it looks for;

foobar.library -> foobar.l.main foobar.dohickey -> foobar.d.main foobar.module -> foobar.m.main

Few notes:

 1. Because of the way how RamLib parse glue-stabs names, you can't call your glue stub's like for let's say "foobar.library", as "foobar.library.main". It
    is understanable from previous description, but can drive to some misleading when you run for example "snoopy" and can see how it tryes to search for

"foobar.library.main", but it tryes to search to for a file, but for "resident". File for search by ramlib are only foobar.l.main if main library called as foobar.library.

 2. You can't put your glue stub to another directories which is not PROGDIR:, PROGDIR:Libs and LIBS: , or RamLib will not find them. I.e. if you have let's 
    say directory "modules", which have some of your plugins which in reality are library, just in different directory and called different, so, then you can't

put to the same "modules" directory your glue stubs, because system will still try to find out those glue files in PROGDIR: PROGDIR:Libs or LIBS: paths.

pragmas/fd/sfd/protos

Old 68k libraries its not something which done on GCC most of time: on old 68k machines SASC was the popular one for C, and pure assembler was even more popular for every needs in compare with todays times. And so, many of those old 68k libraryes, provide for developers only includes for sasc, or only asm, or mix or so. So, when you have by hands such a library, where let's say only pragma file is present, and protos let's say done in diffeent includes like a mess: you do need to understand all of this and create your own proto files, build that stub lib.l.main, make a os4 includes from and so on.

All what we need to know now, is that libraries have a jump table. Jump table have a list of library functions by offsets. Offsets can't be messed, because by offsets in jump table your code can call necessary function from library. Be it old sasc/assembler, or new gcc on os4: if we works with 68k library and create any fd/proto files from scratch, then offsets should be always be the same.

For example you have dopus_pragmas.h file and dopus.fd file (fd files is standard way to describe the interface for libraries) and dopus_pragmas entryes looks like this:

[code]

  1. pragma libcall DOpusBase RemovedFunc0 1e 0
  2. pragma libcall DOpusBase Random 24 001
  3. pragma libcall DOpusBase Atoh 2a 0802

.... [/code]

It mean that first "user" library function in jump table is started on offset 0x1e (30), and then multiply by 6 for every function. So first function on 30(0x1e), second on 36 (0x24), next on 42 (0x2a) and so on for all the functions. That mean, that if for example function Atoh() in the jump table placed by offset 0x2a, then it mean that every single code and includes which we will generate for os4 should respect jump-table offsets.

If we will look at dopus_lib.fd file, we will see:

[code]

  • "dopus.library"
    1. base _DOpusBase
    2. bias 30
  • Support routines for Directory Opus and associated programs
  • (c) Copyright 1995 Jonathan Potter
    1. public

RemovedFunc0()() Random(limit)(d0)

Atoh(str,len)(a0,d0) .... [/code]

What mean that public functions starts from offset 30 (bias 30), and they placed in the same necessary order as they placed in the pragma file. I.e. if you for example will just put Atoh() from FD file at first place, and RemovedFunc0() at place of Atoh(), generate all your files for os4, and build native binary to use library: then instead of Atoh() you will have RemovedFunc0() and instead of RemovedFunc0() you will have Atoh(), what of course mean crashes, bugs and problems.

So, respect offsets in jump table is must.

Need to add, that also .sfd kind of files present (you will not find them usually in old 68k dev archives of libraryes as its something fresh in compare with .fd), and they hold a lot more infomation in compare with .fd and easy to operate. To see full description of .fd and .sfd formats check fd2pragma guide. But in general they more or less the same by logic, the same respect for offsets, etc.

Now how?

We have 2 ways to build lib.l.main and necessary includes. But for both of them you need .fd file as minimum (if you have .sfd its even better), and proto file which will describe all (or not all) your functions. While .fd/.sfd its something which should be done by developers initally, you still can make them from scratch just if you have pragma file with lib calls (you make that files in the same order by the same offsets). If you didn't have one proto file, but many in different includes, then you just can make your own one, just string by string grab from all the include files all the protos, while all your FD entrys will not have necessary proto descriptions in proto file (btw, if you will not have proto for some function, then entry from fd/sfd file will be reserved when you will auto generate os4 code/includes, so respect offsets).

Let's say we have dopus_lib.fd and nothing more nowhere. Only protos in different includes in diffrent places. We then string by string create proto file from all those includes (in refering to the dopus_lib.fd), and then auto-tools time coming. As i say we have 2 ways:


[b]First way: fdtrans + sfd[/b]

[b]1[/b]. generate .sfd file:

[quote] ram:> fd2pragma dopus5_lib.fd clib our_proto.h special 112 SourceFile: dopus5_lib.fd ResultFile: dopus5_lib.sfd [/quote]

[b]2.[/b] generate xml and ppc->68k crosscall stubs via fdtrans:

[quote] ram:> fdtrans dopus5_lib.sfd -x -s [/quote]

Change manually in the crosscall-stubs file (dopus5.c) "main_vectors" on "main_v1_vectors", as it seems some typo beetwen different tools. No other changes need it, fdtrans automatically insert including of interface (while fd2pragma will skip it, see later).

[b]3.[/b] generate proto/interface/inline (i.e. includes) and vectors:

[quote] ram:> idltool -p -i -n -c dopus5.xml [/quote]

[b]4.[/b]. build dopus5.l.main [quote] ram:> gcc -Iinclude -nostartfiles dopus5.c -o dopus5.l.main [/quote]

[b]5.[/b] build test case which will use one of functions from dopus5.library called StrConcat()

[code]

  1. include <proto/exec.h>
  2. include <proto/dopus5.h>
  3. include <stdio.h>
  4. include <stdlib.h>
  5. include <string.h>

struct Library *DOpusBase; struct DOpusIFace *IDOpus;

void main(int argc,char **argv) { char buffer[256] = "First part-"; char *concat = "Second part";

// Need dopus library if (!(DOpusBase=IExec->OpenLibrary("PROGDIR:dopus5.library",1L))) { printf("cant library"); exit(10); }; if (!(IDOpus=IExec->GetInterface((struct Library *)DOpusBase,"main",1L,NULL))) { printf("cant interface"); exit(10);

  };

IDOpus->StrConcat(buffer, concat, 36); printf("Result=%s\n", buffer);

IExec->CloseLibrary(DOpusBase); } [/code]

[quote] ram:> gcc -Iinclude testcase-native-fdtrans.c ram:> a.out ram:> Result=First part-Second part [/quote]

It works !

[b]Second way: fd2pragma + fd[/b]

[b]1.[/b] Create xml:

[quote] ram:> fd2pragma dopus5_lib.fd clib proto.h special 140 SourceFile: dopus5_lib.fd Resultfile: dopus5.xml [/quote]

[b]2.[/b]generate ppc->68k crosscall stubs:

[quote] ram:> fd2pragma dopus5_lib.fd clib proto.h special 141 SourceFile: dopus5_lib.fd Resultfile: dopus5.c [/quote]

In result file (dopus5.c) manually add including of interfaces/dopus5.h and replace value "main_vectors" on "main_v1_vectors"

[b]3.[/b] generate proto/interface/inline (i.e. includes) and vectors:

[quote] ram:> idltool -p -i -n -c dopus5.xml [/quote]

[b]4.[/b] build dopus5.l.main

[quote] ram:> gcc -Iinclude -nostartfiles dopus5.c -o dopus5.l.main [/quote]

[b]5.[/b] build test case (its a bit different in compare with one from fdtrans, because names of iface a bit different (big/small letters, 5 at end, etc) :

[code]

  1. include <proto/exec.h>
  2. include <proto/dopus5.h>
  3. include <stdio.h>
  4. include <stdlib.h>
  5. include <string.h>

struct Library *DOpusBase; struct Dopus5IFace *IDopus5;

void main(int argc,char **argv) { char buffer[256] = "First part-"; char *concat = "Second part";

// Need dopus library if (!(DOpusBase=IExec->OpenLibrary("PROGDIR:dopus5.library",1L))) { printf("cant library"); exit(10); }; if (!(IDopus5=IExec->GetInterface((struct Library *)DOpusBase,"main",1L,NULL))) { printf("cant interface"); exit(10);

  };

IDopus5->StrConcat(buffer, concat, 36); printf("Result=%s\n", buffer);

IExec->CloseLibrary(DOpusBase); } [/code]

Then hard-reboot is need it in case you already run previous test case on dopus5.l.main done from fd2trans and which still in memory (as it can freeze os hard because of different code of the same stub libs), and then:

[quote] ram:> gcc -Iinclude testcase-native-fd2pragma.c ram:> a.out ram:> Result=First part-Second part [/quote]


It works as well !

[b]Conclusion[/b] What way better ? Imho fdtrans one. Because:

1). you need to change less (no need to manually include interfaces/dopus5.h file). 2). the code of stubs from fdtrans looks better and cleaner in compare with code of stubs from fd2pragma. For example such a even small moment as comments saying what value in hex mean your offset in jump table is pretty nice (expectually when you works manually with pragmas, where those hex offsets is present). 3). size of dopus.l.main done from fd2pragma output bigger in compare with size of dopus.l.main done from fdtrans output.


I want one single function to work

Now something intersting : you for example want to just check if it all will works at all for you from os4 and with your new lib.l.main and native os4 includes, code and co. You then choice any "simply" function from library and want to make a simple os4 native test case for it. Let's say it will be StrConcat() function from dopus5.library, which is described in pragma file as:

[code]

  1. pragma libcall DOpusBase StrConcat 47a 09803

[/code]

and in dopus_lib.fd like:

[code] StrConcat(s1,s2,len)(a0/a1,d0) [/code]

We then simply calc:

1. 0x47a = 1146 2. 1146 / 6 = 191 3. 191 - 4 (i.e. 30/6 from begining 5, but 30 is our first one, so -4) = 187

I.e. entry in jump table for StrConcat is 187. And entry in the dopus_lib.fd file for Strconcat also 187 in the list of functions (+ header of .fd)

In other words you can build your .fd file from scratch, which will looks like this:

[code]

  • "dopus.library"
    1. base _DOpusBase
    2. bias 30
  • Support routines for Directory Opus and associated programs
  • (c) Copyright 1995 Jonathan Potter
    1. public

aa()() aa()() aa()() aa()() .... 186 entrys to fill the gap in jump table .... StrConcat(s1,s2,len)(a0/a1,d0) ....

    1. end

[/code]

You of coures can go more elegant way and use .sfd instead, which have "reserved" keyword, so no need to put all those empty-funct. I.e.:

[code] ==id $Id: dopus5_lib.sfd,v 1.0 2013/03/29 09:24:08 noname Exp $

  • "dopus5.library"

==base _DopusBase ==basetype struct Library * ==libname dopus5.library ==bias 30 ==public ==include <exec/types.h> ==reserve 186 BOOL StrConcat(char * s1, char * s2, int len) (a0,a1,d0) ==end [/code]

And when you will generate all necessary stuff via fd2grama/fdtrans/idltool from .fr or from .sfd, offsets will be respected, and when you will call from your code IDopus->StrConcat(blablab);, it will works because code will find out StrConcat in jump-table by right offset and will works.

Final words

As usuall its nothing hardcore in there, just some easy article about problems with which i meet myself. I hope through it can be any help for newbes. In end thanks to Salas00 for hints, and ..... for proofreed and grammar corrections.


Links

[1]. OS4 libraries and devices: http://wiki.amigaos.net/index.php/Libraries_and_Devices [2]. RamLib autodoc. [3]. fd2pragma guide from: http://aminet.net/dev/misc/fd2pragma.lha [4]. How to create PPC stubs for M68K libraries: http://thomas-rapp.homepage.t-online.de/ppclib.html