Moim celem jest możliwość programowania dla wbudowanego systemu Linux. Mam doświadczenie w systemach wbudowanych typu bare-metal korzystających z ARM.
Mam kilka ogólnych pytań na temat programowania dla różnych celów procesora. Moje pytania są następujące:
Jeśli mam aplikację skompilowaną do działania na systemie docelowym x86, linux OS w wersji xyz , czy mogę po prostu uruchomić ten sam skompilowany plik binarny w innym systemie „ docelowym ARM, linux OS w wersji xyz ”?
Jeśli powyższe nie jest prawdą, jedynym sposobem jest uzyskanie kodu źródłowego aplikacji do przebudowania / ponownej kompilacji za pomocą odpowiedniego zestawu narzędzi „na przykład arm-linux-gnueabi”?
Podobnie, jeśli mam ładowalny moduł jądra (sterownik urządzenia), który działa na systemie docelowym x86, Linux x OS w wersji xyz , czy mogę po prostu załadować / użyć tego samego skompilowanego pliku .ko w innym systemie docelowym ARM, Linux OS w wersji xyz ? ?
Jeśli powyższe nie jest prawdą, jedynym sposobem jest uzyskanie kodu źródłowego sterownika w celu przebudowania / ponownej kompilacji za pomocą odpowiedniego zestawu narzędzi „na przykład arm-linux-gnueabi”?
Odpowiedzi:
Nie. Pliki binarne muszą być (ponownie) skompilowane dla architektury docelowej, a Linux nie oferuje niczego takiego jak duże pliki binarne po wyjęciu z pudełka. Powodem jest to, że kod jest kompilowany do kodu maszynowego dla konkretnej architektury, a kod maszynowy jest bardzo różny w większości rodzin procesorów (na przykład ARM i x86 są bardzo różne).
EDYCJA: warto zauważyć, że niektóre architektury oferują poziomy kompatybilności wstecznej (a jeszcze rzadziej kompatybilności z innymi architekturami); w 64-bitowych procesorach powszechna jest kompatybilność wsteczna z wersjami 32-bitowymi (ale pamiętaj: biblioteki zależne również muszą być 32-bitowe, w tym biblioteka standardowa C, chyba że łączysz się statycznie ). Warto również wspomnieć o Itanium , gdzie można było uruchomić kod x86 (tylko 32-bit), choć bardzo wolno; niska prędkość wykonania kodu x86 była przynajmniej jednym z powodów, dla których nie odniósł on zbyt dużego sukcesu na rynku.
Pamiętaj, że nadal nie możesz używać plików binarnych skompilowanych z nowszymi instrukcjami na starszych procesorach, nawet w trybach zgodności (na przykład nie możesz używać AVX w 32-bitowym pliku binarnym na procesorach Nehalem x86 ; procesor po prostu go nie obsługuje.
Zauważ, że moduły jądra muszą zostać skompilowane dla odpowiedniej architektury; ponadto 32-bitowe moduły jądra nie będą działać na 64-bitowych jądrach i odwrotnie.
Aby uzyskać informacje na temat plików binarnych kompilujących się między sobą (abyś nie musiał mieć łańcucha narzędzi na docelowym urządzeniu ARM), zobacz wyczerpującą odpowiedź grochmal poniżej.
źródło
Elizabeth Myers ma rację, każda architektura wymaga skompilowanego pliku binarnego dla danej architektury. Aby zbudować pliki binarne dla innej architektury niż system działa, potrzebujesz
cross-compiler
.W większości przypadków musisz skompilować kompilator krzyżowy. Mam tylko doświadczenie z
gcc
(ale wierzę, żellvm
i inne kompilatory mają podobne parametry).gcc
Cross-kompilator uzyskuje się przez dodanie--target
do skonfigurowania:Trzeba kompilować
gcc
,glibc
abinutils
z tych parametrów (i zapewniają nagłówki jądra jądra na komputerze docelowym).W praktyce jest to znacznie bardziej skomplikowane i różne błędy kompilacji pojawiają się w różnych systemach.
Istnieje kilka przewodników na temat kompilowania zestawu narzędzi GNU, ale zalecę Linux From Scratch , który jest stale utrzymywany i wykonuje bardzo dobrą robotę wyjaśniając, co robią przedstawione polecenia.
Inną opcją jest kompilacja rozruchowa kompilatora krzyżowego. Dzięki walce o kompilację kompilatorów krzyżowych do różnych architektur na różnych architekturach
crosstool-ng
. Daje bootstrap nad łańcuchem narzędzi potrzebnym do zbudowania kompilatora krzyżowego.crosstool-ng
obsługuje kilka docelowych trojetów na różnych architekturach, w zasadzie jest to bootstrap, w którym ludzie poświęcają swój czas na rozwiązywanie problemów pojawiających się podczas kompilacji łańcucha narzędzi międzykompilatorowych.Kilka dystrybucji udostępnia kompilatory krzyżowe jako pakiety:
arch
dostarcza Mingw przekrój kompilator i i ramię EABI przekrój kompilator z pudełka. Oprócz innych kompilatorów krzyżowych w AUR.fedora
zawiera kilka spakowanych kompilatorów krzyżowych .ubuntu
zapewnia również kompilator krzyżowy ramienia .debian
ma całe repozytorium krzyżowych łańcuchów narzędziInnymi słowy, sprawdź, co Twoja dystrybucja jest dostępna pod względem kompilatorów krzyżowych. Jeśli twoja dystrybucja nie ma kompilatora krzyżowego dla twoich potrzeb, zawsze możesz go skompilować samodzielnie.
Bibliografia:
Uwaga dotycząca modułów jądra
Jeśli kompilujesz cross-kompilator ręcznie, masz wszystko, czego potrzebujesz do kompilacji modułów jądra. Wynika to z faktu, że do skompilowania potrzebujesz nagłówków jądra
glibc
.Ale jeśli używasz kompilatora krzyżowego dostarczonego przez twoją dystrybucję, będziesz potrzebować nagłówków jądra jądra, które działa na maszynie docelowej.
źródło
crosstool-ng
. Możesz dodać to do listy. Również ręczne konfigurowanie i kompilowanie krzyżowego łańcucha narzędzi GNU dla dowolnej architektury jest niezwykle zaangażowane i znacznie bardziej nużące niż same--target
flagi. Podejrzewam, że właśnie dlatego LLVM zyskuje na popularności; Jest zaprojektowany w taki sposób, że nie potrzebujesz przebudowy, aby celować w inną architekturę - zamiast tego możesz celować w wiele backendów przy użyciu tych samych bibliotek frontendu i bibliotek optymalizacyjnych.Należy zauważyć, że w ostateczności (czyli gdy nie masz kodu źródłowego), można uruchomić pliki binarne na innej architekturze użyciu emulatorów jak
qemu
,dosbox
lubexagear
. Niektóre emulatory są przeznaczone do emulacji systemów innych niż Linux (np. Są przeznaczone dodosbox
uruchamiania programów MS-DOS, a istnieje wiele emulatorów dla popularnych konsol do gier). Emulacja ma znaczny narzut wydajnościowy: emulowane programy działają 2-10 razy wolniej niż ich natywne odpowiedniki.Jeśli musisz uruchomić moduły jądra na nienatywnym procesorze, będziesz musiał emulować cały system operacyjny, w tym jądro dla tej samej architektury. AFAIK nie można uruchomić obcego kodu w jądrze Linux.
źródło
Pliki binarne nie tylko nie są przenośne między x86 a ARM, istnieją również różne warianty ARM .
W praktyce możesz napotkać ARMv6 vs ARMv7. Raspberry Pi 1 to ARMv6, późniejsze wersje to ARMv7. Możliwe jest więc skompilowanie kodu na późniejszych, które nie działają na Pi 1.
Na szczęście jedną z zalet otwartego oprogramowania i wolnego oprogramowania jest posiadanie tego źródła, dzięki czemu można go odbudować na dowolnej architekturze. Chociaż może to wymagać trochę pracy.
(Wersje ARM są mylące, ale jeśli przed liczbą jest V, to mówi o architekturze zestawu instrukcji (ISA). Jeśli nie, to jest to numer modelu taki jak „Cortex M0” lub „ARM926EJS”. Numery modeli nie mają nic do zrobić z numerami ISA.)
źródło
Zawsze musisz celować w platformę. W najprostszym przypadku docelowy procesor bezpośrednio uruchamia kod skompilowany w pliku binarnym (odpowiada to w przybliżeniu wykonywalnym plikom COM systemu MS DOS). Rozważmy dwie różne platformy, które właśnie wymyśliłem - Armistice i Intellio. W obu przypadkach będziemy mieli prosty program hello world, który wyświetla 42 na ekranie. Zakładam również, że używasz języka wieloplatformowego w sposób niezależny od platformy, więc kod źródłowy jest taki sam dla obu:
W Armistice masz prosty sterownik urządzenia, który zajmuje się drukowaniem liczb, więc wszystko, co musisz zrobić, to wyprowadzić dane do portu. W naszym przenośnym asemblerze odpowiada to mniej więcej tak:
Jednak system Intellio nie ma czegoś takiego, więc musi przejść przez inne warstwy:
Ups, mamy już znaczącą różnicę między nimi, zanim jeszcze przejdziemy do kodu maszynowego! Odpowiadałoby to w przybliżeniu różnicy między Linuksem a MS DOS lub komputerem IBM i X-Boxem (nawet jeśli oba mogą korzystać z tego samego procesora).
Ale po to są systemy operacyjne. Załóżmy, że mamy warstwę HAL, która zapewnia, że wszystkie różne konfiguracje sprzętu są obsługiwane w ten sam sposób na warstwie aplikacji - w zasadzie użyjemy podejścia Intellio nawet w zawieszeniu broni, a nasz kod „przenośnego zestawu” kończy się tak samo. Jest to wykorzystywane zarówno przez nowoczesne systemy uniksopodobne, jak i Windows, często nawet w scenariuszach osadzonych. Dobrze - teraz możemy mieć ten sam, naprawdę przenośny kod asemblacyjny zarówno w zawieszeniu broni, jak i Intellio. Ale co z plikami binarnymi?
Jak zakładaliśmy, procesor musi bezpośrednio wykonać plik binarny. Spójrzmy na pierwszą linię naszego kodu
mov a, 10h
, na Intellio:O. Okazuje się, że
mov a, constant
jest tak popularny, że ma własną instrukcję, własny kod operacji. Jak radzi sobie z tym zawieszenie broni?Hmm Istnieje kod operacji
mov.reg.imm
, więc potrzebujemy innego argumentu, aby wybrać rejestr, do którego przypisujemy. Stała jest zawsze 2-bajtowym słowem, w zapisie big-endian - tak właśnie zaprojektowano zawieszenie broni, w rzeczywistości wszystkie instrukcje w zawieszeniu mają długość 4 bajtów, bez wyjątków.Teraz wyobraź sobie uruchamianie pliku binarnego z Intellio na Armistice: CPU rozpoczyna dekodowanie instrukcji, znajduje opcode
20h
. W przypadku zawieszenia broni odpowiada to, powiedzmy,and.imm.reg
instrukcji. Próbuje odczytać 2-bajtową stałą słowa (która odczytuje10XX
, już problem), a następnie numer rejestru (innyXX
). Wykonujemy niewłaściwą instrukcję z niewłaściwymi argumentami. Co gorsza, następna instrukcja będzie kompletnie nieprawdziwa, ponieważ w rzeczywistości zjedliśmy inną instrukcję, myśląc, że to dane.Aplikacja nie ma szans na działanie i najprawdopodobniej zawiesi się lub zawiesi niemal natychmiast.
To nie znaczy, że plik wykonywalny zawsze musi powiedzieć, że działa na Intellio lub zawieszeniu broni. Musisz tylko zdefiniować platformę niezależną od procesora (jak
bash
w systemie Unix) lub zarówno procesora, jak i systemu operacyjnego (np. Java lub .NET, a obecnie nawet JavaScript). W takim przypadku aplikacja może korzystać z jednego pliku wykonywalnego dla wszystkich różnych procesorów i systemów operacyjnych, podczas gdy w systemie docelowym jest aplikacja lub usługa (która bezpośrednio celuje na właściwy procesor i / lub system operacyjny), która tłumaczy kod niezależny od platformy na coś Procesor może się faktycznie wykonać. To może, ale nie musi, mieć wpływ na wydajność, koszty lub możliwości.Procesory zwykle pochodzą z rodzin. Na przykład wszystkie procesory z rodziny x86 mają wspólny zestaw instrukcji, które są kodowane dokładnie w ten sam sposób, więc każdy procesor x86 może uruchomić każdy program x86, o ile nie próbuje używać żadnych rozszerzeń (na przykład operacje zmiennoprzecinkowe lub operacje wektorowe). Oczywiście na x86 najczęstszymi przykładami są Intel i AMD. Atmel jest znaną firmą projektującą procesory z rodziny ARM, dość popularną na urządzeniach wbudowanych. Apple ma również na przykład własne procesory ARM.
Ale ARM jest całkowicie niezgodny z x86 - mają one bardzo różne wymagania projektowe i mają bardzo mało wspólnego. Instrukcje mają zupełnie inne kody, są dekodowane w inny sposób, adresy pamięci są traktowane inaczej ... Może być możliwe utworzenie pliku binarnego działającego zarówno na procesorze x86, jak i na procesorze ARM, przy użyciu pewnych bezpiecznych operacji rozróżnij te dwa elementy i przejdź do dwóch zupełnie różnych zestawów instrukcji, ale nadal oznacza to, że masz osobne instrukcje dla obu wersji, a tylko bootstrapper, który wybiera właściwy zestaw w czasie wykonywania.
źródło
Można ponownie rzucić to pytanie w środowisko, które może być bardziej znane. Przez analogię:
„Mam program Ruby, który chcę uruchomić, ale moja platforma ma tylko interpreter Pythona. Czy mogę użyć interpretera Python do uruchomienia mojego programu Ruby, czy też muszę przepisać mój program w Pythonie?”
Architektura zestawu instrukcji („cel”) to język - „język maszynowy” - a różne procesory implementują różne języki. Zatem poproszenie procesora ARM o uruchomienie pliku binarnego Intela jest bardzo podobne do próby uruchomienia programu Ruby przy użyciu interpretera Pythona.
źródło
gcc używa terminów „architektura” w znaczeniu „zestawu instrukcji” konkretnego procesora, a „cel” obejmuje kombinację procesora i architektury, a także inne zmienne, takie jak ABI, libc, endian-ness i więcej (ewentualnie włączając „goły metal”). Typowy kompilator ma ograniczony zestaw kombinacji docelowych (prawdopodobnie jeden ABI, jedna rodzina procesorów, ale prawdopodobnie zarówno 32-, jak i 64-bitowy). Kompilator krzyżowy zwykle oznacza albo kompilator z celem innym niż system, na którym działa, albo taki z wieloma celami lub ABI (patrz także to ).
Ogólnie nie. Plik binarny w tradycyjnym ujęciu jest rodzimym kodem obiektowym dla konkretnego procesora lub rodziny procesorów. Ale istnieje kilka przypadków, w których mogą one być średnio do bardzo przenośnych:
-march=core2
)Jeśli uda ci się jakoś rozwiązać ten problem, pojawi się inny przenośny problem binarny niezliczonych wersji bibliotek (glibc patrzę na ciebie). (Większość systemów wbudowanych oszczędza przynajmniej od tego konkretnego problemu).
Jeśli jeszcze tego nie zrobiłeś, teraz jest dobry moment, aby biegać
gcc -dumpspecs
igcc --target-help
zobaczyć, co masz przeciwko.Pliki binarne tłuszczu mają różne wady , ale wciąż mają potencjalne zastosowania ( EFI ).
Istnieją dwie dalsze kwestie, których brakuje w pozostałych odpowiedziach: ELF i interpreter ELF oraz obsługa jądra Linux dla dowolnych formatów binarnych . Nie będę tutaj wchodził w szczegóły na temat plików binarnych lub kodu bajtowego dla nierealnych procesorów, chociaż można traktować je jako „natywne” i wykonywać pliki binarne kodu bajtowego Pythona w Javie lub skompilowanym , takie pliki binarne są niezależne od architektury sprzętowej (ale zależą w odpowiedniej wersji maszyny wirtualnej, która ostatecznie uruchamia natywny plik binarny).
Każdy współczesny system Linux będzie używał plików binarnych ELF (szczegóły techniczne w tym pliku PDF ), w przypadku dynamicznych plików binarnych ELF jądro jest odpowiedzialne za ładowanie obrazu do pamięci, ale jest to zadanie „interpretera” ustawionego w ELF nagłówki do ciężkiego podnoszenia. Zwykle wymaga to upewnienia się, że wszystkie zależne biblioteki dynamiczne są dostępne (za pomocą sekcji „Dynamiczna”, która zawiera listę bibliotek i niektórych innych struktur, które zawierają wymagane symbole) - ale jest to warstwa pośrednia o prawie ogólnym przeznaczeniu.
(
/lib/ld-linux.so.2
jest również plikiem binarnym ELF, nie ma interpretera i jest rodzimym kodem binarnym).Problem z ELF polega na tym, że nagłówek w pliku binarnym (
readelf -h /bin/ls
) oznacza go dla konkretnej architektury, klasy (32- lub 64-bitowej), endianowości i ABI („uniwersalne” binarne pliki tłuszczu Apple używają alternatywnego formatu binarnego Mach-O zamiast tego, który rozwiązuje ten problem, pochodzi z NextSTEP). Oznacza to, że plik wykonywalny ELF musi pasować do systemu, w którym ma być uruchomiony. Jednym z nich jest interpreter, może to być dowolny plik wykonywalny (w tym taki, który wyodrębnia lub odwzorowuje specyficzne dla architektury podsekcje oryginalnie binarnego i je wywołuje), ale nadal jesteś ograniczony typami ELF, które twój system pozwoli na uruchomienie . (FreeBSD ma interesujący sposób obsługi plików ELF systemu Linux ,brandelf
modyfikuje pole ELF ABI.)Istnieje (przy użyciu
binfmt_misc
) wsparcie dla Mach-O na Linuksie , jest tam przykład, który pokazuje, jak utworzyć i uruchomić gruby (32- i 64-bitowy) plik binarny. Widelce zasobów / ADS , jak pierwotnie zrobiono na Macu, mogą być obejściem, ale żaden natywny system plików Linux nie obsługuje tego.Mniej więcej to samo dotyczy modułów jądra,
.ko
pliki są również ELF (chociaż nie mają ustawionego interpretera). W tym przypadku jest dodatkowa warstwa, która używa wersji jądra (uname -r
) na ścieżce wyszukiwania, co można teoretycznie zrobić zamiast tego w ELF z wersjonowaniem, ale podejrzewam, że przy pewnym stopniu złożoności i niewielkim zysku.Jak wspomniano w innym miejscu, Linux nie obsługuje natywnie plików binarnych typu fat, ale istnieje aktywny projekt binarny typu fat-fat: FatELF . Istnieje od lat , nigdy nie był zintegrowany ze standardowym jądrem, częściowo z powodu (wygasłych) obaw patentowych. W tej chwili wymaga to zarówno obsługi jądra, jak i zestawu narzędzi. Nie stosuje tego
binfmt_misc
podejścia, ponieważ omija problemy z nagłówkiem ELF i pozwala również na moduły grubego jądra.Nie z ELF, nie pozwoli ci to zrobić.
Prostą odpowiedzią jest tak. (Skomplikowane odpowiedzi obejmują emulację, reprezentacje pośrednie, translatory i JIT; z wyjątkiem przypadku „obniżenia” wersji binarnej i686 do używania tylko kodów i386, prawdopodobnie nie są one tutaj interesujące, a poprawki ABI są potencjalnie tak trudne jak tłumaczenie kodu natywnego. )
Nie, ELF nie pozwoli ci tego zrobić.
Prostą odpowiedzią jest tak. Wierzę, że FatELF pozwala ci zbudować
.ko
wielowątkową architekturę, ale w pewnym momencie trzeba utworzyć wersję binarną dla każdej obsługiwanej architektury. Rzeczy wymagające modułów jądra często pochodzą ze źródła i są budowane zgodnie z wymaganiami, np. Robi to VirtualBox.To już długa, kłótliwa odpowiedź, jest tylko jeden objazd. Jądro ma już wbudowaną maszynę wirtualną, choć dedykowaną: maszynę wirtualną BPF, która służy do dopasowywania pakietów. Czytelny dla człowieka filtr „host foo, a nie port 22”) jest kompilowany do kodu bajtowego i filtr pakietów jądra go wykonuje . Nowy eBPF jest nie tylko dla pakietów, teoretycznie, że kod VM jest przenośny w dowolnym współczesnym Linuksie i lvvm obsługuje go, ale ze względów bezpieczeństwa prawdopodobnie nie będzie on odpowiedni do niczego innego niż reguły administracyjne.
Teraz, w zależności od tego, jak hojna jesteś z definicją binarnego pliku wykonywalnego, możesz (ab) użyć
binfmt_misc
do implementacji obsługi grubego binarnego skryptu powłoki i plików ZIP jako formatu kontenera:Nazwij to „wozbin” i skonfiguruj go za pomocą czegoś takiego:
Rejestruje
.woz
pliki w jądrze,wozbin
zamiast tego wywoływany jest skrypt z pierwszym argumentem ustawionym na ścieżkę wywoływanego.woz
pliku.Aby uzyskać przenośny (gruby)
.woz
plik, po prostu utwórztest.woz
plik ZIP z hierarchią katalogów, aby:W każdym katalogu arch / OS / libc (dowolny wybór) umieść specyficzne dla architektury pliki
test
binarne i komponenty, takie jak.so
pliki. Po wywołaniu wymagany podkatalog jest rozpakowywany do systemu plików w pamięci tmpfs (/mnt/tmpfs
tutaj) i wywoływany.źródło
boot jagód, rozwiąż niektóre z twoich problemów .. ale to nie rozwiązuje problemu jak uruchomić na arm hf, normall / regullAr linux distro dla x86-32 / 64bit.
Myślę, że powinien on być wbudowany w isolinux (linux loaderloader na USB) w jakiś konwerter na żywo, który mógłby rozpoznać regullar distro oraz w ride / live konwersję do hf.
Dlaczego? Ponieważ jeśli każdy linux może zostać przekonwertowany przez bootowanie jagód do pracy na Arm-HF, może być w stanie wbudować mechanizm rozruchowy Bery, aby wyodrębnić to, co uruchamiamy za pomocą exache Eacher lub wbudowanego dysku startowego kreacji Ubuntu.
źródło