GDB:从单线程调试到多线程调试(MFiX单步调试) 您所在的位置:网站首页 gdb调试程序的全过程 GDB:从单线程调试到多线程调试(MFiX单步调试)

GDB:从单线程调试到多线程调试(MFiX单步调试)

2023-05-01 18:40| 来源: 网络整理| 查看: 265

GDB:从单线程调试到多线程调试 1. 裸跑GDB 1.1 安装GDB sudo apt-get install gdb 1.2 编译程序

由于需要调试,因此编译的时候需要添加-g编译参数:

1.3 GDB调试运行

1.4 常用调试参数

进入上面那个界面以后,说明正常启动GDB了,目前只是GDB启动了,程序还没有跑起来,输入run可以让程序跑起来,但是这样程序就直接执行结束了,没有被逐行调试,没什么意义,因此通常需要先打断点,再启动程序。下面先列出常用的命令:

命令简写形式说明 list l 查看源码(后面可以接数字,表示查看该行附近的源码) next n (执行完该行后)跳到下一行,遇到函数直接执行完成 step s (执行完该行后)跳到下一行,遇到函数会进入 finish   运行到函数结束 continue c 继续运行(如果后面没有断点,则一直执行到函数结束) break b 打断点(d 行号,或d函数名) info breakpoints   显示断点信息(比如目前有几个断点,是否被启用等等) delete d 删除断点(delete 断点编号:删除某一断点,delete:删除所有断点) print p 打印变量值(p 代码中变量名:打印代码中变量的值) info locals   打印当前所在函数局部变量的值 run r 启动程序(设置完断点后用该命令启动程序) until u 执行到指定行 info i 显示信息 help h 显示帮助信息(如:help list) quit q 退出GDB !(shell命令)   在GDB里执行shell命令,第4.2节会用到 shell (shell命令)   同上 1.5 简单示例

用GDB运行程序:查看源码:打断点后运行:然后就可以配合n、s和p来一步步调试源码了。

2. GDB增强实现CGDB 2.1 安装

原生的GDB由于没有独立显示代码的窗口,调试比较麻烦,需要不断l来显示代码,CGDB是GDB的加强版,下载链接为:http://cgdb.github.io/按照教程安装即可,注意configure的时候添加参数:

CXXFLAGS='-std=c++11' ./configure --prefix=/usr/local

因为新版CGDB是用C++11编写的,不添加前面的参数可能会报错,其他的按照官网步骤即可。

2.2 配置

使用规则和GDB基本一致,不同的是有两个窗口,不过默认的是上下分屏,不太好看,这里先配置一下:[谷歌接口报错]:1.网络错误或者文本过长。2.谷歌接口可能对于某些网络不能用,具体不清楚。可以尝试挂VPN试试。3.这个问题我没办法修复,请右键菜单更换百度、腾讯翻译接口。

vim ~/.cgdb/cgdbrc

默认没有这个文件,自己用vim填写一下:

:set ignorecase :set ts=4 :set wso=vertical :set hls map :until

代表的含义依次是:大小写不敏感;tab对应4个空格;分屏为左右垂直分屏;搜索高亮;F11快捷键,用于跳出循环(默认没有这个快捷键)

2.3 使用

在命令行输入

cgdb bugging

后进入如下界面:左侧为源码,右侧为CGDB调试界面,命令基本和GDB一致。按键盘esc可以进入源码界面,用vim的操作方式控制方向;按键盘i又重新回到gdb的调试界面。

 2.4 快捷键

可以先按esc进入源码界面,然后用快捷键控制单步运行,这样操作方便很多。有如下常用快捷键:

F5 - Send a run command to GDB.(相当于r) F6 - Send a continue command to GDB.(相当于c) F7 - Send a finish command to GDB.(相当于f) F8 - Send a next command to GDB.(相当于n) F11 - 跳出循环(自己配置的快捷键) F10 - Send a step command to GDB.(相当于s) 3. CGDB单线程调试运行MFiX(gdb命令行参数) 可以用CGDB来单步调试mfix代码。这里使用的是mfix-19.1,常规的运行命令为: ./mfixsolver -f mfix.dat 如果用CGDB调试,首先: cgdb mfixsolver 进入GDB界面:由于Fortran代码没有提供高亮,因此左侧代码没有花花绿绿的效果,先凑合用。然后打断点,运行mfix。注意需要添加命令行参数: run -f mfix.dat

注意,如果要打印module里的变量,需要带上module名称,例如 p module_name::var.

参考:https://stackoverflow.com/questions/10264329/fortran-module-variables-not-accessible-in-debuggers

然后可以用之前设置的快捷键单步调试mfix程序。注意主循环逻辑都在RUN_MFIX函数里,因此要进入到这个函数里才能进一步单步调试循环过程。最后通过单步调试可以找到循环迭代的位置,在RUN_MFIX这个subroutine里:

4. CGDB调试多线程简单案例

多线程(MPI)调试调试一直都是个很麻烦的事情,虽然有专业的多线程调试软件,但是一般都是收费的。这里提供一种在GDB下的方法。主要是先在函数里人为添加一个死循环,然后用GDB attach到这些进程,然后通过set环境变量的方式使其退出循环,并接管程序,进行单步调试。

4.1 添加死循环

首先有下面一段程序:

#include #include #include const int MAX_STRING = 100; int main(void){ char greeting[MAX_STRING]; int comm_sz; int my_rank; MPI_Init(NULL, NULL); MPI_Comm_size(MPI_COMM_WORLD, &comm_sz); MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); #ifdef MPI_DEBUG int gdb_break = 1; while(gdb_break) {}; #endif if(my_rank != 0){ sprintf(greeting, "Greetings from process %d of %d!", my_rank, comm_sz); /* int MPI_Send( void* msg_buf_p, //发送消息的缓冲区(内存首地址) int msg_size, //发送消息的缓冲区大小(数据个数) MPI_Datatype msg_type, //发送数据类型 int dest, //数据目的地,这里是0号进程 int tag, //标签,非负int。比如同样的数据类型,有的用来打印有的用来计算,只通过前面四个 参数无法区分 MPI_Comm communicator //通讯子,指定通讯范围 ); */ MPI_Send(greeting, strlen(greeting)+1, MPI_CHAR, 0, 0, MPI_COMM_WORLD); }else{ printf("Greetings from process %d of %d!\n", my_rank, comm_sz); for(int q = 1; q < comm_sz; q++){ /* int MPI_Recv( void* msg_buf_p, //接收消息的缓冲区 int buf_size, //接收消息的缓冲区大小 MPI_Datatype buf_type, //接收消息的类型 int source, //消息从哪里发过来 int tag, //标签,与发送过来的标签一致 MPI_Comm communicator, //通讯子,指定通讯范围 MPI_Status* status_p //通常赋值MPI_STATUS_IGNORE即可 ); */ MPI_Recv(greeting, MAX_STRING, \ MPI_CHAR, q, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); printf("%s\n", greeting); } } MPI_Finalize(); return 0; }

我在代码的前面加了这样一句:

#ifdef MPI_DEBUG int gdb_break = 1; while(gdb_break) {}; #endif

这样,我在编译的时候添加参数-DMPI_DEBUG,运行的时候就能停在这个循环处:

4.2 运行程序

现在我们启动这个程序:现在的程序由于进入死循环,因此停在这里。下面我们另外再打开两个terminal,并进入GDB。为了方便说明,上面的运行程序的terminal编号#1,另外打开的两个terminal分别编号#2, #3。

4.3 启动CGDB

现在#2和#3都以管理员身份进入CGDB:

sudo cgdb

4.4 找到进程编号

在GDB里调用shell,查看a.out的进程号:

(gdb) !ps aux | grep a.out

ps aux是显示所有进程信息,|是管道符,把结果传给grep,筛选出含有a.out内容的部分。可以看到,进程号971和972对应的是a.out的两个进程,通常进程号小的是0号进程。

4.5 GDB attach到进程

找到进程号,就可以让GDB attach过去,然后接管程序的运行。

分别让#2和#3 attach到971进程和972进程。

4.6 跳出死循环

可以看到,现在这两个进程都停在了19行while这个地方,现在让程序跳出循环:

(gdb) set gdb_break = 0

4.7 GDB调试

现在GDB已经接管程序了,可以用n往下执行代码了:

这是#2(也即0号进程)的执行结果,已经跳出循环。下面多执行几步:

可以看到进入0号进程的逻辑,并打印输出。继续执行:

这里程序进入等待状态,等待#2发送消息。当#2发送完消息:

#1则收到#2发来的消息,并退出等待,可以继续往下运行:

继续执行#1:

#1也即0号进程,打印出#2发来的数据,并再次进入for循环,如果此时还有别的进程发来消息则会继续,但是这里一共只有两个进程,因此只会收到1号进程发来的消息。当两个进程都全部执行完后,整个程序执行结束:

5. 参考

[1] 实验楼《GDB 简明教程》[2] CSDN 《cgdb的介绍和使用》[3] GitHub (CGDB编译报错解决)[4] CSDN 《gdb调试 -带有命令行参数》[5] 知乎《终端调试哪家强?》[6] CGDB中文手册《CGDB配置命令》[7] segmentfault《MPI并行程序的调试技巧》



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有