1 puntos por GN⁺ 2024-04-07 | 1 comentarios | Compartir por WhatsApp
  • UEFIRC es un cliente IRC gráfico que se ejecuta en el entorno de prearranque UEFI del firmware de la placa madre antes de que inicie el sistema operativo, y demuestra que incluso en un entorno pensado para el bootloader se puede implementar una UI y funciones de red cercanas a las de una app común
  • La implementación aprovecha los drivers NIC y el stack TCP que UEFI ofrece para el arranque por red, y el backend de red vmnet para QEMU hace posible el desarrollo
  • La parte más complicada fue manejar el protocolo TCP de UEFI desde Rust, donde el estado global, los callbacks reentrantes, los buffers scatter-gather y la mezcla de eventos, tokens, handles y protocolos se entrelazan de forma compleja
  • La GUI es una adaptación a UEFI del toolkit GUI en Rust de axle y su renderizador TrueType, y también se mejoró libgui para soportar entrada por mouse, barras de desplazamiento y renderizado de texto en vistas con scroll
  • El resultado final se parece más a un proyecto de broma muy elaborado que a un cliente IRC realmente práctico, pero sirve como herramienta para quejarse del stack TCP/IP de UEFI desde dentro de UEFI por IRC

Qué hace UEFIRC

  • UEFIRC es un cliente IRC gráfico que corre en UEFI
  • Está escrito en Rust y usa el toolkit GUI y el renderizador TrueType creados para espacio de usuario de axle
  • Puede conectarse a un servidor IRC, chatear y leer mensajes
  • Para el desarrollo se utilizó el backend de red vmnet para QEMU

UEFI como entorno de ejecución

  • El bootloader del sistema operativo se carga con ayuda del firmware almacenado en la ROM de la placa madre
  • En el pasado, BIOS tenía varias limitaciones, y por eso se creó el estándar UEFI para reemplazarlo
    • BIOS exige que el bootloader arranque en modo de 16 bits
    • También existe el requisito de que el cargador de primera etapa quepa en 512 bytes
  • UEFI coloca al bootloader en un entorno de 64 bits desde el inicio y ofrece APIs para cambiar la resolución de pantalla VESA, asignar memoria y acceder al sistema de archivos EFI
  • Es un gran avance frente a BIOS, aunque también se le critica por estar excesivamente diseñado

Reutilizar el arranque por red para IRC

  • Algunos bootloaders pueden cargar el sistema operativo por red en lugar de hacerlo desde un dispositivo de bloques local
  • Para soportar ese caso de uso, el firmware UEFI debe incluir un stack de red
    • Driver NIC
    • Implementación de TCP
    • APIs para que aplicaciones que corren en el entorno de prearranque accedan a ese stack
  • Como un bootloader no necesariamente tiene que cargar un sistema operativo, también puede ejecutarse un cliente IRC en ese mismo entorno

La dificultad de manejar TCP de UEFI desde Rust

  • La parte más difícil del proyecto fue implementar en Rust el cliente del protocolo TCP de UEFI
  • El protocolo TCP de UEFI exige ciclos de vida de datos e interacciones difíciles de expresar en Rust
    • Estado global
    • Callbacks reentrantes
    • Buffers scatter-gather
    • Eventos, tokens, handles y protocolos
  • Se probaron durante varios días distintos cambios en el código Rust para eliminar fugas de memoria y casos de use-after-free en el buffer de recepción TCP

La confusión entre NOTIFY_SIGNAL y NOTIFY_WAIT

  • La API de eventos de UEFI no permite predecir fácilmente su comportamiento solo por el nombre
  • Si se especifica NOTIFY_SIGNAL, el callback se invoca cuando ocurre el evento y usar wait() produce un error
  • Si se especifica NOTIFY_WAIT y se llama a wait(), UEFI puede invocar el callback varias veces antes de que ocurra el evento, y cuando el evento sucede wait() se desbloquea
  • Aunque usen el mismo callback, los dos modos tienen significados completamente distintos
    • NOTIFY_SIGNAL: el evento ya ocurrió, es momento de hacer la siguiente tarea
    • NOTIFY_WAIT: el evento todavía no ocurre, es momento de impulsar el avance
  • Para almacenar en buffer de forma asíncrona los datos de paquetes recibidos, al final se usó un bucle con NOTIFY_WAIT junto con un temporizador de timeout corto

Soporte para mouse y cursor

  • Un cliente IRC no necesita mouse de forma estricta, pero lo hace sentirse más interactivo
  • Se usa el Simple Pointer Protocol de UEFI para leer movimiento del mouse y botones, y mostrar retroalimentación de posición del cursor en la GUI
  • Simple Pointer Protocol no soporta rueda de desplazamiento
    • En UEFIRC hay que usar las teclas de flecha o arrastrar la barra de scroll con el cursor
  • Como en el firmware UEFI estándar de OVMF no se podían obtener eventos de mouse, se compiló un firmware UEFI personalizado con los drivers y protocolos necesarios, como UsbMouseDxe
  • Ese firmware UEFI también se subió a las releases para poder probar UEFIRC en QEMU

Escalado del movimiento del mouse

  • El driver del mouse reporta cambios de posición, no posición absoluta
  • Si simplemente se suman delta_x y delta_y con un escalado lineal, la sensación es torpe
  • Los sistemas operativos usan un tipo de escalado más cercano a algo que permita tanto movimientos rápidos como ajustes finos
  • En la implementación de ejemplo, se multiplica el movimiento del cursor por un valor basado en aplicar log2() a la suma de los valores absolutos del desplazamiento
  • Un cursor con movimiento lineal puede hacer que todo el entorno se sienta lento y poco responsivo

Modelado de mensajes IRC

  • Modelar los mensajes IRC fue comparativamente simple y agradable
  • IRC usa un formato de líneas basado en texto, por lo que es fácil de parsear
  • Aun así, también carga con la complejidad de décadas de extensiones solo parcialmente estandarizadas

Uso de libgui en UEFI

  • El toolkit GUI en Rust de axle ya estaba bastante preparado para usarse fuera del contexto de axle, así que ejecutarlo en UEFI no fue especialmente difícil
  • El trabajo principal fue proveer una implementación de AwmWindow que pudiera usarse dentro de UEFI
  • Después de eso, se pudieron reutilizar directamente varias funciones de libgui
    • Gestión de eventos
    • Renderizado de fuentes
    • Composición por capas
    • Decoración de vistas
    • Componentes complejos como vistas con scroll

Barras de desplazamiento y renderizado de texto en vistas con scroll

  • La libgui basada en C de axle ya tenía funcionalidad de barras de scroll, pero en la versión Rust todavía faltaban algunas partes
  • Como la interacción principal de UEFIRC ocurre en una vista con scroll llena de texto, se volvió a implementar la funcionalidad de barras de desplazamiento en Rust libgui
  • Las vistas con scroll tienen un costo de renderizado en píxeles mayor que las vistas de tamaño fijo
    • En una vista de tamaño fijo basta con pensar en un buffer RGB de tamaño width * height
    • Una vista con scroll debe manejar un lienzo expandible sin límite
  • El toolkit GUI en Rust de axle procesa las vistas con scroll por tiles
    • Cada tile es un buffer cuadrado de píxeles de varios cientos de píxeles de ancho
    • Solo se asignan los tiles necesarios para el área donde realmente se renderiza contenido
    • Se calculan los tiles visibles y luego se unen en la imagen final
  • Si el renderizador TrueType llama a putpixel() por cada píxel de un glifo, la vista con scroll no puede conocer de antemano toda el área de renderizado y eso resulta ineficiente
  • Para resolverlo, se agregó a la polygon stack una unidad básica de dibujo para líneas, círculos y rectángulos
    • Así la vista con scroll sabe de antemano que va a dibujar un polígono grande y puede asignar los tiles necesarios antes de renderizar
    • No encanta tener el relleno arbitrario de polígonos como primitiva básica, pero en la práctica funciona bien

Mejoras a libgui hechas al crear UEFIRC

Un resultado totalmente innecesario

  • El cliente IRC en sí es un proyecto de broma muy elaborado, así que no tiene mucha utilidad práctica
  • Puede servir como herramienta para expresar frustración cuando el stack TCP/IP de UEFI te saca de quicio
  • Y como cierre, se usó para entrar al canal IRC de desarrollo de UEFI #edk2 desde dentro de UEFI y dejar un saludo

1 comentarios

 
GN⁺ 2024-04-07
Opiniones en Hacker News
  • Por diversión, hice un cliente gráfico de IRC que solo corre en el entorno previo al arranque UEFI, y le metí funciones exageradas como fuentes TrueType, cursor y decoraciones de GUI.
    Originalmente era un proyecto para hacer algo rápido y ligero porque me había cansado de hacer un receptor GPS desde cero, pero, como siempre, terminó tomando mucho más de lo esperado.
    También le dediqué bastante tiempo a la visualización del artículo que muestra cómo modelar una vista con desplazamiento y renderizarla en un viewport estático; espero que la disfruten.
    Al principio, con la idea de “metamos en UEFI algo que no debería estar ahí”, pensé en un cliente de Twitter, pero ya había alguien que había hecho uno bastante bien usando el protocolo HTTP de UEFI, así que decidí evitar HTTP.
    Por eso elegí IRC, que funciona sobre TCP y además tiene ese aire de red social que no encaja para nada con un entorno previo al arranque.

    • Aunque digan que “se siente como algo que no debería estar ni cerca de un entorno previo al arranque”, si vas a pedir ayuda con un problema de arranque, en realidad parece el lugar perfecto.
    • Quiero tirar a la basura los sistemas operativos innecesariamente enormes y todas sus funciones accesorias, y pasarme a un UEFI más pequeño y simple. Así arrancaría más rápido y el desarrollo “embebido” sería más fácil.
      Claro, es broma. Hasta cierto punto.
      Soy minimalista, así que ni siquiera necesito GUI o mouse, y UEFI ya parece más de lo que necesito.
      El cliente de Twitter mencionado está aquí: https://github.com/arata-nvm/mitnal
    • Si un software es demasiado grande como para meterlo a la fuerza en UEFI, entonces desde el principio hay que considerarlo software inflado e innecesario. Antes bastaban dos disquetes de 360 KB.
    • Muy genial. Desde hace tiempo me preguntaba si sería posible guardar credenciales de VPN en UEFI y hacer que el sistema se conecte a un servidor para arrancar por red con PXE.
      Parece que podría ser una forma bastante buena, quizá segura, de permitir la recuperación automática de sistemas remotos cuya instalación quedó tan rota que ya no pueden arrancar normalmente.
    • Me interesa más la historia del receptor GPS hecho desde cero.
  • Me encanta. También deja muy claro que, debajo de lo que la mayoría considera el sistema, hay software mucho más complejo y potente de lo que uno imagina.
    Es común la idea equivocada de que el sistema operativo es la “capa más baja” del stack de software, pero en realidad existe código de tipo firmware que es el verdadero dueño del sistema.
    A veces desaparece después de terminar su trabajo, y a veces permanece durante todo el tiempo que la máquina está encendida, en un estado que incluso el sistema operativo percibe como transparente.
    Existe la actitud de “solo es código de bajo nivel para manejar dispositivos, ahí no pasa nada serio”, pero si ahí abajo se puede meter hasta un cliente de IRC, también es fácil imaginar muchas otras cosas maliciosas.

  • ¿“Por qué?” Pero ¿qué clase de pregunta es “por qué”? Vengo a HN justamente por este espíritu.
    “Me llegó la revelación más aterradora. No había ninguna razón para lo que hice. Sabía por qué lo hice. Lo hice porque pensé que sería divertido. Pero ellos preguntarían ‘¿por qué demonios hiciste esto?’ y, si no tenía una razón suficientemente convincente, sentía que me iban a encerrar en un psiquiátrico.” — Boyd Rice

  • No tienes que menospreciarte. Aquí hay un proyecto de cliente de comando y control de botnet.
    La UI es algo graciosa.

  • Muy genial. No sabía que la API de UEFI fuera tan accesible y estuviera tan bien documentada.
    Me intriga cómo fue el ciclo de desarrollo. Supongo que lo ejecutaban en una VM, pero me pregunto si tenían que “arrancar” cada vez que corrían el cliente.

    • El ciclo normal de trabajo era arrancar una instancia de QEMU cargando la aplicación UEFI.
      El script principal de ejecución recreaba el sistema de archivos EFI con una nueva build de UEFIRC y se lo pasaba a QEMU.
      Pero al construir la GUI, ese overhead se volvió bastante molesto, así que configuré la app para que pudiera compilarse tanto para UEFI puro como para un entorno host en Mac.
      Al cambiar una bandera de compilación, el toolkit de GUI dibuja directamente sobre el framebuffer que proporciona UEFI, o se conecta al sistema de ventanas de Mac para enviar y recibir eventos.
      El overhead de este enfoque de doble objetivo también se puede ver en el punto de entrada: https://github.com/codyd51/uefirc/blob/main/src/main.rs
      El parseo de mensajes IRC no necesitaba adornos especiales, así que lo desarrollé con un conjunto de pruebas unitarias que corren directamente en Mac; algunas están aquí: https://github.com/codyd51/uefirc/blob/main/src/irc/response...
    • QEMU puede ejecutar apps UEFI.
  • Algún día quiero terminar de escribir el sistema operativo para mi bot de IRC que todavía sigue corriendo.
    Quizá sea lo más inútil que voy a decir, pero el movimiento no lineal del mouse, es decir, la aceleración, es lo primero que desactivo al arrancar un sistema operativo nuevo. Por alguna razón, literalmente me duele la mano.
    Por ejemplo, en Mac existe linearmouse, que es gratis, y en Windows basta con desactivar la aceleración. En Linux, obviamente es fácil.
    Con aceleración del mouse es difícil desarrollar la sensación de cómo se mapea la distancia recorrida por el mouse con la distancia recorrida en pantalla, y a largo plazo creo que es más eficiente usarlo sin aceleración.
    Es algo que aprendí de los gamers, y creo que ellos siguen teniendo buenas razones para hacerlo.

    • No cambio la configuración del mouse, así que no sé cuál es el valor por defecto, pero igual puedo hacer clic con precisión incluso en zonas de la pantalla que están ocultas.
      Creo que uno se acostumbra a cualquiera de las dos opciones. Como el pedal del acelerador de un auto, que normalmente tampoco se mapea directamente a la velocidad.
  • Si preguntas “¿por qué?”, es porque cuando se presentó UEFI se prometieron aplicaciones de bajo nivel como esta.
    Quienes crearon UEFI incluso soñaban con reemplazar esos mini sistemas operativos solo para internet basados en Linux a los que algunos fabricantes daban acceso presionando una tecla específica durante el arranque. No recuerdo el nombre.

    • Eso eran funciones como Quick View / Quick Boot en equipos de empresas como Dell. Por lo general arrancaban directo a unas cuantas apps de productividad.
      Vi un video de YouTube que trataba el tema en profundidad; recuerdo que al principio eran Linux reducidos u otros sistemas operativos personalizados, luego pasaron a ser apps UEFI y al final la moda se apagó.
  • Buen artículo. Me recordó a la broma del Día de los Inocentes de hace 2 años del bootloader barebox. Era una función que, si todos los demás destinos de arranque fallaban, te conectaba a #barebox[1].
    En ese caso el foco estaba en agregar soporte TCP a barebox, y no tenía elementos de GUI tan bonitos como aquí.
    La interfaz era solo de línea de comandos y, si compilabas barebox como payload EFI, podía dibujar sobre EFI GOP.
    [1]: https://lore.barebox.org/barebox/20220401145902.GF4351@telli...

  • Me vino de inmediato a la mente un video reciente de Cathode Ray Dude. Trataba sobre QuickLook, el “cliente de correo” de HP, que en realidad era un plugin de Outlook; también fue un producto implementado y lanzado de esta manera: https://www.youtube.com/watch?v=ssob-7sGVWs
    En el video también aparecen otras cosas más raras que hizo HP. Sin embargo, este proyecto incluso resuelve la parte difícil que QuickLook evitaba: networking.

  • Las visualizaciones del artículo son sorprendentemente buenas e impresionantes.