Debugger do openMSX


  Que tal elevar o nível de depuração de seus programas em Assembly? O debugger do openMSX permite o amplo acesso aos registradores do MSX e o controle da máquina.

  Afinal o que é um depurador?

  Um depurador, ou debugger em inglês, é um programa que permite a análise do funcionamento interno de um programa, de modo que seja possível executar as operações passo a passo e que também seja possível obter o estado da máquina, com o objetivo de permitir ao analista observar o comportamento do programa e identificar possíveis erros de execução.
  No tutorial do RSCII, podemos encontrar um exemplo de depuração de programa na seção "simulador passo a passo". Nesse exemplo, criamos um programa em Assembly e executamos o depurador através da instrução SI.
  Apesar desse depurador ser poderoso, há certas limitações quanto ao seu uso. Por exemplo, ele não permite a troca dos slots 1 e 3 de memória, uma vez que na página 1 se encontra o programa RSCII e a página 3 contém as variáveis de sistema.
  Já o depurador do openMSX está fora da máquina virtual MSX, podendo assim ter acesso irrestrito aos estados do MSX, desde que previstos pelos seus desenvolvedores.


  1. Conhecendo o Debugger do openMSX

  Após instalar o debugger do openMSX no seu sistema, é só abrir. No Linux, geralmente o chamamos na linha de comando assim:
openmsx-debugger

  Após o carregamento, a interface do programa aparece na tela.

 

  A interface é composta das seguintes áreas:
  1.1 Menu superior

  O menu superior contém todas as funções do depurador. Ele é composto das seguintes opções:

Opção Descrição
File Permite carregar/salvar sessões de depuração.
System Opções de controle do emulador.
Search Ferramenta de busca por locais de memória no visualizador de código.
View Escolhe quais painéis de estado de memória devem aparecer.
Execute Controle de execução do depurador.
Breakpoint Permite a criação/eliminação de pontos de parada do programa na memória.


  Opções para o File:
  Opções para o System:
  Opções para o Search:
  Opções para o View:
  Opções para o Execute:   Obs: os steps são opções para quando o micro estiver pausado por um comando break ou breakpoint.


  Opções para o Breakpoint:

  1.2 Botões

  Os botões apresentam as funcionalidades principais do menu, de forma a serem acessadas mais rapidamente.

  Temos os seguintes botões:

 


  1.3 Painéis com os estados da máquina

  Cada painel de estado da máquina apresenta as informações relativa a memória, flags, registradores etc.

  Temos os seguintes painéis:
  Nos exemplos mais adiante, iremos ver como funcionam alguns desses painéis.


  2. Conectando o depurador ao openMSX

  Após abrir o depurador, abra também o emulador openMSX.

  Apesar de ambos os programas estarem agora em funcionamento, é necessário conectar o depurador ao emulador. Para isso, abra a opção "System", "Connect" do menu ou clique no botão "connect".
  Os painéis do depurador passam de inativos a ativos, sendo preenchidos com os dados vindos do emulador. A imagem a seguir apresenta a mudança do depurador, quando conectado ao openMSX.

 

  Ainda não é possível depurar a execução do micro, pois ele está em pleno funcionamento. Veremos como fazer isso na próxima seção.

  De forma a ilustrar como podemos ver e editar e modificar certos recursos do MSX no depurador, vamos utilizar a emulação do Expert 1.1 da Gradiente. Ao atingir a tela principal do MSX (e o depurador conectado), vamos abrir a opção "View", "add debuggable viewer". A opção VRAM é a primeira que aparece e e ela que vamos utilizar.

 

  Observe na figura anterior, que na screen 0 o texto do inicio da VRAM corresponde ao texto que aparece na tela.
  Podemos editar e alterar o texto em tempo real através do "debuggable viewer". Para isso, clique em um caractere da área de texto (assinalada com o retângulo vermelho) e altere os valores contidos ali. Eu alterei o texto de "versão 1.1" para "versão 2.0".

 

  Conforme dito, essa alteração é refletida imediatamente no emulador.

 

  Os painéis da tela principal do depurador são utilizados somente durante a depuração de programas. É o que veremos na próxima seção.


  3. Depurando

  Podemos aproveitar o exemplo do tutorial do RSCII para utilizá-lo nesse depurador.
		10	ORG &HC000
C000  3E 05	20	LD A,5
C002  06 04	30	LD B,4
C004  80	40	ADD A,B
C005  C9	50	RET

  Em Basic, podemos digitar o programa a seguir para colocar o programa na memória.
10 FOR E=&HC000 TO &HC005
20 READ A$
30 POKE E,VAL("&H"+A$)
40 NEXT E
50 DATA 3E,05,06,04,80,C9
  Ao digitar o programa, execute com o RUN para que o programa seja de fato colocado na memória.

  Utilizando a opção do depurador "Search", "Goto ...", digite o endereço $C000.

 

  Observe o nosso programa na área de código, assinalado com um retângulo vermelho.

  Uma observação importante deve ser feita sobre a área de código: a geração dos mnemônicos depende sempre de onde o desassemblador começa seu trabalho. Como ele não começa em &HC000 e devido ao fato de termos instruções em Z80 de um a quatro bytes, a interpretação dessa área de memória pode alterar drasticamente. Por exemplo, se antes da instrução LD A,5 na posição &HC000 tivéssemos um byte relativo a uma instrução de mais de um byte, a seqüência do programa seria alterada.
  Dê o comando Basic a seguir e observe a mudança.
POKE &HBFFF,&H3E
  Resultado:

 

  Nosso código foi bagunçado. Entretanto, isso ocorre somente no visualizador de código do depurador, pois se executarmos o programa no MSX, iremos sempre apontar o inicio para o local correto. Dessa forma, o MSX irá começar a executar o código em &HC000, ignorando o que vem antes.

  De forma a corrigir o problema do visualizador de código, podemos alterar os quatro bytes anterior ao código para &HFF ou &H00.
  É possível alterar valores de memória diretamente no painel "Memória principal" do depurador. Para isso, digite o valor FF nas posições de memória necessárias para que o código não fique bagunçado.

 

  Se você preferir, pode digitar o código de máquina de um programa em Assembly diretamente nesse painel.


  3.1 Criando breakpoints

  Não podemos analisar o código do nosso programa, se não pararmos a execução do MSX. Para isso, estabelecemos pontos de parada ou breakpoints em determinadas instruções do nosso código.
  Como desejamos ver toda a execução de nosso código, criamos um breakpoint no endereço &HC000. Para isso, clique com o mouse do lado esquerdo desse endereço, de forma a aparecer uma bolinha vermelha. Outra forma de fazer isso é clicando e selecionando uma determinada instrução (o retângulo sob ela fica laranja), e depois teclar F5. O F5 habilita ou desabilita o breakpoint.

 

  Uma vez criado o breakpoint, é necessário colocar o nosso programa em execução. Para isso, digite em Basic:
DEFUSR=&HC000:X=USR(0)
  O emulador irá parar (Break). No visualizador de código, a seta verde indica a próxima instrução a ser executada.

 

  A opção do menu "Excecute", "Step into" irá executar a instrução atual e depois ir para a próxima instrução. Essa opção pode ser executada pressionando-se F7 no depurador.
  O estado inicial dos registradores (no meu caso) é o seguinte:

 

  Ao executar a instrução LD A,5, o registrador A é modificado.

 

  A opção "Step back" permite desfazer a execução da última instrução. Assim, se teclarmos F12, voltamos para o endereço &HC000 e o valor dos registradores alterados são recuperados.

 

  A opção "Step over" ou F8 serve para executar direto as funções (rotinas chamadas pela instrução CALL end), sem que vejamos o que ocorre dentro dela.
  Como exemplo, crie um breakpoint no endereço &HC000 do depurador e execute o programa abaixo.
10 POKE &HC000,&HCD ' CALL &HC3 (CLS)
20 POKE &HC001,&HC3
30 POKE &HC001,0
40 POKE &HC001,&HC9 ' RET
50 DEFUSR=&HC000 : X=USR(0)

  Ao pressionar F8, o depurador executa a rotina CLS (&H00C3) da ROM e retorna imediatamente para a próxima linha do nosso programa (instrução RET). O passo a passo das instruções dessa função não é apresentado, mas os resultados da execução dessa rotina são refletidos em toda a máquina. Observe no emulador que a tela foi apagada.
*   C000  CD C3 00    call #00c3
  → C003  C9          ret

  Obs: o depurador não possui uma função para saltar a instrução corrente como faz o RSCII.

  Se repetirmos a experiência anterior, mas fazendo o "Step into", entraremos na função CLS da ROM. Avance duas instruções. Agora, utilize o "Step out" ou tecle F11. O que aconteceu? O restante da função CLS da ROM foi executado, retornando para a próxima instrução do nosso programa.
*   C000  CD C3 00    call #00c3
  → C003  C9          ret

  Ao executarmos todo o programa, verificamos que o registrador A contém o valor 9, o que era esperado por nós.

  Podemos também executar tudo de uma vez. Para isso, introduzimos um novo breakpoint no local onde desejamos que o programa pare. Nesse caso, coloquei no endereço &HC005, em cima da instrução RET (que não é executada).
  Observe na imagem a seguir que a execução está em &HC000 e o ponto de parada é &HC005. Ao teclar F4 ou utilizar a opção "Run to", o programa executa todo o trecho de código compreendido entre o ponto da posição atual e o próximo breakpoint (exclusive).

 

  Para voltar a fazer o MSX rodar normalmente, clique no ícone do "Run".


  3.2 Identificando erros

  O exemplo a seguir aconteceu comigo devido a um erro durante a elaboração de um programa que fiz. Nesse programa, pude constatar no depurador qual era o problema.
  No programa do placar, o código em Assembly que copia uma parte da imagem do buffer para a tela principal continha um erro que fazia a tela toda ser preenchida com lixo.
  Há duas rotinas da ROM que fazem a transferência de blocos de memória entre a RAM e VRAM. Cada uma dessas instruções utiliza os registradores DE, BC e HL como parâmetro de entrada de dados. Esse erro acontecia porque como o valor de BC é o mesmo para as duas rotinas, eu havia eliminado a linha 220. Entretanto ambas as rotinas alteram o valor dos registradores após a sua execução, inclusive o BC.
End   Cód. Máq.   Linha         Mnemônicos
C018  11 00 D0	  170 INI:	LD   DE,&HD000	; Endereço inicial RAM
C01B  01 98 01	  180 		LD   BC,408	; Tamanho do bloco a copiar
C01E  CD 59 00	  190 		CALL &H59	; Copia bloco VRAM -> RAM
C021  21 00 D0	  200 		LD   HL,&HD000	; Endereço inicial da RAM
C024  11 40 0E	  210 		LD   DE,3648	; Endereço do caractere 200 da tab ASCII
C027  01 98 01	  220 		LD   BC,408	; Tamanho do bloco a copiar
C02A  CD 5C 00	  230 		CALL &H5C	; Copia bloco VRAM -> RAM
  Ao executarmos o programa até a linha 180 (inclusive), o estado dos registradores é o seguinte:

 

  No qual percebemos que os registradores BC, DE e HL contêm os valores esperados antes de chamar a rotina que copia da VRAM para a RAM.
  Entretanto, logo após executar a rotina da ROM, observe os valores desses registradores.

 

  Alguns registradores foram alterados, principalmente o BC. Dessa forma, ele deverá ser carregado novamente com o valor &H198, que são 408 bytes. No caso do meu bug, o valor de BC era igual a 0, correspondendo a 65536 bytes a serem copiados.


  3.3 O uso da pilha

  O programa em Basic a seguir ilustra o uso da pilha do Z80. Crie um breakpoint em &HC000 e depois rode-o.
10 POKE &HC000,&HD5 ' PUSH DE
20 POKE &HC001,&HC9 ' RET
30 DEFUSR=&HC000
40 X=USR(0)

 

  Eu editei e alterei o registrador DE para o valor &H1234. Ao executar a instrução PUSH DE (com o F7), temos a pilha sendo alterada. Como o endereço do SP marca o topo da pilha, o próximo valor é colocado na posição imediatamente anterior.

 



MarMSX/Cursos/Assembly - MarMSX 2020