BenBE's humble thoughts Thoughts the world doesn't need yet …

02.04.2014

Statische FreePascal-Bibliotheken mit GCC verwenden

Filed under: Software — Schlagwörter: , , , , , — BenBE @ 22:04:40

Nachdem Martok nach Erhalt des Tumbleweed-Awards bereits drohte, auf dessen goldene Ausführung zuzusteuern, und ich für eines meiner Projekte eh noch mal etwas Unterstützung brauchte, habe ich mich mal des Cases angenommen, um zu schauen, wie man Code von FreePascal statisch in sein C-Programm eingebunden bekommt. War auch – ehrlichgesagt – nicht ganz uneigennützig, da die betroffene Bibliothek durchaus für eigene Projekte bereits ins Auge gefasst wurde, nur wegen „falsche Programmiersprache“ bisher noch nicht näher betrachtet wurde.

Aber zurück zum Thema. Alles fing damit an, dass sich Martok darüber beschwerte, dass das Linken von FreePascal-Code zwar zu dynamischen Libs funktioniert, nicht aber das bauen statischer Bibliotheken. Das Linken dynamischer Bibliotheken ist dabei ein simples

fpc example.lpr

um die passende libexample.so zu erhalten, aber der Versuch eine zugehörige example.a für die Weiterverwendung zum statischen Linken zu erhalten bedarf etwas mehr Kreativität. Also schlage man die zugehörigen Manpages um eine Reihe von Optionen zu erhalten und werfe wahlos gefundene Optionen in die nächstbeste Shell. Direkt ins Auge springen hierbei:

  • -a: Behalte mal alle Assembler-Zwischenergebnisse. Man weiß ja nie, wofür man die mal brauchen könnte …
  • -al: Und wenn Du schon mal dabei bist, schreib mal paar Zeilennummern mit raus.
  • -an: Node-Information können ja nie schaden. Hau eifach mal mit rein. Egal wofür die dann gut sind!
  • -ar: Register Allocations sind immer interessant. Her damit!
  • -at: Auch die temporären wollen wir wissen.
  • -AAS: Als Ausgabe bitte irgendwas, was die binutils verstehen …
  • -Cg: Für statische Bibliotheken möchte man PIC haben. Also nehmen wir PIC.
  • -CD: Dynamisch oder statisch. Das sagt was von Bibliothek, kann also nicht falsch sein!
  • -Cn: Und der Scherz am statischen Linken ist, dass man nicht linkt. Also bitte den Linker rechts liegen lassen. Danke.
  • -g: Generier mal bissl Debugger-Infos. Vielleicht müssen wir ja nochml mit nem Debugger dran (oder auch nicht)
  • -gv: Wenn wir grad mal dabei sind, dann kann ein wenig Info für Valgrind nicht schaden
  • -s: Assemblieren wird eh überbewertet. Außerdem fällt dann wenigstens raus, was der da im Hintergrund treibt.
  • -XS: Statisches Linken … könnte funktionieren.
  • -Xt: Wil so schön war. Warum auch nicht doppelt; hält ja bekanntlich besser – von wegen -static und so 😉
  • -Rintel: Ich will keine -RATTen bei mir. Dann schon lieber Assembler, den man auch lesen kann. Hab schließlich nen Intel(R) drin.
  • -Sc: Warum nicht. Soll ja eh mal in C verwendet werden.
  • -Si: Inlining kann NIE schaden
  • -Sm: Und warum sollte ich auf C-Makros verzichten, die haben mir eh IMMER schon gefehlt!
  • -us: Gegen die US sollte man immer sein. Und mit ner System-Unit erspart man sich da paar Dinge.

Natürlich brauchen wir jetzt noch etwas Source als Grundlage. Nehmen wir also einfach mal folgende Dateien:

  • Makefile
    .PHONY: all
    all: test.a static
    
    test.a: test.o
    	ar cru test.a test.o
    
    test.s: test.lpr
    	fpc -a -al -an -ar -at -AAS -Cg -Cn -g -gv -s -XS -Xt -Rintel -Sc -Si -Sm -us test.lpr
    
    test.o: test.s
    	as --64 -o test.o test.s
    
    static: static.o
    	gcc -o static static.o test.a
    
    static.o: static.c
    	gcc -c -o static.o static.c
    
    .PHONY: clean
    clean:
    	-rm -f *.s
    	-rm -f *.o
    	-rm -f *.a
    	-rm -f *.ppu
    	-rm -f static.o
    	-rm -f static
    	-rm -f lib*.*
    	-rm -f ppas.*
    
  • test.lpr
    library test;
    
    {$mode objfpc}{$H+}
    
    uses
        ctypes;
    
    function GetAnswer: cint; stdcall;
    begin
        Result:= 42;
    end;
    
    exports
        GetAnswer;
    
    end.
    
  • static.c
    #include 
    #include 
    
    //Import from FreePascal
    extern uint32_t GetAnswer();
    
    int main( int argc, char** argv ) {
    	uint32_t answer;
    
    	answer = GetAnswer();
    	printf("%d\n", answer);
    
    	return answer;
    }
    

Erster Anlauf war natürlich auch gegen die test.lpr vom Kumpel und wie zu erwarten knallte das auch gleich:

fpc -a -al -an -ar -at -AAS -Cg -Cn -g -gv -s -XS -Xt -Rintel -Sc -Si -Sm -us test.lpr
as --64 -o test.o test.s
ar cru test.a test.o
gcc -c -o static.o static.c
gcc -o static static.o test.a
test.a(test.o): In Funktion `P$TEST_main':
/mnt/home/Projekte/tmp/fpc-static//test.lpr:16: Nicht definierter Verweis auf `FPC_LIBINITIALIZEUNITS'
test.a(test.o):(.fpc+0x0): Nicht definierter Verweis auf `FPC_LIB_EXIT'
test.a(test.o):(.data.rel+0x10): Nicht definierter Verweis auf `INIT$_SYSTEM'
test.a(test.o):(.data.rel+0x20): Nicht definierter Verweis auf `INIT$_CMEM'
test.a(test.o):(.data.rel+0x28): Nicht definierter Verweis auf `FINALIZE$_CMEM'
test.a(test.o):(.data.rel+0x38): Nicht definierter Verweis auf `FINALIZE$_OBJPAS'
test.a(test.o):(.data.rel+0x44): Nicht definierter Verweis auf `THREADVARLIST_SYSTEM'
test.a(test.o):(.data.rel+0x4c): Nicht definierter Verweis auf `THREADVARLIST_CMEM'
test.a(test.o):(.data.rel+0x54): Nicht definierter Verweis auf `THREADVARLIST_OBJPAS'
test.a(test.o):(.data.rel+0x5c): Nicht definierter Verweis auf `THREADVARLIST_UNIXTYPE'
test.a(test.o):(.data.rel+0x64): Nicht definierter Verweis auf `THREADVARLIST_CTYPES'
collect2: error: ld returned 1 exit status
make: *** [static] Fehler 1

Sieht ja schon mal nicht schlecht aus, aber warum brauch der die zusätzlichen Symbole für die Initializer und Finalizer? Und überhaupt: Warum definiert der da ne main? Das soll der gar nicht!!! Na gut, probieren wir das (neben zahlreichen anderen Dingen, die ich dem Leser an dieser Stelle einfach mal erspare, weil sie eh nicht funktionieren und nicht zielführend sind) was statische Lib-Archive im Gegensatz zu Projekt-Dateien, die fertige Programme darstellen. Was wohl passiert, wenn man ihm statt der Project-File wohl eine Pascal-Unit zum Fraß hinwirft? Naja, probieren wir mal. Ändern wir hierzu die test.lpr durch etwas Ändern von Compiler-Direktiven in eine Unit um, was dann so in etwa wie dies hier aussieht:

unit test;

{$mode objfpc}{$H+}

interface

uses
  ctypes;

function GetAnswer: cint; stdcall;

implementation

function GetAnswer: cint; stdcall;
begin
  Result:= 42;
end;

//exports
//  GetAnswer;

end.

und ersetzen im Makefile mal die Vorkommen von test.lpr durch test.pas, was uns folgendes Makefile beschert:

.PHONY: all
all: test.a static

test.a: test.o
	ar cru test.a test.o

test.s: test.pas
	fpc -a -al -an -ar -at -AAS -Cg -Cn -g -gv -s -XS -Xt -Rintel -Sc -Si -Sm -us test.pas

test.o: test.s
	as --64 -o test.o test.s

static: static.o
	gcc -o static static.o test.a

static.o: static.c
	gcc -c -o static.o static.c

.PHONY: clean
clean:
	-rm -f *.s
	-rm -f *.o
	-rm -f *.a
	-rm -f *.ppu
	-rm -f static.o
	-rm -f static
	-rm -f lib*.*
	-rm -f ppas.*

Und wenn man dann kurz neu baut, fällt einem ein kleines Binary entgegen:

fpc -a -al -an -ar -at -AAS -Cg -Cn -g -gv -s -XS -Xt -Rintel -Sc -Si -Sm -us test.pas
Free Pascal Compiler version 2.6.2-5 [2013/07/25] for x86_64
Copyright (c) 1993-2012 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling test.pas
Closing script ppas.sh
23 lines compiled, 0.0 sec 
as --64 -o test.o test.s
ar cru test.a test.o
gcc -c -o static.o static.c
gcc -o static static.o test.a

Bliebe nur noch die Antwort auf die entscheidende Frage abzuwarten:

$ ./static
42

Geht doch!

Na gut. Windows ist mal wieder die Ausnahme, weshalb Martok auch noch daruaf hinwies, dass man im Pascal-Teil vermutlich eher folgendes Nutzen möchte:

const
{$IFDEF WIN32}
  prefix = '_';
{$ELSE}
  prefix = '';
{$ENDIF}

function GetAnswer: cint; cdecl; public name prefix+'GetAnswer';

Problem gelöst, und jetzt wieder zurück ans Debugging 😉

Flattr this!

Ein Kommentar »

  1. Die Sache wird noch etwas komplizierter, wenn man nicht nur einen weitestgehenden Dummy-Code linken möchte, sondern auch die RTL verwendet. Dann muss man sich nämlich die passenden Objekte zusammensuchen, um alle Abhängigkeiten zu erfüllen – und an der Stelle wird das Makefile dann sehr schnell sehr hässlich.

    Einige globale Konstanten werden nämlich erst von FPC überhaupt generiert, wenn man nicht nur ein Objekt, sondern ein echtes Ziel erstellt (also eine Executable oder Library). Funktionierender Hack: eine solche zur Library bauen und aus deren Object-File die Konstanten entnehmen.

    Eine Implementation zum Bestaunen findet sich in diesem Github-Repo.

    An der Stelle wird’s dann leider auch plattformabhängig, so dass man eigentlich das Makefile noch mit diversen Schaltern versehen muss. Das überlasse ich für heute aber dem geneigten Leser als Hausaufgabe 😉

    Kommentar by Martok — 02.04.2014 @ 23:54:21

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress