O código assembly escrito à mão é cheio de repetição. A mesma sequência curta de instruções aparece de novo e de novo, com pouca variação, e há poucas abstrações para reduzir o código repetitivo. Além disso, programar em assembly exige gerenciar valores sem semântica óbvia. Não há parâmetros nomeados, e a maior parte dos detalhes do funcionamento interno da máquina precisa ser tratada manualmente por quem programa.
O NASM, o montador usado pela trilha, oferece algumas ferramentas que podem ajudar quem programa com esses dois problemas. Essas ferramentas atuam em tempo de montagem, antes que qualquer instrução chegue ao processador. Elas orientam o montador sobre como reescrever o código-fonte antes mesmo de ele ser convertido em código de máquina.
A mais importante dessas ferramentas são as macros, que podem ser pensadas como uma receita para o montador. Elas são expandidas no local onde são usadas, como se o código tivesse sido escrito ali à mão.
As macros podem receber vários tokens literais como parâmetros, o que as torna reutilizáveis em contextos diferentes.
No entanto, elas não são funções: não há call, nem ret, nem transferência de controle para outro ponto do código.
A definição mais simples dá um nome a um valor ou a uma expressão curta com %define:
%define WIDTH 8
%define BUFFER_SIZE (WIDTH * 16)
Toda ocorrência posterior de WIDTH é substituída por 8 antes da montagem.
A substituição é textual, então o nome é substituído onde quer que apareça, seja em um operando, seja dentro de outra definição.
Para constantes numéricas, prefira equ em vez de %define.
Uma macro de uma linha declarada com %define só é expandida quando usada, e pode ser reatribuída ou ter a definição removida.
Uma constante equ, por outro lado, é avaliada no ponto da definição e nunca é modificada.
Um %define também pode receber parâmetros nomeados, comportando-se como uma pequena função inline:
%define double(x) ((x) + (x))
mov eax, double(WIDTH) ; assembled as mov eax, ((8) + (8))
Coloque entre parênteses tanto o corpo inteiro quanto cada parâmetro.
Com %define square(x) x*x, a chamada square(1+1) expande para 1+1*1+1, que é 3.
Escrito como %define square(x) ((x)*(x)), a mesma chamada expande para ((1+1)*(1+1)), que é 4.
Nomes definidos com %define diferenciam maiúsculas de minúsculas.
A variante %idefine torna o nome insensível a maiúsculas e minúsculas, caso isso seja desejado.
Macros de uma linha são úteis para dar sentido a várias partes do código. Se usadas com cuidado, elas melhoram a legibilidade e reduzem a chance de erros de digitação. Por exemplo, um registrador que tem uma finalidade específica dentro de uma função pode ser definido pela sua finalidade:
%define COUNTER rcx
...
.loop:
...
dec COUNTER
jnz .loop
...
Uma sequência inteira de código, com qualquer número de linhas, é capturada com %macro e fechada com %endmacro.
Um número depois do nome indica quantos argumentos a macro recebe, podendo ser zero.
Quando argumentos são passados, eles são separados por vírgula (,):
%macro example_macro 0
...
%endmacro
Argumentos em macros de várias linhas não têm nome.
Eles são referenciados como %1, %2 e assim por diante, na ordem em que são passados:
; Multiplies two values, returning the result in the first argument
%macro mult_two 2 ; %1 = accumulator, %2 = value
imul %1, %2
%endmacro
mult_two eax, ecx ; multiplies eax and ecx and returns the result into eax
mult_two r8, r9 ; multiplies r8 and r9 and returns the result into r8
Repare que os argumentos precisam ter sintaxe válida para as operações realizadas no corpo da macro.
Aqui mult_two eax, ecx e mult_two r8, r9 montam sem problema, mas mult_two eax, cl não montaria, já que imul eax, cl não está definido.
Uma macro que define um rótulo comum quebra no momento em que é usada duas vezes, porque o rótulo também seria definido duas vezes.
Para usar rótulos dentro de uma macro que pode ser chamada mais de uma vez, é necessário prefixar esse rótulo com %%.
Isso instrui o montador a dar a esse rótulo um nome diferente e único a cada expansão:
; clobbers rcx
%macro clear_n 2 ; %1 = base address, %2 = count of dwords to clear
xor ecx, ecx
%%loop:
mov dword [%1 + rcx*4], 0
inc ecx
cmp ecx, %2 ; %2 is compared with ecx: it is an immediate or a 32-bit operand
jb %%loop
%endmacro
clear_n rdi, 4 ; gets its own %%loop
clear_n rsi, 8 ; a different %%loop, no clash
É possível repetir um bloco de código de uma linha usando times.
Isso é especialmente útil ao declarar valores repetidos na memória:
section .data
negative_block: times 16 db -1
; this assembles to 16 db -1 in sequence, i.e., an array of 16 bytes, all equal to -1
Para repetir código de várias linhas, podemos usar %rep:
%rep 2 ; number of repetitions: 2
add rcx, 32
mov dword [rsi + rcx], 0
%endrep
Repare que o número de repetições é especificado logo depois de %rep, e há um %endrep para fechar o bloco repetido.
É comum usar um contador de tempo de montagem dentro de um bloco %rep, muito parecido com um laço em tempo de execução que usa um contador.
Enquanto uma constante inteira de tempo de montagem pode ser declarada com equ, para variáveis inteiras de tempo de montagem usamos %assign.
Diferente de %define, o %assign é avaliado imediatamente para um número e pode ser reatribuído:
xor eax, eax
%assign i 0
%rep 4
add eax, i ; add 0, then 1, then 2, then 3
%assign i i + 1
%endrep
Depois que o pré-processador roda, o laço desaparece e restam quatro instruções add, com os valores imediatos 0, 1, 2 e 3 já embutidos.
Isso é o desenrolar de laço feito pelo montador: sem registrador de contador e sem desvio, apenas código linear.
O pré-processador oferece várias condicionais. Elas escolhem entre vários caminhos diferentes e expandem o corpo no próprio local, mas só quando sua condição for verdadeira.
%if ... %elif ... %else ... %endif selecionam um ramo a partir de um teste numérico:
%if WIDTH > 4
mov eax, 2
%elif WIDTH > 2
mov eax, 1
%else
mov eax, 0
%endif
As macros costumam ficar em um arquivo próprio e são trazidas para cada fonte que precisa delas com %include:
%include "patterns.mac"
O arquivo indicado é inserido literalmente nesse ponto, antes da montagem, exatamente como se o conteúdo dele tivesse sido digitado ali. Isso mantém as definições em um só lugar e permite que vários arquivos-fonte as compartilhem.
Se o arquivo de macros puder ser montado sozinho, ele deve ter a extensão .asm.
Repare que macros não têm ligação.
Mesmo que o arquivo de macros seja montado, suas macros só ficam visíveis para outro arquivo que faça %include dele.
Caso contrário, um arquivo destinado apenas a %include pode ter qualquer extensão.
Usar .asm ou .mac é uma convenção comum.
Você mantém a biblioteca de macros sobre a qual o motor de composição de uma revista é construído. O motor em si, as funções que dispõem cada página, é fixo e você não o escreve. O que você escreve é a maquinaria de tempo de montagem a partir da qual ele é construído.
Este exercício não exige que você escreva nenhuma função, apenas macros. Essas macros são então incluídas e chamadas em funções já predefinidas em outro arquivo.
Você tem quatro tarefas.
Cada macro é chamada pelo nome exato e pela ordem de argumentos que a tarefa indica, então escreva-as exatamente assim. Caso contrário, o arquivo não será montado.
O formato da revista é um punhado de números fixos, e o resto do layout se refere a eles apenas pelo nome. Você deve dar a cada um um nome legível em tempo de montagem:
COLUMN_COUNT é 4
COLUMN_STRIDE é 18
SPACE é 0x20, o byte em brancoRULE é 0x3D, o byte =
RULE_WIDTH é 4
PLAIN é 0x2E, o byte .
RULED é 0x2D, o byte -
BOXED é 0x23, o byte #
O motor reporta esses valores na ordem acima.
out = {4, 18, 32, 61, 4, 46, 45, 35} // the eight named values, in order
fill_run
O motor esvazia uma galé e traça filetes de cabeçalho estampando uma sequência de um único byte. Capture essa etapa como uma macro de várias linhas que o motor possa chamar sempre que uma sequência precisar ser preenchida.
Primeiro, defina um nome legível COUNTER para o registrador rcx.
Em seguida, defina a macro fill_run, que deve preencher um array com N cópias de um byte.
Ela recebe três argumentos nesta ordem:
fill_run rdi, rax, SPACE
fill_run rdx, RULE_WIDTH, RULE
A macro fill_run pode sobrescrever COUNTER livremente, mas nenhum outro registrador.
Repare que a contagem de bytes pode ser passada em um registrador de uso geral de 64 bits, como rax ou rsi, ou como um valor imediato, como 10 ou 4.
Você pode considerar que essa contagem nunca é negativa.
Esta macro é chamada três vezes, em duas funções diferentes.
length = 8
page = {32, 32, 32, 32, 32, 32, 32, 32}
width = 8
page = {61, 61, 61, 61, 32, 32, 32, 32} // '====' then spaces
lay_columns
A grade é fixa para o formato, então os inícios das colunas podem ser calculados em tempo de montagem.
Defina a macro lay_columns, que escreve COLUMN_COUNT deslocamentos, cada um um inteiro de 64 bits.
Ela recebe dois argumentos nesta ordem:
lay_columns rsi, rax
O número de deslocamentos a serem escritos no endereço de destino é fixo e igual a COLUMN_COUNT.
Cada deslocamento é um valor de 64 bits calculado com offset[i] = margin + i * COLUMN_STRIDE, onde i é o índice do deslocamento começando em 0, e margin é o segundo argumento.
Esta macro pode sobrescrever seus dois argumentos de registrador, mas nenhum outro. Ela é chamada uma vez.
margin = 3
out = {3, 21, 39, 57}
set_header
Cada uma das três seções fixas tem um estilo de cabeçalho fixado pelo formato, então o byte é conhecido em tempo de montagem.
Defina set_header, que recebe dois argumentos nesta ordem:
0 é simples, 1 é com filete e 2 é com moldura.O endereço de destino é a expressão de um endereço de memória efetivo.
Ela usa um registrador de base que pode ser somado a um registrador de índice escalado por 1, 2, 4 ou 8, e um valor imediato:
set_header rdx+8*rcx+2, 1
Selecione o byte de cabeçalho (PLAIN, RULED ou BOXED) com montagem condicional (%if / %elif / %else) em vez de um desvio em tempo de execução.
Esta macro não deve sobrescrever nenhum registrador. Ela é chamada três vezes na mesma função.
pitch = 4
out = {46, 0, 0, 0, 45, 0, 0, 0, 35} // '.', '-', '#'
Crie sua conta no Exercism para aprender e dominar x86-64 Assembly com 22 conceitos130 exercícios e mentoria humana de verdade, tudo de graça.