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