Po odkryciu, że kilka typowych poleceń (takich jak read
) jest w rzeczywistości wbudowanymi funkcjami Bash (a kiedy je uruchamiam w wierszu poleceń, faktycznie uruchamiam dwuwierszowy skrypt powłoki, który właśnie przekazuje do wbudowanej wersji), szukałem, czy to samo jest prawdziwe dla true
i false
.
Cóż, zdecydowanie są to pliki binarne.
sh-4.2$ which true
/usr/bin/true
sh-4.2$ which false
/usr/bin/false
sh-4.2$ file /usr/bin/true
/usr/bin/true: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=2697339d3c19235
06e10af65aa3120b12295277e, stripped
sh-4.2$ file /usr/bin/false
/usr/bin/false: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=b160fa513fcc13
537d7293f05e40444fe5843640, stripped
sh-4.2$
Najbardziej zaskoczyła mnie jednak ich wielkość. Spodziewałem się, że będą miały tylko kilka bajtów, co true
jest w zasadzie słuszne exit 0
i false
jest exit 1
.
sh-4.2$ true
sh-4.2$ echo $?
0
sh-4.2$ false
sh-4.2$ echo $?
1
sh-4.2$
Zaskoczyłem jednak, że oba pliki mają rozmiar ponad 28 KB.
sh-4.2$ stat /usr/bin/true
File: '/usr/bin/true'
Size: 28920 Blocks: 64 IO Block: 4096 regular file
Device: fd2ch/64812d Inode: 530320 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2018-01-25 19:46:32.703463708 +0000
Modify: 2016-06-30 09:44:27.000000000 +0100
Change: 2017-12-22 09:43:17.447563336 +0000
Birth: -
sh-4.2$ stat /usr/bin/false
File: '/usr/bin/false'
Size: 28920 Blocks: 64 IO Block: 4096 regular file
Device: fd2ch/64812d Inode: 530697 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2018-01-25 20:06:27.210764704 +0000
Modify: 2016-06-30 09:44:27.000000000 +0100
Change: 2017-12-22 09:43:18.148561245 +0000
Birth: -
sh-4.2$
Moje pytanie brzmi: dlaczego są takie duże? Co zawiera plik wykonywalny inny niż kod powrotu?
PS: Używam RHEL 7.4
linux
reverse-engineering
Kidburla
źródło
źródło
command -V true
niewhich
. Wyjdzie:true is a shell builtin
dla bash.true
ifalse
są wbudowane w każdą nowoczesną powłokę, ale systemy zawierają również ich zewnętrzne wersje programów, ponieważ jest to część standardowego systemu, dzięki czemu programy bezpośrednio wywołujące polecenia (omijając powłokę) mogą z nich korzystać.which
ignoruje wbudowane i wyszukuje tylko polecenia zewnętrzne, dlatego pokazał tylko te zewnętrzne. Spróbujtype -a true
itype -a false
zamiast tego.true
ifalse
29kb każdy? Co zawiera plik wykonywalny inny niż kod powrotu?”false
: muppetlabs.com/~breadbox/software/tiny/teensy.htmlOdpowiedzi:
W przeszłości
/bin/true
iw/bin/false
powłoce znajdowały się skrypty.Na przykład w systemie PDP / 11 Unix System 7:
Obecnie, przynajmniej
bash
, gdytrue
ifalse
polecenia są realizowane jako powłoki wbudowanych poleceń. W związku z tym domyślnie nie są wywoływane pliki wykonywalne, zarówno podczas korzystania z dyrektywfalse
itrue
wbash
wierszu poleceń, jak i wewnątrz skryptów powłoki.Ze
bash
źródłabuiltins/mkbuiltins.c
:Również na komentarze @meuh:
Więc można powiedzieć, z wysokim stopniem pewności
true
ifalse
pliki wykonywalne istnieją głównie na miano z innymi programami .Odtąd odpowiedź będzie się koncentrować na
/bin/true
pliku binarnym zcoreutils
pakietu w bitach 9/64 Debiana. (z/usr/bin/true
systemem RedHat. RedHat i Debian używają obucoreutils
pakietów, przeanalizowali skompilowaną wersję tego drugiego, mając go pod ręką).Jak widać w pliku źródłowym
false.c
,/bin/false
jest on kompilowany z (prawie) tym samym kodem źródłowym co/bin/true
, po prostu zwracając EXIT_FAILURE (1), więc ta odpowiedź może być zastosowana do obu plików binarnych.Jak może to również potwierdzić oba pliki wykonywalne o tym samym rozmiarze:
Niestety, bezpośrednie pytanie do odpowiedzi
why are true and false so large?
może brzmieć, ponieważ nie ma już tak pilnych powodów, aby dbać o ich najlepsze wyniki. Nie są one niezbędne dlabash
wydajności, nie są już używane przezbash
(skrypty).Podobne uwagi dotyczą ich wielkości, 26 KB dla sprzętu, który mamy obecnie, jest nieznaczna. Przestrzeń nie jest już na wagę złota dla typowego serwera / pulpitu i nawet nie zadają sobie trudu, aby użyć tego samego pliku binarnego dla
false
itrue
, ponieważ jest on tylko dwukrotnie wdrażany w dystrybucjicoreutils
.Koncentrując się jednak w prawdziwym duchu pytania, dlaczego coś, co powinno być tak proste i małe, staje się tak duże?
Rzeczywisty rozkład odcinków
/bin/true
jest taki, jak pokazują te wykresy; główny kod + dane wynosi około 3 KB z pliku binarnego 26 KB, co stanowi 12% wielkości pliku/bin/true
.true
Narzędzie dostaje kod rzeczywiście bardziej cruft biegiem lat, przede wszystkim wsparcie dla standardu--version
i--help
.Jednak nie jest to (jedyne) główne uzasadnienie tego, że jest tak duży, ale raczej, że jest dynamicznie łączony (za pomocą współdzielonych bibliotek), ale ma także część ogólnej biblioteki często używanej przez
coreutils
pliki binarne połączone jako biblioteka statyczna. Metada do budowyelf
pliku wykonywalnego również stanowi znaczną część pliku binarnego, ponieważ jest to stosunkowo niewielki plik, jak na dzisiejsze standardy.Reszta odpowiedzi ma na celu wyjaśnienie, w jaki sposób zbudowaliśmy następujące wykresy szczegółowo przedstawiające skład
/bin/true
wykonywalnego pliku binarnego i jak doszliśmy do tego wniosku.Jak mówi @Maks, plik binarny został skompilowany z C; zgodnie z moim komentarzem również potwierdzono, że pochodzi on z rdzeni rdzeniowych. Wskazujemy bezpośrednio na autora (ów) git https://github.com/wertarbyte/coreutils/blob/master/src/true.c , zamiast gnu git jako @Maks (te same źródła, różne repozytoria - to repozytorium został wybrany, ponieważ ma pełne źródło
coreutils
bibliotek)Widzimy tutaj różne elementy składowe
/bin/true
pliku binarnego (Debian 9 - 64 bity odcoreutils
):Tych:
Z 24 KB około 1 KB służy do naprawy 58 funkcji zewnętrznych.
To pozostawia około 23 KB na resztę kodu. Pokażemy poniżej, że rzeczywisty kod pliku głównego - main () + use () jest skompilowany około 1 KB, i wyjaśnimy, do czego służą pozostałe 22 KB.
readelf -S true
Przechodząc dalej do pliku binarnego , widzimy, że chociaż plik binarny ma 26159 bajtów, rzeczywisty skompilowany kod to 13017 bajtów, a reszta to asortowany kod danych / inicjalizacji.Nie
true.c
jest to jednak cała historia, a 13 KB wydaje się być zbyt wygórowane, gdyby był tylko tym plikiem; możemy zobaczyć wywołane funkcjemain()
, które nie są wymienione w funkcjach zewnętrznych widocznych u elfa zobjdump -T true
; funkcje obecne w:Te dodatkowe funkcje niepowiązane zewnętrznie
main()
to:Moje pierwsze podejrzenie było częściowo poprawne, podczas gdy biblioteka korzysta z bibliotek dynamicznych,
/bin/true
plik binarny jest duży *, ponieważ zawiera pewne biblioteki statyczne * (ale to nie jedyna przyczyna).Kompilowanie kodu C zwykle nie jest tak nieefektywne, aby nie uwzględniać takiej przestrzeni, stąd moje początkowe podejrzenie, że coś było nie tak.
Dodatkowa przestrzeń, prawie 90% wielkości pliku binarnego, to rzeczywiście dodatkowe metadane bibliotek / elfów.
Podczas używania Hoppera do dezasemblacji / dekompilacji pliku binarnego w celu zrozumienia, gdzie są funkcje, można zobaczyć, że skompilowany kod binarny funkcji true.c / use () ma w rzeczywistości 833 bajty, a funkcji true.c / main () jest 225 bajtów, czyli mniej więcej nieco mniej niż 1 KB. Logika funkcji wersji, która jest zakopana w bibliotekach statycznych, wynosi około 1 KB.
Rzeczywiste skompilowane main () + użycie () + wersja () + ciągi znaków + zmienne zużywają tylko około 3 KB do 3,5 KB.
To jest rzeczywiście ironiczne, takie małe i skromne narzędzia stały się większe z powodów wyjaśnionych powyżej.
powiązane pytanie: Zrozumienie, co robi plik binarny systemu Linux
true.c
main () z wywoływanymi funkcjami:Rozmiar dziesiętny różnych sekcji pliku binarnego:
Wyjście z
readelf -S true
Wyjście
objdump -T true
(funkcje zewnętrzne dynamicznie połączone w czasie wykonywania)źródło
true
lubfalse
za pomocą 45-bajtowego pliku wykonywalnego ELF x86, pakując kod wykonywalny (4 instrukcje x86) wewnątrz nagłówka programu ELF (bez obsługi żadnych opcji wiersza poleceń!) . Samouczek Whirlwind na temat tworzenia plików wykonywalnych ELF dla systemu Linux . (Lub nieco większy, jeśli chcesz uniknąć, w zależności od szczegółów implementacji modułu ładującego ELF dla systemu Linux: P)Implementacja prawdopodobnie pochodzi z rdzeni GNU. Te pliki binarne są kompilowane z C; nie podjęto żadnych szczególnych starań, aby były one mniejsze niż są domyślnie.
Możesz spróbować skompilować trywialną implementację
true
siebie, a zauważysz, że ma już kilka KB. Na przykład w moim systemie:Oczywiście twoje pliki binarne są jeszcze większe. Jest tak, ponieważ obsługują one również argumenty wiersza poleceń. Spróbuj uruchomić
/usr/bin/true --help
lub/usr/bin/true --version
.Oprócz danych ciągowych, plik binarny zawiera logikę do analizowania flag wiersza poleceń itp. To najwyraźniej stanowi około 20 KB kodu.
W celach informacyjnych kod źródłowy można znaleźć tutaj: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c
źródło
Zredukowanie ich do podstawowej funkcjonalności i pisanie w asemblerze daje znacznie mniejsze pliki binarne.
Oryginalne pliki binarne prawda / fałsz zapisywane są w języku C, który ze swej natury ściąga różne biblioteki i odniesienia do symboli. Jeśli uruchomisz,
readelf -a /bin/true
jest to dość zauważalne.352 bajty dla odizolowanego pliku wykonywalnego ELF (z miejscem na zaoszczędzenie kilku bajtów poprzez optymalizację asm dla rozmiaru kodu).
Lub, stosując nieco nieprzyjemne / pomysłowe podejście (od uznania do stalkr ), utwórz własne nagłówki ELF, zmniejszając je do
132127 bajtów. Wjeżdżamy na terytorium Code Golf .źródło
int 0x80
32-bitowego ABI w 64-bitowym pliku wykonywalnym, co jest niezwykłe, ale obsługiwane . Używaniesyscall
niczego by cię nie uratowało. Wysokie bajtyebx
są ignorowane, więc możesz użyć 2 bajtówmov bl,1
. Lub oczywiściexor ebx,ebx
zero . Linux inits rejestrów całkowitych na zero, więc mógł tylkoinc eax
dostać 1 = __NR_exit i386 (ABI).true
. (Nie widzę łatwy sposób zarządzać mniej niż 128 bajtówfalse
, choć inne niż przy użyciu 32-bitowego ABI lub korzystając z faktu, że Linux zer rejestrów na starcie procesu, takmov al,252
(2 bajty) pracuje.push imm8
/pop rdi
By również działają zamiastlea
ustawianiaedi=1
, ale nadal nie możemy pobić 32-bitowego ABI, w którym moglibyśmymov bl,1
bez prefiksu REXCałkiem duży na moim Ubuntu 16.04. dokładnie ten sam rozmiar? Co czyni je tak dużymi?
(fragment:)
Ach, istnieje pomoc dla prawdy i fałszu, więc spróbujmy:
Nic. Ach, była inna linia:
W moim systemie jest to / bin / true, a nie / usr / bin / true
Jest więc pomoc, są informacje o wersji, wiążące bibliotekę w celu internacjonalizacji. To wyjaśnia znaczną część rozmiaru, a powłoka i tak używa swojego zoptymalizowanego polecenia przez większość czasu.
źródło