Czy plik wykonywalny Linuksa skompilowany na jednym „smaku” Linuksa będzie działał na innym?

59

Czy plik wykonywalny małego, niezwykle prostego programu, takiego jak ten pokazany poniżej, który jest skompilowany na jednym systemie Linux, będzie działał w innym stylu? A może trzeba go ponownie skompilować?

Czy architektura maszyny ma znaczenie w takim przypadku?

int main()
{
  return (99);
}
JCDeen
źródło
2
Dziękujemy wszystkim za wybitne odpowiedzi! Nauczyłem się znacznie więcej, niż się spodziewałem. Celowo uczyniłem kod sztucznie prostym, aby zależał on od jak najmniejszej liczby bibliotek; ale naprawdę powinienem to powiedzieć z góry. Większość mojego kodowania w C ++ na różnych platformach wymagała programowania za pomocą narzędzia Microsoft, takiego jak Visual Studio, a następnie przenoszenia kodu do systemu * nix i ponownej kompilacji.
JCDeen
4
Wiele aspektów i rozważań wyrażonych tutaj zadziwiło mnie! Szczerze chciałbym móc wybrać kilka jako THE. Jeszcze raz dziękuję wszystkim! Z poważaniem.
JCDeen
2
Android to także system operacyjny oparty na systemie Linux. Powodzenia jednak, uruchamianie dowolnego kodu glibctam skompilowanego lub odwrotnie. To prawda, że nie jest to całkowicie niemożliwe .
undercat
2
Aby uzyskać maksymalną kompatybilność narzędzia wiersza poleceń, możesz użyć uClibc, musl lub dietlibc zamiast glibc i statycznie połączyć 32-bitowy plik wykonywalny ( gcc -m32 -static). W ten sposób każdy Linux i386 lub amd64 będzie mógł uruchomić plik wykonywalny.
pkt
10
Powinieneś wrócić 42 ! :)
Homunculus Reticulli

Odpowiedzi:

49

To zależy. Coś skompilowanego dla IA-32 (Intel 32-bit) może działać na amd64, ponieważ Linux na Intel zachowuje zgodność wsteczną z aplikacjami 32-bitowymi (z zainstalowanym odpowiednim oprogramowaniem). Oto twoja codekompilowana na 32-bitowym systemie RedHat 7.3 (około 2002, gcc wersja 2.96), a następnie plik binarny skopiowany i uruchomiony na 64-bitowym systemie Centos 7.4 (około 2017):

-bash-4.2$ file code
code: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.2.5, not stripped
-bash-4.2$ ./code
-bash: ./code: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory
-bash-4.2$ sudo yum -y install glibc.i686
...
-bash-4.2$ ./code ; echo $?
99

Starożytny RedHat 7.3 do Centos 7.4 (zasadniczo RedHat Enterprise Linux 7.4) pozostaje w tej samej rodzinie „dystrybucji”, więc prawdopodobnie będzie miał lepszą przenośność niż przejście z jakiejś losowej instalacji „Linux od zera” od 2002 r. Do innej losowej dystrybucji Linuksa w 2018 r. .

Coś skompilowanego dla amd64 nie działałoby na 32-bitowych wersjach systemu Linux (stary sprzęt nie wie o nowym sprzęcie). Odnosi się to również do nowego oprogramowania skompilowanego na nowoczesnych systemach, które mają być uruchamiane na starych, starych rzeczach, ponieważ biblioteki, a nawet wywołania systemowe mogą nie być przenośne wstecz, więc mogą wymagać sztuczek kompilacyjnych lub uzyskania starego kompilatora i tak dalej, a może zamiast tego kompilacja na starym systemie. (To dobry powód, aby trzymać maszyny wirtualne starych, starych rzeczy).

Architektura ma znaczenie; amd64 (lub IA-32) znacznie różni się od ARM lub MIPS, więc nie można oczekiwać, że binarny jeden z nich będzie działał na innym. Na poziomie montażowego mainsekcji kodu na IA-32 poprzez kompiluje gcc -S code.csię

main:
    pushl %ebp
    movl %esp,%ebp
    movl $99,%eax
    popl %ebp
    ret

z którymi system amd64 może sobie poradzić (w systemie Linux - w przeciwieństwie do OpenBSD na amd64 nie obsługuje 32-bitowych plików binarnych; wsteczna kompatybilność ze starymi architekturami daje atakującym swobodę , np. CVE-2014-8866 i przyjaciele). Tymczasem w systemie big-endian MIPS main zamiast tego kompiluje się w celu:

main:
        .frame  $fp,8,$31
        .mask   0x40000000,-4
        .fmask  0x00000000,0
        .set    noreorder
        .set    nomacro
        addiu   $sp,$sp,-8
        sw      $fp,4($sp)
        move    $fp,$sp
        li      $2,99
        move    $sp,$fp
        lw      $fp,4($sp)
        addiu   $sp,$sp,8
        j       $31
        nop

z którym procesor Intel nie będzie miał pojęcia, co z tym zrobić, a także w przypadku zestawu Intel na platformie MIPS.

Możesz użyć QEMU lub innego emulatora do uruchomienia obcego kodu (być może bardzo, bardzo powoli).

Jednak! Twój kod jest bardzo prostym kodem, więc będzie mniej problemów z przenośnością niż cokolwiek innego; programy zwykle korzystają z bibliotek, które zmieniły się w czasie (glibc, openssl, ...); dla tych może być również konieczne zainstalowanie starszych wersji różnych bibliotek (na przykład RedHat zazwyczaj umieszcza „kompatybilność” gdzieś w nazwie pakietu dla takich)

compat-glibc.x86_64                     1:2.12-4.el7.centos

lub ewentualnie martwić się zmianami ABI (Application Binary Interface) o stare sposoby używające glibc lub nowszymi zmianami spowodowanymi C ++ 11 lub innymi wydaniami C ++. Można również skompilować statyczne (znacznie zwiększając rozmiar pliku binarnego na dysku), aby uniknąć problemów z biblioteką, chociaż to, czy zrobił to jakiś stary plik binarny, zależy od tego, czy stara dystrybucja Linuksa kompiluje większość wszystkiego dynamicznie (RedHat: tak), czy nie. Z drugiej strony, takie rzeczy jak patchelfponowne uruchamianie dynamicznych a.outplików binarnych (ELF, ale prawdopodobnie nie formatowanie) w celu korzystania z innych bibliotek.

Jednak! Możliwość uruchomienia programu to jedno, a właściwie robienie z nim czegoś pożytecznego. Stare 32-bitowe pliki binarne Intela mogą mieć problemy z bezpieczeństwem, jeśli zależą od wersji OpenSSL, która zawiera jakieś okropne i nieobsługiwane problemy bezpieczeństwa, lub program może nie być w stanie negocjować z nowoczesnymi serwerami WWW (jako nowoczesne serwery odrzucają stare protokoły i szyfry starego programu) lub protokół SSH wersja 1 nie jest już obsługiwany lub ...

gałązka
źródło
14
ponownie akapit pierwszy: nie, Intel nazywa to „Intel 64” (obecnie, po wcześniejszym przejrzeniu innych nazw). IA-64 odnosi się do Itanium, a nie do niczego kompatybilnego z x86.
hobbs
1
@ Hobbs dziękuję, zastąpiłem te odniesienia amd64; Nazwy rzeczy pozostawię działowi marketingu firmy Intel.
thrig
3
dlaczego nie wspomnieć o łączeniu statycznym?
dcorking
2
Zmieniają się nie tylko biblioteki ABI - interfejs syscall jądra również z czasem się rozszerza. Zwróć uwagę na for GNU/Linux 2.6.32(lub takie) na wyjściu file /usr/bin/ls.
Charles Duffy
1
@Wilbert Prawdopodobnie brakuje Ci, że thrig odnosi się do systemu Red Hat Linux, który jest inny niż Red Hat Enterprise Linux.
Bob
68

Krótko mówiąc: jeśli przenosisz skompilowany plik binarny z jednego hosta na inny przy użyciu tej samej (lub kompatybilnej) architektury , możesz być w porządku, przenosząc go do innej dystrybucji . Jednak wraz ze wzrostem złożoności kodu prawdopodobieństwo powiązania z biblioteką, która nie jest zainstalowana; zainstalowany w innym miejscu; lub zainstalowany w innej wersji, wzrasta. Weźmy na przykład kod, dla którego lddzgłasza się następujące zależności po kompilacji gcc -o exit-test exit-test.cna (pochodzącym z Debiana) systemie Ubuntu Linux:

$ ldd exit-test
    linux-gate.so.1 =>  (0xb7748000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb757b000)
    /lib/ld-linux.so.2 (0x8005a000)

Oczywiście ten plik binarny nie uruchomi się, jeśli przekopię go, powiedzmy, na komputer Mac ( ./exit-test: cannot execute binary file: Exec format error). Spróbujmy przenieść go do skrzynki RHEL:

$ ./exit-test
-bash: ./exit-test: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory

O jej. Dlaczego to może być?

$ ls /lib/ld-l* # reference the `ldd` output above
ls: cannot access /lib/ld-l*: No such file or directory

Nawet w tym przypadku użycia forklifting nie powiódł się z powodu brakujących bibliotek współdzielonych.

Jednak jeśli go skompiluję gcc -static exit-test-static exit-test.c, przeniesienie go do systemu bez bibliotek działa dobrze. Kosztem oczywiście miejsca na dysku:

$ ls -l ./exit-test{,-static}
-rwxr-xr-x  1 username  groupname    7312 Jan 29 14:18 ./exit-test
-rwxr-xr-x  1 username  groupname  728228 Jan 29 14:27 ./exit-test-static

Innym realnym rozwiązaniem byłoby zainstalowanie wymaganych bibliotek na nowym hoście.

Podobnie jak w przypadku wielu rzeczy we wszechświecie U&L, jest to kot z wieloma skórkami, z których dwie są przedstawione powyżej.

DopeGhoti
źródło
4
Rzeczywiście zapomniałem o statycznych plikach binarnych. Niektórzy dostawcy przyjmują statyczne pliki binarne, a niektórzy autorzy złośliwego oprogramowania również w celu zmaksymalizowania zgodności binarnej między wersjami Linux tej samej architektury
Rui F Ribeiro
8
Wózki widłowe ...?
user253751,
2
@immibis Myślę, że oznacza to kopiowanie danych (pliku wykonywalnego) z jednego środowiska (dystrybucji) do innego, gdzie dane nie są przeznaczone dla środowiska docelowego.
wjandrea
13
Twój przykład Linuksa jest niestety sztuczny i ilustruje twoje zdanie na temat architektur, a nie dystrybucji: zbudowałeś 32-bitowy plik binarny na Debianie i próbowałeś uruchomić go na 64-bitowym RHEL; są to różne architektury ... Pliki binarne o tej samej architekturze z tak małą liczbą zależności bibliotek można skopiować.
Stephen Kitt
7
@MSalters Nie twierdzę, że jest to nierozsądne, mówię, że jest to zły przykład, biorąc pod uwagę punkt, który DopeGhoti próbuje zrobić (nie możesz kopiować plików binarnych z jednej dystrybucji do drugiej - co jest złe). Oczywiście 64-bitowy system Linux na platformie Intel obsługuje również 32-bitowe pliki wykonywalne z odpowiednią infrastrukturą. Poprawnym przykładem w tym przypadku IMO byłoby zbudowanie pliku amd64binarnego i uruchomienie go na innej amd64dystrybucji lub zbudowanie pliku i386binarnego i uruchomienie go na innej i386dystrybucji.
Stephen Kitt
25

Dodanie do doskonałych odpowiedzi na @thrig i @DopeGhoti: Uniksowe lub podobne do systemów operacyjnych, w tym Linux, tradycyjnie zawsze były projektowane i dostosowywane bardziej pod kątem przenośności kodu źródłowego niż plików binarnych.

Jeśli nie masz nic specyficznego dla sprzętu lub nie jest to proste źródło, jak w twoim przykładzie, możesz go przenieść bez żadnego problemu pomiędzy prawie każdą wersją Linuksa lub architektury jako kodem źródłowym, o ile serwery docelowe mają zainstalowane pakiety programistyczne C , niezbędne biblioteki i zainstalowane odpowiednie biblioteki programistyczne.

Ponieważ daleko odsuwa się bardziej zaawansowany kod ze starszych wersji systemu Linux odległych w czasie lub bardziej szczegółowe programy, takie jak moduły jądra dla różnych wersji jądra, może być konieczne dostosowanie i zmodyfikowanie kodu źródłowego w celu uwzględnienia przestarzałych bibliotek / interfejsów API / ABI.

Rui F. Ribeiro
źródło
19

Przez domyślnie , to prawie na pewno napotkasz problemy z bibliotek zewnętrznych. Niektóre inne odpowiedzi zawierają więcej szczegółów na temat tych problemów, więc nie powielę ich pracy.

Państwo może jednak kompilacji wielu programów - nawet te nietrywialne - być przenośne między systemami Linux. Kluczem jest zestaw narzędzi o nazwie Linux Standard Base . LSB jest przeznaczony do tworzenia tylko tych rodzajów zastosowań przenośnych. Skompiluj aplikację dla LSB wer. 5.0 i będzie działać w dowolnym innym środowisku Linux (o tej samej architekturze), które implementuje LSB wer. Kilka dystrybucji Linuksa jest zgodnych z LSB, a inne obejmują zestawy / biblioteki LSB jako pakiet instalacyjny. Jeśli zbudujesz aplikację przy użyciu narzędzi LSB (takich jak lsbccopakowanie gcc) i połączysz się z wersją bibliotek LSB, utworzysz aplikację przenośną.

bta
źródło
a dzięki qemuniemu możesz nawet uruchamiać programy skompilowane dla różnych architektur ((nie wysoka wydajność, ale możesz je uruchomić)
Jasen,
1
Nie byłem nawet świadomy zestawu narzędzi dla Linux Standard Base, więc dziękuję! Pracę z C / C ++ zacząłem bardzo dawno temu, więc wiele informacji w tych odpowiedziach jest dla mnie nowych. I bardzo pomocny.
JCDeen
1
Artykuł w Wikipedii mówi, że Debian i Ubuntu nie implementują LSB (i nie mają takiego zamiaru.)
BlackJack
2
@ BlackJack - sama dystrybucja nie implementuje go w 100% jako części podstawowego systemu operacyjnego, ale można zainstalować biblioteki i zestawy narzędzi kompatybilne z LSB jako pakiety opcjonalne. Użyłem Ubuntu (na przykład) do budowy programów kompatybilnych z LSB, które następnie działały na Suse i Centos, wystarczy apt-get installkilka pakietów.
bta
10

Może.

Do rzeczy, które go psują, należą.

  1. Różne architektury. Oczywiście zupełnie inne architektury nie będą działać (chyba że masz coś takiego jak tryb użytkownika qemu z binfmt_misc, ale nie jest to normalna konfiguracja). Pliki binarne x86 mogą działać na amd64, ale tylko wtedy, gdy dostępne są wymagane biblioteki 32-bitowe.
  2. Wersje bibliotek. Jeśli soversion jest błędna, to w ogóle nie znajdzie biblioteki. Jeśli soversion jest taka sama, ale plik binarny jest zbudowany na nowszej wersji biblioteki niż działa, może się nie załadować z powodu nowych symboli lub nowych wersji symboli. W szczególności glibc jest dużym użytkownikiem wersji symboli, więc binaria zbudowane na nowszym glibc mogą zawieść ze starszym glibc.

Jeśli unikniesz używania szybko zmieniających się bibliotek, unikniesz zmian w architekturze i wykorzystasz najstarszą dystrybucję, na którą chcesz kierować, masz dużą szansę na zrobienie jednego pliku binarnego na wielu dystrybucjach.

płyn do płukania
źródło
4

Oprócz niektórych rzeczy wymienionych wcześniej, wprowadzono pewne zmiany w formacie pliku wykonywalnego. W przeważającej części Linux używa ELF, ale starsze wersje używały a.out lub COFF.

Początek wikihole:

https://en.wikipedia.org/wiki/Comparison_of_executable_file_formats

Może istnieć sposób, aby starsze wersje uruchamiały nowsze formaty, ale ja osobiście nigdy się tym nie zajmowałem.

Ben
źródło
„starszy” jest w tym momencie bardzo stary.
płukanie