debug demo
命令行查看
ps -eLf|grep cam_det?//查看當前運行的輕量級進程
ps -aux | grep 執行文件??//查看當前運行的進程
ps -aL | grep 執行文件??//查看當前運行的輕量級進程
pstree -p 主線程ID??//查看主線程和新線程的關系
查看線程棧結構
pstack 線程ID
步驟:
gdb test
r
ctrl + c ?#中斷
break row_num thread 9
c
ctrl + c
thread apply 9 next
thread 9
bt ???#顯示棧信息,想看第幾步,就輸入f 幾
f 3 #
info locals #顯示當前調用棧的所有變量
只允許當前一個線程運行
info threads #查看當前線程,帶星號*
thread xxxxx ?#切換線程,如 thread 5
set scheduler-locking on
next
只暫停指定線程,其他線程不影響
all-stop模式改為non-stop模式,即暫停某一線程,不會影響其他線程
gdb 執行文件
set?pagination?off
set?non-stop?on
break test.cpp:91
show?non-stop
run
thread apply thread_serial_num next #單步查看這個線程運行
break
gdb -v 查看版本信息,7.0以前不支持non-stop模式
break,tbreak可以根據行號、函數、條件生成斷點。tbreak設置方法與break相同,只不過tbreak只在斷點停一次,過后會自動將斷點刪除,break需要手動控制斷點的刪除和使能。
break 可帶如下參數:
linenum???????????????? 本地行號,即list命令可見的行號
filename:linenum? 制定個文件的行號
function??????????????? 函數,可以是自定義函數也可是庫函數,如open
filename:function? 制定文件中的函數
condtion??????????????? 條件
*address????? 地址,可是函數,變量的地址,此地址可以通過info add命令得到。
例如:
break 10????
break test.c:10
break main
break test.c:main
b 6??????// 表示在程序開始第6行暫停
b +6 ??// 表示在當前暫停位置往后偏移6行,打個斷點
b -6 ??// 表示在當前暫停位置往前偏移6行,打個斷點
b add ??// 表示在程序中函數add開頭處暫停
b test.c:6 ?// 表示在test.c文件的第6行暫停
b test.c:add // 表示在test.c文件中函數add開頭處暫停
break system
break open
如果想在指定的地址設置斷點,比如在main函數的地址出設斷點。
可用info add main 獲得main的地址如0x80484624,然后用break *0x80484624.
條件斷點就是在如上述指定斷點的同時指定進入斷點的條件。
例如:(假如有int 類型變量 index)
break 10 if index == 3
tbreak 12 if index == 5
info break 可列出所有斷點信息
對特定線程打斷點
- break <linespec> thread <threadno>
- break <linespec> thread <threadno> if ...
linespec指定了斷點設置在的源程序的行號 threadno指定了線程的ID ID是GDB分配的 可以通過info threads命令來查看正在運行程序中的線程信息 如果不指定thread <threadno>表示斷點設在所有線程上面
調用GDB調試器的方式
未運行的程序
gdb + 可執行程序: gdb ./test
或
gdb + 源文件: gdb test.c
正在運行的程序
ps -efL | grep 進程名字 ??#查看進程pid
gdb attach 進程PID
調試異常崩潰程序coredump
編譯debug
CMakeLists.txt
SET(CMAKE_BUILD_TYPE?"Debug")
cmake -DCMAKE_BUILD_TYPE=Debug?..
設置core文件大小
終端輸入ulimit -c,若為0,則不會產生對應的coredump,需要進行修改和設置。?
ulimit?-a ?#查看core文件大小, ulimit -c
ulimit -c unlimited #設置core文件無限大
1)臨時修改
在當前終端中輸入?
ulimit? -c?unlimited??
(可以產生coredump且不受大小限制),這種設置僅對當前終端生效。
2)永久生效
sudo vim /etc/profile
ulimit -c unlimited?#?add
source /etc/profile
設置core文件保存路徑
- 臨時有效
新建文件夾coredump
echo '/home/xxxxx/coredump/core-%t-%e-%p-%c' > /proc/sys/kernel/core_pattern
2)永久有效
sudo vim?/etc/sysctl.conf
文件尾部增加:
kernel.core_pattern =/home/xxxxx/coredump/core-%t-%e-%p-%c
sudo sysctl -p /etc/sysctl.conf ??# 重新加載內核參數
這樣配置后,產生的 core文件中將帶有崩潰的程序名、以及它的進程ID。
%t:表示核心轉儲的時間。
%e:表示核心轉儲的程序名。
%p 表示產生核心轉儲的進程ID。
%c:表示核心轉儲的大小
gdb調試運行core file
gdb 可執行文件core_file
gdb 定位程序運行位置
where或backtrace
info frame
在gdb中設置斷點,然后運行程序
使用gdb的display命令,可以在每次程序停下來時顯示某個表達式的值。你可以使用display 變量名來監視某個變量的值,或者使用display 函數名來監視某個函數的返回值
常用命令
- info threads:查看線程狀態信息
- thread <thread-id>:切換當前線程
- thread apply [thread-id-list | all] <command>:針對指定線程執行命令
- break <location> thread <thread-id> if <condition>:設置條件斷點,僅對指定線程生效
- set scheduler-locking on/off/step/replay:控制是否鎖定當前線程,如果當前程序被鎖定,恢復程序運行時,只有當前線程可以運行
- set non-stop on:開啟Non-Stop模式,該模式下,一個線程被中斷執行,不會影響其他線程
- set non-stop off:關閉Non-Stop模式,即進入All-Stop模式。該模式下,一個線程被中斷執行,其他所有線程都會被中斷
- interrut -a:中斷所有線程執行
(gdb) info frame // 顯示棧幀信息
(gdb) info registers // 顯示寄存器
(gdb) info args // 顯示參數
(gdb) info locals // 顯示本地變量
(gdb) p argc // print打印變量argc
在gdb調測時就可以使用l(list)命令查看對應源碼了,加上數字可以指定顯示行,也可以加上函數名稱
(gdb) l
(gdb) list + // 后移
(gdb) list - // 前移
(gdb) list 10 // 顯示指定行
(gdb) list main // 顯示指定函數
(gdb) list main.cpp:5 // 顯示指定文件指定行
(gdb) list main.cpp:main // 顯示指定文件指定函數
命令 | 用法 |
info threads | 顯示當前可調試的所有線程,每個線程有一個GDB為其分配的ID,后面操作線程的時候會用到這個ID 。前面的有*的是當前調試的線程 |
thread ID | 切換但錢的線程為指定ID的線程 |
break thread_test.c:123 thread all(例:在相應函數的位置設置斷點break pthread_run1) | 在所有線程中相應的行上設置斷點 |
thread apply ID1 ID2 command | 讓一個或者多個線程執行GDB命令command |
thread apply all command | 讓所有被調試線程執行GDB命令command |
set scheduler-locking 選項 command | 設置線程是以什么方式來執行命令 |
set scheduler-locking off | 不鎖定任何線程,也就是所有線程都執行,這是默認值 |
set scheduler-locking on | 只有當前被調試程序會執行 |
set scheduler-locking on step | 在單步的時候,除了next過一個函數的情況(熟悉情況的人都知道,這其實是一個設置斷點然后continue的行為)以外,只有當前線程會執行 |
調試過程中常見問題及解決方案
4.1 符號表問題
????????(1)符號表缺失
????????在編譯時未開啟-g選項,導致調試信息未被包含在可執行文件中。解決方法是在編譯時添加-g選項。
????????(2)符號表不匹配
????????源代碼與編譯生成的符號表不匹配,可能是由于源代碼被修改或重新編譯導致。解決方法是確保源代碼與符號表一致,或重新編譯生成新的符號表。
4.2 內存泄漏檢測與定位
????????(1)內存泄漏現象
????????程序在運行過程中不斷占用內存,且無法釋放。這可能是由于動態分配的內存未被正確釋放導致。
????????(2)內存泄漏檢測工具
????????使用Valgrind等內存泄漏檢測工具可以幫助定位內存泄漏問題。這些工具可以檢測出哪些內存塊被分配但未釋放。
????????(2)內存泄漏定位方法
????????通過分析內存泄漏檢測工具的報告,結合源代碼,可以定位到具體的內存泄漏位置。修復方法包括確保在使用完動態分配的內存后及時釋放,以及檢查是否存在野指針等問題。
4.3 段錯誤處理及原因分析
????????(1)段錯誤現象
????????程序在運行過程中突然崩潰,并報告“段錯誤”或“Segmentation fault”。這通常是由于訪問了非法內存地址導致。
????????(2)段錯誤原因分析
????????可能的原因包括解引用空指針、越界訪問數組、非法內存訪問等。通過分析崩潰時的堆棧信息,可以定位到導致段錯誤的代碼位置。
????????(3) 段錯誤處理方法 ???
????????根據段錯誤的原因,采取相應的處理措施。例如,對于解引用空指針的問題,需要確保在使用指針前進行非空檢查;對于越界訪問數組的問題,需要檢查數組索引是否越界,并確保數組長度足夠。
4.4 性能優化建議
????????(1)優化算法
????????針對程序中耗時的算法進行優化,例如使用更高效的算法或數據結構,減少不必要的計算或內存訪問。
????????(2)減少I/O操作
????????減少不必要的文件讀寫或網絡傳輸操作,例如通過緩存數據或使用更高效的數據傳輸方式來提高I/O性能。
????????(3)多線程/并行計算
????????利用多線程或并行計算技術,將程序中的計算任務分配到多個處理器核心上執行,從而提高程序的執行效率。
????????(4)優化編譯器選項
????????調整編譯器的優化選項,例如開啟O2或O3優化級別,可以讓編譯器自動進行代碼優化,提高程序的執行效率。 ?
linux 動態庫
動態庫,不同的設備安裝位置不一樣
動態庫鏈接的路徑,除了系統目錄,還看環境變量LD_LIBRARY_PATH
echo $LD_LIBRARY_PATH
動態庫在鏈接和運行時都需要
靜態庫只在鏈接時用到。
ldd ./執行文件名字 ??#查找該程序所需的動態鏈接庫
man dlopen ?#手動打開動態鏈接庫
find . -name *lib* ?#在當前路徑下查找某個文件
ls -al
readelf -a 1.out
md5sum 1.out a.out #校驗文件是否一樣
多線程示例代碼
多線程調試基礎命令
https://zhuanlan.zhihu.com/p/692414669
設置編譯類型Debug
a.如果使用gcc/g++編譯,需要加入參數“-g"。
b.如果使用cmake編譯,需要在CMakeLists.txt中加入:
SET(CMAKE_BUILD_TYPE "Debug")????//選擇Debug編譯模式
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")??
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
c. cmake 編譯 參數
終端帶有Debug
查看線程狀態信息?info threads
info threads顯示,當前共有三個線程。以主線程(第一行)為例:1是GDB為主線程分配的ID,接下來0x7ffff7da3740是pthread庫為主線程分配的pthread ID,后面括號中的355298是kernel為主線程分配的thread ID,再后面是該線程被中斷時,代碼正在執行的代碼位置。
可用通過ps命令或者proc文件系統查看確認:
root@ubuntu:~# ps -eT | grep test
?355298 ?355298 pts/9 ???00:00:02 test
?355298 ?355301 pts/9 ???00:00:02 test
?355298 ?355302 pts/9 ???00:00:02 test
root@ubuntu:~# ls /proc/355298/task/
355298 ?355301 ?355302
線程1前面有個星號*,表示當前線程。默認情況下,執行的GDB命令是針對當前線程。比如此時執行bt(backtrace)命令,獲取的是線程1的調用棧:
切換線程?thread
thread 2把線程2切換為當前線程.用info threads查看,星號*已經切到了線程2的前面
針對指定線程執行命令
thread apply [thread-id-list | all] command 可以針對指定線程執行命令
如:
- thread apply all bt:打印所有線程的調用棧信息
- thread apply 3 bt:打印線程3的調用棧信息
- thread apply 2-3 bt:打印線程2和線程3的調用棧信息
針對特定線程設置斷點
在設置斷點時,我們可以選擇讓斷點對所有的線程生效,或只對特定線程生效。命令格式為:
break <location> ?# 在<location>設置斷點,并對所有線程生效
break <location> thread <thread-id> # 在<location>設置斷點,僅對<thread-id>指定的線程生效
break <location> thread <thread-id> if <condition> # 在<location>設置條件斷點斷點,僅對<thread-id>指定的線程生效
比如 break do_stuff thread 2, 這個命令在do_stuff()入口設置斷點,只有thread 2調用這個函數是才會觸發斷點,其他thread調用這個函數不會觸發斷點。
GDB中的其他所有類型的斷點,也支持對特定線程設置斷點,如tbreak、watch等。
控制線程創建和退出信息
- set print thread-events on/off 設置是否打印線程創建和退出信息,如上面例子中的:
[New Thread 0x7ffff7da2700 (LWP 355301)]
[New Thread 0x7ffff75a1700 (LWP 355302)]
- 命令縮寫
有時候使用thread apply [thread-id-list | all] command會稍顯繁瑣,GDB貼心的為我們提供了幾種命令縮寫:
taas command 相當于 thread apply all -s command
tfaas command 相當于 thread apply all -s -- frame apply all -s command?
這個命令非常有用。比如,有時我們只記得一個變量或參數的名字,卻忘了或不知道它是在哪個具體的函數中,就可以用這個命令:tfaas p var_name,這個命令會搜索所有線程的調用棧,找到名字為var_name的變量,并打印它的值,如:
(gdb) tfaas p x1
Thread 2 (Thread 0x7ffff7da2700 (LWP 370514) "test"):
#0 ?thread_1 (arg=0x0) at test.c:10
$5 = 432504243
(gdb) tfaas p y2
Thread 3 (Thread 0x7ffff75a1700 (LWP 370515) "test"):
#0 ?thread_2 (arg=0x0) at test.c:18
$6 = 428735002
控制程序執行的兩種模式
- All-Stop Mode:在該模式下,不管因為什么原因,一個線程被中斷執行,其他所有的線程都會同時被中斷執行。
- Non-Stop Mode:在該模式下,一個線程被中斷執行,不會影響其他線程的正常執行。
All-Stop mode
在GDB中調試多線程程序,默認處于All-Stop Mode,即只要有一個線程被中斷執行,其他所有的線程都會被中斷執行。
比如,程序在運行時,按Ctrl+C,所有的線程都會被中斷執行,同樣的,當恢復程序執行時,所有的線程都會同時恢復執行。比如執行continue、step、next等命令。
在All-Stop模式下,只要有一個線程由于觸發斷點等原因停止執行,整個進程都會被停下來,這在一定程度上可以保證所有線程狀態的同步,保持數據的一致性。比如,在查看某個可能會被多個線程修改的數據時,不需要擔心在查看的同時被其他線程修改掉。
但是,這也給程序調試帶來了一些困難,比如,無法100%精確地進行單步調試。有時你會發現,在執行step命令之后,程序卻停在了另外一個線程中。
在線程2和線程3都會調用do_stuff(),在do_stuff()設置斷點后,線程2先觸發斷點停在第4行,同時當前線程切換為線程2,然后執行step命令單步執行一條語句,理論上講,線程2執行一條語句應該停在第5行代碼,然而實際上卻是執行完step命令后,線程3觸發了斷點,而此時線程3并未按照step命令的預期執行一行代碼,而是仍然停留在第4行。再次單步執行,線程2卻再次觸發了斷點,說明線程2執行了不止一條語句
為了解決這個問題,GDB提供了set scheduler-locking mode命令,鎖定當前線程。
線程鎖定模式
set scheduler-locking mode
mode 可能是:
- off:不鎖定任何線程,當恢復程序執行時,所有線程都可以自由執行
- on:鎖定當前線程,執行continue、step、next、finish等命令時,只有當前線程恢復執行,其余線程仍然處于中斷狀態
- step:當單步執行時,和on一樣,其余情況和off一樣。簡單來說,就是在這個鎖定模式下執行單步操作,只有當前線程會執行,其他線程仍然處于中斷狀態。而執行其他命令時,如continue、finish等,和off一樣,即所有線程都可以自由執行。
- replay:在反向調試時和on一樣,其余情況下,和off一樣。后續會有專門文章詳細介紹反向調試。
默認是replay模式,可以通過show scheduler-locking查看當前鎖定模式。
簡單解釋一下:
- 先在do_stuff()設置斷點,并執行continue恢復程序執行。隨后,線程2觸發斷點,停在第4行代代碼。
- 此時程序的狀態是:線程2由于觸發斷點,停在第4行;由于在All-Stop模式,所以線程3也被中斷,恰巧也停在第4行
- 然后執行set scheduler-locking on 鎖定當前線程2。
- 此時單步執行,線程2停在第5行,而線程3仍然停在第4行。再執行continue,線程2會再次觸發斷點,此時發現線程3仍然停在第4行。
可見,執行set scheduler-locking on開啟鎖定模式之后,無論用什么命令恢復程序執行,只有當前線程2會執行,其余線程一直處于中斷狀態。
不過,同時中斷所有的線程,可能會導致有些程序無法正常運行。比如,有些程序需要有專門的線程實時處理外部的消息或事件等,如果把所有的線程都中斷的話,會由于外部事件得不到及時響應而導致程序異常終止。
這時,就需要用到Non-Stop模式
Non-Stop Mode
在Non-Stop模式下,一個線程被中斷執行,并不會影響到其他線程。比如,一個線程觸發斷點,只有這一個線程會被中斷執行,其余線程不受影響繼續執行。同樣的,在程序運行時,執行Ctrl+C,也只會中斷一個線程。
要開啟Non-Stop模式,需要在程序開始運行前,執行下面兩個命令:
set pagination off
set non-stop on
可以通過下面這個命令關閉Non-Stop模式,即進入All-Stop模式
set non-stop off
查看Non-Stop模式是否開啟:
show non-stop
后臺執行
我們知道,在Shell中執行程序時,后面加一個&符號,可以把程序放在后臺執行。在GDB中你同樣可以在命令后面加一個&符號,這樣就能把命令放在后臺執行。
之所以提供后臺執行的模式,其實就是讓GDB始終可以接收用戶輸入,即便被調試程序正常運行時,仍然可以在GDB中執行命令。這在Non-Stop模式下,有時會非常有用。
前面講過,Non-Stop模式下,按Ctrl+C只會中斷那個接收到SIGINT信號的線程,其他線程不會受到影響繼續運行。但是,有時候,為了盡量保證線程狀態的同步,我們可能想要讓所有的線程同時被中斷或同時恢復運行,這種情況下,就可以通過后臺執行命令的方式來實現。
并不是所有的命令都支持后臺執行,目前支持后臺執行的命令有:continue、run、attach、step、stepi、next、nexti、finish、until
常用命令小結
- info threads:查看線程狀態信息
- thread <thread-id>:切換當前線程
- thread apply [thread-id-list | all] <command>:針對指定線程執行命令
- break <location> thread <thread-id> if <condition>:設置條件斷點,僅對指定線程生效
- set scheduler-locking on/off/step/replay:控制是否鎖定當前線程,如果當前程序被鎖定,恢復程序運行時,只有當前線程可以運行
- set non-stop on:開啟Non-Stop模式,該模式下,一個線程被中斷執行,不會影響其他線程
r ????#run
b 10 ?#在第10行打斷點
- set non-stop off:關閉Non-Stop模式,即進入All-Stop模式。該模式下,一個線程被中斷執行,其他所有線程都會被中斷
- interrut -a:中斷所有線程執行
- command &:后臺執行
interrupt -a命令可以中斷所有線程的執行
continue -a?(c -a)命令可以恢復所有線程的執行