Projeto

Geral

Perfil

1. Como depurar um Código Fortran com GDB

Nota: Contribuição original de João Gerd Zell de Mattos (22 Maio 2020)

Este texto foi escrito pensando no uso do compilador gfortran, então algumas informações podem não funcionar para outros compiladores

O GDB, um debuger (depurardor) GNU, permite ver o que está acontecendo "dentro" de um código enquanto ele é executado. O GDB pode ser utilizado em várias linguagens de programação, mas aqui vou descrever o uso a partir de um código escrito em fortran.

1.1 Primeiro Passo - Compilar o código

O primeiro passo para usar o GDB (pensando que o mesmo já esteja instalado na máquina) é a parte de compilação do código. Para que o GDB possa "enxergar" o que está ocorrendo dentro do código durante a execução, é necessário incluir alguns argumentos na linha de comando.
O argumento principal é o '-g', esta opção produz informações de depuração (debug) em um formato nativo do sistema operacional. O GDB é capaz de ler esta informação, no entanto, em alguns compiladores existe a opção '-ggdb' que gera uma informação específica para ser utilizada pelo GDB, ou seja, o compilador irá usar o formato mais expressivo disponível (DWARF, stabs, ou o formato nativo, se nenhum deles for suportado), incluindo extensões GDB, se for possível.

É possível ainda incluir o nível de depuração pelo mesmo flag de compilação (-g, -ggdb). O nível vai de 0 à 3, sendo que o padrão é 2. Então incluindo a opção -g3 ou -gdb3 irá gerar informações extras ao gdb.

Outro ponto importante é remover qualquer flag de otimização do código. Isto é possível incluindo a opção '-O0'

então um flag de compilação ficaria assim

DEBUG_FLAG='-ggdb3 -O0'

1.2 Usando o GDB

Depois de compilar o código com as opções de debug, basta executar o código usando o gdb:

$ gdb ./program.x

Para exemplificar o uso do GDB irei usar informações de depuração do código do programa scantec. Como estou desenvolvendo algumas funcionalidades novas, há uma série de inconsistências no código que ainda é necessário sanar. Para isto estou usando o GDB para me auxiliar nesta tarefa.

Para iniciar o uso do GDB com o SCANTEC incluí alguns outros flags com a finalidade de detectar outras inconsistências no código, tais como erros aritméticos (-ffpe-trap=zero,invalid,overflow,underflow), checagem das bordas dos arrays (-fcheck=bounds), verificação de variáveis não inicializadas (-Wuninitialized) e também um flag para indicar a posição no código onde houve algum erro (-fbacktrace), desta forma o meu flag de debug ficou o seguinte:

DEUBG_FLAG = '-ggdb3 -O0 -fbacktrace -fcheck=bounds -fcheck=all -Wuninitialized -ffpe-trap=zero,invalid,overflow,underflow'

Agora é necessário executar o SCANTEC dentro do ambiente do GDB:

gdb ./scantec.x

Logo após a execução do comando acima o GDB é inicializado e a tela do terminar fica da seguinte forma:

$ gdb ./scantec.x 
GNU gdb (GDB) 9.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-solus-linux".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./scantec.x...
(gdb) 

para executar o código é necessário dar a instrução "run" para o gdb:

(gdb) run

ou, quando for necessário passar um arquivo namelist para o programa:

(gdb) run < namelist.nml

com isso o código é executado até encontrar o primeio erro, ou se tudo estiver correto com o programa ele será executado até o final. No caso do SCANTEC há alguns problemas no código, como já foi mencionado acima, então após o comando run o código aborta indicando onde está ocorrendo o erro. Aí é que entra o GDB para tentar solucionar o problema:

(gdb) run
Starting program: /media/extra/wrk/scantec/SCANTEC_V1/core/scantec.x 
Hello from scan_coreMod::scan_Config_init
 !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
 ! INSIDE :readcard Routine                                            !
 !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
Running Specification:
Starting Time: 2016071500
Ending Time: 2016071500
Time Step:  00
Analisys Time Step:  06
Forecast Time Step:  06
Forecast Total Time:  06
History Time:  48
Grid 01 Specification
lower left latitude  :  -49.875
lower left longitude :  -82.625
upper right latitude :   11.375
upper right longitude:  -35.375
resolution dx        :    0.500
resolution dy        :    0.500
number of points (X) :      095
number of points (Y) :      123
Type : Reference
  |---- Model Name :brams
  |---- Exp Name   :Reference
  |---- File       :../datain/BRAMS_%y4%m2%d200G-A-%y4-%m2-%d2-000000-g1.ctl
 !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
 !                         Climatology Not Found                       !
 !         The mean reference field will be used as climatology        !
 !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
history increment    :    2.000
Analisys increment   :    0.250
Forecast increment   :    0.250
N time steps         :      001
N forecast time steps :      002
Hello from scan_coreMod::scan_Init
xdef  095 linear  277.375    0.500
ydef  123 linear  -49.875    0.500

Getting model info from: brams.model
 xdim: 337 linear    -88.09107     0.19089
 ydim: 365 linear    -47.00000     0.16799
 zdim:  19 levels
  1000.00000   925.00000   900.00000   850.00000   800.00000   750.00000   700.00000   650.00000
   600.00000   550.00000   500.00000   450.00000   400.00000   350.00000   300.00000   250.00000
   200.00000   150.00000   100.00000

          List of Model Variables
         SCANTEC            Model
        vtmp:925             vtmp
        vtmp:850             vtmp
        vtmp:500             vtmp
        temp:850        tempc:850
        temp:500        tempc:500
        temp:250        tempc:250
       psnm:1000   sfc_press:1000
        umes:925             umes
        umes:850             umes
        umes:500             umes
        agpl:925            const
        zgeo:850          geo:850
        zgeo:500          geo:500
        zgeo:250          geo:500
        uvel:850       ue_avg:850
        uvel:500       ue_avg:500
        uvel:250       ue_avg:250
        vvel:850       ve_avg:850
        vvel:500       ve_avg:500
        vvel:250       ve_avg:250

At line 649 of file coord_compute.f90
Fortran runtime error: Index '338' of dimension 1 of array 'rlon' above upper bound of 337

Error termination. Backtrace:
#0  0x7ffff79c0654 in ???
#1  0x7ffff79c1369 in ???
#2  0x7ffff79c184d in ???
#3  0x4696f0 in gridcoord_latlon_
    at /media/extra/wrk/scantec/SCANTEC_V1/lib/BilinInterp/coord_compute.f90:649
#4  0x46bc23 in __coord_compute_MOD_compute_grid_coord
    at /media/extra/wrk/scantec/SCANTEC_V1/lib/BilinInterp/coord_compute.f90:193
#5  0x45c2d6 in __bilininterp_MOD_bilininterp_init1
    at /media/extra/wrk/scantec/SCANTEC_V1/lib/BilinInterp/BilinInterp.f90:259
#6  0x42208e in __scan_modelplugin_MOD_scan_models_plugin
    at /media/extra/wrk/scantec/SCANTEC_V1/core/scan_ModelPlugin.f90:159
#7  0x442e3f in __scan_coremod_MOD_scan_init
    at /media/extra/wrk/scantec/SCANTEC_V1/core/scan_coreMOD.f90:206
#8  0x454c06 in scantec
    at /media/extra/wrk/scantec/SCANTEC_V1/core/scandrv.f90:73
#9  0x454c51 in main
    at /media/extra/wrk/scantec/SCANTEC_V1/core/scandrv.f90:56
[Inferior 1 (process 43819) exited with code 02]
(gdb) 

Como houve a inclusão de um flag para checar as bordas das matrizes e vetores, durante a execução do programa estas variáveis são checadas e no caso do SCANTEC houve um problema na rotina coord_compute.f90, na variável rlon. Com o GDB é possível verificar o porque está ocorrendo isso.

O primeiro passo é criar pontos de parada no código para que seja possível verificar como estão as variáveis nestes pontos. Normalmente colocaríamos um print nos locais do código que queremos verificar, recompilamos o código, e fazemos a execução novamente, e este tipo de operação seria repetida tantas vezes quantas fossem necessárias para identificarmos o problema. Aí é que começa a 'mágica' com o uso de um depurador.

Primeiro incluimos então os pontos de parada. Aqui vou incluir nos locais em que foram indicados os erros. Isso é feito com a instrução break ou pelo alias b:

(gdb) b scan_ModelPlugin.f90:159
Breakpoint 1 at 0x421fa5: file scan_ModelPlugin.f90, line 159.
(gdb) b BilinInterp.f90:259
Breakpoint 2 at 0x45becd: file BilinInterp.f90, line 259.
(gdb) b coord_compute.f90:193
Breakpoint 3 at 0x46b53f: file coord_compute.f90, line 193.
(gdb) b coord_compute.f90:649
Breakpoint 4 at 0x469640: file coord_compute.f90, line 649.
(gdb)

com isto foram incluídos 4 pontos de parada do código. Ao executar o scantec novamente dentro do ambiente do GDB, quando o programa alcançar cada um destes pontos a execução será pausada, e então será possível verificar o estado de cada uma das variáveis naquele ponto do código:

(gdb) run
Starting program: /media/extra/wrk/scantec/SCANTEC_V1/core/scantec.x 
Hello from scan_coreMod::scan_Config_init
 !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
 ! INSIDE :readcard Routine                                            !
 !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
Running Specification:
Starting Time: 2016071500
Ending Time: 2016071500
Time Step:  00
Analisys Time Step:  06
Forecast Time Step:  06
Forecast Total Time:  06
History Time:  48
Grid 01 Specification
lower left latitude  :  -49.875
lower left longitude :  -82.625
upper right latitude :   11.375
upper right longitude:  -35.375
resolution dx        :    0.500
resolution dy        :    0.500
number of points (X) :      095
number of points (Y) :      123
Type : Reference
  |---- Model Name :brams
  |---- Exp Name   :Reference
  |---- File       :../datain/BRAMS_%y4%m2%d200G-A-%y4-%m2-%d2-000000-g1.ctl
 !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
 !                         Climatology Not Found                       !
 !         The mean reference field will be used as climatology        !
 !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
history increment    :    2.000
Analisys increment   :    0.250
Forecast increment   :    0.250
N time steps         :      001
N forecast time steps :      002
Hello from scan_coreMod::scan_Init
xdef  095 linear  277.375    0.500
ydef  123 linear  -49.875    0.500

Breakpoint 1, scan_coremod::scan_init () at scan_coreMOD.f90:206
206        call scan_models_plugin()

Pela mensagem é possível verificar que o programa foi pausado no primeiro ponto escolhido

Breakpoint 1, scan_coremod::scan_init () at scan_coreMOD.f90:206
206        call scan_models_plugin()

Agora é possível começar a avaliar o que pode estar ocorrendo com as variáveis. Para verificar quais são as variáveis locais e o estado delas nesta parte do código utiliza-se a instrução "info locals":

(gdb) info locals
rlat = (-47, -46.8320122, -46.6640205, -46.4960327, -46.3280411, -46.1600533,...)
rlon = (-88.0910721, -87.900177, -87.7092896, -87.5183945, -87.3274994, ...)
xdim = 0x4a4810
ydim = 0x4a5d10

Aqui ele está indicando as variáveis locais rlat, rlon, xdim e ydim. Neste caso aparecem algumas informações estranhas em xdim e ydim, elas indicam que estas duas variáveis estão apontando para algum endereço na memória, ou seja, são ponteiros. Para verificar isso basta pegar mais informações com o comando "whatis"

(gdb) whatis xdim
type = PTR TO -> ( integer(kind=4) )
(gdb) whatis ydim
type = PTR TO -> ( integer(kind=4) )
(gdb) 

Pelos dois comando acima é possível certificar que as variáveis estão apontando para variáveis inteiras de 4 bits. Mas para saber o valor do ponteiro é necessário imprimir a variável com um "*" na frente, sem ele os ponteiros são indicados apenas pela posição na memória que eles estão apontando:

(gdb) p xdim
$1 = (PTR TO -> ( integer(kind=4) )) 0x4a4810
(gdb) p *xdim
$2 = 337
(gdb) 

Nesta mesma subrotina há variáveis globais e alguns tipo derivados também, elas também podem ser impressas. Por exemplo a variável global scantec é um tipo derivado e pode ser impressa da seguinte forma:

(gdb) p scantec
$3 = ( starting_time = 2016071500, ending_time = 2016071500, time_step = 1, loop_count = 1, tables = '/media/extra/wrk/scantec/SCANTEC_V1/tables', ' ' <repeats 214 times>, atime = 2016071500, atime_step = 6, ntime_steps = 1, aincr = 0.25, atime_flag = .TRUE., ftime = 2016071500, ftime_step = 6, ntime_forecast = 2, fincr = 0.25, forecast_time = 6, ftime_idx = 1, ftime_count = (1, 0), output_dir = '../dataout', ' ' <repeats 190 times>, hist_time = 48, hist_incr = 2, griddesc = (0, 95, 123, -49.875, 277.375, 128, 11.375, 324.625, 0.5, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), nxpt = 95, nypt = 123, npts = 11685, nexp = 1, udef = -999.900024, nvar = 20, varname = ('vtmp:925 ', 'vtmp:850 ', 'vtmp:500 ', 'temp:850 ', 'temp:500 ', 'temp:250 ', 'psnm:000 ', 'umes:925 ', 'umes:850 ', 'umes:500 ', 'agpl:925 ', 'zgeo:850 ', 'zgeo:500 ', 'zgeo:250 ', 'uvel:850 ', 'uvel:500 ', 'uvel:250 ', 'vvel:850 ', 'vvel:500 ', 'vvel:250 '), init_modelid = <not allocated>, cflag = 0, currmodel = 0x49a740, firstmodel = 0x49a740 )
(gdb) 

ou então somente um das variáveis contidas no tipo derivado:

(gdb) p scantec%starting_time
$4 = 2016071500
(gdb) 

e assim por diante.

Para continuar a execução até o próximo ponto de parada (breakpoint) basta passa a instrução next ou seu alias n:

(gdb) n

Breakpoint 2, bilininterp::bilininterp_init1 (griddesci=..., rlat=..., rlon=..., w11=<error reading variable: value requires 492020 bytes, which is more than max-value-size>, 
    w12=<error reading variable: value requires 492020 bytes, which is more than max-value-size>, 
    w21=<error reading variable: value requires 492020 bytes, which is more than max-value-size>, 
    w22=<error reading variable: value requires 492020 bytes, which is more than max-value-size>, 
    n11=<error reading variable: value requires 492020 bytes, which is more than max-value-size>, 
    n12=<error reading variable: value requires 492020 bytes, which is more than max-value-size>, 
    n21=<error reading variable: value requires 492020 bytes, which is more than max-value-size>, 
    n22=<error reading variable: value requires 492020 bytes, which is more than max-value-size>) at BilinInterp.f90:259
(gdb)

1.3 Depurando dentro de um loop (do ... enddo)

As vezes o problema ocorre dentro de um loop, então é necessário verificar como as coisas andam alí dentro em cada interação. O primeiro passo é criar um breakpoint no inicio do loop. No SCANTEC há varios loops, e o erro parece estar ocorrendo dentro de um loop, quando tenta ler a informação de rlon(338):

At line 649 of file coord_compute.f90
Fortran runtime error: Index '338' of dimension 1 of array 'rlon' above upper bound of 337

Verificando dentro do código, a linha 649 está dentro de um loop, sendo que o mesmo é iniciado na linha 645, então o próximo passo é incluir um breakpoint na linha 645 e então rodar novamente o programa até que chegue nesta linha:

(gdb) b coord_compute.f90:645
Breakpoint 5 at 0x469550: file coord_compute.f90, line 645.
(gdb) run

Quando chegar no breakpoint desejado podemos fazer as interações passa a passo para tentar entender o que está ocorrendo. Para isto utiliza-se a instrução continue (alias c). A instrução continue irá executar até o próximo breakpoint, que no caso do loop é um passo de interação. Existe a possibilidade de se pular várias interações atribuindo um número de passos após o comando continue. O erro está indicando que se está tentando acessar o índice 338, mas rlon possui somente 337 elementos, então podemos executar o loop por 337 vezes e verificar como está cada variável após este processo:

...
...
Breakpoint 4, coord_compute::gridcoord_latlon_ (gdesc=..., udef=-9999, rlon=..., rlat=..., xpts=..., ypts=..., iret=-73) at coord_compute.f90:647
647              xpts(n) = ( (rlon(n) - rlon1) / dlon ) + 1
(gdb) c 337
Will ignore next 336 crossings of breakpoint 4.  Continuing.

Breakpoint 4, coord_compute::gridcoord_latlon_ (gdesc=..., udef=-9999, rlon=..., rlat=..., xpts=..., ypts=..., iret=240) at coord_compute.f90:647
647              xpts(n) = ( (rlon(n) - rlon1) / dlon ) + 1
(gdb)

Terminada a depuração, para sair do gdb, basta digitar quit:

(gdb) quit

1.4 Notas para uso no XC50 (maquina Cray)

O primeiro passo é fazer um qsub iterativo, por exemplo, utilizando 3 nós de 40 processadores cada:

$ qsub -I -q pesq -l nodes=3:ppn=40 -N jobDBG

A cray possui uma versão própria do gdb, desenvolvida para que seja possível realizar o debug em aplicação MPI, então dentro do ambiente iterativo deve-se carregar o lgdb:

$ module load cray-lgdb

o próximo passo é executar o lgdb:

$ lgdb

lgdb 3.0 - Cray Line Mode Parallel Debugger
With Cray Comparative Debugging Technology.
Copyright 2007-2016 Cray Inc. All Rights Reserved.
Copyright 1996-2016 University of Queensland. All Rights Reserved.

Type "help" for a list of commands.
Type "help <cmd>" for detailed help about a command.
dbg all>

dentro do lgdb é necessário rodar a aplicação com o número de processadores necessários. Seguindo exemplo acima, serão utilizados 120 processadores para rodar o gsi.exe:

dbg all> launch $a{120} ./gsi.exe

A aplicação será inicializada e a partir de então será possível executar o programa, para isto basta executar o comando continue dentro do lgdb:

dbg all> continue

NOTA: Há algum bug no lgdb do XC50 que faz o processo abortar. É algo ruim, pois atrasa o processo, mas basta tentar novamente que o programa executa. Em algumas situações é necessário sair do qsub iterativo e submeter novamente.

ERROR: MRNet failure callback invoked.
Debugger is shutting down! Please file a bugreport.
Re-run with setting CRAY_DBG_LOG_DIR to a cross mounted location
and adding --debug to your launch/attach statement. Submit the
generated logs with the bugreport.

1.5 Referências