fbpx
Whatsapp (31) 3069-8315
atendimento@certificacaolinux.com.br
Bibliotecas Compartilhadas no Linux

Aprenda como funcionam as biblitoecas compartilhadas (shared libraries) no Linux, e como isso pode interferir no funcionamento dos programas.

Determinar quais as bibliotecas compartilhadas de que um programa executável depende para executar e instalá-las quando necessário faz parte dos objetivos do exame.

Para entendermos a gerência das bibliotecas compartilhadas, precisamos primeiro entender o que são bibliotecas e para que elas servem.

Durante a escrita do código fonte de um programa, o desenvolvedor faz uso de diversas funções e procedimentos já definidos pelo sistema operacional em arquivos chamados de bibliotecas.

Estas funções permitem que o programador possa usar recursos como escrita em disco, escrita na tela, receber dados do teclado, do mouse, enviar dados pela rede e muito mais, sem a necessidade de reescrever a roda.

Quando o programa é compilado, o último estágio de sua construção é fazer as ligações.

Alguns compiladores já fazem este processo de reunir todos os objetos necessários e compor um objeto final automaticamente.

Outros necessitam que o programador execute um outro programa chamado de linker. As bibliotecas podem ser entendidas de modo grosseiro como as “DLLs” do Windows.

Diretório /lib64

Nas distribuições modernas de 64 bits as bibliotecas ficam no diretório /lib64. Dependendo da arquitetura do processador, o nome do diretório pode variar (ex. /lib para 32 bits).

Tipos de Biblioteca

Existem dois tipos de bibliotecas no Linux. As estáticas e as dinâmicas. A decisão de qual biblioteca utilizar compete ao programador.

Ao usar uma biblioteca estática, o linker encontra as funções e procedimentos que o programa precisa, e as copia fisicamente no arquivo de saída executável gerado.

Isso faz com que o executável final possa rodar de forma independente sem utilizar nenhuma biblioteca. Mas perde-se no desempenho, no gasto desnecessário de memória e no tamanho do programa final.

É comum o programador fazer uso das bibliotecas compartilhadas ao invés de estáticas. Ao fazer as ligações de um programa que as utiliza, o linker faz uma referência às bibliotecas compartilhadas.

Desta forma, quando este programa for executado, o sistema terá de carregar primeiro as bibliotecas necessárias, se estas já não estiverem na memória.

Desta maneira, os executáveis gerados são mais eficientes, pois tendem a ser menores, usar menos memória e ocupar menos espaço em disco.

O ponto fraco desta metodologia é que os programas necessitam das bibliotecas compartilhadas e uma mudança nas versões destes arquivos também pode afetar o seu funcionamento.

O que são “soname” ?

Toda biblioteca compartilhada tem um nome especial chamado de “soname”. O soname é composto pelo prefixo “lib”, seguido do nome da biblioteca, o sufixo “.so.” e o número da versão da biblioteca que é incrementado quando esta sofre alterações na sua interface.

Por exemplo:

libjack-0.80.0.so.0
libvorbis.so.0
libWand.so.6
libjpeg.so.62
libwv2.so.1

Os arquivos executáveis são examinados no tempo de execução pelo linker de tempo de execução chamado ld.so.

Este interpretador especial completa as ligações entre o executável e as bibliotecas compartilhadas. Se o ld.so não conseguir encontrar e ler as dependências, ele irá falhar e o executável não irá ser carregado.

O Linker liga as bibliotecas ao software

O linker ld.so mantém índice de todas as bibliotecas e a sua localização num arquivo especial chamado /etc/ld.so.cache. Ele é binário e, portanto, pode ser lido rapidamente pelo ld.so.

É por isso que um administrador Linux tem de estar preparado para gerir as bibliotecas compartilhadas e as suas versões para um correto funcionamento do sistema e os seus aplicativos.

Neste artigo você pode aprender mais sobre o processo de compilação e “linkagem” dos programas nas bibliotecas.

Vejamos os utilitários que irão ajudar nesta tarefa:

Programa ldd no Linux

O comando ldd – List Dynamic Dependencies – fornece uma lista das dependências dinâmicas que um determinado programa precisa. Ele irá retornar o nome da biblioteca compartilhada e sua localização esperada.

Exemplo:

# ldd /bin/bash

libreadline.so.4 => /lib/libreadline.so.4 (0x4001c000)

libhistory.so.4 => /lib/libhistory.so.4 (0x40049000)

libncurses.so.5 => /lib/libncurses.so.5 (0x40050000)

libdl.so.2 => /lib/libdl.so.2 (0x40096000)

libc.so.6 => /lib/libc.so.6 (0x40099000)

/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

# ldd ldd teste_dijkstra

libc.so.6 => /lib/libc.so.6 (0x4001c000)

/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

Este comando é importante para determinarmos qual são as bibliotecas necessárias de um executável.

Programa ldconfig no Linux

O utilitário ldconfig cria os links e refaz o índice das bibliotecas dinâmicas do arquivo /etc/ld.so.cache.

Ele procura por bibliotecas nos diretórios /usr/lib e /lib, assim como nos diretórios listados em /etc/ld.so.conf, bem como o diretório informado na linha de comando.

As opções mais comuns são:

  • -p: Lista o conteúdo do cache /etc/ld.so.cache.
  • -v: Mostra o progresso da atualização do cache.
  • -f: arquivo informa um outro arquivo de configuração diferente do padrão /etc/ld.so.conf.

Exemplos:

# ldconfig –p

229 libs found in cache ‘/etc/ld.so.cache’

src_vipa.so (ELF) => /usr/lib/src_vipa.so

libz.so.1 (libc6) => /lib/libz.so.1

libz.so.1 (libc6) => /usr/lib/libz.so.1

# ldconfig –v

/usr/X11R6/lib:

libSM.so.6 -> libSM.so.6.0

libdps.so.1 -> libdps.so.1.0

libXrender.so.1 -> libXrender.so.1.2

(…)

Variável ambiental LD_LIBRARY_PATH

Ainda é possível fornecer ao linker em tempo de execução ld.so uma lista de diretórios extras que podem conter bibliotecas compartilhadas através da variável ambiental LD_LIBRARY_PATH.

Uma lista de diretórios poderá ser configurada, separando-os por dois pontos “:”. Esta lista antecede a lista do arquivo ls.so.conf.

# set | grep LD_LIBRARY_PATH

LD_LIBRARY_PATH=/usr/lib

Por razões de segurança, a variável LD_LIBRARY_PATH é ignorada pelo ld.so quando este faz ligações de programas que possuem o bit SUID ou SGID habilitados.

Seu uso é comum para testar novas rotinas em bibliotecas em desenvolvimento ao invés de executar as rotinas já instaladas.

Toda vez que uma nova biblioteca for instalada, ou uma nova versão de biblioteca, é necessário atualizar o cache do linker ld.so com o comando ldconfig.

Aprenda muito mais sobre Linux em nosso curso online. Você pode fazer a matrícula aqui com trial de 7 dias grátis. Se você já tem uma conta, pode acessar aqui.

Gostou? Compartilhe 🙂

Compilando programas no Linux com o GCC

Um compilador é um programa especial que processa instruções escritas em uma linguagem de programação particular e a transforma em linguagem ou código de máquina.

Geralmente, um programador escreve instruções em uma linguagem de alto nível como Pascal ou C, usando um editor de textos. O arquivo que é criado contém o que chamamos de fonte do programa.

O programador então executa o compilador de linguagem apropriado, especificando o nome do arquivo que contém as instruções fonte.

Quando executado, o compilador primeiro analisa todas as instruções da linguagem sintaticamente uma após a outra e então, em um ou mais estágios ou “passos” sucessivos, constrói o código de saída.

Tradicionalmente, a saída da compilação é chamada de código objeto ou algumas vezes de módulo objeto. O código objeto é o código de máquina que o processador pode executar.

Tradicionalmente em alguns sistemas e linguagens, um passo adicional é necessário após a compilação.

Este passo é destinado a resolver o local relativo de instruções e dados quando mais de um módulo objeto é executado ao mesmo tempo e eles possuírem referências cruzadas de um para o outro. Este processo é conhecido como ligação.

O compilador mais utilizado no Linux é a Coleção de Compiladores GNU – GCC. Ele compila códigos C ANSI, bem como C++, Java e Fortran. O GCC suporta vários níveis de checagem de erros nos códigos-fonte, produz informações de debug e pode ainda otimizar o arquivo objeto produzido.

A compilação envolve até quatro estágios: pré-processamento, compilação propriamente dita, assembly e ligação, sempre nesta ordem.

O GCC é capaz de pré-processar e compilar vários arquivos em um ou mais arquivos em assembler.

O arquivo de assembler gera um ou mais arquivos chamados de objeto e estes são ligados às bibliotecas (linking) para tornarem um arquivo executável.

fig001 Compilando programas no Linux com o GCC

PRÉ-PROCESSAMENTO

O pré-processamento é responsável por expandir as macros e incluir os arquivos de cabeçalho no arquivo fonte. O resultado é um arquivo que contém o código fonte expandido.

COMPILAÇÃO

O próximo estágio chamado de “compilação propriamente dita” é responsável por traduzir o código fonte pré-processado em linguagem assembly (linguagem de máquina) para um processador específico.

ASSEMBLER

O estágio seguinte é chamado de assembler. Nesta etapa o GCC converte o código de máquina de um processador específico em um arquivo objeto.

Se neste código existirem chamadas externas de funções, o gcc deixa seus endereços indefinidos para serem preenchidos posteriormente pelo estágio de ligação.

LINKER

O último estágio chamado de ligação, ou linker, é responsável por ligar os arquivos objeto para criar um arquivo executável.

Ele faz isto preenchendo os endereços das funções indefinidas nos arquivos objeto com os endereços das bibliotecas externas do sistema operacional.

Isto é necessário porque os arquivos executáveis precisam de muitas funções externas do sistema e de bibliotecas do C para serem executados.

As bibliotecas podem ser ligadas ao executável preenchendo os endereços das bibliotecas nas chamadas externas ou de forma estática quando as funções das bibliotecas são copiadas para o executável.

No primeiro caso, o programa utilizará bibliotecas de forma compartilhada e ficará dependente delas para funcionar.

Este esquema economiza recursos, pois, uma biblioteca utilizada por muitos programas precisa ser carregada somente uma vez na memória. O tamanho do executável será pequeno.

Porém, se a biblioteca instalada for de uma versão diferente da que o executável necessita, o programa não será executado até que a versão apropriada da biblioteca seja instalada.

No segundo caso, o programa é independente, uma vez que as funções de que necessita estão no seu código.

Este esquema permite que quando houver uma mudança de versão de biblioteca o programa não será afetado. A desvantagem será o tamanho do executável e necessidades de mais recursos.

O GCC – GNU Compiler Collection

O GNU Compiler Collection – coleção de compiladores GNU – são compiladores completos da linguagem C ANSI com suporte para o K&R C, C++, Objective C, Java, e Fortran. O GCC oferece também diferentes níveis de checagem de erros de código-fonte e informações para depuração e otimizações do programa objeto.

Ele também suporta a moderna plataforma de processadores Intel IA-64. A versão 7.0 inclui novas APIs e bibliotecas C++. O manual também foi substancialmente reescrito e melhorado.

Pré-Processamento

O pré-processamento é responsável por expandir as macros e incluir os arquivos de cabeçalho no arquivo fonte. O resultado é um arquivo que contém o código fonte expandido.

O pré-processamento do simples exemplo abaixo irá gerar um arquivo em C com mais de 800 linhas de código expandido.

Veja como fica este simples programa estilo Hello World que Imprime Certificação Linux na tela:

#include <stdio.h>

int main()

{

        printf(“Certificação Linux!\n”);

};

A opção “-E” do GCC diz ao compilador para fazer apenas o pré-processamento do código fonte.

# gcc –E teste.c –o teste.i

Veja que o arquivo preprocessado teste.i gerou 841 linhas de código pré-processado:

# cat teste.i | wc

    841    2074   16879

Compilação no GCC

O próximo estágio chamado de “compilação propriamente dita” é responsável por traduzir o código-fonte pré-processado em linguagem assembly (linguagem de máquina) para um processador específico.

Para que você veja o resultado do estágio de compilação, a opção “-S” (maiúsculo) do GCC gera o código de máquina para um processador específico.

O código abaixo é o exemplo acima já pré-processado que foi compilado em assembly para o processador Intel Xeon:

# gcc –S teste.i  

# cat teste.s

.file “teste.c”

.section .rodata

.LC0:

.string “Certifica\303\247\303\243o Linux!”

.text

.globl main

.type main, @function

main:

.LFB0:

.cfi_startproc

pushq %rbp

.cfi_def_cfa_offset 16

.cfi_offset 6, -16

movq %rsp, %rbp

.cfi_def_cfa_register 6

movl $.LC0, %edi

call puts

popq %rbp

.cfi_def_cfa 7, 8

ret

.cfi_endproc

.LFE0:

.size main, .-main

.ident “GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)”

.section .note.GNU-stack,””,@progbits

Assembler do Código no gcc

O estágio seguinte é chamado de assembler. Nesta etapa o GCC converte o código de máquina de um processador específico em um arquivo objeto. Se neste código existirem chamadas externas de funções, o gcc deixa seus endereços indefinidos para serem preenchidos posteriormente pelo estágio de ligação.

O seguinte comando irá fazer a compilação do código assembly do exemplo anterior para o código de máquina:

# as teste.s -o teste.o

O comando as é um compilador assembler disponível no pacote GNU GCC.

O resultado será um arquivo “teste.o” que contém instruções de máquina do exemplo com a referência indefinida para a função externa printf.

A linha “call printf” do código assembly informa que esta função está definida em uma biblioteca e deverá ser chamada durante o processamento.

Linker do código com as bibliotecas

O último estágio chamado de ligação, ou linker,  é responsável por ligar os arquivos objeto para criar um arquivo executável.

Ele faz isto preenchendo os endereços das funções indefinidas nos arquivos objeto com os endereços das bibliotecas externas do sistema operacional.

Isto é necessário porque os arquivos executáveis precisam de muitas funções externas do sistema e de bibliotecas do C para serem executados.

As bibliotecas podem ser ligadas ao executável preenchendo os endereços das bibliotecas nas chamadas externas ou de forma estática quando as funções das bibliotecas são copiadas para o executável.

No primeiro caso, o programa utilizará bibliotecas de forma compartilhada e ficará dependente delas para funcionar.

Este esquema economiza recursos, pois uma biblioteca utilizada por muitos programas precisa ser carregada somente uma vez na memória. O tamanho do executável será pequeno.

No segundo caso, o programa é independente, uma vez que as funções que necessita estão no seu código.

Este esquema permite que quando houver uma mudança de versão de biblioteca o programa não será afetado. A desvantagem será o tamanho do executável e necessidades de mais recursos.

Internamente a etapa de ligação é bem complexa, mas o GCC faz isto de forma transparente através do comando:

# gcc teste.o –o teste

O resultado será um executável chamado teste.

# ./teste

Certificação Linux!

Este programa foi ligado às bibliotecas de forma compartilhada. O comando ldd informa quais são as bibliotecas ligadas ao executável:

# ldd teste

linux-vdso.so.1 =>  (0x00007ffd69cff000)

libc.so.6 => /lib64/libc.so.6 (0x00007f8ee7991000)

/lib64/ld-linux-x86-64.so.2 (0x00007f8ee7d5e000)

As bibliotecas libc.so.6 e ld-linux.so.2 são referenciadas dinamicamente pelo programa teste.

A opção “-static” do compilador copia as funções externas das bibliotecas para o executável:

# gcc –static teste.c –o teste1

O comando ldd informará que o executável teste1 não tem ligações dinâmicas:

# ldd teste1

not a dynamic executable

A diferença no tamanho dos executáveis é muito grande. O programa teste que utiliza ligações dinâmicas tem 8.279 bytes.

O teste1 tem 1.884.604 bytes. Portanto, somente utilize a cópia das bibliotecas para o executável se for estritamente necessário.

Aprenda muito mais sobre Linux em nosso curso online. Você pode fazer a matrícula aqui com trial de 7 dias grátis. Se você já tem uma conta, pode acessar aqui.

Gostou? Compartilhe 🙂

Open chat