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.
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.
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:
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).
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.
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.
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:
- Quais são os valores dos registradores
a0,a1,a2ea3em representação hexadecimal quando a execução atinge o rótulo end”? - Quais são os valores dos registradores
a0,a1,a2ea3em representação hexadecimal quando a execução atinge o rótulo “loop” pela quinta vez? - Quais são os valores dos registradores
a0,a1,a2ea3em representação hexadecimal após o simulador executar as primeiras 25 instruções? - Quais são os valores dos registradores
a0,a1,a2ea3em representação hexadecimal quando a execução atinge o rótulo “loop” pela oitava vez? - Quais são os valores dos registradores
a0,a1,a2ea3em representação hexadecimal após o simulador executar as primeiras 30 instruções? - Quais são os valores dos registradores
a0ea3em representação hexadecimal assim que os conteúdos dea1ea2são diferentes de 0 e possuem o mesmo valor? - Qual valor (em representação hexadecimal) é armazenado no local da memória de
resultapós a execução da instruçãosw a1, 0(a0)(localizada após o rótuloend)?
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





