Quando no gcc existem endereços de 16 bits, e de repente a memória é 256k

... ou como dar um tiro no pé em um Arduino




Na escola de informática de verão, usamos um computador antigo feito por nós mesmos para ensinar o desenvolvimento de jogos.

Agora ele possui uma placa Arduino Mega com um processador ATmega2560, no qual existem até 256 kilobytes de memória flash. Supunha-se que isso duraria muito tempo, porque os jogos são simples (a tela tem apenas 64x64 pixels). Na realidade, encontramos alguns problemas quando o firmware atingiu aproximadamente 128 kilobytes.

Na memória do programa, apesar do nome, além do código executável dos jogos, todos os tipos de dados inalterados, como sprites e tabelas de níveis, são armazenados. Esses dados não são muito.

Mas quando conectamos o chip de som YM2149F ao nosso computador e baixamos algumas dezenas de músicas na mesma memória de programa, os problemas começaram.


O prefixo falhou ao tentar tocar uma melodia ou desenhou um pouco de lixo no menu do jogo. Não ficou claro como depurar isso, porque o processador não apenas lida com a lógica do jogo, mas também exibe a imagem e o som. Como resultado, verificou-se que o compilador gcc-avr usa variáveis ​​de dois bytes de tamanho para armazenar ponteiros. Mas endereçar 256 kilobytes com apenas dois bytes é impossível! Como ele sai?

Indicadores de código


Primeiro, as instruções de chamada de função e os saltos podem usar endereços de três bytes. Portanto, é suficiente que o vinculador substitua o endereço completo em uma instrução e ele funcionará. Se o endereço da função for passado por um ponteiro, esse número não passará - porque temos um ponteiro de byte duplo.

Nesta situação, o gcc insere um "trampolim" nos 64kb inferiores - a instrução jmp, que alterna para a função desejada. Então o endereço desse trampolim atuará como o endereço da função que precisa ser armazenada na variável - afinal, ele é colocado em dois bytes. E quando chamado, haverá uma transição onde necessário.

Ponteiros de dados


Mas armazenamos na memória do programa não apenas o código executável. Portanto, os saltos não ajudarão aqui - estamos desreferenciando indicadores, mas não vamos a eles.

A biblioteca AVR ainda possui funções / macros como pgm_read_byte_far (addr) para desreferenciar o ponteiro completo (são passados ​​valores de quatro bytes). Mas o gcc não sabe como obter esses ponteiros usando a linguagem C.

Felizmente, existe uma macro pgm_get_far_address (var) para obter o endereço completo de uma variável. Isso é feito usando o assembler interno (o caso em que o assembler é mais inteligente que o compilador).

Resta reescrever todo o código que usa os dados na ROM. Ou seja, um music player, renderizando sprites, saída de texto, ... Não é uma experiência muito agradável. Sim, e o código se tornará mais inibitório, e para gráficos é muito crítico. Portanto,

Distribuímos dados na ROM


O Linker está tentando muito colocar dados para a memória do programa nos 64k mais baixos. Isso não funciona se houver muitos dados. Mas os maiores dados que temos são arquivos de música. Portanto, se você removê-los apenas, todo o resto caberá na memória mais baixa e você não precisará refazer a parte principal do código.

Para fazer isso, exploraremos os recursos do script do vinculador. Uma das últimas seções que o vinculador coloca na ROM é chamada .fini7. Salve todas as matrizes de música nesta seção:

#define MUSICMEM __attribute__((section(".fini7"))) const uint8_t tetris2[] MUSICMEM = { ... }; 

Agora, o avr-nm nos diz que tudo está em ordem - os dados com sprites e níveis estavam na parte inferior da ROM e a música na parte superior.

 00002f9c t _ZL10level_menu 00002e0f t _ZL10rope_lines 000006de t _ZL10ShipSprite 00023a09 t tetris2 00024714 T the_last_v8 

Resta refazer o player para usar ponteiros de quatro bytes e, em vez de usar um ponteiro para uma matriz com o código de melodia, use a função para obter seu endereço. As funções são necessárias porque temos um aplicativo de player onde você pode ouvir todas as músicas de sua escolha. Agora ele armazena ponteiros para funções desse tipo:

 00006992 <_Z12tetris2_addrv>: 6992: 61 ef ldi r22, 0xF1 ; 241 6994: 7a e3 ldi r23, 0x3A ; 58 6996: 82 e0 ldi r24, 0x02 ; 2 6998: 99 27 eor r25, r25 699a: 08 95 ret 

O fim do mundo está atrasado até que os sprites atinjam os 64k inferiores. Isso é improvável, porque ainda há mais código do que sprites, o que significa que a memória provavelmente terminará em geral.

Bônus


Neste verão, escrevemos um jogo no estilo de Sokoban. Alguns níveis se mostraram bastante difíceis. Tente, por exemplo, passar este:



Referências


  1. Página de projeto do Github
  2. Arduino e display LED
  3. Arduino e a Pedra Musical do Filósofo
  4. Poucos jogos do ano passado

Source: https://habr.com/ru/post/pt421601/


All Articles