PR05
Parte V: Montador e Vinculador
Na última parte do nosso projeto você vai terminar de implementar o montador (ou seja, sua própria versão do MACAs
) e também o vinculador (sua própria versão do MACLK
).
Montador
Usando o parser da parte IV, você deve implementar a função cujo protótipo está em asm.h
que é a parte principal do montador. Esse arquivo pode ser copiado daqui.
O arquivo asm.h
contém apenas o protótipo da função que vocês devem implementar:
int assemble(const char *filename, FILE *input, FILE *output);
O protótipo dessa função não deve ser alterado.
A função assemble()
recebe:
-
o nome
filename
do arquivo de entrada. Esse nome será útil para imprimir mensagens de erro. Por exemplo a seguirbla.c
é ofilename
:sh % gcc bla.c gcc: error: bla.c: No such file or directory gcc: fatal error: no input files compilation terminated.
-
um stream
input
de entrada que é um ponteiro para o arquivo com programa emMACAL
a ser montado. -
um strem
output
de saída que é um ponteiro para o arquivo que será criado com o código-objeto.
A função assemble()
deve imprimir mensagens de erro apropriadas em stderr
, da mesma forma que o montador disponibilizado no PACA. Caso a montagem seja feita com sucesso, a função deve devolver um valor não-zero; caso contrário devolve zero.
Você também deve escrever um arquivo macas.c
que contém a função main()
do montador. Essa função apenas interpreta os argumentos recebido através da linha do comando, abre os arquivos de entrada e saída e chama a função assemble()
para fazer a montagem. A função main()
também deve gerar mensagens de erro apropriadas caso detecte alguma falha.
Erros seu programa deve detectar
Erros relacionados a operandos inválidos para uma operação, por exemplo, são capturados pela função parse()
do parser.c
. Seu trabalho então é apenas reportar esses erros e encerrar a execução. Além desses erros, o montador deve capturar os seguintes erros:
-
um rótulo de uma instrução não pode ser um alias e vice-versa;
-
aliases não podem ser definidos mais de uma vez;
-
uma instrução
EXTERN
não pode ter um rótulo; o rótulo exportado pelaEXTERN
deve ser definido; -
rótulos não podem ser definidos mais de uma vez;
-
as instruções de desvio tais com
JMP
eJZ
suportam deslocamentos de tamanho máximo fixo; veja o capítulo 7 da apostila sobreMACAL
disponível no PACA. Seu programa deve verificar se esses tamanhos foram respeitados.
O código-objeto
Você deve gerar um arquivo com o código-objeto do programa. O formato desse arquivo não está especificado: você deve inventar um formato próprio que facilite sua vida na hora de escrever o vinculador.
Tipicamente, a maior parte das instruções do programa original devem ser convertidas imediatamente em instruções de máquina. A conversão não é possível apenas quando temos uma instrução de desvio, como JMP
, para um rótulo não definido até o momento. Nesse caso, o seu programa deve marcar no código-objeto esse deslocamento para o rótulo desconhecido. Outra informação importante que o seu programa deve registrar no código objeto é a posição de cada rótulo que foi exportado, para que o vinculador determine as posições das instruções correspondentes ao montar o programa final.
O vinculador (linker) será o programa responsável por encontrar os rótulos desconhecidos no código-objeto, calcular os deslocamentos e completar o código de máquina a ser executado pelo macsim
.
Sugestão
Aqui vai uma sugestão de como estruturar a função assemble()
. Você está livre para fazer a implementação do maneira que considerar mais conveniente.
Considere manter três tabelas de símbolos (stable
):
-
alias_table
: guarda os alias definidos através deIS
; -
label_tabel
: guarda os rótulos definidos e o número da instrução a que correspondem; -
extern_tabel
: guarda os rótulos definidos comoEXTERN
.
A alias_table
deve conter inicialmente alguns aliases padrão, a saber os registradores com nomes especiais: rA
rX
, etc.
Você pode dividir a montagem em duas etapas.
Na primeira etapa, seu programa deve ler cada instrução do arquivo de entrada (input
) e montar uma lista de instruções. Esse lista será uma lista ligada de instâncias do tipo Instruction
. Durante esta primeira etapa, o seu programa deve atualizar quando preciso as tabelas de símbolo acima, mas não deve gerar código máquina algum.
Note que seu programa não deve inserir instruções que não geram código, tais como EXTERN
ou IS
, na lista ligada de instruções. Seu programa deve manter um contador com a posição da próxima instrução no código-objeto. No momento em que uma instrução é inserida na lista, você conhece sua posição no arquivo e pode mudar o campo pos
da instrução apropriadamente. Em seguida, basta atualizar o contador com a posição da próxima instrução. Pseudo-operações como CALL
ou PUSH
geram mais de uma instrução de máquina. Quando uma instrução com rótulo é lida, sabemos a posição à qual o rótulo se refere e podemos atualizar a tabela label_table
para guardá-la.
Na segunda etapa, seu programa deve percorrer a lista de instruções e gerar o código-objeto. A medida que percorre a lista, o código de cada instrução é gerado. Se há uma instrução de desvio, como JMP
, devemos verificar se o rótulo do desvio foi definido em label_table
e nesse caso calcular o deslocamento. Se o rótulo não foi definido, a instrução deve ser apenas parcialmente gerada código-objeto. O vinculador resolverá essa dependência mais tarde.
Vinculador
O vinculador é mais simples de escrever do que o montador. Ele pode ser composto de apenas um arquivo maclk.c
. Seu trabalho consiste em ler diversos arquivos com código-objeto maco
e montar o programa final em código de máquina mac
. O vinculador deve verificar se os rótulos não-resolvidos de cada arquivo foram exportados com EXTERN
por um outro arquivo a fim de calcular os deslocamentos finais. O vinculador também deve certificar-se de que o rótulo main
foi exportado por algum arquivo, para determinar a instrução inicial do programa mac
.
O vinculador detecta três tipos de erro:
- rótulo
main
não definido; - um mesmo rótulo definido mais de uma vez;
- algum rótulo não definido.