#include <stdio.h>
void assembly(float *array);
int main(void)
{
float array[4];
assembly(array);
printf("%f, %f, %f, %f\n", array[0], array[1], array[2], array[3]);
return 0;
}
Aprendendo a sintaxe AT&T e a usar o GAS
O GNU assembler (GAS) usa por padrão a sintaxe AT&T e neste tópico irei ensiná-la. Mais abaixo irei ensinar a diretiva usada para usar sintaxe Intel meramente como curiosidade e caso prefira usá-la.
A primeira diferença notável é que o operando destino nas instruções de sintaxe Intel é o mais à esquerda, o primeiro operando. Já na sintaxe da AT&T é o inverso, o operando mais à direita é o operando destino. Conforme exemplo:
E como já pode ser observado valores literais precisam de um prefixo $
, enquanto os nomes dos registradores precisam do prefixo %
.
Na sintaxe da Intel o tamanho dos operandos é especificado com base em palavra-chaves que são adicionadas anteriormente ao operando. Na sintaxe AT&T o tamanho do operando é especificado por um sufixo adicionado a instrução, conforme tabela abaixo:
Exemplos:
Assim como o NASM consegue identificar o tamanho do operando quando é usado um registrador e a palavra-chave se torna opcional, o mesmo acontece no GAS e o sufixo também é opcional nesses casos.
Na sintaxe Intel saltos e chamadas distantes são feitas com jmp far [etc]
e call far [etc]
respectivamente. Na sintaxe da AT&T se usa o prefixo L nessas instruções, ficando: ljmp
e lcall
.
Na sintaxe Intel o endereçamento é bem intuitivo já que ele é escrito em formato de expressão matemática. Na sintaxe AT&T é um pouco mais confuso e segue o seguinte formato:
segment:displacement(base, index, scale)
.
Exemplos com o seu equivalente na sintaxe da Intel:
Como demonstrado no último exemplo o endereço relativo na sintaxe do GAS é feito explicitando RIP como base, enquanto na sintaxe do NASM isso é feito usando a palavra-chave rel
.
Na sintaxe da AT&T os saltos para endereços armazenados na memória devem ter um *
antes do rótulo para indicar que o salto deve ocorrer para o endereço que está armazenado naquele endereço de memória. Sem o *
o salto ocorre para o rótulo em si. Exemplo:
Saltos que especificam segmento e offset separam os dois valores por vírgula. Como em:
As diretivas do GAS funcionam de maneira semelhante as diretivas do NASM com a diferença que todas elas são prefixadas por um ponto.
No GAS comentários de múltiplas linhas podem ser escritos com /*
e */
assim como em C. Comentários de uma única linha podem ser escritos com #
ou //
.
No NASM existem as pseudo-instruções db
, dw
, dd
, dq
etc. que servem para despejar bytes no arquivo binário de saída. No GAS isso é feito usando as seguintes pseudo-instruções:
Exemplos:
O GAS tem diretivas específicas para declarar algumas seções padrão. Conforme tabela:
Porém ele também tem a diretiva .section
que pode ser usada de maneira semelhante a section
do NASM. Os atributos da seção porém são passados em formato de flags em uma string como segundo argumento. As flags principais são w
para dar permissão de escrita e x
para dar permissão de execução. Exemplos:
A diretiva .align
pode ser usada para alinhamento dos dados. Você pode usá-la no início da seção para alinhar a mesma, conforme exemplo:
A diretiva .intel_syntax
pode ser usada para habilitar a sintaxe da Intel para o GAS. Opcionalmente pode-se passar um parâmetro noprefix
para desabilitar o prefixo %
dos registradores.
Uma diferença importante da sintaxe Intel do GAS em relação ao NASM é que as palavra-chaves que especificam o tamanho do operando precisam ser seguidas por ptr
, conforme exemplo abaixo:
O exemplo abaixo é o mesmo apresentado no tópico sobre instruções de movimentação SSE porém reescrito na sintaxe do GAS/AT&T:
Sufixo
Tamanho
Palavra-chave equivalente no NASM
B
byte (8 bits)
byte
W
word (16 bits)
word
L
long/doubleword (32 bits)
dword
Q
quadword (64 bits)
qword
T
ten word (80 bits)
tword
Pseudo-instrução
Tipo do dado (tamanho em bits)
Equivalente no NASM
.byte
byte (8 bits)
db
.short
.hword
.word
word (16 bits)
dw
.long
.int
doubleword (32 bits)
dd
.quad
quadword (64 bits)
dq
.float
.single
Single-precision floating-point (32 bits)
dd
.double
Double-precision floating-point (64 bits)
dq
.ascii
.string
.string8
String (8 bits cada caractere)
db
.asciz
Mesmo que .ascii porém com um terminador nulo no final
-
.string16
String (16 bits cada caractere)
-
.string32
String (32 bits cada caractere)
-
.string64
String (64 bits cada caractere)
-
GAS
Equivalente no NASM
.data
section .data
.bss
section .bss
.text
section .text