Sítio do Piropo

B. Piropo

< Coluna em Fórum PCs >
Volte
03/04/2006

< Computadores XXIX: >
<
Montando o programa executável
>


Se você vem acompanhando esta série de colunas, sabe perfeitamente como um programa é executado (e se não lembra, uma breve releitura das colunas “Busca e execução – Primeiro passo” e “Busca e execução – Final” certamente refrescará sua memória). Repetindo aqui, resumidamente: a execução de um programa nada mais é que uma sucessão de ciclos de busca e execução, onde as instruções “apontadas” pelo Ponteiro de Instruções são “buscadas” na memória uma após a outra, trazidas para o Registrador de Instruções, decodificadas e executadas seqüencialmente.

Por outro lado, se você acompanhou as colunas que descrevem o “conjunto de instruções” fictício que criamos para a UCP que concebemos para ilustrar esta série de colunas, deve lembrar do programa em linguagem de máquina representado na Figura 7 da coluna “O conjunto de instruções – Final”. Este “programa” nada mais é que um conjunto de números binários ocupando posições sucessivas da memória principal, o mesmo conjunto representado na Listagem 1 da coluna “Mnemônicos”, repetida abaixo.

00000000 011100000001

00000001 100100000011

00000010 000100000110

00000011 000000000000

00000100 000000000000

00000101 000000000000

00000110 010100000000

00000111 001000010101

00001000 100100000101

00001001 101100000000

00001010 001000010101

00001011 100100000100

00001100 111000000101

00001101 100100000101

00001110 100000000100

00001111 101100000000

00010000 001000010111

00010001 100100000100

00010010 111000000101

00010011 100100000101

00010100 000100001110

00010101 100000000011

00010110 000100011000

00010111 100000000101

00011000 011000000000

00011001 000000000000

Listagem 1: Programa em linguagem de máquina

Nesta listagem, cada linha representa um endereço de uma posição da memória principal seguido do conteúdo desta posição de memória. E, como acabamos de ver, este conteúdo nada mais é que um programa em linguagem de máquina formado por instruções e seus parâmetros. Estas instruções são lidas na memória uma após a outra, decodificadas, combinadas com os parâmetros e executadas seqüencialmente.

Na prática, como isto acontece? Como este programa é transposto para a memória?

Em nosso sistema ideal, aquele que usa a UCP e o conjunto de instruções “inventados” por nós, as instruções e dados do programa (ou seja, o conteúdo da segunda coluna da listagem) são armazenados seqüencialmente em disco sob a forma de um “arquivo executável” (um arquivo de extensão “Exe”). Este arquivo nada contém além de uma sucessão de números binários, cada um correspondendo a uma linha da segunda coluna da Listagem 1.

Em obediência a um comando (por exemplo: em Windows, um clique duplo sobre o ícone que representa o arquivo executável) o sistema operacional lê este arquivo em disco, transcreve seu conteúdo na memória principal (ou seja, copia em posições de memórias contíguas e sucessivas cada número binário contido no arquivo) e em seguida “aponta” o PI (ponteiro de instruções) da UCP para a primeira instrução (ou seja, copia no registrador PI o endereço da posição da memória principal que contém a primeira instrução). Isto feito, deixa a UCP por sua própria conta. E quem leu as colunas sobre ciclo de busca e execução sabe que a única coisa que uma UCP é capaz de fazer por sua própria conta é executar ciclo de instrução após ciclo de instrução até encontrar a instrução “ALTO”, quando então o programa é encerrado e o controle devolvido ao sistema operacional. Ou seja: para executar o programa do início ao final, basta copiar o conteúdo de seu arquivo executável na memória e fazer o PI apontar para o endereço da primeira instrução. E o arquivo executável nada mais é que um arquivo em disco que contém, em ordem, os números binários que representam as instruções e dados exibidos na coluna da direita da Listagem 1. É simples assim.

Pois muito bem: na própria coluna “Mnemônicos” e na coluna “Rótulos”, anterior a esta que estamos lendo, criamos o programa em assembly correspondente ao mesmo programa em linguagem de máquina mostrado na Listagem 1 acima.

Como fizemos isso?

Na coluna da direita da listagem 1, os números em binário que representam instruções foram substituídos um a um pelos mnemônicos que representam exatamente as mesmas instruções, seguidos de seus parâmetros. Aqueles parâmetros que correspondem a constantes ou permaneciam “vazios” foram, respectivamente, substituídos pelas constantes – na verdade uma só, logo na primeira linha – ou deixados “vazios”. E alguns dos números em binário da primeira coluna, que representam endereços “citados” na segunda, foram substituídos pelos seus rótulos. O resultado é uma nova listagem, desta vez em assembly, já exibida na coluna anterior desta série e repetida aí abaixo apenas para facilitar o entendimento do que vamos explicar adiante.

Listagem 2: Programa em assembly

Pois bem: se você entendeu tudo isto, sabe que as listagens 1 e 2 não apenas se equivalem como também existe entre elas uma correspondência biunívoca (ou seja: cada linha de uma listagem corresponde a uma e apenas uma linha da outra e vice-versa). Portanto deve ser fácil “converter” uma em outra.

Converter um programa de linguagem de máquina para assembly (“desmontá-lo”, ou “desassemblá-lo”, para os que gostam de se exprimir em jargão) tem uma utilidade relativa. Serve apenas para descobrir como ele foi feito. Fora isso, não há porque desmontar um programa. Se você já dispõe dele em linguagem de máquina, pode gravá-lo em um arquivo executável e simplesmente executá-lo (na verdade há um passo adicional que veremos adiante, mas essencialmente é isso aí mesmo).

Já a operação inversa, converter um programa de assembly para linguagem de máquina tem uma utilidade extraordinária. Pois permite que o programador, que criou seu programa em assembly usando mnemônicos para facilitar a programação, faça sua “montagem”, ou seja, o converta em linguagem de máquina e o transforme em um arquivo executável. Na verdade este passo é essencial. Sem “montar” um programa ele não pode ser executado, já que a UCP não “entende” linguagem assembly, só “entende” linguagem de máquina.

Ora, mas devido à correspondência biunívoca entre as duas listagens, a conversão há de ser muito fácil. Basta efetuar o procedimento inverso àquele que recorremos para criar nosso programa em assembly: substituir cada mnemônico e seu parâmetro (mostrados nas duas colunas da direita da listagem 2) pelo número binário correspondente (aquele mostrado na coluna da direita da listagem 1) na mesma ordem, linha a linha. O resultado será o programa em linguagem de máquina que pode ser gravado em disco sob a forma de um programa executável. Que por sua vez pode ser transposto para a memória principal e executado.

E de fato assim é. Pelo menos no nosso exemplo, usando o conjunto de instruções que criamos para rodar na UCP que concebemos.

Repare que a tarefa não exige nenhuma “inteligência”. Trata-se de uma mera conversão feita simplesmente consultando uma tabela semelhante àquela criada na coluna “Mnemônicos”, substituindo as letras que compõem cada mnemônico pelo número binário correspondente à instrução, ação esta combinada com a substituição do rótulo que constitui o parâmetro pelo número binário correspondente (no nosso caso, em geral um endereço). E tarefas desta natureza costumam ser feitas rapidamente e sem erros por um programa.

O programa que faz esta conversão, ou “montagem” do arquivo em linguagem de máquina, chama-se “assembler”, ou “montador” (e só para frisar, tentando evitar um erro freqüente: “assembler”, ou montador, é o programa, enquanto “assembly”, ou montagem, é a linguagem).

Em suma: no exemplo que usamos, com o conjunto de instruções simplificado que concebemos e na UCP que inventamos, para gerar um programa executável basta submeter o programa fonte em linguagem de máquina da listagem 2 ao assembler. Se batizarmos o arquivo texto que contém a listagem 2 (ou seja, o “arquivo fonte” do programa em assembly) de “Fatorial.Asm” (a extensão “Asm” é normalmente usada para designar arquivos fonte em assembly) e o submetermos ao programa assembler, a saída desta operação será um arquivo em binário “Fatorial.Exe” que contém o programa executável. Para executá-lo, basta carregá-lo na MP e apontar o PI para seu inicio (de novo: na prática existe um passo adicional, que será examinado adiante, mas que não altera o entendimento da forma como a coisa “funciona”).

Neste ponto cabem duas observações.

Primeira: cada UCP e seu conjunto de instruções exigem um programa assembler especialmente desenvolvido para ela. Como “nossa” UCP e seu conjunto de instruções não existem (foram concebidos apenas para ilustrar esta série de colunas), não existe programa assembler capaz de converter a listagem 2 na listagem 1 e gerar o programa executável equivalente. E nem adianta tentar usando um assembler de qualquer outro processador, naturalmente.

Segunda: na vida real, ou seja, no mundo das UCPs que efetivamente existem e seus conjuntos de instruções, a coisa é um bocadinho mais complicada.

Complicação esta que examinaremos na próxima coluna.

 

 

B. Piropo