Después de descubrir que varios comandos comunes (como read
) son realmente Bash builtins (y cuando los ejecuto en el indicador, en realidad estoy ejecutando un script de shell de dos líneas que simplemente reenvía al builtin), estaba buscando para ver si el mismo es cierto para true
y false
.
Bueno, definitivamente son binarios.
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$
Sin embargo, lo que más me sorprendió fue su tamaño. Esperaba que solo fueran unos pocos bytes cada uno, ya true
que básicamente es justo exit 0
y false
es exit 1
.
sh-4.2$ true
sh-4.2$ echo $?
0
sh-4.2$ false
sh-4.2$ echo $?
1
sh-4.2$
Sin embargo, para mi sorpresa, encontré que ambos archivos tienen un tamaño superior a 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$
Entonces mi pregunta es: ¿Por qué son tan grandes? ¿Qué hay en el ejecutable que no sea el código de retorno?
PD: estoy usando RHEL 7.4
linux
reverse-engineering
Kidburla
fuente
fuente
command -V true
nowhich
. Saldrá:true is a shell builtin
para bash.true
yfalse
están incorporados en cada shell moderno, pero los sistemas también incluyen versiones de programas externos porque es parte del sistema estándar para que los programas que invocan comandos directamente (sin pasar por el shell) puedan usarlos.which
ignora los builtins y solo busca comandos externos, por lo que solo le mostró los externos. Intentatype -a true
y en sutype -a false
lugar.true
yfalse
29kb cada uno? ¿Qué hay en el ejecutable que no sea el código de retorno?"false
: muppetlabs.com/~breadbox/software/tiny/teensy.htmlRespuestas:
En el pasado,
/bin/true
y/bin/false
en el shell, en realidad eran guiones.Por ejemplo, en un PDP / 11 Unix System 7:
Hoy en día, al menos en
bash
, los comandostrue
yfalse
se implementan como comandos integrados de shell. Por lo tanto, no se invocan archivos binarios ejecutables de forma predeterminada, tanto cuando se usan las directivasfalse
ytrue
en labash
línea de comandos como dentro de los scripts de shell.De la
bash
fuentebuiltins/mkbuiltins.c
:También según los comentarios de @meuh:
Así que se puede decir con un alto grado de certeza el
true
yfalse
archivos ejecutables existen principalmente para ser llamado desde otros programas .De ahora en adelante, la respuesta se centrará en el
/bin/true
binario delcoreutils
paquete en Debian 9/64 bits. (/usr/bin/true
ejecutando RedHat. RedHat y Debian usan elcoreutils
paquete, analizan la versión compilada de este último y lo tienen más a mano).Como se puede ver en el archivo fuente
false.c
,/bin/false
se compila con (casi) el mismo código fuente que/bin/true
, simplemente devolviendo EXIT_FAILURE (1), por lo que esta respuesta se puede aplicar a ambos binarios.Como también puede ser confirmado por ambos ejecutables que tienen el mismo tamaño:
Por desgracia, la pregunta directa a la respuesta
why are true and false so large?
podría ser, porque ya no hay razones tan apremiantes para preocuparse por su alto rendimiento. No son esenciales para elbash
rendimiento, ya no son utilizados porbash
(secuencias de comandos).Comentarios similares se aplican a su tamaño, 26 KB para el tipo de hardware que tenemos hoy en día es insignificante. El espacio no está en la prima para la típica de un servidor / escritorio más, y que ni siquiera se molestan más a utilizar el mismo binario para
false
ytrue
, a medida que se despliega solo dos veces en el uso de distribucionescoreutils
.Sin embargo, centrándonos en el verdadero espíritu de la pregunta, ¿por qué algo que debería ser tan simple y pequeño, se vuelve tan grande?
La distribución real de las secciones de
/bin/true
es como se muestra en estos gráficos; el código principal + datos asciende a aproximadamente 3 KB de un binario de 26 KB, lo que equivale al 12% del tamaño de/bin/true
.La
true
utilidad obtuvo más código crujiente a lo largo de los años, especialmente el soporte estándar para--version
y--help
.Sin embargo, esa no es la (única) justificación principal para que sea tan grande, sino que, mientras está vinculado dinámicamente (usando bibliotecas compartidas), también tiene parte de una biblioteca genérica comúnmente utilizada por
coreutils
binarios vinculados como una biblioteca estática. La metada para construir unelf
archivo ejecutable también representa una parte significativa del binario, ya que es un archivo relativamente pequeño para los estándares actuales.El resto de la respuesta es para explicar cómo llegamos a construir los siguientes cuadros que detallan la composición del
/bin/true
archivo binario ejecutable y cómo llegamos a esa conclusión.Como dice @Maks, el binario se compiló desde C; Según mi comentario también, también se confirma que es de coreutils. Estamos apuntando directamente al autor (es) git https://github.com/wertarbyte/coreutils/blob/master/src/true.c , en lugar del gnu git como @Maks (mismas fuentes, repositorios diferentes - este repositorio fue seleccionado ya que tiene la fuente completa de las
coreutils
bibliotecas)Podemos ver los diversos bloques de construcción del
/bin/true
binario aquí (Debian 9 - 64 bits decoreutils
):De aquellos:
De los 24 KB, alrededor de 1 KB es para arreglar las 58 funciones externas.
Eso todavía deja alrededor de 23 KB para el resto del código. A continuación, mostraremos a continuación que el archivo principal real - el código main () + use () está compilado alrededor de 1 KB, y explicaremos para qué se usan los otros 22 KB.
Al profundizar más en el binario
readelf -S true
, podemos ver que, si bien el binario tiene 26159 bytes, el código compilado real es 13017 bytes, y el resto es una variedad de datos / código de inicialización.Sin embargo,
true.c
no es toda la historia y 13 KB parece bastante excesivo si solo fuera ese archivo; podemos ver funciones llamadasmain()
que no están listadas en las funciones externas vistas en el duende conobjdump -T true
; funciones que están presentes en:Esas funciones adicionales no vinculadas externamente en
main()
son:Entonces, mi primera sospecha fue en parte correcta, mientras que la biblioteca está usando bibliotecas dinámicas, el
/bin/true
binario es grande * porque tiene algunas bibliotecas estáticas incluidas * (pero esa no es la única causa).Compilar el código C no suele ser tan ineficiente para no tener en cuenta ese espacio, de ahí mi sospecha inicial de que algo andaba mal.
El espacio extra, casi el 90% del tamaño del binario, es de hecho bibliotecas / metadatos extra.
Al usar Hopper para desensamblar / descompilar el binario para comprender dónde están las funciones, se puede ver que el código binario compilado de la función true.c / use () es en realidad 833 bytes, y de la función true.c / main () es 225 bytes, que es aproximadamente un poco menos de 1 KB. La lógica para las funciones de versión, que está enterrada en las bibliotecas estáticas, es de alrededor de 1 KB.
El principal () + uso () + versión () + cadenas + vars compilados reales solo están usando alrededor de 3 KB a 3.5 KB.
De hecho, es irónico, tales utilidades pequeñas y humildes se han vuelto más grandes por las razones explicadas anteriormente.
pregunta relacionada: Comprender qué está haciendo un binario de Linux
true.c
main () con la función infractora invoca:El tamaño decimal de las diversas secciones del binario:
Salida de
readelf -S true
Salida de
objdump -T true
(funciones externas vinculadas dinámicamente en tiempo de ejecución)fuente
true
ofalse
con un ejecutable ELF x86 de 45 bytes, empaquetando el código ejecutable (4 instrucciones x86) dentro del encabezado del programa ELF (¡sin soporte para ninguna opción de línea de comandos!) . Un tutorial de Whirlwind sobre la creación de ejecutables ELF realmente para Teensy para Linux . (O un poco más grande si desea evitar depender de los detalles de implementación del cargador Linux ELF: P)La implementación probablemente proviene de GNU coreutils. Estos binarios se compilan a partir de C; no se ha hecho ningún esfuerzo particular para hacerlos más pequeños de lo que son por defecto.
Podrías intentar compilar la implementación trivial de
true
ti mismo, y notarás que ya tiene unos KB de tamaño. Por ejemplo, en mi sistema:Por supuesto, tus binarios son aún más grandes. Eso es porque también admiten argumentos de línea de comandos. Intenta correr
/usr/bin/true --help
o/usr/bin/true --version
.Además de los datos de la cadena, el binario incluye lógica para analizar banderas de línea de comando, etc. Al parecer, esto agrega hasta 20 KB de código.
Como referencia, puede encontrar el código fuente aquí: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c
fuente
Reducirlos a la funcionalidad principal y escribir en ensamblador produce binarios mucho más pequeños.
Los binarios originales verdadero / falso se escriben en C, que por su naturaleza extrae varias referencias de biblioteca + símbolo. Si ejecuta
readelf -a /bin/true
esto es bastante notable.352 bytes para un ejecutable estático ELF eliminado (con espacio para guardar un par de bytes al optimizar el asm para el tamaño del código).
O, con un enfoque un poco desagradable / ingenioso (felicitaciones a stalkr ), cree sus propios encabezados ELF, reduciéndolo a
132127 bytes. Estamos entrando en el territorio de Code Golf aquí.fuente
int 0x80
ABI de 32 bits en un ejecutable de 64 bits, lo cual es inusual pero compatible . Usarsyscall
no te ahorraría nada. Seebx
ignoran los bytes altos de , por lo que podría usar 2 bytesmov bl,1
. O por supuestoxor ebx,ebx
para cero . Linux ensu registros enteros a cero, por lo que podrían simplementeinc eax
para obtener 1 = __NR_exit (i386 ABI).true
. (No veo una manera fácil de manejar menos de 128 bytes parafalse
, sin embargo, que no sean el uso de 32 bits ABI o aprovechando el hecho de que Linux pone a cero registros en el inicio del proceso, por lo quemov al,252
(2 bytes) funciona.push imm8
/pop rdi
Haría También funciona en lugar delea
estableceredi=1
, pero todavía no podemos superar el ABI de 32 bits donde podríamosmov bl,1
sin un prefijo REX.Bastante grande en mi Ubuntu 16.04 también. exactamente del mismo tamaño? ¿Qué los hace tan grandes?
(extracto:)
Ah, hay ayuda para lo verdadero y lo falso, así que probémoslo:
Nada. Ah, había esta otra línea:
Entonces, en mi sistema, es / bin / true, no / usr / bin / true
Por lo tanto, hay ayuda, hay información de versión, vinculante a una biblioteca para la internacionalización. Esto explica gran parte del tamaño, y el shell utiliza su comando optimizado de todos modos y la mayoría de las veces.
fuente