1 pontos por GN⁺ 2024-04-07 | 1 comentários | Compartilhar no WhatsApp
  • UEFIRC é um cliente gráfico de IRC que roda no ambiente de pré-boot UEFI do firmware da placa-mãe antes de o sistema operacional iniciar, mostrando que mesmo em um ambiente voltado a bootloaders é possível implementar uma UI e recursos de rede próximos aos de um app comum
  • A implementação aproveita os drivers de NIC e a pilha TCP que a UEFI oferece para boot via rede, e o backend de rede vmnet para QEMU viabiliza o desenvolvimento
  • O ponto mais complicado foi lidar com o protocolo TCP da UEFI em Rust, com estado global, callbacks reentrantes, buffers scatter-gather e uma mistura complexa de eventos, tokens, handles e protocolos
  • A GUI é uma adaptação para UEFI do toolkit gráfico em Rust e do renderizador TrueType do axle, e também houve melhorias no libgui para entrada de mouse, barra de rolagem e renderização de texto em scroll view
  • O resultado final está mais para um projeto de piada sofisticado do que para um cliente de IRC prático, mas serve como ferramenta para reclamar da pilha TCP/IP da UEFI de dentro da própria UEFI

O que o UEFIRC faz

  • UEFIRC é um cliente gráfico de IRC que roda em UEFI
  • Foi escrito em Rust e usa o toolkit de GUI e o renderizador TrueType criados para o espaço de usuário do axle
  • É possível conectar a um servidor IRC, conversar e ler mensagens
  • No desenvolvimento foi usado o backend de rede vmnet para QEMU

A UEFI como ambiente de execução

  • O bootloader do sistema operacional é carregado com a ajuda do firmware armazenado na ROM da placa-mãe
  • No passado, a BIOS tinha várias limitações, e o padrão UEFI foi criado para substituí-la
    • A BIOS exigia que o bootloader começasse em modo 16-bit
    • Havia também a exigência de que o loader do primeiro estágio coubesse em 512 bytes
  • A UEFI coloca o bootloader desde o início em um ambiente 64-bit e fornece APIs para troca de resolução de vídeo via VESA, alocação de memória e acesso ao sistema de arquivos EFI
  • É um grande avanço em relação à BIOS, mas também é vista por alguns como excessivamente projetada

Reaproveitando o boot por rede para IRC

  • Alguns bootloaders podem carregar o sistema operacional pela rede em vez de usar um dispositivo de bloco local
  • Para dar suporte a esse caso de uso, o firmware UEFI precisa incluir uma pilha de rede
    • Driver de NIC
    • Implementação de TCP
    • API para que aplicações executadas no ambiente de pré-boot acessem essa pilha
  • Como um bootloader não precisa necessariamente carregar um sistema operacional, também é possível rodar um cliente de IRC nesse mesmo ambiente

A dificuldade de lidar com TCP da UEFI em Rust

  • A parte mais difícil do projeto foi implementar em Rust um cliente do protocolo TCP da UEFI
  • O protocolo TCP da UEFI exige tempos de vida de dados e interações difíceis de descrever em Rust
    • Estado global
    • Callbacks reentrantes
    • Buffers scatter-gather
    • Eventos, tokens, handles e protocolos
  • O código em Rust foi testado por vários dias para eliminar vazamentos de memória e use-after-free nos buffers de recepção TCP

A confusão entre NOTIFY_SIGNAL e NOTIFY_WAIT

  • Só pelos nomes, a API de eventos da UEFI já é difícil de prever como funciona
  • Ao especificar NOTIFY_SIGNAL, o callback é chamado quando o evento ocorre, e usar wait() vira erro
  • Ao especificar NOTIFY_WAIT e chamar wait(), a UEFI pode chamar o callback várias vezes antes de o evento ocorrer, e quando ele ocorre o wait() é liberado
  • Os dois modos dão ao mesmo callback significados completamente diferentes
    • NOTIFY_SIGNAL: o evento ocorreu, então é hora de fazer a próxima etapa
    • NOTIFY_WAIT: o evento ainda não ocorreu, então é hora de estimular o progresso
  • Para fazer buffering assíncrono dos dados de pacotes recebidos, no fim foi usado um loop com NOTIFY_WAIT junto com um timer de timeout curto

Suporte a mouse e cursor

  • Um mouse não é essencial para um cliente de IRC, mas faz o app parecer mais interativo
  • O Simple Pointer Protocol da UEFI foi usado para ler movimento do mouse e entrada de botões, além de fornecer feedback de posição do cursor na GUI
  • O Simple Pointer Protocol não oferece suporte à roda de rolagem
    • No UEFIRC, é preciso usar as setas do teclado ou arrastar a barra de rolagem com o cursor
  • No firmware UEFI OVMF padrão não era possível obter eventos de mouse, então foi compilado um firmware UEFI customizado com os drivers e protocolos necessários, como UsbMouseDxe
  • Para permitir testar o UEFIRC no QEMU, esse firmware UEFI também foi enviado para as releases

Escalonamento do movimento do mouse

  • O driver do mouse informa variações de posição, não coordenadas absolutas
  • Somar linearmente delta_x e delta_y dá uma sensação de resposta lenta
  • Sistemas operacionais usam um tipo de escalonamento mais próximo de permitir ao mesmo tempo movimentos rápidos e ajustes finos
  • No exemplo de implementação, o deslocamento do cursor é ampliado multiplicando pela aplicação de log2() sobre a soma dos valores absolutos do movimento
  • Um cursor com movimento linear tende a passar a sensação de que todo o ambiente é lento e pouco responsivo

Modelando mensagens IRC

  • A modelagem das mensagens IRC foi relativamente simples e agradável
  • O IRC usa um formato de linhas baseado em texto, o que facilita o parsing
  • Ainda assim, décadas de extensões deixaram uma certa complexidade, com apenas parte disso padronizada

Usando libgui em UEFI

  • O toolkit de GUI em Rust do axle já havia sido bastante trabalhado para poder ser usado fora do contexto do axle, então executá-lo em UEFI não foi especialmente difícil
  • O trabalho principal foi fornecer uma implementação de AwmWindow que pudesse ser usada dentro da UEFI
  • Depois disso, foi possível aproveitar diretamente vários recursos do libgui
    • Gerenciamento de eventos
    • Renderização de fontes
    • Composição de camadas
    • Decoração de views
    • Componentes mais complexos, como scroll view

Barra de rolagem e renderização de texto em scroll view

  • O libgui em C do axle já tinha funcionalidade de barra de rolagem, mas a versão em Rust ainda não tinha alguns recursos
  • Como a principal interação no UEFIRC acontece em uma scroll view cheia de texto, a funcionalidade de barra de rolagem foi reimplementada no libgui em Rust
  • Uma scroll view tem custo de renderização em pixels maior do que uma view de tamanho fixo
    • Em uma view de tamanho fixo, basta pensar em um buffer RGB de tamanho width * height
    • Em uma scroll view, é preciso lidar com uma tela potencialmente infinita
  • O toolkit de GUI em Rust do axle trata a scroll view de forma baseada em tiles
    • Cada tile é um buffer de pixels quadrado com algumas centenas de pixels de largura
    • Só são alocados os tiles necessários para a área em que o conteúdo realmente será renderizado
    • Os tiles visíveis são calculados e então costurados na imagem final
  • Quando o renderizador TrueType chama putpixel() para cada pixel de um glifo, a scroll view não sabe antecipadamente toda a área de renderização, o que é ineficiente
  • Para resolver isso, a polygon stack passou a incluir primitivas básicas de desenho como linhas, círculos e retângulos
    • Assim, a scroll view consegue saber de antemão que um polígono grande será desenhado e alocar os tiles necessários
    • Ter preenchimento arbitrário de polígonos como primitiva básica não agrada muito, mas na prática funciona bem

Melhorias no libgui surgidas ao criar o UEFIRC

Um resultado totalmente desnecessário

  • O cliente de IRC em si é mais um projeto de piada sofisticado do que algo realmente útil no dia a dia
  • Mas ele pode servir como ferramenta para reclamar da pilha TCP/IP da UEFI quando ela estiver te irritando
  • Por fim, houve até um acesso ao canal IRC #edk2 de desenvolvimento da UEFI, diretamente de dentro da própria UEFI, para deixar um cumprimento

1 comentários

 
GN⁺ 2024-04-07
Opiniões no Hacker News
  • Fiz, de brincadeira, um cliente IRC gráfico que roda apenas no ambiente pré-boot UEFI, e ainda coloquei recursos exagerados como fontes TrueType, cursor e decorações de GUI
    Originalmente era um projeto para tentar fazer algo rápido e leve, depois de ficar cansado de um receptor GPS feito do zero, mas, como sempre, levou muito mais tempo do que o esperado
    Também passei bastante tempo nas visualizações do artigo que mostram como modelei a visualização rolável e a renderizei em um viewport estático; espero que vocês gostem
    No começo, com a ideia de “enfiar no UEFI algo que não deveria estar lá”, pensei em um cliente do Twitter, mas alguém já tinha feito um muito bom usando o protocolo HTTP do UEFI, então decidi evitar HTTP
    Por isso escolhi IRC, que roda sobre TCP e também tem um ar de mídia social que não combina nada com um ambiente pré-boot

    • Embora tenha essa sensação de “não deveria estar nem perto de um ambiente pré-boot”, se você quer pedir ajuda com um problema de boot, parece até o lugar perfeito
    • Quero abandonar sistemas operacionais desnecessariamente enormes e seus recursos aleatórios e ir para um UEFI menor e mais simples. Afinal, ele inicializaria mais rápido e também facilitaria o desenvolvimento “embarcado”
      Claro que é brincadeira. Até certo ponto
      Sou minimalista, então nem preciso de GUI ou mouse, e o UEFI já parece ter mais do que eu preciso
      O cliente de Twitter mencionado está aqui: https://github.com/arata-nvm/mitnal
    • Se um software é grande demais para ser enfiado no UEFI, então, para começo de conversa, ele deve ser considerado software inchado totalmente desnecessário. Antigamente, dois disquetes de 360 KB eram suficientes
    • Muito legal. Há tempos eu me perguntava se seria possível armazenar credenciais de VPN no UEFI e fazer o sistema se conectar ao servidor para dar boot pela rede via PXE
      Parece que poderia ser uma forma bem interessante, talvez segura, de permitir a recuperação automática de sistemas remotos cuja instalação quebrou completamente e não consegue inicializar normalmente
    • Estou mais curioso sobre a história do receptor GPS feito do zero
  • Muito bom. Também mostra bem que, por baixo do sistema em que a maioria pensa, há um software mais complexo e poderoso do que se imagina
    As pessoas costumam achar, equivocadamente, que o sistema operacional é a “camada mais baixa” da pilha de software, mas na verdade existe código com cara de firmware que é quem realmente possui o sistema
    Às vezes ele desaparece depois de cumprir sua função; outras vezes permanece durante todo o tempo em que o sistema está ligado, de um modo que até o sistema operacional percebe como transparente
    Existe a atitude de que “é só código de baixo nível para acionar dispositivos, nada sério vai acontecer ali”, mas, se dá para colocar até um cliente IRC lá embaixo, fica fácil imaginar outras coisas maliciosas

  • “Por quê?” Que tipo de pergunta é “por quê”, afinal? Venho ao HN para ver esse espírito
    “Veio a percepção mais assustadora. Não havia motivo algum para o que eu tinha feito. Eu sabia por que tinha feito. Fiz porque parecia que seria divertido. Mas eles perguntariam ‘por que diabos você fez isso?’ e, se eu não tivesse uma razão suficientemente plausível, parecia que iam me enfiar num hospital psiquiátrico.” — Boyd Rice

  • Não precisa se subestimar. Aqui temos um projeto de cliente de comando e controle de botnet
    A UI é meio engraçada

  • Muito legal. Eu não sabia que a API do UEFI era tão acessível e bem documentada assim
    Fiquei curioso sobre como foi o ciclo de desenvolvimento. Imagino que tenha rodado em uma VM, mas queria saber se era preciso “dar boot” toda vez que fosse executar o cliente

    • O loop de trabalho normalmente era inicializar uma instância do QEMU carregando o aplicativo UEFI
      O script principal de execução recriava um sistema de arquivos EFI contendo a nova build do UEFIRC e o passava para o QEMU
      Mas, ao desenvolver a GUI, esse overhead ficou bem inconveniente, então configurei o app para ser compilado tanto para UEFI puro quanto para um ambiente host que roda no Mac
      Ao mudar uma flag de build, o toolkit de GUI desenha diretamente no framebuffer fornecido pelo UEFI ou se conecta ao sistema de janelas do Mac para trocar eventos
      O overhead dessa abordagem de dois alvos também pode ser visto no ponto de entrada: https://github.com/codyd51/uefirc/blob/main/src/main.rs
      O parsing de mensagens IRC não exigia nenhum enfeite, então foi desenvolvido como um conjunto de testes unitários que rodam diretamente no Mac; alguns estão aqui: https://github.com/codyd51/uefirc/blob/main/src/irc/response...
    • O QEMU consegue executar apps UEFI
  • Um dia quero terminar de escrever o sistema operacional para o meu bot de IRC que ainda está rodando
    Talvez seja a coisa mais inútil a dizer, mas movimento não linear do mouse, ou seja, aceleração, é a primeira configuração que desligo quando inicializo um sistema operacional novo. Estranhamente, minha mão realmente dói
    Por exemplo, no Mac existe o linearmouse, que é gratuito; no Windows basta desligar a aceleração. No Linux, claro, é fácil
    Com aceleração do mouse, fica difícil aprender a sensação de mapear a distância que o mouse se move para a distância percorrida na tela, e acho que, no longo prazo, usar sem aceleração é mais eficiente
    Aprendi isso com gamers, e acho que eles ainda têm bons motivos para agir assim

    • Não mexo nas configurações do mouse, então não sei qual é o padrão, mas ainda consigo clicar com precisão em áreas da tela que estão ocultas
      Acho que a sensação acaba ficando familiar de qualquer forma. Assim como o pedal do acelerador de um carro normalmente não é mapeado diretamente para a velocidade
  • Se a pergunta é “por quê?”, é porque, quando o UEFI foi apresentado, esse tipo de aplicação de baixo nível foi prometido
    Quem criou o UEFI também sonhava em substituir até aqueles mini sistemas operacionais só para internet, baseados em Linux, que alguns fornecedores permitiam acessar durante o boot pressionando uma tecla específica. Não lembro o nome

    • Isso era o recurso Quick View / Quick Boot que existia em máquinas como as da Dell antigas. Em geral, inicializava direto alguns apps de produtividade
      Vi um vídeo no YouTube que explorava isso a fundo; pelo que lembro, no começo era um Linux reduzido ou outro sistema operacional customizado, depois migrou para apps UEFI e acabou saindo de moda
  • Bom artigo. Lembrou-me a pegadinha de 1º de abril do bootloader barebox de dois anos atrás. Se todos os outros destinos de boot falhassem, ele conectava ao #barebox[1]
    O foco deles era adicionar suporte a TCP ao barebox, sem elementos de GUI legais como aqui
    A interface era só linha de comando e, se o barebox fosse compilado como payload EFI, ele conseguia desenhar sobre o EFI GOP
    [1]: https://lore.barebox.org/barebox/20220401145902.GF4351@telli...

  • Pensei imediatamente em um vídeo recente do Cathode Ray Dude. Ele tratava do QuickLook da HP, um “cliente de e-mail” que na prática era um plugin do Outlook, e que também foi implementado e lançado como produto desse jeito: https://www.youtube.com/watch?v=ssob-7sGVWs
    O vídeo também mostra outras coisas ainda mais estranhas que a HP fez. Só que este projeto conseguiu fazer até a parte difícil que o QuickLook evitava: rede

  • As visualizações do artigo são surpreendentemente boas e impressionantes