A família de processadores 80x86 usa um esquema de gerenciamento poderoso chamado endereçamento segmentado. O endereçamento segmentado possui dois componentes: um segmento e um deslocamento (offset). Este modo de endereçamento permite que se organize programas em componentes logicamente distintos. Ao invés de se colocar todas as variáveis e o código num único lugar, podemos agrupar variáveis relacionadas e seções de código em segmentos diferentes. Isto facilita o entendimento e a manutenção dos programas.
| Endereço lógico | Cálculo | Endereço físico |
| 1000:100 | 10000 + 100 | 10100 |
| 1008:80 | 10080 + 80 | 10100 |
| 1010:0 | 10100 + 0 | 10100 |
| 100C:40 | 100C0 + 40 | 10100 |
| 10:8000 | 100 + 8000 | 8100 |
| 8000:10 | 80000 + 10 | 80010 |
O único problema é que as CPUs 80x86 endereçam a memória como um array linear de bytes e que precisam converter segmentos: deslocamento de endereços lógicos em endereços lineares de uma dimensão. Quando opera no modo real, o 80x86 transforma endereços lógicos em endereços físicos multiplicando o valor do segmento por 16 e depois somando o deslocamento. Veja alguns exemplos na tabela ao lado.
Quando se usa números hexadecimais, a transformação do endereço lógico no endereço físico é muito fácil. Basta adicionar um zero no final do valor do segmento e somar o resultado com o deslocamento.
(800h x 10h) + 8000 = 8000 + 8000 = 1 0000
12340 + 5678 ------ 179B8
1 ABCD0 + 1234 ------ ACF04
Um programa 80x86, na realidade, nunca vê um endereço físico. Endereços físicos são quantidades que aparecem no barramento de endereços. Um programa nunca as manipula. Por que, então, se preocupar com endereços físicos? Para entender, dê mais uma olhada na tabela. Observe que os quatro primeiros endereços, apesar de diferentes, acabam sendo mapeados para o mesmo endereço físico. Sempre que armazenarmos um valor de dado na memória, ele acaba em algum endereço físico. Duas variáveis diferentes, mesmo que tenham endereços lógicos diferentes, serão as mesmas se tiverem o mesmo endereço físico. Por exemplo, se a variável I aparece na memória na posição 1000:10 e a variável J aparece na memória na posição 1001:0, ao armazenar um valor em I o valor de J será alterado. O inverso também ocorre porque ambas estão localizadas no mesmo endereço físico. Lembre-se de que o endereço físico é onde a CPU armazena os dados. O motivo pelo qual precisamos saber converter endereços lógicos em físicos é para evitar sobrescrever o valor de uma variável simplesmente porque possui o mesmo endereço físico que uma outra.
Para verificar se um endereço lógico é um alias de outro (isto é, ambos compartilham a mesma posição na memória), simplesmente convertemos os dois endereços lógicos para endereços físicos. Se os endereço físicos forem iguais, uma é o alias da outra.
20000 + 2000 = 22000 38000 + 0 = 38000 10000 + 8000 = 18000 20800 + 1800 = 22000 30000 + 8000 = 38000 22000 + 0 = 22000 2F000 + 9000 = 38000 1FFF0 + 2010 = 22000 18000 + A000 = 22000
| Endereço lógico | Endereço físico | Endereço normalizado |
| 1000:100 | 10100 | 1010:0 |
| 1008:80 | 10100 | 1010:0 |
| 1010:0 | 10100 | 1010:0 |
| 100C:40 | 10100 | 1010:0 |
| 10:8000 | 8100 | 810:0 |
| 8000:10 | 80010 | 8001:0 |
A possibilidade de ocorrência de aliases é apenas uma das razões para se preocupar com endereços físicos. Comparar ponteiros é outro grande problema para os projetistas de software 80x86. Seria bom se ponteiros iguais apontassem para o mesmo objeto. Comparando endereços segmentados é bem possível encontrar dois ponteiros diferentes (isto é, os padrões dos seus bits não são iguais) que apontem para o mesmo endereço físico. Quando surge a necessidade de comparar ponteiros, a maioria dos programadores usam uma forma especial para endereços, canônica ou normalizada. Para normalizar um endereço lógico basta convertê-lo para seu endereço físico e depois colocar dois pontos entre o quarto e o quinto dígito do endereço físico de 20 bits.
Note que, se os ponteiros tiverem endereços normalizados, o padrão de bits será o mesmo se apontarem para o mesmo objeto.
82030 + 3583 = 855B3 = 855B:3
A família 80x86 possui 17 modos diferentes de endereçamento à memória que podem ser facilmente agrupados em cerca de cinco grupos diferentes: apenas deslocamento, indireto por registrador, indexado, base com indexado e base com indexado com deslocamento.
O modo de endereçamento mais fácil de ser entendido é o apenas por deslocamento. As instruções que usam este modo de endereçamento consistem num opcode (um ou mais bytes) seguido por uma constante de 16 bits. Esta constante é o deslocamento (offset) no segmento de dados desta instrução. Exemplos assumindo que I esteja no deslocamento 110h, J esteja no deslocamento 112h e que K esteja no deslocamento 100h do segmento extra:
mov ax, DS:I ; Busca AX da posição de memória DS:110h mov DS:J, ax ; Armazena AX na posição de memória DS:112h mov bx, ES:K ; Carrega BX com a posição ES:100h
DS:I 10000 + 110 = 10110 DS:J 10000 + 112 = 10112 ES:K 20000 = 100 = 20100
DS:I 10000 + 110 = 10110 -> 1011:0 DS:J 10000 + 112 = 10112 -> 1011:2 ES:K 20000 = 100 = 20100 -> 2010:0
O modo de endereçamento apenas por deslocamento é muito fácil de ser entendido pois calcular o endereço efetivo do operando é algo trivial. O único problema é que, na maior parte das vezes, usamos nomes simbólicos como "I" e "J", de modo que ficamos sem saber qual é o deslocamento (o assembler o determina para nós). Para nossa sorte, na maioria das vezes não precisamos do valor do deslocamento.
O próximo na escala de complexidade é o modo de endereçamento indireto por registrador. Estes modos incluem [BX], [BP], [SI] e [DI]. As instruções que usam este modo de endereçamento não codificam um deslocamento na instrução. Na verdade, são constituídas por um byte de opcode e um byte MOD-REG-R/M. Estes modos de endereçamento calculam os endereços efetivos usando os valores dos registradores especificados. Como padrão, os modos de endereçamento [BX], [DI] e [SI] usam o segmento de dados. O [BP] usa o segmento da pilha. Veja os exemplos assumindo que BX=100h, SI=200h, DI=300h, BP=400h; DS=1000h, ES=2000h, e SS=3000h:
mov ax, [bx] ; Carrega ax da posição DS:100h mov bx, [si] ; Carrega bx da posição DS:200h mov ss:[di], bx ; Armazena bx na posição SS:300h mov al, es:[bp] ; Carrega al da posição ES:400h
[bx] = 10000 + 100 = 10100 [si] = 10000 + 200 = 10200 ss:[di] = 30000 + 300 = 30300 es:[bp] = 20000 + 400 = 20400
mov ss:[di], bx => mov [di], bx -> Segmento de dados (DS) mov al, es:[bp] => mov al, [bp] -> Segmento da pilha (SS)
O modo de endereçamento indexado é o próximo no nível de complexidade. É uma combinação do apenas por deslocamento com o indireto por registrador. A codificação da instrução consiste tipicamente de um byte de opcode, um byte MOD-REG-R/M e um ou dois bytes da constante de deslocamento. A CPU calcula o endereço efetivo adicionando o valor do deslocamento (complemento de dois com sinal) ao valor do registrador. Modos de endereçamento possíveis incluem desloc[bx], desloc[di], desloc[si] e desloc[bp]. O MASM também permite a entrada destes modos sob a forma de [bx+desloc], [di+desloc], [si+desloc] e [bp+desloc]
Os modos de endereçamento desloc[bp]/[bp+desloc] usam o segmento da pilha como padrão (se não usarmos o cancelamento de segmento). Os outros modos usam o segmento de dados como padrão. O deslocamento dentro do segmento é simplesmente a soma do valor do registrador e do deslocamento. Deslocamentos na faixa de -128 a 127 requerem apenas um byte de codificação, os na faixa de cerca de 32k requerem dois bytes. Isto significa que instruções usando deslocamentos na faixa -128 a 127 serão mais curtas (e mais rápidas). A CPU coloca sinal nestes deslocamentos mais curtos no momento em que calcula o endereço efetivo.
Alguns exemplos assumindo que DS=1000h, ES=2000h, SS=3000h, BX=10h, SI=-8, DI=100h e BP=300h. Cada um dos modos de endereçamento seguintes geram o endereço efetivo (EE) especificado. O deslocamento correspondente possui um ou dois bytes:
1[bx] ; EE é 11h, deslocamento é um byte 10h[si] ; EE é 8h, deslocamento é um byte [di-80h] ; EE é 80h, deslocamento é um byte 100h[bp] ; EE é 400h, deslocamento são dois bytes
1000h[bx] = 1000 + 10 = 1010 80h[si] = 80 - 8 = 78 SS:1[di] = 1 + 100 = 101 [bp-100h] = 300 - 100 = 200
1000h[bx] = 10000 (DS) + 1010 = 11010 80h[si] = 10000 (DS) + 78 = 10078 SS:1[di] = 30000 (SS) + 101 = 30101 [bp-100h] = 30000 (SS) + 200 = 30200
1000h[bx] -> 2 bytes 80h[si] -> 1 byte SS:1[di] -> 1 byte [bp-100h] -> 2 bytes
Os modos de endereçamento base com indexado calculam seus endereços efetivos através da soma do registrador base (BX ou BP) e do registrador índice (DI ou SI). Como o usual, os modos [bx][di] e [bx][si] fornecem deslocamentos no segmento de dados e os modos [bp][di] e [bp][si] no segmento da pilha. O endereço efetivo é apenas a soma dos dois registradores.
Os modos de endereçamento base com indexado com deslocamento somam um deslocamento com os dois registradores para obter o endereço efetivo. Os exemplos seguintes usam os valores dos registradores dos exemplos anteriores:
1[bx][si] ; EE é 19h, deslocamento um byte 10h[bp][si] ; EE é 308h, deslocamento um byte [bx][di-80h] ; EE é 90h, deslocamento um byte 100h[bp][di] ; EE é 500h, deslocamento dois bytes
1000h[bx][si] = 1000 + 10 - 8 = 1008 80h[bp][si] = 80 + 300 - 8 = 378 SS:1[bx][di] = 1 + 10 + 100 = 111 [bp-100][di] = (300 - 100) + 100 = 300
![]() Figura do Diagrama para memorização |
Para lembrar com facilidade dos modos de endereçamento 80x86 válidos memorize a figura do diagrama. Se escolhermos nenhum ou um item de cada coluna de modo a obtermos pelo menos um termo, o modo de endereçamento é válido. Inversamente, se tivermos um modo de endereçamento que não possa ser construído escolhendo nenhum ou um item de cada coluna, então o modo de endereçamento não é válido.
Observe que todos os modos de endereçamento do 8086 envolvendo [bp] usam o segmento da pilha como padrão. Todos os outros modos usam o segmento de dados como padrão.
Exemplos de modos de endereçamento válidos são 1[bx], [bp], [si], [bx], 10h[bp][si], ds:[5], [bp+2], [di][bx] e 10[bx][di]. Modos inválidos seriam [dx], [si][di], [bp+bx], [ax][bx], [bx][bx] e [bx][si][di].
[si][di] e [bp][bx]
[bp][si+2] -> Segmento da pilha (SS) [si][bx] -> Segmento de dados (DS) 2[di] -> Segmento de dados (DS) [bp] -> Segmento da pilha (SS) [bx][si] -> Segmento de dados (DS) [si-4] -> Segmento de dados (DS)
A instrução 80x86 MOV copia dados de um local para outro. A sintaxe desta instrução é:
![]() A instrução MOV |
Existem várias restrições em relação aos operandos destino e fonte. A forma mais comum desta instrução move dados entre dois registradores ou entre uma posição de memória e um registrador. A instrução usa a codificação mostrada na figura ao lado.
Os campos desta instrução têm o seguinte significado:
W: Seleciona operações MOV de 8 bits (w=0) ou 16/32 bits (w=1).
D: Seleciona a direção da operação MOV.
Se D=0, a instrução MOV copia dados do operando reg para o operando mod-r/m. Se D=1, a instrução MOV copia dados do operando mod-r/m para o operando reg.
|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||
| R/M | Modo de endereçamento (MOD=00, 01 ou 10) |
| 000 | [BX+SI] ou DESLOC[BX][SI] (depende de MOD) |
| 001 | [BX+DI] ou DESLOC[BX+DI] (depende de MOD) |
| 010 | [BP+SI] ou DESLOC[BP+SI] (depende de MOD) |
| 011 | [BP+DI] ou DESLOC[BP+DI] (depende de MOD) |
| 100 | [SI] ou DESLOC[SI] (depende de MOD) |
| 101 | [DI] ou DESLOC[DI] (depende de MOD) |
| 110 | Apenas deslocamento ou DESLOC[BP] (depende de MOD) |
| 111 | [BX] ou DESLOC[BX] (depende de MOD) |
| Tabela 3 - R/M | |
O campo REG seleciona um dos registradores 80x86 usando o padrão de bits mostrado na tabela 1. Os campos MOD e R/M selecionam um operando com a codificação mostrada nas tabelas 2 e 3.
Para ver como estas tabelas são usadas, considere a instrução
O primeiro byte da instrução é o opcode "10001001" ou 89h. O segundo (e último) byte da instrução é um byte mod-reg-r/m. Como o campo D do opcode é zero, o campo reg do byte mod-reg-r/m é o operando fonte. Na instrução acima, BX é o operando fonte e seu código é 011 (veja a tabela 1). O destino também é um registrador, portanto, o campo mod precisa ser 11 e o campo r/m precisa ser 000, indicando o registrador AX. Isto produz o byte mod-reg-r/m 11011000 ou D8h. Como resultado, o primeiro byte desta instrução MOV é 89h e o segundo é D8h.
Como D=1, o operando fonte é AX ou reg = 000. O destino é BX, portanto, mod = 11 e r/m = 011. mod-reg-r/m = 11000011 ou C3h
1000 1001 = 89h D=0: operando fonte é AX e reg=000. Operando destino é BX, então mod = 11 e r/m = 011. mod-reg-r/m = 11000011 = C3h MOV BX, AX = 89h C3h 1000 1011 = 8Bh D=1: operando fonte é BX e reg=011. Operando destino é AX, então mod = 11 e r/m = 000. mod-reg-r/m = 11011000 = D8h MOV BX, AX = 8Bh D8h
Este mesmo formato básico do opcode também permite que os processadores 80x86 movam dados entre os registradores e a memória. Se o campo mod não for "11", então os campos mod e r/m especificam uma posição de memória. Por exemplo, a instrução MOV AX, [BX] é construída da seguinte forma:
Como resultado, a instrução completa é 8Bh 07h.
Note que existem três instruções diferentes que carregarão o registrador AX com um word cujo deslocamento seja dado pelo valor do registrador BX. Apesar do 80x86 fornecer um modo de endereçamento especial de "deslocamento+BX", que não requer qualquer byte de deslocamento, nada impede que codifiquemos uma instrução com um ou dois bytes de deslocamento. Poderíamos, por exemplo, especificar um valor mod de 01 e incluir um único byte zero de deslocamento. Isto produziria a instrução 8Bh 43h 00h, que teria o mesmo efeito da sequência anterior. É claro que esta sequência de instrução é mais longa e mais lenta que a anterior.
Observe que o modo de endereçamento apenas por deslocamento do 80x86 na realidade corresponde ao modo de endereçamento [BP]. Programas raramente usam o modo de endereçamento [BP] apesar de usarem o modo de endereçamento apenas por deslocamento o tempo todo. Se realmente precisarmos do modo de endereçamento [BP], usamos simplesmente o modo 0[BP].
O opcode é 8Bh porque o operando reg é um registrador de 16 bits. MOD = 10 indica deslocamento de dois bytes. Operando destino é AX, então reg = 000 R/M como endereçamento BP = 011 mod-reg-r/m = 10000011 = 83h Deslocamento: 00 00 Sequência: 8Bh 83h 00h 00h
O opcode é 10001011 = 8Bh Endereçamento apenas por deslocamento: mod = 00 Registrador BP: reg = 101 Apenas deslocamento: R/M = 110 mod-reg-r/m: 00101110 = 2Eh Deslocamento: 25h Sequência: 8Bh 2Eh 25h 00h
O opcode é 10001011 = 8Bh Endereçamento base/indexado/deslocamento: mod = 01 Registrador BP: reg 101. Deslocamento[BP]: R/M = 110 mod-reg-r/m: 01101110 = 6Eh Deslocamento: 00 Sequência: 8Bh 6Eh 00h
Alguns exercícios com o CodeView vão começar a dar o gostinho de programar em Assembly. Não é uma programação usual, mas é um bom começo. Usaremos algumas das instruções mais comuns e observaremos os resultados - ver para crer... e para aprender.
Prepare o exercício seguindos os seguintes passos:
mov ax, ds:[0] add ax, ds:[2] mov ds:[4], ax int 3
05 00 Byte menos significativo: 05 e byte mais significativo: 00 Invertendo os bytes -> 00 05 0005 = 0002 + 0003
1. Ativar o painel dos registradores. 2. Alterar o ponteiro de instruções (IP) para 0000.
7000:0000 AX = 0000 IP = 0000 FL = 0200 7000:0004 AX = 0002 IP = 0004 FL = 3203 7000:0009 AX = 0005 IP = 0009 FL = 3206 7000:000D IP = 000D 7000:000E IP = 000E
mov al, ds:[0] mov al, [bx] mov al, [si] mov al, [di] mov al, [bp] mov al, 2[bx] mov al, 2[si] mov al, 2[di] mov al, 2[bp] mov al, [bx][si] mov al, [bx][di] mov al, [bp][si] mov al, [bp][di] mov al, 2[bx][si] mov al, 2[bx][di] mov al, 2[bp][si] mov al, 2[bp][di] int 3
7000:0000 mov al, ds:[0] AX=0000 <- 8000:0000 00 7000:0004 mov al, [bx] AX=0008 <- 8000:0008 08 (BX=0008) 7000:0006 mov al, [si] AX=0006 <- 8000:0006 06 (SI=0006) 7000:0008 mov al, [di] AX=0004 <- 8000:0004 04 (DI=0004) 7000:000B mov al, [bp] AX=0002 <- 8000:0002 02 (BP=0002) 7000:000E mov al, 2[bx] AX=000A <- 8000:000A 0A (BX+2=000A) 7000:0011 mov al, 2[si] AX=0008 <- 8000:0008 08 (SI+2=0008) 7000:0014 mov al, 2[di] AX=0006 <- 8000:0006 06 (DI+2=0006) 7000:0017 mov al, 2[bp] AX=0004 <- 8000:0004 04 (BP+2=0004) 7000:0019 mov al, [bx][si] AX=000E <- 8000:000E 0E (BX+SI=000E) 7000:001B mov al, [bx][di] AX=000C <- 8000:000B 0B (BX+DI=000B) 7000:001D mov al, [bp][si] AX=0008 <- 8000:0008 08 (BP+SI=0008) 7000:001F mov al, [bp][di] AX=0006 <- 8000:0006 06 (BP+DI=0006) 7000:0022 mov al, 2[bx][si] AX=0000 <- 8000:0010 00 (BX+SI+2=0010) 7000:0025 mov al, 2[bx][di] AX=000E <- 8000:000E 0E (BX+DI+2=000E) 7000:0028 mov al, 2[bp][si] AX=000A <- 8000:000A 0A (BP+SI+2=000A) 7000:002B mov al, 2[bp][di] AX=0008 <- 8000:0008 08 (BP+DI+2=0008)
As instruções que usam BP acessam o segmento da pilha ao invés do segmento de dados. No exercício acima, tanto SS quanto DS apontam para o mesmo segmento. Faça com que SS aponte para o endereço 9000:0000.
7000:000B mov al, [bp] AX=008B <- 9000:0002 8B (BP=0002) 7000:0017 mov al, 2[bp] AX=0063 <- 9000:0004 63 (BP+2=0004) 7000:001D mov al, [bp][si] AX=0061 <- 9000:0008 61 (BP+SI=0008) 7000:001F mov al, [bp][di] AX=0089 <- 9000:0006 89 (BP+DI=0006) 7000:0028 mov al, 2[bp][si] AX=0033 <- 9000:000A 33 (BP+SI+2=000A) 7000:002B mov al, 2[bp][di] AX=0061 <- 9000:0008 61 (BP+DI+2=0008)
O comando EW (Enter Word) do CodeView permite dar entrada de valores de 16 bits (words) e o comando DW (Display Word) permite mostrá-los.
1. No painel de comando, digitar N 16 [Enter] para ativar o modo hexadecimal. 2. Digitar EW 8000:20 3. Digitar, por exemplo, ABCD [Enter] 4. Memória mostra 8000:0020 CD AB 00 00 00 ... 5. Digitar DW 8002:0 6. Painel de comando mostra 8002:0000 ABCD 0000 0000 ... 1a. Digitar EW 8002:0 2a. Digitar, por exemplo, 1234 [Enter] 3a. Memória mostra 8002:0000 34 12 00 00 00 ... 4a. Digitar DW 8000:20 5a. Painel de comando mostra 8000:0020 1234 0000 0000 ...
8000:0020 é o endereço segmentado, ao qual corresponde o endereço físico 80020 e o endereço normalizado 8002:0 Cálculo do endereço normalizado: - Endereço segmentado 8000:0020 - Endereço físico: (8000 x 10) + 0020 = 80020 - Endereço normalizado: 8002:0
Deu para sentir o gostinho da programação? Pena que o CodeView não permite salvar os programinhas criados. Mas isto foi apenas o começo, um treino para pegar o jeito da coisa. O importante foi fixar o conceito dos modos de endereçamento, fundamental para uma programação séria e de qualidade. Vamos lá que tem mais!