Como Usar Bibliotecas Padrão do Linux em Assembly NASM
O sistema operacional Linux vem com um conjunto de bibliotecas compartilhadas, principalmente as da linguagem C, que podemos utilizar em nossos programas Assembly para acessar funcionalidades de alto nível.
As bibliotecas compartilhadas do sistema geralmente começam com o prefixo lib, têm a extensão .so e estão localizadas em diretórios como /usr/lib. Em sistemas x86-64, a biblioteca C principal, libc.so, pode ser encontrada em /usr/lib/x86_64-linux-gnu. Esta biblioteca contém funções essenciais como printf, scanf, malloc, entre outras.
Utilizando a Função printf
Vamos criar um programa app.asm que usa a função printf da libc para imprimir uma mensagem no console.
global _start
extern printf ; Declara a função printf como externa
section .data
message: db "Hello programicio.com", 10, 0
section .text
_start:
lea rdi, [rel message] ; 1º parâmetro de printf: endereço da string
xor rax, rax ; Indica que nenhum argumento de ponto flutuante é usado
call printf ; Chama a função printf
mov rax, 60
syscallNeste código, declaramos printf como externa e a chamamos, passando o endereço da string no registrador RDI, conforme a convenção de chamada do System V ABI.
Linkando com a Biblioteca C
Para conectar nosso programa com a biblioteca libc.so, usamos a opção -l do linker ld. Essa opção é seguida pelo nome da biblioteca, omitindo o prefixo lib e a extensão .so. Portanto, para linkar com libc.so, usamos -lc.
Montar o arquivo-objeto:
$ nasm -felf64 app.asm -o app.o
Linkar com
libc:$ ld --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -lc app.o -o app
O comando instrui o linker a usar o loader dinâmico e a procurar por símbolos não resolvidos (como
printf) na bibliotecac(que corresponde alibc.so).
Agora, podemos executar o programa:
$ ./app Hello programicio.com
Podemos usar a ferramenta ldd para verificar que nosso aplicativo agora depende da libc.so:
$ ldd app
linux-vdso.so.1 (0x00007ffc129e1000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f354a205000)
/lib64/ld-linux-x86-64.so.2 (0x00007f354a43b000)A segunda linha confirma a dependência da biblioteca C.
Usando Bibliotecas em um Executável PIE
Se estivermos criando um executável independente de posição (PIE), devemos nos lembrar de usar o endereçamento relativo às tabelas PLT e GOT para acessar funções e variáveis externas.
Vamos adaptar o código app.asm para ser um PIE:
global _start
extern printf
section .data
message: db "Hello programicio.com", 10, 0
section .text
_start:
lea rdi, [rel message]
xor rax, rax
call printf wrt ..plt ; Chamada via PLT
mov rax, 60
syscallA única mudança é adicionar wrt ..plt à chamada. A compilação também requer a flag -pie:
$ nasm -felf64 app.asm -o app.o $ ld --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -pie -lc app.o -o app $ ./app Hello programicio.com
Acessando Variáveis Globais de Bibliotecas
Além de funções, as bibliotecas também podem exportar variáveis globais. Por exemplo, a libc exporta um ponteiro stdout que representa o fluxo de saída padrão. Vamos usá-lo com a função fprintf, que tem a seguinte assinatura em C:
int fprintf(FILE *stream, const char *format, ...)
global _start
extern fprintf
extern stdout ; Declara o objeto global stdout como externo
section .data
message: db "Hello World", 10, 0
section .text
_start:
mov rdi, [rel stdout wrt ..got] ; Carrega o endereço do ponteiro stdout via GOT
mov rdi, [rdi] ; De-referencia para obter o valor do ponteiro (o stream)
lea rsi, [rel message] ; 2º parâmetro: endereço da string
xor rax, rax
call fprintf wrt ..plt ; Chama fprintf via PLT
mov rax, 60
syscallNeste exemplo:
- Declaramos
fprintfestdoutcomo externos. - Usamos a GOT para obter o endereço da variável global
stdout. - Como
stdouté um ponteiro para uma estruturaFILE, nós de-referenciamos o endereço para obter o valor real do ponteiro, que é o quefprintfespera como primeiro argumento. - Chamamos
fprintfvia PLT.
O processo de compilação é o mesmo do exemplo PIE anterior.
Resumo
- É possível chamar funções de bibliotecas padrão do Linux (como a
libc.so) a partir de programas Assembly. - Para linkar uma biblioteca compartilhada, use a opção
-l<nome>do linkerld(por exemplo,-lcparalibc.so). - Ao criar executáveis independentes de posição (PIE), as chamadas a funções externas devem ser feitas via PLT (
wrt ..plt). - O acesso a variáveis globais exportadas por bibliotecas em um PIE deve ser feito via GOT (
wrt ..got).