Módulos aceleraram o processo de desenvolvimento, portanto é necessário prover uma forma de construir programas eficientemente em módulos. Cada módulo contém uma coleção de tipos, constantes, variáveis e procedimentos e deve se articular com os demais.
A compilação de um módulo passa pelas seguintes etapas:
1. pré-processamento;
2. geração de código em Assembly;
3. geração de código objeto (.obj).
Um módulo objeto não pode ser executado diretamente pela dependência de outros módulos. Como a compilação é feita módulo a módulo as dependências não podem ser resolvidas durante a compilação. Desta forma, o módulo objeto irá conter lacunas a serem preenchidas em outra etapa. O nome da etapa que resolverá esta questão é amarração.
Outro motivo pelo qual o módulo objeto não pode ser executado diretamente é que não há garantia de que ele vá ser carregado sempre no mesmo lugar da memória (invalidando assim os endereços utilizados). Isto será resolvido em tempo de carga, quando será feita a relocação do programa.
A construção de uma aplicação irá passar necessariamente por estas etapas mesmo quando a aplicação possui apenas um módulo.
1. Tabela de símbolos exportados
2. Tabela de referências externas
3. Código (também chamado texto)
4. Dados inicializados
5. Dicionário de relocação
A tabela de símbolos exportados contém apenas os nomes dos símbolos do módulo.
A tabela de referências externas irá conter os nomes dos símbolos utilizados pelo módulo de forma a checar a existência dos mesmos em outros módulos.
O dicionário de relocação é uma tabela com entradas para cada posição no código onde existe uma referência a uma posição de memória. Normalmente a referência usa o próprio espaço de endereçamento do módulo objeto, mas precisará ser alterada quando os vários módulos objetos forem unidos.
É comum também que no formato objeto exista algum tipo de suporte a debug para que, ao rodar um programa sobre o controle de um debugger, este possa saber a linha corrente, os símbolos exportados, os tipos da variáveis, etc.
O processo de amarração visa unir todos os módulos objeto em um único arquivo executável. Este poderá ser posteriormente relocado e carregado em memória para futura execução.
A amarração irá conter as seguintes tarefas principais:
1. resolver as referências externas dos diversos módulos;
2. relocar as referências absolutas à memória;
3. unir todos os módulos em um arquivo executável.
A resolução das referências é feita de forma que todos os símbolos importados de todos os módulos possuam um símbolo exportado de qualquer módulo com igual nome.
A relocação das referências absolutas é necessária porque todos os módulos estarão em um mesmo espaço de endereçamento.
A união de todos os módulos em um arquivo executável requer uma relocação dos endereços em cada módulo de forma a acrescentar sua posição dentro da concatenação. Um processo similar é feito durante a etapa de relocação.
Bibliotecas estaticamente amarradas também entram como módulos no executável.
Relocação é o processo de designar endereços de carga às várias partes do programa, ajustando o código e dados para refletir os endereços designados.
Um exemplo de partes de um programa são as bibliotecas dinâmicas. Estas possuem diversas vantagens de uso como melhor aproveitamento de memória e diminuição do tempo de carga (se imaginarmos que uma biblioteca destas pode estar sempre carregada por exemplo). Porém, usar uma biblioteca dinâmica pode implicar em mais do que carregar o código dela para memória. Referências aos símbolos feitas em tempo de amarração não são mais válidas e precisam ser adequadas à nova posição onde o código está sendo inserido.
Na etapa de amarração as posições de memória são calculadas com referência ao endereço zero, que seria o início do código. Quando a relocação é feita os endereços devem basear-se na posição de memória inicial onde o trecho de código está sendo inserido, portando as posições de memória calculadas em tempo de amarração funcionarão como distâncias (offsets).
Algumas variações a relocação existem:
1. através de registradores de base (por hardware);
2. por espaços de endereçamento virtual.
Chamamos de carga o ato de colocar um trecho de código em memória encaixando-o no espaço de endereçamento do programa; naturalmente é necessário fazer relocação.
Em bibliotecas dinâmicas, é necessário existir algum meio de resolver símbolos em tempo de execução. Isto pode ser feito através de uma tabela de símbolos dentro da biblioteca dinâmica, mas também é comum usar um pequeno código estático que serve de "cola" (chamado também de stub) entre a biblioteca dinâmica e o executável.