GDB:從單線程調試到多線程調試(MFiX單步調試)


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運行程序:

查看源碼:

打斷點后運行:

然后就可以配合nsp來一步步調試源碼了。

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 <F11> :until<cr>

代表的含義依次是:
大小寫不敏感;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 <stdio.h>
#include <string.h>
#include <mpi.h>
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內容的部分。
可以看到,進程號971972對應的是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並行程序的調試技巧


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM