Técnicas para desenvolvimento e aceleração de códigos científicos


Prof. Edson Borin
Instituto de Computação
Universidade Estadual de Campinas
Raul Baldin
Faculdade de Engenharia Civil, Arquitetura e Urbanismo
Universidade Estadual de Campinas

Atividade Prática: Usando GDB para depurar códigos científicos

Nesta atividade, exercitaremos como utilizar o programa GDB para descobrir problemas em programas de computador.

Atividades

Cada atividade possui um pequeno arquivo de código que exemplifica alguns dos problemas que podem ser facilmente depurados através do GDB.

Problemas estes, que compiladores não avisam e que muitas vezes passam despercebidos até que algo estranho aconteça com o programa, por exemplo:

Depurando Falhas de segmentação

O que é uma falha de segmentação?
A falha de segmentação é uma tentativa de acesso (leitura ou escrita) a um endereço de memória inválido (por exemplo, um ponteiro = NULL) ou que não possui as permissões devidas (por exemplo, tentativa de escrita em memória read-only).

Essas falhas são bem dificeis de resolver, pois muitas vezes elas acontecem somente com algumas "entradas de dados" específicas.

O exemplo abaixo simula uma falha de segmentação ao acessar um ponteiro NULL. Vamos depurar esse programa.

Comandos para compilar e executar o programa, através do GDB:

    g++ -g segfault.cpp -o segfault
    gdb ./segfault

Exercitaremos os comandos run, break, print, set variable e continue

programa: segfault.cpp


Depurando Falhas de segmentação - múltiplos arquivos

Este exemplo é similar ao anterior, porém vamos depurar um programa com múltiplos arquivos.

Comandos para compilar e executar o programa, através do GDB:

    g++ -g multiplos_arquivos.cpp -o multiplos_arquivos
    gdb ./multiplos_arquivos

Exercitaremos os comandos run, break, print, set variable, backtrace, frame e continue

programa: multiplos_arquivos.cpp
classe1: class1.h
classe2: class2.h


Depurando programas recursivos

O que são rotinas recursivas?
São rotinas que chamam elas mesmas dentro de seu código até que uma condição de parada aconteça.
E se essa condição não acontecer?
Acontece o que chamamos de 'estouro de pilha' ou Stack overflow.
Como funciona a recursão internamente?
Cada vez que uma subrotina é chamada o endereço para o qual ela deve retornar quando acabar é guardado em uma pilha. Quando muitas chamadas acontecem, por exemplo no caso da recursão, esse espaço irá acabar e o programa aborta com a exceção de estouro de pilha. Também pode haver estouro de pilha quando as rotinas recursivas possuem variáveis locais muito grandes, que ajudam a esgotar o espaço da pilha.
(Por exemplo, tente adicionar no programa dentro da rotina recursiva o seguinte código: double boom[1000000];)

Nesse exemplo, baseado na rotina de cálculo fatorial recursivo, vamos simular um estouro de pilha e depurá-lo usando o GDB.

Comandos para compilar e executar o programa, através do GDB:

    g++ -g stackoverflow.cpp -o stackoverflow
    gdb ./stackoverflow

Exercitaremos os comandos run, backtrace, break, display, return e continue

programa: stackoverflow.cpp



Depure o programa: Fatorial simples

E agora? Meu código não está retornando valor certo de fatorial, como resolver?

Comandos para compilar e executar o programa, através do GDB:

    g++ -g fatorial.cpp -o fatorial
    $ ./fatorial 
    Entre com um numero para calcular seu fatorial: 4 
    O fatorial de 4 = 0

Exercitaremos os comandos run, list, break, set variable e continue

programa: fatorial.cpp



Arquivo CMake para facilitar a compilação

Comandos utilizados para compilar os exemplos através do uso do CMake:

    ############ 1) Fazer download e Salvar todos os .h e .cpp dentro de um diretório. (acima)
    ############ 2) Fazer download e Salvar o CMakeLists.txt dentro desse mesmo diretório.
    cmake .
    make