Diferenças

Aqui você vê as diferenças entre duas revisões dessa página.

Link para esta página de comparações

Ambos lados da revisão anterior Revisão anterior
Próxima revisão
Revisão anterior
unix:desenvolvimento_em_linux [2015/11/23 14:40] – [Depuração] mazierounix:desenvolvimento_em_linux [2020/08/18 19:56] (atual) – edição externa 127.0.0.1
Linha 1: Linha 1:
 +===== UNIX: Ferramentas de Desenvolvimento =====
 +
 +No processo de codificação de um software, são atividades básicas:
 +
 +  * Edição
 +  * Compilação
 +  * Execução
 +  * Depuração
 +
 +Além destas, também são atividades necessárias, sobretudo à medida em que os softwares desenvolvidos crescem em tamanho e complexidade:
 +
 +  * Construção de bibliotecas
 +  * Gerência de versões
 +  * Documentação
 +
 +Neste módulo serão enumeradas algumas ferramentas de código aberto (//open source//) disponíveis para auxiliar nas diversas etapas do desenvolvimento de programas C/C++ em plataformas UNIX, mais particularmente no mundo Linux.
 +
 +
 +===== Edição de código =====
 +
 +Para a edição de código-fonte pode-se optar por editores de código-fonte como [[http://www.gnu.org/software/emacs/emacs.html|Emacs]] (ou [[http://www.xemacs.org|XEmacs]]), [[http://www.vim.org/|Vi]], [[http://www.nano-editor.org/|Nano]], [[http://www.jedsoft.org/jed/|Jed]], [[http://kate.kde.org/|Kate]] (no ambiente KDE), [[http://gedit.sourceforge.net/|Gedit]] (no ambiente Gnome), [[https://www.sublimetext.com|Sublime Text]], [[https://atom.io/|Atom]] e muitos outros. Pode-se também optar por ambientes de desenvolvimento integrado (IDEs) como [[http://www.eclipse.org/|Eclipse]], [[http://www.kdevelop.org/|Kdevelop]], [[http://anjuta.sourceforge.net/|Anjuta]], [[http://www.codeblocks.org/|Code::Blocks]] e diversos outros.
 +
 +Qual é a melhor opção ? Esta é uma pergunta difícil de responder, pois há muitas controvérsias...
 +
 +===== Compilação =====
 +
 +Existem vários compiladores C/C++ disponíveis livremente em ambiente Linux, mas o [[http://gcc.gnu.org/|GCC]] (GNU Compiler Collection) é de longe o compilador mais popular e certamente um dos melhores. O GCC compreende compiladores de front-end e geradores de código para várias linguagens de programação (C, C++, ObjC, Fortran, Ada, Java, ...) em [[http://gcc.gnu.org/install/specific.html|diversas plataformas]]. Além dele, sugere-se o compilador [[http://clang.llvm.org/|CLANG]] (para C/C++/ObjC), pela qualidade do código gerado e suas mensagens de erro bem mais compreensíveis que as do GCC.
 +
 +O GCC é um compilador que opera em linha de comando. Por default, o compilador irá realizar a compilação e ligação do(s) fonte(s), gerando um arquivo executável denominado ''a.out'' (que tem esse nome por razões históricas). Para executar esse arquivo basta invocá-lo, em uma linha de comando:
 +
 +<code C hello.c>
 +#include <stdio.h>
 +
 +int main ()
 +{
 +  printf ("Hello, world\n") ;
 +  return (0) ;
 +}
 +</code>
 +
 +<code>
 +$ gcc hello.c
 + 
 +$ ls -l
 +-rwxr-xr-x 1 prof 11724 Set 28 16:25 a.out
 +-rw-r--r-- 1 prof 45 Set 28 16:13 hello.c
 + 
 +$ a.out
 +Hello, world
 +</code>
 +
 +Pode-se redefinir o arquivo de saída com a opção ''-o'': 
 +
 +<code>
 +$ cc -o hello hello.c
 +</code>
 +
 +É possível compilar diversos arquivos interdependentes simultaneamente, como mostra o seguinte exemplo:
 + 
 +
 +<code C hello.c>
 +int main ()
 +{
 +  escreva ("Hello, world\n") ;
 +  return (0) ;
 +}
 +</code>
 + 
 +<code C escreva.c>
 +#include <stdio.h>
 +
 +void escreva (char *msg)
 +{
 +  printf ("%s", msg) ;
 +}
 +</code>
 + 
 +<code>
 +$ cc -o hello hello.c escreva.c
 +</code>
 +
 +Também é possível efetuar somente a compilação dos códigos-fonte, sem efetuar a ligação e a geração do executável. Nesse caso, serão gerados os arquivos com o código-objeto (''*.o'') dos correspondentes arquivos de código fonte (''*.c''), como mostra o exemplo abaixo:
 + 
 +<code>
 +$ cc -c hello.c escreva.c
 + 
 +$ ls -l
 +-rw-r--r--  1  prof     50  Set 28 16:13  escreva.c
 +-rw-r--r--  1  prof    788  Set 28 16:16  escreva.o
 +-rw-r--r--  1  prof     45  Set 28 16:13  hello.c
 +-rw-r--r--  1  prof    800  Set 28 16:16  hello.o
 +</code>
 +
 +Esses arquivos objeto poderão posteriormente ser ligados, gerando o executável:
 +
 +<code>
 +$ cc -o hello *.o
 +
 +$ ls -l
 +-rw-r--r--  1  prof     50  Set 28 16:13  escreva.c
 +-rw-r--r--  1  prof    788  Set 28 16:16  escreva.o
 +-rwxr-xr-x  1  prof  11724  Set 28 16:25  hello
 +-rw-r--r--  1  prof     45  Set 28 16:13  hello.c
 +-rw-r--r--  1  prof    800  Set 28 16:16  hello.o
 +</code>
 +
 +As opções usuais de execução do compilador GCC podem ser consultadas em sua página de manual (''man gcc'').
 +
 +===== Usando bibliotecas =====
 +
 +A linguagem C é rica em poder de expressão, mas relativamente pobre em funcionalidades. Para construir aplicações que fazem uso de funcionalidades específicas, como interfaces gráficas, comunicação via rede, fórmulas matemáticas complexas, etc, devem ser usadas bibliotecas. Algumas bibliotecas encapsulam chamadas do sistema operacional, sendo então chamadas de //bibliotecas de sistema//, enquanto outras provêm funcionalidades no espaço de usuário, como funções matemáticas e interfaces gráficas.
 +
 +As bibliotecas mais comuns, utilizadas por todas as aplicações e utilitários do sistema, são:
 +
 +  * ''libc'': na verdade um grande pacote de bibliotecas que provê funcionalidades básicas de entrada/saída, de acesso a serviços do sistema, à rede, etc.
 +
 +  * ''ld-linux'': provê as funções necessárias para a carga de bibliotecas dinâmicas, durante a inicialização do programa.
 +
 +Por default, essas duas bibliotecas são automaticamente incluídas e ligadas em todos os programas. Para compilar um programa que utiliza outras bibliotecas externas, algumas opções devem ser informadas ao compilador. Por exemplo, considere o seguinte programa, que faz o cálculo de Pi através de uma série de Gregory:
 +
 +<code c pi.c>
 +#include <stdio.h>
 +#include <math.h>
 +
 +int main ()
 +{
 +  int i ;
 +  double pi = 0 ;
 + 
 +  for (i=0; i < 1000000; i++)
 +    pi += pow (-1,i) / (2*i+1) ;
 +  pi *= 4 ;
 + 
 +  printf ("O valor aproximado de Pi é: %f\n", pi) ;
 +  return (0) ;
 +}
 +</code>
 +
 +Ao compilar esse programa, obtemos:
 +
 +<code>
 +$ cc pi.c -o pi
 +/tmp/ccCANYTf.o(.text+0x42): In function `main':
 +: undefined reference to `pow'
 +collect2: ld returned 1 exit status
 +</code>
 +
 +Esse erro ocorre porque o ligador não conseguiu encontrar a função ''pow'' em nenhum dos arquivos fonte ou objeto informados no comando, nem nas bibliotecas padrão. Essa função se encontra na biblioteca matemática ''/usr/lib/libm'', que deve ser informada ao ligador da seguinte forma:
 +
 +<code>
 +$ cc pi.c -o pi -lm
 +</code>
 +
 +A opção ''-lm'' indica o uso da biblioteca ''libm''. Da mesma forma, ''-lpthread'' indica o uso da biblioteca ''libpthread'', e assim por diante. O ligador irá procurar por arquivos de bibliotecas nos diretórios padrão (''/lib'', ''/usr/lib'', ...). Pode-se informar outros diretórios de bibliotecas ao ligador através da opção ''-L'' (detalhada mais adiante neste texto).
 +
 +Há duas formas básicas de ligar as bibliotecas a um programa executável:
 +
 +  * Na ligação estática (//static linking//), o código da biblioteca é incorporado ao executável. O executável fica maior, mas não depende de bibliotecas instaladas no sistema para poder executar.
 +  * Na ligação dinâmica (//dynamic linking//), o executável guarda apenas referências às bibliotecas necessárias, que são resolvidas somente quando o programa executável for lançado. O executável fica muito menor, mas precisa que todas as bibliotecas necessárias estejam presentes no sistema para executar.
 +
 +A ligação dinâmica é feita por default. Para compilar um programa, ligando-o estaticamente à bibliotecas, devemos executar:
 +
 +<code>
 +$ cc -static pi.c -o pi -lm
 +</code>
 +
 +O utilitário ''ldd'' permite verificar de quais bibliotecas dinâmicas um executável depende:
 +
 +<code>
 +$ ldd pi
 +    libm.so.6 => /lib/i686/libm.so.6 (0x40028000)
 +    libc.so.6 => /lib/i686/libc.so.6 (0x4004b000)
 +    /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
 +</code>
 +
 +Para encontrar as bibliotecas dinâmicas, são percorridos os diretórios indicados pelo arquivo de configuração ''/etc/ld.so.conf'' e pela variável de ambiente ''LD_LIBRARY_PATH''. O utilitário ''ldconfig'' permite atualizar as informações sobre bibliotecas dinâmicas nos diretórios padrão (ou nos diretórios informados via linha de comando).
 +
 +===== Construindo bibliotecas estáticas =====
 +
 +Para construir uma biblioteca de ligação estática são necessários vários passos. Inicialmente, todos os arquivos-fonte que irão compor a biblioteca devem ser compilados, para gerar seus arquivos-objeto correspondentes:
 +
 +<code>
 +$ gcc -c file1.c
 +$ gcc -c file2.c
 +$ gcc -c file3.c
 +...
 +</code>
 +
 +A seguir, deve ser usado o utilitário ''ar'' (//archiver//) para juntar todos os arquivos-objeto em uma biblioteca:
 + 
 +<code>
 +$ ar rvs libtest.a file1.o file2.o file3.o ...
 +</code>
 +
 +Os flags ''rvs'' indicam: 
 +
 +  * ''r'' (//replace//): substituir versões anteriores dos arquivos na biblioteca, caso existam
 +  * ''v'' (//verbose//): mostrar na tela as inclusões que estão sendo realizadas
 +  * ''s'' (//symbols//): criar uma tabela dos símbolos que estão sendo agregados à biblioteca
 +
 +O utilitário ''ar'' possui diversos outros //flags//. Por exemplo, pode-se consultar o conteúdo de uma biblioteca estática:
 +
 +<code>
 +$ ar t libtest.a
 +file1.o
 +file2.o
 +file3.o
 +</code>
 +
 +Pode-se consultar todos os símbolos definidos em uma biblioteca estática (ou em qualquer arquivo objeto) através do utilitário ''nm'':
 +
 +<code>
 +$ nm libtest.a
 +file1.o:
 +00000000 T a
 +         U printf
 +
 +file2.o:
 +00000000 T b
 +         U printf
 + 
 +file3.o:
 +00000000 T c
 +           U printf
 +</code>
 +
 +A opção ''-L.'' é necessária para incluir o diretório corrente nos caminhos de busca de bibliotecas do ligador. Esta abordagem é melhor que a anterior, pois neste caso o ligador somente irá incluir no executável final os objetos que forem efetivamente necessários. 
 +
 +Observe que a biblioteca foi informada ao ligador na opção ''-ltest''. Por default, ao encontrar uma opção ''-labc'', o ligador irá procurar pela biblioteca ''libabc.a'' nos diretórios default de bibliotecas (''/lib'', ''/usr/lib'', ''/usr/local/lib'', ...) e nos diretórios informados pela opção ''-L''.
 +
 +Para atualizar/incluir qualquer arquivo da biblioteca, basta executar ''ar'' novamente, indicando o(s) arquivo(s) a atualizar/incluir:
 +
 +<code>
 +$ ar rvs libtest.a file2.o
 +</code>
 +
 +A forma mais simples de usar a biblioteca é indicá-la ao compilador no momento da ligação:
 +
 +<code>
 +$ gcc -o meuprograma meuprograma.c libtest.a
 +</code>
 +
 +Uma opção abreviada de ligação pode ser utilizada. Nela, não é necessário indicar o nome completo da biblioteca:
 +
 +<code>
 +$ gcc -o meuprograma meuprograma.c -L. -ltest
 +</code>
 +
 +===== Construindo bibliotecas dinâmicas =====
 +
 +A construção de uma biblioteca de ligação dinâmica é um pouco mais complexa. Primeiro, é necessário compilar os arquivos-fonte que irão compor a biblioteca usando a opção ''-fPIC'', que irá gerar código independente de posição (//PIC - Position Independent Code//):
 +
 +<code>
 +$ gcc -fPIC -c file1.c
 +$ gcc -fPIC -c file2.c
 +$ gcc -fPIC -c file3.c
 +...
 +</code>
 +
 +Depois, pode-se criar a biblioteca dinâmica, a partir dos arquivos-objeto:
 +
 +<code>
 +$ gcc -g -shared -Wl,-soname,libtest.so.0 -o libtest.so.0.0 file1.o file2.o file3.o
 +</code>
 +
 +Observe que a opção ''-Wl'' permite informar a opção ''-soname=libtest.so.0'' ao ligador. Essa opção permite definir o nome e versão da biblioteca.
 +
 +Finalmente, para instalar a biblioteca, deve-se movê-la para o diretório adequado e gerar os links necessários:
 +
 +<code>
 +$ mv libtest.so.0.0 /usr/local/lib
 +$ ln -s libtest.so.0.0 libtest.so.0
 +$ ln -s libtest.so.0 libtest.so
 +
 +$ ls -l /usr/local/lib
 +lrwxrwxrwx  1  prof      12   Out 2 18:20  libtest.so -> libtest.so.0
 +lrwxrwxrwx  1  prof      14   Out 2 18:06  libtest.so.0 -> libtest.so.0.0
 +-rwxr-xr-x  1  prof    6914   Out 2 18:06  libtest.so.0.0
 +</code>
 +
 +Para usar a biblioteca:
 +
 +<code>
 +$ gcc -o meuprograma -L. -ltest meuprograma.c
 +$ meuprograma
 +</code>
 +
 +Caso a biblioteca esteja em um diretório não listado em ''/etc/ld.so.conf'' (arquivo de configuração do carregador e ligador dinâmico), deve-se incluir o diretório nesse arquivo e a seguir executar ''ldconfig'', ou informar o carregador dinâmico através da variável de ambiente ''LD_LIBRARY_PATH'':
 +
 +<code>
 +$ export LD_LIBRARY_PATH=.
 +$ meuprograma"
 +</code>
 +
 +===== Organização do código =====
 +
 +À medida em que um software cresce em tamanho e funcionalidades, seu código-fonte deve ser organizado corretamente para facilitar sua compreensão, manutenção e evolução. É importante quebrar o código-fonte em arquivos separados,  dividindo-o de acordo com os módulos e/ou funcionalidades do sistema.
 +
 +Ao dividir o código-fonte em arquivos separados, alguns cuidados devem ser tomados:
 +
 +  * Agrupe funções correlatas em um mesmo arquivo
 +  * Coloque os protótipos das funções públicas e as estruturas de dados necessárias às mesmas em um arquivo de cabeçalho ''.h'', a ser incluso pelos demais arquivos que usarem essas funções.
 +  * Somente efetue inclusões (''#include'') de arquivos de cabeçalho (''.h''). Evite inclusões de arquivos ''.c'' (deixe para o ligador efetuar a inclusão desse código no executável final).
 +
 +O arquivo principal (''main.c'') deve incluir todos os arquivos de cabeçalho necessários e também deve definir a função ''main''.
 +
 +<code c main.c>
 +#include "complex.h"
 +
 +int main()
 +{
 +  complex_t a, b, c ;
 + 
 +  complex_define (&a, 10, 17) ;
 +  complex_define (&b, -2, 4) ;
 +  complex_sum (&c, a, b);
 +  ...
 +}
 +</code>
 +
 +O arquivo de cabeçalho ''complex.h'' deve declarar somente informações públicas: tipos de dados e protótipos de funções:
 +
 +<code c complex.h>
 +#ifndef __COMPLEX__
 +#define __COMPLEX__
 + 
 +typedef struct {
 +  float r,i;
 +} complex_t ;
 + 
 +void complex_define (complex_t *v, float r, float i) ;
 +...
 + 
 +#endif
 +</code>
 +
 +Deve-se observar o uso das macros de pré-compilação ''#ifdef'' e ''#define'', para evitar a repetição das definições no caso de múltiplas inclusões do arquivo de cabeçalho.
 + 
 +O arquivo correspondente ''complex.c'' conterá então as informações privadas dos módulos: estruturas de dados internas, variáveis globais e implementações das funções.
 +
 +<code c complex.c>
 +#include "complex.h"
 +
 +void complex_define (complex_t *v, float r, float i) 
 +{
 +  /* implementation of the function */
 +  ...
 +}
 +
 +...
 +</code>
 +
 +O arquivo ''complex.c'' pode ser compilado separadamente (gerando um arquivo objeto ''complex.o'' que poderá ser ligado ao arquivo ''main.o'' posteriormente). Essa organização torna mais simples a construção de bibliotecas e a distribuição de código binário para incorporação em outros projetos. Além disso, essa estruturação agiliza a compilação de grandes projetos.
 +
 +Um outro aspecto importante da organização do código é o uso de declarações ''extern'' para variáveis globais usadas em vários arquivos de código-fonte. [[http://stackoverflow.com/questions/1433204/how-do-i-use-extern-to-share-variables-between-source-files-in-c|Esta página]] contém uma excelente explicação sobre o uso correto da declaração ''extern''.
 +
 +===== O sistema Make =====
 +
 +O processo de compilação e ligação de um software composto por um grande conjunto de arquivos de código-fonte pode ser complexo e trabalhoso. Uma ferramenta que permite acelerar e facilitar esse processo é o sistema ''make''.
 +
 +O sistema ''make'' permite definir regras de dependência para a compilação de um grande conjunto de arquivos de códigos-fonte. Usando esse sistema, o processo de compilação pode ser muito agilizado, pois somente os arquivos necessários são compilados e ligados para reconstruir o arquivo executável final.
 +
 +Ao ser invocado, o comando ''make'' procura um arquivo ''Makefile'' no diretório local, contendo regras para a construção dos programas. O conteúdo típico de um arquivo ''Makefile'' pode ser visto {{unix:example.makefile.txt|neste arquivo de exemplo}} (extraído [[http://vergil.chemistry.gatech.edu/resources/programming/c-tutorial/make.html|desta página]]). A construção de arquivos ''Makefile'' está além do escopo deste curso. Mais informações podem ser facilmente obtidas na [[http://www.google.com/search?q=tutorial+make|Internet]] e no [[http://www.gnu.org/software/make/manual|manual do make]].
 +
 +===== Depuração =====
 +
 +O primeiro passo para a depuração de um programa executável é compilá-lo de forma a incluir todos os símbolos necessários ao processo de depuração (nomes das variáveis, referências às linhas do código fonte, etc). Isso é feito adicionando a opção ''-g'' ao comando de compilação:
 +
 +<code>
 +$ gcc -g -o pi pi.c -lm
 +</code>
 +
 +O depurador padrão para a linguagem C no Linux é o GDB (//GNU Debugger//). O GDB é um depurador em modo texto, com muitas funcionalidades mas relativamente complexo de usar para os iniciantes.
 +
 +Para iniciar uma depuração, basta invocar o GDB com o executável:
 +
 +<code>
 +$ gdb pi
 +GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
 +... (msgs diversas)
 +Lendo símbolos de pi...concluído.
 +(gdb) run
 +Starting program: /home/prof/maziero/organizar/pi/pi 
 +O valor aproximado de Pi é: 3.141592
 +[Inferior 1 (process 24652) exited normally]
 +(gdb) quit
 +</code>
 +
 +O prompt do GDB aceita diversos comandos, entre eles o //run// e o //quit//, ilustrados acima. Os comandos mais básicos disponíveis são:
 +
 +^ comando ^ ação ^ exemplos ^
 +| ''r''  | inicia a execução (//run//) | ''run''\\ ''r < dados.dat''\\ ''r > saida.txt'' |
 +| ''l'' | lista linhas do código-fonte | ''l 23'' |
 +| ''b'' | cria um ponto de parada (//breakpoint//) | ''b 17''\\ ''b main'' |
 +| ''c'' | continua após o ponto de parada | ''c'' |
 +| ''s'' | avança para a próxima linha de código (//step//) | ''s'' |
 +| ''n'' | avança para a próxima linha de código (//next// - não entra em funções) | ''n'' |
 +| ''p'' | imprime o valor de uma variável ou expressão | ''p soma''\\ ''p /x soma'' |
 +| ''watch'' | avisa quando uma variável muda de valor | '' watch i'' |
 +| ''disp'' | mostra o valor de uma variável ou expressão a cada pausa (//display//) | ''disp soma'' |
 +| ''set variable'' | Ajusta o valor de uma variável | ''set variable soma = 100'' |
 +| ''bt'' | Mostra a posição atual do programa (//backtrace//) | ''bt'' |
 +| ''frame'' | Seleciona/muda o frame de execução | ''frame 3'' |
 +| ''^x^a'' | alterna entre o modo padrão e NCurses ||
 +
 +Uma relação mais extensa de comandos pode ser encontrada neste {{:unix:gdb-refcard.pdf|GDB Reference Card}}.
 +
 +Vários guias de uso do GDB podem ser encontrados nos links abaixo: 
 +
 +  * [[http://beej.us/guide/bggdb|Beej's Quick Guide to GDB]]
 +  * [[http://www-2.cs.cmu.edu/%7Egilpin/tutorial/|A GDB tutorial]]
 +  * [[http://www.delorie.com/gnu/docs/gdb/gdb_toc.html|Debugging with GDB]]
 +  * [[http://heather.cs.ucdavis.edu/%7Ematloff/UnixAndC/CLanguage/Debug.html|Guide to faster, less frustrating debugging]]
 +  * [[http://www.dirac.org/linux/gdb/|Using GNU's GDB debugger]]
 +  * [[http://www.lrc.ic.unicamp.br/~luciano/courses/mc202-2s2009/tutorial_gdb.txt|Tutorial de GDB]] (em português)
 +
 +Como interfaces alternativas para o GDB, existe o modo //NCurses//, que pode ser ativado invocando o GDB com a opção ''-tui'' ou pelo comando ''^x^a''. Outras interfaces disponíveis em modo texto são o ''cgdb'', que usa comandos similares aos do VI, e o modo GDB do [[http://www.gnu.org/software/emacs/manual/html_node/emacs/GDB-Graphical-Interface.html|EMACS]].
 +
 +Também existem diversas interfaces gráficas para o GDB, como o [[http://www.gnu.org/software/ddd/|Data Display Debugger]] (ddd), [[https://wiki.gnome.org/Apps/Nemiver|Nemiver]], [[http://gede.acidron.com/|Gede]] e [[http://www.ultragdb.com|UltraGDB]]. O GDB também pode ser usado através de IDEs (//Integrated Development Environments//) gráficos como //Code::Blocks//, //Codelite//, //Eclipse//, //KDevelop//, //NetBeans//, etc.
 +
 +===== Depuração de memória =====
 +
 +Um dos problemas mais frequentes (e de depuração mais difícil) na programação em C é o uso incorreto da memória. Situações como acesso a posições inválidas de vetores ou matrizes (// buffer overflow//), uso de ponteiros não inicializados, não-liberação de memória dinâmica (//memory leaks//) podem gerar comportamentos erráticos difíceis de depurar.
 +
 +Há várias ferramentas para auxiliar na depuração de problemas de memória, vistas na sequência.
 +
 +=== Análise estática ===
 +
 +Ferramentas de análise estática examinam o código-fonte de uma forma mais detalhada que o compilador, permitindo encontrar diversos erros que podem passar despercebidos, como índices de vetores fora da faixa válida.
 +
 +Algumas ferramentas disponíveis para análise estática de código:
 +
 +  * ''cppcheck'':  verificador estático de código C/C++ (recomenda-se usar  flag ''--enable=all'')
 +  * ''splint'': idem
 +
 +=== Flags do compilador ===
 +
 +Opções do GCC para depuração de memória:
 +
 +  * ''-fsanitize=address'': ativa o [[https://en.wikipedia.org/wiki/AddressSanitizer|AddressSanitizer]], um detector de erros de memória em tempo de execução. O código do executável é instrumentado (são adicionadas instruções) para verificar erros de acesso a posições inválidas de memória.
 +  * ''-fcheck-pointer-bounds'': ativa a verificação de limites de ponteiros.
 +  * ''-fstack-protector'': gera código adicional para verificar a integridade da pilha (//flag// habilitado por default).
 +
 +<note important>
 +As verificações adicionadas por esses //flags// são efetuadas a cada acesso à memória, por isso têm um forte impacto no desempenho e no uso de memória do executável. Então, só devem ser usadas durante o processo de desenvolvimento e nunca no produto final.
 +</note>
 +
 +=== Bibliotecas de depuração ===
 +
 +São bibliotecas que instrumentam as rotinas de alocação/liberação de memória, permitindo depurar erros relacionados ao uso de memória dinâmica, como //memory leaks//, //double free// e //use after free//.
 +
 +  * [[http://dmalloc.com/|DMalloc]]
 +  * [[https://launchpad.net/ubuntu/+source/electric-fence/2.2.3|Electric Fence]]
 +
 +=== Depuradores de memória ===
 +
 +  * [[https://en.wikipedia.org/wiki/Mtrace|Mtrace]]
 +  * [[http://valgrind.org/|Valgrind]] (vide opções ''--tool=memcheck'' ou ''--tool=exp-sgcheck'')
 +
 +=== Despejo de memória ===
 +
 +Na maioria dos sistemas UNIX permite salvar em um arquivo o conteúdo da memória de um programa em execução (processo), quando este é interrompido por um erro. Esse arquivo se chama ''core file'' ([[https://en.wikipedia.org/wiki/Core_dump|despejo de memória]]) e pode ser aberto por depuradores, para auxiliar na compreensão da causa do erro.
 +
 +Para usar esse recurso deve-se seguir os seguintes passos:
 +
 +  - Habilitar a geração de arquivo //core// no terminal atual: <code>$ ulimit -c unlimited</code>
 +  - Compilar o programa com o flag de depuração:<code>$ cc -g teste.c -o teste</code>
 +  - Lançar o programa:<code>$ teste</code>
 +  - Quando o programa abortar por erro, será gerado um arquivo //core// no diretório corrente:<code>
 +$ ls -l
 +-rw-------  1 maziero maziero 262144 Nov 30 14:57 core
 +-rwxrwxr-x  1 maziero maziero   9417 Nov 30 14:56 teste
 +-rw-rw-r--  1 maziero maziero    101 Nov 30 14:56 teste.c
 +</code>
 +  - Alternativamente, pode-se **forçar o encerramento** (e a consequente geração do arquivo //core//) através de um sinal ''SIGQUIT'' (sinal n° 3). Esse sinal deve ser enviado ao processo através do comando ''kill'':<code>$ kill -3 PID</code> onde PID é o identificador numérico do processo a ser encerrado.
 +  - Uma vez de posse do arquivo //core//, basta abri-lo através do depurador (GDB):<code>$ gdb teste core</code>
 +
 +===== Tracing =====
 +
 +Uma ferramenta útil para auxiliar na depuração de programas é o utilitário ''strace'', que permite listar as chamadas de sistema efetuadas por um executável qualquer. Por não precisar de opções especiais de compilação, nem do código-fonte do executável, é uma ferramenta útil para investigar o comportamento de executáveis de terceiros. Além disso, o ''strace'' pode analisar processos já em execução (através da opção ''-p'').
 +
 +Eis um exemplo (abreviado) da execução de ''strace'' sobre o programa ''pi'':
 +
 +<code>
 +$ strace ./pi
 +execve("./pi", ["./pi"], [/* 31 vars */]) = 0
 +brk(0)                                  = 0x1d27000
 +access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
 +mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f24cac2f000
 +access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
 +open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
 +fstat(3, {st_mode=S_IFREG|0644, st_size=237552, ...}) = 0
 +mmap(NULL, 237552, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f24cabf5000
 +close(3)                                = 0
 +access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
 +open("/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
 +read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20V\0\0\0\0\0\0"..., 832) = 832
 +...
 +mprotect(0x600000, 4096, PROT_READ)     = 0
 +mprotect(0x7f24cac31000, 4096, PROT_READ) = 0
 +munmap(0x7f24cabf5000, 237552)          = 0
 +fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 9), ...}) = 0
 +mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f24cac2e000
 +write(1, "O valor aproximado de Pi \303\251: 3.1"..., 38O valor aproximado de Pi é: 3.141592
 +) = 38
 +exit_group(0)                           = ?
 ++++ exited with 0 +++
 +</code>
 +
 +De forma similar, o comando ''ltrace'' gera uma listagem seqüencial de todas as chamadas de biblioteca geradas durante a  execução de um programa:
 +
 +<code>
 +$ ltrace ./pi
 +__libc_start_main(0x40063d, 1, 0x7ffdb52b7238, 0x4006e0 <unfinished ...>
 +pow(1, 0x7ffdb52b7238, 0x7ffdb52b7248, 0)        = 0x3ff0000000000000
 +pow(1, 0x3ff00000, 0x7fefffffffffffff, 0x7fffffffffffffff) = 0x3ff0000000000000
 +pow(1, 0x3ff00000, 0x7fefffffffffffff, 0x7fffffffffffffff) = 0x3ff0000000000000
 +pow(1, 0x3ff00000, 0x7fefffffffffffff, 0x7fffffffffffffff) = 0x3ff0000000000000
 +pow(0x4330000000000000, 0xc3700000, 0x7fefffffffffffff, 0x7fffffffffffffff) = 0x3ff0000000000000
 +pow(0x4330000000000000, 0xc3700000, 0x7fefffffffffffff, 0x7fffffffffffffff) = 0x3ff0000000000000
 +pow(0x4330000000000000, 0xc3700000, 0x7fefffffffffffff, 0x7fffffffffffffff) = 0x3ff0000000000000
 +...
 +pow(0x4330000000000000, 0xc3700000, 0x7fefffffffffffff, 0x7fffffffffffffff) = 0x3ff0000000000000
 +pow(0x4330000000000000, 0xc3700000, 0x7fefffffffffffff, 0x7fffffffffffffff) = 0x3ff0000000000000
 +pow(0x4330000000000000, 0xc3700000, 0x7fefffffffffffff, 0x7fffffffffffffff) = 0x3ff0000000000000
 +printf("O valor aproximado de Pi \303\251: %f\n"..., 3.141493) = 38
 +O valor aproximado de Pi é: 3.141493
 ++++ exited (status 0) +++
 +</code>
 +
 +===== Performance profiling =====
 +
 +Além da depuração propriamente dita, em busca de erros, por vezes torna-se necessário analisar o comportamento temporal do programa. Para realizar essa análise pode-se utilizar o //GNU Profiler// (''gprof''). O ''gprof'' permite verificar:
 +
 +  * o tempo dispendido em cada função
 +  * o grafo de chamadas
 +    * que funções são chamadas por que funções
 +    * que funções chamam outras funções
 +  * quantas vezes cada função é chamada
 +  * ...
 +
 +Para realizar o profiling de um executável, é necessário incialmente compilá-lo com o flag adequado (''-pg'') e em seguida executá-lo:
 +
 +<code>
 +$ gcc -pg -g -o pi pi.c -lm
 +$ pi
 +</code>
 +
 +A execução irá gerar um arquivo binário ''gmon.out'', que contém os dados de profiling. Esse arquivo é usado pelo utilitário ''gprof'' para gerar as estatísticas desejadas: 
 +
 +<code>
 +$ gprof pi gmon.out
 +</code>
 +
 +Um exemplo de relatório de saída do programa ''gprof'' pode ser encontrado {{unix:exemplo.gprof.txt|neste arquivo}}. Mais detalhes e opções de relatório podem ser obtidas no [[http://www.gnu.org/software/binutils/manual/gprof-2.9.1/|manual GNU gprof]].
 +
 +O grafo de chamadas (//call graph//) pode ser visualizado de forma gráfica através da ferramenta [[https://github.com/jrfonseca/gprof2dot|Gprof2dot]].
 +
 +O Valgrind também permite realizar //profiling//, através de sua ferramenta interna ''callgrind'' e do visualizador externo [[https://kcachegrind.github.io/html/Home.html|KCachegrind]].
 +
 +===== Tratamento de erros =====
 +
 +A maior parte das chamadas de sistema e funções UNIX retorna erros na forma de códigos numéricos, que são descritos nas páginas de manual das chamadas e funções (vide ''man fopen'' para um bom exemplo). Normalmente, uma chamada com erro retorna o valor -1 e ajusta a variável global inteira ''errno'' para o código do erro. Além disso, as seguintes funções podem ser úteis na interpretação dos erros:
 +
 +  * ''assert (int expression)'' : se a expressão indicada for nula, encerra a execução com uma mensagem de erro da forma ''assertion failed in file xxx.c, function yyy(), line zzz''.
 +  * ''perror (char * msg)'' : imprime na saída de erro (''stderr'') a mensagem ''msg'' seguida de uma descrição do erro encontrado. O programa não é encerrado.
 +  * ''strerror(int errnum)'' : retorna a descrição do erro indicado por ''errnum''.
 +
 +Além disso, alguns erros de execução, como operações matemáticas inválidas (divisão por zero, etc) ou violações de acesso à memória (ponteiros inválidos, etc) pode ser interceptados e tratados pelo programa, sem que seja necessária sua finalização. Esses erros geram sinais que são enviados ao processo em execução, que pode interceptá-los e tratá-los de forma a contornar o erro e continuar funcionando.
 +
 +
 +===== Outras ferramentas =====
 +
 +  * ''strip'': permite remover os símbolos e código não usado de um executável ou arquivo-objeto, diminuindo consideravelmente seu tamanho (mas impedindo futuras depurações no mesmo).
 +  * ''diff'': permite comparar dois arquivos ou diretórios (recursivamente), apontando as diferenças entre seus conteúdos. Muito útil para comparar diferentes versões de árvores de código fonte.
 +  * ''patch '': permite aplicar um arquivo de diferenças (gerado pelo comando ''diff'') sobre uma árvore de arquivos, modificando os arquivos originais de forma a obter uma nova árvore. Muito usado para divulgar novas versões de códigos-fonte muito grandes. 
 +  * ''grep'': permite encontrar linhas em arquivos contendo uma determinada string ou expressão regular. Pode ser muito útil para encontrar trechos de código específicos em grandes volumes de código.
 +
 +===== Documentação online =====
 +
 +O sistema UNIX implementa um sistema de documentação //on-line// simples, mas bastante útil e eficiente, chamado **páginas de manual** (//man pages//). As páginas de manual estão estruturadas em sessões:
 +
 +  * Sessão 1: Comandos do usuário.
 +  * Sessão 2: Chamadas ao sistema
 +  * Sessão 3: Bibliotecas e funções standard
 +  * Sessão 4: Descrição de dispositivos e formatos de arquivos de dados
 +  * Sessão 5: Formato de arquivos de configuração
 +  * Sessão 6: Jogos
 +  * Sessão 7: Diversos
 +  * Sessão 8: Comandos de administração do sistema
 +
 +O acesso às páginas de manual é normalmente efetuado através do comando ''man''. Assim, ''man ls'' apresenta a página de manual do comando ''ls'', enquanto ''man man'' apresenta a página de manual do próprio comando ''man''. Ambientes gráficos normalmente oferecem ferramentas mais versáteis para consulta às páginas de manual.
 +
 +Além do comando ''man'', outros comandos são úteis para a busca de informações no sistema:
 +
 +  * ''whatis'', ''apropos'' :  para localizar páginas de manual que contenham informações sobre algum assunto específico.
 +  * ''locate'' :  para localizar arquivos no sistema
 +  * ''which'' : para informar onde se encontra um determinado executável
 +  * ''info'' : sistema de informações mais completo que o ''man'', mas não disponível em todas as plataformas UNIX.
 +
 +===== Alguns links =====
 +
 +  * [[http://www.yolinux.com/TUTORIALS/LinuxTutorialSoftwareDevelopment.html|YoLinux Tutorial - Software Development on Linux]]
 +  * [[http://www.network-theory.co.uk/docs/gccintro/|An Introduction to GCC]]
 +  * [[http://www.dwheeler.com/program-library/Program-Library-HOWTO/index.html|Linux Program Library HowTo]]
 +  * [[http://www.linuxbase.org/|Linux Standard Base - Padrões de Compatibilidade em Linux]]
 +
 +===== Atividades =====
 +
 +  - Escreva e compile o arquivo ''pi.c'' (cálculo de Pi através da série de Gregory) usando compilação estática e dinâmica. Compare o tamanho final dos executáveis e os símbolos internos de cada executável (comando ''nm'').
 +  - Construa uma biblioteca de operações aritméticas entre pares de inteiros, que implemente as operações abaixo indicadas. Devem ser gerados o arquivo fonte (''.c''), de cabeçalho (''.h''), a biblioteca estática (''.a'') e a biblioteca dinâmica (''.so'').
 +    * ''int soma (int, int)''
 +    * ''int subt (int, int)''
 +    * ''int mult (int, int)''
 +    * ''int divi (int, int)''
 +  - Construa um pequeno programa que use a biblioteca do exercício anterior, ligando-a (a) estaticamente e (b) dinamicamente com seu código.
 +  - Efetue a execução passo-a-passo do programa escrito na questão anterior, usando o ''ddd'' ou diretamente o ''gdb''.
 +  - Faça o profiling do programa escrito na questão 3, usando o ''gprof''.
 +  - Identifique todos os arquivos abertos durante a execução do comando de sistema ''uptime''.