Setelah mengetahui bahwa beberapa perintah umum (seperti read
) sebenarnya Bash builtins (dan ketika menjalankannya di prompt saya benar-benar menjalankan skrip shell dua baris yang hanya meneruskan ke builtin), saya mencari untuk melihat apakah sama berlaku untuk true
dan false
.
Ya, mereka pasti binari.
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$
Namun, yang paling mengejutkan bagi saya adalah ukurannya. Saya harapkan mereka untuk menjadi hanya beberapa byte masing-masing, seperti true
pada dasarnya hanya exit 0
dan false
adalah exit 1
.
sh-4.2$ true
sh-4.2$ echo $?
0
sh-4.2$ false
sh-4.2$ echo $?
1
sh-4.2$
Namun saya terkejut bahwa kedua file berukuran lebih dari 28KB.
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$
Jadi pertanyaan saya adalah: Mengapa mereka begitu besar? Apa yang ada dalam executable selain dari kode pengembalian?
PS: Saya menggunakan RHEL 7.4
linux
reverse-engineering
Kidburla
sumber
sumber
command -V true
tidak menggunakannyawhich
. Ini akan menampilkan:true is a shell builtin
untuk bash.true
danfalse
yang builtin di setiap shell modern, tetapi sistem juga termasuk versi program eksternal dari mereka karena itu adalah bagian dari sistem standar sehingga program memohon perintah langsung (melewati shell) dapat menggunakannya.which
mengabaikan builtin, dan mencari perintah eksternal saja, itulah sebabnya hanya menunjukkan kepada Anda perintah eksternal. Cobatype -a true
dantype -a false
sebagai gantinya.true
danfalse
29kb? Apa yang ada di executable selain kode kembali?"false
: muppetlabs.com/~breadbox/software/tiny/teensy.htmlJawaban:
Di masa lalu,
/bin/true
dan/bin/false
di shell sebenarnya skrip.Misalnya, dalam Sistem Unix 7 PDP / 11:
Saat ini, setidaknya di
bash
,true
danfalse
perintah diimplementasikan sebagai perintah built-in shell. Dengan demikian tidak ada file biner yang dapat dieksekusi dipanggil secara default, baik saat menggunakanfalse
dantrue
arahan dalambash
baris perintah dan skrip shell di dalam.Dari
bash
sumbernyabuiltins/mkbuiltins.c
:Juga per @ saya komentar:
Jadi dapat dikatakan dengan tingkat kepastian yang tinggi
true
danfalse
file yang dapat dieksekusi ada terutama untuk dipanggil dari program lain .Mulai sekarang, jawabannya akan fokus pada
/bin/true
biner daricoreutils
paket di Debian 9/64 bit. (/usr/bin/true
Menjalankan RedHat. RedHat dan Debian menggunakan keduacoreutils
paket, menganalisis versi terkompilasi dari yang terakhir memiliki lebih di tangan).Seperti yang dapat dilihat pada file sumber
false.c
,/bin/false
dikompilasi dengan (hampir) kode sumber yang sama dengan/bin/true
, hanya mengembalikan EXIT_FAILURE (1) sebagai gantinya, jadi jawaban ini dapat diterapkan untuk kedua binari.Karena juga dapat dikonfirmasi oleh kedua executable yang memiliki ukuran yang sama:
Sayangnya, pertanyaan langsung untuk jawabannya
why are true and false so large?
bisa jadi, karena tidak ada lagi alasan yang begitu mendesak untuk peduli dengan kinerja terbaik mereka. Mereka tidak penting untukbash
kinerja, tidak digunakan lagi olehbash
(scripting).Komentar serupa berlaku untuk ukurannya, 26KB untuk jenis perangkat keras yang kita miliki saat ini tidak signifikan. Ruang tidak lagi premium untuk server / desktop biasa, dan mereka bahkan tidak repot-repot lagi menggunakan biner yang sama untuk ,
false
dantrue
karena hanya digunakan dua kali dalam distribusi menggunakancoreutils
.Namun, berfokus pada semangat pertanyaan yang sebenarnya, mengapa sesuatu yang seharusnya begitu sederhana dan kecil, menjadi begitu besar?
Distribusi nyata bagian
/bin/true
adalah seperti yang ditunjukkan oleh bagan ini; kode utama + data berjumlah sekitar 3KB dari biner 26KB, yang berjumlah 12% dari ukuran/bin/true
.The
true
utilitas mendapat kode memang lebih cruft selama bertahun-tahun, terutama dukungan standar untuk--version
dan--help
.Namun, itu bukan pembenaran utama (hanya) untuk itu menjadi begitu besar, tetapi lebih tepatnya, sementara sedang terkait secara dinamis (menggunakan shared libs), juga memiliki bagian dari perpustakaan umum yang biasa digunakan oleh
coreutils
biner yang dihubungkan sebagai perpustakaan statis. Metada untuk membangunelf
file yang dapat dieksekusi juga berjumlah sebagian besar dari biner, karena itu file yang relatif kecil menurut standar saat ini.Sisa jawabannya adalah untuk menjelaskan bagaimana kami harus membuat bagan berikut yang merinci komposisi
/bin/true
file biner yang dapat dieksekusi dan bagaimana kami sampai pada kesimpulan itu.Seperti yang dikatakan @Maks, biner dikompilasi dari C; sesuai komentar saya juga, juga dikonfirmasi itu dari coreutils. Kami menunjuk langsung ke penulis git https://github.com/wertarbyte/coreutils/blob/master/src/true.c , alih-alih gnu git sebagai @Maks (sumber yang sama, repositori berbeda - repositori ini dipilih karena memiliki sumber
coreutils
perpustakaan yang lengkap)Kita dapat melihat berbagai blok bangunan
/bin/true
biner di sini (Debian 9 - 64 bit daricoreutils
):Dari mereka:
Dari 24KB, sekitar 1KB adalah untuk memperbaiki 58 fungsi eksternal.
Itu masih menyisakan sekitar 23KB untuk sisa kode. Kami akan menunjukkan di bawah ini bahwa file utama sebenarnya - kode utama () + penggunaan () adalah sekitar 1KB yang dikompilasi, dan menjelaskan untuk apa 22KB lainnya digunakan.
Pengeboran lebih jauh ke bawah biner dengan
readelf -S true
, kita dapat melihat bahwa sementara biner adalah 26159 byte, kode kompilasi yang sebenarnya adalah 13017 byte, dan sisanya adalah berbagai data / kode inisialisasi.Namun,
true.c
bukan keseluruhan cerita dan 13KB tampaknya cukup berlebihan jika hanya file itu; kita bisa melihat fungsi-fungsi yang dipanggilmain()
yang tidak terdaftar dalam fungsi-fungsi eksternal yang terlihat di elf denganobjdump -T true
; fungsi yang hadir di:Fungsi-fungsi ekstra yang tidak ditautkan secara eksternal
main()
adalah:Jadi kecurigaan pertama saya sebagian benar, sementara perpustakaan menggunakan perpustakaan dinamis,
/bin/true
biner besar * karena memiliki beberapa perpustakaan statis yang disertakan dengannya * (tapi itu bukan satu-satunya penyebab).Kompilasi kode C biasanya tidak yang efisien untuk memiliki ruang seperti belum ditemukan, maka saya awal kecurigaan sesuatu yang salah.
Ruang ekstra, hampir 90% dari ukuran biner, memang merupakan perpustakaan ekstra / metadata elf.
Saat menggunakan Hopper untuk membongkar / mendekompilasi biner untuk memahami di mana fungsi berada, dapat dilihat kode biner yang dikompilasi dari fungsi true.c / use () sebenarnya 833 byte, dan dari fungsi true.c / main () adalah 225 byte, yang kira-kira sedikit kurang dari 1KB. Logika untuk fungsi versi, yang terkubur di perpustakaan statis, sekitar 1KB.
Sebenarnya kompilasi utama () + penggunaan () + versi () + string + vars hanya menggunakan sekitar 3KB hingga 3.5KB.
Sungguh ironis, utilitas kecil dan sederhana telah menjadi lebih besar karena alasan yang dijelaskan di atas.
pertanyaan terkait: Memahami apa yang dilakukan biner Linux
true.c
main () dengan panggilan fungsi yang menyinggung:Ukuran desimal dari berbagai bagian biner:
Output dari
readelf -S true
Output of
objdump -T true
(fungsi eksternal terhubung secara dinamis pada saat run-time)sumber
true
ataufalse
dengan executable ELF x86 45-byte, mengemas kode yang dapat dieksekusi (instruksi 4 x86) di dalam header program ELF (tanpa dukungan untuk opsi baris perintah!) . Tutorial Whirlwind tentang Membuat Executables ELF yang Sangat Berlebihan untuk Linux . (Atau sedikit lebih besar jika Anda ingin menghindari tergantung pada rincian implementasi loader ELF Linux: P)Implementasinya mungkin berasal dari GNU coreutils. Binari ini dikompilasi dari C; tidak ada upaya khusus yang dilakukan untuk membuatnya lebih kecil dari yang ada secara default.
Anda dapat mencoba mengkompilasi implementasi sepele dari
true
diri Anda, dan Anda akan melihat itu sudah beberapa KB. Misalnya, di sistem saya:Tentu saja, binari Anda bahkan lebih besar. Itu karena mereka juga mendukung argumen baris perintah. Coba jalankan
/usr/bin/true --help
atau/usr/bin/true --version
.Selain data string, biner menyertakan logika untuk mengurai bendera baris perintah, dll. Itu menambahkan hingga sekitar 20 KB kode, tampaknya.
Untuk referensi, Anda dapat menemukan kode sumber di sini: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c
sumber
Melucuti mereka ke fungsionalitas inti dan menulis di assembler menghasilkan biner yang jauh lebih kecil.
Binari benar / salah asli ditulis dalam C, yang pada dasarnya menarik berbagai referensi pustaka + simbol. Jika Anda menjalankan
readelf -a /bin/true
ini cukup terlihat.352 byte untuk executable statis ELF yang dilucuti (dengan ruang untuk menyimpan beberapa byte dengan mengoptimalkan asm untuk ukuran kode).
Atau, dengan sedikit pendekatan jahat / cerdik (pujian untuk stalkr ), buat header ELF Anda sendiri, turunkan ke
132127 byte. Kami memasuki wilayah Code Golf di sini.sumber
int 0x80
ABI 32-bit dalam eksekusi 64-bit, yang tidak biasa tetapi didukung . Menggunakansyscall
tidak akan menyelamatkan Anda apa pun. Bytes tinggiebx
diabaikan, jadi Anda bisa menggunakan 2 bytemov bl,1
. Atau tentu sajaxor ebx,ebx
nol . Linux inits register integer ke nol, sehingga Anda bisa hanyainc eax
untuk mendapatkan 1 = __NR_exit (i386 ABI).true
. (Saya tidak melihat cara mudah untuk mengelola kurang dari 128 byte untukfalse
, meskipun, selain menggunakan 32-bit ABI atau mengambil keuntungan dari fakta bahwa Linux angka nol register pada proses startup, sehinggamov al,252
(2 bytes) bekerja.push imm8
/pop rdi
Akan juga bekerja alih-alihlea
untuk pengaturanedi=1
, tapi kami masih tidak bisa mengalahkan ABI 32-bit di mana kami bisamov bl,1
tanpa awalan REXCukup besar di Ubuntu 16.04 saya juga. ukuran persis sama? Apa yang membuat mereka begitu besar?
(kutipan:)
Ah, ada bantuan untuk benar dan salah, jadi mari kita coba:
Tidak ada. Ah, ada baris lain ini:
Jadi pada sistem saya, ini / bin / true, bukan / usr / bin / true
Jadi ada bantuan, ada informasi versi, mengikat ke perpustakaan untuk internasionalisasi. Ini menjelaskan banyak ukuran, dan shell tetap menggunakan perintah yang dioptimalkan dan sebagian besar waktu.
sumber