国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

程序調(diào)試利器——GDB使用指南

這篇具有很好參考價值的文章主要介紹了程序調(diào)試利器——GDB使用指南。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

1. GDB介紹

GDB是GNU Debugger的簡稱,其作用是可以在程序運行時,檢測程序正在做些什么。GDB程序自身是使用C和C++程序編寫的,但可以支持除C和C++之外很多編程語言的調(diào)試。GDB原生支持調(diào)試的語言包含:

?C

?C++

?D

?Go

?Object-C

?OpenCL C

?Fortran

?Pascal

?Rust

?Modula-2

?Ada

此外,通過擴展GDB,也可以用來調(diào)試Python語言。

使用GDB,我們可以方便地進行如下任務:

?如果程序崩潰后產(chǎn)生了core dump文件,gdb可以通過分析core dump文件,找出程序crash的位置,調(diào)用堆棧等用于找出問題原因的關鍵信息

?在程序運行時,GDB可以檢測當前程序正在做什么事情

?在程序運行時,修改變量的值

?可以使程序在特定條件下中斷

?監(jiān)視內(nèi)存地址變動

?分析程序Crash后的core文件

GDB是了解三方中間件,無源碼程序,解決程序疑難雜癥的利器。使用GDB,可以了解程序在運行時的方方面面。尤其對于在測試(Test),集成(SIT),驗收(UAT),預發(fā)布(Staging)等環(huán)境下的問題調(diào)查和解決,GDB有著日志無法比擬的優(yōu)勢。此外,GDB還非常適合對多種開發(fā)語言混合的程序進行調(diào)試。

GDB不適合用來做什么:

?GDB可以用來輔助調(diào)試內(nèi)存泄露問題,但GDB不能用于內(nèi)存泄露檢測

?GDB可以用來輔助程序性能調(diào)優(yōu),但GDB不能用于程序性能問題分析

?GDB不是編譯器,不能運行有編譯問題的程序,也不能用來調(diào)試編譯問題

2. 安裝GDB

2.1. 從已發(fā)布的二進制包安裝

在基于Debian的Linux系統(tǒng),可以使用apt-get命令方便地安裝GDB

apt-get update
apt-get install gdb


復制代碼

2.2. 從源代碼安裝

前置條件

# 安裝必要的編譯工具
apt-get install build-essential
復制代碼

首先,我們需要下載GDB的源碼。官網(wǎng)下載源碼的地址是:
ftp.gnu.org/gnu/gdb/

# 下載源代碼
wget http://ftp.gnu.org/gnu/gdb/gdb-9.2.tar.gz

# 解壓安裝包
tar -xvzf gdb-9.2.tar.gz

# 編譯GDB
cd gdb-7.11
mkdir build
cd build
../configure
make

# 安裝GDB
make install

# 檢查安裝結(jié)果
gdb --version //輸出


復制代碼

3. 準備使用GDB

3.1. 在docker容器內(nèi)使用GDB

GDB需要使用ptrace 方法發(fā)送PTRACE_ATTACH請求給被調(diào)試進程,用來監(jiān)視和控制另一個進程。

Linux 系統(tǒng)使用
/proc/sys/kernel/yama/ptrace_scope設置來對ptrace施加安全控制。默認ptrace_scope的設置的值是1。默認設置下,進程只能通過PTRACE_ATTACH請求,附加到子進程。當設置為0時,進程可以通過PTRACE_ATTACH請求附加到任何其它進程。

在docker容器內(nèi),即使是root用戶,仍有可能沒有修改這個文件的權限。使得在使用GDB調(diào)試程序時會產(chǎn)生“ptrace: Operation not permitted “錯誤。

為了解決docker容器內(nèi)使用GDB的問題,我們需要使用特權模式運行docker容器,以便獲得修改
/proc/sys/kernel/yama/ptrace_scope文件的權限。

# 以特權模式運行docker容器
docker run --privileged xxx
# 進入容器,輸入如下指令改變PTRACE_ATTACH請求的限制
echo 0 > /proc/sys/kernel/yama/ptrace_scope


復制代碼

3.2. 啟用生成core文件

默認情況下,程序Crash是不生成core文件的,因為默認允許的core文件大小為0。

為了在程序Crash時,能夠生成core文件來幫助排查Crash的原因,我們需要修改允許的core文件大小設置

# 查看當前core文件大小設置
ulimit -a 
# 設置core文件大小為不限制
ulimit -c unlimited
# 關閉core文件生成功能
ulimit -c 0
復制代碼

修改core文件設置后,再次查看core文件的設置時,會看到下面的結(jié)果

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

這樣,當程序Crash時,會在程序所在的目錄,生成名稱為core.xxx的core文件。

當程序運行在Docker容器內(nèi)時,在容器內(nèi)進行上述設置后,程序Crash時仍然無法生成core文件。這時需要我們在Docker容器的宿主機上,明確指定core文件的生成位置。

# 當程序Crash時,在/tmp目錄下生成core文件
echo '/tmp/core.%t.%e.%p' > /proc/sys/kernel/core_pattern
復制代碼

設置中的字段的含義如下:

?/tmp 存放core文件的目錄

?core 文件名前綴

?%t 系統(tǒng)時間戳

?%e 進程名稱

?%p 進程ID

3.3. 生成調(diào)試符號表

調(diào)試符號表是二進制程序和源代碼的變量,函數(shù),代碼行,源文件的一個映射。一套符號表對應特定的一套二進制程序,如果程序發(fā)生了變化,那么就需要一套新的符號表。

如果沒有調(diào)試符號表,包含代碼位置,變量信息等很多調(diào)試相關的能力和信息將無法使用。在編譯時加入-ggdb編譯選項,就會在生成的二進制程序中加入符號表,此時生成的二進制程序的大小會有顯著的增加。

-ggdb 用來生成針對gdb的調(diào)試信息,也可以使用-g來代替

另外,只要條件允許,建議使用-O0來關閉編譯優(yōu)化,用來避免調(diào)試時,源代碼和符號表對應不上的奇怪問題。

-O0 關閉編譯優(yōu)化

3.4. 使用screen來恢復會話

GDB調(diào)試依賴于GDB控制臺來和進程進行交互,如果我們的連接終端關閉,那么原來的控制臺就沒有辦法再使用了。此時我們可以通過開啟另一個終端,關閉之前的GDB進程,并重新attach到被調(diào)試進程,但此時的斷點,監(jiān)視和捕獲都要重新設置。另一種方法就是使用screen。使用screen運行的程序,可以完全恢復之前的會話,包括GDB控制臺。

# 安裝screen
apt install screen
# 查看安裝結(jié)果
screen -v //output: Screen version 4.08.00 (GNU) 05-Feb-20

# 使用screen啟動調(diào)試
screen gdb xxx
# 查看screen會話列表
screen -ls
# 恢復screen會話
screen -D -r [screen session id]


復制代碼

4. 啟動GDB的幾種方式

4.1. 使用GDB加載程序,在GDB命令行啟動運行

這是經(jīng)典的使用GDB的方式。程序可以通過GDB命令的參數(shù)來加載,也可以在進入GDB控制臺后,通過file命令來加載。

# 使用GDB加載可執(zhí)行程序
gdb [program]
# 使用GDB加載可執(zhí)行程序并傳遞命令行參數(shù)
gdb --args [program] [arguments]

# 開始調(diào)試程序
(gdb) run
# 傳遞命令行參數(shù)并開始調(diào)試程序
(gdb) run arg1 arg2
# 開始調(diào)試程序并在main函數(shù)入口中斷
(gdb) start
# 傳遞命令行參數(shù),開始調(diào)試程序并在main函數(shù)入口中斷
(gdb) start arg1 arg2


復制代碼

4.2. 附加GDB到運行中的進程

GDB可以直接通過參數(shù)的方式,附加到一個運行中的進程。也可以在進入GDB控制臺后,通過attach命令附加到進程。

需要注意的是一個進程只允許附加一個調(diào)試進程,如果被調(diào)試的進程當前已經(jīng)出于被調(diào)試狀態(tài),那么要么通過detach命令來解除另一個GDB進程的附加狀態(tài),要么強行結(jié)束當前附加到進程的GDB進程,否則不能通過GDB附加另一個調(diào)試進程。

# 通過GDB命令附加到進程
gdb --pid [pid]

# 在GDB控制臺內(nèi),通過attach命令附加的進程
gdb
(gdb) attach [pid]
復制代碼

4.3. 調(diào)試core文件

在程序Crash后,如果生成了core文件,我們可以通過GDB加載core文件,調(diào)試發(fā)生異常時的程序信息。core文件是沒有限制當前機器相關信息的,我們可以拷貝core文件到另一臺機器進行core分析,但前提是產(chǎn)生core文件的程序的符號表,需要和分析core文件時加載的程序的符號表保持一致。

使用GDB調(diào)試core文件

# 使用GDB加載core文件進行異常調(diào)試
gdb --core [core file] [program]
復制代碼

4.4. 使用GDB加載程序并自動運行

在自動化測試場景中,需要程序能夠以非中斷的方式流暢地運行,同時又希望附加GDB,以便隨時可以了解程序的狀態(tài)。這時我們可以使用--ex參數(shù),指定GDB完成程序加載后,自動運行的命令。

# 使用GDB加載程序,并在加載完成后自動運行run命令
gdb --ex r --args [program] [arguments]
復制代碼

5. 使用GDB

5.1. 你好,GDB

我們先從一個Hello world的例子,通過GDB設置斷點來調(diào)試程序,近距離接觸下GDB。

首先使用記事本或其它工具編寫下面的main.cc代碼:

#include <iostream>
#include <string>

int main(int argc, char *argv[]) {
  std::string text = “Hello world”;
  std::cout << text << std::endl;
  return 0;
}


復制代碼

接下來我們使用g++編譯器編譯源碼,并設置-ggdb -O0編譯選項。

g++ -ggdb -O0 -std=c++17 main.cc -o main
復制代碼

生成可執(zhí)行程序后,我們使用GDB加載可執(zhí)行程序,并設置斷點。

# 使用gdb加載main
gdb main

# 在main.cc源文件的第六行設置斷點
(gdb) b main.cc:6

# 運行程序
(gdb) run
復制代碼

之后,程序會運行到斷點位置并停下來,接下來我們使用一些常用的GDB指令來檢查程序的當前狀態(tài)

# 輸出text變量數(shù)據(jù) “Hello world“
(gdb) p text
# 輸出局部變量列表,當前斷點位置只有一個text局部變量
(gdb) info locals
# 輸出當前棧幀的方法參數(shù),當前棧幀函數(shù)是main,參數(shù)包含了argc和argv 
(gdb) info args
# 查看堆棧信息,當前只有一個棧幀 
(gdb) bt
# 查看當前棧幀附近的源碼
(gdb) list
# 繼續(xù)運行程序
(gdb) c
# 退出GDB
(gdb) q


復制代碼

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

5.2. Segmentation Fault問題排查

Segmentation Fault是進程訪問了由操作系統(tǒng)內(nèi)存保護機制規(guī)定的受限的內(nèi)存區(qū)域觸發(fā)的。當發(fā)生Segmentation Fault異常時,操作系統(tǒng)通過發(fā)起一個“SIGSEGV”信號來終止進程。此外,Segmentation Fault不能被異常捕捉代碼捕獲,是導致程序Crash的常見誘因。

對于C&C++等貼近操作系統(tǒng)的開發(fā)語言,由于提供了靈活的內(nèi)存訪問機制,所以自然成為了Segmentation Fault異常的重災區(qū),由于默認的Segmentation Fault異常幾乎沒有詳細的錯誤信息,使得開發(fā)人員處理此類異常時變得更為棘手。

在實際開發(fā)中,使用了未初始化的指針,空指針,已經(jīng)被回收了內(nèi)存的指針,棧溢出,堆溢出等方式,都會引發(fā)Segmentation Fault。

如果啟用了core文件生成,那么當程序Crash時,會在指定位置生成一個core文件。通過使用GDB對core文件的分析,可以幫助我們定位引發(fā)Segmentation Fault的原因。

為了模擬Segmentation Fau我們首先在main.cc中添加一個自定義類Employee

class Employee{
public:
    std::string name;
};


復制代碼

然后編寫代碼,模擬使用已回收的指針,從而引發(fā)的Segmentation Fault異常

void simulateSegmentationFault(const std::string& name) {
    try {
        Employee *employee = new Employee();
        employee->name = name;
        std::cout << "Employee name = " << employee->name << std::endl;
        delete employee;
        std::cout << "After deletion, employee name = " << employee->name << std::endl;

    } catch (...) {
        std::cout << "Error occurred!" << std::endl;
    }
}
復制代碼

最后,在main方法中,添加對simulateSegmentationFault方法的調(diào)用

在main方法中,添加對simulateSegmentationFault方法的調(diào)用

int main(int argc, char *argv[]) {
  std::string text = "Hello world";
  std::cout << text << std::endl;
  simulateSegmentationFault(text);
  return 0;
}


復制代碼

編譯并執(zhí)行程序,我們會得到如下的運行結(jié)果

$ ./main
Hello world
Employee name = Hello world
Segmentation fault (core dumped)


復制代碼

從結(jié)果上來看,首先我們的異常捕獲代碼對于Segmentation Fault無能為力。其次,發(fā)生異常時沒有打印任何對我們有幫助的提示信息。

由于代碼非常簡單,從日志上很容易了解到問題發(fā)生在”std::cout << "After deletion, employee name = " << employee->name << std::endl;” 這一行。在實際應用中,代碼和調(diào)用都非常復雜,很多時候僅通過日志沒有辦法準確定位異常發(fā)生的位置。這時,就輪到GDB出場了

# 使用GDB加載core文件
gdb --core [core文件路徑] main
//對于沒有生成core文件的情況,請參考3.2. 啟用生成core文件


復制代碼

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

注意其中的”Reading symbols from main..”,如果接下來打印了找不到符號表的信息,說明main程序中沒有嵌入調(diào)試符號表,此時變量,行號,等信息均無法獲取。若要生成調(diào)試符號表,可以參考 “3.3. 生成調(diào)試符號表”。

成功加載core文件后,我們首先使用bt命令來查看Crash位置的錯誤堆棧。從堆棧信息中,可以看到__GI__IO_fwrite方法的buf參數(shù)的值是0x0,這顯然不是一個合法的數(shù)值。序號為5的棧幀,是發(fā)生異常前,我們自己的代碼壓入的最后一個棧幀,信息中甚至給出了發(fā)生問題時的調(diào)用位置在main.cc文件的第15行(main.cc:15),我們使用up 5 命令向前移動5個棧幀,使得當前處理的棧幀移動到編碼為5的棧幀。

# 顯示異常堆棧
(gdb) bt
#向上移動5個棧幀
(gdb) up 5


復制代碼

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

此時可以看到傳入的參數(shù)name是沒有問題的,使用list命令查看下問題調(diào)用部分的上下文,再使用info locals命令查看調(diào)用時的局部變量的情況。最后使用 p *employe命令,查看employee指針指向的數(shù)據(jù)

# 顯示所有的參數(shù)
(gdb) info args
# 顯示棧幀所在位置的上下文代碼
(gdb) list
# 顯示所有的局部變量
(gdb) info locals
# 打印employee指針的數(shù)據(jù)
(gdb) p *employee


復制代碼

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

此時可以看到在main.cc代碼的第15行,使用std::cout輸出Employee的name屬性時,employee指針指向的地址的name屬性已經(jīng)不再是一個有效的內(nèi)存地址(0x0)。

5.3. 程序阻塞問題排查

程序阻塞在程序運行中是非常常見的現(xiàn)象。并不是所有的阻塞都是程序產(chǎn)生了問題,阻塞是否是一個要解決的問題,在于我們對于程序阻塞的預期。比如一個服務端程序,當完成了必要的初始化后,需要阻塞主線程的繼續(xù)執(zhí)行,避免服務端程序執(zhí)行完main方法后退出。就是正常的符合預期的阻塞。但是如果是一個客戶端程序,執(zhí)行完了所有的任務后在需要退出的時候,還處于阻塞狀態(tài)無法關閉進程,就是我們要處理的程序阻塞問題。除了上面提到的程序退出阻塞,程序阻塞問題一般還包括:

?并發(fā)程序中產(chǎn)生了死鎖,線程無法獲取到鎖對象

?遠程調(diào)用長時間阻塞無法返回

?程序長時間等待某個事件通知

?程序產(chǎn)生了死循環(huán)

?訪問了受限的資源和IO,出于排隊阻塞狀態(tài)

對于大多數(shù)阻塞來說,被阻塞的線程會處于休眠狀態(tài),放置于等待隊列,并不會占用系統(tǒng)的CPU時間。但如果這種行為不符合程序的預期,那么我們就需要查明程序當前在等待哪個鎖對象,程序阻塞在哪個方法,程序在訪問哪個資源時卡住了等問題.

下面我們通過一個等待鎖釋放的阻塞,使用GDB來分析程序阻塞的原因。首先引入線程和互斥鎖頭文件

#include <thread>
#include <mutex>


復制代碼

接下來我們使用兩個線程,一個線程負責加鎖,另一個線程負責解鎖

std::mutex my_mu; 

void thread1_func() {
    for (int i = 0; i < 5; ++i) {
        my_mu.lock();
        std::cout << "thread1 lock mutex succeed!" << std::endl;
        std::this_thread::yield();
    }
}

void thread2_func() {
    for (int i = 0; i < 5; ++i) {
        my_mu.unlock();
        std::cout << "thread2 unlock mutex succeed!" << std::endl;
        std::this_thread::yield();
    }
}

void simulateBlocking() {
    std::thread thread1(thread1_func);
    std::thread thread2(thread2_func);
    thread1.join();
    thread2.join();
}


復制代碼

最后,重新編譯main程序,并在g++編譯時,加入lpthread鏈接參數(shù),用來鏈接pthread庫

g++ -ggdb -O0 -std=c++17  main.cc -o main -lpthread
復制代碼

直接運行main程序,此時程序大概率會阻塞,并打印出類似于如下的信息

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

為了調(diào)查程序阻塞的原因,我們使用命令把gdb關聯(lián)到運行中的進程

gdb --pid xxx
復制代碼

進入GDB控制臺后,依舊是先使用bt打印當前的堆棧信息

# 打印堆棧信息
(gdb) bt
# 直接跳轉(zhuǎn)到我們的代碼所處的編號為2的棧幀
(gdb) f 2
# 查看代碼
(gdb) list


復制代碼

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

此時我們通過查看堆棧信息,知道阻塞的位置是在main.cc的45行,即thread1.join()沒有完成。但這并不是引發(fā)阻塞的直接原因。我們還需要繼續(xù)調(diào)查為什么thread1沒有結(jié)束

# 查看所有運行的線程
(gdb) info threads
# 查看編號為2的線程的堆棧
(gdb) thread apply 2 bt
# 切換到線程2
(gdb) thread 2


復制代碼

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

由于示例程序比較簡單,所有運行的線程只有兩個,我們可以很容易地找到我們需要詳細調(diào)查的thread1所在的線程。

當進程當前運行較多線程時,想找到我們程序中的特定線程并不容易。info threads中給出的線程ID,是GDB的thread id,和thread1線程的id并不相同。而LWP中的線程ID,則是系統(tǒng)賦予線程的唯一ID,同樣和我們在進程內(nèi)部直接獲取的線程ID不相同。這里我們通過thread apply命令,直接調(diào)查編號為2的線程的堆棧信息,確認了其入口函數(shù)是thread1_func,正是我們要找到thread1線程。我們也可以通過thread apply all bt命令,查看所有線程的堆棧信息,用來查找我們需要的線程。更簡單的方式是調(diào)用gettid函數(shù),獲取操作系統(tǒng)為線程分配的輕量進程ID(LWP)。

接下來,我們調(diào)查thread1的堆棧,找到阻塞的位置并調(diào)查阻塞的互斥鎖my_mu的信息,找到當前持有該鎖的線程id(Linux系統(tǒng)線程ID),再次通過info threads查到持有鎖的線程。最后發(fā)現(xiàn)是因為當前線程持有了互斥鎖,當再次請求獲取鎖對象my_mu時,由于my_mu不可重入,導致當前線程阻塞,形成死鎖。

# 查看thread1的堆棧
(gdb) bt
# 直接跳轉(zhuǎn)到我們的代碼所處的棧幀
(gdb) f 4
# 查看鎖對象my_mu
(gdb) p my_mu
# 確認持有鎖的線程
(gdb) info threads


復制代碼

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

5.4. 數(shù)據(jù)篡改問題排查

數(shù)據(jù)篡改不一定會引發(fā)異常,但很可能會導致業(yè)務結(jié)果不符合預期。對于大量使用了三方庫的項目來說,想知道數(shù)據(jù)在哪里被修改成了什么,并不是一件容易的事。對于C&C++來說,還存在著指針被修改后,導致指針原來指向的對象可能無法回收的問題。單純使用日志,想要發(fā)現(xiàn)一個變量在何時被哪個程序修改成了什么,幾乎是不可能的事,通過使用GDB的監(jiān)控斷點,我們可以方便地調(diào)查這類問題。

我們?nèi)匀皇褂枚嗑€程模式,一個線程模擬讀取數(shù)據(jù),當發(fā)現(xiàn)數(shù)據(jù)被修改后,打印一條出錯信息。另一個線程用來模擬修改數(shù)據(jù)。

這里我們使用的Employee對象的原始的name和修改后的name都大于15個字符,如果長度小于這個數(shù)值,你將會觀察到不一樣的結(jié)果。

void check_func(Employee& employee) {
    auto tid = gettid();
    std::cout << "thread1 " << tid << " started" << std::endl;
    while (true) {
        if (employee.name.compare("origin employee name") != 0) {
            std::cout << "Error occurred, Employee name changed, new value is:" << employee.name << std::endl;
            break;
        }
        std::this_thread::yield();
    }
}

void modify_func(Employee& employee) {
    std::this_thread::sleep_for(std::chrono::milliseconds(0));
    employee.name = std::string("employee name changed");
}

void simulateDataChanged() {
    Employee employee("origin employee name");
    std::thread thread1(check_func, std::ref(employee));
    std::thread thread2(modify_func, std::ref(employee));
    thread1.join();
    thread2.join();
}
復制代碼

在main方法中,加入simulateDataChanged方法的調(diào)用,之后編譯并運行程序,會得到如下的結(jié)果:

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

現(xiàn)在,我們假設修改了name屬性的modify_func在一個三方庫中,我們對其內(nèi)部實現(xiàn)不了解。我們需要要通過GDB,找到誰動了employee對象的name屬性

# 使用gdb加載main
(gdb) gdb main
# 在進入gdb控制臺后,在simulateDataChanged方法上增加斷點
(gdb) b main.cc:simulateDataChanged
# 運行程序
(gdb) r
# 連續(xù)執(zhí)行兩次下一步,使程序執(zhí)行到employee對象創(chuàng)建完成后
(gdb) n
(gdb) n
復制代碼

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

之后,我們對employee.name屬性進行監(jiān)控,只要name屬性的值發(fā)生了變化,就會觸發(fā)GDB中斷

# 監(jiān)視employee.name變量對應的地址數(shù)據(jù)
(gdb) watch -location employee.name
# 繼續(xù)執(zhí)行
(gdb) c

# 在觸發(fā)watch中斷后,查看中斷所在位置的堆棧
(gdb) bt
#直接跳轉(zhuǎn)到我們的代碼所處的棧幀
(gdb) f 1


復制代碼

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

在觸發(fā)中斷后,我們發(fā)現(xiàn)是中斷位置是在modify_func方法中。正是這個方法,在內(nèi)部修改了employee的name屬性。至此調(diào)查完畢。

5.5. 堆內(nèi)存重復釋放問題排查

堆內(nèi)存的重復釋放,會導致內(nèi)存泄露,被破壞的內(nèi)存可以被攻擊者利用,從而產(chǎn)生更為嚴重的安全問題。目標流行的C函數(shù)庫(比如libc),會在內(nèi)存重復釋放時,拋出“double free or corruption (fasttop)”錯誤,并終止程序運行。為了修復堆內(nèi)存重復釋放問題,我們需要找到所有釋放對應堆內(nèi)存的代碼位置,用來判斷哪一個釋放堆內(nèi)存的操作是不正確的。

使用GDB可以解決我們知道哪一個變量產(chǎn)生了內(nèi)存重復釋放,但我們不知道都在哪里對此變量釋放了內(nèi)存空間的問題。如果我們對產(chǎn)生內(nèi)存重復釋放問題的變量一無所知,那么還需要借助其它的工具來輔助定位。

下面我們使用兩個線程,在其中釋放同一塊堆內(nèi)存,用來模擬堆內(nèi)存重復釋放問題

void free1_func(Employee* employee) {
    auto tid = gettid();
    std::cout << "thread " << tid << " started" << std::endl;
    employee->name = "new employee name1";
    delete employee;
}

void free2_func(Employee* employee) {
    auto tid = gettid();
    std::cout << "thread " << tid << " started" << std::endl;
    employee->name = "new employee name2";
    delete employee;
}

void simulateDoubleFree() {
    Employee *employee = new Employee("origin employee name");
    std::thread thread1(free1_func, employee);
    std::thread thread2(free2_func, employee);
    thread1.join();
    thread2.join();
}


復制代碼

編譯程序并運行,程序會因為employee變量的double free問題而終止

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

現(xiàn)在我們使用GDB來找到所有釋放employee變量堆內(nèi)存的代碼的位置,以便決定那個釋放操作是不需要的

# 使用GDB加載程序
gdb main
# 在employee變量創(chuàng)建完成后的位置設置斷點
(gdb) b main.cc:101
# 運行程序
(gdb) r


復制代碼

在程序中斷后,我們打印employee變量的堆內(nèi)存地址,并在所有釋放此內(nèi)存地址的位置添加條件斷點之后繼續(xù)執(zhí)行程序

# 查看employee變量
(gdb) p employee //$1 = (Employee *) 0x5555555712e0

# 在釋放employee變量時,增加條件斷點
(gdb) b  __GI___libc_free if mem == 0x5555555712e0
# 繼續(xù)運行程序
(gdb) c


復制代碼

在程序中斷時,我們找到了釋放employee變量堆內(nèi)存的第一個位置,位于main.cc文件89行的delete employee操作。繼續(xù)執(zhí)行程序,我們會找到另一處釋放了employee堆內(nèi)存的代碼的位置。至此,我們已經(jīng)可以調(diào)整代碼來修復此double free問題

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

6. 常用的GDB命令

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

總結(jié)

GDB是探查查詢運行中各種疑難問題的利器。在實際應用中,問題產(chǎn)生的原因通常要復雜得多。程序可能在標準庫中產(chǎn)生了Crash,整個堆??赡芏际菢藴蕩齑a;程序可能由于我們的代碼的操作,最終在三方中間件中產(chǎn)生了問題;整個異常堆棧可能都不包含我們自己開發(fā)的代碼;面對被三方庫不知以何種方式使用的變量。我們除了需要熟悉GDB的使用之外,在這些復雜的實際問題上,我們還需要盡可能多地了解我們使用的其它庫的機制和原理。

最后:?下方這份完整的軟件測試視頻學習教程已經(jīng)整理上傳完成,朋友們?nèi)绻枰梢宰孕忻赓M領取【保證100%免費】

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python

這些資料,對于【軟件測試】的朋友來說應該是最全面最完整的備戰(zhàn)倉庫,這個倉庫也陪伴上萬個測試工程師們走過最艱難的路程,希望也能幫助到你!

apt install ptrace,軟件測試,職場經(jīng)驗,軟件測試工程師,單元測試,程序人生,軟件測試,自動化測試,python文章來源地址http://www.zghlxwxcb.cn/news/detail-764847.html

到了這里,關于程序調(diào)試利器——GDB使用指南的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • jasypt-spring-boot敏感信息加密解密利器使用指南

    jasypt-spring-boot敏感信息加密解密利器使用指南

    Springboot 整合Jasypt,實現(xiàn)配置信息的安全,如數(shù)據(jù)庫連接.賬號和密碼.接口憑證信息等。 Jasypt可以為Springboot加密的信息很多,主要有: System Property 系統(tǒng)變量 Envirnment Property 環(huán)境變量 Command Line argument 命令行參數(shù) Application.properties 應用配置文件 Yaml properties 應用配置文件 other

    2024年02月03日
    瀏覽(33)
  • Py的利器:Python庫——dlib庫的介紹、使用指南及安裝

    Py的利器:Python庫——dlib庫的介紹、使用指南及安裝 dlib庫是一個適用于C++和Python的現(xiàn)代化機器學習、計算機視覺和圖像處理工具包,它具有優(yōu)雅和高效的編程接口。 本文將為大家介紹dlib庫的特點、安裝方法和使用指南,并為大家提供一些實用的示例代碼來幫助大家更好地了

    2024年02月12日
    瀏覽(94)
  • 淘寶API接口:提高電商運營效率與用戶體驗的利器(淘寶API接口使用指南)

    淘寶API接口:提高電商運營效率與用戶體驗的利器(淘寶API接口使用指南)

    淘寶API接口:提高電商運營效率與用戶體驗的利器 隨著電商行業(yè)的快速發(fā)展,淘寶作為國內(nèi)最大的電商平臺之一,不斷探索和創(chuàng)新,以滿足不斷變化的用戶需求和商家需求。其中,淘寶API接口便是其創(chuàng)新的一個重要方面。本文將深入探討淘寶API接口的作用、功能、優(yōu)勢以及使

    2024年02月10日
    瀏覽(25)
  • 【一文秒懂】Ftrace系統(tǒng)調(diào)試工具使用終極指南

    【一文秒懂】Ftrace系統(tǒng)調(diào)試工具使用終極指南

    Ftrace 是 Function Trace 的簡寫,由 Steven Rostedt 開發(fā)的,從 2008 年發(fā)布的內(nèi)核 2.6.27 中開始就內(nèi)置了。 Ftrace 是一個系統(tǒng)內(nèi)部提供的追蹤工具,旨在幫助內(nèi)核設計和開發(fā)人員去追蹤系統(tǒng)內(nèi)部的函數(shù)調(diào)用流程。 隨著 Ftrace 的不斷完善,除了追蹤函數(shù)調(diào)用流程的作用外,還可以用來調(diào)試

    2024年01月23日
    瀏覽(20)
  • NetAssist網(wǎng)絡調(diào)試工具使用指南 (附NetAssist工具包)

    1、NetAssist簡介 NetAssist網(wǎng)絡調(diào)試助手,是Windows平臺下開發(fā)的TCP/IP網(wǎng)絡調(diào)試工具,集TCP/UDP服務端及客戶端于一體,是網(wǎng)絡應用開發(fā)及調(diào)試工作必備的專業(yè)工具之一,可以幫助網(wǎng)絡應用設計、開發(fā)、測試人員檢查所開發(fā)的網(wǎng)絡應用軟/硬件產(chǎn)品的數(shù)據(jù)收發(fā)狀況,提高開發(fā)速度,簡

    2024年02月16日
    瀏覽(22)
  • IntelliJ IDEA遠程調(diào)試:使用IDEA Remote Debug進行高效調(diào)試的指南

    IntelliJ IDEA遠程調(diào)試:使用IDEA Remote Debug進行高效調(diào)試的指南

    在開發(fā)分布式系統(tǒng)時,調(diào)試是一個重要但復雜的環(huán)節(jié)。開發(fā)者通常需要跨越多個服務、模塊和線程來追蹤和解決問題。在沒有遠程調(diào)試的情況下,許多開發(fā)者會在代碼中添加各種日志語句,然后重新部署和上線來調(diào)試。這種方法不僅費時,而且可能引入額外的錯誤或問題。

    2024年02月09日
    瀏覽(26)
  • 深度解析:使用Postman調(diào)試微信支付接口的完美指南

    深度解析:使用Postman調(diào)試微信支付接口的完美指南

    在使用 Postman 調(diào)試微信支付接口之前,你需要做好以下準備: 安裝 Postman 客戶端應用,或使用網(wǎng)頁版; 成為 微信支付商戶; 已申請 商戶API私鑰。 當你已經(jīng)具備這三個條件,就可以進入微信支付接口調(diào)試之旅了~ 方式一:通過 fork 方式 為了幫助商戶開發(fā)者快速上手,微信官

    2024年02月08日
    瀏覽(16)
  • Linux 使用gdb調(diào)試C程序

    Linux 使用gdb調(diào)試C程序

    一、gdb的一些基礎命令 l :顯示代碼 l n :跳轉(zhuǎn)到當前代碼頁的第n行的代碼 l filename.c :n :跳轉(zhuǎn)到filename.c文件的第n行代碼 b 行號 :加斷點 info break :查看斷點信息 delete 斷點編號 :刪除斷點 r ,運行程序 n ,單步執(zhí)行 c ,繼續(xù)執(zhí)行,遇到斷點停止執(zhí)行 p ,打印 s ,進入函數(shù)

    2024年02月13日
    瀏覽(21)
  • 字節(jié)小程序交易組件使用指南

    字節(jié)小程序交易組件使用指南

    前言? 通過插件的形式,預先實現(xiàn)了一些頁面模板,例如退款頁模板,小程序開發(fā)者只需要直接引入相應插件,并且遵循插件約定的規(guī)范,與插件之間實現(xiàn)相互通信,即可完成相應的頁面,從而提高開發(fā)效率。 交易系統(tǒng)前端模板在頁面維度上提供了提單頁模板、退款頁模板等

    2023年04月16日
    瀏覽(19)
  • 微信小程序:Mobx的使用指南

    微信小程序中有時需要進行全局狀態(tài)管理,這個時候就需要用到Mobx.下面我們來看一下在小程序中是如何使用Mobx的 根目錄下新建store文件夾,新建store.js文件

    2024年02月12日
    瀏覽(26)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包