Sebelumnya, /bin/true
dan /bin/false
di shell sebenarnya adalah skrip.
Misalnya, dalam Sistem Unix PDP/11 7:
$ ls -la /bin/true /bin/false
-rwxr-xr-x 1 bin 7 Jun 8 1979 /bin/false
-rwxr-xr-x 1 bin 0 Jun 8 1979 /bin/true
$
$ cat /bin/false
exit 1
$
$ cat /bin/true
$
Saat ini, setidaknya di bash
, true
dan false
perintah diimplementasikan sebagai perintah bawaan Shell. Jadi tidak ada file biner yang dapat dieksekusi yang dipanggil secara default, baik saat menggunakan false
dan true
direktif dalam bash
baris perintah dan di dalam skrip shell.
Dari bash
sumber, builtins/mkbuiltins.c
:
char *posix_builtins[] = { "alias", "bg", "cd", "command", "**false**", "fc", "fg", "getopts", "jobs", "kill", "newgrp", "pwd", "read", "**true**", "umask", "unalias", "wait", (char *)NULL };
Juga per komentar @meuh:
$ command -V true false
true is a shell builtin
false is a shell builtin
Jadi dapat dikatakan dengan tingkat kepastian yang tinggi true
dan false
file yang dapat dieksekusi ada terutama untuk dipanggil dari program lain .
Mulai sekarang, jawabannya akan fokus pada /bin/true
biner dari coreutils
paket dalam Debian 9/64 bit. (/usr/bin/true
menjalankan RedHat. RedHat dan Debian menggunakan keduanya coreutils
paket, menganalisis versi terkompilasi dari yang terakhir memiliki lebih banyak di tangan).
Seperti yang dapat dilihat pada file sumber false.c
, /bin/false
dikompilasi dengan (hampir) kode sumber yang sama dengan /bin/true
, cukup kembalikan EXIT_FAILURE (1), jadi jawaban ini dapat diterapkan untuk kedua biner.
#define EXIT_STATUS EXIT_FAILURE
#include "true.c"
Karena itu juga dapat dikonfirmasi oleh kedua executable yang memiliki ukuran yang sama:
$ ls -l /bin/true /bin/false
-rwxr-xr-x 1 root root 31464 Feb 22 2017 /bin/false
-rwxr-xr-x 1 root root 31464 Feb 22 2017 /bin/true
Sayangnya, pertanyaan langsung ke jawaban why are true and false so large?
bisa jadi, karena tidak ada lagi alasan yang begitu mendesak untuk peduli dengan performa puncak mereka. Mereka tidak penting untuk bash
kinerja, tidak digunakan lagi oleh bash
(skrip).
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 lagi menggunakan biner yang sama untuk false
dan true
, karena hanya diterapkan dua kali dalam distribusi menggunakan coreutils
.
Akan tetapi, berfokus pada semangat pertanyaan yang sebenarnya, mengapa sesuatu yang seharusnya begitu sederhana dan kecil, menjadi begitu besar?
Distribusi sebenarnya dari bagian /bin/true
adalah seperti yang ditunjukkan bagan ini; kode+data utama berjumlah kira-kira 3KB dari biner 26KB, yang berjumlah 12% dari ukuran /bin/true
.
true
utilitas memang mendapatkan lebih banyak kode kasar selama bertahun-tahun, terutama dukungan standar untuk --version
dan --help
.
Namun, itu bukan (satu-satunya) pembenaran utama untuk itu menjadi begitu besar, tetapi, saat ditautkan secara dinamis (menggunakan lib bersama), juga memiliki bagian dari perpustakaan umum yang biasa digunakan oleh coreutils
binari terkait sebagai perpustakaan statis. Metada untuk membuat elf
file yang dapat dieksekusi juga merupakan bagian penting dari biner, karena merupakan file yang relatif kecil menurut standar saat ini.
Sisa jawabannya adalah untuk menjelaskan bagaimana kami 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, itu juga dikonfirmasi dari coreutils. Kami menunjuk langsung ke pembuat 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 lengkap dari coreutils
perpustakaan)
Kita bisa melihat berbagai blok bangunan dari /bin/true
biner di sini (Debian 9 - 64 bit dari coreutils
):
$ file /bin/true
/bin/true: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9ae82394864538fa7b23b7f87b259ea2a20889c4, stripped
$ size /bin/true
text data bss dec hex filename
24583 1160 416 26159 662f true
Dari mereka:
- teks (biasanya kode) berukuran sekitar 24KB
- data (variabel yang diinisialisasi, kebanyakan string) berukuran sekitar 1KB
- bss (data yang belum diinisialisasi) 0,5KB
Dari 24KB, sekitar 1KB digunakan untuk memperbaiki 58 fungsi eksternal.
Itu masih menyisakan sekitar 23KB untuk sisa kode. Kami akan menunjukkan di bawah bahwa file utama sebenarnya - kode main()+usage() sekitar 1KB dikompilasi, dan menjelaskan untuk apa 22KB lainnya digunakan.
Mengebor lebih jauh ke bawah biner dengan readelf -S true
, kita dapat melihat bahwa meskipun binernya adalah 26159 byte, kode yang dikompilasi sebenarnya adalah 13017 byte, dan sisanya adalah berbagai macam data/kode inisialisasi.
Namun, true.c
bukan keseluruhan cerita dan 13KB tampaknya cukup berlebihan jika hanya file itu; kita dapat melihat fungsi yang dipanggil di main()
yang tidak tercantum dalam fungsi eksternal terlihat di elf dengan objdump -T true
; fungsi yang ada di:
- https://github.com/coreutils/gnulib/blob/master/lib/progname.c
- https://github.com/coreutils/gnulib/blob/master/lib/closeout.c
- https://github.com/coreutils/gnulib/blob/master/lib/version-etc.c
Fungsi tambahan tersebut tidak ditautkan secara eksternal di main()
adalah:
- set_program_name()
- close_stdout()
- version_etc()
Jadi kecurigaan pertama saya sebagian benar, sementara perpustakaan menggunakan perpustakaan dinamis, /bin/true
biner itu besar *karena memiliki beberapa pustaka statis disertakan dengannya* (tapi itu bukan satu-satunya penyebab).
Mengkompilasi kode C biasanya tidak itu tidak efisien karena ruang seperti itu tidak terhitung, oleh karena itu kecurigaan awal saya ada sesuatu yang salah.
Ruang ekstra, hampir 90% dari ukuran biner, memang merupakan metadata elf/pustaka tambahan.
Saat menggunakan Hopper untuk membongkar/mendekompilasi biner untuk memahami di mana fungsi berada, dapat dilihat kode biner yang dikompilasi dari fungsi true.c/usage() sebenarnya 833 byte, dan fungsi true.c/main() adalah 225 byte, yang kira-kira sedikit kurang dari 1KB. Logika untuk fungsi versi, yang terkubur dalam pustaka statis, berukuran sekitar 1KB.
Main()+usage()+version()+strings+vars yang dikompilasi sebenarnya hanya menggunakan sekitar 3KB hingga 3,5KB.
Sungguh ironis, utilitas kecil dan sederhana seperti itu menjadi lebih besar karena alasan yang dijelaskan di atas.
pertanyaan terkait:Memahami apa yang dilakukan biner Linux
true.c
main() dengan pemanggilan fungsi yang menyinggung:
int
main (int argc, char **argv)
{
/* Recognize --help or --version only if it's the only command-line
argument. */
if (argc == 2)
{
initialize_main (&argc, &argv);
set_program_name (argv[0]); <-----------
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
atexit (close_stdout); <-----
if (STREQ (argv[1], "--help"))
usage (EXIT_STATUS);
if (STREQ (argv[1], "--version"))
version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS, <------
(char *) NULL);
}
exit (EXIT_STATUS);
}
Ukuran desimal dari berbagai bagian biner:
$ size -A -t true
true :
section size addr
.interp 28 568
.note.ABI-tag 32 596
.note.gnu.build-id 36 628
.gnu.hash 60 664
.dynsym 1416 728
.dynstr 676 2144
.gnu.version 118 2820
.gnu.version_r 96 2944
.rela.dyn 624 3040
.rela.plt 1104 3664
.init 23 4768
.plt 752 4800
.plt.got 8 5552
.text 13017 5568
.fini 9 18588
.rodata 3104 18624
.eh_frame_hdr 572 21728
.eh_frame 2908 22304
.init_array 8 2125160
.fini_array 8 2125168
.jcr 8 2125176
.data.rel.ro 88 2125184
.dynamic 480 2125272
.got 48 2125752
.got.plt 392 2125824
.data 128 2126240
.bss 416 2126368
.gnu_debuglink 52 0
Total 26211
Output dari readelf -S true
$ readelf -S true
There are 30 section headers, starting at offset 0x7368:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000000254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000000274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000298 00000298
000000000000003c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000000002d8 000002d8
0000000000000588 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000000860 00000860
00000000000002a4 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000000b04 00000b04
0000000000000076 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000000b80 00000b80
0000000000000060 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000000be0 00000be0
0000000000000270 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000000e50 00000e50
0000000000000450 0000000000000018 AI 5 25 8
[11] .init PROGBITS 00000000000012a0 000012a0
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000000012c0 000012c0
00000000000002f0 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 00000000000015b0 000015b0
0000000000000008 0000000000000000 AX 0 0 8
[14] .text PROGBITS 00000000000015c0 000015c0
00000000000032d9 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 000000000000489c 0000489c
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000000048c0 000048c0
0000000000000c20 0000000000000000 A 0 0 32
[17] .eh_frame_hdr PROGBITS 00000000000054e0 000054e0
000000000000023c 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000005720 00005720
0000000000000b5c 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000206d68 00006d68
0000000000000008 0000000000000008 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000206d70 00006d70
0000000000000008 0000000000000008 WA 0 0 8
[21] .jcr PROGBITS 0000000000206d78 00006d78
0000000000000008 0000000000000000 WA 0 0 8
[22] .data.rel.ro PROGBITS 0000000000206d80 00006d80
0000000000000058 0000000000000000 WA 0 0 32
[23] .dynamic DYNAMIC 0000000000206dd8 00006dd8
00000000000001e0 0000000000000010 WA 6 0 8
[24] .got PROGBITS 0000000000206fb8 00006fb8
0000000000000030 0000000000000008 WA 0 0 8
[25] .got.plt PROGBITS 0000000000207000 00007000
0000000000000188 0000000000000008 WA 0 0 8
[26] .data PROGBITS 00000000002071a0 000071a0
0000000000000080 0000000000000000 WA 0 0 32
[27] .bss NOBITS 0000000000207220 00007220
00000000000001a0 0000000000000000 WA 0 0 32
[28] .gnu_debuglink PROGBITS 0000000000000000 00007220
0000000000000034 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 00007254
000000000000010f 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
Output dari objdump -T true
(fungsi eksternal ditautkan secara dinamis saat run-time)
$ objdump -T true
true: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __uflow
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 getenv
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 free
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 abort
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __errno_location
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strncmp
0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 _exit
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __fpending
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 textdomain
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fclose
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 bindtextdomain
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 dcgettext
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __ctype_get_mb_cur_max
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strlen
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.4 __stack_chk_fail
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 mbrtowc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strrchr
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 lseek
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 memset
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fscanf
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 close
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 memcmp
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fputs_unlocked
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 calloc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strcmp
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.14 memcpy
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fileno
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 malloc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fflush
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 nl_langinfo
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 ungetc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __freading
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 realloc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fdopen
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 setlocale
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __printf_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 error
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 open
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fseeko
0000000000000000 w D *UND* 0000000000000000 _Jv_RegisterClasses
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_atexit
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 exit
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fwrite
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __fprintf_chk
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 mbsinit
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 iswprint
0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_finalize
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3 __ctype_b_loc
0000000000207228 g DO .bss 0000000000000008 GLIBC_2.2.5 stdout
0000000000207220 g DO .bss 0000000000000008 GLIBC_2.2.5 __progname
0000000000207230 w DO .bss 0000000000000008 GLIBC_2.2.5 program_invocation_name
0000000000207230 g DO .bss 0000000000000008 GLIBC_2.2.5 __progname_full
0000000000207220 w DO .bss 0000000000000008 GLIBC_2.2.5 program_invocation_short_name
0000000000207240 g DO .bss 0000000000000008 GLIBC_2.2.5 stderr
Implementasinya mungkin berasal dari GNU coreutils. Binari ini dikompilasi dari C; tidak ada upaya khusus yang dilakukan untuk membuatnya lebih kecil dari aslinya.
Anda dapat mencoba mengkompilasi implementasi sepele dari true
sendiri, dan Anda akan menyadari ukurannya sudah beberapa KB. Misalnya, di sistem saya:
$ echo 'int main() { return 0; }' | gcc -xc - -o true
$ wc -c true
8136 true
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 mem-parsing flag baris perintah, dll. Sepertinya itu menambah hingga sekitar 20 KB kode.
Untuk referensi, Anda dapat menemukan kode sumbernya di sini:http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c
Mengupasnya ke fungsionalitas inti dan menulis di assembler menghasilkan binari yang jauh lebih kecil.
Binari asli benar/salah ditulis dalam C, yang pada dasarnya menarik berbagai pustaka + referensi simbol. Jika Anda menjalankan readelf -a /bin/true
ini cukup terlihat.
352 byte untuk ELF statis yang dapat dieksekusi (dengan ruang untuk menyimpan beberapa byte dengan mengoptimalkan asm untuk ukuran kode).
$ more true.asm false.asm
::::::::::::::
true.asm
::::::::::::::
global _start
_start:
mov ebx,0
mov eax,1 ; SYS_exit from asm/unistd_32.h
int 0x80 ; The 32-bit ABI is supported in 64-bit code, in kernels compiled with IA-32 emulation
::::::::::::::
false.asm
::::::::::::::
global _start
_start:
mov ebx,1
mov eax,1
int 0x80
$ nasm -f elf64 true.asm && ld -s -o true true.o # -s means strip
$ nasm -f elf64 false.asm && ld -s -o false false.o
$ ll true false
-rwxrwxr-x. 1 steve steve 352 Jan 25 16:03 false
-rwxrwxr-x. 1 steve steve 352 Jan 25 16:03 true
$ ./true ; echo $?
0
$ ./false ; echo $?
1
$
Atau, dengan sedikit pendekatan jahat/cerdik (pujian untuk penguntit), buat header ELF Anda sendiri, turunkan ke 132 127 byte. Kami memasuki wilayah Code Golf di sini.
$ cat true2.asm
BITS 64
org 0x400000 ; _start is at 0x400080 as usual, but the ELF headers come first
ehdr: ; Elf64_Ehdr
db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident
times 8 db 0
dw 2 ; e_type
dw 0x3e ; e_machine
dd 1 ; e_version
dq _start ; e_entry
dq phdr - $$ ; e_phoff
dq 0 ; e_shoff
dd 0 ; e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx
ehdrsize equ $ - ehdr
phdr: ; Elf64_Phdr
dd 1 ; p_type
dd 5 ; p_flags
dq 0 ; p_offset
dq $$ ; p_vaddr
dq $$ ; p_paddr
dq filesize ; p_filesz
dq filesize ; p_memsz
dq 0x1000 ; p_align
phdrsize equ $ - phdr
_start:
xor edi,edi ; int status = 0
; or mov dil,1 for false: high bytes are ignored.
lea eax, [rdi+60] ; rax = 60 = SYS_exit, using a 3-byte instruction: base+disp8 addressing mode
syscall ; native 64-bit system call, works without CONFIG_IA32_EMULATION
; less-golfed version:
; mov edi, 1 ; for false
; mov eax,252 ; SYS_exit_group from asm/unistd_64.h
; syscall
filesize equ $ - $$ ; used earlier in some ELF header fields
$ nasm -f bin -o true2 true2.asm
$ ll true2
-rw-r--r-- 1 peter peter 127 Jan 28 20:08 true2
$ chmod +x true2 ; ./true2 ; echo $?
0
$