Atualizado: 16/11/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Bibliotecas Compartilhadas (Shared Libraries) em Linux

Organizar o código de uma aplicação em múltiplos arquivos simplifica o desenvolvimento e a manutenção. Vamos considerar um exemplo prático.

Primeiro, criamos um arquivo print.asm com uma função que imprime texto no console:

global print

; 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        ; rdx = número de caracteres
    mov rdi, 1          ; stdout
    mov rax, 1          ; syscall write
    syscall
    ret

Esta função recebe o endereço de uma string em RSI e seu tamanho em RDI.

Em seguida, criamos o arquivo principal da aplicação, app.asm, que utilizará essa função:

global _start
extern print

section .data
    message: db "Hello World!", 10
    count equ $ - message

section .text
_start:
    mov rdi, count
    lea rsi, [message]
    call print

    mov rax, 60
    syscall

Aqui, a função print é declarada como externa e chamada para imprimir a message.

Linkagem Estática (Static Linking)

O método mais direto para combinar esses arquivos é a linkagem estática. Compilamos cada arquivo .asm em um arquivo-objeto (.o) e, em seguida, o linker (ld) une todos em um único executável.

$ nasm -felf64 print.asm -o print.o
$ nasm -felf64 app.asm -o app.o
$ ld app.o print.o -o app
$ ./app
Hello World!

Neste processo, o código da função print é copiado para dentro do arquivo executável app.

Desvantagens da Linkagem Estática:

  1. Aumento do Tamanho: O código de funções comuns é duplicado em cada programa que as utiliza, aumentando o tamanho de cada executável.
  2. Manutenção: Se um bug for encontrado na função print, todos os programas que a utilizam precisam ser recompilados e linkados novamente.

Linkagem Dinâmica e Bibliotecas Compartilhadas

O Linux oferece uma alternativa mais flexível: a linkagem dinâmica (dynamic linking). Com este método, o código das bibliotecas não é incorporado ao aplicativo. Em vez disso, as bibliotecas permanecem como arquivos separados, e o aplicativo apenas contém referências a elas. A união ocorre em tempo de execução, quando o programa é carregado.

Essas bibliotecas externas são chamadas de bibliotecas compartilhadas (shared libraries) ou objetos compartilhados (shared objects), e em sistemas Linux, elas têm a extensão .so. Abordagens semelhantes existem em outros sistemas, como as .dll no Windows e as .dylib no macOS.

Vantagens da Linkagem Dinâmica:

  • Flexibilidade: Bibliotecas podem ser atualizadas independentemente dos aplicativos. Uma correção de segurança na biblioteca beneficia todos os programas que a utilizam, sem a necessidade de recompilá-los.
  • Economia de Espaço: O código da função existe em um único lugar no sistema de arquivos, economizando espaço em disco.

Criando uma Biblioteca Compartilhada (.so)

Vamos transformar nosso arquivo print.asm em uma biblioteca compartilhada. É necessário fazer uma pequena alteração:

global print:function   ; Especifica o tipo do símbolo como '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
    ret

A diretiva global print:function informa ao linker que print é uma função, o que é necessário para a vinculação dinâmica.

Agora, seguimos os passos para criar a biblioteca:

  1. Compilar para um arquivo-objeto:

    $ nasm -felf64 print.asm -o print.o
  2. Criar a biblioteca compartilhada: Usamos o linker ld com a opção -shared.

    $ ld -shared print.o -o libprint.so

    É uma convenção em Linux nomear bibliotecas com o prefixo lib. Agora temos o arquivo libprint.so, nossa biblioteca compartilhada.

Usando a Biblioteca Compartilhada

Vamos compilar nosso app.asm e linká-lo dinamicamente com a biblioteca que criamos.

  1. Compilar o aplicativo principal:

    $ nasm -felf64 app.asm -o app.o
  2. Linkar dinamicamente:

    $ ld app.o -lprint -L. -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o app

    Vamos entender os novos parâmetros do linker (ld):

    • -lprint: Pede ao linker para procurar uma biblioteca chamada libprint.so.
    • -L.: Adiciona o diretório atual (.) à lista de locais onde o linker deve procurar por bibliotecas.
    • --dynamic-linker: Especifica o caminho para o loader dinâmico do sistema. Em sistemas x86-64, geralmente é /lib64/ld-linux-x86-64.so.2.

Ao tentar executar o programa, você provavelmente encontrará um erro:

$ ./app
./app: error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory

Isso acontece porque, em tempo de execução, o loader dinâmico procura bibliotecas apenas em caminhos padrão do sistema (como /lib e /usr/lib). Nosso arquivo libprint.so não está em nenhum desses locais.

Para resolver isso, podemos usar a variável de ambiente LD_LIBRARY_PATH para adicionar temporariamente o diretório atual ao caminho de busca do loader:

$ export LD_LIBRARY_PATH=.
$ ./app
Hello World!

Agora o programa funciona! O comando export define a variável apenas para a sessão atual do terminal.

O Papel do Loader Dinâmico

Quando você executa um programa linkado dinamicamente, o sistema operacional primeiro carrega o loader dinâmico (ex: ld-linux-x86-64.so.2) na memória. Este loader é responsável por:

  1. Ler as dependências do executável.
  2. Encontrar e carregar as bibliotecas compartilhadas necessárias (.so) na memória.
  3. Realizar a realocação, ajustando os endereços de memória para que o código do aplicativo possa chamar corretamente as funções na biblioteca.
  4. Transferir o controle para o ponto de entrada do aplicativo (_start).

Podemos inspecionar as dependências de um executável com o comando ldd:

$ ldd app
    linux-vdso.so.1 (0x00007ffc455f3000)
    libprint.so => ./libprint.so (0x00007efc8c478000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3c3a000000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f3c3a42b000)

A saída mostra que nosso app depende da libprint.so (encontrada no diretório atual) e de outras bibliotecas do sistema.

GOT e PLT

Para fazer a mágica da linkagem dinâmica funcionar, o linker utiliza duas tabelas importantes dentro do executável:

  • GOT (Global Offset Table): Uma tabela que armazena os endereços de funções e variáveis externas. Inicialmente, ela não contém os endereços reais.
  • PLT (Procedure Linkage Table): Uma tabela de "trampolins". Quando o programa chama uma função externa pela primeira vez (como print), a chamada vai para uma entrada na PLT. Essa entrada contém um código que instrui o loader dinâmico a encontrar o endereço real da função print, armazená-lo na GOT e, em seguida, saltar para lá. Nas chamadas subsequentes, a PLT saltará diretamente para o endereço já resolvido na GOT, tornando o processo mais rápido. Esse mecanismo é conhecido como lazy loading (carregamento tardio).

Resumo

  • Linkagem estática copia todo o código para o executável, enquanto a linkagem dinâmica mantém as bibliotecas como arquivos separados.
  • Bibliotecas compartilhadas em Linux têm a extensão .so e são criadas com a opção -shared do linker ld.
  • Para linkar um aplicativo com uma biblioteca compartilhada, usam-se as opções -l<nome> e -L<caminho>.
  • O loader dinâmico é responsável por carregar as bibliotecas .so em tempo de execução. A variável de ambiente LD_LIBRARY_PATH pode ser usada para especificar caminhos de busca adicionais.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com