O que você está procurando?
Technology

Sobre a caça ao elefante incomum

RENÉ DAMM / UNITY TECHNOLOGIESContributor
Apr 22, 2014|5 Min
Sobre a caça ao elefante incomum
Esta página da Web foi automaticamente traduzida para sua conveniência. Não podemos garantir a precisão ou a confiabilidade do conteúdo traduzido. Se tiver dúvidas sobre a precisão do conteúdo traduzido, consulte a versão oficial em inglês da página da Web.

Caçar insetos é divertido. E de vez em quando você escapa vivo com uma história para entediar seus netos ("Na minha época, ainda caçávamos insetos com paus e pedras" e tudo mais).

A GDC 2014 nos reservou outro safári de caça digno de troféus. Estávamos a cinco dias de apresentar o Unity 5 ao mundo quando "avistámos" (bom, era meio difícil não notar) um pequeno e feio bug: o nosso novo editor de 64 bits estava a falhar aleatoriamente no OSX, a ponto de ficar completamente inutilizável. Não há nada como subir no palco para mostrar o quão incrível seu repórter de insetos é a cada dois minutos.

Então, Levi, Jonathan e eu largamos todas as coisas incríveis em que estávamos trabalhando (mais histórias com as quais queremos entediar nossos netos) e fomos caçar. Tudo o que sabíamos naquele momento era que havia falhado em algum lugar no código nativo que o Mono gera em tempo de execução.

Como todo programador sabe, quando você se depara com um bug que não é óbvio, você simplesmente começa reunindo evidências. Depois que você aprender o suficiente sobre os padrões de comportamento do inseto, você eventualmente terá uma chance de atacá-lo. E com o tempo passando, estávamos prontos para atirar em praticamente qualquer coisa.

Mas ficamos perplexos. Para um elefante, o inseto se mostrou surpreendentemente ágil e furtivo.

Pareceu acontecer apenas no OSX 10.9, embora Kim tenha visto algo muito parecido no Windows com seu branch de depuração de memória de alto desempenho. E se você habilitasse o Guard Malloc em versões anteriores do OSX, você obteria algo bem parecido também. No entanto, como ele estava travando em código de script aleatório em profundidades arbitrárias na hierarquia de chamadas, era difícil dizer com certeza o que era a mesma falha e o que não era. E o acidente pode ser consistente por dez corridas consecutivas, apenas para ser totalmente diferente nas cinco seguintes.

Então, enquanto Kim e eu nos aprofundamos na memória e nas coxas no código assembly, Levi executou um rastreamento abrangente de todas as atividades secretas e não tão secretas do Mono para gerar um log de gigabytes e um editor que rodava na velocidade da minha avó. Isso nos deu a primeira ideia interessante: aparentemente, estávamos sempre compilando o método que usamos antes que as coisas ficassem feias.

Mas o que fez com que ele caísse? A causa imediata foi que estávamos tentando executar código de um endereço inválido. Como chegamos lá? Um bug no tratamento de sinal do Mono onde não retomamos corretamente? Um bug no compilador JIT do Mono que não retorna corretamente ao código compilado? Um thread diferente está corrompendo a memória da pilha no thread principal? Fadas e grumkins? (por um momento, a última opção pareceu a mais provável).

Depois de dois dias de caça, o elefante ainda estava vivo e andando por aí.

Então, no sábado à noite, equipei-me com um caderno, quatro canetas de cores diferentes e um amplo suprimento de cerveja da nossa geladeira Unity (tomando cuidado para não tocar na horrível cerveja de Natal em lata que ainda estava presa em suas fendas). Então, criei instâncias do Unity até ter quatro travamentos diferentes congelados no depurador, rotulei-os como "Falha Vermelha", "Falha Azul", "Falha Verde" e "Falha Preta" e comecei a trabalhar com minhas canetas coloridas para fazer anotações e desenhar alguns diagramas não tão bonitos de tudo que encontrei.

Aqui estão minhas anotações para Blue Crash:

E foi então que fiz minha primeira descoberta: em todos os casos, a pilha era 16 bytes maior do que deveria ser!

Isso levou à próxima descoberta: para todas as falhas, olhar para aqueles 16 bytes extras revelou um endereço de retorno para a função em que ocorreu a falha. Pelo rastreamento ficou claro que em todos os casos já havíamos executado algumas chamadas do mesmo método, e a princípio pensei que o endereço fosse da última chamada que havíamos rastreado. No entanto, uma inspeção mais detalhada revelou que, na verdade, era o endereço de retorno de uma chamada cujo método ainda não havia sido compilado!

Isso me intrigou por um momento, pois em alguns casos havia várias chamadas entre o último método rastreado e esta chamada que ainda não tinham sido compiladas. Olhando mais de perto, no entanto, descobrimos que sempre pulamos ao redor deles.

Então, eu olhei para aquela função da qual aparentemente deveríamos retornar…

E aí está (destacado em azul): Estávamos pulando na direção errada!

O que o Mono faz aqui é criar pequenas funções "trampolim" que contêm apenas uma chamada para o compilador JIT e alguns dados codificados no fluxo de instruções após a chamada (usado pelo compilador JIT para saber qual método compilar). Depois que o compilador JIT tiver feito seu trabalho, ele excluirá esses trampolins e apagará todos os vestígios de conexão com a chamada do método.

Entretanto, a instrução de chamada que você vê ali é o que chamamos de "chamada próxima", que, incidentalmente, usa um deslocamento de 32 bits assinado para saltar em relação à próxima instrução.

E como um número assinado de 32 bits pode atingir apenas 2 GB para cima e para baixo, e estamos executando 64 bits aqui, de repente soubemos por que o layout da memória heap desempenhou um papel tão crucial na reprodução do bug: quando os trampolins do Mono estavam a mais de 2 GB de distância do compilador JIT, os deslocamentos não caberiam mais em 32 bits e seriam truncados ao emitir a instrução de chamada.

Naquele momento, Jonathan rapidamente identificou a solução correta e, quando seu domingo terminou, tínhamos uma versão estável e funcional, pronta a tempo para o GDC.

Todos vocês conhecem a história de lá. Fizemos uma demonstração bem-sucedida do Unity 5 na GDC 2014, recebendo ótimas críticas e, após o lançamento, ele rapidamente se tornou o software mais amado de todos os tempos. Ah, espere, essa parte ainda está por vir…

Antes desse lançamento, há muito mais travamentos em preto e azul para consertar :).