Jak przenieść projekt ze środowiska IAR Embedded Workbench do Atollic TrueSTUDIO – poradnik migracyjny

Przykładowe komendy przeprowadzające ten proces wyglądają następująco:

wyciąga plik modul.o z biblioteki

tworzy nową bibliotekę z wszystkimi modułami

Tworzenie interfejsu binarnego

W rzadkich przypadkach może zaistnieć konieczność stworzenia biblioteki będącej interfejsem dla biblioteki zewnętrznej. Dla bibliotek języka C proces ten jest relatywnie prosty, dla bibliotek C++ może się okazać dużo bardziej skomplikowany. W zależności od charakteru biblioteki rozsądnym może się okazać napisanie w języku C kodu „obudowującego” (wrappera) dla biblioteki C++, co pozwala zamaskować różnice między kompilatorami jeśli chodzi o ABI.

W niektórych sytuacjach może to jednak być nie do zaakceptowania. Wtedy to trzeba napisać wrappery w C++, co z kolei wymaga dobrego zrozumienia niskopoziomowej implementacji języka (jak obiekty są tworzone w pamięci, jak poprzez wirtualne tablice wskaźników do funkcji obsługiwany jest polimorfizm, jak rzucane i chwytane są wyjątki) i wykracza poza zakres niniejszego tekstu. W dalszej części tego rozdziału przedstawione są metody, którymi można się posłużyć, aby wykonać większość zadań pojawiających się w czasie migracji bibliotek w formie binarnej.

Wywoływanie i powrót z funkcji

Pierwszą rzeczą, którą trzeba zrozumieć przed rozpoczęciem tworzenia interfejsów binarnych jest mechanizm wywoływania funkcji i powrotu z nich. Chodzi tu o poznanie rejestrów używanych do przekazywania parametrów i zwracania wyników oraz zasad rządzących modyfikacjami zawartości rejestrów bez zachowywania ich dotychczasowego stanu dokonywanymi przez funkcje. Parametry wejściowe funkcji są najpierw zapisywane do rejestrów, a następnie na stosie.

 

Tab. 21.

Element Opis
Argumenty wejściowe Rejestry od r0 do r3.
Dla parametrów 64-bitowych rejestry mogą być grupowane w pary r0:r1 oraz r2:r3.
Pierwszym parametrem może być adres:
– wskaźnik this w C++
– adres dużej/złożonej wartości zwracanej
Wartość zwracana Rejestr r0 lub para r0:r1 dla wartości 64-bitowych;
r0 może zawierać adres zwracanych danych w przypadku dużych lub złożonych wartości zwracanych.
Rejestry robocze Rejestry od r0 do r3 oraz r12 mogą być modyfikowane przez wywoływaną funkcję bez zachowywania ich wartości (bezpowrotnie).
Rejestry zachowywane Zawartość rejestrów od r4 do r11 w razie modyfikacji musi być zachowywana i odtwarzana przed powrotem z funkcji.
Rejestry specjalne r13 to wskaźnik stosu;
r14 do rejestr adresu powrotu z procedury (link register);
r15 to licznik programu.

 

Jak widać, przy powrocie z funkcji, rejestry od r4 do r11 muszą zawierać te same wartości, co w momencie wywołania jej, podobnie jak wskaźnik stosu i link register. Rejestr r0 przechowuje wartość zwracaną. Rejestry od r1, r2, r3 i r12 mogą zawierać dowolne dane.

Użycie kompilatora do tworzenia interfejsu

Kompilator środowiska, z którego następuje migracja może zostać użyty do skonstruowania interfejsu wywoływania i powrotu z funkcji, który z kolei może zastosować nowy kompilator. Można to osiągnąć wykorzystując zdolność kompilatora do generowania kodu asemblera z kodu w C i C++, tworząc w starym zestawie narzędziowym działający interfejs asemblerowy, wywoływalny z C lub C++, który można następnie zintegrować z nowymi narzędziami.

WSKAZÓWKA

Należy zwrócić uwagę na to, że opisana procedura zakłada, że dwa kompilatory generują kod z niekompatybilnymi interfejsami.  Jeśli jest inaczej, należy się posłużyć metodami opisanymi we wcześniejszych rozdziałach tekstu, aby zintegrować kompatybilne biblioteki z projektem.

Dla przykładu rozważmy przypadek, w którym biblioteka zewnętrzna zawiera funkcję foo o prototypie przedstawionym poniżej.

Funkcja przyjmuje dwa argumenty, z których jeden jest wskaźnikiem i zwraca strukturę. Założeniem jest, że z jakiegoś powodu budowa struktury jest różna dla dwóch kompilatorów, co jest przykładem najgorszego możliwego scenariusza.

Po pierwsze, tworzymy w C funkcję „opakowującą” (wrapper), jako podstawę interfejsu i zapisujemy w pliku wrapper.c.

WSKAZÓWKA

Plik z kodem źródłowym należy skompilować dwa razy, raz za pomocą kompilatora środowiska IAR i raz za pomocą GNU, w obu przypadkach korzystając z możliwości generacji kodu asemblerowego bez optymalizacji przez kompilator. Wyłączenie optymalizacji jest ważne, ponieważ jeśli tego nie zrobimy, kompilator może wygenerować kod wyjątkowo trudny do zaadaptowania.

O autorze