Este artigo tem como propósito explicar como se pode fazer uma chamada de sistema (syscall) do macOS, através de linguagem de máquina (Intel i386). Uma chamada de sistema é o mecanismo padrão para obter recursos e funcionalidades básicas do sistema operacional, como por exemplo, ler e escrever dados em um descritor de arquivos.
Os exemplos desses artigos foram testados em um MacBook (Core Duo) e um MacbookPro (Core 2 Duo) rodando macOS versão 10.6.7.
O macOS segue o estilo de chamadas de sistema do FreeBSD, de forma que os parâmetros são postos na pilha, o número da syscall é gravado no registrador EAX e então, chama-se a instrução int $0x80
.
O valor de retorno da syscall ou status está contida no registrador EAX e deve-se restaurar a pilha ao seu estado original a chamada.
É adequado chamar int $0x80
indiretamente através de uma subrotina, em vez de usar diretamente a instrução. O motivo é por questão de alinhamento de pilha. Faremos uso então de uma subrotina de nome _syscall
, assim definida:
_syscall: int $0x80 ret
O próximo passo é conhecer quais são as chamadas de sistema disponíveis (os valores decimais) e parâmetros esperados. Estas chamadas são definidas em um arquivo de template do componente XNU, de nome syscalls.master.
Vamos fazer uso das chamadas de nome exit
, write
e sync
, que possuem as seguintes assinaturas:
1 AUE_EXIT ALL { void exit(int rval); } 4 AUE_NULL ALL { user_ssize_t write( int fd, user_addr_t cbuf, user_size_t nbyte); } 36 AUE_SYNC ALL { int sync(void); }
O exemplo abaixo é o fragmento de uma chamada de sistema para sync
, que avisa ao sistema operacional para esvaziar/sincronizar qualquer buffer que esteja em memória para o disco. Esta syscall não aceita parâmetros e não retorna nenhuma informação de execução.
.text _syscall: int $0x80 ret .globl start start: movl $36, %eax # sync = 36 call _syscall # sync()
Nota: diferentemente dos programas em C, programas em código de máquina possuem como ponto de entrada o símbolo start
.
Exemplo simples de uma chamada de sistema que retorna imediatamente ao shell do usuário o valor de status 42, o número mágico da resposta do sentido da vida (e tudo mais). O valor de retorno (rval) deve ser empilhado como um parâmetro.
.text _syscall: int $0x80 ret .globl start start: pushl $42 # rval = 42 movl $1,%eax # exit = 1 call _syscall # exit(42)
Para transformar este fonte em um arquivo executável, faz-se:
$ as -arch i386 retval.s -o retval.o $ ld retval.o -o retval
E o resultado após a execução é o status 42 para o shell, que pode ser obtido com a variável do shell $?
.
$ ./retval $ echo $? 42
Para um exemplo mais complexo, vejamos um programa que escreve a mensagem Hello World! na saída padrão (STDOUT) e retorna ao shell o valor 0. Os parâmetros são empilhados seguindo a ordem da direita para a esquerda na assinatura da chamada do sistema.
.text _syscall: int $0x80 ret .globl start start: pushl len # nbyte = len pushl $msg # cbuf = msg pushl $1 # fd = STDOUT = 1 movl $4,%eax # write = 4 call _syscall # write(fd=1,cbuf=&msg,nbyte=len) addl $12,%esp # restore stack (3 * 4 = 12) pushl $0 movl $1,%eax call _syscall # exit(0) .data msg: .ascii "Hello World!\n" len: .long . - msg
Para gerar o programa, faz-se:
$ as -arch i386 hello.s -o hello.o $ ld hello.o -o hello
O resultado pode ser observado como segue:
$ ./hello Hello World! $ echo $? 0