Assembly NumaBoa - Capítulo 4

LABORATÓRIO - MÓDULO 1/2

Para escrever bons programas em Assembly é preciso entender como a CPU referencia dados na memória. Os processadores Intel da família 80x86 permitem uma grande variedade de modos de endereçamento.

Os processadores 80386 e posteriores possuem um conjunto ampliado de modos de endereçamento. Por um lado, a maior variedade de modos de endereçamento amplia as possibilidades de otimização. Por outro lado, podem complicar a tarefa de escolher o modo mais apropriado. Neste laboratório vamos trabalhar com a memória do PC e estudar os vários modos de endereçamento 80x86. Também vamos explorar tipos de dados de linguagens de alto nível e sua implementação na linguagem Assembly.

Como suporte, usaremos o CodeView da Microsoft e o SIMx86. Finalmente vamos começar a escrever programas reais em Assembly, compilando-os e fazendo a linkedição com o MASM, o macroassembler da Microsoft.

O software necessário

Uma olhada rápida no CodeView

Para rodar o Codeview, digite este comando de linha do DOS ou clique no botão [Iniciar] do Windows, item [Executar] e digite:

CV programa.exe

Programa.exe é o nome do programa que se deseja debugar (o sufixo ".exe" é opcional). O CodeView pede o nome de um programa ".exe" ou ".com". Caso não seja fornecido o parâmetro, o CodeView apresentará a opção de escolher um arquivo.

Como ainda não temos um executável, sugiro que você use inicialmente o programa SHELL.EXE, disponível para download. A tela mostrada será algo como:

CodeView

Fig.1 - Tela principal do CodeView
Item de menu

Fig.1a - Item "Windows" do menu

Existem seis seções na tela acima: a barra de menu, o painel das variáveis locais [1], o painel do código fonte [3], o painel dos registradores [7], o painel de comando [9] e a barra de ajuda/status. Observe que o CodeView possui muitos outros painéis além dos mostrados na Fig.1. Com a tecla F6 podemos passar facilmente de um painel para outro.

Os painéis (ou janelas) são totalmente configuráveis. O menu "Windows" permite selecionar os painéis que aparecem na tela. Como com a maioria dos produtos Microsoft, seleciona-se os itens da barra de menu digitando a tecla "Alt" e a tecla correspondente à primeira letra do item de menu desejado. Por exemplo, digitando "Alt" e "W", abre-se o menu "Windows".

O painel de código

Os itens Source1 e Source2 referem-se a dois painéis de código. Estes dois painéis permitem que se veja simultaneamente seções diferentes do programa debugado.

Painéis de código são úteis para fazer debugging em nível de código (voltaremos mais tarde ao assunto).

O painel da memória

Os itens Memory1 e Memory2 do item de menu "Windows" permite abrir painéis que mostram e permitem a modificação do conteúdo da memória. Como padrão, estes painéis mostram as variáveis no segmento de dados, mas é possível visualizar qualquer área da memória digitando-se o endereço desejado. Escolha o item de menu "Windows" com "Alt+W", selecione "Memory1" e depois escolha "Maximize" para obter uma tela parecida com a mostrada abaixo:

Memória

Fig.1b - Memória do segmento de dados

Os valores à esquerda do painel são os endereços segmentados da memória. As colunas centrais com valores hexadecimais representam os valores de 16 bytes localizados a partir do endereço especificado. Finalmente, os caracteres à direita representam os caracteres ASCII de cada um dos 16 bytes mostrados. Observe que o CodeView mostra pontos para os valores que não possuam caracteres ASCII que não podem ser impressos.

Quando este painel é chamado pela primeira vez, começa mostrando os dados a partir do deslocamento zero do segmento de dados. Existem vários modos de mostrar localizações diferentes da memória. Primeiro, é possível usar as teclas "Page Up" e "Page Down" para rolar os dados no painel. Outra opção é mover o cursor sobre um segmento ou porção do deslocamento e digitar um novo valor. À medida que se digita, o CodeView mostra automaticamente os dados do novo endereço.

Se quisermos modificar valores na memória basta mover o cursor sobre o byte desejado e digitar o novo valor hexadecimal. O CodeView atualiza automaticamente o byte correspondente.

O CodeView permite abrir dois painéis de memória de modo que seja possível comparar valores não contíguos da memória. Para trocar os painéis, basta usar a tecla F6.

Q04.01

	1. Abrir o painel de memória.
	2. Digitar 1000 na porção do segmento do endereço.
	3. Digitar 0000 na porção do deslocamento do endereço.

Q04.02

	1. Posicionar o painel de memória em 1000:000.
	2. Mover o cursor para o byte correspondente.
	3. Digitar 00.

Com o painel de memória ativado, digitar "Shift+F3" alterna o modo de apresentação dos dados entre bytes hexadecimais, caracteres ASCII, words, double words, inteiros com sinal, valores em ponto flutuante e outros tipos de dados. Esta característica é muito útil para observar a memória usando diferentes tipos de dados.

O painel dos registradores

O item "Register" do menu "Windows" mostra ou esconde o painel dos registradores 80x86 com seus valores atuais (veja a Fig.1).

Para alterar o valor de um registrador, ative o painel dos registradores (usando F6, se não estiver ativado), mova o cursor sobre o valor que deseja alterar e digite o novo valor. Observe que FL se refere às flags. Pode-se alterar o valor do registrador de flags movendo o cursor para FL= e digitando um novo valor. Outra maneira de alterar as flags é movendo o cursor para a parte de baixo do painel de registradores, posicionar o cursor sobre a flag desejada e digitar uma tecla alfabética (por exemplo, "A"). Isto trocará o valor da flag em questão. Os valores das flags são (0/1): overflow (OV/NV), direção (DN/UP), interrupção (DI/EI), sinal (PL/NG), zero (NZ/ZR), carry auxiliar (NA/AC), paridade (PO/PE) e carry (NC/CY).

Observe que, pressionando a tecla F2, muda a forma da apresentação do painel de registradores. Esta característica é muito útil quando se debuga programas. O painel de registradores ocupa cerca de 20% da tela e, com F2, é possível escondê-lo ou chamá-lo quando necessário.

Q04.03

	1. Ativar o painel de registradores com F6.
	2. Posicionar o cursor sobre o valor do registrador
	   que deve ser alterado.
	3. Digitar o novo valor.
O painel de comando

O painel de comando permite digitar comandos no estilo do SIMx86 no CodeView. Apesar de praticamente qualquer comando disponível no painel de comando também estar disponível em qualquer outro painel, muitas operações são mais fáceis de serem efetuadas através do painel de comando. Além disso, geralmente podemos executar com maior rapidez uma sequência de comandos completamente diferentes no painel de comando do que ficar trocando de painéis. A operação do painel de comando será vista a seguir.

O item de menu "Output"

Selecionando "View Output" do menu "Windows" (ou digitando a tecla F4) alterna o display entre o CodeView e a saída do programa. No entanto, quando o programa assume o controle, a janela do debugger oculta a saída. Se precisarmos do valor de saída do programa, então a tecla F4 realiza esta tarefa.

O painel de comando do CodeView

Na verdade, o CodeView são dois debugadores em um. Por um lado, é um sistema de debugging com suporte a mouse. Por outro, funciona como um debugador tradicional baseado em linhas de comando (como o SIMx86). O painel de comando permite este funcionamento tradicional. Abaixo estão alguns comandos comuns que usaremos neste laboratório:

ComandoResultado
A endereçoConstruir (Assemble)
BC numero_bpLimpar ponto de parada (Breakpoint Clear)
BD numero_bpDesabilitar ponto de parada (Breakpoint Disable)
BE numero_bpHabilitar ponto de parada (Breakpoint Enable)
BLListar pontos de parada (Breakpoint List)
BP endereçoColocar ponto de parada (Breakpoint Set)
D faixaListar memória (Dump Memory)
EAnimar execução
Ex endereçoEntrar comandos (x= " ", b, w, d, etc)
G {endereço}Ir (Go - endereço é opcional)
HAjuda (Help)
I portaLer dados de uma porta de entrada (Input)
LReiniciar o programa
MC faixa endereçoComparar dois blocos de memória (Memory Compare)
MF faixa valor(es)Preencher a memória com valor(es) especificado(s) (Memory Fill)
MM faixa endereçoCopiar bloco de memória (Memory Move)
MS faixa valor(es)Procurar num intervalo da memória um conjunto de valores (Memory Search)
N valor10Estabelecer a base padrão
O porta valorPor valor numa porta de saída (Output)
PPasso do programa
QTerminar (Quit)
RRegistrador
Rxx valorPor valor no registrador xx
TRastrear (Trace)
U endereçoDesassemblar declarações no endereço (Unassemble)
O comando seleção da base (N)

O primeiro comando que precisamos aprender é o da seleção da base. O padrão do CodeView é decimal (base 10), o que não é muito conveniente para programadores Assembly que trabalham com a base hexadecimal. Para alterar a base use o comando N 16.

O comando construir (A)

O comando construir (assemble) é parecido com o do SIMx86. A sintaxe é A endereço.

Endereço é o endereço inicial das instruções de máquina. Pode ser um endereço segmentado completo (ssss:dddd, onde s é o segmento e d o deslocamento) ou um simples valor de deslocamento na forma dddd. Se apenas o deslocamento for especificado, o CodeView usa o valor atual de CS como o endereço do segmento.

Depois de pressionar a tecla [Enter], o CodeView solicita a entrada de uma sequência de instruções de máquina. Pressionando [Enter] novamente encerra a entrada das instruções Assembly.

O comando Assemble é um dos poucos que só estão disponíveis no painel de comando. Em geral, este comando é útil para pequenas alterações no código mas não é um substituto para o MASM. Qualquer alteração feita através do comando A não atualiza o código fonte! Cuidado, é comum fazer correções no CodeView e depois esquecer de atualizar o código fonte.

Q04.04

	1. Ativar o painel de memória com F6.
	2. Digitar o endereço inicial 0000:0000
O comando de comparação de memória (MC)

Este comando compara os bytes de um bloco da memória com os bytes de um bloco diferente, indicando as diferenças encontradas. É muito útil, por exemplo, para ver se um programa inicializou dois arrays idênticos ou para comparar strings longas. O comando MC pode ter duas formas:

MC end_inicial end_final end_segundo_bloco [Enter]

MC end_inicial L compr_bloco end_segundo_bloco [Enter]

A primeira forma compara os bytes das posições de memória de end_inicial até end_final com os dados que se iniciam na posição end_segundo_bloco. A segunda forma permite especificar o tamanho do bloco ao invés do endereço final do primeiro bloco. Se o CodeView detectar alguma diferença, ele mostra estas diferenças e seus respectivos endereços. Os comandos seguintes são aceitos:

MC 8000:0 8000:100 9000:80

MC 8000:100 L 20 9000:0

MC 0 100 200

O primeiro comando compara o bloco de bytes nas posições 8000:0 a 8000:100 com os de um bloco do mesmo tamanho que se inicia no endereço 9000:80 (isto é, de 9000:80 a 9000:180).

O segundo comando demonstra o uso da opção "L". Neste exemplo, o CodeView compara os valores na faixa 8000:00 até 8000:1F (20h = 32 bytes) com os que se iniciam no endereço 9000:0.

O terceiro exemplo mostra que não há necessidade de se indicar endereços segmentados completos. Como padrão, o CodeView usa o segmento de dados (DS:) se a porção de segmento do endereço for omitida. No entanto, observe que, se fornecermos um endereço inicial e final, ambos precisam estar no mesmo segmento.

Q04.05

	MC F00:100 L 1000 2000:0

Q04.06

	MC DS:100 DS:1FF DS:200
O comando listar memória - dump (D)

O comando dump permite mostrar os valores de células de memória selecionadas. O painel de memória do CodeView também permite ver (e modificar) a memória. O comando dump, no entanto, algumas vezes é mais conveniente, principalmente quando se quer ver pequenos blocos de memória.

O comando dump possui diversas formas, dependendo do tipo de dados que se quer mostrar na tela. Tipicamente assume uma das seguintes formas:

D end_inicial end_final [Enter]

D end_inicial L comprimento [Enter]

Como padrão, o comando dump mostra 16 valores de bytes por linha, em hexadecimal ou ASCII. Se entrarmos o comando dump sem um endereço, o CodeView mostrará os dados imediatamente seguintes ao último comando dump. Isto pode ser prático quando se mostra a memória. Existem várias outras formas, nas quais é possível especificar o formato dos dados. Por exemplo:

DA faixa_end
DB faixa_end
DI faixa_end
DIU faixa_end
DIX faixa_end
DL faixa_end
DLU faixa_end
DLX faixa_end
DR faixa_end
DRL faixa_end
DRT faixa_end
Dump de caracteres ASCII
Dump de bytes hexa/ASCII (default)
Dump de words inteiros
Dump de words inteiros sem sinal
Dump de valores de 16 bits em hexa
Dump de inteiros de 32 bits
Dump de inteiros de 32 bits sem sinal
Dump de valores de 32 bits em hexa
Dump de valores reais de 32 bits
Dump de valores reais de 64 bits
Dump de valores reais de 80 bits

Q04.07

	DR

Q04.08

	DRT

Q04.09

	DA (ou DB)
O comando Entrar (E)

O painel de memória do CodeView permite mostrar e modificar o conteúdo da memória. A partir do painel de comando, são necessários dois comandos diferentes para realizar esta tarefa: dump para mostrar a memória e Entrar para modificar os dados da memória. Para a maioria das modificações, o painel de memória acaba sendo mais prático. Entretanto, o comando Entrar pode ser melhor em alguns casos.

Assim como o comando dump, o comando Entrar permite a entrada de dados em diversos formatos. Os comandos são:

EA-
EB-
ED-
EI-
EIU-
EIX-
EL-
ELU-
ELX-
ER-
ERL-
ERT-
Entrada de caracteres ASCII
Entrada de bytes em hexa
Entrada de double words em hexa
Entrada de inteiro de 16 bits com sinal no formato decimal
Entrada de inteiro de 16 bits sem sinal no formato decimal
Entrada de inteiro de 16 bits no formato hexa
Entrada de inteiro de 32 bits com sinal no formato decimal
Entrada de inteiro de 32 bits sem sinal no formato decimal
Entrada de inteiro de 32 bits no formato hexa
Entrada de valores 32 bits em ponto flutuante
Entrada de valores 64 bits em ponto flutuante
Entrada de valores 80 bits em ponto flutuante

Entrar tem duas formas possíveis:

Ex end_inicial [Enter]

Ex end_inicial lista_de_valores [Enter]

A primeira forma é a forma interativa do comando Entrar. O CodeView mostrará o endereço inicial e o dado neste endereço, depois solicitará um novo valor para esta posição. Digite o novo valor seguido pela tecla de espaço para que o CodeView solicite o valor da próxima posição. Digitando duas vezes seguidas a tecla de espaço salta-se uma posição. Digitando um valor seguido da tecla [Enter] ou duas vezes [Enter] termina o modo interativo. Observe que o comando EA não permite a entrada de valores ASCII no modo interativo. Ele se comporta exatamente como o comando EB durante a entrada de dados.

A segunda forma do comando Entrar permite a entrada de uma sequência de valores na memória com uma única entrada. Com esta forma, o CodeView armazena cada um dos valores em posições sucessivas de memória a partir do endereço inicial. Esta forma permite a entrada de caracteres ASCII colocando-os entre aspas.

Q04.10

	Usar o painel de memória.

Existem alguns pontos importantes que devem ser observados no comando Entrar. O primeiro é que "E" não pode ser usado como comando. Ao contrário do comando Dump, apenas "E" não significa que os dados serão inseridos após o endereço do último comando Entrar. Outro ponto é que o modo de apresentação (ASCII, byte, word, etc) e o modo de entrada corrente não são independentes. Mudando o modo de apresentação default para word também altera o modo de entrada para word e vice versa.

Q04.11

	EA 1000:10D "s"

Q04.12

	ERL endereço 1.25

Q04.13

	Vai mostrar valores do tipo word.
O comando preencher memória (MF)

O comando Entrar e o painel de memória permitem alterar com facilidade o valor de posições individuais da memória ou alterar os valores de um intervalo de posições de memória. Se quisermos limpar um array ou inicializar um bloco de memória de modo que contenham os mesmos valores, o comando Preencher Memória (Memory Fill) é uma alternativa melhor. Este comando usa a seguinte sintaxe:

MF end_inicial end_final valores [Enter]

MF end_inicial L compr_bloco valores [Enter]

O comando MF preenche as posições da memória do end_inicial até o end_final com bytes cujos valores estejam especificados na lista de valores. Na segunda forma, especifica-se o comprimento do bloco ao invés do endereço final.

A lista de valores pode ser um único valor ou uma lista de valores. Se valores for um único byte, então o comando MF inicializa todos os bytes do bloco da memória com este valor. Se valores for uma lista de bytes, o comando MF repete a sequência de bytes tantas vezes quantas forem necessárias para preencher o bloco de memória. Por exemplo, o comando MF 8000:0 L 100 1 2 3 4 5 coloca 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5... em 256 bytes iniciando na posição 8000:0.

Infelizmente o comando MF funciona apenas com bytes (ou string ASCII). Entretanto, é possível simular words, double words, etc, separando estes valores nos seus componentes byte. Mas não se esqueça, o byte menos significativo sempre vem primeiro.

Q04.14

		MF 1000:200 L 400 0
	ou
		MF 1000:200 1000:5ff 0

Q04.15

	Preenche 256 (100h) bytes com os caracteres "Oi pessoal"
	repetidos a partir do endereço inicial 1000:0
O comando copiar bloco de memória (MM)

O comando copiar (ou mover) memória copia os dados de um bloco de memória para outro. Isto permite copiar dados de um array para outro, mudar a posição de dados na memória, reinicializar um grupo de variáveis a partir de um bloco de memória e assim por diante. A sintaxe do comando é:

MM end_inicial end_final end_destino [Enter]

MM end_inicial L compr_bloco end_destino [Enter]

Se os blocos fonte e destino ficarem sobrepostos, o CodeView detecta a irregularidade e manipula a operação corretamente.

Q04.16

	MM FF0:124 L 132 8000:0

Q04.17

	MC FF0:124 L 132 8000:0
O comando Input (I)

O comando Input permite ler dados de uma das 65.536 portas de entrada do 80x86. A sintaxe deste comando é:

I end_porta [Enter]

onde end_porta é um valor de 16 bits que indica o endereço da porta E/S que deve ser lida. O comando lê o byte na porta e mostra seu valor.

Saiba que não é uma boa idéia usar este comando com um endereço arbitrário. Certos dispositivos ativam determinadas funções assim que suas portas E/S são lidas. Lendo estes valores pode-se provocar perda de dados ou distúrbios no dispositivo em questão.

Observe que este comando lê apenas um único byte da porta especificada. Se quisermos ler words ou double words, é necessário executar Inputs sucessivos na porta desejada e nas sucessivas.

Q04.18

	I 3F9
O comando Output (O)

O comando Output complementa o comando Input. Ele permite enviar um valor para uma porta e usa a sintaxe:

O end_porta valor_saída [Enter]

valor_saída é um único byte que o CodeView irá escrever na porta de saída especificada pelo end_porta.

Note que o CodeView também usa o comando "O" para definir opções. Se ele não reconhecer um endereço válido de porta como primeiro operando, pensará que se trata de um comando de opção. Se o comando Output não estiver funcionando adequadamente, provavelmente você desligou o modo Assembly (o CodeView aceita BASIC, Pascal, C, Fortran e Assembly) e o endereço da porta não é um número válido no novo modo. Certifique-se de usar o comando N 16 para garantir o uso da base hexadecimal antes de usar este comando!

Q04.19

		O 3F8 41
	ou
		0 3F8 "A"

Q04.20

	I 3F8
O comando registrador (R)

O comando registrador permite verificar ou alterar os valores de registradores. Para ver os valores atuais dos registradores 80x86 usa-se o seguinte comando:

R [Enter]

Este comando mostra os registradores e desassembla a instrução do endereço CS:IP. Também é possível alterar o valor de um registrador específico usando um comando na forma:

Rxx [Enter]
ou
Rxx = valor [Enter]

onde xx representa um dos nomes dos registradores 80x86: AX, BX, CX, DX, SI, DI, BP, SP, CS, DS, ES, SS, IP ou FL. A primeira versão (Rxx [Enter]) mostra o registrador especificado e depois solicita a entrada do novo valor. A segunda forma atribui imediatamente o valor ao registrador indicado.

Q04.21

	Usar o painel de registradores.
O comando desassemblar (U)

O comando Unassemble funciona como no SIMx86. Desassembla uma sequência de instruções no endereço especificado, convertendo os códigos de máquina binários em instruções de máquina (quase) legíveis. O comando básico usa a seguinte sintaxe:

U endereço

Cuidado: o painel do código precisa estar aberto para que este comando funcione corretamente!

Em geral, o comando Unassemble tem pouco uso porque o painel do código permite ver o programa em nível de fonte. Entretanto, o comando é excelente para desassemblar BIOS, DOS, TSRs e outros códigos residentes na memória.

Alguns comentários sobre os endereços no CodeView

Os exemplos anteriores contendo endereços parecem indicar que a única forma aceita é ssss:dddd ou dddd. Na realidade, existem várias maneiras de especificar endereços de memória. Por exemplo, se tivermos uma variável num programa Assembly de nome var1, poderíamos usar um comando como

D var1

Isto mostraria o valor da variável. Não é preciso saber o endereço e nem mesmo o segmento desta variável. Outro modo de especificar um endereço é através do conjunto de registradores 80x86. Por exemplo, se ES:BX apontar para um bloco de memória, podemos usar o seguinte comando para apresentar os dados:

D ES:BX

O CodeView usará os valores atuais dos registradores ES e BX como endereço do bloco. Não há nada de mágico no uso de registradores. Eles podem ser utilizados como qualquer outro componente de endereços. No exemplo acima, ES contém o valor do segmento e BX o valor do deslocamento - muito característico para um programa em linguagem Assembly 80x86. Entretanto, o CodeView não requer o uso legal de combinações 80x86. Por exemplo, pode-se fazer um dump dos bytes no endereço CX:AX usando o comando

D CX:AX

O uso dos registradores 80x86 não se limita em especificar endereços fonte. Também os endereços destino e até comprimentos podem ser especificados através de registradores:

D CX:AX L BX ES:DI

É claro que podemos misturar e combinar o uso de registradores e endereços numéricos no mesmo comando sem problemas:

D CX:AX L 100 8000:0

Também podemos usar expressões aritméticas complexas para especificar um endereço da memória. Podemos usar o operador de adição para calcular a soma dos vários componentes de um endereço. Isto funciona realmente bem quando precisamos simular modos de endereçamento 80x86. Por exemplo, se quisermos ver qual é o byte no endereço 1000[bx], podemos usar o comando:

D BX+1000 L 1

Para simular o modo de endereçamento [BX][SI] e buscarmos o word neste endereço podemos usar o comando:

DIX BX+SI L 1

Os exemplos mostrados usam o comando dump, mas podemos utilizar esta técnica com qualquer um dos comandos do CodeView. Para maiores informações sobre endereçamentos válidos no CodeView, consulte a ajuda (help) do programa.

Q04.22

	DW CS:BX+SI+4

Q04.23

	EW ES:DI+2 0

Comentários

Falamos muito do CodeView e, ainda assim, falta muita coisa. Esta foi apenas a parte preparatória e, à medida em que formos avançando, outros aspectos importantes serão detalhados. Agora prepare-se para enfrentar uma maratona sobre modos de endereçamento usando o CodeView.



| AAAA | Página Inicial | Mapa do Site | Novidades | Busca | Indique esta página | Mestre da Teia | Voltar |
| Localizador || @ Info NumaBoa > Assembly NumaBoa > Memória III > Laboratório cap.4 > Laboratório II cap.4
Autoria: Randall Hyde - Art of Assembly Language Programming. Tradução: vovó Vicki

webdesign sobMedida by vickiSoft - /informatica/assembly/cap4_lab.php (29.01.04) versão 1.0 de 05.02.04
Licença Creative Commons 1998-2006 Aldeia NumaBoa
Exceto onde especificamente declarado, todo material deste site é disponibilizado de acordo com a Licença Creative Commons.