A primeira coisa que você precisa é algo como este arquivo . Este é o banco de dados de instruções para processadores x86, conforme usado pelo montador NASM (que eu ajudei a escrever, embora não as partes que realmente traduzem instruções). Vamos escolher uma linha arbitrária do banco de dados:
ADD rm32,imm8 [mi: hle o32 83 /0 ib,s] 386,LOCK
O que isto significa é que descreve a instrução ADD
. Existem várias variantes desta instrução, e a específica que está sendo descrita aqui é a variante que pega um registro de 32 bits ou um endereço de memória e adiciona um valor imediato de 8 bits (ou seja, uma constante incluída diretamente na instrução). Um exemplo de instrução de montagem que usaria esta versão é esta:
add eax, 42
Agora, você precisa pegar sua entrada de texto e analisá-la em instruções e operandos individuais. Para a instrução acima, isso provavelmente resultaria em uma estrutura que contém a instrução ADD
, e uma matriz de operandos (uma referência ao registro EAX
e ao valor 42
). Depois de ter essa estrutura, você percorre o banco de dados de instruções e encontra a linha que corresponde ao nome da instrução e aos tipos dos operandos. Se você não encontrar uma correspondência, é um erro que precisa ser apresentado ao usuário ("combinação ilegal de opcode e operandos" ou similar é o texto usual).
Depois de obter a linha do banco de dados, olhamos para a terceira coluna, que para esta instrução é:
[mi: hle o32 83 /0 ib,s]
Este é um conjunto de instruções que descrevem como gerar a instrução de código de máquina necessária:
- A
mi
é uma descrição dos operandos: um operando modr/m
(registrador ou memória) (o que significa que precisaremos acrescentar um modr/m
byte ao final da instrução, que veremos mais adiante) e um uma instrução imediata (que irá ser usado na descrição da instrução).
- O próximo é
hle
. Isso identifica como lidamos com o prefixo "lock". Nós não usamos "lock", então nós o ignoramos.
- O próximo é
o32
. Isso nos diz que, se estivermos montando código para um formato de saída de 16 bits, a instrução precisará de um prefixo de substituição de tamanho de operando. Se estivéssemos produzindo uma saída de 16 bits, produziríamos o prefixo now ( 0x66
), mas assumirei que não estamos e continuamos.
- O próximo é
83
. Este é um byte literal em hexadecimal. Nós produzimos isso.
O próximo é /0
. Isso especifica alguns bits extras que precisaremos no modr / m bytem e nos faz gerá-lo. O modr/m
byte é usado para codificar registradores ou referências indiretas de memória. Temos um único operando, um registro. O registro possui um número, especificado em outro arquivo de dados :
eax REG_EAX reg32 0
Verificamos que reg32
concorda com o tamanho exigido da instrução no banco de dados original. O 0
é o número do registro. Um modr/m
byte é uma estrutura de dados especificada pelo processador, com a seguinte aparência:
(most significant bit)
2 bits mod - 00 => indirect, e.g. [eax]
01 => indirect plus byte offset
10 => indirect plus word offset
11 => register
3 bits reg - identifies register
3 bits rm - identifies second register or additional data
(least significant bit)
Porque estamos trabalhando com um registro, o mod
campo é 0b11
.
- O
reg
campo é o número do registro que estamos usando,0b000
- Como há apenas um registro nesta instrução, precisamos preencher o
rm
campo com algo. É para isso que servem os dados extras especificados /0
, então colocamos isso no rm
campo 0b000
,.
- O
modr/m
byte é, portanto, 0b11000000
ou 0xC0
. Nós produzimos isso.
- O próximo é
ib,s
. Isso especifica um byte imediato assinado. Observamos os operandos e observamos que temos um valor imediato disponível. Nós o convertemos em um byte assinado e o produzimos ( 42
=> 0x2A
).
A instrução montada completa é, portanto: 0x83 0xC0 0x2A
. Envie-o para o seu módulo de saída, juntamente com uma nota de que nenhum dos bytes constitui referência de memória (o módulo de saída pode precisar saber se o faz).
Repita para todas as instruções. Acompanhe os rótulos para saber o que inserir quando forem referenciados. Adicione recursos para macros e diretivas que são passadas para os módulos de saída do arquivo de objeto. E é basicamente assim que um montador funciona.