Tener Linux en tantos dispositivos es a la vez una bendición y una condena. Por un lado, es genial poder trastear las cosas, modificarlas o incluso reconvertirlas por completo. Por otro, significa que tienes una flota de equipos Linux que administrar y controlar.

Mi impresora 3D "principal" actual es una Flashforge AD5X: una máquina barata y buena que imprime en cuatro colores con el método de purga e intercambio. Corre Klipper, más o menos. Digo más o menos porque Flashforge tiene Klipper funcionando sobre un host Linux dentro de la caja, pero está muy recortado y modificado. Estoy seguro de que a la mayoría le sirve, y de que si no sabes nada de Linux, Klipper o impresión 3D, la experiencia probablemente sea mejor gracias a las interfaces en la nube de apuntar y hacer clic. Pero, claro, yo no cumplo ninguna de esas condiciones.

Tengo la impresora hace más de un año. Casi de inmediato le puse un "mod" para darle una interfaz Klipper más real y, de paso, acceso al shell. Hay varios que sirven para esto, pero usé Zmod, que no reemplaza del todo el firmware de la impresora: lo parcha y lo extiende. Puedes anularlo o incluso quitarlo fácilmente y volver a la impresora de fábrica, aunque yo no querría.

En mi caso el problema era una impresora, pero la misma idea puede aplicar a cualquier sistema Linux embebido, desde un router hasta un termostato. Sí, corre Linux, pero ¿es un Linux que puedas cambiar?

El problema

La AD5X corre Linux… más o menos.
La AD5X corre Linux… más o menos.

El firmware de Flashforge y Zmod también corren en la hermana menor de la AD5X, la AD5M. Pero la AD5M tiene una placa de procesador bastante menos capaz, así que el Linux de esas cajas es muy recortado. Desde el punto de vista de Flashforge, nadie debería estar en el sistema operativo de todos modos, y el autor de Zmod probablemente piensa que cada byte usado es un byte que le quitas al usuario o a otras funciones avanzadas.

Puede parecer un problema de primer mundo, pero había dos cosas del Linux de la impresora que me irritaban. No había comando less ni more para hurgar en los archivos. Y solo estaba vi como editor. Hice algunos parches para quedar contento: escribí un paginador en shell, por ejemplo. Era una lástima que faltaran herramientas tan básicas.

Sin instalador

Para ahorrar espacio, la impresora en realidad no tiene programas como ls, cat o grep. En su lugar, tiene un único ejecutable busybox. Esto es común en sistemas pequeños: obtienes una sola copia de las bibliotecas y un único ejecutable que hace todo el trabajo. Puedes invocar, por ejemplo, grep ejecutando "busybox grep" o, si haces un enlace simbólico a busybox llamado grep, el usuario quizá nunca note que no tienes grep instalado de verdad.

Sin embargo, busybox hay que compilarlo. No puedes instalarle paquetes con facilidad. Mi plan era producir yo mismo un nuevo paquete de busybox que aportara al menos los comandos faltantes. ¿Qué tan difícil podía ser?

Qué tan difícil, en efecto

El primer problema fue averiguar exactamente qué corría la impresora. El sistema operativo era una compilación de buildroot, como mostraba /etc/os-release:

Código
NAME=Buildroot
VERSION=2020.02.1-g0b1f992-dirty
ID=buildroot
VERSION_ID=2020.02.1
PRETTY_NAME="Buildroot 2020.02.1"

La siguiente pieza del rompecabezas era la arquitectura. Resultó ser MIPS, que tiene muchas variantes, un problema que volvería a atormentarme después. Para descifrarlo, copié el busybox de la máquina a mi computador normal y leí el archivo ELF:

Código
readelf -h busybox
Class:   ELF32
Data:    2's complement, little endian
Type:    EXEC (Executable file)
Machine: MIPS R3000
Flags:   0x70001405, noreorder, cpic, nan2008, o32, mips32r2

Análisis forense

Saber que la CPU era MIPS apenas alcanzaba. El procesador podría haber sido big endian, pero este era little endian. Además, las banderas mostraban un par de cosas importantes: mips32r2, que define un conjunto de instrucciones particular, y nan2008, que significa que todo usa una pila de punto flotante más moderna que en algunas configuraciones MIPS antiguas.

El siguiente paso era encontrar una cadena de herramientas que corriera en mi Linux y produjera ejecutables para esa máquina. Había dos candidatos: zig y crosstool-ng.

Quizá recuerdes que Zig es un lenguaje completamente distinto de C. Así que puede no tener sentido querer compilar busybox, un programa en C, con Zig. Pero Zig tiene una característica interesante: puede hacerse pasar por gcc. La idea es que, si tienes mucho código en C, podrías empezar a migrar a Zig sin convertir todo hoy, ni nunca. Usando Clang y LLVM, Zig pretende poder colarse en un proyecto como el compilador C del sistema.

¿Por qué importa eso aquí? Zig también soporta muchos objetivos, incluido MIPS, y provee bibliotecas de tiempo de ejecución para ellos. Así que si simplemente le digo al sistema de compilación que use Zig, todo estaría bien, ¿verdad?

Verdad…

Como era de esperar, no funcionó tan bien como pensaba. Puede que no entienda Zig lo suficiente, pero pasarle --target mipsel-linux-musleabihf -mcpu=mips32r2 a zig cc no lograba producir un "hola mundo" funcional. ¿La razón? Zig parece no proveer una biblioteca nan2008 para MIPS. Hasta donde puedo ver, Zig podría haber hecho lo correcto para mips32r6, pero eso, como era previsible, llevaba a problemas de instrucción ilegal. Además, yo quería mantenerme lo más cerca posible del montaje existente.

Podría haber hecho que Zig reconstruyera sus bibliotecas, pero entonces mejor armaba una cadena de herramientas completa. Sin embargo, me pregunté si el punto flotante importaría mucho para lo que hacía, así que decidí hacer trampa. Dejé que Zig compilara un ejecutable y luego simplemente parché el encabezado de salida para que dijera que usaba nan2008. Funcionó para un programa simple.

Busybox

El menú de configuración de busybox permite personalizarlo a tu gusto o, al menos, a tus necesidades.
El menú de configuración de busybox permite personalizarlo a tu gusto o, al menos, a tus necesidades.

Configurar busybox es cuestión de usar un sistema menuconfig parecido al de compilar un kernel. Básicamente le pedí que hiciera una copia estática y elegí un montón de cosas que quería, sobre todo less y more.

Una vez listo, tenía que correr make con los compiladores de Zig y luego aplicar el parche para engañar a la impresora y que lo ejecutara. La línea de comandos que mejor funcionó fue:

Código
make V=1 CC="zig cc -target mipsel-linux-musleabihf -mcpu=mips32r2 -static -Os" STRIP='llvm-strip' -j6

Puedes ajustar el -j6 según cuántas CPU tengas. Luego el parche es tan simple como:

Código
printf '\024' | dd of=busybox bs=1 seek=37 count=1 conv=notrunc

¿Funcionó?

Una vez que transferí el archivo, renombrado como busybox-ad5x, a la impresora, pude ejecutar con éxito cosas como busybox less o incluso busybox ls. Todo parecía funcionar. Puedes pedirle a busybox que instale un montón de enlaces simbólicos para todo lo que sabe hacer, pero quise empezar de a poco, así que solo creé enlaces para lo que no existía en el busybox nativo.

Cuando no encontré problemas, terminé reemplazando incluso las utilidades viejas de busybox por las nuevas. Quedó pendiente un experimento más: usar crosstool-ng para armar una cadena de herramientas propia. Pero eso es tema para otro día. Por ahora, Zig, con un poco de parcheo, hizo el trabajo. Hay muchas formas de lograr las cosas bajo Linux. Resulta muy difícil para un fabricante bloquear del todo un sistema, y si hay una vía de entrada, casi siempre hay alguna manera de ajustar todo a tu gusto.