sexta-feira, 13 de novembro de 2015

Tornando o programa mais portável com linker script

Até agora, nosso programa tinha particularidades para o processador da placa STM32F0Discovery. As principais delas dizem respeito a mapeamento e tamanho das memórias flash e ram. A forma mais prática de se fazer isso é separando as particularidades de cada processador em um arquivo do linker: o linker script. Vamos começar com algo simples:


  1. /* Definindo as areas de memoria
  2. */
  3. MEMORY
  4. {
  5. FLASH : ORIGIN = 0x08000000, LENGTH = 64K
  6. RAM : ORIGIN = 0x20000000, LENGTH = 8K
  7. }
  8. /* Definindo o endereco do inicio
  9. * da pilha de sistema
  10. */
  11. _stack_init = ORIGIN(RAM)+LENGTH(RAM);
  12. /* Definindo as sessoes
  13. * por enquanto temos apenas uma
  14. * sessao de interesse: a de código
  15. */
  16. SECTIONS
  17. {
  18. .text : { *(.text) } > FLASH
  19. .data : { *(.data) } > RAM
  20. }

O linker script é um arquivo texto onde definimos as particularidades que desejamos para o processo do linker. Primeiramente faremos um arquivo chamado stm32f0discovery.ld que, como o nome diz, será particular para o processador STM32F05xx da placa STM32F0Discovery. Começamos pelo comando MEMORYEsse comando dá ao linker as características dos tipos de memória que o nosso processador tem. No nosso caso, o processador STM32F05xx possui 64k KBytes de memória flash iniciando no endereço 0x08000000 e 8 KBytes de memória ram iniciando no endereço 0x20000000. Pronto: já definimos o tipo, tamanho e endereço de nossa memória física. Com isso, na linha 14, criamos um símbolo que possui, automaticamente, o endereço da pilha de sistema.

O próximo comando faz o mapeamento entre as sessões internas (definidas no programas) com as sessões externas nas áreas e endereços físicos da(s) memória(s) do processador. Temos, como padrão, as seguintes sessões:

  • .text : contém as instruções do programa a serem executadas;
  • .data : contém as áreas de memórias pré-inicializadas com valores;
  • .bss : contém as áreas de memórias não inicializadas com valores.

Por enquanto, nos preocuparemos com a sessão .text. A sessão .data está presente por ser obrigatória no processo do linker mas nós a utilizaremos efetivamente mais adiante. No nosso arquivo, começamos esse mapeamento através do comando SECTIONS. De forma simples, mapeamos a sessão .text externa para a memória flash e a sessão externa .data para a memória ram. Como o processo do linker pode unir muitos programas em um único módulo objeto (aliás essa é sua função principal), determinamos que todas as sessões .text internas (dos programas) serão alocadas na memória flash e todas as sessões .data internas para a memória ram. Isso se dá pelo caractere coringa '*' presente no comando. Em suma:

.text : { *(.text) } > FLASH

.text : a sessão .text do módulo objeto de saída
... } engloba
*(.text) todas as sessões .text dos arquivos objetos dos programas individuais
> FLASH  e residirá na memória flash

Raciocínio semelhante é usado para a sessão .data.

Completando o processo, faremos as alterações necessárias no nosso programa e no makefile.


  1. // Segundo programa para ARM Cortex-M0 configurado para linker script
  2. .thumb // Define código como THUMB
  3. .globl _start // Necessario para o linker
  4. .globl _reset_handler // visto externamente
  5. .text // Inicio da área de código
  6. _start:
  7. // Inicio do Vector Table
  8. .word _stack_init // Inicio da pilha
  9. .word _reset_handler // Endereco do Reset Handler
  10. // define o label como função para que o linker
  11. // resolva como código THUMB
  12. .weak _reset_handler // pode ser redefinido em outro modulo
  13. .type _reset_handler, function
  14. // tabela de bytes para somar 1
  15. tbl:
  16. .byte 3,7,9,0
  17. .align
  18. _reset_handler: // inicio do tratamento do reset
  19. ldr r0,=tbl // carrega r0 com o endereco da tabela
  20. mov r1,#1 // move o valor 1 para r1
  21. segue:
  22. ldrb r2,[r0] // carrega em r2 o byte enderecado por r0
  23. cmp r2,#0 // se r2 for zero,
  24. beq _stop // encerra
  25. add r3, r2, r1 // soma r1 e r2 guardando em r3
  26. add r0,#1 // avanca na tabela
  27. b segue // continua
  28. _stop:
  29. b . // loop infinito

Primeiramente, rebatizamos nosso função de tratamento de reset como _reset_handler (linha 25) e o definimos como global (linha 5) e, com a diretiva .weak (linha 18), é possível que outro módulo externo ao nosso redefina essa função. O inicio da pilha do sistema (_stack_init) agora é uma referência resolvida externamente no linker script e não mais inicializada dentro do programa.

Quanto ao makefile, retiramos os passos de geração do arquivo binário que não usaremos por agora. Além disso, retiramos o parâmetro -Ttext=0x00000000 do passo do linker substituindo-o pelo parâmetro informando nosso arquivo de linker script: -T stm32f0discovery.ld.


  1. rst_0.elf: rst_0.o
  2. @echo&echo&echo&echo&echo&echo ///////////// Executando o linker
  3. @echo
  4. arm-none-eabi-ld -o rst_0.elf rst_0.o -T stm32f0discovery.ld
  5. @echo&echo&echo&echo&echo&echo ///////////// Mostrando os simbolos apos o linker
  6. @echo
  7. arm-none-eabi-nm rst_0.elf
  8. @echo&echo&echo&echo&echo&echo ///////////// Dump das sessoes do programa
  9. @echo
  10. arm-none-eabi-objdump -h rst_0.elf
  11. rst_0.o: rst_0.s
  12. @echo&echo&echo&echo&echo&echo ///////////// Montando o programa assembly
  13. @echo
  14. arm-none-eabi-as -mcpu=cortex-m0 -g -o rst_0.o rst_0.s
  15. clean:
  16. @echo&echo&echo&echo&echo&echo ///////////// Eliminando os arquivos de saida
  17. @echo
  18. rm *.o
  19. rm *.elf

Pronto. Nosso processo de geração do módulo objeto pode ser executado:


  1. T:\arm_03>make
  2. ///////////// Montando o programa assembly
  3. arm-none-eabi-as -mcpu=cortex-m0 -g -o rst_0.o rst_0.s
  4. ///////////// Executando o linker
  5. arm-none-eabi-ld -o rst_0.elf rst_0.o -T stm32f0discovery.ld
  6. ///////////// Mostrando os simbolos apos o linker
  7. arm-none-eabi-nm rst_0.elf
  8. 0800000c W _reset_handler
  9. 20002000 A _stack_init
  10. 08000000 T _start
  11. 0800001c t _stop
  12. 08000010 t segue
  13. 08000008 t tbl
  14. ///////////// Dump das sessoes do programa
  15. arm-none-eabi-objdump -h rst_0.elf
  16. rst_0.elf: file format elf32-littlearm
  17. Sections:
  18. Idx Name Size VMA LMA File off Algn
  19. 0 .text 00000024 08000000 08000000 00008000 2**2
  20. CONTENTS, ALLOC, LOAD, READONLY, CODE
  21. 1 .ARM.attributes 00000021 00000000 00000000 00008024 2**0
  22. CONTENTS, READONLY
  23. 2 .debug_line 00000042 00000000 00000000 00008045 2**0
  24. CONTENTS, READONLY, DEBUGGING
  25. 3 .debug_info 0000003a 00000000 00000000 00008087 2**0
  26. CONTENTS, READONLY, DEBUGGING
  27. 4 .debug_abbrev 00000014 00000000 00000000 000080c1 2**0
  28. CONTENTS, READONLY, DEBUGGING
  29. 5 .debug_aranges 00000020 00000000 00000000 000080d8 2**3
  30. CONTENTS, READONLY, DEBUGGING
  31. T:\arm_03>


Vemos duas informações importantes: na linha 27 o símbolo _stack_init foi gerado pelo linker e com conteúdo correto: 0x20002000. Nas linhas 44 e 45 temos as informações por ora relevantes da nossa sessão .text do nosso módulo objeto: ela possui um tamanho de 0x24 bytes, e possui os endereços de memória VMA e LMA iguais a 0x08000000 que é o nosso endereço da memória flash. Por enquanto é o suficiente. No próximo post quando falarmos da utilização da memória ram, descreveremos as diferenças e usos dos endereços VMA e LMA.

Agora é só depurarmos da mesma forma como feito no post anterior. Boa sorte.


REFERÊNCIAS
The GNU linker - http://www.eecs.umich.edu/courses/eecs373/readings/Linker.pdf
Using AS - http://www.eecs.umich.edu/courses/eecs373/readings/Assembler.pdf

Nenhum comentário:

Postar um comentário