O GNU Assembler, comumente conhecido como gas ou as, é o assembler desenvolvido pelo Projeto GNU.
É o back-end padrão do GCC.
Ele é usado para montar o sistema operacional GNU e o kernel Linux, e vários outros softwares.
Faz parte do pacote GNU Binutils.
O GAS é multiplataforma e pode ser executado e montado em diversas arquiteturas de computador diferentes.
É um software livre lançado sob a Licença Pública Geral GNU v3.
A extensão padrão é .s
(o ideal é para identificação), mas você pode usar qualquer extensão desde que haja somente código GAS dentro do arquivo, exemplos: .gas
, .as
e .S
.
Os registradores em assembly x86_64
são utilizados para armazenar dados temporários e realizar operações aritméticas e lógicas. São eles:
Esses são os principais registradores em GAS/NASM para x86_64
. Eles são essenciais para manipulação de dados, controle de fluxo, e execução de operações em programas assembly.
Os registradores possuem o mesmo conceito e funcionalidade básica tanto no NASM quanto no GNU Assembler (GAS) para a arquitetura x86_64
. A principal diferença entre NASM e GAS é a sintaxe usada para escrever os programas, mas os registradores e suas utilizações permanecem consistentes.
rax
, rbx
, rcx
, rdx
, rsi
, rdi
, rbp
, rsp
, r8-r15
%rax
, %rbx
, %rcx
, %rdx
, %rsi
, %rdi
, %rbp
, %rsp
, %r8-%r15
eax
, ebx
, ecx
, edx
, esi
, edi
, ebp
, esp
, r8d-r15d
%eax
, %ebx
, %ecx
, %edx
, %esi
, %edi
, %ebp
, %esp
, %r8d-%r15d
ax
, bx
, cx
, dx
, si
, di
, bp
, sp
, r8w-r15w
%ax
, %bx
, %cx
, %dx
, %si
, %di
, %bp
, %sp
, %r8w-%r15w
ah
, al
, bh
, bl
, ch
, cl
, dh
, dl
, spl
, bpl
, sil
, dil
, r8b-r15b
%ah
, %al
, %bh
, %bl
, %ch
, %cl
, %dh
, %dl
, %spl
, %bpl
, %sil
, %dil
, %r8b-%r15b
cs
, ds
, ss
, es
, fs
, gs
%cs
, %ds
, %ss
, %es
, %fs
, %gs
rip
, rflags
%rip
, %rflags
xmm0-xmm15
, ymm0-ymm15
, zmm0-zmm31
%xmm0-%xmm15
, %ymm0-%ymm15
, %zmm0-%zmm31
Para instalar o GNU Assembler (GAS) você deve instalar o pacote binutils, que inclui o assembler as (parte do conjunto de ferramentas GNU Binutils).
Você pode fazer o download direto da página: https://www.gnu.org/software/binutils/ ou usar o seu gerenciador de pacotes, exemplo no Ubuntu:
sudo apt install binutils
No Windows você deve usar o MinGW e fazer download aqui do binutils.
Agora veremos alguns exemplos de códigos básicos para nos adaptarmos como a sintaxe é utilizada.
.section .data
hello:
.ascii "Hello, World!\0"
.section .text
.globl _start
_start:
mov $1, %rax # syscall: sys_write
mov $1, %rdi # file descriptor: stdout
mov $hello, %rsi # endereço da string
mov $13, %rdx # comprimento da string
syscall # chama o kernel
mov $60, %rax # syscall: sys_exit
xor %rdi, %rdi # status de saída: 0
syscall # chama o kernel
.section .data
: Declara a seção de dados, onde as variáveis e strings são armazenadas.hello: .ascii "Hello, World!\0"
: Define uma string terminada em nulo (0
)..section .text
: Declara a seção de código, onde o código executável é armazenado..globl _start
: Torna a label _start
visível para o linker._start
: Ponto de entrada do programa.mov $1, %rax
: Coloca o número do syscall sys_write no registrador rax
.mov $1, %rdi
: Coloca o número do file descriptor (stdout) no registrador rdi
.mov $hello, %rsi
: Coloca o endereço da string hello
no registrador rsi
.mov $13, %rdx
: Coloca o comprimento da string no registrador rdx
.syscall
: Chama o kernel para executar o syscall
.mov $60, %rax
: Coloca o número do syscall sys_exit no registrador rax
.xor %rdi, %rdi
: Zera o registrador rdi
para definir o status de saída como 0
.syscall
: Chama o kernel para terminar o programa.Compilar e executar:
as --64 -o hello.o hello.s
ld -o hello hello.o
./hello
No Windows é o
hello.exe
em vez de./hello
.
Após rodar, note que o Hello, World!
vai colar com o prompt:
Hello, World!$prompt>
Para resolver isso, troque o \0
por \n
:
# .ascii "Hello, World!\0"
.ascii "Hello, World!\n"
E aumente o comprimento da string para 14 bytes (13 caracteres + 1 para a quebra de linha \n
):
# mov $13, %rdx
mov $14, %rdx
Recompile e rode!
Mesmo código, mas com NASM:
section .data
msg db 'Hello, World!', 0
section .text
global _start
_start:
mov rax, 1 ; syscall: write
mov rdi, 1 ; file descriptor: stdout
mov rsi, msg ; pointer to message
mov rdx, 13 ; message length
syscall ; make the syscall
mov rax, 60 ; syscall: exit
xor rdi, rdi ; status: 0
syscall ; make the syscall
Para compilar e rodar o NASM:
# Antes instale o NASM, ex.: sudo apt install nasm
nasm -f elf64 hello.asm
ld -s -o hello hello.o
./hello
Note: Além do símbolo %
(do GAS) na frente do registradores e os comentários serem #
(GAS) e ;
(NASM), o GAS também pode comentar estilo C: /* Comentário para multiplas linhas */
, além de outras formas dependendo da arquitetura, exemplos:
;
— AMD 29k family, ARC, H8/300 family, HPPA, PDP-11, picoJava, Motorola e M32C@
— ARM 32-bit//
— AArch64|
— M680x0!
— Renesas SH#
— i386, x86-64, i960, 68HC11, 68HC12, VAX, V850, M32R, PowerPC, MIPS, M680x0, e RISC-VEmbora a sintaxe seja diferente (NASM usa uma abordagem mais “intel” enquanto GAS usa uma abordagem “AT&T”), os registradores desempenham as mesmas funções em ambas as assemblers.
Além do GNU Assembler (GAS) e do NASM (Netwide Assembler), existem vários outros assemblers conhecidos e amplamente utilizados. Aqui estão alguns dos mais notáveis:
.section .bss
.lcomm buffer, 13 # reserva 13 bytes para o buffer
.section .data
hello:
.ascii "Hello, World!\0"
.section .text
.globl _start
_start:
mov $buffer, %rdi # endereço do buffer
mov $hello, %rsi # endereço da string
call copy_string # chama a função para copiar a string
mov $1, %rax # syscall: sys_write
mov $1, %rdi # file descriptor: stdout
mov $buffer, %rsi # endereço do buffer
mov $13, %rdx # comprimento da string
syscall # chama o kernel
mov $60, %rax # syscall: sys_exit
xor %rdi, %rdi # status de saída: 0
syscall # chama o kernel
copy_string:
mov $13, %rcx # comprimento da string
.loop:
mov (%rsi), %al # lê um byte da string
mov %al, (%rdi) # escreve no buffer
inc %rsi # avança para o próximo byte da string
inc %rdi # avança para o próximo byte do buffer
loop .loop # repete até copiar todos os bytes
ret # retorna para _start
.lcomm buffer, 13
: Reserva 13 bytes para o buffer na seção BSS.call copy_string
: Chama a função copy_string
para copiar a string hello
para o buffer.copy_string
: Função que copia a string para o buffer.mov $13, %rcx
: Define o contador de repetição para o comprimento da string.loop .loop
: Laço de repetição para copiar cada byte da string..section .data
hello:
.ascii "Hello, World!\0"
.section .text
.globl _start
_start:
call print_hello # chama a função para imprimir a string
mov $60, %rax # syscall: sys_exit
xor %rdi, %rdi # status de saída: 0
syscall # chama o kernel
print_hello:
mov $1, %rax # syscall: sys_write
mov $1, %rdi # file descriptor: stdout
mov $hello, %rsi # endereço da string
mov $13, %rdx # comprimento da string
syscall # chama o kernel
ret # retorna para _start
call print_hello
: Chama a função print_hello
para imprimir a string hello
;print_hello
: Função que executa o syscall sys_write para imprimir a string.E imprime na tela com quebra de linha/nova linha (sem colar no prompt)!
.section .data
quebra:
.ascii "\n" # para pular/quebrar uma linha
num1:
.quad 5 # define o primeiro número
num2:
.quad 10 # define o segundo número
result:
.quad 0 # reserva espaço para o resultado
buffer:
.space 2 # espaço para a string do número (2 dígitos)
.section .text
.globl _start
_start:
mov num1(%rip), %rax # carrega num1 em rax
add num2(%rip), %rax # soma num2 a rax
mov %rax, result(%rip) # armazena o resultado
# código para imprimir o resultado
mov result(%rip), %rax # carrega o resultado em rax
mov $buffer + 2, %rsi # aponta para o final do buffer(2 dígitos)
call int_to_string # converte o número para string
mov $1, %rax # syscall: sys_write
mov $1, %rdi # file descriptor: stdout
lea buffer(%rip), %rsi # endereço da string
mov $2, %rdx # comprimento máximo da string (2 dígitos)
syscall # chama o kernel
mov $1, %rax # syscall: sys_write
mov $1, %rdi # file descriptor: stdout
mov $quebra, %rsi # endereço da quebra
mov $1, %rdx # comprimento da quebra(1 dígito)
syscall # chama o kernel
mov $60, %rax # syscall: sys_exit
xor %rdi, %rdi # status de saída: 0
syscall # chama o kernel
int_to_string:
# converte %rax para string decimal e armazena em %rsi
mov %rax, %rcx # copia o número para rcx
mov $10, %rbx # base decimal
convert_loop:
xor %rdx, %rdx # limpa rdx (dividendo)
div %rbx # divide rax por 10
add $'0', %dl # converte o resto para caractere ASCII
dec %rsi # move o ponteiro do buffer para trás
mov %dl, (%rsi) # armazena o caractere no buffer
test %rax, %rax # verifica se rax é 0
jnz convert_loop # se não for 0, continua o loop
ret # retorna para _start
num1: .quad 5
e num2: .quad 10
: Define os números a serem somados.result: .quad 0
: Reserva espaço para armazenar o resultado.add num2(%rip), %rax
: Soma num2
ao valor de num1
armazenado em rax
.mov %rax, result(%rip)
: Armazena o resultado da soma.call print_result
: Chama a função print_result
para imprimir o resultado.Para mais informações e uma documentação completa do GNU Assembler visite o endereço: https://sourceware.org/binutils/docs-2.42/as.html.