MyToyOS: Usandao a lib gnu-efi

Podemos, é claro, codificar um bootloader EFI manualmente, mas também podemos usar a libgnuefi e aproveitarmos todos os recursos que o padrão e a BIOS podem nos oferecer. Para tanto, basta instalar:

$ sudo apt-get install gnu-efi

Eis um “hello, world”:

#include <efi/efi.h>
#include <efi/efilib.h>
 
EFI_STATUS EFIAPI efi_main(
    EFI_HANDLE ImageHandle, 
    EFI_SYSTEM_TABLE *SystemTable)
{
  InitializeLib(ImageHandle, SystemTable);
  Print(L"Hello, world!\n");
  return EFI_SUCCESS;
}

O detalhe é como compilar e montar o arquivo UEFI. Eis um Makefile:

#
# HELLO.EFI makefile
#

hello.efi: hello.so
    objcopy -j .text \
      -j .sdata \
      -j .data \
      -j .dynamic \
      -j .dynsym \
      -j .rel \
      -j .rela \
      -j .reloc \
      --target=efi-app-x86_64 \
      $^ $@

hello.so: hello.o
    ld $^ \
      /usr/lib/crt0-efi-x86_64.o \
      -nostdlib \
      -znocombreloc \
      -T /usr/lib/elf_x86_64_efi.lds \
      -shared \
      -Bsymbolic \
      -L /usr/lib \
      -l:libgnuefi.a \
      -l:libefi.a \
      -o $@

hello.o: hello.c
    gcc -c \
      -fno-stack-protector \
      -fpic \
      -fshort-wchar \
      -mno-red-zone \
      -I /usr/include/efi \
      -I /usr/include/efi/x86_64 \
      -DEFI_FUNCTION_WRAPPER \
      -o $@ $<

Eis a explicação para os comandos. O compilador, obviamente, irá criar um objeto que não contém referências à libc e código independente de posição (Position Independent Code, ou PIC). É necessário informar o diretório dos headers da gnu-efi… Logo depois criamos um shared object “linkando” o objeto com as libs estáticas libgnuefi.a e libefi.a, também com o objeto de inicialização crt0-efi-x86_64.o. A opção -Bsymbolic é importante para manter todos símbolos auto contidos no shared object.

É claro que UEFI não suporta o formato ELF e, por isso, usa-se o utilitário objcopy para copiar as sessões dela para o novo arquivo EFI. As opções -j dizem quais sessões devem ser copiadas (se existirem) e --target informa o formato do arquivo de saída: efi-app-x86_64 para aplicações EFI, efi-bsd-x86_64 para “bootstrap driver” (ou bootloader) e efi-rtd-x86_64 para “runtime driver”.

Pode ser uma boa idéia acrescentar as opções -ffreestanding e -nostdlib na linha de comando do GCC… Se não retirarmos o stack-protector e as red-zones (esse código ai é para x86-64!), o executável não funcionará!

Criando uma partição GPT de teste

Para criarmos um disco “virtual” de 100 MiB (195312 setores) com uma partição GPT e colocarmos o programinha acima na partição EFI, podemos fazer:

# dd if=/dev/zero of=disk.img bs=512 count=195312
# gdisk disk.img
GPT fdisk (gdisk) version 1.0.1

Partition table scan:
  MBR: not present
  BSD: not present
  APM: not present
  GPT: not present

Creating new GPT entries.

Command (? for help): o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): Y

Command (? for help): n
Partition number (1-128, default 1): 1
First sector (34-63966, default = 2048) or {+-}size{KMGTP}: 
Last sector (2048-63966, default = 195279) or {+-}size{KMGTP}: 
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): ef00
Changed type of partition to 'EFI System'

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): Y
OK; writing new GUID partition table (GPT) to disk.img.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.

# losetup --offset 1048576 --sizelimit 98934272 /dev/loop0 disk.img
# mkdosfs -F32 /dev/loop0
mkfs.fat 3.0.28 (2015-05-16)
Loop device does not match a floppy size, using default hd params
# mount /dev/loop0 /mnt
# cp hello.efi /mnt/
# umount /dev/loop0
# losetup -d /dev/loop0

Os valores passados para losetup merecem uma explicação. Como criamos a partição EFI no setor 2048 (como sugerido pelo gdisk), o offset inicial é 2048\cdot512. O tamanho dessa partição é de (195279-2048)\cdot512=98934272 bytes, onde 195279 é o último setor da partição. Usar losetup permite mapear um pedaço de um arquivo num dispositivo de loopback e ai podemos formatá-lo e montá-lo…

Se você pretende criar uma imagem de um disco mais “realista”, coloque uma partição pequena para EFI (FAT32) — digamos, uns 32 MiB — e outra partição vazia que será formatada depois (com o sistema de arquivos do MyToyOS).

Testando o novo “HD” no QEMU

Temos que, antes, instalar um pacote chamado ovmf. Ele contém uma BIOS que suporta UEFI 2.6 e, para iniciar a emulação basta fazer:

$ qemu-system-x86_64 -bios /usr/share/ovmf/OVMF.fd \
  -drive file=disk.img,if=ide

Repare que não temos um bootstrap, mas uma aplicação EFI. Podemos executá-la do shell, bastando selecionar o drive FS0: e digitar hello.efi:

efiapp
Grab da tela do qemu-system-x86_64

Vantagens e desvantagens:

As óbvias vantagens são as de que este programinha executa no modo protegido x86-64! A lib gnu-efi toma conta disso pra você… A outra é que todas as funções padronizadas EFI estão a sua disposição…

A desvantagem, também óbvia, é que o executável fica grande. Note que para imprimir uma simples string de 14 caracteres (incluindo o salto de linha final) criou um arquivão de quase 45 KiB!

Anúncios