Emulando Linux x86 no Apple Silicon com QEMU-System

Estou escrevendo esse post por causa de um problema de configuração que meus amigos tiveram em uma aula de segurança. Tinha um exercício sobre fazer engenharia reversa em um binário de Linux x86, e isso foi um problema para quem tinha um MacBook com Apple Silicon, por causa do processador ARM. Isso significa que rodar binários x86 não é tão direto.

Minha ideia era fazer uma máquina virtual x86 com Linux usando o QEMU (com o qual eu já tinha alguma experiência) para ter um sistema parecido com o que o professor estava utilizando, mas infelizmente essa tarefa estava sendo muito mais difícil do que deveria, e não encontramos um bom tutorial passo-a-passo 🙁

Então estou escrevendo isso para ajudar gente preguiçosa igual eu que gostariam de ter um tutorial simples, com comandos que pode-se simplesmente copiar e colar para que tudo funcione, mas sem deixar de lado a compreender da solução. Essa estratégia emula todo o computador x86, então qualquer característica do processador ARM da máquina host não deve ter qualquer efeito, em contrapartida a performance é ruim.

Ao invés de configurar o QEMU na mão, também encontrei o projeto UTM, que cria uma interface mais amigável e oficialmente oferece suporte para rodar uma máquina guest em hosts ARM. Mas para esse post eu queria entrar mais a fundo em como a emulação funciona e mostrar uma solução que pode ser mais facilmente adaptada para outros sistemas.

Introdução ao QEMU

QEMU logo – by Benoît Canet, CC-BY license

QEMU (uma abreveação para Quick EMUlator) é um projeto Open Source para emulação e virtualização de máquinas. Ele usa emulação para rodar uma máquina com arquitetura diferente da máquina host (e.g. rodar Linux MIPS em um computador x86), mas, se a arquitetura for a mesma, a emulação não é necessária e ele consegue utilizar o processador mais diretamente com apenas certo isolamento do sistema principal.

Ele suporta muitos sistemas operacionais diferentes, Linux, Windows, Mac OS, e BSDs. E também muitas arquiteturas de processadores diferentes, como x86_64, ARM, MIPS, RISC-V. Então ele é uma tecnologia bastante portável e versátil. Uma aplicação comum que o utiliza é o emulador do Android Studio, o AVD, como mostra a documentação.

O QEMU pode executar em dois modos diferentes: usuário e sistema. O modo usuário executa o binário diretamente no seu sistema atual, emulando o processador e traduzindo as syscalls para o kernel do host, mas esse modo só está disponível para Linux e BSD. O modo sistema, por outro lado, cria todo um computador virtual com seu próprio processador, memória, armazenamento, rede e outros dispositivos.

No nosso problema é necessário utilizar uma emulação completa com o qemu-system, uma vez que estamos utilizando Mac OS e qemu-user não o suporta. Nos também não podemos aproveitar da virtualização porque a arquitetura do host (ARM) é diferente da do guest (x86).

Configurando

Como mostra a página de download, Podemos fazer a instalação pelo Homebrew.

brew install qemu

Depois de um certo tempo o programa deve estar instalado. Antes de continuar precisamos criar um disco rígido virtual.

qemu-img create -f qcow2 linux_hd.qcow2 20G

Neste comando qemu-img é a ferramenta que trata imagens de sistemas de arquivo, create é o comando para criar uma imagem nova, -f qcow2 é a configuração que seleciona o formato qcow2, linux_hd.qcow2 é o nome do arquivo e 20G é o tamanho máximo do HD. O formato qcow2 é interessante porque é o mais versátil de todos e suporta cifra, compressão e múltiplos snapshots.

Com o disco rígido em mãos, podemos iniciar nosso sistema com uma imagem de instalação de Linux x86. Nesse case é importante escolher uma leve porque a performance será ruim uma vez que estamos emulando. Nesse exemplo estou utilizando essa imagem de disco de Puppy Linux.

qemu-system-x86_64 -m 1G -smp 2 -accel hvf -accel tcg \
-hda linux_hd.qcow2 \
-cdrom fossapup64-9.5.iso -boot d

Esse comando é o que realmente roda a máquina virtual. A primeira linha define as características da máquina: -m confira a RAM da máquina guest para 1GB, -smp seleciona o número de núcleos da máquina guest. Sinta-se livre para alterar esses valores para dar para a VM mais memória e processamento para obter mais performance dentro dela (e menos performance fora dela).

Por fim, -accel seleciona o acelerador para melhorar a performance. hvf é o hypervisor nativo do MacOS, mas ele pode não estar diponível para uma emulação de uma máquina x86 em ARM, então também temos o tcg do QEMU como uma alternativa.

A segunda linha inclui nosso HD recém criado como o primeiro do sistema com -hda, e a terceira linha configura a inicialização pelo CD de instalação ao carregar o arquivo .iso com -cdrom e selecionando-o como meio de inicialização com -boot d (Nessa configuração ‘a’ e ‘b’ representam disquetes, ‘c’ representa HD principal e ‘d’ disco óptico).

Então, executando o comando devemos agora ter uma tela 🙂

Alguns atalhos de teclado úteis agora que temos uma tela são ctrl+alt+g para trocar o cursor do host para o guest, ctrl+alt++ or ctrl+alt+- para alterar o tamanho da tela e ctrl+alt+f para entrar no modo tela cheia. Agora podemos apenas instalar o sistema normalmente.

Para uma instalação permanente devemos usar o ícone no topo da tela. Na janela do instalador, a maneira mais simples de instalar é com o terceiro ícone, “Install” e depois selecionando “internal hard drive” e o único disco rígido disponível.

Esse disco rígido não tem qualquer particionamento porque acabamos de criá-lo, então precisamos confgurá-lo com o GParted. No topo da janela podemos criar uma tabela de particionamento com “Device > Create Partition Table …” e criar uma tabela “msdos”. Então devemos criar uma partição clicando com o botão direito na área desalocada e adicionando uma partição ext4 para o disco todo. Então aplicamos essas mudanças clicando na marca verde.

Depois disso, clique com o botão direito na partição e use o “Manage Flags” para configurá-la como bootável e com isso o disco rígido deve estar pronto. Agora podemos fechar o GParted e “Install Puppy to sda1” agora está disponível. Daí deve haver uma instalação do GRUB4DOS e algumas configurações simples. E depois disso o sistema estará instalado e podemos desligar.

Para as próximas inicializações desse sistema, o CD de instalação não é mais necessário, então o removemos do comando. Também é um bom momento para adicionar um nome para a máquina.

qemu-system-x86_64 -m 1G -smp 2 -accel hvf -accel tcg \
-hda linux_hd.qcow2 \
-name x86-on-arm

Agora tudo deve estar funcionando corretamente. Além disso, eu recomendaria olhar as páginas de manual do QEMU com man qemu-system-x86_64 para ter uma boa noção de tudo o que ele é capaz, porque elas descrevem uma grande quantidade de configurações, como ativar snapshots ou usar um servidor remoto como disco rígido.

Outros truques úteis

Para transferir arquivos entre o host e o guest, pode-se utilizar o Virtual FAT do QEMU para compartilhar uma pasta entre os dois, apenas tenha o cuidado de não escrever qualquer arquivo nela enquanto o guest está executando porque esse recurso está em beta e tem comportamento estranho.

-hdb fat:rw:<shared_directory>

Uma vez que a performance da máquina guest é reduzida, pode ser mais viável utilizar a máquina host para compilar programas para a máquina guest. Isso pode ser feito com uma toolchain de cross-compiling como osxcross ou dockcross.

Since performance reduced in the guest machine, it may also be useful use the host machine to compile code for the guest machine. This can be done with a cross-compiling toolchain like osxcross or dockcross.

Conclusão

Esse procedimento permite ter um sistema x86 rodando dentro de um Mac Apple Silicon e deve ser útil para tarefas envolvendo binários já compilados de Linux x86 sem se preocupar com diferenças dos sistemas. A performance é ruim, mas deve ser utilizável, e esse guia pode ser facilmente adaptado para emular outros sistemas em outros dispositivos. O QEMU é versátil assim 😉

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *