Nota perev. : El autor de este material es arquitecto en Barclays y el entusiasta británico de código abierto Ian Miell. Su objetivo es hacer una imagen conveniente de Docker (con un binario "dormido"), que no es necesario descargar, sino simplemente copiar mediante copiar y pegar. Mediante prueba, error y experimentación con el código Assembler, alcanza el objetivo preparando una imagen de menos de un kilobyte de tamaño.
Aquí está (codificado en base64)H4sICIa2A1sCA2IA7Vrrbts2FFYL7M9+7QUGGNyfDYhtkuJFFLAhWZOhBYJmaLMOWBAEFC+xVlkyJLpYEBjdY+0l+k6jfGvqtkEWp2qD8TMg8vAqnsNzDg9lQhhmEjHDhY4zgWJBBUQJ5ZnCGAubMUQMyhJqoRRMJxYbo7Q2CedYxlQO/myqMroeEEHICIngApspxohEKI4h5DHmGEUQQw7jqAejDjBtnKz9q2w7zubi7gkugazVKHdGuWltQArkWDMCdoCqSpufg/QSPK4aV8pxW+nL96uxzMu39G+NqRe5PeekGj13Oi9BamXRmCtl1dS9X2jqel147C7W+aOJKd8dZ04dlcqsSw7KVyA9Ab/uHT/+cTht6mFRKVkMmywv0yv0mnxbMc8sSP8Apzvg0ViDtJwWxQ54Mpbny5W9qIrp2DSrmt+r+mVenu/ny+UelK6+mFR56VYtjsqfp3mxHupQZqZYdp/NGeo850x99r9j7QloyWEz8kvpK//47vuymvzQ29vf79m8MKnIaIa8bUmwRdByw6TKREIoIzE3xBrjrY7MGDUilomQ3GrNrFaIKqSZ4lkvL3tD12sn/IQCrI10xtcC7C1kH9I+xseQpYilRAwoZ5AI9IcfWFfqpRfzK1M3eeUZDRAfQDGAfc/jHTDKG1fVXiInlzcfctnwLPP9Vszs9VXvUzFy5jlZV5WzTbtN3cWkZWkhL/yS2gXm1p7lumkl24wkpv51FbYcU0EZy7SV0ucEZowkiCjvLbAVikCaGUqhyjT0c0Lj/YrElmmSWANOZ7MooHPwRCiLRaJEzBXKFGTCy49lUHNKjEigVdD6H4uTzPj9wzDCSawU0TQT2ujhjVwjgZzSj/n/eX7D/xPm/T8N/v/Ll/+Lg2fPnxw93eL85xFvyB9Rn4TzXwdAAxiMYLD/t9f/7eM/xDja1P+YBf3vKP7L2+PnttsA/IfjcQiE7nkgdH18Ey4O7pjdH7ygmX0p9n8eFA5aG3pb+0/eP/9jzFmw/13AdTBHK3/OPx7/Ic4X8qecQ9K244QG/98JXh8c/vLwwYM1/TD6KWqpv6LdOb37gT67URKterTpVxu1V9PXq3lW1d8skn++9Y83f4cDeEBAQMBnwliWuTWNu8l33G38/3X3fzGk79wFQ4S4Lwr+vwOcXIJHy4ANkLv4L4APcJ6ZSXUsz+efh1xaSOf3VxstHS6+H/nSu4s6wOns9OugxrdG7WXV5K6qc9NEn0n/ESab+s9o0P+O7v9ce1WzVNI7uAiczYI6BgQEBNwD/AvqV/+XACoAAA==
¿Cómo llegué a esto?
Un colega mostró una vez una imagen de Docker que utilizó para probar los grupos de Kubernetes. No hizo nada: solo corrió y esperó a que lo mataras.
“¡Mira, solo se necesitan 700 kilobytes! ¡Descarga realmente rápida! ”
Y luego sentí curiosidad por la mínima imagen de Docker que podía crear. Quería obtener uno que pudiera codificarse en base64 y enviarse literalmente a cualquier lugar con simple copiar y pegar. Dado que la imagen de Docker es solo un archivo tar y el archivo tar es solo un archivo, todo debería funcionar.
Pequeño binario
En primer lugar, necesitaba un binario Linux muy pequeño que no hace nada. Se necesita un poco de magia, y aquí hay dos artículos maravillosos, informativos y legibles sobre la creación de pequeños ejecutables:
No necesitaba "Hello World", sino un programa que simplemente duerme y se ejecuta en x86_64. Comencé con un ejemplo del primer artículo:
SECTION .data msg: db "Hi World",10 len: equ $-msg SECTION .text global _start _start: mov edx,len mov ecx,msg mov ebx,1 mov eax,4 int 0x80 mov ebx,0 mov eax,1 int 0x80
Ejecutar:
nasm -f elf64 hw.asm -o hw.o ld hw.o -o hw strip -s hw
Resulta un binario de
504 bytes .
Pero aún así, no es necesario usar "Hello World" ... Primero, descubrí que las secciones
.data
o
.text
son innecesarias y no se requiere la carga de datos. Además, la mitad superior de la sección
_start
se ocupa de la salida de texto. Como resultado, probé el siguiente código:
global _start _start: mov ebx,0 mov eax,1 int 0x80
Y se compiló ya en
352 bytes .
Pero este no es el resultado deseado, porque el programa simplemente termina su trabajo y lo necesitamos para dormir
a . Como resultado de una investigación adicional, resultó que el comando
mov eax
llena el registro del procesador con el número de llamada del sistema Linux correspondiente, e
int 0x80
realiza la llamada en sí. Esto se describe con más detalle
aquí .
Y
aquí encontré la lista deseada. Syscall 1 es
exit
, y lo que necesitamos es
syscall 29:pause
. El siguiente programa resultó:
global _start _start: mov eax, 29 int 0x80
Guardamos otros 8 bytes: la compilación produjo un resultado de
344 bytes , y ahora es un binario adecuado para nosotros, que no hace nada y espera una señal.
Excavando en hexes
Es hora de obtener una motosierra y tratar con el binario ... Para esto, utilicé
hexer , que es esencialmente vim para archivos binarios con la capacidad de editar directamente hexes. Después de largos experimentos, obtuve de esto:

... aquí está:

Este código hace lo mismo, pero tenga en cuenta cuántas líneas y espacios quedan. En el proceso de mi trabajo, fui guiado por
dicho documento , pero en general fue un camino de prueba y error.
Entonces, el tamaño ha disminuido a
136 bytes .
¿Menos de 100 bytes?
Quería saber si podía ir más lejos. Después de leer
esto , supuse que sería posible alcanzar los 45 bytes, ¡pero por desgracia! no Los trucos descritos allí están diseñados solo para binarios de 32 bits, pero para los de 64 bits no se aprobaron.
Lo mejor que logré fue tomar
esta versión de 64 bits del programa e incorporarla a mi llamada al sistema:
BITS 64 org 0x400000 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: mov eax, 29 int 0x80 filesize equ $ - $$
La imagen resultante es de
127 bytes . Sobre esto, dejé de intentar reducir el tamaño, pero acepto las propuestas.
Tiny Docker
Ahora que hay un binario que implementa una espera interminable, queda por ponerlo en una imagen Docker.
Para guardar cada byte posible, creé un binario con un nombre de archivo de un byte -
t
- y lo puse en el
Dockerfile
, creando una imagen casi vacía:
FROM scratch ADD t /t
Tenga en cuenta que no hay
CMD
en el
Dockerfile
, ya que esto aumentaría el tamaño de la imagen. Para comenzar, debe pasar el comando a través de los argumentos para
docker run
.
Luego, el
docker save
creó un archivo tar y luego se comprimió con la máxima compresión gzip. El resultado es un archivo de imagen portátil de Docker con un tamaño de menos de 1000 bytes:
$ docker build -tt . $ docker save t | gzip -9 - | wc -c 976
También intenté reducir el tamaño del archivo tar experimentando con el archivo de manifiesto Docker, pero en vano: debido a la especificidad del formato tar y el algoritmo de compresión gzip, tales cambios solo llevaron al crecimiento del gzip final. Probé otros algoritmos de compresión, pero gzip resultó ser el mejor para este pequeño archivo.
PD del traductor
Lea también en nuestro blog: