使用 GDB 远程调试 MPI

Debugging MPI Remotely Using GDB

我正在尝试调试我使用来自 pi 的远程访问组的 MPI 编写的代码。为了能够使用 GUI 调试代码,我无法直接访问 Pis。

我已经尝试过像 question 中显示的那样使用屏幕,但是任何时候我尝试使用屏幕 我收到此消息:

There are not enough slots available in the system to satisfy the 2 slots
that were requested by the application:
  screen

Either request fewer slots for your application, or make more slots available
for use.

如果我尝试告诉它只使用 1 个屏幕,mpiexec 会失败

mpiexec -N 16 --host 10.0.0.3 -np 1 screen -oversubscribe batSRTest3 shortpass.bat
--------------------------------------------------------------------------
mpiexec was unable to find the specified executable file, and therefore
did not launch the job.  This error was first reported for process
rank 0; it may have occurred for other processes as well.

NOTE: A common cause for this error is misspelling a mpiexec command
      line parameter option (remember that mpiexec interprets the first
      unrecognized command line token as the executable).

Node:       node1
Executable: screen

我查看了 openMPI 常见问题解答,但该信息不适用于远程访问。我尝试关注 this 部分,但是当我输入

gdb --pid

使用代码 运行ning 没有任何反应。该部分中的方法 2 也不起作用,因为在使用 Putty 访问 PI 时我无法打开多个 windows。

我希望能够在理想情况下在所有节点上 运行 调试它,目前 运行 我必须使用的程序:

$ mpiexec -N 4 --host 10.0.0.3,10.0.0.4,10.0.0.5,10.0.0.6 -oversubscribe batSRTest shortpass.bat

这也造成了混乱,因为我什至不确定我是否正确添加了额外的参数。

我确实尝试使用类似于共享答案 here 的 gdb 进行调试,但这只会导致 MPI 失败,因为它没有被赋予多个任务。

(gdb) exec-file batSRTest3
(gdb) run
Starting program: /home/pi/progs/batSRTest3 mpiexec -N 16 --host 10.0.0.3 -oversubscribe batSRTest3 shortpass.bat
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/arm-linux-gnueabihf/libthread_db.so.1".
[Detaching after fork from child process 17157]
[New Thread 0x7691a460 (LWP 17162)]
[New Thread 0x75d3d460 (LWP 17163)]
[node1:17153] *** An error occurred in MPI_Group_incl
[node1:17153] *** reported by process [141361153,0]
[node1:17153] *** on communicator MPI_COMM_WORLD
[node1:17153] *** MPI_ERR_RANK: invalid rank
[node1:17153] *** MPI_ERRORS_ARE_FATAL (processes in this communicator will now abort,
[node1:17153] ***    and potentially your MPI job)
[Thread 0x7691a460 (LWP 17162) exited]
[Thread 0x76ff5010 (LWP 17153) exited]
[Inferior 1 (process 17153) exited with code 06]
(gdb) q

调试 MPI 应用程序的问题在于它们 运行 以多进程的形式出现,而且您通常无法直接访问这些进程。因此,存在能够将自身集成到 MPI 作业中的特殊并行调试器。两个最受欢迎的是 TotalView 和 Arm DDT(以前称为 Allinea DDT)。两者都是 昂贵的 商业产品,但许多学术机构购买许可证,因此请检查您的情况是否如此。穷人的解决方案是使用 GDB,它本身不是并行调试器,所以必须要有创意。

简而言之,这个想法是在 GDB 的监督下启动您的 MPI 进程。但首先,让我们看看 Open MPI 如何在多个节点上执行作业。下图应该说明它:

mpiexec <--+--> orted on node1 <--+--> rank 0
           |                      |
           |                      +--- rank 1
           |                      :
           |                      +--- rank N-1
           |
           +--- orted on node2 <--+--- rank N
           |                      |
           |                      +--- rank N+1
           |                      :
           :                      +--- rank 2N-1

mpiexec是MPI程序启动器,负责读入MPI等级数、主机列表、绑定策略等信息,并使用这些信息启动作业。对于与执行 mpiexec 的同一主机上的进程,它只是多次生成可执行文件。对于远程节点上的进程,它使用 RSH、SSH 或其他一些机制(srun 用于 SLURM、TM2 等)在每个远程主机上启动 orted 帮助程序,然后生成尽可能多的根据需要在其特定主机上排名。

与常规 Unix 程序不同,您永远不会通过控制台或 Unix 信号直接与 MPI 进程交互。相反,MPI 运行time 提供了 I/O 转发和信号传播的机制。你与 mpiexec 的标准输入和输出进行交互,然后它使用一些基础设施将你的输入发送到等级 0 并向你显示从所有等级接收到的输出。同样,发送到 mpiexec 的信号被转换并传播到 MPI 等级。 I/O 重定向和信号传播都没有在 MPI 标准中完全指定,因为它们非常特定于平台,但一般的集群实现共识是所有级别的标准输出都转发到 [=22 的标准输出=] 而只有等级 0 从标准输入接收;其余队伍的标准输入连接到 /dev/null。这在上图中用定向箭头显示。实际上,Open MPI 允许您通过将 --stdin rank 传递给 mpiexec 来 select 哪个级别将接收标准输入。

如果您这样做 gdb mpiexec ...,则您不是在调试 MPI 应用程序。相反,您将调试 MPI 启动器本身,这不是 运行ning 您的代码。您需要在 MPI 运行time 和 MPI ranks 本身之间插入 GDB,即上图应转换为:

mpiexec <--+--> orted on node1 <--+--> gdb <---> rank 0
           |                      |
           |                      +--- gdb <---> rank 1
           |                      :
           |                      +--- gdb <---> rank N-1
           |
           +--- orted on node2 <--+--- gdb <---> rank N
           |                      |
           |                      +--- gdb <---> rank N+1
           |                      :
           :                      +--- gdb <---> rank 2N-1

现在的问题变成了如何与众多 GDB 实例交互,主要是因为您只能直接与其中一个实例对话。使用 TotalView 和 DDT,有一个 GUI 可以使用网络套接字与调试器组件对话,所以这个问题就解决了。对于许多 GDB,您有几个选择(或者更确切地说,hack)。

第一个选项是只调试一个行为不当的 MPI rank。如果错误总是发生在同一个级别,你可以让它运行在GDB的控制下,其余的运行自己,然后用--stdin rank告诉mpiexec 让你在排名不为 0 时与调试器交互。你需要一个简单的包装脚本(称为 debug_rank.sh):

#!/bin/sh
# Usage: debug_rank.sh <rank to debug> <executable> <arguments>

DEBUG_RANK=
shift
if [ $OMPI_COMM_WORLD_RANK == $DEBUG_RANK ]; then
   exec gdb -ex=run --args $*
else
   exec $*
fi

-ex=run告诉GDB在加载可执行文件后自动执行run命令。如果你需要先设置断点,你可以省略它。像这样使用包装器,例如调试等级 3:

$ mpiexec ... --stdin 3 ./debug_rank.sh 3 batSRTest shortpass.bat

一旦 rank 3 做了坏事或到达断点,您将进入 GDB 命令提示符。您也可以不使用包装脚本,而是直接使用 运行 gdb,希望它不会进入除您期望调试的级别之外的任何其他级别的命令提示符。如果发生这种情况,GDB 将退出,因为它的标准输入将连接到 /dev/null,从而关闭整个 MPI 作业,因为 mpiexec 会注意到一个等级在没有调用 MPI_Finalize().[=90= 的情况下退出]

如果您不知道哪个特定等级行为不当,或者它与 运行 运行 不同,或者如果您想在其中多个设置断点,那么您需要解决输入重定向问题。而 "simplest" 解决方案是使用 X11 终端仿真器,例如 xterm。这里的技巧是 GUI 程序从窗口系统而不是标准输入获取输入,因此您可以愉快地输入并将输入发送到 xterm 内的命令 运行ning,尽管它的标准输入已连接至 /dev/null。此外,X11 是一个 client/server 协议,可以 运行 over TCP/IP,允许您远程 运行 xterm 并在 运行ning 一些 X11 实现,例如 X.org 或 XWayland。这正是 Open MPI 页面上显示的命令所做的:

$ mpiexec ... xterm -e gdb -ex=run --args batSRTest shortpass.bat

这会启动许多 xterm 副本,每个副本都会执行 gdb -ex=run --args batSRTest shortpass.bat。因此,您可以在它们自己的终端 windows 中获得许多 GDB 实例,这使您可以与它们中的任何一个进行交互。为此,您需要做一些事情:

  • 每个 Pi
  • 上应该安装 xterm 的副本
  • 您的网络应该是低延迟网络,因为 X11 协议运行在延迟较长的网络上非常慢
  • 您的 X11 服务器应该可以从所有树莓派访问,并且应该配置为接受来自它们的连接
  • 应该相应地设置 DISPLAY 环境变量

任何 X11 客户端应用程序,例如 xterm 使用 DISPLAY 环境变量中的值来确定如何连接到 X11 服务器。它的值具有一般形式 <optional hostname>:<display>[.<screen>]。对于管理单个显示器的本地服务器,DISPLAY 通常是 :0.0 甚至只是 :0。当缺少 <optional hostname> 时,隐含特殊值 host/unix,这意味着 X11 服务器正在侦听位于 /tmp/.X11-unix/ 的 Unix 域套接字。默认情况下,出于安全原因,X11 服务器仅侦听 Unix 域套接字,这使得网络客户端无法访问它们。您需要在 TCP/IP 套接字上启用侦听并覆盖绑定地址,默认情况下为 127.0.0.1,并确保您的主机可以从树莓派访问,即它们可以直接连接到您的X11 服务器侦听的 TCP 端口上的 IP 地址。如果你这样做,那么它的工作原理是这样的:

  1. 为 X11 启用 TCP 连接并使其在网络接口上侦听
  2. 检查系统上 DISPLAY 的值
  3. 添加您的 IP 地址
  4. 运行 MPI 作业是这样的:

$ mpiexec ... -x DISPLAY=your.ip:d.s xterm -e gdb -ex=run --args batSRTest shortpass.bat

其中 d.s 是您的本地 DISPLAY 变量设置的显示和屏幕值。确保您的防火墙允许端口 6000+d.

上的入站 TCP 连接

从网络启用 TCP 连接并不总是可取的,甚至是不可能的,尤其是当您位于 NAT 后面时。因此,另一种解决方案是通过 SSH 使用 X11 转发。为此,您需要在连接到 SSH 服务器时将 -X-Y 传递给 SSH 客户端:

 $ ssh -X username@server

-Y 而不是 -X 启用一些不受信任的扩展,并且可能是某些 X11 应用程序所必需的。 X11 转发只有在服务器端启用时才有效。它还需要在服务器上安装 xauth。但是简单地在服务器上启用 X11 转发是不够的,因为默认情况下 SSH 服务器将在环回接口上侦听 X11 连接转发。对于 OpenSSH,必须相应地设置以下两个配置参数:

X11Forwarding yes    # Enable X11 forwarding
X11UseLocalhost no   # Listen on all network interfaces

如果 SSH 服务器配置正确并且存在 xauth 命令,当您通过 SSH 进入系统时 DISPLAY 的值应该类似于 hostname:10.0 和 运行ning netstat -an | grep 6010 应该产生这样的东西:

tcp        0      0 0.0.0.0:6010            0.0.0.0:*               LISTEN
tcp6       0      0 :::6010                 :::*                    LISTEN

表示X11转发套接字绑定到所有网络接口。然后您应该像这样启动 MPI 作业:

$ mpiexec -x DISPLAY=server.ip:10.0 xterm -e gdb -ex=run --args batSRTest shortpass.bat

其中 server.ip 是服务器在将其连接到树莓派的网络中的 IP(我怀疑在您的情况下是 10.0.0.1)。此外,应在服务器的防火墙中启用一系列以 6010 开头的 TCP 端口。实际值取决于有多少 X11 转发会话。默认情况下,X11DisplayOffset 设置为 10,因此 SSH 服务器将从显示 10 开始,直到找到未分配的显示编号。此外,如果您在 Pis 上的主目录未以某种方式与服务器上的主目录共享(例如,通过 NFS 挂载),您还需要将在服务器主目录中找到的 .Xauthority 文件复制到您的主目录在所有 Pis 上。此文件包含 MIT 魔术 cookie 需要使用 X11 转发器进行身份验证,并在每次 SSH 进入启用 X11 转发的服务器时重新生成,因此请确保在每次 SSH 登录后再次将其复制到所有 Pis。

现在,如果这一切看起来过于复杂,GDB 也有远程调试能力。您可以在服务器上启动 GDB,然后在 GDB 服务器程序 gdbserver 的监督下 运行 远程 MPI 进程,然后使用本地 GDB 中的远程调试命令连接到其中一个 GDB 服务器。这是相当麻烦的。您需要告诉每个 GDB 服务器监听不同的端口。包装脚本 (debug_server.sh) 可能会有所帮助:

#!/bin/sh
# Usage: debug_server.sh <executable> <arguments>

GDB_HOST=$(hostname)
GDB_PORT=$(( 60000 + $OMPI_COMM_WORLD_RANK ))
echo "GDB server for rank $OMPI_COMM_WORLD_RANK available on $GDB_HOST:$GDB_PORT"
exec gdbserver :$GDB_PORT $*

运行 像这样:

$ mpiexec ... ./debug_server.sh batSRTest shortpass.bat

它将打印不同 GDB 服务器实例正在侦听的主机名和端口列表。不带参数启动 GDB 并发出以下命令:

(gdb) target remote hostname:port

其中 hostnameport 是感兴趣的 Pi 的 IP(或主机名,如果可解析)。 GDB 服务器在可执行文件的入口点自动中断,这很可能在动态链接器中的某处,您需要发出 continue 命令使其成为 运行。您需要为每个 GDB 服务器实例执行此操作,我不知道如何在不停止当前目标的情况下断开与当前目标的连接,因此您可能也需要启动大量 GDB。

可能有一些 GDB 的 GUI 可以简化这一点。你可能看看 Eclipse PTP project, which provides a parallel debugger, and see whether it works for you. You may find these slides 有用。我个人从未使用过 PTP,也不知道它能做什么。


这基本上就是为什么大多数 MPI 调试都是使用 printf() 完成的,除了最复杂的情​​况。将 --tag-output 添加到 mpiexec 参数列表中,使其在每个输出行前加上它来自的作业 ID 和等级 ID,这样您就不必自己打印该信息。

对于仅命令行体验,您可以查看 tmpi,它在 tmux 面板中运行 MPI 进程(并将键盘输入多路复用到每个面板!)