Como funcionam os compiladores no Linux: do código fonte ao binário

Aula 42 · GNU/Linux para Servidores

Transcrição do áudio

Chegou a hora de falar sobre compiladores, é coleguinha. Naturalmente, nós não escrevemos linguagem de máquina, somos seres humanos. Então, naturalmente, nós temos que ter um processo de conversão do nosso código humano para um código de máquina. Então, vamos lá. Então, é um programa em uma determinada sintaxe. Posso estar programando em C, C++, P, Python. Bom, e, naturalmente, muitas outras. Então, nós temos a possibilidade de pegar essa linguagem com essa sintaxe, bem humano, e, naturalmente, converter ela em algum binário. E, sim, Python dá para fazer binários, né? Procura um tal de PyStaller. Bom, vamos ver aqui os tipos de compiladores. Nós temos vários tipos de compiladores, fornecedores diferentes. Alguns utilizam uma virtualização de aplicação, como alguns, por exemplo, como C-Sharp. A maioria deles é projetada para converter, então, um código humano para um código que a máquina vai compreender. Naturalmente, nesse processo, nós temos algumas etapas. E uma das etapas é o link do meu programa que estou compilando e o sistema operacional, alvo do meu programa. Bom, então, você não vai conseguir compilar, por exemplo, o Nulinox, um programa nativo Linux para rodar no Windows e nem do Windows para o Linux. Embora existam algumas bibliotecas que você consegue fazer compilação, multiplataforma, mas aí, cara, é uma grande arrada infernal. Beleza? Bom, e aí nós temos agora a seguinte... Não é bem uma guerra, né? Nós temos os namantes do interpretado, ou seja, o cara que faz o script. Eu sou um deles. Eu faço mais scripts ao longo do meu dia do que, realmente, eu programo algo que será compilado. Por exemplo, Python, por exemplo, PHP, entre outros. Está cada dia mais raro o dia que eu abro lá o meu GT e começo a programar em C mais mais. Quando eu abro o terminal e começo a programar diretamente lá no C C mais mais. Bom, eu acredito que isso vai ficar por passado. A vantagem do script que você é rapidamente sem precisar de um procedimento muito complexo, você edita e altera o que tiver que alterar. Ponto final. Legal? Isso é importante. Lembrando que é muito mais rápido e muito mais fácil você executar algo que está na linguagem de máquina, porque máquina entende máquina, do que você pegar o script, executar ele, desculpe, executar o interpretador que o interpreta. É muito mais difícil você ter que executar um programa, então, que interpreta o script. Ah, trava a língua agora, hein? E desses executores, desses interpretadores de script, você tem o BESCH, o CSH, o KSH, o SH, os VSH, o BESCH, o Python, o PHP, entre tantos outros, tantos outros. Beleza. Algo que vocês provavelmente já viram eu fazer, então vamos lá, vou abrir um terminal, vou aplicar um zoom aqui. Bom, então, quando eu crio, por exemplo, um script na minha script.sh, eu tenho que na primeira linha colocar um tal de shellbang, de barra bem barra BESCH, certo? O que é essa linha? Essa linha, veja, isso é um script, isso não vai ser convertido em um programa, uma linguagem de máquina. Então, como que o sistema operacional sabe qual o interpretador vai conseguir entender esse texto que vai estar aqui? É um texto humano. Então, o sistema operacional, ele pega essa primeira linha que tem o shellbang, tá? Então, ele carrega esse programa executável e passa o meu script.sh como argumento para o BESCH. Então, o BESCH executa, na verdade, interpreta e executa os comandos que ele interpretou. Da mesma forma, todos esses. Legal? Mas vamos aqui, então, ver como que é um programa realmente criado. Vamos utilizar o GCC, tá? Clássico do projeto GNU. Nós vamos fazer esse programa aqui, naturalmente, em C, e aí nós vamos passar por um procedimento. Primeiro, é um pré-processamento. Bom, então, quando você escreve o teu código, ele tem que ser pré-processado, localizado árvore sintática, dicionário de palavras, no caso, nome de variáveis, nome de funções, entre outras coisas. Então, ele faz uma análise sintática, faz uma análise ali, então, do dicionário de palavras reservadas e, bom, se tudo estiver certinho, então, ele passa por um compiler que vai transformar o meu código fonte, escrito em linguagem humana, como esse aqui. Cadê? Como esse aqui, tá? Ele vai passar esse cara aqui para assemble. Bom, legal. O assemble eu posso executar? Sim, eu posso executar o assemble. Mas, não fica muito legal. Então, geralmente, nós precisamos de chegar numa linguagem que a máquina compreenda aqui embaixo, tá? Ainda não tá legal. É um intermediário. Pega o meu assemble, meu código assemble, e passo por um procedimento chamado assembler. E aí, ele vai gerar uma pancada de arquivos .o. Esses arquivos .o são artefatos. Esses artefatos são códigos, são como eu posso dizer, rotinas. Vai gerar uma pancada de arquivos .o, que eu não posso pôs executar. Legal. Tá. Agora, eu preciso de juntar as bibliotecas do sistema opressional e vocês já utilizaram as bibliotecas do sistema opressional. Eu preciso de jogar as bibliotecas do sistema opressional e os arquivos .o dentro de um processo chamado linker. Então, eu gero uma linguagem de máquina para esse sistema opressional. Veja, quando eu faço isso aqui, eu tenho duas formas de jogar uma biblioteca num programa, tá? Preste atenção. Eu posso aqui em cima, injetar o código da biblioteca no meu programa. E aí, o meu programa vai ficar gigante. Bom, vai ficar um assemble monstruoso, tá? Vai gerar uma pancada de .o, mas porém, eu vou ter menos biblioteca do sistema opressional dependente aqui. Esse passo. Quer dizer que, naturalmente, o meu executável, ele é menos aderente a um sistema específico. Ou seja, ele é mais genérico. Entendeu? Quando eu evito de colocar as bibliotecas aqui em cima, injetar as bibliotecas aqui em cima no meu código, e eu trago, eu praticamente faço link as bibliotecas já compiladas no sistema opressional, ou seja, eu não injeto o código de outras bibliotecas, é que eu posso ter o código ou eu posso ter ele já compilado. Quando eu faço referência ao compilado, aqui embaixo em library, ele vai adicionar, então, a referência a algo compilado. Então, você vai estar mais aderente a um sistema opressional muito específico. Até o ponto de você, tipo assim, só roda isso no Débian, ou só roda isso no Fedora, nesse nível. Beleza? Então, cara, temos, sim, alguns problemas no mundo Linux com relação à compilação. É muito complexo a compilação. No mundo Windows, a compilação é muito fácil, porque, geralmente, um núcleo do Windows dura 11 anos, 10 anos, 11 anos. Então, se você não é programador, você não sabe. Então, vou te explicar. Sempre que você faz um produto novo, você gasta muito dinheiro. Então, você tem que passar alguns anos ganhando dinheiro. Então, se você fica toda hora construindo algo novo, você nunca ganha dinheiro. Então, você para, começa a ganhar dinheiro nos próximos anos. Por isso que o cor do Windows demora tantos anos para evoluir. 10 anos, né? Veja o Windows XP. Veja o Windows 7. Então, o que que acontece? Veja a própria linha 9x, né? No mundo Linux, cara, eu tenho um grande problema, que eu tenho uma pancada de gente construindo a todo momento. Não tem esse pensamento. Bom, vamos lá. Cara, eu vou dar um curso para você de forense. Está lá no curso hacker.com.br, curso hacker.com.br, curso de forense. No curso de forense, nós fazemos extração de hexadecimal e strings. É possível compreender que tipo de programa é esse, que palavras tem aqui dentro. Dá para às vezes extrair o RLP, tá? Aqui dentro, aqui dentro. Então, lá no curso, eu vou mostrar para vocês o resultado da compilação. Legal? Beleza? Bom, então vamos lá. Vamos ter aqui um programinha, né? Eu não queria fazer nessa máquina que eu estou, porque, bom... Muito lixo, né? Vamos lá. Meu programa... Nano, programa.c. Legal? E aí, aqui, vamos pegar o general, o general include. Opa! O include st.h. Tá? O Linux, ele não é Windows, lógico. Então, você vai encontrar no Linux tudo o que é naturalmente... Todo o código do Linux está no Linux, o código. Além dele em binário, você encontra no Linux ele todo em código lá dentro. Você pode simplesmente importar essas bibliotecas, incluindo as bibliotecas no C++. Então, estou incorporando st.io.h. Dentro do meu código, eu não estou fazendo referência ao binário. Estou puxando o código. Então, vai ficar bem grande isso aqui. Int, main, legal? E aí, eu aprendi que a melhor forma de você nunca esquecer as chaves é você já abrir e fecha a chave. Eu aprendi isso. Printf, bom, botafogo é uma bosta. Não seja botafoguinha assim. Legal? E nem deixe seu filho virar o botafoguinha assim. Ok? Deixa que toda a minha raiva, toda a minha revolta com relação ao botafogo. Legal? Beleza. Então, vamos finalizar aqui o main. Tá ok? Legal. Contro x, y, ok. Acabei de fazer um programa, um programa muito simples. Legal? Eu vou colocar esse cara aqui no topo sempre. Talvez eu vou ter que instalar o g mais mais, porque essa distribuição ali, no que você tem toda a restrita. Sudo apt, instal, g mais mais e o gcc. Já está instalado também. Os dois compiladores estão instalados. Então, primeiro passo que nós temos que fazer aqui, tá, é pegar e fazer tipo um pré-processamento dessa história toda. Client. Então, gcc, traço, traço e, ponto, barra. Eu chamei de programa, né? Programa C, traço O, o, traço O mais ou menos que o aluminúsculo. Cara, eu esqueci, acho que é o aluminúsculo. Beleza. Barra programa, ponto, I. Legal? Ah, bom, vamos lá. O programa, ponto, I. Peraí, ô minha Nossa Senhora, peraí, coleguinha, tá explicado. Oh, que vacilo, pô. Aqui é na raiz, cara. Viu porque que nós não usamos a conta de superusuário? Se eu tivesse como superusuário, ia dar certo, quer dizer, ia dar errado. E aí gerar um arquivo lá no meio do limbo do nada. E aquele arquivo ia ficar perdido lá, entendeu? As coisas são feitas para serem certas, sabe? Clear. More, beleza. More, programa, ponto, I, certo? Então, tá aqui. Então, repare programa, ponto, C, os builtings, os command lines, tá? O nome do arquivo, cara. E aqui ele vai gerar toda uma árvore, um dicionário de palavras, tá? Porque meu código, ele tem uma certa estrutura. Beleza? Bom, tem que passar por isso. Agora é a hora de a gente pegar e fazer uma compilação. A próxima estágia, então, é uma compilação propriamente dita para gerar o nosso assemble. Legal? Detalhe, traz o assemble, nós vamos chegar, então, na linguagem final ali da máquina. Então, vamos lá. Clear, G, C, C, traço, X, maiúsculo, ponto, barra, programa, ponto, I. Legal? Então, agora ele vai gerar alguns arquivos aqui, legal? Teste, ponto, S. Então, opa! Deu um monte de entera aqui, cara. Deu um monte de entera aqui. E também que isso aqui é um, como eu posso dizer, é um PDF, não é um documento original. Então, aqui eu consigo chegar aqui, ó. Aham! Olha só, WC, podemos, né? Traço L. Então, nós podemos pegar um catch, o ponto, barra, programa, programa, ponto, I. E eu jogo lá no WC, traço L. Oitocentas e quinze linhas, cara. Oitocentas e quinze linhas. Se você quer ver, MOA, programa, opa! Não, ele é o I, né? Ali é o I, poeguinha. Ali é o I, é o S. 30 linhas. Então, vamos lá. catch.barra, programa, ponto S. Aqui, ó. Aqui é tudo aqui, o que você vê lá na aula de AOC. Repare que string fixada, as strings elas são fixadas para entrar como algo estático. Lembra? Ele gerou tudo aqui, tá? O, ó, ó, o move key, o call. Tudo aqui. Beleza. Assemble gerado. Assemble gerado, tá? Próximo passo. Ah, eu já tinha também colocado os print screens aqui. Próximo passo. Nós vamos pegar e passar pelo assembler e gerar os arquivos ponto O. Um monte de arquivos ponto O agora depende do teu código, né? S, V, não é GCC, né? Barra, programa, ponto S, normalmente, traço output, ponto barra, programa, ponto O. Cara, cada arquivo que você compila, ou seja, imagine que seu sistema tenha 200 arquivos lá. Cara, você vai ter que fazer uns 200, ó. Beleza? Legal. Agora eu tenho o arquivo ponto O. Então, chegou a hora do último passo de fazer o linker. Então, finalizar a compilação para essa máquina, tá? Não podemos usar o GCC. Nós podemos usar o... Não poderia até usar o G mais mais. Não tem problema nenhum. Eu vou usar aqui o GCC. Eu vou usar o traço estático para juntar tudo num único arquivo para deixar o mais independente possível dessa versão do Debian, para aquele rode de outros Debianas. Por exemplo, GCC, traço estático, das bibliotecas mais estáticas a possível, do programa ponto C, traço O, tá? E vamos lá, ponto barra, programa. Ponto barra, programa. Só isso. E aí ele vai gerar tudo para mim. Então, se agora só eu chegar aqui e fazer o seguinte, ponto barra, programa e aí botar fogama, bosta. Tá claro, né? Coleguinha, parabéns, hein? Estamos aqui começando a fazer os programinhas, dá certo. Pogo logo. Nós vamos aprender a fazer também. Já tá no livro, né? Vou ensinar a fazer os instaladores, bonitinho, usar os pontos Debian, para cortar o botinho, tá? Já tá aí no livro, se você tiver dúvida, vá lá na frente e dê uma olhada. Até mais, tchau.
Voltar ao curso