fdCompile BackGround on AmigaDOS Shareable Libraries 'fd' files are the files that Commodore and others supply which describe routines in AmigaDOS shareable libraries and how to call them. As an example, here is the 'fd' file for the 'translator.library': ##base _TranslatorBase ##bias 30 ##public Translate(inputString,inputLength,outputBuffer,bufferSize)(A0,D0/A1,D1) ##end The 'translator' library has only one routine, 'Translate', in it. This file says that: - interface stubs should use the external symbol '_TranslatorBase' to save the address of the library when it is loaded - the first jump vector of the library starts at offset 30 in the negative part of the library structure (thus at offset -30 from the pointer stored in '_TranslatorBase') - routine 'Translate' has 4 parameters, which should be passed in registers A0, D0, A1 and D1. The symbolic names given are not important, but do provide a human reader with some help in knowing which is which. Thus, to use the 'Translate' routine (which translates ASCII strings of English text into phoneme sequences which the narrator.device can pronounce), a programmer must do the following: 1) call the Exec routine 'OpenLibrary', passing it the name of the library ("translator.library"), along with a version number (often just 0). Exec will check to see if the library is already loaded. If it isn't, it will try to LoadSeg from file "LIBS:translator.library". After the load, Exec will call the library's initialization routine which will set up a Library structure which Exec can add to its list of loaded libraries. The 'OpenLibrary' call will return the address of the Library structure. Note that this pointer is actually into the middle of the structure - at negative offsets are 6-byte entries, one for each routine in the library, which are typically an absolute JMP to the loaded routine. 2) Store the returned pointer into "_TranslatorBase" then, on each call to 'Translate': 3) Set up the parameter registers: A0 - address of ASCII string D0 - length of ASCII string A1 - address of buffer for phoneme sequence D1 - length of buffer for phoneme sequence 4) Load the Library address (from "_TransalatorBase") into A6 5) JSR -30(A6) This is all well and good for an assembler programmer, but what about those using high-level languages? The simple approach is to write an interface routine, which takes parameters from where-ever the high- level language puts them on a call (typically the stack), moves them to the required register, loads the library base into A6, and does a branch to the proper offset from that base. The latest Lattice C compiler is able to generate the correct code directly, based on special directives given to the compiler. Ignoring that technique, a C programer does the following to call the routine: 1) call 'OpenLibrary' (which is itself a stub, since it lives in exec.library) with the proper parameters 2) store the resulting library address in the extern symbol "TranslatorBase" (C usually adds the leading '_'). NOTE: I am ignoring the case of the 'OpenLibrary' call failing. then, on each call to 'Translate': 3) call 'Translate' with the appropriate parameters then, when done with the library: 4) call 'CloseLibrary(TranslatorBase)' when done with the library. Step 3 will actually call the stub routine '_Translate' which copies the parameters from the stack into the proper registers, loads the Library pointer from '_TranslatorBase', and does the JMP through it. Since the compiler uses register A6 internally, the stub routine should save and restore it. In Draco, I did things a little bit differently: - the compiler doesn't stick on a '_', so the stub routine names don't have one (the base name does, for consistency if someone links C and Draco stuff together) - the calling convention requires the subroutine to remove the parameters from the stack, so the stubs must do that - I kept the library base private to the stub routines, so for each library there is an e.g. 'OpenTranslatorLibrary' and an e.g. 'CloseTranslatorLibrary'. The first is given only the version number and the second has no argument. The first returns the Library address in case the program needs it for some other reason. Thus, the Draco sequence is: 1) call 'OpenTranslatorLibrary' with the version number ... 2) call 'Translate' as needed ... 3) call 'CloseTranslatorLibrary' There are lots of libraries on the Amiga, and more keep coming, since programmers can write more. There are LOTS of routines. Being lazy and a compiler-writer to boot, I couldn't stand the idea of writing all of those interface stubs in assembler. Thus, when I first ported Draco to the Amiga, I wrote a program (then called 'convfd') which read in an 'fd' file, parsed it, and produced an object file containing all of the needed interface stubs. fdCompile is the direct descendant of that program, which was the first serious one I compiled with the Amiga Draco compiler. fdCompile has grown a lot since then. With this as introduction, here follows a description of fdCompile. fdCompile The usage line for fdCompile says: Use is: fdCompile -lLosSd fdfile ... fdfile This means that it takes one or more flags and one or more filenames, which are expected to name 'fd' files. Each such file is processed independently. In the following discussion, assume that fdCompile was invoked with "fdcompile -lLosSd test_lib.fd". Also, assume that file 'test_lib.fd' contains: ##base _TestBase ##bias 30 ##public DoThing(p)(a0) DoOtherThing(p,inputPtr,inputLen,output)(a0,a1,d0,d1) ##end The flags, in detail, are: -l - produce an interface stub file for Draco. This is an unlinked AmigaDOS object file which contains interface stubs which let a Draco program open the library and call its routines. In our example usage, the created file would be called 'test.o' (fdCompile looks for the '_lib.fd' in the given file names.) It would contain the equivalent of the following assembler code: _TestBase ds.b 4 OpenTestLibrary movea.l (sp)+,a1 move.l (sp)+,d0 move.l a1,-(sp) lea L0020,a1 move.l a6,-(sp) movea.l 0x00000004,a6 jsr -552(a6) movea.l (sp)+,a6 move.l d0,_TestBase rts L0020 dc.b 'test.library' dc.b 0 CloseTestLibrary movea.l _TestBase,a1 move.l a6,-(sp) movea.l 0x00000004,a6 jsr -414(a6) movea.l (sp)+,a6 rts DoThing move.l a6,-(sp) movea.l 8(sp),a0 movea.l _TestBase,a6 jsr -30(a6) movea.l (sp)+,a6 movea.l (sp)+,a1 addq.l #4,sp jmp (a1) DoOtherThing move.l a6,-(sp) movea.l 20(sp),a0 movea.l 16(sp),a1 move.l 12(sp),d0 move.l 8(sp),d1 movea.l _TestBase,a6 jsr -36(a6) movea.l (sp)+,a6 movea.l (sp)+,a1 adda.w #16,sp jmp (a1) 'OpenTestLibrary' calls 'OpenLibrary' in Exec, passing the string "test.library" and the version number given by the user. The returned library pointer is stored into '_TestBase' and returned. 'CloseTestLibrary' calls 'CloseLibrary' in Exec, passing the contents of '_TestBase'. Both of the calls to Exec are done directly, using the pointer to the Exec library which the system stores in location 4. The two interface stubs do the required copying of parameters and saving/restoring of registers. This was the original purpose behind fdCompile, but is of use only for people who use Draco. Changing fdCompile to work with some other compiler is not hard, so those who want to do that can contact me and I'll send them the source to fdCompile. If I get enough requests, I can send it to 'comp.source.amiga' and/or CompuServe, but I don't think that will be needed. -L - print the library entries and their offsets from the library base pointer. This is of marginal use, but does supply the proper offsets to those who want to do it by hand. The output for our example library is: DoThing @ -30 DoOtherThing @ -36 -o - produce a file which defines 'LVO_' symbols for the library routine offsets from the library pointer. 'LVO' stands for Library Vector Offset (or some such!). The file produced has an unnamed HUNK_UNIT followed by a HUNK_EXT which contains EXT_ABS definitions for the symbol offsets. In our example case, the file would be called 'LVO_test.o' and, dumped, contains: hunk_ext: LVO_DoThing: ext_abs: ffffffe2 LVO_DoOtherThing: ext_abs: ffffffdc This file can be linked in with the other object files of a program in order to define, as external symbols, the offsets, which are needed in order to call the Exec routine 'SetFunction'. -s - produce a file which has definitions for the loaded, absolute addresses of the routines in the library. This is only useful with libraries that are always at fixed addresses, i.e. are in the ROM, or with some types of debugging. The addresses are found by actually opening the library and peeking at the real vector. Again, the file, in our example, would be called 'test_sym.o' and would contain: hunk_ext: #DoThing: ext_abs: 00296c20 #DoOtherThing: ext_abs: 00296d00 The actual addresses would of course be different. Note that the symbols defined all have a '#' on the front. This is to distinguish them from the interface stub routines. My intended use for this type of file is to read them into a debugger I am writing, so that it can do at least partially symbolic disassembly of the ROM code. -S - this is related - it simple prints the information that '-s' puts into the file. Output for our example: Compile @ 00296c20 Match @ 00296d00 -d - this flag produces a disk-resident-library header file. The AmigaDOS shareable libraries that live in LIBS: are said to be disk-resident since they are not in the ROMs and therefore must be loaded from disk when they are needed. Such files have a very special format, which allows AmigaDOS and Exec to load them and add them to the system's list of loaded libraries. I plan on writing a lot of such libraries, so I wanted an automatic way of producing them. I won't describe in detail what is required, but instead refer the interested reader to the appropriate sections in the ROM Kernel Manual. For our example, we would get a file called 'test_head.o', which contains (in case you are wondering, all of my pretty disassemblies and object file dumps are done by my snazzy new disassembler/dumper, 'Dis', which I should be sending out a couple of days after this goes out): L0000 dc.w 0x4afc ; RTC_MATCHWORD dc.l L0000 dc.l L001a dc.b 128 dc.b 1 dc.b 9 dc.b 0 dc.l L00d8 dc.l L00e5 dc.l L001c L001a dc.b 0,0 L001c dc.l 38 ; positive library size dc.l L00bc dc.l L002c dc.l L0048 L002c dc.b 160,8,9,0,128,10 dc.l L00d8 dc.b 160,14,6,0,144,20,0,1,128,24 dc.l L00e5 dc.b 0,0,0,0 L0048 move.l a1,-(sp) movea.l d0,a1 move.l a0,34(a1) movea.l (sp)+,a1 rts L0054 addq.w #1,32(a6) bclr.b #0x0003,14(a6) move.l a6,d0 rts L0062 clr.l d0 subq.w #1,32(a6) bne L0074 btst.b #0x0003,14(a6) beq L0074 bsr L0076 L0074 rts L0076 movem.l ,-(sp) clr.l d0 movea.l a6,a5 tst.w 32(a5) beq L008c bset.b #0x0003,14(a5) bra L00b2 L008c movea.l a5,a1 movea.l 0x00000004,a6 jsr -252(a6) movea.l a5,a1 move.w 16(a5),d0 suba.w d0,a1 add.w 18(a5),d0 movea.l 0x00000004,a6 jsr -210(a6) move.l 34(a5),d0 L00b2 movem.l (sp)+, rts L00b8 clr.l d0 rts L00bc dc.l L0054 dc.l L0062 dc.l L0076 dc.l L00b8 dc.l L0000 dc.l L0000 dc.l -1 L00d8 dc.b 'test.library' dc.b 0 L00e5 dc.b 'test 1.0 ( 3 Aug 1989)' dc.b 13,10,0,0,0 This code includes the library header tables and data, along with the required open, close, expunge and null routines. To use this file to create a complete disk-resident library, it must be linked with object files containing the actual library routines. Any compiler-specific startup code should NOT be included. Anyone intending to write such a library should be sure to understand their nature fully - there are many restrictions on what features of the language and its standard libraries that can be used. Note that, in order to be compatible with the interface stubs generated by the '-l' option, the actual library routines must take their parameters from the registers specified in the 'fd' file. Also, since the interface stubs save and restore register A6, specifying no parameters in the 'fd' file will NOT let you just pass the required parameters on the stack. To be compatible with the standard set by the existing shareable libraries, all parameters should be passed in registers. Writing the library routines in a high-level language can be done if that language provides facilities to get at the registers on function entry. In Draco, this is done with the 'code' construct. Lattice C can, I believe, do it with the proper pragmas and declarations. Aztec C can do it with #asm statements. Accompanying fdCompile in this package is an implementation of the AmigaDOS pattern matcher as a general-purpose shareable library. Although it is written in Draco, since it uses the standard register interface, it can be called from any language. It serves mostly as an example of a complete library. The header of the actual library and the Draco interface stubs, were produced by fdCompile from the included 'fd' file. AmigaDOS devices are a lot like AmigaDOS libraries - they share the same header structure and can be loaded from DEVS: just like libraries can be loaded from LIBS:. Some devices, such as 'timer.device' and 'console.device' contain useful routines, just like libraries do. fdCompile can handle devices as well as libraries with the '-l' flag. If the specified offset is 42 instead of 30, it will assume it is dealing with a device. It will then produce a 'SetXXXDevice' routine instead of the 'OpenXXXLibrary' and 'CloseXXXLibrary' routines. In order to use the routines in the device, the programmer must pass the address of the device structure (returned by 'OpenDevice') to the 'SetXXXDevice' routine, so that it is available to the stubs for the device's routines. Again, consult the RKM for details. I haven't added the ability to deal with devices to the '-s' or '-d' code, but if someone needs it, let me know.