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.