Узнав, что некоторые общие команды (такие как read
) на самом деле являются встроенными в Bash (и когда я запускаю их в командной строке, я на самом деле запускаю двухстрочный сценарий оболочки, который просто перенаправляет их во встроенную систему), я посмотрел, нет ли верно для true
и false
.
Ну, они, безусловно, двоичные файлы.
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
и false
есть exit 1
.
sh-4.2$ true
sh-4.2$ echo $?
0
sh-4.2$ false
sh-4.2$ echo $?
1
sh-4.2$
Однако, к моему удивлению, оба файла имеют размер более 28 КБ.
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
linux
reverse-engineering
Kidburla
источник
источник
command -V true
неwhich
. Будет выводиться:true is a shell builtin
для bash.true
иfalse
являются встроенные функции в каждой современной оболочке, но эти системы также включает в себя внешние программные версии из них , потому что это часть стандартной системы , так что программы вызова команд напрямую ( в обход оболочки) может использовать их.which
игнорирует встроенные функции и ищет только внешние команды, поэтому он показывает только внешние команды. Попробуйtype -a true
иtype -a false
взамен.true
иfalse
29KB каждый? Что в исполняемый файл, отличный от кода возврата?»false
: muppetlabs.com/~breadbox/software/tiny/teensy.htmlОтветы:
В прошлом
/bin/true
и/bin/false
в оболочке были собственно скрипты.Например, в PDP / 11 Unix System 7:
В настоящее время, по крайней мере
bash
, вtrue
иfalse
команды реализованы в виде встроенных команд оболочки. Таким образом , не исполняемые бинарные файлы не вызывается по умолчанию, как при использованииfalse
иtrue
директив вbash
командной строке и в сценариях оболочки.Из
bash
источникаbuiltins/mkbuiltins.c
:Также за @meuh комментарии:
Так что можно сказать с высокой степенью уверенности
true
иfalse
исполняемые файлы существуют в основном для того вызваны из других программ .Отныне ответ будет сосредоточен на
/bin/true
двоичном файле изcoreutils
пакета в Debian 9/64 бит. (при/usr/bin/true
запуске RedHat. RedHat и Debian используют обаcoreutils
пакета, проанализировали скомпилированную версию последнего, имеющую его под рукой).Как видно из исходного файла
false.c
,/bin/false
он компилируется с (почти) тем же исходным кодом, что/bin/true
и просто возвращает EXIT_FAILURE (1), поэтому этот ответ может быть применен к обоим двоичным файлам.Как это также может быть подтверждено обоими исполняемыми файлами одинакового размера:
Увы, прямой вопрос к ответу
why are true and false so large?
может быть, потому что нет больше неотложных причин заботиться об их максимальной производительности. Они не важны дляbash
производительности и больше не используютсяbash
(скриптами).Аналогичные комментарии относятся к их размеру, 26 КБ для того оборудования, которое у нас есть в настоящее время, незначительно. Пространство не в почете для типичного сервера / рабочего стола больше, и они даже не потрудились больше использовать тот же двоичный файл для
false
иtrue
, так как он просто развернут дважды в распределениях с использованиемcoreutils
.Сосредоточение, однако, в реальном духе вопроса, почему то, что должно быть таким простым и маленьким, становится таким большим?
Реальное распределение разделов таково,
/bin/true
как показывают эти диаграммы; основной код + данные составляют примерно 3 КБ из двоичного файла размером 26 КБ, что составляет 12% от размера/bin/true
.За эти годы
true
утилита действительно получила больше грязного кода, прежде всего стандартную поддержку--version
и--help
.Тем не менее, это не единственное (главное) обоснование того, что он такой большой, а скорее потому, что он динамически связан (использует разделяемые библиотеки), а также имеет часть универсальной библиотеки, обычно используемой
coreutils
двоичными файлами, связанными как статическая библиотека. Метада для построенияelf
исполняемого файла также составляет значительную часть двоичного файла, являющегося относительно небольшим файлом по современным стандартам.Оставшаяся часть ответа - для объяснения того, как нам удалось построить следующие диаграммы, подробно описывающие состав
/bin/true
исполняемого двоичного файла и как мы пришли к такому выводу.Как говорит @Maks, двоичный файл был скомпилирован из C; Согласно моему комментарию, также подтверждается, что это от coreutils. Мы указываем непосредственно на автора (ов) git https://github.com/wertarbyte/coreutils/blob/master/src/true.c вместо gnu git as @Maks (те же источники, разные репозитории - этот репозиторий был выбран, так как имеет полный источник
coreutils
библиотек)Мы можем увидеть различные строительные блоки
/bin/true
двоичного файла здесь (Debian 9 - 64 бита изcoreutils
):Из тех:
Из 24 КБ около 1 КБ предназначено для исправления 58 внешних функций.
Это все еще оставляет около 23 КБ для остальной части кода. Ниже мы покажем, что фактический код main file - main () + using () составляет около 1 КБ, и объясним, для чего используются другие 22 КБ.
Детализируя далее двоичный файл с помощью
readelf -S true
, мы можем видеть, что, хотя двоичный файл равен 26159 байтам, фактический скомпилированный код составляет 13017 байт, а остальное представляет собой отсортированный код данных / инициализации.Тем
true.c
не менее, это еще не все, и 13KB кажется чрезмерным, если бы это был только этот файл; мы можем видеть функции, вызываемые вmain()
, которые не перечислены во внешних функциях, видимых в эльфе сobjdump -T true
; функции, которые присутствуют в:Те дополнительные функции, которые не связаны внешне в
main()
:Поэтому мое первое подозрение было отчасти верным, хотя библиотека использует динамические библиотеки,
/bin/true
двоичный файл большой *, потому что в него включены некоторые статические библиотеки * (но это не единственная причина).Компиляция кода C не обычно , что неэффективно , имеющие такое пространство неучтенных, следовательно , мое первоначальное подозрение , что что - то неладно.
Дополнительное пространство, почти 90% размера двоичного файла, действительно является дополнительными метаданными библиотек / эльфов.
Используя Hopper для дизассемблирования / декомпиляции двоичного файла, чтобы понять, где находятся функции, можно увидеть, что скомпилированный двоичный код функции true.c / creation () на самом деле составляет 833 байта, а функции true.c / main () - 225. байт, что примерно немного меньше 1 КБ. Логика для функций версий, скрытых в статических библиотеках, составляет около 1 КБ.
Фактически скомпилированные main () + using () + version () + strings + vars занимают всего около 3KB до 3,5KB.
Это действительно иронично, такие маленькие и скромные утилиты стали больше в размерах по причинам, изложенным выше.
связанный вопрос: Понимание того, что делает бинарный файл Linux
true.c
main () с вызывающими ошибочными вызовами функций:Десятичный размер различных разделов двоичного файла:
Выход из
readelf -S true
Вывод
objdump -T true
(внешние функции, динамически связанные во время выполнения)источник
true
илиfalse
с 45-байтовой x86 ELF исполняемый файл, паковать исполняемый код (4 инструкции x86) внутри (! Без поддержки каких - либо параметров командной строки) заголовка ELF программы , Вихревое руководство по созданию действительно исполняемых исполняемых файлов ELF для Linux . (Или немного больше, если вы хотите избежать зависимости от деталей реализации загрузчика Linux ELF: P)Реализация, вероятно, исходит от GNU coreutils. Эти двоичные файлы составлены из C; не было предпринято никаких особых усилий, чтобы сделать их меньше, чем они есть по умолчанию.
Вы можете попробовать скомпилировать тривиальную реализацию
true
самостоятельно, и вы заметите, что ее размер уже несколько КБ. Например, в моей системе:Конечно, ваши двоичные файлы еще больше. Это потому, что они также поддерживают аргументы командной строки. Попробуйте запустить
/usr/bin/true --help
или/usr/bin/true --version
.В дополнение к строковым данным, двоичный файл содержит логику для разбора флагов командной строки и т. Д. Это, очевидно, составляет около 20 КБ кода.
Для справки вы можете найти исходный код здесь: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c
источник
Разъединение их до основной функциональности и запись на ассемблере дает намного меньшие двоичные файлы.
Исходные двоичные файлы true / false написаны на C, который по своей природе тянет в различные библиотеки + ссылки на символы. Если вы запускаете
readelf -a /bin/true
это довольно заметно.352 байта для удаленного статического исполняемого файла ELF (с возможностью сэкономить пару байтов путем оптимизации asm для размера кода).
Или, с помощью немного неприятного / изобретательного подхода ( слава к сталкеру ), создайте свои собственные заголовки ELF, уменьшив его до
132127 байт. Мы входим на территорию Code Golf здесь.источник
int 0x80
32-битный ABI в 64-битном исполняемом файле, что необычно, но поддерживается . Использованиеsyscall
ничего не спасет. Старшие байтыebx
игнорируются, поэтому вы можете использовать 2 байтаmov bl,1
. Или конечноxor ebx,ebx
за ноль . В Linux целочисленные регистры записываются в ноль, так что вы можете простоinc eax
получить 1 = __NR_exit (i386 ABI).true
. (Однако я не вижу простого способа управления менее чем 128 байтамиfalse
, кроме использования 32-битного ABI или использования того факта, что нули Linux регистрируются при запуске процесса, поэтомуmov al,252
(2 байта) работает.push imm8
/pop rdi
Будет также работают вместоlea
настройкиedi=1
, но мы все еще не можем превзойти 32-битный ABI там, где мы могли быmov bl,1
без префикса REXДовольно большой на моем Ubuntu 16.04 тоже. точно такой же размер? Что делает их такими большими?
(Отрывок :)
Ах, есть помощь для истинных и ложных, так что давайте попробуем:
Ничего. Ах, была эта другая строка:
Так что в моей системе это / bin / true, а не / usr / bin / true
Так что есть помощь, есть информация о версии, привязка к библиотеке для интернационализации. Это объясняет большую часть размера, и оболочка использует свою оптимизированную команду в любом случае и большую часть времени.
источник