Carregamento Dinâmico de Bibliotecas em Tempo de Execução em Assembly NASM
Normalmente, o loader dinâmico carrega automaticamente todas as bibliotecas necessárias quando um aplicativo é iniciado. No entanto, é possível carregar bibliotecas manualmente durante a execução do programa. Esse processo, conhecido como carregamento dinâmico explícito (explicit dynamic loading), é realizado com as funções dlopen e dlsym da biblioteca C.
dlopen: Abre uma biblioteca compartilhada especificada por seu nome de arquivo. Se bem-sucedida, retorna um "handle" (um ponteiro) para a biblioteca, que pode ser usado em chamadas subsequentes.dlsym: Procura por um símbolo (geralmente o nome de uma função) dentro de uma biblioteca aberta. Se o símbolo for encontrado, a função retorna um ponteiro para ele.
Essa técnica é comumente usada para implementar sistemas de plugins ou extensões, onde a funcionalidade pode ser adicionada a um programa sem a necessidade de recompilá-lo.
Exemplo Prático
Vamos criar um exemplo onde um programa principal carrega dinamicamente uma biblioteca e chama uma função contida nela.
Primeiro, criamos a nossa biblioteca compartilhada, print.asm, com a função print:
global print:function
; Função print: imprime um texto no console
; Parâmetros:
; RDI - número de caracteres
; RSI - endereço da string
section .text
print:
mov rdx, rdi
mov rdi, 1
mov rax, 1
syscall
retCompilamos este arquivo para criar a biblioteca libprint.so:
$ nasm -felf64 print.asm -o print.o $ ld -shared print.o -o libprint.so
Agora, criamos o programa principal, app.asm, que irá carregar libprint.so e usar a função print.
global _start
extern dlopen
extern dlsym
section .data
lib_name: db "libprint.so", 0
function_name: db "print", 0
message: db "Hello programicio.com", 10
len: equ $-message
section .text
_start:
; 1. Abrir a biblioteca
lea rdi, [rel lib_name] ; 1º parâmetro: nome do arquivo da biblioteca
mov rsi, 1 ; 2º parâmetro: flag (RTLD_LAZY)
call dlopen wrt ..plt ; Carrega a biblioteca
; 2. Encontrar o endereço da função
mov rdi, rax ; 1º parâmetro: handle da biblioteca retornado por dlopen
lea rsi, [rel function_name] ; 2º parâmetro: nome da função
call dlsym wrt ..plt ; Obtém o ponteiro para a função (retornado em rax)
; 3. Chamar a função através do ponteiro
mov rdi, len ; 1º parâmetro para 'print': tamanho da string
lea rsi, [rel message] ; 2º parâmetro para 'print': endereço da string
call rax ; Chama a função cujo endereço está em rax
mov rax, 60
syscallO código é dividido em três etapas claras:
- Abrir a biblioteca: Chamamos
dlopen, passando o nome do arquivo da biblioteca ("libprint.so") e um flag. O valor1corresponde aRTLD_LAZY, que indica que os símbolos só serão resolvidos quando forem efetivamente usados.dlopenretorna o handle da biblioteca no registradorrax. - Encontrar a função: Chamamos
dlsym, passando o handle da biblioteca (que está emrax) e o nome da função que procuramos ("print").dlsymretorna o endereço de memória da funçãoprintno registradorrax. - Chamar a função: Preparamos os parâmetros para a nossa função
print(tamanho e endereço da string) e, em seguida, usamoscall raxpara executar o código no endereço retornado pordlsym.
Para compilar o aplicativo, precisamos linká-lo com a libc (que contém dlopen e dlsym), mas não com a nossa libprint.so, pois ela será carregada em tempo de execução.
$ nasm -felf64 app.asm -o app.o $ ld --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -lc app.o -o app
Ao executar, o programa app encontrará e carregará libprint.so por conta própria:
$ ./app Hello programicio.com
É importante notar que o loader dinâmico ainda precisa ser capaz de encontrar libprint.so. Se a biblioteca não estiver em um diretório padrão, a variável LD_LIBRARY_PATH pode ser necessária, como vimos no artigo anterior.
Resumo
- O carregamento dinâmico explícito permite que um programa carregue bibliotecas compartilhadas sob demanda durante sua execução.
- A função
dlopené usada para abrir um arquivo de biblioteca (.so) e obter um handle para ela. - A função
dlsymé usada para obter o endereço de uma função (ou variável) dentro de uma biblioteca já aberta, usando seu nome como uma string. - Uma vez obtido o endereço de uma função, ela pode ser chamada indiretamente, por exemplo, com
call rax.