Skip to content

Laboratório 3

Objetivo

O objetivo desta atividade é a familiarização com o processo de depuração de código no simulador ALE.

Depurando Código com ALE

O simulador ALE possui uma interface interativa que permite ao usuário controlar a execução do programa e inspecionar os valores dos registradores e da memória. Esta interface é muito parecida com a interface do depurador GDB e permite que o usuário depure a execução do código.

Visuão Geral Sobre a Interface Interativa

Para habilitar a interface de execução interativa, basta clicar na seta ao lado do botão RUN e selecionar a opção "Debug", como ilustrado na figura a seguir.

t

Uma vez que você clicar na opção Debug, o simulador abrirá um terminal interativo para que você possa digitar comandos para controlar a execução e/ou inspecionar o estado da memória e dos registradores do processador RISC-V. A a seguir mostra um terminal interativo.

t2

Comandos do Depurador

Para ver a lista completa de comandos disponíveis no modo de depuração, você pode executar o comando “help” no terminal interativo. Para isso, basta digitar "help" no terminal interativo e teclar ENTER. A seguir é presentado um trecho da lista de comandos exibidos pelo comando help.

run
  Run till interrupted.
until <address>
  Run until address or interrupted.
step [<n>]
  Execute n instructions (1 if n is missing).
peek <res> <addr>
  Print value of resource res (one of r, f, c, m) and address addr.
  For memory (m) up to 2 addresses may be provided to define a range
  of memory locations to be printed.
  examples: peek r x1   peek c mtval   peek m 0x4096
peek pc
  Print value of the program counter.
peek all
  Print value of all non-memory resources
poke res addr value
  Set value of resource res (one of r, c or m) and address addr
  Examples: poke r x1 0xff  poke c 0x4096 0xabcd
disass opcode <code> <code> ...
  Disassemble opcodes. Example: disass opcode 0x3b 0x8082
disass function <name>
  Disassemble function with given name. Example: disas func main
disass <addr1> <addr2>>
  Disassemble memory locations between addr1 and addr2.
elf file
  Load elf file into simulated memory.

 ...

A tabela a seguir resume os principais comandos disponíveis na interface interativa do simulador ALE:

Comando Descrição
symbols Mostra o endereço dos símbolos (p.ex., _start, loop, end, result) do programa. O endereço é exibido na representação hexadecimal (p.ex., 0x11180).
until <address> Executa as instruções do programa até um certo endereço. O endereço deve ser fornecido na representação hexadecimal (p.ex., 0x11180).
step [n=1] Executa as próximas n instruções.
peek r <register> Exibe o valor armazenado no registrador <register> (p.ex., peek r x1 ou peek r mtval). O valor é exibido na representação hexadecimal. O comando peek r all mostra o valor de todos os registradores.
peek m <address> Exibe o valor armazenado na palavra de memória associada ao endereço <address>. O valor é exibido na representação hexadecimal.
poke r <register> <value> Modifica o conteúdo do registrador <register> com o valor <value>. Por exemplo, o comando poke r x1 0xff grava o valor 0xff no registrador x1.
poke m <address> <value> Modifica o conteúdo da posição de memória associada ao endereço <address> com o valor <value>. Por exemplo, o comando poke m 0x800 0xfe grava o valor 0xfe na posição de memória associada ao endereço 0x800.
run Executa o programa continuamente até que a execução termine através da chamada de sistema exit ou da execução de instruções inválidas.

Exemplo de Execução Interativa

Nesta seção mostraremos um exemplo de execução interativa com o simulador ALE. Para isso, utilizaremos o programa simple-debug.s, como segue:

.globl _start

_start:
  li x11, 21          # carrega o valor 21 no registrador x11
  li x12, 21          # carrega o valor 21 no registrador x12
  add x10, x11, x12   # soma o conteúdo dos registradores x11 e x12 e 
                      # grava o resultado no registrador x10
  li a7, 93           # carrega o valor 93 no registrador a7
  ecall               # gera uma interrupção por software

Este programa carrega o valor 21 nos registradores x11 e x12, realiza a soma destes valores e grava o resultado no registrador x10, e, por fim, invoca a syscall exit. Para isso, o programa carrega o valor 93 no registrador a7 e gera uma interrupção por software invocando a executando ecall.

O primeiro passo é carregar o arquivo simple-debug.s no simulador, para isso, basta utilizar o botão de carregamento de arquivos (azul), como discutido no Laboratório 2. Em seguida, você deve abrir o Terminal de Execução Interativa. Uma vez que você abrir o terminal de execução interativa, você deve ver mensagens que indicam que o programa simple-debug.s foi montado e sua execução iniciada no modo interativo, como ilustrado na figura a seguir:

t3

Agora basta clicar no terminal e digitar comandos para iniciar a interação com o simulador. Para ilustrar este procedimento, executaremos, a seguinte sequência de comandos.

peek r x11
step
peek r x11

A abaixo mostra o conteúdo do terminal interativo após a execução dos comandos peek r x11, step e peek r x11. O primeiro comando mostra que o conteúdo do registrador x11 é zero (0x00000000). O segundo comando avança a execução do programa em uma instrução. Note que o simulador exibe a instrução addi x11, x0, 0x15, que realiza a soma do conteúdo do registrador x0 (que é sempre zero) com a constante 0x15 (ou seja, 21 na base decimal) e grava o resultado no registrador x11. Em nosso programa em linguagem de montagem tínhamos a instrução li x11, 21 (load immediate), que carrega o valor 21 no registrador x11. A instrução load immediate é uma pseudo instrução (não existe na arquitetura) e ela é sempre transformada pelo montador para a instrução addi. Por fim, o terceiro comando inspeciona o conteúdo do registrador x11. Note, que desta vez ele contém o valor 21 (0x00000015).

t4

Agora, executaremos a seguinte sequência de comandos para avançar com nosso programa até o ponto final, ou seja, até invocamos a syscall exit.

step
step
step
peek r x10
step

Os três primeiros comandos step avançam a execução até a instrução ecall, ou seja, executa as instruções li x12, 21, add x10, x11, x12 e a instrução li a7, 93. O comando seguinte exibe o conteúdo do registrador x10 e, por fim, o último comando avança a execução levando o simulador a executar a instrução ecall. A figura abaixo mostra o terminal interativo após a execução destes comandos. Observe que, novamente, as instruções li do programa fonte foram codificadas como instruções addi. Além disso, observe que o valor do registrador x10 foi modificado para 0x2a (42 na base decimal). Por fim, veja que o programa terminou com o código 42. Isso aconteceu por que passamos como parâmetro para a syscall exit o valor 42.

t5

No exemplo acima nós usamos o comando step para avançar a execução do programa instrução por instrução. No entanto, em diversas situações é de interesse avançar a simulação automaticamente até um certo ponto do programa. Para isso, você pode usar o comando until. Este comando toma como parâmetro o endereço da instrução que está no ponto onde você quer avançar. Na figura anterior podemos ver que a instrução ecall está no endereço 0x000110c4, logo, se quiséssemos avançar a execução até este ponto, poderíamos executar o comando until 0x000110c4. A figura a seguir mostra o terminal interativo após a execução do comando until 0x000110c4. Note que o simulador mostrou a execução das quatro primeiras instruções do programa.

t6

Identificando o Endereço Associado às Instruções

Como visto acima, você pode usar o endereço da instrução para avançar a execução até a instrução de interesse. Para isso, você precisa do endereço da instrução. A forma mais direta de se obter o endereço de uma instrução é utilizando o desmontador. Para isso, você deve desmontar o arquivo executável final. O Laboratório 1 discute como invocar o desmontador.

Outra forma de se identificar o endereço de uma instrução é a partir do rótulo que a precede no código. Em nosso exemplo, o endereço da primeira instrução corresponde ao endereço do rótulo _start. Para identificar os endereços associados aos rótulos do seu programa basta executar o comando symbols.

Caso a instrução de interesse não tenha um rótulo que a precede, você pode adicionar um rótulo novo (com um nome diferente dos outros rótulos do programa). Este rótulo não afetará a geração do código, ou seja, o conjunto de instruções emitidas será o mesmo. O código a seguir mostra o programa simple-debug-2.s modificado com um rótulo para facilitar a identificação do endereço da instrução li a7, 93. Ao carregar este programa no simulador e executar o comando symbols no terminal interativo você verá os endereços associados aos símbolos _start e `before_exit_ .

globl _start
_start:
  li x11, 21          # carrega o valor 21 no registrador x11
  li x12, 21          # carrega o valor 21 no registrador x12
  add x10, x11, x12   # soma o conteúdo dos registradores x11 e x12 e 
                      # grava o resultado no registrador x10
before_exit:
  li a7, 93           # carrega o valor 93 no registrador a7
  ecall               # gera uma interrupção por software

Exercícios

Este exercício exige que os alunos usem os comandos interativos do simulador ALE para inspecionar o conteúdo dos registradores e locais de memória de um programa em execução.

Neste exercício você deve modificar o código abaixo com o número do seu histórico acadêmico (RA), montar e vincular o código e executá-lo passo a passo no modo interativo do simulador, conforme explicado nas seções anteriores.

.globl _start

_start:
  li a0, 134985  #<<<=== Registro do Aluno (RA)
  li a1, 0
  li a2, 0
  li a3, -1
loop:
  andi t0, a0, 1
  add  a1, a1, t0
  xor  a2, a2, t0
  addi a3, a3, 1
  srli a0, a0, 1
  bnez a0, loop

end:
  la a0, result
  sw a1, 0(a0)
  li a0, 0
  li a7, 93
  ecall

result:
  .word 0

Este programa recebe como entrada o valor do seu RA no registrador a0 e produz como saída os valores nos registradores a1, a2 e a3. Suponha que o programa recebeu seu RA como entrada e responda às seguintes perguntas:

  1. Quais são os valores dos registradores a0, a1, a2 e a3 em representação hexadecimal quando a execução atinge o rótulo end”?
  2. Quais são os valores dos registradores a0, a1, a2 e a3 em representação hexadecimal quando a execução atinge o rótulo “loop” pela quinta vez?
  3. Quais são os valores dos registradores a0, a1, a2 e a3 em representação hexadecimal após o simulador executar as primeiras 25 instruções?
  4. Quais são os valores dos registradores a0, a1, a2 e a3 em representação hexadecimal quando a execução atinge o rótulo “loop” pela oitava vez?
  5. Quais são os valores dos registradores a0, a1, a2 e a3 em representação hexadecimal após o simulador executar as primeiras 30 instruções?
  6. Quais são os valores dos registradores a0 e a3 em representação hexadecimal assim que os conteúdos de a1 e a2 são diferentes de 0 e possuem o mesmo valor?
  7. Qual valor (em representação hexadecimal) é armazenado no local da memória de result após a execução da instrução sw a1, 0(a0) (localizada após o rótulo end)?

Entrega

O arquivo a ser submetido no Classroom deve ter o nome lab03.txt contendo as respostas das perguntas anteriores e seguindo a seguinte estrutura

RA: SEU_RA
Q1: HEXADECIMAL_PARA_a0, HEXADECIMAL_PARA_a1, HEXADECIMAL_PARA_a2, HEXADECIMAL_PARA_a3
Q2: HEXADECIMAL_PARA_a0, HEXADECIMAL_PARA_a1, HEXADECIMAL_PARA_a2, HEXADECIMAL_PARA_a3
Q3: HEXADECIMAL_PARA_a0, HEXADECIMAL_PARA_a1, HEXADECIMAL_PARA_a2, HEXADECIMAL_PARA_a3
Q4: HEXADECIMAL_PARA_a0, HEXADECIMAL_PARA_a1, HEXADECIMAL_PARA_a2, HEXADECIMAL_PARA_a3
Q5: HEXADECIMAL_PARA_a0, HEXADECIMAL_PARA_a1, HEXADECIMAL_PARA_a2, HEXADECIMAL_PARA_a3
Q6: HEXADECIMAL_PARA_a0, HEXADECIMAL_PARA_a3
Q7: VALOR_PARA_LOCAL_DE_MEMÓRIA_result

Dica

  • Para ver a lista completa de comandos disponíveis no modo interativo você pode executar o comando "help"
  • Para entender quando os valores de a1 e a2 serão diferentes de 0 mas possuirão o mesmo valor busque identificar como esses registradores são atualizados a partir do código