Você está na página 1de 235

21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).

html

Compilação
G e C ++
Um guia de engenharia para compilar, Knkng e
bibliotecas usando C e C ++.
Milan Stevanovic

Apress
Para sua conveniência, a Apress colocou parte do material anterior após o índice. Use os links de Favoritos e
Conteúdo para acessá-los.

Apress
amigos de

Resumo do conteúdo
Sobre o autor............................................... .................................................. ............... xv

Sobre os revisores técnicos .............................................. .......................................... xvii

Agradecimentos ................................................. .................................................. .......... xix

Introdução................................................. .................................................. .................... xxi

■ Capítulo 1: Noções básicas do sistema operacional multitarefa .......................................... ......................................... 1

■ Capítulo 2: Estágios Simples de Vida do Programa ......................................... ............................ 9


file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 1/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

■ Capítulo 3: Estágios de execução do programa ........................................... .................................. 43

■ Capítulo 4: O Impacto do Conceito de Reutilização ........................................ ............................. 53

■ Capítulo 5: Trabalhando com bibliotecas estáticas ......................................... ............................... 75

■ Capítulo 6: Projetando Bibliotecas Dinâmicas: Fundamentos ......................................... .................... 81

■ Capítulo 7: Localizando as Bibliotecas .......................................... ........................................ 115

■ Capítulo 8: Projetando Bibliotecas Dinâmicas: Tópicos Avançados ....................................... ... 137

■ Capítulo 9: Manipulando Símbolos Duplicados ao Vincular em Bibliotecas Dinâmicas ............... 155

■ Capítulo 10: Controle de versão de bibliotecas dinâmicas .......................................... .......................... 187

■ Capítulo 11: Bibliotecas Dinâmicas: Tópicos Diversos ......................................... ......... 233

■ Capítulo 12: Linux Toolbox ........................................... .................................................. 243

■ Capítulo 13: Linux How To's .......................................... ................................................. 277

■ Capítulo 14: Caixa de ferramentas do Windows ........................................... ............................................ 291

Índice................................................. .................................................. .............................. 309

Introdução
Levei algum tempo para me dar conta da incrível analogia que existe entre a arte culinária e a arte da programação de computadores.

Provavelmente, a comparação mais óbvia que vem à mente é que tanto o especialista em culinária quanto o programador têm objetivos finais semelhantes:
alimentar-se. Para um chef, é o ser humano, para o qual muitas matérias-primas são utilizadas para fornecer nutrientes comestíveis e também prazer
gastronômico, enquanto para o programador é o microprocessador, para o qual uma série de procedimentos diferentes são usados ​para fornecer o código isso não
só precisa produzir algumas ações significativas, mas também precisa ser executado da forma ideal.

Por mais que este ponto de comparação introdutório possa parecer um pouco rebuscado ou até infantil, os pontos de comparação subsequentes são algo que
considero muito mais aplicável e convincente.

As receitas e instruções para preparar pratos de todos os tipos são abundantes e onipresentes. Quase todas as revistas populares têm uma seção de culinária
dedicada a todos os tipos de alimentos e todos os tipos de cenários de preparação de alimentos, desde receitas rápidas e fáceis / de última hora até receitas
realmente elaboradas, desde aquelas com foco em tabelas nutricionais de ingredientes para aqueles que se concentram na interação delicada entre ingredientes
extraordinários e difíceis de encontrar.

No entanto, no próximo nível de especialização na arte culinária, a disponibilidade de recursos cai exponencialmente. As receitas e instruções para o
funcionamento do negócio alimentar (produção por volume, funcionamento do restaurante ou negócio de catering), planejamento das quantidades e ritmo de
entrega para o processo de preparação de alimentos, técnicas e estratégias para otimizar a eficiência da entrega de alimentos, técnicas para escolher o certo
ingredientes, minimizando a deterioração dos ingredientes armazenados - esse tipo de informação é substancialmente mais difícil de encontrar. Com razão, já que
esses tipos de tópicos delineiam a diferença entre a culinária amadora e o negócio de alimentação profissional.

A situação com a programação é bastante semelhante.

As informações sobre uma grande variedade de linguagens de programação estão prontamente disponíveis, por meio de milhares de livros, revistas, artigos,
fóruns da web e blogs, desde o nível de iniciante absoluto até as dicas de "preparação para a entrevista de programação do Google".

Esses tipos de tópicos, entretanto, cobrem apenas cerca de metade das habilidades exigidas pelo profissional de software. Logo após a gratificação imediata de ver
o programa que criamos realmente executando (e fazendo certo) vem o próximo nível de questões importantes: como arquitetar o código para permitir
modificações posteriores fáceis, como extrair partes reutilizáveis ​da funcionalidade para uso futuro , como permitir um ajuste suave para diferentes ambientes
(começando com diferentes idiomas e alfabetos humanos, até a execução em diferentes sistemas operacionais).

Em comparação com os outros tópicos de programação, esses tipos de tópicos raramente são discutidos e até hoje pertencem à forma de "arte negra" reservada
para alguns raros espécimes de profissionais de ciência da computação (principalmente arquitetos de software e engenheiros de construção) também quanto ao
domínio das classes de nível universitário relacionadas ao projeto do compilador / vinculador.

Um fator particular - a ascensão do Linux e a proliferação de suas práticas de programação em uma infinidade de ambientes de design - trouxe um grande ímpeto
para um programador prestar atenção a esses tópicos. Ao contrário dos colegas que escrevem software para plataformas "bem protegidas" (Windows e Mac, em
que a plataforma, IDEs e SDKs dispensam o programador de pensar sobre certos aspectos de programação), a rotina diária de um programador Linux é combinar
o código proveniente de variedade de fontes, práticas de codificação e em formas que requerem compreensão imediata do funcionamento interno do compilador,
do vinculador, do mecanismo de carregamento do programa e, portanto, dos detalhes do projeto e uso dos vários tipos de bibliotecas.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 2/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O objetivo deste livro é discutir uma variedade de informações valiosas reunidas em uma base de conhecimento esparsa e escassa e validá-la por meio de uma
série de experimentos simples cuidadosamente adaptados. É importante ressaltar que o autor não tem formação em ciência da computação. Sua formação sobre o
tema veio como resultado de sua imersão como engenheiro elétrico na fronteira tecnológica da indústria de multimídia do Vale do Silício na época da revolução
digital, do final dos anos 90 até os dias atuais. Esperançosamente, esta coleção de tópicos será útil para um público mais amplo.

Público (quem precisa deste livro e por quê)


O efeito colateral de eu mesmo ser um consultor prático de design de software (muito ocupado, devo dizer com orgulho) é que regularmente entro em contato
com uma variedade extraordinária de perfis profissionais, níveis de maturidade e realização. A amostra estatística sólida da população de programadores
(principalmente do Vale do Silício) que conheço mudando de ambiente de escritório várias vezes durante uma semana de trabalho me ajudou a ter uma visão
bastante boa dos perfis de quem pode se beneficiar da leitura deste livro. Então, aqui estão eles.

O primeiro grupo é formado por programadores C / C ++ vindos de várias áreas de engenharia (EE, mecânica, robótica e controle de sistemas, aeroespacial, física,
química, etc.) que lidam com programação diariamente. A falta de educação formal e mais focada em ciência da computação, bem como a falta de literatura não
teórica sobre o assunto, tornam este livro um recurso precioso para este grupo específico.

O segundo grupo é formado por programadores de nível júnior com formação em ciência da computação. Este livro pode ajudar a concretizar o corpo de seu
conhecimento existente adquirido em cursos básicos e focalizá-lo no nível operacional. Manter os resumos rápidos dos Capítulos 12-14 em algum lugar acessível
pode valer a pena, mesmo para os perfis mais experientes desse grupo em particular.

O terceiro grupo é formado por pessoas com interesse no domínio da integração e personalização do sistema operacional. Compreender o mundo dos binários e
os detalhes de seu funcionamento interno pode ajudar a "limpar o ar" tremendamente.

Sobre o livro
Originalmente, eu não tinha planos de escrever este livro em particular. Nem mesmo um livro no domínio da ciência da computação. (Processamento de sinais?
Arte de programar? Talvez ... mas um livro de ciência da computação? Naaah ...)

A única razão pela qual este livro surgiu é o fato de que, ao longo de minha carreira profissional, tive que lidar com certas questões, que na época achei que outra
pessoa deveria cuidar.

Era uma vez, eu fiz a escolha de seguir o caminho profissional de um tipo de assassino de alta tecnologia, o cara que é chamado pelos cidadãos de comunidades de
alta tecnologia calmas e decentes para livrá-los do terror da desagradável multimídia que se aproxima. problemas de design relacionados, causando estragos,
juntamente com uma gangue de bugs horríveis. Essa escolha de carreira praticamente não deixava espaço para exclusividade nas preferências pessoais
tipicamente encontradas pelas crianças que comeriam frango, mas não ervilhas. O sinistro "ou então" está sempre lá. Embora FFTs, wavelets, transformadas Z,
filtros FIR e IIR, oitavas, semitons, interpolações e decimações sejam naturalmente minha escolha preferida de tarefas (junto com uma quantidade decente de
programação C / C ++), eu tive que lidar com problemas que iriam não tem sido minha preferência pessoal. Alguém tinha que fazer isso.

Surpreendentemente, ao procurar respostas diretas para perguntas muito simples e pontuais, tudo que pude encontrar foi uma variedade dispersa de artigos da
web, principalmente sobre os detalhes de alto nível. Eu estava pacientemente coletando as "peças do quebra-cabeça" e consegui não apenas completar as tarefas de
design em mãos, mas também aprender ao longo do caminho.

Um belo dia, chegou a hora de consolidar minhas notas de design (algo que eu faço regularmente para a variedade de tópicos com os quais lido). Desta vez,
porém, quando o esforço foi concluído, tudo parecia. . . Nós vamos . . . como um livro. Este livro.

De qualquer forma . . .

Dado o estado atual do mercado de trabalho, estou profundamente convencido de que (desde meados da primeira década do século 21) conhecer as
complexidades da linguagem C / C ++ perfeitamente - e mesmo algoritmos, estruturas de dados e padrões de design - simplesmente não é o suficiente.

Na era do código aberto, a realidade da vida do programador profissional se torna cada vez menos sobre "saber como escrever o programa" e, em vez disso,
substancialmente mais sobre "saber como integrar os corpos de código existentes". Isso pressupõe não apenas ser capaz de ler o código de outra pessoa (escrito em
uma variedade de estilos e práticas de codificação), mas também saber a melhor maneira de integrar o código aos pacotes existentes que estão disponíveis
principalmente na forma binária (bibliotecas) acompanhados pela exportação arquivos de cabeçalho.

Esperançosamente, este livro irá educar (aqueles que podem precisar), bem como fornecer uma referência rápida para a maioria das tarefas relacionadas à análise
dos binários C / C ++.

Por que estou ilustrando os conceitos principalmente no Linux?


Não é nada pessoal.

Na verdade, quem me conhece sabe o quanto (na época em que era minha plataforma de design preferida) eu gostava e respeitava o ambiente de design do
Windows - o fato de ser bem documentado, bem suportado e até que ponto os componentes certificados funcionaram de acordo com a especificação. Uma série de
aplicativos de nível profissional que projetei (GraphEdit para Windows Mobile for Palm, Inc., projetado do zero e repleto de recursos extras, sendo provavelmente
o mais complexo, seguido por vários formatos de mídia / aplicativos de análise de DSP) lideraram em relação à compreensão completa e, em última análise, ao
respeito pela tecnologia Windows da época.

Nesse ínterim, a era do Linux chegou e isso é um fato da vida. O Linux está em todo lugar e há poucas chances de um programador ser capaz de ignorá-lo e evitá-
lo.

O ambiente de design de software Linux provou ser aberto, transparente, simples e direto ao ponto. O controle sobre estágios de programação individuais, a
disponibilidade de documentação bem escrita e ainda mais "línguas ao vivo" na Web tornam o trabalho com a cadeia de ferramentas GNU um prazer.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 3/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O fato de que a experiência de programação Linux C / C ++ é diretamente aplicável à programação de baixo nível no MacOS contribuiu para a decisão final de
escolher o Linux / GNU como o ambiente de design primário coberto por este livro.

Mas espere! Linux e GNU não são exatamente a mesma coisa !!!
Sim eu conheço. Linux é um kernel, enquanto GNU cobre muitas coisas acima dele. Apesar do fato de que o compilador GNU pode ser usado em outros sistemas
operacionais (por exemplo, MinGW no Windows), na maioria das vezes o GNU e o Linux andam de mãos dadas. Para simplificar toda a história e chegar mais
perto de como o programador médio percebe a cena da programação, e especialmente em contraste com o lado do Windows, irei me referir coletivamente ao GNU
+ Linux simplesmente como "Linux".

A Visão Geral do Livro


Os capítulos 2 a 5 estão, principalmente, preparando o terreno para fazer o ponto mais tarde. As pessoas com formação formal em ciência da computação
provavelmente não precisam ler esses capítulos com atenção concentrada (felizmente, esses capítulos não são tão longos). Na verdade, qualquer livro de ciência da
computação decente pode fornecer a mesma estrutura com muito mais detalhes. Meu favorito pessoal é o livro Computer Systems - A Programmer's Perspective de
Bryant e O'Hallaron , que recomendo enfaticamente como uma fonte de informações bem organizadas relacionadas ao assunto mais amplo.

Os capítulos 6-12 fornecem uma visão essencial do tópico. Investi muito esforço para ser conciso e tentar combinar palavras e imagens de objetos familiares da
vida real para explicar os conceitos mais vitais cuja compreensão é fundamental. Para aqueles sem formação formal em ciência da computação, a leitura e a
compreensão desses capítulos são altamente recomendadas. Na verdade, esses capítulos representam a essência de toda a história.

Os capítulos 13-15 são uma espécie de folha de referência prática, uma forma de lembretes rápidos e legais. O conjunto de ferramentas específico da plataforma
para a análise de arquivos binários é discutido, seguido pela referência cruzada "Como fazer" parte que contém receitas rápidas de como realizar certas tarefas
isoladas.

O Apêndice A contém os detalhes técnicos dos conceitos mencionados no Capítulo 8. O Apêndice A está disponível online apenas em www .apress .com . Para
obter informações detalhadas sobre como localizá-lo, vá para www.apress.com/source-code/ . Depois de compreender os conceitos do Capítulo 8, pode ser
muito útil tentar seguir as explicações práticas de como e por que certas coisas realmente funcionam. Espero que um pequeno exercício possa servir de
treinamento prático para o leitor ávido.

CAPÍTULO 1

Noções básicas do sistema operacional


multitarefa
O objetivo final de toda a arte relacionada à construção de executáveis ​é estabelecer o máximo de controle possível sobre o processo de execução do programa.
Para realmente compreender o propósito e o significado de certas partes da estrutura executável, é de extrema importância obter o entendimento completo do que
acontece durante a execução de um programa, como a interação entre o kernel do sistema operacional e as informações incorporadas dentro do executável
desempenham os papéis mais significativos. Isso é particularmente verdadeiro nas fases iniciais de execução, quando é muito cedo para impactos de tempo de
execução (como configurações do usuário, vários eventos de tempo de execução, etc.) que normalmente acontecem.

O primeiro passo obrigatório nessa direção é entender o entorno em que os programas operam. O objetivo deste capítulo é fornecer em esboços gerais os detalhes
mais poderosos da funcionalidade de um sistema operacional multitarefa moderno.

Os sistemas operacionais multitarefa modernos são, em muitos aspectos, muito próximos uns dos outros em termos de como a funcionalidade mais importante é
implementada. Como resultado, um esforço consciente será feito para ilustrar os conceitos de maneiras independentes de plataforma primeiro. Além disso, será
dada atenção aos meandros das soluções específicas de plataforma (onipresentes Linux e formato ELF vs. Windows) e estes serão analisados ​em grande detalhe.

Abstrações úteis
Mudanças no domínio da tecnologia de computação tendem a acontecer em um ritmo muito rápido. A tecnologia de circuitos integrados fornece componentes
que não são apenas ricos em variedade (óptico, magnético, semicondutor), mas também são continuamente atualizados em termos de capacidades. De acordo com
a Lei de Moore, o número de transistores em circuitos integrados dobra aproximadamente a cada dois anos. A potência de processamento, que está fortemente
associada ao número de transistores disponíveis, tende a seguir uma tendência semelhante.

Como foi descoberto muito cedo, a única maneira de se adaptar substancialmente ao ritmo da mudança é definir os objetivos gerais e a arquitetura dos sistemas de
computador de uma forma abstrata / generalizada, no nível acima dos detalhes das implementações em constante mudança. A parte crucial desse esforço é
formular a abstração de tal forma que qualquer nova implementação real se encaixe na definição essencial, deixando de lado os detalhes da implementação real
como relativamente sem importância. A arquitetura geral do computador pode ser representada como um conjunto estruturado de abstrações, conforme mostrado
na Figura 1-1.

Máquina virtual

<-
-►
Processo
Memória Virtual r
v v

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 4/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Conjunto de instruções
v w
Sistema operacional
Byte Stream
CPU

r Memória principal

Dispositivos I / O

Figura 1-1. Abstrações de arquitetura de computador

A abstração no nível mais baixo lida com a vasta variedade de dispositivos de E / S (mouse, teclado, joystick, trackball, caneta de luz, scanner, leitores de código de
barras, impressora, plotter, câmera digital, câmera da web), representando-os com sua quintessência propriedade do fluxo de bytes. Na verdade,
independentemente das diferenças entre os vários objetivos, implementações e capacidades dos dispositivos, são os fluxos de bytes que esses dispositivos
produzem ou recebem (ou ambos) que são os detalhes de extrema importância do ponto de vista do projeto de sistema de computador.

O próximo nível de abstração, o conceito de memória virtual, que representa a ampla variedade de recursos de memória tipicamente encontrados no sistema, é o
assunto de extraordinária importância para o tópico principal deste livro. A maneira como essa abstração em particular realmente representa a variedade de
dispositivos de memória física não apenas impacta o design do hardware e software reais, mas também estabelece uma base na qual o design do compilador,
linker e carregador depende.

O conjunto de instruções que abstrai a CPU física é a abstração do próximo nível. Compreender os recursos do conjunto de instruções e a promessa do poder de
processamento que ele carrega é definitivamente o tópico de interesse do programador mestre. Do ponto de vista de nosso tópico principal, esse nível de abstração
não é de importância primária e não será discutido em grandes detalhes.

As complexidades do sistema operacional representam o nível final de abstração. Certos aspectos do design do sistema operacional (mais notavelmente,
multitarefa) têm um impacto decisivo na arquitetura do software em geral. Os cenários em que as várias partes tentam acessar o recurso compartilhado requerem
uma implementação cuidadosa em que a duplicação desnecessária de código seria evitada - o fator que levou diretamente ao projeto de bibliotecas
compartilhadas.

Vamos fazer um pequeno desvio em nossa jornada de análise dos meandros do sistema de computador geral e, em vez disso, prestar atenção especial às questões
importantes relacionadas ao uso da memória.

Hierarquia de memória e estratégia de cache


Existem vários fatos interessantes da vida relacionados à memória em sistemas de computador:

• A necessidade de memória parece insaciável. Sempre há necessidade de muito mais do que está disponível atualmente. Cada salto quântico no fornecimento de
quantidades maiores (de memória mais rápida) é imediatamente atendido com a demanda há muito aguardada das tecnologias que estiveram conceitualmente
prontas por algum tempo, e cuja realização foi atrasada até o dia em que a memória física tornou-se disponível em quantidades suficientes .

• A tecnologia parece ser muito mais eficiente em superar as barreiras de desempenho dos processadores do que a memória. Esse fenômeno é normalmente
conhecido como "lacuna entre processador e memória".

• A velocidade de acesso à memória é inversamente proporcional à capacidade de armazenamento. Os tempos de acesso dos dispositivos de armazenamento de
maior capacidade são normalmente várias ordens de magnitude maiores do que os dos dispositivos de memória de menor capacidade.

Agora, vamos dar uma olhada rápida no sistema do ponto de vista do programador / designer / engenheiro. Idealmente, o sistema precisa acessar toda a memória
disponível o mais rápido possível - o que sabemos que nunca será possível alcançar. A próxima pergunta imediata é: há algo que possamos fazer a respeito?

O detalhe que traz um grande alívio é o fato de o sistema não usar toda a memória o tempo todo, mas apenas alguma memória por algum tempo. Nesse caso, tudo
o que realmente precisamos fazer é reservar a memória mais rápida para executar a execução imediata e usar os dispositivos de memória mais lentos para o
código / dados que não são executados imediatamente. Enquanto a CPU busca da memória rápida as instruções agendadas para a execução imediata, o hardware
tenta adivinhar qual parte do programa será executada a seguir e fornece essa parte do código para a memória mais lenta para aguardar a execução. Pouco antes
de chegar a hora de executar as instruções armazenadas na memória mais lenta, elas são transferidas para a memória mais rápida. Este princípio é conhecido
como cache.

A analogia da vida real com o armazenamento em cache é algo que uma família comum faz com seu suprimento de comida. A menos que vivamos em lugares
muito isolados, normalmente não compramos e levamos para casa todos os alimentos necessários durante um ano inteiro. Em vez disso, mantemos
principalmente um armazenamento moderadamente grande em casa (geladeira, despensa, prateleiras), no qual mantemos um suprimento de comida suficiente
para uma ou duas semanas. Quando percebemos que essas pequenas reservas estão prestes a se esgotar, fazemos uma viagem ao armazém e compramos apenas a
quantidade de comida necessária para encher o estoque local.

O fato de a execução de um programa ser tipicamente afetada por uma série de fatores externos (as configurações do usuário sendo apenas um deles) torna o
mecanismo de armazenamento em cache uma forma de adivinhação ou um jogo de acerto ou erro. Quanto mais previsível for o fluxo de execução do programa
(medido pela falta de saltos, interrupções, etc.), mais suave o mecanismo de armazenamento em cache funcionará. Por outro lado, sempre que um programa
encontra a mudança de fluxo, as instruções que foram anteriormente acumuladas acabam sendo descartadas por não serem mais necessárias, e uma parte nova e
mais apropriada do programa precisa ser fornecida da memória mais lenta.

A implementação de um princípio de armazenamento em cache é onipresente e se estende por vários níveis de memória, conforme ilustrado na Figura 1-2.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 5/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 1-2. Princípio de hierarquia de cache de memória

Memória virtual
A abordagem genérica do cache de memória obtém a implementação real no próximo nível de arquitetura, no qual o programa em execução é representado pela
abstração chamada processo.

Os sistemas operacionais multitarefa modernos são projetados com a intenção de permitir que um ou mais usuários executem vários programas simultaneamente.
Não é incomum para o usuário médio ter vários aplicativos (por exemplo, navegador da web, editor, reprodutor de música, calendário) rodando
simultaneamente.

A desproporção entre as necessidades de memória e a limitada disponibilidade de memória foi resolvida pelo conceito de memória virtual, que pode ser
delineada pelo seguinte conjunto de diretrizes:

• As permissões de memória do programa são fixas, iguais para todos os programas e de natureza declarativa.

N
Os sistemas operacionais normalmente permitem que o programa (processo) use 2 bytes de memória, onde N hoje é 32 ou 64. Este valor é fixo e é independente
da disponibilidade de memória física no sistema

• A quantidade de memória física pode variar. Normalmente, a memória está disponível em quantidades várias vezes menores do que o espaço de endereço do
processo declarado. Não é incomum que a quantidade de memória física disponível para a execução de programas seja um número ímpar.

• A memória física em tempo de execução é dividida em pequenos fragmentos (páginas), com cada página sendo usada para programas rodando
simultaneamente.

• O layout completo da memória do programa em execução é mantido na memória lenta (disco rígido). Apenas as partes da memória (código e dados) que estão
prestes a serem executadas são carregadas na página da memória física.

A implementação real do conceito de memória virtual requer a interação de vários recursos do sistema, como hardware (exceções de hardware, tradução de
endereço de hardware), disco rígido (arquivos de troca), bem como o software de sistema operacional de nível mais baixo (kernel). O conceito de memória virtual
é ilustrado na Figura 1-3.

Figura 1-3. Implementação do conceito de memória virtual

Processo A

Processo B

Memória física

Endereçamento Virtual
O conceito de endereçamento virtual está na base da implementação da memória virtual e, de muitas maneiras, impacta significativamente o design de
compiladores e vinculadores.

Como regra geral, o designer do programa está completamente livre de se preocupar com a faixa de endereçamento que seu programa ocupará em tempo de
execução (pelo menos isso é verdade para a maioria dos aplicativos do espaço do usuário; os módulos do kernel são um tanto excepcionais neste sentido). Em vez
N
disso, o modelo de programação assume que a faixa de endereço está entre 0 e 2 (faixa de endereço virtual) e é a mesma para todos os programas.

A decisão de conceder um esquema de endereçamento simples e unificado para todos os programas tem um enorme impacto positivo no processo de
desenvolvimento de código. A seguir estão os benefícios:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 6/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• A vinculação é simplificada.

• O carregamento é simplificado.

• O compartilhamento de processos em tempo de execução torna-se disponível.

• A alocação de memória é simplificada.

A colocação real do tempo de execução da memória do programa em um intervalo de endereço concreto é realizada pelo sistema operacional por meio do
mecanismo de tradução de endereço. Sua implementação é realizada pelo módulo de hardware denominado unidade de gerenciamento de memória (MMU), que
não requer nenhum envolvimento do próprio programa.

A Figura 1-4 compara o mecanismo de endereçamento virtual com um esquema de endereçamento físico simples e simples (usado até hoje no domínio de sistemas
de microcontroladores simples).

Figura 1-4. Endereçamento físico vs. virtual

Esquema de Divisão de Memória de Processo


A seção anterior explicou por que é possível fornecer o mapa de memória idêntico ao projetista de (quase) qualquer programa. O tópico desta seção é discutir os
detalhes da organização interna do mapa de memória do processo. Presume-se que o endereço do programa (conforme visualizado pelo programador) reside no
N
intervalo de endereço entre 0 e 2 , sendo N 32 ou 64.

Vários sistemas operacionais multitarefa / multiusuário especificam diferentes layouts de mapa de memória. Em particular, o mapa de memória virtual do
processo Linux segue o esquema de mapeamento mostrado na Figura 1-5.

funcionalidade do sistema operacional para controlar a execução do programa

variáveis ​ambientais

argv (lista de argumentos de linha de comando)

argc (número de argumentos da linha de comando)

MEMORIA COMPARTILHADA

TEXTO

variáveis ​locais para a função main ()

variáveis ​locais para outra função

funções de bibliotecas dinâmicas vinculadas

o
HEAP

dados inicializados

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 7/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
dados não inicializados

funções de bibliotecas estáticas vinculadas

outras funções do programa

função principal (main.o)

rotinas de inicialização (crtO.o)

0x00000000

Figura 1-5. Layout do mapa de memória do processo Linux

Independentemente das peculiaridades do esquema de divisão de memória do processo de uma determinada plataforma, as seguintes seções do mapa de
memória devem ser sempre suportadas:

• Seção de código contendo as instruções de código de máquina para a CPU executar ( seção de texto )

• Seções de dados contendo os dados nos quais a CPU irá operar. Normalmente, seções separadas são mantidas para dados inicializados ( seção .data ), para
dados não inicializados ( seção .bss ), bem como para dados constantes ( seção .rdata )

• A pilha na qual a alocação de memória dinâmica é executada

• A pilha, que é usada para fornecer espaço independente para funções

• A parte superior pertencente ao kernel onde (entre outras coisas) as variáveis ​de ambiente específicas do processo são armazenadas

Uma discussão lindamente detalhada sobre este tópico específico escrita por Gustavo Duarte pode ser encontrada em
http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory .

As funções de binários, compilador, vinculador e carregador


A seção anterior lançou alguma luz sobre o mapa de memória do processo em execução. A questão importante que vem a seguir é como o mapa de memória do
processo em execução é criado no tempo de execução. Esta seção fornecerá uma visão elementar desse lado específico da história.

Em um esboço,

• Os binários do programa carregam os detalhes do blueprint do mapa de memória do processo em execução.

• O esqueleto de um arquivo binário é criado pelo vinculador. Para completar sua tarefa, o vinculador combina os arquivos binários criados pelo compilador para
preencher a variedade de seções do mapa de memória (código, dados, etc.).

• A tarefa de criação inicial do mapa de memória do processo é executada pelo utilitário do sistema chamado carregador de programa. No sentido mais simples, o
carregador abre o arquivo executável binário, lê as informações relacionadas às seções e preenche a estrutura do mapa de memória do processo.

Essa divisão de funções pertence a todos os sistemas operacionais modernos.

Esteja ciente de que esta descrição mais simples está longe de fornecer o quadro completo e completo. Deve ser visto como uma introdução suave às discussões
subsequentes, por meio das quais substancialmente mais detalhes sobre o tópico de binários e carregamento de processo serão transmitidos à medida que
avançamos no tópico.

Resumo
Este capítulo forneceu uma visão geral dos conceitos que mais afetam fundamentalmente o design de sistemas operacionais multitarefa modernos. Os conceitos
básicos de memória virtual e endereçamento virtual não afetam apenas a execução do programa (que será discutido em detalhes no próximo capítulo), mas
também impactam diretamente como os arquivos executáveis ​do programa são construídos (o que será explicado em detalhes posteriormente neste livro )

CAPÍTULO 2

Estágios simples para toda a vida do programa


No capítulo anterior, você obteve uma visão ampla dos aspectos da funcionalidade do sistema operacional multitarefa moderno que desempenha um papel
durante a execução do programa. A próxima pergunta natural que vem à mente do programador é o que fazer, como e por que para que a execução do programa
aconteça.

Assim como a vida útil de uma borboleta é determinada por seu estágio de lagarta, a vida útil de um programa é amplamente determinada pela estrutura interna
do binário, que o carregador do SO carrega, descompacta e coloca seu conteúdo na execução. Não deve ser uma grande surpresa que a maioria de nossas
discussões subsequentes serão dedicadas à arte de preparar um projeto e incorporá-lo adequadamente ao corpo do (s) arquivo (s) executável (s) binário (s).
Assumiremos que o programa foi escrito em C / C ++.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 8/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Para entender completamente a história toda, os detalhes do resto da vida do programa, o estágio de carregamento e execução, serão analisados ​detalhadamente.
Outras discussões serão focadas nas seguintes fases da vida do programa:

1 Criação do código-fonte
2 Compilando
3 Linking
4 Carregando
5 Executando

Verdade seja dita, este capítulo cobrirá muito mais detalhes sobre o estágio de compilação do que sobre os estágios subsequentes. A cobertura dos estágios
subsequentes (especialmente o estágio de ligação) só começa neste capítulo, no qual você verá apenas a proverbial "ponta do iceberg". Após a introdução mais
básica de ideias por trás do estágio de vinculação, o restante do livro tratará dos meandros da vinculação, bem como do carregamento e da execução do programa.

Premissas iniciais
Embora seja muito provável que uma grande porcentagem de leitores pertença à categoria de programadores avançados a especialistas, começarei com exemplos
iniciais bastante simples. As discussões neste capítulo serão pertinentes ao caso muito simples, mas muito ilustrativo. O projeto de demonstração consiste em dois
arquivos de origem simples, que serão primeiro compilados e, em seguida, vinculados. O código é escrito com a intenção de manter a complexidade de compilar e
vincular no nível mais simples possível.

Em particular, nenhum link de bibliotecas externas, particularmente o link dinâmico, estará ocorrendo neste exemplo de demonstração. A única exceção será a
vinculação com a biblioteca de tempo de execução C (que de qualquer forma é necessária para a grande maioria dos programas escritos em C). Sendo um
elemento tão comum na vida da execução do programa C, por uma questão de simplicidade, intencionalmente fecharei os olhos aos detalhes particulares da
ligação com a biblioteca de tempo de execução C e assumirei que o programa é criado de tal forma que todos o código da biblioteca de tempo de execução C é
inserido "automagicamente" no corpo do mapa de memória do programa.

Seguindo essa abordagem, ilustrarei os detalhes dos problemas essenciais da construção do programa de uma forma simples e limpa.

Escrita de Código
Dado que o principal tópico deste livro é o processo de construção do programa (ou seja, o que acontece depois que o código-fonte é escrito), não vou gastar muito
tempo no processo de criação do código-fonte.

Exceto em alguns casos raros quando o código-fonte é produzido por um script, presume-se que um usuário o faça digitando os caracteres ASCII em seu editor de
escolha em um esforço para produzir as instruções escritas que satisfaçam as regras de sintaxe do linguagem de programação de escolha (C / C ++ em nosso caso).
O editor de escolha pode variar desde o editor de texto ASCII mais simples possível até a ferramenta IDE mais avançada. Supondo que o leitor médio deste livro
seja um programador bastante experiente, não há realmente nada de especial a dizer sobre esse estágio do ciclo de vida do programa.

No entanto, existe uma prática de programação específica que impacta significativamente o rumo da história a partir deste ponto, e vale a pena prestar atenção
extra a ela. Para organizar melhor o código-fonte, os programadores normalmente seguem a prática de manter as várias partes funcionais do código em arquivos
separados, resultando em projetos geralmente compostos de muitos arquivos-fonte e de cabeçalho diferentes.

Essa prática de programação foi adotada muito cedo, desde a época dos ambientes de desenvolvimento feitos para os primeiros microprocessadores. Por ser uma
decisão de design muito sólida, ela tem sido praticada desde então, pois está comprovado que fornece uma organização sólida do código e torna as tarefas de
manutenção de código significativamente mais fáceis.

Esta prática de programação, sem dúvida útil, tem consequências de longo alcance. Como você verá em breve, praticá-lo leva a certa quantidade de
indeterminismo nos estágios subsequentes do processo de construção, cuja resolução requer algum pensamento cuidadoso.

Ilustração do conceito: projeto de demonstração

Para ilustrar melhor as complexidades do processo de compilação, bem como a experiência prática de aquecimento, um projeto de demonstração simples foi
fornecido. composto de, no máximo, um cabeçalho e dois arquivos de origem. No entanto, é extremamente importante para a compreensão do quadro mais
amplo. Os seguintes arquivos fazem parte do projeto:

• Arquivo fonte main.c, que contém a função main () .

• Arquivo de cabeçalho function.h, que declara as funções chamadas e os dados acessados ​pela função main () .

• Arquivo-fonte function.c, que contém as implementações do código-fonte das funções e instanciação dos dados referenciados pela função main () .

O ambiente de desenvolvimento usado para construir este projeto simples será baseado no compilador gcc rodando no Linux. As listagens 2-1 a 2-3 contêm o
código usado no projeto de demonstração.

Listagem2-1. function.h #pragma uma vez

#define FIRST_OPTION #ifdef FIRST_OPTION #define MULTIPLIER (3.0) #else

#define MULTIPLIER (2.0) #endif float add_and_multiply (float x, float y);

para fornecer um pouco ao leitor. O código é excepcionalmente simples; é cuidadosamente projetado para ilustrar os pontos de
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 9/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Listagem 2-2. function.c int nCompletionStatus = 0;

float add (float x, float y) {

float z = x + y; return z;

float add_and_multiply (float x, float y) {

float z = add (x, y); z1 = MULTIPLICADOR; return z;

Listagem 2-3. main.c

#include "function.h"

extern int nCompletionStatus = 0;

int main (int argc, char * argv []) {

float x = 1,0; float y = 5,0; float z;

z = add_and_multiply (x, y); nCompletionStatus = 1; return 0;

Compilando
Depois de escrever seu código-fonte, é hora de mergulhar no processo de construção do código, cuja primeira etapa obrigatória é o estágio de compilação. Antes
de entrar nos meandros da compilação, alguns termos introdutórios simples serão apresentados primeiro.

Definições introdutórias

Compilar em sentido amplo pode ser definido como o processo de transformar o código-fonte escrito em uma linguagem de programação em outra linguagem de
programação. O seguinte conjunto de fatos introdutórios é importante para sua compreensão geral do processo de compilação:

• O processo de compilação é executado pelo programa chamado compilador.

• A entrada para o compilador é uma unidade de tradução. Uma unidade de tradução típica é um arquivo de texto contendo o código-fonte.

• A saída da compilação é uma coleção de arquivos de objetos binários , um para cada uma das unidades de tradução de entrada.

• Para se tornarem adequados para execução, os arquivos-objeto precisam ser processados ​por meio de outro estágio de construção do programa, chamado de
vinculação.

A Figura 2-1 ilustra o conceito de compilação.

O
int function_sum (int x, int y)

retorno (x + y)

int main (int argc, char * argv [])

int x int y int z; = 5; = 3; sum.c

z = soma_função (x, y); return 0;

main.c

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 10/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

1011 1100101001001 11 o ° 1001001010010

o ° 1001001010010010
1010010101001010101 0101010111011011011 111

1001001010010010101 100 001 xyz.o


1001010010101001001 1101101101101101101 100

1001010101010001001 1001001001001001001 100 0001 soma.o

1001001001001001001 0100100101001001010 main.o

Figura 2-1. A fase de compilação

Definições Relacionadas

A seguinte variedade de casos de uso do compilador é normalmente encontrada:

• Compilação no sentido estrito denota o processo de tradução do código de uma linguagem de nível superior para o código de arquivos de produção de uma
linguagem de nível inferior (normalmente, assembler ou mesmo código de máquina).

• Se a compilação é realizada em uma plataforma (CPU / OS) para produzir código a ser executado em alguma outra plataforma (CPU / OS), é chamada de
compilação cruzada. A prática usual é usar alguns dos sistemas operacionais de desktop (Linux, Windows) para gerar o código para dispositivos embarcados ou
móveis.

• Descompilação (desmontagem) é o processo de conversão do código-fonte de uma linguagem de nível inferior para a linguagem de nível superior.

• Tradução de linguagem é o processo de transformar o código-fonte de uma linguagem de programação em outra linguagem de programação do mesmo nível e
complexidade.

• A reescrita de linguagem é o processo de reescrever as expressões de linguagem em uma forma mais adequada para certas tarefas (como otimização).

As fases da compilação

O processo de compilação não é monolítico por natureza. Na verdade, ele pode ser dividido em vários estágios

(pré-processamento, análise linguística, montagem, otimização, emissão de código), cujos detalhes serão

discutido a seguir.

Pré-processando
A primeira etapa padrão no processamento dos arquivos de origem é executá-los por meio de um programa especial de processamento de texto chamado pré -
processador, que executa uma ou mais das seguintes ações:

• Inclui os arquivos que contêm definições (arquivos de inclusão / cabeçalho) nos arquivos de origem, conforme especificado pela palavra-chave #include .

• Converte os valores especificados usando instruções #define em constantes.

• Converte as definições de macro em código na variedade de locais em que as macros são chamadas.

• Inclui ou exclui condicionalmente certas partes do código, com base na posição das diretivas #if, #elif e #endif .

A saída do pré-processador é o código C / C ++ em sua forma final, que será passado para a próxima etapa, a análise de sintaxe.

Exemplo de pré-processamento de projeto de demonstração

O compilador gcc fornece o modo em que apenas o estágio de pré-processamento é executado nos arquivos de origem de entrada:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 11/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
gcc -i <arquivo de entrada> -o <arquivo pré-processado de saída> .i

A menos que seja especificado de outra forma, a saída do pré-processador é o arquivo que tem o mesmo nome do arquivo de entrada e cuja extensão de arquivo é
.i. O resultado da execução do pré-processador no arquivo function.c é semelhante ao da Listagem 2-4.

Listagem 2-4. function.i

# 1 "function.c"

# 1 "# 1"

# 1 "function.h" 1

# 11 "function.h"

float add_and_multiply (float x, float y);

# 2 "function.c" 2

int nCompletionStatus = 0;

float add (float x, float y) {

float z = x + y; return z;

float add_and_multiply (float x, float y) {

float z = add (x, y); z * = MULTIPLICADOR; return z;

Uma saída de pré-processador mais compacta e significativa pode ser obtida se alguns sinalizadores extras forem passados ​para o gcc, como

gcc -E -P -i <arquivo de entrada> -o <arquivo de saída pré-processado> .i que resulta no arquivo pré-processado visto na Listagem 2-5.

Listagem 2-5. function.i (versão reduzida)

float add_and_multiply (float x, float y); int nCompletionStatus = 0;

float add (float x, float y) {

float z = x + y; return z;

float add_and_multiply (float x, float y) {

float z = add (x, y); z * = 3,0; return z;

Obviamente, o pré-processador substituiu o símbolo MULTIPLIER, cujo valor real, a partir do fato de que a variável USE_FIRST_OPTION foi definida, acabou
sendo 3,0.

Análise Lingüística
Durante esse estágio, o compilador primeiro converte o código C / C ++ em uma forma mais adequada para processamento (eliminando comentários e espaços em
branco desnecessários, extraindo tokens do texto, etc.). Essa forma otimizada e compactada de código-fonte é analisada lexicamente, com a intenção de verificar se
o programa satisfaz as regras de sintaxe da linguagem de programação na qual foi escrito. Se desvios das regras de sintaxe forem detectados, erros ou avisos serão
relatados. Os erros são causa suficiente para o término da compilação, enquanto os avisos podem ou não ser suficientes, dependendo das configurações do
usuário.

Uma visão mais precisa deste estágio do processo de compilação revela três estágios distintos:

• Análise lexical, que divide o código-fonte em tokens não divisíveis. O próximo estágio,

• A análise sintática / sintática concatena os tokens extraídos nas cadeias de tokens,

e verifica se sua ordem faz sentido do ponto de vista das regras da linguagem de programação. Finalmente,

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 12/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• A análise semântica é executada com a intenção de descobrir se as declarações sintaticamente corretas realmente fazem algum sentido. Por exemplo, uma
instrução que adiciona dois inteiros e atribui o resultado a um objeto passará pelas regras de sintaxe, mas pode não passar na verificação semântica (a menos que o
objeto tenha o operador de atribuição substituído).

Durante o estágio de análise lingüística, o compilador provavelmente mais merece ser chamado de "reclamante", pois tende a reclamar mais de erros de digitação
ou outros erros que encontra do que realmente compilar o código.

Montagem

O compilador atinge esse estágio somente depois que o código-fonte é verificado para não conter erros de sintaxe. Neste estágio, o compilador tenta converter as
construções de linguagem padrão em construções específicas para o conjunto real de instruções da CPU. Diferentes CPUs apresentam diferentes recursos de
funcionalidade e, em geral, diferentes conjuntos de instruções, registros e interrupções disponíveis, o que explica a grande variedade de compiladores para uma
variedade ainda maior de processadores.

Exemplo de montagem de projeto de demonstração

O compilador gcc fornece o modo de operação no qual o código-fonte dos arquivos de entrada é convertido em um arquivo de texto ASCII contendo as linhas de
instruções do montador específicas para o chip e / ou sistema operacional.

$ gcc -S <arquivo de entrada> -o <arquivo assembler de saída> .s

A menos que seja especificado de outra forma, a saída do pré-processador é o arquivo que tem o mesmo nome do arquivo de entrada e cuja extensão de arquivo é
.s.

O arquivo gerado não é adequado para execução; é apenas um arquivo de texto contendo os mnemônicos legíveis por humanos das instruções do assembler, que
podem ser usados ​pelo desenvolvedor para obter uma visão melhor dos detalhes do funcionamento interno do processo de compilação.

No caso particular da arquitetura do processador X86, o código assembler pode estar em conformidade com um dos dois formatos de impressão de instrução
suportados,

• formato AT&T

• formato Intel

a escolha de qual pode ser especificada passando um argumento de linha de comando extra para o assembler gcc. A escolha do formato depende principalmente
do gosto pessoal do desenvolvedor.

Exemplo de formato de montagem AT&T

Quando o arquivo function.c é montado no formato AT&T executando o seguinte comando $ gcc -S -masm = att function.c -o function.s

ele cria o arquivo assembler de saída, que se parece com o código mostrado na Listagem 2-6.

Listagem 2-6. function.s (formato AT&T Assembler)

.file "function.c"

.globl nCompletionStatus .bss

.align 4

.type nCompletionStatus, @object

.size nCompletionStatus, 4 nCompletionStatus:

.zero 4 .text

.globl add

.type add, @function

adicionar: .LFB0:

.cfi_startproc pushl% ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl% esp,% ebp .cfi_def_cfa_register 5 subl $ 20,% esp flds 8
(% ebp) fadds 12 (% ebp) fstps -4 (% ebp) movl -4 (% ebp),% eax movl% eax, -20 (% ebp) flds -20 (% ebp) licença

.cfi_restore 5 .cfi_def_cfa 4, 4 ret

.cfi_endproc

.LFE0:

.size add,.-add .globl add_and_multiply .type add_and_multiply, @function add_and_multiply: .LFB1:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 13/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
.cfi_startproc pushl% ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl% esp,% ebp .cfi_def_cfa_register 5 subl $ 28,% espl 12 (%
ebp),% eax movl% eax, 4 (%) movl 8 (mov% esp ebp),% eax movl% eax, (% esp) chamar add fstps -4 (% ebp) flds -4 (% ebp) flds .LC1

fmulp% st,% st (1) fstps -4 (% ebp) movl -4 (% ebp),% eax movl% eax, -20 (% ebp) flds -20 (% ebp) licença

.cfi_restore 5 .cfi_def_cfa 4, 4 ret

.cfi_endproc

add_and_multiply, .rodata

1077936128

"GCC: (Ubuntu / Linaro 4.6.3-1ubuntu5) 4.6.3" .note.GNU-stack, "", @ progbits

-add_and_multiply

.size .section .align 4

.grande

.ident

.seção

.LC1:

.LFE1:

Exemplo de Intel Assembly Format

O mesmo arquivo (function.c) pode ser montado no formato assembler Intel executando o seguinte comando, $ gcc -S -masm = intel function.c -o
function.s que resulta com o arquivo assembler mostrado na Listagem 2-7 .

Listagem 2-7. function.s (formato Intel Assembler)

.file "function.c" .intel_syntax noprefix .globl nCompletionStatus .bss

.align 4

.type nCompletionStatus, @object .size nCompletionStatus, 4 nCompletionStatus: .zero 4 .text

.globl add

.type add, @function

adicionar: .LFB0:

.cfi_startproc push ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 mov ebp, esp

.cfi_def_cfa_register 5 sub esp, 20

fld DWORD PTR [ebp + 8]

fadd DWORD PTR [ebp + 12] fstp DWORD PTR [ebp-4] mov eax, DWORD PTR [ebp-4]

mov DWORD PTR [ebp-20], eax

fld DWORD PTR [ebp-20]

sair

.cfi_restore 5 .cfi_def_cfa 4, 4 ret

.cfi_endproc

.LFE0:

e
.size .globl .typ add_and_multiply: .LFB1:

.cfi_startproc push ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 14/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
add,.-add add_and_multiply add_and_multiply, function

mov ebp, esp


.cfi def cfa register 5
sub esp, 28
mov eax, DWORD PTR [ebp + 12]
mov DWORD PTR [esp + 4], eax
mov eax, DWORD PTR [ebp + 8]
mov DWORD PTR [esp], eax
ligar adicionar
fstp DWORD PTR [ebp-4]
fld DWORD PTR [ebp-4]
fld DWORD PTR .LC1
fmulp st (1), st
fstp DWORD PTR [ebp-4]
mov eax, DWORD PTR [ebp-4]
mov DWORD PTR [ebp-20], eax
fld DWORD PTR [ebp-20]
sair

.cfi_restore 5 .cfi_def_cfa 4, ret

.cfi_endproc

.LFE1:

add_and_multiply, .rodata

1077936128

"GCC: (Ubuntu / Linaro 4.6.3-1ubuntu5) 4.6.3" .note.GNU-stack, "", i®progbits

.-add_and_multiply

.size .section .align 4

.grande

.ident

.seção

.LC1:

Otimização
Uma vez criada a primeira versão do assembler correspondente ao código-fonte original, inicia-se o esforço de otimização, no qual o uso dos registradores é
minimizado. Além disso, a análise pode indicar que certas partes do código não precisam de fato ser executadas e essas partes do código são eliminadas.

Emissão de código
Finalmente, chegou o momento de criar a saída da compilação: arquivos-objeto, um para cada unidade de tradução. As instruções de montagem (escritas em
código ASCII legível) são neste estágio convertidas nos valores binários das instruções de máquina correspondentes (opcodes) e escritas nos locais específicos nos
arquivos de objeto.

O arquivo objeto ainda não está pronto para ser servido como refeição para o processador faminto. Os motivos são o tópico essencial de todo o livro. O tópico
interessante neste momento é a análise de um arquivo-objeto.

Ser um arquivo binário torna o arquivo objeto substancialmente diferente das saídas dos procedimentos de pré-processamento e montagem, ambos os quais são
arquivos ASCII, inerentemente legíveis por humanos. As diferenças se tornam mais óbvias quando nós, os humanos, tentamos olhar mais de perto o conteúdo.

Além da escolha óbvia de usar o editor hexadecimal (não é muito útil, a menos que você escreva compiladores para viver), um procedimento específico chamado
desmontagem é usado para obter uma visão detalhada do conteúdo de um arquivo de objeto.

No caminho geral dos arquivos ASCII em direção aos arquivos binários adequados para execução na máquina de concreto, a desmontagem pode ser vista como
um pequeno desvio em que o arquivo binário quase pronto é convertido no arquivo ASCII para ser servido a os olhos curiosos do desenvolvedor de software.
Felizmente, esse pequeno desvio serve apenas para fornecer ao desenvolvedor uma melhor orientação e normalmente não é realizado sem uma causa real.
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 15/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Exemplo de compilação de projeto de demonstração


O compilador gcc pode ser configurado para realizar a compilação completa (pré-processamento, montagem e compilação), um procedimento que gera o arquivo
de objeto binário (extensão padrão .o) cuja estrutura segue as diretrizes do formato ELF. Além da sobrecarga usual (cabeçalho, tabelas, etc.), ele contém todas as
seções pertinentes (.text, .code, .bss, etc.). Para especificar apenas a compilação (sem vinculação ainda), a seguinte linha de comando pode ser usada:

$ gcc -c <arquivo de entrada> -o <arquivo de saída> .o

A menos que seja especificado de outra forma, a saída do pré-processador é o arquivo que tem o mesmo nome do arquivo de entrada e cuja extensão de arquivo é
.o.

O conteúdo do arquivo de objeto gerado não é adequado para visualização em um editor de texto. O editor / visualizador hexadecimal é um pouco mais
adequado, pois não será confundido com caracteres não imprimíveis e ausências de caracteres de nova linha. A Figura 2-2 mostra o conteúdo binário do arquivo
objeto function.o gerado pela compilação do arquivo function.c deste projeto de demonstração.

00000000 7f 45 4c 46 Ol Ol 01 00 00 00 00 00 00 00 00 00 | .ELF ...

00000010 01 00 03 00 Ol 00 00 00 00 00 00 00 00 00 00 00 1 .......

00600020 6c 01 00 00 00 00 00 00 34 00 00 00 00 00 28 00 | 1 ...... .4 ..... (. 1


O000O030 0d 00 Oa 00 WL 89 eS 83 ec 14 d9 45 08 d8 4S Oc | ... .u .. ____ E., E. |
00000040 d9 5d fc 8b 45 fc 89 45 ec d9 45 ec c9 c3 55 89 E..E ... U.1

00000050 e5 83 ec lc 8b 45 0C 89 44 24 04 8b 45 08 89 04 | ..... E. .D $ .. E. .. |
00000060 24 e8 fc ff ff ff d9 5d fc d9 45 fc d9 05 00 00 1 $ ...... ] .. E ..... |
00000070 00 00 de C9 d9 5 <i fc 8b 45 fc 89 45 ec 09 45 ec 1 .....] • .E., E..E. |
00000080 C9 c3 00 00 00 00 40 40 00 47 43 43 3a 20 28 55 1 ...... @ .GCC: (U |
00000090 62 75 5e 74 75 2f 4c 69 6e 61 72 6f 20 34 2e 36 | buntu / Linaro 4.6 |
OOOOOOaO 2e 33 2d 31 75 62 75 6e 74 75 35 29 20 34 2e 36 | .3-lubuntuS) 4.6 |
OOOOOObO 2e 33 00 00 14 00 00 00 00 00 00 00 01 7a 52 00 | .3 ..... ...... ZR. j
O0O0O0C0 01 7c 08 01 Libra 0C 04 04 88 Ol 00 00 lc 00 00 00 M .....

oooooodo lc 00 00 00 00 00 00 00 la 00 00 00 00 41 Oe 08 1 ....... ......UMA. . |


000000e0 85 02 42 od 05 56 C5 0C 04 04 00 00 lc 00 00 00

0000OOf0 3c 00 00 00 la 00 00 00 34 00 00 00 00 41 Oe 08

00000100 85 02 42 Od SO 70 cS Oc 04 04 00 00 00 2e 73 79 | ..8..p. ....... sy 1


00000110 6d 74 61 62 00 2e 73 74 72 74 61 62 00 2e 73 68 | ntab..strtab..shj
00000120 73 74 72 74 61 62 00 2e 72 65 6c 2e 74 65 78 74 | strtab. .rel.text |
00000130 00 2e 64 61 74 61 00 2e 62 73 73 00 2e 72 6f 64 | ..data. .bss..rod |
00000140 61 74 61 00 2e 63 ef 6d 6d 65 6e 74 00 2e 6e 6f lata..connent..no |
00000150 74 65 2e 47 4e 55 2d 73 74 61 63 6b 00 2e 72 65 | te.GNU- stack..re |
00000160 6c 2e 55 68 5f 66 72 61 6d 65 00 00 00 00 00 00 | \ .eh frame ...... |
00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1 .......

00000190 00 00 00 00 Se 00 00 00 01 00 00 00 06 00 00 00 1 .......

OOOOOlaO 00 00 00 00 34 00 00 00 4e 00 00 00 00 00 00 00 | ____ 4. . .N ....... |


00O0Olb0 00 00 00 00 04 00 00 00 00 00 00 00 Libra 00 00 00 1 .......

0O0OO1C0 09 00 00 00 00 00 00 00 00 00 00 00 68 04 00 00 1 ....... ..... h. .. |


O000Old0 10 00 00 00 Ob 00 00 00 Ol 00 00 00 04 00 00 00 1 .......

OOOOOleO 08 00 00 00 25 00 00 00 Ol 00 00 00 03 00 00 00 | ____ 54. .

OOOOOlfO 00 00 00 00 84 00 00 00 00 00 00 00 00 00 00 00 1 .......

00000200 00 00 00 00 04 00 00 00 00 00 00 00 2b 00 00 00 1 .......

00000210 08 00 00 00 03 00 00 00 00 00 00 00 84 00 00 00 1 .......

00000220 04 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 1 .......

00000230 00 00 00 00 30 00 00 00 Ol 00 00 00 02 00 00 00 | ____ 0. .

00000240 00 00 00 00 84 00 00 00 04 00 00 00 00 00 00 00 1 .......

00000250 00 00 00 00 04 00 00 00 00 00 00 00 38 00 00 00 1 ....... ..... 8. . . |


00000260 01 00 00 00 30 00 00 00 00 00 00 00 88 00 00 00 | ____ 0. .

00000270 2b 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00

00000280 01 00 00 00 41 00 00 00 01 00 00 00 00 00 00 00 | . ..UMA..

00000290 00 00 00 00 b3 00 00 00 00 00 00 00 00 00 00 00 ! .......

OOOOO2a0 00 00 00 00 01 00 00 00 00 00 00 00 55 00 00 00 1 ....... ..... U ... j

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 16/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
000002b0 01 00 00 00 02 00 00 00 00 00 00 00 b4 00 00 00 1 .......
O0O0O2C0 58 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 | X ......

0O0OO2d0 00 00 00 00 51 00 00 00 09 00 00 00 00 00 00 00

0OO0O2e0 00 00 00 00 78 04 00 00 10 00 00 00 Ob 00 00 00

Figura 2-2. Conteúdo binário de um arquivo de objeto

Obviamente, apenas dar uma olhada nos valores hexadecimais do arquivo objeto não nos diz muito. O procedimento de desmontagem tem o potencial de nos
dizer muito mais.

A ferramenta Linux chamada objdump (parte do popular pacote binutils ) é especializada em desmontar os arquivos binários, entre muitas outras coisas. Além de
converter a sequência de instruções binárias de máquina específicas para uma determinada plataforma, ele também especifica os endereços nos quais as instruções
residem.

Não deve ser uma grande surpresa que ele suporta AT&T (padrão), bem como os sabores Intel de impressão do código assembler.

Ao executar a forma simples do comando objdump,

$ objdump -D <arquivo de entrada> .o

você obtém o seguinte conteúdo impresso na tela do terminal:

saída desmontada de function.o (formato do montador AT&T)

function.o: formato de arquivo elf32-i386

Desmontagem da seção .text: 00000000 <add>:

0 55 Empurre % ebp
1 89 e5 mov % esp,% ebp
3 83 ec 14 sub $ 0x14,% esp
6 d9 45 08 flds 0x8 (% ebp)
9 d8 45 0c modismos 0xc (% ebp)
c d9 5d fc fstps -0x4 (% ebp)
f 8b 45 fc mov -0x4 (% ebp),% eax
12 89 45 ec mov % eax, -0x14 (% ebp)
15 d9 45 ec flds -0x14 (% ebp)
18 c9 sair

19 c3 ret

0000001a <add_and_multiply>:

1a 55 Empurre % ebp
1b 89 e5 mov % esp,% ebp
1d 83 ec 1c sub $ 0x1c,% esp
20 8b 45 0c mov 0xc (% ebp),% eax
23 89 44 24 04 mov % eax, 0x4 (% esp)
27 8b 45 08 mov 0x8 (% ebp),% eax
2a 89 04 24 mov % eax, (% esp)
2d e8 fc ff ff ff ligar 2e <adicionar e multiplicar + 0x14>
32 d9 5d fc fstps -0x4 (% ebp)
35 d9 45 fc flds -0x4 (% ebp)
38 d9 05 00 00 00 00 flds 0x0
3e de c9 fmulp % st,% st (1)
40 d9 5d fc fstps -0x4 (% ebp)
43 8b 45 fc mov -0x4 (% ebp),% eax
46 89 45 ec mov % eax, -0x14 (% ebp)
49 d9 45 ec flds -0x14 (% ebp)
4c c9 sair

4d c3 ret

Desmontagem da seção .bss: 00000000 <nCompletionStatus>:

adicione% al, (% eax)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 17/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
0: 00 00

Desmontagem da seção .rodata:

00000000 <.rodata>: 0: 00 00 2: 40 3: 40

adicione% al, (% eax) inc% eax inc% eax

Desmontagem da seção. Comentário:

00000000 < : .comment>:


0: 00 47 43

3: 43

4: 3a 20

6: 28 55 62

9: 75 6e

b: 74 75

d: 2f

e: 4c

f: 69 6e 61 72 6f
16: 2e 36 2e 33 2d
1d: 62 75

1f: 6e

20: 74 75

22: 35 29 20 34 2e
27: 36 2e 33 00

adicione% al, 0x43 (% edi)

inc% ebx

cmp (% eax),% ah

sub% dl, 0x62 (% ebp)

jne 79 <add_and_multiply + 0x5f>

je 82 <add_and_multiply + 0x68>

das

dez% esp

imul $ 0x34206f72,0x61 (% esi),% ebp cs ss xor% cs:% ss: 0x75627531,% ebp

outb% ds: (% esi), (% dx)

je 97 <add_and_multiply + 0x7d>

xor $ 0x2e342029,% eax ss xor% cs:% ss: (% eax),% eax

Desmontagem da seção .eh_frame:

00000000 <.eh frame>:

0: 14 00 adc $ 0x0,% al
2: 00 00 adicionar % al, (% eax)
4: 00 00 adicionar % al, (% eax)
6: 00 00 adicionar % al, (% eax)
8: 01 7a 52 adicionar % edi, 0x52 (% edx)
b: 00 01 adicionar % al, (% ecx)
d: 7c 08 jl 17 <.eh frame + 0x17>
f: 01 1b adicionar % ebx, (% ebx)
11: 0c 04 ou $ 0x4,% al

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 18/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

13: 04 88 adicionar $ 0x88,% al


15: 01 00 adicionar % eax, (% eax)
17: 00 1c 00 adicionar % bl, (% eax,% eax, 1)
1a: 00 00 adicionar % al, (% eax)
1c: 1c 00 sbb $ 0x0,% al
1e: 00 00 adicionar % al, (% eax)
20: 00 00 adicionar % al, (% eax)

22 00 00 adicionar % al, (% eax)

24 1a 00 sbb (% eax),% al

26 00 00 adicionar % al, (% eax)

28 00 41 0e adicionar % al, 0xe (% ecx)


2b 08 85 02 42 0d 05 ou % al, 0x50d4202 (% ebp)
31 56 Empurre % esi

32 c5 0c 04 lds (% esp,% eax, 1),% ecx


35 04 00 adicionar $ 0x0,% al

37 00 1c 00 adicionar % bl, (% eax,% eax, 1)


3a 00 00 adicionar % al, (% eax)

3c 3c 00 cmp $ 0x0,% al

3e 00 00 adicionar % al, (% eax)

40 1a 00 sbb (% eax),% al

42 00 00 adicionar % al, (% eax)

44 34 00 xor $ 0x0,% al

46 00 00 adicionar % al, (% eax)

48 00 41 0e adicionar % al, 0xe (% ecx)


4b 08 85 02 42 0d 05 ou % al, 0x50d4202 (% ebp)
51 70 c5 jo 18 <.eh_frame + 0x18>

53 0c 04 ou $ 0x4,% al

55 04 00 adicionar $ 0x0,% al

Da mesma forma, ao especificar o tipo Intel, $ objdump -D -M intel <arquivo de entrada> .o, você obtém o seguinte conteúdo impresso na tela do
terminal:

saída desmontada de function.o (formato Intel assembler)

function.o: formato de arquivo elf32-i386

Desmontagem da seção .text: 00000000 <add & gt:

0: 55 Empurre vazante

1: 89 e5 mov ebp, esp

3: 83 ec 14 sub esp, 0x14

6: d9 45 08 fld DWORD PTR [ebp + 0x8]


9: d8 45 0c modismo DWORD PTR [ebp + 0xc]
c: d9 5d fc fstp DWORD PTR [ebp-0x4]
f: 8b 45 fc mov eax, DWORD PTR [ebp-0x4]
12: 89 45 ec mov DWORD PTR [ebp-0x14], eax
15: d9 45 ec fld DWORD PTR [ebp-0x14]
18: c9 sair

19: c3 ret

0000001a <add_and_multiply>:

1a 55 Empurre vazante

1b 89 e5 mov ebp, esp

1d 83 ec 1c sub esp, 0x1c

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 19/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
20 8b 45 0c mov eax, DWORD PTR [ebp + 0xc]
23 89 44 24 04 mov DWORD PTR [esp + 0x4], eax
27 8b 45 08 mov eax, DWORD PTR [ebp + 0x8]
2a 89 04 24 mov DWORD PTR [esp], eax
2d e8 fc ff ff ff ligar 2e <add_and_multiply + 0x14>
32 d9 5d fc fstp DWORD PTR [ebp-0x4]
35 d9 45 fc fld DWORD PTR [ebp-0x4]
38 d9 05 00 00 00 00 fld DWORD PTR ds: 0x0
3e de c9 fmulp st (1), st

40 d9 5d fc fstp DWORD PTR [ebp-0x4]


43 8b 45 fc mov eax, DWORD PTR [ebp-0x4]
46 89 45 ec mov DWORD PTR [ebp-0x14], eax
49 d9 45 ec fld DWORD PTR [ebp-0x14]
4c c9 sair

4d c3 ret

Desmontagem da seção .bss: 00000000 <nCompletionStatus>:

BYTE PTR [eax], al

adicionar

0: 00 00

Desmontagem da seção .rodata:

00000000 <.rodata>: 0: 00 00 2: 40 3: 40

adicionar inc inc

BYTE PTR [eax], al

eax

eax

Desmontagem da seção. Comentário:

00000000 <.comment>:

0: 00 47 43

3: 43

4: 3a 20

6: 28 55 62

9: 75 6e

b: 74 75

d: 2f

e: 4c

f: 69 6e 61 72
16: 2e 36 2e 33
1d: 62 75

1f: 6e

20: 74 75

22: 35 29 20 34
27: 36 2e 33 00

adicionar inc cmp sub

jne je das

dez esp

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 20/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
imul ebp, DWORD PTR [esi + 0x61], 0x34206f72 cs ss xor ebp, DWORD PTR cs: ss: 0x75627531

outs dx, BYTE PTR ds: [esi] je 97 <add_and_multiply + 0x7d> xor eax, 0x2e342029 ss xor eax, DWORD PTR cs: ss: [eax]

BYTE PTR [edi + 0x43], al ebx

ah, BYTE PTR [eax] BYTE PTR [ebp + 0x62], dl 79 <add_and_multiply + 0x5f> 82 <add_and_multiply + 0x68>

20 34 31 75

Desmontagem da seção .eh_frame:

00000000 <.eh frame>:

0 14 00 adc

2 00 00 adicionar

4 00 00 adicionar

6 00 00 adicionar

8 01 7a 52 adicionar

b 00 01 adicionar

d 7c 08 jl

f 01 1b adicionar

11 0c 04 ou

13 04 88 adicionar

15 01 00 adicionar

17 00 1c 00 adicionar

1a 00 00 adicionar

1c 1c 00 sbb

1e 00 00 adicionar

20 00 00 adicionar

22 00 00 adicionar

24 1a 00 sbb

26 00 00 adicionar

28 00 41 0e adicionar

2b 08 85 02 42 0d 05 ou
31 56 Empurre

32 c5 0c 04 lds

35 04 00 adicionar

37 00 1c 00 adicionar

3a 00 00 adicionar

3c 3c 00 cmp

3e 00 00 adicionar

40 1a 00 sbb

42 00 00 adicionar

44 34 00 xor

46 00 00 adicionar

48 00 41 0e adicionar

4b 08 85 02 42 0d 05 ou
51 70 c5 jo

53 0c 04 ou

55 04 00 adicionar

al, 0x0

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 21/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
BYTE PTR [eax], al BYTE PTR [eax], al BYTE PTR [eax], al DWORD PTR [edx + 0x52], edi BYTE PTR [ecx], al

17 <.eh_frame + 0x17> DWORD PTR [ebx], ebx al, 0x4

al, 0x88

DWORD PTR [eax], eax BYTE PTR [eax + eax * 1], bl BYTE PTR [eax], al al, 0x0

BYTE PTR [eax], al

BYTE PTR [eax], al

BYTE PTR [eax], al

al, BYTE PTR [eax]

BYTE PTR [eax], al

BYTE PTR [ecx + 0xe], al

BYTE PTR [ebp + 0x50d4202], al

esi

ecx, FWORD PTR [esp + eax * 1] al, 0x0

BYTE PTR [eax + eax * 1], bl BYTE PTR [eax], al al, 0x0

BYTE PTR [eax], al al, BYTE PTR [eax] BYTE PTR [eax], al al, 0x0

BYTE PTR [eax], al

BYTE PTR [ecx + 0xe], al

BYTE PTR [ebp + 0x50d4202], al

18 <.eh_frame + 0x18> al, 0x4

al, 0x0

Propriedades do arquivo de objeto

A saída do processo de compilação é um ou mais arquivos de objetos binários, cuja estrutura é o próximo tópico de interesse natural. Como você verá em breve, a
estrutura dos arquivos de objetos contém muitos detalhes importantes no caminho para realmente compreender o quadro mais amplo.

Em um esboço,

• Um arquivo-objeto é o resultado da tradução de seu arquivo-fonte original correspondente. O resultado da compilação é a coleção de tantos arquivos de objeto
quantos arquivos de origem no projeto.

Após a conclusão da compilação, o arquivo-objeto continua representando seu arquivo-fonte original em estágios subsequentes do processo de construção do
programa.

• Os ingredientes básicos de um arquivo de objeto são os símbolos (referências aos endereços de memória no programa ou memória de dados), bem como as
seções.

Entre as seções mais freqüentemente encontradas nos arquivos de objeto estão o código (.texto), dados inicializados (.data), dados não inicializados (.bss) e
algumas das seções mais especializadas (informações de depuração, etc.).

• A intenção final por trás da ideia de construir o programa é que as seções obtidas pela compilação de arquivos-fonte individuais sejam combinadas (lado a lado)
em um único arquivo executável binário.

Esse arquivo binário conteria as seções do mesmo tipo (.text, .data, .bss,...) Obtidas agrupando as seções dos arquivos individuais. Falando figurativamente, um
arquivo de objeto pode ser visto como um simples ladrilho esperando para encontrar seu lugar no gigante mosaico do mapa de memória do processo.

• A estrutura interna do arquivo de objeto não sugere, entretanto, onde as seções individuais residirão no mapa de memória do programa. Por esse motivo, os
intervalos de endereço de cada seção em cada um dos arquivos de objeto são provisoriamente configurados para começar a partir de um valor zero.

O intervalo de endereços real no qual uma seção de um arquivo de objeto residirá no mapa do programa será determinado nos estágios subsequentes (vinculação)
do processo de construção do programa.

• No processo de agrupar as seções dos arquivos de objeto no mapa de memória do programa resultante, o único parâmetro verdadeiramente importante é o
comprimento de suas seções ou, para ser mais preciso, seu intervalo de endereços.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 22/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• O arquivo objeto não contém seções que contribuam para a pilha e / ou heap. O conteúdo dessas duas seções do mapa de memória é completamente
determinado no tempo de execução e, exceto o comprimento de byte padrão, não requer configurações iniciais específicas do programa.

• A contribuição do arquivo objeto para a seção .bss (dados não inicializados) do programa é muito rudimentar; a seção .bss é descrita meramente por seu
comprimento de byte. Essas poucas informações são exatamente o que é necessário para o carregador estabelecer a seção .bss como uma parte da memória na qual
alguns dados serão armazenados.

Em geral, as informações são armazenadas nos arquivos objeto de acordo com um determinado conjunto de regras resumidas na forma de especificação de
formato binário, cujos detalhes variam entre as diferentes plataformas (Windows vs. Linux, 32 bits vs. 64 bits, x86 vs. família de processadores ARM).

Normalmente, as especificações de formato binário são projetadas para suportar as construções da linguagem C / C ++ e os problemas de implementação
associados. Muito frequentemente, a especificação do formato binário cobre uma variedade de modos de arquivo binário, como executáveis, bibliotecas estáticas e
bibliotecas dinâmicas.

No Linux, o Executable and Linkable Format (ELF) ganhou prevalência. No Windows, os binários geralmente estão em conformidade com a especificação de
formato PE / COFF.

Limitações do processo de compilação

Passo a passo, as peças do gigantesco quebra-cabeça do processo de construção do programa estão começando a se encaixar e a imagem ampla e clara de toda a
história emerge lentamente. Até agora, você aprendeu que o processo de compilação converte os arquivos de origem ASCII na coleção correspondente de arquivos
de objetos binários. Cada um dos arquivos objeto contém seções, o destino de cada uma é, em última análise, tornar-se parte do gigantesco quebra-cabeça do mapa
de memória do programa, conforme ilustrado na Figura 2-3.

Mapa de memória do programa

filel.o

sem Arquivo

file2.o

A tarefa que resta é agrupar as seções individuais armazenadas em arquivos de objetos individuais no corpo do mapa de memória do programa. Conforme
mencionado nas seções anteriores, essa tarefa precisa ser deixada para outro estágio do processo de construção do programa, denominado vinculação.

A pergunta que um observador cuidadoso não pode deixar de fazer (antes de entrar nos detalhes do procedimento de vinculação) é exatamente por que precisamos
de um novo estágio do processo de construção, ou mais precisamente, exatamente por que o processo de compilação descrito não pode até agora, completou a parte de mosaico da
tarefa?

Existem algumas razões muito sólidas para dividir o procedimento de construção e o restante desta seção tentará esclarecer as circunstâncias que levaram a tal
decisão.

Figura 2-3. Organizando as seções individuais no mapa de memória do programa final

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 23/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Em suma, a resposta pode ser fornecida em algumas declarações simples. Primeiro, combinar as seções (especialmente as seções de código) nem sempre é simples.
Esse fator definitivamente desempenha certo papel, mas não é suficiente; existem muitas linguagens de programação cujo processo de construção do programa
pode ser concluído em uma etapa (em outras palavras, elas não requerem a divisão do procedimento em duas etapas).

Em segundo lugar, o princípio de reutilização de código aplicado ao processo de construção do programa (e a capacidade de combinar as partes binárias
provenientes de vários projetos) afirmou definitivamente a decisão de implementar a construção C / C ++ como um procedimento de duas etapas (compilar e
vincular) .

O que torna a combinação de seções tão complicada?


Para a maior parte, a tradução do código-fonte em arquivos de objetos binários é um processo bastante simples. As linhas de código são traduzidas em instruções
de código de máquina específicas do processador; o espaço para variáveis ​inicializadas é reservado e os valores iniciais são escritos nele; o espaço para variáveis ​
não inicializadas é reservado e preenchido com zeros, etc.

No entanto, há uma parte de toda a história que pode causar alguns problemas: embora o código-fonte esteja agrupado nos arquivos-fonte dedicados, fazer parte
do mesmo programa implica que certas conexões mútuas devem existir. Na verdade, as conexões entre as partes distintas do código são normalmente
estabelecidas por meio das duas opções a seguir:

• Chamadas de função entre corpos de código funcionalmente separados:

Por exemplo, uma função no arquivo de origem relacionado à GUI de um aplicativo de bate-papo pode chamar uma função no arquivo de origem de rede TCP /
IP, que por sua vez pode chamar uma função localizada no arquivo de origem de criptografia.

• Variáveis ​externas:

No domínio da linguagem de programação C (substancialmente menos no domínio C ++), era uma prática comum reservar variáveis ​globalmente visíveis para
manter o estado de interesse para várias partes do código. Uma variável destinada a um uso mais amplo é geralmente declarada em um arquivo de origem como
variável global e referenciada a partir de todos os outros arquivos de origem como variável externa.

Um exemplo típico é a variável errno usada em bibliotecas C padrão para manter o valor do último erro encontrado.

Para acessar qualquer um dos dois (comumente chamados de símbolos), seus endereços (mais precisamente, o endereço da função na memória do programa e /
ou o endereço da variável global na memória de dados) devem ser conhecidos.

No entanto, o endereço real não pode ser conhecido antes que as seções individuais sejam incorporadas na seção do programa correspondente (ou seja, antes que a
seção ladrilhada seja concluída !!!). Até então, uma conexão significativa entre uma função e seu chamador e / ou acesso à variável externa é impossível de
estabelecer, ambos devidamente relatados como referências não resolvidas. Observe que esse problema não ocorre quando a função ou variável global é
referenciada a partir do mesmo arquivo de origem em que foi definida. Nesse caso particular, tanto a função / variável quanto seu chamador / usuário acabam
fazendo parte da mesma seção, e suas posições em relação umas às outras são conhecidas antes da "conclusão do grande quebra-cabeça". Nesses casos, assim que
o revestimento das seções for concluído,

Conforme mencionado anteriormente nesta seção, resolver esse tipo de problema ainda não exige que um procedimento de construção seja dividido em dois
estágios distintos. Na verdade, muitas linguagens diferentes implementam com sucesso um procedimento de compilação de uma passagem. No entanto, o
conceito de reutilização (reutilização binária, neste caso) aplicado ao domínio da construção do programa (e o conceito de bibliotecas), em última análise, confirma
a decisão de dividir a construção do programa em dois estágios (compilar e vincular).

Linking
A segunda etapa do processo de construção do programa é a vinculação.A entrada para o processo de vinculação é a coleção de arquivos-objeto criados pelo
estágio de compilação concluído anteriormente. Cada arquivo de objeto pode ser visto como armazenamento binário de contribuições de arquivos de origem
individuais para as seções do mapa de memória do programa de todos os tipos (código, dados inicializados, dados não inicializados, informações de depuração,
etc.). A tarefa final do vinculador é formar a seção do mapa de memória do programa resultante a partir de contribuições individuais e resolver todas as
referências. Como um lembrete, o conceito de memória virtual simplificou a tarefa do vinculador na medida em que permite assumir que o mapa de memória do
programa que o vinculador precisa preencher é um intervalo de endereço baseado em zero de tamanho idêntico para cada programa, independentemente do que
faixa de endereços o processo será fornecido pelo sistema operacional em tempo de execução.

Para simplificar, cobrirei neste exemplo o caso mais simples possível, no qual as contribuições para as seções do mapa de memória do programa vêm
exclusivamente dos arquivos pertencentes ao mesmo projeto. Na realidade, devido ao avanço do conceito de reutilização de binários, isso pode não ser verdade.

Estágios de ligação

O processo de vinculação acontece por meio de uma sequência de estágios (realocação, resolução de referência), que serão discutidos em detalhes a seguir.

Relocação
O primeiro estágio de um procedimento de vinculação nada mais é do que tiling, um processo no qual seções de vários tipos contidos em arquivos de objetos
individuais são combinados para criar as seções do mapa de memória do programa (consulte a Figura 2-4). Para completar esta tarefa, os intervalos de endereços
baseados em zero, anteriormente neutros, das seções de contribuição são traduzidos em intervalos de endereços mais concretos do mapa de memória do programa
resultante.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 24/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
•••

Dados
Código

- 0x080032101 Ar 0x00003210 0x08000000 0x00000000

Arquivo de objeto

Mapa de memória do programa

Figura 2-4. Relocação, a primeira fase da fase de vinculação

A expressão "mais concreto" é usada para enfatizar o fato de que a imagem do programa resultante criada pelo vinculador ainda é neutra por si só. Lembre-se de
que o mecanismo de endereçamento virtual torna possível que todo e qualquer programa tenha a mesma visão simples e idêntica do espaço de endereço do
N
programa (que reside entre 0 e 2 ), enquanto o endereço físico real no qual o programa é executado é determinado em tempo de execução pelo sistema
operacional, invisível para o programa e o programador.

Assim que o estágio de realocação for concluído, a maior parte (mas não todos!) Do mapa de memória do programa foi criada.

Resolvendo Referências
Agora vem a parte difícil. Tomar seções, traduzir linearmente seus intervalos de endereços em intervalos de endereços do mapa de memória do programa foi uma
tarefa bastante fácil. Uma tarefa muito mais difícil é estabelecer as conexões necessárias entre as várias partes do código, tornando o programa homogêneo.

Vamos supor (com razão, dada a simplicidade deste programa de demonstração) que todos os estágios de construção anteriores (compilação completa, bem como
realocação de seção) foram concluídos com êxito. Agora é o momento de apontar exatamente quais tipos de problemas são deixados para a última etapa de
conexão resolver.

Como mencionado anteriormente, a causa raiz dos problemas de ligação é bastante simples: pedaços de código originados de unidades de tradução diferentes (ou
seja, arquivos de origem) e estão tentando fazer referência uns aos outros, mas não podem saber onde na memória esses itens irão residir até o os arquivos de
objeto são colocados lado a lado no corpo do mapa de memória do programa. Os componentes do código que causam mais problemas são aqueles fortemente
ligados ao endereço nas variáveis ​de memória do programa (pontos de entrada de função) ou na memória de dados (global / estático / externo).

Neste exemplo de código específico, você tem a seguinte situação:

• A função add_and_multiply chama a função add, que reside no mesmo arquivo de origem (ou seja, a mesma unidade de tradução no mesmo arquivo objeto).
Nesse caso, o endereço na memória do programa da função add () é, até certo ponto, uma quantidade conhecida e pode ser expresso por seu deslocamento
relativo da seção de código do arquivo-objeto function.o.

• Agora, a função principal chama a função add_and_multiply e também faz referência à variável externa nCompletionStatus e tem grandes problemas
para descobrir o endereço real da memória do programa em que residem. Na verdade, ele apenas pode assumir que esses dois símbolos em algum ponto no futuro
residirão em algum lugar no mapa de memória do processo. Mas, até que o mapa de memória seja formado, dois itens não podem ser considerados nada mais do
que referências não resolvidas.

A situação é descrita graficamente na Figura 2-5.

function.o

main.o

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 25/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

O
D-
a Principal

n Status de conclusão

i = (d = i
add_and_multiply () (?) > '
Referências não resolvidas

Figura 2-5. O problema das referências não resolvidas em sua forma essencial

Para resolver esse tipo de problema, deve ocorrer uma etapa de encadeamento de resolução das referências. O que o vinculador precisa fazer nessa situação é

• Examine as seções já agrupadas no mapa de memória do programa.

• Descubra qual parte do código faz chamadas fora de sua seção original.

• Descobrir onde exatamente (em qual endereço no mapa de memória) reside a parte referenciada do código.

Depois que o vinculador conclui sua mágica, a situação pode ser semelhante à Figura 2-6.

Mapa de memória do programa

Figura 2-6. Referências resolvidas

Exemplo de vinculação de projeto de demonstração


Existem duas maneiras de compilar e vincular o projeto de demonstração completo para criar o arquivo executável de modo que esteja pronto para execução.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 26/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Na abordagem passo a passo, você primeiro invocará o compilador em ambos os arquivos de origem para produzir os arquivos de objeto. Na etapa subsequente,
você vinculará os dois arquivos-objeto ao executável de saída.

$ gcc -c function.c main.c $ gcc function.o main.o -o demoApp

Na abordagem tudo de uma vez, a mesma operação pode ser concluída invocando o compilador e o vinculador com apenas um comando.

$ gcc function.c main.c -o demoApp

Para os fins desta demonstração, vamos seguir a abordagem passo a passo, pois isso irá gerar o arquivo de objeto main.o , que contém detalhes muito
importantes que desejo demonstrar aqui. A desmontagem do arquivo main.o,

$ objdump -D -M intel main.o

revela que contém referências não resolvidas.

saída desmontada de main.o (formato Intel assembler)

main.o: formato de arquivo elf32-i386

Desmontagem da seção .text: 00000000 <main>:

0: 55 Empurre vazante

1: 89 e5 mov ebp, esp

3: 83 e4 f0 e esp, 0xfffffff0

6: 83 ec 20 sub esp, 0x20

9: b8 00 00 80 3f mov eax, 0x3f800000


e: 89 44 24 14 mov DWORD PTR [esp + 0x14], eax
12: b8 00 00 a0 40 mov eax, 0x40a00000
17: 89 44 24 18 mov DWORD PTR [esp + 0x18], eax
1b: 8b 44 24 18 mov eax, DWORD PTR [esp + 0x18]
1f: 89 44 24 04 mov DWORD PTR [esp + 0x4], eax
23: 8b 44 24 14 mov eax, DWORD PTR [esp + 0x14]
27: 89 04 24 mov DWORD PTR [esp], eax

2a: e8 fc ff ff ff ligar 2b <principal + 0x2b>


2f: d9 5c 24 1c fstp DWORD PTR [esp + 0x1c]
33: c7 05 00 00 00 00 01 mov DWORD PTR ds: 0x0 , 0x1
3a: 00 00 00

3d: b8 00 00 00 00 mov eax, 0x0

42: c9 sair

43: c3 ret

A linha 2a apresenta uma instrução de chamada que salta para si mesma (estranho, não é?), Enquanto a linha 33 apresenta o acesso da variável residente no
endereço 0x0 (ainda mais estranho). Obviamente, esses dois valores obviamente estranhos foram inseridos pelo linker propositalmente.

A saída desmontada do executável de saída, no entanto, mostra que não apenas o conteúdo do arquivo de objeto main.o foi realocado para a faixa de endereço
começando no endereço 0x08048404, mas também esses dois pontos problemáticos foram resolvidos pelo vinculador.

$ objdump -D -M intel demoApp

saída desmontada de demoApp (formato Intel assembler)

080483ce <add_and_multiply>:

80483ce 55 Empurre vazante

80483cf 89 e5 mov ebp, esp

80483d1 83 ec 1c sub esp, 0x1c

80483d4 8b 45 0c mov eax, DWORD PTR [ebp + 0xc]

80483d7 89 44 24 04 mov DWORD PTR [esp + 0x4], eax


80483db 8b 45 08 mov eax, DWORD PTR [ebp + 0x8]

80483de 89 04 24 mov DWORD PTR [esp], eax

80483e1 e8 ce ff ff ff ligar 80483b4 <adicionar>

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 27/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
80483e6 d9 5d fc fstp DWORD PTR [ebp-0x4]
80483e9 d9 45 fc fld DWORD PTR [ebp-0x4]

80483ec d9 05 20 85 04 08 fld DWORD PTR ds: 0x8048520


80483f2 de c9 fmulp st (1), st

80483f4 d9 5d fc fstp DWORD PTR [ebp-0x4]

80483f7 8b 45 fc mov eax, DWORD PTR [ebp-0x4]

80483fa 89 45 ec mov DWORD PTR [ebp-0x14], eax

80483fd d9 45 ec fld DWORD PTR [ebp-0x14]

8048400 c9 sair

8048401 c3 ret

8048402 90 nop

8048403 90 nop

08048404 <main>:

8048404 55 Empurre vazante

8048405 89 e5 mov ebp, esp

8048407 83 e4 f0 e esp, 0xfffffff0

804840a 83 ec 20 sub esp, 0x20

804840d b8 00 00 80 3f mov eax, 0x3f800000


8048412 89 44 24 14 mov DWORD PTR [esp + 0x14], eax
8048416 b8 00 00 a0 40 mov eax, 0x40a00000
804841b 89 44 24 18 mov DWORD PTR [esp + 0x18], eax
804841f 8b 44 24 18 mov eax, DWORD PTR [esp + 0x18]
8048423 89 44 24 04 mov DWORD PTR [esp + 0x4], eax
8048427 8b 44 24 14 mov eax, DWORD PTR [esp + 0x14]
804842b 89 04 24 mov DWORD PTR [esp], eax

804842e e8 9b ff ff ff ligar 80483ce <add_and_multiply>


8048433 d9 5c 24 1c fstp DWORD PTR [esp + 0x1c]
8048437 c7 05 18 a0 04 08 01 mov DWORD PTR ds: 0x804a018 , 0x1
804843e 00 00 00

8048441 b8 00 00 00 00 mov eax, 0x0


8048446 c9 sair t:

A linha no endereço do mapa de memória 0x8048437 faz referência à variável que reside no endereço 0x804a018. A única questão em aberto agora é o que reside
naquele endereço específico?

A versátil ferramenta objdump pode ajudá-lo a obter a resposta a essa pergunta (uma parte decente dos capítulos subsequentes é dedicada a essa ferramenta
excepcionalmente útil). Executando o seguinte comando

$ objdump -x -j .bss demoApp

você pode desmontar a seção .bss carregando os dados não inicializados, o que revela que sua variável nCompletionStatus reside exatamente no endereço
0x804a018, conforme mostrado na Figura 2-7.

milão @ milão $ objdump -x -j .bss demoApp

TABELA DE SÍMBOLOS:
08043010 eu d .bss
08043010 eu 0 . bss
08043014 eu 0 .bss
08043018 9 0 .bss

.bss

completado.6159

dtor_idx.6l61
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 28/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

nConpletionStatus
Oüo

00900000 00000001 00000004 00000004

Figura 2-7. bss desmontado

Ponto de Vista do Linker

"Quando você tem um martelo na mão, tudo parece um prego"

- Síndrome do Martelo Handy

Mas, falando sério, pessoal. . . .

Agora que você conhece os meandros da tarefa de vinculação, é útil diminuir um pouco o zoom e tentar resumir a filosofia que orienta o vinculador durante a
execução de suas tarefas usuais. Na verdade, o vinculador é uma ferramenta específica que, ao contrário de seu irmão mais velho, o compilador, não está
interessada nos mínimos detalhes do código escrito. Em vez disso, ele vê o mundo como um conjunto de arquivos de objetos que (bem como peças de um quebra-
cabeça) estão prestes a ser combinados em uma imagem mais ampla do mapa de memória do programa, conforme ilustrado na Figura 2-8.

Símbolos locais (necessários apenas pela unidade local)

oooooo

Os símbolos precisam ser resolvidos; eles estão localizados dentro de outras unidades de tradução

Símbolos exportados (necessários para outras unidades de tradução)

C
TTT ...... ??
:(? Y

TTT ...... ??
: <? X: <? X
Segmento de código (.text)

Segmento de dados (.data, .bss, ...)

EH

TTT ...... ??
Outros segmentos ...

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 29/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
ßiX

.TTT — YT
TTT ...... ??
TTT ...... ??
TTT ...... ??
ßlX

Figura 2-8. A visão de mundo do vinculador

Não é preciso muita imaginação para descobrir que a Figura 2-8 tem muita semelhança com a parte esquerda da Figura 2-9, ao passo que a tarefa final do linkador
poderia ser representada pela parte direita da mesma figura.

D
3H

Figura 2-9. A visão de mundo do Linker vista pelos humanos

Propriedades do arquivo executável


O resultado final do procedimento de vinculação é o arquivo executável binário, cujo layout segue as regras do formato executável adequado para a plataforma de
destino. Independentemente das diferenças de formato reais, o arquivo executável normalmente contém as seções resultantes (.text, .data, .bss e muitos outros
especializados) criados combinando as contribuições de arquivos de objeto individuais. Mais notavelmente, a seção de código (.texto) não contém apenas os blocos
individuais de arquivos objeto, mas o vinculador a modificou para garantir que todas as referências entre os blocos individuais foram resolvidas, de modo que a
função chama entre diferentes partes do código assim como os acessos variáveis ​são precisos e significativos.

Entre todos os símbolos contidos no arquivo executável, um lugar muito único pertence à função principal , pois do ponto de vista dos programas C / C ++ é a
função a partir da qual toda a execução do programa começa. No entanto, esta não é a primeira parte do código que é executada quando o programa é iniciado.

Um detalhe excepcionalmente importante que precisa ser apontado é que o arquivo executável não é inteiramente feito de código compilado dos arquivos-fonte
do projeto. Na verdade, um trecho de código estrategicamente importante responsável por iniciar a execução do programa é adicionado no estágio de vinculação
ao mapa de memória do programa. Este código de objeto, que o vinculador normalmente armazena no início do mapa de memória do programa, vem em duas
variantes:

• ertO é o ponto de entrada "plain vanilla", a primeira parte do código do programa que é executado sob o controle do kernel.

• crtl é a rotina de inicialização mais moderna com suporte para tarefas a serem concluídas antes que a função principal seja executada e depois que o
programa terminar.

Tendo esses detalhes em mente, a estrutura geral do executável do programa pode ser representada simbolicamente pela Figura 2-10.

Diferente para cada processo

Idêntico para cada processo

% esp -> ■

brk

0x08048000 (32) 0x40000000 (64)

Estruturas de dados específicas do processo (por exemplo, tabelas de página, tarefas e estruturas de mm, pilha de kernel)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 30/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Código e dados do kernel

Região mapeada de memória para bibliotecas compartilhadas

Heap em tempo de execução (via malloc)

Dados não inicializados (.bas)

Dados inicializados (.data)

Rotina de inicialização (crtl)

Memória física

Pilha de usuário

Texto do programa (.text)

Figura 2-10. Estrutura geral de um arquivo executável

Como você verá mais adiante no capítulo dedicado a bibliotecas dinâmicas e vinculação dinâmica, essa parte extra de código, que é fornecida pelo sistema
operacional, faz quase toda a diferença entre um executável e uma biblioteca dinâmica; o último não tem essa parte específica do código.

Mais detalhes sobre a sequência de etapas que acontecem quando a execução de um programa começa serão discutidos no próximo capítulo.

Variedade de tipos de seção

Assim como dirigir um automóvel não pode ser imaginado sem o motor e um conjunto de quatro rodas, a execução do programa não pode ser imaginada sem as
seções de código (.text) e de dados (.data e / ou .bss). Esses ingredientes são naturalmente a parte essencial da funcionalidade mais básica do programa.

No entanto, assim como o automóvel não é apenas o motor e as quatro rodas, o arquivo binário contém muito mais seções. Para sincronizar com precisão a
variedade de tarefas operacionais, o vinculador cria e insere no arquivo binário muitos outros tipos de seção diferentes.

Por convenção, o nome da seção começa com o caractere ponto (.). Os nomes dos tipos de seção mais importantes são independentes da plataforma; eles são
chamados da mesma forma, independentemente da plataforma e do formato binário ao qual pertencem.

Ao longo deste livro, os significados e funções de certos tipos de seção no esquema geral das coisas serão discutidos longamente. Esperançosamente, quando o
livro for lido, o leitor terá uma compreensão substancialmente mais ampla e focada das seções do arquivo binário.

Na Tabela 2-1, a especificação do formato binário ELF predominante do Linux traz a seguinte ( http://man7.org/ linux / man-pages / man5 /
elf.5.html) lista de vários tipos de seção fornecidos no ordem alfabética. Mesmo que as descrições das seções individuais sejam um pouco escassas, dar uma
olhada na variedade de seções neste ponto pode dar ao leitor uma ideia bastante boa sobre a variedade disponível.

Tabela 2-1. Tipos de seção de linker

Nome da seção _ Descrição _

.bss Esta seção contém os dados não inicializados que contribuem para a imagem da memória do programa.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 31/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Por definição, o sistema inicializa os dados com zeros quando o programa começa a ser executado. Esta seção é do tipo SHT_NOBITS. Os tipos de atributos são
SHF_ALLOC e SHF_WRITE.

.comment Esta seção contém informações de controle de versão. Esta seção é do tipo SHT_PROGBITS.

Nenhum tipo de atributo é usado.

Esta seção contém ponteiros inicializados para as funções do construtor C ++. Esta seção é do tipo SHT_PROGBITS. Os tipos de atributos são SHF_ALLOC e
SHF_WRITE.

Esta seção contém dados inicializados que contribuem para a imagem da memória do programa. Esta seção é do tipo SHT_PROGBITS. Os tipos de atributos são
SHF_ALLOC e SHF_WRITE.

Esta seção contém dados inicializados que contribuem para a imagem da memória do programa. Esta seção é do tipo SHT_PROGBITS. Os tipos de atributos são
SHF_ALLOC e SHF_WRITE.

Esta seção contém informações para depuração simbólica. Os conteúdos não são especificados. Esta seção é do tipo SHT_PROGBITS. Nenhum tipo de atributo é
usado.

Esta seção contém ponteiros inicializados para as funções destruidoras do C ++. Esta seção é do tipo SHT_PROGBITS. Os tipos de atributos são SHF_ALLOC e
SHF_WRITE.

Esta seção contém informações de links dinâmicos. Os atributos da seção incluem o bit SHF_ALLOC . Se o bit SHF_WRITE está definido é específico do processador.
Esta seção é do tipo SHT_DYNAMIC. Veja os atributos acima

(continuação) 38

.ctors

.dados

.data1

.depurar

.dtors

.dinâmico

Esta seção contém as strings necessárias para a vinculação dinâmica, mais comumente as strings que representam os nomes associados às entradas da tabela de
símbolos. Esta seção é do tipo SHT_STRTAB. O tipo de atributo usado é SHF_ALLOC.

Esta seção contém a tabela de símbolos de vínculo dinâmico. Esta seção é do tipo SHT_DYNSYM. O atributo usado é SHF_ALLOC.

Esta seção contém instruções executáveis ​que contribuem para o código de encerramento do processo. Quando um programa é encerrado normalmente, o sistema
organiza a execução do código nesta seção. Esta seção é do tipo SHT_PROGBITS. Os atributos usados ​são SHF_ALLOC e SHF_EXECINSTR.

Esta seção contém a tabela de símbolos de versão, uma matriz de elementos ElfN_Half. Esta seção é do tipo SHT_GNU_versym. O tipo de atributo usado é
SHF_ALLOC.

Esta seção contém as definições de símbolo de versão, uma tabela de estruturas ElfN_Verdef. Esta seção é do tipo SHT_GNU_verdef . O tipo de atributo usado é
SHF_ALLOC.

Esta seção contém os elementos necessários do símbolo de versão, uma tabela de estruturas ElfN_Verneed. Esta seção é do tipo SHT_GNU_versym. O tipo de
atributo usado é SHF_ALLOC.

Esta seção contém a tabela de deslocamento global. Esta seção é do tipo SHT_PROGBITS. Os atributos são específicos do processador.

Esta seção contém a tabela de ligação do procedimento. Esta seção é do tipo SHT_PROGBITS. Os atributos são específicos do processador.

Esta seção contém uma tabela de hash de símbolos. Esta seção é do tipo SHT_HASH. O atributo usado é SHF_ALLOC.

Esta seção contém instruções executáveis ​que contribuem para o código de inicialização do processo. Quando um programa começa a ser executado, o sistema
organiza a execução do código nesta seção antes de chamar o ponto de entrada do programa principal. Esta seção é do tipo SHT_PROGBITS. Os atributos usados ​
são SHF_ALLOC e SHF_EXECINSTR.

Esta seção contém o nome do caminho de um interpretador de programa. Se o arquivo tiver um segmento carregável que inclui a seção, os atributos da seção
incluirão o bit SHF_ALLOC . Caso contrário, esse bit estará desligado. Esta seção é do tipo SHT_PROGBITS.

Esta seção contém as informações do número da linha para depuração simbólica, que descreve a correspondência entre a fonte do programa e o código de
máquina. O conteúdo não foi especificado. Esta seção é do tipo SHT_PROGBITS. Nenhum tipo de atributo é usado.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 32/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Esta seção contém informações no formato "Seção de notas". Esta seção é do tipo SHT_NOTE. Nenhum tipo de atributo é usado. Os executáveis ​nativos do
OpenBSD geralmente contêm um .note. seção openbsd.ident para se identificarem, para que o kernel ignore quaisquer testes de emulação binária ELF de
compatibilidade ao carregar o arquivo.

Esta seção é usada em arquivos de objeto Linux para declarar atributos de pilha. Esta seção é do tipo SHT_PROGBITS. O único atributo usado é SHF_EXECINSTR.
Isso indica ao vinculador GNU que o arquivo-objeto requer uma pilha executável.

.dynstr

.dynsym .fini

.gnu.version

.gnu.version_d

.gnu_version.r

.conseguiu

.got.plt

.cerquilha

.iniciar

.interp

.linha

.Nota

.note.GNU-stack .plt

Descrição

Tabela 2-1. ( continuação )

Nome da Seção

Esta seção contém a tabela de ligação do procedimento. Esta seção é do tipo SHT_PROGBITS. Os atributos são específicos do processador.

(contínuo)

Esta seção contém informações de realocação conforme descrito abaixo. Se o arquivo tiver um segmento carregável que inclui realocação, os atributos da seção
incluirão o bit SHF_ALLOC . Caso contrário, a broca estará desligada. Por convenção, "NOME" é fornecido pela seção à qual se aplicam as realocações. Portanto,
uma seção de realocação para .text normalmente teria o nome .rel.text. Esta seção é do tipo SHT_REL.

Esta seção contém informações de realocação conforme descrito abaixo. Se o arquivo tiver um segmento carregável que inclui realocação, os atributos da seção
incluirão o bit SHF_ALLOC . Caso contrário, a broca estará desligada. Por convenção, "NOME" é fornecido pela seção à qual se aplicam as realocações. Portanto,
uma seção de realocação para .text normalmente teria o nome .rela.text. Esta seção é do tipo SHT_RELA.

Esta seção contém dados somente leitura que normalmente contribuem para um segmento não gravável na imagem do processo. Esta seção é do tipo
SHT_PROGBITS. O atributo usado é SHF_ALLOC.

Esta seção contém dados somente leitura que normalmente contribuem para um segmento não gravável na imagem do processo. Esta seção é do tipo
SHT_PROGBITS. O atributo usado é SHF_ALLOC.

Esta seção contém nomes de seção. Esta seção é do tipo SHT_STRTAB. Nenhum tipo de atributo é usado.

Esta seção contém strings, mais comumente as strings que representam os nomes associados às entradas da tabela de símbolos. Se o arquivo tiver um segmento
carregável que inclua a tabela de sequência de símbolos, os atributos da seção incluirão o bit SHF_ALLOC . Caso contrário, a broca estará desligada. Esta seção é do
tipo SHT_STRTAB.

Esta seção contém uma tabela de símbolos. Se o arquivo tiver um segmento carregável que inclui a tabela de símbolos, os atributos da seção incluirão o bit
SHF_ALLOC . Caso contrário, a broca estará desligada. Esta seção é do tipo SHT_SYMTAB.

Esta seção contém o "texto" ou instruções executáveis ​de um programa. Esta seção é do tipo SHT PROGBITS. Os atributos usados ​são SHF ALLOC e SHF
EXECINSTR.

.relNAME

.relaNAME

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 33/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
.rodata .rodatal .shrstrtab .strtab

.symtab .text

Descrição

Tabela 2-1. ( continuação )

Nome da Seção

Uma variedade de tipos de símbolos

O formato ELF oferece uma grande variedade de tipos de símbolos de vinculação, muito maiores do que você pode imaginar neste estágio inicial de seu caminho
para a compreensão dos meandros do processo de vinculação. No momento, você pode distinguir claramente que os símbolos podem ser de escopo local ou de
visibilidade mais ampla, normalmente necessários para os outros módulos. Ao longo do material do livro, mais tarde, os vários tipos de símbolos serão discutidos
em substancialmente mais detalhes.

A Tabela 2-2 apresenta a variedade de tipos de símbolos, conforme mostrado nas páginas do manual ( http://linux.die.net/man/1/nm ) do útil programa
utilitário de exame de símbolo nm . Como regra geral, a menos que explicitamente indicado (como no caso de "U" vs. "u"), a letra minúscula denota símbolos
locais, enquanto a letra maiúscula indica melhor visibilidade do símbolo (externo, global).

O valor do símbolo é absoluto e não será alterado por links adicionais. O símbolo está na seção de dados não inicializados (.bss).

O símbolo é comum. Os símbolos comuns são dados não inicializados. Ao vincular, vários símbolos comuns podem aparecer com o mesmo nome. Se o símbolo for
definido em qualquer lugar, os símbolos comuns serão tratados como referências indefinidas.

O símbolo está na seção de dados inicializados.

O símbolo está em uma seção de dados inicializada para pequenos objetos. Alguns formatos de arquivo de objeto permitem um acesso mais eficiente a pequenos
objetos de dados, como uma variável int global em oposição a uma grande matriz global.

Para arquivos de formato PE, isso indica que o símbolo está em uma seção específica para a implementação de DLLs. Para arquivos no formato ELF, isso indica
que o símbolo é uma função indireta. Esta é uma extensão GNU para o conjunto padrão de tipos de símbolos ELF. Indica um símbolo que, se referenciado por
uma relocação, não avalia seu endereço, mas deve ser invocado em tempo de execução. A execução do tempo de execução retornará então o valor a ser usado na
realocação.

O símbolo é um símbolo de depuração.

Os símbolos estão em uma seção de desenrolamento da pilha.

O símbolo está em uma seção de dados somente leitura.

O símbolo está em uma seção de dados não inicializada para pequenos objetos.

O símbolo está na seção de texto (código).

O símbolo é indefinido. Na verdade, esse binário não define esse símbolo, mas espera que ele eventualmente apareça como resultado do carregamento das
bibliotecas dinâmicas.

O símbolo é um símbolo global único. Esta é uma extensão GNU para o conjunto padrão de associações de símbolos ELF. Para tal símbolo, o vinculador dinâmico
se certificará de que em todo o processo haja apenas um símbolo com esse nome e tipo em uso.

O símbolo é um objeto fraco. Quando um símbolo definido como fraco é vinculado a um símbolo definido normal, o símbolo definido normal é usado sem erro.
Quando um símbolo fraco indefinido é vinculado e o símbolo não é definido, o valor do símbolo fraco torna-se zero sem erro. Em alguns sistemas, letras
maiúsculas indicam que um valor padrão foi especificado.

O símbolo é um símbolo fraco que não foi especificamente marcado como um símbolo de objeto fraco. Quando um símbolo definido fraco é vinculado a um
símbolo definido normal, o símbolo definido normal é usado sem erro. Quando um símbolo indefinido fraco é vinculado e o símbolo não é definido, o valor do
símbolo é determinado de uma maneira específica do sistema sem erros. Em alguns sistemas, maiúsculas indica que um valor padrão foi especificado.

"UMA"

"B" ou "b" "C"

"D" ou "d" "G" ou "g"

"N" "p"

"R" ou "r" "S" ou "s" "T" ou "t"

"VOCÊ"

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 34/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
"V" ou "v"

"W" ou "w"

Tabela 2-2. Tipos de símbolo de linker

Descrição do tipo de símbolo

O símbolo é um símbolo de punhaladas em um arquivo de objeto a.out . Nesse caso, os próximos valores impressos são o campo stabs other, o campo stabs desc
e o tipo de stab. Os símbolos Stabs são usados ​para armazenar informações de depuração.

O tipo de símbolo é desconhecido ou específico do formato do arquivo de objeto.

CAPÍTULO 3

Estágios de execução do programa


O objetivo deste capítulo é descrever a seqüência de eventos que acontecem quando o usuário inicia um programa. A análise concentra-se principalmente em
apontar os detalhes da interação entre o sistema operacional e o layout do arquivo binário executável, que está estreitamente conectado ao mapa de memória do
processo. Desnecessário dizer que o foco principal desta discussão é a sequência de execução dos binários executáveis ​criados pela construção do código em C / C
++.

Importância da Shell
A execução do programa sob o controle do usuário normalmente ocorre por meio de um shell, um programa que monitora as ações do usuário no teclado e no
mouse. O Linux apresenta muitos shells diferentes, sendo os mais populares sh, bash e tcsh.

Depois que o usuário digita o nome do comando e pressiona a tecla Enter, o shell tenta primeiro comparar o nome do comando digitado com o de seus próprios
comandos embutidos. Se o nome do programa for confirmado como não sendo nenhum dos comandos suportados pelo shell, o shell tentará localizar o binário
cujo nome corresponda à string de comando. Se o usuário digitou apenas o nome do programa (ou seja, não o caminho completo para o binário executável), o shell
tenta localizar o executável em cada uma das pastas especificadas pela variável de ambiente PATH. Uma vez que o caminho completo do binário executável é
conhecido, o shell ativa o procedimento para carregar e executar o binário.

A primeira ação obrigatória do shell é criar um clone de si mesmo bifurcando o processo filho idêntico. Criar o novo mapa de memória de processo copiando o
mapa de memória existente do shell parece um movimento estranho, pois é muito provável que um novo mapa de memória de processo não tenha nada em
comum com o mapa de memória do shell. Essa estranha manobra é feita por um bom motivo: assim, o shell efetivamente passa todas as suas variáveis ​de
ambiente para o novo processo. De fato, logo após o novo mapa de memória do processo ser criado, a maioria de seu conteúdo original é apagado / zerado (exceto
a parte que carrega as variáveis ​de ambiente herdadas) e sobrescrito com o mapa de memória do novo processo, que fica pronto para a execução estágio. A Figura
3-1 ilustra a ideia.

Processar estruturas de dados específicas (por exemplo, tabelas de página, tarefas e estruturas de mm, pilha de kernel)

Processar estruturas de dados específicas (por exemplo, tabelas de página, tarefas e estruturas de mm, pilha de kernel)

Memória virtual do kernel

^ Meio Ambiente

variáveis ​Processar memória virtual

Código e dados do kernel da memória física

Região mapeada de memória para bibliotecas compartilhadas

Heap em tempo de execução (via malloc)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 35/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Dados não inicializados (.bas)

Dados inicializados (.data)

Texto do programa (.text)

1
O® ^ milan @ milan

nllän @ nilan $ flrefox

O Shell primeiro cria uma cópia de seu próprio mapa de memória de processo, que será usado por um novo processo.

Logo depois disso, o kernel irá limpar o mapa de memória recém-criado, retendo apenas a parte que carrega as variáveis ​de ambiente.

A partir daí, tudo está pronto para o carregador preencher o mapa de memória vazio com o conteúdo encontrado no arquivo binário do programa
iniciado.

Figura 3-1. O shell começa a criar o novo mapa de memória de processo copiando seu próprio mapa de memória de processo, com a intenção de passar suas próprias variáveis ​de
ambiente para o novo processo

Deste ponto em diante, o shell pode seguir um dos dois cenários possíveis. Por padrão, o shell espera que seu processo de clonagem bifurcada conclua o comando
(ou seja, que o programa iniciado conclua a execução). Como alternativa, se o usuário digitar um "e" comercial após o nome do programa, o processo filho será
colocado em segundo plano e o shell continuará monitorando os comandos digitados subseqüentemente pelo usuário. O mesmo modo pode ser alcançado pelo
usuário não anexando o "e" comercial após o nome do executável; em vez disso, após o programa ser iniciado, o usuário pode pressionar Ctrl-Z (que emite o sinal
SIGSTOP para o processo filho) e imediatamente após digitar "bg" (que emite o sinal SIGCONT para o processo filho) na janela do shell, que irá causar o efeito
idêntico (colocar o processo filho do shell em segundo plano).

Um cenário muito semelhante de inicialização do programa ocorre quando o usuário aplica um clique do mouse no ícone do aplicativo. O programa que fornece o
ícone (como uma sessão gnome e / ou Nautilus File Explorer no Linux) assume a responsabilidade de traduzir o clique do mouse na chamada do sistema () , o
que faz com que uma sequência de eventos muito semelhante aconteça como se o aplicativo foram chamados digitando na janela do shell.

Papel do Kernel
Assim que o shell delega a tarefa de executar o programa, o kernel reage invocando uma função da família de funções exec , todas as quais fornecem
praticamente a mesma funcionalidade, mas diferem nos detalhes de como os parâmetros de execução são especificados. Independentemente de qual função do
tipo exec particular é escolhida, cada um deles faz uma chamada para a função sys_execve , que inicia o trabalho real de execução do programa.

A próxima etapa imediata (que acontece na função search_binary_handler (arquivo fs / exec.c) é identificar o formato executável. Além de suportar o
formato binário executável ELF mais recente, o Linux oferece compatibilidade com versões anteriores suportando vários outros formatos binários. O formato ELF
é identificado, o foco da ação passa para a função load_elf_binary (arquivo fs / binfmt_elf.c).

Depois que o formato executável é identificado como um dos formatos suportados, o esforço de preparação do mapa de memória do processo para a execução
começa. Em particular, o processo filho criado pelo shell (um clone do próprio shell) é passado do shell para o kernel com as seguintes intenções:

• O kernel obtém a sandbox (o ambiente de processo) e, mais importante, a memória associada, que pode ser usada para iniciar o novo programa.

A primeira coisa que o kernel fará é limpar completamente a maior parte do mapa de memória. Imediatamente depois, ele delegará ao carregador o processo de
preencher o mapa de memória apagado com os dados lidos do arquivo executável binário do novo programaSharePoint.

• Ao clonar o processo shell (por meio da chamada fork () ), as variáveis ​de ambiente definidas no shell são passadas para o processo filho, o que ajuda a que a
cadeia de herança das variáveis ​de ambiente não seja quebrada.

Função do carregador
Antes de entrar nos detalhes da funcionalidade do carregador, é importante ressaltar que o carregador e o vinculador têm perspectivas um tanto diferentes sobre o
conteúdo do arquivo binário.

Visualização específica do carregador de um arquivo binário (seções vs. segmentos)

O vinculador pode ser considerado um módulo altamente sofisticado, capaz de distinguir com precisão uma ampla variedade de seções de várias naturezas
(código, dados não inicializados, dados inicializados, construtores, informações de depuração, etc.). Para resolver as referências, deve conhecer intimamente os
detalhes de sua estrutura interna.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 36/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Por outro lado, as responsabilidades do carregador são muito mais simples. Na maior parte, sua tarefa é copiar as seções criadas pelo vinculador no mapa de
memória do processo. Para completar suas tarefas, ele não precisa saber muito sobre a estrutura interna das seções. Em vez disso, tudo o que se preocupa é se os
atributos das seções são somente leitura, leitura e gravação e (como será discutido mais tarde) se é necessário aplicar algum patch antes que o executável esteja
pronto para ser iniciado.

■ Nota Como será mostrado mais tarde em discussões sobre o processo de vinculação dinâmica, os recursos do carregador são um pouco mais complexos
do que meros blocos de cópia de dados.

Portanto, não é uma grande surpresa que o carregador tenda a agrupar as seções criadas pelo vinculador em segmentos com base em seus requisitos de
carregamento comuns. Conforme mostrado na Figura 3-2, os segmentos do carregador normalmente carregam várias seções que possuem atributos de acesso
comuns (leitura ou leitura e gravação, ou o mais importante, para serem corrigidos ou não).

Figura 3-2. Linker vs. carregador

Conforme mostrado na Figura 3-3, usar o utilitário readelf para examinar os segmentos ilustra o agrupamento de muitas seções diferentes do linker nos
segmentos do carregador.

nilan @ nllan $ readelf --segnents li.bnreloc.so

O tipo de arquivo Elf é DYN (arquivo de objeto compartilhado) Ponto de entrada 0x399

Existem 7 cabeçalhos de programa, começando no deslocamento 52 Cabeçalhos de Progran:

Tipo Offset VlrtAddr PhysAddr FlleSlz MemSlz Fig Align

CARGA 0X000000 0X00000003 0X00000000 0X00S40 9X00S40 RE 0X1000

CARGA 0X000f0C exOOOOlfOc Ox000Olf0C 0X00104 9x9910c RW 0X1000

DINÂMICO Ox000f29 9x00001f20 0x00001f29 OX0O0C8 9XO00C8 RW 0x4

NOTA 0x000114 0x00000114 0x00000114 0x00024 0x00024 R 0x4

GNU_EH_FRAME 0X0004C4 OX0O00O4C4 0X00O004C4 0x0001c 0x0001c R 0x4 GNU STACK 0X000000 0X00000009 0X00000000 0X00000 9x90000 RW 0X4

GNURELRO 0x9O0f0C 9x99901f0c 0x90001f0c 9x900f4 9x990f4 R 0X1

Seção para cochilar do segmento: Seções do segmento ...

00 .note.gnu.build-Id .gnu.hash .dynsyn .dynstr .gnu.version .gnu.verslon_r .rel, dy n .rel.pit .Init .pit .text .flnl
.eh_frane_hdr .eh ^ frsme

01 .ctors .dtors, jcr .dynamic .got .got.pit .data, bss

02 .dynamic

03 .note.gnu.build-Id

04 .eh_franehdr

05

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 37/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
06 .ctors .dtors .jcr .dynamic .got

nllan @ mllan $

Figura 3-3. Seções agrupadas em segmentos

Estágio de carregamento do programa

Uma vez que o formato binário é identificado, a função do módulo carregador do kernel entra em ação. O carregador tenta primeiro localizar o segmento
PT_INTERP no arquivo binário executável, o que o ajudará na tarefa de carregamento dinâmico.

A fim de evitar as armadilhas da proverbial situação de "carroça à frente do cavalo" - uma vez que o carregamento dinâmico ainda não foi explicado - vamos
assumir o cenário mais simples possível em que o programa está estaticamente vinculado e não há necessidade de carregamento dinâmico de qualquer tipo.

EXEMPLO DE CONSTRUÇÃO ESTÁTICA

O termo construção estática é usado para indicar o executável, que não possui nenhuma das dependências de vinculação dinâmica. Todas as bibliotecas
externas necessárias para criar esse executável são vinculadas estaticamente. Como consequência, o binário obtido é totalmente portável, pois não requer a
presença de nenhuma biblioteca compartilhada do sistema (nem mesmo a libc) para ser executado. O benefício da portabilidade total (que raramente
requer tais medidas drásticas) vem com o preço do tamanho de byte muito maior do executável.

além da portabilidade total, a razão para o executável estaticamente construído pode ser puramente educacional, já que se presta bem ao processo de
explicar as funções originais e mais simples possíveis do carregador.

O efeito da construção estática pode ser ilustrado com o exemplo do exemplo "Hello World" puro e simples. Vamos usar o mesmo arquivo de origem para
construir os dois aplicativos, um dos quais é construído com o sinalizador de vinculador -static ; consulte as Listagens 3-1 e 3-2.

Listagem 3-1. main.cpp #include <stdio.h>

int main (int argc, char * argv []) {

printf ("Olá, mundo \ n"); return 0;

Listagem 3-2. build.sh

gcc main.cpp -o regularBuild

gcc -static main.cpp -o staticBuild

A comparação dos tamanhos de bytes dos dois executáveis ​mostrará que o tamanho dos bytes do executável construído estaticamente é muito maior (cerca
de 100 vezes neste exemplo específico). _

O carregador continua lendo os cabeçalhos dos segmentos do arquivo binário do programa para determinar os endereços e comprimentos de byte de cada um dos
segmentos. Um detalhe importante a destacar é que, neste estágio, o carregador ainda não grava nada no mapa de memória do programa. Tudo o que o
carregador faz neste estágio é estabelecer e manter um conjunto de estruturas (vm_are_struct por exemplo), carregando os mapeamentos entre os segmentos do
arquivo executável (na verdade, partes da página inteira de cada um dos segmentos) e o mapa de memória do programa.

A cópia real dos segmentos do executável ocorre após o início da execução do programa. Nessa altura, o mapeamento de memória virtual entre a página de
memória física concedida ao processo e o mapa de memória do programa foi estabelecido; as primeiras solicitações de paginação começam a chegar do kernel,
solicitando que intervalos de toda a página de segmentos de programa estejam disponíveis para execução. Como consequência direta dessa política, apenas as
partes do programa que são realmente necessárias no tempo de execução são carregadas (Figura 3-4).

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 38/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 3-4. Estágio de carregamento do programa

Executando o Ponto de Entrada do Programa


Da perspectiva de programação C / C ++ usual, o ponto de entrada do programa é a função main () . Do ponto de vista da execução do programa, entretanto,
não é. Antes do ponto em que o fluxo de execução atinge a função main () , algumas outras funções são executadas, o que nivela o campo de jogo para o
programa ser executado.

Vamos examinar mais de perto o que normalmente acontece no Linux entre o carregamento do programa e a execução da primeira linha de código da função main
() .

O carregador encontra o ponto de entrada

Depois de carregar o programa (isto é, preparar o blueprint do programa e copiar as seções necessárias para a memória para sua execução), o carregador dá uma
olhada rápida no valor do campo e_entry do cabeçalho ELF. Este valor contém o endereço de memória do programa a partir do qual a execução será iniciada.

A desmontagem do arquivo binário executável normalmente mostra que o valor e_entry carrega nada menos do que o primeiro endereço da seção do código
(.text). Coincidentemente, este endereço de memória de programa normalmente denota a origem da função de inicialização .

A seguir está a desmontagem da seção .text:

08048320 <_início>:

8048320 31 ed xor ebp, ebp

8048322 5e pop esi

8048323 89 e1 mov ecx, esp

8048325 83 e4 f0 e esp, 0xfffffff0


8048328 50 Empurre eax

8048329 54 Empurre esp

804832a 52 Empurre edx

804832b 68 60 84 04 08 Empurre 0x8048460


8048330 68 f0 83 04 08 Empurre 0x80483f0
8048335 51 Empurre ecx

8048336 56 Empurre esi

8048337 68 d4 83 04 08 Empurre 0x80483d4


804833c e8 cf ff ff ff ligar 8048310 <_ libc_start_main @plt>
8048341 f4 hlt

O papel da função _start ()

O papel da função _start é preparar os argumentos de entrada para a função_libc_start_main que será

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 39/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
chamado em seguida. Seu protótipo é definido como

int _libc_start_main (int (* main) (int, char * *, char * *), / * endereço da função principal * /

int argc, / * número de argumentos da linha de comando * /

char * * ubp_av, / * linha de comando arg array * /

void (* init) (void), / * endereço da função init * /

void (* fini) (void), / * endereço da função fini * / void (* rtld_fini) (void), / * endereço da função fini do vinculador
dinâmico * /

void (* stack_end) / * end of the stack address * /);

Na verdade, tudo o que as instruções anteriores à instrução de chamada fazem é empilhar os argumentos necessários para a chamada na ordem esperada.

Para entender exatamente o que essas instruções fazem e por quê, dê uma olhada na próxima seção, que é dedicada a explicar o mecanismo de pilha. Mas antes de
irmos lá, vamos primeiro completar a história sobre como iniciar a execução do programa.

O papel da função__libc_start_main ()

Esta função é a peça chave no processo de preparação do ambiente para a execução do programa. Ele não apenas configura as variáveis ​de ambiente para o
programa durante a execução do programa, mas também faz o seguinte:

• Inicia o threading do programa.

• Chama a função _init () , que executa as inicializações necessárias para serem concluídas antes do início da função main () .

O compilador GCC por meio da palavra-chave _attribute_ ((construtor)) oferece suporte personalizado

design das rotinas que você pode desejar concluir antes do início do programa.

• Registra as funções _fini () e _rtld_fini () a serem chamadas para limpeza após o término do programa. Normalmente, a ação de _fini () é inversa
às ações da função _init () .

O compilador GCC, por meio da palavra- chave_ attribute_ ((destructor)) , oferece suporte a custom

design das rotinas que você pode querer concluir antes de iniciar o programa.

Finalmente, depois que todas as ações de pré-requisito foram concluídas, the_libc_start_main () chama o main ()

função, fazendo assim o seu programa funcionar.

Convenções de pilha e chamada

Como qualquer pessoa com experiência em programação acima do nível de iniciante absoluto sabe, o fluxo de programa típico é, na verdade, uma sequência de
chamadas de função. Normalmente, a função principal chama pelo menos uma função, que por sua vez pode chamar um grande número de outras funções.

O conceito de pilha é a base do mecanismo de chamadas de função. Este aspecto particular da execução do programa não é de suma importância para o tópico
geral deste livro, e não gastaremos muito mais tempo discutindo os detalhes de como a pilha funciona. Esse assunto é lugar-comum há muito tempo e não há
necessidade de reiterar os fatos conhecidos.

Em vez disso, apenas alguns pontos importantes relacionados à pilha e às funções serão apontados.

• O mapa de memória do processo reserva certa área para as necessidades da pilha.

• A quantidade de memória de pilha usada em tempo de execução realmente varia; quanto maior a sequência de chamadas de função, mais memória de pilha está
em uso.

• A pilha de memória não é ilimitada. Em vez disso, a quantidade de memória de pilha disponível é vinculada à quantidade de memória disponível para alocação
(que é a parte da memória do processo conhecida como heap).

Convenções de chamada de funções


Como uma função passa os argumentos para a função que chama é um tópico muito interessante. Uma variedade de mecanismos muito elaborados de passagem
de variáveis ​para as funções foi projetada, resultando em rotinas específicas de linguagem assembly. Esses mecanismos de implementação de pilha são
normalmente chamados de convenções de chamada.

Na verdade, muitas convenções de chamada diferentes foram desenvolvidas para a arquitetura X86, como cdecl, stdcall, fastcall, thiscall, para citar
apenas alguns. Cada um deles é feito sob medida para um cenário específico de uma variedade de pontos de vista de design. O artigo intitulado "Calling
Conventions Demystified" por Nemanja Trifunovic ( www.codeproject.com/Articles/1388/Calling-Conventions-Demystified ) fornece uma visão

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 40/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
interessante sobre as diferenças entre as várias convenções de chamada. A lendária série de artigos do blog de Raymond Chen intitulada "The History of Calling
Conventions", que surgiu alguns anos depois ( http://blogs.msdn.com/b/oldnewthing/ archive / 2004/01/02 / 47184.aspx) , são provavelmente a
fonte única de informação mais completa sobre o assunto.

Sem gastar muito tempo neste tópico em particular, um detalhe de particular importância é que entre todas as convenções de chamada disponíveis, uma delas em
particular, a convenção de chamada cdecl , é fortemente preferida para implementar a interface de bibliotecas dinâmicas exportadas para o outro mundo . Fique
ligado para mais detalhes, pois as discussões no Capítulo 6 sobre as funções da biblioteca ABI fornecerão uma visão melhor do tópico.

CAPÍTULO 4

O impacto do conceito de reutilização


O conceito de reutilização de código é onipresente e encontrou uma variedade impressionante de formas de se manifestar. Seu impacto no processo de construção
de programas aconteceu muito antes da conhecida transição das linguagens de programação procedural para as orientadas a objetos.

Os motivos iniciais para dividir as tarefas entre compilador e vinculador já foram descritos nos capítulos anteriores. Resumidamente, tudo começou com o hábito
útil de manter o código em arquivos de origem separados; então, no tempo de compilação, tornou-se óbvio que o compilador não poderia completar facilmente a
tarefa de resolver as referências simplesmente porque o agrupamento das seções de código no quebra-cabeça final do mapa de memória do programa tinha que
acontecer primeiro.

A ideia de reutilização de código acrescentou um argumento extra à decisão de dividir os estágios de compilação e vinculação. A quantidade de indeterminismo
trazida pelos arquivos objeto (todas as seções tendo intervalos de endereços baseados em zero mais referências não resolvidas), que inicialmente certamente
parecia uma desvantagem, à luz da ideia de compartilhamento de código, na verdade, começou a parecer uma nova qualidade preciosa.

O conceito de reutilização de código aplicado ao domínio da construção dos executáveis ​do programa encontrou sua primeira realização na forma de bibliotecas
estáticas, que são coleções agrupadas de arquivos-objeto. Mais tarde, com o advento dos sistemas operacionais multitarefa, outra forma de reutilização, chamada
de bibliotecas dinâmicas, ganhou destaque. Hoje em dia, ambos os conceitos (bibliotecas estáticas e dinâmicas) estão em uso, cada um com prós e contras,
exigindo, portanto, uma compreensão mais profunda dos detalhes internos de sua funcionalidade. Este capítulo descreve detalhadamente esses dois conceitos um
tanto semelhantes, mas também substancialmente diferentes.

Bibliotecas estáticas
A ideia por trás do conceito de bibliotecas estáticas é excepcionalmente simples: uma vez que o compilador traduz uma coleção de unidades de tradução (ou seja,
arquivos de origem) em arquivos de objetos binários, você pode querer manter os arquivos de objetos para uso posterior em outros projetos, onde eles podem ser
prontamente combinados em tempo de link com os arquivos de objetos inerentes a esse outro projeto.

Para que seja possível integrar os arquivos de objetos binários em algum outro projeto, pelo menos um requisito adicional precisa ser satisfeito: que os arquivos
binários sejam acompanhados pelo arquivo de inclusão de cabeçalho de exportação, que fornecerá a variedade de definições e funções declarações de pelo menos
essas funções que podem ser usadas como pontos de entrada. A seção intitulada "A Conclusão: O Impacto do Conceito de Reutilização Binária" explica por que
algumas funções são mais importantes do que outras.

Existem várias maneiras pelas quais um conjunto de arquivos de objeto pode ser usado em vários projetos:

• A solução trivial é salvar os arquivos-objeto gerados pelo compilador e copiar

(recortar e colar) ou transferir de qualquer maneira possível para um projeto que precisa deles (onde serão vinculados ao lado de outros arquivos-objeto no
executável), conforme mostrado na Figura 4-1.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 41/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 4-1. Um método trivial de reutilização de código binário, o precursor de bibliotecas estáticas

COMPILADOR i = D>

PROJETO 2

• A melhor maneira é agrupar os arquivos de objeto em um único arquivo binário, uma biblioteca estática.

É muito mais simples e muito mais elegante entregar um único arquivo binário para o outro projeto do que cada arquivo de objeto separadamente (Figura 4-2).

Figura 4-2. A biblioteca estática como forma de reutilização de código binário

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 42/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
PROJETO 2

• O requisito óbvio neste caso é que o vinculador entenda o formato do arquivo da biblioteca estática e seja capaz de extrair seu conteúdo (ou seja, arquivos-objeto
agrupados) para vinculá-los. Felizmente, esse requisito foi atendido por provavelmente todos e cada um vinculador desde os primórdios da programação de
microprocessadores.

• Observe também que o processo de criação de uma biblioteca estática não é irreversível. Mais especificamente, uma biblioteca estática é meramente um arquivo
de arquivos de objeto, que pode ser manipulado de várias maneiras. Por meio do uso fácil de ferramentas apropriadas, uma biblioteca estática pode ser
desmontada na coleção de arquivos de objetos originais; um ou mais arquivos-objeto podem ser descartados da biblioteca, novos arquivos-objeto podem ser
adicionados e, finalmente, os arquivos-objeto existentes podem ser substituídos por uma versão mais recente.

Qualquer que seja uma dessas duas abordagens que você decida seguir, a trivial ou a abordagem de biblioteca estática mais sofisticada, você essencialmente terá o
processo de reutilização de código binário acontecendo, já que os arquivos binários gerados em um projeto são usados ​em outros projetos. O impacto geral da
reutilização de código binário na paisagem do design de software será discutido em detalhes posteriormente.

Bibliotecas Dinâmicas
Ao contrário do conceito de bibliotecas estáticas, que existe desde os primeiros dias da programação em assembler, o conceito de bibliotecas dinâmicas foi
totalmente aceito muito mais tarde. As circunstâncias que levaram à sua criação e adoção estão intimamente relacionadas ao surgimento de sistemas operacionais
multitarefa.

Em qualquer análise do funcionamento de um sistema operacional multitarefa, uma noção particular rapidamente ganha destaque: independentemente da
variedade de tarefas simultâneas, certos recursos do sistema são únicos e devem ser compartilhados por todos. Os exemplos típicos de recursos compartilhados no
sistema de desktop são o teclado, o mouse, o adaptador gráfico de vídeo, a placa de som, a placa de rede e assim por diante.

Seria contraproducente e até desastroso se cada aplicativo que pretende acessar os recursos comuns tivesse que incorporar o código (seja como uma fonte ou como
uma biblioteca estática) que fornece controle sobre o recurso. Isso seria muito ineficiente, desajeitado e muito espaço de armazenamento (disco rígido e memória)
seria desperdiçado no armazenamento de duplicatas do mesmo código.

O sonho de sistemas operacionais melhores e mais eficientes levou à ideia de ter um mecanismo de compartilhamento que não pressuporia a compilação dos
arquivos de origem duplicados nem a vinculação dos arquivos de objetos duplicados. Em vez disso, seria implementado como algum tipo de compartilhamento
de tempo de execução. Em outras palavras, o aplicativo em execução seria capaz de integrar em seu mapa de memória de programa as partes compiladas e
vinculadas de algum outro executável, onde a integração ocorreria sob demanda, conforme a necessidade, em tempo de execução. Esse conceito é conhecido como
vinculação dinâmica / carregamento dinâmico, que será ilustrado com mais detalhes na próxima seção.

Desde os primeiros estágios de design, um fato importante se tornou óbvio: de todas as partes da biblioteca dinâmica, só faz sentido compartilhar sua seção de código
(.texto), mas não os dados com os outros processos. Na analogia culinária, vários chefs diferentes podem compartilhar o mesmo livro de receitas (código). No
entanto, dado que chefs diferentes podem estar preparando simultaneamente pratos totalmente diferentes do mesmo livro de receitas, seria desastroso se eles
compartilhassem os mesmos utensílios de cozinha (dados).

Obviamente, se vários processos diferentes tivessem acesso à seção de dados da biblioteca dinâmica, a substituição da variável aconteceria em momentos
arbitrários e a execução da biblioteca dinâmica seria imprevisível, o que tornaria a ideia toda sem sentido. Dessa forma, mapeando apenas a seção de código, os
vários aplicativos ficam livres para executar o código compartilhado cada um em seu próprio compartimento, separadamente um do outro.

Bibliotecas dinâmicas vs. compartilhadas

As ambições dos designers de sistema operacional desde o início eram evitar a presença múltipla desnecessária das mesmas partes do código do sistema
operacional nos binários de cada aplicativo que pudesse precisar deles. Por exemplo, cada aplicativo que precisasse imprimir o documento teria que incorporar a
pilha de impressão completa, terminando com o driver da impressora, a fim de fornecer o recurso de impressão. Se o driver da impressora mudasse, todo o
exército de designers de aplicativos precisaria recompilar seus aplicativos; caso contrário, um caos surgiria devido à presença de uma infinidade de versões de
driver de impressora diferentes no tempo de execução.

Obviamente, a solução certa seria implementar o sistema operacional de forma que aconteça o seguinte:

• A funcionalidade comumente necessária é fornecida na forma de bibliotecas dinâmicas.

• O aplicativo que precisa de acesso à funcionalidade comum precisa simplesmente carregar a biblioteca dinâmica em tempo de execução.

A ideia básica por trás do conceito de bibliotecas dinâmicas é ilustrada na Figura 4-3.

TEMPO DE CONSTRUÇÃO

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 43/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

rotina

Figura 4-3. O conceito de bibliotecas dinâmicas

A primeira solução para esse problema (ou seja, a primeira versão de implementação de vinculação dinâmica, conhecida como relocação de tempo de
carregamento (LTR)), atingiu a meta com sucesso parcial. A boa notícia é que os aplicativos foram dispensados ​de carregar a bagagem desnecessária do código do
sistema operacional em seus binários; em vez disso, eles foram implantados apenas com o código específico do aplicativo, enquanto todas as necessidades
relacionadas ao sistema foram satisfeitas vinculando dinamicamente os módulos fornecidos pelo sistema operacional.

A má notícia, entretanto, era que se vários aplicativos precisassem de certa funcionalidade do sistema em tempo de execução, cada um dos aplicativos teria que
carregar sua própria cópia da biblioteca dinâmica. A causa subjacente dessa limitação foi o fato de que a técnica de realocação do tempo de carregamento
modificou os símbolos da seção .text da biblioteca dinâmica para se ajustar ao mapeamento de endereço específico do aplicativo fornecido. Para outro aplicativo,
que carregaria a biblioteca dinâmica no intervalo de endereços possivelmente diferente, o código de biblioteca modificado simplesmente não se ajustaria ao layout
de memória diferente.

Como resultado, várias cópias das bibliotecas dinâmicas residiam nos mapas de memória dos processos em tempo de execução. Isso é algo com que poderíamos
conviver por algum tempo, mas os objetivos de longo prazo do design eram muito mais ambiciosos: fornecer um mecanismo mais eficiente que permitiria que a
biblioteca dinâmica fosse carregada apenas uma vez (por qualquer aplicativo que aconteça para carregá-la primeiro) e ser disponibilizado para qualquer outro
aplicativo que tente carregá-lo em seguida.

Esse objetivo foi alcançado por meio do conceito conhecido como código independente de posição (PIC). Alterando como o código da biblioteca dinâmica
acessava os símbolos, apenas uma cópia da biblioteca dinâmica carregada no mapa de memória de qualquer processo se tornaria compartilhável por mapeamento
de memória para qualquer mapa de memória de processo do aplicativo (Figura 4-4).

TEMPO DE EXECUÇÃO
Estruturas de dados específicas de processos (por exemplo, tabelas de páginas, tarefas e estruturas de mm, pilha de kernel)

Pilha de usuário

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 44/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 4-4. Os avanços trazidos pela técnica PIC de vinculação dinâmica

Além disso, não é incomum que o sistema operacional carregue certos recursos comuns do sistema (drivers de nível superior, por exemplo) na memória física,
sabendo que provavelmente serão necessários pela abundância de processos em execução. O efeito da vinculação dinâmica é que cada um dos processos tem a
ilusão perfeita de que são os únicos proprietários do driver.

Desde a invenção do conceito PIC, as bibliotecas dinâmicas projetadas para suportá-lo foram chamadas de bibliotecas compartilhadas. Hoje em dia, o conceito PIC
é predominante, e em sistemas de 64 bits é fortemente favorecido pelos compiladores, então a distinção de nomenclatura entre os termos biblioteca dinâmica e
biblioteca compartilhada está desaparecendo, e os dois nomes são usados ​mais ou menos indistintamente.

O conceito de memória virtual pavimentou a base para o sucesso da ideia de compartilhamento de tempo de execução (sintetizado pelo conceito de código
independente de posição). A ideia inicial é bastante simples: se o mapa de memória do processo real (com endereços reais e concretos) nada mais é do que o
resultado do mapeamento 1: 1 do mapa de memória do processo baseado em zero, o que realmente nos impede de criar um monstro, um real mapa de memória
de processo obtido mapeando partes de mais de um processo diferente? Na verdade, é exatamente assim que funciona o mecanismo de compartilhamento em
tempo de execução de bibliotecas dinâmicas.

A implementação bem-sucedida do conceito PIC representa a pedra angular dos modernos sistemas operacionais multitarefa.

Estruturas de dados específicas de processos (por exemplo, tabelas de páginas, tarefas e estruturas de mm, pilha de kernel)

Estruturas de dados específicas de processos (por exemplo, tabelas de páginas, tarefas e estruturas de mm, pilha de kernel)

Vinculação dinâmica em mais detalhes

O conceito de vinculação dinâmica está no cerne do conceito de bibliotecas dinâmicas. É praticamente impossível entender completamente como as bibliotecas
dinâmicas funcionam sem entender a complexa interação entre a biblioteca dinâmica, o executável do cliente e o sistema operacional. O foco desta seção é fornecer
o amplo nível de compreensão necessário do processo de vinculação dinâmica. Uma vez que sua essência seja compreendida, as seções subsequentes deste
documento prestarão a devida atenção aos detalhes.

Então, vamos ver o que realmente acontece durante o processo de vinculação dinâmica.

Parte 1: Construindo a Biblioteca Dinâmica

Como as figuras anteriores sugerem, o processo de construção de uma biblioteca dinâmica é uma construção completa, pois abrange tanto a compilação
(conversão da fonte em arquivos de objetos binários) quanto a resolução das referências. O produto do processo de construção da biblioteca dinâmica é o arquivo
binário cuja natureza é idêntica à natureza do executável, a única diferença é que a biblioteca dinâmica não possui as rotinas de inicialização que permitiriam que
ela fosse iniciada como um programa independente (Figura 4-5).

Símbolos de exportação

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 45/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 4-5. Construindo a biblioteca dinâmica

Aqui estão algumas notas a serem consideradas:

• No Windows, construir uma biblioteca dinâmica requer estritamente que todas as referências sejam resolvidas. Se o código da biblioteca dinâmica chamar uma
função em alguma outra biblioteca dinâmica, essa outra biblioteca e o símbolo de referência que ela contém devem ser conhecidos no momento da construção.

• No Linux, no entanto, a opção padrão permite um pouco mais de flexibilidade, permitindo que alguns símbolos não sejam resolvidos com a expectativa de que
eventualmente apareçam no binário final depois que alguma outra biblioteca dinâmica for vinculada. Além disso, o vinculador Linux fornece o opção para
corresponder totalmente à rigidez do vinculador do Windows.

• No Linux, é possível modificar a biblioteca dinâmica para torná-la executável por si mesma (ainda pesquisando se tal opção existe no Windows). Na verdade, a
libc (biblioteca de tempo de execução C) é executável por si só; quando invocado digitando seu nome de arquivo na janela do shell, ele imprime uma mensagem
na tela e termina. Para obter mais detalhes sobre como implementar esse recurso, consulte o Capítulo 14.

Parte 2: Jogando pela confiança ao construir o executável do cliente (procurando apenas os símbolos)

A próxima etapa no cenário de uso de uma biblioteca dinâmica acontece quando você tenta construir o executável que pretende usar a biblioteca dinâmica em
tempo de execução. Ao contrário do cenário de bibliotecas estáticas em que o vinculador está criando o executável por conta própria à vontade, o cenário de
vinculação das bibliotecas dinâmicas é peculiar, pois o vinculador tenta combinar seu trabalho atual com os resultados existentes do procedimento de vinculação
concluído anteriormente que criou binário de biblioteca dinâmica.

O detalhe crucial nesta parte da história é que o vinculador presta quase toda a sua atenção aos símbolos da biblioteca dinâmica. Parece que, neste estágio, o
vinculador quase não está interessado em nenhuma das seções, nem em código (.texto), nem em dados (.data / .bss).

Mais especificamente, o vinculador neste estágio de operação "joga por confiança".

Ele não examina o binário da biblioteca dinâmica completamente; ele não tenta encontrar as seções ou seus tamanhos, nem tenta integrá-los no binário resultante.
Em vez disso, ele tenta apenas verificar se a biblioteca dinâmica contém os símbolos necessários para o binário resultante. Depois de encontrá-lo, ele conclui a
tarefa e cria o binário executável (consulte a Figura 4-6).

Figura 4-6. Construir tempo de vinculação com uma biblioteca dinâmica

A abordagem de "jogar por confiança" não é completamente não intuitiva. Vamos considerar um exemplo da vida real: se você disser a alguém que para enviar
uma carta, ele precisa ir ao quiosque na praça próxima e comprar um selo postal, você está basicamente baseando seu conselho em uma quantidade razoável de
confiança. Você sabe que deveria haver um quiosque na praça e que ele tem selos postais. O fato de você não saber os detalhes particulares do funcionamento do
quiosque (horário de funcionamento, quem trabalha lá, o preço dos selos) não diminui a validade do seu conselho, pois em tempo de execução todos esses
detalhes menos importantes serão resolvidos. A ideia de link dinâmico é baseada em suposições completamente análogas.

Note, entretanto, que essa quantidade de confiança deixa portas abertas para muitos cenários interessantes, todos os quais se enquadram no paradigma "construir
com um, carregar o outro". As implicações práticas variam de truques peculiares de design de software até todo o novo paradigma (plug-ins), ambos os quais
serão discutidos posteriormente neste livro.

Parte 3: carregamento em tempo de execução e resolução de símbolos

Os eventos que acontecem no momento do carregamento são de importância crucial, pois este é o momento em que a confiança que o vinculador tinha nas
promessas da biblioteca dinâmica precisa ser confirmada. Anteriormente, o procedimento de construção (possivelmente concluído em uma máquina de
construção "A") examinava a cópia do binário da biblioteca dinâmica na busca por símbolos que o executável precisa. Agora, o que precisa acontecer no tempo de
execução (possivelmente em uma máquina de tempo de execução "B" diferente) é o seguinte:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 46/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
1. O arquivo binário da biblioteca dinâmica precisa ser encontrado.

Cada sistema operacional possui um conjunto de regras que estipulam em qual diretório o carregador deve procurar os binários das bibliotecas dinâmicas.

2. A biblioteca dinâmica precisa ser carregada com sucesso no processo.

Este é o momento em que a promessa de vinculação em tempo de construção deve ser cumprida em tempo de execução.

Na verdade, a biblioteca dinâmica carregada em tempo de execução deve conter o conjunto idêntico de símbolos prometidos para estar disponíveis em tempo de construção.
Mais especificamente, no caso de símbolos de função, o termo "idêntico" significa que os símbolos de função encontrados na biblioteca dinâmica em tempo de
execução devem corresponder exatamente à assinatura de função completa (afiliações, nome, lista de argumentos, ligação / convenção de chamada) prometida na
construção Tempo.

Curiosamente, ele é não exigido que o código de montagem real (ou seja, o conteúdo seções) da biblioteca dinâmica encontrada em tempo de execução
corresponde ao código encontrado no binário biblioteca dinâmica usada durante o tempo de compilação. Isso abre muitos cenários interessantes que serão
discutidos em detalhes mais adiante.

3. Os símbolos executáveis ​precisam ser resolvidos para apontar para o endereço correto na parte do processo do mapa de memória onde a biblioteca dinâmica é
mapeada.

É neste estágio que a integração da biblioteca dinâmica no mapa de memória do processo realmente merece ser chamada de vinculação dinâmica, pois , ao
contrário da vinculação convencional, ela ocorre no momento do carregamento.

Se todas as etapas deste estágio forem concluídas com sucesso, você pode ter seu aplicativo executando o código contido na biblioteca dinâmica, conforme
ilustrado na Figura 4-7.

Peculiaridades da ligação dinâmica no Windows

Como é verdade que a vinculação dinâmica acontece em duas fases (tempo de construção vs. tempo de execução) nas quais o vinculador se concentra em
diferentes detalhes do binário da biblioteca dinâmica, não há nenhuma razão substancial para que a mesma cópia idêntica do binário da biblioteca dinâmica não
pudesse não pode ser usado em ambas as fases.

Mesmo que na fase de vinculação dinâmica em tempo de construção apenas os símbolos da biblioteca desempenhem uma função, não há nada de errado se a
mesma cópia exata do arquivo binário for usada na fase de tempo de execução também.

Este princípio é seguido principalmente em uma variedade de sistemas operacionais, incluindo Linux. No Windows, no entanto, na tentativa de tornar mais clara
a separação entre os estágios de vinculação dinâmica, as coisas são um pouco mais complicadas de uma forma que pode confundir um pouco os iniciantes.

Tipos especiais de arquivos binários relacionados à vinculação dinâmica no Windows

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 47/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
No Windows, a distinção entre as diferentes fases da vinculação dinâmica é acentuada pelo uso de tipos de arquivos binários ligeiramente diferentes em cada uma
das fases. Ou seja, quando um projeto DLL do Windows é criado e compilado, o compilador produz vários arquivos diferentes.

Biblioteca dinamicamente vinculada (.dll)

Este tipo de arquivo é na verdade a biblioteca dinâmica, um objeto compartilhado usado em tempo de execução pelos processos por meio do mecanismo de
vinculação dinâmica. Mais especificamente, a maioria dos fatos apresentados até agora sobre os princípios nos quais as funções da biblioteca dinâmica são
totalmente aplicáveis ​aos arquivos DLL.

Importar arquivo de biblioteca (.lib)

Um arquivo binário de biblioteca de importação dedicado (.lib) é usado no Windows especificamente na fase "Parte 2" da vinculação dinâmica (Figura 4-8). Ele
contém apenas a lista de símbolos DLL e nenhuma de suas seções de vinculador, e seu objetivo é apenas apresentar o conjunto de símbolos exportados da
biblioteca dinâmica para o binário do cliente.

Biblioteca de importação

Figura 4-8. Biblioteca de importação do Windows

A extensão do arquivo da biblioteca de importação (.lib) é a fonte potencial de confusão, pois a mesma extensão de arquivo também é usada para indicar as
bibliotecas estáticas.

Outro detalhe que merece um pouco de discussão é o fato desse arquivo ser denominado biblioteca de importação, mas na verdade desempenha um papel no
processo de exportação dos símbolos DLL. Como é verdade que a escolha da nomenclatura depende do lado de onde olhamos para o processo de vinculação
dinâmica, também é verdade que este arquivo pertence ao projeto DLL, é criado pela construção do projeto DLL, e pode ser disseminado para inúmeras
aplicações. Por todos esses motivos, não deve ser errado adotar a direção "da DLL para fora" e, portanto, usar o nome biblioteca de exportação.

A prova óbvia de que outra pessoa na Microsoft compartilhava desse ponto de vista, pelo menos até certo ponto, pode ser encontrada no

seção que discute o uso da palavra-chave of_ declspec , onde a nomenclatura (_ declspec (dllexport)) é usada para indicar

exportar do DLL para os aplicativos cliente (ou seja, na direção externa).

Um dos motivos pelos quais o pessoal da Microsoft decidiu manter essa convenção de nomenclatura específica é que o projeto DLL produz outro tipo de arquivo
de biblioteca que pode ser usado em vez deste nos cenários de dependências circulares. Esse outro tipo de arquivo é chamado de arquivo de exportação (.exp)
(veja abaixo), e para distinguir entre os dois, a nomenclatura existente foi mantida.

Exportar arquivo (.exp)

O arquivo de exportação tem a mesma natureza do arquivo de biblioteca de importação . No entanto, é normalmente usado no cenário em que dois executáveis ​têm
dependências circulares que impossibilitam a conclusão da construção de qualquer um. Nesse caso, o arquivo exp é fornecido com a intenção de possibilitar que
pelo menos um dos binários seja compilado com êxito, o que, por sua vez, pode ser usado por outros binários dependentes para concluir suas compilações.

■ Nota As DLLs do Windows são estritamente necessárias para resolver todos os símbolos no momento da criação. no Linux, no entanto, é possível deixar
alguns símbolos de biblioteca dinâmica não resolvidos com a expectativa de que os símbolos ausentes eventualmente apareçam no mapa de memória do
processo como resultado da vinculação dinâmica em algumas outras bibliotecas dinâmicas.

Natureza Única da Biblioteca Dinâmica

É importante entender desde o início que, no conjunto de tipos binários, a biblioteca dinâmica tem uma natureza bastante única, cujos detalhes são importantes
para se ter em mente ao lidar com os problemas de design relacionados usuais.

Ao olhar para os outros tipos binários, as naturezas opostas dos executáveis ​e das bibliotecas estáticas tornam-se óbvias quase imediatamente. A criação de uma
biblioteca estática não envolve a etapa de vinculação, enquanto no caso do executável é a última etapa obrigatória. Como consequência, a natureza do executável é
muito mais completa, pois contém referências resolvidas e, devido às rotinas de início extras incorporadas, está pronto para execução.

Nesse sentido, apesar da palavra "biblioteca", que sugere semelhanças entre as bibliotecas estáticas e dinâmicas, é o fato de que a natureza da biblioteca dinâmica
está muito mais próxima da natureza do executável.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 48/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Propriedade 1: a criação de biblioteca dinâmica requer o procedimento de compilação completo


O processo de criação da biblioteca dinâmica envolve não apenas a compilação, mas também o estágio de vinculação. Apesar do que as semelhanças de
nomenclatura sugerem, a integridade do processo de construção da biblioteca dinâmica (ou seja, vincular além de compilar) torna a biblioteca dinâmica muito
mais semelhante ao executável do que à biblioteca estática. A única diferença é que o executável contém o código de inicialização que permite ao kernel iniciar o
processo. É definitivamente possível (no Linux, com certeza) adicionar algumas linhas de código à biblioteca dinâmica que tornam possível executar a biblioteca a
partir da linha de comando como se fosse um tipo binário executável. Para obter mais detalhes, consulte o Capítulo 14.

Propriedade 2: a biblioteca dinâmica pode ser vinculada a outras bibliotecas


Este é um fato realmente interessante: não é apenas o executável que pode carregar e vincular a biblioteca dinâmica, mas também pode ser outra biblioteca
dinâmica. Conseqüentemente, não podemos mais dizer "executável" para indicar o binário vinculado a uma biblioteca dinâmica; devemos usar outro termo mais
apropriado.

■ Nota Decidi, a partir de agora, usar o termo "binário do cliente" para indicar o executável ou a biblioteca dinâmica que carrega uma biblioteca dinâmica.

Interface Binária do Aplicativo (ABI)

Quando o conceito de interface é aplicado ao domínio das linguagens de programação, ele é normalmente usado para denotar a estrutura dos ponteiros de função.
C ++ adiciona alguns significados extras, definindo-o como uma classe de ponteiros de função; além disso, ao declarar os ponteiros de função como iguais a NULL,
a interface recebe um impulso extra de abstração, pois se torna inadequada para instanciação, mas pode ser usada como modelo idealista para outras classes
implementarem.

A interface exportada por um módulo de software para os clientes é normalmente chamada de interface de programação de aplicativo (API). Quando aplicado ao
domínio de binários, o conceito de uma interface obtém um sabor específico de domínio adicional chamado interface binária de aplicativo (ABI). Não é errado
pensar na ABI como um conjunto de símbolos (principalmente um conjunto de pontos de entrada de função) criado no processo de compilação / vinculação da
interface do código-fonte.

O conceito de ABI é útil para explicar com mais precisão o que acontece durante a vinculação dinâmica.

• Durante a primeira fase (tempo de construção) da vinculação dinâmica, o binário do cliente de fato é vinculado à ABI exportada da biblioteca.

Como mencionei, no momento da construção, o binário do cliente, na verdade, apenas verifica se a biblioteca dinâmica exporta os símbolos (ponteiros de função,
como o ABI) e não se importa em nada com as seções (os corpos da função).

• Para concluir com êxito a segunda fase (tempo de execução) de vinculação dinâmica, o espécime binário da biblioteca dinâmica disponível em tempo de
execução deve exportar a ABI inalterada, idêntica à encontrada em tempo de construção.

A segunda afirmação é considerada o requisito básico da vinculação dinâmica.

Pontos de comparação de bibliotecas estáticas e dinâmicas


Embora eu mal tenha tocado nos conceitos por trás das bibliotecas estáticas e dinâmicas, algumas comparações entre as duas já podem ser feitas.

Diferenças nos critérios de seletividade de importação

A diferença mais interessante entre as bibliotecas estáticas e dinâmicas é a diferença nos critérios de seletividade aplicados por um binário cliente que tenta
vinculá-las.

Critérios de seletividade de importação para bibliotecas estáticas


Quando o binário do cliente vincula a biblioteca estática, ele não vincula o conteúdo completo da biblioteca estática. Em vez disso, ele vincula estrita e
exclusivamente os arquivos-objeto que contêm os símbolos realmente necessários, conforme mostrado na Figura 4-9.

Cliente binário

c =? h =?

-O 9

abcdefghij

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 49/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

? rnmrn
c
Biblioteca estática

Figura 4-9. Critérios de seletividade de importação para bibliotecas estáticas

O comprimento do byte do binário do cliente aumenta, embora apenas pela quantidade de código relevante ingerido da biblioteca estática.

■ Nota Apesar do fato de que o algoritmo de vinculação é seletivo ao escolher quais arquivos de objeto para vincular, a seletividade não vai além da
granularidade de arquivos de objeto individuais. Ainda pode acontecer que além dos símbolos realmente necessários, o arquivo objeto escolhido contenha
alguns símbolos desnecessários.

Critérios de seletividade de importação para bibliotecas dinâmicas

Quando o binário do cliente vincula a biblioteca dinâmica, ele apresenta a seletividade apenas no nível da tabela de símbolos, na qual apenas os símbolos da
biblioteca dinâmica realmente necessários são mencionados na tabela de símbolos.

Em todos os outros aspectos, a seletividade é praticamente inexistente. Independentemente de quão pequena uma parte da funcionalidade da biblioteca dinâmica
seja concretamente necessária, toda a biblioteca dinâmica é vinculada dinamicamente (Figura 4-10).

Cliente binário

99

abcdefghij

Biblioteca dinâmica

Figura 4-10. Critérios de seletividade de importação para bibliotecas dinâmicas

O aumento da quantidade de código só acontece em tempo de execução. O comprimento do byte do binário do cliente não aumenta significativamente. Os bytes
extras necessários para a contabilidade de novos símbolos tendem a somar pequenas contagens de bytes. No entanto, vincular a biblioteca dinâmica impõe o
requisito de que o binário da biblioteca dinâmica precisa estar disponível em tempo de execução na máquina de destino.

Cenário de importação de arquivo completo

Uma reviravolta interessante ocorre quando a funcionalidade da biblioteca estática precisa ser apresentada aos clientes binários por meio da biblioteca dinâmica
intermediária (Figura 4-11).

Cliente binário

abcdefghij OQOO OOOOOO

Biblioteca dinâmica

abcdefghij î l î l î l î l î l î l î l î l î l î

Biblioteca estática

Figura 4-11. Cenário de "arquivo inteiro" de importação de biblioteca estática

A própria biblioteca dinâmica intermediária não precisa de nenhuma funcionalidade da biblioteca estática. Portanto, de acordo com as regras de seletividade de
importação formuladas, ele não vinculará em nada da biblioteca estática. Ainda, a única razão pela qual a biblioteca dinâmica é projetada é ingerir a
funcionalidade da biblioteca estática e exportar seus símbolos para o resto do mundo usar.

Como mitigar esses requisitos opostos?

Felizmente, esse cenário foi identificado antecipadamente e o suporte de vinculador adequado foi fornecido por meio do sinalizador de vinculador --whole-
archive . Quando especificado, este sinalizador de vinculador indica que uma ou mais bibliotecas listadas a partir de então serão totalmente vinculadas
incondicionalmente, independentemente de o binário do cliente que as vincula precisar ou não de seus símbolos.
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 50/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Em reconhecimento a este cenário, o sistema de desenvolvimento nativo Android, além de suportar a variável de construção LOCAL_STATIC_LIBRARIES , o
sistema de construção nativo também suporta a variável de construção LOCAL_WHOLE_STATIC_LIBRARIES , assim:

$ gcc -fPIC <arquivos de origem> -Wl, - whole-archive -l <bibliotecas estáticas> -o <nome do arquivo shlib>

Curiosamente, há um sinalizador de linker de contra-ação (--no-whole-archive). Seu efeito é neutralizar o efeito de --whole-archive para todas as
bibliotecas subsequentes sendo especificadas para vinculação na mesma linha de comando do vinculador.

$ gcc -fPIC <arquivos de origem> -o <arquivo de saída executável> \

-Wl, - whole-archive -l <libraries-to-be-entirely-linked-in> \ -Wl, - no-whole-archive -l <all-other-libraries>

De natureza semelhante ao sinalizador - -whole-archive é o sinalizador do vinculador -rdynamic . Ao passar esse sinalizador de vinculador, você
basicamente está solicitando que o vinculador exporte todos os símbolos (presentes na seção .symtab ) para a seção dinâmica (.dynsym) , o que basicamente os
torna utilizáveis ​para fins de vinculação dinâmica. Curiosamente, esse sinalizador não parece exigir o prefixo -Wl .

Cenários de dilema de implantação

Ao projetar os pacotes de implantação de software, os engenheiros de construção normalmente enfrentam um requisito para minimizar o tamanho em bytes do
pacote de implantação. Em um dos cenários mais simples possíveis, o produto de software que precisa ser implantado é composto de um executável que delega a
tarefa de fornecer certa parte de sua funcionalidade a uma biblioteca. Digamos que a biblioteca possa vir em ambos os tipos, a biblioteca estática e a dinâmica. A
questão básica que o engenheiro de compilação enfrenta é que tipo de cenários de vinculação utilizar para minimizar o tamanho do byte do pacote de software
implantado.

Opção 1: vinculando a uma biblioteca estática

Uma das opções que um engenheiro de compilação enfrenta é vincular o executável à versão estática da biblioteca. Essa decisão vem com prós e contras.

• Prós: o executável é totalmente autocontido, pois carrega todo o código de que precisa.

• Contras: o tamanho do byte executável é aumentado pela quantidade de código ingerido da biblioteca estática.

Opção 2: vincular a uma biblioteca dinâmica


Outra possibilidade, é claro, é vincular o executável à versão dinâmica da biblioteca. Essa decisão também traz prós e contras.

• Prós: O tamanho do byte executável não é alterado (exceto talvez pela pequena despesa de contabilidade de símbolos).

• Contras: sempre há uma chance de que a biblioteca dinâmica necessária, por qualquer motivo, não esteja fisicamente disponível na máquina de destino. Se
precauções forem tomadas e a biblioteca dinâmica necessária for implantada junto com o executável, vários problemas potenciais podem surgir.

• Primeiro, o tamanho geral em bytes do pacote de implantação definitivamente fica maior, pois agora você implanta um executável e uma biblioteca dinâmica.

• Em segundo lugar, a versão da biblioteca dinâmica implantada pode não corresponder aos requisitos de outros aplicativos que podem depender dela.

• Terceiro, quarto e assim por diante, há todo um conjunto de problemas que podem acontecer ao lidar com as bibliotecas dinâmicas, conhecido pelo nome de
"DLL hell".

Veredicto Final

A vinculação com bibliotecas estáticas é uma boa escolha quando o aplicativo é vinculado a porções relativamente menores de um número relativamente menor
de bibliotecas estáticas.

A vinculação com bibliotecas dinâmicas vem como uma boa escolha quando o aplicativo depende das bibliotecas dinâmicas esperadas com grande certeza que
existam em tempo de execução na máquina de destino.

Os prováveis ​candidatos são bibliotecas dinâmicas específicas do sistema operacional, como biblioteca de tempo de execução C, subsistemas gráficos, drivers de
dispositivo de nível superior do espaço do usuário e / ou bibliotecas provenientes de pacotes de software muito populares. A Tabela 4-1 resume as diferenças entre
lidar com bibliotecas estáticas e dinâmicas.

Tabela 4-1. Resumo de pontos de comparação

Categoria de
Bibliotecas estáticas Bibliotecas Dinâmicas
Comparação
Procedimento
Incompleto: Completo:
de construção
Compilando:
Compilando: simLinking: sim
simLinking: não
Natureza do Arquivo de arquivo (s)
O executável sem a inicialização
binário objeto
Todas as seções existem, rotinas.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 51/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
mas a maioria das
referências não estão
Contém referências resolvidas (exceto quando
resolvidas (exceto local

referências). pretendido de outra forma), alguns dos quais são

Não pode existir


autônomo; as pretende ser globalmente visível.
circunstâncias
do binário do cliente
Muito independente (no Linux, com alguns
determina muitos

detalhes. adições simples a inicialização ausente

Todos os seus símbolos


têm algum significado rotinas podem ser efetivamente adicionadas).
apenas
dentro do executável do Altamente especializado em determinadas tarefas estratégicas; uma vez carregado no processo, normalmente muito
cliente. confiável e confiável para fornecer serviços especializados.
Integração Acontece durante a
Acontece por meio de duas fases distintas de
com construção executável
processo, concluído
Executável link dinâmico:
durante a vinculação

estágio. 1) Vinculação com os símbolos disponíveis

Eficiente: apenas os
arquivos de objeto 2) Símbolos e integração de seções em
necessários

do arquivo é vinculado tempo de carregamento


ao executável. Ineficiente: a biblioteca completa obtém

O tamanho do byte do
carregado no processo, independentemente de
binário do cliente obtém
qual parte da biblioteca é realmente necessária. O tamanho do byte do binário do cliente quase não muda. No
aumentou, no entanto. entanto, a disponibilidade do binário da biblioteca dinâmica em tempo de execução é uma coisa a mais com que se
preocupar.
Impacto no Aumenta o tamanho do
Reduz o tamanho do executável, pois apenas
executável executável, como seções
seja adicionado às seções o código específico do aplicativo reside no executável do aplicativo, enquanto as partes compartilháveis ​são extraídas
Tamanho
executáveis. para a biblioteca dinâmica.
Excelente, pois tudo que
Portabilidade Varia.
o aplicativo precisa é

dentro de seu binário. Bom para bibliotecas dinâmicas padrão do sistema operacional

A ausência de
(libc, drivers de dispositivo, etc.), como eles são
dependências externas
garantia de existência na máquina de tempo de execução.

torna a portabilidade Menos bom para cenários específicos do aplicativo ou do fornecedor.


fácil.
Existem muitos cenários para problemas potenciais (controle de versão, bibliotecas ausentes, caminhos de pesquisa,
etc.)

(contínuo)

Tabela 4-1. (contínuo)

Categoria de
Bibliotecas estáticas Bibliotecas Dinâmicas
Comparação
Facilidade de
Muito limitado. Excelente.
combinação
Não é possível criar uma
biblioteca estática usando Uma biblioteca dinâmica pode se conectar a um ou mais
o
outras bibliotecas (nem
bibliotecas estáticas e / ou uma das mais dinâmicas
estáticas nem dinâmicas).
Só pode ligar todos eles
bibliotecas.
no
Na verdade, o Linux pode ser visto como "Legoland", um conjunto de construções feitas por bibliotecas
mesmo executável. dinâmicas vinculadas a outras bibliotecas dinâmicas. A magnitude da integração é muito facilitada pela
disponibilidade do código-fonte.
Facilidade de Bastante fácil. Praticamente impossível para a maioria dos mortais.
conversão
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 52/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

A função padrão do
Algumas soluções comerciais foram vistas
arquivador
utilidade é a extração do
aquela tentativa com vários graus de sucesso
objeto ingrediente
arquivos. Uma vez
para implementar a conversão de uma dinâmica
extraídos, eles podem ser
eliminado, substituído ou
para uma biblioteca estática.
recombinado
em uma nova biblioteca
estática ou dinâmica.
Apenas em casos muito
excepcionalmente
especiais (em
a seção "Dicas e truques",
consulte o tópico
sobre ligar a biblioteca
estática a uma dinâmica
lib no Linux de 64 bits)
isso pode não ser bom
o suficiente, e você pode
precisar recompilar

as fontes originais.

Adequado para Pesado. Excelente.


Mesmo as menores
Desenvolvimento A melhor maneira de trabalhar em um recurso isolado
mudanças no código
requer a recompilação de
é extraí-lo para a biblioteca dinâmica.
todos os executáveis ​que
Desde que os símbolos exportados (assinaturas de função e / ou layouts de estrutura de dados) não sejam
vincular a biblioteca.
alterados, a recompilação da biblioteca não requer a recompilação do restante do código.
Forma mais simples,
Diversos / Outros Nova forma de reutilização de código binário.
mais antiga e onipresente
de compartilhamento
O sistema multitarefa moderno nem pode ser
binário aplicado mesmo
no microcontrolador
imaginado sem eles.
mais simples
ambientes de
Essencial para o conceito de plug-ins.
desenvolvimento.

Analogias de comparação úteis


As tabelas 4-2 a 4-4 listam várias analogias muito úteis e ilustrativas, que podem ajudá-lo a entender melhor a função do processo de compilação.

Tabela 4-2. Analogia Legal

Tipo binário _ Equivalente legal _

Biblioteca estática O parágrafo da lei

Em geral, é escrito de uma forma indeterminista. Por exemplo: se uma pessoa (que pessoa?) For condenada por cometer uma contravenção de classe A (que
contravenção específica? O que exatamente a pessoa fez?), Ela será condenada a pagar uma multa que não exceda 2.000 dólares (exatamente como muito?), ou para
cumprir a pena de prisão não superior a 6 meses (exatamente quanto tempo?) ou ambos (qual das três combinações possíveis?).

Acusação de concreto da biblioteca dinâmica

John Smith é condenado por resistir à prisão e desobedecer ao policial. A promotoria pede que ele pague uma multa de US $ 1.500 e passe 30 dias na prisão.

Executável cumprindo a sentença

Todas as referências (quem, o quê, quando e possivelmente por quê) são resolvidas: as violações da lei foram provadas no tribunal, o juiz sentenciou John Smith de
acordo com a letra da lei e tudo está pronto para ele cumprir sua pena na instalação de correção estadual próxima.

Tabela 4-3. Analogia Culinária

Tipo binário _ Equivalente culinário _

Biblioteca estática Ingredientes de alimentos crus (por exemplo, carne crua ou vegetais crus)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 53/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Definitivamente adequados para o consumo, mas não podem ser servidos imediatamente, pois precisam de uma certa quantidade de processamento (marinar,
adicionar especiarias, combinar com outros ingredientes e, o mais importante, processamento térmico) que deve ser concluído primeiro.

Biblioteca Dinâmica Prato pré-cozido ou pronto

Pronto para o consumo, mas servir como está não faz muito sentido. No entanto, se o resto do almoço estiver pronto, será um ótimo complemento para a refeição
servida.

Curso de almoço completo executável

Consiste no pão fresco do dia, na salada da hora e no prato principal preparado, que pode ser enriquecido com uma certa quantidade de um prato aquecido
cozinhado há poucos dias.

Tabela 4-4. Analogia da expedição na selva tropical

Tipo binário _ Equivalente de função de expedição _

Senhor britânico executável, o líder da expedição

Veterano de combate condecorado, conhecido por suas excelentes habilidades e instintos de sobrevivência. Designado pela British Geographic Society para
investigar rumores de que nas profundezas das selvas tropicais existem templos de civilizações avançadas há muito perdidas, escondendo numerosos tesouros
materiais e científicos. Ele tem direito a apoio logístico do departamento consular britânico local, que se encarrega de coordenar os esforços com as autoridades
locais e oferece todo tipo de ajuda com suprimentos, dinheiro, logística e transporte.

Dynamic Library Local hunter, o guia de expedição

Esse cara nasceu e cresceu na área geográfica alvo da expedição. Ele fala todas as línguas locais, conhece todas as religiões e culturas tribais; tem muitas conexões
pessoais na área; conhece todos os lugares perigosos e como evitá-los; tem habilidades de sobrevivência excepcionais; é um bom caçador, excelente desbravador e
pode prever as mudanças climáticas. Altamente especializado em tudo relacionado à selva, podendo cuidar de si mesmo na íntegra. A maior parte de seu tempo
adulto foi gasto como guia contratado para expedições como esta. Entre as expedições, ele não faz quase nada, a não ser passar o tempo com a família, ir pescar e
caçar, etc. Não tem ambição nem poder financeiro para começar nada sozinho.

Biblioteca estática, jovem assistente pessoal

Jovem rapaz britânico da família aristocrática. Pouca ou nenhuma experiência na vida real, mas o diploma de Oxford em arqueologia e conhecimento de línguas
antigas, bem como o conhecimento operacional de estenografia, telegrafia e código Morse, rendeu a ele um lugar na equipe. Embora suas habilidades sejam
potencialmente aplicáveis ​a muitas funções e muitos cenários, ele nunca esteve nas áreas tropicais, não fala as línguas locais e, na maior parte, dependerá de
autoridade superior e / ou especialização superior de vários tipos. Muito provavelmente, ele não terá autoridade formal sobre o curso da expedição e nenhum
poder de tomar decisões de qualquer tipo, exceto no domínio de sua especialidade imediata e apenas quando solicitado a fazê-lo.

■ Nota Na analogia culinária, você (o designer do software) está administrando um restaurante no qual (por meio do processo de construção dos
executáveis) prepara uma refeição para o CPU faminto que mal espera para começar a mastigar a refeição.

A conclusão: o impacto do conceito de reutilização binária


Assim que se provou que o conceito de reutilização binária funcionava, ele teve as seguintes consequências imediatas para o panorama do design de software:

• O aparecimento de projetos dedicados cuja intenção não é construir código executável, mas sim construir um pacote binário de código reutilizável.

• Assim que a prática de construir o código para outros usarem começou a ganhar impulso, a necessidade de seguir o princípio do encapsulamento passou a se
destacar.

A essência da ideia de encapsulamento é que, se estamos construindo algo para que outros usem, é sempre bom se esses produtos de exportação vierem com
recursos essenciais claramente separados dos detalhes de funcionalidade interna menos importantes. Uma das maneiras obrigatórias de conseguir isso é declarar a
interface, o conjunto de funções essenciais que o usuário está mais interessado em usar.

• A interface (conjunto de funções quintessenciais / mais importantes) é normalmente declarada no arquivo de cabeçalho de exportação (um arquivo de inclusão
que fornece a interface de nível superior entre o código binário reutilizável e o usuário potencial).

Resumindo, a maneira de distribuir o código para que outros usem é entregar o pacote de software que contém o conjunto de arquivos binários e o conjunto de
arquivos de cabeçalho de exportação. Os arquivos binários exportam a interface, principalmente o conjunto de funções quintessenciais para usar o pacote.

A próxima onda de consequências ocorreu imediatamente depois disso:

• O aparecimento de SDKs (kits de desenvolvimento de software), que são, na versão mais básica,

um conjunto de cabeçalhos e binários de exportação (bibliotecas estáticas e / ou dinâmicas) destinados à integração com binários criados durante a compilação de
arquivos de origem originais do projeto do cliente.

• O surgimento do paradigma "um motor, variedade de GUIs".

Existem muitos exemplos em que o mecanismo popular é usado por diferentes aplicativos, apresentando as diferentes GUIs ao usuário, mas executando o mesmo
mecanismo (carregado das mesmas bibliotecas dinâmicas) em segundo plano. Exemplos típicos no domínio da multimídia são ffmpeg e avisynth.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 54/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• Um potencial para troca controlada de propriedades intelectuais.

Ao entregar binários em vez de código-fonte, as empresas de software podem entregar sua tecnologia sem revelar as ideias por trás dela. A disponibilidade de
desmontadores torna essa história um pouco mais complicada, mas no longo prazo a ideia básica ainda se aplica.

CAPÍTULO 5

Trabalho com bibliotecas estáticas


Neste capítulo, revisarei o ciclo de vida típico ao lidar com bibliotecas estáticas. Iniciarei com diretrizes simples para a criação de biblioteca estática, depois
apresentarei uma visão geral dos cenários de casos de uso típicos e, por fim, examinarei mais de perto algumas dicas e truques de design de nível especialista.

Criação de biblioteca estática


Uma biblioteca estática é criada quando os arquivos-objeto criados pelo compilador a partir do conjunto de arquivos de origem são agrupados em um único
arquivo. Essa tarefa é executada por uma ferramenta chamada arquivador.

Criação de biblioteca estática do Linux

No Linux, a ferramenta de arquivamento, chamada simplesmente ar, está disponível como parte da cadeia de ferramentas do GCC. O exemplo simples a seguir
demonstra o processo de criação de biblioteca estática a partir de dois arquivos de origem:

$ gcc -c first.c second.c $ ar rcs libstaticlib.a first.o second.o

Pela convenção do Linux, os nomes das bibliotecas estáticas começam com o prefixo lib e têm a extensão de arquivo .a. Além de realizar sua tarefa básica de
agrupar os arquivos objeto no arquivo (biblioteca estática), o ar pode realizar várias tarefas adicionais:

• Remova um ou mais arquivos de objeto da biblioteca.

• Substitua um ou mais arquivos de objeto da biblioteca.

• Extraia um ou mais arquivos de objeto da biblioteca.

A lista completa de recursos suportados pode ser encontrada na página do manual da ferramenta ar ( http://linux.die.net/man/l7ar ).

Criação de uma biblioteca estática do Windows

A tarefa de criar a biblioteca estática no Windows não difere substancialmente da mesma tarefa executada no Linux. Mesmo que possa ser concluído a partir da
linha de comando, o fato da vida é que na grande maioria dos casos a tarefa de criar a biblioteca estática é realizada criando um projeto Visual Studio dedicado (ou
outra ferramenta IDE semelhante) com a opção de construir a biblioteca estática. Ao examinar a linha de comando do projeto, você pode ver na Figura 5-1 que a
tarefa se resume essencialmente ao mesmo uso de uma ferramenta de arquivamento (embora seja uma versão do Windows).

Figura 5-1. Criação de uma biblioteca estática Win32

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 55/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Usando a biblioteca estática


Bibliotecas estáticas são usadas na fase de vinculação de projetos que criam executáveis ​ou bibliotecas dinâmicas. Os nomes das bibliotecas estáticas são
normalmente passados ​para o vinculador junto com a lista de arquivos-objeto que precisam ser vinculados. Se o projeto também for vinculado nas bibliotecas
dinâmicas, seus nomes farão parte da mesma lista de argumentos de entrada do vinculador.

Cenários de casos de uso recomendados

As bibliotecas estáticas são a forma mais básica de compartilhamento binário do código, que já estava disponível por muito tempo antes da invenção das
bibliotecas dinâmicas. Nesse ínterim, o paradigma mais sofisticado de bibliotecas dinâmicas assumiu o domínio do compartilhamento de código binário. No
entanto, ainda existem alguns cenários em que o recurso ao uso de bibliotecas estáticas ainda faz sentido.

As bibliotecas estáticas são perfeitamente adequadas para todos os cenários que implementam o núcleo de vários algoritmos (principalmente proprietários), desde
algoritmos elementares, como busca e classificação, até algoritmos científicos ou matemáticos muito complexos. Os seguintes fatores podem fornecer um impulso
extra para a decisão de usar a biblioteca estática como a forma de entrega do código:

• A arquitetura geral do código pode ser descrita mais como uma "ampla coleção de várias habilidades" em vez de um "módulo com a interface estritamente
definida".

• O cálculo real não depende de um recurso de sistema operacional específico (como um driver de dispositivo de placa gráfica ou temporizadores de sistema de
alta prioridade, etc.) que requer o carregamento de bibliotecas dinâmicas.

• O usuário final deseja usar seu código, mas não necessariamente deseja compartilhá-lo com mais ninguém.

• Os requisitos de implantação de código sugerem a necessidade de implantação monolítica (ou seja, pequeno número geral de arquivos binários entregues à
máquina do cliente).

Usar a biblioteca estática sempre significa controle mais rígido sobre o código, embora ao preço de flexibilidade reduzida. A modularidade é normalmente
reduzida e o aparecimento de novas versões de código normalmente significa recompilar cada aplicativo que o utiliza.

No domínio da multimídia, as rotinas de processamento de sinal (análise, codificação, decodificação, DSP) são normalmente entregues na forma de bibliotecas
estáticas. Por outro lado, sua integração com os frameworks de multimídia (DirectX, GStreamer, OpenMAX) são implementados na forma de bibliotecas
dinâmicas que se ligam nas bibliotecas estáticas relacionadas ao algoritmo. Neste esquema, as funções simples e estritas de se comunicar com a estrutura são
delegadas à camada fina da parte da biblioteca dinâmica, enquanto as complexidades de processamento de sinal pertencem à parte da biblioteca estática.

Dicas e truques para bibliotecas estáticas


A seção a seguir cobre a lista de dicas e truques importantes relacionados ao uso de bibliotecas estáticas.

Potencial de perda da visibilidade e exclusividade do símbolo

A maneira como o vinculador integra as seções e símbolos da biblioteca estática no binário do cliente é genuinamente bastante simples e direta. Quando
vinculadas ao binário do cliente, as seções da biblioteca estática combinam-se perfeitamente com as seções provenientes dos arquivos de objetos nativos do binário
do cliente. Os símbolos da biblioteca estática tornam-se parte da lista de símbolos binários do cliente e mantêm sua visibilidade original; os símbolos globais da
biblioteca estática tornam-se os símbolos globais do binário do cliente e os símbolos locais da biblioteca estática tornam-se os símbolos locais do binário do cliente.

Quando o binário do cliente é a biblioteca dinâmica (ou seja, não o aplicativo), o resultado dessas regras simples e diretas de integração pode ser comprometido
pelas regras de design de outras bibliotecas dinâmicas.

Onde está a reviravolta?

O pressuposto implícito no conceito de bibliotecas dinâmicas é a modularidade. Não é errado pensar em uma biblioteca dinâmica como um módulo que é
projetado para ser facilmente substituído quando surgir a necessidade. Para implementar adequadamente o conceito de modularidade, o código da biblioteca
dinâmica é tipicamente estruturado em torno da interface, o conjunto de funções que expõe a funcionalidade do módulo para o mundo externo, enquanto os
internos da biblioteca dinâmica são normalmente mantidos longe dos olhares curiosos de os usuários da biblioteca.

Por sorte, as bibliotecas estáticas são normalmente projetadas para fornecer o "coração e a alma" das bibliotecas dinâmicas. Independentemente de quão preciosa é
a contribuição da biblioteca estática para a funcionalidade geral de sua biblioteca dinâmica hospedeira, as regras de design das bibliotecas dinâmicas estipulam
que elas devem exportar (ou seja, tornar visíveis) apenas o mínimo necessário para a biblioteca se comunicar com o mundo exterior .

Como consequência direta de tais regras de design (como você verá nos próximos capítulos), a visibilidade dos símbolos da biblioteca estática acaba sendo
reduzida. Em vez de permanecerem visíveis globalmente (o que estavam imediatamente após a conclusão da vinculação), os símbolos da biblioteca estática
tornam-se imediatamente rebaixados para os privados ou podem até mesmo ser removidos (ou seja, completamente eliminados da lista de símbolos da biblioteca
dinâmica).

Por outro lado, um detalhe peculiar, mas muito importante, é que as bibliotecas dinâmicas gozam de total autonomia sobre seus símbolos locais. Na verdade,
várias bibliotecas dinâmicas podem ser carregadas no mesmo processo, cada biblioteca dinâmica apresentando símbolos locais que têm os mesmos nomes que os
símbolos locais de outras bibliotecas dinâmicas. No entanto, o vinculador consegue evitar quaisquer conflitos de nomenclatura.

A existência permitida de várias instâncias dos símbolos com o mesmo nome pode levar a uma série de consequências indesejadas. Um cenário é conhecido como
as múltiplas instâncias do paradoxo da classe singleton, que será ilustrado com mais detalhes no Capítulo 10.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 56/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Cenários de casos de uso contra-indicados

Digamos que você tenha um trecho de código que fornece certa funcionalidade e deve decidir se vai ou não encapsulá-lo na forma de uma biblioteca estática. Aqui
estão alguns cenários típicos em que o caso da biblioteca estática é contra-indicado:

• Quando a vinculação da biblioteca estática requer a vinculação de várias bibliotecas dinâmicas (exceto talvez a libc), então a biblioteca estática provavelmente
não deve ser usada, e a opção de biblioteca dinâmica correspondente deve ser favorecida.

A opção de biblioteca dinâmica correspondente pode significar um dos seguintes:

• A versão da biblioteca dinâmica existente da mesma biblioteca deve ser usada. ou

• O código-fonte da biblioteca (se disponível) deve ser reconstruído para criar a biblioteca dinâmica. ou

• A biblioteca estática disponível deve ser desmontada nos arquivos objeto, que (exceto em alguns casos raros) podem ser usados ​no projeto de construção que
constrói a biblioteca dinâmica.

Isso é completamente análogo à situação que acontece quando uma pessoa com necessidades especiais (hábitos alimentares especiais ou requisitos de condições
médicas / ambientais especiais) decide ficar na casa de um amigo ao visitar a cidade em que o amigo mora. Para que o amigo possa atender às necessidades
especiais do hóspede, ele precisa reorganizar significativamente sua vida cotidiana, a fim de fazer viagens extras incomuns às lojas de alimentos especializados, ou
fornecer condições especiais de que ele mesmo não precisa realmente no seu dia a dia. Faz muito mais sentido que o visitante assuma um papel mais
independente, como conseguir um quarto de hotel ou providenciar o suporte para suas necessidades específicas; e assim que suas próprias referências forem
resolvidas, entre em contato com o amigo cuja cidade ele está visitando.

• Se a funcionalidade que você implementa requer a existência de uma única instância de uma classe (padrão singleton), seguir as boas práticas de design de
biblioteca dinâmica acabará levando à forte sugestão de encapsular seu código em uma biblioteca dinâmica em vez de estática. A razão por trás disso foi explicada
no parágrafo anterior.

Um bom exemplo da vida real desse cenário é o projeto de um utilitário de registro. Normalmente apresenta uma única instância de uma classe visível para uma
variedade de módulos de funcionalidade, especializada em serializar todas as instruções de log possíveis e enviar o fluxo de log para a mídia de gravação (stdout,
disco rígido ou arquivo de rede, etc.).

Se os módulos de funcionalidade forem implementados como bibliotecas dinâmicas, é altamente recomendável hospedar a classe do criador de logs em outra
biblioteca dinâmica.

Regras específicas de vinculação de bibliotecas estáticas

A vinculação de bibliotecas estáticas no Linux segue o seguinte conjunto de regras:

• A vinculação de bibliotecas estáticas acontece sequencialmente, uma biblioteca estática por uma.

• A vinculação de bibliotecas estáticas começa na última biblioteca estática na lista de bibliotecas estáticas passadas para o vinculador (da linha de comando ou
por meio do makefile) e retrocede em direção à primeira biblioteca da lista.

• O vinculador pesquisa as bibliotecas estáticas em detalhes e, de todos os arquivos-objeto contidos na biblioteca estática, ele vincula apenas ao arquivo-objeto,
que contém símbolos realmente necessários ao binário do cliente.

Como resultado dessas regras específicas, às vezes é necessário especificar a mesma biblioteca estática mais de uma vez na mesma lista de bibliotecas estáticas transmitidas ao
vinculador. As chances de isso acontecer aumentam quando uma biblioteca estática fornece vários conjuntos de funcionalidades não relacionados.

Conversão de biblioteca estática em dinâmica

A biblioteca estática pode ser convertida em biblioteca dinâmica de forma bastante simples. Tudo que você precisa fazer é o seguinte:

• Use a ferramenta archiver (ar) para extrair todos os arquivos-objeto da biblioteca, como

$ ar -x <biblioteca estática> .a

que resulta com a coleção de arquivos-objeto extraídos da biblioteca estática para a pasta atual.

No Windows, você pode usar a ferramenta lib.exe que está disponível no console do Visual Studio. Com base na documentação online do MSDN (
http://support.microsoft.com/ kb / 31339) , é possível extrair pelo menos um arquivo de objeto (primeiro você precisa listar o conteúdo da biblioteca
estática, o que também pode ser obtido usando o ferramenta lib.exe ).

• Construir a biblioteca dinâmica a partir do conjunto de arquivos de objetos extraídos para o vinculador.

Esta receita funciona em quase todos os casos. Os casos especiais em que requisitos adicionais devem ser satisfeitos são apresentados a seguir.

Problemas de bibliotecas estáticas no Linux de 64 bits

O uso de bibliotecas estáticas no Linux de 64 bits vem com um cenário interessante de caso secundário. Aqui está o esboço:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 57/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• Vincular a biblioteca estática ao executável não difere de fazer a mesma coisa no Linux de 32 bits.

• No entanto, vincular a biblioteca estática à biblioteca compartilhada requer que a biblioteca estática seja construída com o sinalizador do compilador -fPIC
(sugerido pela impressão de erro do compilador) ou com o sinalizador do compilador -mcmodel = large .

Este é um cenário bastante interessante.

Primeiro, uma mera menção do sinalizador do compilador -fPIC no contexto de bibliotecas estáticas pode ser um pouco confuso. Como discutirei no próximo
capítulo, lidando com bibliotecas dinâmicas, o uso do sinalizador -fPIC tem sido tradicionalmente associado à construção de bibliotecas dinâmicas.

É uma crença popular que passar o sinalizador -fPIC para o compilador é um dos dois requisitos principais estritamente exigidos por bibliotecas dinâmicas, mas
nunca exigido para compilar bibliotecas estáticas. Qualquer menção ao sinalizador do compilador -fPIC no contexto de bibliotecas estáticas é um pouco chocante.

Na verdade, essa crença não é exatamente correta, mas é bastante segura contra erros. A verdade é que o uso do sinalizador -fPIC não é o fator decisivo se a
biblioteca estática ou dinâmica será criada; é o sinalizador de vinculador -shared .

De volta à dura realidade. A verdadeira razão pela qual o compilador insiste em compilar a biblioteca estática com o sinalizador -fPIC é que na plataforma de 64
bits o intervalo de deslocamentos de endereço não pode ser coberto pelas construções montadoras de compilador usuais em que os registros de 32 bits são usados.
O compilador precisa de uma espécie de kick (o uso de -fPIC ou o -mcmodel = sinalizadores de compilador grandes ) para implementar o mesmo código com
os registradores de 64 bits.

Resolvendo o problema em cenários da vida real


Não é completamente impossível obter o pacote de software projetado muito antes da era dos sistemas operacionais de 64 bits, nos quais a biblioteca estática foi
construída sem o sinalizador -fPIC (ou -mcmodel = large) . Além disso, as pessoas que fornecem suas bibliotecas estáticas não são necessariamente as
estrelas em lidar com os problemas relacionados a compiladores / vinculadores / bibliotecas / (ao contrário dos caras que completam a leitura deste livro;). Se você
teve sorte (como eu) em obter a biblioteca estática de desenvolvedores terceirizados que não estavam cientes desse cenário específico, há algumas más notícias:
não há solução fácil para esse tipo de problema.

Tentar desmontar a biblioteca estática no arquivo objeto não muda a situação nem por um pouquinho; os arquivos de objeto não foram compilados com os
sinalizadores de compilador necessários para este cenário específico, e nenhuma mágica de conversão de biblioteca pode ajudar a evitar a necessidade de
recompilar as fontes de biblioteca estática.

A única solução verdadeira para esse tipo de problema é que alguém que tem o código-fonte (o distribuidor do código ou o usuário final) modifica os parâmetros
de construção (edite o Makefile) adicionando os sinalizadores necessários ao conjunto de sinalizadores do compilador.

Se isso servir de consolo, imagine que você não tem o código-fonte da biblioteca. Agora, isso seria assustador, hein?

CAPÍTULO 6

Projetando Bibliotecas Dinâmicas: Noções


Básicas
O Capítulo 5 cobriu os detalhes das idéias básicas por trás do conceito de bibliotecas estáticas, então agora é hora de examinar os detalhes de como lidar com
bibliotecas dinâmicas. Isso é importante porque esses detalhes afetam o trabalho diário do programador / designer de software / arquiteto de software.

Criação da biblioteca dinâmica


Os compiladores e linkers normalmente fornecem uma grande variedade de sinalizadores que podem, no final das contas, fornecer muitos sabores para o processo
de construção de uma biblioteca dinâmica. Para que as coisas sejam realmente interessantes, mesmo a receita mais simples e amplamente usada que requer um
compilador e um sinalizador de vinculador pode não ser tão clara e simples quanto parece inicialmente, e uma análise mais profunda pode revelar um conjunto
de fatos realmente interessante. De qualquer forma, vamos começar do início.

Criação da biblioteca dinâmica no Linux

O processo de construção de bibliotecas dinâmicas tradicionalmente consiste no seguinte conjunto mínimo de sinalizadores:

• sinalizador do compilador -fPIC

• -shared linker flag

O exemplo simples a seguir demonstra o processo de criação da biblioteca dinâmica a partir de dois arquivos de origem:

$ gcc -fPIC -c first.c second.c $ gcc -shared first.o second.o -o libdynamiclib.so

Pela convenção do Linux, as bibliotecas dinâmicas começam com o prefixo lib e têm a extensão de nome de arquivo .so. Se você seguir esta receita, há poucas
chances de se perder. Se esses sinalizadores forem passados ​para o compilador e o vinculador, respectivamente, sempre que você pretende construir uma
biblioteca dinâmica, o resultado final será a biblioteca dinâmica correta e utilizável. No entanto, aceitar essa receita como verdade incontestável e universal não é a

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 58/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
coisa certa a fazer. Mais precisamente, por mais que não haja nada de errado em passar o sinalizador - shared para o vinculador, o uso do sinalizador do
compilador -fPIC é um tópico realmente intrigante, que merece atenção extra.

O restante desta seção será focado principalmente no lado do Linux (embora alguns dos conceitos também existam no Windows).

Sobre o sinalizador do compilador -fPIC

Os detalhes sobre o uso do sinalizador -fPIC podem ser melhor ilustrados por meio da sequência de perguntas e respostas a seguir.

Pergunta 1: O que significa -fPIC?

O "PIC" em -fPIC é a sigla para código independente de posição. Antes que o conceito de código independente de posição ganhasse destaque, era possível criar
bibliotecas dinâmicas que o carregador era capaz de carregar no espaço de memória do processo. No entanto, apenas o processo que carregou pela primeira vez a
biblioteca dinâmica poderia aproveitar os benefícios de sua presença; todos os outros processos em execução que precisavam carregar a mesma biblioteca
dinâmica não tinham escolha a não ser carregar outra cópia da mesma biblioteca dinâmica na memória. Quanto mais processos são necessários para carregar uma
biblioteca dinâmica específica, mais cópias devem existir na memória.

A causa subjacente de tais limitações era um projeto de procedimento de carregamento abaixo do ideal. Ao carregar a biblioteca dinâmica no processo, o
carregador alterou o segmento de código (.text) da biblioteca dinâmica de uma forma que tornou todos os símbolos da biblioteca dinâmica significativos
apenas dentro do domínio do processo que carregou a biblioteca. Embora essa abordagem fosse adequada para as necessidades de tempo de execução mais
básicas, o resultado final foi que a biblioteca dinâmica carregada foi irreversivelmente alterada de forma que seria bastante difícil para qualquer outro processo
reutilizar a biblioteca já carregada. Esta abordagem de projeto de carregador original é conhecida como realocação de tempo de carregamento e será discutida em
mais detalhes nos parágrafos subsequentes.

O conceito PIC foi claramente um grande passo à frente. Ao redesenhar o mecanismo de carregamento para evitar amarrar o segmento de código (.text) da
biblioteca carregada ao mapa de memória do primeiro processo que o carregou, o entalhe de funcionalidade extra desejado foi alcançado fornecendo o caminho
para vários processos mapearem perfeitamente para seu mapa de memória a biblioteca dinâmica já carregada.

Pergunta 2: O uso do sinalizador do compilador -fPIC é estritamente necessário para construir a biblioteca dinâmica?

A resposta não é única. Na arquitetura de 32 bits (X86), não é necessário. Se não for especificado, no entanto, a biblioteca dinâmica estará em conformidade com o
mecanismo de carregamento de realocação de tempo de carregamento mais antigo, no qual apenas o processo que carrega a biblioteca dinâmica primeiro será
capaz de mapeá-la em seu mapa de memória de processo.

Em arquiteturas de 64 bits (X86_64 e I686), a simples omissão do sinalizador do compilador -fPIC (em uma tentativa de implementar o mecanismo de realocação
de tempo de carregamento) resultará no erro do vinculador. Uma discussão sobre por que isso acontece e como contornar o problema será fornecida
posteriormente neste livro. A solução para esse tipo de situação é passar o sinalizador -fPIC ou -mcmodel = large para o compilador.

Pergunta 3: O uso do sinalizador do compilador -fPIC está estritamente restrito ao domínio de bibliotecas dinâmicas?
Ele pode ser usado ao construir a biblioteca estática?

É crença popular que o uso do sinalizador -fPIC é estritamente confinado ao domínio das bibliotecas dinâmicas. A verdade é um pouco diferente.

Na arquitetura de 32 bits (X86), realmente não importa se você compila a biblioteca estática com o sinalizador -fPIC ou não. Isso terá um certo impacto na
estrutura do código compilado; no entanto, terá um impacto insignificante na vinculação e no comportamento geral do tempo de execução da biblioteca.

Na arquitetura de 64 bits (X86_64 com certeza), as coisas são ainda mais interessantes.

• A biblioteca estática vinculada ao executável pode ser compilada com ou sem o sinalizador do compilador -fPIC (ou seja, não importa se você especifica ou
não).

Contudo:

• A biblioteca estática vinculada à biblioteca dinâmica deve ser compilada com o sinalizador -fPIC !!! (Alternativamente, em vez do sinalizador -fPIC , você
pode especificar o sinalizador do compilador -mcmodel = large .)

Se a biblioteca estática não foi compilada com nenhum dos dois sinalizadores, a tentativa de vinculá-la à biblioteca dinâmica resulta no erro do vinculador
mostrado na Figura 6-1.

1
/ usr / bin / Id:. JstaticLib / libstatlclinklngderio.aCtestStaticLlriklng.o): relocação R_X86_64_32 contra .rodata não pode ser usado ao
fazer um

../stattcLlb/llbstatlellnklngdemo.a: não foi possível ler synbols: Valor incorreto Figura 6-1. Erro de linker

Uma discussão técnica interessante relacionada a esse problema pode ser encontrada no seguinte artigo da web: www.technovelty.org/c/position-
independent-code-and-x86-64-libraries.html .

Criação da biblioteca dinâmica no Windows

O processo de construção de uma biblioteca dinâmica simples no Windows requer seguir uma receita bastante simples. A sequência de capturas de tela (Figuras 6-
2 a 6-6) ilustra o processo de criação do projeto DLL. Uma vez que o projeto é criado, construir a DLL não requer nada mais do que lançar o comando Build.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 59/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Novo Projeto | g | ya> |

| Modelos Recentes | .NET Framework 4 'Classificar por: Padrão -J jj jj Pesquisar Modelos Instalados P |
Modelos Instalados Tipo: Visual C ++
Aplicativo de console Win32 Visual C ++
um Visual C ++ Um projeto para criar um aplicativo Win32.
ATL
Projeto Win32 Visual C ++ aplicativo de console, DLL ou biblioteca estática
CLR
Em geral

MFC

Teste

Win32

Outras línguas

P Outros Tipos de Projeto

Base de dados

b Projetos de teste

1 Modelos Online

Nome: DemoDLL
Localização: C: \ Users \ miian \ Navegar...

Nome da solução: DemoDLL 0 Criar diretório para solução

□ Adicionar ao controle de origem

Figura 6-2. A primeira etapa na criação de biblioteca dinâmica Win32 (DLL)

Figura 6-3. Clique no botão Avançar para especificar a escolha de DLL

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 60/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 6-4. Configurações de DLL Win32 disponíveis

Figura 6-5. Sinalizadores de compilador DLL criados

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 61/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 6-6. Sinalizadores de linker DLL criados

Projetando Bibliotecas Dinâmicas


O processo de projeto de uma biblioteca dinâmica em geral não difere muito do projeto de qualquer outro software. Dada a natureza específica das bibliotecas
dinâmicas, existem alguns pontos específicos de importância que precisam ser discutidos em detalhes.

Projetando a interface binária

Por sua natureza, uma biblioteca dinâmica em geral fornece uma funcionalidade específica para o mundo externo, cuja maneira deve minimizar o envolvimento
do cliente nos detalhes da funcionalidade interna. A forma como isso é feito é através da interface, onde o cliente fica ao máximo aliviado por saber de algo com
que não precisa se preocupar.

O conceito de interface, que é onipresente no domínio da programação orientada a objetos, obtém um sabor extra no domínio da reutilização de código binário.
Conforme explicado na seção "O impacto do conceito de reutilização binária" no Capítulo 5, a imutabilidade da interface binária do aplicativo (ABI) entre as fases
de tempo de construção e tempo de execução da vinculação dinâmica é o requisito mais básico de uma vinculação dinâmica bem-sucedida.

À primeira vista, o design da ABI não difere muito do design da API. O significado básico do conceito de interface permanece inalterado: um conjunto de funções
que precisam ser disponibilizadas ao cliente para utilizar os serviços prestados por um módulo especializado.

Na verdade, enquanto o programa não for escrito em C ++, o esforço de design da ABI da biblioteca dinâmica não exige mais raciocínio do que projetar a API de
um módulo de software reutilizável. O fato de a ABI ser apenas um conjunto de símbolos de vinculação que precisam ser carregados em tempo de execução não
torna as coisas substancialmente diferentes.

No entanto, o impacto da linguagem C ++ (mais notavelmente, a falta de padronização estrita) requer pensamento adicional ao projetar a biblioteca dinâmica ABI.

Problemas C ++

Um fato infeliz da vida é que o progresso no domínio das linguagens de programação não foi seguido simetricamente pelo design de linkers, ou, para dizer
exatamente, pelo rigor dos corpos normativos que trazem os padrões no domínio do software. As boas razões para não fazer isso serão apontadas ao longo desta
seção. Um excelente artigo que ilustra essas questões é o "Guia do Iniciante para Linkers " em www.lurklurk.org/linkers/linkers.html . Vamos começar
com os fatos simples e revisar algumas das questões.

Problema nº 1: C ++ impõe requisitos de nome de símbolo mais complexos


Ao contrário da linguagem de programação C, o mapeamento de funções C ++ para os símbolos do vinculador traz muito mais desafios ao projeto do vinculador.
A natureza orientada a objetos do C ++ traz as seguintes considerações extras:

• Em geral, as funções C ++ raramente são independentes; em vez disso, tendem a ser afiliados a várias entidades de código.

A primeira coisa que vem à mente é sim, em C ++, as funções geralmente pertencem às classes (e, como tal, até têm o nome especial: métodos). Além disso, as
classes (e, portanto, seus métodos) podem pertencer aos namespaces. A situação fica ainda mais complicada quando os modelos entram em cena.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 62/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Para identificar exclusivamente a função, o vinculador deve de alguma forma incluir as informações de afiliação da função ao símbolo que ele cria para o ponto de
entrada da função.

• O mecanismo de sobrecarga do C ++ permite que métodos diferentes da mesma classe tenham o mesmo nome, o mesmo valor de retorno, mas diferem em
termos de listas de argumentos de entrada.

Para identificar exclusivamente as funções (métodos) que compartilham o mesmo nome, o vinculador deve de alguma forma adicionar as informações sobre os
argumentos de entrada ao símbolo que ele cria para o ponto de entrada da função.

Os esforços de design do linker para responder a esses requisitos substancialmente mais complexos resultaram na técnica conhecida como name muting. Em
poucas palavras, a mutilação de nome é o processo de combinar o nome da função, as informações de afiliação da função e a lista de argumentos da função para
criar o nome do símbolo final. Normalmente, a afiliação da função é anexada (prefixada), enquanto as informações de assinatura da função são anexadas (pós-
fixadas) ao nome da função.

A principal fonte de problemas é que as convenções de mutilação de nomes não são padronizadas exclusivamente e, até hoje, são específicas do fornecedor. O
artigo da Wikipedia ( http://en.wikipedia.org/wiki/Name_mangling#How_ different_compilers_mangle_the_same_functions) ilustra as diferenças
nas implementações de mutilação de nomes em diferentes vinculadores. Conforme declarado no artigo, muitos outros fatores além da ABI desempenham um
papel na implementação do mecanismo de mutilação (pilha de manipulação de exceções, layout de tabelas virtuais, estrutura e preenchimento de quadro de
pilha). Dada a grande variedade de requisitos, o Manual de Referência C ++ Anotado ainda recomenda a manutenção de esquemas individuais de mutilação.

FUNÇÕES ESTILO C

Ao usar o compilador C ++, coisas interessantes acontecem ao usar funções de estilo C. mesmo que as funções C não exijam mutilação, o vinculador por
padrão cria nomes mutilados para elas. nos casos em que se deseja evitar o mutilamento, deve-se aplicar uma palavra-chave especial para sugerir ao
vinculador que não o faça.

a técnica é baseada no uso da palavra-chave extern "C" . Quando uma função é declarada (normalmente em um arquivo de cabeçalho) da seguinte
maneira

#ifdef_cplusplus

extern "C" {

#endif // _cplusplus

int minhaFunção (int x, int y);

#ifdef_cplusplus

#endif // _cplusplus

o resultado final é que o linker cria seu símbolo privado de qualquer mutilação. Posteriormente neste capítulo, a seção sobre exportação de ABI conterá uma
explicação mais detalhada de por que essa técnica é tão importante.

Problema nº 2: Fiasco de pedido de inicialização estática

Um dos legados das linguagens C é que o linker pode lidar com variáveis ​inicializadas de maneira bastante simples, sejam eles tipos de dados simples ou as
estruturas. Tudo o que o vinculador precisa fazer é reservar o armazenamento na seção .data e gravar o valor inicial nesse local. No domínio da linguagem C, a
ordem em que as variáveis ​são inicializadas geralmente não tem nenhuma importância particular. Tudo o que importa é que a inicialização das variáveis ​seja
concluída antes do início do programa.

Em C ++, entretanto, o tipo de dados é um objeto em geral, e sua inicialização é concluída em tempo de execução por meio do processo de construção do objeto,
que é concluído quando o método construtor da classe completa sua execução. Obviamente, o vinculador precisa fazer muito mais coisas para inicializar os objetos
C ++. Para facilitar o trabalho do vinculador, o compilador incorpora no arquivo-objeto a lista de todos os construtores que precisam ser executados para um
arquivo específico e armazena essas informações no segmento de arquivo-objeto específico. No momento do link, o vinculador examina todos os arquivos-objeto e
combina essas listas de construção na lista final que será executada no tempo de execução.

É importante mencionar neste ponto que os ligadores observam a ordem de execução dos construtores com base na cadeia de herança. Em outras palavras, é
garantido que o construtor da classe base será executado primeiro, seguido pelos construtores das classes derivadas. Essa lógica incorporada ao vinculador é
suficiente para a maioria dos cenários possíveis.

O vinculador, no entanto, não é indefinidamente inteligente. Infelizmente, há toda uma categoria de casos em que um programador não se desvia de forma
alguma das regras de sintaxe do C ++, mas a lógica limitada do vinculador causa travamentos muito desagradáveis ​que acontecem antes de o programa ser
carregado, muito antes que qualquer depurador possa detectá-lo.

O cenário típico desse tipo acontece quando a inicialização de um objeto depende de algum outro objeto sendo inicializado de antemão. Vou primeiro explicar o
mecanismo subjacente do problema e, em seguida, sugerir maneiras para o programador evitá-los. Em círculos de programadores C ++, essa classe de problemas é
normalmente referida como um fiasco de ordem de inicialização estática.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 63/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
■ Nota Uma excelente ilustração do problema e da solução é apresentada no livro quintessencial de Scott Meyer "Effective C ++" ("item 47: Certifique-se de
que objetos estáticos não locais sejam inicializados antes de serem usados").

Descrição do Problema

Objetos estáticos não locais são as instâncias de uma classe C ++ cujo escopo de visibilidade excede os limites de uma classe. Mais especificamente, tais objetos
podem ser um dos seguintes:

• Definido no escopo global ou de namespace

• Estática declarada em uma classe

• Estática definida no escopo do arquivo

Esses objetos são inicializados rotineiramente pelo vinculador antes de o programa começar a ser executado. Para cada um desses objetos, o vinculador mantém a
lista de construtores necessários para criar tal objeto e os executa na ordem especificada pela cadeia de herança.

Infelizmente, este é o único esquema de ordenação de inicialização de objeto que o vinculador reconhece e implementa. Agora é a hora da reviravolta especial em
toda a história.

Vamos supor que um desses objetos dependa de algum outro objeto sendo inicializado de antemão. Suponha, por exemplo, que você tenha dois objetos estáticos:

• Objeto A (instância da classe a), que inicializa a infraestrutura de rede, consulta a lista de redes disponíveis, inicializa os soquetes e estabelece a conexão inicial
com o servidor de autenticação.

• Objeto B (instância da classe b), que é necessário para enviar a mensagem pela rede ao servidor de autenticação remoto, chamando os métodos de interface na
instância da classe b.

Obviamente, a ordem correta de inicialização é que o objeto B seja inicializado após o objeto A. É óbvio que violar a ordem de inicialização do objeto tem um
potencial muito real de causar estragos. Mesmo que os designers tenham sido cuidadosos o suficiente para prever casos em que a inicialização não foi concluída
(ou seja, verificar os valores do ponteiro antes de fazer as chamadas reais), o melhor que pode acontecer é que a tarefa da classe B não seja concluída quando
esperado.

Na verdade, não existe uma regra que dite a ordem em que a inicialização de objetos estáticos acontecerá. As tentativas de implementar o algoritmo que
examinaria o código reconhecem tais cenários e sugerem a ordenação correta para o vinculador provaram pertencer à categoria de problemas que são muito
difíceis de resolver. A presença de outros recursos da linguagem C ++ (modelos) apenas agrava o caminho para a solução do problema.

Como consequência final, o vinculador pode decidir inicializar o objeto estático não local em qualquer ordem. Para piorar as coisas, a decisão do vinculador de
qual ordem seguir pode depender de um número inimaginável de circunstâncias de tempo de execução não relacionadas.

Na vida real, esses problemas são assustadores por vários motivos. Primeiro, eles são difíceis de rastrear, pois resultam em travamentos que acontecem antes que
o carregamento do processo seja conectado, muito antes que o depurador possa ajudar. Além disso, as ocorrências de travamento podem não ser persistentes; a
falha pode acontecer de vez em quando ou em alguns cenários sempre com sintomas diferentes.

Evitando o problema

Mesmo que o problema não seja para os fracos de coração, há uma maneira de evitar a confusão feia. As regras do vinculador não especificam a ordem de
inicialização das variáveis, mas a ordem é especificada com muita precisão para as variáveis ​estáticas declaradas dentro de um corpo de função. Ou seja, o objeto
declarado como estático dentro da função (ou método de classe) é inicializado quando sua definição é encontrada pela primeira vez durante uma chamada para
essa função.

A solução para esse problema se torna óbvia. As instâncias não devem ser mantidas em roaming livre na memória de dados. Em vez disso, eles deveriam ser

• Declarado como variáveis ​estáticas dentro de uma função.

• Uma função deve ser convenientemente usada como a única maneira de acessar tal variável (retornando a referência ao objeto, por exemplo) definida estática no
escopo do arquivo.

Em resumo, as duas soluções possíveis a seguir são tradicionalmente aplicadas para resolver esses tipos de problemas:

• SOLUÇÃO 1: Proporcionar a implementação customizada do método _init () , um método padrão chamado imediatamente quando a biblioteca dinâmica é
carregada, no qual um método estático de classe instancia o objeto, forçando assim a inicialização pela construção. Conseqüentemente, a implementação
personalizada do _fini () padrão , um método padrão chamado imediatamente antes que a biblioteca dinâmica seja descarregada, pode ser fornecida em que
a desalocação do objeto pode ser concluída.

• SOLUÇÃO 2: Substituir o acesso direto a tal objeto por uma chamada a uma função personalizada. Essa função conterá uma instância estática da classe C ++ e
retornará a referência para

isto. Antes do primeiro acesso, será construída uma variável declarada como estática, garantindo que sua inicialização aconteça antes da primeira chamada real. O
compilador GNU, bem como o padrão C ++ 11, garantem que essa solução seja segura para threads.

Questão 3: Modelos

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 64/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O conceito de modelos é introduzido com o propósito de eliminar implementações duplicadas e possivelmente dispersas dos mesmos algoritmos que diferem
mutuamente apenas no tipo de dados em que o algoritmo opera. Por mais útil que seja o conceito, ele apresenta problemas adicionais ao procedimento de
vinculação.

A essência do problema é que diferentes especializações de modelos têm representações de código de máquina completamente diferentes. Por sorte, uma vez
escrito, o modelo pode ser especializado em cerca de zilhões de maneiras, dependendo de como o usuário do modelo deseja usá-lo. O seguinte modelo

modelo <classe T>

T max (T x, T y) {

if (x> y) {return x;} else {return y;}

pode ser especializado para tantos tipos de dados que suportam operador de comparação (tipos de dados simples variando de char até double são os
candidatos imediatos).

Quando o compilador encontra o modelo, ele precisa materializá-lo em alguma forma de código de máquina. Mas, isso não pode ser feito até que todos os outros
arquivos de origem tenham sido examinados para descobrir qual especialização específica ocorreu no código. Como isso pode ser relativamente fácil no caso de
aplicativos autônomos, a tarefa requer muita reflexão quando o modelo é exportado por uma biblioteca dinâmica.

Existem duas abordagens gerais para resolver esses tipos de problemas:

• O compilador pode gerar todas as especializações de modelo possíveis e criar símbolos fracos para cada uma delas. A explicação completa do conceito de
símbolos fracos pode ser encontrada na discussão sobre os tipos de símbolo do linker. Observe que o vinculador tem a liberdade de descartar símbolos fracos, uma
vez que determina que eles não são realmente necessários na compilação final.

• A abordagem alternativa é que o vinculador não inclui as implementações de código de máquina de nenhuma das especializações de modelo até o final. Depois
que todo o resto estiver concluído, o vinculador pode examinar o código, determinar exatamente quais especializações são realmente necessárias, chamar o
compilador C ++ para criar as especializações de modelo necessárias e, finalmente, inserir o código de máquina no executável. Essa abordagem é favorecida pelo
conjunto de compiladores Solaris C ++.

Projetando a interface binária do aplicativo

Para minimizar possíveis problemas, melhorar a portabilidade para diferentes plataformas e até mesmo aumentar a interoperabilidade entre os módulos criados
por diferentes compiladores, é altamente recomendável praticar as seguintes diretrizes.

Diretriz nº 1: implemente a ABI da biblioteca dinâmica como um conjunto de funções de estilo C

Existem muitas boas razões para explicar por que esse conselho faz muito sentido. Por exemplo, você pode

• Evite vários problemas com base em C ++ vs. interação do vinculador

• Melhorar a portabilidade de plataforma cruzada

• Melhorar a interoperabilidade entre os binários produzidos por diferentes compiladores. (Alguns dos compiladores tendem a produzir binários que podem ser
usados ​por outros compiladores. Exemplos notáveis ​são os compiladores MinGW e Visual Studio.)

Para exportar os símbolos ABI como funções de estilo C, use a palavra-chave extern "C" para direcionar o vinculador a não aplicar a mutilação de nome nesses
símbolos.

Diretriz nº 2: Forneça o arquivo de cabeçalho contendo a declaração ABI completa

A "declaração ABI completa" significa não apenas protótipos de função, mas também as definições do pré-processador, layouts de estruturas, etc.

Diretriz nº 3: use palavras-chave C padrão amplamente suportadas


Mais especificamente, usar suas definições de tipo de dados específicas do projeto, ou tipos de dados específicos da plataforma, ou qualquer coisa que não seja
universalmente suportada em diferentes compiladores e / ou plataformas diferentes, nada mais é do que um convite para problemas futuros. Portanto, tente não
agir como um cara esperto e chique; em vez disso, tente escrever seu código da maneira mais clara e simples possível.

Diretriz nº 4: use um mecanismo de fábrica de classe (C ++) ou módulo (C)

Se a funcionalidade interna da biblioteca dinâmica for implementada por uma classe C ++, isso ainda não significa que você deve violar a diretriz nº 1. Em vez
disso, você deve seguir a chamada abordagem de fábrica de classes (Figura 6-7).

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 65/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 6-7. O conceito de fábrica de classe

A fábrica de classes é uma função no estilo C que representa uma ou mais classes C ++ para o mundo externo (semelhante a um agente de Hollywood que
representa muitos atores famosos em negociações com estúdios de cinema).

Como regra, a fábrica de classes tem conhecimento íntimo do layout da classe C ++, que normalmente é obtido declarando-o como um método estático da mesma
classe C ++.

Quando chamada pelo cliente interessado, a fábrica de classes cria uma instância da classe C ++ que representa. Para manter os detalhes do layout da classe C ++
longe dos olhos curiosos do cliente, ele nunca encaminha a instância da classe de volta para o chamador. Em vez disso, ele converte a classe C ++ para uma
interface de estilo C e converte o ponteiro para o objeto C ++ criado como o ponteiro da interface.

Obviamente, para que esse esquema funcione corretamente, a classe C ++ representada pela fábrica de classes é obrigada a implementar a interface de exportação.
No caso particular de C ++, isso significa que a classe deve herdar publicamente a interface. Dessa forma, lançar o ponteiro da classe para o ponteiro da interface é
muito natural.

Finalmente, esse esquema requer que um certo mecanismo de rastreamento de alocação mantenha o controle de todas as instâncias alocadas pela função de fábrica
de classe. Na tecnologia Microsoft Component Object Model (COM), a contagem de referência garante que o objeto alocado seja destruído quando não estiver
mais sendo usado. Em outras implementações, sugere-se manter a lista de ponteiros para os objetos alocados. No momento do encerramento (delineado por uma
chamada para uma função de limpeza de um tipo), cada elemento da lista seria excluído e a lista finalmente limpa.

O equivalente em C da fábrica de classes é normalmente conhecido como módulo. É o corpo do código que fornece a funcionalidade para o mundo externo por
meio de um conjunto de funções de interface cuidadosamente projetadas.

O design modular é típico para módulos de kernel de baixo nível e drivers de dispositivo, mas sua aplicação não é de forma alguma limitada a esse domínio
específico. O módulo típico exporta funções como Open () (ou Initialize ()), uma ou mais funções de trabalho (Read () Write (), SetMode (), etc.)
e, finalmente, Close () (ou Deinitialize ()) .

Muito típico para módulos é o uso de handle, um tipo de identificador de instância de módulo, muito frequentemente implementado como um ponteiro void,
um predecessor desse ponteiro em C ++.

O identificador normalmente é criado dentro do método Open () e é retornado ao chamador. Em chamadas para outros métodos de interface de módulo, o
identificador é o primeiro argumento de função obrigatório.

Nos casos em que C ++ não é uma opção, projetar o módulo C é completamente viável, equivalente ao conceito orientado a objetos de uma fábrica de classes.

Diretriz nº 5: exportar apenas os símbolos realmente importantes

Por ser modular por natureza, a biblioteca dinâmica deve ser projetada de modo que sua funcionalidade seja exposta ao mundo exterior por meio de um conjunto
definido de símbolos de função (a interface binária do aplicativo, ABI), enquanto os símbolos de todas as outras funções usadas apenas internamente devem ser
acessível aos executáveis ​do cliente. Existem vários benefícios para esta abordagem:

• A proteção dos conteúdos proprietários é aprimorada.

• O tempo de carregamento da biblioteca pode ser tremendamente melhorado como resultado da redução significativa no número de símbolos exportados.

• A chance de símbolos em conflito / duplicados entre as diferentes bibliotecas dinâmicas torna-se significativamente reduzida.

A ideia é bastante simples: a biblioteca dinâmica deve exportar apenas os símbolos das funções e dados que são absolutamente necessários para quem carrega a
biblioteca, e todos os outros símbolos devem ficar invisíveis. A seção a seguir trará mais detalhes sobre como controlar a visibilidade dos símbolos da biblioteca
dinâmica.

Diretriz nº 6: Use namespaces para evitar a colisão de nomes de símbolos


file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 66/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Ao incluir o código da biblioteca dinâmica em um namespace exclusivo, você elimina as chances de que as diferentes bibliotecas dinâmicas apresentem símbolos
com nomes idênticos (a função Initialize () é um excelente exemplo de uma função que provavelmente pode aparecer em bibliotecas dinâmicas de escopos
de funcionalidade completamente diferentes )

Controlando a visibilidade dos símbolos da biblioteca dinâmica

Da perspectiva de alto nível, o mecanismo de exportar / ocultar os símbolos do vinculador é resolvido quase de forma idêntica no Windows e no Linux. A única
diferença substancial é que, por padrão, todos os símbolos do vinculador DLL do Windows ficam ocultos, enquanto no Linux todos os símbolos do vinculador da
biblioteca dinâmica são exportados por padrão.

Na prática, devido a um conjunto de recursos fornecidos pelo GCC em uma tentativa de alcançar uniformidade de plataforma cruzada, os mecanismos de
exportação de símbolo são muito semelhantes e fazem praticamente a mesma coisa, no sentido de que, em última análise, apenas os símbolos de vinculação que
compõem o aplicativo a interface binária é exportada, enquanto todos os símbolos restantes ficam ocultos / invisíveis.

Exportando os Símbolos da Biblioteca Dinâmica do Linux


Ao contrário do Windows, no Linux todos os símbolos de vinculação da biblioteca dinâmica são exportados por padrão, de forma que são visíveis por quem tenta
vincular dinamicamente a biblioteca. Apesar do fato de que tal padrão torna mais fácil lidar com as bibliotecas dinâmicas, manter todos os símbolos exportados /
visíveis não é a prática recomendada por vários motivos. Expor demais aos olhos curiosos dos clientes nunca é uma boa prática. Além disso, carregar apenas o
número mínimo necessário de símbolos versus carregar um zilhão deles pode fazer uma diferença perceptível no tempo necessário para carregar a biblioteca.

É óbvio que algum tipo de controle sobre quais símbolos são exportados é necessário. Além disso, como esse controle já está implementado nas DLLs do
Windows, atingir o paralelismo facilitaria tremendamente os esforços de portabilidade.

Existem vários mecanismos para como o controle sobre a exportação de símbolos pode ser obtido no momento da construção. Além disso, a abordagem de força
bruta pode ser aplicada executando a ferramenta de linha de comando strip sobre o binário da biblioteca dinâmica. Finalmente, é possível combinar vários
métodos diferentes com o mesmo objetivo de controlar a visibilidade dos símbolos da biblioteca dinâmica.

O controle de exportação de símbolo em tempo de construção

O compilador GCC fornece vários mecanismos para configurar a visibilidade dos símbolos do vinculador: MÉTODO 1: (afetando todo o corpo do código)

sinalizador do compilador -fvisibility

Conforme declarado pela página de manual do GCC ( http://linux.die.net/man/1/gcc ), passando o

-fvisibility = sinalizador do compilador oculto , é possível fazer com que todos os símbolos da biblioteca dinâmica não sejam exportados / invisíveis para
quem tentar vincular dinamicamente à biblioteca dinâmica.

MÉTODO 2: (afetando apenas símbolos individuais)

_attribute_ ((visibilidade ("<padrão | oculto>")))

Ao decorar a assinatura da função com a propriedade do atributo, você instrui o vinculador a permitir (padrão) ou não permitir (oculto) a exportação do símbolo.

MÉTODO 3: (afetando símbolos individuais ou um grupo de símbolos) #pragma visibilidade GCC [push | pop]

Esta opção é normalmente usada nos arquivos de cabeçalho. Fazendo algo assim

#pragma visibilidade push (oculto) void someprivatefunction_l (void); void someprivatefunction_2 (void);

void someprivatefunction_N (void); #pragma visibilidade pop

você está basicamente tornando invisíveis / não exportadas todas as funções declaradas entre as instruções #pragma . Esses três métodos podem ser combinados
de qualquer maneira que o programador considere adequada.

Os Outros Métodos

O vinculador GNU suporta um método sofisticado de lidar com o controle de versão da biblioteca dinâmica, no qual um arquivo de script simples é passado para
o vinculador (por meio do sinalizador -Wl, - version-script, <nome do arquivo de script> vinculador). Por mais que o objetivo original do
mecanismo seja especificar as informações da versão, ele também tem o poder de afetar a visibilidade do símbolo. A simplicidade com que realiza a tarefa torna
esta técnica a forma mais elegante de controlar a visibilidade do símbolo. Mais detalhes sobre essa técnica podem ser encontrados no Capítulo 11 nas seções que
discutem o controle de versão das bibliotecas do Linux.

O Exemplo de Demonstração do Controle de Exportação de Símbolo

Para ilustrar o mecanismo de controle de visibilidade, criei um projeto de demonstração no qual duas bibliotecas dinâmicas idênticas foram construídas com
configurações de visibilidade diferentes. As bibliotecas são apropriadamente chamadas de libdefaultvisibility.so e libcontrolledvisibility.so.
Depois que as bibliotecas são construídas, seus símbolos são examinados usando o utilitário nm (que é abordado em detalhes nos Capítulos 12 e 13).

O Caso de Visibilidade de Símbolos Padrão

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 67/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O código-fonte de libdefaultvisibility.so é mostrado na Listagem 6-1.

Listagem 6-1. libdefaultvisibility.so #include "sharedLibExports.h"

void mylocalfunction1 (void)

printf ("função1 \ n");

void mylocalfunction2 (void)

printf ("função2 \ n");

void mylocalfunction3 (void)

printf ("função3 \ n");

void printMessage (void)

printf ("Executando a função exportada da biblioteca compartilhada \ n");

O exame dos símbolos presentes no binário da biblioteca construída não traz surpresas, pois os símbolos de todas as funções são exportados e visíveis, conforme
mostrado na Figura 6-8.

nilan @ mi "lan $ nm -D li.bdefaultvi.sibili.ty.so

PiD0O0510 T _Z16nylocalfuncti_on2v 0tiC * 0540 T _Z16myloc3lfunction3v

Figura 6-8. Todos os símbolos da biblioteca são originalmente exportados / visíveis

O caso de visibilidade de símbolos controlados

No caso de uma biblioteca dinâmica na qual você deseja controlar a visibilidade / exportabilidade do símbolo, o sinalizador do compilador -fvisibility foi
especificado no Makefile do projeto, conforme mostrado na Listagem 6-2.

Listagem 6-2. O sinalizador do compilador -fvisibility

# Compilador

INCLUDES = $ (COMMON_INCLUDES)

DEBUG_CFLAGS = -Wall -g -O0 RELEASE_CFLAGS = -Wall -O2

VISIBILITY_FLAGS = -fvisibility = hidden -fvisibility-inlines-hidden

ifeq ($ (DEBUG), 1)

CFLAGS = $ (DEBUG_CFLAGS) -fPIC $ (INCLUI)

outro

CFLAGS = $ (RELEASE_CFLAGS) -fPIC $ (INCLUI)

fim se

CFLAGS + = $ (VISIBILITY_FLAGS)

COMPILAR = g ++ $ (CFLAGS)

Quando a biblioteca é construída exclusivamente com essa configuração de visibilidade de símbolo em particular, o exame dos símbolos indicou que os símbolos
de função não foram exportados (Figura 6-9).

nm -D ^ libcontrolledvisibility.so

Figura 6-9. Todos os símbolos da biblioteca agora estão ocultos

Em seguida, quando a decoração de assinatura de função com os atributos de visibilidade é aplicada, conforme mostrado na Listagem 6-3,

o efeito líquido é que a função declarada com o_atributo_ ((visibilidade ("padrão"))) torna-se visível

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 68/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
(Figura 6-10).

Listagem 6-3. A função de decoração de assinatura com os atributos de visibilidade aplicados #include "sharedLibExports.h"

#if 1

#define FOR_EXPORT __attribute__ ((visibilidade ("padrão"))) #else

#define FOR_EXPORT #endif

void mylocalfunctionl (void) {

printf ("função1 \ n");

... etc ...

//

// também compatível:

// FOR_EXPORT void printMessage (void)

// mas isso não é compatível: // void printMessage FOR_EXPORT (void) // nem este:

// void printMessage (void) FOR_EXPORT

//

// isto é, o atributo pode ser declarado em qualquer lugar // antes do nome da função

void FOR EXPORT printMessage (void) {

printf ("Executando a função exportada da biblioteca compartilhada \ n");

milan@mi.lan $ nm -D "libcontrolledvisibility. so Figura 6-10. Controle de visibilidade aplicado à junção printMessage

Usando o utilitário strip

Outro mecanismo de controle da visibilidade dos símbolos está disponível. Não é tão sofisticado e não é programável. Em vez disso, ele é implementado
executando um utilitário de linha de comando strip (Figura 6-11). Esta abordagem é muito mais brutal, pois tem o poder de apagar completamente qualquer
informação sobre qualquer um dos símbolos da biblioteca, a tal ponto que nenhum dos utilitários de exame de símbolo usuais será capaz de ver qualquer um dos
símbolos, seja em a seção .dynamic ou não.

milan @ ciilan $ strip --strip-symbol _Z16nylocalfunctionlv libcontrolledvisibility.so ntlangntlan $ strip --strip-symbol


_Z16mylocalfunctiori2v libcontrolledvisibility.so nilan @ iiilan $ strip --strip-synbol _Zl6mylocalfunctlon3v. então mllan @ milão
$ nm libcontrolledvisibility.so OO001f28 a „DINÂMICO 00001ff4 a _GLOBAL_OFFSET_TABLE_ _Jv_ReglsterClasses

CTOR END —CT0R ~ LI5T ~ —DTORENO -

Ij) TOR ~ LIST3_

__FRAME_END__

_JCR_END_

_3CR_LIST__

__bss_start

_cxa_finalize @@ GLIBC_2.1.3

__do_global_ctors_aux

__do_global_dtors_aux

__dso_haridle

_gnon_start_

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 69/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
__1686.get_pc_thunk.bx

_edata _end _fini _init

completado.6159 dtor_i.dx.6161 f rapie _d unit printMessage PUtS @@ GLIBC_2.0

awddddrdd A W ttdwt AATT bbt TU

pulan @ milan

OOOQlf18 OOOQlf14 OOO01f2O OOOOlflC 000996bc 00001f24 00001f24 0OO02S1O

00000520 000O03a0 0000200c

00000457 00002010 00002018 00000558 0000032c 00002010 00002014 00000420 0O0094f0

Figura 6-11. Usando o utilitário strip para eliminar certos símbolos

■ Nota Mais informações sobre o utilitário strip podem ser encontradas no Capítulo 13.

Exportando os Símbolos da Biblioteca Dinâmica do Windows


No Linux, todos os símbolos do vinculador encontrados na biblioteca dinâmica são, por padrão, acessíveis pelos executáveis ​do cliente. No Windows, entretanto,
esse não é o caso. Em vez disso, apenas os símbolos que foram exportados corretamente se tornam visíveis para o executável do cliente. A parte importante de
impor essa limitação é o uso de um binário separado (biblioteca de importação) durante a fase de tempo de construção, que contém apenas os símbolos planejados
para serem exportados.

Felizmente, o mecanismo de exportação dos símbolos DLL está completamente sob o controle do programador. Na verdade, há dois mecanismos com suporte de
como os símbolos DLL podem ser declarados para exportação.

Usando a palavra-chave__declspec (dllexport)

Esse mecanismo é fornecido por padrão pelo Visual Studio. Marque a caixa de seleção "Exportar símbolos" na caixa de diálogo de criação de novo projeto,
conforme mostrado na Figura 6-12.

Figura 6-12. Selecionando a opção "Exportsymbols" na caixa de diálogo Win32 DLL Wizzard

Aqui, você especifica que deseja que o assistente de projeto gere o cabeçalho de exportação da biblioteca contendo os trechos de código que se parecem com a
Figura 6-13.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 70/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
milanDLLdemo.h XE (escopo global)

// - O seguinte bloco ifdef é a maneira padrão de criar macros que tornam a exportação // • de uma DLL mais simples. Todos os arquivos
dentro desta DLL são compilados com o MI LANDLLDEMO_EXPORTS // - símbolo definido na linha de comando »Este símbolo não deve ser definido em
nenhum projeto // - que usa esta DLL. Desta forma, qualquer outro projeto cujos arquivos fonte incluam este arquivo veja // - as funções
MILANDLLDEMO_API como sendo importadas de um OIL; enquanto essa DLL vê símbolos // - definidos com esta macro como ■ sendo exportados.
#ifdef HILANOLLD £ MO_ £ XPOftTS

se definir MIlANDLLDEMO_API _declspec (dllexport)

#outro

«Define MILANDLLDEMO_APl _declspec (dllimport)

#fim se

// Esta classe é exportada do milanDLLderao.dll

classe MILANDLLDEMO_API CmilanDLldemo {

público:

CailanDLLdem (vazio);

// TODO: adicione seus métodos aqui.

>;

extern MILAN DL LDEf »_API int nmilanDLLdemo; MI LAND LL DEi-10_A PI int frmilanDLLdemo (vazio);

Figura 6-13. O Visual Studio gera a declaração específica do projeto de palavras-chave_declspec (dllexport)

Como mostra a Figura 6-13, o cabeçalho de exportação pode ser usado tanto dentro do projeto DLL quanto pelo executável do cliente

projeto. Quando usada dentro do projeto DLL, a macro específica do projeto é avaliada como the_ declspec (dllexport)

palavra-chave dentro do projeto DLL, enquanto dentro do projeto executável do cliente ela avalia to_ declspec (dllimport).

Isso é imposto pelo Visual Studio, que insere automaticamente a definição do pré-processador no projeto DLL (Figura 6-14).

Figura 6-14. O Visual Studio gera automaticamente a definição de pré-processador específica do projeto

Quando a palavra-chave específica do projeto que avalia to_ declspec (dllexport) é adicionada à função

declaração, o símbolo do vinculador de função é exportado. Caso contrário, omitir essa palavra-chave específica do projeto evitará a exportação do símbolo de
função. A Figura 6-15 apresenta duas funções, das quais apenas uma é declarada para exportação.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 71/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
#include "stdafx.h" Sinclude "niLanDlldemo.h"

// ■ Isso2 é um exemplo de uma variável exportada | / * MILANDLLDEMO_API * / int nmilanOLLdemo = 0;

// - Este é um exemplo - de - uma função - exportada

| EMI LANDLLDEMO_API int fnmilanDLLdeitro (vazio) {

■ * return 256; ^>

Hint notExportingThisFunetion (void) {

■ * return -1; }

Ajuda e suporte

programas e arquivos s

Figura 6-16. Iniciando o prompt de comando do Visual Studio para acessar a coleção de ferramentas de linha de comando de análise binária

Figura 6-15. O Visual Studio gera automaticamente um exemplo de uso de palavras-chave de controle de exportação de símbolo específicas do projeto

Agora é o momento perfeito para apresentar o utilitário dumpbin do Visual Studio que você pode usar para analisar a DLL na busca por símbolos exportados. É
parte das ferramentas do Visual Studio e pode ser usado apenas executando o prompt de comando especial das Ferramentas do Visual Studio (Figura 6-16).

A Figura 6-17 mostra o que a ferramenta dumpbin (chamada com o sinalizador / EXPORT ) relata sobre os símbolos exportados por sua DLL.

i Prompt de comando do Visual Studio (2010)

e: \ milanDLLdema \ DebU £ ("> duripbin ✓EXPORTS milanDLLdemo.dll Microsoft <R> COFF ^ PE Dumper Uersion 10.00.-10219.01 Copyright <C> Microsoft Corporation
- direitos de preenchimento reservados.

Despejo do arquivo milanDLLdemo.dll Tipo de arquivo: DLL

A seção contém as seguintes exportações para características roilanDLLclemo.dll 00000000

5172E312 hora data stanp Sáb 20 de abril 11:48:50 2013 versão 0,00

1 base ordinal 4 número de funções 4 número de nomes

dica ordinal nome RUfl

1 0 00011184 ?? 0CmilanDLLdeitoPPQAEPXZ = PILT * 255 <?? 0CnilanDLLdenoPP <1AEI? XZ>

2 1 00011172 ?? 4CmilanDLLdenoPPQflEAftU0 (? ABU0ePZ = PI LT + 365 <?? 4Cmilan DLL de nto 6 PQA Efl ft U 0 PA BU 0 PPZ>

3 2 0001 108r; TfnmilanDLLdemnPeVflHXZ = SILT + 13S (? FnnilarDLLdemoUeVfiH

HZ)

>a

3 00017130? NmilanDLLdemoPP3Hfl =? NnilanDLLdemoPP3Hfi <int nmilanD

LLdemo) Resumo

1000 .data 1000 .idata 2000 .rdata 1000. re loc 1000 .rsrc 4000 .text 10000 .textbss

c: Sm xlan DLLde no \ Debug>

Figura 6-17. Usando dumpbin.exe para visualizar a lista de símbolos exportados por DLL

Obviamente, o símbolo da função declarado com o símbolo de exportação específico do projeto acaba sendo exportado pela DLL. No entanto, o vinculador o
processou de acordo com as diretrizes C ++, que usam a mutilação de nomes. Os executáveis ​do cliente geralmente não têm problemas para interpretar tais
símbolos, mas se tiverem, você pode declarar a função como extern "C", o que resultará com o símbolo de função seguindo a convenção de estilo C (Figura 6-
18).

i Prompt de comando do Visual Studio (2010)

c: SnilanDLLdemoM> ebag> dunipbin / EXPORTS milanDLLderno.dll Microsoft <JO COFF / PE Dumper Uersion 10-00-40219.01 Copyright ^ C} Microsoft Corporation-
Todos os direitos reservados.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 72/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Saliência do arquivo milanDLLdemo-d11 Pile Tsipe: DLL

A seção contém as seguintes exportações para nilanDLLdemo.dll 00000000 características

5172E63H carimbo de data sabado 20 de abril 12:02 = 88 2013 versão 0,00

1 base ordinal 4 número de funções 4 número de nomes

dica ordinal nome RUA

1 0 000110FF ?? 0Crii lan DLLde mo (? (? QAE (? XZ = P1MT * 250 <?? 0Cnil «nDLLdenoG (?

QAEGXZ>

2 1 00011172 ?? 4CmilanDLLde ™> CGQAEftAU0 (? FtBU0 [ ? EZ = <? I LI * 365 <?? 4Cmilan DLL de mo 0 5 QA EA AII00 AB U0 Z>

3 2 00017130? N roilan DLLde mo PG3HA ■? NnilanDLLdemoeP3HA <int nmilanD

Lderao)

4 3 00011118 FnnilanDLLdemo) = PILT + 27SCJfrnnilanDLLdeno)

Sumwjf

1000 .data

1000 idata

2000 .rdata

1000 .reloc

1000 .rsrc

4000 .text

10000 .textbss

c: SmilanDLLdemoSl> ebug>

Figura 6-18. Declarando a função como extern "C"

Usando o arquivo de definição de módulo (.def)

A maneira alternativa de controlar a exportação de símbolos DLL é por meio do uso de arquivos de definição de módulo (.def) .

Ao contrário do mecanismo descrito anteriormente (com base nas palavras- chave_declspec (dllexport) ), que pode ser especificado

por meio do assistente de criação de projeto, marcando a caixa de seleção "Exportar símbolos", o uso de um arquivo de definição de módulo requer algumas
medidas mais explícitas.

Para começar, se você planeja usar o arquivo .def , é recomendável não marcar a caixa de seleção "Exportar símbolos". Em vez disso, use o menu Arquivo ^ Novo
para criar um novo arquivo de definição (.def) . Se esta etapa for concluída corretamente, as configurações do projeto indicarão que o arquivo de definição do
módulo é oficialmente parte do projeto, conforme mostrado na Figura 6-19.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 73/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 6-19. O arquivo de definição de módulo (.def) é oficialmente parte do projeto

Alternativamente, você pode escrever manualmente o arquivo .def , adicioná-lo manualmente à lista de arquivos de origem do projeto e, finalmente, editar
manualmente a página de propriedades do vinculador para parecer conforme mostrado na Figura 6-19. O arquivo de definição de módulo que especifica a função
de demonstração para exportação se parece com a Figura 6-20.

Solution Explorer X miianDLLDemo.def X |


BIBLIOTECA MILANDLLDEMO

3 Solução 'milanDLLdemo' (1 proj s milanDLLdemo EXPORTAÇÕES

> Dependências Externas Gjp fnmilanDLLdemo (Ell

um arquivo de cabeçalho
milanDLLdemo.h

stdafx.h

targetver.h

□ Arquivos de recursos

a Li Source Files
C "] dllmain.cpp

milanDLLdemo.cpp

milanDLLDemo.def

t3 stdafx.cpp

Q ReadMe.txt

Figura 6-20. Exemplo de arquivo de definição de módulo

Na linha EXPORTS, ele pode conter tantas linhas quantas forem as funções cujos símbolos você planeja exportar.

Um detalhe interessante é que o uso do arquivo de definição de módulo resulta em símbolos de função exportados como funções estilo C, sem a necessidade de
declarar a função como extern "C" . Se isso é uma vantagem ou desvantagem depende das preferências pessoais e das circunstâncias do projeto.

Uma vantagem particular de usar os arquivos de definição de módulo (.def) como método de exportação dos símbolos DLL é que, em certos cenários de
compilação cruzada, os compiladores não Microsoft tendem a oferecer suporte a essa opção.

Um exemplo é usar o compilador MinGW, que compila um projeto de código aberto (por exemplo, ffmpeg) para criar DLLs do Windows e arquivos .def
associados . Para que o DLL seja vinculado dinamicamente no momento da construção, você precisa usar sua biblioteca de importação, que infelizmente não foi
gerada pelo compilador MinGW.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 74/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Felizmente, as Ferramentas do Visual Studio fornecem o utilitário de linha de comando lib.exe, que pode gerar o arquivo de biblioteca de importação com base
no conteúdo do arquivo .def (Figura 6-20). A ferramenta lib está disponível por meio do prompt de comando Visual Studio Tools. O exemplo na Figura 6-21
ilustra como a ferramenta foi usada após a sessão de compilação cruzada na qual o compilador MinGW executado no Linux produziu os binários do Windows
(mas não forneceu as bibliotecas de importação).

X: \ MilanFFMpegWin32Build> dir * .def Uolume na unidade X é UBOX_U Bo xS bared Uolume O número de série é 9AE7-0879

Diretório de K: MlinFFMpegBuiltOr »Linux

14/02/2013 11h51

14/02/2013 11h51

14/02/2013 11h51

14/02/2013 11 = 51 AM

14/02/2013 11h51

14/02/2013 11h51

14/02/2013 11h51

7.012 avcodec-53.def

115 audeuice-53_def 5.107 auf ilter — 2 _ def 5.119 auformat-53.def 4.762 aoutil-5i.def

232 postproc-Sl-def

155 swresanple-0.def 7.084 swscale — 2.def 29.586 bytes

14/02/2013 11h51

8 Arquivo <s>

0 Dir <s> 465.080.082.432 bytes livres

X: sMilanFFMpegVlin32Build> lib / máquina = X86 / def: aocodec-S3 .def / out: avcodec. lib

Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os rigJits reserados.

Criação da biblioteca aucodec.lib e do objeto avcodec.exp

X: \ MilanFFMpegWin32Build> lib / machine: X86 /def:audeuice-53.def /out:avdevice.lib

Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

Criação da biblioteca avdeuice.lib e do objeto audevice.exp

K: \ MilanFFMpegWin32Build> lib / machine: X86 /def:aofilter-2.def /out:aufilter.lib

Microsoft <R) Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

Criação da biblioteca avfilter.lib e do objeto aufilter.exp

X: \ MilanFFMpegUin32Build> lifc / machine: X86 /def:aoforroat-53.def /out:auformat.lib

Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

Criação da biblioteca auformat.lib e do objeto auformat.exp

i: \ MilanFFMpegWin32Build> lib / ntacliine: X86 /def:auutil-51.def / out: auutil.lib Microsoft (R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft
Corporation. Todos os direitos »• eserued.

Criação da biblioteca auutil.lib e do objeto auutil.exp

<: \ MilanFFMpeglJin32Build> lib / machine: X86 /def:postproc~51.def /out:postproc.lib

Microsoft CR) Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

Criação da biblioteca postproc.lib e do objeto postproc.exp

<: \ MilanFFMpegl) in32Build> lib / máquina: X86 / def: suresample-0. def / out: suresample. lib

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 75/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

Criação da biblioteca suresample.lib e do objeto suresample.exp

X: xMilanFFMpegVlin32Build> lib / machine: X86 /def:swscale~2.def / out: swscale.lib Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft
Corporation. Todos os direitos reservados.

Criação da biblioteca suseale.lib e objeto suscale.exp K: \ MilanFFMpegWin32Build>

Figura 6-21. Gerando arquivos de biblioteca de importação para DLLs gerados pelo compilador MingW com base em arquivos de definição de módulo (.def) especificados

Deficiências de lidar com o arquivo de definição de módulo (.def)

Ao experimentar os arquivos .def , as seguintes deficiências foram percebidas:

• Incapacidade de discernir os métodos da classe C ++ das funções C: se dentro de uma DLL você tiver uma classe, e a classe tiver um método com o mesmo nome da
função C que você especificou para exportação no arquivo .def , o compilador irá relatar um conflito ao tentar descobrir qual dos dois deve ser exportado.

• peculiaridades extern "C" : Em geral, a função declarada para exportação dentro do arquivo .def não precisa ser declarada como extern "C", pois o
vinculador tomará cuidado para que seu símbolo siga a convenção C. No entanto, se você decidir decorar a função como extern "C", certifique-se de fazê-lo no
arquivo de cabeçalho e no arquivo .cpp de origem (o último dos quais normalmente não deve ser exigido). Não fazer isso irá de alguma forma confundir o
vinculador e o aplicativo cliente não será capaz de vincular seu símbolo de função exportado. Para que o problema seja mais difícil, a saída do utilitário dumpbin
não indicará nenhuma diferença, tornando o problema mais difícil de resolver.

Requisitos de conclusão de vinculação

O processo de criação de biblioteca dinâmica é um procedimento de construção completo, pois envolve tanto a fase de compilação quanto a de vinculação. Em
geral, o estágio de vinculação é concluído depois que cada símbolo do vinculador é resolvido, e esse critério deve ser observado independentemente de o destino
ser uma biblioteca executável ou dinâmica.

No Windows, essa regra é estritamente aplicada. O processo de vinculação nunca é considerado completo e o binário de saída nunca é criado até que cada símbolo
de biblioteca dinâmica tenha sido resolvido. A lista completa de bibliotecas dependentes é pesquisada até que a última referência de símbolo seja resolvida.

No Linux, no entanto, esta regra é um pouco distorcida por padrão ao construir as bibliotecas dinâmicas, pois é permitido que a vinculação da biblioteca dinâmica
seja concluída (e o arquivo binário criado) mesmo que nem todos os símbolos tenham sido resolvidos.

A razão por trás de permitir esse desvio da rigidez da regra sólida de outra forma é que é implicitamente assumido que os símbolos ausentes durante o estágio de
vinculação eventualmente aparecerão de alguma forma no mapa de memória do processo, muito provavelmente como resultado de alguma outra biblioteca
dinâmica sendo carregada em tempo de execução. Os símbolos necessários não fornecidos pela biblioteca dinâmica são marcados como indefinidos ("U").

Como regra, se o símbolo esperado por qualquer motivo não aparecer no mapa de memória do processo, o sistema operacional tende a relatar a causa de maneira
organizada, imprimindo a mensagem de texto no fluxo stderr, especificando o símbolo ausente.

Esta flexibilidade nas regras do Linux de vinculação de bibliotecas dinâmicas foi comprovada em várias ocasiões como um fator positivo, permitindo que certas
limitações de vinculação muito complexas sejam efetivamente superadas.

- Sinalizador de linker não indefinido

Apesar do fato de que a vinculação das bibliotecas dinâmicas é muito mais relaxada no Linux por padrão, o vinculador GCC oferece suporte à opção de
estabelecer os critérios de rigidez de vinculação que correspondem aos seguidos pelo vinculador do Windows.

Passar o sinalizador --no-undefined para o vinculador gcc resultará em uma construção malsucedida se cada símbolo não for resolvido no momento da
construção. Dessa forma, o padrão do Linux de tolerar a presença de símbolos não resolvidos é efetivamente revertido para os critérios estritos semelhantes aos do
Windows.

Observe que, ao invocar o vinculador por meio do gcc, os sinalizadores do vinculador devem ser precedidos pelo prefixo -Wl, como:

$ gcc -fPIC <arquivos de origem> -l <bibliotecas> -Wl, - no-undefined -o <nome do arquivo de saída shlib>

Modos de ligação dinâmica


A decisão de vincular a biblioteca dinâmica pode ser feita em diferentes estágios do ciclo de vida do programa. Em alguns cenários, você sabe de antemão que o
binário do seu cliente precisará carregar determinada biblioteca dinâmica, não importa o que aconteça. Em outros cenários, a decisão sobre o carregamento de
certa biblioteca dinâmica vem como resultado das circunstâncias do tempo de execução ou das preferências do usuário definidas no tempo de execução. Com base
em quando a decisão sobre a vinculação dinâmica é realmente feita, os seguintes modos de vinculação dinâmica podem ser diferenciados.

Vinculação dinâmica estaticamente ciente (tempo de carregamento)

Em todas as discussões até agora, assumi implicitamente esse cenário particular. Na verdade, acontece com muita frequência que a necessidade de uma
funcionalidade de biblioteca dinâmica específica é necessária desde o momento em que o programa é iniciado até o seu encerramento, e esse fato é conhecido de
antemão. Neste cenário, o procedimento de construção requer os seguintes itens. Em tempo de compilação:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 76/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• O arquivo de cabeçalho de exportação da biblioteca dinâmica, especificando tudo o que pertence à interface ABI da biblioteca

No momento do link:

• A lista de bibliotecas dinâmicas exigidas pelo projeto

• Os caminhos para os binários da biblioteca dinâmica necessários ao binário do cliente para configurar a lista de símbolos de biblioteca esperados.

Para obter mais detalhes sobre como os caminhos podem ser especificados, consulte a seção "Regras de localização da biblioteca em tempo de construção".

• Sinalizadores de linker opcionais especificando os detalhes do processo de vinculação

Runtime Dynamic Linking

Toda a beleza do recurso de vinculação dinâmica é a capacidade do programador determinar em tempo de execução se a necessidade de uma determinada
biblioteca dinâmica realmente existe e / ou qual biblioteca específica precisa ser carregada.

Muitas vezes, o design requer que existam várias bibliotecas dinâmicas, cada uma das quais suporta a ABI idêntica, e que apenas uma delas seja carregada
dependendo da escolha do usuário. Um exemplo típico de tal cenário é o suporte a vários idiomas onde, com base nas preferências do usuário, o aplicativo carrega
a biblioteca dinâmica que contém todos os recursos (strings, itens de menu, arquivos de ajuda) escritos no idioma de escolha do usuário. Neste cenário, o
procedimento de construção requer os seguintes itens. Em tempo de compilação:

• O arquivo de cabeçalho de exportação da biblioteca dinâmica, especificando tudo o que pertence à interface ABI da biblioteca

No momento do link:

• Pelo menos o nome do arquivo da biblioteca dinâmica a ser carregada. O caminho exato do nome do arquivo da biblioteca dinâmica normalmente é resolvido
implicitamente, contando com o conjunto de regras de prioridade que regem a escolha dos caminhos nos quais o binário da biblioteca deve ser encontrado no
tempo de execução.

Todos os principais sistemas operacionais fornecem um conjunto simples de funções API que permitem ao programador explorar totalmente esse recurso precioso
(Tabela 6-1).

Tabela 6-1. Funções API

Finalidade Versão Linux Versão Windows

Carregando biblioteca dlopen () LoadLibrary ()

Localizando o símbolo dlsym () GetProcAddress ()

Descarregando Biblioteca dlclose () FreeLibrary ()

Error Reporting dlerror () GetLastError ()

Independentemente do sistema operacional e / ou ambiente de programação, o paradigma típico de uso dessas funções pode ser descrito pela seguinte sequência
de pseudocódigo:

1) handle = do_load_library ("<library path>", optional_flags); if (NULL == identificador)

Reportar erro();

2) pFunction = (function_type) do_find_library_symbol (handle);

if (NULL == pFunction) {

Reportar erro(); unload_library (); lidar com = NULL; Retorna;

3) pFunction (argumentos da função); // executa a função

4) do_unload_library (identificador); lidar com = NULL;

As listagens 6-4 e 6-5 fornecem ilustrações simples do carregamento dinâmico do tempo de execução.

Listagem 6-4. Carregamento dinâmico do Linux Runtime

#include <stdlib.h> #include <stdio.h> #include <dlfcn.h>

# define PI (3.1415926536)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 77/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
typedef double (* PSINE_FUNC) (double x);

int main (int argc, char ** argv) {

void * pHandle;

pHandle = dlopen ("libm.so", RTLD_LAZY); if (NULL == pHandle) {

fprintf (stderr, "% s \ n", dlerror ()); return -1;

PSINE_FUNC pSineFunc = (PSINE_FUNC ) dZsy »(pHandle," sin "); if (NULL == pSineFunc) {

fprintf (stderr, "% s \ n", dZerror ());

dlclose (pHandle);

pHandle = NULL;

return -1;

printf ("sin (PI / 2) =% f \ n", pSineFunc (PI / 2));

dZcZose (pHandle);

pHandle = NULL;

return 0;

A Listagem 6-5 ilustra o carregamento dinâmico do tempo de execução do Windows, no qual tentamos carregar a DLL, localizar os símbolos das funções
DllRegisterServer () e / ou DllUnregisterServer () e executá-los.

Listagem 6-5. Carregamento dinâmico do tempo de execução do Windows

#include <stdio.h> #include <Windows.h>

#ifdef __cplusplus

extern "C" {

#endif // __cplusplus

typedef HRESULT (* PDLL_REGISTER_SERVER) (vazio); typedef HRESULT (* PDLL_UNREGISTER_SERVER) (vazio);

#ifdef __cplusplus}

#endif // __cplusplus

enum {

CMD_LINE_ARG_INDEX_EXECUTABLE_NAME = 0, CMD_LINE_ARG_INDEX_INPUT_DLL, CMD_LINE_ARG_INDEX_REGISTER_OR_UNREGISTER,


NUMBER_OF_SUPPORTED_CMD_LINE_ARGUMENTS} CMD_LINE_ARG_INDEX;

int main (int argc, char * argv []) {

HINSTANCE dllHandle = :: ioadiiftraryü (argv [CMD_LINE_ARG_INDEX_INPUT_DLL]);

if (NULL == dllHandle) {

printf ("Falha ao carregar% s \ n", argv [CMD_LINE_ARG_INDEX_INPUT_DLL]); return -1;

if (NUMBER_OF_SUPPORTED_CMD_LINE_ARGUMENTS> argc) {

PDLL_REGISTER_SERVER pDllRegisterServer =

(PDLL_REGISTER_SERVER ) fiett »roo] iMress (dnHandle," DllRegisterServer ");

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 78/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
if (NULL == pDllRegisterServer) {

printf ("Falha ao encontrar o símbolo V'DllRegisterServerV '"); :: FreeLibrary (dllHandle); dllHandle = NULL; return -1;

pDllRegisterServer ();

outro {

PDLL_UNREGISTER_SERVER pDllUnregisterServer =

(PDLL_UNREGISTER_SERVER ) GetProoIiMress (dllHandle, "DllUnregisterServer");

if (NULL == pDllUnregisterServer) {

printf ("Falha ao encontrar o símbolo \" DllUnregisterServer \ ""); :: FreeLibrary (dllHandle); dllHandle = NULL; return -1;

pDllUnregisterServer ();

:: FreeLibrary (dllHandle); dllHandle = NULL; return 0;

Comparação de modos de vinculação dinâmica

Existem muito poucas diferenças substanciais entre os dois modos de vinculação dinâmica. Mesmo que o momento em que o link dinâmico acontece seja
diferente, o mecanismo real de link dinâmico é completamente idêntico em ambos os casos.

Além disso, a biblioteca dinâmica que pode ser carregada estaticamente ciente também pode ser carregada dinamicamente no tempo de execução. Não há
elementos do design dinâmico da biblioteca que qualificariam a biblioteca estritamente para uso em um ou outro cenário.

A única diferença substancial é que, no cenário com reconhecimento estático, há um requisito extra que precisa ser satisfeito: você precisa fornecer o local da
biblioteca em tempo de construção. Como será mostrado no próximo capítulo, essa tarefa requer algumas sutilezas que um bom desenvolvedor de software
precisa estar ciente em ambientes Linux e Windows.

CAPÍTULO 7

Localizando as Bibliotecas
A ideia de compartilhamento de código binário está no cerne do conceito de bibliotecas. Um pouco menos óbvio é que normalmente significa que a única cópia do
arquivo binário da biblioteca residirá em um local fixo em uma determinada máquina, enquanto muitos binários de cliente diferentes precisarão localizar a
biblioteca necessária (tanto em tempo de construção quanto em tempo de execução) . A fim de abordar a questão da localização das bibliotecas, uma variedade de
convenções foram concebidas e implementadas. Neste capítulo, discutirei os detalhes dessas convenções e diretrizes.

Cenários Típicos de Caso de Uso de Biblioteca


O uso de bibliotecas provou ser uma maneira muito poderosa de compartilhar código na comunidade de software. É uma prática muito comum que empresas
com experiência acumulada em certos domínios entreguem sua propriedade intelectual na forma de bibliotecas, que terceiros podem integrar em seus produtos e
entregar aos clientes.

A prática de usar as bibliotecas acontece por meio de dois cenários de casos de uso distintos. O primeiro cenário de caso de uso ocorre quando os desenvolvedores
tentam integrar as bibliotecas de terceiros (estáticas ou dinâmicas) em seu produto. Outro cenário ocorre quando as bibliotecas (neste caso, especificamente as
bibliotecas dinâmicas) precisam ser localizadas no tempo de execução para que o aplicativo instalado na máquina do cliente seja executado corretamente.

Ambos os cenários de caso de uso apresentam o problema de localização dos arquivos binários da biblioteca. A maneira como esses problemas foram
estruturalmente resolvidos será descrita neste capítulo.

Cenário de caso de uso de desenvolvimento

Normalmente, o pacote de terceiros que contém a biblioteca, cabeçalhos de exportação e, possivelmente, alguns extras (como documentação, ajuda online, ícones
de pacotes, aplicativos utilitários, código e amostras de mídia, etc.) é instalado em um caminho predeterminado na máquina do desenvolvedor . Imediatamente
depois, o desenvolvedor pode criar uma infinidade de projetos residindo em muitos caminhos diferentes em sua máquina.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 79/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Obviamente, todo e qualquer projeto que requer vinculação com bibliotecas de terceiros precisa ter acesso aos binários da biblioteca. Caso contrário, seria
impossível terminar a construção do projeto.

Copiar a biblioteca de terceiros para todo e qualquer projeto que um desenvolvedor possa criar é definitivamente uma possibilidade, embora seja uma escolha
muito ruim. Obviamente, ter cópias da biblioteca na pasta de cada projeto que possa precisar delas vai contra a ideia original de reutilização de código que está
por trás do conceito de bibliotecas.

A alternativa aceitável seria ter apenas uma cópia dos binários da biblioteca e um conjunto de regras ajudando os projetos binários do cliente a localizá-la. Esse
conjunto de regras, comumente referido como regras de localização da biblioteca de tempo de construção, é normalmente suportado pelo vinculador da plataforma de
desenvolvimento. Essas regras estipulam basicamente como as informações sobre o caminho das bibliotecas necessárias para concluir a vinculação binária do
cliente podem ser passadas para o vinculador.

As regras de localização da biblioteca de tempo de construção são bastante elaboradas e tendem a vir em uma variedade de opções. Cada uma das principais
plataformas de desenvolvimento geralmente fornece um conjunto muito sofisticado de opções de como essas regras podem ser aplicadas.

É muito importante entender que as regras de localização da biblioteca de tempo de construção são pertinentes às bibliotecas estáticas e dinâmicas. Independentemente das
diferenças reais entre vincular bibliotecas estáticas e dinâmicas, o vinculador deve saber a localização dos binários de biblioteca necessários.

Cenário de caso de uso de tempo de execução do usuário final

Depois que os desenvolvedores integram as bibliotecas de terceiros, seus produtos estão prontos para serem entregues aos clientes finais. Com base na vasta
variedade de critérios de design e considerações da vida real, a estrutura do produto entregue pode vir em uma ampla variedade de opções:

• No caso mais simples possível, o pacote do produto contém apenas um único arquivo de aplicativo. O uso pretendido é que o cliente simplesmente execute o
aplicativo.

Este caso é bastante trivial. Para acessar e executar o aplicativo, tudo o que o usuário precisa fazer é adicionar seu caminho à variável de ambiente PATH geral.
Qualquer pessoa que não seja completamente analfabeta em informática é capaz de completar esta tarefa simples.

• Nos cenários mais complexos, o pacote do produto contém uma combinação de bibliotecas dinâmicas e um ou mais aplicativos utilitários. As bibliotecas
dinâmicas podem ser bibliotecas de terceiros encaminhadas diretamente, ou podem ser criadas pelo fornecedor do pacote, ou podem ser uma combinação de
ambas.

O uso pretendido é que uma variedade de aplicativos se vinculem dinamicamente às bibliotecas dinâmicas fornecidas. Os exemplos típicos de tal cenário no
domínio da multimídia são os frameworks multimídia, como DirectX ou GStreamer, já que cada um deles fornece (ou conta com estar disponível em tempo de
execução) um elaborado conjunto de bibliotecas dinâmicas, cada uma fornecendo um certo conjunto de funcionalidades.

Muito parecido com o cenário de caso de uso de desenvolvimento, a abordagem significativa para o problema pressupõe que haverá apenas uma cópia das
bibliotecas dinâmicas necessárias, residindo no caminho onde o procedimento de instalação as implementou. Por outro lado, essas bibliotecas podem ser
necessárias para uma infinidade de binários de cliente (outras bibliotecas dinâmicas ou os aplicativos) que residem em uma infinidade de caminhos diferentes.

Para estruturar o processo de localização dos binários das bibliotecas dinâmicas no tempo de execução (ou um pouco antes, no tempo de carregamento), um
conjunto de regras de localização da biblioteca em tempo de execução precisa ser estabelecido. As regras de localização da biblioteca de tempo de execução geralmente são
bastante elaboradas. Cada uma das plataformas de desenvolvimento fornece seu próprio sabor de opções sofisticadas de como essas regras podem ser aplicadas.

Finalmente - correndo o risco de reiterar o óbvio - as regras de localização da biblioteca em tempo de execução pertencem apenas às bibliotecas dinâmicas. A integração de
bibliotecas estáticas é sempre concluída muito antes do tempo de execução (ou seja, no estágio de vinculação do processo de construção do binário do cliente) e
nunca há necessidade de localizar as bibliotecas estáticas no tempo de execução.

Regras de localização da biblioteca de tempo de construção


Nesta seção, discutirei as técnicas de fornecimento de caminhos de tempo de construção para os arquivos binários da biblioteca. Além do movimento mais simples
possível de apenas fornecer o caminho completo para o vinculador, existem alguns níveis extras de sutileza que merecem sua atenção.

Regras de localização da biblioteca de tempo de construção do Linux

A parte importante da receita de como as regras de localização da biblioteca de tempo de construção são implementadas no Linux pertence às convenções de
nomenclatura das bibliotecas do Linux.

Convenções de nomenclatura da biblioteca estática do Linux

Os nomes de arquivo da biblioteca estática do Linux são criados de acordo com o seguinte padrão: nome do arquivo da biblioteca estática = lib +
<nome da biblioteca> + .a

A parte do meio do nome do arquivo da biblioteca é o nome real da biblioteca, que é usado para enviar a biblioteca ao vinculador.

Convenções de nomenclatura da biblioteca dinâmica do Linux


O Linux apresenta um esquema de convenção de nomenclatura de bibliotecas dinâmicas muito elaborado. Embora a intenção original fosse abordar os problemas
de versão da biblioteca, o esquema de convenção de nomenclatura impacta os mecanismos de localização da biblioteca. Os parágrafos a seguir ilustrarão os pontos
importantes.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 80/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Nome do arquivo da biblioteca dinâmica vs. nome da biblioteca

Os nomes de arquivo da biblioteca dinâmica do Linux são criados de acordo com o seguinte padrão:

nome de arquivo da biblioteca dinâmica = lib + <nome da biblioteca> + .so + <informações da versão da biblioteca>

A parte intermediária do nome do arquivo da biblioteca é o nome real da biblioteca, que é usado para enviar a biblioteca ao vinculador e, posteriormente, à
pesquisa da biblioteca em tempo de construção, bem como aos procedimentos de pesquisa da biblioteca em tempo de execução.

Informações sobre a versão da biblioteca dinâmica

As informações da versão da biblioteca transportadas pela última parte do nome do arquivo da biblioteca seguem a seguinte convenção: informações da
versão da biblioteca dinâmica = <M>. <m>. <p>

onde cada um dos mnemônicos pode representar um ou mais dígitos indicando

• M: versão principal

• m: versão secundária

• p: versão do patch (pequena alteração de código)

A importância das informações de controle de versão da biblioteca dinâmica será discutida em detalhes no Capítulo 11.

Dynamic Library Soname

Por definição, o soname da biblioteca dinâmica pode ser especificado como

biblioteca soname = lib + < nome da biblioteca > + .so + < dígito (s) da versão principal da biblioteca >

Por exemplo, o soname da biblioteca libz.so.1.2.3.4 seria libz.so.1.

O fato de que apenas os dígitos da versão principal desempenham um papel no soname da biblioteca implica que as bibliotecas cujas versões secundárias diferem
ainda serão descritas pelo mesmo valor de soname . Exatamente como esse recurso é usado, será discutido na seção Capítulo 11 dedicada ao tópico de manipulação
de versões de biblioteca dinâmica.

O soname da biblioteca é normalmente incorporado pelo vinculador no campo ELF dedicado do arquivo binário da biblioteca. A string que especifica o soname da
biblioteca é normalmente passada para o vinculador por meio do sinalizador do vinculador dedicado, assim:

$ gcc -shared <lista de arquivos de objetos> -Wl, -soname, libfoo.so.1 -o libfoo.so.1.0.0

Os programas utilitários para examinar o conteúdo dos arquivos binários geralmente fornecem a opção de recuperar o valor do soname (Figura 7-1).

ciilangpnilani-S readelf -d /llb/i386-ltnux-gnu/libz.so.l.2.3.4

A seção dinâmica no deslocamento 0xl3ee8 contém 23 entradas:

0x00900001 (NEEDED) Biblioteca compartilhada : [libc.so.6]

0x0900000e (SONAHE) Biblioteca sonane: [libz.so.

Figura 7-1. Soname da biblioteca embutido no cabeçalho ELF do binário da biblioteca

Percepção do Nome da Biblioteca por Linker vs. Humano

Observe que o nome da biblioteca conforme descrito por essas convenções não é necessariamente usado em conversas humanas para denotar a biblioteca. Por
exemplo, a biblioteca que fornece a funcionalidade de compactação em uma determinada máquina pode residir no nome de arquivo libz.so.1.2.3.4. De acordo com
a convenção de nomenclatura da biblioteca, o nome dessa biblioteca é simplesmente "z", que será usado em todas as negociações com o linker e o carregador. Do
ponto de vista da comunicação humana, a biblioteca pode ser referida como "libz", como por exemplo na seguinte descrição de bug em um sistema de
rastreamento de bugs: "Problema 3142: problema com o binário libz ausente '! Para evitar a confusão, às vezes a biblioteca nome também é conhecido como o nome
do vinculador da biblioteca.

Detalhes das regras de localização da biblioteca de tempo de construção do Linux

A especificação do caminho da biblioteca de tempo de construção é implementada no Linux na forma da chamada opção -L -l . A maneira realmente correta de
usar essas duas opções pode ser descrita pelo seguinte conjunto de diretrizes:

• Divida o caminho completo da biblioteca em duas partes: o caminho da pasta e o nome do arquivo da biblioteca.

• Passe o caminho da pasta para o vinculador, anexando-o após o sinalizador do vinculador -L .

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 81/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• Passe o nome da biblioteca (nome do vinculador) apenas ao vinculador, anexando-o após o sinalizador -l .

Por exemplo, a linha de comando para criar a demonstração do aplicativo compilando o arquivo main.cpp e vinculando-a à biblioteca dinâmica
libworkingdemo.so localizada na pasta ../sharedLib pode ter a seguinte aparência:

$ gcc main.o - / ... / sharedLib -I workingdemo -o demo

A A

Eu eu

caminho da pasta da biblioteca nome da biblioteca apenas

(não é o nome de arquivo completo da biblioteca!)

Nos casos em que a linha gcc combina a compilação com a vinculação, esses sinalizadores do linker devem ser prefixados com o sinalizador -Wl, assim:

$ gcc -Wall -fPIC main.cpp -Ml, -L ../sharedLib -Ml, -l workingdemo -o demo

Erros de iniciante: o que pode dar errado e como evitá-lo


Os problemas típicos acontecem com o programador impaciente e inexperiente em cenários que lidam com bibliotecas dinâmicas, quando uma das seguintes situações
acontece:

• O caminho completo para uma biblioteca dinâmica é passado para a opção -l (a parte -L não está sendo usada).

• A parte do caminho é passada pela opção -L , e o resto do caminho, incluindo o nome do arquivo, é passada pela opção -l .

O vinculador geralmente aceita formalmente essas variações de especificação dos caminhos da biblioteca de tempo de construção. No caso em que o caminho para
bibliotecas estáticas é fornecido, esses tipos de "liberdade criativa" não causam problemas no futuro.

No entanto, quando o caminho para a biblioteca dinâmica é passado, os problemas introduzidos pelo desvio da maneira realmente correta de passar o caminho da
biblioteca começam a aparecer no tempo de execução. Por exemplo, digamos que uma demonstração de aplicativo cliente dependa da biblioteca libmilan.so, que
reside na máquina do desenvolvedor na seguinte pasta:

/home/milan/mylibs/case_a/libmilan.so.

O aplicativo cliente é construído com sucesso pela seguinte linha de comando do vinculador: $ gcc main.o -l / home / milan / mylibs / case_a /
libmilan.so -o demo

e funciona perfeitamente na mesma máquina.

Vamos supor agora que o projeto seja implantado em uma máquina diferente e fornecido a um usuário cujo nome é "john". Quando esse usuário tentar executar o
aplicativo, nada acontecerá. Uma investigação cuidadosa (técnicas das quais serão discutidas nos Capítulos 13 e 14) revelará que o aplicativo requer em tempo de
execução a biblioteca dinâmica libmilan.so (que está OK), mas espera encontrá-la no caminho / home / milan / mylibs / case_a /.

Infelizmente, esta pasta não existe na máquina do usuário "john"!

Especificar caminhos relativos em vez de caminhos absolutos pode aliviar apenas parcialmente o problema. Se, por exemplo, o caminho da biblioteca for
especificado como relativo à pasta atual (ou seja, ../mylibs/case_a/libmilan.so), o aplicativo na máquina de john será executado apenas se o binário do
cliente e a biblioteca dinâmica necessária forem implantado na máquina de john na estrutura de pastas que mantém as posições relativas exatas entre a biblioteca
executável e dinâmica. Mas, se john se atrever a copiar o aplicativo para uma pasta diferente e tentar executá-lo de lá, o problema original reaparecerá.

Não só isso, mas o aplicativo pode parar de funcionar até mesmo na máquina do desenvolvedor, onde costumava funcionar perfeitamente. Se você decidir copiar
o binário do aplicativo em um caminho diferente na máquina do desenvolvedor, o carregador começará a pesquisar a biblioteca nos caminhos relativos ao ponto
onde o binário do aplicativo reside. Muito provavelmente esse caminho não existirá (a menos que você se preocupe em recriá-lo)!

A chave para entender a causa subjacente do problema é saber que o vinculador e o carregador não valorizam igualmente os caminhos da biblioteca passados ​na opção -L e
na opção -l .

Na verdade, o vinculador dá muito mais significado ao que você passou na opção -l . Mais especificamente, a parte do caminho que passou pela opção -L
encontra seu uso apenas durante o estágio de vinculação, mas não desempenha nenhuma função depois disso.

A parte especificada na opção -l , entretanto, é impressa no binário da biblioteca e continua desempenhando um papel importante durante o tempo de execução.
Na verdade, ao tentar encontrar as bibliotecas necessárias no tempo de execução, o carregador primeiro lê o arquivo binário do cliente tentando localizar essas
informações específicas.

Se você se atrever a desviar da regra estrita e passar qualquer coisa além do nome do arquivo da biblioteca por meio da opção -l , o aplicativo construído na
máquina de milan quando implantado e executado na máquina de john irá procurar a biblioteca dinâmica no caminho codificado, que a maioria provavelmente
existe apenas na máquina do desenvolvedor (milan), mas não na máquina do usuário (john). Uma ilustração desse conceito é fornecida na Figura 7-2.

Regras de localização da biblioteca do Windows Build Time

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 82/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Existem várias maneiras de como as informações sobre a biblioteca dinâmica exigidas no momento do link podem ser passadas para o projeto.
Independentemente da forma escolhida para especificar as regras de localização de tempo de construção, o mecanismo funciona para bibliotecas estáticas e
dinâmicas.

-L / home / milan / Demo / FirstExample / sharedLib

-primeiro

Cliente binário

Figura 7-2. A convenção -L desempenha um papel apenas durante a construção da biblioteca. O impacto da convenção -l, no entanto, permanece importante no tempo de execução
também

TEMPO DE CONSTRUÇÃO TEMPO DE EXECUÇÃO

A biblioteca necessária está localizada com êxito na máquina de tempo de execução, embora resida em uma estrutura de pastas completamente diferente.

Configurações do vinculador de projeto

A opção padrão é fornecer as informações sobre a DLL exigida pelo vinculador da seguinte maneira: • Especifique o arquivo de biblioteca de importação da DLL
(.lib) na lista de entradas do vinculador (Figura 7-3).

Páginas de propriedades do transcodificador

Configuração: Ativo (Depurar)

▼ Plataforma: ativa (Win32)

Gerenciador de configuração...

> Propriedades Comuns 4 Propriedades de Configuração Depuração Geral Diretórios VC ++ t> C / C ++ a Linker

Entrada Geral

Depuração do arquivo de manifesto Otimização do sistema IDL incorporado Linha de comando avançada t> Ferramenta de manifesto t> Gerador de documento XML 0 Navegar nas informações t> Eventos de
compilação 0 Etapa de compilação personalizada

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 83/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Dependências adicionais Ignorar todas as bibliotecas padrão Ignorar bibliotecas padrão específicas Arquivo de definição do módulo Adicionar módulo ao assembly Incorporar Arquivo de recurso gerenciado
Forçar referências de símbolo Atraso no carregamento Dlls Assembly Link Resource

uaf xc wd Jib; libc mtd. Ii b; Ii bmf xl ïb; dxva2.l ib; d 3d 9.lîb; d wm z uaf xc wd. lib; libc mt J ib;

dwma pLdll;% (D elay Load DL Ls)

Dependências Adicionais

uafxcwdJib »

libcmtd.lib SI

1
libmfx.lib
_
dxva2.tib

d3d9.ltb

4 >

Valores herdados:

Dependências Adicionais

Especifica itens adicionais para adicionar a t

[Vl Herdar dos padrões pai ou do projeto

Macros >>

Cancelar

OK

Figura 7-3. Especifique as bibliotecas necessárias na lista de dependências

• Adicione o caminho da biblioteca de importação ao conjunto de diretórios de caminho da biblioteca (Figura 7-4).

Figura 7-4. Especifique os caminhos da biblioteca

#pragma Comentário

O requisito da biblioteca pode ser especificado adicionando uma linha como esta a um arquivo de origem: #pragma comment (lib, "<nome da biblioteca de
importação, caminho completo ou caminho relativo>");

Ao encontrar esta diretiva, o compilador irá inserir um registro de pesquisa da biblioteca no arquivo objeto, que será eventualmente obtido pelo linker. Se apenas
o nome do arquivo da biblioteca for fornecido entre aspas duplas, a pesquisa pela biblioteca seguirá as regras de pesquisa da biblioteca do Windows.
Normalmente, esta opção é usada para exercer mais precisão durante o processo de pesquisa de bibliotecas e, portanto, é mais comum especificar o caminho
completo e a versão da biblioteca do que o contrário.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 84/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Uma grande vantagem de especificar os requisitos da biblioteca de tempo de construção dessa maneira é que, por estar no código-fonte, dá ao designer a
capacidade de definir os requisitos de vinculação dependendo das diretivas do pré-processador. Por exemplo,

#ifdef CUSTOMER_XYZ

#pragma comment (lib, "<biblioteca específica do cliente XYZ>"); #outro

#ifdef CUSTOMER_ABC

#pragma comment (lib, "<biblioteca específica do cliente ABC>"); #outro

#ifdef CUSTOMER_MPQ

#pragma comment (lib, "<clientMPQ-specific library>"); #endif // CUSTOMER_MPQ #endif // CUSTOMER_ABC #endif // CUSTOMER_XYZ

Referenciamento Implícito do Projeto Biblioteca

Esta opção pode ser usada apenas em casos especiais, quando o projeto de biblioteca dinâmica e seu projeto executável do cliente são partes da mesma solução do
Visual Studio. Se o projeto DLL for adicionado à lista de referências do projeto do aplicativo cliente, o ambiente do Visual Studio fornecerá tudo (automática e
quase invisivelmente para o programador) necessário para construir e executar o aplicativo.

Para começar, ele passará o caminho completo da DLL para a linha de comando do vinculador do aplicativo. Por fim, fará a cópia da DLL para a pasta runtime do
aplicativo (normalmente Debug para a versão de depuração e Release para a versão de lançamento), atendendo da maneira mais fácil possível as regras de
localização da biblioteca runtime.

As Figuras 7-5 a 7-8 ilustram a receita de como fazê-lo, usando o exemplo da solução (SystemExamination) composta pelos dois projetos relacionados: uma DLL
SystemExaminer que está estaticamente ciente vinculada pelo aplicativo SystemExaminerDemoApp.

Não especificarei a dependência da DLL contando com o primeiro método descrito anteriormente (ou seja, especificando o arquivo de biblioteca de importação
(.lib) da DLL na lista de entradas do vinculador). Esse detalhe aparentemente peculiar e um pouco contra-intuitivo é ilustrado pela Figura 7-5.

Figura 7-5. Neste método, você não precisa especificar a dependência da biblioteca diretamente

Em vez disso, será suficiente definir o projeto binário do cliente para fazer referência ao projeto de biblioteca de dependência. Visite a guia Common Properties ^
Frameworks and References (Figura 7-6).

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 85/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 7-6. Adicionando uma referência ao projeto de biblioteca de dependência

A Figura 7-7 mostra a referência do projeto de biblioteca de dependências concluído.

Figura 7-7. A referência ao projeto de biblioteca de dependência está concluída

O resultado final será que o caminho de tempo de construção da DLL necessária é passado para a linha de comando do vinculador, conforme mostrado na Figura
7-8.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 86/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 7-8. O resultado da referência implícita: o caminho exato para a biblioteca é passado para o vinculador

Regras de localização da biblioteca dinâmica em tempo de execução


O carregador precisa saber a localização exata do arquivo binário da biblioteca dinâmica para abri-lo, lê-lo e carregá-lo no processo. A variedade de bibliotecas
dinâmicas que podem ser necessárias para a execução de um programa é vasta, desde as bibliotecas de sistema sempre necessárias até as bibliotecas
personalizadas, proprietárias e específicas do projeto.

Da perspectiva do programador, a codificação dos caminhos de cada caminho de biblioteca dinâmica parece totalmente errada. Faria muito mais sentido se um
programador pudesse apenas fornecer o nome do arquivo da biblioteca dinâmica, e o sistema operacional saberia de alguma forma onde procurar a biblioteca.

Todos os principais sistemas operacionais reconheceram a necessidade de implementar tal mecanismo, que seria capaz de pesquisar e encontrar a biblioteca
dinâmica em tempo de execução com base no nome do arquivo da biblioteca fornecido pelo programa. Não apenas um conjunto de locais de biblioteca
predeterminados foi definido, mas também a ordem de pesquisa foi definida, especificando onde o sistema operacional procurará primeiro.

Finalmente, saber a localização do tempo de execução da biblioteca dinâmica é igualmente importante, independentemente de a biblioteca dinâmica ser carregada
com reconhecimento estático ou no tempo de execução.

Regras de localização da biblioteca dinâmica do Linux Runtime

O algoritmo de busca da biblioteca dinâmica em tempo de execução é governado pelo seguinte conjunto de regras, listadas na ordem de prioridade mais alta.

Bibliotecas pré-carregadas

A prioridade mais alta inquestionável acima de qualquer pesquisa de biblioteca é reservada para as bibliotecas especificadas para pré-carregamento, pois o
carregador primeiro carrega essas bibliotecas e, em seguida, começa a pesquisar as outras. Existem duas maneiras de especificar as bibliotecas pré-carregadas:

• Definindo a variável de ambiente LD_PRELOAD .

export LD_PRELOAD = / home / milan / project / libs / libmilan.so : $ LD_PRELOAD

• Por meio do arquivo /etc/ld.so.preload .

Este arquivo contém uma lista separada por espaços em branco de bibliotecas compartilhadas ELF a serem carregadas antes do programa.

Especificar as bibliotecas pré-carregadas não é a norma de design padrão. Em vez disso, ele é usado em cenários especiais, como teste de estresse de design,
diagnósticos e correção de emergência do código original.

Nos cenários de diagnóstico, você pode criar rapidamente uma versão personalizada de uma função padrão, vinculá-la às impressões de depuração e construir
uma biblioteca compartilhada cujo pré-carregamento substituirá efetivamente a biblioteca dinâmica que fornece essa função de maneira padrão.

Após a conclusão do carregamento das bibliotecas indicadas para pré-carregamento, inicia-se a busca por outras bibliotecas listadas como dependências. Ele segue
um conjunto elaborado de regras, cuja lista completa (organizada do método de prioridade mais alta para baixo) é explicada nas seções a seguir.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 87/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

rpath
Desde os primeiros dias, o formato ELF apresentava o campo DT_RPATH usado para armazenar a string ASCII contendo os detalhes do caminho de pesquisa
relevantes para o binário. Por exemplo, se o executável XYZ depende da presença de tempo de execução da biblioteca dinâmica ABC, então XYZ pode carregar em
seu DT_RPATH a string especificando o caminho no qual a biblioteca ABC pode ser encontrada em tempo de execução.

Esse recurso claramente representou um bom passo à frente ao permitir que o programador estabeleça um controle mais rígido sobre os problemas de
implantação, principalmente para evitar a ampla escala de possíveis incompatibilidades entre as versões das bibliotecas pretendidas e as disponíveis.

As informações transportadas pelo campo DT_RPATH do executável XYZ seriam lidas em tempo de execução pelo carregador. Um detalhe importante a lembrar é
que o caminho a partir do qual o carregador é iniciado desempenha um papel na interpretação das informações DT_RPATH . Mais notavelmente, nos casos em que
DT_RPATH carrega um caminho relativo, ele será interpretado não em relação à localização da biblioteca XYZ, mas sim em relação ao caminho a partir do qual o
carregador (ou seja, o aplicativo) é iniciado. Por melhor que seja, o conceito de rpath sofreu algumas modificações.

De acordo com fontes da web, por volta de 1999, quando a versão 6 da biblioteca de tempo de execução C estava em processo de substituição da versão 5, certas
desvantagens do rpath foram notadas, e ele foi substituído por um campo muito semelhante chamado runpath (DT_RUNPATH) do Formato de arquivo binário
ELF.

Hoje em dia, tanto o rpath quanto o runpath estão disponíveis, mas o runpath recebe maior consideração na lista de prioridades de pesquisa em tempo de
execução. Apenas na ausência de seu runpath irmão mais novo ( campo DT_RUNPATH ), o rpath ( campo DT_RPATH ) permanece a informação do caminho de
busca de maior prioridade para o carregador Linux. Se, entretanto, o campo runpath (DT_RUNPATH) do binário ELF não estiver vazio, o rpath será ignorado.

capítulo 7 ■ localizando os freios □

O rpath é normalmente definido passando ao vinculador o sinalizador -R ou -rpath imediatamente seguido pelo caminho que você deseja atribuir como
runpath. Além disso, por convenção, sempre que o vinculador é invocado indiretamente (ou seja, chamando gcc ou g ++), os sinalizadores do vinculador
precisam ser prefixados pelo prefixo -Wl (ou seja, "menos vírgula Wl"):

$ gcc -Wl, -R / home / milan / projects / -lmilanlibrary

A A A

III

| | valor rpath real

II

| sinalizador do linker do caminho de execução

-Wl, prefixo necessário ao invocar o linker indiretamente, por meio do gcc, em vez de invocar diretamente o ld

Alternativamente, o rpath pode ser definido especificando a variável de ambiente LD_RUN_PATH :

$ export LD_RUN_PATH = / home / milão / projetos : $ LD_RUN_PATH

Finalmente, o rpath do binário pode ser modificado após o fato, executando o programa utilitário chrpath . Uma desvantagem notável do chrpath é que ele não
pode modificar o rpath além de seu comprimento de string já existente. Mais precisamente, chrpath pode modificar e excluir / esvaziar o campo DT_RPATH ,
mas não pode inseri-lo ou estendê-lo para uma string mais longa.

A maneira de examinar o arquivo binário para o valor do campo DT_RPATH é examinar o cabeçalho ELF do binário (como executar readelf -d ou objdump -
f).

Variável de ambiente LD_LIBRARY_PATH

Foi muito cedo durante o desenvolvimento do conceito de caminho de pesquisa de biblioteca quando foi reconhecida a necessidade de um tipo de mecanismo
temporário, rápido e sujo, mas eficaz, que poderia ser usado por desenvolvedores para experimentar e testar seus projetos. A necessidade foi abordada fornecendo
o mecanismo no qual uma variável de ambiente específica (LD_LIBRARY_PATH) seria usada para satisfazer essas necessidades.

Quando o valor rpath (DT_RPATH) não é definido, este caminho fornecido desta forma é usado como a informação de caminho de pesquisa de maior prioridade.

■ Observe que neste esquema de prioridade, há uma batalha desigual entre o valor embutido no arquivo binário e a variável de ambiente. se as coisas
permanecessem iguais, a presença de rpath no binário tornaria a solução de problemas com um produto de software de terceiros impossível. Felizmente, o
novo esquema de prioridade tratou desse problema reconhecendo que o rpath é muito ditatorial e fornecendo uma maneira de substituir temporariamente
suas configurações. O runpath irmão mais novo do rpath tem o poder de silenciar o rpath desonesto e autoritário , neste caso ld_library_path tem uma
chance de obter temporariamente o tratamento de prioridade mais alta.

A sintaxe de configuração de LD_LIBRARY_PATH é idêntica à sintaxe de configuração de qualquer tipo de variável de caminho. Isso pode ser feito na instância
específica do shell, digitando, por exemplo, algo assim:

$ export LD_LIBRARY_PATH = / home / milão / projetos : $ LD_LIBRARY_PATH

Mais uma vez, o uso deste mecanismo deve ser reservado para fins de experimentação. As versões de produção dos produtos de software não devem depender
desse mecanismo.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 88/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

caminho de corrida

O conceito de runpath segue o mesmo princípio do rpath. É o campo (DT_RUNPATH) do formato binário ELF, que pode ser definido no momento da
construção para apontar para o caminho onde a biblioteca dinâmica deve procurar. Ao contrário do rpath, cuja autoridade é inquestionável, o runpath é
projetado para ser tolerante com as necessidades urgentes do mecanismo LD_LIBRARY_PATH .

O caminho de execução é definido de maneira muito semelhante a como o caminho de execução é definido. Além de passar o linkerflag -R ou - rpath ,
um sinalizador de linker --enable-new-dtags adicional precisa ser usado. Como já explicado no caso de rpath, sempre que o vinculador é chamado
indiretamente, por meio da chamada de gcc (ou g ++) em vez de invocar diretamente ld, por convenção, os sinalizadores do vinculador precisam ser
precedidos pelo prefixo -Wl :

$ gcc -Wl, -R / home / milan / projects / -Wl, - enable-new-dtags -lmilanlibrary

A A A A

I II I

| | valor real de rpath tanto rpath quanto runpath definidos

| | para o mesmo valor de string

| sinalizador do linker do caminho de execução

-Wl, prefixo necessário ao invocar o linker indiretamente, por meio do gcc, em vez de invocar diretamente o ld

Como regra, sempre que o runpath é especificado, o vinculador configura rpath e runpath com o mesmo valor.

A maneira de examinar o arquivo binário para o valor do campo DT_RUNPATH é examinar o cabeçalho ELF do binário (como executar readelf -h ou objdump -
f).

Do ponto de vista da prioridade, sempre que DT_RUNPATH contém uma string não vazia, o campo DT_RPATH é ignorado pelo carregador. Desta forma, o poder
ditatorial de rpath é subjugado e a vontade de LD_LIBRARY_PATH tem a chance de ser respeitada quando realmente é necessária.

O útil programa utilitário patchelf é capaz de modificar o campo do caminho de execução do arquivo binário. No momento, não faz parte do repositório
oficial, mas seu código-fonte e o manual simples podem ser encontrados em http://nixos.org/patchelf.html . Compilar o binário é bastante simples. O
exemplo a seguir ilustra o uso do patchelf :

$ patchelf --set-rpath <um ou mais caminhos> <executável>

A |

vários caminhos podem ser definidos, separados por dois pontos (:)

■ Nota Embora a documentação do patchelf mencione rpath, o patchelf de fato atua no campo do runpath .

Cache Idconfig

Um dos procedimentos de implantação de código padrão é baseado na execução do utilitário Linux ldconfig ( http://linux.die.net/ man / 8 /
ldconfig). A execução do utilitário ldconfig é geralmente uma das últimas etapas durante o procedimento de instalação do pacote padrão, que normalmente
requer a passagem do caminho para uma pasta contendo bibliotecas como argumento de entrada. O resultado é que o ldconfig insere o caminho da pasta
especificada na lista de pastas de pesquisa da biblioteca dinâmica mantida no arquivo /etc/ld.so.conf . No mesmo token, o caminho da pasta recém-
adicionado é verificado em busca de bibliotecas dinâmicas, o resultado é que os nomes de arquivos das bibliotecas encontradas são adicionados à lista de nomes
de arquivos das bibliotecas mantida noArquivo /etc/ld.so.cache . Por exemplo, o exame de minha máquina Ubuntu de desenvolvimento revela o conteúdo do
arquivo /etc/ld.so.conf na Figura 7-9.

mtlan @ nilan $ cat /etc/ld.so.conf include /etc/Id.so,conf,d/*.conf

nilan@ni.lan5 Is -alg /etc/Id.so.conf.d/ total 24

drwxr-xr-x 2 root 4096 17 de agosto drwxr-xr-x 131 root 12288 Fev S Irwxrwxrwx 1 root 40 Fev S es / 1386-linux-gnu_gl_conf -rw-r -
r- 1 root 108 Abr 19 -rw-r --r-- 1 root 44 abr 19 nllan @ nilan-ub-1204-32-lts: - $ cat /etc/Id.so.conf.d/* / usr / lib / \ 386-
linux-gnu / mesa V Suporte multiarch / Ub / i386-linux-gnu / us r / lib / i.386-linux-gnu / lib / i686-linux-gnu / usr / lib /
1686-llnux-gnu ff llbc configuração padrão / usr / local / lib nilan @ milanS

2012 16:69 ..

1S: 14 i386-ltnux-gnu_GL.conf -> / etc / alternativ

2012 i686-linux-griu. conf 2012 libc.conf

Figura 7-9. O conteúdo do arquivo /etc/ld.so.conf

Quando ldconfig examina todos os diretórios listados no arquivo /etc/ld.so.conf , ele encontra zilhões de bibliotecas dinâmicas cujos nomes de arquivo ele
mantém no arquivo /etc/ld.so.cache (apenas uma pequena parte é mostrada na Figura 7-10).

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 89/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
.so.OlibSOL-1.2.so.0 / usr / iib / i386-linux-gnu / libSDL-1.2.so.BlibQtXmlPatterns.so.4 / usr / lib / i386-linux-gnu /
libQtXmlPatterns. so.4libQtXnl. so.4 / usr / lib / i386-linux-gn u / libQtXml.so.4libQtSvg.so.4 / usr / lib / i386 -linux-gnu /
libQtSvg.so.4libQtSql.so.4 / u sr / lib / l386-llnux-gnu / UbQtSql.so.4llb (} tScrlpt.so.4 / usr / llb / l386-linux-gnu / UbQt
Script. so, 4UbQt0penGL. so. 4 / usr / lib / i386-linux-gnu / libQtOpenGL. so. Rede 4libQt. so.4 / usr / lib / i386-linux-gnu /
libQtNetwork.so.4libQtGui.so.4 / usr / Ub / i386-linux-gr> u / llbQtCui.so.4libQtCConf. so . 1 / usr / llb / llbQtGCortf,
portanto, lllbQtDee. So. 2 / usr / llb / libQ tDee.so.2libQtDeclarative.so.4 / usr / lib / i386-linux-gnu /
libQtDeclarative.so.4libQt DBus. .4 / usr / lib / i386-linux-gnu / libQtDBus .so.4libQtCore.so.4 / usr / lib / i386-linux -gnu /
libQtCore.so.4libQtCLucene.so.4 / usr / ltb / i386-ltnux -gnu / libQtCLucene.so.4lib QtBamf.so. 1 / usr / lib / libQtBamf.
so.HibORBltCosNaming-2.so.0 / usr / lib / i386-linux-gn u / libORBitCosNaming-2.so.OlibORBit-2.so.0 / usr / lib / i386-linux-gnu /
libORBit-2.so .0 libORBit-imodule ■ 2.so.0 / usr / lib / i386-linux-gnu / libORBit-inodule-2.so.OlibLLVM ■ 3. 0.so.1 / us r / llb
/ 1386-linux-gnu / libLLVM- 3.0.so.llIblSlQXvMC.so.l / usr / lib / llbI81SXv MC, so.llibIntelXvMC, so.l / usr / lib /
libIntelXvHC, então, llibIOL-2, então, 0 / usr / lib / i386-lin ux-gnu / libIDL-2.so.0libICE.so.6 / usr / lib / i386-linux-gnu /
libICE.so.6libCeoIPUpdate .so. .O / usr / lib / libCeoIPUpdate.so.aiibCeoIP.so.i / usr / lib / libCeolP.so.ilibCLU.so.i / usr /
lib / i386-linux-gnu / libGLU.so.llibGLEMnx.so.1.6 / usr / lib / 1386-linux-gnu / libGLE Wnx.so.1.SlibGLEW.so.1.6 / usr / lib /
i386-linux-gnu / libGLEW.so.1.6libGL.so.1 / usr / lib / i386 -linux-gnu / nesa / libGL.so.llibFS.so.6 / usr / lib /
libFS.so.6libFLAC.so.8 / usr / lib / i386-linux-gnu / lib 0 / usr / lib / i386-linux-gn u / libORBitCosNaming-2.so.OlibORBit-
2.so.0 / usr / lib / i386-linux-gnu / libORBit-2.so.0 libORBit-imodule ■ 2. so.0 / usr / lib / i386-linux-gnu / libORBit-inodule-
2.so.OlibLLVM ■ 3. 0.so.1 / us r / llb / 1386-linux-gnu / libLLVM- 3.0.so.llIblSlQXvMC.so.l / usr / lib / llbI81SXv MC,
so.llibIntelXvMC, so.l / usr / lib / libIntelXvHC, então, llibIOL-2, então, 0 / usr / lib / i386-lin ux-gnu / libIDL-
2.so.0libICE.so.6 / usr / lib / i386-linux-gnu / libICE.so.6libCeoIPUpdate .so. .O / usr / lib / libCeoIPUpdate.so.aiibCeoIP.so.i
/ usr / lib / libCeolP.so.ilibCLU.so.i / usr / lib / i386-linux-gnu / libGLU.so.llibGLEMnx.so.1.6 / usr / lib / 1386-linux-gnu /
libGLE Wnx.so.1.SlibGLEW.so.1.6 / usr / lib / i386-linux-gnu / libGLEW.so.1.6libGL.so.1 / usr / lib / i386 -linux-gnu / nesa /
libGL.so.llibFS.so.6 / usr / lib / libFS.so.6libFLAC.so.8 / usr / lib / i386-linux-gnu / lib 0 / usr / lib / i386-linux-gn u /
libORBitCosNaming-2.so.OlibORBit-2.so.0 / usr / lib / i386-linux-gnu / libORBit-2.so.0 libORBit-imodule ■ 2. so.0 / usr / lib /
i386-linux-gnu / libORBit-inodule-2.so.OlibLLVM ■ 3. 0.so.1 / us r / llb / 1386-linux-gnu / libLLVM- 3.0.so.llIblSlQXvMC.so.l /
usr / lib / llbI81SXv MC, so.llibIntelXvMC, so.l / usr / lib / libIntelXvHC, então, llibIOL-2, então, 0 / usr / lib / i386-lin ux-
gnu / libIDL-2.so.0libICE.so.6 / usr / lib / i386-linux-gnu / libICE.so.6libCeoIPUpdate .so. .O / usr / lib /
libCeoIPUpdate.so.aiibCeoIP.so.i / usr / lib / libCeolP.so.ilibCLU.so.i / usr / lib / i386-linux-gnu / libGLU.so.llibGLEMnx.so.1.6
/ usr / lib / 1386-linux-gnu / libGLE Wnx.so.1.SlibGLEW.so.1.6 / usr / lib / i386-linux-gnu / libGLEW.so.1.6libGL.so.1 / usr / lib
/ i386 -linux-gnu / nesa / libGL.so.llibFS.so.6 / usr / lib / libFS.so.6libFLAC.so.8 / usr / lib / i386-linux-gnu / lib 0
libORBit-imodule ■ 2.so.0 / usr / lib / i386-linux-gnu / libORBit-inodule-2.so.OlibLLVM ■ 3. 0.so.1 / us r / llb / 1386-linux-gnu
/ libLLVM- 3.0.so.llIblSlQXvMC.so.l / usr / lib / llbI81SXv MC, so.llibIntelXvMC, so.l / usr / lib / libIntelXvHC, então, llibIOL-
2, então, 0 / usr / lib / i386-lin ux-gnu / libIDL-2.so.0libICE.so.6 / usr / lib / i386-linux-gnu / libICE.so.6libCeoIPUpdate .so.
.O / usr / lib / libCeoIPUpdate.so.aiibCeoIP.so.i / usr / lib / libCeolP.so.ilibCLU.so.i / usr / lib / i386-linux-gnu /
libGLU.so.llibGLEMnx.so.1.6 / usr / lib / 1386-linux-gnu / libGLE Wnx.so.1.SlibGLEW.so.1.6 / usr / lib / i386-linux-gnu /
libGLEW.so.1.6libGL.so.1 / usr / lib / i386 -linux-gnu / nesa / libGL.so.llibFS.so.6 / usr / lib / libFS.so.6libFLAC.so.8 / usr /
lib / i386-linux-gnu / lib 0 libORBit-imodule ■ 2.so.0 / usr / lib / i386-linux-gnu / libORBit-inodule-2.so.OlibLLVM ■ 3. 0.so.1 /
us r / llb / 1386-linux-gnu / libLLVM- 3.0.so.llIblSlQXvMC.so.l / usr / lib / llbI81SXv MC, so.llibIntelXvMC, so.l / usr / lib /
libIntelXvHC, então, llibIOL-2, então, 0 / usr / lib / i386-lin ux-gnu / libIDL-2.so.0libICE.so.6 / usr / lib / i386-linux-gnu /
libICE.so.6libCeoIPUpdate .so. .O / usr / lib / libCeoIPUpdate.so.aiibCeoIP.so.i / usr / lib / libCeolP.so.ilibCLU.so.i / usr /
lib / i386-linux-gnu / libGLU.so.llibGLEMnx.so.1.6 / usr / lib / 1386-linux-gnu / libGLE Wnx.so.1.SlibGLEW.so.1.6 / usr / lib /
i386-linux-gnu / libGLEW.so.1.6libGL.so.1 / usr / lib / i386 -linux-gnu / nesa / libGL.so.llibFS.so.6 / usr / lib /
libFS.so.6libFLAC.so.8 / usr / lib / i386-linux-gnu / libFl AC.SO.8llbFLAC ++. So.6 / usr / lib / i386-linux-gnu / libFLAC ++. TÃO.
6 libBrokenLocale.so.1 / lib / \ 386-linux-gnu / libsrokenLocale.so.llibBrokenLocale.so / u sr / lib / 1386-linux-gnu /
libBrokenLocale.sold-linux.so.2 / lib / 1386-linux -gnu / Id-linu

Figura 7-10. O conteúdo (pequena parte) do /etc/ld.so.cachefile

capítulo 7 ■ localizando os freios □

■ Observe que algumas das bibliotecas referenciadas pelo arquivo /etc/ld.so.conf podem residir nos chamados caminhos de biblioteca confiáveis. se o
sinalizador do vinculador -z nodeflib foi usado ao construir o executável, as bibliotecas encontradas nos caminhos da biblioteca confiável do sistema
operacional serão ignoradas durante a pesquisa da biblioteca.

Os caminhos da biblioteca padrão (/ lib e / usr / lib)


Os caminhos / lib e / usr / lib são os dois locais padrão onde o Linux OS mantém suas bibliotecas dinâmicas. Os programas de terceiros projetados para
serem usados ​com privilégios de superusuário e / ou para estarem disponíveis para todos os usuários normalmente implantam sua biblioteca dinâmica em um
desses dois locais.

Por favor note que o caminho / usr / local / lib faz não pertencem a esta categoria. Claro, nada impede que você adicione à lista de prioridades usando um
dos mecanismos descritos anteriormente.

■ Observe que se o executável foi vinculado ao sinalizador de vinculador -z nodeflib , todas as bibliotecas encontradas nos caminhos de bibliotecas
confiáveis ​do sistema operacional serão ignoradas durante a pesquisa da biblioteca.

Resumo do esquema de prioridade


Em resumo, o esquema de prioridade tem as duas versões operacionais a seguir. Quando o campo RUNPATH é especificado (ou seja, DT_RUNPATH não está vazio)

1. LD_LIBRARY_PATH

2. runpath ( campo DT_RUNPATH )

3. ld.so.cache

4. caminhos de biblioteca padrão (/ lib e / usr / lib)

Na ausência de RUNPATH (ou seja, DT_RUNPATH é uma string vazia)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 90/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
1. RPATH do binário carregado, seguido pelo RPATH do binário, que carrega todo o caminho até o executável ou a biblioteca dinâmica que carrega todos eles

2. LD_LIBRARY_PATH

3. ld.so.cache

4. caminhos de biblioteca padrão (/ lib e / usr / lib)

Para obter mais detalhes sobre este tópico específico, verifique a página do manual do carregador do Linux ( http://linux.die.net/man/1/ld ).

Regras de localização da biblioteca dinâmica do Windows Runtime

No conhecimento mais simples, popular e difundido sobre o tópico, os dois locais a seguir são mais usados ​como os caminhos favoritos para implantar a DLL
necessária no tempo de execução:

• O mesmo caminho em que reside o arquivo binário do aplicativo

• Uma das pastas DLL do sistema (como C: \ Windows \ System ou C: \ Windows \ System32)

No entanto, não é aqui que a história termina. Os esquemas de prioridade de pesquisa da biblioteca dinâmica de tempo de execução do Windows são muito mais
sofisticados, pois os seguintes fatores desempenham um papel no esquema de prioridade:

• Os aplicativos da Windows Store (Windows 8) têm um conjunto de regras diferente dos aplicativos da área de trabalho do Windows.

• Se a DLL com o mesmo nome já está carregada na memória.

• Se a DLL pertence ao grupo de DLLs conhecidas para a versão fornecida do sistema operacional Windows.

Para obter informações mais precisas e atualizadas, faz mais sentido verificar a documentação oficial da Microsoft sobre este tópico, atualmente localizada em
http://msdn.microsoft.com/en-us/library/windows/ desktop / ms682586 (v = vs. 85) .aspx.

Demonstração do Linux de tempo de construção e convenções de tempo de execução


O exemplo a seguir ilustra os efeitos positivos de seguir estritamente as convenções -L e -R . O projeto usado neste exemplo é composto do projeto de biblioteca
dinâmica e seu projeto de aplicativo de teste. Para demonstrar a importância da aplicação da convenção -L , os dois aplicativos de demonstração são criados. O
primeiro, apropriadamente denominado testApp_withMinusL, demonstra os efeitos positivos do uso do sinalizador de vinculador -L . O outro
(testApp_withoutMinusL) demonstra que tipo de problemas podem acontecer se a convenção -L não for seguida.

Ambos os aplicativos também contam com a opção rpath para especificar o local do tempo de execução da biblioteca dinâmica necessária. A pasta do projeto da
biblioteca dinâmica e a pasta do projeto dos aplicativos são estruturadas como a Figura 7-11.

Mlnus_L_lnvestlgatton

'fi _} A dependência do tempo de execução está na biblioteca

residir na pasta no mesmo nível (../deploy folder) -

denoNoMinusL deploy

1
- llbdynanicllnklngdeno.so UlustratlngMlnusLInportance, png Makefile sharedLib

1
- exportações - sharedLibExports.h

- Itbdynanicllnklngdeno.so

- Makefile - <- src

No momento da construção, a dependência está na biblioteca que reside na pasta localizada um nível acima, (ou seja, pasta ../ implantar), que é diferente do caminho do tempo de
execução

1
- testDynanicLinking.c

testApp_wi.thMT.nusL

- demoMtnusL

- Makefile

1
- src - naln.c

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 91/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
testApp_wtthoutMinusL

- dernoNoMinusL

- Makefile

- src

1
- naln.c 9 diretórios, 15 arquivos

Figura 7-11. A estrutura de pastas do projeto projetada para ilustrar os benefícios de seguir estritamente as convenções -L -l

CAPÍTULO 7 LOCALIZANDO AS LiBRARiES O Makefile do aplicativo que não depende da convenção -L é semelhante à Listagem 7-1.

Listagem 7-1. Makefile não depende da convenção -L

# Importação inclui

COMMON_INCLUDES = -I ../ sharedLib / exports /

# Sources / objects SRC_PATH = ./src

OBJETOS = $ (SRC_PATH) /main.o

# Bibliotecas

SYSLIBRARIES = \

-lpthread \ -lm \

-ldl

DEMOLIB_PATH = ../deploy

# especificar o caminho completo ou parcial pode sair pela culatra em tempo de execução !!! DEMO_LIBRARY =
../deploy/libdynamiclinkingdemo.so

LIBS = $ (SYSLIBRARIES) $ (DEMO_LIBRARY) -Wl, -Bdynamic

# Saídas

EXECUTABLE = demoNoMinusL

# Compilador

INCLUDES = $ (COMMON_INCLUDES)

DEBUG_CFLAGS = -Wall -g -O0

RELEASE_CFLAGS = -Wall -O2

ifeq ($ (DEBUG), 1)

CFLAGS = $ (DEBUG_CFLAGS) $ (INCLUDES) else

CFLAGS = $ (RELEASE_CFLAGS) $ (INCLUDES) Endif

COMPILAR = g ++ $ (CFLAGS)

# Linker

RUNTIME_LIB_PATH = -Wl, -R $ (DEMOLIB_PATH)

LINK = g ++

# Procedimentos de compilação / descrições de destino padrão: $ (EXECUTABLE)

% .o:% .c

$ (COMPILAR) -c $ <-o $ @ $ (EXECUTÁVEL): $ (OBJETOS)

$ (LINK) $ (OBJETOS) $ (LIBS) $ (RUNTIME_LIB_PATH) -o $ (EXECUTÁVEL)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 92/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
limpar:

rm $ (OBJETOS) $ (EXECUTÁVEL)

implantar:

tornar limpo; faço; patchelf --set-rpath ../deploy:./deploy $ (EXECUTABLE); \ cp $ (EXECUTABLE) ../;

O Makefile do aplicativo que segue a convenção -L se parece com a Listagem 7-2.

Listagem 7-2. Makefile Seguindo a Convenção -L

# A importação inclui INCLUIÇÕES COMUNS

-I ../ sharedLib / exports /

# Sources / objects SRC_PATH OBJECTS

= ./src

= $ (SRC_PATH) /main.o

# Bibliotecas SYSLIBRARIES

-lpthread \ -lm \

-ldl

SHLIB_BUILD_PATH = ../sharedLib

DEMO_LIBRARY = -L $ (SHLIB_BUILD_PATH) -ldynamiclinkingdemo

libs " = $ (SYSLIBRARIES)" $ (DEMO_LIBRARY) -Wl, -Bdynamic

# Saídas EXECUTÁVEIS

# Compiler INCLUDES DEBUG_CFLAGS RELEASE CFLAGS

demoMinusL

= $ (COMMON_INCLUDES) = -Wall -g -Où = -Wall -O2

ifeq ($ (DEBUG), 1)

CFLAGS = $ (DEBUG_CFLAGS) $ (INCLUI)

else CFLAGS endif

= $ (RELEASE_CFLAGS) $ (INCLUDES) = g ++ $ (CFLAGS) ../deploy

COMPILAR

# Linker DEMOLIB_PATH

RUNTIME_LIB_PATH = -Wl, -R $ (DEMOLIB_PATH) LINK = g ++

# Procedimentos de compilação / descrições de destino padrão: $ (EXECUTABLE)

% .o:% .c

$ (COMPILAR) -c $ <-o $ @ $ (EXECUTÁVEL): $ (OBJETOS)

$ (LINK) $ (OBJETOS) $ (LIBS) $ (RUNTIME_LIB_PATH) -o $ (EXECUTÁVEL)

limpar:

rm $ (OBJETOS) $ (EXECUTÁVEL)

implantar:

tornar limpo; faço; patchelf --set-rpath ../deploy:./deploy $ (EXECUTABLE); \ cp $ (EXECUTABLE) ../;

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 93/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
demoMtnusL denoNoMlnusL implantar Makefile sharedLib

testApp_withMlnusL testApp_wlthoutMtnusL L_lnvestlgation $ L_investigation $ L_investigation $ L_ Investigar

L jjiip ' III ill i hi Idd denoMlnusL

Lnux-gate.so.l => (0xb77d909O) llbdynanicllnklngderio.so => ​


./deploy/libdynamlclifikingderio.so (0xb77d40O0) "llbc.so.6 /lib/l386-
r
llnux-gnu/llbc.so12,6 (0xb7612,6) Ub / ld-Unux.so.2 (Oxb77daO30)

, L_lnvestlgat \ o _L_lnvestigation $ ^ Inve ^^^ yoLLOnS

investigações Idd demoNorinusL llnux-gate.so.l => (0xb7700000) ../deploy/libdynantcllnklngdeno.so => ​


rot found ltbc.so.6 =>
/Ub/l386-llnux-gnu/llbc.so.6 (0xb7S3c000 ) /llb/ld-llnux.so.2 (0xb77O100O) Z ^ w ^ tlgatlonS J L_l nves tig3tWn $ -,
L_lnvestlgation_S _L_tnvesttgatri! Sn $ nkdlr ../deploy
=

, L_lnvestlgctJon $ cp ./deploy/llbdynasnlclinklngdeno■ so ../deploy/ L_investigation ^ - ■ - -_ __ ^ f __ ""

IH nimtHinat-iMAi

21:30 21:34 21:33 21:33 21:30 21:15 21:33 21:33 21133 / MlnusJ / Minusl / MinusJ / Mlnusj / Minusl

A biblioteca especificada como -L <path> -l <name> pode ser tratada perfeitamente tanto na vinculação quanto no tempo de execução (onde seu
nome pode ser combinado com rpath).

/ Menos / Menos / Menos, / Menos / Menos

A biblioteca especificada sem requer a preocupação com a manutenção dos caminhos relativos.

/ Minus / MinusJ / Minus / Minus / Minus / Minus / Minus / Minus / Minu?

Quando o processo de construção da biblioteca dinâmica é concluído, seu binário é implantado na pasta de implantação , que reside nos dois níveis de
profundidade acima da pasta na qual o aplicativo Makefile reside. Portanto, o caminho de tempo de construção precisa ser especificado como
../deploy/libdynamiclinkingdemo.so.

A Figura 7-12 ilustra a vantagem de aderir à convenção -L : a imunidade do programa à mudança dos caminhos da biblioteca em tempo de execução.

/ Minus_L_investlgatlonS Is -alg

.L 1'-. litígios

ZA

Investigations idd denoNoMinusL llnux-gate.so.l => (0xb77dl000) ../deploy/llbdynamlclinkingdeno.so (0xb77cc000) llbc.so.6 =:>
/Ilb/l386-llnux-gnu/llbc.so,öO ) /llb/ld-llnux.so.2 (Oxb77d20O0) / Mlnus_L_uTv; 'i-iQatlonS

Figura 7-12. A vantagem de seguir cuidadosamente as convenções -L -l. Seguir a convenção normalmente significa estar livre de preocupações em tempo de execução

Quando o caminho da biblioteca de tempo de construção foi especificado com a opção -L , o nome da biblioteca é efetivamente separado do caminho e, como tal,
impresso no arquivo binário do cliente. Quando chega a hora de conduzir a pesquisa em tempo de execução, o nome impresso (ou seja, não o caminho mais o
nome, mas apenas o nome da biblioteca!) Se encaixa muito bem com a implementação do algoritmo de pesquisa em tempo de execução.

CAPÍTULO 8

Projetando Bibliotecas Dinâmicas: Tópicos


Avançados
O objetivo deste capítulo é discutir os detalhes do processo de vinculação dinâmica. Um fator crucial neste processo é o conceito de mapeamento de memória.
Basicamente, ele permite que uma biblioteca dinâmica que já está carregada no mapa de memória de um processo em execução seja mapeada no mapa de
memória de outro processo em execução simultaneamente.

A regra importante da vinculação dinâmica é que processos diferentes compartilham o segmento de código da biblioteca dinâmica, mas não compartilham os
segmentos de dados. Espera-se que cada um dos processos que carregam a biblioteca dinâmica forneça sua própria cópia dos dados nos quais o código da
biblioteca dinâmica opera (ou seja, o segmento de dados da biblioteca). Seguindo a analogia culinária, vários chefs em vários restaurantes podem usar
simultaneamente o mesmo livro de receitas (instruções). É muito provável, entretanto, que chefs diferentes usem receitas diferentes do mesmo livro. Além disso,
presume-se que os pratos elaborados a partir das receitas de um mesmo livro de receitas serão servidos a diferentes clientes. Obviamente, apesar de os chefs lerem
o mesmo livro de receitas, cada um deve usar seu próprio conjunto de pratos e utensílios de cozinha. Caso contrário, seria uma grande confusão.

Por melhor e simples que a história toda pareça agora, vários problemas técnicos precisaram ser resolvidos ao longo do caminho. Vamos olhar mais de perto.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 94/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Por que endereços de memória resolvidos são obrigatórios


Antes de entrar nos detalhes dos problemas técnicos encontrados durante o projeto de implementações de links dinâmicos, vale a pena reiterar alguns fatos
simples enraizados no domínio da linguagem assembly e das instruções de máquina que, em última análise, determinam tantos outros detalhes.

Ou seja, certos grupos de instruções esperam que o endereço do operando na memória seja conhecido em tempo de execução. Em geral, os dois grupos de
instruções a seguir exigem estritamente os endereços calculados com precisão:

• As instruções de acesso a dados (mov, etc.) requerem o endereço do operando na memória. Para

Por exemplo, para acessar uma variável de dados, a instrução mov assembler da arquitetura X86 requer o endereço de memória absoluto da variável para
transferir os dados entre a memória e um registro de CPU.

A seguinte sequência de instruções de montagem é usada para incrementar uma variável armazenada na memória:

mov eax, ds: 0xBFD10000; carregue a variável do endereço 0xBFD10000 para registrar eax add eax, 0x1; incrementar o valor carregado

mov ds: 0xBFD10000, eax; armazene o resultado de volta no endereço 0xBFD10000

• Chamadas de subrotina (call, jmp, etc.) requerem o endereço da função no segmento de código. Por exemplo, para chamar uma função, a instrução de
chamada deve ser fornecida com o endereço de memória do segmento de código do ponto de entrada da função.

A seguinte sequência de instruções de montagem executa a chamada de função real:

ligue para 0x0A120034; função de chamada cujo ponto de entrada reside no endereço 0x0A120034

que é equivalente a

pressione eip + 2; o endereço de retorno é o endereço atual + tamanho de duas instruções jmp 0x0A120034; saltando para o endereço
de my_function

Para que as coisas sejam um pouco mais fáceis, existem cenários em que apenas um deslocamento relativo desempenha um papel. Os endereços de variáveis ​
estáticas, bem como os pontos de entrada de funções de escopo local (ambos declarados usando a palavra-chave static no sentido da linguagem de
programação C) podem ser resolvidos conhecendo-se apenas o deslocamento relativo das instruções que os referenciam. Tanto o acesso a dados quanto as
instruções do assembler de chamada de sub-rotina têm os tipos que exigem o deslocamento relativo em vez do endereço absoluto. Isso, no entanto, não remove o
problema geral; apenas o diminui até certo ponto.

Problema geral de resolução de referências


Vamos considerar o caso mais simples possível em que o executável (aplicativo) é o binário do cliente que carrega uma única biblioteca dinâmica. O seguinte
conjunto de fatos conhecidos descreve o cenário de trabalho:

• Uma parte fixa e predeterminada do blueprint do mapa de memória do processo é fornecida pelo binário executável.

• Depois que o carregamento dinâmico é concluído, a biblioteca dinâmica se torna uma parte legítima do processo.

• A conexão entre o executável e a biblioteca dinâmica ocorre naturalmente pelo executável chamando uma ou mais funções implementadas e devidamente
exportadas por uma biblioteca dinâmica.

Aí vem a parte interessante.

O processo de carregamento da biblioteca no mapa de memória do processo começa traduzindo o intervalo de endereços do segmento da biblioteca para um novo
local. Em geral, a faixa de endereços onde a biblioteca dinâmica será carregada não é conhecida com antecedência. Em vez disso, é determinado no momento do
carregamento pelo algoritmo interno do módulo do carregador.

O nível de indeterminismo neste cenário é apenas ligeiramente diminuído pelo fato de que o formato executável estipula o intervalo de endereços onde a
biblioteca dinâmica pode ser carregada. No entanto, o intervalo estipulado de endereços permitidos é bastante amplo, pois foi projetado para acomodar muitas
bibliotecas dinâmicas carregadas ao mesmo tempo. Isso claramente não ajuda muito a adivinhar onde exatamente a biblioteca dinâmica será carregada.

O processo de tradução de endereços (ilustrado na Figura 8-1) que acontece durante o carregamento dinâmico da biblioteca é o problema crucial da ligação
dinâmica, o que torna todo o conceito bastante complexo.

gj
0xA0120000

0x00000000

Figura 8-1. A tradução de endereços acontece inevitavelmente quando o carregador tenta encontrar um lugar para a biblioteca dinâmica no mapa de memória do processo

Qual é exatamente o problema com a tradução do endereço?

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 95/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
A tradução de endereços não é o problema em si. Você viu nos capítulos anteriores que o vinculador executa rotineiramente essa operação simples ao tentar
agrupar os arquivos de objeto no mapa de memória do processo. No entanto, é muito importante qual módulo realiza a tradução do endereço.

Mais especificamente, existem diferenças substanciais no cenário em que o vinculador executa a conversão de endereço do cenário em que o carregador faz a mesma
coisa.

• Ao realizar a tradução de endereços, o vinculador em geral tem uma situação de "quadro limpo / neve virgem". Nenhum dos arquivos-objeto obtidos pelo
vinculador durante o processo de tiling tem qualquer uma das referências resolvidas. Isso dá ao vinculador um grande grau de liberdade para manipular os
arquivos de objeto ao tentar encontrar o lugar certo para eles. Ao concluir a colocação inicial dos arquivos de objeto, o vinculador examina a lista de referências
não resolvidas, as resolve e marca os endereços corretos nas instruções de montagem.

• A carregadeira, por outro lado, opera em circunstâncias significativamente diferentes. Toma como entrada o binário da biblioteca dinâmica, que já passou no
processo completo de construção e resolveu todas as referências. Em outras palavras, todas as instruções do montador são carimbadas com os endereços corretos.

Nos casos particulares em que o linker imprimiu os endereços absolutos nas instruções do montador, a tradução de endereço realizada pelo carregador torna os endereços
impressos completamente sem sentido. Executar essas instruções fundamentalmente interrompidas fornece, na melhor das hipóteses, resultados falsos e pode ser
muito perigoso. Obviamente, a tradução de endereços realizada durante o tipo de carregamento dinâmico cai na ampla categoria do paradigma "elefante na loja
de porcelana".

Em resumo, a tradução de endereços do loader não pode ser evitada, pois é inerente à ideia de carregamento dinâmico. No entanto, isso impõe imediatamente um
tipo de problema muito sério. Felizmente, embora isso não possa ser evitado, algumas maneiras de dançar em torno dele foram implementadas com sucesso.

Quais símbolos provavelmente sofrerão com a tradução de endereços?

É quase óbvio que as funções e variáveis ​declaradas estáticas (no sentido da linguagem C, como relevantes apenas para o arquivo em que residem) estão fora de
perigo. Na verdade, uma vez que apenas as instruções próximas precisam acessar esses símbolos, todos os acessos podem ser implementados fornecendo os
deslocamentos de endereço relativos. Qual é a situação com as funções e variáveis ​que não são declaradas estáticas?

Como se constatou, não ser declarado estático ainda não significa que tal função ou variável estará inevitavelmente condenada a sofrer com a tradução do
endereço.

Na verdade, apenas as funções e variáveis ​cujos símbolos são exportados pela biblioteca dinâmica têm garantia de sofrer os efeitos negativos da tradução de endereços.
Na verdade, quando o vinculador sabe que um determinado símbolo é exportado, ele implementa todos os acessos por meio dos endereços absolutos. A tradução
do endereço torna essas instruções inutilizáveis.

O exemplo de código analisado no Apêndice A ilustra esse ponto, no qual duas variáveis ​não estáticas são apresentadas no código, das quais apenas uma é
exportada pela biblioteca dinâmica. Como mostra a análise, a variável exportada é aquela que é afetada pela tradução de endereço de carregamento dinâmico.

Problemas causados ​pela tradução de endereços


Há momentos em que a tradução de endereços durante o carregamento dinâmico pode causar problemas. Felizmente, eles podem ser sistematizados em dois
cenários gerais.

Cenário 1: O cliente binário precisa saber o endereço dos símbolos da biblioteca dinâmica

Este é o cenário mais básico, que acontece quando o binário do cliente (um executável ou uma biblioteca dinâmica) conta com os símbolos de uma biblioteca
dinâmica carregada disponíveis em tempo de execução, mas não sabe qual será o endereço final, conforme ilustrado na Figura 8 -2.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 96/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 8-2. Cenário 1: o binário do cliente deve resolver os símbolos da biblioteca dinâmica

Se você assumir a abordagem usual em que a tarefa de resolver os endereços de símbolo tradicionalmente pertence ao vinculador (e apenas ao vinculador), você
está em uma situação problemática. Ou seja, o vinculador já concluiu sua tarefa de construir tanto o binário do cliente quanto a biblioteca, que está sendo
carregada dinamicamente.

Rapidamente se torna óbvio que certos pensamentos "fora da caixa" precisam ser aplicados para resolver esse tipo de situação. A solução leva à concessão de parte
das responsabilidades do vinculador de resolver os símbolos para o carregador.

No novo esquema das coisas, a nova capacidade do carregador de obter algumas das habilidades do vinculador é normalmente implementada como um módulo
comumente referido como vinculador dinâmico.

Cenário 2: a biblioteca carregada não conhece mais os endereços de seus próprios símbolos

Normalmente, as funções ABI exportadas pelas bibliotecas dinâmicas são os pontos de entrada bem encapsulados na funcionalidade interna da biblioteca. A
sequência típica que acontece em tempo de execução é que o binário do cliente normalmente chama um dos métodos ABI, que por sua vez chama as funções
internas da biblioteca, que não são de interesse particular para o binário do cliente e, portanto, não são exportadas.

Um possível cenário diferente (embora um pouco menos frequentemente encontrado) é quando uma função ABI de biblioteca dinâmica chama internamente a
outra função ABI.

Vamos supor, por exemplo, que uma biblioteca dinâmica hospede um módulo que exporta as duas funções de interface:

• Inicializar ()

• Desinicializar ()

O fluxo de execução interna de cada uma das duas funções muito provavelmente assumirá a sequência de chamadas das funções internas da biblioteca, declaradas
com escopo estático. Chamar os métodos internos é normalmente executado pela família de instruções de chamada do assembler que apresenta endereços
relativos. A tradução de endereço não afeta negativamente a implementação das funções de chamada, conforme ilustrado na Figura 8-3.

Independentemente do fato de que o endereço absoluto da função ABI não é conhecido, os deslocamentos relativos às funções estáticas internas da biblioteca são suficientes para
implementar as instruções de chamada.

A tradução do endereço não causou problemas, pelo menos nesta parte do quadro geral.

^ pt Função interna da biblioteca, declarada 'estática', acessível por parente

deslocamento de endereço

Função exportada da biblioteca (acessível por endereço absoluto)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 97/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Devido à tradução do endereço, o endereço do símbolo é desconhecido.

Figura 8-3. Independentemente da tradução de endereço, as chamadas para funções locais (que podem ser implementadas como saltos relativos) podem ser facilmente resolvidas

É perfeitamente possível, entretanto, que os projetistas da biblioteca decidam fornecer a função de interface Reinitialize () . Não seria surpreendente nem
errado que essa função primeiro chame internamente a função de interface Uninitialize () , seguida imediatamente pela chamada para a função de interface
Initialize () .

Por ser a função de interface ABI, o ponto de entrada da função Reinitialize () deve pertencer ao conjunto de símbolos exportados da biblioteca dinâmica. As
instruções de salto que se referem a esta função não podem ser implementadas como saltos relativos. Em vez disso, o vinculador deve implementar as instruções
de salto / chamada como saltos para os endereços absolutos.

Obviamente, agora você tem um tipo de situação interessante. A parte danificada neste cenário não é mais apenas o binário do cliente, mas também a biblioteca
carregada. Depois que a tradução da memória é realizada pelo carregador, os endereços da função não são mais aplicáveis. As instruções de chamada do
assembler para que o linker tenha uma boa impressão com os endereços absolutos não só não têm sentido, mas são potencialmente perigosas, já que seu alvo de
salto não está mais onde foi planejado originalmente, como mostrado na Figura 8-4.

Figura 8-4. Cenário 2: uma função ABI que chama internamente outra sofre de problemas de referências não resolvidos. Ambos os pontos de entrada da função são designados para
exportação, o que estimula o compilador a implementar chamadas como saltos absolutos. Resolver os endereços absolutos não é possível até que o carregador conclua a tradução do
endereço

Devido à tradução do endereço, o endereço do símbolo é desconhecido.

o
Novamente, o problema idêntico que você enfrenta com as funções ABI existe com as variáveis ​de escopo global da biblioteca dinâmica.

Coordenação Linker-Loader
Foi reconhecido no início que no cenário de vinculação dinâmica, o vinculador não pode resolver completamente tudo o que normalmente resolve durante a
construção do executável monolítico.

Durante o estágio inicial de vinculação dinâmica, o carregador carrega o segmento de código da biblioteca dinâmica no novo intervalo de endereços. Mesmo que o
vinculador tenha concluído legitimamente a tarefa de resolver as referências ao construir a biblioteca dinâmica, isso simplesmente não é suficiente; o processo de
tradução de endereços tornou inválidos os endereços absolutos impressos nas instruções de chamada do assembler.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 98/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O fato de o "terremoto" acontecer depois que o vinculador fez tudo o que poderia fazer implica que deve haver "alguém inteligente" para consertar os problemas
após o fato. Esse "alguém inteligente" foi escolhido para ser o carregador.

Estratégia Geral

Conhecendo todas as restrições descritas anteriormente, a cooperação entre o vinculador e o carregador foi estabelecida de acordo com o seguinte conjunto de
diretrizes gerais:

• O vinculador reconhece suas próprias limitações.

• O vinculador estima precisamente o dano, prepara as diretivas para consertá-lo e incorpora as diretivas ao arquivo binário.

Linker reconhece suas próprias limitações

Ao criar uma biblioteca dinâmica, além de ser inteligente em descobrir as relações entre as várias partes do quebra-cabeça, o vinculador também deve ser
inteligente o suficiente para reconhecer o que será interrompido como resultado do carregamento do segmento de código em diferentes intervalos de endereços .

Primeiro, o intervalo de endereço de código do mapa de memória da biblioteca dinâmica é baseado em zero, ao contrário dos executáveis, caso em que o
vinculador lida com os intervalos de endereço não baseados em zero mais específicos.

Em segundo lugar, ao reconhecer que os endereços de certos símbolos não podem ser resolvidos antes do tempo de carregamento, o vinculador para de tentar; em
vez disso, ele preenche os símbolos não resolvidos com valores temporários (normalmente sendo alguns valores obviamente errados, como todos os zeros ou algo
assim).

Isso não significa, entretanto, que o vinculador desistiu de completar a tarefa.

O ligador estima com precisão o dano e prepara as diretrizes para corrigi-lo

É completamente possível classificar todos os cenários nos quais a tradução do endereço do carregador tornará ineficazes as referências resolvidas anteriormente.
Esses casos acontecem sempre que as instruções do assembler exigem endereços absolutos. Ao concluir o estágio de vinculação da construção da biblioteca
dinâmica, o vinculador pode identificar tais ocorrências e, de alguma forma, avisar o carregador sobre elas.

A fim de fornecer suporte para a coordenação do linker-carregador, as especificações do formato binário oferecem suporte a novas seções cujo propósito é
unicamente fornecer o lugar para o linker deixar as diretivas para o carregador de como consertar os danos causados ​pela tradução de endereço ocorrida durante
o carregamento dinâmico. Além disso, uma sintaxe simples específica foi desenvolvida para que o vinculador possa especificar com precisão para o carregador o
curso de ação a ser executado. Essas seções são chamadas de seções de realocação no binário, das quais a seção .rel.dyn é a mais antiga.

Em geral, as diretivas de realocação são gravadas no binário pelo vinculador, para serem lidas posteriormente pelo carregador. Eles especificam

• Em quais endereços o carregador precisa aplicar algum patch depois de definir o mapa de memória final de todo o processo.

• O que exatamente o carregador precisa fazer para corrigir corretamente os endereços não resolvidos.

O carregador segue precisamente as diretivas do vinculador

A última fase pertence ao carregador. Ele lê na biblioteca dinâmica criada pelo vinculador, lê nos segmentos do carregador (cada um carregando uma variedade
de seções do vinculador) e os coloca no mapa de memória do processo, junto com o código pertencente ao executável original.

Finalmente, ele localiza a seção .rel.dyn , lê as diretivas que o vinculador deixou e, de acordo com essas diretivas, executa o patching da biblioteca dinâmica
original. Quando o patch for concluído, o mapa de memória estará pronto para iniciar o processo.

Obviamente, a tarefa de lidar com o carregamento dinâmico da biblioteca requer que um pouco mais de inteligência seja concedido ao carregador do que o
necessário para suas tarefas básicas.

Táticas

Em geral, a troca de informações entre o vinculador e o carregador acontece por meio da seção .rel.dyn específica inserida pelo vinculador no corpo do binário.
A única questão é em qual dos arquivos binários o vinculador inserirá a seção .rel.dyn ?

A resposta é simples: é a roda que faz barulho que obtém o óleo. O binário cuja seção de código precisa de reparo geralmente carregará a seção .rel.dyn .

Concretamente, no Cenário 1, o vinculador incorpora a seção .rel.dyn ao binário do cliente (a biblioteca executável ou dinâmica cujas instruções foram
"danificadas" pelo carregamento de uma nova biblioteca dinâmica), pois é aqui que a tradução do endereço de carregada bibliotecas causaram problemas. A
Figura 8-5 ilustra a ideia.

Intervalo de endereços da biblioteca dinâmica carregada

Diretivas de vinculador para carregador:

Depois que a biblioteca é »carregada e o intervalo de endereço de sua seção de código é determinado, certas instruções na seção de código do executável que sofreu com a tradução
do intervalo de endereço precisam ser corrigidas.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 99/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

seção rel.dyn

Cliente binário

Figura 8-5. No Cenário 1, as diretivas do vinculador são incorporadas ao arquivo binário do cliente

No Cenário 2, entretanto, o vinculador incorpora a seção .rel.dyn ao binário da biblioteca carregada, pois ela precisa de ajuda para reconstruir a coerência entre
os endereços e as instruções que apontam para eles (Figura 8-6).

Diretivas de vinculador para o carregador:

Depois que a biblioteca é carregada e o intervalo de endereço da seção de código é determinado, certas instruções na seção de código da biblioteca carregada que sofreram com a
tradução do intervalo de endereço precisam ser corrigidas.

Intervalo de endereços da biblioteca dinâmica carregada

1
seção rel.dyn

function_xyz ()

Biblioteca dinâmica binária do cliente

Figura 8-6. No Cenário 2, as diretivas do vinculador são incorporadas à biblioteca dinâmica

Neste exemplo específico, você tem o cenário mais simples possível, no qual um executável carrega uma biblioteca dinâmica. Um caso muito mais realista é que a
própria biblioteca dinâmica pode carregar outra biblioteca dinâmica, que por sua vez pode carregar outra biblioteca dinâmica, etc. Qualquer uma das bibliotecas
dinâmicas no meio da cadeia de carregamento dinâmico pode ter dupla função. O Cenário 1, bem como o Cenário 2, podem ser aplicáveis ​ao mesmo binário.

Visão geral das diretivas do linker

As especificações do formato binário normalmente especificam em detalhes as regras de sintaxe de comunicação entre o vinculador e o carregador. As diretivas do
linker para o carregador geralmente tendem a ser muito simples, mas muito precisas e diretas (Figura 8-7). Conseqüentemente, a estruturação das informações
transportadas pelas diretivas do vinculador não exige um grande esforço de implementação e compreensão.

Tipo de informação de deslocamento

0804a000 00000107 R_386_JUMP_SL0T

0804a004 00000207 R_386_3UMP_SLOT

0804aOO8 00000307 R_386_JUMP_SL0T

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 100/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
0804aOOc 00000407 R_386_3UMP_SLOT

08043010 OOO0OSO7 R_386_3UMP_SL0T

0804a014 00000607 R 386 JUMP SLOT

Syn.value Syn. Nane

OO0OOOO0 príntf

00000000 shlib_abi_function

00OOOO00 _gmon_start__

00000000 dl_lterate_phdr

00000000 _libc_start_nain

00000000 putchar

Tipo de informação de deslocamento

Sym.Value Syei. Nane

0O00O4b8 00000008 R_386_RELATIVE 00002008 00000008 R_386_RELATIVE O0OOO4C8 00000801 R 386 32

0OO004dO 00000801 R_386_32 OOO0O4ea OOOOObOl R 386 32

O0OOlfe8 00000106 R_386_GLOB_DAT 0O001fec 00000206 R_386_CL0B_DAT OOOOlffO 00000306 R 386 GLOB DAT 0000201c shlibNonStaticAccessed
0000201c shltbNonstatlcAccessed 0000200c shUbNon

00000000 cxa finalizar

000OOO00 _gnon_start__

OOOOOOOO _3v_RegisterClasses

Figura 8-7. Exemplos de diretivas de vinculação

Em particular, o formato de arquivo ELF carrega as definições detalhadas de como o vinculador especifica as diretivas para o carregador. As diretivas são
armazenadas principalmente na seção .rel.dyn , bem como em algumas outras seções especializadas (rel.plt, got, got.plt). As ferramentas como
readelf ou objdump podem ser usadas para exibir o conteúdo das diretivas. A Figura 8-7 mostra alguns dos exemplos.

A interpretação dos campos da sintaxe da diretiva é a seguinte:

• Offset especifica o deslocamento de byte da seção de código para o operando da instrução assembler, que é tornado sem sentido pela tradução do endereço e
precisa de reparo.

• As informações são descritas pela especificação do formato ELF como

#define ELF32_R_SYM (i) ((i) >> 8)

#define ELF32_R_TYPE (i) ((unsigned char) (i))

#define ELF32_R_INFO (s, t) (((s) << 8) + (unsigned char) (t))

#define ELF64_R_SYM (i) ((i) >> 32)

#define ELF64_R_TYPE (i) ((i) & 0xffffffffL)

#define ELF64_R_INFO (s, t) (((s) << 32) + ((t) & 0xffffffffL)

Onde

• ELFxx_R_SYM denota o índice da tabela de símbolos em relação ao qual a realocação deve ser feita:

Uma das seções do arquivo binário contém a lista de símbolos. Este valor representa simplesmente o índice do item da tabela de símbolos que representa este
símbolo específico. O readelf e objdump podem fornecer a lista completa de símbolos contidos na tabela de símbolos do binário.

• ELFxx_R_TYPE denota o tipo de realocação a ser aplicada. Uma descrição detalhada dos tipos de realocação disponíveis é mostrada abaixo.

Nome

Valor
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 101/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Campo

Cálculo

R_386_NONE 0 Nenhum Nenhum

R_386_32 1 wrd32 S -1 UMA

R_386_PC32 2 ui> rd32 s3 UMA - P


R_386_GOT32 3 palavra 32 G-i UMA - P
R_386_PLT32 4 palavra 32 L t UMA - P
R _386 _COPY b Nenhum Nenhum

R _386 _GLOB_DAT 6 palavra 32 S

R_3 86_JMP_SLOT 7 palavra 32 S

R _386 _RELATIVE 8 palavra 32 B + UMA

R_386_GOTOFF 9 m vrd32 St UMA - CONSEGUIU

R_386_GOTPC 10 palavra 32 CONSEGUIU + UMA - P

Alguns tipos de realocação têm semântica além do cálculo simples.

R_336_GOT32 Este tipo de realocação calcula a distância da base do deslocamento global

Figura 1-22: Tipos de realocação

tabela para a entrada da tabela de deslocamento global do símbolo. Além disso, instrui o editor de links a construir uma tabela de deslocamento global.

Este tipo de realocação calcula o endereço da entrada tabica de ligação de procedimento do símbolo e, adicionalmente, instrui o editor de link a construir uma tabela de
ligação de procedimento.

O editor de links cria esse tipo de realocação para links dinâmicos. Seu membro deslocado refere-se a um local em um segmento gravável. O índice da tabela de símbolos
especifica um símbolo que deve existir no arquivo objeto atual e em um objeto compartilhado. Durante a execução, o vinculador dinâmico copia os dados associados ao
símbolo do objeto compartilhado para o local especificado pelo deslocamento.

Este tipo de realocação é usado para definir uma entrada da tabela de deslocamento global para o endereço do símbolo especificado. O tipo de realocação especial permite
determinar a correspondência entre os símbolos e as entradas tabicas de deslocamento global.

O editor de links cria esse tipo de realocação para links dinâmicos. Seu membro de deslocamento fornece a localização de uma entrada da tabela de ligação de
procedimento. O vinculador dinâmico modifica a entrada da tabela de ligação de procedimento para transferir o controle para o endereço do símbolo designado [ver 'Tabela
de ligação de procedimento "na Parte 2],

O editor de links cria esse tipo de realocação para links dinâmicos. Seu membro deslocado fornece uma localização dentro de um objeto compartilhado que contém um valor
que representa um endereço relativo. O I inker dinâmico calcula o endereço virtual correspondente adicionando o endereço virtual no qual o objeto compartilhado foi
carregado ao endereço relativo. As entradas de realocação para este tipo devem especificar 0 para o índice da tabela de símbolos.

Este tipo de realocação calcula a diferença entre o valor de um símbolo e o endereço da tabela de deslocamento global. Além disso, instrui o editor de links a construir a
tabela de deslocamento global.

Este tipo de realocação se assemelha a R_38 6_PC32, exceto que usa o endereço da tabela de deslocamento global em seu cálculo. O símbolo referenciado nesta realocação
normalmente é _GLOBAL_OFFSET_TABLE_. que adicionalmente instrui o editor de links a construir a tabela de deslocamento global.

R_336_PLT32

R 336 CÓPIA

R 386 GLOB DAT

R_3 8 6 2_JMP_S LOT

R_386_RELATIVE

R 38 6 GOTOFF

R 38 6 GOTPC

Figura 8-8. Visão geral dos tipos de diretiva do vinculador (da especificação do formato ELF)

• Sym.Value especifica o deslocamento provisório e temporário dentro da seção de código (no caso de funções) ou dentro do segmento de dados (no caso de
variáveis) onde o símbolo reside atualmente no arquivo binário original. Presume-se que a tradução do endereço afetará esses valores.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 102/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• Sym.Name especifica o nome do símbolo legível por humanos (nome da função, nome da variável)

Técnicas de Implementação de Coordenação Linker-Loader


Ao longo da evolução do conceito de vínculo dinâmico, as duas técnicas de implementação foram utilizadas: Load Time Relocation (LTR) e Position Independent
Code (PIC).

Realocação de tempo de carga (LTR)

Cronologicamente, a primeira implementação do conceito de vínculo dinâmico veio na forma do chamado Load Time Relocation. Em termos gerais, essa técnica
foi a primeira técnica de carregamento dinâmico que realmente funcionou. Seu benefício imediato foi a capacidade de liberar os binários do aplicativo da
necessidade de carregar "bagagem" desnecessária (o código que lida com as tarefas habituais específicas do sistema operacional).

Os benefícios imediatos que o conceito LTR trouxe foi que não apenas o tamanho dos bytes dos binários dos aplicativos tornou-se substancialmente menor, mas
também a maneira como certas tarefas específicas do sistema operacional foram executadas tornou-se unificada em uma ampla variedade de aplicativos.

Apesar dos benefícios óbvios que esse conceito trouxe, ele tinha várias desvantagens importantes. Primeiro, essa técnica modifica (corrige) o código da biblioteca
dinâmica com os valores literais de endereços de variáveis ​e funções, significativos apenas no contexto do aplicativo que o carregou primeiro. No contexto de
qualquer outro aplicativo (que muito possivelmente apresentaria o layout do mapa de memória do processo diferente), as modificações de código muito
provavelmente seriam inúteis, sem sentido e simplesmente não aplicáveis.

Como consequência, se vários aplicativos precisassem dos serviços de uma biblioteca dinâmica ao mesmo tempo, isso significaria que você teria exatamente esse
número de cópias da mesma biblioteca dinâmica na memória.

A segunda desvantagem era que uma quantidade proporcionalmente grande de modificações de código seria necessária. Com essa técnica em vigor, o carregador
precisa modificar / corrigir exatamente tantos lugares no código que fazem referência a uma determinada variável ou chamam uma determinada função. Nos
casos em que o aplicativo carrega muitas bibliotecas dinâmicas, o tempo de carregamento aumenta para uma latência inicial significativa e perceptível durante o
início do aplicativo.

A terceira desvantagem é que o segmento de código gravável (.texto) representa uma ameaça potencial à segurança. Com essa técnica em vigor, o sonho de
carregar a biblioteca dinâmica na memória física apenas uma vez e mapeá-la na infinidade de diferentes endereços de mapas de memória de aplicativos não foi
alcançável. A Figura 8-9 ilustra a ideia por trás do conceito de realocação de tempo de carga.

Código de biblioteca dinâmica (seção .text)

Biblioteca dinâmica

código (seção de texto) ♦

• ♦
Após a conclusão do carregamento, os endereços finais dos símbolos são conhecidos. Agora é a hora de conectar a instrução com os símbolos aos quais fazem referência.

A abordagem de realocação de tempo de carregamento modifica a fonte da biblioteca dinâmica (seção .text) carregando os operandos de endereço de instruções para apontar para os
endereços do mapa de memória do processo atual.

No entanto, codificar os endereços torna a biblioteca dinâmica inutilizável para qualquer outro mapa de memória de processo no qual os símbolos residam em endereços diferentes.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 103/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Figura 8-9. Conceito LTR e suas limitações

Todas as desvantagens foram abordadas com o design da abordagem mais recente e, em muitos aspectos, superior do Código Independente de Posição (PIC), que
rapidamente se tornou a escolha predominante de técnicas de vinculação.

Código Independente de Posição (PIC)

As limitações do esquema Load Time Relocation foram abordadas na próxima implementação do link dinâmico, a técnica conhecida como Código Independente
de Posição (Figura 8-10). As modificações diretas indesejadas das instruções do segmento de código da biblioteca dinâmica foram evitadas com uma etapa extra de
indireção. No jargão das linguagens de programação, a abordagem pode ser descrita como o uso de ponteiro a ponteiro em vez de ponteiro.

Biblioteca dinâmica

código (seção de texto) • ♦♦

A tabela de deslocamento global (GOT) mantém os slots de armazenamento que transportam os endereços de cada um dos símbolos não resolvidos.

O deslocamento GOT é constante e conhecido no momento do link. Conseqüentemente, as instruções da CPU que fazem referência ao slot GOT são independentes do layout de mapa
de memória de processo específico.

O conteúdo dos slots de armazenamento, entretanto, depende do layout do mapa de memória do processo específico. Depois que tudo é carregado e os endereços dos símbolos são
conhecidos, o carregador visita o GOT e atualiza os slots de armazenamento com os valores corretos dos endereços dos símbolos.

Biblioteca dinâmica

código (seção de texto) ♦

r eu
eu ■ M
<
n r?

(
1
o eu
Q
Tabela de deslocamento global

(GOT) que mantém os slots de armazenamento para cada um dos endereços de símbolo necessários

Código de biblioteca dinâmica (seção .text)

E+X
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 104/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Fixo

Morada

Deslocamento

Deslocamento de endereço variável

Deslocamento de endereço

Figura 8-10. Conceito PIC

Basicamente, os endereços dos símbolos são fornecidos às instruções necessitadas em duas etapas. Para obter o endereço do símbolo, primeiro uma instrução mov
acessa a localização do endereço do endereço e carrega seu conteúdo (um endereço de símbolo necessário) em um registro de CPU disponível. Imediatamente
depois, o endereço de símbolo recuperado agora armazenado em um registro pode ser usado como o operando nas instruções subsequentes (mov para dados,
chamada para chamadas de função).

A diferença especial na solução é que os endereços dos símbolos são mantidos em uma chamada tabela de deslocamento global (GOT), para a qual o vinculador
reserva uma seção .got dedicada . A distância entre a seção .text e a seção .got é constante e conhecida no momento do link. Para cada um dos símbolos que
precisam ser resolvidos, a tabela de deslocamento global mantém um slot dedicado no deslocamento conhecido e fixo desde o início da tabela.

Dada a distância GOT fixa e deslocamento de slot fixo (ambos conhecidos no tempo de link), torna-se possível para um compilador implementar as instruções de
código para fazer referência aos locais fixos. Mais importante ainda, o código implementado não depende dos endereços de símbolo reais e pode ser usado sem
quaisquer alterações mapeadas diretamente em uma infinidade de outros processos.

O ajuste final às peculiaridades de um layout de mapa de memória específico é concluído pelo carregador. Neste esquema, entretanto, o carregador não modifica
irreversivelmente o código ( seção .text ). Em vez disso, uma vez que os endereços dos símbolos são conhecidos, o carregador corrige a seção .got , que é
(muito parecida com as seções de dados) sempre implementada por processo.

■ Observe que , para implementar este esquema, foi necessário um esforço substancial de projeto, que se espalhou além dos limites do linker-carregador.
na verdade, para implementar o conceito PiC, a história deve começar no nível do compilador. em particular, o sinalizador -fPic deve ser passado para o
compilador. O mnemônico "fPiC" ou simplesmente "PiC" eventualmente se tornou um sinônimo de link dinâmico.

Vinculação preguiçosa

O fato de que a referência dos símbolos na abordagem PIC passa pelo nível extra de indireção fornece o potencial para alcançar os benefícios de desempenho
adicionais em tempo de execução. A estratégia de implementação do pontapé desempenho extra é baseada no fato de que o carregador não desperdiça tempo
precioso configurar o conteúdo dos .got e .got.plt seções até que o programa é iniciado.

As instruções do montador que fazem referência aos símbolos são definidas para apontar para o ponto intermediário de qualquer maneira, e não há nada
terrivelmente errado com a forma geral do código que interromperia o carregamento do programa.

Na verdade, o carregador normalmente não se incomodam mesmo para completar a configuração do conteúdo dos .got e .got.plt seções até que seja
absolutamente necessário. Esses momentos acontecem depois que o programa já começou, e somente quando o fluxo de execução vem com as instruções que
fazem referência os símbolos cujos endereços são mantidos no .got e .got.plt seções.

O benefício óbvio da procrastinação do carregador (comumente referido como ligação lenta) é que o processo de carregamento é concluído mais rápido, o que
torna o início do aplicativo mais rápido. Uma pequena penalidade de desempenho única ocorre quando o carregador rapidamente compensa seu descuido inicial
(embora premeditado). Isso acontece apenas conforme a necessidade e apenas uma vez, na primeira ocorrência de referência de símbolo. Quanto menos símbolos
da biblioteca dinâmica forem realmente referenciados no tempo de execução, mais economia de desempenho o carregador será capaz de obter.

O conceito de ligação lenta é um recurso extra da abordagem PIC, que obviamente adiciona outra boa razão para os desenvolvedores escolherem o PIC em vez da
implementação LTR. Na verdade, a abordagem PIC é uma implementação favorita do tipo de problema Cenário 1 quando o binário do cliente é o arquivo
executável (ou seja, aplicativo).

Regras e limitações da cadeia recursiva de vínculo dinâmico

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 105/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Os cenários que você examinou em detalhes até agora pertencem aos cenários de vinculação dinâmica mais simples possíveis. Depois de dar uma olhada mais de
perto no nível atômico, agora vamos recuar um pouco e dar uma olhada no nível molecular da ligação dinâmica, pois apresenta certas regras e limitações que não
são óbvias no nível atômico da história.

Na realidade, a estrutura de um programa típico pode ser descrita como a cadeia recursiva de ligação dinâmica, na qual cada uma das bibliotecas dinâmicas da
cadeia carrega várias outras bibliotecas dinâmicas. Visualmente, a cadeia recursiva de carregamento pode ser representada como a elaborada estrutura de árvore
com muitas conexões laterais entre os ramos. O comprimento de ramos individuais, em alguns casos, pode acabar sendo muito grande. Por mais interessante que
seja a complexidade da cadeia recursiva de ligação dinâmica, e por mais impressionante que pareça o comprimento de seus ramos, esses não são os detalhes mais
poderosos de toda a história.

Muito mais importante é o fato emergente de que na cadeia de carregamento dinâmico cada uma das bibliotecas dinâmicas participantes pode se ver desempenhando os
papéis do Cenário 1 e do Cenário 2. A Figura 8-11 ilustra o ponto.

Em outras palavras, uma biblioteca dinâmica na cadeia de carregamento pode precisar resolver as referências da biblioteca que carrega, bem como resolver
novamente a referência de seus próprios símbolos. Isso torna toda a história um pouco mais interessante.

Um certo conjunto de fortes preferências de implementação que residem neste nível molecular estipula os detalhes de implementação, que revisarei brevemente
na próxima seção.

Fortes preferências de implementação

Independentemente do cenário, sempre há duas maneiras de como a coordenação linker-carregador pode ser implementada: a abordagem LTR ou PIC pode ser
aplicada. A escolha da técnica de coordenação linker-loader não é totalmente gratuita. Além da escolha do designer com base nos prós e contras de cada técnica,
existem algumas outras limitações que precisam ser explicitamente apontadas:

• Código Independente de Posição (PIC) é a técnica fortemente preferida de executável para resolver as referências do primeiro nível de bibliotecas carregadas (o
cenário marcado com a letra A dentro de um círculo na Figura 8-11).

Em termos de escolha entre LTR ou PIC, as bibliotecas dinâmicas na cadeia de carregamento podem apresentar uma variedade de combinações. Uma biblioteca
dinâmica que implementa LTR pode, por sua vez, carregar dinamicamente a próxima biblioteca dinâmica, que implementa o PIC, que por sua vez pode carregar
dinamicamente a biblioteca que implementa ... você escolher - seja qual for sua escolha, é permitido.

Figura 8-11. Um ramo da cadeia recursiva típica de ligação dinâmica

Cenário 1 Cenário 2

CAPÍTULO 8 ■ DESENVOLVENDO LIBRAS DINÂMICAS: TÓPICOS AVANÇADOS A Figura 8-12 ilustra as regras descritas.

Ambos são LTR

/ Pi

Ambos são LTR

Ambos são LTR

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 106/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Ambos são PIC

LTR

LTR

xu.

LTR

FOTO

y
Cenário 1 Cenário 2

Figura 8-12. Fortes preferências de implementação (no nível molecular) que regem a implementação da cadeia recursiva de ligação dinâmica

CAPÍTULO 9

Tratamento de símbolos duplicados ao vincular


em bibliotecas dinâmicas
O conceito de link dinâmico claramente representou um grande passo à frente no domínio do design de software. A flexibilidade sem precedentes que trouxe
abriu muitos caminhos para o progresso técnico e muitas novas portas para conceitos substancialmente novos.

Da mesma forma, a complexidade de como as bibliotecas dinâmicas funcionam internamente trouxe vários desafios distintos para o domínio da cadeia de
ferramentas de software (compiladores, vinculadores, carregadores). A necessidade inicialmente reconhecida de o vinculador e o carregador cooperarem mais
estreitamente e as técnicas de implementação foram discutidas no capítulo anterior.

No entanto, não é aqui que a história termina.

Outro paradigma interessante intimamente associado ao domínio das bibliotecas dinâmicas é a questão do tratamento de símbolos duplicados. Mais
especificamente, quando as bibliotecas dinâmicas são ingredientes de entrada no processo de vinculação, o vinculador se desvia da abordagem usual de senso
comum normalmente seguida em casos em que arquivos de objetos individuais e / ou bibliotecas estáticas são combinados no arquivo binário.

Definição de Símbolos Duplicada


O problema mais comum que pode ocorrer durante o processo de resolução das referências é o aparecimento de símbolos duplicados, que ocorre quando, na etapa
final da vinculação, a lista de todos os símbolos disponíveis contém dois ou mais símbolos com o mesmo nome.

Como uma observação lateral, o algoritmo do vinculador para seus próprios fins internos normalmente aplica modificações dos nomes dos símbolos originais.
Como consequência direta, os problemas duplicados relatados impressos pelo vinculador podem se referir a nomes um tanto diferentes dos nomes originais. As
modificações do nome do símbolo podem variar de simples decorações de nome (por exemplo, antes do sublinhado) até o tratamento sistemático de questões de
afiliação de função C ++. Felizmente, as modificações são normalmente realizadas de maneira estritamente uniforme e previsível.

Cenários típicos de símbolos duplicados

As causas de símbolos duplicados podem variar. No caso mais simples possível, aconteceu que os diferentes designers escolheram o nome mais óbvio para suas
classes de módulos, funções, estruturas (por exemplo, classe Timer, função getLength () ou variáveis lastError ou libVersion). Tentar combinar os
módulos desses designers inevitavelmente leva à descoberta da existência de símbolos duplicados.

As outras possibilidades cobrem os casos típicos quando a instância do tipo de dados (de uma classe, estrutura ou tipo de dados simples) é definida em um
arquivo de cabeçalho. Mais de uma inclusão do arquivo de cabeçalho cria inevitavelmente o cenário de símbolos duplicados.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 107/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Símbolos C duplicados
A linguagem C impõe critérios bastante simples para que dois ou mais símbolos sejam considerados duplicatas um do outro. Desde que os nomes das funções,
estruturas ou tipos de dados sejam idênticos, os símbolos são considerados idênticos. Por exemplo, construir o seguinte código falhará:

arquivo: main.c

#include <stdio.h>

int function_with_duplicated_name (int x)

printf ("% s \ n", _FUNCTION_);

return 0;

int function_with_duplicated_name (int x, int y)

printf ("% s \ n", _FUNCTION_);

return 0;

int main (int argc, char * argv [])

function_with_duplicated_name (1); function_with_duplicated_name (1,2); return 0;

Isso produzirá a seguinte mensagem de erro:

main.c: 9: 5: erro: tipos conflitantes para 'function_with_duplicated_name' main.c: 3: 5: nota: a definição anterior de
'function_with_duplicated_name' estava aqui main.c: Na função 'main':

main.c: 17: 5: erro: poucos argumentos para a função 'function_with_duplicated_name'

main.c: 9: 5: nota: declarado aqui

gcc: erro: main.o: Não existe esse arquivo ou diretório

gcc: erro fatal: nenhum arquivo de entrada

compilação terminada.

Símbolos C ++ duplicados

Sendo uma linguagem de programação orientada a objetos, C ++ impõe critérios de símbolos duplicados mais relaxados. Em termos de namespaces, classes /
estruturas e tipos de dados simples, o uso de nomes idênticos ainda permanece como o único critério de símbolos duplicados. No entanto, no domínio das
funções, os critérios de símbolos duplicados não se limitam mais apenas aos nomes das funções, mas também levam em consideração a lista de argumentos.

Os princípios de sobrecarga de função (métodos) permitem usar o mesmo nome para diferentes métodos da mesma classe com diferentes listas de argumentos de
entrada, desde que o tipo de valor de retorno não seja diferente.

O mesmo princípio se aplica aos casos secundários de duas ou mais funções pertencentes ao mesmo namespace, não sendo membros de nenhuma classe. Mesmo
que essas funções não sejam afiliadas a nenhuma classe, os critérios de duplicado C ++ mais elásticos se aplicam - eles são considerados duplicados apenas se seus
nomes forem idênticos e sua lista de argumentos de entrada idêntica.

CAPÍTULO 9 ■ SÍMBOLOS HANDUNG DUPUCATE QUANDO DESCONHECIDO EM UBRÁRIOS DINÂMICOS A construção do código a seguir será concluída com sucesso:

arquivo: main.cpp

#include <iostream> usando namespace std;

classe CTest {

público:

CTest () {x = 0;}; ~ CTest () {}; público:

int runTest (void) {return x;}; privado: int x;

};

int function_with_duplicated_name (int x)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 108/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
{

cout << _FUNCTION_ << "(x)" << endl;

return 0;

int function_with_duplicated_name (int x, int y)

cout << _FUNCTION_ << "(x, y)" << endl;

return 0;

int main (int argc, char * argv []) {CTest test;

int x = test.runTest ();

function_with_duplicated_name (x); function_with_duplicated_name (x, 1); return 0;

arquivo: build.sh

g ++ -Wall -g -O0 -c main.cpp g ++ main.o -o clientApp

Executar o binário produzido criará a seguinte saída:

function_with_duplicated_name (x) function_with_duplicated_name (x, y)

No entanto, tentando adicionar a declaração do método a seguir a main.cpp

float function_with_duplicated_name (int x) {

cout << _FUNCTION_ << "(x)" << endl;

return 0.0f;

violará as regras básicas de sobrecarga de função C ++, o que resultará na seguinte falha de compilação:

main.cpp: Na função 'float function_with_duplicated_name (int)':

main.cpp: 23: 42: erro: nova declaração 'float function_with_duplicated_name (int)'

main.cpp: 17: 5: erro: ambigua a declaração antiga 'int function_with_duplicated_name (int)'

g ++: erro: main.o: Esse arquivo ou diretório não existe

g ++: erro fatal: nenhum arquivo de entrada

compilação terminada.

Tratamento padrão de símbolos duplicados


Quando arquivos de objetos individuais ou bibliotecas estáticas estão sendo vinculados no arquivo binário resultante, o vinculador segue estritamente a política de
tolerância zero em relação aos símbolos duplicados.

Quando o vinculador detecta os símbolos duplicados, ele imprime uma mensagem de erro especificando os arquivos / linhas de código onde ocorrem as
ocorrências dos símbolos duplicados e a vinculação é declarada uma falha. Isso basicamente significa que os desenvolvedores precisam voltar à prancheta e tentar
resolver o problema, o que muito provavelmente significa que o código precisa ser recompilado.

O exemplo a seguir ilustra o que acontece quando você tenta vincular ao mesmo binário cliente as duas bibliotecas estáticas que apresentam símbolos duplicados.
O projeto é composto por duas bibliotecas estáticas muito simples, apresentando símbolos duplicados, bem como o aplicativo cliente que tenta vincular os dois:

Biblioteca estática libfirst.a:

arquivo: staticlibfirstexports.h

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 109/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
#pragma uma vez

int staticlibfirst_function (int x); int staticlib_duplicate_function (int x);

arquivo: staticlib.c

#include <stdio.h>

int staticlibfirst_function (int x) {

printf ("% s \ n", __FUNCTION__); retorno (x + 1);

int staticlib_duplicate_function (int x) {

printf ("% s \ n", __FUNCTION__); retorno (x + 2);

arquivo: build.sh

gcc -Wall -g -O0 -c staticlib.c ar -rcs libfirst.a staticlib.o

Biblioteca estática libsecond.a:

arquivo: staticlibsecondexports.h

#pragma uma vez

int staticlibsecond_function (int x); int staticlib_duplicate_function (int x);

arquivo: staticlib.c

#include <stdio.h>

int staticlibsecond_function (int x) {

printf ("% s \ n", _FUNCTION_);

retorno (x + 1);

int staticlib_duplicate_function (int x) {

printf ("% s \ n", __FUNCTION__); retorno (x + 2);

arquivo: build.sh

gcc -Wall -g -O0 -c staticlib.c ar -rcs libsecond.a staticlib.o

ClientApplication:

arquivo: main.c

#include <stdio.h>

#include "staticlibfirstexports.h"

#include "staticlibsecondexports.h"

int main (int argc, char * argv []) {

int nRetValue = 0;

nRetValue + = staticlibfirst_function (1); nRetValue + = staticlibsecond_function (2); nRetValue + = staticlib_duplicate_function


(3); printf ("nRetValue =% d \ n", nRetValue); return nRetValue;

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 110/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
arquivo: build.sh

gcc -Wall -g -O0 -I ../ libFirst -I ../ libSecond -c main.c

gcc main.o -L ../ libFirst -lfirst -L ../ libSecond -lsecond -o clientApp

Devido à presença de símbolos duplicados em ambas as bibliotecas estáticas, tente construir os resultados do aplicativo cliente com o erro do vinculador:

/ home / milan / Desktop / duplicateSymbolsHandlingResearch / 01_duplicateSymbolsCriteria / 02_duplicatesIn TuoStaticLibs /


01_plainAndSimple / libSecond / staticlib.c: 10: definição múltipla de 'staticlib_duplicate_function'

../libFirst/libfirst.a(staticlib.o):/home/milan/Desktop/duplicateSymbolsHandlingResearch/01_dupl icateSymbolsCriteria /
02_duplicatesInTwoStaticLibs / 01_plainAndSimple / libFirst / statib.c: 10: primeiro definido aqui

collect2: ld retornou 1 status de saída

Comentar a chamada à função duplicada não ajuda a evitar a falha do vinculador. Obviamente, o vinculador primeiro tenta agrupar tudo que vem das bibliotecas
estáticas de entrada e arquivos de objetos individuais (main.c). Se os símbolos duplicados acontecerem tão cedo no jogo de vinculação, o vinculador declarará
uma falha, independentemente do fato de ninguém ter tentado fazer referência aos símbolos duplicados.

São permitidos símbolos locais duplicados

Curiosamente, as funções locais declaradas com a palavra-chave estática no significado da linguagem C dessa palavra-chave (ou seja, escopo de visibilidade
limitado apenas às funções que residem no mesmo arquivo de origem) não são registradas como duplicatas. Modifique os arquivos de origem das bibliotecas
estáticas em seu exemplo com o seguinte código:

Biblioteca estática libfirst.a: arquivo: staticlib.c

static int local_staticlib_duplicate_function (int x) {

printf ("libfirst:% s \ n", __FUNCTION__); return 0;

int staticlibfirst_function (int x) {

printf ("% s \ n", __FUNCTION__); local_staticlib_duplicate_function (x); retorno (x + 1);

Biblioteca estática libsecond.a: arquivo: staticlib.c

static int local_staticlib_duplicate_function (int x) {

printf ("libsecond:% s \ n", __FUNCTION__); return 0;

int staticlibsecond_function (int x) {

printf ("% s \ n", __FUNCTION__); local_staticlib_duplicate_function (x); retorno (x + 1);

ClientApplication:

arquivo: main.c

#include <stdio.h>

#include "staticlibfirstexports.h"

#include "staticlibsecondexports.h"

int main (int argc, char * argv []) {

staticlibfirst_function (1); staticlibsecond_function (2); return 0;

O aplicativo cliente agora será construído com sucesso e produzirá a seguinte saída: staticlibfirst_function

libfirst: local_staticlib_duplicate_function staticlibsecond_function

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 111/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
libsecond: local_staticlib_duplicate_function

Obviamente, o vinculador mantém os compartimentos separados para as funções locais. Mesmo que seus nomes de símbolo sejam completamente idênticos, a
colisão não acontece.

Tratamento de símbolos duplicados ao vincular em bibliotecas dinâmicas


Quando as bibliotecas dinâmicas são adicionadas à mistura de ingredientes de entrada no estágio de vinculação, a maneira como o vinculador lida com os
símbolos duplicados se torna muito mais interessante e envolvente. Em primeiro lugar, o vinculador abandona a política de tolerância zero para símbolos
duplicados e não declara imediatamente a falha de vinculação. Em vez disso, ele aplica a abordagem aproximada, menos do que ideal, para resolver a colisão de
nomenclatura de símbolos.

Para ilustrar a abordagem totalmente diferente do vinculador para este cenário específico, o projeto de demonstração simples é criado. É composto por duas
bibliotecas dinâmicas que apresentam os símbolos duplicados e o aplicativo cliente que os vincula:

Biblioteca compartilhada libfirst.so:

arquivo: shlibfirstexports.h

#pragma uma vez

int shlibfirst_function (int x); int shlib_duplicate_function (int x);

arquivo: shlib.c

#include <stdio.h>

static int local_shlib_duplicate_function (int x) {

printf ("shlibFirst:% s \ n", _FUNCTION_);

return 0;

int shlibfirst_function (int x) {

printf ("shlibFirst:% s \ n", __FUNCTI0N__); local_shlib_duplicate_function (x); retorno (x + 1);

int shlib_duplicate_function (int x) {

printf ("shlibFirst:% s \ n", __FUNCTI0N__); local_shlib_duplicate_function (x); retorno (x + 2);

arquivo: build.sh

gcc -Wall -g -00 -fPIC -c shlib.c

gcc -shared shlib.o -Wl, -soname, libfirst.so.1 -o libfirst.so.1.0.0 ldconfig -n.

ln -s libfirst.so.1 libfirst.so Biblioteca compartilhada libsecond.so:

arquivo: shlibsecondexports.h

#pragma uma vez

int shlibsecond_function (int x); int shlib_duplicate_function (int x);

arquivo: shlib.c

#include <stdio.h>

static int local_shlib_duplicate_function (int x) {

printf ("shlibSecond:% s \ n", __FUNCTI0N__); return 0;

int shlibsecond_function (int x) {

printf ("shlibSecond:% s \ n", __FUNCTI0N__); local_shlib_duplicate_function (x); retorno (x + 1);

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 112/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
}

int shlib_duplicate_function (int x) {

printf ("shlibSecond:% s \ n", _FUNCTION_);

local_shlib_duplicate_function (x); retorno (x + 2);

arquivo: build.sh

gcc -Wall -g -O0 -fPIC -c shlib.c

gcc -shared shlib.o -Wl, -soname, libsecond.so.1 -o libsecond.so.1.0.0 ldconfig -n.

ln -s libsecond.so.1 libsecond.so ClientApplication:

arquivo: main.c

#include <stdio.h>

#include "shlibfirstexports.h"

#include "shlibsecondexports.h"

int main (int argc, char * argv []) {

int nRetValue = 0;

nRetValue + = shlibfirst_function (1); nRetValue + = shlibsecond_function (2); nRetValue + = shlib_duplicate_function (3); return
nRetValue;

arquivo: build.sh

gcc -Wall -g -O0 -I ../ libFirst -I ../ libSecond -c main.c gcc main.o -Wl, -L ../ libFirst -Wl, -lfirst \ -Wl, -L .. / libSecond
-Wl, -lsecond \ -Wl, -R ../ libFirst \

-Wl, -R ../ libSecond \

-o clientApp

Mesmo que as duas bibliotecas compartilhadas apresentem as duplicatas e até mesmo uma das duplicatas (shlib_duplicate_function) não seja uma função
local, a construção do aplicativo cliente é concluída com sucesso. A execução do aplicativo cliente, no entanto, traz um pouco de surpresa:

shlibFirst: shlibfirst_function shlibFirst: local_shlib_duplicate_function shlibSecond: shlibsecond_function shlibSecond:


local_shlib_duplicate_function shlibFirst: shlib_duplicate_function shlibFirst: local_shlib_função_duplicate

Obviamente, o vinculador encontrou alguma maneira de resolver os símbolos duplicados. Ele resolveu escolhendo uma das ocorrências de símbolo (aquela em
shlibfirst.so) e direcionou todas as referências a shlib_duplicate_function para aquela ocorrência de símbolo particular.

A decisão desse linker é claramente uma etapa muito controversa. Em cenários do mundo real, as funções nomeadas de forma idêntica de diferentes bibliotecas
dinâmicas podem transportar funcionalidades substancialmente diferentes. Imagine, por exemplo, que cada uma das bibliotecas dinâmicas
libcryptography.so, libnetworkaccess.so e libaudioport.so apresentam o método Initialize () . Imagine agora que o vinculador decidiu que a
chamada para Initialize () sempre significa apenas inicializar uma das bibliotecas (e nunca inicializar as outras duas).

Obviamente, esses tipos de cenários devem ser evitados com cuidado. Para fazer isso direito, a maneira de como o vinculador "pensa" deve ser completamente
entendida primeiro.

Os detalhes do algoritmo interno do vinculador para lidar com símbolos duplicados da biblioteca dinâmica serão discutidos posteriormente neste capítulo.

Estratégias gerais de eliminação de problemas de símbolos duplicados

Em geral, a melhor abordagem para resolver os símbolos duplicados é reforçar as afiliações dos símbolos aos seus módulos particulares, visto que isso geralmente
elimina a grande maioria dos problemas potenciais de símbolos duplicados.

Em particular, recorrer ao uso de namespaces é a técnica mais recomendada, pois está comprovado que funciona em muitos cenários diferentes,
independentemente da forma em que o código é disponibilizado para a comunidade de software (biblioteca estática vs. biblioteca compartilhada ) Este recurso
está confinado ao domínio da linguagem C ++ e requer o uso do compilador C ++.

Alternativamente, se por qualquer razão o uso de um compilador estritamente C for fortemente preferido, adicionar os nomes das funções com o prefixo exclusivo
pode ser usado como uma alternativa viável, embora um pouco menos poderosa e menos flexível.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 113/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Símbolos duplicados e modos de ligação dinâmica


Antes de entrar nos detalhes da nova abordagem do vinculador para lidar com os símbolos duplicados, é importante apontar alguns fatos significativos.

O carregamento dinâmico de bibliotecas dinâmicas em tempo de execução (por meio das chamadas dlopen () ou LoadLibrary () ) não impõe praticamente
nenhum risco de ter símbolos duplicados. Os símbolos recuperados da biblioteca dinâmica são normalmente atribuídos (por meio das chamadas dlsym () ou
GetProcAddress () ) à variável cujo nome provavelmente já foi escolhido para não duplicar nenhum dos símbolos existentes no binário do cliente.

Ao contrário, é a vinculação estaticamente ciente de bibliotecas dinâmicas que representa o cenário típico em que ocorrem as ocorrências de símbolos duplicados.

A razão genuína para decidir vincular em uma biblioteca dinâmica é o interesse no conjunto de símbolos ABI da biblioteca dinâmica ou seu subconjunto. Muito
frequentemente, no entanto, a biblioteca dinâmica pode carregar muito mais símbolos remotos ou sem importância para o projeto binário do cliente, e o
desconhecimento de sua presença pode levar a uma escolha não intencional de uma função nomeada duplicada ou dados provenientes de diferentes bibliotecas
dinâmicas.

Existe um limite de precaução que os desenvolvedores de bibliotecas dinâmicas podem tomar para tornar as coisas melhores. Reduzir a exportação dos símbolos
da biblioteca dinâmica para apenas o conjunto essencial de símbolos é definitivamente uma medida que pode reduzir significativamente a probabilidade de
colisão de nomes de símbolo. No entanto, essa prática de design altamente recomendada não atua diretamente contra a raiz do problema. Independentemente de
quão frugal você seja ao exportar seus símbolos de biblioteca dinâmica, ainda é possível que diferentes desenvolvedores escolham os nomes mais simples para os
símbolos, o que resulta em dois ou mais arquivos binários disputando o direito de usar o nome do símbolo.

Por fim, é importante ressaltar que você não está lidando com a peculiaridade de um vinculador específico em uma plataforma específica; o vinculador do
Windows (certamente Visual Studio 2010) segue quase completamente o mesmo conjunto de regras para determinar como lidar com os símbolos duplicados
encontrados durante o processo de vinculação dinâmica.

Critérios do Linker no Algoritmo Aproximado de Resolução de Símbolos Duplicados de Bibliotecas


Dinâmicas

Na busca pelo melhor candidato para representar o nome do símbolo duplicado, o vinculador toma a decisão com base nas seguintes circunstâncias:

• Localização dos símbolos duplicados: O vinculador atribui diferentes níveis de importância aos símbolos localizados em diferentes partes do mapa de memória do
processo. Uma explicação mais detalhada segue imediatamente.

• Ordem de ligação especificada das bibliotecas dinâmicas: Se dois ou mais símbolos residem nas partes do código de prioridades iguais, o símbolo que reside na
biblioteca dinâmica que foi passado para o vinculador anteriormente na lista de bibliotecas dinâmicas especificadas vencerá a luta para representar o símbolo
duplicado sobre o símbolo que reside na biblioteca dinâmica declarada posteriormente na lista.

Localização, localização, localização: Regras de zoneamento de prioridade de código

A variedade de símbolos de vinculação que participam da construção do binário do cliente pode residir em uma variedade de locais. O primeiro critério que o
vinculador aplica para resolver colisões de nomes entre símbolos é baseado na comparação entre o seguinte esquema de prioridade de símbolos.

Símbolos de prioridade de primeiro nível: símbolos binários do cliente

O ingrediente inicial da construção do arquivo binário é a coleção de seus arquivos de objeto, que são originários do projeto ou vêm na forma de biblioteca
estática. No caso do Linux, as seções provenientes desses ingredientes normalmente ocupam a parte inferior do mapa de memória do processo.

Símbolos de prioridade de segundo nível: símbolos visíveis da biblioteca dinâmica

Os símbolos exportados da biblioteca dinâmica (que residem na seção dinâmica das bibliotecas dinâmicas) são considerados pelo vinculador como o próximo
nível de prioridade no esquema de prioridade.

Símbolos de prioridade de terceiro nível (sem prioridade, não competitiva)

Os símbolos declarados como estáticos normalmente nunca são o assunto dos conflitos de nome de símbolo duplicado, independentemente de pertencerem ao
binário do cliente ou à biblioteca dinâmica vinculada estaticamente.

Ao mesmo grupo pertencem os símbolos despojados da biblioteca dinâmica, que obviamente não participam da etapa de vinculação do binário cliente. A Figura 9-
1 ilustra a abordagem de zoneamento de prioridade de símbolos.

>
Símbolos locais visíveis;
símbolos invisíveis / retirados

n
.dynsym: ^^ 0
exportado

dinâmico

símbolos

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 114/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Bibliotecas dinâmicas

Símbolos locais visíveis;


símbolos invisíveis / retirados

n
.dynsym: 0
exportado

dinâmico

símbolos y
k

Figura 9-1. Zoneamento de prioridade do linker

Arquivos de objeto e

bibliotecas estáticas

(tolerância zero para símbolos duplicados)

Análises de casos específicos de nomes duplicados


As seções a seguir abrangem vários casos de uso.

Caso 1: o símbolo binário do cliente colide com a função ABI da biblioteca dinâmica

Este cenário pode ser basicamente descrito como o símbolo pertencente à zona prioritária 1 colidindo com o símbolo pertencente à zona prioritária 2 (Figura 9-2).

1"

Figura 9-2. Caso 1: o símbolo binário do cliente colide com o símbolo ABI da biblioteca dinâmica

Como regra geral, o símbolo relacionado à zona de código de prioridade mais alta sempre vence; em outras palavras, ele é escolhido pelo vinculador como o
destino de todas as referências ao símbolo nomeado duplicado.

O projeto a seguir é criado para demonstrar este cenário específico. Ele consiste em uma biblioteca estática, uma biblioteca dinâmica e o aplicativo cliente que
vincula as duas (a biblioteca dinâmica é vinculada estaticamente). As bibliotecas apresentam um símbolo de nome duplicado:

Biblioteca estática libstaticlib.a:

arquivo: staticlibexports.h

#pragma uma vez

int staticlib_first_function (int x); int staticlib_second_function (int x);

int shared_static_duplicate_function (int x);

arquivo: staticlib.c

#include <stdio.h> #include "staticlibexports.h"

int staticlib_first_function (int x) {

printf ("% s \ n", __FUNCTION__); retorno (x + 1);

Biblioteca dinâmica ---------


file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 115/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
int staticlib_second_function (int x) {

printf ("% s \ n", _FUNCTION_);

retorno (x + 2);

int shared_static_duplicate_function (int x) {

printf ("staticlib:% s \ n", _FUNCTION_);

return 0;

arquivo: build.sh

gcc -Wall -g -OO -c staticlib.c ar -rcs libstaticlib.a staticlib.o

Biblioteca compartilhada libshlib.so:

arquivo: shlibexports.h

#pragma uma vez

função_shlib int (void);

int shared_static_duplicate_function (int x);

arquivo: shlib.c

#include <stdio.h> #include "staticlibexports.h"

int shlib_function (void) {

printf ("sharedLib:% s \ n", _FUNCTION_);

return 0;

int shared_static_duplicate_function (int x) {

printf ("sharedLib:% s \ n", _FUNCTION_);

return 0;

arquivo: build.sh

gcc -Wall -g -OO -I ../ staticLib -c shlib.c

gcc -shared shlib.o -Wl, -soname, libshlib.so.1 -o libshlib.so.1.0.0 ldconfig -n.

ln -s libshlib.so.1 libshlib.so

ClientApplication:

arquivo: main.c

#include <stdio.h> #include "staticlibexports.h" #include "shlibexports.h"

int main (int argc, char * argv []) {

int nRetValue = 0;

nRetValue + = staticlib_first_function (1); nRetValue + = staticlib_second_function (2);

shlib_function ();

shared_static_duplicate_function (1); printf ("nRetValue =% d \ n", nRetValue); return nRetValue;

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 116/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
}

arquivo: build.sh

gcc -Wall -g -O0 -I ../ staticLib -I ../ sharedLib -c main.c gcc main.o -Wl, -L ../ staticLib -lstaticlib \ -Wl, -L ../ sharedLib
- lshlib \ -Wl, -R ../ sharedLib \

-o clientApp

O aplicativo cliente é compilado com sucesso e produz a seguinte saída:

staticlib_first_function

staticlib_second_function

sharedLib: shlib_function

staticlib: shared_static_duplicate_function

nRetValue = 6

Obviamente, o vinculador escolhe o símbolo da biblioteca estática, pois ele pertence à zona de código de prioridade mais alta. Altere a ordem de construção,
conforme mostrado aqui:

arquivo: buildDifferentLinkingOrder.sh

gcc -Wall -g -O0 -I ../ staticLib -I ../ sharedLib -c main.c gcc main.o -Wl, -L ../ sharedLib -lshlib \ -Wl, -L ../ staticLib -
lstaticlib \ -Wl, -R ../ sharedLib \

-o clientAppDifferentLinkingOrder

Observe que a mudança no código não altera o resultado final:

$ ./clientAppDifferentLinkingOrder

staticlib_first_function

staticlib_second_function

sharedLib: shlib_function

staticlib: shared_static_duplicate_function

nRetValue = 6

Twist específico do Windows

O vinculador do Visual Studio tem uma maneira ligeiramente diferente de implementar essa regra neste caso específico (ou seja, quando a biblioteca estática
apresenta o símbolo do mesmo nome com o símbolo ABI da biblioteca dinâmica).

Quando a biblioteca estática aparece como a primeira na lista de bibliotecas, os símbolos da DLL são silenciosamente ignorados, o que é exatamente o esperado.

No entanto, se a DLL for especificada como a primeira na lista de bibliotecas, o que acontece não é o que você esperava (ou seja, o símbolo da biblioteca estática
sempre prevalece). Em vez disso, o link falha com uma mensagem dizendo algo como

StaticLib (staticlib.obj): erro LNK2005: function_xyz já definido \

em SharedLib.lib (SharedLib.dll) ClientApp.exe: erro fatal LNK1169: um ou mais símbolos definidos de multiplicação encontrados
FALHA DE CONSTRUÇÃO.

Caso 2: Símbolos ABI de diferentes bibliotecas dinâmicas colidem

Este cenário pode ser basicamente descrito como dois símbolos, ambos pertencentes à zona de prioridade 2, colidindo entre si (Figura 9-3).

Figura 9-3. Caso 2: símbolos ABI de diferentes bibliotecas dinâmicas colidem

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 117/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Claramente, uma vez que nenhum dos símbolos tem a vantagem de zoneamento, o fator decisivo neste caso será a ordem de ligação. Para demonstrar este cenário
específico, o seguinte projeto de demonstração é criado, que consiste em duas bibliotecas compartilhadas apresentando os símbolos ABI duplicados e o aplicativo
cliente que vincula estaticamente as duas bibliotecas dinâmicas. Para fornecer mais alguns detalhes importantes, uma das funções ABI da biblioteca compartilhada
chama internamente a função ABI duplicada:

Biblioteca compartilhada libfirst.so:

arquivo: shlibfirstexports.h

#pragma uma vez

função_shlib int (void); // função ABI duplicada int shlibfirst_function (void);

arquivo: shlib.c

#include <stdio.h>

int shlib_function (void) {

printf ("shlibFirst:% s \ n", __FUNCTION__); return 0;

int shlibfirst_function (void) {

printf ("% s \ n", _FUNCTION_);

return 0;

arquivo: build.sh

gcc -Wall -g -OO -c shlib.c

gcc -shared shlib.o -Wl, -soname, libfirst.so.1 -o libfirst.so.1.0.0 ldconfig -n.

ln -s libfirst.so.1 libfirst.so Biblioteca compartilhada libsecond.so:

arquivo: shlibsecondexports.h

#pragma uma vez

função_shlib int (void);

int shlibsecond_function (void);

int shlibsecond_another_function (void);

arquivo: shlib.c

#include <stdio.h>

int shlib_function (void) {

printf ("shlibSecond:% s \ n", _FUNCTION_);

return 0;

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 118/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
int shlibsecond_function (void) {

printf ("% s \ n", __FUNCTI0N__); return 0;

int shlibsecond_another_function (void) {

printf ("% s \ n", __FUNCTI0N__);

shlib_function (); // chamada interna para a função ABI duplicada return 0;

arquivo: build.sh

gcc -Wall -g -00 -fPIC -c shlib.c

gcc -shared shlib.o -Wl, -soname, libsecond.so.1 -o libsecond.so.1.0.0 ldconfig -n.

ln -s libsecond.so.1 libsecond.so ClientApplication:

arquivo: main.c

#include <stdio.h>

#include "shlibfirstexports.h"

#include "shlibsecondexports.h"

int main (int argc, char * argv []) {

shlib_function (); // função ABI duplicada

shlibfirst_function ();

shlibsecond_function ();

shlibsecond_another_function (); // este chama internamente shlib_function () return 0;

arquivo: build.sh

gcc -Wall -g -00 -I ../ libFirst -I ../ libSecond -c main.c gcc main.o -Wl, -L ../ libFirst -Wl, -lfirst \ -Wl, -L .. / libSecond
-Wl, -lsecond \ -Wl, -R ../ libFirst \

-Wl, -R ../ libSecond \

-o clientApp

Mesmo que as duas bibliotecas compartilhadas apresentem as duplicatas e até mesmo uma das duplicatas (shlib_duplicate_function) não seja uma função
local, a construção do aplicativo cliente é concluída com sucesso. A execução do aplicativo cliente resulta na seguinte saída:

$ ./clientApp

shlibFirst: shlib_function shlibfirst_function shlibsecond_function shlibsecond_another_function shlibFirst: shlib_function

Obviamente, o vinculador escolheu a versão do shlibFirst do símbolo duplicado para representar exclusivamente o nome do símbolo duplicado. Além disso,
embora shlibsecond_another_function () chame internamente a shlib_function () duplicada , isso não afeta o resultado final do estágio de vinculação.

Sendo o símbolo ABI (a parte da seção .dynsym ), o símbolo de função duplicado sempre é resolvido da mesma maneira, independentemente do fato de residir
no mesmo arquivo de origem com as funções ABI restantes.

Nenhum impacto de ordem diferente de chamadas de função

Como parte da investigação, o impacto da ordem de chamada de função invertida é examinado (consulte a Listagem 9-1).

Listagem 9-1. main_differentOrderOfCalls.c

#include <stdio.h>

#include "shlibfirstexports.h"

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 119/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
#include "shlibsecondexports.h"

int main (int argc, char * argv []) {

// Ordem reversa das chamadas - primeiros métodos shlibsegundos

// seja chamado, seguido pelos métodos shlibfirst

shlibsecond_function ();

shlibsecond_another_function ();

shlib_function (); // função ABI duplicada

shlibfirst_function ();

return 0;

Essa mudança específica não afetou o resultado final de forma alguma. Obviamente, os momentos importantes do estágio de vinculação que impactam
criticamente o processo de resolução de símbolo duplicado acontecem durante um estágio anterior de vinculação.

Impacto da ordem de ligação diferente


Construir o aplicativo com a ordem de vinculação diferente, entretanto, produz resultados diferentes: file: buildDifferentLinkingOrder.sh

gcc -Wall -g -O0 -I ../ shlibFirst -I ../ shlibSecond -c main.c gcc main.o -Wl, -L ../ shlibSecond -lsecond \ -Wl, -L ../
shlibFirst - lfirst \ -Wl, -R ../ shlibFirst \

-Wl, -R ../ shlibSecond \

-o clientAppDifferentLinkingOrder

$ ./clientAppDifferentLinkingOrder shlibSecond: shlib_function shlibfirst_function shlibsecond_function


shlibsecond_another_function shlibSecond: shlib_function

Obviamente, a ordem de vinculação reversa especificada afetou a decisão do vinculador. O shlibSecond versão 's de duplicada shlib_function agora é
escolhido para representar o símbolo duplicado.

Caso 3: o símbolo ABI da biblioteca dinâmica colide com outro símbolo local da biblioteca dinâmica

Este cenário pode ser basicamente descrito como um símbolo pertencente à zona prioritária 2 colidindo com um símbolo pertencente à zona prioritária 3 (Figura 9-
4).

- \

Biblioteca dinâmica

Figura 9-4. Caso 3: o símbolo ABI da biblioteca dinâmica colide com outro símbolo local da biblioteca dinâmica

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 120/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Como regra geral, assim como no exemplo do Caso 1, o símbolo relacionado à zona de código de maior prioridade sempre vence a luta; em outras palavras, ele é
escolhido pelo vinculador como o destino de todas as referências ao símbolo nomeado duplicado.

Para ilustrar este cenário específico, o seguinte projeto de demonstração é criado; ele consiste em duas bibliotecas compartilhadas (apresentando os símbolos
duplicados) e o aplicativo cliente que vincula estaticamente as duas bibliotecas:

Biblioteca compartilhada libfirst.so:

arquivo: shlibfirstexports.h

#pragma uma vez

função_shlib int (void); int shlibfirst_function (void);

arquivo: shlib.c

#include <stdio.h>

int shlib_function (void) {

printf ("shlibFirst:% s \ n", _FUNCTION_);

return 0;

int shlibfirst_function (void) {

printf ("% s \ n", _FUNCTION_);

return 0;

arquivo: build.sh

gcc -Wall -g -OO -c shlib.c

gcc -shared shlib.o -Wl, -soname, libfirst.so.1 -o libfirst.so.1.0.0 ldconfig -n.

ln -s libfirst.so.1 libfirst.so Biblioteca compartilhada libsecond.so:

arquivo: shlibsecondexports.h

#pragma uma vez

int shlibsecond_function (void);

arquivo: shlib.c

#include <stdio.h>

static int shlib_function (void) {

printf ("shlibSecond:% s \ n", _FUNCTION_);

return 0;

int shlibsecond_function (void) {

printf ("% s \ n", _FUNCTION_);

shlib_function (); return 0;

arquivo: build.sh

gcc -Wall -g -O0 -c shlib.c

gcc -shared shlib.o -Wl, -soname, libsecond.so.1 -o libsecond.so.1.0.0 ldconfig -n.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 121/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
ln -s libsecond.so.1 libsecond.so

CAPÍTULO 9 ■ MANUSEIO DE SÍMBOLOS DUPLICADOS QUANDO LIGADO EM LÍBRICAS DINÂMICAS Aplicação do cliente :

arquivo: main.c

#include <stdio.h>

#include "shlibfirstexports.h"

#include "shlibsecondexports.h"

int main (int argc, char * argv []) {

shlibfirst_function (); shlibsecond_function (); return 0;

arquivo: build.sh

gcc -Wall -g -O0 -I ../ shlibFirst -I ../ shlibSecond -c main.c gcc main.o -Wl, -L ../ shlibFirst -lfirst \ -Wl, -L ../
shlibSecond - lsecond \ -Wl, -R ../ shlibFirst \

-Wl, -R ../ shlibSecond \

-o clientApp

A construção do aplicativo cliente foi concluída com êxito. A execução dos resultados do aplicativo cliente com a seguinte saída:

$ ./clientApp

shlibFirst: shlib_function shlibsecond_function shlibSecond: shlib_function

Aqui temos uma situação um pouco interessante.

Primeiro, quando o binário do cliente invoca a duplicata chamada shlib_function, o vinculador não tem dúvidas de que esse símbolo deve ser representado
pelo método da biblioteca shlibFirst , simplesmente porque ele reside na zona de código de maior prioridade. A primeira linha da saída do aplicativo cliente
testemunha esse fato.

No entanto, muito antes de a deliberação do vinculador acontecer, durante a construção da própria biblioteca dinâmica, as chamadas internas de
shlibsecond_function () para seu shlib_function () local já foram resolvidas, simplesmente porque os dois símbolos são locais entre si. Esta é a razão
pela qual a chamada interna de uma função shlibSecond para outra função shlibSecond não é afetada pelo processo de construção do binário do cliente.

Como esperado, quando a decisão do vinculador é determinada pelas diferenças nas prioridades da zona de código, a reversão da ordem de vinculação não tem
impacto no resultado final.

Caso 4: o símbolo não exportado da biblioteca dinâmica colide com outro símbolo não exportado da
biblioteca dinâmica

Este cenário pode ser basicamente descrito como dois símbolos, ambos pertencentes à zona de prioridade 3, colidem um com o outro (Figura 9-5).

- \

Biblioteca dinâmica

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 122/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Figura 9-5. Caso 4: o símbolo não exportado da biblioteca dinâmica colide com o símbolo não exportado de outra biblioteca dinâmica

Os símbolos pertencentes à zona de código 3 são geralmente invisíveis para o processo de construção do binário do cliente. Esses símbolos são declarados de
escopo local (e completamente não interessantes para o vinculador) ou removidos (invisíveis para o vinculador).

Mesmo que os nomes dos símbolos possam ser duplicados, esses símbolos não acabam na lista de símbolos do vinculador e não causam conflitos. Sua importância
está estritamente confinada ao domínio das bibliotecas dinâmicas das quais fazem parte.

Para ilustrar este cenário específico, o seguinte projeto de demonstração é criado; ele consiste em uma biblioteca estática, uma biblioteca compartilhada e o
aplicativo cliente que vincula as duas bibliotecas. A biblioteca dinâmica está vinculada estaticamente.

Cada um dos binários apresenta funções locais cujos nomes são duplicatas dos nomes das funções locais encontradas nos módulos restantes. Além disso, o
aplicativo cliente tem a função local identicamente nomeada como a função da biblioteca compartilhada cujos símbolos são intencionalmente removidos.

Biblioteca estática libstaticlib.a:

arquivo: staticlibexports.h

#pragma uma vez

função_ staticlib int (int x);

arquivo: staticlib.c

#include <stdio.h> #include "staticlibexports.h"

static int local_function (int x) {

printf ("staticLib:% s \ n", __FUNCTI0N__); return 0;

int staticlib_function (int x) {

printf ("% s \ n", __FUNCTI0N__);

função_local (x);

retorno (x + 1);

arquivo: build.sh

gcc -Wall -g -00 -c staticlib.c ar -rcs libstaticlib.a staticlib.o

Biblioteca compartilhada libshlib.so: arquivo: shlibexports.h

#pragma uma vez

função_shlib int (void);

arquivo: shlib.c

#include <stdio.h> #include "staticlibexports.h"

static int local_function (int x) {

printf ("sharedLib:% s \ n", __FUNCTI0N__); return 0;

static int local_function_strippedoff (int x) {

printf ("sharedLib:% s \ n", __FUNCTI0N__); return 0;

int shlib_function (void) {

printf ("sharedLib:% s \ n", _FUNCTION_);

função_local (l); local_function_strippedoff (l); return 0;

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 123/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
}

arquivo: build.sh

gcc -Wall -g -00 -I ../ staticLib -c shlib.c

gcc -shared shlib.o -Wl, -soname, libshlib.so.1 -o libshlib.so.1.0.0 strip -N local_function_strippedoff libshlib.so.1.0.0

ldconfig -n.

ln -s libshlib.so.1 libshlib.so

Aplicativo cliente: arquivo: main.c

#include <stdio.h> #include "staticlibexports.h" #include "shlibexports.h"

static int local_function (int x)

printf ("clientApp:% s \ n", _FUNCTION_);

return 0;

static int local_function_strippedoff (int x)

printf ("clientApp:% s \ n", _FUNCTION_);

return 0;

int main (int argc, char * argv [])

shlib_function (); staticlib_function (l); função_local (l); local_function_strippedoff (l); return 0;

arquivo: build.sh

gcc -Wall -g -O0 -I ../ staticLib -I ../ sharedLib -c main.c gcc main.o -Wl, -L ../ staticLib -lstaticlib \ -Wl, -L ../ sharedLib
- lshlib \ -Wl, -R ../ sharedLib \

-o clientApp

Como esperado, o aplicativo cliente foi criado com sucesso e produziu a seguinte saída:

sharedLib: shlib_function

sharedLib: local_function

sharedLib: local_function_strippedoff

staticlib_function

staticLib: local_function

clientApp: local_function

clientApp: local_function_strippedoff

Obviamente, o vinculador não percebeu nenhum problema de símbolo duplicado. Todos os símbolos locais / removidos foram resolvidos em seus módulos
específicos e não entraram em conflito com nenhum dos símbolos locais / removidos com nomes idênticos nos outros módulos.

Cenário interessante: Singleton na biblioteca estática


Agora que você sabe como o vinculador lida com o domínio não priorizado / não concorrente de símbolos locais / removidos de bibliotecas dinâmicas, é muito
mais fácil entender o cenário interessante descrito no Capítulo 6 como um problema de "várias instâncias de uma classe única" ( um dos cenários contra-indicados
para o uso de bibliotecas estáticas).

Imagine por um momento o seguinte cenário do mundo real: digamos que você precise projetar uma classe de utilitário de registro em todo o processo exclusivo.
Deve existir em uma instância e deve ser visível para todos os diferentes módulos funcionais.

O paradigma de implementação seria normalmente baseado no padrão de design singleton. Vamos supor por um momento que a origem de sua classe singleton
seja uma biblioteca estática dedicada.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 124/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Para obter acesso ao utilitário de registro, várias bibliotecas dinâmicas que hospedam seus módulos funcionais se vinculam a essa biblioteca estática específica.
Sendo apenas parte da funcionalidade interna da biblioteca dinâmica (ou seja, não faz parte da interface ABI da biblioteca dinâmica), os símbolos de classe
singleton não foram exportados. Os símbolos da classe singleton começam automaticamente a pertencer à zona de código não priorizada / não competitiva.

Uma vez que o processo é iniciado e todas as bibliotecas dinâmicas são carregadas, você acaba tendo uma situação em que várias bibliotecas dinâmicas vivem no
mesmo processo, cada uma delas tendo uma classe única em seus próprios "quintais privados". E eis que, devido à natureza não competitiva da zona de símbolos
locais das bibliotecas dinâmicas, de repente você acaba tendo várias instâncias (bem coexistentes) de sua classe de utilitário de registro único.

O único problema é que você queria uma única instância de classe singleton única, não muitas delas !!!

Para ilustrar este cenário específico, o próximo projeto de demonstração é criado com os seguintes componentes:

• Uma biblioteca estática que hospeda a classe singleton

• Duas bibliotecas compartilhadas, cada uma conectando-se à biblioteca estática. Cada uma das bibliotecas compartilhadas exporta apenas um símbolo: uma
função que chama internamente os métodos do objeto singleton. Os símbolos de classe singleton vindos da biblioteca estática vinculada não são exportados.

• Um aplicativo cliente que se vincula à biblioteca estática para acessar a própria classe singleton. Ele também possui links estaticamente compatíveis em ambas as
bibliotecas compartilhadas.

O aplicativo cliente e ambas as bibliotecas compartilhadas fazem suas próprias chamadas para a classe singleton. Como você verá em breve, o aplicativo
apresentará três instâncias diferentes da classe singleton:

Biblioteca estática libsingleton.a:

arquivo: singleton.h

#pragma uma vez

class Singleton {

público:

estático Singleton & GetInstance (void);

público:

~ Singleton () {};

int DoSomething (vazio);

privado:

Singleton () {};

Singleton (Const & Singleton); // propositalmente não implementado void operator = (Singleton const &); // propositalmente não
implementado

privado:

Singleton estático * m_pInstance;

};

arquivo: singleton.cpp

#include <iostream> #include "singleton.h" usando o namespace std;

Singleton * Singleton :: m_pInstance = NULL;

Singleton & Singleton :: GetInstance (void) {

if (NULL == m_pInstance)

m_pInstance = novo Singleton (); return * m_pInstance;

int Singleton :: DoSomething (void) {

cout << "endereço de instância singleton =" << this << endl; return 0;

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 125/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
arquivo: build.sh

# para SO de 64 bits também deve passar -mcmodel = sinalizador de compilador grande g ++ -Wall -g -O0 -c singleton.cpp ar -rcs
libsingleton.a singleton.o

Biblioteca compartilhada libfirst.so:

arquivo: shlibfirstexports.h

#pragma uma vez

#ifdef __cplusplus

extern "C" {

#endif // __cplusplus

int shlibfirst_function (void);

#ifdef __cplusplus}

#endif // __cplusplus

arquivo: shlib.c

#include <iostream> #include "singleton.h" usando o namespace std;

#ifdef __cplusplus

extern "C" {

#endif // __cplusplus

int shlibfirst_function (void) {

cout << _FUNCTION_ << ":" << endl;

Singleton & singleton = Singleton :: GetInstance ();

singleton.DoSomething ();

return 0;

#ifdef __cplusplus}

#endif // _cplusplus

arquivo: build.sh

rm -rf * .o lib *

g ++ -Wall -g -00 -fPIC -I ../ staticLib -c shlib.cpp g ++ -shared shlib.o -L ../ staticLib -lsingleton \ -Wl, - version-script =
versionScript \

-Wl, -soname, libfirst.so.1 -o libfirst.so.1.0.0 ldconfig -n.

ln -s libfirst.so.1 libfirst.so

arquivo: versionScript

global:

shlibfirst_function; local:

};

Biblioteca compartilhada libsecond.so:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 126/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
arquivo: shlibfirstexports.h

#pragma uma vez

#ifdef __cplusplus

extern "C" {

#endif // __cplusplus

int shlibsecond_function (void);

#ifdef __cplusplus}

#endif // __cplusplus

arquivo: shlib.c

#include <iostream> #include "singleton.h" usando o namespace std;

#ifdef _cplusplus

extern "C" {

#endif // _cplusplus

int shlibsecond_function (void) {

cout << _FUNCTION_ << ":" << endl;

Singleton & singleton = Singleton :: GetInstance ();

singleton.DoSomething ();

return 0;

#ifdef __cplusplus}

#endif // __cplusplus

arquivo: build.sh

rm -rf * .o lib *

g ++ -Wall -g -00 -fPIC -I ../ shlibFirst -I ../ staticLib -c shlib.cpp g ++ -shared shlib.o -L ../ staticLib -lsingleton \

-Wl, - version-script = versionScript \

-Wl, -soname, libsecond.so.1 -o libsecond.so.1.0.0 ldconfig -n.

ln -s libsecond.so.1 libsecond.so

arquivo: versionScript

global:

shlibsecond_function; local:

};

ClientApplication:

arquivo: main.c

#include <iostream> #include "shlibfirstexports.h" #include "shlibsecondexports.h" #include "singleton.h"

usando namespace std;

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 127/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
int main (int argc, char * argv []) {

shlibfirst_function (); shlibsecond_function ();

cout << "Acessando o singleton diretamente do aplicativo cliente" << endl; Singleton & singleton = Singleton :: GetInstance ();
singleton.DoSomething (); return 0;

CAPÍTULO 9 ■ MANUSEIO DE SÍMBOLOS DUPLICADOS AO LINGUAR no arquivo LiBRARiES DINÂMICAS : build.sh

g ++ -Wall -g -O0 -I ../ staticLib -I ../ shlibFirst -I ../ shlibSecond -c main.cpp g ++ main.o -L ../ staticLib -lsingleton \ -L
../ shlibFirst - lfirst \ -L ../ shlibSecond -lsecond \ -Wl, -R ../ shlibFirst \

-Wl, -R ../ shlibSecond \

-o clientApp

O aplicativo cliente produziu a seguinte saída:

shlibfirst_function:

endereço de instância singleton = 0x9a01008

shlibsecond_function:

endereço de instância singleton = 0x9a01018

Acessando o singleton diretamente do aplicativo cliente

endereço de instância singleton = 0x9a01028

■ Observe que é deixado para o leitor diligente descobrir que o carregamento dinâmico do tempo de execução (dlopen) não mudaria nada a esse respeito.

Como uma nota final sobre este tópico, foi tentada uma versão de singleton thread-safe em que a instância singleton seria uma variável estática de função em vez
de uma variável estática de classe:

Singleton & Singleton :: GetInstance (void) {

Static Singleton uniqueInstance; return uniqueInstance;

Essa abordagem resultou apenas com resultados um pouco melhores, em que ambas as bibliotecas compartilhadas imprimem o valor de endereço de instância
singleton idêntico, embora o aplicativo cliente imprima valores de endereço de instância singleton substancialmente diferentes.

Resolvendo o problema

Para não ser totalmente pessimista, existem várias maneiras de resolver esse tipo de problema.

Uma das possibilidades é baseada no relaxamento dos critérios de exportação de símbolo um pouco, permitindo que as bibliotecas dinâmicas exportem
adicionalmente os símbolos de classe singleton. Depois de exportados, os símbolos singleton não pertencerão mais à categoria de símbolos não prioritários / não
concorrentes cuja existência é permitida em zilhões de instâncias. Em vez disso, eles serão promovidos à categoria de "símbolos ABI concorrentes". De acordo com
as regras elaboradas, o vinculador escolheria apenas um dos símbolos e direcionaria todas as referências a esse símbolo de classe de singleton específico.

A solução definitiva para o problema seria hospedar a classe singleton em uma biblioteca dinâmica. Dessa forma, a grande maioria dos possíveis cenários
indesejados seria completamente eliminada. Nenhuma das regras de design da ABI seria violada, e o design de novos módulos não enfrentaria os absurdos
requisitos extras de design.

Observação final: a vinculação não fornece nenhum tipo de herança de namespace


O uso de namespaces é definitivamente a ferramenta mais poderosa para evitar completamente as surpresas desagradáveis ​provenientes de muita confiança no
raciocínio interno do vinculador ao lidar com os símbolos duplicados.

Independentemente do fato de que uma biblioteca compartilhada pode vincular outra biblioteca compartilhada, que pode vincular ainda outra biblioteca
compartilhada, que eventualmente pode vincular a biblioteca estática, protegendo a singularidade dos símbolos transportados por uma biblioteca situada em
algum lugar no meio da cadeia de vinculação requer que exatamente o código daquela biblioteca em particular seja encapsulado em seu próprio namespace
proprietário.

Esperar que o namespace da biblioteca superior protegerá a exclusividade dos símbolos da biblioteca entre possíveis conflitos com as outras bibliotecas dinâmicas
é simplesmente errado.

O único plano sólido, aquele que realmente funciona, é que cada biblioteca, estática ou dinâmica, deve apresentar seu próprio namespace dedicado.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 128/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

CAPÍTULO 10

Controle de versão de bibliotecas dinâmicas


Na maioria das vezes, o desenvolvimento de código é um trabalho em andamento. Como resultado do esforço para fornecer mais e mais recursos, bem como para
solidificar o corpo de código existente, o código muda inevitavelmente. Mais frequentemente, os saltos quânticos do projeto tendem a quebrar a compatibilidade
entre os componentes do software. O ideal de alcançar compatibilidade com versões anteriores normalmente requer um esforço dedicado e focado. Um papel
muito importante nesses esforços pertence ao conceito de controle de versão.

Dado o fato de que as bibliotecas dinâmicas fornecem funcionalidade que normalmente é usada por muito mais de um binário cliente, a precisão de rastrear as
versões da biblioteca e a disciplina em respeitar as informações de versão indicadas requerem um nível extra de rigidez. Deixar de perceber e reagir às
discrepâncias entre a funcionalidade fornecida pelas diferentes versões de uma biblioteca dinâmica pode significar não apenas o mau funcionamento de um único
aplicativo, mas às vezes o caos na funcionalidade mais ampla do sistema operacional (sistema de arquivos, rede, sistema de janelas , etc).

Gradação de versões e seu impacto na compatibilidade com versões anteriores


Nem todas as alterações de código têm o mesmo impacto na funcionalidade do módulo. Algumas das mudanças são de natureza cosmética, outras representam
correções de bugs e ainda outras trazem mudanças substanciais que rompem com os paradigmas que existiam antes. A gradação da importância geral das
mudanças se manifesta no sofisticado esquema de controle de versão cujos detalhes merecem discussão dedicada.

Mudanças no código da versão principal

Como regra, as alterações no código da biblioteca dinâmica que quebram a funcionalidade suportada anteriormente devem resultar no aumento do número da
versão principal . Os sintomas de interrupção da funcionalidade anterior abrangem uma ampla gama de possibilidades, incluindo o seguinte:

• Uma mudança substancial na funcionalidade de tempo de execução fornecida, como a eliminação completa de um recurso suportado anteriormente, mudança
substancial de requisitos para um recurso a ser suportado, etc.

• Incapacidade do binário do cliente de se vincular à biblioteca dinâmica devido a uma ABI alterada, como funções removidas ou interfaces inteiras, assinaturas
de função exportadas alteradas, estrutura reordenada ou layout de classe, etc.

• Paradigmas completamente alterados na manutenção do processo em execução ou mudanças que exigem grandes mudanças na infraestrutura (como mudar
para um tipo de banco de dados completamente diferente, passar a contar com diferentes formas de criptografia, passar a exigir diferentes tipos de hardware, etc.).

Pequenas alterações de código de versão

Alterações no código da biblioteca dinâmica que introduzem nova funcionalidade sem quebrar a funcionalidade existente normalmente resultam no número de
versão secundária incrementado . As alterações de código que se qualificam para o incremento de números de versão secundária da biblioteca dinâmica normalmente
não impõem recompilar / revincular os binários do cliente, nem causam alterações substanciais no comportamento do tempo de execução. Os recursos adicionados
normalmente não representam curvas radicais, mas sim um leve aprimoramento da variedade existente de opções disponíveis.

As modificações das interfaces ABI não são excluídas automaticamente no caso de alterações de código de incremento de versão secundária. As modificações ABI
no caso de alterações de versão menores, no entanto, geralmente significam adições de novas funções, constantes e estruturas ou classes - em outras palavras,
alterações que não afetam a definição e o uso das interfaces existentes anteriormente. Mais importante ainda, os binários do cliente que dependiam da versão
anterior não requerem reconstrução para usar a nova versão secundária da biblioteca dinâmica.

Versão Patch

As alterações de código que são principalmente de escopo interno, que não causam nenhuma alteração na interface ABI nem trazem uma alteração substancial de
funcionalidade, normalmente se qualificam para o status de "patch".

Esquemas de controle de versão da biblioteca dinâmica do Linux


A implementação específica do Linux do conceito de controle de versão será discutida em detalhes, já que a sofisticação com a qual ele resolve algumas das
questões mais importantes relacionadas ao problema de controle de versão de bibliotecas dinâmicas definitivamente merece atenção. Dois esquemas distintos de
versionamento estão atualmente em uso: o esquema de versionamento baseado no soname da biblioteca e o esquema de versionamento de símbolos individuais.

Esquema de controle de versão baseado em Soname do Linux Nome do arquivo da biblioteca do Linux contém as
informações da versão

Conforme mencionado no capítulo 7, a última parte do nome do arquivo de uma biblioteca dinâmica Linux representa as informações de versão da biblioteca:

nome do arquivo da biblioteca = lib + <nome da biblioteca> + .so + <informações da versão da biblioteca>

As informações da versão da biblioteca normalmente usam o formato informações da versão da biblioteca dinâmica = <M>. <m>. <p>

onde o M representa um ou mais dígitos indicando a versão principal da biblioteca, om representa um ou mais dígitos indicando a versão secundária da biblioteca
e op representa um ou mais dígitos indicando o número do patch da biblioteca (isto é, alteração muito secundária).
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 129/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

As práticas usuais de atualização de biblioteca dinâmica

Em cenários típicos do mundo real, as chegadas de novas versões secundárias de bibliotecas dinâmicas tendem a acontecer com bastante frequência. As
expectativas de uma atualização de versão secundária causando problemas são geralmente muito baixas, especialmente se o fornecedor seguir procedimentos de
teste sólidos antes de publicar o novo código.

Na maioria das vezes, a instalação da nova versão secundária da biblioteca dinâmica deve ser um procedimento bastante simples e tranquilo, como uma cópia
simples de um novo arquivo.

No entanto, independentemente de quão pequenas sejam as chances de que uma nova versão secundária quebre a funcionalidade existente, ainda existe a
possibilidade de que isso aconteça. Para poder voltar e restaurar elegantemente a versão anterior da biblioteca dinâmica que funcionou perfeitamente, a cópia
simples de arquivos precisa ser substituída por uma abordagem um pouco mais sutil.

Preâmbulo: A Flexibilidade dos Softlinks

Por definição, um softlink é o elemento do sistema de arquivos que carrega uma string contendo o caminho para outro arquivo. Na verdade, podemos dizer que o
softlink aponta para um arquivo existente. Na maioria dos aspectos, o sistema operacional trata o softlink como o arquivo para o qual ele aponta. O acesso a um
softlink e o redirecionamento ao arquivo que ele representa impõe penalidades de desempenho insignificantes.

O softlink pode ser criado facilmente.

Caminho do arquivo $ ln -s> <caminho do softlink>

Ele também pode ser redirecionado para apontar para outro arquivo. $ ln -s -f <outro arquivo> <softlink existente>

Finalmente, o softlink pode ser destruído quando não for mais necessário. $ rm -rf <caminho do softlink>

Preâmbulo: Biblioteca Soname vs. Biblioteca de nome de arquivo

Conforme mencionado na discussão do Capítulo 7 sobre as convenções de nomenclatura da biblioteca do Linux, o nome do arquivo da biblioteca deve seguir o
seguinte esquema:

nome do arquivo da biblioteca = lib + <nome da biblioteca> + .so + <informações da versão completa da biblioteca>

O soname da biblioteca dinâmica Linux é definido como

biblioteca soname = lib + < nome da biblioteca > + .so + <(apenas os) dígitos da versão principal da biblioteca >

Obviamente, o soname é quase idêntico ao nome do arquivo da biblioteca, a única diferença é que ele não carrega as informações completas de controle de versão,
mas carrega apenas a versão principal da biblioteca dinâmica . Como você verá, esse fato desempenha um papel particularmente importante nos esquemas de
controle de versão de bibliotecas dinâmicas.

Combinando Softlink e Soname no Esquema de Atualização da Biblioteca

A flexibilidade do softlink se adapta muito bem a cenários de atualização de bibliotecas dinâmicas. As seguintes diretrizes descrevem o procedimento:

• Na mesma pasta onde reside o nome do arquivo da biblioteca dinâmica real, é mantido um softlink que aponta para o arquivo da biblioteca real.

• Seu nome corresponde exatamente ao soname da biblioteca para a qual aponta. Dessa forma, o softlink de fato carrega o nome da biblioteca no qual as
informações de versão são um pouco relaxadas (ou seja, carrega não mais do que as informações da versão principal).

• Como regra, os binários do cliente nunca são (ou seja, apenas excepcionalmente raramente) vinculados ao nome do arquivo da biblioteca dinâmica que contém
as informações de versão totalmente detalhadas. Em vez disso, como você verá em detalhes em breve, o procedimento de construção do binário do cliente é
propositalmente definido para resultar com o binário do cliente sendo vinculado ao soname da biblioteca.

• O raciocínio por trás desta decisão é bastante simples: especificar as informações completas e exatas de versão da biblioteca dinâmica imporia muitas restrições
desnecessárias, pois impediria diretamente a vinculação com qualquer versão mais recente da mesma biblioteca.

A Figura 10-1 ilustra o conceito.

Figura 10-1. A função do softlink cujo nome corresponde ao soname da biblioteca

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 130/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Softlink extra necessário como conveniência para cenários de desenvolvimento

Ao construir o binário do cliente, você precisa determinar a localização do tempo de construção da biblioteca dinâmica, durante a qual se espera que você siga as
regras da convenção "-L -l". Mesmo que seja possível passar o nome do arquivo da biblioteca dinâmica exata (ou softlink / soname) para o vinculador, adicionando
o caractere de dois pontos entre o "-l" e o nome do arquivo (-l: <nome_do_arquivo>), como

$ gcc -shared <inputs> -2: libxyz.so.1 -o <clientBinary>

é uma convenção informal, mas bem estabelecida, de passar apenas o nome da biblioteca privado de qualquer informação de controle de versão. Por exemplo,

$ gcc -shared <inputs> -l m -l dl -l pthread -l xml2 -l xyz -o <clientBinary>

indica que o binário do cliente requer vinculação com bibliotecas cujos nomes são libm, libdl, libpthread, libxml2 e libxyz, respectivamente.

Por esse motivo, além do softlink que carrega o soname da biblioteca, é típico fornecer o softlink que carrega apenas o nome da biblioteca mais a extensão de
arquivo .so , conforme ilustrado na Figura 10-2.

Figura 10-2. O uso de softlinks durante o tempo de construção vs. durante o tempo de execução

Existem várias maneiras de fornecer o softlink extra. A maneira mais estruturada de fazer isso é por meio da configuração de implantação do pacote (pkg-config).
Uma maneira um pouco menos estruturada é fazê-lo no destino de implantação do makefile que governa a construção da biblioteca dinâmica. Finalmente, sempre
é possível criar o softlink manualmente a partir da linha de comando ou configurando um script simples para fazê-lo.

Análise do esquema de controle de versão baseado em Soname

O esquema descrito obviamente combina duas flexibilidades: a flexibilidade inerente de um softlink com a flexibilidade de versionamento do soname. Aqui está
como as duas flexibilidades atuam juntas no esquema geral das coisas.

O papel do Softlink

Como o sistema operacional trata o softlink como o arquivo para o qual ele aponta e fornece mecanismos de desreferenciação eficientes, o carregador não tem
nenhum problema particular ao conectar o binário do cliente por meio do softlink ao arquivo de biblioteca real disponível no tempo de execução.

Quando uma nova versão de uma biblioteca dinâmica chega, leva muito pouco esforço e tempo para copiar seu arquivo na mesma pasta onde a versão mais antiga
já reside e para modificar o link de software para apontar para o arquivo de versão mais recente.

$ ln -s -f <nova versão do arquivo de biblioteca dinâmica> <soname existente>

Os benefícios deste esquema são óbvios:

• Não há necessidade de reconstruir o binário do cliente.

• Não há necessidade de apagar ou sobrescrever a versão atual do arquivo de biblioteca dinâmica. Ambos os arquivos podem coexistir na mesma pasta.

• Configuração fácil, elegante e rápida do binário do cliente para usar a versão mais recente da biblioteca dinâmica.

• A capacidade de restaurar com elegância a conexão do binário do cliente com a versão mais antiga da biblioteca dinâmica nos casos em que a atualização resulta
com uma funcionalidade inesperada.

Função de proteção da versão do Soname

Conforme mencionado na seção anterior, nem todos os tipos de alterações no código da biblioteca dinâmica terão um impacto prejudicial na funcionalidade
binária do cliente. É razoável esperar que os incrementos da versão secundária não causem problemas maiores (como a incapacidade de vincular ou executar
dinamicamente, ou alterações graves de tempo de execução indesejadas e inesperadas). As atualizações requerem incrementos de versão principais; por outro
lado, são proposições extremamente arriscadas e devem ser tomadas com extrema cautela.

Não é necessário pensar muito para concluir que o soname é de fato projetado para agir como uma espécie de salvaguarda bastante elástica.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 131/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• Ao usá-lo como um identificador de biblioteca dinâmica no processo de construção do binário do cliente, você basicamente impõe limites à versão principal da
biblioteca dinâmica.

O carregador é projetado para ser inteligente o suficiente para reconhecer a tentativa de atualizar a biblioteca dinâmica para uma versão principal diferente da que
o soname sugere e evitar que isso aconteça.

• Ao omitir propositalmente os detalhes sobre a versão secundária e o número do patch, você permite implicitamente que as alterações na versão secundária
aconteçam sem muitos problemas.

Por melhor que tudo isso pareça, este esquema é bastante seguro apenas nos cenários em que você tem boas razões para esperar que as mudanças trazidas pela
nova versão da biblioteca não quebrem a funcionalidade geral, o que é o caso quando, no máximo, a versão secundária alterar. A Figura 10-3 ilustra a função de
proteção da versão do soname.

libxyz.so.

Versão principal

Versões menores

Figura 10-3. O Soname protege contra vinculação com versões principais incompatíveis da biblioteca compartilhada, mas não interfere nas atualizações de versões secundárias

Em situações em que a nova biblioteca dinâmica apresenta uma versão principal atualizada, esse esquema foi projetado para impedir a execução. Explicar como
exatamente as medidas de limitação funcionam neste caso exige que nos aprofundemos um pouco mais nos detalhes da implementação.

Aspectos técnicos da implementação do Soname

Por mais fundamentalmente sólido que pareça, o esquema baseado no uso de soname não seria tão poderoso, a menos que sua implementação apresentasse uma
faceta muito importante. Mais especificamente, o soname é incorporado aos binários. O formato ELF reserva os campos dedicados da seção dinâmica que são usados ​
(dependendo da finalidade) para transportar as informações do soname. Durante o estágio de vinculação, o vinculador pega a string de soname especificada e a
insere no campo de formato ELF de sua escolha.

A "vida secreta" do soname começa quando o vinculador o imprime na biblioteca dinâmica, com o objetivo de declarar a versão principal da biblioteca. No
entanto, não termina aí. Sempre que um binário do cliente se vincula à biblioteca dinâmica, o vinculador extrai o soname da biblioteca dinâmica e o insere no
arquivo do binário do cliente também, embora desta vez com um propósito um pouco diferente - indicar os requisitos de versão do binário do cliente.

Soname incorporado ao arquivo de biblioteca dinâmica

Ao construir uma biblioteca dinâmica, você pode usar o sinalizador do vinculador dedicado para especificar o soname da biblioteca. $ gcc -shared <lista de
entradas do linker> -Wl, -soname, <soname> -o <nome do arquivo da biblioteca>

O vinculador incorpora a string de soname especificada no campo DT_SONAME do binário, conforme mostrado na Figura 10-4.

nilan @ nilaii $ total 12 drwxrwxr-x 2 drwxr-xr-x 7 -rw-rw-r-- l -rw-rw-r-- 1 nilan @ nilan $ nilan @ nilan $ nilan @ nilan $ total 24 drwxrwxr- x drwxr-xr-x -rwxrwxr-x -rw-rw-r --- rw-rw-r --- rw-rw-r--

ls -alg

nilan 4096 11 de dezembro 22:41. nilan 4096 10 de dezembro 00:10 .. nilan 43 11 de dezembro 22:40 test.c nilan 41 11 de dezembro 23:01 test.h gcc -fPIC -c test.c -o test.o

gcc -shared test.o -Wl, -sonane, libtest.so.1 -o libtest.so.1.0.0 ls -alg

nilan 4096, 11 de dezembro, 22:42. nilan 4096 10 de dezembro 00:10

nilan 6864 11 de dezembro 22:42 libtest.so.1.0.0 nilan 43 11 de dezembro 22:40 test.c nilan 41 11 de dezembro 23:01 test.h

nilan 864 11 de dezembro 22:41 test.o nilan @ nilan $ readelf -d libtest. tão. 1.0.0

A seção dinâmica no deslocamento 0xf20 contém 21 entradas:

Tipo de tag Nane / Valor

0x00000001 (NEEDED) Biblioteca compartilhada: [Ubc.so.6]

OxOOOOOOOe (SONAME) Biblioteca sonana: [libtest.so.1]

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 132/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
0x0000000c (INIT) 0x304

0X478

OxOOOOOOOd (FINI)

OOOO

Figura 10-4. Soname é incorporado ao campo DT_SONAME do arquivo binário

Soname propagado para o arquivo binário do cliente

Quando o binário do cliente é vinculado (diretamente ou por meio do softlink) à biblioteca dinâmica, o vinculador obtém o soname da biblioteca dinâmica e o
insere no campo DT_NEEDED do binário do cliente, conforme mostrado na Figura 10-5.

drwxrwxr-x 2 mllan 4096 11 de dezembro 23:21 cltentBtnary

Irwxrwxrwx 1 Mian 12 Dez 11 23:23 Itbtest.so -> llbtest.so.l

Irwxrwxrwx 1 mllan 16 dez 11 23:22 llbtest.so.l -> llbtest.so.1.0.a

-rwxrwxr-x 1 mtlan 6662 11 de dezembro 22:42 Itbtest, então, 1,8,0

piilan @ milão: cllentBlnaryS gcc -I ../ -c fiain.c -o roaln.o rotlan @ mtlan: cllentBlnary $ gcc -shared -L., / -Itest naln.o -o cltentBtnary

-rwxrwxr-x 1 mllan 6683 11 de dezembro 23:21 cltentBtnary

rvilan @ ntlan: cllentBlnaryS readelf -d cltentBtnary

A seção Dynanic no deslocamento 0xfl8 contém 22 entradas:

0x00000001 (NEEDED) Biblioteca compartilhada: [llbtest.so.l]

0x00000001 (NEEDED) Biblioteca compartilhada: [ltbc.so.6]

Figura 10-5. O soname da biblioteca vinculada é propagado para o binário do cliente

Dessa forma, as informações de versão transportadas pelo soname são propagadas ainda mais, estabelecendo regras de versão firmes entre todas as partes
envolvidas (o vinculador, o arquivo de biblioteca dinâmica, o arquivo binário do cliente e o carregador).

Ao contrário dos nomes de arquivos da biblioteca, que podem ser facilmente modificados por todos (desde um irmão mais novo com muitos dedos por célula
cerebral e muito tempo até hackers maliciosos), alterar o valor do soname não é uma tarefa simples nem prática , pois requer não apenas modificações do arquivo
binário, mas também total familiaridade com o formato ELF.

O suporte de outros programas utilitários (ldconfig)

Além de serem suportadas por todos os jogadores necessários no cenário de vinculação dinâmica (ou seja, o vinculador, os arquivos binários, o carregador), as
outras ferramentas tendem a oferecer suporte ao conceito de soname. O programa utilitário ldconfig é um exemplo notável a esse respeito. Além de seu escopo
original de responsabilidades, esta ferramenta tem um recurso extra de "canivete suíço".

Quando -n <diretório> argumentos de linha de comando são passados, o ldconfig abre todos os arquivos de biblioteca dinâmica (cujos nomes estão em
conformidade com a convenção de nomenclatura de biblioteca!), Extrai seu soname e para cada um deles cria um softlink cujo nome é igual a o soname extraído.

A opção -l <arquivo de biblioteca específico> é ainda mais flexível, pois neste caso o nome do arquivo da biblioteca dinâmica pode ser absolutamente
qualquer nome de arquivo válido. Não importa a aparência do nome do arquivo (seja o nome completo da biblioteca original com as informações de versão
completa ou um nome de arquivo severamente alterado), o soname embutido no arquivo especificado é extraído e o softlink correto é criado apontando
inequivocamente para o arquivo da biblioteca .

Para demonstrar isso, um pequeno experimento foi executado no qual o nome da biblioteca original foi alterado propositalmente. Ainda assim, o ldconfig
conseguiu criar o softlink correto, conforme mostrado na Figura 10-6.

-rwxrwxr-x 1 nilan 6662 11 de dezembro 22:42 libtest.so.1.0.a nilan (0piilan $ civ libtest.so. 1.0.0 propositadamenteChangedNane

-rwxrwxr-x 1 milão 6864 11 de dezembro 22:42 propositadamenteChangedNane

Irwxrwxrwx 1 nilan 23 dez 11 23:02 libtest.so.1 -> propositadamenteChangedNane -rwxrwxr-x 1 milão 6864 dez 11 22:42 propositadamenteChangedNane

Figura 10-6. Independentemente do nome da biblioteca, ldconfig extrai seu soname

Esquema de controle de versão de símbolo do Linux

Além de controlar as informações de versão de toda a biblioteca dinâmica, o vinculador GNU oferece suporte a um nível extra de controle sobre a versão, no qual
as informações de versão podem ser atribuídas a símbolos individuais. Neste esquema, os arquivos de texto conhecidos como scripts de versão com uma sintaxe
bastante simples são passados ​para o vinculador durante o estágio de vinculação, que o vinculador insere nas seções ELF (.gnu.version e similares)
especializadas em transportar as informações de versão de símbolo .

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 133/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

A vantagem do mecanismo de controle de versão de símbolo


O esquema de versão de símbolo é em muitos aspectos mais sofisticado do que a versão baseada em soname. Um detalhe particularmente interessante da
abordagem de controle de versão de símbolo é que ela permite que um único arquivo binário de biblioteca dinâmica carregue simultaneamente várias versões
diferentes do mesmo símbolo. Os diferentes binários do cliente que precisam de diferentes versões da mesma biblioteca dinâmica carregarão o mesmo, um e
somente arquivo binário, e ainda serão capazes de se vincular aos símbolos de uma versão especificada.

Para comparação, quando o método de versão baseado em soname é usado, a fim de oferecer suporte a várias versões principais da mesma biblioteca, você precisa
exatamente que muitos binários diferentes (cada um carregando um valor diferente de soname) estejam fisicamente presentes na máquina de destino. A Figura 10-
7 ilustra a diferença entre os esquemas de controle de versão.

CAPÍTULO 10 ■ VERSÃO DINÂMICA DE LiBRARiES Esquema de versionamento Soname

Esquema de versão de símbolo

Figura 10-7. Comparação de esquemas de versão com base em soname e com base em símbolo

Como um bônus adicional, devido à riqueza de recursos suportados pela sintaxe do arquivo de script, também é possível controlar a visibilidade do símbolo (ou
seja, quais símbolos são exportados pela biblioteca e quais permanecem ocultos), da maneira cuja elegância e a simplicidade ultrapassa todos os métodos de
visibilidade de símbolo descritos até agora.

Modelo de análise de mecanismos de versão de símbolo

Para entender completamente o mecanismo de controle de versão de símbolo, é importante definir os cenários de caso de uso usuais em que ele é usado.

Fase 1: Versão Inicial

No início, digamos que uma primeira versão publicada da biblioteca dinâmica fique felizmente ligada ao binário cliente "A" e tudo corra bem. A Figura 10-8
descreve essa fase inicial do ciclo de desenvolvimento.

Cliente binário

s
Biblioteca dinâmica versão 1.0.0

Figura 10-8. Cronologicamente, as primeiras linhas binárias "A" do cliente na versão 1.0.0 da biblioteca. Este é, no entanto, apenas o começo da história.

Fase 2: Pequenas alterações de versão

A cada dia que passa, o progresso do desenvolvimento dinâmico da biblioteca inevitavelmente traz mudanças. Ainda mais importante, não apenas a biblioteca
dinâmica é alterada, mas também uma nova série de binários de cliente ("B," "C" etc.) emergem, que não existiam no momento em que a ligação da biblioteca

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 134/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
dinâmica com o primeiro cliente binário "A" aconteceu. Esse estágio é ilustrado pela Figura 10-9.

Cliente binário

Figura 10-9. Linlcs binários "B" do cliente um pouco mais recentes na versão de biblioteca mais recente (1.1.0)

Cliente binário

■ '///////,
UMA /■

■ Si
Biblioteca dinâmica versão 1.0.0

Versão da
biblioteca dinâmica Novos recursos

Algumas das alterações da biblioteca dinâmica podem não ter implicações na funcionalidade dos binários de cliente já existentes. Tais mudanças são corretamente
consideradas as mudanças da versão secundária .

Fase 3: Mudanças de versão principais

Ocasionalmente, as alterações do código da biblioteca dinâmica acontecem para trazer diferenças que são muito radicais e significam uma ruptura completa com o
que as versões anteriores da biblioteca forneciam. Os novos binários do cliente ("C") criados no momento dessas novas mudanças normalmente não têm
problemas em se relacionar com o novo paradigma.

Os binários do cliente mais velhos ("A" e "B"), no entanto, podem acabar na situação ilustrada pela Figura 10-10, que é semelhante a um casal de idosos em uma
recepção de casamento rock'n'roll esperando eternamente pela banda para tocar sua música favorita de Glenn Miller.

Cliente binário

Biblioteca dinâmica versão 1.0.0

Biblioteca dinâmica versão 1.0.0

Mudanças substanciais de funcionalidade

Biblioteca dinâmica versão 2.0.0

Figura 10-10. Os melhores e mais recentes linlcs binários "C" do cliente na versão mais recente da biblioteca dinâmica (2.0.0), que é incompatível para uso pelos binários clientes
mais antigos "A" e "B"

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 135/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
A tarefa dos desenvolvedores de software é tornar a transição das atualizações de funcionalidade o mais suave possível. Romper a compatibilidade com a
infraestrutura existente raramente é uma atitude sábia. Quanto mais a biblioteca é popular entre os desenvolvedores, menos recomendado é romper com a
funcionalidade esperada da biblioteca. A verdadeira solução para o problema é que a nova biblioteca dinâmica continua fornecendo tanto versões de
funcionalidade mais antigas quanto mais novas, pelo menos por algum tempo. Essa ideia é ilustrada na Figura 10-11.

Figura 10-11. O controle de versão do símbolo resolve problemas de incompatibilidade

Cliente binário

Biblioteca dinâmica versão 1.0.0

Biblioteca dinâmica Biblioteca dinâmica

versão 1.0.0 'versão 2.0.0

O controle de versão do símbolo permite que funcionalidades novas e antigas coexistam

Os ingredientes básicos de implementação

O esquema de controle de versão de símbolo é implementado combinando o script de versão do vinculador com a diretiva assembler .symver , ambas as quais
serão elaboradas em detalhes a seguir.

Linker Version Script

A implementação mais básica do mecanismo de controle de visibilidade do símbolo é baseada na leitura do vinculador GNU nas informações da versão do
símbolo fornecidas na forma do arquivo de texto do script de versão .

Vamos começar uma demonstração simples com o exemplo de uma biblioteca dinâmica simples (libsimple.so) que apresenta as três funções mostradas na
Listagem 10-1.

Listagem 10-1. simple.c

int first_function (int x) {

retorno (x + 1).

int second_function (int x) {

retorno (x + 2).

int third_function (int x) {

retorno (x + 3).

Digamos agora que você deseja que as duas primeiras funções de biblioteca (mas não a terceira!) Carreguem as informações de controle de versão. A maneira de
especificar a versão do símbolo é criar um arquivo de script de versão bastante simples, que pode ser parecido com o código da Listagem 10-2.

Listagem 10-2. simpleVersionScript

LIBSIMPLE_1.0 {global:

first_function; second_function; local:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 136/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
t

Finalmente, vamos construir a biblioteca dinâmica. O nome do arquivo do script de versão pode ser convenientemente passado para o vinculador usando o
sinalizador de vinculador dedicado, assim:

$ gcc -fPIC -c simple.c

$ gcc -shared simple.o -Wl, - version-script, simpleVersionScript -o libsimple.so.1.0.0

O vinculador extrai as informações do arquivo de script e as incorpora à seção de formato ELF dedicada ao controle de versão. Mais informações sobre como as
informações de versão de símbolo são incorporadas aos arquivos binários ELF serão disponibilizadas em breve.

Diretiva Assembler .symver

Ao contrário do arquivo de script de versão que representa o "pão com manteiga" do conceito de controle de versão de símbolo, usado em todas as fases e todos os
cenários, o paradigma de controle de versão de símbolo depende de outro ingrediente - a diretiva assembler .symver - para resolver os casos difíceis.

Vamos supor um cenário de mudanças de versão principais em que uma assinatura de função não mudou entre as versões, mas a funcionalidade subjacente
mudou um pouco. Além disso, há uma função que originalmente costumava retornar uma série de elementos vinculados, mas na versão mais recente foi
reprojetada para retornar o número total de bytes ocupados pela lista vinculada (ou vice-versa). Veja a Listagem 10-3.

Listagem 10-3. Exemplo de implementações substancialmente diferentes da mesma função que se qualificam para diferentes versões principais

// VERSÃO 1.0:

unsigned long list_occupancy (struct List * pStart) {

// aqui, examinamos a lista e retornamos o número de elementos return nElements;

// VERSÃO 2.0:

unsigned long list_occupancy (struct List * pStart) {

// aqui nós escaneamos a lista, mas agora retornamos o número total de bytes return nElements * sizeof (struct List);

Obviamente, os clientes da primeira versão da biblioteca terão problemas, pois o valor retornado pela função não corresponderá mais ao esperado.

Conforme declarado anteriormente, o credo dessa técnica de controle de versão é fornecer as diferentes versões do mesmo símbolo no mesmo arquivo binário.
Muito bem dito, mas como fazer? Uma tentativa de construir as duas versões de função resultará no linker relatando os símbolos duplicados. Felizmente, o
compilador GCC suporta a diretiva montadora .symver customizada , que ajuda a aliviar o problema (consulte a Listagem 10-4).

Listagem 10-4. O mesmo par de versões diferentes da função apresentada na Listagem 10-3, desta vez com controle de versão de símbolo aplicado corretamente

_asm _ (". symver list_occupancy_1_0, list_occupancy@MYLIBVERSION_1.0");

ocupação de lista longa sem sinal 1 0 (struct List * pStart) {

// aqui, examinamos a lista e retornamos o número de elementos return nElements;

// versão do símbolo padrão indicada pelo adicional

// EU

// v _asm __ (". symver list_occupancy_2_0, list_occupancy @@ MYLIBVERSION_2.0");

ocupação de lista longa sem sinal 2 0 (struct List * pStart) {

// aqui nós escaneamos a lista, mas agora retornamos o número total de bytes return nElements * sizeof (struct List);

Como funciona esse esquema?

Para eliminar o vinculador que enfrenta o problema de símbolos duplicados, você pode criar nomes diferentes para versões diferentes da mesma função, que serão
usados ​apenas para fins internos (ou seja, não serão exportados). Essas duas funções são list_occupancy_1_0 e list_occupancy_2_0.
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 137/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Da perspectiva do mundo externo, no entanto, o vinculador criará o símbolo apresentando o nome da função esperada (ou seja, list_occupancy ()), embora
decorado com as informações de versão do símbolo apropriadas, aparecendo nas duas versões diferentes: list_occupancy@MYLIBVERSION_1.0 e
list_occupancy @ MYLIBVERSION_2.0.

Como consequência, tanto o binário do cliente mais antigo quanto o mais novo serão capazes de identificar o símbolo que esperam. O binário do cliente mais
antigo ficará feliz em ver que o símbolo list_occupancy@MYLIBVERSION_1.0 existe. Suas chamadas para este símbolo de função intermediária serão roteadas
internamente para o lugar certo - para a função de biblioteca dinâmica list_occupancy_1_0 () , que é o símbolo real .

Finalmente, os novos binários do cliente, que não se preocupam particularmente com o histórico de versões anteriores, escolherão o símbolo padrão, indicado pelo
caractere @ extra no nome (neste caso, list_occupancy @@ MYLIBVERSION_2.0).

Amostra de análise do projeto: Fase 1 (versão inicial)


Agora que você entende como os ingredientes básicos da implementação (script de versão e / ou diretiva assembler .symver ) funcionam, é hora de dar uma
olhada em um exemplo real. Para ilustrar os pontos importantes, vamos voltar ao exemplo original usado para ilustrar o script de versão do vinculador (ou seja, a
biblioteca libsimple.so apresentando três funções, as duas primeiras das quais estarão sujeitas a controle de versão de símbolo). Para tornar a demonstração
mais convincente, alguns printf's serão adicionados ao código original; consulte a Listagem 10-5 até a Listagem 10-8.

Listagem 10-5. simple.h #pragma uma vez

primeira_função int (int x); segunda_função int (int x); terceira_função int (int x);

Listagem 10-6. simple.c

#include <stdio.h> #include "simple.h"

int first_function (int x) {

printf ("lib:% s \ n", _FUNCTION_);

retorno (x + 1);

int second_function (int x) {

printf ("lib:% s \ n", _FUNCTION_);

retorno (x + 2);

int third_function (int x) {

printf ("lib:% s \ n", _FUNCTION_);

retorno (x + 3);

Listagem 10-7. simpleVersionScript

LIBSIMPLE_1.0 {global:

first_function; second_function; local:

Listagem 10-8. build.sh

gcc -Wall -g -00 -fPIC -c simple.c

gcc -shared simple.o -Wl, - version-script, simpleVersionScript -o libsimple.so.1.0.0

Agora que a biblioteca foi construída, vamos examinar mais de perto como o formato ELF oferece suporte ao conceito de controle de versão de símbolo.

Suporte de formato ELF

A análise da seção do arquivo de biblioteca indica que existem três seções com nomes bastante semelhantes que são usadas para transportar as informações da
versão, conforme mostrado na Figura 10-12.

rnilan @ nilan $ readelf -S libsimple.so

Existem cabeçalhos de seção 3S, começando no deslocamento Oxl54c:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 138/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Cabeçalhos da seção: [Nr] Nome

Digite NULL l NOTE GNU_HASH DYNSYM STRTAB

[O] t 1] [2] [3]

L SJ .gnu.version VERSYM

[6], gnu.version_d VERDEF [7] .gnu.version_r VERNEED

.note.gnu.build .gnu.hash .dynsyni

[4] .dynstr

Addr Desligado Tamanho ES FIG Lk Inf Al


00000000 000000 oooooo 00 0 0 0

00000114 000114 000024 00 UMA 0 0 4


00000138 000138 00002c 04 UMA 3 0 4
00000164 000164 000080 10 UMA 4 1 4
OOOO01e4 OO01e4 000098 00 UMA 0 0 1
0000027c 00027c 000010 02 JT T 0 "T
0000028c 00028c 000038 00 UMA 4 2 4
O0O0O2C4 0002C4 000030 00 UMA 4 1 4

Figura 10-12. Suporte ao formato ELF para informações de versão

Invocar o utilitário readelf com o argumento de linha de comando -V fornece um relatório sobre o conteúdo dessas seções de uma maneira particularmente
interessante, conforme mostrado na Figura 10-13.

1 dos
A seção símbolos de versão .gnu.version 'contém 8 entradas: Addr: O00OOO000Q00QZ7C Offset: 0x00027c Link: 3 (.dynsym)

004: 0 (* local *) 2 (LIBSIMPLE_1.0) 2 (LIBSIMPLE_1.0) 2 (LI8SIMPLE_1.0)

A seção de definição de versão '.gnu, verslon_d' contém 2 entradas: Addr: 0x000000000000028c Offset: 0x00028c Link: 4 (.dynstr) 000900: Rev: 1 Sinalizadores: Índice BASE: 1 Cnt: 1 Nome: libsimple.so.1.0.0
0x081c : Rev: 1 Sinalizadores: nenhum Índice: 2 Cnt: 1 Nane: LIBSIMPLE_1.0

A seção de necessidades de versão '.gnu.version_r' contém 1 entradas: Addr: 0X0O0OO00O00O002C4 Offset: 0xOO02c4 Link: 4 (.dynstr)

Figura 10-13. Usando readelf para listar o conteúdo das seções relacionadas à versão

Torna-se aparente que o

• A seção .gnu.version_d descreve as informações de versão definidas nesta biblioteca particular (daí o apêndice "_d" no nome da seção).

• A seção .gnu.version_r descreve as informações de versão das outras bibliotecas, que são referenciadas por esta biblioteca (daí o apêndice "_r" no nome da
seção).

• A seção .gnu_version fornece uma lista resumida de todas as informações de versão relacionadas à biblioteca.

É interessante, neste ponto, verificar se as informações de versão foram associadas aos símbolos especificados no script de versão.

De todas as maneiras disponíveis (nm, objdump, readelf) para examinar os símbolos do arquivo binário, é novamente o utilitário readelf que fornece a
resposta na forma mais agradável em que a associação de símbolos com as informações de versão especificada torna-se aparente, conforme ilustrado em Figura 10-
14.

nilan @ milan $ readelf --symbols libsinple.so) função grep

6: 00000488 44 FUNC GLOBAL PREDEFINIÇÃO 12 second_functio @@ LIBSIMPLE_1.0


7: 0000045c 44 FUNC GLOBAL PREDEFINIÇÃO 12 flrst_functi (<a@LIBSIMPLE_1.0
52: 00000404 44 FUNC LOCAL PREDEFINIÇÃO 12 terceira_função
64: 0000045c 44 FUNC GLOBAL PREDEFINIÇÃO 12 first_function
66: 00000488 44 FUNC GLOBAL PREDEFINIÇÃO 12 segunda função
nllan @ milan $ readelf - -dyn- syns Ubsinple.so | função grep
6: 00000488 44 FUNC GLOBAL PREDEFINIÇÃO 12 second_f unctio @ (i) LIBSIMPLE_l .0
7: 0000045c 44 FUNC GLOBAL PREDEFINIÇÃO 12 first_functic @@ LIBSIMPLE_1,0

nilan @ nilan $

Figura 10-14. Usando readelf para imprimir informações de versão de símbolo

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 139/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Claramente, as informações de controle de versão especificadas no script de versão e passadas ao vinculador encontraram seu caminho para o arquivo binário e
certamente se tornaram o atributo dos símbolos destinados ao controle de versão.

Como uma observação interessante, a desmontagem do arquivo binário, no entanto, mostra que não existe first_function @@ LIBVERSIONDEMO_1.0. Tudo
que você pode encontrar é o símbolo da primeira função real . A desmontagem em tempo de execução (executando gdb) mostra a mesma coisa.

Obviamente, o símbolo exportado decorado com a informação de versão do símbolo é um tipo de ficção (útil, mas ainda uma ficção), enquanto a única coisa que
conta no final é o símbolo da função real existente.

Propagação de informações de símbolo de versão para binários de cliente

Outra rodada de descobertas interessantes acontece quando você examina os binários do cliente vinculados à sua biblioteca dinâmica com versão de símbolo. Para
explorar o controle de versão do símbolo nessa direção específica, vamos criar um aplicativo de demonstração simples que faz referência aos símbolos com versão;
consulte a Listagem 10-9.

Listagem 10-9. main.c

#include <stdio.h> #include "simple.h"

int main (int argc, char * argv []) {

int nPrimeiro = primeira_função (l); int nSegundo = segunda_função (2); int nRetValue = nPrimeiro + nSegundo; printf ("primeiro
(l) + segundo (2) =% d \ n", nRetValue); return nRetValue;

Agora vamos construí-lo.

$ gcc -g -00 -c -I ../ sharedLib main.c $ gcc main.o -Wl, -L ../ sharedLib -lsimple \

-Wl, -R ../ sharedLib -o firstDemoApp

Observe que, para exercer apenas o mecanismo de controle de versão de símbolos, a especificação da biblioteca soname foi propositalmente omitida.

Não é uma grande surpresa que o aplicativo demo, sendo um arquivo binário ELF, também carregue as seções relacionadas à versão (conforme mostrado pela
seção de inspeção ilustrada na Figura 10-15).

milan @ milanS readelf -S ./firstDemoApp

Existem 36 cabeçalhos de seção, começando no deslocamento 0x1454

Cabeçalhos de seção

[Nr] Nane Modelo Addr Desligado Tamanho ES FIG Lk Inf Al


[0] NULO 00000000 000000 000000 00 0 0 0

[H .interp PROGBITS 08048154 000154 000013 00 UMA 0 0 1


[2] .Nota. ab i -tag NOTA 08048168 000168 000020 00 UMA 0 0 4
[3] .note.gnu.build-i NOTA 08048188 000188 000024 00 UMA 0 0 4
[4] .gnu.hash gnu_hash 080481ac OOOlac 000020 04 UMA 5 0 4
[S] .dynsyn DYNSYM Q80481CC 0O01CC 000080 10 UMA 6 1 4
[6] .dynstr STRTAB 0904824c 00024c 0O00a7 00 UMA 0 0 1
[7] .gnu.version VERSYM 080482f4 0002f4 000010 02 UMA 5 0 2
[8] .gnu.versionr VERNEED 08048304 000304 000040 00 UMA 6 2 4

Figura 10-15. O aplicativo de demonstração também apresenta seções relacionadas a versões

É muito mais importante que as informações da versão do símbolo da biblioteca dinâmica de demonstração tenham sido ingeridas pelo binário do cliente por
meio do processo de vinculação, conforme mostrado na Figura 10-16.

a seção de símbolos de versão '.gnu.version' contém 8 entradas: Addr: O000O00008O482f4 Offset: 0xOOO2f4 Link: 5 (.dynsyn) 000: 0 (»local4 ) 2 (GLIBC_2.0) 3 (LIBSIMPLE_1.0) 3 (LIBSIMPLE_1.0)

1
A versão precisa da seção .gnu.version_r 'contém 2 entradas: Addr: 0x0000000008048394 Offset: OxO0O3O4 Link: 6 (.dynstr)

Figura 10-16. O binário do cliente "ingere" as informações de versão de símbolo da biblioteca vinculada

Exatamente como acontece no cenário de versão com base em soname descrito anteriormente, o mecanismo de versão de símbolo também é passado da biblioteca
dinâmica para seu binário cliente. Desta forma, foi estabelecida uma forma de contrato entre o binário cliente e o controle de versão da biblioteca dinâmica.

Por que isso é importante? A partir do momento em que a vinculação do binário do cliente com a biblioteca dinâmica aconteceu, o código da biblioteca dinâmica
pode passar por uma infinidade de mudanças e, conseqüentemente, por uma infinidade de versões secundárias e principais.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 140/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Independentemente das alterações da biblioteca dinâmica, seu binário cliente continuará carregando as informações de versão que estavam presentes no momento
da vinculação. Se exatamente essa versão (e, claro, exatamente a funcionalidade associada a essa versão em particular) estiver faltando, a compatibilidade com
versões anteriores quebrada será fortemente indicada.

Antes de avançar, vamos nos certificar de que seu esquema de controle de versão não impeça a execução do aplicativo. O experimento simples é mostrado na
Figura 10-17.

Figura 10-17. Esquema de controle de versão funcionando corretamente

Amostra de análise de projeto: Fase 2 (pequenas alterações de versão)

Depois de entender os fundamentos de como o esquema de controle de versão de símbolo opera, é hora de simular o cenário no qual o desenvolvimento da
biblioteca dinâmica resulta com as mudanças não disruptivas (ou seja, a versão secundária). Na tentativa de simular os cenários da vida real, serão realizadas as
seguintes etapas:

• Você modificará a biblioteca dinâmica adicionando mais algumas funções. Apenas uma das funções adicionadas recentemente será exportada. O script de
controle de versão será enriquecido pelo item extra anunciando a atualização da versão secundária LIBSIMPLE_1.1.

• O novo binário do cliente (outro aplicativo de demonstração simples) será criado e vinculado à biblioteca dinâmica atualizada. Dessa forma, ele servirá como um
exemplo de um novo binário cliente, criado na época da última e maior biblioteca dinâmica versão 1.1, desconhecendo qualquer uma das versões anteriores da
biblioteca.

• Para simplificar a demonstração, seu código não será significativamente diferente do aplicativo de demonstração simples original. A diferença mais notável é
que ele chamará a nova função ABI, que não existia antes da última versão 1.1.

As Listagens 10-10 e 10-11 mostram como o arquivo de origem da biblioteca dinâmica modificada se parece agora.

Listagem 10-10. simple.h #pragma uma vez

primeira_função int (int x); segunda_função int (int x); terceira_função int (int x);

int quarta_função (int x); quinta_função int (int x);

Listagem 10-11. simple.c

#include <stdio.h> #include "simple.h"

int first_function (int x)

printf ("lib:% s \ n", _FUNCTION_);

retorno (x + 1);

int second_function (int x)

printf ("lib:% s \ n", _FUNCTION_);

retorno (x + 2);

terceira_função int (int x)

printf ("lib:% s \ n", _FUNCTION_);

retorno (x + 3);

int four_function (int x) // exportado na versão 1.1

printf ("lib:% s \ n", _FUNCTION_);

retorno (x + 4);

int quinta_função (int x) {

printf ("lib:% s \ n", __FUNCTI0N__); retorno (x + 5);

A Listagem 10-12 mostra como o script de versão ficará após as mudanças.

Listagem 10-12. simpleVersionScript

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 141/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
LIBSIMPLE_1.0 {global:

first_function; second_function; local:

LIBSIMPLE_1.1 {

global:

quarta função ;

local:

O novo arquivo de origem do aplicativo demo se parecerá com a Listagem 10-13.

Listagem 10-13. main.c

#include <stdio.h> #include "simple.h"

int main (int argc, char * argv []) {

int nPrimeiro = primeira_função (l);

int nSegundo = segunda_função (2);

int nQuarta = quarta função_ (4);

int nRetValue = nPrimeiro + nSegundo + nQuarto;

printf ("primeiro (l) + segundo (2) + quarto (4) =% d \ n", nRetValue);

retornar nRetValue.

Agora vamos construí-lo.

$ gcc -g -00 -c -I ../ sharedLib main.c $ gcc main.o -Wl, -L ../ sharedLib -lsimple \ -Wl, -R ../ sharedLib -o newerApp

Vamos agora dar uma olhada mais de perto nos efeitos dessa pequena aventura de versionamento, que imita perfeitamente o cenário da vida real que acontece
quando versões menores de bibliotecas dinâmicas são atualizadas.

Primeiro, conforme mostrado na Figura 10-18, as informações de versão agora apresentam não apenas a versão original (1.0), mas também a versão mais recente
(1.1)

milan @ mlan $ readelf -V libsimple. tão

A seção de símbolos de versão ', gnu.version' contém entradas IS: Addr: O0O0000000O0O2c2 Offset: OxO002c2 Link: 3 (.dynsyn) 000: 0 (noeal *) 4 (GLIBC_2.1.3) 5 (GLIBC_2.0) 0 (* local * )

004: 0 (* local *) 3 (LIBSIMPLE 1.1} 2 (LIBSIMPLE_1.0) 2 (LIBSIMPLE_1.0)

008: 2 (LIBSIMPLEl.0) 3 (LIBSIMPLE 1.1)

A seção de definição de versão '.gnu.version_d' contém 3 entradas: Addr: 0xOO0O00OO0OO002d8 Offset: OxO0O2d8 Link: 4 (.dynstr) 090000: Rev: 1 Sinalizadores: Índice BASE: 1 Cnt: 1 Nane:
libsinple.so.1.0.0 0x001c : Rev: 1 Sinalizadores: nenhum Índice: 2 Cnt: 1 Nane: LIBSIMPLE_1.0 8x6838: Rev: 1 Sinalizadores: nenhum Índice: 3 Cnt: 1 Nome: LIBSIMPLE 1.1

A versão precisa da seção '.gnu.version_r' contém 1 entradas: Addr: 0x000000000000032c Offset: 0x00032c Link: 4 (.dynstr) 00O0OO: Versão: 1 Arquivo: libc.so.6 Cnt: 2 0x0010: Nome: GLI8t_2.0
Sinalizadores: nenhum Versão: S 0x0020: Nane: GLIBC 2.1.3 Sinalizadores: nenhum Versão: 4 nilan @ nilan $

Figura 10-18. Informações completas de versão ingeridas pelo binário do cliente

O conjunto de símbolos exportados agora é composto pelos símbolos da versão 1.0 e da versão 1.1, conforme mostrado na Figura 10-19.

ntlan (Einilan $ readelf -dyn-sym libsimple.so

Valor Tamanho 00000000 O 00600000 00000000 00000000 00000000 00000550 00000000 0000O4f8 000OO4CC O0ROOO0O

contém 10 ent
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 142/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Tipo Bind

NOTYPE LOCAL

FUNC FRACO

FUNC GLOBAL

NOTYPE WEAK

NOTYPE WEAK

FUNC GLOBAL

0B3ECT GLOBAL

FUNC GLOBAL

FUNC GLOBAL

OBJETO GLOBAL

Tabela Synbol '.dynsyn'

ries: Vis

DEFAULT DEFAULT DEFAULT DEFAULT DEFAULT DEFAULT DEFAULT DEFAULT DEFAULT DEFAULT

Freira

0 12

8 9

Ndx Nane UND

UND _cxa_fi.nalize@GLIBC_2.1.3 (4)

Pontos UND (3GLIBC_2. 0 (S)

UND _gnon_start__

UND _3v_RegisterClasses

12 quarta função (a @ LIBSIMPLE 1.1 ABS LIBSIMPLEl.0 12 segundos_função @@ LIBSIMPLE_l.0 12 primeira_functton @@ LIBSIMPLE_l, 0 ABS LIBSIMPLE 1.1

nilan @ nilan $

Figura 10-19. Símbolos de diferentes versões presentes na biblioteca compartilhada

Vamos ver agora como as coisas ficam com o binário de cliente mais novo e moderno (newerApp) criado pela primeira vez após o lançamento da versão 1.1.
Conforme ilustrado na Figura 10-20, o vinculador leu as informações sobre todas as versões suportadas pela biblioteca dinâmica e as inseriu no binário cliente do
aplicativo mais recente.

ni.1an@mi.lan $ readelf -V, / newerApp

A seção de synbols de versão '.gnu.version' contém 9 entradas: Addr: 0000000008048322 Offset: 0x000322 Link: S (.dynsyn) 000: 0 (* local *) 2 (GLIBC2.0) 3 (LIBSIMPLEl.0} 4 (LIBSIMPLE 1.1 )

004: 3 (LIBSIMPLE_1 .0) 0 (* local *) 2 (GLrBC _2 .0) 0 (* local *)

008: 1 (* global *)

A seção de necessidades de versão '.gnu.verslonr' contém 2 entradas: Addr; 0x0000000003048334 Deslocamento: 0x000334 Link: 6 (.dynstr) OOOOOO: Versão: 1 Arquivo: libs triplo .so Cnt: 2 0x9010: Nome:
libsimple ll Sinalizadores: nenhuma versão: 4 0x0020: Nome: " libsimple _1.0 Sinalizadores: nenhum Versão : 3 0x0030: Versão: 1 Arquivo: ltbc.so.6 Cnt: 1 0x0040: Nome: glibc _2.0 Sinalizadores:
nenhum Versão: 2 ntlan @ mtlan $

Figura 10-20. O binário do cliente mais recente ingeriu informações completas de controle de versão (versões de símbolo antigas e mais recentes)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 143/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
A lista de símbolos da biblioteca dinâmica em cuja presença o binário do cliente conta em tempo de execução contém os símbolos de ambas as versões. A Figura
10-21 ilustra esse ponto.

mllan @ nll3n $ readelf --dyn-syns, / newerApp

A tabela de símbolos '.dynsyn' contém 9 entradas:

Valor Tamanho Tipo Vincular Vis

00000000 0 NOTYPE LOCAL DEFAULT

00000000 0 FUNC GLOBAL DEFAULT

00000000 0 FUNC GLOBAL DEFAULT

Nome Ndx JND

UND prtntf @ GLIBC_2,0 (2)

FUNÇÃO SEGUNDA UND FLIBSIMPLE 1.8 (3)

0 função GLOBAL DEFAULT UND four_functlongLIBSIMPLE_l.I (4)

0 FUNC. GLOBAL DtFAULI UND Ttrst_Tunct \ on @ LiBMMPLt_l. 0 (0,3}

O NOTYPE WEAK DEFAULT UND _gnon_start_

0 FUNC GLOBAL DEFAULT UND __UbC_Start_malngGLIBC_2 .0 (2)

0 NOTYPE WEAK DEFAULT UND _Jv_RegtsterClasses

4 OBJETO GLOBAL DEFAULT IS 10 Stdtn usado

00000000

00 (300000 00000000 00000000 00000000 0804864c

mi.lan@ntlan$

Figura 10-21. Símbolos de todas as versões ingeridas da biblioteca compartilhada

Agora, para verificar se a adição da nova funcionalidade e as informações de versão modificadas funcionam conforme o esperado, você pode tentar executar o
aplicativo antigo e o novo. Conforme mostrado na Figura 10-22, a execução do aplicativo antigo provará que a nova versão secundária da biblioteca dinâmica não
trouxe nenhuma surpresa desagradável.

ntlan @ nulan $ ./newerApp lib: ftrst_function lib: second_function lib: quarta_função flrst (l) + second (2) + quarta (4) = 14 ntl3n @ nllan $, / flrstDemoApp lib: flrst_function lib: second_function ftrst (l) + segundo
(2) = 6 ntlan (0ntlan $

Figura 10-22. Os aplicativos mais antigos e mais recentes vinculam a mesma biblioteca, mas usam os símbolos de versões diferentes

Amostra de análise do projeto: Fase 3 (mudanças na versão principal)


Nos exemplos analisados ​anteriormente, cobri casos em que as alterações do novo código geralmente não afetavam a forma como os clientes usavam a base de
código existente. Essas alterações de código são reconhecidas com justiça como incrementos de versão menores.

Não tentarei cobrir situações muito mais dramáticas, nas quais as mudanças de código prejudicam seriamente a maneira como os clientes usavam o código, caindo
claramente na categoria de incrementos de versão principais.

O caso de alteração do comportamento da função ABI

Potencialmente, as alterações de código mais desagradáveis ​acontecem quando aparentemente nada acontece com os símbolos da biblioteca dinâmica (ou seja, as
funções não têm seus protótipos alterados e / ou as estruturas não têm seu layout alterado), mas o significado subjacente do que as funções fazem com os dados - e
o mais importante, os valores que eles retornam - muda.

Imagine por um momento que você tem uma função que costumava retornar o valor do tempo em milissegundos. Um belo dia, os desenvolvedores perceberam
que o milissegundo como medida não era preciso o suficiente e decidiram retornar o valor em nanossegundos (que é 1.000 vezes maior).

Esse cenário é o que usaremos como tópico do próximo exemplo; Mostrarei como problemas dessa natureza podem ser resolvidos pelo uso inteligente do
mecanismo de controle de versão de símbolo. (Concordo que o exemplo é um pouco infantil / fantástico / ingênuo. Na verdade, há cerca de um milhão de
maneiras de evitar o caos resultante dessa mudança. Por exemplo, você poderia introduzir uma nova função ABI com a palavra "nanossegundos" no nome, que
retornaria o tempo em nanossegundos. Mesmo assim, um exemplo como este é bom o suficiente para fins de demonstração.)

De volta ao tópico, vamos supor que o cabeçalho de exportação da biblioteca dinâmica de demonstração não mudou nada, portanto, os protótipos de função
permanecem inalterados. No entanto, os requisitos de design mais recentes ditam que first_function () a partir de agora precisa retornar um valor diferente
do que costumava retornar.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 144/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
int first_function (int x) {

printf ("lib:% s \ n", _FUNCTION_); retornar 1000 * (x + l);

Desnecessário dizer que esse tipo de mudança está fadado a causar estragos nos binários do cliente existentes. Sua infraestrutura de código existente simplesmente
não espera um valor dessa ordem de magnitude. É possível que sair dos limites das matrizes cause uma exceção. Em cenários de plotagem de gráfico, o valor
estaria fora dos limites, etc.

Portanto, agora você precisa de uma maneira de garantir que os clientes antigos recebam o tratamento normal (ou seja, as chamadas dos binários do cliente
existente para first_function () retornam o valor que costumava ser), enquanto os novos clientes obtêm o benefício de um novo design.

O único problema é que você precisa resolver o conflito; o mesmo nome de função deve ser usado em dois cenários substancialmente diferentes. Felizmente, o
mecanismo de controle de versão de símbolo prova que é capaz de lidar com problemas desse tipo.

Como uma primeira etapa, você modificará o script da versão para indicar o suporte para a nova versão principal; consulte a Listagem 10-14.

Listagem 10-14. simpleVersionScript

LIBSIMPLE_1.0 {global:

first_function; second_function; local:

};

LIBSIMPLE_1.1 {global:

quarta_função;

local: *.

LIBSIMPLE_2.0 {

global:

first_function;

local: *.

Em seguida, você aplicará a receita com base no uso da diretiva assembler .symver, conforme mostrado na Listagem 10-15. Listagem 10-15. simple.c (apenas as
alterações mostradas aqui)

_asm__ (".symver first_function_1_0, first_function @ LIBSIMPLE_1.0");

int first_function 1 0 (int x)

printf ("lib:% s \ n", __FUNCTI0N__); retorno (x + 1);

_asm _ (". symver first_function_2_0, first_function @@ LIBSIMPLE_2.0");

int first_function 2 0 (int x)

printf ("lib:% s \ n", __FUNCTI0N__); retornar 1000 * (x + l);

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 145/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Conforme mostrado na Figura 10-23, a biblioteca dinâmica agora apresenta uma parte extra de informações de controle de versão.

milan @ nllan $ readelf -V libsinple.so

1
A seção de símbolos de versão '.gnu.version contém 12 entradas: Addr: aooooBOOOBoaozfe offset: ox6002fe Link: 3 (.dynsyn) 006: 6 (* local *) 5 (GLIBC_2.0) 6 (GLIBC_2.1.3) 0 (* local *)

004: 0 <* local *) 3 (LIBSIMPLEl. 1) 2 (LIB5IMPLE_1.0) 4 (LIBSIMPLE_2.0)

008: 2 (LIBSIMPLEl.0) 4 (LIBSIMPLE 2.0) 3 (LIBSIMPLE_1.1) 2h (LIBSIMPLE_1,0)

A seção de definição de versão ', gnu, version_d' contém 4 entradas: Addr: 0X0O00O00000O0O318 Offset: 0x000318 Link: 4 (.dynstr) O0O000: Rev: 1 Sinalizadores: Índice BASE: 1 Cnt: 1 Nome:
llbslmple.so.1.0.0 0x001c : Rev: 1 Sinalizadores: nenhum Índice: 2 Cnt: 1 Nane: LIBSIMPLE_1.0 0x6038: Rev: 1 Sinalizadores: nenhum Índice: 3 Cnt: 1 Nane: LIB5IMPLE_1.1 6x6054: Rev: 1 Sinalizadores:
nenhum Índice: 4 Cnt: 1 Nane: LIB5IMPLE_2.6

A versão precisa da seção '.gnu.version ^ r' contém 1 entradas: Addr: 0x0000000000000388 Offset: 0x000388 Link: 4 (.dynstr) 000000: Versão: 1 Arquivo: llbc.so.6 Cnt: 2 0x0010: Nane: GLIBC_2.1.3
Sinalizadores: nenhum Versão: 6 0x6020: Nane: GIIBC_2.0 Sinalizadores: nenhum Versão: 5 i <itlan @ mllanS

Figura 10-23. A última e melhor versão da biblioteca contém todas as versões de símbolos

Curiosamente, conforme mostrado na Figura 10-24, parece que a diretiva .symver realmente fez sua mágica.

milan @ milan $ readelf --dyn-syms llbslnple.so A tabela Synbol '.dynsyn' contém 12 entradas:

1,1

Freira: Valor Tamanho Modelo Ligar Vis Ndx


0: 00000006 0 NOTYPE LOCAL PREDEFINIÇÃO UND
1: 00000000 0 FUNC GLOBAL PREDEFINIÇÃO UND
2: 00000000 0 FUNC FRACO PREDEFINIÇÃO UND
3: 00000000 0 NOTYPE FRACO PREDEFINIÇÃO UND
4: 00000000 0 NOTYPE FRACO PREDEFINIÇÃO UND
5: 0O0O05f3 54 FUNC GLOBAL PREDEFINIÇÃO 12
6: 00000000 0 OBJETO GLOBAL PREDEFINIÇÃO abdômen
7: 00000000 0 OBJETO GLOBAL PREDEFINIÇÃO abdômen
8: 0000058e 54 FUNC GLOBAL PREDEFINIÇÃO 12
9: 00000552 60 FUNC GLOBAL PREDEFINIÇÃO 12
10: 00000000 0 OBJETO GLOBAL PREDEFINIÇÃO abdômen
11: 0000051c 54 FUNC GLOBAL PREDEFINIÇÃO 12

nilangnilanS nn llbslmple.so | função grep

00000630 t flfth_function

00000552 T flrst_functiong@LIBSIMPLE_2.0

0000051c T first_functlon0LIBSIMPLE_l.0

0000051c t flrst_function_l_0

00000552 t flrst_functton_2_0

OOOOOSfa T quarta_função

0000058e T second_function

OOOOOSC4 t terceiro função

nllan @ rnilanS

Figura 10-24. Ambas as versões da primeira função Q existem

O efeito final de todo o esquema .symver é a magia de exportar duas versões do símbolo first_function () , apesar do fato de que uma função com tal nome
não existe mais porque foi substituída por first_function_1_0 () e first_function_2_0 ().

Para mostrar claramente as diferenças de implementação, você criará o novo aplicativo cuja fonte não difere da versão anterior (consulte a Listagem 10-16).

Listagem 10-16. main.c

#include <stdio.h> #include "simple.h"

int main (int argc, char * argv []) {

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 146/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
int nPrimeiro = primeira_função (1); // ver um valor de retorno 1000 vezes maior será divertido!

int nSegundo = segunda_função (2);

int nQuarta = quarta função_ (4);

int nRetValue = nPrimeiro + nSegundo + nQuarto;

printf ("primeiro (1) + segundo (2) + quarto (4) =% d \ n", nRetValue);

retornar nRetValue.

O novo nome do aplicativo será escolhido de acordo:

$ gcc -g -00 -c -I ../ sharedLib main.c $ gcc main.o -Wl, -L ../ sharedLib -lsimple \

-Wl, -R ../ sharedLib -o ver2PeerApp

A comparação do tempo de execução mostrará claramente que os clientes antigos não terão sua funcionalidade afetada pelas principais alterações de versão. O
app contemporâneo, porém, contará com a nova funcionalidade trazida pela versão 2.0. A Figura 10-25 resume o ponto.

Figura 10-25. Três aplicativos (cada um dos quais depende de diferentes versões de símbolos da mesma biblioteca dinâmica) são executados conforme pretendido

O caso da alteração do protótipo da função ABI

O caso descrito anteriormente é um pouco bizarro. Devido às inúmeras maneiras de evitar isso, as chances de isso acontecer na vida real são bastante baixas. Do
ponto de vista da educação, entretanto, é precioso, pois o procedimento para corrigir esse problema é o mais simples possível.

Um caso muito mais comum que se enquadra nas principais alterações do código de versão é quando a assinatura de uma função precisa ser alterada. Por
exemplo, vamos supor que para os novos cenários de caso de uso, a first_function () precisa aceitar um argumento de entrada adicional.

primeira_função int (int x, int normfactor);

Obviamente, agora você precisa oferecer suporte às funções com o mesmo nome, mas com assinaturas diferentes. Para demonstrar esse problema, vamos criar
outra versão, mostrada na Listagem 10-17.

Listagem 10-17. simpleVersionScript

LIBSIMPLE_1.0 {global:

first_function; second_function; local:

LIBSIMPLE_1.1 {

global:

quarta_função;

local: *.

LIBSIMPLE_2.0 {

global:

first_function;

local: *.

LIBSIMPLE_3.0 {

global:

first_function;

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 147/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
local: *.

Em geral, a solução para este problema não difere substancialmente do caso anterior, pois a receita baseada

na diretiva assembler .symver será usada da mesma maneira que no exemplo anterior (consulte a Listagem 10-18).

Listagem 10-18. simple.c (apenas as alterações mostradas aqui)

_asm _ (". symver first_function_1_0, first_function @ LIBSIMPLE_1.0");

int first_function_1_0 (int x) {

printf ("lib:% s \ n", __FUNCTION__); retorno (x + 1);

_asm _ (". symver first_function_2_0, first_function @ LIBSIMPLE_2.0");

int first_function_2_0 (int x) {

printf ("lib:% s \ n", __FUNCTION__); retornar 1000 * (x + l);

_asm _ (". symver first_function_3_0, first_function @@ LIBSIMPLE_3.0");

int first_function_3_0 (int x, int normfactor) {

printf ("lib:% s \ n", __FUNCTION__); retornar normfactor * (x + l);

A diferença mais substancial, entretanto, é que o cabeçalho de exportação deve ser modificado, conforme mostrado na Listagem 10-19.

Listagem 10-19. simple.h #pragma uma vez

// definido ao construir o binário do cliente mais recente #ifdef SIMPLELIB_VERSION_3_0 int first_function (int x, int
normfactor); #outro

primeira_função int (int x); #endif // ~ SIMPLELIB_VERSION_3_0

segunda_função int (int x); terceira_função int (int x);

int quarta_função (int x); quinta_função int (int x);

Apenas o binário cliente construído com a constante de pré-processador SIMPLELIB_VERSI0N_3_0 passada para o compilador incluirá o novo protótipo
first_function () .

$ gcc -g -00 -c -DSIMPLELIB_VERSI0N_3_0 -I ../ sharedLib main.c $ gcc main.o -Wl, -L ../ sharedLib -lsimple \ -Wl, -R ../
sharedLib -o ver3PeerApp

Será um pequeno exercício agradável para o leitor verificar se em todos os outros aspectos (informações de versão, presença de símbolos, resultados de tempo de
execução) o exemplo atende às suas expectativas.

Visão geral da sintaxe do script de versão

Os scripts de versão mostrados nos exemplos de código até agora apresentam apenas um subconjunto da ampla gama de recursos de sintaxe suportados. O
objetivo desta seção é fornecer uma breve visão geral das opções com suporte.

Nó de versão

A entidade básica do script de versão é o nó de versão, a construção nomeada encapsulada entre as chaves que descrevem certa versão, como

LIBXYZ_1.0.6 {

... <alguns descritores residem aqui>

};

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 148/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O nó de versão geralmente encapsula várias palavras-chave controlando diferentes aspectos do processo de controle de versão, cuja variedade será discutida em
mais detalhes em breve.

Regras de nomenclatura de nó de versão

O nome do nó é normalmente escolhido para descrever com precisão a versão completa descrita pelo nó. Normalmente, o nome termina com dígitos separados
por pontos ou sublinhados. É uma prática de bom senso que os nós que representam as versões posteriores venham após os nós que representam as versões
anteriores.

Porém, esta é apenas uma prática que facilita a vida dos humanos. O vinculador não se preocupa particularmente como você nomeia seus nós de versão, nem se
importa com a ordem em que eles aparecem no arquivo. Tudo isso realmente requer que os nomes sejam diferentes.

Uma situação semelhante ocorre com as bibliotecas dinâmicas e seus binários de cliente. O que realmente importa para eles é a cronologia em que os nós de
versão foram adicionados aos arquivos de versão - qual versão específica estava presente no momento em que foram construídos.

Controle de exportação de símbolos

Os modificadores globais e locais de um nó de versão controlam diretamente a exportação do símbolo. A lista de símbolos separados por ponto e vírgula
declarada no rótulo global será exportada, ao contrário dos símbolos declarados no rótulo local.

LIBXYZ_1.0.6 {global:

first_function; second_function; local: t

};

Embora não seja o tópico principal do esquema de controle de versão, esse mecanismo de exportação dos símbolos é, na verdade, uma forma completamente
legítima (e em muitos aspectos a mais elegante) de especificar a lista de símbolos exportados. Um exemplo de como esse mecanismo funciona será fornecido nas
seções subsequentes.

Suporte a curinga

O script de versão suporta o mesmo conjunto de curingas que os shells suportam para operações de correspondência de expressão. Por exemplo, o seguinte script
de versão declara como globais todas as funções cujo nome começa com "primeiro" ou "segundo:"

LIBXYZ_1.0.6 {global:

primeiro*; segundo*; local: t

};

Além disso, o asterisco sob o rótulo local especifica todas as outras funções como sendo do escopo local (aquelas que não devem ser exportadas). Os nomes de
arquivo especificados entre aspas duplas devem ser interpretados literalmente, independentemente de quaisquer caracteres curinga que possam conter.

Suporte para especificador de ligação

O script de versão pode ser usado para especificar o especificador de ligação externo "C" (sem alteração do nome) ou externo "C ++" .

LIBXYZ_1.0.6 {global:

extern "C" {

first_function;

local: *.

};

Suporte a namespace

Os scripts de versão também suportam o uso de namespace na especificação da afiliação dos símbolos versionados e / ou exportados.

LIBXYZ_1.0.6 {global:

extern "C ++" {

namespace libxyz :: *

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 149/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
}

local: *.

};

Nó Sem Nome

Um nó sem nome pode ser usado para especificar os símbolos não versionados. Além disso, sua finalidade pode ser hospedar os especificadores de exportação de
símbolos (globais e / ou locais).

Na verdade, quando o controle sobre a exportação do símbolo é seu único motivo para usar o mecanismo de script de controle de versão, é muito comum ter um
script de versão contendo apenas um nó sem nome.

Recurso do lado do script de versão: controle de visibilidade do símbolo

Um recurso secundário do mecanismo de script de versão é que ele também fornece controle sobre a visibilidade do símbolo. Os símbolos listados no nó do script
de versão na guia global acabam sendo exportados, enquanto os símbolos listados na guia local não são exportados.

É perfeitamente legal usar o mecanismo de script de versão apenas com o propósito de especificar os símbolos a serem exportados. No entanto, é altamente
recomendável, nesse caso, usar os nós de versão de script sem nome, conforme demonstrado na demonstração simples ilustrada pela Figura 10-26.

m ^ lan ^ | função grep

Figura 10-26. O script de versão pode ser usado como a forma mais elegante de controlar a visibilidade do símbolo, pois não requer nenhuma modificação do código-fonte

Controle de versão de bibliotecas dinâmicas do Windows


A implementação de controle de versão no Windows segue um conjunto idêntico de princípios como sua contraparte do Linux. As alterações de código que se
afastam significativamente da funcionalidade de tempo de execução existente ou exigem a reconstrução dos binários do cliente levam às principais alterações de
versão. As adições / expansões da funcionalidade fornecida que não interrompem a funcionalidade dos binários de cliente existentes se qualificam para as
alterações de versão secundárias.

As alterações de código que afetam principalmente os detalhes da funcionalidade interna são referidas no Linux como patches e no Windows como versões de
compilação. Além das diferenças óbvias de nomenclatura, não há diferenças substanciais entre os dois conceitos.

Informação da versão DLL

Como acontece com as bibliotecas dinâmicas do Linux, as informações de versão das bibliotecas dinâmicas do Windows (DLL) são opcionais. A menos que um
esforço de design consciente seja feito para especificar essas informações, elas não aparecerão como parte da DLL. Como uma boa regra de design, no entanto,
todos os principais fornecedores de DLL (começando com a Microsoft, é claro) garantem que as bibliotecas dinâmicas que fornecem carreguem as informações de
versão. Quando disponíveis, as informações da versão DLL são fornecidas como uma guia dedicada nas páginas de propriedades do arquivo, que podem ser
recuperadas clicando com o botão direito do mouse no ícone do arquivo no painel Explorador de Arquivos, conforme mostrado na Figura 10-27.

► Computador> Windows (C :) ► Windows ► System32 ►

H Abrir com ... Soma Nova pasta

Nome

® msvcrll0_cli0400.dll

Data de modificação 11/09/2013 20:39

Modelo

Extensão do aplicativo ...

Tamanho

836 KB

msvcrt.dll

msvfw32.dll msvidc32.dll MSVidCtl.dll mswmdm.dll mswsock.dll & msxml3.dll ^ m sxm I3r.dll 1% msxmlfi.dll 1%, rnsxml6r.dll msyuv.dll MTril + ÇS MTnl + 64 Ç! MTrigger2 Mtriggeri ■ J mtstocom mbrelu.dll
mtxdm.dll mtxex.dll% mtxoci.dli 1%, mgifontsetup.dll MUtLanguageCleanup.

13 MuiUnattend

MultiDigiMon mycomputdll mydocs.dll P, Mystify gl NAPCLCFG l ^ j iNAPCRYPT.DLL

G3
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 150/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

s: «
grepWin ...

7-Zip KDiff3

Abrir com..

Verificar arquivos selecionados com o Avira

TextPad

WinMerge

Restaurar versões anteriores

Enviar para

Cortar copiar

Criar atalho

Excluir

Renomear

Propriedades

dtl

13/07 / 20C 11/20 / 2C 13/07 / 2W 13/07 / 2W 11/20 / 2a 13/07 / 20C 11/20 / 2a 11/20 / 2C 6 / 1T / 2K 11/20/21

620 KB

Extensão do aplicativo ...

Ésteres de aplicativos ... Exteris de aplicativos ... Extensões de aplicativos ...

T ^ T

OK

ippty

HI KB 38 KB 3.565 KB

Propriedades msvcrtdll

Dotais

Geral (Segurança

Versões prévias

Propriedade Valor

Descrição

Descrição do arquivo DLL CRT do Windows NT


Modelo ^ extensão de plicação
Versão do arquivo 7.07601 17744
Nome do Produto Sistema Operativo Microsoft® Windows®

Versão do produto 707601 17744


direito autoral ® Microsoft Coipor ^ ion. M direitos reservados ...
Tamanho 620 KB
Data modificada 16/12/2011 12h46
Língua Inglês (United Slates)
Nome do arquivo original msvcrt.dll

Remover propriedades e informações pessoais

Caned

Figura 10-27. Exemplo de informações de versão DLL

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 151/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Especificando informações de versão DLL

Para ilustrar os aspectos mais importantes do controle de versão de DLL do Windows, uma solução de demonstração do Visual Studio é criada com dois projetos:

• Projeto VersionedDLL, que cria a DLL cujas informações de versão são fornecidas

• Projeto VersionedDLLClientApp, que cria o aplicativo cliente que carrega a DLL com versão e tenta recuperar suas informações de versão

A maneira usual de fornecer as informações de versão ao projeto DLL é adicionar o elemento de recurso de versão dedicado ao arquivo de recurso de biblioteca,
conforme mostrado na Figura 10-28.

à LJi Sou

«B m

fj. *

Versão CjRea 33

Explorador de Soluções »fl X

a IQ A
^ Solução 'DLLVersioningDemc' (2 projetos) a . "J] VersionedDLL

(ji) J Dependências externas * Arquivos de cabeçalho Li J t] stdafx.h

targetver.h ijj VersionedDLL.h

-Eu- eu

Adicionar

Ctfl + Shift +

Ctrl + X

Ctrf + C

Ctrl + V

Assistente de aula ...

Cortar

cópia de

Colar De

Re i Prd

Adicionar Recurso

Tipo de recurso:

r:
l_J Resr ----- '-

(Âmbito global)

El // VersionedDLL.cpp: define o

U
I

VersionedDLL.cpp X

«Incluir" stdafx.h "« incluir "VersionedDLL.h"

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 152/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
// Este é um exemplo de exportação

M / cnc TrAtcrvni * _IDT. , -Ln + "__ nn ^^ Hl L

m Novo item.., Ctrl + Shift + A

s Item Existente .., Shift + Alt + A

iZj Novo Filtro


Classe...

1 Recurso...

Novo

Accelerator .fc Bitmap S Cursor

Diálogo ffl-SI
Ill HTML [Ícone Ml § Menu m Ribbon ii® String Table Toolba r

Importar.,,

Personalizado..

Cancelar

Ajuda

@r
Versão

Figura 10-28. Adicionando o campo de versão ao arquivo de recurso do projeto 220

Depois que o recurso de versão é adicionado ao arquivo de recurso de projeto DLL, ele pode ser exibido e modificado por meio do editor de recursos do Visual
Studio.

Como indica a Figura 10-29, as informações de versão fornecem dois componentes distintos de versão, FILEVERSION e PRODUCTVERSION. Apesar do fato de
que na grande maioria dos cenários da vida real esses dois componentes têm valores idênticos, existem certas diferenças na forma como os valores desses
componentes são definidos. Se uma DLL for usada em mais de um projeto, o número da versão do arquivo provavelmente será notavelmente maior que o número
da versão do produto.

VersionedDLLcpp

VersionedDLL.rc - „. SIONJNFO - Versão X

Chave _ lvalue _

V If X

Explorador de Soluções

Solução 'DLLVirsioningDtmo' (2 projetos :) jj ^ j VersionedDLL

> i ^ l Dependências externas s L? Arquivos de cabeçalho resource.h ^ stdafx.h • i "] targetver.h, _h] VersionedDLLh j Arquivos de recursos

^ VersionedDLLrc * J Arquivos de origem

dllmain.cpp stdafx.cpp VersionedDLLcpp Q RtadMe.trt (> VersionedDLLCIientApp

16, 24, 7123,1

0x3fL OxOL

VOS.NT.WINDOWS32 VFTJJNKNOWN VFT2 DESCONHECIDO

Block Header Inglês (Estados Unidos) (040904 bOl

Nome da empresa Wi ndo wsD LL Ve rsi oning Pesquisa C o / p

FileDescription DLL que demonstra controle de versão

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 153/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
FileVersion 16,24.7123.1

DLL com versão simples de InternalName

Le gal Copyright Copyright (C) 2013

OriginalFilename VersionedDLL.dll

ProductName VersionedDLL.dll

Versão do Produto 41.7,1

FILEVERSION

VERSÃO DO PRODUTO

RLEFLAGSMASK RLE FLAG S RLE OS RLETYPE RLESUBTYPE

Figura 10-29. Usando o editor do Visual Studio para definir a versão do arquivo e as informações da versão do produto

Normalmente, quando a DLL acaba de ser criada, a versão (versão principal, versão secundária, número de compilação) é normalmente definida para valores
razoavelmente pequenos e arredondados, como 1.0.0. Neste exemplo, no entanto, eu escolhi propositalmente por causa de uma demonstração convincente não
apenas definir as informações de versão para valores numéricos razoavelmente grandes, mas também para que os valores FILEVERSION difiram dos valores
PRODUCTVERSION.

Quando a biblioteca é construída, as informações de versão especificadas pela edição do arquivo de recurso de versão podem ser visualizadas clicando com o
botão direito do mouse no ícone do arquivo no painel File Explorer e escolhendo o item de menu Propriedades (Figura 10-30).

VersionedDLL.rc - 3 VersionedDLL.cpp
Chave Valor
FILEVERSION 16.21.7123,1

FILEFLAGSMASK Qx3fL
FILEFLAGS (MIL
FILEOS VOS_NT_WINDOWS32
TIPO DE ARQUIVO VFT_UN KNOWN
FILESUBTYPE VFT2_UNKNOWN
Bloco de Cabeçalho Inglês (Estados Unidos) (040904b0)
CompanyNarme C o rp de Pesquisa de Versão do Wi n dowsD LL
Descrição do arquivo DLL que demonstra Yersioning
FileVersion 16.24.7123.1
Nome Interno DLL com versão simples
legalCopyright Copyright (C) 2013
Nome do Arquivo Original VeriionedDLl.dll
Nome do Produto VersionedOLL.dll
Versão do produto 4.1.71
Saída

Mostrar saída de: | Construir

Todas as saídas estão atualizadas. Todas as saídas estão atualizadas. ManifestResourceCompile:

Todas as saídas estão atualizadas. Ligação:

Criando a biblioteca C: VUsers \ icelero \ Desktop \ Dl.L. Manifesto:

Todas as saídas estão atualizadas., LinkEnbedManifest; Todas as saídas estão atualizadas.

VersionedDLL.vcxproj -> C; \ lsers \ icelero \ Desfctofi \ FinalizeBuildStatus:

H,
neletiniJ filp nphijp \ Vprsiori ^ rini I. ur ^ ur rpsc-ful hui

^> DLLVersioningDemo ► Depurar

Organize * - Opto com. ,, Compartilhe com ** Favoritos de novas dobras

Área de Trabalho

OK

Aplicar

ISi VersionedDLLdll

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 154/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Versão ed D IL .d 11P ro p erti es

GeneraJ | Detalhes de segurança Versons anteriores

Propriedade Valor

Descrição

Fie descrição DLL que demonstra controle de versão


Modelo ^ Extensão de aplicativo
Versão fie 16,24 7123,1
Nome do Produto VersionedDLLdfl
Versão do produto 4.1.7.1

1 direito autoral Copyright 2013 ■


Tamanho 30,5 KB
Data modificada 21/12/2013 3 39 PM
Língua Inglês dos Estados Unidos)
Fiename original VersionedDLLdll

Remover informações de Prooertiea e Pereonal

Cancelar

Figura 10-30. Defina os valores que aparecem nas propriedades do arquivo binário DLL construído

Consultando e recuperando informações de versão DLL

As informações sobre a versão do DLL podem ser de particular importância para várias partes interessadas e em diversos cenários. Os binários do cliente cuja
funcionalidade depende criticamente da versão da DLL podem querer examinar programaticamente os detalhes da versão da DLL a fim de realizar um curso
apropriado de ações adicionais. Os pacotes de instalação / implementação podem primeiro recuperar as informações de versão das DLLs existentes para decidir se
devem ou não substituir / sobrescrever as DLLs existentes por versões mais novas do mesmo arquivo. Finalmente, as pessoas que executam as funções de
manutenção de administração do sistema ou solução de problemas podem desejar dar uma olhada mais de perto na versão DLL.

Nesta seção, enfocarei principalmente as maneiras programáticas pelas quais as informações da versão da DLL podem ser recuperadas.

Estrutura VERSIONINFO

A estrutura DLLVERSIONINFO, declarada no arquivo de cabeçalho <shlwapi.h> , é normalmente usada para passar as informações de versão. A Figura 10-31
mostra seus detalhes de layout.

// IDs de plataforma para DLLVERSIONINFO Figura 10-31. Estrutura DLLVERSIONINFO

Requisitos de ligação

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 155/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Os módulos de software que precisam acessar a funcionalidade relacionada ao versionamento devem ser vinculados ao version.dll (ou seja, sua biblioteca de
importação version.lib deve ser especificada na lista de entradas do linker), conforme ilustrado na Figura 10-32.

Figura 10-32. É necessário vincular a version.lib (version.dll)

As maneiras pelas quais as informações da versão da DLL podem ser recuperadas serão discutidas a seguir.

Maneira elegante: chamando a função DllGetVersion da DLL

DLLs bem projetadas normalmente exportam a implementação da função DllGetVersion () , cuja assinatura segue a seguinte especificação:

HRESULT CALLBACK DllGetVersion (DLLVERSIONINFO * pdvi);

Isso é mencionado na documentação do MSDN em http://msdn.microsoft.com/enus/library/windows/ desktop / bb776404 (v = vs.85) .aspx. As


DLLs fornecidas pela Microsoft geralmente fornecem a funcionalidade esperada.

Não é complicado para as DLLs de design personalizado implementá-lo também. Aqui está o esboço da receita: o protótipo da função deve ser devidamente
declarado e exportado, ilustrado pela Listagem 10-20, bem como pela Figura 10-33.

Listagem 10-20. VersionedDll.h

// O seguinte bloco ifdef é a maneira padrão de criar macros que tornam a exportação // de uma DLL mais simples. Todos os arquivos
dentro dessa DLL são compilados com o símbolo VERSIONEDDLL_EXPORTS // definido na linha de comando. Este símbolo não deve ser
definido em nenhum projeto // que use esta DLL. Desta forma, qualquer outro projeto cujos arquivos de origem incluam este arquivo
vê as funções // VERSIONEDDLL_API como sendo importadas de uma DLL, enquanto esta DLL vê os símbolos // definidos com esta macro
como sendo exportados.

#ifdef VERSIONEDDLL_EXPORTS

#define VERSIONEDDLL_API __declspec (dllexport)

#outro

#define VERSIONEDDLL_API __declspec (dllimport) #endif

#include <Shlwapi.h>

VERSIONEDDLL_API HRESULT CALLBACK DllGetVersion (DLLVERSIONINFO * pdvi);

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 156/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 10-33. Exportando corretamente a função DllGetVersion () da DLL

A função pode ser implementada de várias maneiras.

? Os membros da estrutura DLLVERSIONINFO podem ser definidos para o conjunto predeterminado de valores.

É preferível ter os valores da versão na forma de constantes parametrizadas (em vez de constantes literais).

• A estrutura DLLVERSIONINFO pode ser preenchida carregando os recursos DLL, extraindo as cadeias de informações de versão e analisando os detalhes sobre a
versão principal, secundária e de compilação.

A Listagem 10-21 ilustra a combinação de ambos os métodos. Se a recuperação do recurso de versão falhou, os valores predeterminados podem ser retornados.
(Para simplificar, as constantes literais são usadas nesta lista. Todos nós sabemos que isso pode ser realizado de uma forma mais estruturada).

Listagem 10-21. VersionedDLL.cpp

# define SERVICE_PACK_HOTFIX_NUMBER (16385)

VERSIONEDDLL_API HRESULT CALLBACK DllGetVersion (DLLVERSIONINFO * pdvi) {

if (pdvi-> cbSize! = sizeof (DLLVERSIONINFO) && pdvi-> cbSize! = sizeof (DLLVERSIONINFO2))

return E_INVALIDARG;

if (FALSE == extractVersionInfoFromThisDLLResources (pdvi)) {

// não deve acontecer que acabemos aqui, // mas apenas no caso - tente salvar o dia // aderindo aos números de versão reais //
TBD: use valor parametrizado em vez de literais pdvi-> dwMajorVersion = 4; pdvi-> dwMinorVersion = 1; pdvi-> dwBuildNumber = 7;

pdvi-> dwPlatformID = DLLVER_PLATFORM_WINDOWS;

if (pdvi-> cbSize == sizeof (DLLVERSIONINFO2)) {

DLLVERSIONINFO2 * pdvi2 = (DLLVERSIONINFO2 *) pdvi; pdvi2-> dwFlags = 0;

pdvi2-> ullVersion = MAKEDLLVERULL (pdvi-> dwMajorVersion,

pdvi-> dwMinorVersion, pdvi-> dwBuildNumber, SERVICE_PACK_HOTFIX_NUMBER);

return S_OK;

Os detalhes da função que extrai as informações da versão dos recursos DLL seguem imediatamente na Listagem 10-22.

Listagem 10-22. VersionedDLL.cpp (parte superior) extern HMODULE g_hModule;

BOOL extractVersionInfoFromThisDLLResources (DLLVERSIONINFO * pDLLVersionInfo) {

arquivo WCHAR estáticoVersion [256]; LPWSTR lpwstrVersion = NULL;

UINT nVersionLen = 0; DWORD dwLanguageID = 0; BOOL retVal;

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 157/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
if (NULL == pDLLVersionInfo) retorna FALSE;

HRSRC hVersion = FindResource (g_hModule,

MAKEINTRESOURCE (VS_VERSION_INFO), RT_VERSION);

if (NULL == hVersion) retorna FALSE;

HGLOBAL hGlobal = LoadResource (g_hModule, hVersion); if (NULL == hGlobal) retorna FALSE;

LPVOID lpstrFileVersionInfo = LockResource (hGlobal); if (NULL == lpstrFileVersionInfo) return FALSE;

wsprintf (fileVersion, L "\\ VarFileInfo \\ Translation"); retVal = VerQueryValue (lpstrFileVersionInfo,

fileVersion, (LPVOID *) & lpwstrVersion, (UINT *) & nVersionLen);

if (retVal && (4 == nVersionLen)) {

memcpy (& dwLanguageID, lpwstrVersion, nVersionLen);

wsprintf (fileVersion, L "\\ StringFileInfo \\% 02X% 02X% 02X% 02X \\ ProductVersion", (dwLanguageID & 0xff00) >> 8, dwLanguageID
& 0xff, (dwLanguageID & 0xff000000) >> 24, (dwLanguageID & 0xff0000) ) >> 16);

outro

wsprintf (fileVersion, L "\\ StringFileInfo \\% 04X04B0 \\ ProductVersion", GetUserDefaultLangIDO);

if (FALSE == VerQueryValue (lpstrFileVersionInfo,

fileVersion,

(LPVOID *) & lpwstrVersion, (UINT *) & nVersionLen))

retorna falso;

LPWSTR pwstrSubstring = NULL; WCHAR * pContext = NULL;

pwstrSubstring = wcstok_s (lpwstrVersion, L ".", & pContext); pDLLVersionInfo-> dwMajorVersion = _wtoi (pwstrSubstring);

pwstrSubstring = wcstok_s (NULL, L ".", & pContext); pDLLVersionInfo-> dwMinorVersion = _wtoi (pwstrSubstring);

pwstrSubstring = wcstok_s (NULL, L ".", & pContext); pDLLVersionInfo-> dwBuildNumber = _wtoi (pwstrSubstring);

pwstrSubstring = wcstok_s (NULL, L ".", & pContext); pDLLVersionInfo-> dwPlatformID = _wtoi (pwstrSubstring);

pDLLVersionInfo-> cbSize = 5 * sizeof (DWORD);

UnlockResource (hGlobal); FreeResource (hGlobal);

return TRUE;

A parte importante da receita é que o bom momento para capturar o valor do identificador do módulo da DLL é quando a função DllMain () é chamada,
conforme mostrado na Listagem 10-23.

Listagem 10-23. dllmain.cpp

// dllmain.cpp: define o ponto de entrada para o aplicativo DLL. #include "stdafx.h"

HMODULE g_hModule = NULL;

BOOL APIENTRY DllMain (HMODULE hModule,

DWORD ul_reason_for_call, LPVOID lpReserved

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 158/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
switch (ul_reason_for_call) {

case DLL_PROCESS_DETACH: g_hModule = NULL; pausa;

case DLL_PROCESS_ATTACH:

g_hModule = hModule;

case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break;

return TRUE;

Finalmente, a Listagem 10-24 mostra como o binário do cliente recupera as informações de versão. Listagem 10-24. main.cpp (aplicativo cliente)

BOOL extractDLLProductVersion (HMODULE hDll, DLLVERSIONINFO * pDLLVersionlnfo) {

if (NULL == pDLLVersionInfo) retorna FALSE;

DLLGETVERSIONPROC pDllGetVersion;

pDllGetVersion = (DLLGETVERSIONPROC) GetProcAddress (hDll, "DllGetVersion"); if (NULL == pDllGetVersion) retorna FALSE;

ZeroMemory (pDLLVersionInfo, sizeof (DLLVERSIONINFO)); pDLLVersionInfo-> cbSize = sizeof (DLLVERSIONINFO); HRESULT hr = (*


pDllGetVersion) (pDLLVersionInfo); if (FAILED (hr)) retorna FALSE;

return TRUE;

Alternativa brutal: examinar a versão do arquivo diretamente

Se acontecer de a DLL não exportar a função DllGetVersion () , você pode recorrer à medida mais brutal de extrair as informações de versão incorporadas nos
recursos do arquivo. O esforço completo de implementação dessa abordagem reside no lado binário do cliente. Como pode ser facilmente concluído comparando
o código a seguir com o código apresentado na descrição da abordagem anterior, a mesma metodologia é aplicada, com base no carregamento dos recursos do
arquivo, extraindo a sequência de versão e extraindo os números de versão dela (consulte a Listagem 10 -25).

Listagem 10-25. main.cpp (aplicativo cliente)

BOOL versionInfoFromFileVersionInfoString (LPSTR lpstrFileVersionInfo,

DLLVERSIONINFO * pDLLVersionInfo)

arquivo WCHAR estáticoVersion [256]; LPWSTR lpwstrVersion = NULL; UINT nVersionLen = 0; DWORD dwLanguageID = 0; BOOL retVal;

if (NULL == pDLLVersionInfo) retorna FALSE;

wsprintf (fileVersion, L "\\ VarFileInfo \\ Translation"); retVal = VerQueryValue (lpstrFileVersionInfo,

fileVersion, (LPVOID *) & lpwstrVersion, (UINT *) & nVersionLen);

if (retVal && (4 == nVersionLen)) {

memcpy (& dwLanguageID, lpwstrVersion, nVersionLen);

wsprintf (fileVersion, L "\\ StringFileInfo \\% 02X% 02X% 02X% 02X \\ FileVersion", (dwLanguageID & 0xff00) >> 8, dwLanguageID &
0xff, (dwLanguageID & 0xff000000) >> 24, (dwLanguageID & 0xff0000) ) >> 16);

outro

wsprintf (fileVersion, L "\\ StringFileInfo \\% 04X04B0 \\ FileVersion", GetUserDefaultLangID ());

if (FALSE == VerQueryValue (lpstrFileVersionInfo,

fileVersion,

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 159/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
(LPVOID *) & lpwstrVersion, (UINT *) & nVersionLen))

retorna falso;

LPWSTR pwstrSubstring = NULL; WCHAR * pContext = NULL;

pwstrSubstring = wcstok_s (lpwstrVersion, L ".", & pContext); pDLLVersionInfo-> dwMajorVersion = _wtoi (pwstrSubstring);

pwstrSubstring = wcstok_s (NULL, L ".", & pContext); pDLLVersionInfo-> dwMinorVersion = _wtoi (pwstrSubstring);

pwstrSubstring = wcstok_s (NULL, L ".", & pContext); pDLLVersionInfo-> dwBuildNumber = _wtoi (pwstrSubstring);

pwstrSubstring = wcstok_s (NULL, L ".", & pContext); pDLLVersionInfo-> dwPlatformID = _wtoi (pwstrSubstring);

pDLLVersionInfo-> cbSize = 5 * sizeof (DWORD); return TRUE;

BOOL extractDLLFileVersion (DLLVERSIONINFO * pDLLVersionInfo) {

DWORD dwVersionHandle = 0;

DWORD dwVersionInfoSize = GetFileVersionInfoSize (DLL_FILENAME, & dwVersionHandle); if (0 == dwVersionInfoSize) return FALSE;

LPSTR lpstrFileVersionInfo = (LPSTR) malloc (dwVersionInfoSize); if (lpstrFileVersionInfo == NULL) retorna FALSE;

BOOL bRetValue = GetFileVersionInfo (DLL_FILENAME,

dwVersionHandle,

dwVersionInfoSize,

lpstrFileVersionInfo);

if (bRetValue) {

bRetValue = versionInfoFromFileVersionInfoString (lpstrFileVersionInfo, pDLLVersionInfo);

livre (lpstrFileVersionInfo); return bRetValue;

int main (int argc, char * argv []) {

//

// Examinando nós mesmos o arquivo DLL //

memset (& dvi, 0, sizeof (DLLVERSIONINFO));

if (extractDLLFileVersion (& dvi)) {

printf ("Versão do arquivo DLL (principal, secundária, build, platformID) =% d.% d.% d.% d \ n", dvi.dwMajorVersion,
dvi.dwMinorVersion, dvi.dwBuildNumber, dvi.dwPlatformID);

outro

printf ("Falha na extração da versão do arquivo DLL \ n"); FreeLibrary (hDll); return 0;

Finalmente, o resultado da execução do aplicativo demo que demonstra ambas as abordagens (consulta de DLL e "força bruta") é mostrado na Figura 10-34.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 160/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 10-34. Extraindo programaticamente a versão do produto DLL, bem como a versão do arquivo

CAPÍTULO 11

Bibliotecas dinâmicas: tópicos diversos


Depois de entender as idéias mais profundas por trás do conceito de bibliotecas dinâmicas e antes de mergulhar nos detalhes da caixa de ferramentas do
profissional de software que lida com bibliotecas diariamente, é um bom momento para examinar mais de perto alguns problemas restantes. Primeiro, vamos
examinar mais de perto o conceito de plug-ins, o mecanismo onipresente de estender perfeitamente a funcionalidade básica de uma estrutura. Em seguida,
apontarei algumas implicações práticas decorrentes do conceito de bibliotecas dinâmicas. Finalmente, examinarei mais de perto alguns tópicos diversos que um
desenvolvedor pode encontrar no trabalho diário.

Conceito de Plug-in
Provavelmente, o conceito mais importante possibilitado pelo avanço da vinculação dinâmica é o conceito de plug-ins. Não há nada substancialmente difícil de
entender no conceito em si, já que o encontramos em uma infinidade de cenários do dia-a-dia, a maioria dos quais não requer nenhum conhecimento técnico. Um
bom exemplo do conceito de plug-in é a broca e a variedade de brocas que podem ser trocadas de acordo com as necessidades da situação particular e a decisão do
usuário final (Figura 11-1).

Figura 11-1. Drill and bits, um exemplo diário do conceito de plug-in

O conceito de software de plug-ins segue o mesmo princípio. Basicamente, há um aplicativo principal (ou ambiente de execução) que executa uma determinada
ação em um determinado assunto de processamento (por exemplo, o aplicativo de processamento de fotos que modifica as propriedades de uma imagem) e há um
conjunto de módulos especializados em realizar uma ação específica sobre o assunto de processamento (por exemplo, filtro de desfoque, filtro de nitidez, filtro
sépia, filtro de contraste de cor, filtro passa-alto, filtro de média, etc.), um conceito muito fácil de compreender.

Mas isso não é tudo.

Nem todos os sistemas que compõem o aplicativo principal e os módulos associados merecem ser chamados de "arquitetura de plug-in". Para que a arquitetura
suporte o modelo de plug-in, os seguintes requisitos também precisam ser atendidos:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 161/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• Adicionar ou remover os plug-ins não deve exigir que o aplicativo seja recompilado; em vez disso, o aplicativo deve ser capaz de determinar a disponibilidade
de plug-ins no tempo de execução.

• Os módulos devem exportar sua funcionalidade por meio de algum tipo de mecanismo carregável em tempo de execução.

Na realidade, os requisitos acima são normalmente suportados por meio das seguintes decisões de design:

• Os plug-ins são implementados como bibliotecas dinâmicas. Independentemente da funcionalidade interna, todas as bibliotecas dinâmicas de plug-in exportam
a interface padronizada (um conjunto de funções que permite ao aplicativo controlar a execução do plug-in).

• O aplicativo carrega os plug-ins por meio do processo de carregamento dinâmico da biblioteca. As duas opções a seguir são normalmente suportadas:

• O aplicativo examina a pasta predefinida e tenta carregar todas as bibliotecas dinâmicas que encontrar lá em tempo de execução. Ao carregar, ele tenta encontrar
os símbolos correspondentes

para a interface que se espera que os plug-ins exportem. Se os símbolos não forem encontrados (ou apenas alguns deles), a biblioteca de plug-ins é descarregada.

• O usuário, por meio de uma opção GUI dedicada em tempo de execução, especifica o local do plug-in e diz ao aplicativo para carregar o plug-in e começar a
fornecer sua funcionalidade.

Regras de Exportação

Não existem regras rigorosamente estritas para cada arquitetura de plug-in. No entanto, existe um conjunto de diretrizes de bom senso. De acordo com o
parágrafo que explica o impacto da linguagem C ++ nos problemas do linker, a maioria das arquiteturas de plug-in tende a seguir o esquema mais simples
possível, no qual um plug-in exporta um ponteiro para a interface composta de funções C-linkage.

Mesmo que a funcionalidade interna dos plug-ins possa ser implementada como uma classe C ++, essa classe geralmente implementa a interface exportada por seu
contêiner de biblioteca dinâmica e passando um ponteiro para a instância da classe (lançado como o ponteiro para a interface) para o a aplicação é prática usual.

Arquiteturas de plug-in populares

Há uma grande variedade de programas populares que oferecem suporte à arquitetura de plug-in, como (mas não se limitando a):

• Aplicativos de processamento de imagem (Adobe Photoshop, etc.)

• Aplicativos de processamento de vídeo (Sony Vegas, etc.)

• Processamento de som (arquitetura de plug-in Steinberg VST, com suporte universal em todos os principais editores de áudio)

• Estruturas multimídia (GStreamer, avisynth) e aplicativos populares (Winamp, mplayer)

• Editores de texto (um grande número dos quais possui plug-ins fornecendo certas funcionalidades)

• Ambientes de desenvolvimento integrado de desenvolvimento de software (IDEs) que suportam uma variedade de recursos por meio dos plug-ins

• Aplicativos GUI front-end dos sistemas de controle de versão

• Navegadores da Web (arquitetura de plug-in NPAPI)

• Etc.

Para cada uma dessas arquiteturas de plug-in, normalmente há um documento de interface de plug-in publicado estipulando em detalhes a interação entre o
aplicativo e o plug-in.

Dicas e truques
A última etapa em sua jornada para compreender totalmente o conceito de bibliotecas dinâmicas requer que você dê um pequeno passo para trás e organize tudo
o que aprendeu até agora em outro conjunto de fatos simples. Formular as coisas de uma maneira diferente às vezes pode significar muita diferença no domínio
das práticas de design do dia a dia.

Implicações práticas de trabalhar com bibliotecas dinâmicas

Depois que todos os detalhes sobre as bibliotecas dinâmicas foram examinados, a verdade mais potente sobre elas é que vincular a bibliotecas dinâmicas é uma
espécie de vinculação na promessa. De fato, durante o estágio de construção, tudo o que o executável do cliente se preocupa são os símbolos das bibliotecas
dinâmicas. É apenas no estágio de carregamento em tempo de execução que o conteúdo das seções da biblioteca dinâmica (código, dados, etc.) começa a ser
reproduzido. Existem várias implicações na vida real decorrentes do conjunto de circunstâncias descrito.

Desenvolvimento compartimentado e mais rápido

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 162/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O conceito de bibliotecas dinâmicas permite muita liberdade para um programador. Contanto que o conjunto de símbolos importantes para o executável do
cliente não mude, o programador está livre para continuar modificando o código da biblioteca dinâmica real por tanto tempo quanto desejar.

Este simples fato tem um impacto tremendo nas rotinas diárias de programação, pois tende a reduzir muito o tempo de compilação desnecessário. Em vez de ter
que recompilar todo o corpo do código sempre que uma mudança minúscula de código acontece, usando as bibliotecas dinâmicas, os programadores podem
reduzir a necessidade de reconstruir o código para a própria biblioteca dinâmica. Não é de se admirar que os programadores frequentemente decidam hospedar o
código em desenvolvimento na biblioteca dinâmica, pelo menos até que o desenvolvimento seja concluído.

Capacidade de substituição rápida em tempo de execução

No momento da construção, o binário do cliente não precisa da biblioteca dinâmica totalmente desenvolvida, com todos os recursos adequados. Em vez disso,
tudo o que o binário do cliente realmente precisa em tempo de construção é o conjunto de símbolos da biblioteca dinâmica - nada mais e nada menos do que isso.

Isso é realmente interessante. Por favor, respire fundo e vamos dar uma olhada no que essa afirmação realmente significa. O arquivo binário da biblioteca
dinâmica que você usa no tempo de construção e o arquivo da biblioteca dinâmica que é carregado no tempo de execução podem ser substancialmente diferentes
em todos os aspectos, exceto em um: os símbolos devem corresponder.

Em outras palavras (e sim, isso é verdade e exatamente como deveria ser), para fins de construção com reconhecimento estático, você pode usar uma biblioteca
dinâmica cujo código (flash e sangue) ainda está para ser implementado, mas seus símbolos ( o esqueleto) já estão em sua forma final.

Ou você pode usar uma biblioteca cujo código você sabe que será alterado, contanto que tenha certeza de que o conjunto de símbolos exportados não será
alterado.

Ou você pode usar a biblioteca dinâmica adequada para um tipo específico (como pacote de idioma) no momento da construção, mas vincular no tempo de
execução a outra biblioteca dinâmica - desde que os binários da biblioteca dinâmica exportem o mesmo conjunto de símbolos.

Isso é muito, muito interessante. Um exemplo extremo de como podemos nos beneficiar dessa importante descoberta ocorre no domínio da programação nativa
do Android. Durante o esforço para desenvolver um módulo (biblioteca dinâmica ou aplicativo nativo), não é completamente incomum para equipes inteiras de
desenvolvedores desnecessária e imprudentemente tomar o caminho demorado de adicionar seu código-fonte na gigantesca árvore de origem do Android cuja
construção pode levar algumas horas.

Alternativamente, um procedimento muito mais eficaz é desenvolver um módulo como um projeto Android autônomo, não relacionado à árvore de origem do
Android. Em questão de minutos, as bibliotecas dinâmicas nativas do Android necessárias para completar a fase de compilação podem ser copiadas ("adb
puxado" no jargão do Android) de qualquer dispositivo / telefone Android funcional e adicionadas à estrutura de compilação do projeto. Em vez de demorar
várias horas, o procedimento de construção agora leva vários minutos, no máximo.

Mesmo que o código (a seção .text ) da biblioteca dinâmica extraída do telefone Android disponível mais próximo possa diferir significativamente do código
encontrado na árvore de origem do Android, a lista de símbolos é muito provavelmente idêntica em ambas as bibliotecas dinâmicas. Obviamente, a biblioteca de
substituição rápida retirada do dispositivo Android pode satisfazer os requisitos de construção, enquanto em tempo de execução "a certa" do binário da biblioteca
dinâmica será carregada.

Dicas Diversas

No restante deste capítulo, cobrirei as seguintes informações interessantes:

• Converter a biblioteca dinâmica em executável

• Cenários conflitantes de manipulação de memória em tempo de execução de bibliotecas do Windows

• Símbolos fracos do linker

Convertendo Biblioteca Dinâmica em Executável


Como foi apontado anteriormente nas discussões introdutórias sobre bibliotecas dinâmicas, a diferença entre a biblioteca dinâmica e o executável está no fato de
que posteriormente possui rotinas de inicialização que permitem que o kernel realmente inicie a execução. Em todos os outros aspectos, especialmente se
comparados às bibliotecas estáticas, parece que a biblioteca dinâmica e o executável são da mesma natureza, como o arquivo binário no qual todas as referências
foram resolvidas.

Dadas tantas semelhanças e tão poucas diferenças, é possível converter a biblioteca dinâmica em executável? A resposta a esta pergunta é positiva. É certamente
possível no Linux (ainda estou tentando confirmar a afirmação no Windows também). Na verdade, a biblioteca que implementa a biblioteca de tempo de execução
C (libc.so) é realmente executável. Quando invocado digitando seu nome de arquivo na janela do shell, você obtém a resposta mostrada na Figura 11-2.

FillanfcjmilanJ / 1 lb / 1386 -llnux- gnu /libc.so. 6

GNU C Library (Ubuntu EGLIBC 2.15-0ubuntul0) versão estável versão 2.15, de Roland McGrath et al.

Copyright (C) 2012 Free Software Foundation, Inc.

Este é um software livre; consulte a fonte para as condições de cópia.

NÃO há garantia; nem mesmo para COMERCIALIZAÇÃO ou ADEQUAÇÃO PARA UM

PROPÓSITO PARTICULAR.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 163/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Compilado por GNU CC versão 4.6.3.

Compilado em um sistema Linux 3.2,14 em 19/04/2012.

Extensões disponíveis:

Crypt add-on versão 2.1 por Michael Glad e outros GNU Libidn por Simon Josefsson

Biblioteca de Threads POSIX nativa de Ulrlch Drepper et al BIND-8.2.3-T5B llbc ABIs: UNIQUE IFUNC

Para obter instruções de relatório de bug, consulte: <http: //www,deblan.org/Bugs/>. nllangmilanS

Figura 11-2. Executando libc.so como arquivo executável

A questão que surge naturalmente a seguir é como implementar a biblioteca para torná-la executável? A seguinte receita torna isso possível:

• Implemente a função principal dentro da biblioteca dinâmica - a função cujo protótipo é int main (int argc, char * argv [];

• Declare a função main () padrão como o ponto de entrada da biblioteca. Passar o sinalizador de vinculador -e é como você realiza essa tarefa.

gcc -shared -Wl, -e, main -o <libname>

• Transforme a função main () em uma função sem retorno. Isso pode ser feito inserindo a chamada _exit (0) como a última linha da função main () .

• Especifique o intérprete para ser o vinculador dinâmico. A seguinte linha de código faria isso: #ifdef _LP64_

const char service_interp [] _attribute _ ((section (". interp"))) =

"/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2";

#outro

const char service_interp [] _attribute _ ((section (". interp"))) =

"/lib/ld-linux.so.2";

#fim se

• Construiu a biblioteca sem a otimização (com o sinalizador de compilador -O0 ).

Um projeto de demonstração simples é feito para ilustrar a ideia. A fim de provar a natureza verdadeiramente dual da biblioteca dinâmica (ou seja, embora agora
possa ser executada como executável, ainda permanece capaz de funcionar como uma biblioteca dinâmica regular), o projeto de demonstração contém não apenas
a biblioteca dinâmica de demonstração, mas também o executável que o carrega dinamicamente e chama sua função printMessage () . A Listagem 11-1 ilustra
os detalhes do projeto de biblioteca compartilhada executável:

Listagem 11-1.

arquivo: executableSharedLib.c

#include "sharedLibExports.h"

#include <unistd.h> // necessário para a função _exit ()

// Deve definir o intérprete para ser o vinculador dinâmico #ifdef __LP64__

const char service_interp [] _attribute _ ((section (". interp"))) =

"/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"; #outro

const char service_interp [] _attribute _ ((section (". interp"))) =

"/lib/ld-linux.so.2"; #fim se

void printMessage (void) {

printf ("Executando a função exportada da biblioteca compartilhada \ n");

int main (int argc, char * argv []) {

printf ("Função de biblioteca compartilhada% s () \ n", __FUNCTION__);

// deve fazer com que a função de ponto de entrada seja uma função 'sem retorno' tipo _exit (0);

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 164/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
}

arquivo: build.sh

g ++ -Wall -00 -fPIC -I./exports/ -c src / executableSharedLib.c -o src / executableSharedLib.o g ++ -shared -Wl, -e, main
./src/executableSharedLib.o -pthread -lm -ldl -o ../deploy/libexecutablesharedlib.so

A Listagem 11-2 ilustra os detalhes do aplicativo demo cujo objetivo é provar que, ao se tornar executável, nossa biblioteca compartilhada não perdeu sua
funcionalidade original:

Listagem 11-2.

arquivo: main.c

#include <stdio.h> #include "sharedLibExports.h"

int main (int argc, char * argv []) {

printMessage (); return 0;

arquivo: build.sh

g ++ -Wall -02 -I ../ sharedLib / exports / -c src / main.c -o src / main.o

g ++ ./src/main.o -lpthread -lm -ldl -L ../ deploy -lexecutablesharedlib -Wl, -Bdynamic -Wl, -R ../deploy -o demoApp

Quando você tenta usá-lo, os resultados mostrados na Figura 11-3 aparecem.

árvore milarnaroilan $

- demoApp

- implantar

1
- llbexecutablesharedltb.so

- Makefile

- Notas

1
- README.txt

- sharedLlb

- exportações

1
- sharedLtbExports.h

- llbexecutablesharedltb.so

- Makefile - src

1
I— executableSharedLlb.c - executableSharedLtb.o

- testApp

E denoApp
Makefile

src

| - rcaln.c '- naln.o

7 diretórios, 13 arquivos

milarignUanS, / deploy / Ubexecutablesharedlib.so Biblioteca compartilhada matn (} função nilan @ nilan $ ./demoApp

Executando a função exportada da "biblioteca fitlan compartilhada (jlpitlan $

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 165/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Figura 11-3. Ilustrando a natureza dual (lib dinâmico, executável) da biblioteca demo O tarball do código-fonte do projeto fornece mais informações sobre os detalhes.

Manipulação de memória em tempo de execução conflitante de bibliotecas do Windows


Em geral, uma vez que a biblioteca dinâmica é carregada no processo, ela se torna uma parte legítima do processo e praticamente herda todos os privilégios do
processo, incluindo o acesso ao heap (o pool de memória no qual a alocação de memória dinâmica é executada). Por essas razões, é perfeitamente normal que uma
função de biblioteca dinâmica aloque o buffer de memória e o passe para uma função pertencente a outra biblioteca dinâmica (ou para o código executável) onde a
memória pode ser desalocada quando não for mais necessária.

No entanto, há uma reviravolta especial em toda a história que precisa ser examinada com cuidado.

Normalmente, independentemente de quantas bibliotecas dinâmicas são carregadas no processo, todas elas se vinculam à mesma instância da biblioteca de tempo
de execução C, que fornece a infraestrutura de alocação de memória - malloc e free (ou no caso de C ++, new e delete), bem como a implementação da lista
mantendo o controle dos buffers de memória alocados. Se essa infraestrutura for única por processo, não há realmente nenhuma razão para que o esquema
descrito, no qual qualquer pessoa possa desalocar a memória alocada por outra pessoa, não funcione.

O caso interessante, entretanto, pode acontecer no domínio da programação do Windows. O Visual Studio fornece (pelo menos) duas DLLs básicas sobre as quais
todos os executáveis ​(aplicativos / bibliotecas dinâmicas) são construídos - a biblioteca de tempo de execução C usual (msvcrt.dll) , bem como a biblioteca
Microsoft Foundation Classes (MFC) (mfx42.dll ) Às vezes, os requisitos de projetos podem ditar a combinação e correspondência das DLLs construídas em
DLLs de base diferentes, o que pode causar imediatamente desvios muito desagradáveis ​das regras esperadas.

Digamos que, para fins de clareza, no mesmo projeto você tenha as duas DLLs a seguir carregadas no tempo de execução: DLL "A", criada em msvcrt.dll, e
DLL "B", criada em DLL MFC. Vamos agora supor que a DLL "A" aloque buffers de memória e os transmita para a DLL "B", que os usa e depois os desaloca. Nesse
caso, a tentativa de desalocar a memória resultará em um travamento (a exceção se parece com a da Figura 11-4).

void _cdecl _unlock (

int lockrium)

J *

* saia da seção crítica. * /

LeaveCriticalSection (_locktable [locknum] .lock) j} _

Microsoft Visual Studio

O Windows acionou um ponto de interrupção em FLVTranscoderTestApp.exe,

eu

ou em
Isso pode ser devido a uma interrupção do heap, o que indica um bug no exe qualquer uma das DLLs carregadas.

Isso também pode ser devido ao usuário pressionar F12 enquanto | tem foco.

A janela de saída pode ter mais informações de diagnóstico.

#ifdef #pragm

#endif j ***

*_trancar *

* Purpo

Prosseguir

Pausa

Ignorar

Figura 11-4. Caixa de diálogo de mensagem de erro típica para problemas de memória em conflito entre DLLs

A causa do problema é que existem duas autoridades de contabilidade em torno do pool de memória heap disponível; tanto a DLL de tempo de execução C
quanto a DLL MFC mantêm suas próprias listas separadas de buffers alocados (consulte a Figura 11-5).

DLL vinculado a msvcrtl.dll

DLL vinculado a mfx42.dll

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 166/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Figura 11-5. O mecanismo de problemas de tempo de execução causados ​por registros de alocação de memória não relacionados mantidos por diferentes DLLs

Normalmente, ao enviar o buffer para desalocação, a infraestrutura de alocação de memória pesquisa a lista de endereços de memória alocados e, se o buffer
passado para desalocação for encontrado na lista, a desalocação pode ser concluída com êxito. Se, no entanto, o buffer alocado for mantido em uma lista (digamos,
mantido pela DLL de tempo de execução C) e passado para desalocação para a outra lista (digamos, mantido pela DLL MFC), o endereço de memória do buffer
não será encontrado no lista, e a chamada de desalocação lançará uma exceção. Mesmo que você lide com a exceção silenciosamente, é questionável se o aplicativo
será capaz de enviar o buffer para a DLL correta para desalocação, causando vazamentos de memória.

Para piorar as coisas, virtualmente nenhuma das ferramentas usuais de verificação de limite de memória foi capaz de detectar e relatar algo errado. Na defesa das
ferramentas, você pode notar que de fato nenhuma das violações de memória típicas acontecem neste caso particular (como escrever além dos limites do buffer,
sobrescrever o endereço do buffer, etc). Tudo isso torna o problema desagradável de lidar e, a menos que você tenha uma ideia inicial sobre os problemas em
potencial, pode ser muito difícil identificar a causa, quanto mais a solução para o problema.

A solução para o problema é excepcionalmente simples: os buffers de memória alocados em uma DLL devem, em última instância, ser passados ​de volta para a
mesma DLL para serem desalocados. O único problema é que para aplicar esta solução simples é necessário ter acesso ao código-fonte de ambas as DLLs, o que
nem sempre é possível.

Símbolos Fracos do Linker Explicados

A ideia de um símbolo de linker fraco é, em sua essência, semelhante ao recurso predominante das linguagens orientadas a objetos (que é uma das manifestações
do princípio do polimorfismo). Quando aplicada ao domínio da vinculação, a ideia de símbolos fracos significa praticamente o seguinte:

• Compiladores (mais notavelmente, gcc) suportam a construção da linguagem, permitindo que você declare um símbolo (uma função e / ou uma variável
global ou estática de função) como fraco.

O exemplo a seguir demonstra como declarar uma função C como um símbolo fraco: int _ attribute __ ((fraco)) someFunction (int, float);

• O vinculador obtém essas informações para lidar com esse símbolo de uma maneira única.

• Se outro símbolo com o mesmo nome aparecer durante a vinculação e não for declarado fraco, esse outro símbolo substituirá o símbolo fraco.

• Se outro símbolo com nome idêntico aparecer durante a vinculação e for declarado fraco, o vinculador é livre para decidir qual dos dois será realmente
implementado.

• A presença de dois símbolos não fracos (ou seja, fortes) com o mesmo nome é considerada um erro (o símbolo já está definido).

• Se durante a vinculação nenhum outro símbolo com nome idêntico aparecer, o vinculador pode não implementar tal símbolo. Se o símbolo for um ponteiro de
função, a proteção do código é obrigatória (na verdade, é altamente recomendável fazê-lo sempre).

Uma excelente ilustração do conceito de símbolos fracos é encontrada na postagem do blog de Winfred CH Lu em http://winfred-
lu.blogspot.com/2009/11/understand-weak-symbols-by-examples.html . O cenário da vida real de quando esses recursos podem ser úteis é descrito na
postagem do blog de Andrew Murray em www.embedded-bits.co.uk/2008/gcc-weak-symbols/ .

CAPÍTULO 12

Linux Toolbox
O objetivo deste capítulo é apresentar ao leitor um conjunto de ferramentas (programas utilitários, bem como outros métodos) para analisar o conteúdo dos
arquivos binários do Linux.

Ferramentas de percepção rápida


A visão mais fácil e imediata da natureza de um arquivo binário pode ser obtida usando os programas utilitários de arquivo e / ou tamanho .

Programa utilitário de arquivo

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 167/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O utilitário de linha de comando chamado simplesmente arquivo ( http://linux.die.net/man/l7file ) é usado para descobrir detalhes sobre
praticamente qualquer tipo de arquivo. Ele pode ser útil rapidamente porque determina as informações mais básicas sobre o arquivo binário (Figura 12-1).

$ file /usr/bin/gst-inspect-0.10

/usr/bin/gst-inspect-0.10: Executável ELF LSB de 32 bits, Intel 89386, versão 1 (SY

SV), dinamicamente vinculado (usa bibliotecas compartilhadas), para GNLi / Linux 2.6.24, BuildID [shai] = ex4ib8f8a4

14SO3SbO9099222Oee852afe2f9dO0c2, despojado $

$ file /usr/llb/i386-linux-gnu/xen/libpthread.a

/usr/lib/i386-linux-gnu/xen/libpthread.a: arquivo ar atual $

$ file /lib/i386-linux-gnu/ltbc-2.15.so

/lib/i386-linux-gnu/libc-2.15.so: ELF objeto compartilhado LSB de 32 bits, Intel 80386, versão 1 (SY SV), vinculado dinamicamente (usa libs compartilhadas), BuildID [shal] =
0xe4aOe031bf28aaf48f716bee471e36

f5262d7730, para GNU / Linux 2.6.24, despojado de $

Figura 12-1. Usando o utilitário de arquivo

Programa Utilitário de tamanho

O utilitário de linha de comando denominado size ( http://linux.die.net/man/lZsize ) pode ser usado para obter instantaneamente uma visão dos
comprimentos de byte das seções ELF (Figura 12-2).

5 tamanho /usr/bin/gst-i.nspect-0.10

dados de texto bss dec hex nome de arquivo 29056 836 20 29912 74d8 /usr/bln/gst-tnspect-0.10

$$

$ size /lib/i386-linux-gnu/libc-2.15.so

dados de texto bss dec hex nome do arquivo

1696633 11508 11316 1719457 la3cal /lib/i386-linux-gnu/libc-2.15.so $

$ size /usr/lib/i386-linux-gnu/xen/libdl.a

texto dados bss dezembro nome de arquivo hexadecimal


83 0 0 83 53 dlopen.o (ex /usr/lib/i386-llnux-gnu/xen/libdl.a)
49 0 0 49 31 dlclose.o (ex /usr/lib/i386-linux-gnu/xen/libdl.a)
83 0 0 83 53 dlsym.o (ex /usr/lib/i386-linux-gnu/xen/libdl.a)
91 0 0 91 5b dlvsypi.o (ex /usr/lib/i386-ltnux-gnu/xen/libdl.a)
49 0 0 49 31 dlerror.o (ex /usr/lib/i386-linux-gnu/xen/libdl.a)
49 0 0 49 31 dladdr.o (ex /usr/lib/i386-linux-gnu/xen/libdl.a)
49 0 0 49 31 dladdrl.o (ex /usr/ltb/i386-llnux-gnu/xen/libdl.a)
91 0 0 91 5b dllnfo.o (ex /usr/lib/i386-linux-gnu/xen/libdl.a)
91 0 0 91 5b dlnopen.o (ex /usr/lib/i386-linux-gnu/xen/libdl.a)

Figura 12-2. Usando o utilitário de tamanho

Ferramentas de análise detalhada


Uma visão detalhada das propriedades do arquivo binário pode ser obtida contando com a coleção de utilitários chamados coletivamente de binutils (
www.gnu.org/software/binutils/ ). Ilustrarei o uso dos utilitários ldd, nm, objdump e readelf . Mesmo que formalmente não pertença aos binutils, o
script de shell chamado ldd (escrito por Roland McGrath e Ulrich Drepper) se encaixa perfeitamente no mesmo compartimento da caixa de ferramentas e,
portanto, seu uso também será ilustrado.

ldd

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 168/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O comando ldd ( http://linux.die.net/man/1/ldd ) é uma ferramenta excepcionalmente útil, pois mostra a lista completa das bibliotecas dinâmicas que
um binário cliente tentará carregar estaticamente ciente (ou seja, o dependências de tempo de carregamento).

Ao analisar as dependências de tempo de carregamento, o ldd primeiro examina o arquivo binário tentando localizar o campo de formato ELF no qual a lista das
dependências mais imediatas foi impressa pelo vinculador (conforme sugerido pela linha de comando do vinculador durante o processo de construção).

Para cada uma das bibliotecas dinâmicas cujos nomes foram encontrados embutidos no arquivo binário do cliente, o ldd tenta localizar seus arquivos binários
reais de acordo com as regras de pesquisa de localização da biblioteca em tempo de execução (conforme descrito em detalhes no Capítulo 7). Uma vez que os
binários das dependências mais imediatas tenham sido localizados, o ldd executa o próximo nível de seu procedimento recursivo, tentando encontrar suas
dependências. Em cada uma das dependências de "segunda geração", o ldd executa outra rodada de investigação e assim por diante.

Depois que a pesquisa recursiva descrita é concluída, o ldd reúne a lista de dependências relatadas, elimina as duplicatas e imprime o resultado (conforme
mostrado na Figura 12-3).

piilan @ nilan: ~ $ Idd /usr/bin/gst-inspect-0.10 linux-gate.so.1 => (0xb772f000)

"llbgstreamer-0.10 .so .0 => /usr/ltb/1386-llnux-gnu/"libgstreaner-0.10.so.0 (0xb7633O00) li.bgobject-2.0, então, 0 => / usr / lib / i386- Unux-gnu / li.bgobject-2.0.so.O (0xb75e4000) libglib-2.0.so.0 == ■ /li.b/l386-
"Llnux-gnu/libgl\b-2.0.so.0 (0xb74ea000) "libpthread.so.0 => / lib / 1386-Unux-gnu / llbpthread. so.0 (0xb74cf000) VLbc.so.6 => /Ub/t386-lT.nux-gnu/llbc.so,6 (0xb732500e)

VLbgmodule-2-0.so.0 => /usr/llb/1386-linux-gnu/libgmodule-2.0.so.0 (0xb732Q00O)

"libxnl2. so. 2 => /usr/li.b/1386-llnux-gnu/libxnl2.so.2 (Oxb71d3O00)

Ubn.so.6 => /Ub/t386-Unux-gnu/Ubci.so,6 (0xb7ia6ooe)

librt.so.1 => /Ub/U86-Unux-gnu/Ubrt.so.l (0xb719dOO9)

libdl. tão. 2 => / lib / i 386 - "Linux-gnu / libdl. So.2 (0xb7198000)

Vibffi.so.6 => /usr/lib/i386-linux-gnu/ltbffi.so.6 (0xb7191000)

VLbpcre.so.3 => /lib/1386-linux-gnu/libpcre.so.3 (0xb7155000)

/lib/ld-linux.so.2 (0xb7730O98)

libz.so.l => /l\b/i.386-linux-gnu/libz.so. 1 (0xb713eO00) m.lan@nilan: ~ $

Figura 12-3. Usando o utilitário Idd

Antes de usar o ldd , é importante estar ciente de suas limitações:

• O ldd não pode identificar as bibliotecas carregadas dinamicamente em tempo de execução chamando a função dlopen () . Para obter este tipo de
informação, diferentes abordagens devem ser aplicadas. Para obter mais detalhes, visite o Capítulo 13.

• De acordo com sua página de manual, a execução de certas versões do ldd pode na verdade representar uma ameaça à segurança.

Alternativas mais seguras do ldd

Conforme indicado na página de manual:

Esteja ciente, entretanto, que em algumas circunstâncias, algumas versões do ldd podem tentar obter as informações de dependência executando diretamente o programa. Portanto,
você nunca deve empregar o ldd em um executável não confiável, pois isso pode resultar na execução de código arbitrário. Uma alternativa mais segura ao lidar com executáveis ​não
confiáveis ​é a seguinte (e também mostrada na Figura 12-4):

$ objdump -p / caminho / para / programa | grep NECESSÁRIO

milan @ niilan: - $ objdump -p /usr/bin/gst-i.nspect-0.10 | grep NEEDED HEEDED libgstreaner-O.10.so.0

libgobject-2.0, so.0 libglAb-2.O.so.0 Ubpthread.so.O libc.so.6

mi aterro sanitário e Q

Figura 12-4. Usando objdump para (apenas parcialmente) substituir o utilitário ldd

O mesmo resultado pode ser alcançado usando o utilitário readelf (Figura 12-5):

$ readelf -d / caminho / para / programa | grep NECESSÁRIO

milan @ nilan: ~ $ readelf -d /usr/bin/gst-inspect-O.19 | grep NECESSÁRIO

0x00000001 0X00000001 0X009OG001 0X00900001 0x00900001 milan@nil.ari

(NECESSÁRIO) (NECESSÁRIO) (NECESSÁRIO) (NECESSÁRIO) (NECESSÁRIO)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 169/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Biblioteca compartilhada: Biblioteca compartilhada: Biblioteca compartilhada: Biblioteca compartilhada:
Biblioteca compartilhada:

[libgstreaner-O.lO.so.Q] [libgobject-2.O.so.0] [libgUb-2.0.so.o] [libpthread.so.o] [libc.so.6]

Figura 12-5. Usando readelf para (apenas parcialmente) substituir o utilitário ldd

Obviamente, na análise de dependências, ambas as ferramentas não vão além da simples leitura da lista das dependências mais imediatas do arquivo binário. Do
ponto de vista da segurança, esse é definitivamente um método mais seguro de encontrar a resposta.

No entanto, a lista fornecida está longe de ser tão exaustivamente completa quanto normalmente fornecida pelo ldd. Para fazer a correspondência, você
provavelmente precisará conduzir a pesquisa recursiva por conta própria.

nm

O utilitário nm ( http://linux.die.net/man/1/nm ) é usado para listar os símbolos de um arquivo binário (Figura 12-6). A linha de saída que imprime o
símbolo também indica o tipo de símbolo. Se o binário contiver código C ++, os símbolos serão impressos por padrão na forma fragmentada. Aqui estão algumas
das combinações de argumento de entrada mais usadas:

• $ nm <path-to-binary> lista todos os símbolos de um arquivo binário. No caso de bibliotecas compartilhadas, isso significa não apenas o exportado (da
seção .dynamic ), mas todos os outros símbolos também. Se a biblioteca foi removida (usando o comando strip ), nm sem argumentos relatará nenhum símbolo
encontrado.

• $ nm -D <path-to-binary> lista apenas os símbolos na seção dinâmica (ou seja, símbolos exportados / visíveis de uma biblioteca compartilhada).

• $ nm -C <caminho para o binário lista os símbolos em formato demangled (Figura 12-6).

09084ac9 T pspell_aspell_dumny () 00094040 T aconmon :: BetterList :: set_cur_rank () O9O93ed0 T acomnon :: BetterList :: set_best_fron_cur () 09094000 T aconmon :: BetterLlst :: init ()

09O945f9 T acommon :: BetterList :: BetterLlst ()

09094Sf9 T acommon :: BetterList :: BetterLlst () 09096C20 w acoromon :: BetterList :: - BetterList {) 09096af9 W aconmon: rBetterList :: ~ BetterLlst ()

O0O96af0 W aconmon :: BetterList :: ~ BetterList ()

O0093f30 T acomrton:: BetterSize:: set_cur_rank ()

09093f19 T acommon :: BetterSize :: set_best_fron_cur ()

09093ef0 T acommon :: BetterSize :: inlt ()

B9096aa9 W acommon :: BetterSize :: ~ BetterSize ()

O0096a70 W acoromon :: BetterSize :: ~ BetterSize ()

O0O96a70 W aconmon :: BetterSize :: ~ BetterSlze {)

0902dd5B W aconmon :: BlockSList <aconmon :: StringPair> :: clear ()

O002df50 W acommon: iBlockSListiacomnon :: StringPair> :: add_block (int unslgned)

090Sda09 W aconmon :: BlockSListcaconnon :: String? - :: add_block {unsigned int)

09082900 W aconmon :: BlockSList <aspeller :: Conds const * »:: add_block (unsigned int)

Figura 12-6. Usando o utilitário nm para listar símbolos não mutilados 246

• $ nm -D --no-demangle <path-to-binary> imprime os símbolos dinâmicos da biblioteca compartilhada e exige estritamente que os símbolos não sejam
demangled (Figura 12-7).

_Z19pspell_aspell_dunnyv

_ZN7aconnonl0BetterListl2set_cur_rankEv

_ZN7aconnonl0Betterl_i.stl7set_best_f ron_curEv

,
_ZN7acomi ioril0BetterList4lnitE \ /

_ZN7aconnonl0BetterListClEv

_ZN7aconnonl0BetterListC2Ev

i
_ZN7acoi iPionl0BetterL1.stD0Ev

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 170/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
_ZN7aconnonl0BetterListDlEv

_ZN7aconnonl0Betterl_i.stD2Ev

_ZN7acommonl0BetterSi.zel2set_cur_rankEv

ZN7aconnonl0BetterSizel7set_best_fron_curEv _ZN7a comum 10BetterSi.ze4invtEv _ZN7acommon10BetterSizeD0Ev _ZN7aconmonl0BetterSi.zeDlEv _ZN7aconmonl0BetterSi.zeD2Ev

_ZN7aconmonl0B "lockSLi.stINS_10StringPairEE5c" learEv _ZN7aconnonl0BlockSListINS_10StringPairEE9add_blockEi _ZN7aconmonl0BlockSLi.stINS_6StringEE9add_blockEj


_ZN7acommonl0BlockSListIPKN8aspen.er5CondsEE9add_bT.ockE j _ZN7acocinoni0BlockSLlstIPKcE9add_b \ ockEj

Figura 12-7. Usando o utilitário nm para listar símbolos mutilados

Esta opção é extremamente útil na detecção do bug mais comum ao projetar a biblioteca compartilhada - o caso em que o designer esquece o especificador
externo "C" na declaração / definição da função ABI (que é exatamente o que o binário do cliente espera encontrar) .

• $ nm -A <library-folder-path> /5 | grep symbol-nameé útil quando você procura um símbolo em vários binários localizados na mesma pasta, como-
Umaopção imprime o nome de cada biblioteca na qual um símbolo é encontrado (Figura 12-8).

objdump

O programa utilitário objdump ( http://linux.die.net/man/1/objdump ) é provavelmente a ferramenta de análise binária mais versátil. Cronologicamente,
é mais velho do que pronto, o que é paralelo às suas habilidades em muitos casos. A vantagem do objdump é que, além do ELF, ele suporta cerca de 50 outros
formatos binários. Além disso, suas capacidades de desmontagem são melhores do que as do readelf.

As seções a seguir cobrem as tarefas que mais frequentemente usam objdump.

Parsing ELF Header

A opção de linha de comando objdump -f é usada para obter uma visão do cabeçalho do arquivo de objeto. O cabeçalho fornece muitas informações úteis. Em
particular, o tipo binário (arquivo-objeto / biblioteca estática x biblioteca dinâmica x executável), bem como as informações sobre o ponto de entrada (início da
seção de texto ), podem ser obtidas rapidamente (Figura 12-9).

milan @ nllan $ objdump -f. / driverApp / drlver

./driverApp/driver: arquivo da arquitetura elf32-l386: 1386, sinalizadores 0x00000112: EXECP, HASSYMS, endereço inicial DPACED 0x080484c0

milan @ mllan $ objdump -f ./sharedLlb/libnreloc.so

./sharedLlb/llbnreloc.so: arquivo fornat elf32-i386 arquitetura: 1386, sinalizadores 0x00000150: HAS_SYMS, DYNAMIC, D_PACED endereço inicial 0x00000390

mlan @ pulan $ objdump -f ./nl_malnreloc.o

./ml_nalnreloc.o: formato de arquivo elf32-1386 arquitetura: i386, sinalizadores 0x00000011: HAS_REL0C, endereço inicial HAS_SYMS 0x00000000

Figura 12-9. Usando objdump para analisar o cabeçalho ELF de vários tipos de arquivos binários

Ao examinar a biblioteca estática, objdump -f imprime o cabeçalho de cada arquivo de objeto encontrado na biblioteca.

Listando e examinando seções

A opção objdump -h é usada para listar as seções disponíveis (Figura 12-10).

00084ac0 00094040 00093ed0 00094000 000945f0 000945f0 00096C20 00096af0 W 00096af0 W 00093f30 00093f10 00093efO 00096aa0 00O96a70 W 00096a70 W 0002dd50 W 0002df50 W 0005da00 W
00082900 W O00Sd740 W 00082900 W O00Sd740

ntlangnllanS objdunp -h llbnreloc.so libnreloc.so: formato de arquivo elf32-l386 Seções;

Idx Nome Tamanho VMA Arquivo LMA desligado Algn 9 .note.gnu.build-id 00000024 00009114 00009114 09009114 2 ** 2

CONTEÚDO, ALLOC, LOAD, READONLY, DATA

00000040 00000138 00000138 00000138 2 ** 2

CONTEÚDO, ALLOC, LOAD, READONLY, DATA

000000b0 09000178 00000178 00000178 2 ** 2

CONTEÚDO, ALLOC, LOAD, READONLY, DATA

0000007c O000022B O0OO022B 00OO022B 2 ** 0

CONTEÚDO, ALLOC, LOAD, READONLY, DATA

4 .gnu.version 00000016 000002a4 O00002a4 000002a4 2 ** 1 CONTEÚDO, ALLOC, LOAD, READONLY, DATA

Ú Ú
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 171/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
5 .gnu.verston_r 0O00002O 000002bc 00O002bc 0O0002bc 2 ** 2 CONTEÚDOS, ALLOC, LOAD, READONLY, DATA 00000038 000002dc O00002dc O00002dc 2 ** 2 CONTEÚDOS, ALLOC, LOAD,
READONLY, DATA300010 00000314 000, CONTEÚDO300010 00000314 0003 , CARREGAR, APENAS LEITURA, DADOS O000002e 00000324 00000324 00000324 2 ** 2 CONTEÚDOS, ATRIBUIR,
CARREGAR, APENAS LEITURA, CÓDIGO 00000030 00000360 00000360 00000360 2 ** 4 CONTEÚDOS, ATRIBUIR, CARREGAR, READONLY, CÓDIGO 00000118 00000390 00000390 00000390 000
ALLOC, CARREGAR, APENAS LEITURA, CÓDIGO 0000001a O000O4aB 00OO04aB 00OO04aB 2 ** 2

OQ

oo

1 .gnu.hash

2 .dynsyn

3 .dynstr

6 .rel.dyn

7 .rel.plt

8 .tntt

9 .pit

10 .texto

11 .ftnt

00000008 00002010 00002010 00001010 2 ** 2 ALLOC

0000002a 00000000 00000000 00001010 2 ** 0

CONTEÚDO, APENAS LEITURA

21 .bss

22 .connent

2 ** 0

23 .debugaranges 00000020 00000000 00000000


CONTEÚDO, SOMENTE LEITURA, DEPURAÇÃO

24 .debug info 00000075 00000000 00000000

CONTEÚDO, SOMENTE LEITURA, DEPURAÇÃO

25 , debug_abbreu 00000058 00000000 00000000


CONTEÚDO, SOMENTE LEITURA, DEPURAÇÃO

26 .debugllne O000003f 00000000 00000000


CONTEÚDO, SOMENTE LEITURA, DEPURAÇÃO

27 .debug_str 0000004c 00000000 00000000


CONTEÚDO, SOMENTE LEITURA, DEPURAÇÃO

28 , debug_loc 00000038 00000000 00000000


CONTEÚDO, SOMENTE LEITURA, DEPURAÇÃO

,
nilan @ r itlan $

Figura 12-10. Usando objdump para listar as seções do arquivo binário

Quando se trata de exames de seção, objdump fornece opções de comando dedicadas para as seções que são o tópico de interesse mais frequente para os
programadores. Nas seções a seguir, examinarei alguns dos exemplos notáveis.

Listando todos os símbolos

A execução de objdump -t <path-to-binary> fornece uma saída que é o equivalente completo da execução de nm <path-to-binary> (Figura 12-11).

t \ 1 an £ n11 anS objdump t libdenol.so

llbdemol.so: formato de arquivo elf32l386 TABELA DE SÍMBOLOS:

00090114 I d. observe .gnu .build-id NNNH

«060138 I d .gnu, hash 00006600

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 172/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
80008171 I d .dynsym 00006600

Bee08z2I ld .dynstr Qaooeeaa

0OO082bi! I a .gnu.version 08006600

800882CC I d .gnu.verston_r 68000088

000002fc ld .rel.dyn ~ 0aeoeeS9

8008032c ld .rel.plt 00000000

0000033c L d .Inlt 00000000

00000370 I d .pit 00005000

80088330 I d .text 08900800

8OO004b8 V. d. flnt 00009060

80000Id4 1 d .rodara 00000088

000004f8 I d .eh_frame_hdr 99000009

00006514 I d .eh'frane "90000000

eeeeifec ld .ctors 00000000

80OO1M4 L d .dtors 00009000

0OO01flc 1 d. jcr 00000000

0OO01f26 I d .dynamic 00000600

OOOOlfeS I d .got 00609000

00O01ff4 I d .got.pit 66006600

d .data oooeosee

d, bS5 00600000

d. comentário 86006608

d .debug_aranges 99000000

d .debug_tnfo 00006600

d .debug_abbrev 98006688

d .debug_line 96000000

d .debu g_ str 06006688

.note.gnu.build - id • gnu.hash • dynsyri .dynstr, gnu.version .gnu.verslon_r .rel.dyn .rel.plt

.Inlt ■ pit • text .fini

-rodata .eh_frane_hdr <!, frame

.ctors .dtors ■ Jcr

.conseguiu

.data .bss

.dinâmico

• got.pit

IIII

66088000 I 00088080 I 0O0000O0 I 00000006 I 00000900 I 06088006 I 86081f8C 0O001fl4 eeeeific 0oo003ao 0000200c 00082016 00000420 60000000 8009 If10 I 9O090S7O 0000 Ifle I 80099489
06000006 000004S7 I

00082088 0000200c

. confient .debug_aranges .debug_lnfo .debug_abbrev .debug_line .debug_str .debug_loc crtstuff.c _CT0k_LIST_

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 173/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
_otor "list_

_3CR_LIS1_

_d o_g loba l_d tor s_a ux

completado.6159 dtor_tdx.6iai

frd'e Jjnny

crtstuff.c

_CTOR_EHD_

__FR AHE_E ND_

_3CR _END_

_do_global_ctors_aux

sharedLlblFunctions.c

_1686.get_pc_thunk.bx

_DTOR_EN [> _ ~

_dso_handle

_DYNAHIC

_G L OBAL DE FSET TAB L E_

prlntfg (! GLlBC_2.8

_edata

_fini

_cxa_ftnallzeg0C.LIBe_2.1.3

__gnon_start_

_fim

_bss_start

_3v_RegisterClasses sharedLlblFunctlon nele

d. d ebu g _ loc 98000000

00001ff4 00090000 8609200c 060004bS 06698608 06099000 86602614 8809200c 00000009 886064Sc 0009933c mtlan ^ milanS

eu df *ABDÔMEN* 60660066
eu □ .ctors anaafluan UODuOODU
eu 0 .dtors 00600000
eu 0 ■ Jcr 00660860
eu F .texto 00600800
eu 0 .bss 60660001
eu 0 . bss 00808864
eu r .texto 00609000
eu df *ABDÔMEN* 00600800
eu 0 .ctors nflnnAnnn DwOuuvJvJO
eu 0 , eh_frame 1
eu 0 .jcr 00000000
eu F • texto 60900000
eu df *ABDÔMEN* 60060060
eu F .texto 60080086
eu 0 -dors 60900000
eu 0 .dados 00000000
eu 0 *ABDÔMEN* 00080080
eu 0 00000000

F * UND * 0GO00O00

g * A8S * ntiifîiiartJîiiaA OUUOOUBO

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 174/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
g F . fini 0O006O0O
C F * UND * 00000000
C • UNO * 00000000

g * A8S * 00000000

g *ABDÔMEN* afaramjajnact OUuDOuOD

C * UND * 00000000

g F • texto 0O000O1C
g F • inlt 00000066

Figura 12-11. Usando objdump para listar todos os símbolos

Listando apenas símbolos dinâmicos

A execução de objdump -T <path-to-binary> fornece uma saída que é o equivalente completo da execução de nm -D <path-to-binary> (Figura 12-12).

pitlangini'lan.S objdump -T li.bdenol.so libdeniol.so: arquivo fornat elf32-i386

TABELA DE SÍMBOLOS: DF * UND * w DF + UND * w D * UND * WD 0 0 0

00000000 00000000 00000000 00000000 OOOOOOOO 00000000 oooooeoo

OOOOOOOO 00000000 0000001C

GLIBC_2.0 CLIBC 2.1.

* UND * + ABS * * ABS * * ABS * DF .intt DF .fint DF .text

Base Base Base Base Base

prlntf

_cxa_finalize

_gnon_start_

_Jv_RegisterClasses

_edata

_fim

_bss_start

_init _flni

sharedLiblFunction

DINÂMICA 00000000 00000000 0OO0O0O0 00000000 0000200c 00002014 0000200c 0000033c OOO0O4b8 0000045c

Figura 12-12. Usando objdump para listar apenas símbolos dinâmicos

Examinando a seção dinâmica

A execução de objdump -p <path-to-binary> examina a seção dinâmica (útil para localizar as configurações DT_RPATH e / ou DT_RUNPATH ). Observe que
neste cenário você se preocupa com a parte final da saída exibida (Figura 12-13).

ciiTangmilanS objdunp -p denoApp denoApp: formato de arquivo elf64-x86-64 Cabeçalho do programa:

PHDR desligado 9x0000000600000040 vaddr 0x0O0O0OO00O3ffO40 paddr 0xO0O0O0O0003ff040 atig

ooo

Seção dinâmica:

PRECISAVA libpthread.so.0
PRECISAVA libdl.so.2
PRECISAVA libdynaciiclinkingdemo.so
PRECISAVA libstdc ++. so.6
PRECISAVA libci.so.6

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 175/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
PRECISAVA ltbgcc_s.so.1
PRECISAVA libc.so.6
RUNPATH ../deploy:./deploy
INICIAR 0x00000000004005d8
FINI 0x0000000000400808
CERQUILHA 0x00000000003ff4d0
GNU_HASH OxO0OO00OO0O3ff490
STRTAB OxO0O000O00O3ff27O
SYMTAB OxO0O000O0003ff388
STRSZ 0X0000000000000115
SYMENT 0X0000000000000018
DEPURAR 0X0000000000000000
PLTGOT 0X00000000006O0fe8
PLTRELSZ 0X0000000000000048
PLTREL 0X0000000000000007
JMPREL 0X0000000000400590
RELA 0x0000000000400578
RELASZ 0X0000000000000018
RELAENTAR 0x0000000000000018
VERNEED 0X0000000000400538
VERNEEDNUM 0x0000000000000002
VERSYM 0x0000000000400522

Referências de versão:

exigido de llbstdc ++. so.6:

0xO56bafd3 0x00 03 CXXABI_1.3 exigido de libc.so.6:

0x09691375 0x00 02 GLIBC_2.2.5

mtlan @ mtlan $

Figura 12-13. Usando objdump para examinar a seção dinâmica da biblioteca

Examinando a seção de realocação

A execução de objdump -R <path-to-binary> examina a seção de realocação (Figura 12-14).

milan ^ nllan $ objdunp -R sharedLib / libnreloc.so sharedLib / libnreloc.so: arquivo fornat elf32-i386

REGISTOS DE REALOCAÇÃO DINÂMICA OFFSET TYPE 00002008 R_386_RELATIVE 00000450 R_386_32 00000458 R_386_32 000O045d R_386_32 OOOOlfeS R_386_CL0B_DAT OOOOlfec
R_386_CL0B0038_GL2000_DATB_Olff6 R_386 000038_GL2000_DATB_Olff6 RLO6 000038_GL2000 JLOT_LOT RLOT82000 RLOT3000 RLO6000 RLO6 0000382000_GL2000_Olff6 RLO6003800_GL2000
JLOT3000_Olff6 R_386 0000380038_GL2000 JLOT_Olfec R_386 000038_GL2000_DATB_Oloff6

Figura 12-14. Usando objdump para listar a seção de realocação

Examinando a seção de dados

Executar objdump -s -j <nome da seção> <path-to-binary> fornece o dump hexadecimal dos valores transportados pela seção. Na Figura 12-15, é a seção
.got .

Figura 12-15. Usando objdump para examinar a seção de dados

Listando e examinando segmentos

Executar objdump -p <path-to-binary> exibe informações sobre os segmentos binários ELF. Observe que apenas a primeira parte da saída exibida pertence a
esta tarefa específica (Figura 12-16).

VALOR

*ABDÔMEN*

nyglob

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 176/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Fiyglob

nyglob

_cxa_flnallze

__gnon_start_

_Jv_RegisterClasses

_cxa_finalize

_gnon_start_

nilangmilanS objdump -p denoApp

demoApp: formato de arquivo elf64-x86-64

Cabeçalho do programa:

PHDR desativado 0x0000000060000040 arquivo vaddrSZ 0x0000000000000230 memSZ STACK desativado 0X00000000OO0010O0 arquivos vaddr; 0x0000000060000000 memsz carga fora
0x0000000000000000 vaddr filesz 0x0000000000001000 memsz INTERP off 0x0000000000000510 vaddr filesz 0x000000000000001c memsz carga fora 0x0000000000001000 vaddr filesz
Ox0800000060O008dc memsz NOTA off 0x0000000060001254 vaddr filesz 0x0000000060000044 memsz EH_FRAME off 0x000000000000181c vaddr filesz 0x0000000000000024 memsz carga fora
OxOO0000000000Idas vaddr filesz 0x0000000000000280 memsz RELRo off oxo000oo000000ida8 vaddr filesz 0x0000000060000258 memsz DYNAMIC desligado 0x0O0000O000e01dd0 vaddr

filesz 0x0000000000000210 memsz

0x00000600003ff040 paddr 0x0000000000000230 sinalizadores 0x0000000000000900 paddr 0x0000000000000000 sinalizadores Ox0O0O0OOO003ff000 paddr 0x0000000000001000 sinalizadores
0x0000O000003ff510 paddr 0x000000000000001c sinalizadores 0x0000000000400000 paddr

Sinalizadores 0x000000000O00O8de

0x00000000004002 54 paddr 0x0000000000000044 sinalizadores 0x000000000040081c paddr 0X0000000000000024 sinalizadores Ox0O0O000000600da8 paddr 0x0000000000000290 sinalizadores
0x0000O0000O600da8 paddr 0x0000000000000258 sinalizadores 0xO00OO00000600dd0 paddr 0x000000000210 sinalizadores

Ox0O00O00OO03ff040 rx

0X0000000000000000 rw-

OxOO0OOOO0OO3ffO0O rw-

0X0000O000003ff510

r- -

0x0000000000400000

rx

0x0000000000400254

r- -

0x00000000004008lc r- -

0xOO00OO0000600da8 rw-

0x0O0O60O0O0600da8

r--

0x00000000006o0dd0 rw-

alinhar 2 ** 3 alinhar 2 ** 3 alinhar 2 ** 12 alinhar 2 ** 0 alinhar 2 ** 12 alinhar 2 ** 2 alinhar 2 ** 2 alinhar 2 ** 12 alinhar 2 ** 0 alinhar 2 ** 3

Figura 12-16. Usando objdump para listar segmentos

Desmontando o Código

Aqui estão alguns exemplos de como objdump pode ser usado para desmontar o código:

• Desmontar e especificar o tipo de notação assembler (estilo Intel neste caso), conforme mostrado na Figura 12-17.

ntlangnilanS objdunp -d -Mintel llbnreloc.so | grep -A 10 ml_ O0OGO44C «ni runo:

44c 55 Empurre vazante

44d 89 e5 nov ebp, esp

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 177/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
44f al 00 00 00 00 nov eax, ds: 0x0
454 03 45 68 adicionar eax, DWORD PTR
457 a3 00 00 00 00 nov ds: oxe, eax
45C al 00 00 00 00 nov eax, ds: 0x0
461 03 45 0C adicionar eax, DWORD PTR

464 SD pop vazante

465 C3 ret

466 90 nop

mlan @ nilen5

mllan @ nllan $ objdunp -d -Mintel driver | grep -A 10

55 push ebp

89 e5 agora ebp, esp

B3 e4 fo e esp, 0xfffffff0

83 ec 20 sub esp, 0x20

C7 44 24 04 00 00 00 agora DWORD PTR [esp + 6x4], 6x0

00

c7 04 24 74 85 04 08 agora OWORD PTR [esp], 0x8048574

es 2d fe ff ff call 804849Q <dl_lterate_phdrgplt>

8b 45 08 agora eax, DWORD PTR [ebp + OxB]

08O48646 <maln>: 8048646: 8048647: 8048649: 804864c: 804864f: 8048656; 8048657: 804865e: 8048663: 8B48666: mllan @ nllan5

89 44 24 64 agora OWORD PTR [esp + 0x4], eax

Figura 12-17. Usando objdump para desmontar o arquivo binário

Desmontagem e estilo Intel e intercalando o código-fonte original (Figura 12-18).

nllangnllanS cbldurop -d -5 - m -Intel, / llbdenol.so | grep - a 26 0606045c <sharedLtblFunctton>: »Inclui" sharedLlbiFunctlons.h "

<sharedLiblFunction> *

sharedLiblFunction (lnt x) 55

89 e5 83 ec 18

printf ("sharedLlblFunct be d4 04 OS 00 8b 55 08 89 54 24 04

89 04 24

e8 fC ff ff ff

C9 c3

90 90 90 90 90 90 90 90

vazio {

45c: 45d: 45f:

462: 467: 46a: 46e; 471:

EU

476: 477:

478

479 47a 47b 47c 47d 47e 47f

Empurre vazante
nov ebp, esp
sub esp, 0xl8
em (Sd) é chamado de \ n *, x);

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 178/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
nov eax, 0x4d4
nov edx, DWORD PTR [ebp + OxS]
nov DWORD PTR [esp + 0x4], edx
nov DWORD PTR [esp], eax
ligar 472 <sharedLiblFunctlon + oxl6>
sair

ret

nop

nop

nop

nop

nop

nop

nop

nop

06000480 <_do_global_ctors_aux>: nllangnilan S

Figura 12-18. Usando objdump para desmontar o arquivo binário (sintaxe Intel)

Esta opção funciona apenas se o binário for construído para depuração (ou seja, com a opção -g ).

• Desmontagem de seções específicas.

Além da seção .text que contém o código, o binário pode conter outras seções (.plt, por exemplo) que também contêm o código. Por padrão, objdump
desmonta todas as seções que contêm o código. No entanto, pode haver cenários nos quais você esteja interessado em examinar o código realizado estritamente
por uma determinada seção (Figura 12-19).

objdump -d -M Intel -j .pit driver do driver: formato de arquivo elf32-l386

B8O48480 <strstr @ plt>: 8048489: ff 25 00

8048486: 68 08 06

804848b: e9 £ 0 ff

08048498 <prlntf @ plt>: 8048498: ff 2S 04

8048496: 68 08 00

804849b: e9 d8 ff

880484a8 <pil_funcgplt>: 8O484a0: "ff 25 08 80484a (S: 68 18 88

80484ab: e9 cs ff

Desmontagem da seção .pit;

Empurre

jnp

adicionar

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 179/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

jnp

Empurre

jnp

push jnp

jnp

jnp

Empurre

jnp

880484e8 <putchargplt>: 80484e0: ff 25 18

80484e6: 68 38 08

80484eb: e9 88 ff

(ntlan? nllan $

a8 04 08 jnp

88 00 push

ff ff jnp

como 04 88 jnp

80 O0 push

ff ff jnp

aO 04 88 jnp

88 push OO

ff ff jnp

aO 04 08 jnp

88 push OO

ff ff jnp

□ HORB PTR ds; 0X8049ff8 DWORD PTR tJs: 0X8049ffC byte PTR [eax], al

DWORD PTR ds: 0x8043000 0x6

8048476 <lnlt + 0x38>

DWORD PTR ds: 0 * 804a804 0x8

8048476 < lnlt + Ox3B>

DWORD PTR ds: Qx804aOO8 OX 10

8048476 <lnlt + 0X38>

DWORD PTR ds: 0x804a8Oc 0x18

8048476 <_init + 0x38?

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 180/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
DWORD PTR ds: 0x804a610 0x20

8048476 <Inlt + 0X38>

DWORD PTR ds: 0X3043614

0x28

8048476 * lnit + 0x38i

DWORD PTR ds: Ox804a61S 0x30

8048478 <lnlt + 0X38>

Figura 12-19. Usando objdump para desmontar uma seção específica

equivalentes nm objdump

objdump pode ser usado para fornecer equivalentes completos do comando nm :

• $ nm <path-to-binary> equivalente é

$ objdump -t <path-to-binary>

• $ nm -D <path-to-binary>

equivalente é

$ objdump -T <path-to-binary>

• $ nm -C <path-to-binary>

equivalente é

$ objdump -C <path-to-binary>

readelf

O utilitário de linha de comando readelf ( http://linux.die.net/man/lZreadelf ) fornece funcionalidade quase completamente duplicada encontrada no
utilitário objdump . As diferenças mais notáveis ​entre readelf e objdump são

• readelf suporta apenas o formato binário ELF. Por outro lado, o objdump pode analisar cerca de 50 formatos binários diferentes, incluindo o formato
Windows PE / COFF.

• readelf não depende da biblioteca Binary File Descriptor ( http://en.wikipedia.org/ wiki / Binary_File_Descriptor_library), da qual todas as
ferramentas de análise de arquivo de objeto GNU dependem, fornecendo assim uma visão independente do conteúdo do formato ELF

As duas seções a seguir fornecem uma visão geral das tarefas mais comuns que usam objdump.

Parsing ELF Header

A opção de linha de comando readelf -h é usada para obter uma visão do cabeçalho do arquivo de objeto. O cabeçalho fornece muitas informações úteis. Em
particular, o tipo binário (arquivo-objeto / biblioteca estática vs. biblioteca dinâmica vs. executável), bem como as informações sobre o ponto de entrada (o início
da seção .text ) podem ser obtidos rapidamente (Figura 12-20).

milan @ milan $ readelf -h driverApp / driver elf Cabeçalho:

Magia: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 00

Classe: ELF32

Dados: complemento de 2, pequeno endlan

Versão: 1 (atual)

OS / ABI: UNIX - System V

Versão ABI: 0

Tipo: EXEC (arquivo executável)

Máquina: Intel 80386

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 181/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Versão: Endereço do ponto de entrada Oxl:

0X80484C0

52 (bytes no arquivo) 6196 (bytes no arquivo) 0x0

52 (bytes)

32 (bytes) 9

40 (bytes) 36

33

Início dos cabeçalhos do programa: Início dos cabeçalhos da seção: Sinalizadores:

Tamanho deste cabeçalho: Tamanho dos cabeçalhos do programa: Número de cabeçalhos do programa: Tamanho dos cabeçalhos da seção: Número dos
cabeçalhos da seção: String do cabeçalho da seção índice da tabela: milan @ milan $

milan @ milan $ readelf -h sharedLib / libmreloc.so ELF Header:

Magic: 7f 45 4c 46 01 01 01 00 Classe: Dados: Versão: OS / ABI: ABI Versão: Tipo: Máquina: Versão:

Endereço do ponto de entrada: Início dos cabeçalhos do programa: Início dos cabeçalhos da seção: Sinalizadores:

00 00 00 00 00 00 00 00 ELF32

1
2 complemento de s, 1 (atual) Unix - System v 0

DYN (arquivo de objeto compartilhado)

Intel 80386

Oxl

0x390

52 (bytes no arquivo) 4884 (bytes no arquivo) 0x0

52 (bytes)

32 (bytes) 7

40 (bytes)

33 30

pequeno endian

Tamanho deste cabeçalho: Tamanho dos cabeçalhos do programa: Número de cabeçalhos do programa: Tamanho dos cabeçalhos da seção: Número dos
cabeçalhos da seção: String do cabeçalho da seção índice da tabela: mllan @ mllan $

Figura 12-20. (contínuo)

milan @ rnilan $ readelf -h mlmainreloc. ELF Header:

Magia: 7f 45 4c 46 01 01 01 00 00

Classe:

Dados:

Versão:

OS / ABI:

Versão ABI:

Modelo:

Máquina:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 182/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Versão:

Endereço do ponto de entrada: Início dos cabeçalhos do programa: Início dos cabeçalhos da seção: Sinalizadores:

Tamanho deste cabeçalho: Tamanho dos cabeçalhos do programa: Número de cabeçalhos do programa: Tamanho dos cabeçalhos da seção: Número dos
cabeçalhos da seção: String do cabeçalho da seção índice da tabela: mllan @ nilan $

00 00 00 oo oo 00 00 ELF32

Complemento de 2, iittle endian 1 (atual) UNIX - System V 0

REL (arquivo relocável)

Intel 80386

0x1

0x0

0 (bytes no arquivo) 832 (bytes no arquivo) 0X0

52 (bytes) 0 (bytes) 0

40 (bytes) 21 18

Figura 12-20. Exemplos de uso de readelf para examinar o cabeçalho ELF de executável, biblioteca compartilhada e arquivo de objeto / biblioteca estática

Ao examinar a biblioteca estática, readelf -h imprime o cabeçalho de cada arquivo de objeto encontrado na biblioteca.

Listando e examinando seções

A opção readelf -S é usada para listar as seções disponíveis (Figura 12-21).

nilan @ milan $ readelf -S libnreloc.so

Existem 33 cabeçalhos de seção, começando no deslocamento 0x1314

Cabeçalhos de seção

[Nr] Nane Modelo Addr Desligado Tamanho ES FIG Lk Inf Al


[0] NULO 00000000 000000 000000 00 0 0 0

[1] .note.gnu.build-i NOTA 00000114 000114 000024 00 UMA 0 0 4


[2] .gnu.hash GNUJHASH 00000138 000138 000040 04 UMA 3 0 4
I 3] .dynsyn DYNSYM 00000178 000178 0000b0 16 UMA 4 1 4
[4] .dynstr STRTAB 00000228 000228 00007c 00 UMA 0 0 1
[5] .gnu.version VERSYM 00O002a4 0O02a4 000016 02 UMA 3 0 2
[6] .gnu.version_r VERNEED 0O0002bc 0002bc 000020 00 UMA 4 1 4
[7] .rel.dyn REL O0O0O2dc 0002dc 000038 08 UMA 3 0 4
[8] .rel.plt REL 00000314 000314 000010 08 UMA 3 10 4
[9] • iniciar PROGBITS 00000324 000324 00002e 00 MACHADO 0 0 4
[10] .poço PROGBITS 00000360 000360 000030 04 MACHADO 0 0 16
[11] .texto PROGBITS 00000390 000390 000118 00 MACHADO 0 0 16
[12] .fini PROGBITS 0O00O4a8 0004a8 00001a 00 MACHADO 0 0 4
[13] .eh_frame_hdr PROGBITS OOO0O4C4 0OO4C4 00001c 00 UMA 0 0 4
[14] .eh_frame PROGBITS 0OO0O4e0 0OO4e0 000060 06 UMA 0 0 4
[15] , ctors PROGBITS O0O01fOc OOOfOc 000008 06 MA 0 0 4
[16] . dtors PROGBITS 00O01f14 0OOf14 000008 06 WA 0 0 4
[17] . jcr PROGBITS 00O01flC 000flc 000004 00 MA 0 0 4
[18] •dinâmico DINÂMICO 00001fZ0 0OOÍZ0 0000C8 08 MA 4 0 4
[19] .conseguiu PROGBITS O0O01fe8 0OOfe8 0O000C 04 MA 0 0 4
[20] .got.pit PROGBITS OOOOlff4 000 f f4 000014 04 MA 0 0 4
[21] .dados PROGBITS 00002008 001008 000008 00 wa 0 0 4
[22] . bss NOBITS 00002010 001010 000008 00 MA 0 0 4
[23] .Comente PROGBITS 00000000 001010 00002a 01 em 0 0 1
[24] .debug_aranges PROGBITS 00000000 00103a 000020 00 0 0 1

[25] .debug_info PROGBITS 00006000 00105a 000075 00 0 0 1

[26] , debug_abbrev PROGBITS 00000000 0010Cf OO0OÍ.8 00 0 0 1

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 183/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
[27] .debug_line PROGBITS 00000000 001127 00003f 00 0 0 1
[28] , debug_str PROGBITS 00000000 001166 00004c 01 em 0 0 1
[29] .debug_loc PROGBITS 00000000 0Ollb2 000038 00 0 0 1

[30] • shstrtab STRTAB 00000000 OOllea 000129 00 0 0 1

[31] .symtab SYMTAB 00000000 00183c 0O03b0 10 32 49 4

[32] .strtab STRTAB 00000000 OOlbee 000182 00 0 0 1

y para Bandeiras:

H (escrever), A (alocar), X (executar). M (mesclar), S (cordas)

I (informações), L (ordem do link), G (grupo), T (TLS), E (excluir), x (desconhecido) 0 (processamento de SO extra necessário) o (específico do SO), p (específico do processador)

Figura 12-21. Usando readelf para listar seções

Quando se trata de exames de seção, readelf fornece opções de comando dedicadas para as seções que são os tópicos de interesse mais frequentes para
programadores, como as seções .symtab, .dynsym e .dynamic .

Listando todos os símbolos

Executando readelf --symbols fornece uma saída que é o equivalente completo da execução de nm <path-to-binary> (Figura 12-22).

Freira: Valor Fenda


9: 69696969 6
1: 69000609 6
2: 69090909 6
3: 69000000 6
4: 09000000 6
S: 6900200c 6
9: 69692914 6
7: 6909290c 6
6: 0909033c 6
9: 696904b3 6
19: 690904SC 28

9 * nd Vis hdx

local predefinição uno

global oefault uno

fraco predefinição uno

fraco oefault uno

fraco predefinição uno

global oefault aos

global oefault bunda

global oefault bunda

global oefault 9
global predefinição 12
global predefinição 11

_edata _end

_bss start

_tnlt ftnt

ntlan @ ntlan $ readelf --synbols llbdemol.so A tabela Synbol '.dynsyiV contém 11 entradas:

FUNC FUNC

FUNC FUNC

Tabela de símbolos '♦ symtsb' Nun: Tamanho do valor

0: 09000000 0

1: 89000111 O

2: 09000138 0

3; 69000174 O

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 184/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
J; 69000224 o

5: esssejhí 8

4: 89090Jet O

7: 69090? FC O

s: 6900032c o

9: 0909033C O

1 «: essas 370 9

11: 69896330 6

12: 696964ba 8

13: 090904d4 O

14: 090904fa O

IS: 09090S14 o

16: 8989lf0C 8

17: 6969lf14 6

19: 69891flc 8

19: 69891f20 8

28: 0909ifea o

11: 69091ff4 O

22: 09882BBS 8

13: 6909200c 6 24: 25:

26: 09090909 O

27: 69898000 6

29: 60090000 8

29: 60666000 6

36: 69000000 0

32: 6900lf0C 0

33: 090®lfl4 0

34: eoeoific e

35: 000003a0 0

36: 0000200c 1

37: 60002016 4

38: 69000420 6

39: 60000000 8

46: 8089lf16 8

41: 80890576 8

42: 69661flc 6

43: 69690466 6

44: 69890000 6

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 185/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
45: 69090457 8

46: 6066lf19 8

47: 60662669 8

46: 6066lf26 6

49: 66661ff4 O

56: 69090006 6

51: 8080200c 8

52: 600QO4bB 8 S3: 54:

5S: 66662614 8

56: 6906286c 6

59: 8686845c 28

59: 6666033c 6 nllan £ pllan $

contém M entradas:

Tipo Otnd Vis

NOTYPE LOCAL OtfAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 186/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DFFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

SEÇÃO LOCAL DEFAULT

ARQUIVO PADRÃO LOCAL

093ECT LOCAL DEFAULT

PADRÃO LOCAL DO OBJETO

PADRÃO LOCAL DO OBJETO

FUNC LOCAL DEFAULT

PADRÃO LOCAL DO OBJETO

PADRÃO LOCAL DO OBJETO

FUNC LOCAL DEFAULT

ARQUIVO PADRÃO LOCAL

PADRÃO LOCAL DO OBJETO

PADRÃO LOCAL DO OBJETO

PADRÃO LOCAL DO OBJETO

FUNC LOCAL DEFAULT

ARQUIVO PADRÃO LOCAL

FUNC LOCAL DEFAULT

PADRÃO LOCAL DO OBJETO

PADRÃO LOCAL DO OBJETO

PADRÃO LOCAL DO OBJETO

PADRÃO LOCAL DO OBJETO

FUNC GLOBAL DEFAULT

PADRÃO GLOBAL NOTYPE

FUNC GLOBAL DEFAULT

FUNC FRACO PADRÃO

NÃO TIPO PADRÃO FRACO

PADRÃO GLOBAL NOTYPE

PADRÃO GLOBAL NOTYPE

NÃO TIPO PADRÃO FRACO

FUNC GLOBAL DEFAULT

FUNC GLOBAL DEFAULT

Ndx Mane UNO 1 2 J

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 187/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
5

8 9

16 11 12

13

14

15

16

19 26

21 22

26

71 28

crtstuff.c

_CT0R_LIST_

_DTOB_LrST_

_: CR_LIST_

_do_g1obal_d para r s_au x

C0ftplitfd.61S9 dtor_Ldx.6161 frane_dijpwy crtstuff.c

_CTOR_ENB_

_FftAH £ _EN [> _

_JCR_ £ MD_

_dú_globaL_c para r s_au x

sharedL.lblFLinctlons.c

_1989.get_pc.Chunk.bx

_DTOR_END_

__dso_handle

_DYNAÍlC

~ G LOBAL_OFF 5ET_TABLE_ prtntf # §GLrBC_2.0 _tdata flni

3_c xa_fIn alLze | $ G LIBC_2.1

_gnon_start_

_fim

_bss_start

_Jv_Reg L ste rcIa s ses s ha redL Lbl F Ljnctl on tnlt

Figura 12-22. Usando readelf para listar todos os símbolos

Listando apenas símbolos dinâmicos

Executar readelf --dyn-syms fornece uma saída que é o equivalente completo da execução de nm -D <path-to-binary> (Figura 12-23).

ntlan @ milari $ readelf --dyn-syns libdertol.so

A tabela de símbolos '.dynsym' contém o tipo NOTYPE FUNC FUNC NOTYPE NOTYPE NOTYPE NOTYPE NOTYPE FUNC FUNC FUNC

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 188/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
11 entradas:

Stnd VtS Ndx


LOCAL PREDEFINIÇÃO UND
GLOBAL PREDEFINIÇÃO UND
FRACO PREDEFINIÇÃO UND
FRACO PREDEFINIÇÃO UND
FRACO PREDEFINIÇÃO UND
GLOBAL PREDEFINIÇÃO abdômen
GLOBAL PREDEFINIÇÃO abdômen
GLOBAL PREDEFINIÇÃO abdômen
GLOBAL PREDEFINIÇÃO 9
GLOBAL PREDEFINIÇÃO 12
GLOBAL PREDEFINIÇÃO 11

Freira: valor Tamanho


0: 00000000 0
1: 90000000 0
2: 00000000 0
3: 00000000 0
4: 00000000 0
5: 0000200c 0
6: 00002014 0
7: 0000200c 0
8: 0000033c 0
9: 000O04b8 0
19: 0000045c 28
pitlan @ milan $

Edata End

_bss_start

_init _fini

sharedLlblFunctton

Figura 12-23. Usando readelf para listar símbolos dinâmicos

Examinando a seção dinâmica

A execução de readelf -d examina a seção dinâmica (útil para localizar as configurações DT_RPATH e / ou DT_RUNPATH ), conforme mostrado na Figura 12-
24.

nilan ^ Fiilan $ readelf -d denoApp

A seção dinâmica no deslocamento OxlddO contém 28 entradas Nome / valor do tipo de tag

OX0OD0000O0000000 1 (PRECISAVA) Biblioteca compartilhada [libpthread.so.0]


0X0000000000090001 (PRECISAVA) Biblioteca compartilhada [libdl.so.2]
9X0000000000000001 (PRECISAVA) Biblioteca compartilhada [libdynaniclinkingdeno
0X0000000000000001 (PRECISAVA) Biblioteca compartilhada [libstdc ++. so.6]
0X0000000000000001 (PRECISAVA) Biblioteca compartilhada [libm.so.6]
0x0000000000090001 (PRECISAVA) Biblioteca compartilhada [libgcc_s.so.l]
0x0000000000000001 (PRECISAVA) Biblioteca compartilhada [llbc.so.6]
Ox000O00OOOO00O01d (RUNPATH) Caminho de execução da biblioteca: [,. / Implantar: ./ implantar]

0x000000000000000c (1NIT) Ox4O05d8

0X0000000000000000 (FINI) 0X400808

0X0000000000000004 (CERQUILHA) 0x3ff4dO

0xO00O000O6ff ffef5 (GNUJHASH) 0x3ff490

0X0000000000000005 (STRTAB) 0x3ff 270

0X0000000000000006 (SYMTAB) 0x3ff388

0x000000000000000a (STRSZ) 277 (bytes)

0x000000000000000b (SYMENT) 24 (bytes)

0X0000000000000015 (DEPURAR) 0x0

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 189/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
0X0000000000000003 (PLTGOT) 0x600fe8
0x0000000000000002 (PLTRELSZ) 72 (bytes)

0x0000000000000014 (PLTREL) RELA

0x0000000000000017 (3MPREL) 0x400590

0X0000000000000007 (RELA) 0X400S78

0x0000000000000008 (RELASZ) 24 (bytes)

0x0000000000000009 (RELAENTAR) 24 (bytes)

OxO00O000O6ffffffe (VERNEED) 0X400538

OxO00000006fffffff (VERNEEDNUM) 2

OxO00O0O0O6ffffff0 (VERSYM) 0X400522

0x0000000000000000 (NULO) 0x0

nilan @ nilan $

Figura 12-24. Usando readelf para exibir a seção dinâmica

Examinando a seção de realocação


A execução de readelf -r examina a seção de realocação, conforme mostrado na Figura 12-25.

milangjmilanj readelf -r llbmreloc.so

A seção de realocação '.rel.dyn' no deslocamento 0x2dc contém 7 entradas:

Modelo

386_RELATIVE 386_32 386_32 386_32

386_GL0B_DAT 386_CL06_0AT 386 GLOB DAT

Offset 00002908 00000450 00000458 OO0O04Sd OO001fe8 OOOOlfec ooeoiffo

Info 00000008 00000401 00000401 00000401 00000106 00000206 00000306

Sym, Value Sym, Nane

0000200c myglob

6000200c myglob

0000200c myglob

00000000 _cxafinalize

00000000 _gmon_start_

0O0O0O0O JvRegisterClasses

A seção de realocação '.rel.pit' no deslocamento 6x314 contém 2 entradas: Offset Info Type Sym.Value Sym. Nane

00002800 00600167 R_38JUMP_SLOT 60066000 _cxa_finalize

00002004 00000207 R_386_3UMP_SL0T 00000000 _gmon_start_ mllan @ milan $

Figura 12-25. Usando readelf para listar a seção de realocação (.rel.dyn)

Examinando a seção de dados

A execução de readelf -x fornece o dump hexadecimal dos valores transportados pela seção. Na Figura 12-26, é a seção .got . Figura 12-26. Usando readelf para
fornecer um dump hexadecimal de uma seção (a seção .got neste exemplo)

Listando e examinando segmentos

Executar readelf --segments exibe informações sobre os segmentos binários ELF (Figura 12-27).

CAPÍTULO 12 ■ CAIXA DE FERRAMENTAS LiNUX

milanlilmilanS readelf --segments libmreloc.so

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 190/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O tipo de arquivo Elf é DYN (arquivo de objeto compartilhado) Ponto de entrada 0x393

Existem 7 cabeçalhos de programa, começando no deslocamento 52

Cabeçalhos do Progran

Modelo Desvio VirtAddr PhysAddr FileSiz MemSiz Vergonhoso Alinhar


CARGA 0x000000 0x00000000 0x00000000 0X00540 0X00540 RÉ 0x1000
CARGA OxOOOfOc 0X000Olf0C 0X0O001f0C 0x00104 OX0O10C RW 0X1OO0
DINÂMICO OxQOOf20 0X000Olf2O 0xOO001f20 0X000C8 OX0OOC8 RM 0X4
NOTA 0x000114 0X00000114 0x00000114 0x00024 0x00024 R 0x4
GNU_EH_FRAME OX0O04C4 0XOO00O4C4 0X0O00O4C4 0x0001c OxOOOlC R 0X4
GNUSTACK 0X000000 0X00000000 0X00000000 0X00000 0x00000 RW 0X4
GNU RELRO OxOOOfOc 0X0000If0C 0XOO001f0C OxOOOf4 OxOOOf4 R 0X1

Mapeamento de seção para segmento: Seções de segmento ...

00 .note.gnu.build-id .gnu.hash .dynsyn .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .pit .text .fini .eh_frame_hdr .eh_frame

01 .ctors .dtors -jcr .dynamic .got .got.pit .data .bss

02 .dynamic

03 .note.gnu.build-id

04 .eh_frame_hdr

05

06 .ctors .dtors .jcr .dynamic .got

milan @ riilan $

Figura 12-27. Usando readelf para examinar segmentos

Detectando a compilação de depuração

O comando readelf tem um suporte muito bom para exibir todos os tipos de informações específicas de depuração contidas no binário (Figura 12-28).

Ferramentas de desenvolvimento GNU READELF (l)

- Exibe informações sobre arquivos ELF.

[-a | --all] [-hj - file-header] [-1 | --program-headers | --segments] [-S | --section-headers | --sections] [-g | - -section-groups] [-t j - section-details] [-e | --headers] [-s | --sypis | - -symbols] [- -dyn-syns] [-n | --notes] [-r
| --relocs] [-u | - -unwind] [-d | --dynamic] [-V | --version-info] [-Aj - específico do arco] [-D | --use-dynamic]

[-x <número ou nome> | --hex-dump = -; número ou nane>] [-p «número ou nomes | --string-dump = <número ou nome>] [-R <número ou nome> | --relocated-dump = <número ou nome>] [-c | --archive-index] [-w
[lLlaprmfFsoRt] | --debug-dump [^ rawline ^ decodedline ^ info. ^ abrev ^ pubnames ^ aran ges, = macro, = fraries, = frames-interp, = str, = loc, = Ranges, = pubtypes, = trace_info, = trace _abbrev , =
trace_aranges, = gdb_index]]

Figura 12-28. Readelf oferece a opção de examinar as informações de depuração do arquivo binário

Para determinar rapidamente se o binário foi construído para depuração ou não, no caso de uma compilação de depuração, a saída da execução de readelf --
debug-dump com qualquer uma das opções disponíveis será composta de um número de linhas impressas em stdout. Ao contrário, se o binário não foi criado
para depuração, a saída será uma linha vazia. Uma das maneiras rápidas e práticas de limitar o spew de saída no caso em que o binário contém as informações de
depuração é canalizar a saída readelf para o comando wc :

$ readelf --debug-dump = linha <caminho do arquivo binário> | wc -l

Alternativamente, o seguinte script simples pode ser usado para exibir o readelf 's descobertas em forma de texto puro e simples. Requer que o caminho para o
arquivo binário seja passado como um argumento de entrada.

arquivo: isDebugVersion.sh

if readelf --debug-dump = linha $ 1> / dev / null; então echo "$ 1 é construído para depuração"; fi

Ferramentas da fase de implantação


Depois de construir seus arquivos binários com sucesso e começar a pensar sobre os detalhes do estágio de implantação, utilitários como chrpath, patchelf,
strip e ldconfig podem ser úteis.

READELF (l) NOME

readelf

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 191/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
SINOPSE

readelf

chrpath

O programa utilitário de linha de comando chrpath ( http://linux.die.net/man/1Zchrpath ) é usado para modificar o rpath ( campo DT_RPATH ) dos
binários ELF. O conceito básico por trás do campo runpath é descrito no Capítulo 7, na seção "Regras de localização da biblioteca de tempo de execução Linux".

Os detalhes a seguir ilustram o uso (Figura 12-29), bem como algumas das limitações (Figura 12-30) do chrpath:

• Pode ser usado para modificar DT_RPATH dentro de seu comprimento de string original.

• Pode ser usado para excluir o campo DT_RPATH existente . No entanto, tenha cuidado!

• Se a string DT_RPATH estiver inicialmente vazia, ela não pode ser substituída por uma nova string não vazia.

• Pode ser usado para converter DT_RPATH em DT_RUNPATH.

• Não pode substituir a string DT_RPATH existente pela string mais longa.

nilan (Sntlan $ readelf -d decio_rpath | grep RPATH

oxoooeoeof (RPATH) Biblioteca rpath: [/ hone / nilan / Desktop / Test]

ntlan @ r »iilan $ chrpath -r / hone / john / Desktop / Test ./demo_rpath ./demo_rpath: RPATH = / hone / itilan / Desktop / Test

. / demo_rpath: novo RPATH: / hone / john / Desktop / Test 1) pode modificar o RPATH existente dentro do

comprimento da corda original

Biblioteca rpath: [/ hone / john / Desktop / Test]

2) pode converter RPATH em RUNPATH

milan @ nilan $ readelf -d demo_rpath | grep RPATH

OxOOOOOeOf (RPATH) nilan (5nilan $ chrpath -c ./deno_rpath ./derco_rpath: RPATH convertido em RUNPATH ./deno_rpath: RUNPATH = / hone / john / Desktop / Teste nllan (j] nilan $ readelf -d demo_rpath |
grep PATH

OxOOOOOeid (RUNPATH) Caminho de execução da biblioteca: [/ hone / john / Desktop / Test]

nilan @ niian $ 3) não posso tornar a string RPATH mais longa

nilan @ nllan $ chrpath -r / excepttonally / long / new / rpath / para / deno / finalidades, / deno_rpath, / deno_rpath: RUNPATH = / hone / john / Desktop / Test

novo rpath '/ exceptlonaily / long / new / rpath / para / deno / finalidades' muito grande; comprimento naxinun 24 nilan (0ntlan $

nilan (Bnilan $ readelf -d denorpath | grep PATH

Biblioteca Qxooeooaof (RPATH) rpath: [/ hone / mllan / Desktop / Test]

nUan (3nUan $ chrpath -d deno_rpath chrpath pode deletar o RPATH existente

nilan (0nilan $ readelf -d deno_rpath] grep PATH nllan @ ntlan $

Figura 12-29. Usando o utilitário chrpath para modificar RPATH

nt "l3n @ eiUan $ Is -alg total 12

drwxrwxr-x 2 nilan 4096 28 de abril 12:30. drwxr-xr-x 4 nilan 4096 28 de abril 12h14 .. -rw-rw-r-- 1 nilan 95 28 de abril 12h15 nain.cpp nilan @ nilan $ gcc nain.cpp -o deno_no_rpath_set_inltially nllan ^ nllanj
readelf - d ./dei6 io_no_rpath_set_lnltially

A seção dinâmica no deslocamento 0xf28 contém 20 entradas:

Marcação Modelo Nacie / Value


0x00600901 (PRECISAVA) Biblioteca compartilhada: [libc.so.6]
0X0O00O00C (INICIAR) 0x80482b0
Qxooeooood (FINI) 0x804849c
0x6ffffef5 (CNUHASH) 0x8048 lac
0x00000005 (STRTAB) 0x804821c
0X00000006 (SYMTAB) 0x8048 lcc
0x0000000a (STRSZ) 74 (bytes)

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 192/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
0x0000000b (SYMENT) 16 (bytes)
0XO000001S (DEPURAR) 0X0
0x00000003 (PLTGOT) 0x8049ff4
0X00000002 (PLTRELSZ) 24 (bytes)
REL
0x00000014 (PLTREL)
se a string RPATH for diferente
0x00000017 (JMPREL) 0x8048293 (inexistente) chrpath não pode
0X00000011 (REL) 0x8048290 substituí-lo por um novo não vazio
0X00000012 (RELSZ) Valor 8 (bytes)
0X00000013 (RELENT) a (bytes)
0x6ffffffe (VERNEED) 0x8048270
Oxófffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x8048266
0x00000000 (NULO) 0x0

ntlan @ milan $ chrpath -c / hone / nilan / Desktop / ./deno_no_rpath_set_lnitially open: É um diretório elfopen: É um diretório nilan @ nilan $

Figura 12-30. Limitações do utilitário chrpath

remendar

O programa utilitário de linha de comando patchelf ( http://nixos.org/patchelf.html ) útil atualmente não faz parte dos repositórios padrão, mas é
possível construí-lo a partir do tarball de origem. Documentação simples e básica também está disponível.

Este utilitário pode ser usado para definir e modificar o caminho de execução ( campo DT_RUNPATH ) dos binários ELF. O conceito básico por trás do campo
runpath é descrito no Capítulo 7, na seção "Regras de localização da biblioteca de tempo de execução Linux". A maneira mais simples de configurar o caminho de
execução é emitir um comando como este:

$ patchelf --set-rpath <um ou mais caminhos> <executável>

UMA

Os recursos do patchelf de modificar o campo DT_RUNPATH excedem em muito os recursos do chrpath de modificar o campo DT_RPATH , pois ele pode
modificar o valor da string de DT_RUNPATH de qualquer maneira imaginável (substituindo por uma string mais curta ou mais longa, inserindo vários caminhos,
apagando, etc. )

faixa

O programa utilitário de linha de comando strip ( http://linux.die.net/man/1/strip ) pode ser usado para eliminar todos os símbolos da biblioteca que
não são necessários no processo de carregamento dinâmico. Uma ilustração dos efeitos de faixa foi demonstrada no Capítulo 7, na seção "Exportando os símbolos
da biblioteca dinâmica do Linux".

ldconfig

No Capítulo 7 (dedicado às regras de localização de bibliotecas em tempo de execução do Linux), indiquei que uma das maneiras (embora não seja a prioridade
mais alta) para especificar os caminhos onde o carregador deve procurar bibliotecas em tempo de execução é através do uso de cache ldconfig .

O programa utilitário de linha de comando ldconfig ( http://linux.die.net/man/8Zldconfig ) é normalmente executado como a última etapa de um
procedimento de instalação de pacote. Quando um caminho contendo a biblioteca compartilhada é passado como argumento de entrada para ldconfig, ele
pesquisa o caminho para as bibliotecas compartilhadas e atualiza o conjunto de arquivos que usa para contabilidade:

• O arquivo /etc/ld.so.conf contendo a lista de pastas que ele verifica normalmente

• O arquivo /etc/ld.so.cache , que contém a lista textual ASCII de todas as bibliotecas encontradas nas varreduras de vários caminhos que foram passados ​
como argumento de entrada

Ferramentas de análise de tempo de execução


As análises de problemas de tempo de execução podem se beneficiar usando ferramentas como strace, addr2line e especialmente GNU debugger (gdb).

strace

O programa utilitário de linha de comando strace ( http://linux.die.net/man/1/strace ) rastreia as chamadas do sistema feitas pelo processo, bem
como os sinais recebidos pelo processo. Pode ser útil para descobrir as dependências de tempo de execução (ou seja, não apenas dependências de tempo de
carregamento para as quais o comando ldd é adequado). A Figura 12-31 ilustra a saída típica do strace.

nilaii @ milan $ strace

execvef ./driver ", [" ./driver "], [/ * 36 vars * /]) = 0

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 193/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
brk (e) = oxaecfeoo

accessf / etc / ld.so.rohwcap ", F_OK) = -1 ENOENT ( Não existe esse arquivo ou diretório)

mnap2 (NULL, 8192, PROTREAD | PROTWRITE, MAPPRIVATE | MAPANONYMOUS, -1, 0) m 8xb773aOO0

accessf /etc/ld.so.preload ", R_OK) = -1 ENOENT (Não existe tal arquivo ou diretório)

openf ../ sharedLlb / tls / l686 / sse2 / criov / llbiireloc.so ", O_RDONLY | O_CL0EXEC) = -1 ENOENT (nenhum arquivo ou diretório)

abra {",. / sharedLib / tls / L686 / sse2 / lU> i" ireloc. então ", OJtDONlY | 0_CL0EXEC) openf. ./sharedLtb/tls/lese/crnov/llbfireloc.so",
ORDONLY | OCLOEXEC) enoent (Nenhum arquivo ou diretório) enoent (Nenhum arquivo ou diretório)

= -1 = -1

openf. ./sharedLlb/tls / l & 86 / VLbnreloc. so ", o_rdonly | o_cloexec) = -l enoent (nenhum arquivo ou diretório) openf .. / sharedLib / tls /
sse2 / cnov / libnreloc.so", 0_RD0NLY | 0_CL0EXEO = -1 ENOENT (Nenhum arquivo ou diretório) openf . ./sharedLib/tlS/$Se2/UbPirelOC.SO ",
0_RDONLY | 0_CLOEXEC) = -1 ENOENT (Não existe esse arquivo ou diretório) aberto {" ../ sharedLlb / tls / cmov / llbi "ireloc.so" 0
p
RDONLYIO ^ CLOEXEC) = -1 ENOENT (Nenhum arquivo ou diretório)., / SharedLib / tls / libnreloc.so ", 0_RD0NLY | 0_CL0EXEO = -1 ENOENT
(Nenhum arquivo ou diretório) ../sharedLib/i686/sse2/cmov /llbmreloc.so ", oroonly | o ^ cloexec) = -1 enoent (Nenhum arquivo ou diretório)
../sharedLlb/l686/sse2/llbnreloc.so", 0_roonly | 0_cl0exec) = -1 enoent(Não existe esse arquivo ou diretório)
../sharedLtb/i686/cinov/libnreloc.so ", O ~ rd0nly | O ~ CL0EXEC) - -1 ENOENT (Não existe esse arquivo ou diretório)

,. / sharedLib / i686 / libnreloc.so ", 0_RD0NLY | 0_CL0EXEC) = -1 ENOENT (nenhum arquivo ou diretório)

../sharedLib/sse2/cnov/llbnreloc.so ", 0_RDDNLY | Q_CLOEXEC) = -1 ENOENT (Não existe esse arquivo ou diretório) openf.
./sharedLlb/sse2/Ubmreloc.so'*, O_RD0NLY | O_CL0EXEC) = t - 1 ENOENT (Nenhum arquivo ou diretório) openf. ./SharedLib/cnov/Ubmreloc.so ",
O_RD0NLY | O_CL0EXEC) = -1 ENOENT (Nenhum arquivo ou diretório) openf. ./sharedLlb/llbmreloc.so ", 0_FtDONLY | o_CLOEXEC) = 3

leia {3, "\ 177ELF \ l \ l \ l \ O \ 0 \ a \ O \ 0 \ 0 \ 0 \ e \ e \ 3 \ O \ 3 \ 0 \ l \ O \ 0 \ 0 \ 260 \ 3 \ 0 \ aO04 \ 0 \ O \ e "... , 512) = 512 fstat64 {3,
{st_mode = S_IFR EG10775, st_size = 7727, ...}) - 0

getcwd ("/ horie / nilaii / Desktop / Test / loadTti'ief! elocatlon / example2 / driverApp", 128) = 63 nnap2 (NULL, 8216, PR0T_READ |
PR0T_EXEC, NAP_PRIVATE | MAP_DENYWRITE, 3, 0) = 0xb7737000

rinap2 (exb77380O0, 8192, PROTREAOIPROTWRITE, MAPPRIVATE | MAPFIXEO | NAP_DENYWRITE, 3, 0) = Oxb7738O0O fechar (3) = O

,,
open < ../sharedLlb/tls/l686/sse2/cnov/T.lbc.so.6 ", 0_RDONLY | O_CLOEXEC) = -l ENOENT (Não existe esse arquivo ou diretório)

openC openC openC openC open ('openC

abrir {"../ sharedLib / tIs / i686 / sse2 / libc. so. 6", 0_RD0NLY | 0_CL0EXEC) openf ../ sharedLib / tls / l686 / cnov / llbc.so.6 ",
0_RD0NLY | 0 CLOEXEC)

- -1 = -1

ENOENT (Sem esse arquivo ou diretório) ENOENT (Sem esse arquivo ou diretório)

openf ../ sharedLib / tls / l686 / libc.so. 6 ", o_rdonly | o_cloexec) = -l enoent (nenhum arquivo ou diretório)

../sharedLib/tls/sse2/cnov/Ubc.so,6 ", 0_RD0NLY | 0_CL0EXEC) - -1 ENOENT (Não existe esse arquivo ou diretório),. / sharedLib / tls / sse2
/ libc.so, &", 0_RD0NLY | 0_CL0EXEC) = -1 ENOENT (Nenhum arquivo ou diretório) ../sharedLlb/tls/cmov/llbc.so.6 ", 0_RD0NLY] 0_CL0EXEC) = -l
ENOENT (Nenhum arquivo ou diretório) ../sharedLib/ tls / libc.so.e ", 0_RD0NLY | 0_CL0EXEC) = -1 ENOENT (Nenhum arquivo ou diretório)
../shared Lib / 1686 / sse2 / cciov / libc. então, 6 ", 0_RD0NLY | Q_CL0EXEC) = -1 ENOENT (nenhum arquivo ou diretório)

../sharedLlb/l686/sse2/llbc.so.6 ", o_roonly | o_cloexec} ../sharedLib/i686/cnov/libc.so.6", 0_RD0NLY [0_CL0EXEC)

../sharedLib/i686/libc-so.6 ", 0_RD0NLY | 0_CL0EXEC) - -1 ENOENT (Não existe esse arquivo ou diretório)

., / sharedLib / sse2 / cnov / llbc, so.6 ", OROONLY | OCLOEXEC) ../sharedLlb/sse2/llbc.so,6. ./sharednb/cmov/llbc.so.6

../sharedLib/llbc.so.fl ", 0_rd0nty | 0_cl0exec) = -1 enoent (nenhum arquivo ou diretório) aberto {" / etc / Id.so.cache ", 0 _rd0nly | 0 _cl0exec)
= 3 fstat64 { 3 {st_mode = s_ifr EG 1 0644, st_size = 70505, ...}) - 0 Fimap2 (nulo, 70595, pr0t_read, nap_private 3, O) = Oxb7725O0O
1 1

openf openf openC openC openC openC openC openf openf openC openC openC

enoent (sem esse arquivo ou diretório) ENOENT {Sem esse arquivo ou diretório)

= -1 = -1

= -1 ENOENT {Nenhum tal lima ou diretório) 0_RD0NLY | 0_CL0EXEC) «-1 ENOENT (Nenhum tal lima ou diretório) 0I rD 0 NLY | 0_ CL 0 EXEC) =
-1 ENOENT (Nenhum arquivo ou diretório)

Figura 12-31. Usando o utilitário strace

addr2line
file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 194/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O programa utilitário de linha de comando addr2line ( http://linux.die.net/man/1/addr2line ) pode ser usado para converter o endereço de tempo de
execução em informações sobre o arquivo de origem e o número da linha correspondente ao endereço.

Se (e se apenas) o binário for construído para depuração (passando os sinalizadores de compilador -g -O0 ), usar este comando pode ser muito útil ao
analisar informações de travamento em que o endereço do contador do programa onde o travamento ocorreu é impresso no terminal tela como algo assim:

# 00 pc 0000d8cc6 /usr/mylibs/libxyz.so

A execução de addr2line nessa saída de console $ addr2line -C -f -e /usr/mylibs/libxyz.so 0000d8cc6 resultará em uma saída que pode ser
semelhante a esta: /projects/mylib/src/mylib.c: 45

gdb (GNU Debugger)

A lendária ferramenta de depuração GNU conhecida como gdb pode ser usada para realizar a desmontagem do código em tempo de execução. A vantagem de
desmontar o código em tempo de execução é que todos os endereços já foram resolvidos pelo carregador e os endereços são em sua maioria finais.

Os seguintes comandos gdb podem ser úteis durante a desmontagem do código de tempo de execução:

• definir tipo de desmontagem <intel | att>

• desmontar <nome da função>

Os dois sinalizadores a seguir podem ser úteis ao invocar o comando disassemble:

• O sinalizador / r requer que as instruções do montador sejam mostradas adicionalmente em notação hexadecimal (Figura 12-32).

(gdb) set disassembly-flavour Intel

(gdb) desmontar / r principal

Dunp de código assembler para função principal

0X38048875 <+0>: 55 Empurre vazante

0X08048876 <+ i>: 89 eS mov ebp, esp

0X08048878 <+3>: 83 e4 para e esp, 0xfffffff0

0X0804887b <+6>: 83 ec 20 sub esp, 0x20

0x08O4887e <+9>: c7 44 24 14 03 00 00 00 mov DWORD PTR [esp + 0xl4], 0x0


0x08048886 <+17>: c7 44 24 04 00 03 00 00 mov DWORD PTR [esp + 0x4], 0x0
0X0804888e <+25>: C7 04 24 5f 87 04 08 mov DWORD PTR [esp], 0X804875
0X08048895 <+32>: e8 aõ fc ff ff ligar 0x8048540 <dl_iterate_phdr @ plt>
0X0804889a <+37>: 31 30 30 04 08 mov eax, ds: 0x804a030

0X0804889f <+42>: 83 C0 01 adicionar eax, 0xl

0X080488a2 <+45>: 89 44 24 18 mov DWORD PTR [esp + 0xl8], eax

0X08048836 <+49>: 8b 4S 08 mov e3X, DWORD PTR [ebp + 0X8]

0x080488a9 89 44 24 04 mov DWORD PTR [esp + 0x4], eax

0X080488ad c + 56>: 8b 44 24 18 mov eax, DWORD PTR [esp + 0xl8]

0x080488bl <+66>: 89 04 24 mov DWORD PTR [esp], eax

0x080488b4 <+63>: e8 a7 fc ff ff ligar 0x8048560 <initiali2e @ plt>

0X08048809 <+68>: 89 44 24 14 mov DWORD PTR [esp + 0xl4], eax

0x080488bd <+72>: b8 f8 8b 04 08 mov eax, 0x8048bf8

0X080488C2 <+77>: 8b 54 24 14 mov edx, DWORD PTR [esp + 0xl4]

0X080488C6 <+817 : 89 54 24 Oc mov DWORD PTR [esp + Oxc], edx

(gdb) desmontar / m principal

Despejo do código assembler para a função principal:

117 {

0xo8o48875 <+0>: push ebp

0x08048876 t + l>: mov ebp, esp

0x08048878 <+3>: e esp , Oxf ffffffe

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 195/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
0x0804887b <+6>: sub esp, 0x29

118 int t = 0;

0xO8O4887e <+9>: mov DWORD PTR [esp + 0xl4], 0x0

119 dl_lterate_phdr (header_handler, NULL); 0x08048886 <+17>; mov DWORD PTR [esp + 0x4], 0x0 0x0804888e <+25>: mov DWORD PTR [esp], 0x804875f 0XO8O4889S <+32>: chamar 0x8048540
<dl_Uerate_phdr @ plt>

120

121 int primeiro = shllbNonStatlcAccessedAsExternvarlable 4 l; 0x0804889a <+37>: mov eax, ds: 6x804a030

0x0804889f <+42>: adicionar eax, 0x1

0xO8O488a2 <+45>: mov DWORD PTR [esp + 0xl8], eax

122 t = Inltlallze (primeiro, argc); 0xO8O488a6 <+49>: mov eax, DWORD PTR [ebp + 0x8]

Figura 12-33. Sabor de desmontagem intercalada (montagem e código-fonte)

Para combinar esses dois sinalizadores, digite-os juntos (ou seja, / rm) em vez de separadamente (ou seja, / r / m), conforme mostrado na Figura 12-34.

Ferramentas de biblioteca estática


A grande maioria das tarefas relacionadas com as bibliotecas estáticas podem ser realizadas pelo arquivador ar utilidade. Ao usar ar, você pode não apenas
combinar os arquivos-objeto na biblioteca estática, mas também listar seu conteúdo, remover arquivos-objeto individuais ou substituí-los pela versão mais recente.

ar

O exemplo simples a seguir ilustra os estágios usuais de uso da ferramenta AR . O projeto de demonstração é composto por quatro arquivos de origem
(primeiro.c, segundo.c, terceiro.c e quarto.c) e um arquivo de cabeçalho de exportação que pode ser usado pelos binários do cliente (mostrado nos
cinco exemplos a seguir).

first.c

#include "mystaticlibexports.h"

int first_function (int x) {

retorno (x + 1);

segundo.c

#include "mystaticlibexports.h"

int quarta_função (int x) {

retorno (x + 4);

terceiro.c

#include "mystaticlibexports.h"

int second_function (int x) {

retorno (x + 2);

quarto.c

#include "mystaticlibexports.h"

int third_function (int x) {

retorno (x + 3);

} mystaticlibexports.h

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 196/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
#pragma uma vez

primeira_função int (int x); segunda_função int (int x); terceira_função int (int x); int quarta_função (int x);

Vamos supor que você tenha os arquivos de objeto criados compilando cada um dos arquivos de origem:

$ gcc -Wall -c primeiro.c segundo.c terceiro.c quarto.c

Os instantâneos de tela a seguir ilustram os vários estágios de como lidar com a biblioteca estática.

Criação da biblioteca estática

A execução de ar -rcs <nome da biblioteca> <lista de arquivos-objeto> combina os arquivos-objeto especificados na biblioteca estática (Figura 12-35).

nilan @ milan $ ar -res libnystaticlib.a primeiro.o segundo.o terceiro.o quarto, o piilan @ piilan $ Is -alg total 48

drwxrwxr-x 2 nilan drwxrwxr-x 5 nilan -rw-rw-r-- 1 milan -rw-rw-r— 1 nilan -rw-rw-r --- rw-rw-r- --rw-rw- r --- rw-rw-r- --rw-rw-r --- rw-rw-r- --rw-rw-r- * -rw-rw-r--

1 nilan 1 nilan! 1 nilan 3) 1 nilan 1 nilan 1 nilan! 1 nilan 1 nilan i milan @ nllan $ file libnystaticlib.a libmystaticlib.a: arquivo ar atual

Figura 12-35. Usando ar para combinar arquivos de objeto para biblioteca estática

Listando os arquivos de objeto da biblioteca estática

A execução de ar -t <nome da biblioteca> imprime a lista dos arquivos-objeto transportados pela biblioteca estática (Figura 12-36).

nilan @ rnlan $ ar -t libnystaticlib.a

primeiro.o

segundo.o

terceiro.

quarto.

Dez 25 11: : 37

Dez 25 10: : 48

Dez 25 10: : 36 primeiro .c


Dez 25 11: ; 35 primeiro.o
Dez 25 10: : 36 quarto.c
Dez 25 11: ! 35 quarto .o
Dez 25 11: : 37 libnystaticlib.a
Dez 25 10: : 37 nystaticlibexports.h
Dez 25 10: : 35 segundo. c
Dez 25 11: : 35 segundo.o
Dez 25 10: : 35 terceiro.c
Dez 25 11: : 35 terceiro.

nilan @ milan $

Figura 12-36. Usando ar para imprimir a lista de arquivos de objeto da biblioteca estática

Excluindo um arquivo de objeto da biblioteca estática

Digamos que você queira modificar o arquivo first.c (para corrigir um bug ou simplesmente para adicionar um recurso extra) e por enquanto não deseja que
sua biblioteca estática carregue o arquivo de objeto first.o. A maneira de excluir o arquivo-objeto da biblioteca estática é executar ar -d <nome da
biblioteca> <arquivo-objeto a ser removido> (Figura 12-37).

ntlan @ rit'lan $ ar -d libnystaticlib.a first.o Figura 12-37. Usando ar para excluir um arquivo de objeto da biblioteca estática

Adicionando o novo arquivo de objeto à biblioteca estática

Digamos que você esteja satisfeito com as alterações feitas no arquivo primeiro.c e que o tenha recompilado. Agora você deseja colocar o arquivo de objeto
recém-criado primeiro.o de volta na biblioteca estática. A execução de ar -r <nome da biblioteca> <arquivo-objeto a ser anexado> basicamente
anexa seu novo arquivo-objeto à biblioteca estática (Figura 12-38).

milão @ milão $ gcc -Wall -I ../ staticLib -c first.c milão @ milão $ ar -r libmystaticlib.a first.o

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 197/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Figura 12-38. Usando ar para adicionar um novo arquivo de objeto à biblioteca estática

Observe que a ordem em que os arquivos-objeto residem na biblioteca estática foi alterada. O novo arquivo foi efetivamente anexado ao arquivo.

Restaurando a Ordem dos Arquivos Objeto

Se você insiste em que seus arquivos de objeto apareçam na ordem original que existia antes das alterações de código, você pode corrigi-lo. A execução de ar -m
-b <arquivo-objeto antes de> <nome da biblioteca> <arquivo-objeto a ser movido> realiza a tarefa (Figura 12-39).

piilan (Bmilan $ ar -t libmystaticlib.a


segundo.o

terceiro.

quarto.

primeiro.o

nilangmilanS ar -m -b segundo.o libmystaticlib.a primeiro.o


nilangmilanS ar -t libmystaticlib.a
primeiro.o

segundo.o

terceiro.

quarto.

pii "lari @ milari $

Figura 12-39. Usando ar para restaurar a ordem dos arquivos de objeto na biblioteca estática

CAPÍTULO 13

Linux How To's


O capítulo anterior forneceu uma revisão dos utilitários de análise úteis disponíveis no Linux, então agora é um bom momento para fornecer uma visão
alternativa do mesmo tópico. Desta vez, o foco não será nos utilitários em si, mas em mostrar como algumas das tarefas executadas com mais frequência podem
ser concluídas.

Normalmente, há mais de uma maneira de concluir uma tarefa de análise. Para cada uma das tarefas descritas neste capítulo, maneiras alternativas de concluir a
tarefa serão fornecidas.

Depurando a vinculação
Provavelmente, a ajuda mais poderosa na depuração do estágio de vinculação é o uso da variável de ambiente LD_DEBUG (Figura 13-1). É adequado para testar
não apenas o processo de construção, mas também o carregamento dinâmico da biblioteca em tempo de execução.

milão @ milão $ LD_DEBUG = ajudar gato

As opções válidas para a variável de ambiente LD_DEBUG são:

caminhos de pesquisa de biblioteca de exibição de libs

reloc exibir processamento de realocação

arquivos mostram o progresso do arquivo de entrada

símbolos exibir processamento da tabela de símbolos

ligações exibem informações sobre ligação de símbolo

versões exibem dependências de versão

escopos exibem informações de escopo

todas as opções anteriores combinadas

estatísticas exibir estatísticas de realocação

DSOs não usados ​determinados não usados

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 198/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
ajude a exibir esta mensagem de ajuda e saia

Para direcionar a saída de depuração para um arquivo em vez da saída padrão, um nome de arquivo pode ser especificado usando a variável de ambiente
LD_DEBUG_OUTPUT. milan @ riilan $

Figura 13-1. Usando a variável de ambiente LD_DEBUG para depurar links

O sistema operacional suporta um conjunto predeterminado de valores para os quais LD_DEBUG pode ser definido antes de executar a operação desejada
(construção ou execução). A maneira de listá-los é digitar

$ LD_DEBUG = ajudar gato

Como com qualquer outra variável de ambiente, existem várias maneiras de definir o valor de LD_DEBUG:

• Imediatamente, na mesma linha a partir da qual o vinculador é invocado

• Uma vez por toda a vida do shell do terminal

$ export LD_DEBUG = <opção_escolhida>

que pode ser revertido por $ unset LD_DEBUG

• De dentro do arquivo de perfil de shell (como .bashrc) , configurando-o para cada sessão de terminal. A menos que sua tarefa diária seja testar o processo de
vinculação, essa opção provavelmente não é a mais adequada.

Determinando o tipo de arquivo binário


Existem algumas maneiras simples de determinar o tipo binário:

• O utilitário de arquivo (entre a ampla variedade de tipos de arquivo que ele pode manipular) fornece provavelmente a maneira mais simples, rápida e elegante
de determinar a natureza do arquivo binário.

• a análise do cabeçalho ELF readelf fornece, entre outros detalhes, informações sobre o tipo de arquivo binário. Correndo

$ readelf -h <path-of-binary> | tipo grep

exibirá uma das seguintes opções:

• EXEC (arquivo executável)

• DYN (arquivo de objeto compartilhado)

• REL (arquivo relocável)

No caso de bibliotecas estáticas, a saída REL aparecerá uma vez para cada um dos arquivos-objeto transportados pela biblioteca.

Determinando o ponto de entrada do arquivo binário


Determinar o ponto de entrada do arquivo binário é uma tarefa que varia em complexidade desde o muito simples (no caso de arquivos executáveis) até o um
pouco mais envolvido (determinar o ponto de entrada da biblioteca dinâmica em tempo de execução), ambos os quais serão ilustrados em esta seção.

Determinando o ponto de entrada executável

O ponto de entrada do executável (ou seja, o endereço da primeira instrução no mapa de memória do programa) pode ser determinado por qualquer

• análise de cabeçalho ELF readelf , que fornece, entre outros detalhes, informações sobre o tipo de arquivo binário. Correndo

$ readelf -h <path-of-binary> | grep Entry exibirá uma linha parecida com esta: Endereço do ponto de entrada: 0x <address>

• análise de cabeçalho EFL objdump , que pode fornecer uma análise semelhante com um relatório um pouco menos detalhado. A saída deste comando

$ objdump -f <path-of-binary> | grep start

irá exibir algo parecido com isto:

endereço inicial 0x <endereço>

Determinando o ponto de entrada da biblioteca dinâmica

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 199/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Quando o ponto de entrada de uma biblioteca dinâmica é procurado, a investigação não é tão direta. Embora seja possível usar um dos métodos descritos
anteriormente, as informações fornecidas (normalmente um número hexadecimal de baixo valor, como 0x390) não são particularmente úteis. Dado o fato de que a
biblioteca dinâmica é mapeada no mapa de memória do processo binário do cliente, o verdadeiro ponto de entrada da biblioteca pode ser determinado apenas em
tempo de execução.

Provavelmente, a maneira mais simples é rodar o executável que carrega a biblioteca dinâmica no depurador GNU. Se a variável de ambiente LD_DEBUG for
definida, as informações sobre a biblioteca carregada serão impressas. Tudo o que você precisa fazer é definir o ponto de interrupção na função main () . É muito
provável que este símbolo exista independentemente de o executável ter sido criado para depuração ou não.

Nos casos em que a biblioteca dinâmica está vinculada de forma estática, no momento em que a execução do programa atinge o ponto de interrupção, o processo
de carregamento já estará concluído.

Em casos de carregamento dinâmico em tempo de execução, provavelmente a abordagem mais fácil é redirecionar a impressão da tela massiva para o arquivo
para inspeção visual mais tarde.

A Figura 13-2 ilustra o método que depende da variável LD_DEBUG .

milão @ milão $ LD_DEBUG = voa gdb -q ./driver 3226:

arquivo = libreadline.so.6 [8]; necessário para gdb [O] file = libreadline.so.6 [0]; gerando mapa de link dinâmico: Oxb775bb8S base: 0xb772600O tamanho: entrada: Oxb7730efO phdr: 0xb7726G34 phnun:

3226: 3226: 3226: 3226: 3226:

1) antes de executar o depurador, ative a depuração do vinculador definindo a variável de ambiente LD_DEBUG (escolha

0xO0039de4 7

2) Inicie o depurador

Lendo synbols de /home/nilan/driverApp/driver...done, (gdb) b main

Ponto de interrupção 1 em 0x804864f: arquivo driver.c, linha 28. (gdb) r

Iniciando programa: / hone / milan / driverApp / driver

3229:

3229: arquivo = libtinfo.so.5 [0]; 3229: arquivo = libtinfo.so.S [8];

necessário para / bin / bash [O] gerar mapa de links

3) definir o ponto de interrupção em um local de convenção (como o símbolo 'principal').

4) Execute o processo uma vez que carregará todas as bibliotecas necessárias (assumindo que as regras de localização do tempo de execução foram satisfeitas).

i
arquivo = libi ireloc.so [8]; arquivo = libpireloc.so [0]; dynamic: 6xb7fd9f20 entry: 0xb7fd8390

necessário para / home / nilan / driverApp / driver [0] gerando link nap base: 0xb7fd800O | tamanho: 0x00002018 phnun: 7

3229: 3229: 3229: 3229: 3229: 3229: 3229:

imprimir os detalhes de carregamento em tempo de execução da biblioteca em que estamos interessados

ir: | exb
(

phdr: 10xb7fd8034

Observe que ° entry - base = 0x390, o que é o valor lido por readelf a partir do binário da biblioteca

Ponto de interrupção 1, nain (argc = l, argv = 0xbffff344) em driver.c: 28 28 dl_iterate_phdr (header_handler, NULL);

(gdb) set disassembly-flavour intel (gdb) disassemble 0xb7fd8390

Despejo do código assembler para a função _do_global_dtors_aux:

6) quando o ponto de interrupção for atingido, tente desmontar o código ao redor do endereço que está sendo relatado como ru n -time

endereço do ponto de entrada

Oxb7fd8390 0xb7fd8391 0xb7fd8393 0xb7fd8394 0xb7fd8395

Empurre

ROV

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 200/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
push push call

<+0>: <+ l>: • = + 3>: <+4>: <+5>:

vazante

ebp.esp

esi

ebx

0xb7fd8447

<i686.qet pc thunk.bx>

Se tudo estiver OK, o gdb relatará que este endereço também é um ponto de entrada de função.

Figura 13-2. Determinando o ponto de entrada da biblioteca dinâmica em símbolos de runtimeList

Símbolos de lista
As seguintes abordagens podem ser seguidas ao tentar listar os símbolos de executáveis ​e bibliotecas:

• utilitário nm

• utilitário readelf Em particular,

• Uma lista de todos os símbolos visíveis pode ser obtida executando $ readelf --symbols <path-to-binary>

• Uma lista apenas dos símbolos exportados para fins de link dinâmico pode ser obtida executando

$ readelf --dyn-syms <path-to-binary>

• utilitário objdump Em particular,

• Uma lista de todos os símbolos visíveis pode ser obtida executando $ objdump -t <path-to-binary>

• Uma lista apenas dos símbolos exportados para fins de link dinâmico pode ser obtida executando

$ objdump -T <path-to-binary>

Liste e examine as seções


Existem várias maneiras de obter as informações sobre as seções binárias. O insight mais rápido e rudimentar pode ser obtido executando o comando size . Para
uma visão mais estruturada e detalhada, você pode normalmente contar com ferramentas como objdump e / ou readelf, o último sendo especializado
estritamente no formato binário ELF. Normalmente, a primeira etapa obrigatória é listar todas as seções presentes no arquivo binário. Uma vez obtido esse insight,
o conteúdo de um segmento específico é examinado em detalhes.

Listando as Seções Disponíveis

A lista de seções do arquivo binário ELF pode ser obtida por um dos seguintes métodos:

• utilitário readelf

$ readelf -S <path-to-binary>

• utilitário objdump

$ objdump -t <path-to-binary>

Examinando Seções Específicas

De longe, as seções examinadas com mais frequência são as que contêm os símbolos do linker. Por esse motivo, uma ampla variedade de ferramentas foi
desenvolvida para atender a essa necessidade específica. Pela mesma razão, embora pertença à ampla categoria de examinar as seções, o parágrafo que descreve a
extração de símbolos foi apresentado primeiro como um tópico separado.

Examinando a seção dinâmica

A seção dinâmica do binário (a biblioteca dinâmica em particular) contém muitas informações interessantes. A listagem do conteúdo desta seção específica pode
ser realizada com base em um dos seguintes:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 201/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
• utilitário readelf

$ readelf -d <path-to-binary>

• utilitário objdump

$ objdump -p <path-to-binary>

Entre as informações úteis que podem ser extraídas da seção dinâmica, aqui estão as que são extremamente valiosas:

• Os valores de DT_RPATH ou DT_RUNPATH campos

• O valor do campo SONAME da biblioteca dinâmica

• A lista de bibliotecas dinâmicas necessárias ( campo DT_NEEDED )

Determinar se a biblioteca dinâmica é PIC ou LTR

Se a biblioteca dinâmica for construída sem o sinalizador do compilador -fPIC , sua seção dinâmica apresenta o campo TEXTREL , que de outra forma não estaria
presente. O seguinte script simples (pic_or_ltr.sh) pode ajudá-lo a determinar se a biblioteca dinâmica foi construída com o sinalizador -fPIC ou não:

if readelf -d $ 1 | grep TEXTREL> / dev / null; \

então echo "a biblioteca é LTR, construída sem o sinalizador -fPIC"; \

else echo "a biblioteca foi construída com o sinalizador -fPIC"; fi

Examinando a seção de realocação

Esta tarefa pode ser realizada com base no seguinte:

• utilitário readelf

$ readelf -r <path-to-binary>

• utilitário objdump

$ objdump -R <path-to-binary>

Examinando a seção de dados


Esta tarefa pode ser realizada com base no seguinte:

• utilitário readelf

$ readelf -x <nome da seção> <path-to-binary>

• utilitário objdump

$ objdump -s -j <nome da seção> <path-to-binary>

Listar e examinar segmentos


Esta tarefa pode ser realizada com base no seguinte:

• utilitário readelf

$ readelf --segments <path-to-binary>

• utilitário objdump

$ objdump -p <path-to-binary>

Desmontando o Código
Nesta seção, você examinará diferentes abordagens para desmontar o código.

Desmontando o arquivo binário

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 202/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
A melhor ferramenta para essa tarefa específica é o comando objdump . Na verdade, este é provavelmente o único caso em que readelf não fornece uma solução
paralela. Em particular, a seção .text pode ser desmontada executando

$ objdump -d <path-to-binary>

Além disso, você pode especificar o tipo de impressão (AT&T vs. Intel).

$ objdump -d -M intel <path-to-binary>

Se você quiser ver o código-fonte (se disponível) intercalado com as instruções de montagem, você pode executar o seguinte:

$ objdump -d -M intel -S <path-to-binary>

Finalmente, você pode querer analisar o código em uma determinada seção. Além da seção .text , que é famosa por conter código, algumas outras seções
(.plt, por exemplo) podem conter código-fonte.

Por padrão, objdump desmonta todas as seções que contêm código. Para especificar a seção individual a ser desmontada, use a opção -j :

$ objdump -d -S -M intel -j .plt <path-to-binary>

Desmontando o Processo de Execução

A melhor maneira é confiar no depurador gdb. Consulte a seção do capítulo anterior dedicada a esta ferramenta maravilhosa.

Identificando a compilação de depuração


Parece que a maneira mais confiável de reconhecer se o binário foi criado para depuração (ou seja, com a opção -g ) é confiar na ferramenta readelf . Em
particular, correr

$ readelf --debug-dump = linha <path-to-binary>

fornecerá saída não vazia no caso de versão de depuração do arquivo binário.

Listando Dependências de Tempo de Carregamento


Para listar o conjunto de bibliotecas compartilhadas das quais um executável (aplicativo e / ou biblioteca compartilhada) depende no momento do carregamento,
dê uma olhada mais de perto na discussão dedicada ao comando ldd (em que o método ldd e um método mais seguro baseado no objdump) foram
mencionados). Em suma, executando o ldd

$ ldd <path-to-binary>

fornecerá a lista completa de dependências.

Alternativamente, confiar em objdump ou readelf para examinar a seção dinâmica dos binários é uma proposição um pouco mais segura, que tem o custo de
fornecer apenas o primeiro nível de dependências.

$ objdump -p / caminho / para / programa | grep NEEDED $ readelf -d / path / to / program | grep NECESSÁRIO

Listando as bibliotecas conhecidas pelo carregador


Para listar todas as bibliotecas cujos caminhos de tempo de execução são conhecidos e estão disponíveis para o carregador, você pode contar com o utilitário
ldconfig . Correndo

$ ldconfig -p

irá imprimir a lista completa de bibliotecas conhecidas pelo carregador (isto é, atualmente presentes no arquivo /etc/ld.so.cache ) junto com seus respectivos
caminhos.

Consequentemente, a busca por uma determinada biblioteca em toda a lista de bibliotecas disponíveis para o carregador pode ser realizada executando

$ ldconfig -p | grep <library-of-interest>

Listando bibliotecas dinamicamente vinculadas


Ao contrário das tarefas listadas até agora neste capítulo, esta tarefa específica não foi mencionada no contexto das ferramentas de análise binárias. O motivo é
simples: as ferramentas de análise de arquivo binário são de pouca utilidade em tempo de execução, quando ocorre o carregamento da biblioteca dinâmica de
tempo de execução. Ferramentas como ldd não cobrem as bibliotecas dinâmicas carregadas em tempo de execução pela chamada da função dlopen () .

Os métodos a seguir fornecerão a lista completa de bibliotecas dinâmicas carregadas. A lista inclui tanto as bibliotecas vinculadas dinamicamente como
estaticamente cientes quanto as bibliotecas vinculadas dinamicamente no tempo de execução.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 203/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

Strace Utility

Chamar strace <linha de comando do programa> é um método útil para listar a sequência de chamadas do sistema entre as quais open () e mmap () são
as mais interessantes para nós. Este método revela a lista completa de bibliotecas compartilhadas carregadas. Sempre que uma biblioteca compartilhada é
mencionada, normalmente as poucas linhas de saída abaixo da chamada mmap () revelam o endereço de carregamento.

Variável de ambiente LD_DEBUG

Dada a sua flexibilidade e grande variedade de opções, esta opção está sempre na lista de ferramentas para rastrear tudo relacionado ao processo de vinculação /
carregamento. Para este problema específico, a opção LD_DEBUG = files pode fornecer muitas impressões contendo informações excessivas sobre as bibliotecas
carregadas dinamicamente no tempo de execução (seus nomes, caminhos de tempo de execução, endereços de pontos de entrada, etc.).

Arquivo / proc / <ID> / maps

Sempre que um processo é executado, o sistema operacional Linux mantém um conjunto de arquivos na pasta / proc , controlando os detalhes importantes
relacionados ao processo. Em particular, para o processo cujo PID é NNNN, o arquivo em location / proc / <NNNN> / maps contém a lista de bibliotecas e seus
respectivos endereços de carregamento. Por exemplo, a Figura 13-3 mostra o que esse método informa para o navegador Firefox.

m_lan @ nilan $ ps -ef | grep firefox nilan 15536 ​14480 8 22:57 pts / 0 nilan 15596 14480 0 22:58 pts / O m.lan@nilan$ cat / proc / 15536 ​/ niaps a2cO0000-a2dO0O00 rw-p 0OOO00O0 00:00 0 rw-p 09000000
00:00 O —p OOOO0000 00:00 O rw-p 00000000 00:00 0

--- p 00000000 00:00 0

rw-p 00000000 00:00 0

--- p 00000000 00:00 0

rw-p 00000000 00:00 0 —p O000O000 00:00 0 rw-p 00000000 00:00 0

oooo o

00:00:07 / usr / Ub / firefox / firefox 00:00:00 grep --color = ftrefox automático

a2e00000-a2fO0000 a2ffcoo0-a2ffd000 a2ffdO0O-a37fdO0O a37fd0O0-a37feO06 a37fe000-a3ffe000 a3ffe00O-a3fffOOO a3fffO0O-a47ff000 a47ff00O-a48O10000 a47ff0000O-a48O10000

r-xp 00000000 08:

—P 00038000 08:

r - p 00038000 08:

rw-p 0003a000 08:

r-xp 00000000 08:

r - p 00036000 08:

rw-p 00037000 08:

r - p 00000000 08: 01 7868984 /usr/lib/i386-linux-gnu/libcroco-O.6.so.3.0.1

a99640O0 a999c000 a999dO00 a999f000 ■ a99a00OO-a99d7000 ■ a99d8O00 a99d9000-

a999cO00 a999d00O a999f000 a99a0000 a99d7O00 a99d8O00 a99d9OO0 aaaooooo

01 7868984 /usr/lib/i386•linux-gnu/libcroco-0.6.so.3.O.1

01 7868984 /usr/Iib/i386-linux-gnu/libcroco-0.6.so.3.O.1

01 7868984 / usr / lib / i386-linux-gnu / libcroco- 0.6.so.3.0.1

01 7869354 /usr/lib/i386-linux-gnu/librsvg-2.so.2.36.1

01 7869354 /usr/lib/i386-linux-gnu/librsvg-2.so.2.36.1

01 7869354 /usr/lib/i386-linux-gnu/librsvg-2.so.2.36.1

oi 9S68812 /usr/share/xul-ext/ubufox/chroroe/ubufox.jar

Figura 13-3. Examinando o arquivo / proc / <PID> / maps para examinar o mapa de memória do processo

OBSERVAÇÃO 1:

Um pequeno problema potencial pode ser que certos aplicativos sejam concluídos rapidamente, não deixando tempo suficiente para examinar o mapa de memória
do processo. A solução mais simples e rápida neste caso seria iniciar o processo através do depurador gdb e colocar um ponto de interrupção na função principal.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 204/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Enquanto a execução do programa permanece bloqueada no ponto de interrupção, você terá tempo ilimitado para examinar o mapa de memória do processo.

OBSERVAÇÃO 2:

Se você tiver certeza de que apenas uma instância do programa está sendo executada atualmente, você pode eliminar a necessidade de procurar o PID do
processo, contando com o comando pgrep (process grep). No caso do navegador Firefox, você digitaria

$ cat / proc / "pgrep firefox" / maps

lsof Utility

O utilitário lsof analisa o processo em execução e imprime no fluxo de saída padrão a lista de todos os arquivos abertos por um processo. Conforme declarado
em sua página de manual ( http://linux.die.net/man/8/lsof ), um arquivo aberto pode ser um arquivo normal, um diretório, um arquivo especial de
bloco, um arquivo especial de caractere, uma referência de texto em execução , uma biblioteca, um fluxo ou um arquivo de rede (soquete de Internet, arquivo NFS
ou soquete de domínio UNIX).

Dentre a ampla seleção de tipos de arquivo que relata estar aberto, ele também relata a lista de bibliotecas dinâmicas carregadas pelo processo,
independentemente se o carregamento foi executado estaticamente ou dinamicamente (executando dlopen em tempo de execução).

O recorte a seguir ilustra como obter a lista de todas as bibliotecas compartilhadas abertas pelo navegador Firefox mostrado na Figura 13-4:

$ lsof -p "pgrep firefox"

,
mlan | ilmi Lan: - / Desktop $ ps -ef | grep

nilan 3463 2625 10 20: : 59 1


Milão 3506 2625 0 20: : 59 1
mllân £ jmllan: - - / Desktops lsof -p

Raposa de fogo 3463 Milão mem REG

Raposa de fogo 3463 Milão mem REG

Raposa de fogo 3463 Milão mem REG

Raposa de fogo 3463 Milão mem REG

Raposa de fogo 3463 Milão mem REG

Raposa de fogo 3463 Milão mem REG

Raposa de fogo 3463 Milão mem REG

Raposa de fogo

00:80:01 / usr / lib / firefox / flrefox

eû: û0: 0S grep --color = auto firefox | grep "\ .so"

8,1 458376 7867638 / usr / llb / firefox / ltbnssckbl so

8,1 394592 7867626 / usr / llb / fIrefox / llbfreebl3 so

8,1 22080 7344057 /lib/1386-Unux-gnu/llbnss_dns-2.15 .so

8,1 268144 7867915 / usr / llb / firefox / llbsoftokn3. tão

8,1 161096 7867492 / usr / llb / flrefox / libnssdbm3 então

8,1 239248 7868934 / usr / llb / 1386- Unux -gnu / Ubcroco-0.6. tão. 3.Ô. 1

8,1 227972 7869354 / usr / llb / 1386 ■ llnux-gnu / Ubrsvg-2, portanto. 2,36.1

Raposa de fogo 3463 Milão mem GRAVANDO


Raposa de fogo 3463 Milão mem GRAVANDO
Raposa de fogo 3463 Milão mem REG
Raposa de fogo 3463 Milão mem REG
Raposa de fogo 3463 Milão mem REG
Raposa de fogo 3463 Milão mem REG
Raposa de fogo 3463 Milão mem REG
Raposa de fogo 3463 Milão mem REG
Raposa de fogo 3463 Milão mem REG
mllangmllan: - - / Desktops

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 205/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

8, , 1 905712 7869397
8, , 1 30684 7344054
8, , 1 13940 7344062
uma, , 1 124663 7344052
8, , 1 5408 7865724
8, , 1 9624 7867962
8, , 1 13604 7867057
8, , 1 17700 7867631
8, , 1 134344 7344053

/ usr / Ub / i386-llnux-gnu / libstdt: ++ so. 6 .0.16

/ Vlb / i386-linux-gnu / librt-Z, 15. tão

/lib/1386-llnux-gnu/llbdl■2.IS, então

/lib/1386-llnux-gru/llbptbread-2.15 so

/usr/lib/i386-llnux-gnu/Ubgthread-2.0. > 0,6.3200.4

/ usr / lib / firefox / llbmozalloc .so

/usr/llb/firefox/libplds4.so

/ usr / llb / flrefox / llbplc4 .so

/ Ub / i3B6-llrux-gnu / ld -2 .lS .so

Figura 13-4. Usando o utilitário lsof para examinar o mapa de memória do processo

Observe que lsof fornece a opção de linha de comando para executar o exame do processo periodicamente. Ao especificar o período de exame, você pode
capturar os momentos em que ocorre o carregamento e descarregamento dinâmico em tempo de execução.

Ao executar lsof com a opção -r , o exame periódico do processo continua em um loop infinito, exigindo que o usuário pressione Ctrl-C para encerrar. Executar
lsof com a opção + r tem o efeito de encerrar o lsof quando nenhum outro arquivo aberto é detectado.

Forma programática

Também é possível escrever código para que ele imprima as bibliotecas que estão sendo carregadas pelo processo. Quando o código do aplicativo incorpora
chamadas à função dl_iterate_phdr () , suas impressões em tempo de execução podem ajudá-lo a determinar a lista completa de bibliotecas compartilhadas
que carrega, bem como os dados extras associados a cada biblioteca (como o endereço inicial da biblioteca carregada).

Para ilustrar o conceito, foi criado um código de demonstração composto por um aplicativo de driver e duas bibliotecas dinâmicas simples. O arquivo de origem
do aplicativo é mostrado no exemplo a seguir. Uma das bibliotecas dinâmicas é vinculada dinamicamente com reconhecimento estático, enquanto a outra
biblioteca é carregada dinamicamente invocando a função dlopen () :

#define _GNU_SOURCE

#include <link.h>

#include <stdio.h>

#include <dlfcn.h>

#include "sharedLib1Functions.h"

#include "sharedLib2Functions.h"

static const char * segment_type_to_string (uint32_t type) {

interruptor (tipo)

caso PT_NULL: // 0

retornar "Não utilizado"; pausa;

case PT_LOAD: // 1

retornar "Segmento de programa carregável"; pausa;

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 206/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
case PT_DYNAMIC: // 2

retornar "Informações de vínculo dinâmico"; pausa;

case PT_INTERP: // 3

retornar "Intérprete de programa"; pausa;

caso PT_NOTE: // 4

retornar "Informações auxiliares"; pausa;

case PT_SHLIB: // 5

retornar "Reservado"; pausa;

case PT_PHDR: // 6

return "Entrada para a própria tabela de cabeçalho"; pausa;

case PT_TLS: // 7

retornar "segmento de armazenamento local de segmento"; pausa;

// case PT_NUM: // 8 / * Número de tipos definidos * /

case PT_LOOS: // 0x60000000

retornar "Início específico do sistema operacional"; pausa;

case PT_GNU_EH_FRAME: // 0x6474e550

retornar "segmento GCC .eh_frame_hdr"; pausa;

case PT_GNU_STACK: // 0x6474e551

return "Indica a executabilidade da pilha"; pausa;

case PT_GNU_RELRO: // 0x6474e552

retornar "Somente leitura após relocação"; pausa;

// case PT_LOSUNW: // 0x6ffffffa

case PT_SUNWBSS: // 0x6ffffffa

retornar "segmento específico da Sun"; pausa;

case PT_SUNWSTACK: // 0x6ffffffb return "segmento Sun Stack"; pausa;

// case PT_HISUNW: // 0x6fffffff

// case PT_HIOS: // 0x6fffffff / * Fim específico do sistema operacional * /

// case PT_LOPROC: // 0x70000000 / * Início específico do processador * /

// case PT_HIPROC: // 0x7fffffff / * Fim do processador específico * /

predefinição:

Retorna "???";

static const char * flags_to_string (uint32_t flags) {

switch (sinalizadores) {

caso 1:

return "--x"; pausa; caso 2:

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 207/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
return "-w-"; pausa; caso 3:

return "-wx"; pausa; caso 4:

return "r--"; pausa; caso 5:

return "rx"; pausa; caso 6:

return "rw-"; pausa; caso 7:

return "rwx"; pausa;

predefinição:

Retorna "???"; pausa;

static int header_handler (struct dl_phdr_info * info, size_t size, void * data) {

int j;

printf ("nome =% s (% d segmentos) endereço =% p \ n",

info-> dlpi_name, info-> dlpi_phnum, (void *) info-> dlpi_addr); para (j = 0; j <info-> dlpi_phnum; j ++) {

printf ("\ t \ t cabeçalho% 2d: endereço =% 10p \ n", j,

(void *) (info-> dlpi_addr + info-> dlpi_phdr [j] .p_vaddr)); printf ("\ t \ t \ t tipo = 0x% X (% s), \ n \ t \ t \ t
sinalizadores = 0x% X (% s) \ n", info-> dlpi_phdr [j] .p_type,

segment_type_to_string (info-> dlpi_phdr [j] .p_type),

info-> dlpi_phdr [j] .p_flags,

flags_to_string (info-> dlpi_phdr [j] .p_flags));

printf ("\ n"); return 0;

int main (int argc, char * argv []) {

// função da biblioteca carregada estaticamente ciente sharedLiblFunction (argc);

// função da biblioteca carregada dinamicamente em tempo de execução

void * pLibHandle = dlopen ("libdemo2.so", RTLD_GLOBAL | RTLD_NOW);

if (NULL == pLibHandle) {

printf ("Falha ao carregar libdemo2.so, erro =% s \ n", dlerror ()); return -1;

PFUNC pFunc = (PFUNC) dlsym (pLibHandle, "sharedLib2Function");

if (NULL == pFunc) {

printf ("Falha ao identificar o símbolo \" sharedLib2Function \ "\ n");

dlclose (pLibHandle);

pLibHandle = NULL;

return -1;

pFunc (argc); if (2 == argc) getchar (); if (3 == argc)


file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 208/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
dl_iterate_phdr (header_handler, NULL); return 0;

O local central neste exemplo de código pertence à chamada à função dl_iterate_phdr () . Essa função extrai essencialmente as informações de mapeamento
de processo relevantes em tempo de execução e as passa para o responsável pela chamada. O chamador é responsável por fornecer a implementação
personalizada da função de retorno de chamada (header_handler () neste exemplo). A Figura 13-5 mostra como pode ser a impressão da tela produzida.

nane = .. / sharedLlbl / ltbdercoi.so (7 segmentos) endereço = 0xb77adO00 nome = .. / sharedLi.b2 / ltbdeno2.so (7 segmentos) endereço = 0xb77c3000

Figura 13-5. A maneira programática (baseada na chamada dl_iterate_phdr ()) de examinar os locais de carregamento da biblioteca dinâmica no mapa de memória do processo

Criação e manutenção da biblioteca estática


A maioria das tarefas relacionadas a lidar especificamente com as bibliotecas estáticas podem ser concluídas usando o arquivador de ar do Linux. Concluir tarefas
como desmontar o código da biblioteca estática ou inspecionar seus símbolos não difere de como é executado nos aplicativos ou bibliotecas dinâmicas.

CAPÍTULO 14

Caixa de ferramentas do Windows


O objetivo deste capítulo é apresentar ao leitor o conjunto de ferramentas (programas utilitários, bem como outros métodos) para analisar o conteúdo dos arquivos
binários do Windows. Mesmo que o utilitário objdump do Linux tenha alguns recursos de análise do formato PE / COFF, o foco estrito neste capítulo será nas
ferramentas nativas do Windows, que são mais prováveis ​de estar à altura de quaisquer alterações no formato PE / COFF que podem acontecer ao longo do
caminho.

Gerenciador de biblioteca (lib.exe)


O lib.exe do gerenciador de bibliotecas de 32 bits do Windows vem como parte padrão das ferramentas de desenvolvimento do Visual Studio (Figura 14-1).

Prompt de comando do 3IH visual Studio (2010]

Configurando o ambiente para usar as ferramentas do Microsoft Visual Studio 2010 x86.

C = sProgram PilessMicrosoft Uisual Studio 10.0 \ UOlib.exe rlicrosoft (R> Library Manager Uersioo IB.00.40219.01 Copyright CO Microsoft Corporation. Todos os direitos reservados.

uso: LIB [opções] [f dies]

opções:

^ DEPC ^ filcname]

/ ERRORREPORT: iNONE \ PROMPT! SUEDE iSENM / EXPORT: símbolo / EXTRACT-membeinsme / INCLUIR: símbolo / LIBPAÏH: dii * / LISTA I: nome do arquivo] / LI CG

/ MACH! NE: <ARMIEBC! 1Ab ^ iMIPSIM1PS1 & PSFPU! M1PSFFU16 \

SH41THUMB IK641HB £> / NOME: fllsnane / N0DEPAULTL1B [: biblioteca] / N0L0G0 / OUT = nome do arquivo / REMOU E r me mbo i * nome

/ SUBSISTEMA: <BOOT_APPLI CAT 1 ONÎCONSOLE! EPIPPLICAT I ON I

efi_boot_seruice_hriuer! efi_rom: ef] juntimejriuer:

NRIIUEIPOSlKiWINOOWSiUlNDOUSCEJUBt-tinn

/ UERBOSE / I «(: NÃO]

C: Arquivos SProgranMlicrosoft Uisiial Studio

Microsoft Si fertig ht Microsoft SiEverlight 5 SDK Microsoft Si (verlight4 SDK Microsoft SQL Server 2008. Microsoft Sync Framework. Microsoft Visual Studio 2010 <0; Microsoft Visual Studio2010 Doc um oo
Microsoft Visual Studio 2010 Ferramentas do Microsoft Windows SDK Team Foundation Server Ferramentas J * Visual Toots de estúdio

Ci Dotfusealor Software Services ^ Gerenciar configurações de ajuda - Ferramenta EMU M FC-ATI Tract

eu. Espião-

Visual Studio 2010 Remote Debu SB Visual Studio Prompt de comando SB Visual Studio x64 Cross Tools Co

Saco

pl

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 209/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

I | 5eflrrii prvgroms e arquivos

Figura 14-1. Usando o utilitário lib.exe

Este programa utilitário não apenas lida com as bibliotecas estáticas da mesma maneira que sua contraparte do Linux (arquivador), mas também desempenha um
papel no domínio das bibliotecas dinâmicas como a ferramenta que pode criar bibliotecas de importação (a coleção de símbolos DLL, arquivo extensão .lib) ,
bem como os arquivos de exportação (capaz de resolver as dependências circulares, extensão de arquivo .exp). A documentação detalhada sobre lib.exe pode
ser encontrada no site do MSDN ( http://msdn.microsoft.com/en-us/library/7ykb2k5f.aspx ).

lib.exe como uma ferramenta de biblioteca estática

Nesta seção, ilustrarei as funções típicas nas quais a ferramenta lib.exe pode ser realmente útil.

lib.exe como ferramenta de arquivamento padrão

Quando o Visual Studio é usado para criar um projeto de biblioteca estática C / C ++, lib.exe é definido como a ferramenta de arquivador / bibliotecário padrão e
a guia Bibliotecário das configurações do projeto é usada para especificar as opções de linha de comando para ele (Figura 14-2) .

Figura 14-2. Usando lib.exe como arquivador padrão

Por padrão, a construção do projeto de biblioteca estática invoca lib.exe após o estágio de compilação, que ocorre sem nenhuma ação exigida pelo
desenvolvedor. No entanto, não é necessariamente aí que o uso de lib.exe deve terminar. É possível executar lib.exe a partir do prompt de comando do
Visual Studio da mesma forma que o arquivador de ar do Linux é usado para executar os mesmos tipos de tarefas.

lib.exe como um utilitário de linha de comando

Para ilustrar o uso de lib.exe, você criará uma biblioteca estática do Windows que corresponde totalmente à funcionalidade da biblioteca estática do Linux
usada para demonstrar o uso de ar no Capítulo 10. O projeto demo é composto de quatro arquivos de origem (first.c , second.c, third.c e quarter.c) e
um arquivo de cabeçalho de exportação que pode ser usado pelos binários do cliente. Esses arquivos são mostrados nos cinco exemplos a seguir.

arquivo: first.c

#include "mystaticlibexports.h"

int first_function (int x) {

retorno (x + 1);

arquivo: second.c

#include "mystaticlibexports.h"

int quarta_função (int x) {

retorno (x + 4);

arquivo: third.c

#include "mystaticlibexports.h"

int second_function (int x) {


file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 210/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
retorno (x + 2);

arquivo: quarto.c

#include "mystaticlibexports.h"

int third_function (int x) {

retorno (x + 3);

arquivo: mystaticlibexports.h

#pragma uma vez

primeira_função int (int x); segunda_função int (int x); terceira_função int (int x); int quarta_função (int x);

Criação de uma biblioteca estática

Vamos supor que você compilou todos os quatro arquivos de origem e que tem quatro arquivos de objeto disponíveis (primeiro.obj, segundo.obj,
terceiro.obj e quarto.obj). Passar o nome da biblioteca desejada para lib.exe (após o sinalizador / OUT ) seguido pela lista de arquivos de objetos
participantes terá o efeito de criar a biblioteca estática, conforme mostrado na Figura 14-3.

c: \ U se rs \ ni lan \ myst em iclib \ riy static lib \ De bug> dir * .ohj Uolume na unidade C não tem rótulo. O número de série do Uolume é F4F7-CFD4

Diretório de c: \ Users \ milan \ nystaticlib \ mystaticlib \ Debug

25/12/2013 06 -48 PM 2.626 first.obj

25/12/2613 06:48 PM 2.635 quarta.obj

25/12/2013 18:48 2.635 segundos.obj

25/12/2013 06:48 2.626 terceiro.obj

4 Arquivo <s> 10.522 bytes

0 DirCs) 132.602.314.752 bytes livres

c: \ Usuários \ nilan \ mystaticlib \ riystaticlib \ Debug> lib.exe /OUT:mystaticlib.lib / NOLOGO primeiro.obj segundo.obj terceiro.obj quarto.obj

c: \ Users \ milan \ mystaticlib \ nystaticlib \ Dehutf> dir ».lib Uolume no driue C não tem rótulo. Número de série do Uolume é F4F7-CFD4

Diretório de c: MJsers ^ milan \ nystaticlibNmystaticlib \ Debug

25/12/2013 06:50 11,140 mystaticlib.lib

1 arquivo (s> 11.140 bytes

0 Dir (s> 132.602.302.464 bytes livres

c: \ Usuários \ nilan \ r) ystat iclib \ nystat iclib \ Debug>

Figura 14-3. Usando lib.exe para combinar arquivos de objeto em uma biblioteca estática

Para imitar completamente as configurações padrão fornecidas pelo Visual Studio ao criar o projeto de biblioteca estática, adicionei o argumento / NOLOGO .

Listando o conteúdo da biblioteca estática

Quando o sinalizador / LIST é passado para lib.exe, ele imprime a lista de arquivos-objeto atualmente contidos pela biblioteca estática, conforme mostrado
na Figura 14-4.

c: \ Usuários \ nilan \ mystaticlih \ mystat iclib \ Debug> lib-exe / LI SI mystat iclib. lib Microsoft (R> Library Manager Uersion 10.00.40219.01 Copyright (C) Microsoft Corporation. preencher direitos reservados,

primeiro.obj segundo.obj terceiro.obj quarto.obj

c: SUsers \ milan \ mystaticlibNriystat ic lihNDebug> Figura 14-4. Usando lib.exe para listar os arquivos objeto da biblioteca estática

Removendo arquivos de objetos individuais da biblioteca estática

Os arquivos de objetos individuais podem ser removidos da biblioteca estática passando o sinalizador / REMOVE para lib.exe (Figura 14-5).

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 211/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
c: \ llsers \ niLanMnystat iclib \ mystaticlib \ Debugilib.exe / REMO (JE: primeiro .obj mystaticlib. lib

Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright (C> Microsoft Corporation. Todos os direitos reservados.

c: \ llsers \ milan \ mystat iclib \ mystat iclib \ Debug> lib.exe / LIST nystat iclih. lib Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright (C> Microsoft Corporation. Todos os direitos reservados.

fo urt) i .obj terceiro.obj segundo.obj

c: \ Usuários \ (nilanXnystat iclibxnystaticlibxDebug>

Figura 14-5. Usando lib.exe para remover arquivo de objeto individual da biblioteca estática

Inserindo o arquivo de objeto na biblioteca estática

O novo arquivo objeto pode ser adicionado à biblioteca estática existente, passando o nome do arquivo da biblioteca seguido pela lista de arquivos objeto a serem
adicionados. Essa sintaxe é muito semelhante ao cenário de criação da biblioteca estática, exceto que o sinalizador / OUT pode ser omitido (Figura 14-6).

c : \ Users \ milan \ nystaticlib \ nystaticlib \ DehLigJlib.exe / LISI mystatic lib.lib Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos
reservados.

Quarta.obj terceira.ohj segunda.obj

c: \ Usei's \ niltin \ inystatie lib \ nystat ic lib ^ Depurar> lib.exe ciystat ic lib. lib f irst -obj Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

c: \ Usei's \ nilan \ nystaticlib \ nystat iclib \ Dehug> lib.exe / LIST nystat iclib.lib Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

primeiro.obj segundo.obj terceiro.obj quarto.obj

c: \ Usuários \ nilan \ tnystatic libNnystat ic lib \ Debug>

Figura 14-6. Usando lib.exe para inserir arquivo de objeto na biblioteca estática

Extraindo o arquivo de objeto individual da biblioteca estática

Finalmente, os arquivos de objetos individuais podem ser extraídos da biblioteca estática. Para demonstrar isso, primeiro excluí propositadamente o arquivo
objeto original (first.obj), cuja extração da biblioteca estática está planejada para acontecer (Figura 14-7).

c: SUsers \ piilanSnystat iclibSmystat iclib \ Debug> dir * .obj Uolune na unidade C não tem rótulo. O número de série do Uolume é F4F7-CFD4

Diretório de c: \ Users \ milan \ mystaticlib \ roystaticlib \ Debug

12 / 2S / 2 013 06 = S7 PM 2.626 primeiro.obj

25/12/2013 06:58 PM 2.635 quarta.obj

25/12/2013 18:58 2.635 segundos.obj

25/12/2013 18:58 2.626 terceiro.obj

4 filetes) 10.522 bytes

0 Dir (s> 132.601.217.024 bytes livres

c: SUsers \ nilanSnystat iclibSmystat iclib \ Debug> del f irst * .obj

c: \ Users \ miltU »Srriystt» t iclib \ i <i.ysttiticlib \ Debiig> dir «.obj Uolune na unidade C não tem rótulo. O número de série do Uolume é F4F7-CFD4

Diretório de c: \ Users \ milan \ rtystAt iclibNmystat iclibSDebug

25/12/2013 06:58 PM 2.635 quarta.obj

25/12/2013 18:58 2.635 segundos.obj

25/12/2013 18:58 2.626 terceiro.obj

3 filetes) 7.896 bytes

0 Dir <s> 132.601.221.120 bytes livres

c: \ Users \ milan \ mystaticlib \ mystatic1ib \ Debug> lib.exe / LIST mystaticlib.lib Microsoft <FD Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. preencher direitos reservados.

primeiro obj segundo.obj terceiro.obj quarto.obj

c: \ Users \ milan \ rnystat iclib \ mystaticlib \ Debug> lib.exe / EXTRACT: primeiro .obj mystat iclib-lib

Microsoft <P> Library Manager Uersion 10.00.40219.01 Copyright (C> Microsoft Corporation. Preencher direitos reservados.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 212/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
c: SUsersNnilan \ Fiystat iclibNmystaticlib \ Debug> dir * .obj Uolune na unidade C não tem rótulo. O número de série Uolame é F4F7-CFD4

Diretório de c: \ Usuários \ nilan \ mystaticlibNmystaticlib \ Debug

25/12/2013 06: 5? PM 2.626 first.obj

25/12/2013 06:58 PM 2.635 quarta.obj

25/12/2013 18:58 2.635 segundos.obj

25/12/2013 18:58 2.626 terceiro.obj

4 filetes) 10.522 bytes

0 Dir (s> 132.601.217.024 bytes livres

c: SUsersXnilan \ mystat iclibNmystaticlib \ Debug>

Figura 14-7. Usando lib.exe para extrair um arquivo de objeto individual da biblioteca estática

lib.exe no Reino de Bibliotecas Dinâmicas (Ferramenta de Importação de Biblioteca)

lib.exe também é usado para criar o arquivo de biblioteca de importação DLL (.lib) e o arquivo de exportação (.exp) com base no arquivo de definição de
exportação disponível (.def). Ao trabalhar estritamente no ambiente do Visual Studio, essa tarefa normalmente é atribuída automaticamente ao lib.exe. Um
cenário muito mais interessante ocorre quando a DLL é criada por um compilador de terceiros que não cria a biblioteca de importação e o arquivo de exportação
correspondentes. Nesses casos, lib.exe deve ser executado a partir da linha de comando (ou seja, o prompt de comando do Visual Studio).

O exemplo a seguir ilustra como lib.exe pode ser usado para criar as bibliotecas de importação ausentes após a sessão de compilação cruzada na qual o
compilador MinGW executado no Linux produziu os binários do Windows, mas não forneceu as bibliotecas de importação necessárias (Figura 14-8).

X: \ MilanFFMpegUin32Build> lib / machine: X86 /def:avcodec-53.def / out: aucodec.lib

Microsoft <R> library Manager Versão 10.00.40219.01 Copyright <C> Microsoft Corporation- preencher direitos reservados-

Criação da biblioteca aveodec.lib e do objeto avcodec.exp

X: \ MilanFFMpegUin32Build> lib / machine: X86 /def:avdevice-53.def /out=avdevice.lib

Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

Criando a biblioteca avdevice-lib e o objeto avdevice-exp

X: \ MilanFFMpegUin32Build> lib / máquina: X86 / def = awfilter-2-def /out:avfilter.lib

Microsoft (R) Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. preencher direitos reservados.

Criação da biblioteca avfilter.lib e do objeto avfilter.exp

X: \ MilanFFMpegUin32Build> lib / machine: X86 /def:avformat-53.def /out:avformat.lib

Microsoft <R> Library Manager Uersion 10,00.40219.01 Copyright CO Microsoft Corporation. Todos os direitos reservados.

Criação da biblioteca avformat -lib e do objeto avformat.exp

<: \ MilanFFMpegWin32Build> lib / machine: X86 /def:avutil-51.def /out:avutil.lib Microsoft <R) Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft
Corporation - Todos os direitos reservados.

Criação da biblioteca avutil.lib e do objeto avutil.exp

■ {: \ MilanFFMpegWin32Build> lib / machine: X86 / def: postproc-51-def / out: postproc-lib

Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados-

Criação da biblioteca postproc.lib e do objeto postproc.exp

<MilanFFMpegWin32Build> lib / máquina: X86 /def:swresample-0.def / out: suresample_

Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft Corporation- Todos os direitos reservados-

Criação da biblioteca suresample-lib e object suresample-exp

X: \ MilanFFMpegUin32Build> lib / machine: X86 /def:swscale—2.def /out:swscale.lib Microsoft <R> Library Manager Uersion 10.00.40219.01 Copyright <C> Microsoft
Corporation- Todos os direitos reservados.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 213/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Criando a biblioteca suscale.lib e object suscale.exp

Figura 14-8 . Usando lib.exe para criar uma biblioteca de importação baseada em DLL e sua definição (.DEF) _ arquivo

utilitário dumpbin
O utilitário dumpbin do Visual Studio ( http://support.microsoft.com/kb/177429 ) é, em sua maior parte, o equivalente do Windows ao utilitário
objdump do Linux , pois executa o exame e a análise de detalhes importantes do executável, como símbolos exportados, seções, desmontagem das seções de
código (.text) , lista de arquivos-objeto na biblioteca estática, etc.

Essa ferramenta também é uma parte padrão do pacote do Visual Studio. Semelhante à ferramenta lib descrita anteriormente , ela é executada normalmente a
partir do Prompt de Comando do Visual Studio (Figura 14-9).

Prompt de comando do SI Visual Studio (2010) | n | | <3

Ambiente de configuração para uso das ferramentas Microsoft Uisual Studio 2010 x86. ,

C: \ Arquivos Pi-ograniSMicrosoft Uisual Studio 10.0 \ UC> dunpbin

Microsoft <R) COFF / PE Dumper Versão 10.00.10219.01 i

Copyright <C> Microsoft Corporation, direitos de preenchimento reservados.

uso: DUMPBIN [opções] [arquivos]

opções:

/TUDO

/ A RCHIU EMEMBERS / CLRHEADER / DEPENDENTES ✓DIRETIVOS

/ DISASMI: iBYTES! N0BYTES>]

/ ERRORREPORT: (NONE! PROMPT! QUEUE! SEND}

/ EXPORTAÇÕES

/ FPO

/ HEfiDERS

/ IMPORTS [: nome do arquivo]

/NÚMEROS DE LINHA

/ L1NKERMEMBER [: <1! 2> 1

/ LOADCONFIG

/ OUT: nome do arquivo

/ FDATA

/ PDBPAIH [: UERBOSE]

/ RANGE: vaMin t, vaMax]

/ RAUDATA [: <N0NE! 11214I8> 1.111

/ RELOCATIONS

/Nome da Seção

/RESUMO

/ SYMBOLS

/ ILS

/ UNWINDINFO

C: \ Pfogram Files \ Mici> osoft Uisual Studio 10.0 \ UC>

Figura 14-9. Usando o utilitário dumpbin


file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 214/235
21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
As tarefas típicas descritas nas seções a seguir podem ser concluídas executando dumpbin.

Identificando o tipo de arquivo binário

Quando executado sem os sinalizadores extras, o dumpbin relata o tipo de arquivo binário (Figura 14-10).

c: \ Users \ milan \ DLLUersioningDemo \ Uers ionedDLL \ Debug> dunpbin d Una em .abj Microsoft <R> COFF / PE Dumper Uersion
10.00.40219.01 Copyright <C> Microsoft Corporat ion. Todos os direitos reservados.

Despejo do arquivo dllmain.obj Tipo de arquivo: Resumo do objeto COFF

1F50! Debug $ S 64 .debugîT 41 .drectue 4. rtc $ IMZ 4 .rtcSTMZ 5D .text

c: \ Users \ milan \ DLLUersioningDemoNUersionedDLL \ Debug> cd. .SDebug

1
c: \ Users \ milan \ DLHJersioningDemo \ Debusf> dumpbin UersionedDLL.dll Microsoft <R> COFF / PE Dumper Uersion
10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

Despejo do arquivo UersionedDLL.dll

Tipo de arquivo: DLL

Resumo

1000 .data 1000 .idata 2000 .rdata 1000 .reloc 1000 .rsrc 4000 .text 10000 .textbss

c: \ Users \ milan \ DLHJersioningDemo \ Debug> dumpbin UersionedDLLClientApp.exe Microsoft <R> COFF / PE Dumper Uersion
10.00.40219.01 Copyright <C> Mic rosoft Corporation. Todos os direitos reservados.

Despejo do arquivo UersionedDLLClientApp.exe

Tipo de arquivo: IMAGEM EXECUTÁVEL

Resumo

1000 .data 1000 .idata 2000 .rda ta 1000 .reloc 1000 .rsrc 4000 .text 10000 .textbss

c iMIsers'smilanVDLLUers ioningDemo \ Debug>

Figura 14-10. Usando o utilitário dumpbin para identificar tipos de arquivos binários

Listando os símbolos DLL exportados

Executar dumpbin / EXPORTS <dll path> fornece a lista de símbolos exportados (Figura 14-11).

c: \ Users \ niilan \ DH.UersioningDen »o \ Debug> durtpbin / EXPORTS UersionedDLL.dll Microsoft <R> COFF / PE Dumper Uersion
10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

A seção contém as seguintes exportações para UERSIONEDDLL.dll 52B625A0 carimbo de data Sábado 21 de dezembro 15:34:56 de 2013

8 00011087
SIONINFOee @ Z> DllGetUersion = PIU + 130 <? DllGetUersion (? <? VGJPAUJt> U.UER c: \ Users \ milan \ BLLUers ion
ingDemo \ Debug>

Figura 14-11. Usando o utilitário dumpbin para listar os símbolos exportados do arquivo DLL

Listando e examinando as seções

Executar dumpbin / HEADERS <caminho do arquivo binário> imprime a lista completa das seções presentes no arquivo (Figura 14-12).

c: \ Users \ milan \ DLLUersionincfDemo \ Debug> dumpbin / HEADERS UersionedDLL.dll Microsoft <R> COFF / PE Dumper Uersion
10.00.40219.01 Copyright <C> Microsoft Corporation. preencher direitos reservados.

Despejo do arquivo UersionedDLL.dll

Assinatura FE encontrada

Tipo de arquivo: DLL

FILE HEADER UALUES

Máquina 14C <x86>

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 215/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
7 número de seções 52B697A6 carimbo de data sabado 21 de dezembro 23:41:26 2013 0 ponteiro de arquivo para a tabela de símbolos 0
número de símbolos E0 tamanho do cabeçalho opcional 2102 características Executável de máquina de palavras de 32 bits DLL

UALUES DE CABEÇALHO OPCIONAIS

10B mágico ft <PE32>

ooo

CABEÇALHO DA SEÇÃO ftl .texthss name

10000 tamanho virtual 1000 endereço virtual <10001000 a 10010FFF> 0 tamanho dos dados brutos 0 apontador de arquivo para dados
brutos 0 apontador de arquivo para tabela de realocação 0 apontador de arquivo para número de linha 0 número de relocações 0 número
de números de linha E00000A0 sinalizadores Código

Dados não inicializados Executar Leitura e Gravação

CABEÇALHO DA SEÇÃO ft2 .text

3CA3 11000 3E00 400 0 0 0 0

£ 0000020 flags Código Executar

nome

tamanho virtual

endereço virtual <10011000 a 10014CA2> tamanho dos dados brutos

ponteiro de arquivo para dados brutos <00000400 a 000041FF>

ponteiro de arquivo para tabela de realocação

ponteiro de arquivo para números de linha

número de realocações

número de números de linha

Leitura

ooo

Figura 14-12. Usando dumpbin para listar as seções

Depois que os nomes das seções são listados, as informações das seções individuais podem ser obtidas executando dumpbin / SECTION: <nome da seção>
<caminho do arquivo binário> (Figura 14-13).

c: \ Users \ milan \ DLLUersioningDemo \ Debug> diinpbin /SECTION:.text UersionedDLL.dll Microsaft <R> COFF / PE Dumper Uersion
10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

Despejo do arquivo UersionedDLL.dll

Tipo de arquivo: DLL

CABEÇALHO DA SEÇÃO »2 .nome do texto 3CA3 tamanho virtual 11000 endereço virtual <10011000 a 10014CA2> 3E00 tamanho dos
dados brutos 400 apontador de arquivo para dados brutos <00000400 a 000041FF> 0 apontador de arquivo para tabela de realocação 0
apontador de arquivo para números de linha 0 número de deslocamentos 0 número de números de linha 60000020 sinalizadores Código

Executar leitura

Resumo

4000 .text

c: \ Users \ milan \ DLLUersioningDemo \ Debug> dumpbin /SECTION:.data UersionedDLL.dll Microsoft <R> COFF / PE Dumper Uersion
10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

Despejo do arquivo UersionedDLL.dll

Tipo de arquivo: DLL

CABEÇALHO DA SEÇÃO tt4 .data name

7C0 tamanho virtual 17000 endereço virtual <10017000 a 100177BF> 200 tamanho de dados brutos 5E00 ponteiro de arquivo para
dados brutos <00005E00 a 00005FFF> 0 ponteiro de arquivo para tabela de realocação 0 arquivo po inter para números de linha 0
número de relocações 0 número de números de linha C0000040 bandeiras

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 216/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Dados inicializados, leitura, gravação

Resumo

1000 .data c: \ Users \ milan \ DLLUers ioningDemo \ Debug>

Figura 14-13. Usando o dumpbin para obter informações detalhadas sobre uma seção específica

Desmontando o Código

A execução de dumpbin / DISASM <caminho do arquivo binário> fornece a lista desmontada do arquivo binário completo (Figura 14-14).

c: \ Users \ nilan \ DLLUers ion ingDemo \ Debug> diinpbin ^ DISflSM Uers ionedDLL.dll Microsoft <R> COFF / PE Dunper Uersion
10.00.40219.01 Copyright <C> Microsoft Corporation. Todos os direitos reservados.

Despejo do arquivo UersionedDLL.dll Tipo de arquivo: DLL

10011000: 10011001: 10011002: 10011003: 10011004:

CC CC CC CC CC

int int int int int

eiLT + 0 <_ucstok_s>:

10011005: E9 04 00 00 00 jnp

PI LT + 5 <_utoi>:

1001100ft: E9 F9 09 00 00 jnp PI LT +10 <_RT C_Ge t ErrDe sc>:

1001100F: E9 2C 13 00 00 jnp

PI LT +15 <_na 1lo c _dbg>:

10011014: E9 E7 IE 00 00 jnp

PI LT +2 0 <P__parece »it y_c mentira c k_c ook ie P4>:

10011019: E9 D2 2ft 00 00 jnp PI LT + 2 5 <_É Depuração rPre enviada P0>:

1001101E: E9 99 2C 00 00 jnp PI LT +30 (_GetUsei'Def aultLangIDP0>: 10011023: E9 66 09 00 00 jnp

PI LT + 35 (_BTC_Terninate>:

10011028: E9 03 IE 00 00 jnp PI LT +40 (_WideCJiarToMult iByteP32):

jnp

ligar

não voce

não voce

não voce

Empurre

ligar

adicionar

enp

ligar

não voce

não voce

nov

agora

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 217/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
não voce

não voce

Empurre

ligar

enp

01 10

01 10

JOP

1001102D: E9 84 2C 00 00 eiLT +45 <_DllMainG12>: 10011032: E9 79 03 00 00

ooo

FF FF

100116B4 E8 91 Ffl
100116B9 89 45 ft4
100116BC SB F4
100116BE SB 45 A4
10011SC1 50
100116C2 FF 15 38
100116C8 83 C4 04
100116CB 3B F4
100116CD ES 78 Ffl
100116D2 8B 4D 08
100116D5 89 ■ 41 10
100116D8 8B 45 08
100316DB C7 00 14
100116E1 8B F4
100116E3 8B 45 AC
100116E6 50
100116E7 FF 15 28
100116ED 3B F4

_wcstûk_s _litoi

_RT C_Ge t Er r De sc

_nalloc_dbg

P__security_check_cookieP4

sDebuggerPresentP0 „GetUserDefaultLangiDP0

._RTC_Terninate

JlideCharToMultiByte032 _DllMain @ 12

PI LT + 325Î_RTC_CheckEsp>

duord ptr [ebp-5Ch], eax esi.esp

eax, duord ptr [ebp-5ChI

eax

duord ptr [_inp _. «labuta

esp, 4 esi.esp

PI LT + 325 <_RTC_CheckEsp>

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 218/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
ecx, dword ptr [ebp + 8) dword ptr [ecx + 10Ii], eax eax, dword ptr [ebp + 8) duoi-d ptr [eax], 141) esi, esp

eax, dword ptr [ebp-44h] eax

dword ptr [_inp_Fi'eeResoui'ceP4]

esi, esp

oo Ö

Figura 14-14. Usando dumpbin para desmontar o código

Identificando a compilação de depuração

O utilitário dumpbin é usado para identificar a versão de depuração de um arquivo binário. Os indicadores de uma compilação de depuração variam dependendo
do tipo de arquivo binário real.

Arquivos de objeto

Executar dumpbin / SYMBOLS <caminho do arquivo binário> nos arquivos objeto (* .obj) relatará o arquivo objeto construído para depuração como um
arquivo do tipo COFF OBJECT (Figura 14-15).

c: \ U5er & * M * lilan \ DLLUers ion inciDemo \ UersionedDLL \ Debuj}} dimpbin / SVMEOLS dllmain -obJ

Microsoft (R> COFF / PE Duniper llersion 10.00.40219.01 Copyright <C> Microsoft Corporation, direitos de preenchimento reservados-

Despejo do arquivo dllmain.obj Tipo de pilha: COFF OBJECT

TABELA DE SÍMBOLOS DE COFF 0UH HHfiEVDIB flBS 901 00000001 ftBS 002 00000000 SECT1 Comprimento da seção Relocação CRC 00000000 005 00000000 SECT2
notype

Comprimento da seção 1E24, 8relocs Relocation CRC 8E00PI6DS 008 00000000 SECT3 notype

Comprimento da seção 4, 8relocs Relocation CRC 00000000 00B 00000000 SECT3 notype

struct HINSTANCE_ * g_hModule>

00C 00000000 SECT4 notype

Comprimento de seção de íons 5D, 8 relocs íon 1 <não escolher duplicatas)

Relocação CRC D94D98E5 00F 00000000 SECT5 notype

Comprimento da seção 12C, íon de Brelocs 5 <selecionar seção associativa 0x4> Relocação CFC 638E05RE

012 00000000 SECT4 notype O Externo

013 00800000 SECT6 notype Comprimento da seção estática 4, 8relocs 1, 81

íon 5 <selecionar seção associativa 0x4) Relocação CFC 4C2E11CC

016 00000000 SECT6 notype Estático

017 00000000 Notype UNDEF <> Externo

018 00000000 SECT? notype Static Section length 4, Srelocs 1, 81

íon S (escolha a seção associativa 0x4)

Realocação CRC 5DJ07BVE 01B 00000000 SECT? notype estático

01C 00000000 Notype UNDEF O Externo 01D 00000000 SECTS notype Estático

Comprimento de seção de íon 64, 8 relocs 0 »81 Relocação CRC 00000000

Tamanho da tabela de string = 0x7B bytes

Resumo

4 .bss 1F50 -debug $ S 64 .debuyiT 41 .drectue 4 .rtcSIMZ 4 .rtcSTMZ ED .text

c: \ Dsei * s \ nilanNDLLUersioningDeino \ UersionedDLL \ Deliug>

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 219/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Static Stiit ic Static 0, 81

Estático 2, 81

Estático 0. 81

Externo

! Pconp.id! Pfeat.00 I .dreetue inenuns 0, checksum

! .dehug $ S inenuns 0, checksum

! . bss

inenuns 0, checksum

notype notype notype 41, 8relocs

I? G_hModuleP [> 3FfiUHI NS TANCE_ (

Eu. Texto

Estático]

2 * 81inenuns

0, checksum A5F7BE6C, selecione

! .dehugf $ S inenuns 0, checksum

E estático, 81

0, selecione

I _J) llMain (? 12! .Rtc $ TMZ inenuns 0. checksum

0, selecione

_RTC_Shutdoun.rtciTrtZ

_RTC_Shutdown

.rtcSIMZ

inenuns 0, checksum 0, selecione

_RTC_Eu nisto Base. rtc $ IMZ

_HI C_I nit Base

. dehuc | $ T

inenuns 0, checksum 0

Figura 14-15. Usando dumpbin para detectar a versão de depuração do arquivo objeto

A versão de lançamento do mesmo arquivo será relatada como tipo de arquivo ANONYMOUS OBJECT (Figura 14-16).

c: sUsers \ nilan \ DLLUers ion intfDemo \ Uers ionedDLL \ Re lease> dumpbin / SYMBOLS d lima em .o

Microsoft <R> COFF / PE Dumper Uersion 10.00.40219.01 Copyright (C> Microsoft Corporation. (Il direitos reservados.

c: \ Users \ milan \ DLLUersion incfDemoNUersionedDLL \ Release> Figura 14-16. Indicação da liberação construída do arquivo objeto

DLLs e executáveis
O indicador certo de que uma DLL ou arquivo executável foi criado para depuração é a presença da seção .idata na saída da execução da opção dumpbin /
HEADERS . O objetivo desta seção é oferecer suporte ao recurso "editar e continuar" que está disponível apenas no modo de depuração. Mais especificamente, para
habilitar essa opção, o sinalizador de vinculador / INCREMENTAL é necessário e normalmente definido para Depurar e desabilitado para configuração de versão
(Figura 14-17).

c: \ DsersSmilan \ DLLUers ion ingDemoNDebug> dumpbin / HEADERS Uers ionedDLL. dll Microsoft <JD COFF / PE Dumper Versão
10.00.40219.01 Copyright <C> Microsoft Corporation, direitos reservados rtIX.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 220/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Despejo do arquivo UersionedDLL.dll

PE s ignature £ out

Tipo de arquivo: DLL

UflUES DE CABEÇALHO DE ARQUIVOS

Máquina 14C <x86>

7 número de seções 52B697A6 carimbo de data sabado 21 de dezembro 23:41:26 2013 0 ponteiro de arquivo para a tabela de símbolos 0
número de símbolos E0 tamanho do cabeçalho opcional 2102 característico ics Executável de máquina de palavras de 32 bits DLL

oo o

CABEÇALHO DA SEÇÃO ttS nome do .idata

961 tamanho virtual 18000 endereço virtual <10018000 a 10018960> A00 tamanho de dados brutos 6000 apontador de arquivo para
dados brutos <00006000 a 000069FF) 0 apontador de arquivo para relocação tabic 0 apontador de arquivo para números de linha 0
número de relocações 0 número de números de linha C0000040 sinalizadores

Dados inicializados Read Urite

oo o

Figura 14-17. Usando o dumpbin para detectar a versão de depuração da DLL

Listando as Dependências de Tempo de Carregamento

A lista completa das bibliotecas de dependências e dos símbolos importados delas pode ser obtida executando dumpbin / IMPORTS <caminho do arquivo
binário> (Figura 14-18).

c: \ Users \ milan \ DLLUersioningDemo \ Dehug> ctunipbin / IMPORTS Oer s ionedDLL-dll Microsoft <R> COFF / PE Dumper Uersion 10.00.40219.01 Copyright (C)
Microsoft Corporation. Todos os direitos reservados.

Despejo do arquivo UersionedDLL.dll

Tipo de arquivo: DLL

A seção contém as seguintes importações:

UERSION.dll

10018 Tabela de endereço de importação 3AC 100181F0 Tabela de nome de importação 0 carimbo de data e hora

0 Índice da referência do primeiro encaminhador E UerQuerylPalueW

KERHEL32.dll

10018220 Tabela de endereços de importação 1001S664 Tabela de nomes de importação 0 carimbo de data e hora

0 Índice da primeira referência do despachante

4AS SetllnhandledExceptionFiIter 4D3 UnhandledExceptionFilter 165 FreePesource 29C GetUserDefaultLanglD 354 LockResource 341 LoadResource 14E FindFesourcelJ 1C0
GetCurrentFrocess

oo o

245 GetProcAddress S4D IstrlenA 3E1 RaiseException 367 MultiByteToUideChar 300 IsDebuggerFresent 511 UideCharToMultiByte 162 FreeLibrary 2E9
InterlockedCoropareExchange 4B2 Sleep

2EC InterlookedExobange CA DecodePointer EA EncodePointer

USER32.dll

1001837C Tabela de endereços de importação 100181C0 Tabela de nomes de importação 0 carimbo de data e hora

0 Índice da referência do primeiro encaminhador 333 wsprintfW

Figura 14-18. Usando dumpbin para listar as dependências de carregamento

Dependency Walker

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 221/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
O Dependency Walker (também conhecido como Dependency.exe, consulte www.dependencywalker.com/ ) é o utilitário capaz de rastrear a cadeia de
dependências das bibliotecas dinâmicas carregadas (Figura 14-19). Ele não é apenas capaz de analisar o arquivo binário (nesse caso, ele é paralelo ao utilitário ldd
do Linux ), mas também pode realizar análises de tempo de execução nas quais pode detectar e relatar o carregamento dinâmico de tempo de execução. Ele foi
originalmente desenvolvido por Steve Miller e fazia parte do conjunto de ferramentas do Visual Studio até a versão VS2005.

Figura 14-19. Usando o utilitário Dependency Walker

Índice

■A
Programa utilitário de linha de comando addr2line, interface binária de aplicativo 270 (ABI), ferramenta 64, 87 ar, 273

■B
Reutilização de código binário

analogia culinária, 71

bibliotecas dinâmicas (consulte Bibliotecas dinâmicas)

analogia da expedição, 72

analogia legal, 71

design de software, 72

bibliotecas estáticas

arquivos de objeto, 53, 55 método trivial, 54 bibliotecas estáticas vs. dinâmicas (consulte Bibliotecas estáticas vs. dinâmicas) Ponto de entrada do arquivo binário

ponto de entrada da biblioteca dinâmica, 279 ponto de entrada executável, 279 regras de localização da biblioteca em tempo de construção

Arquivo de biblioteca de importação de DLL (.lib), 121 referência implícita, caminho de biblioteca 123, 121

percepção do linker vs. humano, 118 biblioteca de tempo de construção do Linux, 118 biblioteca dinâmica do Linux

nome de arquivo de biblioteca dinâmica vs.

nome da biblioteca, 117 biblioteca soname incorporada, 118 biblioteca x informações, 117 biblioteca estática do Linux, 116 -L x opção -l, 119 # comentário pragma,
122

■C

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 222/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Programa utilitário de linha de comando chrpath, mecanismo de fábrica de 267 classes, 92-93 Montagem do estágio de compilação

Formato AT&T, 15 projetos de demonstração, 15 formato intel, 17 conteúdos binários, 20 emissão de código, 19 limitações do processo de compilação, variáveis ​
externas, 28 chamadas de função, 28 grandes quebra-cabeças, 28 ligações, 27

mapa de memória do programa, formato 27 ELF, 19 função de arquivo .c, 19 definições introdutórias, 11 análise linguística, 14 comando objdump, 20-21
propriedades de arquivo de objeto, 26 otimização, 18 pré-processamento, 13 definições relacionadas, 12 tela de terminal, 21, 23

■D
Ferramenta de arquivamento padrão, 292 Dependency Walker, 307 Projetando a interface binária do aplicativo de bibliotecas dinâmicas

mecanismo de fábrica de classe, 92-93 declaração ABI completa, 92

Projetando bibliotecas dinâmicas ( cont.) Funções de estilo C, 92 namespaces, 94 palavras-chave C padrão, 92 símbolos, 94 interface binária ABI, 88 problemas de C
++, 88 modelos, inicialização de 91 variáveis, 89 requisitos de conclusão de vinculação, visibilidade de 109 símbolos

Símbolos do Linux ( ver Linux dinâmico

símbolos da biblioteca) Símbolos do Windows (consulte os símbolos da biblioteca dinâmica do Windows) Projetando a biblioteca dinâmica

processo de tradução de endereço, binário de cliente 138-139, 141 vinculador dinâmico, 141 funções e variáveis, 140 função de interface Initialize (), função de
interface 142 Reinitialize (), função de interface 142 Uninitialize (), 142 instruções de montagem, 137 instruções de acesso a dados, 137 coordenação linker-loader
(consulte coordenação Linker-loader) função dl_iterate_phdr (), 287, 289 sinalizadores de compilador DLL, 86 sinalizadores de linker DLL, 87 função dlopen (),
284, 287 utilitário dumpbin

tipo de arquivo binário, 298 compilação de depuração

DLLs e executáveis, 305 arquivos de objeto, 304 desmontagem, 302 símbolos exportados de DLL, 299 listando e examinando, 300 dependência de tempo de
carregamento, pacote do Visual Studio 306, 297 classes de símbolos duplicados, funções

e estruturas, 155 símbolos C, 156 símbolos C ++, 156 main.cpp, 158 erro de linker de manipulação padrão, 160 funções locais, biblioteca estática 160-161, definição
158-159, 155

Bibliotecas dinâmicas, 53, 57 ABI, 65

aplicativo cliente, 172, 176 projeto demo, 174-176 ordem de chamada de função, 173 ordem de vinculação, 171, 173 zona de prioridade, 170, 174 shlib_function (),
173, 176 arquivo binário, 61 processo de construção, 59 vinculação de tempo de construção, 60 cliente aplicativo, símbolo binário de 163 cliente

aplicativo cliente, 169 símbolo de nome duplicado, zona de prioridade 167-168, símbolo de biblioteca estática 166, vinculador do Visual Studio 169, 170
compartimentado,

desenvolvimento mais rápido, 236 criação

no Linux, 81 no Windows, 83 projetando ( consulte Projetando bibliotecas dinâmicas) símbolos duplicados (consulte Símbolos duplicados) Biblioteca vinculada
dinamicamente (.dll), 63 vinculação dinâmica, 59 comparação, 113 tempo de execução, 110-113 estaticamente ciente (tempo de carga) , 110 arquivo executável

código-fonte da biblioteca de demonstração, 238 libc.so, 237 função main (), saída 238, arquivo de exportação 238-239 (.exp), biblioteca de importação 63 (.lib),
método 63 Initialize (), símbolo fraco do ligador 163-164, 241 modo de vinculação, 164 ordem de vinculação, 165 estágio de vinculação, 161-162 processo de tempo
de carregamento, 61 vinculação de tempo de carregamento, 62 namespace (s), 164 herança de namespace, 185 aplicativo cliente de símbolo não exportado, 179
componentes, 180 paradigma de implementação, 180 zona prioritária, 176 biblioteca compartilhada, 177-178 única classe, 180- 183

instância singleton, solução 184 , 184

zona de código não priorizada / não competitiva, sistema operacional 180, técnica de PIC 56, abordagem de reprodução por confiança 58, modelo de plug-in 60

arquitetura, 235 brocas e bits, 233-234 exportando, 235 requisitos, 234-235 regras de zoneamento de prioridade, 165 funções de API de vínculo dinâmico de tempo
de execução, procedimento de construção 111, carregamento dinâmico de tempo de execução 110 Linux, sequência de pseudocódigos 111, carregamento dinâmico
de tempo de execução 111 Windows, 112 manipulação de memória de tempo de execução, 239 capacidade de substituição rápida de tempo de execução, 236 contra
bibliotecas compartilhadas, 56 shlib_duplicate_function, 163 símbolos, 61, 236 natureza única, 64 versionamento

Esquemas de controle de versão da biblioteca dinâmica do Linux

(ver esquemas de controle de versão da biblioteca dinâmica do Linux) alterações de código de versão principal, 187 alterações de código de versão secundária,
versão de patch 188 , 188

Controle de versão das bibliotecas dinâmicas do Windows (consulte Controle de versão das bibliotecas dinâmicas do Windows)

Vinculação dinâmica, 61 tipos de arquivos binários, 62 processos de construção, 59 tempo de construção vs. tempo de execução, 62

■E

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 223/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Ponto de entrada executável, 279

■F
compilador bandeira fPIC, 81- 82

■ G, H
Tabela de deslocamento global (GOT), 151

Depurador GNU, 271

■ I, J, K
Função de interface Initialize (), 142

■L
Programa utilitário de linha de comando ldconfig, 269 variável de ambiente LD_DEBUG, 277 lib.exe

Biblioteca de importação de DLL, ferramenta de biblioteca estática 296 (consulte Ferramenta de biblioteca estática) Ferramentas de desenvolvimento do Visual
Studio, 291 Localização da biblioteca

construir regras de localização da biblioteca de tempo (ver tempo de construção

regras de localização de biblioteca) biblioteca de tempo de execução do usuário final, convenções 116 -L e -R, 132, 135 regras de localização de biblioteca dinâmica
de tempo de execução Linux, caminhos de biblioteca padrão, 131 ldconfig Cache, 129

Variável de ambiente LD_LIBRARY_PATH, 128 versões operacionais, 131 biblioteca pré-carregada, 127 rpath, 127 runpath, 129 bibliotecas estáticas e dinâmicas,
115 regras de localização da biblioteca dinâmica de tempo de execução do Windows, 131 erro do Linker, 83 limitações de coordenação do Linker-loader, 144
diretivas do linker

Formato de arquivo ELF, 147 ferramentas readelf / objdump, 147 seção .rel.dyn, 144-145 tipos de realocação, 148 LTR, 149 PIC

GOT, 151

implementação, 154 ligação lenta, esquema de realocação de tempo de carregamento 152, abordagem 150 ponteiro a ponteiro, cadeia recursiva 150 de ligação
dinâmica, 152 seções de realocação, estágio de ligação 144

abordagem tudo de uma vez, 33 .bss desmontar, 35 definição, 29 saída desmontada, 34 ponto de vista do vinculador, 35 ferramenta objdump, 35 resolução de
referência

variáveis ​de memória de dados, 30 problemas, 31

mapa de memória do programa, 30 realocação, 29 abordagem passo a passo, 32

Tempo de construção de símbolos de biblioteca dinâmica Linux, 95

Sinalizador do compilador fvisibility, 97 libdefaultvisibility.so, 96 símbolos da biblioteca, 96-97 atributos de visibilidade, 98 biblioteca dinâmica do Linux

esquemas de controle de versão com base em Soname softlink de esquema de versão, 189-191 salvaguardas Soname, 192 aspectos técnicos, 192 esquema de versão
de símbolo ( ver esquema de versão de símbolo do Linux) Vantagens do esquema de versão de símbolo do Linux, 195 versão inicial build.sh, 202 binários de
cliente, 204 suporte ao formato ELF , 202 simple.c, 201 simple.h, 201 simpleVersionScript, 202 linker version script, 199 versão principal

Comportamento da função ABI, protótipo de função 210 ABI, versão 214 secundária

binário cliente, 208 símbolos de biblioteca dinâmica, 209 main.c, 207

aplicativo mais antigo e mais recente, 209 biblioteca compartilhada, 208 simple.c, 206-207 simple.h, 206 simpleVersionScript, 207 mecanismo de controle de versão
de símbolo, 196 diretiva assembler symver, 200 scripts de versão

suporte para especificador de ligação, suporte para namespace 217, controle de exportação de 217 símbolos, controle de visibilidade de símbolo 216, 218 nó não
nomeado, 217 nó de versão, 216 regras de nomenclatura de nó de versão, 216 suporte curinga, 217 tarefas Linux

tipo de arquivo binário, 278 seção de dados, 282 compilação de depuração, 284 vinculação de depuração, 277 código de desmontagem, 283 ponto de entrada da
biblioteca dinâmica, 279

carregamento de biblioteca dinâmica, 284 LD_DEBUG, 285 utilitário lsof, arquivo 286 / proc / <ID> / maps, 285 via programática, 287 utilitário strace, 285 seção
dinâmica, 282 arquivo binário ELF, 281 ponto de entrada executável, 279 executáveis ​e símbolos de bibliotecas, Carregador 280, 284

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 224/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
dependência do tempo de carregamento, 284 seção de realocação, 282 biblioteca estática, 290 caixa de ferramentas Linux

deploying stage chrpath, 267 ldconfig, 269 patchelf, 268 strip, 269 file utility program, 243 ldd

limitações, 245 objdump, 245 readelf, 246 recursive search, 244 nm utility

símbolos mutilados, 247 $ nm -D <path-to-binary>, 246 $ nm <path-to-binary>, 246 pesquisa recursiva, 247 tipo de símbolo, 246 símbolos não mutilados, 246
objdump

seções de arquivo binário, 249 desmontar código, 254 examinar segmentos, 253 examinar seção de dados, 253 seção dinâmica de biblioteca, 252 listar todos os
símbolos, 250 listar e examinar seção, 248 listar símbolo dinâmico, 251 nm equivalente, 257 analisar cabeçalho ELF, 248 seção de realocação , 252 pronto

informações de depuração, exibição de 265 seção dinâmica, 263 símbolos dinâmicos, 262 examinar segmentos, 265 despejo hexadecimal, 264

listar e examinar as seções, 259 análise do cabeçalho ELF, 257 seção de realocação, 263

runtime analysis tools addr2line, 270 gdb, 271 strace, 269 size utility program, 243 static library tools add object file, 275 ar tool, 273 creation, 274 delete object file,
275 object file list, 274 restore object file, 276 load_elf_binary function, 45 Dependência do tempo de carregamento, 244 Relocação do tempo de carregamento
(LTR), 57, 149

■ M, N
chamada mmap (), 285 abstrações de sistemas operacionais multitarefa, 1

binários, compilador, vinculador e carregador, 7 hierarquia de memória e sistemas de computador em cache, 2 princípios, 3 analogias da vida real, 3 processos de
esquema de divisão de memória, 6 endereçamento virtual, 5 diretrizes de memória virtual, 3-4 implementação, 4

■O
objdump EFL header analysis, 278-279 open () call, 285

P, Q
linha de comando patchelf

programa utilitário, 268 código independente de posição (PIC), 57 GOT, 151

implementação, 154

ligação lenta, 152

esquema de realocação de tempo de carregamento, 150

abordagem ponteiro a ponteiro, 150

cadeia recursiva de

vínculo dinâmico, pasta 152 / proc, 285 ponto de entrada dos estágios de execução do programa

convenções de chamada, 51 campo e_entry, 49

função _libc_start_main (), 50

função principal (), 49 pilha, 51

função _start (), função do kernel 50, tamanho do byte da função do carregador 45, carregamento dinâmico 48, mapa da memória do programa 47, 48-49 seções vs.
segmentos, construção estática 45, shell 48

processo filho, 44

novo mapa de memória de processo, ambiente 43-44 PATH, 43 sh, bash e tcsh, 43 chamada de sistema (), 45 escrita de código de estágios de vida do programa, 10

estágio de compilação (consulte Estágio de compilação) projeto de demonstração, função principal de 10 propriedades de arquivo executáveis, 37 código de objeto,
37 tipos de seção, 38 estrutura, 37 tipos de símbolo, 40 suposições iniciais, 9 estágio de vinculação (consulte Estágio de vinculação)

■R
readelf ELF header analysis, 278-279 Reinitialize () função de interface, 142 Runtime dynamic linking, 110

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 225/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

■ S, T
Estático (tempo de carga)

vinculação dinâmica, procedimento de construção de 110 bibliotecas estáticas vs. dinâmicas, 69 cenários de dilema de implantação, 68 facilidade de combinação,
70 facilidade de conversão, 70 impacto no tamanho do executável, 69 critérios de seletividade de importação, 65 integração com executável, 69 misc / outro, 70
natureza de binário, 69 portabilidade, 69 prós e contras, 68 adequado para desenvolvimento, 70 cenário de arquivo completo, 67

Bibliotecas estáticas, 53 ferramentas de arquivamento, 75 Linux de 64 bits, implementação de código 79-80, 76 contra-indicação, 78 vs. biblioteca dinâmica, 79
vinculação, 78 criação Linux, 75 modularidade, 77 domínio multimídia, 77 visibilidade e exclusividade de símbolos, 77 janelas criação, 75 ferramenta de biblioteca
estática

ferramenta de arquivamento padrão, 292 arquivos de objeto

criação, extrato 293, inserção 295, listagem 295, 294

remover arquivos de objetos individuais, 294 tipos, 292 linha de comando strace

programa utilitário, programa utilitário de linha de comando de 269 strip, 269 função sys_execve, 45

■ U, V
Função de interface Uninitialize (), 142

■ W, X, Y, Z
Arquivo de recursos de projeto de versão de bibliotecas dinâmicas do Windows, item de menu de 220 propriedades, 221 consulta e recuperação de alternativa
brutal, 229 função DllGetVersion, 224 requisitos de vinculação, 223 estrutura VERSIONINFO, 223 editor Visual Studio, 221

Símbolos da biblioteca dinâmica do Windows dumpbin.exe, 104 opção "Exportar símbolos", 100 extern "C" 105

arquivo de definição de módulo (.def), 106-108 Visual Studio, 101-103

Caixa de ferramentas do Windows

Dependency Walker, 307 utilitário dumpbin (consulte o utilitário dumpbin) Library Manager (consulte lib.exe)

Compilação C e C ++ Avançada
Milan Stevanovic

Apress
Compilação C e C ++ Avançada

Copyright © 2014 por Milan Stevanovic

Este trabalho está sujeito a direitos autorais. Todos os direitos são reservados pela Editora, seja a totalidade ou parte do material em causa, especificamente os
direitos de tradução, reimpressão, reutilização de ilustrações, recitação, transmissão, reprodução em microfilmes ou de qualquer outra forma física, e transmissão
ou armazenamento de informações e recuperação, adaptação eletrônica, software de computador ou por metodologia semelhante ou diferente agora conhecida ou
desenvolvida no futuro. Estão dispensados ​desta reserva legal breves trechos relativos a resenhas ou análises acadêmicas ou materiais fornecidos especificamente
para fins de inserção e execução em sistema de computador, para uso exclusivo do adquirente da obra. A duplicação desta publicação ou de partes dela é
permitida apenas de acordo com as disposições da Lei de Direitos Autorais do local do Editor, em sua versão atual, e a permissão de uso deve sempre ser obtida
da Springer. As permissões de uso podem ser obtidas através do RightsLink no Copyright Clearance Center. As violações podem ser processadas de acordo com a
respectiva Lei de Direitos Autorais.

ISBN-13 (pbk): 978-1-4302-6667-9

ISBN-13 (eletrônico): 978-1-4302-6668-6

Nomes de marcas registradas, logotipos e imagens podem aparecer neste livro. Em vez de usar um símbolo de marca registrada em todas as ocorrências de um
nome, logotipo ou imagem de marca registrada, usamos os nomes, logotipos e imagens apenas de forma editorial e para o benefício do proprietário da marca
registrada, sem intenção de violação da marca registrada.

O uso nesta publicação de nomes comerciais, marcas registradas, marcas de serviço e termos semelhantes, mesmo que não sejam identificados como tal, não deve
ser interpretado como uma expressão de opinião sobre se estão ou não sujeitos a direitos de propriedade.

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 226/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Embora os conselhos e as informações neste livro sejam considerados verdadeiros e precisos na data da publicação, nem os autores, nem os editores, nem a editora
podem aceitar qualquer responsabilidade legal por quaisquer erros ou omissões que possam ser cometidos. O editor não oferece nenhuma garantia, expressa ou
implícita, com relação ao material aqui contido.

Presidente e editor: Paul Manning Editor principal: Michelle Lowman Editor de desenvolvimento: James Markham

Revisores técnicos: Nemanja Trifunovic, Ben Combee, Miroslav Ristic

Conselho Editorial: Steve Anglin, Mark Beckner, Ewan Buckingham, Gary Cornell, Louise Corrigan, Jim DeWolf, Jonathan Gennick, Jonathan Hassell, Robert
Hutchinson, Michelle Lowman, James Markham, Matthew Moodie, Jeff Olson, Jeffrey Pepper, Douglas Pundick, Ben Renow -Clarke, Dominic Shakeshaft,
Gwenan Spearing, Matt Wade, Steve Weiss Editor Coordenador: Jill Balzano Editor de texto: Mary Behr Compositor: SPi Global Indexer: SPi Global Artista: SPi
Global Cover Designer: Anna Ishchenko

Distribuído para o mercado mundial de livros pela Springer Science + Business Media New York, 233 Spring Street, 6th Floor, New York, NY 10013. Telefone 1-
800-SPRINGER, fax (201) 348-4505, e-mail orders-ny @ springer-sbm.com , ou visite www.springeronline.com . Apress Media, LLC é uma LLC da
Califórnia e o único membro (proprietário) é a Springer Science + Business Media Finance Inc (SSBM Finance Inc). SSBM Finance Inc é uma empresa de Delaware.

Para obter informações sobre traduções, por favor, e-mail rights@apress.com , ou visita www.apress.com .

Os livros da Apress e amigos de ED podem ser adquiridos a granel para uso acadêmico, corporativo ou promocional. Versões e licenças de e-books também estão
disponíveis para a maioria dos títulos. Para obter mais informações, consulte nossa página da web Licenciamento de e-books em massa especial em
www.apress.com/bulk-sales .

Qualquer código-fonte ou outro material suplementar citado pelo autor neste texto está disponível para os leitores em www.apress.com . Para obter
informações detalhadas sobre como localizar o código-fonte do seu livro, vá para www.apress.com/source-code/ .

O livro é dedicado a Milena, Pavle e Selina.

Conteúdo
Sobre o autor............................................... .................................................. ............... xv

Sobre os revisores técnicos .............................................. .......................................... xvii

Agradecimentos ................................................. .................................................. .......... xix

Introdução................................................. .................................................. .................... xxi

■ Capítulo 1: Noções básicas do sistema operacional multitarefa .......................................... .......................................... 1

Abstrações úteis ................................................ .................................................. ..................... 1

Hierarquia de memória e estratégia de cache ............................................. ......................................... 2

Memória virtual................................................ .................................................. ............................ 3

Endereçamento Virtual ................................................ .................................................. ....................... 5

Esquema de Divisão de Memória de Processo .............................................. .................................................. 6

As funções de binários, compilador, vinculador e carregador ....................................... ............................... 7

Resumo................................................. .................................................. .................................... 7

■ Capítulo 2: Estágios Simples de Vida do Programa ......................................... ............................ 9

Premissas iniciais ................................................ .................................................. ...................... 9

Escrita de código ................................................ .................................................. .............................. 10

Ilustração do conceito: Projeto de demonstração ............................................. .................................................. ....................... 10

Compilando ................................................. .................................................. ................................. 11

Definições introdutórias ................................................ .................................................. ..................................... 11

Definições relacionadas ................................................ .................................................. ............................................ 12

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 227/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
As fases da compilação .............................................. .................................................. ...................................... 12

Propriedades do arquivo de objeto ............................................... .................................................. ......................................... 26

Limitações do processo de compilação ............................................... .................................................. ........................ 27

Vinculando ................................................. .................................................. ...................................... 29

Estágios de ligação ................................................ .................................................. .................................................. .29

Ponto de vista do vinculador ................................................ .................................................. ............................................. 35

Propriedades do arquivo executável ............................................... .................................................. .......... 37

Variedade de tipos de seção .............................................. .................................................. ....................................... 38

Uma variedade de tipos de símbolos ............................................. .................................................. ..................................... 40

■ Capítulo 3: Estágios de execução do programa ........................................... .................................. 43

Importância da Shell .............................................. .................................................. ............... 43

Função do Kernel ................................................ .................................................. ................................ 45

Função do carregador ................................................ .................................................. ............................... 45

Visualização específica do carregador de um arquivo binário (seções vs. segmentos) .................................... ....................................... 45

Estágio de carregamento do programa ............................................... .................................................. ...................................... 47

Executando o Ponto de Entrada do Programa .............................................. .................................................. ... 49

O carregador encontra o ponto de entrada ............................................ .................................................. ........................... 49

O papel da função _start () ........................................... .................................................. ................................. 50

A função da função de_libc_start_main () ............................................ .................................................. ............ 50

Convenções de pilha e chamada .............................................. .................................................. ............................ 51

■ Capítulo 4: O Impacto do Conceito de Reutilização ........................................ ............................. 53

Bibliotecas estáticas ................................................ .................................................. .......................... 53

Bibliotecas dinâmicas ................................................ .................................................. ..................... 56

Bibliotecas dinâmicas vs. compartilhadas ............................................. .................................................. ............................... 56

Vinculação dinâmica em mais detalhes ............................................. .................................................. ............................ 59

Peculiaridades da ligação dinâmica no Windows ............................................ .................................................. ....... 62

Natureza Única da Biblioteca Dinâmica ............................................. .................................................. ........................ 64

Interface Binária do Aplicativo (ABI) ............................................ .................................................. .......................... 64

Pontos de comparação de bibliotecas estáticas e dinâmicas ........................................... .............................. 65

Diferenças nos critérios de seletividade de importação ............................................. .................................................. ........ 65

Cenários de dilema de implantação ............................................... .................................................. ......................... 68

Analogias de comparação úteis ............................................... .................................................. ..... 71

A Conclusão: O Impacto do Conceito de Reutilização Binária ......................................... ....................... 72

■ Capítulo 5: Trabalhando com bibliotecas estáticas ......................................... ............................... 75

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 228/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Criando biblioteca estática ............................................... .................................................. ............... 75

Criando biblioteca estática do Linux .............................................. .................................................. ............................... 75

Criando uma biblioteca estática do Windows ............................................. .................................................. ....................... 75

Usando a biblioteca estática .............................................. .................................................. ............... 76

Cenários de casos de uso recomendados .............................................. .................................................. .................... 76

Dicas e truques para bibliotecas estáticas ............................................. .................................................. ..... 77

Potencial de perda da visibilidade e exclusividade do símbolo .......................................... ........................................ 77

Cenários de casos de uso contra-indicados .............................................. .................................................. ................. 78

Regras específicas de vinculação de bibliotecas estáticas ............................................ .................................................. .............. 78

Convertendo biblioteca estática em dinâmica ............................................. .................................................. ................... 79

Problemas de bibliotecas estáticas no Linux de 64 bits .......................................... .................................................. .................... 79

■ Capítulo 6: Projetando Bibliotecas Dinâmicas: Fundamentos ......................................... .................... 81

Criando a Biblioteca Dinâmica .............................................. .................................................. ...... 81

Criando a Biblioteca Dinâmica no Linux ............................................ .................................................. ................... 81

Criando a Biblioteca Dinâmica no Windows ............................................ .................................................. ............. 83

Projetando Bibliotecas Dinâmicas ............................................... .................................................. ...... 87

Projetando a interface binária .............................................. .................................................. ............................ 87

Projetando a interface binária do aplicativo ............................................. .................................................. .......... 92

Controlando a visibilidade dos símbolos da biblioteca dinâmica ............................................ .................................................. .... 94

Requisitos de conclusão de vinculação ............................................... .................................................. .................... 109

Modos de ligação dinâmica ............................................... .................................................. ........... 110

Vinculação dinâmica estaticamente ciente (tempo de carregamento) ......................................... .................................................. ...... 110

Runtime Dynamic Linking ............................................... .................................................. ................................. 110

Comparação de modos de vinculação dinâmica .............................................. .................................................. ................ 113

■ Capítulo 7: Localizando as Bibliotecas .......................................... ........................................ 115

Cenários Típicos de Caso de Uso de Biblioteca ............................................. ............................................. 115

Cenário de Caso de Uso de Desenvolvimento .............................................. .................................................. ....................... 115

Cenário de caso de uso de tempo de execução do usuário final ............................................ .................................................. ................. 116

Regras de localização da biblioteca de tempo de construção ............................................. ................................................ 116

Regras de localização da biblioteca de tempo de construção do Linux ............................................ .................................................. .............. 116

Regras de localização da biblioteca do Windows Build Time ............................................ .................................................. ........ 120

Regras de localização da biblioteca dinâmica em tempo de execução ............................................. .................................... 126

Regras de localização da biblioteca dinâmica do Linux Runtime ............................................ .................................................. ..127

Regras de localização da biblioteca dinâmica do Windows Runtime ............................................ .............................................. 131

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 229/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Demonstração do Linux de tempo de construção e convenções de tempo de execução .......................................... ...................... 132

■ Capítulo 8: Projetando Bibliotecas Dinâmicas: Tópicos Avançados ....................................... ... 137

Por que endereços de memória resolvidos são obrigatórios ........................................... ............................... 137

Problema geral de resolução de referências ............................................. ................................... 138

Quais símbolos provavelmente sofrerão com a tradução de endereços? ........................................ ................................ 140

Problemas causados ​pela tradução de endereços ............................................. ..................................... 140

Cenário 1: O cliente binário precisa saber o endereço dos símbolos da biblioteca dinâmica .................................... ..... 140

Cenário 2: a biblioteca carregada não conhece mais os endereços de seus próprios símbolos .................................... ....... 141

Coordenação Linker-Loader .............................................. .................................................. ....... 143

Estratégia Geral ................................................ .................................................. ............................................... 143

Tática ................................................. .................................................. .................................................. .......... 144

Visão geral das diretivas do vinculador ............................................... .................................................. ............................... 146

Técnicas de Implementação de Coordenação Linker-Loader ............................................ .............. 149

Realocação de tempo de carga (LTR) ............................................ .................................................. .................................. 149

Código Independente de Posição (PIC) ............................................ .................................................. .......................... 150

■ Capítulo 9: Manipulando Símbolos Duplicados ao Vincular em Bibliotecas Dinâmicas ............... 155

Definição de Símbolos Duplicada ............................................... .................................................. ... 155

Cenários típicos de símbolos duplicados .............................................. .................................................. ................ 155

Tratamento padrão de símbolos duplicados .............................................. .......................................... 158

São permitidos símbolos locais duplicados ............................................. .................................................. ................. 160

Manipulação de símbolos duplicados ao vincular em bibliotecas dinâmicas .......................................... ... 161

Estratégias gerais de eliminação de problemas de símbolos duplicados ........................................... ............................ 164

Critérios de Linker no Algoritmo Aproximado de Resolução de Símbolos Duplicados de Bibliotecas Dinâmicas ................ 165

Análises de Casos de Nomes Duplicados Específicos ............................................ ............................... 166

Caso 1: o símbolo binário do cliente colide com a função ABI da biblioteca dinâmica ...................................... ................ 166

Caso 2: Símbolos ABI de diferentes bibliotecas dinâmicas colidem ........................................ ..................................... 170

Caso 3: O símbolo ABI da biblioteca dinâmica colide com outro símbolo local da biblioteca dinâmica ............................. 174

Caso 4: o símbolo não exportado da biblioteca dinâmica colide com outra biblioteca dinâmica

Símbolo não exportado .............................................. .................................................. ........................................ 176

Observação final: a vinculação não fornece nenhum tipo de herança de namespace ......................... 185

■ Capítulo 10: Controle de versão de bibliotecas dinâmicas .......................................... .......................... 187

Gradação de versões e seu impacto na compatibilidade com versões anteriores ........................................ 187

Alterações de código de versão principal .............................................. .................................................. ............................. 187

Pequenas alterações de código de versão .............................................. .................................................. ............................. 188

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 230/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Versão do patch ................................................ .................................................. .................................................. 0,188

Esquemas de controle de versão da biblioteca dinâmica do Linux ............................................. ................................ 188

Esquema de controle de versão baseado em Soname do Linux ............................................ .................................................. ........... 188

Esquema de controle de versão de símbolo do Linux .............................................. .................................................. ...................... 195

Controle de versão das bibliotecas dinâmicas do Windows .............................................. ...................................... 218

Informações sobre a versão DLL ............................................... .................................................. ................................... 219

Especificando informações de versão DLL .............................................. .................................................. ................... 220

Consultando e recuperando informações de versão DLL ............................................ ................................................. 222

■ Capítulo 11: Bibliotecas Dinâmicas: Tópicos Diversos ......................................... ......... 233

Conceito de Plug-in .............................................. .................................................. ......................... 233

Regras de exportação ............................................... .................................................. ............................................ 235

Arquiteturas de plug-in populares ............................................. .................................................. ............................. 235

Dicas e truques............................................... .................................................. .......................... 236

Implicações práticas de trabalhar com bibliotecas dinâmicas ........................................... ..................................... 236

Dicas diversas ................................................ .................................................. .......................................... 237

■ Capítulo 12: Linux Toolbox ........................................... .................................................. 243

Ferramentas de percepção rápida ............................................... .................................................. ................... 243

Programa utilitário de arquivo ............................................... .................................................. ............................................ 243

Programa utilitário de tamanho ............................................... .................................................. .......................................... 243

Ferramentas de análise detalhada ............................................... .................................................. ............. 244

ldd ................................................. .................................................. .................................................. ................. 244

nm ................................................. .................................................. .................................................. ................ 246

objdump ................................................. .................................................. .................................................. ........ 248

readelf ................................................. .................................................. .................................................. .......... 257

Ferramentas de fase de implantação ............................................... .................................................. .......... 266

chrpath ................................................. .................................................. .................................................. ......... 267

patchelf ................................................. .................................................. .................................................. ......... 268

faixa................................................. .................................................. .................................................. .............. 269

ldconfig ................................................. .................................................. .................................................. ......... 269

Ferramentas de análise de tempo de execução ............................................... .................................................. ............. 269

strace ................................................. .................................................. .................................................. ........... 269

addr2line ................................................. .................................................. .................................................. ....... 270

gdb (GNU Debugger) ............................................. .................................................. .......................................... 271

Ferramentas de biblioteca estática ............................................... .................................................. ................... 273

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 231/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
ar ................................................. .................................................. .................................................. .................. 273

■ Capítulo 13: Linux How To's .......................................... ................................................. 277

Depurando a vinculação ............................................... .................................................. ............. 277

Determinando o tipo de arquivo binário ............................................. ................................................. 278

Determinando o ponto de entrada do arquivo binário ............................................ ........................................ 279

Determinando o ponto de entrada executável ............................................. .................................................. ............. 279

Determinando o ponto de entrada da biblioteca dinâmica ............................................ .................................................. ..... 279

Símbolos de lista ................................................ .................................................. ............................ 280

Liste e examine as seções .............................................. .................................................. ........ 281

Listando as seções disponíveis .............................................. .................................................. ............................ 281

Examinando Seções Específicas ............................................... .................................................. ............................ 281

Listar e examinar segmentos .............................................. .................................................. ...... 283

Desmontando o Código ............................................... .................................................. ........... 283

Desmontando o arquivo binário .............................................. .................................................. ........................... 283

Desmontando o processo em execução .............................................. .................................................. ................. 283

Identificando a compilação de depuração .............................................. .................................................. ....... 284

Listando Dependências de Tempo de Carregamento ............................................. .................................................. 284

Listando as bibliotecas conhecidas pelo carregador ........................................... ...................................... 284

Listando bibliotecas vinculadas dinamicamente .............................................. ........................................... 284

strace Utility ................................................ .................................................. .................................................. ... 285

Variável de ambiente LD_DEBUG ............................................... .................................................. ..................... 285

/ proc / <ID> / maps Arquivo ......................................... .................................................. ............................................. 285

Utilitário lsof ................................................ .................................................. .................................................. ....... 286

Maneira Programática ................................................ .................................................. .......................................... 287

Criando e mantendo a biblioteca estática ............................................ .................................. 290

■ Capítulo 14: Caixa de ferramentas do Windows ........................................... ............................................ 291

Gerenciador de biblioteca (lib.exe) ........................................... .................................................. ............. 291

lib.exe como uma ferramenta de biblioteca estática .......................................... .................................................. ............................... 292

lib.exe no Reino de Bibliotecas Dinâmicas (Ferramenta de Importação de Biblioteca) .................................... .................................... 296

Utilitário dumpbin ................................................ .................................................. ........................ 297

Identificando o tipo de arquivo binário ............................................. .................................................. .......................... 298

Listando os símbolos DLL exportados ............................................. .................................................. ...................... 299

Listando e examinando as seções ............................................. .................................................. ................... 300

Desmontando o Código ............................................... .................................................. ................................... 302

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 232/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
Identificando a compilação de depuração .............................................. .................................................. ............................... 303

Listando as dependências de tempo de carregamento ............................................. .................................................. .................. 306

Dependency Walker ................................................ .................................................. ................. 307

Índice................................................. .................................................. .............................. 309

Sobre o autor

Milan Stevanovic é consultor sênior de software de multimídia baseado na área da baía de São Francisco. A extensão de sua experiência em engenharia cobre uma
infinidade de disciplinas, que vão desde o design de hardware digital e analógico em nível de placa e programação de montagem, até o design C / C ++ e
arquitetura de software. Seu foco profissional tem sido no domínio da análise de uma variedade de formatos de multimídia compactados e design relacionado a
uma variedade de estruturas de multimídia (GStreamer, DirectX, OpenMAX, ffmpeg) no Linux (desktop, incorporado, Android nativo), bem como no Windows.

Ele projetou software multimídia para várias empresas (C-Cube, Philips, Harman, Thomson, Gracenote, Palm, Logitech, Panasonic, Netflix), criando uma
variedade de produtos de tecnologia de ponta (chip decodificador de DVD ZiVA-1 e ZiVA-3, Processador de mídia Philips TriMedia, telefones celulares Palm Treo
e Palm Pre, aplicativo Netflix para Android).

Ele é um membro original da dupla de desenvolvedores (junto com David Ronca) que criou o projeto de código aberto avxsynth (o primeiro port de avisynth para
Linux).

Ele possui um diploma de MSEE pela Purdue University (1994) e um diploma de graduação em EE (1987) e Música - performance de flauta (1990) pela University
of Belgrado.

Sobre os revisores técnicos


Nemanja Trifunovic nasceu em Kragujevac, Sérvia. Agora ele mora na área de Boston com sua esposa e filhas. Ele escreveu seu primeiro programa aos 13 anos
em um Sinclair Spectrum e se tornou um desenvolvedor de software profissional após se formar. Atualmente ele é um especialista em desenvolvimento na SAP,
trabalhando no sistema de banco de dados HANA. No passado, ele trabalhou na Microsoft, Lionbridge e Lernout & Hauspie Speech Products. Ele é um dos mais
ilustres colaboradores do Code Project de artigos relacionados à programação (consulte www.codeproject.com/Members/Nemanja-Trifunovic ,
www.codeproject.com/Articles/Nemanja-Trifunovic#articles ).

Ben Combee é um desenvolvedor líder na estrutura Enyo JS. Em uma vida anterior, ele trabalhou nas ferramentas CodeWarrior para Palm OS e CodeWarrior para
Win32 na Metrowerks, além de muitos projetos na Palm, incluindo o netbook Foleo e na arquitetura do Palm webOS.

Miroslav Ristic, por uma cadeia de eventos, ao invés de se tornar um piloto de caça a jato, formou-se e recebeu seu MSc em Engenharia da Computação na
Universidade de Novi Sad, Sérvia. Atualmente ele mora na área da Baía de São Francisco, buscando seus objetivos de carreira. Seus interesses abrangem uma
ampla variedade de tópicos. Ele é apaixonado por criar coisas do zero. Ele tem um casamento feliz com a extraordinária e surpreendente senhora dos seus sonhos,
Iva.

Agradecimentos
Muitas pessoas causaram um impacto duradouro na maneira como penso e raciocino, e como vejo o mundo, especialmente o mundo da tecnologia. Como este é
meu primeiro livro publicado, que imagino ser uma ocasião especial para todos os autores, terei a liberdade de expressar minha gratidão a uma longa lista de
pessoas.

Se eu não tivesse encontrado um grupo de professores superestrelas ensinando no meu 12º Ginásio de Belgrado, toda a minha trajetória de vida provavelmente
teria tomado uma direção significativamente diferente (não, eu não teria acabado no lado errado da lei; provavelmente teria tornar-se um músico / arranjador /
compositor profissional, um Quincy Jones ou Dave Grusin de algum tipo). As habilidades matemáticas que ganhei como aluno do professor Stevan Sijacki foram

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 233/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html
meu ingresso para os estudos de graduação em EE, embora uma carreira musical tenha sido planejada originalmente. Minha professora de física Ljubinka Prokic e
sua insistência na clareza e precisão, além de seu amor e devoção à física e aos alunos dela, moldaram minha abordagem em relação ao conhecimento em geral,
resumida melhor por Einstein "se você não pode explicar simplesmente, você não entendo bem o suficiente. " Finalmente, a professora de língua e literatura Jelena
Hristodulo me ensinou a mais importante de todas as lições: que aqueles que mergulham mais fundo em "porquês" em vez de nadar na superfície entre "o que é"
estão fadados a realmente entender o mundo ao seu redor e mudá-lo substancialmente. Embora ela tivesse poetas e filósofos em mente, acho que essa receita se
aplica surpreendentemente bem a pessoas com formação técnica.

Sem o Dr. George Wodicka, cujo assistente de pesquisa eu estava na Purdue University, provavelmente não estaria nos Estados Unidos hoje, então mais de 20 anos
de minha carreira no Vale do Silício não teriam acontecido, e muito provavelmente este livro nunca teria sido escrito.

O incentivo do Dr. George Adams, da Purdue University, para fazer seu curso básico de Modelos e Métodos Computacionais foi um primeiro passo decisivo na
longa jornada de transformação da minha carreira de um jovem engenheiro de design de hardware para um profissional de software experiente com mais de 20
anos de experiência.

A forte crença de David Berkowitz, meu gerente na Palm, de que minhas habilidades em design de multimídia podiam e deveriam se expandir para o território do
Linux foi um dos momentos decisivos de minha carreira. Suas habilidades pessoais únicas e sua capacidade de criar uma equipe coesa fizeram do meu tempo em
sua equipe do Palm Multimedia Group uma experiência memorável. A atmosfera na cafeteria do Palm depois de assistir a transmissão do vídeo da apresentação
do Palm Pre que tirou o mundo da tecnologia de suas meias no show CES em Las Vegas fez daquele 8 de janeiro de 2009 o dia mais memorável da minha carreira
profissional. Meu conjunto de habilidades de engenharia foi enriquecido por muitas experiências e orientações significativas, algumas das quais me levaram
diretamente ao material apresentado neste livro.

Trabalhar com Saldy Antony foi outra experiência verdadeiramente inspiradora. Após os anos que passamos juntos na equipe Philips TriMedia, nossas carreiras
seguiram diferentes direções. Enquanto eu permanecia profundamente imerso nos detalhes imediatos da multimídia, ele espalhou tremendamente suas
habilidades no domínio da arquitetura e gerenciamento de software. Quando nossos caminhos se cruzaram novamente na 2-Wire e mais tarde na Netflix, a era do
código aberto já entrou na vida de um desenvolvimento profissional de multimídia. A cada dia que passa, o trabalho diário do profissional de software multimídia
significa muito menos escrita de código e mais integração existente de terceiros / código-fonte aberto. As conversas com Saldy nas quais ele tentou me convencer
de que eu deveria aprimorar meu conjunto de habilidades com as habilidades de um engenheiro de desenvolvimento de software definitivamente tiveram algum
efeito em mim. Contudo, vê-lo em ação, arregaçar as mangas no tempo livre entre as reuniões de nível gerencial, juntar-se à equipe nos cubículos e resolver
rapidamente qualquer problema relacionado ao compilador, linker, bibliotecas e problemas de implantação de código definitivamente teve uma impressão
duradoura no mim. Foi quando decidi "abraçar o monstro" e aprender o que originalmente não considerava vital para o meu papel pessoal na indústria.

O convite de David Ronca, gerente do Netflix Encoding Technology Group, para trabalhar em algo realmente interessante é outra pedra angular na jornada de
criação deste livro. O "algo realmente interessante" foi o projeto de código aberto de conversão da popular ferramenta de pós-produção de vídeo avisynth do
Windows para Linux,

o projeto conhecido como avxsynth. Sua visão excepcionalmente clara e requisitos arquitetônicos firmemente definidos, combinados com a liberdade que ele me
deu para investigar os detalhes de implementação, levaram a um tremendo sucesso. O projeto, realizado em um período de apenas 2,5 meses, também foi um
imenso aprendizado para mim. Encontrar e superar as dificuldades ao longo do caminho exigiu passar horas pesquisando tópicos e formando meu tesouro
pessoal de dicas e truques relacionados. Conversas diárias com os membros do meu grupo (Dra. Anne Aaron, Pradip Gajjar e especialmente Brian Feinberg, o
arquivo ambulante de peças raras de conhecimento) sobre os lattes de David me ajudaram a ter uma ideia de quanto mais ainda tenho que aprender.

O encontro com o Diretor Editorial da Apress, Steve Anglin, foi uma experiência semelhante à de um filme. A última vez que vi alguém remotamente parecido
com ele foi quando assistia a uma série de detetives na TV nos anos 1970, quando era criança, em Belgrado, na Sérvia. Inteligente, comunicativo, de raciocínio
rápido, reconhecendo imediatamente as coisas certas, reagindo a um palpite na hora certa e direto ao ponto, ele era o tipo de profissional que eu quase parei de
acreditar que existia. A colaboração com ele tornou o processo de publicação deste livro uma experiência memorável.

A editora de aquisição da Apress, Michelle Lowman, realizou o esforço decisivo para revisar e apresentar os materiais do livro por meio de várias rodadas de
discussões da equipe da Apress, pelas quais sou profundamente grato.

Meus agradecimentos especiais também à minha equipe editorial da Apress (Ewan Buckingham, Kevin Shea, Jill Balzano, James Markham e o exército de outras
pessoas com quem não tive contato direto). O caractere 'senhor britânico' que uso no livro para apontar a natureza do arquivo executável não foi, de fato,
inspirado por Ewan Buckingham, mas não seria um erro se fosse. Seu controle sobre o fluxo principal do esforço editorial foi soberano e direto ao ponto em muitas
ocasiões.

Sem a intervenção de última hora da minha talentosa sobrinha Jovana Stefanovic e seu algoritmo DSP Brosnan-Clooney, minha foto da capa seria igual a mim, ou
talvez até pior.

Finalmente, a contribuição da minha equipe de revisores (Nemanja Trifunovic, Miroslav Ristic, Ben Combee), o apoio de amigos (David Moffat, Pradip Gajjar) e
do grupo misterioso conhecido coletivamente como "Arques 811 ex-grupo Philips" (especialmente Daniel Ash e Thierry Seegers) provou ser muito valioso. Sou
profundamente grato por seus esforços e tempo gasto em suas vidas profissionais ocupadas para fornecer feedback sobre o conteúdo do livro.

Por fim, sem o amor e o apoio de minha esposa Milena, meu filho Pavle e sua filha Selina, e sua paciência durante muitos fins de semana e noites trabalhando no
material do livro, todo o projeto não teria acontecido.

xx

1 Um programa é normalmente composto de muitas unidades de tradução. Embora seja perfeitamente possível e legal manter todo o código-fonte do projeto
em um único arquivo, existem boas razões (explicadas na seção anterior) para que isso não aconteça.

2 Ferramentas do Team Foundation Server Jj Ferramentas do Visual Studio

Dotfuscator Software Services ^ Gerenciar configurações de ajuda ■ ENU nh MFC-ATI Trace Tool jf Spy * -
t

it Visual Studio 2010 Remote Debu ^^^ EU Visual Studio Command Prompt QZ Visual Studio x64 Cross Tools Co

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 234/235


21/09/2021 22:07 Advanced C and C++ Compiling ( PDFDrive ).html

3 Tipo especifica o tipo de ação que o carregador precisa executar no operando da instrução montador para reparar os problemas causados ​pela tradução do
endereço. O formato binário ELF mostrado na Figura 8-8 (Figura 1-22 da especificação ELF) especifica os seguintes tipos de realocação.

Uma única biblioteca dinâmica utiliza estritamente uma das técnicas de coordenação do linker-carregador para resolver tanto o Cenário 1 quanto o Cenário 2 (se
necessário). Não pode acontecer que a mesma biblioteca dinâmica resolva o Cenário 1 pela abordagem LRT e os problemas do Cenário 2 pela abordagem PIC (ou
vice-versa).

4 O binário cliente original (ou seja, o aplicativo de demonstração simples inicial) será deixado intencionalmente intocado. Ao não reconstruí-lo, ele irá imitar
perfeitamente o aplicativo legado, construído no momento em que a biblioteca dinâmica apresentava a versão 1.0 inicial.

5 $ nm -u <path-to-binary> é útil quando você deseja listar os símbolos indefinidos da biblioteca (ou seja, os símbolos que a própria biblioteca não contém,
mas conta para serem fornecidos em tempo de execução, possivelmente por algum outro biblioteca dinâmica).

O artigo da web em www.thegeekstuff.com/2012/03/linux-nm-command/ lista os 10 comandos nm mais úteis .

6
vários caminhos podem ser definidos, separados por dois pontos (:)

7 O sinalizador / m intercala as instruções do montador com as linhas de código C / C ++ (se disponíveis), conforme mostrado na Figura 12-33.

(gdb) disassemble / mr main

Salto do código assembler para a função principal:

117 {

0X08048875 <+0>: 55 push ebp

0x08048876 * + i>: 89 eS mov ebp, esp

0x08048878 <+3>: 83 e4 f0 e esp.OxfffffffO

0x0804887b <+6>: 83 ec 20 sub esp, 0x20

118 int t = 0; Ox0804887e <+9>: C7 44 24 14 00 00 00 00 00 mov DWORD PTR [esp + 0Xl4], 0X0

119 dl_iterate_phdr (header_handler, NULL); 0X08048886 <+17>: C7 44 24 04 00 00 09 00 mov DWORD PTR [esp + 0x4] , 0x0 Ox0804888e <+25>: C7 04 24 5f 87 04 08 mov DWORD PTR
[esp], 0x804875f 0X08048895 <+32> : e8 a6 fc ff ff chamada 0x8048540 <dl_iterate_phdr @ plt>

120

121 int primeiro = shlibNonStaticAccessedAsExternVarlable + 1;

0x0804889a c + 37>: al 30 a0 04 08 mov eax, ds: 0x804a030 0x0804889f <+42>: 83 cO 01 add eax, 0xl

0x080488a2 <+45>: 89 44 24 18 mov DWORD PTR [esp + 0xl8], eax Figura 12-34. Combinando sinalizadores de desmontagem / r e / m

file:///C:/Users/noels/Desktop/Advanced C and C++ Compiling ( PDFDrive ).html 235/235

Você também pode gostar