ทำไมขนาดใหญ่จริงและเท็จ?


80

หลังจากพบว่าคำสั่งทั่วไปหลายอย่าง (เช่นread) จริงๆแล้วคือ Bash builtins (และเมื่อรันคำสั่งที่พรอมต์ฉันกำลังใช้เชลล์สคริปต์สองบรรทัดซึ่งส่งต่อไปยัง builtin) ฉันกำลังดูว่าเหมือนกันหรือไม่ เป็นจริงสำหรับและtruefalse

พวกมันเป็นไบนารีอย่างแน่นอน

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$

อย่างไรก็ตามสิ่งที่ฉันพบว่าน่าประหลาดใจที่สุดคือขนาดของมัน ผมคาดว่าพวกเขาจะมีเพียงไม่กี่ไบต์แต่ละเป็นtrueเป็นเพียงexit 0และเป็นfalseexit 1

sh-4.2$ true
sh-4.2$ echo $?
0
sh-4.2$ false
sh-4.2$ echo $?
1
sh-4.2$

อย่างไรก็ตามฉันรู้สึกประหลาดใจที่ไฟล์ทั้งสองมีขนาดเกินกว่า 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$

ดังนั้นคำถามของฉันคือทำไมพวกเขาใหญ่ สิ่งใดในปฏิบัติการที่นอกเหนือจากรหัสส่งคืน?

PS: ฉันใช้ RHEL 7.4


9
คุณควรใช้ไม่ได้command -V true whichมันจะออก: true is a shell builtinสำหรับทุบตี
meuh

32
trueและfalse เป็น builtins ในทุก ๆ เชลล์ที่ทันสมัย ​​แต่ระบบยังรวมถึงรุ่นของโปรแกรมภายนอกเพราะเป็นส่วนหนึ่งของระบบมาตรฐานเพื่อให้โปรแกรมที่เรียกใช้คำสั่งโดยตรง (ผ่านเชลล์) สามารถใช้งานได้ whichละเว้น builtins และค้นหาคำสั่งภายนอกเท่านั้นซึ่งเป็นสาเหตุที่แสดงให้คุณเห็นคำสั่งภายนอกเท่านั้น ลองtype -a trueและtype -a falseแทน
mtraceur

74
เป็นเรื่องน่าขันที่คุณเขียนคำถามยาว ๆ แบบนี้เพื่อพูดว่า "ทำไมtrueและfalseแต่ละตัวมีขนาด 29kb? อะไรคือสิ่งที่สามารถรันได้นอกเหนือจากโค้ดส่งคืน"
David Richerby

7
ยูนิกซ์รุ่นแรกบางรุ่นเพิ่งมีไฟล์ว่างสำหรับจริงเนื่องจากเป็นโปรแกรม sh ที่ถูกต้องที่จะส่งคืนรหัสทางออก 0 ฉันหวังว่าฉันจะหาบทความที่ฉันอ่านเมื่อหลายปีก่อนเกี่ยวกับประวัติของยูทิลิตีจริงจากไฟล์ว่างเปล่าไปยัง ความโหดร้ายมันคือวันนี้ แต่สิ่งที่ฉันสามารถหาคือ: trillian.mit.edu/~jc/humor/ATT_Copyright_true.html trillian.mit.php
Philip

9
ภาระหน้าที่ - การนำไปใช้ที่เล็กที่สุดของfalse: muppetlabs.com/~breadbox/software/tiny/teensy.html
d33tah

คำตอบ:


117

ในอดีต/bin/trueและ/bin/falseในเปลือกเป็นสคริปต์จริง

ตัวอย่างเช่นใน PDP / 11 Unix System 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
$  

ปัจจุบันอย่างน้อยในbashการtrueและfalseคำสั่งที่จะดำเนินการในตัวคำสั่งเชลล์ ดังนั้นจะไม่มีการเรียกใช้ไฟล์ไบนารีที่เรียกใช้งานโดยค่าเริ่มต้นทั้งเมื่อใช้falseและtrueคำสั่งในbashบรรทัดคำสั่งและภายในเชลล์สคริปต์

จากbashแหล่งที่มาbuiltins/mkbuiltins.c:

ถ่าน * posix_builtins [] =
    {
      "alias", "bg", "cd", "command", "** false **", "fc", "fg", "getopts", "งาน",
      "kill", "newgrp", "pwd", "read", "** true **", "umask", "unalias", "รอ"
      (ถ่าน *) NULL
    };

ยังมีต่อความคิดเห็น @meuh:

$ command -V true false
true is a shell builtin
false is a shell builtin

ดังนั้นจึงอาจกล่าวได้มีระดับสูงของความเชื่อมั่นtrueและfalseไฟล์ปฏิบัติการที่มีอยู่ส่วนใหญ่สำหรับการถูกเรียกจากโปรแกรมอื่น

จากนี้ไปคำตอบจะมุ่งเน้นไปที่/bin/trueไบนารีจากcoreutilsแพคเกจใน Debian 9/64 บิต ( /usr/bin/trueใช้ RedHat. RedHat และ Debian ใช้ทั้ง coreutilsแพ็คเกจวิเคราะห์รุ่นที่คอมไพล์ของหลังซึ่งมีอยู่ในมือมากกว่า)

ในขณะที่มันสามารถเห็นได้ในแฟ้มแหล่งที่มาfalse.c, /bin/falseจะรวบรวมกับ (เกือบ) รหัสที่มาเช่นเดียวกับ/bin/trueเพียงแค่กลับ EXIT_FAILURE (1) แทนเพื่อให้คำตอบนี้สามารถนำมาใช้สำหรับทั้งไบนารี

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

เนื่องจากยังสามารถยืนยันได้โดยโปรแกรมเรียกทำงานทั้งสองที่มีขนาดเท่ากัน:

$ 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

อนิจจาคำถามตรงไปยังคำตอบwhy are true and false so large?อาจเป็นเพราะไม่มีอีกต่อไปดังนั้นกดเหตุผลที่จะดูแลเกี่ยวกับประสิทธิภาพสูงสุดของพวกเขา สิ่งเหล่านี้ไม่จำเป็นสำหรับbashการแสดง แต่ไม่ถูกใช้โดยbash(การเขียนสคริปต์) อีกต่อไป

ความเห็นที่คล้ายกันนั้นใช้กับขนาดของพวกเขา 26KB สำหรับประเภทของฮาร์ดแวร์ที่เรามีอยู่ทุกวันนี้นั้นไม่มีนัยสำคัญ พื้นที่ไม่ได้อยู่ที่พรีเมี่ยมสำหรับเซิร์ฟเวอร์ทั่วไป / สก์ท็อปอีกต่อไปและพวกเขาไม่ได้รำคาญอีกต่อไปที่จะใช้ไบนารีเดียวกันfalseและขณะที่มันถูกนำไปใช้เพียงแค่สองครั้งในการกระจายการใช้truecoreutils

อย่างไรก็ตามในการมุ่งเน้นไปที่วิญญาณที่แท้จริงของคำถามทำไมสิ่งที่ควรจะเรียบง่ายและเล็กกลายเป็นเรื่องใหญ่?

การแจกแจงที่แท้จริงของส่วนต่าง ๆ/bin/trueนั้นเป็นไปตามแผนภูมิเหล่านี้แสดง; รหัส + ข้อมูลหลักจำนวนประมาณ 3KB ออกจากไบนารี 26 KB ซึ่งจะมีจำนวน 12% /bin/trueของขนาดของ

trueยูทิลิตี้มีรหัสแน่นอน cruft มากขึ้นกว่าปีที่สะดุดตาที่สุดสนับสนุนมาตรฐานและ--version--help

อย่างไรก็ตามมันไม่ได้เป็นเหตุผลหลัก (เท่านั้น) สำหรับมันใหญ่ แต่ในขณะที่ถูกเชื่อมโยงแบบไดนามิก (ใช้ libs ที่ใช้ร่วมกัน) ยังมีส่วนหนึ่งของห้องสมุดทั่วไปที่ใช้โดยcoreutilsไบนารีที่เชื่อมโยงกันเป็นห้องสมุดแบบคงที่ เมตาดาสำหรับการสร้างelfไฟล์ที่เรียกใช้งานได้นั้นมีส่วนสำคัญของไบนารีด้วยเช่นกันเพราะมันเป็นไฟล์ที่ค่อนข้างเล็กตามมาตรฐานในปัจจุบัน

ส่วนที่เหลือของคำตอบสำหรับการอธิบายวิธีที่เราได้สร้างแผนภูมิต่อไปนี้โดยมีรายละเอียดองค์ประกอบของ/bin/trueไฟล์ไบนารีที่ปฏิบัติการได้และวิธีที่เรามาถึงข้อสรุปนั้น

bintrue bintrue2

ตามที่ @Maks พูดว่าไบนารีถูกรวบรวมจาก C; ตามความคิดเห็นของฉันมันยังได้รับการยืนยันมันมาจาก coreutils เรากำลังชี้ไปที่ผู้เขียน git https://github.com/wertarbyte/coreutils/blob/master/src/true.cแทนที่จะเป็น gnu git เป็น @Maks (แหล่งเดียวกันที่เก็บที่ต่างกัน - ที่เก็บนี้ ถูกเลือกเนื่องจากมีcoreutilsไลบรารีเต็มรูปแบบ)

เราสามารถเห็นหน่วยการสร้างต่างๆของ/bin/trueไบนารีที่นี่ (Debian 9 - 64 บิตจาก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

ของบรรดา:

  • ข้อความ (รหัสปกติ) ประมาณ 24KB
  • data (ตัวแปรเริ่มต้น, สตริงส่วนใหญ่) อยู่ที่ประมาณ 1KB
  • bss (ข้อมูลที่ไม่ได้กำหนดค่าเริ่มต้น) 0.5KB

จาก 24KB ประมาณ 1KB สำหรับแก้ไขฟังก์ชั่นภายนอก 58 ตัว

ที่ยังเหลือประมาณ 23KB ประมาณรหัสที่เหลือ เราจะแสดงให้เห็นว่าไฟล์หลักที่แท้จริง - main () + การใช้งาน () รหัสคือประมาณ 1KB รวบรวมและอธิบายสิ่งที่ 22KB อื่น ๆ ที่ใช้สำหรับ

การเจาะลึกลงไปอีกไบนารีด้วยreadelf -S trueเราจะเห็นได้ว่าในขณะที่ไบนารีคือ 26159 ไบต์รหัสที่คอมไพล์จริงคือ 13017 ไบต์และส่วนที่เหลือเป็นข้อมูล / รหัสเริ่มต้นที่แตกต่างกัน

อย่างไรก็ตามtrue.cไม่ใช่เรื่องราวทั้งหมดและดูเหมือนว่า 13KB จะมากเกินไปหากไฟล์นั้นเป็นเพียงไฟล์นั้นเท่านั้น เราสามารถเห็นฟังก์ชั่นที่เรียกว่าmain()ที่ไม่ได้อยู่ในรายการฟังก์ชั่นภายนอกที่เห็นในเอลฟ์ด้วยobjdump -T true; ฟังก์ชั่นที่มีอยู่ใน:

ฟังก์ชั่นพิเศษเหล่านั้นไม่ได้เชื่อมโยงภายนอกmain()คือ:

  • set_program_name ()
  • close_stdout ()
  • version_etc ()

ดังนั้นความสงสัยครั้งแรกของฉันจึงถูกต้องบางส่วนในขณะที่ห้องสมุดใช้ไลบรารีแบบไดนามิก/bin/trueไบนารีมีขนาดใหญ่ * เนื่องจากมีห้องสมุดแบบคงที่บางส่วนที่รวมอยู่ในนั้น * (แต่นั่นไม่ใช่สาเหตุเดียว)

การคอมไพล์รหัส C ไม่ใช่ว่าไม่มีประสิทธิภาพในการมีช่องว่างดังกล่าวดังนั้นฉันสงสัยว่ามีบางอย่างผิดปกติ

พื้นที่พิเศษเกือบ 90% ของขนาดของไบนารีนั้นเป็นเมตาดาต้า / ไลบรารีของเอลฟ์พิเศษ

ในขณะที่ใช้สิ่งที่กระโดดเพื่อแยกส่วน / แยกส่วนไบนารีเพื่อทำความเข้าใจว่าฟังก์ชั่นอยู่ที่ไหนจะเห็นรหัสไบนารีที่คอมไพล์ของฟังก์ชั่น true.c / usage () เป็นจริง 833 ไบต์และฟังก์ชั่น true.c / main () คือ 225 ไบต์ซึ่งประมาณน้อยกว่า 1KB เล็กน้อย ตรรกะสำหรับฟังก์ชั่นเวอร์ชันซึ่งถูกฝังอยู่ในห้องสมุดคงอยู่ประมาณ 1KB

main main () + compiled ที่ใช้จริง () + version () + strings + vars นั้นใช้งานได้เพียงประมาณ 3KB ถึง 3.5KB

เป็นเรื่องน่าขันจริง ๆ ที่สาธารณูปโภคขนาดเล็กและต่ำต้อยดังกล่าวได้มีขนาดใหญ่ขึ้นด้วยเหตุผลที่อธิบายไว้ข้างต้น

คำถามที่เกี่ยวข้อง: ทำความเข้าใจกับสิ่งที่ไบนารี Linux กำลังทำอยู่

true.c main () ด้วยการเรียกฟังก์ชันที่ละเมิด:

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);
}

ขนาดทศนิยมของส่วนต่าง ๆ ของไบนารี:

$ 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

ผลผลิตของ 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)

เอาท์พุทของobjdump -T true(ฟังก์ชั่นภายนอกเชื่อมโยงแบบไดนามิกในเวลาทำงาน)

$ 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

5
เมื่อไม่นานมานี้การเขียนโปรแกรมด้วยไมโครคอนโทรลเลอร์ 64kB + 2kB นั้น 28kB ดูเหมือนจะไม่เล็กเลย ..
Barleyman

1
@Barleyman คุณมี OpenWRT, yocto, uClinux, uclib, busybox, microcoreutils และโซลูชั่นอื่น ๆ สำหรับสภาพแวดล้อมแบบนั้น แก้ไขโพสต์ด้วยความกังวลของคุณ
Rui F Ribeiro

4
@Barleyman: หากคุณปรับขนาดของไบนารีที่สามารถเรียกใช้งานได้คุณสามารถนำไปปฏิบัติtrueหรือfalseด้วยปฏิบัติการขนาด45 ไบต์ x86 ELF, บรรจุรหัสปฏิบัติการ (คำสั่ง 4 x86) ภายในส่วนหัวโปรแกรม ELF (โดยไม่สนับสนุนตัวเลือกบรรทัดคำสั่งใด ๆ !) . ลมกรดสอนในการสร้างจริงๆ teensy ELF Executables สำหรับลินุกซ์ (หรือขนาดใหญ่กว่าเล็กน้อยถ้าคุณต้องการที่จะหลีกเลี่ยงการขึ้นอยู่กับลินุกซ์เอลฟ์รายละเอียดการดำเนินการตัก: P)
ปีเตอร์ Cordes

3
ไม่จริงไม่ ยกตัวอย่างเช่น Yocto สามารถยัดเยียดให้มีขนาดน้อยกว่าเมกะไบต์ซึ่งมีขนาดเล็กกว่า 64kB .. ในอุปกรณ์ประเภทนี้คุณสามารถใช้ RTOS บางชนิดด้วยการจัดการกระบวนการ / หน่วยความจำขั้นพื้นฐาน แต่แม้จะหนักเกินไป ฉันเขียนระบบมัลติเธรดแบบร่วมมืออย่างง่ายและใช้การป้องกันหน่วยความจำในตัวเพื่อป้องกันรหัสจากการเขียนทับ ทุกคนบอกว่าเฟิร์มแวร์ใช้ 55kB บางตัวในขณะนี้ดังนั้นจึงไม่มีพื้นที่มากเกินไปสำหรับค่าใช้จ่ายเพิ่มเติม 2kB ที่ดูน่าทึ่งเหล่านั้นดูตาราง ..
Barleyman

2
@PeterCordes แน่นอน แต่คุณต้องการทรัพยากรเพิ่มขึ้นอีกสองเท่าก่อนที่ Linux จะทำงานได้ สำหรับสิ่งที่คุ้มค่า C ++ ก็ไม่สามารถใช้งานได้ในสภาพแวดล้อมนั้น ไม่ใช่ห้องสมุดมาตรฐานเลย Iostream อยู่ที่ประมาณ 200kB เป็นต้น
Barleyman

34

การใช้งานอาจมาจาก coreutils ของ GNU ไบนารีเหล่านี้รวบรวมจาก C; ไม่มีความพยายามพิเศษที่จะทำให้พวกเขามีขนาดเล็กกว่าพวกเขาโดยค่าเริ่มต้น

คุณสามารถลองรวบรวมการใช้งานเล็กน้อยของtrueตัวคุณเองและคุณจะสังเกตเห็นว่ามีขนาดไม่กี่กิโลไบต์ ตัวอย่างเช่นในระบบของฉัน:

$ echo 'int main() { return 0; }' | gcc -xc - -o true
$ wc -c true
8136 true

แน่นอนไบนารีของคุณนั้นยิ่งใหญ่กว่า นั่นเป็นเพราะพวกเขายังสนับสนุนอาร์กิวเมนต์บรรทัดคำสั่ง ลองใช้หรือ/usr/bin/true --help/usr/bin/true --version

นอกเหนือจากข้อมูลสตริงแล้วไบนารียังมีตรรกะในการแยกวิเคราะห์แฟล็กบรรทัดคำสั่ง ฯลฯ ซึ่งจะเพิ่มโค้ดประมาณ 20 KB

สำหรับการอ้างอิงคุณสามารถค้นหาซอร์สโค้ดได้ที่นี่: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c


2
FYI ผมก็บ่นเกี่ยวกับการใช้งานเหล่านี้ coreutils เกี่ยวกับการติดตามข้อผิดพลาดของพวกเขา แต่ไม่มีโอกาสที่จะได้รับมันคงlists.gnu.org/archive/html/bug-coreutils/2016-03/msg00040.html
rudimeier

7
มันไม่ใช่ตรรกะสำหรับการขัดแย้ง C ไม่ใช่ว่าไม่มีประสิทธิภาพ ... เป็นไลบรารีแบบอินไลน์ / งานการดูแลบ้าน ลองดูที่คำตอบของฉันสำหรับรายละเอียดเต็มไปด้วยเลือด
Rui F Ribeiro

8
นี่เป็นสิ่งที่ทำให้เข้าใจผิดเพราะมันแสดงให้เห็นว่ารหัสเครื่องที่คอมไพล์แล้ว (จาก C หรืออย่างอื่น) เป็นสิ่งที่ต้องใช้พื้นที่จำนวนมาก - ค่าโสหุ้ยขนาดจริงนั้นมีส่วนเกี่ยวข้องกับไลบรารี C / runtime runtime จำนวนมหาศาล เพื่อทำงานร่วมกับไลบรารี C (glibc ยกเว้นว่าคุณเคยได้ยินว่าระบบของคุณใช้อย่างอื่น) และในระดับที่น้อยกว่าส่วนหัว / ข้อมูลเมตาของ ELF (ซึ่งส่วนใหญ่ไม่จำเป็นอย่างเคร่งครัด แต่ถือว่าคุ้มค่าพอสมควร เพื่อรวมไว้ในบิลด์เริ่มต้น)
mtraceur

2
main () + การใช้งานจริง () + สตริงในทั้งสองฟังก์ชั่นอยู่ที่ประมาณ 2KB ไม่ใช่ 20KB
Rui F Ribeiro

2
@JdeBP ลอจิกสำหรับ --version / version funtions 1KB, --usage / - help 833 bytes, main () 225 ไบต์และข้อมูลคงที่ทั้งหมดของไบนารีคือ 1KB
Rui F Ribeiro

25

การแยกมันออกเป็นฟังก์ชั่นหลักและการเขียนในแอสเซมเบลอร์จะให้ไบนารีที่เล็กลงกว่าเดิม

ไบนารีจริง / เท็จต้นฉบับถูกเขียนใน C ซึ่งโดยธรรมชาติแล้วมันจะดึงในการอ้างอิงสัญลักษณ์ + ไลบรารีต่างๆ หากคุณเรียกใช้readelf -a /bin/trueสิ่งนี้จะค่อนข้างชัดเจน

352ไบต์สำหรับเอลฟ์แบบคงที่ที่ถูกปล้น (ด้วยห้องเพื่อบันทึกสองสามไบต์โดยการปรับ asm สำหรับขนาดรหัส)

$ 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
$

หรือด้วยวิธีการที่น่ารังเกียจ / แยบยล (ความรุ่งโรจน์สู่stalkr ) สร้างส่วนหัวเอลฟ์ของคุณเองทำให้ได้ถึง132 127ไบต์ เรากำลังเข้าสู่ดินแดนCode Golfที่นี่

$ 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
$

2
ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
terdon

2
ดูการเขียนที่ยอดเยี่ยมเช่นนี้: muppetlabs.com/~breadbox/software/tiny/teensy.html
mic_e

3
คุณกำลังใช้int 0x8032 บิต ABI ในปฏิบัติการ 64 บิตซึ่งเป็นเรื่องผิดปกติ แต่ได้รับการสนับสนุน การใช้syscallจะไม่ช่วยอะไรคุณเลย ไบต์สูงของebxจะถูกละเว้นเพื่อให้คุณสามารถใช้ mov bl,12 หรือของหลักสูตรสำหรับศูนย์xor ebx,ebx ลินุกซ์ในไฟล์ลงทะเบียนจำนวนเต็มศูนย์เพื่อให้คุณสามารถเพียงแค่inc eaxจะได้รับ 1 = __NR_exit (i386 ABI)
Peter Cordes

1
ฉันปรับปรุงรหัสในตัวอย่างแข็งแรงเล่นกอล์ฟของคุณเพื่อใช้ 64 บิต ABI และกอล์ฟมันลงไป 127 trueไบต์สำหรับ (ผมไม่เห็นว่าเป็นวิธีที่ง่ายในการจัดการน้อยกว่า 128 ไบต์สำหรับfalseแม้ว่าอื่น ๆ กว่าการใช้ 32 บิต ABI หรือการใช้ประโยชน์จากความจริงที่ว่าลินุกซ์ศูนย์ลงทะเบียนในการเริ่มต้นกระบวนการดังนั้นmov al,252(2 ไบต์) ทำงาน. push imm8/ pop rdiจะ ยังทำงานแทนleaสำหรับการตั้งค่าedi=1แต่เรายังไม่สามารถชนะ 32 บิต ABI ที่เราสามารถทำได้mov bl,1โดยไม่ต้องมีคำนำหน้า REX.
ปีเตอร์ Cordes

2
l $(which true false)
-rwxr-xr-x 1 root root 27280 Mär  2  2017 /bin/false
-rwxr-xr-x 1 root root 27280 Mär  2  2017 /bin/true

ค่อนข้างใหญ่ใน Ubuntu 16.04 ของฉันด้วย ขนาดเท่ากันทุกประการ อะไรทำให้พวกเขาใหญ่มาก

strings $(which true)

(ตัดตอนมา :)

Usage: %s [ignored command line arguments]
  or:  %s OPTION
Exit with a status code indicating success.
      --help     display this help and exit
      --version  output version information and exit
NOTE: your shell may have its own version of %s, which usually supersedes
the version described here.  Please refer to your shell's documentation
for details about the options it supports.
http://www.gnu.org/software/coreutils/
Report %s translation bugs to <http://translationproject.org/team/>
Full documentation at: <%s%s>
or available locally via: info '(coreutils) %s%s'

อามีความช่วยเหลือสำหรับเรื่องจริงและเท็จดังนั้นลองทำดู:

true --help 
true --version
#

ไม่มีอะไร อ่ามีอีกบรรทัดนี้:

NOTE: your shell may have its own version of %s, which usually supersedes
    the version described here.

ดังนั้นในระบบของฉันมัน / bin / จริงไม่ใช่ / usr / bin / จริง

/bin/true --version
true (GNU coreutils) 8.25
Copyright © 2016 Free Software Foundation, Inc.
Lizenz GPLv3+: GNU GPL Version 3 oder höher <http://gnu.org/licenses/gpl.html>
Dies ist freie Software: Sie können sie ändern und weitergeben.
Es gibt keinerlei Garantien, soweit wie es das Gesetz erlaubt.

Geschrieben von Jim Meyering.

LANG=C /bin/true --version
true (GNU coreutils) 8.25
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Jim Meyering.

ดังนั้นจึงมีความช่วยเหลือมีข้อมูลรุ่นเชื่อมโยงกับไลบรารีเพื่อความเป็นสากล สิ่งนี้จะอธิบายขนาดส่วนใหญ่และเชลล์ใช้คำสั่งที่ปรับให้เหมาะสมแล้วและส่วนใหญ่


รวมถึงไลบรารีแบบคงที่และครึ่งหนึ่งของขนาดของไบนารีสำหรับ elf metada ดูคำตอบของฉัน
Rui F Ribeiro
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.