使用 GDB 在 Fortran 中调试 MPI 程序

Using GDB to debug an MPI program in Fortran

本文关键字:调试 MPI 程序 Fortran GDB 使用      更新时间:2023-10-16

我读了这篇文章并到达了这里,所以现在我想我应该(如果不是这样,请告诉我(重写代码

{
    int i = 0;
    char hostname[256];
    gethostname(hostname, sizeof(hostname));
    printf("PID %d on %s ready for attachn", getpid(), hostname);
    fflush(stdout);
    while (0 == i)
        sleep(5);
}

在福特兰。从这个答案中我明白,在 Fortran 中,我可以简单地用MPI_Get_processor_name代替gethostname.其他一切都很简单,但flush。怎么样?

我应该把它放在哪里?在主程序中MPI_Init ?然后?我该怎么办?

对于有关编译选项的内容,我提到了这一点,并使用-v -da -Q作为mpifort包装器的选项。

这个解决方案不适合我的情况,因为我至少需要在 27 个进程上运行该程序,所以我只想检查一个进程。

最简单的方法:

我实际上经常做的是我只是在本地运行 MPI 作业,看看它的作用。没有上述任何代码。然后,如果它挂起,我使用 top 来找出进程的PID,通常人们可以很容易地从 PID 中猜出哪个等级是哪个等级(它们往往是连续的,最低的等级是等级 0(。低于等级 0 是进程 1641,而不是等级 1 pid 1642,依此类推......

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                                                         
 1642 me        20   0  167328   7716   5816 R 100.0 0.047   0:25.02 a.out                                                                                                                                           
 1644 me        20   0  167328   7656   5756 R 100.0 0.047   0:25.04 a.out                                                                                                                                           
 1645 me        20   0  167328   7700   5792 R 100.0 0.047   0:24.97 a.out                                                                                                                                           
 1646 me        20   0  167328   7736   5836 R 100.0 0.047   0:25.00 a.out                                                                                                                                           
 1641 me        20   0  167328   7572   5668 R 99.67 0.046   0:24.95 a.out 

然后我只做gdb -pid,检查进程中的堆栈和局部变量。(在 GDB 控制台中使用help stack(

最重要的是获取回溯,因此只需在控制台中打印bt即可。

这在检查死锁时会很好地工作。当您必须在某个特定地点停下来时,情况就不太好了。然后,您必须尽早附加调试器。


您的代码:

我认为在 Fortran 中不需要冲洗。我认为 Fortran 至少在我使用的编译器中writeprint刷新是必要的。

但是您绝对可以使用flush语句

use iso_fortran_env
flush(output_unit)

只需将其冲洗放在打印hostnamepidwrite之后即可。但正如我所说,我只会从打印开始。

你要做的是登录到该节点并使用类似的东西将 gdb 附加到 righ 进程

gdb -pid 12345

对于睡眠,您可以使用许多编译器中可用的非标准sleep内部子例程,也可以编写自己的子例程。

无论是在MPI_Init之前还是之后?如果要打印排名,则必须在之后。也用于使用MPI_Get_processor_name它必须是之后。通常建议在程序中尽早调用MPI_Init

然后代码类似于

  use mpi
  implicit none
  character(MPI_MAX_PROCESSOR_NAME) :: hostname
  integer :: rank, ie, pid, hostname_len
  integer, volatile :: i
  call MPI_Init(ie)
  call MPI_Get_processor_name(hostname, hostname_len, ie)
  !non-standard extension
  pid = getpid()
  call MPI_Comm_rank(MPI_COMM_WORLD, rank, ie)
  write(*,*) "PID ", pid,  " on ",  trim(hostname), " ready for attach is world rank ", rank
  !this serves to block the execution at a specific place until you unblock it in GDB by setting i=0
  i = 1
  do
    !non-standard extension
    call sleep(1)
    if (i==0) exit
  end do
end

重要说明:如果使用优化进行编译,编译器可以看到i==0永远不会为真,并且将完全删除检查。您必须降低优化或将i声明为 volatile 。易失性意味着该值可以随时更改,编译器必须从内存中重新加载其值以进行检查。这需要Fortran 2003。

附加正确的流程:

上面的代码将打印,例如,

> mpif90 -ggdb mpi_gdb.f90 
> mpirun -n 4 ./a.out
 PID         2356  on linux.site ready for attach is world rank            1
 PID         2357  on linux.site ready for attach is world rank            2
 PID         2358  on linux.site ready for attach is world rank            3
 PID         2355  on linux.site ready for attach is world rank            0

在顶部,它们看起来像

 PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                                                         
 2355 me        20   0  167328   7452   5564 R 100.0 0.045   1:42.55 a.out                                                                                                                                           
 2356 me        20   0  167328   7428   5548 R 100.0 0.045   1:42.54 a.out                                                                                                                                           
 2357 me        20   0  167328   7384   5500 R 100.0 0.045   1:42.54 a.out                                                                                                                                           
 2358 me        20   0  167328   7388   5512 R 100.0 0.045   1:42.51 a.out

您只需选择您想要的等级并执行

gdb -pid 2355

以附加等级 0,依此类推。当然,在不同的终端窗口中。

然后你会得到类似的东西

MAIN__ () at mpi_gdb.f90:26
26          if (i==0) exit
(gdb) info locals
hostname = 'linux.site', ' ' <repeats 246 times>
hostname_len = 10
i = 1
ie = 0
pid = 2457
rank = 0
(gdb) set var i = 0
(gdb) cont
Continuing.
[Inferior 1 (process 2355) exited normally]

发布的代码基本上只是一个无限循环,旨在在附加调试器时"暂停"执行。然后,可以使用调试器控件退出此循环,程序将继续。你可以在 fortran 中编写一个等效的循环,所以只要你愿意从另一种方法获取主机名和 pid(参见 VladimirF 在他的回答中提到的mpi_get_processor_name,如果你乐于使用编译器扩展,GNU 和 Intel 编译器都提供了getpid扩展(,你可以使用类似以下内容的内容(感谢这个答案作为睡眠示例(。

module fortran_sleep
  !See https://stackoverflow.com/a/6932232                                                                                                                                                                           
  use, intrinsic :: iso_c_binding, only: c_int
  implicit none
  interface
     !  should be unsigned int ... not available in Fortran                                                                                                                                                         
     !  OK until highest bit gets set.                                                                                                                                                                              
     function FortSleep (seconds)  bind ( C, name="sleep" )
       import
       integer (c_int) :: FortSleep
       integer (c_int), intent (in), VALUE :: seconds
     end function FortSleep
  end interface
end module fortran_sleep
program mpitest
  use mpi
  use fortran_sleep
  use, intrinsic :: iso_c_binding, only: c_int
  implicit none
  integer :: rank,num_process,ierr, tmp
  integer :: i
  integer (c_int) :: wait_sec, how_long
  wait_sec = 5
  call mpi_init (ierr)
  call mpi_comm_rank (MPI_COMM_WORLD, rank, ierr)
  call mpi_comm_size (MPI_COMM_WORLD, num_process, ierr)
  call mpi_barrier (MPI_COMM_WORLD, ierr)
  print *, 'rank = ', rank
  call mpi_barrier (MPI_COMM_WORLD, ierr)
  i=0
  do while (i.eq.0)
     how_long = FortSleep(wait_sec)
  end do
  print*,"Rank ",rank," has escaped!"
  call mpi_barrier(MPI_COMM_WORLD, ierr)
  call mpi_finalize (ierr)
end program mpitest

现在编译类似的东西

> mpif90 prog.f90 -O0 -g -o prog.exe

如果我现在在本地机器的两个内核上使用

> mpirun -np 2 ./prog.exe

在屏幕上,我只看到

 rank =            0
 rank =            1

现在在另一个终端中,我连接到相关机器并使用

ps -ef | grep prog.exe

这为我提供了几个对应于不同等级的进程 id 值。然后,我可以使用以下命令附加到其中之一

gdb --pid <pidFromPSCmd> ./prog.exe

现在我们gdb可以使用 bt(回溯(看到我们在程序中的位置,很可能我们处于sleep .然后我们使用 s(tep( 逐步完成程序,直到到达主程序。现在我们将i设置为非零值,然后执行 c(ontinue(,这允许此排名过程继续,我们看到排名已转义消息等。gdb部分将如下所示:

(gdb) bt
#0  0x00007f01354a1d70 in __nanosleep_nocancel () from /lib64/libc.so.6
#1  0x00007f01354a1c24 in sleep () from /lib64/libc.so.6
#2  0x0000000000400ef9 in mpitest () at prog.f90:35
#3  0x0000000000400fe5 in main (argc=1, argv=0x7ffecdc8d0ae) at prog.f90:17
#4  0x00007f013540cb05 in __libc_start_main () from /lib64/libc.so.6
#5  0x0000000000400d39 in _start () at ../sysdeps/x86_64/start.S:122
(gdb) s
Single stepping until exit from function __nanosleep_nocancel,
which has no line number information.
0x00007f01354a1c24 in sleep () from /lib64/libc.so.6
(gdb) s
Single stepping until exit from function sleep,
which has no line number information.
mpitest () at prog.f90:34
34    do while (i.eq.0)
(gdb) bt
#0  mpitest () at prog.f90:34
#1  0x0000000000400fe5 in main (argc=1, argv=0x7ffecdc8d0ae) at prog.f90:17
#2  0x00007f013540cb05 in __libc_start_main () from /lib64/libc.so.6
#3  0x0000000000400d39 in _start () at ../sysdeps/x86_64/start.S:122
(gdb) set var i = 1
(gdb) c
Continuing.

在我们原来的终端中,我们会看到类似的东西

 Rank            0  has escaped!