Chamadas de Sistema no Linux e a Instrução SYSCALL | Assembly NASM
Em sistemas operacionais modernos, muitos recursos — como dispositivos de entrada e saída — não estão diretamente acessíveis para aplicações comuns. Para acessar esses recursos e outras funcionalidades internas do sistema, o programa executa uma chamada de sistema (system call).
Durante essa operação, a execução da aplicação é temporariamente interrompida, e o controle é transferido para o núcleo do sistema operacional. O kernel verifica se o pedido é válido e se o processo tem permissão para executá-lo. Após realizar a tarefa solicitada, o controle retorna à aplicação.
Por exemplo, para encerrar corretamente um programa em Linux, é necessário chamar a função do sistema identificada pelo número 60.
global _start
section .text
_start:
mov rdi, 22 ; código de status de saída
mov rax, 60 ; número da função do sistema (exit)
syscall ; chamada de sistemaA instrução syscall executa a função do sistema cujo número foi carregado no registrador RAX. Cada função possui um número próprio; o kernel identifica qual operação realizar com base nesse número.
Registradores e Parâmetros
Além do número da função, algumas chamadas de sistema exigem parâmetros adicionais, que são enviados nos seguintes registradores, nesta ordem:
rdi, rsi, rdx, r10, r8, r9
Se a função requer apenas um parâmetro, ele é colocado em rdi. Se forem dois, o segundo é colocado em rsi, e assim por diante.
A função exit, por exemplo, precisa de um único argumento: o código de status, colocado em rdi.
A instrução syscall também altera os registradores rcx e r11.
rcxarmazena o endereço da próxima instrução (valor anterior derip).r11guarda o valor antigo derflags.
Se o programa faz uso desses registradores, é importante salvá-los antes da chamada (por exemplo, empilhando-os com push) para evitar perda de dados.
Após a execução, o valor retornado pela função é armazenado em rax.
Uma tabela completa e atualizada das chamadas de sistema do Linux x86-64 está disponível na documentação oficial do kernel (Linux kernel syscall tables).
Tempo em Unix
Os sistemas baseados em Unix medem o tempo em segundos desde o início da época Unix (00:00 de 1º de janeiro de 1970). A chamada de sistema time, de número 201 (0xc9), retorna o tempo atual. Ela requer um parâmetro: o endereço de uma variável de 64 bits onde o tempo será armazenado.
Quando a função é executada com sucesso, rax contém o mesmo valor do ponteiro fornecido em rdi.
Trecho do código-fonte da função time no kernel:
SYSCALL_DEFINE1(time, __kernel_old_time_t __user *, tloc)
{
__kernel_old_time_t i = (__kernel_old_time_t)ktime_get_real_seconds();
if (tloc) {
if (put_user(i,tloc))
return -EFAULT;
}
force_successful_syscall_return();
return i;
}Exemplo de programa em NASM que aguarda aproximadamente 5 segundos antes de encerrar:
global _start
section .data
curtime dq 0 ; variável para armazenar o tempo
section .text
_start:
; obtém o tempo inicial
mov rax, 0xc9 ; número da função time
mov rdi, curtime ; endereço da variável curtime
syscall
mov rdx, [curtime] ; armazena o tempo atual em rdx
add rdx, 5 ; adiciona 5 segundos
timeloop:
mov rax, 0xc9 ; verifica o tempo novamente
mov rdi, curtime
syscall
cmp qword [curtime], rdx
jb timeloop ; enquanto não atingir o tempo desejado, repete
exit:
mov rdi, 22 ; código de status
mov rax, 60 ; chamada de saída (exit)
syscallNesse exemplo, o programa obtém o tempo inicial e entra em um laço que consulta o tempo atual repetidamente, saindo apenas após o acréscimo de 5 segundos.
Escrita em Arquivo ou Console
Uma das chamadas de sistema mais utilizadas é write, responsável por escrever dados em um arquivo ou em um fluxo de saída.
Em Linux, praticamente tudo é tratado como arquivo: dispositivos, terminais, e até mesmo o teclado e o monitor.
Quando um arquivo é aberto, o kernel associa a ele um número chamado descritor de arquivo (file descriptor). Os três descritores padrão de todo processo são:
0: entrada padrão (stdin) — teclado1: saída padrão (stdout) — console2: saída de erro (stderr) — mensagens de erro
A função write possui o número 1 e recebe três parâmetros:
ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
O registrador rdi contém o descritor do destino (por exemplo, 1 para o console).
rsi armazena o endereço do texto a ser escrito, e rdx define o tamanho do texto.
Exemplo que exibe uma mensagem no console:
global _start
section .data
message db "Hello www.programicio.com", 10
len equ $ - message ; tamanho da string
section .text
_start:
mov rax, 1 ; número da função write
mov rdi, 1 ; descritor de saída padrão
mov rsi, message ; endereço da mensagem
mov rdx, len ; comprimento da mensagem
syscall
mov rdi, rax ; quantidade de bytes realmente escritos
mov rax, 60 ; chamada de saída
syscallApós a execução de syscall, o número de bytes efetivamente escritos é retornado em rax. No exemplo, esse valor é transferido para rdi antes do encerramento do programa.
Compilação e execução no Linux:
nasm -f elf64 hello.asm -o hello.o ld hello.o -o hello ./hello Hello www.programicio.com echo $? 30
O código acima imprime a mensagem no terminal e retorna o valor 30, que corresponde ao número de bytes escritos.
Resumo
- Chamadas de sistema permitem que programas interajam com o kernel.
- A instrução
syscallexecuta funções identificadas por números específicos armazenados emrax. - Parâmetros são enviados nos registradores
rdi,rsi,rdx,r10,r8er9. rcxer11são alterados durante a execução e devem ser preservados quando necessário.- Exemplos comuns incluem
exit(encerramento),time(obtenção de tempo) ewrite(escrita em arquivo ou console).